mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 10:39:07 +02:00
Compare commits
1 Commits
master
...
fix/editor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7126a2cfac |
@@ -93,6 +93,8 @@ export type SavePayload = {
|
||||
ignoreEdit: boolean;
|
||||
tabId: string;
|
||||
pendingChanges?: boolean;
|
||||
sourceNoteId?: string;
|
||||
pendingChangesAt?: number;
|
||||
};
|
||||
|
||||
export type AppState = {
|
||||
|
||||
@@ -411,16 +411,20 @@ export const useEditorEvents = (
|
||||
.getState()
|
||||
.getNoteIdForTab(editorMessage.tabId);
|
||||
|
||||
const saveNoteId = editorMessage.noteId || noteId;
|
||||
|
||||
switch (editorMessage.type) {
|
||||
case EditorEvents.content:
|
||||
DatabaseLogger.log("EditorEvents.content");
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
content: editorMessage.value.html as string,
|
||||
noteId: noteId,
|
||||
noteId: saveNoteId,
|
||||
sourceNoteId: editorMessage.noteId,
|
||||
tabId: editorMessage.tabId,
|
||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
pendingChanges: editorMessage.value?.pendingChanges,
|
||||
pendingChangesAt: editorMessage.value?.pendingChangesAt
|
||||
});
|
||||
break;
|
||||
case EditorEvents.title:
|
||||
@@ -428,10 +432,12 @@ export const useEditorEvents = (
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
title: editorMessage.value?.title as string,
|
||||
noteId: noteId,
|
||||
noteId: saveNoteId,
|
||||
sourceNoteId: editorMessage.noteId,
|
||||
tabId: editorMessage.tabId,
|
||||
ignoreEdit: false,
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
pendingChanges: editorMessage.value?.pendingChanges,
|
||||
pendingChangesAt: editorMessage.value?.pendingChangesAt
|
||||
});
|
||||
break;
|
||||
case EditorEvents.logger:
|
||||
|
||||
@@ -263,26 +263,63 @@ export const useEditor = (
|
||||
ignoreEdit,
|
||||
sessionHistoryId: currentSessionHistoryId,
|
||||
tabId,
|
||||
pendingChanges
|
||||
pendingChanges,
|
||||
sourceNoteId,
|
||||
pendingChangesAt
|
||||
}: SavePayload) => {
|
||||
if (currentNotes.current[id as string]?.readonly || readonly) return;
|
||||
|
||||
if (sourceNoteId && id && sourceNoteId !== id) {
|
||||
DatabaseLogger.error(
|
||||
new Error(
|
||||
`Refused to save content of note ${sourceNoteId} into note ${id}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (id && !(await db.notes?.note(id))) {
|
||||
await reset(tabId);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
noteId: undefined,
|
||||
noteLocked: undefined,
|
||||
locked: undefined,
|
||||
readonly: undefined,
|
||||
scrollTop: undefined,
|
||||
selection: undefined,
|
||||
spellCheckDisabled: false
|
||||
}
|
||||
});
|
||||
if (useTabStore.getState().getNoteIdForTab(tabId) === id) {
|
||||
await reset(tabId);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
noteId: undefined,
|
||||
noteLocked: undefined,
|
||||
locked: undefined,
|
||||
readonly: undefined,
|
||||
scrollTop: undefined,
|
||||
selection: undefined,
|
||||
spellCheckDisabled: false
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
let note = id ? await db.notes?.note(id) : undefined;
|
||||
|
||||
// A restored pending change can be older than what is already in the
|
||||
// db (it was saved on another device, or the save actually went
|
||||
// through and only the acknowledgement was lost). Applying it would
|
||||
// roll the note back, so verify it is still the newest edit. Content
|
||||
// and title are compared separately so that a newer title doesn't
|
||||
// discard pending content, and vice versa.
|
||||
if (pendingChanges && pendingChangesAt && note) {
|
||||
const dateEdited = data
|
||||
? note.contentId
|
||||
? (await db.content?.get(note.contentId))?.dateEdited
|
||||
: undefined
|
||||
: note.dateEdited;
|
||||
|
||||
if (dateEdited && dateEdited > pendingChangesAt) {
|
||||
DatabaseLogger.log(
|
||||
`Discarding stale pending ${
|
||||
data ? "content" : "title"
|
||||
} for note ${id}: edited at ${dateEdited}, change captured at ${pendingChangesAt}`
|
||||
);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
|
||||
if (note?.conflicted) {
|
||||
@@ -321,6 +358,9 @@ export const useEditor = (
|
||||
let saved = false;
|
||||
setTimeout(() => {
|
||||
if (saved) return;
|
||||
// Don't report progress on a tab that has moved on to another note.
|
||||
if (id && useTabStore.getState().getNoteIdForTab(tabId) !== id)
|
||||
return;
|
||||
commands.setStatus(
|
||||
getFormattedDate(note ? note.dateEdited : Date.now(), "date-time"),
|
||||
strings.saving(),
|
||||
@@ -436,14 +476,26 @@ export const useEditor = (
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
id &&
|
||||
id === useTabStore.getState().getCurrentNoteId() &&
|
||||
pendingChanges
|
||||
) {
|
||||
postMessage(NativeEvents.title, title || note?.title, tabId);
|
||||
postMessage(NativeEvents.html, data, tabId);
|
||||
currentNotes.current[id] = note;
|
||||
if (id && pendingChanges) {
|
||||
if (data) {
|
||||
currentContents.current[id] = {
|
||||
data: data,
|
||||
type: "tiptap",
|
||||
noteId: id
|
||||
};
|
||||
}
|
||||
lastContentChangeTime.current[id] = Date.now();
|
||||
|
||||
// Push the restored change into the editor only if the note is
|
||||
// actually open in a tab, and only into that tab.
|
||||
const noteTabId = useTabStore.getState().getTabForNote(id);
|
||||
if (noteTabId !== undefined) {
|
||||
postMessage(NativeEvents.title, title || note?.title, noteTabId);
|
||||
if (data) {
|
||||
postMessage(NativeEvents.html, { data: data }, noteTabId);
|
||||
}
|
||||
currentNotes.current[id] = note;
|
||||
}
|
||||
}
|
||||
|
||||
if (!saveCount.current[tabId]) {
|
||||
@@ -935,7 +987,9 @@ export const useEditor = (
|
||||
ignoreEdit,
|
||||
noteId,
|
||||
tabId,
|
||||
pendingChanges
|
||||
pendingChanges,
|
||||
sourceNoteId,
|
||||
pendingChangesAt
|
||||
}: {
|
||||
noteId?: string;
|
||||
title?: string;
|
||||
@@ -944,6 +998,8 @@ export const useEditor = (
|
||||
ignoreEdit: boolean;
|
||||
tabId: string;
|
||||
pendingChanges?: boolean;
|
||||
sourceNoteId?: string;
|
||||
pendingChangesAt?: number;
|
||||
}) => {
|
||||
DatabaseLogger.log(
|
||||
`saveContent... title: ${!!title}, content: ${!!content}, noteId: ${noteId}`
|
||||
@@ -971,7 +1027,10 @@ export const useEditor = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteId) {
|
||||
// A restored pending change is not a live edit: it may still be
|
||||
// discarded as stale by saveNote, so it must not claim to be the newest
|
||||
// content until it is actually written.
|
||||
if (noteId && !pendingChanges) {
|
||||
lastContentChangeTime.current[noteId] = Date.now();
|
||||
localTabState.current?.setEditTime(noteId, Date.now());
|
||||
localTabState?.current?.set(tabId, {
|
||||
@@ -979,7 +1038,7 @@ export const useEditor = (
|
||||
});
|
||||
}
|
||||
|
||||
if (type === EditorEvents.content && noteId) {
|
||||
if (type === EditorEvents.content && noteId && !pendingChanges) {
|
||||
currentContents.current[noteId as string] = {
|
||||
data: content,
|
||||
type: "tiptap",
|
||||
@@ -995,12 +1054,15 @@ export const useEditor = (
|
||||
ignoreEdit,
|
||||
sessionHistoryId: noteId ? editorSessionHistory.get(noteId) : undefined,
|
||||
tabId: tabId,
|
||||
pendingChanges
|
||||
pendingChanges,
|
||||
sourceNoteId,
|
||||
pendingChangesAt
|
||||
};
|
||||
|
||||
withTimer(
|
||||
noteId || "newnote",
|
||||
`${noteId || tabId}:${type}`,
|
||||
() => {
|
||||
if (!params.id) {
|
||||
if (!params.id && !params.sourceNoteId) {
|
||||
params.id = useTabStore.getState().getNoteIdForTab(tabId);
|
||||
}
|
||||
if (onChange && params.data) {
|
||||
|
||||
@@ -153,18 +153,17 @@ export function useEditorController({
|
||||
const titleChange = useCallback(async (title: string) => {
|
||||
if (!isReactNative()) return;
|
||||
const currentSessionId = globalThis.sessionId;
|
||||
post(
|
||||
EditorEvents.contentchange,
|
||||
undefined,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
);
|
||||
const editedAt = Date.now();
|
||||
|
||||
const tabId = tabRef.current.id;
|
||||
const noteId = tabRef.current.session?.noteId;
|
||||
post(EditorEvents.contentchange, undefined, tabId, noteId);
|
||||
const params = [
|
||||
{
|
||||
title
|
||||
},
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId,
|
||||
tabId,
|
||||
noteId,
|
||||
currentSessionId,
|
||||
1000
|
||||
];
|
||||
@@ -186,12 +185,12 @@ export function useEditorController({
|
||||
`Saving title failed, setting pending request ${pendingTitleIds.length}`
|
||||
);
|
||||
if (params[2]) {
|
||||
pendingSaveRequests.setTitle(params);
|
||||
pendingSaveRequests.setTitle(params, editedAt);
|
||||
}
|
||||
const element = document.getElementById("editor-saving-failed-overlay");
|
||||
if (element) {
|
||||
element.style.display = "flex";
|
||||
editors[tabRef.current.id]?.commands?.blur();
|
||||
editors[tabId]?.commands?.blur();
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
@@ -217,26 +216,36 @@ export function useEditorController({
|
||||
return;
|
||||
}
|
||||
const currentSessionId = globalThis.sessionId;
|
||||
post(
|
||||
EditorEvents.contentchange,
|
||||
undefined,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
);
|
||||
const tabId = tabRef.current.id;
|
||||
const noteId = tabRef.current.session?.noteId;
|
||||
post(EditorEvents.contentchange, undefined, tabId, noteId);
|
||||
if (!editor) return;
|
||||
if (typeof timers.current.change === "number") {
|
||||
clearTimeout(timers.current?.change);
|
||||
}
|
||||
|
||||
timers.current.change = setTimeout(async () => {
|
||||
if (tabRef.current.session?.noteId !== noteId) {
|
||||
logger(
|
||||
"info",
|
||||
`Edit discarded, tab ${tabId} moved from note ${noteId} to ${tabRef.current.session?.noteId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (editorControllers[tabId]?.loading) {
|
||||
logger("info", "Edit discarded, tab is in loading state");
|
||||
return;
|
||||
}
|
||||
|
||||
const editedAt = Date.now();
|
||||
htmlContentRef.current = editor.getHTML();
|
||||
const params = [
|
||||
{
|
||||
html: htmlContentRef.current,
|
||||
ignoreEdit: ignoreEdit
|
||||
},
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId,
|
||||
tabId,
|
||||
noteId,
|
||||
currentSessionId,
|
||||
5000
|
||||
];
|
||||
@@ -262,7 +271,7 @@ export function useEditorController({
|
||||
}`
|
||||
);
|
||||
if (params[2]) {
|
||||
pendingSaveRequests.setContent(params);
|
||||
pendingSaveRequests.setContent(params, editedAt);
|
||||
}
|
||||
|
||||
const element = document.getElementById(
|
||||
|
||||
@@ -23,13 +23,14 @@ class PendingSaveRequests {
|
||||
static TITLES = "pendingTitles";
|
||||
static CONTENT = "pendingContents";
|
||||
|
||||
async setTitle(value: any) {
|
||||
async setTitle(value: any, editedAt: number) {
|
||||
const pendingTitles = JSON.parse(
|
||||
this.get(PendingSaveRequests.TITLES) || "[]"
|
||||
);
|
||||
|
||||
(pendingTitles as any[]).push({
|
||||
id: randId("title-pending"),
|
||||
editedAt,
|
||||
params: value
|
||||
});
|
||||
return localStorage.setItem(
|
||||
@@ -45,13 +46,14 @@ class PendingSaveRequests {
|
||||
return pendingTitles;
|
||||
}
|
||||
|
||||
async setContent(value: any) {
|
||||
async setContent(value: any, editedAt: number) {
|
||||
const pendingContents = JSON.parse(
|
||||
this.get(PendingSaveRequests.CONTENT) || "[]"
|
||||
);
|
||||
|
||||
(pendingContents as any[]).push({
|
||||
id: randId("content-pending"),
|
||||
editedAt,
|
||||
params: value
|
||||
});
|
||||
return localStorage.setItem(
|
||||
@@ -118,7 +120,10 @@ class PendingSaveRequests {
|
||||
const pendingTitles = await this.getPendingTitles();
|
||||
this.remove(PendingSaveRequests.TITLES);
|
||||
for (const pending of pendingTitles) {
|
||||
if (pending.params[0]) pending.params[0].pendingChanges = true;
|
||||
if (pending.params[0]) {
|
||||
pending.params[0].pendingChanges = true;
|
||||
pending.params[0].pendingChangesAt = pending.editedAt;
|
||||
}
|
||||
await postAsyncWithTimeout(EditorEvents.title, ...pending.params);
|
||||
}
|
||||
};
|
||||
@@ -127,7 +132,10 @@ class PendingSaveRequests {
|
||||
const pendingContents = await this.getPendingContent();
|
||||
this.remove(PendingSaveRequests.CONTENT);
|
||||
for (const pending of pendingContents) {
|
||||
if (pending.params[0]) pending.params[0].pendingChanges = true;
|
||||
if (pending.params[0]) {
|
||||
pending.params[0].pendingChanges = true;
|
||||
pending.params[0].pendingChangesAt = pending.editedAt;
|
||||
}
|
||||
await postAsyncWithTimeout(EditorEvents.content, ...pending.params);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user