chore(client/ts): port revisions

This commit is contained in:
Elian Doran 2025-02-13 20:25:13 +02:00
parent e7f5d1d280
commit 8f6376e537
No known key found for this signature in database

View File

@ -9,6 +9,8 @@ import protectedSessionHolder from "../../services/protected_session_holder.js";
import BasicWidget from "../basic_widget.js";
import dialogService from "../../services/dialog.js";
import options from "../../services/options.js";
import type FNote from "../../entities/fnote.js";
import type { NoteType } from "../../entities/fnote.js";
const TPL = `
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -76,7 +78,43 @@ const TPL = `
</div>
</div>`;
interface RevisionItem {
noteId: string;
revisionId: string;
dateLastEdited: string;
contentLength: number;
type: NoteType;
title: string;
isProtected: boolean;
mime: string;
}
interface FullRevision {
content: string;
mime: string;
}
export default class RevisionsDialog extends BasicWidget {
private revisionItems: RevisionItem[];
private note: FNote | null;
private revisionId: string | null;
//@ts-ignore
private modal: bootstrap.Modal;
//@ts-ignore
private listDropdown: bootstrap.Dropdown;
private $list!: JQuery<HTMLElement>;
private $listDropdown!: JQuery<HTMLElement>;
private $content!: JQuery<HTMLElement>;
private $title!: JQuery<HTMLElement>;
private $titleButtons!: JQuery<HTMLElement>;
private $eraseAllRevisionsButton!: JQuery<HTMLElement>;
private $maximumRevisions!: JQuery<HTMLElement>;
private $snapshotInterval!: JQuery<HTMLElement>;
private $revisionSettingsButton!: JQuery<HTMLElement>;
constructor() {
super();
@ -87,10 +125,12 @@ export default class RevisionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
//@ts-ignore
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$list = this.$widget.find(".revision-list");
this.$listDropdown = this.$widget.find(".revision-list-dropdown");
//@ts-ignore
this.listDropdown = bootstrap.Dropdown.getOrCreateInstance(this.$listDropdown);
this.$content = this.$widget.find(".revision-content");
this.$title = this.$widget.find(".revision-title");
@ -110,7 +150,9 @@ export default class RevisionsDialog extends BasicWidget {
"keydown",
(e) => {
// Close the revision dialog when revision element is focused and ESC is pressed
if (e.key === "Escape" || e.target.classList.contains(["dropdown-item", "active"])) {
// TODO: Is this corret?
// @ts-ignore
if (e.key === "Escape" || ((e.target as HTMLElement)?.classList?.contains(["dropdown-item", "active"]))) {
this.modal.hide();
}
},
@ -122,6 +164,10 @@ export default class RevisionsDialog extends BasicWidget {
});
this.$eraseAllRevisionsButton.on("click", async () => {
if (!this.note) {
return;
}
const text = t("revisions.confirm_delete_all");
if (await dialogService.confirm(text)) {
@ -147,18 +193,22 @@ export default class RevisionsDialog extends BasicWidget {
}
async showRevisionsEvent({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
if (!noteId) {
return;
}
utils.openDialog(this.$widget);
await this.loadRevisions(noteId);
}
async loadRevisions(noteId) {
async loadRevisions(noteId: string) {
this.$list.empty();
this.$content.empty();
this.$titleButtons.empty();
this.note = appContext.tabManager.getActiveContextNote();
this.revisionItems = await server.get(`notes/${noteId}/revisions`);
this.revisionItems = await server.get<RevisionItem[]>(`notes/${noteId}/revisions`);
for (const item of this.revisionItems) {
this.$list.append(
@ -184,9 +234,9 @@ export default class RevisionsDialog extends BasicWidget {
// Show the footer of the revisions dialog
this.$snapshotInterval.text(t("revisions.snapshot_interval", { seconds: options.getInt("revisionSnapshotTimeInterval") }));
let revisionsNumberLimit = parseInt(this.note.getLabelValue("versioningLimit") ?? "");
let revisionsNumberLimit: number | string = parseInt(this.note?.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionsNumberLimit)) {
revisionsNumberLimit = parseInt(options.getInt("revisionSnapshotNumberLimit"));
revisionsNumberLimit = options.getInt("revisionSnapshotNumberLimit") ?? 0;
}
if (revisionsNumberLimit === -1) {
revisionsNumberLimit = "∞";
@ -198,6 +248,9 @@ export default class RevisionsDialog extends BasicWidget {
const revisionId = this.$list.find(".active").attr("data-revision-id");
const revisionItem = this.revisionItems.find((r) => r.revisionId === revisionId);
if (!revisionItem) {
return;
}
this.$title.html(revisionItem.title);
@ -206,7 +259,7 @@ export default class RevisionsDialog extends BasicWidget {
await this.renderContent(revisionItem);
}
renderContentButtons(revisionItem) {
renderContentButtons(revisionItem: RevisionItem) {
this.$titleButtons.empty();
const $restoreRevisionButton = $(`<button class="btn btn-sm" type="button">${t("revisions.restore_button")}</button>`);
@ -252,10 +305,10 @@ export default class RevisionsDialog extends BasicWidget {
}
}
async renderContent(revisionItem) {
async renderContent(revisionItem: RevisionItem) {
this.$content.empty();
const fullRevision = await server.get(`revisions/${revisionItem.revisionId}`);
const fullRevision = await server.get<FullRevision>(`revisions/${revisionItem.revisionId}`);
if (revisionItem.type === "text") {
this.$content.html(fullRevision.content);
@ -266,11 +319,15 @@ export default class RevisionsDialog extends BasicWidget {
renderMathInElement(this.$content[0], { trust: true });
}
} else if (revisionItem.type === "code") {
this.$content.html($("<pre>").text(fullRevision.content));
this.$content.html($("<pre>")
.text(fullRevision.content).html());
} else if (revisionItem.type === "image") {
if (fullRevision.mime === "image/svg+xml") {
let encodedSVG = encodeURIComponent(fullRevision.content); //Base64 of other format images may be embedded in svg
this.$content.html($("<img>").attr("src", `data:${fullRevision.mime};utf8,${encodedSVG}`).css("max-width", "100%").css("max-height", "100%"));
this.$content.html($("<img>")
.attr("src", `data:${fullRevision.mime};utf8,${encodedSVG}`)
.css("max-width", "100%")
.css("max-height", "100%").html());
} else {
this.$content.html(
$("<img>")
@ -278,13 +335,16 @@ export default class RevisionsDialog extends BasicWidget {
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%")
.css("max-height", "100%").html()
);
}
} else if (revisionItem.type === "file") {
const $table = $("<table cellpadding='10'>")
.append($("<tr>").append($("<th>").text(t("revisions.mime")), $("<td>").text(revisionItem.mime)))
.append($("<tr>").append($("<th>").text(t("revisions.file_size")), $("<td>").text(utils.formatSize(revisionItem.contentLength))));
.append($("<tr>")
.append(
$("<th>").text(t("revisions.mime")),
$("<td>").text(revisionItem.mime)))
.append($("<tr>").append($("<th>").text(t("revisions.file_size")), $("<td>").text(utils.formatSize(revisionItem.contentLength))));
if (fullRevision.content) {
$table.append(
@ -294,15 +354,23 @@ export default class RevisionsDialog extends BasicWidget {
);
}
this.$content.html($table);
this.$content.html($table.html());
} else if (["canvas", "mindMap"].includes(revisionItem.type)) {
const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>").attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`).css("max-width", "100%"));
this.$content.html(
$("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`)
.css("max-width", "100%")
.html());
} else if (revisionItem.type === "mermaid") {
const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>").attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`).css("max-width", "100%"));
this.$content.html(
$("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`)
.css("max-width", "100%")
.html());
this.$content.append($("<pre>").text(fullRevision.content));
} else {