dsh-web-annotator 0.0.0-stage → 0.2.0-alpha.11

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/CONTRIBUTING.md +90 -0
  3. package/LICENSE +21 -0
  4. package/README.md +110 -2
  5. package/README_en.md +136 -0
  6. package/THIRD_PARTY_NOTICES.md +15 -0
  7. package/cordis.patch.yml +15 -0
  8. package/docs/AGENTS.md +16 -0
  9. package/docs/README.md +25 -0
  10. package/docs/architecture.md +170 -0
  11. package/docs/browser-integration.md +94 -0
  12. package/docs/distribution.md +66 -0
  13. package/docs/harness-browser-annotation.patch +1909 -0
  14. package/docs/images/annotation-expanded-dark.png +0 -0
  15. package/docs/images/annotation-expanded.png +0 -0
  16. package/docs/images/annotation-image.png +0 -0
  17. package/docs/images/annotation-message.png +0 -0
  18. package/docs/images/annotation-region.png +0 -0
  19. package/docs/marketplace/cslkkl__dsh-web-annotator.yml +7 -0
  20. package/docs/publishing.md +129 -0
  21. package/docs/roadmap.md +31 -0
  22. package/docs/verification.md +179 -0
  23. package/dsh-plugin.naming.json +27 -0
  24. package/examples/responsive-demo/AGENTS.md +12 -0
  25. package/examples/responsive-demo/README.md +33 -0
  26. package/examples/responsive-demo/index.html +13 -0
  27. package/examples/responsive-demo/src/main.tsx +110 -0
  28. package/examples/responsive-demo/src/style.css +344 -0
  29. package/examples/responsive-demo/vite.config.ts +9 -0
  30. package/lib/THIRD_PARTY_LICENSES.md +345 -0
  31. package/lib/bridge.js +890 -0
  32. package/lib/bridge.js.map +7 -0
  33. package/lib/client.js +1083 -0
  34. package/lib/client.js.map +7 -0
  35. package/lib/host/THIRD_PARTY_NOTICES.md +15 -0
  36. package/lib/host/browser-provider/LICENSE +21 -0
  37. package/lib/host/browser-provider/README.md +130 -0
  38. package/lib/host/browser-provider/lib/client.css +217 -0
  39. package/lib/host/browser-provider/lib/client.css.map +7 -0
  40. package/lib/host/browser-provider/lib/client.js +2360 -0
  41. package/lib/host/browser-provider/lib/client.js.map +7 -0
  42. package/lib/host/browser-provider/lib/index.js +7 -0
  43. package/lib/host/browser-provider/lib/index.js.map +7 -0
  44. package/lib/host/browser-provider/package.json +23 -0
  45. package/lib/host/bundle.json +27 -0
  46. package/lib/host/chat-provider/LICENSE +21 -0
  47. package/lib/host/chat-provider/README.md +186 -0
  48. package/lib/host/chat-provider/lib/client.css +1604 -0
  49. package/lib/host/chat-provider/lib/client.css.map +7 -0
  50. package/lib/host/chat-provider/lib/client.js +11074 -0
  51. package/lib/host/chat-provider/lib/client.js.map +7 -0
  52. package/lib/host/chat-provider/lib/index.js +911 -0
  53. package/lib/host/chat-provider/lib/index.js.map +7 -0
  54. package/lib/host/chat-provider/package.json +31 -0
  55. package/lib/host/layout-provider/LICENSE +21 -0
  56. package/lib/host/layout-provider/README.md +109 -0
  57. package/lib/host/layout-provider/lib/client.css +177 -0
  58. package/lib/host/layout-provider/lib/client.css.map +7 -0
  59. package/lib/host/layout-provider/lib/client.js +565 -0
  60. package/lib/host/layout-provider/lib/client.js.map +7 -0
  61. package/lib/host/layout-provider/lib/index.js +7 -0
  62. package/lib/host/layout-provider/lib/index.js.map +7 -0
  63. package/lib/host/layout-provider/package.json +25 -0
  64. package/lib/index.d.ts +2 -0
  65. package/lib/index.js +9 -0
  66. package/lib/index.js.map +7 -0
  67. package/lib/vite.d.ts +8 -0
  68. package/lib/vite.js +1254 -0
  69. package/lib/vite.js.map +7 -0
  70. package/licenses/deepseek-harness-MIT.txt +21 -0
  71. package/locale/AGENTS.md +11 -0
  72. package/locale/README.md +11 -0
  73. package/locale/en.json +6 -0
  74. package/locale/zh.json +6 -0
  75. package/package.json +154 -4
  76. package/screenshots.json +7 -0
