@mikuexe/annotator-react 0.1.0 → 0.2.2
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.
- package/CHANGELOG.md +31 -0
- package/README.md +69 -27
- package/dist/index.cjs +668 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +666 -110
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -10,13 +10,18 @@ var MAX_TEXT_LENGTH = 240;
|
|
|
10
10
|
var MAX_HTML_LENGTH = 640;
|
|
11
11
|
var MAX_SELECTOR_DEPTH = 6;
|
|
12
12
|
async function captureElementAnnotation(element, note, id = createAnnotationId()) {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
note,
|
|
16
|
+
targets: [await captureAnnotationTarget(element)]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function captureAnnotationTarget(element) {
|
|
13
20
|
const elementInfo = await safeResolveElementInfo(element);
|
|
14
21
|
const source = normalizeSource(elementInfo?.source, elementInfo?.componentName);
|
|
15
22
|
const sourceStack = normalizeSourceStack(elementInfo?.stack, source);
|
|
16
23
|
const componentPath = getComponentPath(sourceStack, source);
|
|
17
24
|
return {
|
|
18
|
-
id,
|
|
19
|
-
note,
|
|
20
25
|
source,
|
|
21
26
|
sourceStack,
|
|
22
27
|
componentPath,
|
|
@@ -170,10 +175,19 @@ async function copyTextToClipboard(text) {
|
|
|
170
175
|
|
|
171
176
|
// src/format.ts
|
|
172
177
|
var TASK_FRAMING = "Please update the UI based on these source-linked annotations.";
|
|
173
|
-
function createAnnotationCollection(annotations) {
|
|
178
|
+
function createAnnotationCollection(annotations, page = getPageContext()) {
|
|
174
179
|
return {
|
|
175
180
|
annotations,
|
|
176
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
181
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
182
|
+
page
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function getPageContext(targetDocument) {
|
|
186
|
+
const activeDocument = targetDocument ?? (typeof document === "undefined" ? null : document);
|
|
187
|
+
const location = activeDocument?.location;
|
|
188
|
+
return {
|
|
189
|
+
domain: location?.hostname ?? "",
|
|
190
|
+
path: location?.pathname ?? ""
|
|
177
191
|
};
|
|
178
192
|
}
|
|
179
193
|
function formatAnnotationCollection(collection, output = "markdown") {
|
|
@@ -193,43 +207,28 @@ ${formatJson(collection)}
|
|
|
193
207
|
\`\`\``;
|
|
194
208
|
}
|
|
195
209
|
function formatMarkdown(collection) {
|
|
196
|
-
const lines = [
|
|
210
|
+
const lines = [
|
|
211
|
+
TASK_FRAMING,
|
|
212
|
+
"",
|
|
213
|
+
`Collected at: ${collection.createdAt}`,
|
|
214
|
+
`Domain: ${collection.page.domain}`,
|
|
215
|
+
`Path: ${collection.page.path}`,
|
|
216
|
+
""
|
|
217
|
+
];
|
|
197
218
|
if (collection.annotations.length === 0) {
|
|
198
219
|
lines.push("No annotations were collected.");
|
|
199
220
|
return lines.join("\n");
|
|
200
221
|
}
|
|
201
222
|
collection.annotations.forEach((annotation, index) => {
|
|
202
|
-
const source = formatSource(annotation);
|
|
203
|
-
const sourceStack = formatSourceStack(annotation);
|
|
204
|
-
const nearestComponent = annotation.source?.componentName;
|
|
205
|
-
const ownerPath = annotation.componentPath.join(" \u203A ");
|
|
206
223
|
lines.push(`## Annotation ${index + 1}`);
|
|
207
224
|
lines.push("");
|
|
208
225
|
lines.push(`ID: ${annotation.id}`);
|
|
209
226
|
lines.push(`Note: ${annotation.note || "(no note provided)"}`);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
if (ownerPath && ownerPath !== nearestComponent) {
|
|
217
|
-
lines.push(`React owner path: ${ownerPath}`);
|
|
218
|
-
}
|
|
219
|
-
if (sourceStack.length) {
|
|
220
|
-
lines.push("React source stack:");
|
|
221
|
-
sourceStack.forEach((frame) => lines.push(`- ${frame}`));
|
|
222
|
-
}
|
|
223
|
-
lines.push(`Element tag: ${annotation.element.tagName}`);
|
|
224
|
-
if (annotation.element.html) {
|
|
225
|
-
lines.push(`Element HTML: ${annotation.element.html}`);
|
|
226
|
-
}
|
|
227
|
-
if (annotation.element.text) {
|
|
228
|
-
lines.push(`Element text: ${annotation.element.text}`);
|
|
229
|
-
}
|
|
230
|
-
if (annotation.element.selector) {
|
|
231
|
-
lines.push(`Selector: ${annotation.element.selector}`);
|
|
232
|
-
}
|
|
227
|
+
annotation.targets.forEach((target, targetIndex) => {
|
|
228
|
+
const isSingleTarget = annotation.targets.length === 1;
|
|
229
|
+
const label = isSingleTarget ? "" : `Target ${targetIndex + 1} `;
|
|
230
|
+
appendTargetMarkdown(lines, target, label);
|
|
231
|
+
});
|
|
233
232
|
lines.push("");
|
|
234
233
|
});
|
|
235
234
|
return lines.join("\n").trimEnd();
|
|
@@ -237,8 +236,37 @@ function formatMarkdown(collection) {
|
|
|
237
236
|
function formatJson(collection) {
|
|
238
237
|
return JSON.stringify(collection, null, 2);
|
|
239
238
|
}
|
|
240
|
-
function
|
|
241
|
-
const source =
|
|
239
|
+
function appendTargetMarkdown(lines, target, label) {
|
|
240
|
+
const source = formatSource(target);
|
|
241
|
+
const sourceStack = formatSourceStack(target);
|
|
242
|
+
const nearestComponent = target.source?.componentName;
|
|
243
|
+
const ownerPath = target.componentPath.join(" \u203A ");
|
|
244
|
+
if (source) {
|
|
245
|
+
lines.push(`${label}Source: ${source}`);
|
|
246
|
+
}
|
|
247
|
+
if (nearestComponent) {
|
|
248
|
+
lines.push(`${label}Nearest React component: ${nearestComponent}`);
|
|
249
|
+
}
|
|
250
|
+
if (ownerPath && ownerPath !== nearestComponent) {
|
|
251
|
+
lines.push(`${label}React owner path: ${ownerPath}`);
|
|
252
|
+
}
|
|
253
|
+
if (sourceStack.length) {
|
|
254
|
+
lines.push(`${label}React source stack:`);
|
|
255
|
+
sourceStack.forEach((frame) => lines.push(`- ${frame}`));
|
|
256
|
+
}
|
|
257
|
+
lines.push(`${label}Element tag: ${target.element.tagName}`);
|
|
258
|
+
if (target.element.html) {
|
|
259
|
+
lines.push(`${label}Element HTML: ${target.element.html}`);
|
|
260
|
+
}
|
|
261
|
+
if (target.element.text) {
|
|
262
|
+
lines.push(`${label}Element text: ${target.element.text}`);
|
|
263
|
+
}
|
|
264
|
+
if (target.element.selector) {
|
|
265
|
+
lines.push(`${label}Selector: ${target.element.selector}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function formatSource(target) {
|
|
269
|
+
const source = target.source;
|
|
242
270
|
if (!source?.filePath) {
|
|
243
271
|
return "";
|
|
244
272
|
}
|
|
@@ -246,8 +274,8 @@ function formatSource(annotation) {
|
|
|
246
274
|
const column = source.columnNumber ? `:${source.columnNumber}` : "";
|
|
247
275
|
return `${source.filePath}${line}${column}`;
|
|
248
276
|
}
|
|
249
|
-
function formatSourceStack(
|
|
250
|
-
return
|
|
277
|
+
function formatSourceStack(target) {
|
|
278
|
+
return target.sourceStack.map((frame) => {
|
|
251
279
|
const location = formatSourceFrame(frame);
|
|
252
280
|
const component = frame.componentName ? ` (${frame.componentName})` : "";
|
|
253
281
|
return location ? `${location}${component}` : frame.componentName || "";
|
|
@@ -267,10 +295,22 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
267
295
|
var ROOT_ATTR = "data-mikuexe-annotator-root";
|
|
268
296
|
var DEFAULT_HOTKEY = "alt+a";
|
|
269
297
|
var DEFAULT_OUTPUT = "markdown";
|
|
298
|
+
var BLOCKED_INTERACTION_EVENTS = [
|
|
299
|
+
"pointerdown",
|
|
300
|
+
"pointerup",
|
|
301
|
+
"mousedown",
|
|
302
|
+
"mouseup",
|
|
303
|
+
"dblclick",
|
|
304
|
+
"auxclick",
|
|
305
|
+
"contextmenu",
|
|
306
|
+
"touchstart",
|
|
307
|
+
"touchend"
|
|
308
|
+
];
|
|
270
309
|
function SourceAnnotator({
|
|
271
310
|
enabled = true,
|
|
272
311
|
hotkey = DEFAULT_HOTKEY,
|
|
273
312
|
output = DEFAULT_OUTPUT,
|
|
313
|
+
target,
|
|
274
314
|
onCollect,
|
|
275
315
|
renderToaster = true
|
|
276
316
|
}) {
|
|
@@ -279,17 +319,41 @@ function SourceAnnotator({
|
|
|
279
319
|
const [selected, setSelected] = useState(null);
|
|
280
320
|
const [note, setNote] = useState("");
|
|
281
321
|
const [annotations, setAnnotations] = useState([]);
|
|
322
|
+
const [previewedAnnotation, setPreviewedAnnotation] = useState(null);
|
|
282
323
|
const [status, setStatus] = useState(null);
|
|
324
|
+
const [linkingAnnotationId, setLinkingAnnotationId] = useState(null);
|
|
283
325
|
const selectedRef = useRef(null);
|
|
326
|
+
const resolvedTarget = useResolvedTarget(target);
|
|
284
327
|
selectedRef.current = selected;
|
|
285
|
-
|
|
286
|
-
()
|
|
287
|
-
|
|
288
|
-
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
setHoverRect(null);
|
|
330
|
+
setSelected(null);
|
|
331
|
+
setAnnotations([]);
|
|
332
|
+
setPreviewedAnnotation(null);
|
|
333
|
+
setNote("");
|
|
334
|
+
setStatus(null);
|
|
335
|
+
setLinkingAnnotationId(null);
|
|
336
|
+
}, [resolvedTarget.document, resolvedTarget.frameElement]);
|
|
289
337
|
const refreshTrackedRects = useCallback(() => {
|
|
290
338
|
setHoverRect(null);
|
|
291
|
-
setSelected(
|
|
292
|
-
|
|
339
|
+
setSelected(
|
|
340
|
+
(current) => current ? {
|
|
341
|
+
...current,
|
|
342
|
+
targets: current.targets.map((targetEntry) => ({
|
|
343
|
+
...targetEntry,
|
|
344
|
+
rect: getRect(targetEntry.element, targetEntry.frameElement)
|
|
345
|
+
}))
|
|
346
|
+
} : current
|
|
347
|
+
);
|
|
348
|
+
setAnnotations(
|
|
349
|
+
(existing) => existing.map((annotation) => ({
|
|
350
|
+
...annotation,
|
|
351
|
+
targets: annotation.targets.map((targetEntry) => ({
|
|
352
|
+
...targetEntry,
|
|
353
|
+
rect: getRect(targetEntry.targetElement, targetEntry.frameElement)
|
|
354
|
+
}))
|
|
355
|
+
}))
|
|
356
|
+
);
|
|
293
357
|
}, []);
|
|
294
358
|
useEffect(() => {
|
|
295
359
|
if (!enabled) {
|
|
@@ -304,67 +368,229 @@ function SourceAnnotator({
|
|
|
304
368
|
event.preventDefault();
|
|
305
369
|
setIsAnnotating((current) => enabled && !current);
|
|
306
370
|
};
|
|
371
|
+
if (typeof document === "undefined") {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
307
374
|
document.addEventListener("keydown", onKeyDown);
|
|
308
|
-
|
|
309
|
-
|
|
375
|
+
if (resolvedTarget.document && resolvedTarget.document !== document) {
|
|
376
|
+
resolvedTarget.document.addEventListener("keydown", onKeyDown);
|
|
377
|
+
}
|
|
378
|
+
return () => {
|
|
379
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
380
|
+
if (resolvedTarget.document && resolvedTarget.document !== document) {
|
|
381
|
+
resolvedTarget.document.removeEventListener("keydown", onKeyDown);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
}, [enabled, hotkey, resolvedTarget.document]);
|
|
385
|
+
const handleElementSelection = useCallback(
|
|
386
|
+
async (eventTarget, frameElement, extendSelection) => {
|
|
387
|
+
if (linkingAnnotationId) {
|
|
388
|
+
const rect2 = getRect(eventTarget, frameElement);
|
|
389
|
+
setStatus("Resolving linked element\u2026");
|
|
390
|
+
try {
|
|
391
|
+
const targetData = await captureAnnotationTarget(eventTarget);
|
|
392
|
+
setAnnotations(
|
|
393
|
+
(existing) => existing.map((annotation) => {
|
|
394
|
+
if (annotation.id !== linkingAnnotationId) {
|
|
395
|
+
return annotation;
|
|
396
|
+
}
|
|
397
|
+
if (annotation.targets.some((targetEntry) => targetEntry.targetElement === eventTarget)) {
|
|
398
|
+
return annotation;
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
...annotation,
|
|
402
|
+
targets: [
|
|
403
|
+
...annotation.targets,
|
|
404
|
+
{
|
|
405
|
+
targetElement: eventTarget,
|
|
406
|
+
rect: rect2,
|
|
407
|
+
frameElement,
|
|
408
|
+
data: targetData
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
};
|
|
412
|
+
})
|
|
413
|
+
);
|
|
414
|
+
setLinkingAnnotationId(null);
|
|
415
|
+
setPreviewedAnnotation(null);
|
|
416
|
+
setStatus("Element linked to annotation.");
|
|
417
|
+
} catch {
|
|
418
|
+
setStatus("Element linked without source info.");
|
|
419
|
+
setAnnotations(
|
|
420
|
+
(existing) => existing.map((annotation) => {
|
|
421
|
+
if (annotation.id !== linkingAnnotationId) {
|
|
422
|
+
return annotation;
|
|
423
|
+
}
|
|
424
|
+
if (annotation.targets.some((targetEntry) => targetEntry.targetElement === eventTarget)) {
|
|
425
|
+
return annotation;
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
...annotation,
|
|
429
|
+
targets: [
|
|
430
|
+
...annotation.targets,
|
|
431
|
+
{
|
|
432
|
+
targetElement: eventTarget,
|
|
433
|
+
rect: rect2,
|
|
434
|
+
frameElement,
|
|
435
|
+
data: {
|
|
436
|
+
source: null,
|
|
437
|
+
sourceStack: [],
|
|
438
|
+
componentPath: [],
|
|
439
|
+
element: {
|
|
440
|
+
tagName: eventTarget.tagName.toLowerCase(),
|
|
441
|
+
text: eventTarget.textContent?.trim() ?? "",
|
|
442
|
+
html: "",
|
|
443
|
+
selector: ""
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
};
|
|
449
|
+
})
|
|
450
|
+
);
|
|
451
|
+
setLinkingAnnotationId(null);
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const rect = getRect(eventTarget, frameElement);
|
|
456
|
+
const shouldAppend = extendSelection && Boolean(selectedRef.current) && !selectedRef.current?.editingId;
|
|
457
|
+
setSelected((current) => {
|
|
458
|
+
if (shouldAppend && current) {
|
|
459
|
+
if (current.targets.some((targetEntry) => targetEntry.element === eventTarget)) {
|
|
460
|
+
return current;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
...current,
|
|
464
|
+
targets: [...current.targets, { element: eventTarget, rect, frameElement, target: null, loading: true }]
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
targets: [{ element: eventTarget, rect, frameElement, target: null, loading: true }]
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
if (!shouldAppend) {
|
|
472
|
+
setNote("");
|
|
473
|
+
}
|
|
474
|
+
setStatus("Resolving source\u2026");
|
|
475
|
+
try {
|
|
476
|
+
const targetData = await captureAnnotationTarget(eventTarget);
|
|
477
|
+
setSelected((current) => {
|
|
478
|
+
if (!current) {
|
|
479
|
+
return current;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
...current,
|
|
483
|
+
targets: current.targets.map(
|
|
484
|
+
(targetEntry) => targetEntry.element === eventTarget ? { ...targetEntry, target: targetData, loading: false } : targetEntry
|
|
485
|
+
)
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
setStatus(targetData.source ? "Source captured." : "Element captured without source info.");
|
|
489
|
+
} catch {
|
|
490
|
+
setSelected((current) => {
|
|
491
|
+
if (!current) {
|
|
492
|
+
return current;
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
...current,
|
|
496
|
+
targets: current.targets.map(
|
|
497
|
+
(targetEntry) => targetEntry.element === eventTarget ? { ...targetEntry, loading: false } : targetEntry
|
|
498
|
+
)
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
setStatus("Element captured without source info.");
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
[linkingAnnotationId]
|
|
505
|
+
);
|
|
310
506
|
useEffect(() => {
|
|
311
|
-
if (!enabled || !isAnnotating) {
|
|
507
|
+
if (!enabled || !isAnnotating || !resolvedTarget.document) {
|
|
312
508
|
setHoverRect(null);
|
|
313
509
|
return;
|
|
314
510
|
}
|
|
511
|
+
const activeDocument = resolvedTarget.document;
|
|
315
512
|
const onPointerOver = (event) => {
|
|
316
|
-
const
|
|
317
|
-
if (!
|
|
513
|
+
const eventTarget = getAnnotatableTarget(event.target, activeDocument);
|
|
514
|
+
if (!eventTarget) {
|
|
318
515
|
setHoverRect(null);
|
|
319
516
|
return;
|
|
320
517
|
}
|
|
321
|
-
setHoverRect(getRect(
|
|
518
|
+
setHoverRect(getRect(eventTarget, resolvedTarget.frameElement));
|
|
519
|
+
};
|
|
520
|
+
const suppressInteraction = (event) => {
|
|
521
|
+
const eventTarget = getAnnotatableTarget(event.target, activeDocument);
|
|
522
|
+
if (!eventTarget) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
event.preventDefault();
|
|
526
|
+
event.stopPropagation();
|
|
527
|
+
event.stopImmediatePropagation();
|
|
322
528
|
};
|
|
323
529
|
const onClick = (event) => {
|
|
324
|
-
const
|
|
325
|
-
if (!
|
|
530
|
+
const eventTarget = getAnnotatableTarget(event.target, activeDocument);
|
|
531
|
+
if (!eventTarget) {
|
|
326
532
|
return;
|
|
327
533
|
}
|
|
328
534
|
event.preventDefault();
|
|
329
535
|
event.stopPropagation();
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
setNote("");
|
|
333
|
-
setStatus("Resolving source\u2026");
|
|
334
|
-
captureElementAnnotation(target, "").then((annotation) => {
|
|
335
|
-
setSelected((current) => {
|
|
336
|
-
if (current?.element !== target) {
|
|
337
|
-
return current;
|
|
338
|
-
}
|
|
339
|
-
return { ...current, annotation, loading: false };
|
|
340
|
-
});
|
|
341
|
-
setStatus(annotation.source ? "Source captured." : "Element captured without source info.");
|
|
342
|
-
}).catch(() => {
|
|
343
|
-
setSelected((current) => current?.element === target ? { ...current, loading: false } : current);
|
|
344
|
-
setStatus("Element captured without source info.");
|
|
345
|
-
});
|
|
536
|
+
event.stopImmediatePropagation();
|
|
537
|
+
void handleElementSelection(eventTarget, resolvedTarget.frameElement, shouldExtendSelection(event));
|
|
346
538
|
};
|
|
347
|
-
|
|
348
|
-
|
|
539
|
+
const onFramePointerLeave = () => {
|
|
540
|
+
setHoverRect(null);
|
|
541
|
+
};
|
|
542
|
+
activeDocument.addEventListener("pointerover", onPointerOver, true);
|
|
543
|
+
activeDocument.addEventListener("click", onClick, true);
|
|
544
|
+
BLOCKED_INTERACTION_EVENTS.forEach((eventName) => activeDocument.addEventListener(eventName, suppressInteraction, true));
|
|
545
|
+
resolvedTarget.frameElement?.addEventListener("pointerleave", onFramePointerLeave);
|
|
349
546
|
return () => {
|
|
350
|
-
|
|
351
|
-
|
|
547
|
+
activeDocument.removeEventListener("pointerover", onPointerOver, true);
|
|
548
|
+
activeDocument.removeEventListener("click", onClick, true);
|
|
549
|
+
BLOCKED_INTERACTION_EVENTS.forEach((eventName) => activeDocument.removeEventListener(eventName, suppressInteraction, true));
|
|
550
|
+
resolvedTarget.frameElement?.removeEventListener("pointerleave", onFramePointerLeave);
|
|
352
551
|
};
|
|
353
|
-
}, [enabled, isAnnotating]);
|
|
552
|
+
}, [enabled, handleElementSelection, isAnnotating, resolvedTarget]);
|
|
354
553
|
useEffect(() => {
|
|
355
|
-
if (!enabled || !isAnnotating) {
|
|
554
|
+
if (!enabled || !isAnnotating || !resolvedTarget.document) {
|
|
356
555
|
return;
|
|
357
556
|
}
|
|
358
|
-
|
|
557
|
+
const activeDocument = resolvedTarget.document;
|
|
558
|
+
activeDocument.addEventListener("scroll", refreshTrackedRects, true);
|
|
559
|
+
if (typeof document !== "undefined" && activeDocument !== document) {
|
|
560
|
+
document.addEventListener("scroll", refreshTrackedRects, true);
|
|
561
|
+
}
|
|
359
562
|
window.addEventListener("resize", refreshTrackedRects);
|
|
360
563
|
return () => {
|
|
361
|
-
|
|
564
|
+
activeDocument.removeEventListener("scroll", refreshTrackedRects, true);
|
|
565
|
+
if (typeof document !== "undefined" && activeDocument !== document) {
|
|
566
|
+
document.removeEventListener("scroll", refreshTrackedRects, true);
|
|
567
|
+
}
|
|
362
568
|
window.removeEventListener("resize", refreshTrackedRects);
|
|
363
569
|
};
|
|
364
|
-
}, [enabled, isAnnotating, refreshTrackedRects]);
|
|
570
|
+
}, [enabled, isAnnotating, refreshTrackedRects, resolvedTarget.document]);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
if (!previewedAnnotation) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const onKeyDown = (event) => {
|
|
576
|
+
if (event.key === "Escape") {
|
|
577
|
+
setPreviewedAnnotation(null);
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
document.addEventListener("keydown", onKeyDown);
|
|
581
|
+
if (resolvedTarget.document && resolvedTarget.document !== document) {
|
|
582
|
+
resolvedTarget.document.addEventListener("keydown", onKeyDown);
|
|
583
|
+
}
|
|
584
|
+
return () => {
|
|
585
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
586
|
+
if (resolvedTarget.document && resolvedTarget.document !== document) {
|
|
587
|
+
resolvedTarget.document.removeEventListener("keydown", onKeyDown);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}, [previewedAnnotation, resolvedTarget.document]);
|
|
365
591
|
const addAnnotation = useCallback(async () => {
|
|
366
592
|
const current = selectedRef.current;
|
|
367
|
-
if (!current || current.loading) {
|
|
593
|
+
if (!current || current.targets.some((targetEntry) => targetEntry.loading)) {
|
|
368
594
|
return;
|
|
369
595
|
}
|
|
370
596
|
const trimmedNote = note.trim();
|
|
@@ -372,14 +598,67 @@ function SourceAnnotator({
|
|
|
372
598
|
setStatus("Add a note before saving this annotation.");
|
|
373
599
|
return;
|
|
374
600
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
601
|
+
const currentTargets = await Promise.all(
|
|
602
|
+
current.targets.map(async (targetEntry) => {
|
|
603
|
+
if (targetEntry.target) {
|
|
604
|
+
return targetEntry;
|
|
605
|
+
}
|
|
606
|
+
const annotation = await captureElementAnnotation(targetEntry.element, trimmedNote);
|
|
607
|
+
return { ...targetEntry, target: annotation.targets[0], loading: false };
|
|
608
|
+
})
|
|
609
|
+
);
|
|
610
|
+
const storedAnnotation = {
|
|
611
|
+
id: current.editingId ?? createAnnotationId(),
|
|
612
|
+
note: trimmedNote,
|
|
613
|
+
targets: currentTargets.map((targetEntry) => ({
|
|
614
|
+
targetElement: targetEntry.element,
|
|
615
|
+
rect: getRect(targetEntry.element, targetEntry.frameElement),
|
|
616
|
+
frameElement: targetEntry.frameElement,
|
|
617
|
+
data: targetEntry.target
|
|
618
|
+
}))
|
|
619
|
+
};
|
|
620
|
+
setAnnotations((existing) => {
|
|
621
|
+
if (!current.editingId) {
|
|
622
|
+
return [...existing, storedAnnotation];
|
|
623
|
+
}
|
|
624
|
+
return existing.map((item) => item.id === current.editingId ? storedAnnotation : item);
|
|
625
|
+
});
|
|
377
626
|
setSelected(null);
|
|
378
627
|
setNote("");
|
|
379
|
-
|
|
628
|
+
setPreviewedAnnotation(null);
|
|
629
|
+
setStatus(current.editingId ? "Annotation updated." : "Annotation saved.");
|
|
380
630
|
}, [note]);
|
|
631
|
+
const editAnnotation = useCallback((annotation) => {
|
|
632
|
+
setLinkingAnnotationId(null);
|
|
633
|
+
setSelected({
|
|
634
|
+
editingId: annotation.id,
|
|
635
|
+
targets: annotation.targets.map((targetEntry) => ({
|
|
636
|
+
element: targetEntry.targetElement,
|
|
637
|
+
rect: getRect(targetEntry.targetElement, targetEntry.frameElement),
|
|
638
|
+
frameElement: targetEntry.frameElement,
|
|
639
|
+
target: targetEntry.data,
|
|
640
|
+
loading: false
|
|
641
|
+
}))
|
|
642
|
+
});
|
|
643
|
+
setNote(annotation.note);
|
|
644
|
+
setPreviewedAnnotation(null);
|
|
645
|
+
setStatus("Editing annotation.");
|
|
646
|
+
}, []);
|
|
647
|
+
const startLinkingAnnotation = useCallback((annotationId) => {
|
|
648
|
+
setSelected(null);
|
|
649
|
+
setPreviewedAnnotation(null);
|
|
650
|
+
setLinkingAnnotationId(annotationId);
|
|
651
|
+
setStatus("Click another element to link it to this annotation.");
|
|
652
|
+
}, []);
|
|
653
|
+
const deleteAnnotation = useCallback((annotationId) => {
|
|
654
|
+
setAnnotations((existing) => existing.filter((annotation) => annotation.id !== annotationId));
|
|
655
|
+
setSelected((current) => current?.editingId === annotationId ? null : current);
|
|
656
|
+
setPreviewedAnnotation((current) => current?.id === annotationId ? null : current);
|
|
657
|
+
setLinkingAnnotationId((current) => current === annotationId ? null : current);
|
|
658
|
+
setStatus("Annotation deleted.");
|
|
659
|
+
}, []);
|
|
381
660
|
const collect = useCallback(async () => {
|
|
382
|
-
const payload = createAnnotationCollection(annotations
|
|
661
|
+
const payload = createAnnotationCollection(stripStoredAnnotations(annotations), getPageContext(resolvedTarget.document));
|
|
383
662
|
const text = formatAnnotationCollection(payload, output);
|
|
384
663
|
try {
|
|
385
664
|
await copyTextToClipboard(text);
|
|
@@ -387,14 +666,17 @@ function SourceAnnotator({
|
|
|
387
666
|
setIsAnnotating(false);
|
|
388
667
|
setSelected(null);
|
|
389
668
|
setHoverRect(null);
|
|
669
|
+
setAnnotations([]);
|
|
670
|
+
setPreviewedAnnotation(null);
|
|
390
671
|
setNote("");
|
|
672
|
+
setStatus(null);
|
|
673
|
+
setLinkingAnnotationId(null);
|
|
391
674
|
toast.success("Annotations copied", { description: `${payload.annotations.length} copied to clipboard.` });
|
|
392
|
-
setStatus(`Copied ${payload.annotations.length} annotation${payload.annotations.length === 1 ? "" : "s"}.`);
|
|
393
675
|
} catch (error) {
|
|
394
676
|
toast.error("Copy failed", { description: error instanceof Error ? error.message : "Clipboard copy failed." });
|
|
395
677
|
setStatus(error instanceof Error ? error.message : "Clipboard copy failed.");
|
|
396
678
|
}
|
|
397
|
-
}, [annotations, onCollect, output]);
|
|
679
|
+
}, [annotations, onCollect, output, resolvedTarget.document]);
|
|
398
680
|
if (!enabled) {
|
|
399
681
|
return null;
|
|
400
682
|
}
|
|
@@ -412,11 +694,33 @@ function SourceAnnotator({
|
|
|
412
694
|
}
|
|
413
695
|
),
|
|
414
696
|
isAnnotating && hoverRect ? /* @__PURE__ */ jsx(Box, { rect: hoverRect, kind: "hover" }) : null,
|
|
415
|
-
selected
|
|
416
|
-
isAnnotating ? annotations.map(
|
|
417
|
-
|
|
697
|
+
selected?.targets.map((targetEntry, index) => /* @__PURE__ */ jsx(Box, { rect: targetEntry.rect, kind: "selected" }, index)),
|
|
698
|
+
isAnnotating ? annotations.map(
|
|
699
|
+
(annotation, index) => annotation.targets.map((targetEntry, targetIndex) => /* @__PURE__ */ jsx(
|
|
700
|
+
Pin,
|
|
701
|
+
{
|
|
702
|
+
annotation,
|
|
703
|
+
rect: targetEntry.rect,
|
|
704
|
+
index,
|
|
705
|
+
isPreviewed: previewedAnnotation?.id === annotation.id,
|
|
706
|
+
onPreview: setPreviewedAnnotation
|
|
707
|
+
},
|
|
708
|
+
`${annotation.id}:${targetIndex}`
|
|
709
|
+
))
|
|
710
|
+
) : null,
|
|
711
|
+
isAnnotating && previewedAnnotation ? /* @__PURE__ */ jsx(
|
|
712
|
+
AnnotationPreview,
|
|
713
|
+
{
|
|
714
|
+
annotation: previewedAnnotation,
|
|
715
|
+
index: annotations.findIndex((annotation) => annotation.id === previewedAnnotation.id),
|
|
716
|
+
onEdit: editAnnotation,
|
|
717
|
+
onDelete: deleteAnnotation,
|
|
718
|
+
onClose: () => setPreviewedAnnotation(null)
|
|
719
|
+
}
|
|
720
|
+
) : null,
|
|
721
|
+
selected?.targets.length ? /* @__PURE__ */ jsxs("div", { style: getPopoverStyle(selected.targets[selected.targets.length - 1].rect), role: "dialog", "aria-label": "Add source annotation", children: [
|
|
418
722
|
/* @__PURE__ */ jsx("div", { style: styles.popoverTitle, children: "Annotation" }),
|
|
419
|
-
/* @__PURE__ */ jsx("div", { style: styles.metaText, children:
|
|
723
|
+
/* @__PURE__ */ jsx("div", { style: styles.metaText, children: formatSelectedTargets(selected.targets) }),
|
|
420
724
|
/* @__PURE__ */ jsx(
|
|
421
725
|
"textarea",
|
|
422
726
|
{
|
|
@@ -430,17 +734,33 @@ function SourceAnnotator({
|
|
|
430
734
|
),
|
|
431
735
|
/* @__PURE__ */ jsxs("div", { style: styles.popoverActions, children: [
|
|
432
736
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setSelected(null), style: styles.secondaryButton, children: "Cancel" }),
|
|
433
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: addAnnotation, style: styles.primaryButton, disabled: selected.loading, children: "Save note" })
|
|
737
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: addAnnotation, style: styles.primaryButton, disabled: selected.targets.some((targetEntry) => targetEntry.loading), children: selected.editingId ? "Update note" : "Save note" })
|
|
434
738
|
] })
|
|
435
739
|
] }) : null,
|
|
436
740
|
isAnnotating ? /* @__PURE__ */ jsxs("section", { style: styles.panel, "aria-label": "Collected annotations", children: [
|
|
437
741
|
/* @__PURE__ */ jsxs("div", { style: styles.panelHeader, children: [
|
|
438
742
|
/* @__PURE__ */ jsx("strong", { children: "Annotations" }),
|
|
439
|
-
/* @__PURE__ */ jsx("span", { style: styles.badge, children:
|
|
743
|
+
/* @__PURE__ */ jsx("span", { style: styles.badge, children: annotations.length })
|
|
440
744
|
] }),
|
|
441
|
-
annotations.length ? /* @__PURE__ */ jsx("ol", { style: styles.annotationList, children: annotations.map((annotation) => /* @__PURE__ */ jsxs("li", { style: styles.annotationItem, children: [
|
|
442
|
-
/* @__PURE__ */
|
|
443
|
-
|
|
745
|
+
annotations.length ? /* @__PURE__ */ jsx("ol", { style: styles.annotationList, children: annotations.map((annotation, index) => /* @__PURE__ */ jsxs("li", { style: styles.annotationItem, children: [
|
|
746
|
+
/* @__PURE__ */ jsxs("div", { style: styles.annotationContent, children: [
|
|
747
|
+
/* @__PURE__ */ jsx("div", { style: styles.noteText, children: annotation.note }),
|
|
748
|
+
/* @__PURE__ */ jsx("div", { style: styles.metaText, children: formatStoredAnnotationSummary(annotation) })
|
|
749
|
+
] }),
|
|
750
|
+
/* @__PURE__ */ jsxs("div", { style: styles.annotationActions, children: [
|
|
751
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => startLinkingAnnotation(annotation.id), style: styles.linkButton, children: "Link element" }),
|
|
752
|
+
/* @__PURE__ */ jsx(
|
|
753
|
+
"button",
|
|
754
|
+
{
|
|
755
|
+
type: "button",
|
|
756
|
+
onClick: () => deleteAnnotation(annotation.id),
|
|
757
|
+
style: { ...styles.deleteButton, ...styles.iconButton },
|
|
758
|
+
"aria-label": `Delete annotation ${index + 1}`,
|
|
759
|
+
title: `Delete annotation ${index + 1}`,
|
|
760
|
+
children: "\u{1F5D1}"
|
|
761
|
+
}
|
|
762
|
+
)
|
|
763
|
+
] })
|
|
444
764
|
] }, annotation.id)) }) : /* @__PURE__ */ jsx("p", { style: styles.emptyText, children: "Hover an element, click it, then add a note." }),
|
|
445
765
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: collect, style: styles.collectButton, disabled: !annotations.length, children: "Collect" }),
|
|
446
766
|
status ? /* @__PURE__ */ jsx("div", { style: styles.status, children: status }) : null
|
|
@@ -451,6 +771,7 @@ function Box({ rect, kind }) {
|
|
|
451
771
|
return /* @__PURE__ */ jsx(
|
|
452
772
|
"div",
|
|
453
773
|
{
|
|
774
|
+
"data-mikuexe-annotator-box": kind,
|
|
454
775
|
style: {
|
|
455
776
|
...styles.box,
|
|
456
777
|
...kind === "selected" ? styles.selectedBox : styles.hoverBox,
|
|
@@ -462,37 +783,170 @@ function Box({ rect, kind }) {
|
|
|
462
783
|
}
|
|
463
784
|
);
|
|
464
785
|
}
|
|
465
|
-
function Pin({
|
|
786
|
+
function Pin({
|
|
787
|
+
annotation,
|
|
788
|
+
rect,
|
|
789
|
+
index,
|
|
790
|
+
isPreviewed,
|
|
791
|
+
onPreview
|
|
792
|
+
}) {
|
|
466
793
|
return /* @__PURE__ */ jsx(
|
|
467
|
-
"
|
|
794
|
+
"button",
|
|
468
795
|
{
|
|
469
|
-
|
|
796
|
+
type: "button",
|
|
797
|
+
style: { ...styles.pin, top: Math.max(8, rect.top - 10), left: Math.max(8, rect.left - 10) },
|
|
470
798
|
title: annotation.note,
|
|
799
|
+
"aria-label": `Show annotation ${index + 1}`,
|
|
800
|
+
"aria-haspopup": "dialog",
|
|
801
|
+
"aria-expanded": isPreviewed,
|
|
802
|
+
onClick: () => onPreview(annotation),
|
|
803
|
+
onMouseOver: () => onPreview(annotation),
|
|
804
|
+
onFocus: () => onPreview(annotation),
|
|
471
805
|
children: index + 1
|
|
472
806
|
}
|
|
473
807
|
);
|
|
474
808
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
809
|
+
function AnnotationPreview({
|
|
810
|
+
annotation,
|
|
811
|
+
index,
|
|
812
|
+
onEdit,
|
|
813
|
+
onDelete,
|
|
814
|
+
onClose
|
|
815
|
+
}) {
|
|
816
|
+
const displayIndex = index >= 0 ? index + 1 : 1;
|
|
817
|
+
return /* @__PURE__ */ jsxs(
|
|
818
|
+
"div",
|
|
819
|
+
{
|
|
820
|
+
role: "dialog",
|
|
821
|
+
"aria-label": `Annotation ${displayIndex}`,
|
|
822
|
+
style: getPreviewStyle(annotation.targets[0]?.rect ?? { top: 8, left: 8, width: 0, height: 0 }),
|
|
823
|
+
children: [
|
|
824
|
+
/* @__PURE__ */ jsxs("div", { style: styles.previewHeader, children: [
|
|
825
|
+
/* @__PURE__ */ jsxs("div", { style: styles.previewTitle, children: [
|
|
826
|
+
"Annotation ",
|
|
827
|
+
displayIndex
|
|
828
|
+
] }),
|
|
829
|
+
/* @__PURE__ */ jsxs("div", { style: styles.previewActions, children: [
|
|
830
|
+
/* @__PURE__ */ jsx(
|
|
831
|
+
"button",
|
|
832
|
+
{
|
|
833
|
+
type: "button",
|
|
834
|
+
onClick: onClose,
|
|
835
|
+
style: { ...styles.secondaryButton, ...styles.iconButton },
|
|
836
|
+
"aria-label": `Close annotation ${displayIndex}`,
|
|
837
|
+
title: `Close annotation ${displayIndex}`,
|
|
838
|
+
children: "\xD7"
|
|
839
|
+
}
|
|
840
|
+
),
|
|
841
|
+
/* @__PURE__ */ jsx(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
type: "button",
|
|
845
|
+
onClick: () => onEdit(annotation),
|
|
846
|
+
style: { ...styles.secondaryButton, ...styles.iconButton },
|
|
847
|
+
"aria-label": `Edit annotation ${displayIndex}`,
|
|
848
|
+
title: `Edit annotation ${displayIndex}`,
|
|
849
|
+
children: "\u270E"
|
|
850
|
+
}
|
|
851
|
+
),
|
|
852
|
+
/* @__PURE__ */ jsx(
|
|
853
|
+
"button",
|
|
854
|
+
{
|
|
855
|
+
type: "button",
|
|
856
|
+
onClick: () => onDelete(annotation.id),
|
|
857
|
+
style: { ...styles.deleteButton, ...styles.iconButton },
|
|
858
|
+
"aria-label": `Delete annotation ${displayIndex}`,
|
|
859
|
+
title: `Delete annotation ${displayIndex}`,
|
|
860
|
+
children: "\u{1F5D1}"
|
|
861
|
+
}
|
|
862
|
+
)
|
|
863
|
+
] })
|
|
864
|
+
] }),
|
|
865
|
+
/* @__PURE__ */ jsx("div", { style: styles.noteText, children: annotation.note }),
|
|
866
|
+
/* @__PURE__ */ jsx("div", { style: styles.metaText, children: formatStoredAnnotationSummary(annotation) })
|
|
867
|
+
]
|
|
868
|
+
}
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
function stripStoredAnnotations(annotations) {
|
|
872
|
+
return annotations.map((annotation) => ({
|
|
873
|
+
id: annotation.id,
|
|
874
|
+
note: annotation.note,
|
|
875
|
+
targets: annotation.targets.map((targetEntry) => targetEntry.data)
|
|
876
|
+
}));
|
|
877
|
+
}
|
|
878
|
+
function getAnnotatableTarget(target, ownerDocument) {
|
|
879
|
+
if (!isElement(target, ownerDocument)) {
|
|
477
880
|
return null;
|
|
478
881
|
}
|
|
479
882
|
if (target.closest(`[${ROOT_ATTR}]`)) {
|
|
480
883
|
return null;
|
|
481
884
|
}
|
|
482
|
-
if (target ===
|
|
885
|
+
if (target === ownerDocument.body || target === ownerDocument.documentElement) {
|
|
483
886
|
return null;
|
|
484
887
|
}
|
|
485
888
|
return target;
|
|
486
889
|
}
|
|
487
|
-
function getRect(element) {
|
|
890
|
+
function getRect(element, frameElement) {
|
|
488
891
|
const rect = element.getBoundingClientRect();
|
|
892
|
+
const frameRect = frameElement?.getBoundingClientRect();
|
|
489
893
|
return {
|
|
490
|
-
top: rect.top,
|
|
491
|
-
left: rect.left,
|
|
894
|
+
top: rect.top + (frameRect?.top ?? 0),
|
|
895
|
+
left: rect.left + (frameRect?.left ?? 0),
|
|
492
896
|
width: rect.width,
|
|
493
897
|
height: rect.height
|
|
494
898
|
};
|
|
495
899
|
}
|
|
900
|
+
function useResolvedTarget(target) {
|
|
901
|
+
const [navigationVersion, setNavigationVersion] = useState(0);
|
|
902
|
+
const resolvedTarget = useMemo(() => resolveTarget(target), [target, navigationVersion]);
|
|
903
|
+
const currentDocumentRef = useRef(resolvedTarget.document);
|
|
904
|
+
currentDocumentRef.current = resolvedTarget.document;
|
|
905
|
+
useEffect(() => {
|
|
906
|
+
if (typeof HTMLIFrameElement !== "undefined" && target instanceof HTMLIFrameElement) {
|
|
907
|
+
const updateTarget = () => {
|
|
908
|
+
const nextTarget = resolveTarget(target);
|
|
909
|
+
if (nextTarget.document !== currentDocumentRef.current) {
|
|
910
|
+
setNavigationVersion((version) => version + 1);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
target.addEventListener("load", updateTarget);
|
|
914
|
+
return () => target.removeEventListener("load", updateTarget);
|
|
915
|
+
}
|
|
916
|
+
}, [target]);
|
|
917
|
+
return resolvedTarget;
|
|
918
|
+
}
|
|
919
|
+
function resolveTarget(target) {
|
|
920
|
+
const hostDocument = typeof document === "undefined" ? null : document;
|
|
921
|
+
if (typeof HTMLIFrameElement !== "undefined" && target instanceof HTMLIFrameElement) {
|
|
922
|
+
const frameDocument = target.contentDocument;
|
|
923
|
+
if (!frameDocument) {
|
|
924
|
+
console.warn("@mikuexe/annotator-react: SourceAnnotator target iframe must be same-origin; iframe contentDocument is not accessible.");
|
|
925
|
+
return {
|
|
926
|
+
document: null,
|
|
927
|
+
frameElement: target
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
document: frameDocument,
|
|
932
|
+
frameElement: target
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
if (typeof Document !== "undefined" && target instanceof Document) {
|
|
936
|
+
return {
|
|
937
|
+
document: target,
|
|
938
|
+
frameElement: null
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
document: hostDocument,
|
|
943
|
+
frameElement: null
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function isElement(target, ownerDocument) {
|
|
947
|
+
const elementConstructor = ownerDocument.defaultView?.Element ?? Element;
|
|
948
|
+
return target instanceof elementConstructor;
|
|
949
|
+
}
|
|
496
950
|
function getPopoverStyle(rect) {
|
|
497
951
|
const top = Math.min(window.innerHeight - 260, rect.top + rect.height + 8);
|
|
498
952
|
const left = Math.min(window.innerWidth - 340, Math.max(8, rect.left));
|
|
@@ -502,17 +956,45 @@ function getPopoverStyle(rect) {
|
|
|
502
956
|
left
|
|
503
957
|
};
|
|
504
958
|
}
|
|
505
|
-
function
|
|
506
|
-
const
|
|
507
|
-
|
|
959
|
+
function getPreviewStyle(rect) {
|
|
960
|
+
const top = Math.min(window.innerHeight - 120, rect.top + rect.height + 8);
|
|
961
|
+
const left = Math.min(window.innerWidth - 260, Math.max(8, rect.left));
|
|
962
|
+
return {
|
|
963
|
+
...styles.preview,
|
|
964
|
+
top: Math.max(8, top),
|
|
965
|
+
left
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function formatSelectedTargets(targets) {
|
|
969
|
+
if (!targets.length) {
|
|
970
|
+
return "Source unavailable";
|
|
971
|
+
}
|
|
972
|
+
if (targets.length === 1) {
|
|
973
|
+
return formatTargetSource(targets[0].target);
|
|
974
|
+
}
|
|
975
|
+
const resolvedCount = targets.filter((targetEntry) => targetEntry.target).length;
|
|
976
|
+
return `${targets.length} elements selected \xB7 ${resolvedCount}/${targets.length} resolved`;
|
|
977
|
+
}
|
|
978
|
+
function formatStoredAnnotationSummary(annotation) {
|
|
979
|
+
const firstTarget = annotation.targets[0]?.data;
|
|
980
|
+
const targetSummary = formatTargetSource(firstTarget);
|
|
981
|
+
const linkedSummary = annotation.targets.length > 1 ? ` \xB7 ${annotation.targets.length} linked elements` : "";
|
|
982
|
+
return `${targetSummary}${linkedSummary}`;
|
|
983
|
+
}
|
|
984
|
+
function formatTargetSource(target) {
|
|
985
|
+
const componentPath = target?.componentPath.length ? target.componentPath.join(" \u203A ") : null;
|
|
986
|
+
if (!target) {
|
|
508
987
|
return "Source unavailable";
|
|
509
988
|
}
|
|
510
|
-
if (!
|
|
511
|
-
return `${
|
|
989
|
+
if (!target.source?.filePath) {
|
|
990
|
+
return `${target.element.selector} \xB7 source unavailable`;
|
|
512
991
|
}
|
|
513
|
-
const line =
|
|
992
|
+
const line = target.source.lineNumber ? `:${target.source.lineNumber}` : "";
|
|
514
993
|
const component = componentPath ? ` \xB7 ${componentPath}` : "";
|
|
515
|
-
return `${
|
|
994
|
+
return `${target.source.filePath}${line}${component}`;
|
|
995
|
+
}
|
|
996
|
+
function shouldExtendSelection(event) {
|
|
997
|
+
return event.metaKey || event.ctrlKey;
|
|
516
998
|
}
|
|
517
999
|
function matchesHotkey(event, hotkey) {
|
|
518
1000
|
const parts = hotkey.toLowerCase().split("+").map((part) => part.trim()).filter(Boolean);
|
|
@@ -574,6 +1056,7 @@ var styles = {
|
|
|
574
1056
|
},
|
|
575
1057
|
popover: {
|
|
576
1058
|
position: "fixed",
|
|
1059
|
+
zIndex: 2,
|
|
577
1060
|
width: 320,
|
|
578
1061
|
pointerEvents: "auto",
|
|
579
1062
|
background: "#ffffff",
|
|
@@ -626,6 +1109,7 @@ var styles = {
|
|
|
626
1109
|
},
|
|
627
1110
|
panel: {
|
|
628
1111
|
position: "fixed",
|
|
1112
|
+
zIndex: 1,
|
|
629
1113
|
right: 16,
|
|
630
1114
|
bottom: 68,
|
|
631
1115
|
width: 300,
|
|
@@ -662,11 +1146,23 @@ var styles = {
|
|
|
662
1146
|
overflow: "auto"
|
|
663
1147
|
},
|
|
664
1148
|
annotationItem: {
|
|
1149
|
+
display: "grid",
|
|
1150
|
+
gap: 8,
|
|
1151
|
+
alignItems: "start",
|
|
665
1152
|
marginBottom: 8
|
|
666
1153
|
},
|
|
1154
|
+
annotationContent: {
|
|
1155
|
+
minWidth: 0
|
|
1156
|
+
},
|
|
1157
|
+
annotationActions: {
|
|
1158
|
+
display: "flex",
|
|
1159
|
+
gap: 8,
|
|
1160
|
+
flexWrap: "wrap"
|
|
1161
|
+
},
|
|
667
1162
|
noteText: {
|
|
668
1163
|
color: "#0f172a",
|
|
669
|
-
fontWeight: 650
|
|
1164
|
+
fontWeight: 650,
|
|
1165
|
+
overflowWrap: "anywhere"
|
|
670
1166
|
},
|
|
671
1167
|
emptyText: {
|
|
672
1168
|
color: "#64748b",
|
|
@@ -687,30 +1183,90 @@ var styles = {
|
|
|
687
1183
|
color: "#475569",
|
|
688
1184
|
fontSize: 12
|
|
689
1185
|
},
|
|
1186
|
+
linkButton: {
|
|
1187
|
+
border: "1px solid #bfdbfe",
|
|
1188
|
+
borderRadius: 999,
|
|
1189
|
+
background: "#eff6ff",
|
|
1190
|
+
color: "#1d4ed8",
|
|
1191
|
+
padding: "4px 7px",
|
|
1192
|
+
fontSize: 11,
|
|
1193
|
+
fontWeight: 750,
|
|
1194
|
+
cursor: "pointer"
|
|
1195
|
+
},
|
|
1196
|
+
deleteButton: {
|
|
1197
|
+
border: "1px solid #fecaca",
|
|
1198
|
+
borderRadius: 999,
|
|
1199
|
+
background: "#fff1f2",
|
|
1200
|
+
color: "#be123c",
|
|
1201
|
+
padding: "4px 7px",
|
|
1202
|
+
fontSize: 11,
|
|
1203
|
+
fontWeight: 750,
|
|
1204
|
+
cursor: "pointer"
|
|
1205
|
+
},
|
|
1206
|
+
preview: {
|
|
1207
|
+
position: "fixed",
|
|
1208
|
+
maxWidth: 240,
|
|
1209
|
+
pointerEvents: "auto",
|
|
1210
|
+
background: "#ffffff",
|
|
1211
|
+
border: "1px solid #cbd5e1",
|
|
1212
|
+
borderRadius: 10,
|
|
1213
|
+
padding: 10,
|
|
1214
|
+
boxShadow: "0 14px 35px rgba(15, 23, 42, 0.18)"
|
|
1215
|
+
},
|
|
1216
|
+
previewHeader: {
|
|
1217
|
+
display: "flex",
|
|
1218
|
+
alignItems: "center",
|
|
1219
|
+
justifyContent: "space-between",
|
|
1220
|
+
gap: 8,
|
|
1221
|
+
marginBottom: 6
|
|
1222
|
+
},
|
|
1223
|
+
previewTitle: {
|
|
1224
|
+
color: "#475569",
|
|
1225
|
+
fontSize: 12,
|
|
1226
|
+
fontWeight: 800
|
|
1227
|
+
},
|
|
1228
|
+
previewActions: {
|
|
1229
|
+
display: "inline-flex",
|
|
1230
|
+
gap: 6
|
|
1231
|
+
},
|
|
1232
|
+
iconButton: {
|
|
1233
|
+
width: 26,
|
|
1234
|
+
height: 26,
|
|
1235
|
+
display: "inline-flex",
|
|
1236
|
+
alignItems: "center",
|
|
1237
|
+
justifyContent: "center",
|
|
1238
|
+
padding: 0,
|
|
1239
|
+
lineHeight: 1
|
|
1240
|
+
},
|
|
690
1241
|
pin: {
|
|
691
1242
|
position: "fixed",
|
|
1243
|
+
border: 0,
|
|
692
1244
|
width: 20,
|
|
693
1245
|
height: 20,
|
|
694
1246
|
borderRadius: 999,
|
|
695
1247
|
display: "inline-flex",
|
|
696
1248
|
alignItems: "center",
|
|
697
1249
|
justifyContent: "center",
|
|
698
|
-
pointerEvents: "
|
|
1250
|
+
pointerEvents: "auto",
|
|
699
1251
|
background: "#f97316",
|
|
700
1252
|
color: "#ffffff",
|
|
701
1253
|
fontSize: 12,
|
|
702
1254
|
fontWeight: 800,
|
|
703
|
-
boxShadow: "0 8px 18px rgba(15, 23, 42, 0.2)"
|
|
1255
|
+
boxShadow: "0 8px 18px rgba(15, 23, 42, 0.2)",
|
|
1256
|
+
cursor: "pointer",
|
|
1257
|
+
padding: 0
|
|
704
1258
|
}
|
|
705
1259
|
};
|
|
706
1260
|
export {
|
|
707
1261
|
SourceAnnotator,
|
|
1262
|
+
captureAnnotationTarget,
|
|
708
1263
|
captureElementAnnotation,
|
|
709
1264
|
copyTextToClipboard,
|
|
710
1265
|
createAnnotationCollection,
|
|
711
1266
|
formatAnnotationCollection,
|
|
712
1267
|
formatMarkdown,
|
|
713
1268
|
getElementSelector,
|
|
1269
|
+
getPageContext,
|
|
714
1270
|
trimText
|
|
715
1271
|
};
|
|
716
1272
|
//# sourceMappingURL=index.js.map
|