mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 11:09:05 +01:00
feat: show source diff between note and revision
This commit is contained in:
parent
1c451fb98a
commit
c60c738c7e
@ -2376,3 +2376,13 @@ footer.webview-footer button {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.revision-diff-added {
|
||||||
|
background: rgba(100, 200, 100, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.revision-diff-removed {
|
||||||
|
background: rgba(255, 100, 100, 0.5);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
@ -263,10 +263,10 @@
|
|||||||
"confirm_delete_all": "Do you want to delete all revisions of this note?",
|
"confirm_delete_all": "Do you want to delete all revisions of this note?",
|
||||||
"no_revisions": "No revisions for this note yet...",
|
"no_revisions": "No revisions for this note yet...",
|
||||||
"restore_button": "Restore",
|
"restore_button": "Restore",
|
||||||
"diff_button": "Diff",
|
"diff_on": "Show diff",
|
||||||
"content_button": "Content",
|
"diff_off": "Show content",
|
||||||
"diff_button_title": "Show note source diff",
|
"diff_on_hint": "Click to show note source diff",
|
||||||
"content_button_title": "Show revision content",
|
"diff_off_hint": "Click to show note content",
|
||||||
"diff_not_available": "Diff isn't available.",
|
"diff_not_available": "Diff isn't available.",
|
||||||
"confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.",
|
"confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.",
|
||||||
"delete_button": "Delete",
|
"delete_button": "Delete",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { t } from "../../services/i18n";
|
|||||||
import server from "../../services/server";
|
import server from "../../services/server";
|
||||||
import toast from "../../services/toast";
|
import toast from "../../services/toast";
|
||||||
import Button from "../react/Button";
|
import Button from "../react/Button";
|
||||||
|
import FormToggle from "../react/FormToggle";
|
||||||
import Modal from "../react/Modal";
|
import Modal from "../react/Modal";
|
||||||
import FormList, { FormListItem } from "../react/FormList";
|
import FormList, { FormListItem } from "../react/FormList";
|
||||||
import utils from "../../services/utils";
|
import utils from "../../services/utils";
|
||||||
@ -59,17 +60,36 @@ export default function RevisionsDialog() {
|
|||||||
helpPageId="vZWERwf8U3nx"
|
helpPageId="vZWERwf8U3nx"
|
||||||
bodyStyle={{ display: "flex", height: "80vh" }}
|
bodyStyle={{ display: "flex", height: "80vh" }}
|
||||||
header={
|
header={
|
||||||
(!!revisions?.length && <Button text={t("revisions.delete_all_revisions")} size="small" style={{ padding: "0 10px" }}
|
!!revisions?.length && (
|
||||||
onClick={async () => {
|
<>
|
||||||
const text = t("revisions.confirm_delete_all");
|
{["text", "code", "mermaid"].includes(currentRevision?.type ?? "") && (
|
||||||
|
<FormToggle
|
||||||
|
currentValue={showDiff}
|
||||||
|
onChange={(newValue) => setShowDiff(newValue)}
|
||||||
|
switchOnName={t("revisions.diff_on")}
|
||||||
|
switchOffName={t("revisions.diff_off")}
|
||||||
|
switchOnTooltip={t("revisions.diff_on_hint")}
|
||||||
|
switchOffTooltip={t("revisions.diff_off_hint")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
text={t("revisions.delete_all_revisions")}
|
||||||
|
size="small"
|
||||||
|
style={{ padding: "0 10px" }}
|
||||||
|
onClick={async () => {
|
||||||
|
const text = t("revisions.confirm_delete_all");
|
||||||
|
|
||||||
if (note && await dialog.confirm(text)) {
|
if (note && await dialog.confirm(text)) {
|
||||||
await server.remove(`notes/${note.noteId}/revisions`);
|
await server.remove(`notes/${note.noteId}/revisions`);
|
||||||
setRevisions([]);
|
setRevisions([]);
|
||||||
setCurrentRevision(undefined);
|
setCurrentRevision(undefined);
|
||||||
toast.showMessage(t("revisions.revisions_deleted"));
|
toast.showMessage(t("revisions.revisions_deleted"));
|
||||||
}
|
}
|
||||||
}}/>)
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
footer={<RevisionFooter note={note} />}
|
footer={<RevisionFooter note={note} />}
|
||||||
footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
|
footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
|
||||||
@ -104,9 +124,8 @@ export default function RevisionsDialog() {
|
|||||||
<RevisionPreview
|
<RevisionPreview
|
||||||
noteContent={noteContent}
|
noteContent={noteContent}
|
||||||
revisionItem={currentRevision}
|
revisionItem={currentRevision}
|
||||||
setShown={setShown}
|
|
||||||
showDiff={showDiff}
|
showDiff={showDiff}
|
||||||
setShowDiff={setShowDiff}
|
setShown={setShown}
|
||||||
onRevisionDeleted={() => {
|
onRevisionDeleted={() => {
|
||||||
setRefreshCounter(c => c + 1);
|
setRefreshCounter(c => c + 1);
|
||||||
setCurrentRevision(undefined);
|
setCurrentRevision(undefined);
|
||||||
@ -131,12 +150,11 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
|
|||||||
</FormList>);
|
</FormList>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RevisionPreview({noteContent, revisionItem, setShown, showDiff, setShowDiff, onRevisionDeleted }: {
|
function RevisionPreview({noteContent, revisionItem, showDiff, setShown, onRevisionDeleted }: {
|
||||||
noteContent?: string,
|
noteContent?: string,
|
||||||
revisionItem?: RevisionItem,
|
revisionItem?: RevisionItem,
|
||||||
setShown: Dispatch<StateUpdater<boolean>>,
|
|
||||||
showDiff: boolean,
|
showDiff: boolean,
|
||||||
setShowDiff: Dispatch<StateUpdater<boolean>>,
|
setShown: Dispatch<StateUpdater<boolean>>,
|
||||||
onRevisionDeleted?: () => void
|
onRevisionDeleted?: () => void
|
||||||
}) {
|
}) {
|
||||||
const [ fullRevision, setFullRevision ] = useState<RevisionPojo>();
|
const [ fullRevision, setFullRevision ] = useState<RevisionPojo>();
|
||||||
@ -156,17 +174,6 @@ function RevisionPreview({noteContent, revisionItem, setShown, showDiff, setShow
|
|||||||
{(revisionItem && <div className="revision-title-buttons">
|
{(revisionItem && <div className="revision-title-buttons">
|
||||||
{(!revisionItem.isProtected || protected_session_holder.isProtectedSessionAvailable()) &&
|
{(!revisionItem.isProtected || protected_session_holder.isProtectedSessionAvailable()) &&
|
||||||
<>
|
<>
|
||||||
{["text", "code", "mermaid"].includes(revisionItem.type) && (
|
|
||||||
<Button
|
|
||||||
icon={showDiff ? "bx bx-detail" : "bx bx-outline"}
|
|
||||||
text={showDiff ? t("revisions.content_button") : t("revisions.diff_button")}
|
|
||||||
title={showDiff ? t("revisions.content_button_title") : t("revisions.diff_button_title")}
|
|
||||||
onClick={async () => {
|
|
||||||
setShowDiff(!showDiff);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon="bx bx-history"
|
icon="bx bx-history"
|
||||||
text={t("revisions.restore_button")}
|
text={t("revisions.restore_button")}
|
||||||
@ -294,32 +301,46 @@ function RevisionContentText({ content }: { content: string | Buffer<ArrayBuffer
|
|||||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function RevisionContentDiff({ noteContent, itemContent, itemType }: { noteContent?: string, itemContent: string | Buffer<ArrayBufferLike> | undefined, itemType: string }) {
|
function RevisionContentDiff({ noteContent, itemContent, itemType }: {
|
||||||
let diffHtml: string;
|
noteContent?: string,
|
||||||
|
itemContent: string | Buffer<ArrayBufferLike> | undefined,
|
||||||
|
itemType: string
|
||||||
|
}) {
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
if (noteContent && typeof itemContent === "string") {
|
useEffect(() => {
|
||||||
if (itemType === "text") {
|
if (!noteContent || typeof itemContent !== "string") {
|
||||||
noteContent = utils.formatHtml(noteContent);
|
if (contentRef.current) {
|
||||||
itemContent = utils.formatHtml(itemContent);
|
contentRef.current.textContent = t("revisions.diff_not_available");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const diff = diffWords(noteContent, itemContent);
|
|
||||||
diffHtml = diff.map(part => {
|
let processedNoteContent = noteContent;
|
||||||
|
let processedItemContent = itemContent;
|
||||||
|
|
||||||
|
if (itemType === "text") {
|
||||||
|
processedNoteContent = utils.formatHtml(noteContent);
|
||||||
|
processedItemContent = utils.formatHtml(itemContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = diffWords(processedNoteContent, processedItemContent);
|
||||||
|
const diffHtml = diff.map(part => {
|
||||||
if (part.added) {
|
if (part.added) {
|
||||||
return `<span style="background:rgba(100, 200, 100, 0.5)">${utils.escapeHtml(part.value)}</span>`;
|
return `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
|
||||||
} else if (part.removed) {
|
} else if (part.removed) {
|
||||||
return `<span style="background:rgba(255, 100, 100, 0.5);text-decoration:line-through;">${utils.escapeHtml(part.value)}</span>`;
|
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
|
||||||
} else {
|
} else {
|
||||||
return utils.escapeHtml(part.value);
|
return utils.escapeHtml(part.value);
|
||||||
}
|
}
|
||||||
}).join("");
|
}).join("");
|
||||||
} else {
|
|
||||||
return <>{t("revisions.diff_not_available")}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
if (contentRef.current) {
|
||||||
<div className="ck-content" style={{ whiteSpace: "pre-wrap" }}
|
contentRef.current.innerHTML = diffHtml;
|
||||||
dangerouslySetInnerHTML={{ __html: diffHtml }}></div>
|
}
|
||||||
);
|
}, [noteContent, itemContent, itemType]);
|
||||||
|
|
||||||
|
return <div ref={contentRef} className="ck-content" style={{ whiteSpace: "pre-wrap" }}></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RevisionFooter({ note }: { note?: FNote }) {
|
function RevisionFooter({ note }: { note?: FNote }) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user