scroll to and highlight

This commit is contained in:
Michael Freno
2025-12-26 19:59:36 -05:00
parent 4828677828
commit ee67d71f6a

View File

@@ -1021,6 +1021,9 @@ export default function TextEditor(props: TextEditorProps) {
const node = history()[index]; const node = history()[index];
if (!node) return; if (!node) return;
// Get current content before changing
const oldContent = instance.getHTML();
// Set content without triggering history capture // Set content without triggering history capture
instance.commands.setContent(node.content, { emitUpdate: false }); instance.commands.setContent(node.content, { emitUpdate: false });
@@ -1035,6 +1038,140 @@ export default function TextEditor(props: TextEditorProps) {
// Force UI update // Force UI update
setEditorState((prev) => prev + 1); setEditorState((prev) => prev + 1);
// Scroll to first change after a brief delay to allow content to render
setTimeout(() => {
scrollToFirstChange(instance, oldContent, node.content);
}, 100);
};
// Find and scroll to the first difference between old and new content
const scrollToFirstChange = (
editorInstance: any,
oldHTML: string,
newHTML: string
) => {
if (oldHTML === newHTML) return;
// Convert HTML to plain text for comparison
const oldText = editorInstance.state.doc.textContent;
const tempDiv = document.createElement("div");
tempDiv.innerHTML = oldHTML;
const oldTextContent = tempDiv.textContent || "";
// Find first character difference
let firstDiffPos = 0;
const minLength = Math.min(oldTextContent.length, oldText.length);
for (let i = 0; i < minLength; i++) {
if (oldTextContent[i] !== oldText[i]) {
firstDiffPos = i;
break;
}
}
// If no character diff found but lengths differ, use the shorter length
if (firstDiffPos === 0 && oldTextContent.length !== oldText.length) {
firstDiffPos = minLength;
}
// Convert text position to ProseMirror position
let currentTextPos = 0;
let pmPos = 0;
let found = false;
editorInstance.state.doc.descendants((node: any, pos: number) => {
if (found) return false;
if (node.isText) {
const nodeTextLength = node.text?.length || 0;
if (currentTextPos + nodeTextLength >= firstDiffPos) {
// Found the node containing the first change
pmPos = pos + (firstDiffPos - currentTextPos);
found = true;
return false;
}
currentTextPos += nodeTextLength;
}
});
if (pmPos > 0) {
// Scroll to the position
const coords = editorInstance.view.coordsAtPos(pmPos);
const editorElement = editorInstance.view.dom as HTMLElement;
const container = editorElement.closest(".overflow-y-auto");
if (container && coords) {
// Calculate scroll position (center the change in viewport)
const containerRect = container.getBoundingClientRect();
const scrollOffset =
coords.top - containerRect.top - containerRect.height / 3;
container.scrollBy({
top: scrollOffset,
behavior: "smooth"
});
}
// Also set cursor to that position
editorInstance.commands.focus();
editorInstance.commands.setTextSelection(pmPos);
// Flash highlight at the change position
flashHighlight(editorInstance, pmPos);
}
};
// Flash a highlight at a specific position
const flashHighlight = (editorInstance: any, pos: number) => {
const coords = editorInstance.view.coordsAtPos(pos);
if (!coords) return;
const editorElement = editorInstance.view.dom as HTMLElement;
const container = editorElement.closest(".overflow-y-auto");
if (!container) return;
// Create highlight element
const highlight = document.createElement("div");
highlight.style.position = "absolute";
highlight.style.left = `${coords.left}px`;
highlight.style.top = `${coords.top}px`;
highlight.style.width = "300px"; // Cover a good amount of text
highlight.style.height = "1.5em";
highlight.style.backgroundColor = "rgba(239, 68, 68, 0.3)"; // red-500 with opacity
highlight.style.pointerEvents = "none";
highlight.style.borderRadius = "4px";
highlight.style.zIndex = "1000";
highlight.style.transition = "opacity 0.6s ease-out";
highlight.style.opacity = "1";
// Position relative to the container
const containerRect = container.getBoundingClientRect();
const relativeTop = coords.top - containerRect.top + container.scrollTop;
const relativeLeft =
coords.left - containerRect.left + container.scrollLeft;
highlight.style.left = `${relativeLeft}px`;
highlight.style.top = `${relativeTop}px`;
// Append to container
const positionedContainer = container as HTMLElement;
if (
positionedContainer.style.position !== "relative" &&
positionedContainer.style.position !== "absolute"
) {
positionedContainer.style.position = "relative";
}
positionedContainer.appendChild(highlight);
// Fade out and remove
setTimeout(() => {
highlight.style.opacity = "0";
}, 100);
setTimeout(() => {
highlight.remove();
}, 700);
}; };
// Load history from database // Load history from database