package/lib/bridge.js ADDED
@@ -0,0 +1,890 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/browser/page-inspector.ts
4
+ function annotatePage(options) {
5
+ window.__DSH_PAGE_ANNOTATION_CANCEL__?.();
6
+ const doc = document;
7
+ const originalUrl = location.href;
8
+ const originalFocus = doc.activeElement;
9
+ const root = doc.createElement("div");
10
+ root.setAttribute("data-dsh-page-annotation", "");
11
+ root.style.cssText = "position:fixed;inset:0;z-index:2147483647;pointer-events:none;";
12
+ const shadow = root.attachShadow({ mode: "open" });
13
+ const style = doc.createElement("style");
14
+ style.textContent = `
15
+ :host{all:initial;color-scheme:${options.dark ? "dark" : "light"}}
16
+ *{box-sizing:border-box}
17
+ .outline{position:fixed;pointer-events:none;border:2px solid #2979ff;border-radius:5px;background:#2979ff0c}
18
+ .hint{position:fixed;top:12px;left:50%;transform:translateX(-50%);padding:8px 12px;border-radius:8px;background:#181a20;color:#fff;font:13px/20px system-ui;max-width:calc(100vw - 24px);pointer-events:none}
19
+ .editor,.options{position:fixed;pointer-events:auto;border:0;background:${options.dark ? "#292a2d" : "#fff"};color:${options.dark ? "#ededee" : "#303338"};box-shadow:0 0 0 .5px ${options.dark ? "#55565b" : "#dcdfe3"},0 2px 8px #00000014;font:14px/22px system-ui}
20
+ .editor{border-radius:26px;corner-shape:round;padding:7px 8px;width:min(384px,calc(100vw - 24px));overflow:hidden;max-height:calc(100vh - 24px);display:flex;flex-direction:column}
21
+ .editor.expanded{width:min(420px,calc(100vw - 24px));border-radius:24px;padding:0}
22
+ .expanded>.row{padding:12px 14px;flex:none}
23
+ .expanded>.row>.save{display:none}
24
+ .expanded textarea{max-height:min(66px,20vh)}
25
+ .editor:focus-within{box-shadow:0 0 0 .5px ${options.dark ? "#85868b" : "#bfc4cc"},0 2px 8px #00000014}
26
+ .row{display:flex;align-items:center;gap:10px}
27
+ button,select,textarea{font:inherit;color:inherit}
28
+ button{border:0;border-radius:8px;padding:6px 8px;background:transparent;cursor:pointer;flex:none}
29
+ button:disabled{opacity:.4;cursor:default}
30
+ button:hover:not(:disabled){background:${options.dark ? "#3b3c40" : "#f0f1f3"}}
31
+ button:focus-visible,select:focus-visible{outline:2px solid #2979ff;outline-offset:2px}
32
+ .options-toggle,.save{display:flex;align-items:center;justify-content:center;padding:0;width:30px;height:30px;border-radius:50%;corner-shape:round}
33
+ .options-toggle{color:${options.dark ? "#bdc0c5" : "#737880"}}
34
+ .save,.save:disabled{background:${options.dark ? "#797c82" : "#a2a5a9"};color:#fff;opacity:1}
35
+ .save:not(:disabled){background:${options.dark ? "#d2d3d6" : "#777c83"};color:${options.dark ? "#242529" : "#fff"}}
36
+ .save:hover:not(:disabled){background:${options.dark ? "#eeeff1" : "#60656d"}}
37
+ textarea{display:block;flex:1;width:100%;min-width:0;height:22px;max-height:110px;resize:none;border:0;background:transparent;padding:0;margin:0;outline:0;line-height:22px;overflow-y:auto}
38
+ textarea::placeholder{color:${options.dark ? "#aaaeb5" : "#888e96"};opacity:1}
39
+ .options{position:static;box-shadow:none;flex:1;min-height:0;overflow:auto;font-size:14px;line-height:22px;scrollbar-width:thin}
40
+ .target-row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 20px;background:${options.dark ? "#35363a" : "#f4f4f4"}}
41
+ .target{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:${options.dark ? "#c9ccd1" : "#656b73"}}
42
+ .target-controls{display:flex;gap:2px;flex:none}
43
+ .target-controls button{font-size:13px;padding:0;width:24px;height:24px}
44
+ .properties{padding:12px 20px;display:grid;gap:10px}
45
+ .property{display:grid;grid-template-columns:minmax(90px,1fr) minmax(0,180px);align-items:center;gap:12px;color:${options.dark ? "#c9ccd1" : "#6e747b"}}
46
+ .field{display:flex;align-items:center;gap:9px;border:.5px solid ${options.dark ? "#57595f" : "#dedfe2"};border-radius:13px;padding:7px 10px;min-width:0}
47
+ .field input{font:13px/20px ui-monospace,monospace;background:transparent;color:inherit;border:0;outline:0;width:100%;min-width:0}
48
+ .field:focus-within{border-color:${options.dark ? "#a2a5ac" : "#a5a9b1"}}
49
+ .swatch{width:22px;height:22px;border-radius:7px;flex:none;box-shadow:inset 0 0 0 .5px #8886}
50
+ .intent{display:flex;align-items:center;justify-content:space-between;padding:0 20px 12px;gap:8px;font-size:13px;color:${options.dark ? "#c9ccd1" : "#6e747b"}}
51
+ select{font:inherit;border:.5px solid ${options.dark ? "#57595f" : "#dedfe2"};background:transparent;border-radius:10px;padding:5px 8px;max-width:160px}
52
+ .footer{border-top:.5px solid ${options.dark ? "#44464b" : "#eee"};display:flex;align-items:center;justify-content:space-between;padding:10px 12px;flex:none}
53
+ .footer>.cancel{border:.5px solid ${options.dark ? "#55565b" : "#e5e6e9"};border-radius:20px;padding:6px 12px;font-size:13px}
54
+ .style-hint{font-size:12px;line-height:18px;margin:3px 0;color:${options.dark ? "#adb1b8" : "#8a9097"}}
55
+ .error{color:${options.dark ? "#ffb0a9" : "#b82920"};font-size:12px;padding:3px 40px 1px}
56
+ .hidden{display:none!important}
57
+ `;
58
+ const outline = doc.createElement("div");
59
+ outline.className = "outline hidden";
60
+ const hint = doc.createElement("div");
61
+ hint.className = "hint";
62
+ hint.textContent = options.copy.select;
63
+ const editor = doc.createElement("form");
64
+ editor.className = "editor hidden";
65
+ editor.setAttribute("aria-label", options.copy.placeholder);
66
+ const row = doc.createElement("div");
67
+ row.className = "row";
68
+ function icon(parts) {
69
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
70
+ for (const [name, value] of Object.entries({
71
+ width: "18",
72
+ height: "18",
73
+ viewBox: "0 0 16 16",
74
+ fill: "none",
75
+ stroke: "currentColor",
76
+ "stroke-width": "1.3",
77
+ "stroke-linecap": "round",
78
+ "stroke-linejoin": "round",
79
+ "aria-hidden": "true"
80
+ }))
81
+ svg.setAttribute(name, value);
82
+ for (const [tag, attributes] of parts) {
83
+ const node = doc.createElementNS("http://www.w3.org/2000/svg", tag);
84
+ for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
85
+ svg.append(node);
86
+ }
87
+ return svg;
88
+ }
89
+ const settings = doc.createElement("button");
90
+ settings.type = "button";
91
+ settings.className = "options-toggle";
92
+ settings.title = options.copy.settings;
93
+ settings.setAttribute("aria-label", options.copy.settings);
94
+ settings.setAttribute("aria-expanded", "false");
95
+ settings.setAttribute("aria-haspopup", "dialog");
96
+ settings.append(
97
+ icon([
98
+ ["path", { d: "M2.3 5h5.85M12.05 5h1.65" }],
99
+ ["circle", { cx: "9.95", cy: "5", r: "1.45" }],
100
+ ["path", { d: "M2.3 11h1.65M7.85 11h5.85" }],
101
+ ["circle", { cx: "5.75", cy: "11", r: "1.45" }]
102
+ ])
103
+ );
104
+ const optionsPanel = doc.createElement("section");
105
+ optionsPanel.className = "options hidden";
106
+ optionsPanel.setAttribute("role", "dialog");
107
+ optionsPanel.setAttribute("aria-label", options.copy.settings);
108
+ const intentRow = doc.createElement("div");
109
+ intentRow.className = "intent";
110
+ const intentLabel = doc.createElement("span");
111
+ intentLabel.textContent = options.copy.comment + " / " + options.copy.question;
112
+ const intent = doc.createElement("select");
113
+ intent.setAttribute("aria-label", options.copy.comment + " / " + options.copy.question);
114
+ for (const [value, label] of [
115
+ ["comment", options.copy.comment],
116
+ ["question", options.copy.question]
117
+ ]) {
118
+ const option = doc.createElement("option");
119
+ option.value = value;
120
+ option.textContent = label;
121
+ intent.append(option);
122
+ }
123
+ intent.value = options.intent;
124
+ const input = doc.createElement("textarea");
125
+ input.placeholder = options.copy.placeholder;
126
+ input.setAttribute("aria-label", options.copy.placeholder);
127
+ input.maxLength = 1e3;
128
+ const save = doc.createElement("button");
129
+ save.type = "submit";
130
+ save.className = "save";
131
+ save.disabled = true;
132
+ save.append(
133
+ icon([
134
+ [
135
+ "path",
136
+ { d: "M2.25 8.5L5.49732 11.7473C5.90519 12.1552 6.57263 12.1344 6.95426 11.7018L13.75 4" }
137
+ ]
138
+ ])
139
+ );
140
+ save.title = options.copy.save;
141
+ save.setAttribute("aria-label", options.copy.save);
142
+ row.append(settings, input, save);
143
+ const targetLabel = doc.createElement("span");
144
+ targetLabel.className = "target";
145
+ const controls = doc.createElement("div");
146
+ controls.className = "controls";
147
+ function control(label, glyph) {
148
+ const button = doc.createElement("button");
149
+ button.type = "button";
150
+ button.title = label;
151
+ button.setAttribute("aria-label", label);
152
+ button.textContent = glyph + " " + label;
153
+ controls.append(button);
154
+ return button;
155
+ }
156
+ const parent = control(options.copy.parent, "\u2191");
157
+ const child = control(options.copy.child, "\u2193");
158
+ const cancel = control(options.copy.cancel, "\xD7");
159
+ const error = doc.createElement("div");
160
+ error.className = "error hidden";
161
+ error.setAttribute("role", "alert");
162
+ intentRow.append(intentLabel, intent);
163
+ const targetRow = doc.createElement("div");
164
+ targetRow.className = "target-row";
165
+ controls.className = "target-controls";
166
+ cancel.remove();
167
+ parent.textContent = "\u2191";
168
+ child.textContent = "\u2193";
169
+ targetRow.append(targetLabel, controls);
170
+ const properties = doc.createElement("div");
171
+ properties.className = "properties";
172
+ const styleHint = doc.createElement("p");
173
+ styleHint.className = "style-hint";
174
+ const fields = /* @__PURE__ */ new Map();
175
+ for (const [property, label] of [
176
+ ["color", options.copy.color],
177
+ ["background-color", options.copy.background],
178
+ ["opacity", options.copy.opacity],
179
+ ["font-family", options.copy.font],
180
+ ["font-size", options.copy.fontSize],
181
+ ["font-weight", options.copy.fontWeight],
182
+ ["gap", options.copy.spacing]
183
+ ]) {
184
+ const item = doc.createElement("label");
185
+ item.className = "property";
186
+ const labelText = doc.createElement("span");
187
+ labelText.textContent = label;
188
+ const field = doc.createElement("div");
189
+ field.className = "field";
190
+ const value = doc.createElement("input");
191
+ value.setAttribute("aria-label", label);
192
+ value.dataset.property = property;
193
+ value.maxLength = 1e3;
194
+ let swatch;
195
+ if (property === "color" || property === "background-color") {
196
+ swatch = doc.createElement("span");
197
+ swatch.className = "swatch";
198
+ swatch.setAttribute("aria-hidden", "true");
199
+ field.append(swatch);
200
+ }
201
+ field.append(value);
202
+ item.append(labelText, field);
203
+ properties.append(item);
204
+ fields.set(property, { input: value, before: "", ...swatch ? { swatch } : {} });
205
+ }
206
+ properties.append(styleHint);
207
+ const footer = doc.createElement("div");
208
+ footer.className = "footer hidden";
209
+ cancel.className = "cancel";
210
+ cancel.textContent = options.copy.cancel;
211
+ const expandedSave = save.cloneNode(true);
212
+ footer.append(cancel, expandedSave);
213
+ optionsPanel.append(targetRow, properties, intentRow);
214
+ editor.append(row, optionsPanel, error, footer);
215
+ shadow.append(style, outline, hint, editor);
216
+ const surface = doc.createElement("div");
217
+ surface.style.cssText = "position:fixed;inset:0;pointer-events:auto;cursor:crosshair;touch-action:none";
218
+ shadow.prepend(surface);
219
+ doc.documentElement.append(root);
220
+ let target = null;
221
+ let hovered = null;
222
+ let selection = null;
223
+ let gesture = null;
224
+ let suspended = false;
225
+ let suppressClick = false;
226
+ const descendants = [];
227
+ let animation = 0;
228
+ let ended = false;
229
+ let settle;
230
+ const result = new Promise((resolve) => {
231
+ settle = resolve;
232
+ });
233
+ const lifecycle = new AbortController();
234
+ function changes() {
235
+ if (selection?.kind !== "element") return {};
236
+ return Object.fromEntries(
237
+ [...fields].flatMap(([key, field]) => {
238
+ let after = field.input.value.trim();
239
+ if ((key === "font-size" || key === "gap") && /^\d+(\.\d+)?$/.test(after)) after += "px";
240
+ return after === field.before ? [] : [[key, { before: field.before, after }]];
241
+ })
242
+ );
243
+ }
244
+ function refreshProperties(element) {
245
+ const computed = element ? getComputedStyle(element) : null;
246
+ for (const [key, field] of fields) {
247
+ field.before = computed?.getPropertyValue(key) || "";
248
+ field.input.value = field.before;
249
+ field.input.disabled = !element;
250
+ if (field.swatch) field.swatch.style.background = field.before;
251
+ }
252
+ properties.classList.toggle("area", !element);
253
+ for (const field of fields.values())
254
+ field.input.closest(".property")?.classList.toggle("hidden", !element);
255
+ styleHint.textContent = element ? options.copy.styleHint : options.copy.elementOnly;
256
+ }
257
+ for (const [key, field] of fields) {
258
+ field.input.addEventListener(
259
+ "input",
260
+ () => {
261
+ if (field.swatch && CSS.supports(key, field.input.value))
262
+ field.swatch.style.background = field.input.value;
263
+ intent.value = "comment";
264
+ error.classList.add("hidden");
265
+ draw();
266
+ },
267
+ { signal: lifecycle.signal }
268
+ );
269
+ field.input.addEventListener(
270
+ "keydown",
271
+ (event) => {
272
+ if (event.key === "Enter" && !event.isComposing && event.keyCode !== 229) {
273
+ event.preventDefault();
274
+ event.stopPropagation();
275
+ submit(event);
276
+ }
277
+ },
278
+ { signal: lifecycle.signal }
279
+ );
280
+ }
281
+ const observer = new ResizeObserver(draw);
282
+ function finish(value) {
283
+ if (ended) return;
284
+ ended = true;
285
+ lifecycle.abort();
286
+ cancelAnimationFrame(animation);
287
+ observer.disconnect();
288
+ root.remove();
289
+ if (window.__DSH_PAGE_ANNOTATION_CANCEL__ === dismiss)
290
+ delete window.__DSH_PAGE_ANNOTATION_CANCEL__;
291
+ if (originalFocus instanceof HTMLElement && originalFocus.isConnected)
292
+ originalFocus.focus({ preventScroll: true });
293
+ settle(value);
294
+ }
295
+ function dismiss() {
296
+ finish({ cancelled: true });
297
+ }
298
+ window.__DSH_PAGE_ANNOTATION_CANCEL__ = dismiss;
299
+ function showError(message) {
300
+ error.textContent = message;
301
+ error.classList.remove("hidden");
302
+ }
303
+ function valid(element) {
304
+ return !!element && element !== doc.body && element !== doc.documentElement && !element.closest("[data-dsh-page-annotation]");
305
+ }
306
+ function hit(x, y) {
307
+ surface.style.pointerEvents = "none";
308
+ editor.style.pointerEvents = "none";
309
+ let node = doc.elementFromPoint(x, y);
310
+ while (node?.shadowRoot) {
311
+ const inner = node.shadowRoot.elementFromPoint(x, y);
312
+ if (!inner || inner === node) break;
313
+ node = inner;
314
+ }
315
+ surface.style.pointerEvents = suspended ? "none" : "auto";
316
+ editor.style.pointerEvents = "auto";
317
+ return valid(node) ? node : null;
318
+ }
319
+ function rectInDocument(element) {
320
+ const rect = element.getBoundingClientRect();
321
+ return { x: rect.x + scrollX, y: rect.y + scrollY, width: rect.width, height: rect.height };
322
+ }
323
+ function gestureRect(x, y) {
324
+ return {
325
+ x: Math.min(gesture.x, x),
326
+ y: Math.min(gesture.y, y),
327
+ width: Math.max(1, Math.abs(x - gesture.x)),
328
+ height: Math.max(1, Math.abs(y - gesture.y))
329
+ };
330
+ }
331
+ function selector(element) {
332
+ const scope = element.getRootNode();
333
+ if (element.id) {
334
+ const id = "#" + CSS.escape(element.id);
335
+ if (scope.querySelectorAll(id).length === 1) return id;
336
+ }
337
+ const pieces = [];
338
+ let node = element;
339
+ while (node) {
340
+ let part = node.localName;
341
+ const peers = node.parentElement ? Array.from(node.parentElement.children).filter((x) => x.localName === node.localName) : [];
342
+ if (peers.length > 1) part += `:nth-of-type(${peers.indexOf(node) + 1})`;
343
+ pieces.unshift(part);
344
+ const path = pieces.join(" > ");
345
+ if (scope.querySelectorAll(path).length === 1 && scope.querySelector(path) === element)
346
+ return path;
347
+ node = node.parentElement;
348
+ }
349
+ return pieces.join(" > ");
350
+ }
351
+ function locator(element) {
352
+ const shadowHosts = [];
353
+ let scope = element.getRootNode();
354
+ while (scope instanceof ShadowRoot) {
355
+ shadowHosts.unshift(selector(scope.host));
356
+ scope = scope.host.getRootNode();
357
+ }
358
+ return {
359
+ selector: selector(element),
360
+ shadowHosts,
361
+ matchCount: element.getRootNode().querySelectorAll(
362
+ selector(element)
363
+ ).length
364
+ };
365
+ }
366
+ function draw() {
367
+ if (ended) return;
368
+ if (location.href !== originalUrl) {
369
+ dismiss();
370
+ return;
371
+ }
372
+ if (suspended) return;
373
+ const node = target || hovered;
374
+ if (selection?.kind === "element" && !target?.isConnected) {
375
+ outline.classList.add("hidden");
376
+ showError(options.copy.targetGone);
377
+ save.disabled = expandedSave.disabled = true;
378
+ return;
379
+ }
380
+ save.disabled = expandedSave.disabled = !selection || !input.value.trim() && !Object.keys(changes()).length;
381
+ const documentRect = selection?.kind === "element" && target ? rectInDocument(target) : selection?.rect || (node?.isConnected ? rectInDocument(node) : null);
382
+ if (!documentRect) {
383
+ outline.classList.add("hidden");
384
+ return;
385
+ }
386
+ const rect = {
387
+ left: documentRect.x - scrollX,
388
+ top: documentRect.y - scrollY,
389
+ width: documentRect.width,
390
+ height: documentRect.height,
391
+ bottom: documentRect.y - scrollY + documentRect.height
392
+ };
393
+ outline.classList.remove("hidden");
394
+ Object.assign(outline.style, {
395
+ left: `${rect.left - (selection?.kind === "point" ? 5 : 0)}px`,
396
+ top: `${rect.top - (selection?.kind === "point" ? 5 : 0)}px`,
397
+ width: `${selection?.kind === "point" ? 10 : rect.width}px`,
398
+ height: `${selection?.kind === "point" ? 10 : rect.height}px`,
399
+ borderStyle: selection?.kind === "region" ? "dashed" : "solid"
400
+ });
401
+ if (editor.classList.contains("hidden")) return;
402
+ const width = editor.getBoundingClientRect().width;
403
+ const height = editor.getBoundingClientRect().height;
404
+ const left = Math.max(12, Math.min(rect.left, innerWidth - width - 12));
405
+ const below = rect.bottom + 8;
406
+ const above = rect.top - height - 8;
407
+ const top = Math.max(
408
+ 12,
409
+ Math.min(
410
+ below + height <= innerHeight - 12 ? below : above >= 12 ? above : rect.top + 8,
411
+ innerHeight - height - 12
412
+ )
413
+ );
414
+ Object.assign(editor.style, { left: `${left}px`, top: `${top}px` });
415
+ }
416
+ function setOptions(open, restoreFocus = false) {
417
+ optionsPanel.classList.toggle("hidden", !open);
418
+ editor.classList.toggle("expanded", open);
419
+ footer.classList.toggle("hidden", !open);
420
+ settings.setAttribute("aria-expanded", String(open));
421
+ draw();
422
+ if (open) settings.focus({ preventScroll: true });
423
+ else if (restoreFocus) settings.focus({ preventScroll: true });
424
+ }
425
+ settings.addEventListener(
426
+ "click",
427
+ () => {
428
+ setOptions(optionsPanel.classList.contains("hidden"));
429
+ },
430
+ { signal: lifecycle.signal }
431
+ );
432
+ intent.addEventListener(
433
+ "change",
434
+ () => {
435
+ setOptions(false);
436
+ input.focus({ preventScroll: true });
437
+ },
438
+ { signal: lifecycle.signal }
439
+ );
440
+ input.addEventListener(
441
+ "input",
442
+ () => {
443
+ input.style.height = "22px";
444
+ input.style.height = `${Math.min(110, input.scrollHeight)}px`;
445
+ error.classList.add("hidden");
446
+ draw();
447
+ },
448
+ { signal: lifecycle.signal }
449
+ );
450
+ function track() {
451
+ draw();
452
+ if (!ended) animation = requestAnimationFrame(track);
453
+ }
454
+ function choose(element) {
455
+ if (!valid(element)) return;
456
+ observer.disconnect();
457
+ target = element;
458
+ selection = { kind: "element", rect: rectInDocument(element) };
459
+ observer.observe(element);
460
+ hint.classList.add("hidden");
461
+ editor.classList.remove("hidden");
462
+ error.classList.add("hidden");
463
+ const where = locator(element);
464
+ targetLabel.textContent = `<${element.localName}>`;
465
+ targetLabel.title = [...where.shadowHosts, where.selector].join(" \u203A ");
466
+ refreshProperties(element);
467
+ parent.disabled = !valid(element.parentElement) && !(element.getRootNode() instanceof ShadowRoot);
468
+ child.disabled = descendants.length === 0;
469
+ draw();
470
+ if (!animation) animation = requestAnimationFrame(track);
471
+ input.focus({ preventScroll: true });
472
+ }
473
+ function chooseArea(kind, rect) {
474
+ observer.disconnect();
475
+ target = null;
476
+ selection = { kind, rect };
477
+ hint.classList.add("hidden");
478
+ editor.classList.remove("hidden");
479
+ error.classList.add("hidden");
480
+ targetLabel.textContent = kind === "region" ? `${options.copy.region} \xB7 ${Math.round(rect.width)} \xD7 ${Math.round(rect.height)}` : options.copy.point;
481
+ parent.disabled = child.disabled = true;
482
+ targetLabel.title = "";
483
+ refreshProperties(null);
484
+ draw();
485
+ if (!animation) animation = requestAnimationFrame(track);
486
+ input.focus({ preventScroll: true });
487
+ }
488
+ function isOverlay(event) {
489
+ return event.composedPath().includes(root);
490
+ }
491
+ window.addEventListener(
492
+ "pointermove",
493
+ (event) => {
494
+ if (suspended || !editor.classList.contains("hidden")) return;
495
+ if (gesture && gesture.id === event.pointerId) {
496
+ gesture.dragging ||= Math.hypot(event.clientX - gesture.clientX, event.clientY - gesture.clientY) >= 5;
497
+ if (gesture.dragging) {
498
+ selection = {
499
+ kind: "region",
500
+ rect: gestureRect(event.clientX + scrollX, event.clientY + scrollY)
501
+ };
502
+ draw();
503
+ return;
504
+ }
505
+ }
506
+ const node = hit(event.clientX, event.clientY);
507
+ hovered = event.shiftKey && valid(node?.parentElement || null) ? node.parentElement : node;
508
+ draw();
509
+ },
510
+ { capture: true, signal: lifecycle.signal }
511
+ );
512
+ window.addEventListener(
513
+ "pointerdown",
514
+ (event) => {
515
+ if (suspended) {
516
+ suppressClick = false;
517
+ return;
518
+ }
519
+ if (isOverlay(event) && !event.composedPath().includes(surface)) return;
520
+ setOptions(false);
521
+ event.preventDefault();
522
+ event.stopImmediatePropagation();
523
+ suppressClick = true;
524
+ if (event.button !== 0 || !editor.classList.contains("hidden")) return;
525
+ gesture = {
526
+ id: event.pointerId,
527
+ x: event.clientX + scrollX,
528
+ y: event.clientY + scrollY,
529
+ clientX: event.clientX,
530
+ clientY: event.clientY,
531
+ dragging: false
532
+ };
533
+ surface.setPointerCapture(event.pointerId);
534
+ },
535
+ { capture: true, signal: lifecycle.signal }
536
+ );
537
+ window.addEventListener(
538
+ "pointerup",
539
+ (event) => {
540
+ if (!gesture || gesture.id !== event.pointerId || suspended) return;
541
+ event.preventDefault();
542
+ event.stopImmediatePropagation();
543
+ const current = gesture;
544
+ const area = gestureRect(event.clientX + scrollX, event.clientY + scrollY);
545
+ gesture = null;
546
+ if (surface.hasPointerCapture(event.pointerId))
547
+ surface.releasePointerCapture(event.pointerId);
548
+ if (current.dragging || Math.hypot(event.clientX - current.clientX, event.clientY - current.clientY) >= 5)
549
+ chooseArea("region", area);
550
+ else {
551
+ const node = hit(event.clientX, event.clientY);
552
+ descendants.length = 0;
553
+ if (node) choose(event.shiftKey && valid(node.parentElement) ? node.parentElement : node);
554
+ else
555
+ chooseArea("point", {
556
+ x: event.clientX + scrollX,
557
+ y: event.clientY + scrollY,
558
+ width: 0,
559
+ height: 0
560
+ });
561
+ }
562
+ },
563
+ { capture: true, signal: lifecycle.signal }
564
+ );
565
+ function abortGesture() {
566
+ if (!gesture) return;
567
+ if (surface.hasPointerCapture(gesture.id)) surface.releasePointerCapture(gesture.id);
568
+ gesture = null;
569
+ if (editor.classList.contains("hidden")) selection = null;
570
+ draw();
571
+ }
572
+ window.addEventListener("pointercancel", abortGesture, {
573
+ capture: true,
574
+ signal: lifecycle.signal
575
+ });
576
+ window.addEventListener(
577
+ "click",
578
+ (event) => {
579
+ if (isOverlay(event) && !event.composedPath().includes(surface)) return;
580
+ if (suspended && !suppressClick) return;
581
+ event.preventDefault();
582
+ event.stopImmediatePropagation();
583
+ suppressClick = false;
584
+ },
585
+ { capture: true, signal: lifecycle.signal }
586
+ );
587
+ function resume() {
588
+ if (!suspended) return;
589
+ suspended = false;
590
+ surface.style.pointerEvents = "auto";
591
+ hint.textContent = options.copy.select;
592
+ if (selection) {
593
+ editor.classList.remove("hidden");
594
+ hint.classList.add("hidden");
595
+ } else hint.classList.remove("hidden");
596
+ draw();
597
+ }
598
+ window.addEventListener(
599
+ "keydown",
600
+ (event) => {
601
+ if (event.isComposing || event.keyCode === 229) return;
602
+ if (event.key === "Escape") {
603
+ event.preventDefault();
604
+ event.stopImmediatePropagation();
605
+ if (optionsPanel.classList.contains("hidden")) dismiss();
606
+ else setOptions(false, true);
607
+ return;
608
+ }
609
+ if (isOverlay(event) && !event.composedPath().includes(surface)) return;
610
+ if (event.code === "Space" && !event.altKey && !event.ctrlKey && !event.metaKey) {
611
+ event.preventDefault();
612
+ event.stopImmediatePropagation();
613
+ abortGesture();
614
+ suspended = true;
615
+ setOptions(false);
616
+ surface.style.pointerEvents = "none";
617
+ outline.classList.add("hidden");
618
+ editor.classList.add("hidden");
619
+ hint.classList.remove("hidden");
620
+ hint.textContent = options.copy.interact;
621
+ } else if (!suspended && !isOverlay(event)) {
622
+ event.preventDefault();
623
+ event.stopImmediatePropagation();
624
+ }
625
+ },
626
+ { capture: true, signal: lifecycle.signal }
627
+ );
628
+ window.addEventListener(
629
+ "keyup",
630
+ (event) => {
631
+ if (event.code === "Space" && suspended) {
632
+ event.preventDefault();
633
+ event.stopImmediatePropagation();
634
+ resume();
635
+ }
636
+ },
637
+ { capture: true, signal: lifecycle.signal }
638
+ );
639
+ window.addEventListener(
640
+ "blur",
641
+ () => {
642
+ abortGesture();
643
+ resume();
644
+ },
645
+ { signal: lifecycle.signal }
646
+ );
647
+ doc.addEventListener(
648
+ "visibilitychange",
649
+ () => {
650
+ if (doc.hidden) {
651
+ abortGesture();
652
+ resume();
653
+ }
654
+ },
655
+ { signal: lifecycle.signal }
656
+ );
657
+ window.addEventListener("pagehide", dismiss, { once: true, signal: lifecycle.signal });
658
+ parent.addEventListener(
659
+ "click",
660
+ () => {
661
+ if (!target) return;
662
+ const scope = target.getRootNode();
663
+ const node = target.parentElement || (scope instanceof ShadowRoot ? scope.host : null);
664
+ if (valid(node)) {
665
+ setOptions(false);
666
+ descendants.push(target);
667
+ choose(node);
668
+ }
669
+ },
670
+ { signal: lifecycle.signal }
671
+ );
672
+ child.addEventListener(
673
+ "click",
674
+ () => {
675
+ const node = descendants.pop();
676
+ if (node?.isConnected) {
677
+ setOptions(false);
678
+ choose(node);
679
+ }
680
+ },
681
+ { signal: lifecycle.signal }
682
+ );
683
+ cancel.addEventListener("click", dismiss, { signal: lifecycle.signal });
684
+ function evidence(element) {
685
+ const where = locator(element);
686
+ const computed = getComputedStyle(element);
687
+ const styles = {};
688
+ for (const name of [
689
+ "display",
690
+ "position",
691
+ "font-size",
692
+ "font-family",
693
+ "color",
694
+ "background-color",
695
+ "padding",
696
+ "margin",
697
+ "gap",
698
+ "opacity",
699
+ "font-weight"
700
+ ])
701
+ styles[name] = computed.getPropertyValue(name).slice(0, 1e3);
702
+ const source = element.closest("[data-web-annotator-source]")?.getAttribute("data-web-annotator-source")?.slice(0, 1e3);
703
+ return {
704
+ ...where,
705
+ tag: element.localName,
706
+ text: (element instanceof HTMLElement ? element.innerText : element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 400),
707
+ rect: rectInDocument(element),
708
+ styles,
709
+ ...source ? { source } : {}
710
+ };
711
+ }
712
+ function candidates(rect) {
713
+ const found = /* @__PURE__ */ new Set();
714
+ for (let row2 = 0; row2 < 5; row2++)
715
+ for (let col = 0; col < 5; col++) {
716
+ const node = hit(
717
+ rect.x - scrollX + rect.width * (col + 0.5) / 5,
718
+ rect.y - scrollY + rect.height * (row2 + 0.5) / 5
719
+ );
720
+ if (node) found.add(node);
721
+ }
722
+ return [...found].slice(0, 12).map(evidence);
723
+ }
724
+ function submit(event) {
725
+ event.preventDefault();
726
+ if (!selection || selection.kind === "element" && !target?.isConnected || location.href !== originalUrl) {
727
+ showError(options.copy.targetGone);
728
+ return;
729
+ }
730
+ const styleChanges = changes();
731
+ for (const [property, { after }] of Object.entries(styleChanges)) {
732
+ if (!CSS.supports(property, after) || property === "opacity" && !(Number(after) >= 0 && Number(after) <= 1)) {
733
+ showError(options.copy.invalidStyle);
734
+ setOptions(true);
735
+ fields.get(property)?.input.focus();
736
+ return;
737
+ }
738
+ }
739
+ if (!input.value.trim() && !Object.keys(styleChanges).length) {
740
+ showError(options.copy.empty);
741
+ input.focus();
742
+ return;
743
+ }
744
+ const rect = selection.kind === "element" ? rectInDocument(target) : selection.rect;
745
+ const items = selection.kind === "element" ? [evidence(target)] : candidates(rect);
746
+ finish({
747
+ intent: intent.value === "question" ? "question" : "comment",
748
+ note: input.value.trim() || options.copy.applyStyles,
749
+ ...Object.keys(styleChanges).length ? { styleChanges } : {},
750
+ url: originalUrl,
751
+ title: doc.title.slice(0, 300),
752
+ viewport: { width: innerWidth, height: innerHeight },
753
+ selection: {
754
+ kind: selection.kind,
755
+ rect,
756
+ viewportRect: { ...rect, x: rect.x - scrollX, y: rect.y - scrollY },
757
+ scroll: { x: scrollX, y: scrollY },
758
+ candidates: items
759
+ }
760
+ });
761
+ }
762
+ editor.addEventListener("submit", submit, { signal: lifecycle.signal });
763
+ input.addEventListener(
764
+ "keydown",
765
+ (event) => {
766
+ if (event.key === "Enter" && !event.shiftKey && !event.isComposing && event.keyCode !== 229) {
767
+ event.preventDefault();
768
+ event.stopPropagation();
769
+ submit(event);
770
+ }
771
+ },
772
+ { signal: lifecycle.signal }
773
+ );
774
+ animation = requestAnimationFrame(track);
775
+ return result;
776
+ }
777
+
778
+ // src/browser/protocol.ts
779
+ function object(value) {
780
+ return !!value && typeof value === "object" && !Array.isArray(value);
781
+ }
782
+ function string(value, limit) {
783
+ return typeof value === "string" && value.length <= limit;
784
+ }
785
+ function isAnnotationOptions(value) {
786
+ return object(value) && (value.intent === "comment" || value.intent === "question") && typeof value.dark === "boolean" && object(value.copy) && [
787
+ "select",
788
+ "comment",
789
+ "question",
790
+ "placeholder",
791
+ "settings",
792
+ "save",
793
+ "cancel",
794
+ "parent",
795
+ "child",
796
+ "targetGone",
797
+ "empty",
798
+ "region",
799
+ "point",
800
+ "interact",
801
+ "color",
802
+ "background",
803
+ "opacity",
804
+ "font",
805
+ "fontSize",
806
+ "fontWeight",
807
+ "spacing",
808
+ "elementOnly",
809
+ "invalidStyle",
810
+ "applyStyles",
811
+ "styleHint"
812
+ ].every((key) => string(value.copy && object(value.copy) ? value.copy[key] : null, 500));
813
+ }
814
+
815
+ // src/browser/bridge.ts
816
+ function isAllowedParentOrigin(origin, configured) {
817
+ try {
818
+ const url = new URL(origin);
819
+ if (url.origin !== origin) return false;
820
+ if (configured.includes(origin)) return ["http:", "https:"].includes(url.protocol);
821
+ return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(url.hostname));
822
+ } catch {
823
+ return false;
824
+ }
825
+ }
826
+ function installAnnotationBridge(win, pick = annotatePage) {
827
+ const lifecycle = new AbortController();
828
+ const configured = win.__WEB_ANNOTATOR_CONFIG__?.allowedParentOrigins ?? [];
829
+ let current;
830
+ let disposed = false;
831
+ const release = (port) => {
832
+ port.onmessage = null;
833
+ port.close();
834
+ if (current === port) current = void 0;
835
+ };
836
+ const dispose = () => {
837
+ if (disposed) return;
838
+ disposed = true;
839
+ lifecycle.abort();
840
+ const port = current;
841
+ if (port) {
842
+ port.postMessage({ cancelled: true });
843
+ release(port);
844
+ }
845
+ win.__DSH_PAGE_ANNOTATION_CANCEL__?.();
846
+ };
847
+ win.addEventListener(
848
+ "message",
849
+ (event) => {
850
+ if (disposed || win === win.parent || event.source !== win.parent || !isAllowedParentOrigin(event.origin, configured) || event.data?.channel !== "dsh-browser-annotation-v1" || event.data?.type !== "start" || !isAnnotationOptions(event.data.options) || event.ports.length !== 1)
851
+ return;
852
+ const port = event.ports[0];
853
+ if (current) {
854
+ current.postMessage({ cancelled: true });
855
+ release(current);
856
+ }
857
+ win.__DSH_PAGE_ANNOTATION_CANCEL__?.();
858
+ current = port;
859
+ port.onmessage = (message) => {
860
+ if (message.data?.type === "cancel" && current === port)
861
+ win.__DSH_PAGE_ANNOTATION_CANCEL__?.();
862
+ };
863
+ port.postMessage({ ready: true });
864
+ const settle = (value) => {
865
+ if (disposed || current !== port) return;
866
+ port.postMessage(value);
867
+ release(port);
868
+ };
869
+ let started;
870
+ try {
871
+ started = pick(event.data.options);
872
+ } catch {
873
+ settle({ cancelled: true });
874
+ return;
875
+ }
876
+ void started.then(settle, () => {
877
+ settle({ cancelled: true });
878
+ });
879
+ },
880
+ { signal: lifecycle.signal }
881
+ );
882
+ win.addEventListener("pagehide", dispose, { once: true, signal: lifecycle.signal });
883
+ return dispose;
884
+ }
885
+ if (typeof window !== "undefined") {
886
+ window.__DSH_PAGE_ANNOTATION_BRIDGE_DISPOSE__?.();
887
+ window.__DSH_PAGE_ANNOTATION_BRIDGE_DISPOSE__ = installAnnotationBridge(window);
888
+ }
889
+ })();
890
+ //# sourceMappingURL=bridge.js.map