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