- Refactor components to support conditional offline and online CRUD operations. - Introduce `addToQueue` mechanism for syncing offline changes to the server. - Add `isChapterContentExist` method and related existence checks in repositories. - Consolidate data structures and streamline book, chapter, character, and guideline synchronization workflows. - Encrypt additional character fields and adjust repository inserts for offline data.
111 lines
5.7 KiB
TypeScript
111 lines
5.7 KiB
TypeScript
import {ChapterContext} from "@/context/ChapterContext";
|
|
import {EditorContext} from "@/context/EditorContext";
|
|
import {useContext, useEffect, useState} from "react";
|
|
import {Editor} from "@tiptap/react";
|
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
|
import {faBook, faChartSimple, faHeart, faSheetPlastic, faHardDrive} from "@fortawesome/free-solid-svg-icons";
|
|
import {useTranslations} from "next-intl";
|
|
import {AlertContext} from "@/context/AlertContext";
|
|
import {BookContext} from "@/context/BookContext";
|
|
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
|
|
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
|
|
|
export default function ScribeFooterBar() {
|
|
const t = useTranslations();
|
|
const {chapter} = useContext(ChapterContext);
|
|
const {book} = useContext(BookContext);
|
|
const editor: Editor | null = useContext(EditorContext).editor;
|
|
const {errorMessage} = useContext(AlertContext)
|
|
const {offlineMode} = useContext<OfflineContextType>(OfflineContext)
|
|
const {localOnlyBooks,serverSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
|
|
|
const [wordsCount, setWordsCount] = 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);
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(t('errors.wordCountError') + ` (${e.message})`);
|
|
} else {
|
|
errorMessage(t('errors.wordCountError'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="px-6 py-3 bg-tertiary/90 backdrop-blur-sm border-t border-secondary/50 text-text-primary flex justify-between items-center shadow-lg">
|
|
<div>
|
|
<span className="flex items-center gap-2">
|
|
{chapter && (
|
|
<span className="inline-flex items-center px-3 py-1 rounded-lg bg-primary/10 border border-primary/30">
|
|
<span className="text-primary font-bold text-sm">
|
|
{chapter.chapterOrder < 0 ? t('scribeFooterBar.sheet') : `${chapter.chapterOrder}.`}
|
|
</span>
|
|
</span>
|
|
)}
|
|
<span className={'flex items-center gap-2 font-medium'}>
|
|
{
|
|
chapter?.title || book?.title || (
|
|
<>
|
|
<span>{t('scribeFooterBar.madeWith')}</span>
|
|
<FontAwesomeIcon color={'red'} icon={faHeart} className={'w-4 h-4 animate-pulse'}/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
{
|
|
chapter || book ? (
|
|
<div className="flex items-center space-x-3">
|
|
<div
|
|
className="flex items-center gap-2 bg-secondary/50 px-4 py-2 rounded-xl border border-secondary shadow-sm">
|
|
<FontAwesomeIcon icon={faChartSimple} className="text-primary text-sm w-4 h-4"/>
|
|
<span className="text-muted text-sm font-medium">{t('scribeFooterBar.words')}:</span>
|
|
<span className="text-text-primary font-bold">{wordsCount}</span>
|
|
</div>
|
|
<div
|
|
className="flex items-center gap-2 bg-secondary/50 px-4 py-2 rounded-xl border border-secondary shadow-sm">
|
|
<FontAwesomeIcon icon={faSheetPlastic} className={'text-primary w-4 h-4'}/>
|
|
<span className="text-text-primary font-bold">{Math.ceil(wordsCount / 300)}</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center space-x-3">
|
|
{
|
|
!offlineMode.isOffline && <div
|
|
className="flex items-center gap-2 bg-secondary/50 px-4 py-2 rounded-xl border border-secondary shadow-sm">
|
|
<FontAwesomeIcon icon={faBook} className={'text-primary w-4 h-4'}/>
|
|
<span className="text-text-primary font-bold">{serverSyncedBooks.length}</span>
|
|
</div>
|
|
}
|
|
{(localOnlyBooks.length > 0 || offlineMode.isOffline) && (
|
|
<div
|
|
className="flex items-center gap-2 bg-secondary/50 px-4 py-2 rounded-xl border border-secondary shadow-sm">
|
|
<FontAwesomeIcon icon={faHardDrive} className={'text-primary w-4 h-4'}/>
|
|
<span className="text-text-primary font-bold">{localOnlyBooks.length}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
)
|
|
} |