From 463ac99e3992e6078bfa58b7420f1d04de75ae0c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 5 May 2025 01:35:43 +0400 Subject: [PATCH] enh: note versioning --- .../components/common/RichTextInput.svelte | 11 ++ .../components/icons/ArrowUturnLeft.svelte | 19 ++ .../components/icons/ArrowUturnRight.svelte | 19 ++ src/lib/components/notes/NoteEditor.svelte | 171 +++++++++++++++--- 4 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/lib/components/icons/ArrowUturnLeft.svelte create mode 100644 src/lib/components/icons/ArrowUturnRight.svelte diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index 326b033b2..b7bc03e89 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -43,6 +43,7 @@ export let json = false; export let raw = false; + export let editable = true; export let preserveBreaks = false; export let generateAutoCompletion: Function = async () => null; @@ -58,6 +59,16 @@ throwOnError: false }; + $: if (editor) { + editor.setOptions({ + editable: editable + }); + } + + $: if (value === null && html !== null && editor) { + editor.commands.setContent(html); + } + // Function to find the next template in the document function findNextTemplate(doc, from = 0) { const patterns = [{ start: '{{', end: '}}' }]; diff --git a/src/lib/components/icons/ArrowUturnLeft.svelte b/src/lib/components/icons/ArrowUturnLeft.svelte new file mode 100644 index 000000000..ad0a05fa1 --- /dev/null +++ b/src/lib/components/icons/ArrowUturnLeft.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/icons/ArrowUturnRight.svelte b/src/lib/components/icons/ArrowUturnRight.svelte new file mode 100644 index 000000000..f7139c45c --- /dev/null +++ b/src/lib/components/icons/ArrowUturnRight.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/notes/NoteEditor.svelte b/src/lib/components/notes/NoteEditor.svelte index 30effb7dc..ca5ae2ac6 100644 --- a/src/lib/components/notes/NoteEditor.svelte +++ b/src/lib/components/notes/NoteEditor.svelte @@ -9,6 +9,7 @@ const i18n = getContext('i18n'); + import { marked } from 'marked'; import { toast } from 'svelte-sonner'; import { config, settings, showSidebar } from '$lib/stores'; @@ -62,6 +63,8 @@ import SparklesSolid from '../icons/SparklesSolid.svelte'; import Tooltip from '../common/Tooltip.svelte'; import Bars3BottomLeft from '../icons/Bars3BottomLeft.svelte'; + import ArrowUturnLeft from '../icons/ArrowUturnLeft.svelte'; + import ArrowUturnRight from '../icons/ArrowUturnRight.svelte'; export let id: null | string = null; @@ -75,6 +78,7 @@ html: '', md: '' }, + versions: [], files: null }, meta: null, @@ -83,14 +87,15 @@ let files = []; - let selectedVersion = 'note'; - + let versionIdx = null; let recording = false; let displayMediaRecord = false; let showDeleteConfirm = false; let dragged = false; + let loading = false; + let enhancing = false; const init = async () => { loading = true; @@ -118,7 +123,7 @@ } debounceTimeout = setTimeout(async () => { - if (!note) { + if (!note || enhancing || versionIdx !== null) { return; } @@ -141,7 +146,104 @@ init(); } - const versionToggleHandler = () => {}; + function areContentsEqual(a, b) { + return JSON.stringify(a) === JSON.stringify(b); + } + + function insertNoteVersion(note) { + const current = { + json: note.data.content.json, + html: note.data.content.html, + md: note.data.content.md + }; + const lastVersion = note.data.versions?.at(-1); + + if (!lastVersion || !areContentsEqual(lastVersion, current)) { + note.data.versions = (note.data.versions ?? []).concat(current); + return true; + } + return false; + } + + async function aiEnhanceContent(content) { + const md = content.md + '_ai'; + const html = marked.parse(md); + + return { + json: null, + html: html, + md: md + }; + } + + async function enhanceNoteHandler() { + insertNoteVersion(note); + + enhancing = true; + const aiResult = await aiEnhanceContent(note.data.content); + + note.data.content.json = aiResult.json; + note.data.content.html = aiResult.html; + note.data.content.md = aiResult.md; + + enhancing = false; + versionIdx = null; + } + + function setContentByVersion(versionIdx) { + if (!note.data.versions?.length) return; + let idx = versionIdx; + + if (idx === null) idx = note.data.versions.length - 1; // latest + const v = note.data.versions[idx]; + + note.data.content.json = v.json; + note.data.content.html = v.html; + note.data.content.md = v.md; + + if (versionIdx === null) { + const lastVersion = note.data.versions.at(-1); + const currentContent = note.data.content; + + if (areContentsEqual(lastVersion, currentContent)) { + // remove the last version + note.data.versions = note.data.versions.slice(0, -1); + } + } + } + + // Navigation + function versionNavigateHandler(direction) { + if (!note.data.versions || note.data.versions.length === 0) return; + + if (versionIdx === null) { + // Get latest snapshots + const lastVersion = note.data.versions.at(-1); + const currentContent = note.data.content; + + if (!areContentsEqual(lastVersion, currentContent)) { + // If the current content is different from the last version, insert a new version + insertNoteVersion(note); + versionIdx = note.data.versions.length - 1; + } else { + versionIdx = note.data.versions.length; + } + } + + if (direction === 'prev') { + if (versionIdx > 0) versionIdx -= 1; + } else if (direction === 'next') { + if (versionIdx < note.data.versions.length - 1) versionIdx += 1; + else versionIdx = null; // Reset to latest + + if (versionIdx === note.data.versions.length - 1) { + // If we reach the latest version, reset to null + versionIdx = null; + } + } + + setContentByVersion(versionIdx); + } const uploadFileHandler = async (file) => { const tempItemId = uuidv4(); @@ -428,7 +530,7 @@ {:else}
-
+
-
+
+ {#if note.data?.versions?.length > 0} +
+
+ + + +
+
+ {/if} + { downloadHandler(type); @@ -524,6 +653,7 @@ placeholder={$i18n.t('Write something...')} html={note.data?.content?.html} json={true} + editable={versionIdx === null} onChange={(content) => { note.data.content.html = content.html; note.data.content.md = content.md; @@ -609,18 +739,20 @@ }; }} > - + + +
- + - +