diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts index 9ac6c4b71..40b350669 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts @@ -56,6 +56,7 @@ import { useUserStore } from "../../../stores/use-user-store"; import { eClearEditor, eCloseFullscreenEditor, + eEditorTabFocused, eOnLoadNote, eOpenFullscreenEditor, eOpenLoginDialog, @@ -377,7 +378,7 @@ export const useEditorEvents = ( logger.info("[WEBVIEW LOG]", editorMessage.value); break; case EventTypes.contentchange: - editor.onContentChanged(); + editor.onContentChanged(editorMessage.noteId); break; case EventTypes.selection: break; @@ -531,22 +532,25 @@ export const useEditorEvents = ( } case EventTypes.tabFocused: { // Reload the note - console.log("Focused tab", editorMessage.tabId); - eSendEvent("tabsFocused", editorMessage.tabId); + console.log( + "Focused tab", + editorMessage.tabId, + editorMessage.noteId, + "Content:", + editorMessage.value + ); - // const note = await db.notes.note(editorMessage.noteId); - // if (note) { - // eSendEvent(eOnLoadNote, { - // item: note, - // forced: true - // }); - // } - // TODO - // Handle any updates that occured in an note while the tab was not focused. - // If editor has no content, reload the note, because it might be an app reload - // or the tab was destroyed in background... - // Maybe cache changes, something like pendingUpdates list. - // Do proper cleanup when a tab is destroyed though. + eSendEvent(eEditorTabFocused, editorMessage.tabId); + if (!editorMessage.value && editorMessage.noteId) { + const note = await db.notes.note(editorMessage.noteId); + if (note) { + eSendEvent(eOnLoadNote, { + item: note, + forced: true, + tabId: editorMessage.tabId + }); + } + } break; } diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor.ts b/apps/mobile/app/screens/editor/tiptap/use-editor.ts index fdde552f4..699404e60 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor.ts @@ -49,7 +49,11 @@ import SettingsService from "../../../services/settings"; import { TipManager } from "../../../services/tip-manager"; import { useSettingStore } from "../../../stores/use-setting-store"; import { useTagStore } from "../../../stores/use-tag-store"; -import { eClearEditor, eOnLoadNote } from "../../../utils/events"; +import { + eClearEditor, + eEditorTabFocused, + eOnLoadNote +} from "../../../utils/events"; import { tabBarRef } from "../../../utils/global-refs"; import { onNoteCreated } from "../../notes/common"; import Commands from "./commands"; @@ -109,12 +113,11 @@ export const useEditor = ( const commands = useMemo(() => new Commands(editorRef), [editorRef]); const editorSessionHistory = useMemo(() => new SessionHistory(), []); const state = useRef>(defaultState); - const placeholderTip = useRef(TipManager.placeholderTip()); const tags = useTagStore((state) => state.items); const insets = useGlobalSafeAreaInsets(); const isDefaultEditor = editorId === ""; const saveCount = useRef(0); - const lastContentChangeTime = useRef(0); + const lastContentChangeTime = useRef>({}); const lock = useRef(false); const currentLoadingNoteId = useRef(); const loadingState = useRef(); @@ -149,7 +152,7 @@ export const useEditor = ( }, [commands, tags]); useEffect(() => { - const event = eSubscribeEvent("tabsFocused", (tabId) => { + const event = eSubscribeEvent(eEditorTabFocused, (tabId) => { lastTabFocused.current = tabId as number; console.log(tabId); }); @@ -192,6 +195,7 @@ export const useEditor = ( currentNotes.current[noteId] = null; currentContents.current[noteId] = null; editorSessionHistory.clearSession(noteId); + lastContentChangeTime.current[noteId] = 0; } clearTimeout(timers.current["loading-images" + noteId]); @@ -200,13 +204,12 @@ export const useEditor = ( loadingState.current = undefined; lock.current = false; resetContent && postMessage(EditorEvents.title, "", tabId); - lastContentChangeTime.current = 0; + resetContent && (await commands.clearContent(tabId)); resetContent && (await commands.clearTags(tabId)); - // TODO ? - // useTabStore.getState().updateTab(tabId, { - // noteId: undefined - // }); + useTabStore.getState().updateTab(tabId, { + noteId: undefined + }); }, [commands, editorSessionHistory, postMessage] ); @@ -269,16 +272,15 @@ export const useEditor = ( if (!locked) { id = await db.notes?.add(noteData); if (!note && id) { - useTabStore.getState().updateTab(tabId, { - noteId: id - }); - editorSessionHistory.newSession(id); - if (id) { currentNotes.current[id] = await db.notes?.note(id); } + useTabStore.getState().updateTab(tabId, { + noteId: id + }); + const defaultNotebook = db.settings.getDefaultNotebook(); if (!state.current.onNoteCreated && defaultNotebook) { onNoteCreated(id, { @@ -290,9 +292,10 @@ export const useEditor = ( } if (!noteData.title) { + console.log("posting title to tab", tabId); postMessage( EditorEvents.title, - currentNotes.current?.title, + currentNotes.current[id]?.title, tabId ); } @@ -315,7 +318,7 @@ export const useEditor = ( tabId ); - lastContentChangeTime.current = note.dateEdited; + lastContentChangeTime.current[id] = note.dateEdited; if ( saveCount.current < 2 || @@ -369,7 +372,12 @@ export const useEditor = ( ); const loadNote = useCallback( - async (event: { item?: Note; forced?: boolean; newNote?: boolean }) => { + async (event: { + item?: Note; + forced?: boolean; + newNote?: boolean; + tabId?: number; + }) => { state.current.currentlyEditing = true; if ( !state.current.ready && @@ -383,56 +391,54 @@ export const useEditor = ( } if (event.newNote) { - console.log("Create new note"); useTabStore.getState().focusEmptyTab(); const tabId = useTabStore.getState().currentTab; - console.log("empty tab", tabId); currentNotes.current && (await reset(tabId)); setTimeout(() => { if (state.current?.ready) commands.focus(tabId); - lastContentChangeTime.current = 0; }); } else { if (!event.item) return; const item = event.item; - console.log("load note called again...", event.forced, event.item.id); - // If note was already opened in a tab, focus that tab and return. Once the tab is focused - // the note will load. - if (useTabStore.getState().hasTabForNote(event.item.id)) { - const tabId = useTabStore.getState().getTabForNote(event.item.id); - if (typeof tabId === "number") { - useTabStore.getState().updateTab(tabId, { - readonly: event.item.readonly + // If note was already opened in a tab, focus that tab. + if (typeof event.tabId !== "number") { + if (useTabStore.getState().hasTabForNote(event.item.id)) { + const tabId = useTabStore.getState().getTabForNote(event.item.id); + if (typeof tabId === "number") { + useTabStore.getState().updateTab(tabId, { + readonly: event.item.readonly + }); + useTabStore.getState().focusTab(tabId); + } + console.log("Note already loaded, focusing the tab"); + } else { + console.log("Opening note in preview tab"); + // Otherwise we focus the preview tab or create one to open the note in. + useTabStore.getState().focusPreviewTab(event.item.id, { + readonly: event.item.readonly, + locked: false }); - useTabStore.getState().focusTab(tabId); } } else { - console.log("opening note in preview tab"); - // Otherwise we focus the preview tab or create one to open the note in. - useTabStore.getState().focusPreviewTab(event.item.id, { - readonly: event.item.readonly, - locked: false - }); - } - const tabId = useTabStore.getState().currentTab; - console.log(lastTabFocused.current, tabId); - if (lastTabFocused.current !== tabId) { - if ((await waitForEvent("tabsFocused", 1000)) !== tabId) { - console.log("tab id did not match after focus in 1000ms"); - return; + if (lastTabFocused.current !== event.tabId) { + console.log("Focused tab"); + useTabStore.getState().focusTab(event.tabId); } } - // If note is already loaded and forced reload is not requested, return. - if (!event.forced && currentNotes.current[item.id]) return; + const tabId = event.tabId || useTabStore.getState().currentTab; + if (lastTabFocused.current !== tabId) { + // if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) { + // console.log("tab id did not match after focus in 1000ms"); + // return; + // } + console.log("Waiting for tab to focus"); + return; + } state.current.movedAway = false; state.current.currentlyEditing = true; - if (!currentNotes.current[item.id]) { - // Reset current tab if note isn't already loaded. - currentNotes.current && (await reset(tabId, false, false)); - } await loadContent(item); if ( @@ -441,6 +447,7 @@ export const useEditor = ( currentContents.current[item.id]?.data && loadingState.current === currentContents.current[item.id]?.data ) { + // If note is already loading, return. return; } @@ -449,7 +456,7 @@ export const useEditor = ( return; } - lastContentChangeTime.current = item.dateEdited; + lastContentChangeTime.current[item.id] = item.dateEdited; currentLoadingNoteId.current = item.id; currentNotes.current[item.id] = item; @@ -466,6 +473,7 @@ export const useEditor = ( await postMessage(EditorEvents.title, item.title, tabId); loadingState.current = currentContents.current[item.id]?.data; if (currentContents.current[item.id]?.data) { + console.log("loading content for note..."); await postMessage( EditorEvents.html, currentContents.current[item.id]?.data, @@ -512,7 +520,8 @@ export const useEditor = ( const note = await db.notes?.note(noteId); - if (lastContentChangeTime.current >= (data as Note).dateEdited) return; + if (lastContentChangeTime.current[noteId] >= (data as Note).dateEdited) + return; lock.current = true; @@ -538,7 +547,7 @@ export const useEditor = ( } else { const _nextContent = data.data; if (_nextContent === currentContents.current?.data) return; - lastContentChangeTime.current = note.dateEdited; + lastContentChangeTime.current[note.id] = note.dateEdited; await postMessage(EditorEvents.updatehtml, _nextContent, tabId); if (!isEncryptedContent(data)) { currentContents.current[note.id] = data as UnencryptedContentItem; @@ -596,7 +605,9 @@ export const useEditor = ( ) return; - lastContentChangeTime.current = Date.now(); + if (noteId) { + lastContentChangeTime.current[noteId] = Date.now(); + } if (type === EditorEvents.content && noteId) { currentContents.current[noteId as string] = { @@ -655,14 +666,12 @@ export const useEditor = ( }; }, [editorId, loadNote, restoreEditorState, isDefaultEditor]); - const onContentChanged = () => { - lastContentChangeTime.current = Date.now(); + const onContentChanged = (noteId?: string) => { + if (noteId) { + lastContentChangeTime.current[noteId] = Date.now(); + } }; - useEffect(() => { - state.current.saveCount = 0; - }, [loading]); - const onReady = useCallback(async () => { if ( !(await isEditorLoaded( @@ -671,15 +680,9 @@ export const useEditor = ( useTabStore.getState().currentTab )) ) { - console.log( - "ready failed....", - sessionIdRef.current, - useTabStore.getState().currentTab - ); eSendEvent("webview_reset", "onReady"); return false; } else { - console.log("onReady", "sync tabs"); syncTabs(); isDefaultEditor && restoreEditorState(); return true; @@ -691,55 +694,43 @@ export const useEditor = ( clearTimeout(timers.current["editor:loaded"]); timers.current["editor:loaded"] = setTimeout(async () => { postMessage(EditorEvents.theme, theme); - commands.setInsets( isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 } ); await commands.setSettings(); - timers.current["editor:loaded"] = setTimeout(async () => { - if (!state.current.ready && (await onReady())) { - state.current.ready = true; - } - overlay(false); - // TODO: Improve handling this on app launch from a link etc. - const noteId = useTabStore.getState().getCurrentNoteId(); + if (!state.current.ready && (await onReady())) { + state.current.ready = true; + } + overlay(false); - if (noteId) { - if (useSettingStore.getState().isAppLoading) { - const unsub = useSettingStore.subscribe(async (s) => { - if (!s.isAppLoading) { - try { - const note = await db.notes.note(noteId); - if (note) { - loadNote({ item: note, forced: true }); - } else { - console.log("new note after app load"); - loadNote({ newNote: true }); - if (tabBarRef.current?.page === 1) { - state.current.currentlyEditing = false; - } - } - unsub(); - } catch (e) { - console.log(e); - } - } - }); - } else { - const note = await db.notes.note(noteId); - if (note) { - loadNote({ item: note, forced: true }); - } else { - console.log("new note"); - loadNote({ newNote: true }); - if (tabBarRef.current?.page === 1) { - state.current.currentlyEditing = false; - } - } + const noteId = useTabStore.getState().getCurrentNoteId(); + async function restoreTabNote() { + if (!noteId) return; + const note = await db.notes.note(noteId); + if (note) { + loadNote({ item: note, forced: true }); + } else { + console.log("Editor loaded with blank note"); + loadNote({ newNote: true }); + if (tabBarRef.current?.page === 1) { + state.current.currentlyEditing = false; } } - }); + } + + if (noteId) { + if (useSettingStore.getState().isAppLoading) { + const unsub = useSettingStore.subscribe(async (state) => { + if (!state.isAppLoading) { + restoreTabNote(); + unsub(); + } + }); + } else { + restoreTabNote(); + } + } }); }, [ onReady, diff --git a/apps/mobile/app/utils/events.js b/apps/mobile/app/utils/events.js index 3135a6ad5..70da40786 100644 --- a/apps/mobile/app/utils/events.js +++ b/apps/mobile/app/utils/events.js @@ -166,3 +166,4 @@ export const eOnRefreshSearch = "612"; export const eOpenAppLockPasswordDialog = "613"; export const eCloseAppLocKPasswordDailog = "614"; +export const eEditorTabFocused = "613"; diff --git a/apps/mobile/package-lock.json b/apps/mobile/package-lock.json index 804d4a101..3fb1bee8c 100644 --- a/apps/mobile/package-lock.json +++ b/apps/mobile/package-lock.json @@ -2655,6 +2655,8 @@ "mdi-react": "9.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-freeze": "^1.0.3", + "tinycolor2": "1.6.0", "zustand": "^4.4.7" }, "devDependencies": { @@ -43516,6 +43518,28 @@ } } }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-freeze": { "version": "1.0.3", "license": "MIT", @@ -61595,7 +61619,9 @@ "mdi-react": "9.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-freeze": "^1.0.3", "react-scripts": "^5.0.1", + "tinycolor2": "1.6.0", "zustand": "^4.4.7" }, "dependencies": { diff --git a/packages/editor-mobile/src/hooks/useEditorController.ts b/packages/editor-mobile/src/hooks/useEditorController.ts index 732d8593a..03c403704 100644 --- a/packages/editor-mobile/src/hooks/useEditorController.ts +++ b/packages/editor-mobile/src/hooks/useEditorController.ts @@ -149,7 +149,7 @@ export function useEditorController(update: () => void): EditorController { const contentChange = useCallback( (editor: Editor, ignoreEdit?: boolean) => { const currentSessionId = globalThis.sessionId; - post(EventTypes.contentchange); + post(EventTypes.contentchange, undefined, tab.id, tab.noteId); if (!editor) return; if (typeof timers.current.change === "number") { clearTimeout(timers.current?.change); @@ -198,14 +198,15 @@ export function useEditorController(update: () => void): EditorController { return; } - logger( - "info", - "webview message for tab", - message.type, - tab.id, - message.tabId, - useTabStore.getState().currentTab - ); + if (tab.id === message.tabId) { + logger( + "info", + message.type, + tab.noteId, + "Focused:", + tab.id === useTabStore.getState().currentTab + ); + } const editor = editors[tab.id]; switch (type) {