Files
ERitors-Scribe-Desktop/components/ScribeFooterBar.tsx
natreex a315e96633 Enhance security and offline functionality
- Implement stricter security measures in the Electron app, including navigation blocking, URL validation, and external request handling.
- Add offline mode handling and UI improvements in components like `ScribeFooterBar` and `AddNewBookForm`.
- Refactor `DeleteBook` logic to include offline sync methods.
- Improve user feedback for online/offline states and synchronization errors.
2025-12-24 15:20:26 -05:00

118 lines
5.9 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 {SessionContext} from "@/context/SessionContext";
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 {serverOnlyBooks,localOnlyBooks} = 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'));
}
}
}
}
useEffect(() => {
console.log(localOnlyBooks.length > 0 || offlineMode.isOffline);
console.log(localOnlyBooks.length);
console.log(offlineMode.isOffline);
}, []);
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">{serverOnlyBooks.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>
)
}