chore(revisions): address requested changes

This commit is contained in:
Elian Doran
2026-04-18 19:55:26 +03:00
parent 10a2e21636
commit 23cb66bba9
6 changed files with 40 additions and 47 deletions

View File

@@ -2539,5 +2539,9 @@
"move_note": "Move note",
"clone_note": "Clone note"
}
},
"common": {
"save": "Save",
"cancel": "Cancel"
}
}

View File

@@ -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 (
<Modal
className="revisions-dialog"
@@ -489,8 +490,8 @@ function RevisionDescription({ revisionItem, editing, draft, onEdit, onDraftChan
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
/>
<ActionButton icon="bx bx-check" text={t("revisions.edit_description")} onClick={onSave} />
<ActionButton icon="bx bx-x" text={t("revisions.edit_description")} onClick={onCancel} />
<ActionButton icon="bx bx-check" text={t("common.save")} onClick={onSave} />
<ActionButton icon="bx bx-x" text={t("common.cancel")} onClick={onCancel} />
</div>
);
}
@@ -584,41 +585,31 @@ function RevisionContentDiff({ noteContent, itemContent, itemType }: {
itemContent: string | Buffer<ArrayBufferLike> | undefined,
itemType: string
}) {
const contentRef = useRef<HTMLDivElement>(null);
if (!noteContent || typeof itemContent !== "string") {
return <div className="revision-diff-content">{t("revisions.diff_not_available")}</div>;
}
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 `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
} else if (part.removed) {
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
}
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 `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
} else if (part.removed) {
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
}
return utils.escapeHtml(part.value);
}).join("");
if (contentRef.current) {
contentRef.current.innerHTML = diffHtml;
}
}
}, [noteContent, itemContent, itemType]);
return <div ref={contentRef} className={clsx("revision-diff-content", itemType === "text" ? "ck-content" : "revision-diff-code")} />;
return <SanitizedHtml
className={clsx("revision-diff-content", itemType === "text" ? "ck-content" : "revision-diff-code")}
html={diffHtml}
/>;
}

View File

@@ -153,7 +153,7 @@ export default function Modal({ children, className, size, title, customTitleBar
<div className={clsx("modal-content", sidebar && "modal-content-with-sidebar")}>
{sidebar && <div className="modal-sidebar">
{title && <div className="modal-sidebar-header">
<h5>{typeof title === "string" ? title : title}</h5>
<h5>{title}</h5>
</div>}
{sidebar}
</div>}

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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,