diff --git a/packages/core/__tests__/note-history.test.js b/packages/core/__tests__/note-history.test.js index 66f3ed062..2e8e805e4 100644 --- a/packages/core/__tests__/note-history.test.js +++ b/packages/core/__tests__/note-history.test.js @@ -214,28 +214,90 @@ test("note history item can be created by setting note title", () => )); test("note history item can be created by setting note title and content both", () => - noteTest({ title: "Test note", ...TEST_NOTE, sessionId: "notesession" }).then( + noteTest({ + ...TEST_NOTE, + title: "Test note", + sessionId: "notesession" + }).then(async ({ db, id }) => { + expect(await db.noteHistory.get(id).count()).toBe(1); + const history = db.noteHistory.get(id); + const items = await history.items(); + const content = await db.noteHistory.sessionContent.get( + items[0].sessionContentId + ); + expect(await db.noteHistory.collection.count()).toBe(1); + expect(content.data).toBe(TEST_NOTE.content.data); + expect(content.title).toBe("Test note"); + await db.notes.add({ + id: id, + content: TEST_NOTE.content, + sessionId: "notesession" + }); + expect(content.data).toBe(TEST_NOTE.content.data); + expect(content.title).toBe("Test note"); + await db.notes.add({ + id: id, + title: "Test note", + sessionId: "notesession" + }); + expect(content.data).toBe(TEST_NOTE.content.data); + expect(content.title).toBe("Test note"); + })); + +test("restoring an old session should replace note's content title", () => + noteTest({ title: "Test note", sessionId: Date.now() }).then( async ({ db, id }) => { - expect(await db.noteHistory.get(id).count()).toBe(1); - const history = db.noteHistory.get(id); - const items = await history.items(); - const content = await db.noteHistory.sessionContent.get( - items[0].sessionContentId - ); - expect(await db.noteHistory.sessionContent.collection.count()).toBe(1); - expect(content.data).toBe(TEST_NOTE.content.data); - expect(content.title).toBe("Test note"); + await delay(1000); + let newTitle = "Test note (edited)"; + + const sessionId = `${Date.now() + 10000}`; await db.notes.add({ id: id, - content: TEST_NOTE.content + title: newTitle, + sessionId }); - expect(content.data).toBe(TEST_NOTE.content.data); - expect(content.title).toBe("Test note"); - await db.notes.add({ - id: id, - title: "Test note" - }); - expect(content.data).toBe(TEST_NOTE.content.data); - expect(content.title).toBe("Test note"); + + const [, firstVersion] = await db.noteHistory + .get(id) + .items(undefined, { sortBy: "dateModified", sortDirection: "desc" }); + expect(firstVersion.id).not.toBe(`${id}_${sessionId}`); + await db.noteHistory.restore(firstVersion.id); + + const title = (await db.notes.note(id)).title; + expect(title).toBe("Test note"); + } + )); + +test("restoring an old session should replace note's content and title", () => + noteTest({ ...TEST_NOTE, title: "Test note", sessionId: Date.now() }).then( + async ({ db, id }) => { + await delay(1000); + let newTitle = "Test note (edited)"; + let editedContent = { + data: TEST_NOTE.content.data + "

Some new content

", + type: "tiptap" + }; + + const sessionId = `${Date.now() + 10000}`; + await db.notes.add({ + id: id, + title: newTitle, + sessionId, + content: editedContent + }); + + const [, firstVersion] = await db.noteHistory + .get(id) + .items(undefined, { sortBy: "dateModified", sortDirection: "desc" }); + expect(firstVersion.id).not.toBe(`${id}_${sessionId}`); + await db.noteHistory.restore(firstVersion.id); + + const title = (await db.notes.note(id)).title; + expect(title).toBe("Test note"); + + const contentId = (await db.notes.note(id)).contentId; + expect((await db.content.get(contentId)).data).toBe( + TEST_NOTE.content.data + ); } )); diff --git a/packages/core/src/collections/note-history.ts b/packages/core/src/collections/note-history.ts index bd8275baa..0ebff07c4 100644 --- a/packages/core/src/collections/note-history.ts +++ b/packages/core/src/collections/note-history.ts @@ -20,7 +20,7 @@ along with this program. If not, see . import Database from "../api/index.js"; import { isCipher } from "../utils/crypto.js"; import { FilteredSelector, SQLCollection } from "../database/sql-collection.js"; -import { HistorySession, isDeleted, NoteContent } from "../types.js"; +import { HistorySession, isDeleted, Note, NoteContent } from "../types.js"; import { makeSessionContentId } from "../utils/id.js"; import { ICollection } from "./collection.js"; import { SessionContent } from "./session-content.js"; @@ -190,22 +190,48 @@ export class NoteHistory implements ICollection { if (!note || !content) return; if (session.locked && isCipher(content.data)) { + const sessionId = `${Date.now()}`; await this.db.content.add({ id: note.contentId, noteId: session.noteId, - sessionId: `${Date.now()}`, + sessionId: sessionId, data: content.data, type: content.type }); - } else if (content.data && content.type && !isCipher(content.data)) { - await this.db.notes.add({ + + if (content.title) { + await this.db.notes.add({ + id: session.noteId, + sessionId: sessionId, + title: content.title + }); + } + } else if ( + (content.data && content.type && !isCipher(content.data)) || + content.title + ) { + const note: Partial< + Note & { + content: NoteContent; + sessionId: string; + } + > = { id: session.noteId, - sessionId: `${Date.now()}`, - content: { + sessionId: `${Date.now()}` + }; + + if (content.data && content.type && !isCipher(content.data)) { + note.content = { data: content.data, type: content.type - } - }); + }; + } + + if (content.title) { + note.title = content.title; + } + + await this.db.notes.add(note); } } diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po index 44ff11976..fe4127d36 100644 --- a/packages/intl/locale/en.po +++ b/packages/intl/locale/en.po @@ -6552,6 +6552,10 @@ msgstr "This may take a while" msgid "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co." msgstr "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co." +#: src/strings.ts:2623 +msgid "This note is empty" +msgstr "This note is empty" + #: src/strings.ts:1594 msgid "This note is locked" msgstr "This note is locked" diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po index 1227b767d..7a5ae104d 100644 --- a/packages/intl/locale/pseudo-LOCALE.po +++ b/packages/intl/locale/pseudo-LOCALE.po @@ -6511,6 +6511,10 @@ msgstr "" msgid "This must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co." msgstr "" +#: src/strings.ts:2623 +msgid "This note is empty" +msgstr "" + #: src/strings.ts:1594 msgid "This note is locked" msgstr "" diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts index eae277179..d05967901 100644 --- a/packages/intl/src/strings.ts +++ b/packages/intl/src/strings.ts @@ -2631,5 +2631,6 @@ Use this if changes from other devices are not appearing on this device. This wi unsetExpiry: () => t`Unset expiry`, expiryDate: () => t`Expiry date`, exportCsv: () => t`Export CSV`, - importCsv: () => t`Import CSV` + importCsv: () => t`Import CSV`, + noContent: () => t`This note is empty` };