This commit is contained in:
Philipinho
2026-01-31 21:54:22 +00:00
parent 7d7decb459
commit 70124475ab
3 changed files with 90 additions and 12 deletions
@@ -32,3 +32,19 @@
border-radius: rem(2px); border-radius: rem(2px);
padding: 0 rem(2px); padding: 0 rem(2px);
} }
:global(.history-diff-node-added) {
outline: rem(2px) solid light-dark(var(--mantine-color-green-5), var(--mantine-color-green-7));
outline-offset: rem(2px);
border-radius: rem(4px);
}
:global(.history-diff-node-deleted) {
display: inline-block;
background: light-dark(var(--mantine-color-red-0), rgba(255, 0, 0, 0.1));
border: rem(1px) dashed light-dark(var(--mantine-color-red-4), var(--mantine-color-red-6));
border-radius: rem(4px);
padding: rem(4px) rem(8px);
color: light-dark(var(--mantine-color-red-7), var(--mantine-color-red-4));
font-size: rem(12px);
}
@@ -64,27 +64,82 @@ export function HistoryEditor({
editor.commands.setContent(content); editor.commands.setContent(content);
const specialNodeTypes = new Set([
"image",
"attachment",
"video",
"excalidraw",
"drawio",
"mermaid",
"mathBlock",
"mathInline",
"table",
"details",
"callout",
]);
const decorations: Decoration[] = []; const decorations: Decoration[] = [];
for (const change of changes) { for (const change of changes) {
if (change.toB > change.fromB) { if (change.toB > change.fromB) {
decorations.push( let foundSpecialNode: { node: Node; pos: number } | null = null;
Decoration.inline(change.fromB, change.toB, { docNew.nodesBetween(change.fromB, change.toB, (node, pos) => {
class: "history-diff-added", if (specialNodeTypes.has(node.type.name)) {
}), foundSpecialNode = { node, pos };
); return false;
}
});
if (foundSpecialNode) {
const nodeEnd =
foundSpecialNode.pos + foundSpecialNode.node.nodeSize;
decorations.push(
Decoration.node(foundSpecialNode.pos, nodeEnd, {
class: "history-diff-node-added",
}),
);
} else {
decorations.push(
Decoration.inline(change.fromB, change.toB, {
class: "history-diff-added",
}),
);
}
addedCount += 1; addedCount += 1;
} }
if (change.toA > change.fromA) { if (change.toA > change.fromA) {
const deletedText = docOld.textBetween(change.fromA, change.toA, ""); let foundDeletedNode: { node: Node; pos: number } | null = null;
if (deletedText) { docOld.nodesBetween(change.fromA, change.toA, (node, pos) => {
if (specialNodeTypes.has(node.type.name)) {
foundDeletedNode = { node, pos };
return false;
}
});
if (foundDeletedNode) {
decorations.push( decorations.push(
Decoration.widget(change.fromB, () => { Decoration.widget(change.fromB, () => {
const span = document.createElement("span"); const span = document.createElement("span");
span.className = "history-diff-deleted"; span.className = "history-diff-node-deleted";
span.textContent = deletedText; span.textContent = `[${foundDeletedNode!.node.type.name} removed]`;
return span; return span;
}), }),
); );
} else {
const deletedText = docOld.textBetween(
change.fromA,
change.toA,
"",
);
if (deletedText) {
decorations.push(
Decoration.widget(change.fromB, () => {
const span = document.createElement("span");
span.className = "history-diff-deleted";
span.textContent = deletedText;
return span;
}),
);
}
} }
deletedCount += 1; deletedCount += 1;
} }
@@ -104,7 +159,8 @@ export function HistoryEditor({
editor.setOptions({ editor.setOptions({
editorProps: { editorProps: {
...editor.options.editorProps, ...editor.options.editorProps,
decorations: () => (highlightChanges ? decorationSet : DecorationSet.empty), decorations: () =>
highlightChanges ? decorationSet : DecorationSet.empty,
}, },
}); });
}, [title, content, editor, previousContent, highlightChanges]); }, [title, content, editor, previousContent, highlightChanges]);
@@ -3,6 +3,7 @@ import { OnEvent } from '@nestjs/event-emitter';
import { PageHistoryRepo } from '@docmost/db/repos/page/page-history.repo'; import { PageHistoryRepo } from '@docmost/db/repos/page/page-history.repo';
import { Page } from '@docmost/db/types/entity.types'; import { Page } from '@docmost/db/types/entity.types';
import { isDeepStrictEqual } from 'node:util'; import { isDeepStrictEqual } from 'node:util';
import { EnvironmentService } from '../../integrations/environment/environment.service';
export class UpdatedPageEvent { export class UpdatedPageEvent {
page: Page; page: Page;
@@ -12,7 +13,10 @@ export class UpdatedPageEvent {
export class HistoryListener { export class HistoryListener {
private readonly logger = new Logger(HistoryListener.name); private readonly logger = new Logger(HistoryListener.name);
constructor(private readonly pageHistoryRepo: PageHistoryRepo) {} constructor(
private readonly pageHistoryRepo: PageHistoryRepo,
private readonly environmentService: EnvironmentService,
) {}
@OnEvent('collab.page.updated') @OnEvent('collab.page.updated')
async handleCreatePageHistory(event: UpdatedPageEvent) { async handleCreatePageHistory(event: UpdatedPageEvent) {
@@ -20,7 +24,9 @@ export class HistoryListener {
const pageCreationTime = new Date(page.createdAt).getTime(); const pageCreationTime = new Date(page.createdAt).getTime();
const currentTime = Date.now(); const currentTime = Date.now();
const FIVE_MINUTES = 5 * 60 * 1000; const FIVE_MINUTES = this.environmentService.isDevelopment()
? 30 * 1000
: 5 * 60 * 1000;
if (currentTime - pageCreationTime < FIVE_MINUTES) { if (currentTime - pageCreationTime < FIVE_MINUTES) {
return; return;