agent-canvas 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -58,6 +58,7 @@ function getTextNodesInRange(range: Range): Text[] {
|
|
|
58
58
|
// Skip line numbers and existing marks
|
|
59
59
|
const parent = node.parentElement;
|
|
60
60
|
if (parent?.classList.contains("select-none")) continue;
|
|
61
|
+
if (parent?.closest("[data-annotation-id]")) continue;
|
|
61
62
|
|
|
62
63
|
if (range.intersectsNode(node)) {
|
|
63
64
|
nodes.push(node);
|
|
@@ -108,27 +109,18 @@ export function restoreMarks(
|
|
|
108
109
|
container: HTMLElement,
|
|
109
110
|
annotations: { id: string; snippet: string; filePath?: string; context?: { before: string; after: string; hierarchy: string[] } }[]
|
|
110
111
|
) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!toRestore.length) return;
|
|
112
|
+
for (const ann of annotations) {
|
|
113
|
+
// A canvas can be rendered more than once (overview, compare, hidden
|
|
114
|
+
// source panes), so restore decisions must be scoped to this container.
|
|
115
|
+
if (container.querySelector(`[data-annotation-id="${ann.id}"]`)) continue;
|
|
116
116
|
|
|
117
|
-
// Build full text content + mapping from text offset to (textNode, offsetInNode)
|
|
118
|
-
const textNodes: { node: Text; start: number }[] = [];
|
|
119
|
-
let fullText = "";
|
|
120
|
-
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
|
|
121
|
-
let tn: Text | null;
|
|
122
|
-
while ((tn = walker.nextNode() as Text | null)) {
|
|
123
|
-
if (tn.parentElement?.classList.contains("select-none")) continue;
|
|
124
|
-
textNodes.push({ node: tn, start: fullText.length });
|
|
125
|
-
fullText += tn.textContent || "";
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
for (const ann of toRestore) {
|
|
129
117
|
const snippet = ann.snippet;
|
|
130
118
|
if (!snippet) continue;
|
|
131
119
|
|
|
120
|
+
// Rebuild after every inserted mark. Wrapping text splits and moves text
|
|
121
|
+
// nodes, so a cached offset map can point at stale DOM after the first hit.
|
|
122
|
+
const { fullText, textNodes } = buildTextIndex(container);
|
|
123
|
+
|
|
132
124
|
// Find all occurrences of the snippet in the full text
|
|
133
125
|
const occurrences: number[] = [];
|
|
134
126
|
let searchFrom = 0;
|
|
@@ -181,6 +173,24 @@ export function restoreMarks(
|
|
|
181
173
|
}
|
|
182
174
|
}
|
|
183
175
|
|
|
176
|
+
function buildTextIndex(container: HTMLElement): {
|
|
177
|
+
fullText: string;
|
|
178
|
+
textNodes: { node: Text; start: number }[];
|
|
179
|
+
} {
|
|
180
|
+
const textNodes: { node: Text; start: number }[] = [];
|
|
181
|
+
let fullText = "";
|
|
182
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
|
|
183
|
+
let tn: Text | null;
|
|
184
|
+
while ((tn = walker.nextNode() as Text | null)) {
|
|
185
|
+
const parent = tn.parentElement;
|
|
186
|
+
if (parent?.classList.contains("select-none")) continue;
|
|
187
|
+
if (parent?.closest("[data-annotation-id]")) continue;
|
|
188
|
+
textNodes.push({ node: tn, start: fullText.length });
|
|
189
|
+
fullText += tn.textContent || "";
|
|
190
|
+
}
|
|
191
|
+
return { fullText, textNodes };
|
|
192
|
+
}
|
|
193
|
+
|
|
184
194
|
function offsetToNode(
|
|
185
195
|
textNodes: { node: Text; start: number }[],
|
|
186
196
|
offset: number
|