From 23cb66bba90eb850097d10ec2fda9ffe131517db Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Apr 2026 19:55:26 +0300 Subject: [PATCH] chore(revisions): address requested changes --- .../src/translations/en/translation.json | 4 ++ apps/client/src/widgets/dialogs/revisions.tsx | 63 ++++++++----------- apps/client/src/widgets/react/Modal.tsx | 2 +- apps/server/src/etapi/notes.ts | 2 +- apps/server/src/routes/api/notes.ts | 2 +- .../src/services/llm/tools/note_tools.ts | 14 ++--- 6 files changed, 40 insertions(+), 47 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 7d4856a5b9..2baa838fb1 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2539,5 +2539,9 @@ "move_note": "Move note", "clone_note": "Clone note" } + }, + "common": { + "save": "Save", + "cancel": "Cancel" } } diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index d36d509cc8..787264b1ac 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -28,7 +28,7 @@ import FormToggle from "../react/FormToggle"; import { useTriliumEvent } from "../react/hooks"; import Modal from "../react/Modal"; import NoItems from "../react/NoItems"; -import { RawHtmlBlock } from "../react/RawHtml"; +import { RawHtmlBlock, SanitizedHtml } from "../react/RawHtml"; import PdfViewer from "../type_widgets/file/PdfViewer"; export default function RevisionsDialog() { @@ -58,6 +58,7 @@ export default function RevisionsDialog() { } }, [ note, refreshCounter ]); + const revisionsLoaded = revisions !== undefined; const hasRevisions = !!revisions?.length; if (revisions?.length && !currentRevision) { @@ -72,7 +73,7 @@ export default function RevisionsDialog() { setRevisions(undefined); }; - if (!hasRevisions) { + if (revisionsLoaded && !hasRevisions) { return ( - - + + ); } @@ -584,41 +585,31 @@ function RevisionContentDiff({ noteContent, itemContent, itemType }: { itemContent: string | Buffer | undefined, itemType: string }) { - const contentRef = useRef(null); + if (!noteContent || typeof itemContent !== "string") { + return
{t("revisions.diff_not_available")}
; + } - useEffect(() => { - if (!noteContent || typeof itemContent !== "string") { - if (contentRef.current) { - contentRef.current.textContent = t("revisions.diff_not_available"); + let diffHtml: string; + if (itemType === "text") { + // Use proper HTML-aware diff for rich text content + diffHtml = HtmlDiff.execute(noteContent, itemContent); + } else { + // Use word diff for code/mermaid (plain text) + const diff = diffWords(noteContent, itemContent); + diffHtml = diff.map(part => { + if (part.added) { + return `${utils.escapeHtml(part.value)}`; + } else if (part.removed) { + return `${utils.escapeHtml(part.value)}`; } - return; - } + return utils.escapeHtml(part.value); + }).join(""); + } - if (itemType === "text") { - // Use proper HTML-aware diff for rich text content - const diffHtml = HtmlDiff.execute(noteContent, itemContent); - if (contentRef.current) { - contentRef.current.innerHTML = diffHtml; - } - } else { - // Use word diff for code/mermaid (plain text) - const diff = diffWords(noteContent, itemContent); - const diffHtml = diff.map(part => { - if (part.added) { - return `${utils.escapeHtml(part.value)}`; - } else if (part.removed) { - return `${utils.escapeHtml(part.value)}`; - } - return utils.escapeHtml(part.value); - }).join(""); - - if (contentRef.current) { - contentRef.current.innerHTML = diffHtml; - } - } - }, [noteContent, itemContent, itemType]); - - return
; + return ; } diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx index 9c0addce13..dcbb884961 100644 --- a/apps/client/src/widgets/react/Modal.tsx +++ b/apps/client/src/widgets/react/Modal.tsx @@ -153,7 +153,7 @@ export default function Modal({ children, className, size, title, customTitleBar
{sidebar &&
{title &&
-
{typeof title === "string" ? title : title}
+
{title}
} {sidebar}
} diff --git a/apps/server/src/etapi/notes.ts b/apps/server/src/etapi/notes.ts index 9949b7abd2..74faf7aabe 100644 --- a/apps/server/src/etapi/notes.ts +++ b/apps/server/src/etapi/notes.ts @@ -192,7 +192,7 @@ function register(router: Router) { eu.route<{ noteId: string }>(router, "post", "/etapi/notes/:noteId/revision", (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); - const description = req.body?.description || ""; + const description = typeof req.body?.description === "string" ? req.body.description : ""; note.saveRevision({ description, source: "etapi" }); return res.sendStatus(204); diff --git a/apps/server/src/routes/api/notes.ts b/apps/server/src/routes/api/notes.ts index 8aec006a09..e8e7d92ff2 100644 --- a/apps/server/src/routes/api/notes.ts +++ b/apps/server/src/routes/api/notes.ts @@ -351,7 +351,7 @@ function forceSaveRevision(req: Request<{ noteId: string }>) { throw new ValidationError(`Note revision of a protected note cannot be created outside of a protected session.`); } - const description = req.body?.description || ""; + const description = typeof req.body?.description === "string" ? req.body.description : ""; const revision = note.saveRevision({ description, source: "manual" }); return { diff --git a/apps/server/src/services/llm/tools/note_tools.ts b/apps/server/src/services/llm/tools/note_tools.ts index 8a4ec1c97b..d1ce83e3e0 100644 --- a/apps/server/src/services/llm/tools/note_tools.ts +++ b/apps/server/src/services/llm/tools/note_tools.ts @@ -101,11 +101,10 @@ export const noteTools = defineTools({ description: "Replace the entire content of a note. Use this to completely rewrite a note's content. For text notes, provide Markdown content.", inputSchema: z.object({ noteId: z.string().describe("The ID of the note to update"), - content: z.string().describe("The new content for the note (Markdown for text notes, plain text for code notes)"), - changeDescription: z.string().describe("A concise description of what was changed and why (e.g. 'Fix typos in introduction', 'Add section on error handling'). Keep it short, under 100 characters.") + content: z.string().describe("The new content for the note (Markdown for text notes, plain text for code notes)") }), mutates: true, - execute: ({ noteId, content, changeDescription }) => { + execute: ({ noteId, content }) => { const note = becca.getNote(noteId); if (!note) { return { error: "Note not found" }; @@ -117,7 +116,7 @@ export const noteTools = defineTools({ return { error: `Cannot update content for note type: ${note.type}` }; } - note.saveRevision({ description: changeDescription, source: "llm" }); + note.saveRevision({ source: "llm" }); setNoteContentFromLlm(note, content); return { success: true, @@ -131,11 +130,10 @@ export const noteTools = defineTools({ description: "Append content to the end of an existing note. For text notes, provide Markdown content.", inputSchema: z.object({ noteId: z.string().describe("The ID of the note to append to"), - content: z.string().describe("The content to append (Markdown for text notes, plain text for code notes)"), - changeDescription: z.string().describe("A concise description of what was appended (e.g. 'Add meeting notes for May 15', 'Append troubleshooting section'). Keep it short, under 100 characters.") + content: z.string().describe("The content to append (Markdown for text notes, plain text for code notes)") }), mutates: true, - execute: ({ noteId, content, changeDescription }) => { + execute: ({ noteId, content }) => { const note = becca.getNote(noteId); if (!note) { return { error: "Note not found" }; @@ -160,7 +158,7 @@ export const noteTools = defineTools({ newContent = existingContent + (existingContent.endsWith("\n") ? "" : "\n") + content; } - note.saveRevision({ description: changeDescription, source: "llm" }); + note.saveRevision({ source: "llm" }); note.setContent(newContent); return { success: true,