diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx index e9011c995..205447916 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx +++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx @@ -34,7 +34,7 @@ import { useWindowDimensions } from "react-native"; import { WebViewMessageEvent } from "react-native-webview"; -import { db } from "../../../common/database"; +import { DatabaseLogger, db } from "../../../common/database"; import downloadAttachment from "../../../common/filesystem/download-attachment"; import EditorTabs from "../../../components/sheets/editor-tabs"; import LinkNote from "../../../components/sheets/link-note"; @@ -370,6 +370,7 @@ export const useEditorEvents = ( switch (editorMessage.type) { case EventTypes.content: + DatabaseLogger.log("EventTypes.content"); editor.saveContent({ type: editorMessage.type, content: editorMessage.value.html as string, @@ -379,6 +380,7 @@ export const useEditorEvents = ( }); break; case EventTypes.title: + DatabaseLogger.log("EventTypes.title"); editor.saveContent({ type: editorMessage.type, title: editorMessage.value as string, diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor.ts b/apps/mobile/app/screens/editor/tiptap/use-editor.ts index 586745672..2e397e660 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor.ts @@ -50,6 +50,7 @@ import { useTagStore } from "../../../stores/use-tag-store"; import { eEditorTabFocused, eOnLoadNote, + eShowMergeDialog, eUpdateNoteInEditor } from "../../../utils/events"; import { tabBarRef } from "../../../utils/global-refs"; @@ -200,7 +201,8 @@ export const useEditor = ( useTabStore.getState().updateTab(tabId, { noteId: undefined, locked: false, - noteLocked: false + noteLocked: false, + readonly: false }); }, [commands, editorSessionHistory, postMessage] @@ -224,7 +226,11 @@ export const useEditor = ( } let note = id ? await db.notes?.note(id) : undefined; const locked = note && (await db.vaults.itemExists(note)); - if (note?.conflicted) return; + + if (note?.conflicted) { + eSendEvent(eShowMergeDialog, note); + return; + } if (isContentInvalid(data) && id) { // Create a new history session if recieved empty or invalid content @@ -243,7 +249,7 @@ export const useEditor = ( noteData.title = title; if (ignoreEdit) { - console.log("Ignoring edits..."); + DatabaseLogger.log("Ignoring edits..."); noteData.dateEdited = note?.dateEdited; } @@ -261,8 +267,22 @@ export const useEditor = ( }); } + let saved = false; + setTimeout(() => { + if (saved) return; + commands.setStatus( + getFormattedDate(note ? note.dateEdited : Date.now(), "date-time"), + "Saving", + tabId + ); + }, 100); + if (!locked) { + DatabaseLogger.log(`Saving note: ${id}...`); id = await db.notes?.add({ ...noteData }); + saved = true; + DatabaseLogger.log(`Note saved: ${id}...`); + if (!note && id) { editorSessionHistory.newSession(id); if (id) { @@ -553,7 +573,6 @@ export const useEditor = ( data: Note | ContentItem | TrashItem | DeletedItem, isLocal?: boolean ) => { - console.log("Local changes in editor", isLocal, data?.id); if (SettingsService.get().disableRealtimeSync && !isLocal) return; if (!data) return; @@ -634,7 +653,10 @@ export const useEditor = ( } if (data.type === "tiptap" && note && !isLocal) { - if (lastContentChangeTime.current[noteId] >= data.dateEdited) return; + if (lastContentChangeTime.current[noteId] >= data.dateEdited) { + lock.current = false; + return; + } if (locked && isEncryptedContent(data)) { const decryptedContent = await db.vault?.decryptContent(data, noteId); @@ -657,7 +679,10 @@ export const useEditor = ( } } else { const _nextContent = data.data; - if (_nextContent === currentContents.current?.data) return; + if (_nextContent === currentContents.current?.data) { + lock.current = false; + return; + } lastContentChangeTime.current[note.id] = note.dateEdited; await postMessage(EditorEvents.updatehtml, _nextContent, tabId); if (!isEncryptedContent(data)) { @@ -697,12 +722,19 @@ export const useEditor = ( ignoreEdit: boolean; tabId: number; }) => { + DatabaseLogger.log(`Saving content...`); if ( lock.current || (currentLoadingNoteId.current && currentLoadingNoteId.current === noteId) - ) + ) { + DatabaseLogger.log(`Skipped saving conent: + + lock.current: ${lock.current} + currentLoadingNoteId.current: ${currentLoadingNoteId.current} + `); return; + } if (noteId) { lastContentChangeTime.current[noteId] = Date.now(); diff --git a/packages/editor-mobile/src/components/editor.tsx b/packages/editor-mobile/src/components/editor.tsx index 0c4b806eb..8e6fd940b 100644 --- a/packages/editor-mobile/src/components/editor.tsx +++ b/packages/editor-mobile/src/components/editor.tsx @@ -29,6 +29,7 @@ import { } from "@notesnook/editor"; import { toBlobURL } from "@notesnook/editor/dist/utils/downloader"; import { useThemeColors } from "@notesnook/theme"; +import FingerprintIcon from "mdi-react/FingerprintIcon"; import { useCallback, useEffect, @@ -51,7 +52,6 @@ import Header from "./header"; import StatusBar from "./statusbar"; import Tags from "./tags"; import Title from "./title"; -import FingerprintIcon from "mdi-react/FingerprintIcon"; globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL; diff --git a/packages/editor-mobile/src/hooks/useEditorController.ts b/packages/editor-mobile/src/hooks/useEditorController.ts index f99fcc1e1..22593c684 100644 --- a/packages/editor-mobile/src/hooks/useEditorController.ts +++ b/packages/editor-mobile/src/hooks/useEditorController.ts @@ -122,6 +122,9 @@ export function useEditorController({ }): EditorController { const passwordInputRef = useRef(null); const tab = useTabContext(); + const tabRef = useRef(tab); + tabRef.current = tab; + const [loading, setLoading] = useState(true); const setTheme = useThemeEngineStore((store) => store.setTheme); const { colors } = useThemeColors("editor"); @@ -135,30 +138,29 @@ export function useEditorController({ wordCounter: null }); - if (!tab.noteId && loading) { + if (!tabRef.current.noteId && loading) { setLoading(false); } const selectionChange = useCallback((_editor: Editor) => {}, []); - const titleChange = useCallback( - (title: string) => { - post(EventTypes.contentchange, undefined, tab.id, tab.noteId); - post(EventTypes.title, title, tab.id, tab.noteId); - }, - [tab.id, tab.noteId] - ); + const titleChange = useCallback((title: string) => { + post( + EventTypes.contentchange, + undefined, + tabRef.current.id, + tabRef.current.noteId + ); + post(EventTypes.title, title, tabRef.current.id, tabRef.current.noteId); + }, []); - const countWords = useCallback( - (ms = 300) => { - if (typeof timers.current.wordCounter === "number") - clearTimeout(timers.current.wordCounter); - timers.current.wordCounter = setTimeout(() => { - statusBars[tab.id]?.current?.updateWords(); - }, ms); - }, - [tab.id] - ); + const countWords = useCallback((ms = 300) => { + if (typeof timers.current.wordCounter === "number") + clearTimeout(timers.current.wordCounter); + timers.current.wordCounter = setTimeout(() => { + statusBars[tabRef.current.id]?.current?.updateWords(); + }, ms); + }, []); useEffect(() => { injectCss(transform(colors)); @@ -166,9 +168,17 @@ export function useEditorController({ const contentChange = useCallback( (editor: Editor, ignoreEdit?: boolean) => { - if (editorControllers[tab.id]?.loading) return; + if (editorControllers[tabRef.current.id]?.loading) { + logger("info", "Edit skipped, tab is in loading state"); + return; + } const currentSessionId = globalThis.sessionId; - post(EventTypes.contentchange, undefined, tab.id, tab.noteId); + post( + EventTypes.contentchange, + undefined, + tabRef.current.id, + tabRef.current.noteId + ); if (!editor) return; if (typeof timers.current.change === "number") { clearTimeout(timers.current?.change); @@ -181,15 +191,21 @@ export function useEditorController({ html: htmlContentRef.current, ignoreEdit: ignoreEdit }, - tab.id, - tab.noteId, + tabRef.current.id, + tabRef.current.noteId, currentSessionId ); + logger( + "info", + "Editor saving content", + tabRef.current.id, + tabRef.current.noteId + ); }, 300); countWords(5000); }, - [countWords, tab.id, tab.noteId] + [countWords] ); const scroll = useCallback( @@ -226,15 +242,15 @@ export function useEditorController({ const type = message.type; const value = message.value; - if (message.tabId !== tab.id && type !== "native:status") { + if (message.tabId !== tabRef.current.id && type !== "native:status") { return; } - const editor = editors[tab.id]; + const editor = editors[tabRef.current.id]; switch (type) { case "native:updatehtml": { htmlContentRef.current = value; - if (tab.id !== useTabStore.getState().currentTab) { + if (tabRef.current.id !== useTabStore.getState().currentTab) { updateTabOnFocus.current = true; } else { if (!editor) break; @@ -257,7 +273,9 @@ export function useEditorController({ logger("info", "LOADING NOTE HTML"); if (!editor) break; update(); - countWords(0); + setTimeout(() => { + countWords(0); + }, 300); break; case "native:theme": setTheme(message.value); @@ -289,7 +307,7 @@ export function useEditorController({ } post(type); // Notify that message was delivered successfully. }, - [tab, update, countWords, setTheme] + [update, countWords, setTheme] ); useEffect(() => { @@ -306,32 +324,30 @@ export function useEditorController({ }; }, [onMessage]); - const openFilePicker = useCallback( - (type: "image" | "file" | "camera") => { - post(EventTypes.filepicker, type, tab.id, tab.noteId); - }, - [tab.id, tab.noteId] - ); + const openFilePicker = useCallback((type: "image" | "file" | "camera") => { + post(EventTypes.filepicker, type, tabRef.current.id, tabRef.current.noteId); + }, []); - const downloadAttachment = useCallback( - (attachment: Attachment) => { - post(EventTypes.download, attachment, tab.id, tab.noteId); - }, - [tab.id, tab.noteId] - ); - const previewAttachment = useCallback( - (attachment: Attachment) => { - post(EventTypes.previewAttachment, attachment, tab.id, tab.noteId); - }, - [tab.id, tab.noteId] - ); - const openLink = useCallback( - (url: string) => { - post(EventTypes.link, url, tab.id, tab.noteId); - return true; - }, - [tab.id, tab.noteId] - ); + const downloadAttachment = useCallback((attachment: Attachment) => { + post( + EventTypes.download, + attachment, + tabRef.current.id, + tabRef.current.noteId + ); + }, []); + const previewAttachment = useCallback((attachment: Attachment) => { + post( + EventTypes.previewAttachment, + attachment, + tabRef.current.id, + tabRef.current.noteId + ); + }, []); + const openLink = useCallback((url: string) => { + post(EventTypes.link, url, tabRef.current.id, tabRef.current.noteId); + return true; + }, []); const copyToClipboard = (text: string) => { post(EventTypes.copyToClipboard, text); @@ -379,7 +395,7 @@ export function useEditorController({ updateTabOnFocus.current = false; setTimeout(() => { if (!updateTabOnFocus.current) return; - const editor = editors[tab.id]; + const editor = editors[tabRef.current.id]; if (!editor) return; const { from, to } = editor.state.selection; editor?.commands.setContent(htmlContentRef.current, false, { @@ -390,12 +406,10 @@ export function useEditorController({ to }); countWords(); - logger("info", `Tab ${tab.id} updated.`); }, 1); }, passwordInputRef, focusPassInput: () => { - logger("info", "focus pass input..."); passwordInputRef.current?.focus(); }, blurPassInput: () => {