feat(revisions): add a source field

This commit is contained in:
Elian Doran
2026-04-18 12:46:13 +03:00
parent 8aaa4d7bde
commit 4b35881889
13 changed files with 47 additions and 13 deletions

View File

@@ -310,7 +310,12 @@
"description_placeholder": "Add a description (optional)",
"revision_saved": "Note revision has been saved.",
"edit_description": "Edit description",
"description_updated": "Revision description has been updated."
"description_updated": "Revision description has been updated.",
"source_auto": "Auto",
"source_manual": "Manual",
"source_etapi": "ETAPI",
"source_llm": "LLM",
"source_restore": "Restore"
},
"sort_child_notes": {
"sort_children_by": "Sort children by...",

View File

@@ -200,7 +200,23 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
active={currentRevision && item.revisionId === currentRevision.revisionId}
>
<div>
{item.dateCreated && item.dateCreated.substr(0, 16)} ({item.contentLength && utils.formatSize(item.contentLength)})
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "4px" }}>
<span>{item.dateCreated && item.dateCreated.substr(0, 16)}</span>
{item.source && item.source !== "auto" && (
<span className="revision-source-badge" style={{
fontSize: "0.75em",
padding: "0 4px",
borderRadius: "3px",
backgroundColor: "var(--accented-background-color)",
whiteSpace: "nowrap"
}}>
{t(`revisions.source_${item.source}`)}
</span>
)}
</div>
<div style={{ fontSize: "0.85em", opacity: 0.7 }}>
{item.contentLength && utils.formatSize(item.contentLength)}
</div>
{item.description && (
<div className="revision-description" style={{ fontSize: "0.85em", opacity: 0.7, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{item.description}

View File

@@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY,
mime TEXT DEFAULT '' NOT NULL,
`title` TEXT NOT NULL,
`description` TEXT DEFAULT '' NOT NULL,
`source` TEXT DEFAULT 'auto' NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
`utcDateLastEdited` TEXT NOT NULL,

View File

@@ -1,4 +1,4 @@
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow, RevisionSource } from "@triliumnext/commons";
import { dayjs, getNoteIcon } from "@triliumnext/commons";
import cloningService from "../../services/cloning.js";
@@ -1543,7 +1543,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
}
saveRevision(description?: string): BRevision {
saveRevision(opts: { description?: string; source?: RevisionSource } = {}): BRevision {
return sql.transactional(() => {
let noteContent = this.getContent();
@@ -1552,7 +1552,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
noteId: this.noteId,
// title and text should be decrypted now
title: this.title,
description: description || "",
description: opts.description || "",
source: opts.source || "auto",
type: this.type,
mime: this.mime,
isProtected: this.isProtected,

View File

@@ -7,7 +7,7 @@ import becca from "../becca.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
import sql from "../../services/sql.js";
import BAttachment from "./battachment.js";
import type { AttachmentRow, NoteType, RevisionPojo, RevisionRow } from "@triliumnext/commons";
import type { AttachmentRow, NoteType, RevisionPojo, RevisionRow, RevisionSource } from "@triliumnext/commons";
import eraseService from "../../services/erase.js";
interface ContentOpts {
@@ -31,7 +31,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
return "revisionId";
}
static get hashedProperties() {
return ["revisionId", "noteId", "title", "description", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"];
return ["revisionId", "noteId", "title", "description", "source", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"];
}
revisionId?: string;
@@ -40,6 +40,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
mime!: string;
title!: string;
description!: string;
source!: RevisionSource;
dateLastEdited?: string;
utcDateLastEdited?: string;
contentLength?: number;
@@ -63,6 +64,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
this.isProtected = !!row.isProtected;
this.title = row.title;
this.description = row.description || "";
this.source = row.source || "auto";
this.blobId = row.blobId;
this.dateLastEdited = row.dateLastEdited;
this.dateCreated = row.dateCreated;
@@ -196,6 +198,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
isProtected: this.isProtected,
title: this.title,
description: this.description,
source: this.source,
blobId: this.blobId,
dateLastEdited: this.dateLastEdited,
dateCreated: this.dateCreated,

View File

@@ -74,6 +74,7 @@ function mapRevisionToPojo(revision: BRevision) {
isProtected: revision.isProtected,
title: revision.title,
description: revision.description,
source: revision.source,
blobId: revision.blobId,
dateLastEdited: revision.dateLastEdited,
dateCreated: revision.dateCreated,

View File

@@ -193,7 +193,7 @@ function register(router: Router) {
const note = eu.getAndCheckNote(req.params.noteId);
const description = req.body?.description || "";
const revision = note.saveRevision(description);
const revision = note.saveRevision({ description, source: "etapi" });
res.status(201).json(mappers.mapRevisionToPojo(revision));
});

View File

@@ -11,6 +11,7 @@ const MIGRATIONS: (SqlMigration | JsMigration)[] = [
version: 238,
sql: /*sql*/`
ALTER TABLE revisions ADD COLUMN description TEXT DEFAULT '' NOT NULL;
ALTER TABLE revisions ADD COLUMN source TEXT DEFAULT 'auto' NOT NULL;
`,
ignoreErrors: true
},

View File

@@ -352,7 +352,7 @@ function forceSaveRevision(req: Request<{ noteId: string }>) {
}
const description = req.body?.description || "";
const revision = note.saveRevision(description);
const revision = note.saveRevision({ description, source: "manual" });
return {
revisionId: revision.revisionId

View File

@@ -137,7 +137,7 @@ function restoreRevision(req: Request<{ revisionId: string }>) {
const note = revision.getNote();
sql.transactional(() => {
note.saveRevision();
note.saveRevision({ source: "restore" });
for (const oldNoteAttachment of note.getAttachments()) {
oldNoteAttachment.markAsDeleted();

View File

@@ -116,7 +116,7 @@ export const noteTools = defineTools({
return { error: `Cannot update content for note type: ${note.type}` };
}
note.saveRevision();
note.saveRevision({ source: "llm" });
setNoteContentFromLlm(note, content);
return {
success: true,
@@ -158,7 +158,7 @@ export const noteTools = defineTools({
newContent = existingContent + (existingContent.endsWith("\n") ? "" : "\n") + content;
}
note.saveRevision();
note.saveRevision({ source: "llm" });
note.setContent(newContent);
return {
success: true,

View File

@@ -21,6 +21,9 @@ export interface AttachmentRow {
encoding?: "base64";
}
export const REVISION_SOURCES = ["auto", "manual", "etapi", "llm", "restore"] as const;
export type RevisionSource = (typeof REVISION_SOURCES)[number];
export interface RevisionRow {
revisionId?: string;
noteId: string;
@@ -29,6 +32,7 @@ export interface RevisionRow {
isProtected?: boolean;
title: string;
description?: string;
source?: RevisionSource;
blobId?: string;
dateLastEdited?: string;
dateCreated?: string;

View File

@@ -1,4 +1,4 @@
import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType } from "./rows.js";
import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType, RevisionSource } from "./rows.js";
type Response = {
success: true,
@@ -34,6 +34,7 @@ export interface RevisionItem {
type: NoteType;
title: string;
description?: string;
source?: RevisionSource;
isProtected?: boolean;
mime: string;
}
@@ -46,6 +47,7 @@ export interface RevisionPojo {
isProtected?: boolean;
title: string;
description?: string;
source?: RevisionSource;
blobId?: string;
dateLastEdited?: string;
dateCreated?: string;