import { NoteType } from "@triliumnext/commons"; import appContext, { EventData } from "../../components/app_context"; import FNote from "../../entities/fnote"; import dialog, { closeActiveDialog, openDialog } from "../../services/dialog"; import froca from "../../services/froca"; import { t } from "../../services/i18n"; import server from "../../services/server"; import toast from "../../services/toast"; import Button from "../react/Button"; import Modal from "../react/Modal"; import ReactBasicWidget from "../react/ReactBasicWidget"; import FormList, { FormListItem } from "../react/FormList"; import utils from "../../services/utils"; import { useEffect, useRef, useState } from "preact/hooks"; import protected_session_holder from "../../services/protected_session_holder"; import { renderMathInElement } from "../../services/math"; import { CSSProperties } from "preact/compat"; import open from "../../services/open"; interface RevisionsDialogProps { note?: FNote; } interface RevisionItem { noteId: string; revisionId: string; dateLastEdited: string; contentLength: number; type: NoteType; title: string; isProtected: boolean; mime: string; } interface FullRevision { content: string; mime: string; } function RevisionsDialogComponent({ note }: RevisionsDialogProps) { const [ revisions, setRevisions ] = useState([]); const [ currentRevision, setCurrentRevision ] = useState(); if (note) { useEffect(() => { server.get(`notes/${note.noteId}/revisions`).then(setRevisions); }, [ note.noteId ]); } if (revisions?.length && !currentRevision) { setCurrentRevision(revisions[0]); } return (note && { const text = t("revisions.confirm_delete_all"); if (await dialog.confirm(text)) { await server.remove(`notes/${note.noteId}/revisions`); closeActiveDialog(); toast.showMessage(t("revisions.revisions_deleted")); } }}/>) } > { const correspondingRevision = revisions.find((r) => r.revisionId === revisionId); if (correspondingRevision) { setCurrentRevision(correspondingRevision); } }} />
) } function RevisionsList({ revisions, onSelect }: { revisions: RevisionItem[], onSelect: (val: string) => void }) { return ( {revisions.map((item) => {item.dateLastEdited.substr(0, 16)} ({utils.formatSize(item.contentLength)}) )} ); } function RevisionPreview({ revisionItem }: { revisionItem?: RevisionItem}) { const [ fullRevision, setFullRevision ] = useState(); const [ needsRefresh, setNeedsRefresh ] = useState(); useEffect(() => { setNeedsRefresh(false); if (revisionItem) { server.get(`revisions/${revisionItem.revisionId}`).then(setFullRevision); } else { setFullRevision(undefined); } }, [revisionItem, needsRefresh]); return ( <>

{revisionItem?.title ?? t("revisions.no_revisions")}

{(revisionItem &&
{(!revisionItem.isProtected || protected_session_holder.isProtectedSessionAvailable()) && <>
)}
); } const IMAGE_STYLE: CSSProperties = { maxWidth: "100%", maxHeight: "90%", objectFit: "contain" }; const CODE_STYLE: CSSProperties = { maxWidth: "100%", wordBreak: "break-all", whiteSpace: "pre-wrap" }; function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: RevisionItem, fullRevision?: FullRevision }) { if (!revisionItem || !fullRevision) { return <>; } const content = fullRevision.content; switch (revisionItem.type) { case "text": { const contentRef = useRef(); useEffect(() => { if (contentRef.current?.querySelector("span.math-tex")) { renderMathInElement(contentRef.current, { trust: true }); } }); return
} case "code": return
{content}
; case "image": switch (revisionItem.mime) { case "image/svg+xml": { //Base64 of other format images may be embedded in svg const encodedSVG = encodeURIComponent(content); return ; } default: { // the reason why we put this inline as base64 is that we do not want to let user copy this // 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 return } } case "file": return {fullRevision.content && }
{t("revisions.mime")} {revisionItem.mime}
{t("revisions.file_size")} {utils.formatSize(revisionItem.contentLength)}
{t("revisions.preview")}
{fullRevision.content}
; case "canvas": case "mindMap": case "mermaid": { const encodedTitle = encodeURIComponent(revisionItem.title); return ; } default: return <>{t("revisions.preview_not_available")} } } export default class RevisionsDialog extends ReactBasicWidget { private props: RevisionsDialogProps = {}; get component() { return } async showRevisionsEvent({ noteId }: EventData<"showRevisions">) { this.props = { note: await getNote(noteId) ?? undefined }; this.doRender(); openDialog(this.$widget); } } async function getNote(noteId?: string | null) { if (noteId) { return await froca.getNote(noteId); } else { return appContext.tabManager.getActiveContextNote(); } }