- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design. - Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri. - Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling. - Updated `ScribeShell` with migration popup logic to prompt users for data migration. - Integrated `AlertStack` for better alert handling and notification management. - Enhanced Rust/Tauri services with migration command implementations. - Added translations and styles for new components.
97 lines
4.4 KiB
TypeScript
97 lines
4.4 KiB
TypeScript
import {ChapterContext, ChapterContextProps} from "@/context/ChapterContext";
|
|
import {EditorContext, EditorContextProps} from "@/context/EditorContext";
|
|
import React, {useContext, useEffect, useState} from "react";
|
|
import {BookOpen, ChevronRight, FileText, Pilcrow, Type} from "lucide-react";
|
|
import IconLabel from "@/components/ui/IconLabel";
|
|
import {useTranslations} from '@/lib/i18n';
|
|
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
|
|
import {BookContext, BookContextProps} from "@/context/BookContext";
|
|
import {chapterVersions} from "@/lib/constants/chapter";
|
|
|
|
export default function ScribeFooterBar() {
|
|
const t = useTranslations();
|
|
const {chapter}: ChapterContextProps = useContext<ChapterContextProps>(ChapterContext);
|
|
const {editor}: EditorContextProps = useContext<EditorContextProps>(EditorContext);
|
|
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
|
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
|
|
|
|
const [wordsCount, setWordsCount] = useState<number>(0);
|
|
const [paragraphCount, setParagraphCount] = useState<number>(0);
|
|
|
|
useEffect((): void => {
|
|
getWordCount();
|
|
}, [editor?.state.doc.textContent]);
|
|
|
|
function getWordCount(): void {
|
|
if (editor) {
|
|
try {
|
|
const content: string = editor.state.doc.textContent;
|
|
const texteNormalise: string = content
|
|
.replace(/'/g, ' ')
|
|
.replace(/-/g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
const mots: string[] = texteNormalise.split(' ');
|
|
const wordCount: number = mots.filter(
|
|
(mot: string): boolean => mot.length > 0,
|
|
).length;
|
|
setWordsCount(wordCount);
|
|
|
|
let paragraphs: number = 0;
|
|
editor.state.doc.descendants(function (node): void {
|
|
if (node.type.name === 'paragraph' && node.textContent.trim().length > 0) {
|
|
paragraphs++;
|
|
}
|
|
});
|
|
setParagraphCount(paragraphs);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(t('errors.wordCountError') + ` (${e.message})`);
|
|
} else {
|
|
errorMessage(t('errors.wordCountError'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getVersionLabel(): string {
|
|
if (!chapter) return '';
|
|
const version = chapterVersions.find(function (v) {
|
|
return v.value === chapter.chapterContent.version.toString();
|
|
});
|
|
return version ? t(version.label) : '';
|
|
}
|
|
|
|
return (
|
|
<div className="px-5 py-2 bg-tertiary text-text-secondary flex justify-between items-center text-xs">
|
|
{/* Gauche : Breadcrumb */}
|
|
<div className="flex items-center gap-1.5">
|
|
{book ? (
|
|
<>
|
|
<BookOpen className="w-3 h-3 text-muted" strokeWidth={1.75}/>
|
|
<span className="text-text-primary font-medium">{book.title}</span>
|
|
{chapter && (
|
|
<>
|
|
<ChevronRight className="w-3 h-3 text-muted" strokeWidth={1.75}/>
|
|
<span>{chapter.title}</span>
|
|
<ChevronRight className="w-3 h-3 text-muted" strokeWidth={1.75}/>
|
|
<span className="text-primary">{getVersionLabel()}</span>
|
|
</>
|
|
)}
|
|
</>
|
|
) : (
|
|
<span className="text-text-dimmed">{t('scribeFooterBar.madeWith')} ERitors</span>
|
|
)}
|
|
</div>
|
|
{/* Droite : Stats */}
|
|
{(chapter || book) && (
|
|
<div className="flex items-center gap-4">
|
|
<IconLabel icon={Type}>{wordsCount} {t('scribeFooterBar.words')}</IconLabel>
|
|
<IconLabel icon={FileText}>{Math.ceil(wordsCount / 300)} {t('scribeFooterBar.pages')}</IconLabel>
|
|
<IconLabel icon={Pilcrow}>{paragraphCount} {t('scribeFooterBar.paragraphs')}</IconLabel>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|