astro-annotate 0.2.0 → 0.3.0

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/dist/client.js CHANGED
@@ -21,70 +21,6 @@ var OVERLAY_STYLES = `
21
21
  padding: 0;
22
22
  }
23
23
 
24
- /* Toolbar */
25
- .aa-toolbar {
26
- position: fixed;
27
- bottom: 20px;
28
- right: 20px;
29
- display: flex;
30
- align-items: center;
31
- gap: 8px;
32
- background: #1a1a2e;
33
- color: #fff;
34
- padding: 8px 16px;
35
- border-radius: 50px;
36
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
37
- border: 1.5px solid rgba(255, 255, 255, 0.2);
38
- cursor: pointer;
39
- user-select: none;
40
- transition: all 0.2s ease;
41
- }
42
-
43
- .aa-toolbar:hover {
44
- background: #16213e;
45
- transform: translateY(-1px);
46
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
47
- border-color: rgba(255, 255, 255, 0.35);
48
- }
49
-
50
- .aa-toolbar.aa-active {
51
- background: #e94560;
52
- }
53
-
54
- .aa-toolbar.aa-active:hover {
55
- background: #c73e54;
56
- }
57
-
58
- .aa-toolbar-icon {
59
- width: 20px;
60
- height: 20px;
61
- flex-shrink: 0;
62
- }
63
-
64
- .aa-toolbar-label {
65
- font-size: 13px;
66
- font-weight: 500;
67
- white-space: nowrap;
68
- }
69
-
70
- .aa-badge {
71
- background: #e94560;
72
- color: #fff;
73
- font-size: 11px;
74
- font-weight: 700;
75
- min-width: 20px;
76
- height: 20px;
77
- border-radius: 10px;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- padding: 0 6px;
82
- }
83
-
84
- .aa-toolbar.aa-active .aa-badge {
85
- background: rgba(255, 255, 255, 0.3);
86
- }
87
-
88
24
  /* Element Highlight */
89
25
  .aa-highlight {
90
26
  position: fixed;
@@ -396,55 +332,6 @@ var OVERLAY_STYLES = `
396
332
  }
397
333
  `;
398
334
 
399
- // src/client/toolbar.ts
400
- var ANNOTATE_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="aa-toolbar-icon"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="12" y1="6" x2="12" y2="12"/></svg>`;
401
- var Toolbar = class {
402
- el;
403
- label;
404
- badge;
405
- active = false;
406
- onToggle;
407
- constructor(shadowRoot, onToggle) {
408
- this.onToggle = onToggle;
409
- this.el = document.createElement("div");
410
- this.el.className = "aa-toolbar";
411
- this.el.innerHTML = ANNOTATE_ICON;
412
- this.label = document.createElement("span");
413
- this.label.className = "aa-toolbar-label";
414
- this.label.textContent = "Annotate";
415
- this.el.appendChild(this.label);
416
- this.badge = document.createElement("span");
417
- this.badge.className = "aa-badge";
418
- this.badge.textContent = "0";
419
- this.badge.style.display = "none";
420
- this.el.appendChild(this.badge);
421
- this.el.addEventListener("click", () => {
422
- this.toggle();
423
- });
424
- shadowRoot.appendChild(this.el);
425
- }
426
- toggle() {
427
- this.active = !this.active;
428
- this.el.classList.toggle("aa-active", this.active);
429
- this.label.textContent = this.active ? "Stop" : "Annotate";
430
- this.onToggle(this.active);
431
- }
432
- deactivate() {
433
- if (this.active) {
434
- this.active = false;
435
- this.el.classList.remove("aa-active");
436
- this.label.textContent = "Annotate";
437
- }
438
- }
439
- updateCount(count) {
440
- this.badge.textContent = String(count);
441
- this.badge.style.display = count > 0 ? "flex" : "none";
442
- }
443
- destroy() {
444
- this.el.remove();
445
- }
446
- };
447
-
448
335
  // src/client/selector.ts
449
336
  var ASTRO_CLASS_RE = /^astro-[a-zA-Z0-9]+$/;
450
337
  var IGNORED_TAGS = /* @__PURE__ */ new Set(["html", "body", "head"]);
@@ -814,7 +701,6 @@ var PinManager = class {
814
701
  var Overlay = class {
815
702
  host;
816
703
  shadowRoot;
817
- toolbar;
818
704
  highlighter;
819
705
  form;
820
706
  pinManager;
@@ -829,7 +715,6 @@ var Overlay = class {
829
715
  const style = document.createElement("style");
830
716
  style.textContent = OVERLAY_STYLES;
831
717
  this.shadowRoot.appendChild(style);
832
- this.toolbar = new Toolbar(this.shadowRoot, (active) => this.setActive(active));
833
718
  this.highlighter = new Highlighter(this.shadowRoot);
834
719
  this.form = new AnnotationForm(
835
720
  this.shadowRoot,
@@ -838,6 +723,7 @@ var Overlay = class {
838
723
  this.devMode
839
724
  );
840
725
  this.pinManager = new PinManager(this.shadowRoot, () => this.loadAnnotations());
726
+ window.addEventListener("aa:toggle", this.onToolbarToggle);
841
727
  this.loadAnnotations();
842
728
  document.addEventListener("click", (e) => {
843
729
  const target = e.target;
@@ -869,6 +755,9 @@ var Overlay = class {
869
755
  this.form.hide();
870
756
  }
871
757
  }
758
+ onToolbarToggle = ((e) => {
759
+ this.setActive(e.detail.active);
760
+ });
872
761
  onElementClick = (e) => {
873
762
  const target = e.target;
874
763
  if (this.host.contains(target) || this.host === target) return;
@@ -884,7 +773,9 @@ var Overlay = class {
884
773
  const isExternalInput = active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA" || active.isContentEditable) && active !== this.host && !this.host.contains(active);
885
774
  if (e.altKey && e.code === "KeyC" && !isExternalInput) {
886
775
  e.preventDefault();
887
- this.toolbar.toggle();
776
+ const newActive = !this.active;
777
+ this.setActive(newActive);
778
+ window.dispatchEvent(new CustomEvent("aa:state-changed", { detail: { active: newActive } }));
888
779
  return;
889
780
  }
890
781
  if (e.key === "Escape") {
@@ -893,8 +784,8 @@ var Overlay = class {
893
784
  return;
894
785
  }
895
786
  if (this.active) {
896
- this.toolbar.deactivate();
897
787
  this.setActive(false);
788
+ window.dispatchEvent(new CustomEvent("aa:state-changed", { detail: { active: false } }));
898
789
  return;
899
790
  }
900
791
  }
@@ -906,7 +797,8 @@ var Overlay = class {
906
797
  if (!res.ok) return;
907
798
  const data = await res.json();
908
799
  this.annotations = data.annotations || [];
909
- this.toolbar.updateCount(this.annotations.filter((a) => a.status === "open").length);
800
+ const openCount = this.annotations.filter((a) => a.status === "open").length;
801
+ window.dispatchEvent(new CustomEvent("aa:count", { detail: { count: openCount } }));
910
802
  this.renderPins();
911
803
  } catch {
912
804
  }
@@ -924,8 +816,11 @@ var Overlay = class {
924
816
  }
925
817
  destroy() {
926
818
  document.removeEventListener("keydown", this.onKeyDown);
819
+ window.removeEventListener("aa:toggle", this.onToolbarToggle);
820
+ if (this.active) {
821
+ window.dispatchEvent(new CustomEvent("aa:state-changed", { detail: { active: false } }));
822
+ }
927
823
  this.setActive(false);
928
- this.toolbar.destroy();
929
824
  this.highlighter.destroy();
930
825
  this.form.destroy();
931
826
  this.pinManager.destroy();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/styles.ts","../src/client/toolbar.ts","../src/client/selector.ts","../src/client/highlighter.ts","../src/client/form.ts","../src/client/pin.ts","../src/client/overlay.ts","../src/client/index.ts"],"sourcesContent":["export const OVERLAY_STYLES = `\n :host {\n all: initial;\n position: fixed;\n z-index: 2147483647;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #1a1a2e;\n }\n\n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* Toolbar */\n .aa-toolbar {\n position: fixed;\n bottom: 20px;\n right: 20px;\n display: flex;\n align-items: center;\n gap: 8px;\n background: #1a1a2e;\n color: #fff;\n padding: 8px 16px;\n border-radius: 50px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n border: 1.5px solid rgba(255, 255, 255, 0.2);\n cursor: pointer;\n user-select: none;\n transition: all 0.2s ease;\n }\n\n .aa-toolbar:hover {\n background: #16213e;\n transform: translateY(-1px);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);\n border-color: rgba(255, 255, 255, 0.35);\n }\n\n .aa-toolbar.aa-active {\n background: #e94560;\n }\n\n .aa-toolbar.aa-active:hover {\n background: #c73e54;\n }\n\n .aa-toolbar-icon {\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n\n .aa-toolbar-label {\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n }\n\n .aa-badge {\n background: #e94560;\n color: #fff;\n font-size: 11px;\n font-weight: 700;\n min-width: 20px;\n height: 20px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 6px;\n }\n\n .aa-toolbar.aa-active .aa-badge {\n background: rgba(255, 255, 255, 0.3);\n }\n\n /* Element Highlight */\n .aa-highlight {\n position: fixed;\n pointer-events: none;\n border: 2px solid #e94560;\n background: rgba(233, 69, 96, 0.08);\n border-radius: 3px;\n z-index: 2147483646;\n transition: all 0.1s ease;\n }\n\n .aa-highlight-label {\n position: absolute;\n top: -26px;\n left: -2px;\n background: #e94560;\n color: #fff;\n font-size: 11px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 3px 3px 0 0;\n white-space: nowrap;\n max-width: 300px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n /* Annotation Form */\n .aa-form-container {\n position: fixed;\n z-index: 2147483647;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n width: 340px;\n overflow: hidden;\n }\n\n .aa-form-header {\n background: #1a1a2e;\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .aa-form-header-title {\n font-size: 13px;\n font-weight: 600;\n }\n\n .aa-form-header-selector {\n font-size: 11px;\n opacity: 0.7;\n font-family: 'SF Mono', Monaco, monospace;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .aa-form-close {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n font-size: 18px;\n line-height: 1;\n opacity: 0.7;\n padding: 0 0 0 8px;\n }\n\n .aa-form-close:hover {\n opacity: 1;\n }\n\n .aa-form-body {\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .aa-input, .aa-textarea {\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 13px;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s;\n }\n\n .aa-input:focus, .aa-textarea:focus {\n border-color: #e94560;\n }\n\n .aa-textarea {\n resize: vertical;\n min-height: 80px;\n }\n\n .aa-form-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .aa-btn {\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border: none;\n transition: all 0.15s;\n }\n\n .aa-btn-primary {\n background: #e94560;\n color: #fff;\n }\n\n .aa-btn-primary:hover {\n background: #c73e54;\n }\n\n .aa-btn-primary:disabled {\n background: #ccc;\n cursor: not-allowed;\n }\n\n .aa-btn-secondary {\n background: #f0f0f0;\n color: #333;\n }\n\n .aa-btn-secondary:hover {\n background: #e0e0e0;\n }\n\n /* Pins */\n .aa-pin {\n position: absolute;\n width: 28px;\n height: 28px;\n border-radius: 50% 50% 50% 0;\n background: #e94560;\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n transform: rotate(-45deg);\n box-shadow: 0 2px 8px rgba(233, 69, 96, 0.4);\n transition: transform 0.15s, box-shadow 0.15s;\n z-index: 2147483645;\n }\n\n .aa-pin:hover {\n transform: rotate(-45deg) scale(1.15);\n box-shadow: 0 4px 12px rgba(233, 69, 96, 0.5);\n }\n\n .aa-pin.aa-resolved {\n background: #2ecc71;\n box-shadow: 0 2px 8px rgba(46, 204, 113, 0.4);\n }\n\n .aa-pin-number {\n transform: rotate(45deg);\n }\n\n /* Pin Detail Popup */\n .aa-pin-detail {\n position: fixed;\n z-index: 2147483647;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n width: 320px;\n overflow: hidden;\n }\n\n .aa-pin-detail-header {\n background: #1a1a2e;\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .aa-pin-detail-meta {\n font-size: 11px;\n opacity: 0.7;\n }\n\n .aa-pin-detail-body {\n padding: 16px;\n }\n\n .aa-pin-detail-text {\n font-size: 14px;\n margin-bottom: 12px;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .aa-pin-detail-info {\n font-size: 12px;\n color: #888;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .aa-pin-detail-selector {\n font-family: 'SF Mono', Monaco, monospace;\n font-size: 11px;\n color: #666;\n background: #f5f5f5;\n padding: 4px 8px;\n border-radius: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .aa-pin-detail-actions {\n padding: 0 16px 16px;\n display: flex;\n gap: 8px;\n }\n\n .aa-status-btn {\n padding: 4px 12px;\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid #e0e0e0;\n background: #fff;\n transition: all 0.15s;\n }\n\n .aa-status-btn:hover {\n background: #f5f5f5;\n }\n\n .aa-status-btn.aa-resolve {\n color: #2ecc71;\n border-color: #2ecc71;\n }\n\n .aa-status-btn.aa-resolve:hover {\n background: #2ecc71;\n color: #fff;\n }\n\n .aa-status-btn.aa-reopen {\n color: #e94560;\n border-color: #e94560;\n }\n\n .aa-status-btn.aa-reopen:hover {\n background: #e94560;\n color: #fff;\n }\n\n /* Dark mode */\n @media (prefers-color-scheme: dark) {\n .aa-form-container, .aa-pin-detail {\n background: #2d2d3f;\n color: #e0e0e0;\n }\n\n .aa-input, .aa-textarea {\n background: #1a1a2e;\n border-color: #404060;\n color: #e0e0e0;\n }\n\n .aa-btn-secondary {\n background: #404060;\n color: #e0e0e0;\n }\n\n .aa-btn-secondary:hover {\n background: #505070;\n }\n\n .aa-pin-detail-selector {\n background: #1a1a2e;\n color: #aaa;\n }\n\n .aa-status-btn {\n background: #2d2d3f;\n border-color: #404060;\n }\n\n .aa-status-btn:hover {\n background: #404060;\n }\n }\n`;\n","const ANNOTATE_ICON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"aa-toolbar-icon\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"9\"/><line x1=\"12\" y1=\"6\" x2=\"12\" y2=\"12\"/></svg>`;\n\nexport class Toolbar {\n private el: HTMLElement;\n private label: HTMLElement;\n private badge: HTMLElement;\n private active = false;\n private onToggle: (active: boolean) => void;\n\n constructor(shadowRoot: ShadowRoot, onToggle: (active: boolean) => void) {\n this.onToggle = onToggle;\n\n this.el = document.createElement('div');\n this.el.className = 'aa-toolbar';\n\n this.el.innerHTML = ANNOTATE_ICON;\n\n this.label = document.createElement('span');\n this.label.className = 'aa-toolbar-label';\n this.label.textContent = 'Annotate';\n this.el.appendChild(this.label);\n\n this.badge = document.createElement('span');\n this.badge.className = 'aa-badge';\n this.badge.textContent = '0';\n this.badge.style.display = 'none';\n this.el.appendChild(this.badge);\n\n this.el.addEventListener('click', () => {\n this.toggle();\n });\n\n shadowRoot.appendChild(this.el);\n }\n\n toggle(): void {\n this.active = !this.active;\n this.el.classList.toggle('aa-active', this.active);\n this.label.textContent = this.active ? 'Stop' : 'Annotate';\n this.onToggle(this.active);\n }\n\n deactivate(): void {\n if (this.active) {\n this.active = false;\n this.el.classList.remove('aa-active');\n this.label.textContent = 'Annotate';\n }\n }\n\n updateCount(count: number): void {\n this.badge.textContent = String(count);\n this.badge.style.display = count > 0 ? 'flex' : 'none';\n }\n\n destroy(): void {\n this.el.remove();\n }\n}\n","const ASTRO_CLASS_RE = /^astro-[a-zA-Z0-9]+$/;\nconst IGNORED_TAGS = new Set(['html', 'body', 'head']);\n\nfunction isAstroClass(cls: string): boolean {\n return ASTRO_CLASS_RE.test(cls);\n}\n\nfunction getStableClasses(el: Element): string[] {\n return Array.from(el.classList).filter((cls) => !isAstroClass(cls));\n}\n\nfunction isUnique(selector: string): boolean {\n try {\n return document.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n}\n\nfunction escapeSelector(value: string): string {\n return CSS.escape(value);\n}\n\nexport function generateSelector(target: Element): string {\n if (IGNORED_TAGS.has(target.tagName.toLowerCase())) {\n return target.tagName.toLowerCase();\n }\n\n // 1. ID (if stable — not auto-generated)\n if (target.id && !/^[0-9]/.test(target.id) && !target.id.includes(':')) {\n const sel = `#${escapeSelector(target.id)}`;\n if (isUnique(sel)) return sel;\n }\n\n // 2. data-testid\n const testId = target.getAttribute('data-testid');\n if (testId) {\n const sel = `[data-testid=\"${escapeSelector(testId)}\"]`;\n if (isUnique(sel)) return sel;\n }\n\n // 3. Tag + stable classes\n const tag = target.tagName.toLowerCase();\n const classes = getStableClasses(target);\n if (classes.length > 0) {\n const classSel = classes.map((c) => `.${escapeSelector(c)}`).join('');\n const sel = `${tag}${classSel}`;\n if (isUnique(sel)) return sel;\n }\n\n // 4. Build path upward with nth-child\n const path: string[] = [];\n let current: Element | null = target;\n\n while (current && !IGNORED_TAGS.has(current.tagName.toLowerCase())) {\n let segment = current.tagName.toLowerCase();\n\n // Try id first\n if (current.id && !/^[0-9]/.test(current.id) && !current.id.includes(':')) {\n segment = `#${escapeSelector(current.id)}`;\n path.unshift(segment);\n const sel = path.join(' > ');\n if (isUnique(sel)) return sel;\n // ID should be unique, break here\n break;\n }\n\n // Try tag + classes\n const cls = getStableClasses(current);\n if (cls.length > 0) {\n segment += cls.map((c) => `.${escapeSelector(c)}`).join('');\n }\n\n // Add nth-child if not unique enough\n const parent: Element | null = current.parentElement;\n if (parent) {\n const currentTag = current.tagName;\n const siblings = Array.from(parent.children).filter(\n (s: Element) => s.tagName === currentTag,\n );\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1;\n segment += `:nth-child(${index})`;\n }\n }\n\n path.unshift(segment);\n const sel = path.join(' > ');\n if (isUnique(sel)) return sel;\n\n current = parent;\n }\n\n return path.join(' > ') || tag;\n}\n\nexport function getElementLabel(el: Element): string {\n const tag = el.tagName.toLowerCase();\n const classes = getStableClasses(el);\n const classStr = classes.length > 0 ? `.${classes.slice(0, 3).join('.')}` : '';\n return `<${tag}${classStr}>`;\n}\n\nexport function getElementText(el: Element): string {\n const text = el.textContent?.trim() || '';\n return text.slice(0, 200);\n}\n","import { SHADOW_ROOT_ID } from '../constants.js';\nimport { getElementLabel } from './selector.js';\n\nexport class Highlighter {\n private highlight: HTMLElement;\n private label: HTMLElement;\n private currentTarget: Element | null = null;\n\n constructor(private shadowRoot: ShadowRoot) {\n this.highlight = document.createElement('div');\n this.highlight.className = 'aa-highlight';\n this.highlight.style.display = 'none';\n\n this.label = document.createElement('div');\n this.label.className = 'aa-highlight-label';\n this.highlight.appendChild(this.label);\n\n this.shadowRoot.appendChild(this.highlight);\n }\n\n private isOwnElement(el: Element): boolean {\n const root = document.getElementById(SHADOW_ROOT_ID);\n return root !== null && (root === el || root.contains(el));\n }\n\n onMouseMove = (e: MouseEvent): void => {\n const target = e.target as Element;\n\n if (!target || this.isOwnElement(target)) {\n this.hide();\n return;\n }\n\n if (target === this.currentTarget) return;\n this.currentTarget = target;\n\n const rect = target.getBoundingClientRect();\n this.highlight.style.display = 'block';\n this.highlight.style.top = `${rect.top}px`;\n this.highlight.style.left = `${rect.left}px`;\n this.highlight.style.width = `${rect.width}px`;\n this.highlight.style.height = `${rect.height}px`;\n this.label.textContent = getElementLabel(target);\n };\n\n hide(): void {\n this.highlight.style.display = 'none';\n this.currentTarget = null;\n }\n\n getTarget(): Element | null {\n return this.currentTarget;\n }\n\n destroy(): void {\n this.highlight.remove();\n }\n}\n","import type { CreateAnnotationPayload } from '../types.js';\nimport { API_ANNOTATIONS } from '../constants.js';\nimport { generateSelector, getElementText } from './selector.js';\n\nexport class AnnotationForm {\n private container: HTMLElement;\n private onSubmitted: () => void;\n private onClosed: () => void;\n\n constructor(\n private shadowRoot: ShadowRoot,\n onSubmitted: () => void,\n onClosed: () => void,\n private devMode: boolean,\n ) {\n this.container = document.createElement('div');\n this.container.className = 'aa-form-container';\n this.container.style.display = 'none';\n this.shadowRoot.appendChild(this.container);\n this.onSubmitted = onSubmitted;\n this.onClosed = onClosed;\n }\n\n show(target: Element): void {\n const selector = generateSelector(target);\n const elementTag = target.tagName.toLowerCase();\n const elementText = getElementText(target);\n const rect = target.getBoundingClientRect();\n\n // Position: below and to the right of the element, or above if not enough space\n let top = rect.bottom + 8;\n let left = rect.left;\n\n if (top + 300 > window.innerHeight) {\n top = rect.top - 308;\n }\n if (left + 340 > window.innerWidth) {\n left = window.innerWidth - 350;\n }\n if (top < 10) top = 10;\n if (left < 10) left = 10;\n\n this.container.style.top = `${top}px`;\n this.container.style.left = `${left}px`;\n this.container.style.display = 'block';\n\n this.container.innerHTML = `\n <div class=\"aa-form-header\">\n <div>\n <div class=\"aa-form-header-title\">New Annotation</div>\n <div class=\"aa-form-header-selector\">${this.escapeHtml(selector)}</div>\n </div>\n <button class=\"aa-form-close\" data-action=\"close\">&times;</button>\n </div>\n <div class=\"aa-form-body\">\n ${this.devMode ? '' : '<input class=\"aa-input\" type=\"text\" placeholder=\"Your name\" data-field=\"author\" value=\"\" />'}\n <textarea class=\"aa-textarea\" placeholder=\"What should be changed?\" data-field=\"text\"></textarea>\n <div class=\"aa-form-actions\">\n <button class=\"aa-btn aa-btn-secondary\" data-action=\"close\">Cancel</button>\n <button class=\"aa-btn aa-btn-primary\" data-action=\"submit\">Submit</button>\n </div>\n </div>\n `;\n\n // Focus textarea\n const textarea = this.container.querySelector('[data-field=\"text\"]') as HTMLTextAreaElement;\n setTimeout(() => textarea?.focus(), 50);\n\n // Event listeners\n this.container.querySelectorAll('[data-action=\"close\"]').forEach((btn) => {\n btn.addEventListener('click', () => this.hide());\n });\n\n const submitBtn = this.container.querySelector('[data-action=\"submit\"]') as HTMLButtonElement;\n submitBtn.addEventListener('click', () => {\n this.submit(selector, elementTag, elementText);\n });\n\n // Submit on Ctrl+Enter\n textarea.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n this.submit(selector, elementTag, elementText);\n }\n });\n }\n\n private async submit(selector: string, elementTag: string, elementText: string): Promise<void> {\n const text = (this.container.querySelector('[data-field=\"text\"]') as HTMLTextAreaElement)?.value?.trim();\n const author = this.devMode\n ? 'Developer'\n : (this.container.querySelector('[data-field=\"author\"]') as HTMLInputElement)?.value?.trim();\n\n if (!text) return;\n\n const submitBtn = this.container.querySelector('[data-action=\"submit\"]') as HTMLButtonElement;\n submitBtn.disabled = true;\n submitBtn.textContent = 'Saving...';\n\n const payload: CreateAnnotationPayload = {\n page: window.location.pathname,\n selector,\n elementTag,\n elementText,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n device: window.innerWidth < 768 ? 'mobile' : window.innerWidth < 1024 ? 'tablet' : 'desktop',\n text,\n author: author || 'Anonymous',\n };\n\n try {\n const res = await fetch(API_ANNOTATIONS, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) throw new Error('Failed to save');\n\n this.hide();\n this.onSubmitted();\n } catch (err) {\n submitBtn.disabled = false;\n submitBtn.textContent = 'Submit';\n console.error('[astro-annotate] Failed to save annotation:', err);\n }\n }\n\n hide(): void {\n if (this.container.style.display === 'none') return;\n this.container.style.display = 'none';\n this.container.innerHTML = '';\n this.onClosed();\n }\n\n isVisible(): boolean {\n return this.container.style.display !== 'none';\n }\n\n private escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n destroy(): void {\n this.container.remove();\n }\n}\n","import type { Annotation } from '../types.js';\nimport { API_ANNOTATIONS } from '../constants.js';\n\nexport class PinManager {\n private pins: HTMLElement[] = [];\n private detailPopup: HTMLElement;\n private onChanged: () => void;\n\n constructor(\n private shadowRoot: ShadowRoot,\n onChanged: () => void,\n ) {\n this.detailPopup = document.createElement('div');\n this.detailPopup.className = 'aa-pin-detail';\n this.detailPopup.style.display = 'none';\n this.shadowRoot.appendChild(this.detailPopup);\n this.onChanged = onChanged;\n }\n\n render(annotations: Annotation[]): void {\n this.clearPins();\n\n annotations.forEach((annotation, index) => {\n const el = document.querySelector(annotation.selector);\n if (!el) return;\n\n const pin = document.createElement('div');\n pin.className = `aa-pin${annotation.status === 'resolved' ? ' aa-resolved' : ''}`;\n pin.innerHTML = `<span class=\"aa-pin-number\">${index + 1}</span>`;\n\n // Position pin at top-right corner of element\n const updatePosition = () => {\n const rect = el.getBoundingClientRect();\n pin.style.position = 'fixed';\n pin.style.top = `${Math.max(0, rect.top - 10)}px`;\n pin.style.left = `${Math.max(0, rect.right - 24)}px`;\n };\n updatePosition();\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation();\n this.showDetail(annotation, index, el);\n });\n\n this.shadowRoot.appendChild(pin);\n this.pins.push(pin);\n\n // Update position on scroll/resize\n const observer = new IntersectionObserver(() => updatePosition(), { threshold: 0 });\n observer.observe(el);\n });\n }\n\n private showDetail(annotation: Annotation, index: number, el: Element): void {\n const rect = el.getBoundingClientRect();\n let top = rect.bottom + 8;\n let left = rect.left;\n\n if (top + 250 > window.innerHeight) {\n top = rect.top - 258;\n }\n if (left + 320 > window.innerWidth) {\n left = window.innerWidth - 330;\n }\n if (top < 10) top = 10;\n if (left < 10) left = 10;\n\n this.detailPopup.style.top = `${top}px`;\n this.detailPopup.style.left = `${left}px`;\n this.detailPopup.style.display = 'block';\n\n const date = new Date(annotation.timestamp).toLocaleString();\n\n this.detailPopup.innerHTML = `\n <div class=\"aa-pin-detail-header\">\n <div>\n <div class=\"aa-form-header-title\">#${index + 1} — ${this.escapeHtml(annotation.author)}</div>\n <div class=\"aa-pin-detail-meta\">${date} · ${annotation.device} · ${annotation.status}</div>\n </div>\n <button class=\"aa-form-close\" data-action=\"close-detail\">&times;</button>\n </div>\n <div class=\"aa-pin-detail-body\">\n <div class=\"aa-pin-detail-text\">${this.escapeHtml(annotation.text)}</div>\n <div class=\"aa-pin-detail-info\">\n <div class=\"aa-pin-detail-selector\">${this.escapeHtml(annotation.selector)}</div>\n </div>\n </div>\n <div class=\"aa-pin-detail-actions\">\n ${this.getStatusButtons(annotation)}\n </div>\n `;\n\n this.detailPopup.querySelector('[data-action=\"close-detail\"]')?.addEventListener('click', () => {\n this.hideDetail();\n });\n\n this.detailPopup.querySelectorAll('[data-status]').forEach((btn) => {\n btn.addEventListener('click', () => {\n const status = (btn as HTMLElement).dataset.status!;\n this.updateStatus(annotation.id, status as Annotation['status']);\n });\n });\n }\n\n private getStatusButtons(annotation: Annotation): string {\n if (annotation.status === 'open') {\n return `\n <button class=\"aa-status-btn aa-resolve\" data-status=\"resolved\">Done</button>\n `;\n }\n return `<button class=\"aa-status-btn aa-reopen\" data-status=\"open\">Reopen</button>`;\n }\n\n private async updateStatus(id: string, status: Annotation['status']): Promise<void> {\n try {\n const res = await fetch(`${API_ANNOTATIONS}/${id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ status }),\n });\n\n if (!res.ok) throw new Error('Failed to update');\n\n this.hideDetail();\n this.onChanged();\n } catch (err) {\n console.error('[astro-annotate] Failed to update annotation:', err);\n }\n }\n\n hideDetail(): void {\n this.detailPopup.style.display = 'none';\n }\n\n private clearPins(): void {\n this.pins.forEach((pin) => pin.remove());\n this.pins = [];\n this.hideDetail();\n }\n\n private escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n destroy(): void {\n this.clearPins();\n this.detailPopup.remove();\n }\n}\n","import type { Annotation } from '../types.js';\nimport { SHADOW_ROOT_ID, API_ANNOTATIONS } from '../constants.js';\nimport { OVERLAY_STYLES } from './styles.js';\nimport { Toolbar } from './toolbar.js';\nimport { Highlighter } from './highlighter.js';\nimport { AnnotationForm } from './form.js';\nimport { PinManager } from './pin.js';\n\nexport class Overlay {\n private host: HTMLElement;\n private shadowRoot: ShadowRoot;\n private toolbar: Toolbar;\n private highlighter: Highlighter;\n private form: AnnotationForm;\n private pinManager: PinManager;\n private active = false;\n private devMode = !!(window as any).__ASTRO_ANNOTATE_DEV__;\n private annotations: Annotation[] = [];\n\n constructor() {\n // Create host element\n this.host = document.createElement('div');\n this.host.id = SHADOW_ROOT_ID;\n document.body.appendChild(this.host);\n\n // Attach Shadow DOM\n this.shadowRoot = this.host.attachShadow({ mode: 'open' });\n\n // Inject styles\n const style = document.createElement('style');\n style.textContent = OVERLAY_STYLES;\n this.shadowRoot.appendChild(style);\n\n // Create components\n this.toolbar = new Toolbar(this.shadowRoot, (active) => this.setActive(active));\n this.highlighter = new Highlighter(this.shadowRoot);\n this.form = new AnnotationForm(\n this.shadowRoot,\n () => this.onAnnotationCreated(),\n () => this.onFormClosed(),\n this.devMode,\n );\n this.pinManager = new PinManager(this.shadowRoot, () => this.loadAnnotations());\n\n // Load existing annotations\n this.loadAnnotations();\n\n // Close detail popup when clicking outside\n document.addEventListener('click', (e) => {\n const target = e.target as Element;\n if (!this.host.contains(target)) {\n this.pinManager.hideDetail();\n }\n });\n\n // Update pin positions on scroll\n let scrollTimeout: ReturnType<typeof setTimeout>;\n window.addEventListener('scroll', () => {\n clearTimeout(scrollTimeout);\n scrollTimeout = setTimeout(() => this.renderPins(), 50);\n }, { passive: true });\n\n window.addEventListener('resize', () => {\n this.renderPins();\n }, { passive: true });\n\n // Keyboard shortcuts\n document.addEventListener('keydown', this.onKeyDown);\n }\n\n private setActive(active: boolean): void {\n this.active = active;\n\n if (active) {\n this.form.hide();\n this.pinManager.hideDetail();\n document.addEventListener('mousemove', this.highlighter.onMouseMove);\n document.addEventListener('click', this.onElementClick);\n } else {\n document.removeEventListener('mousemove', this.highlighter.onMouseMove);\n document.removeEventListener('click', this.onElementClick);\n this.highlighter.hide();\n this.form.hide();\n }\n }\n\n private onElementClick = (e: MouseEvent): void => {\n const target = e.target as Element;\n\n // Ignore clicks on our own overlay\n if (this.host.contains(target) || this.host === target) return;\n\n // Don't switch targets while form is open\n if (this.form.isVisible()) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Hide highlight and show form\n this.highlighter.hide();\n document.removeEventListener('mousemove', this.highlighter.onMouseMove);\n\n this.form.show(target);\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n // Don't intercept when user is typing in an external input\n const active = document.activeElement;\n const isExternalInput = active &&\n (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' ||\n (active as HTMLElement).isContentEditable) &&\n active !== this.host && !this.host.contains(active);\n\n // Alt+C: toggle annotation mode (Figma-inspired, e.code for layout-independence)\n if (e.altKey && e.code === 'KeyC' && !isExternalInput) {\n e.preventDefault();\n this.toolbar.toggle();\n return;\n }\n\n // Escape: close form or exit annotation mode\n if (e.key === 'Escape') {\n if (this.form.isVisible()) {\n this.form.hide();\n return;\n }\n if (this.active) {\n this.toolbar.deactivate();\n this.setActive(false);\n return;\n }\n }\n };\n\n private async loadAnnotations(): Promise<void> {\n try {\n const page = window.location.pathname;\n const res = await fetch(`${API_ANNOTATIONS}?page=${encodeURIComponent(page)}`);\n if (!res.ok) return;\n\n const data = await res.json();\n this.annotations = data.annotations || [];\n this.toolbar.updateCount(this.annotations.filter((a) => a.status === 'open').length);\n this.renderPins();\n } catch {\n // API not available yet, will retry\n }\n }\n\n private renderPins(): void {\n this.pinManager.render(this.annotations);\n }\n\n private onAnnotationCreated(): void {\n this.loadAnnotations();\n }\n\n private onFormClosed(): void {\n // Re-enable highlighting if still in annotation mode\n if (this.active) {\n document.addEventListener('mousemove', this.highlighter.onMouseMove);\n }\n }\n\n destroy(): void {\n document.removeEventListener('keydown', this.onKeyDown);\n this.setActive(false);\n this.toolbar.destroy();\n this.highlighter.destroy();\n this.form.destroy();\n this.pinManager.destroy();\n this.host.remove();\n }\n}\n","import { SHADOW_ROOT_ID } from '../constants.js';\nimport { Overlay } from './overlay.js';\n\nlet overlay: Overlay | null = null;\n\nfunction init(): void {\n // Clean up previous instance (View Transitions)\n if (overlay) {\n overlay.destroy();\n overlay = null;\n }\n\n overlay = new Overlay();\n}\n\n// Support View Transitions (SPA navigation)\ndocument.addEventListener('astro:page-load', init);\n\n// Fallback for non-View-Transitions pages\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => {\n // Only init if astro:page-load hasn't fired yet\n if (!document.getElementById(SHADOW_ROOT_ID)) {\n init();\n }\n });\n} else {\n if (!document.getElementById(SHADOW_ROOT_ID)) {\n init();\n }\n}\n"],"mappings":";;;;;;AAAO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["sel"]}
1
+ {"version":3,"sources":["../src/client/styles.ts","../src/client/selector.ts","../src/client/highlighter.ts","../src/client/form.ts","../src/client/pin.ts","../src/client/overlay.ts","../src/client/index.ts"],"sourcesContent":["export const OVERLAY_STYLES = `\n :host {\n all: initial;\n position: fixed;\n z-index: 2147483647;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #1a1a2e;\n }\n\n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* Element Highlight */\n .aa-highlight {\n position: fixed;\n pointer-events: none;\n border: 2px solid #e94560;\n background: rgba(233, 69, 96, 0.08);\n border-radius: 3px;\n z-index: 2147483646;\n transition: all 0.1s ease;\n }\n\n .aa-highlight-label {\n position: absolute;\n top: -26px;\n left: -2px;\n background: #e94560;\n color: #fff;\n font-size: 11px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 3px 3px 0 0;\n white-space: nowrap;\n max-width: 300px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n /* Annotation Form */\n .aa-form-container {\n position: fixed;\n z-index: 2147483647;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n width: 340px;\n overflow: hidden;\n }\n\n .aa-form-header {\n background: #1a1a2e;\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .aa-form-header-title {\n font-size: 13px;\n font-weight: 600;\n }\n\n .aa-form-header-selector {\n font-size: 11px;\n opacity: 0.7;\n font-family: 'SF Mono', Monaco, monospace;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .aa-form-close {\n background: none;\n border: none;\n color: #fff;\n cursor: pointer;\n font-size: 18px;\n line-height: 1;\n opacity: 0.7;\n padding: 0 0 0 8px;\n }\n\n .aa-form-close:hover {\n opacity: 1;\n }\n\n .aa-form-body {\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .aa-input, .aa-textarea {\n width: 100%;\n padding: 8px 12px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 13px;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s;\n }\n\n .aa-input:focus, .aa-textarea:focus {\n border-color: #e94560;\n }\n\n .aa-textarea {\n resize: vertical;\n min-height: 80px;\n }\n\n .aa-form-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .aa-btn {\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border: none;\n transition: all 0.15s;\n }\n\n .aa-btn-primary {\n background: #e94560;\n color: #fff;\n }\n\n .aa-btn-primary:hover {\n background: #c73e54;\n }\n\n .aa-btn-primary:disabled {\n background: #ccc;\n cursor: not-allowed;\n }\n\n .aa-btn-secondary {\n background: #f0f0f0;\n color: #333;\n }\n\n .aa-btn-secondary:hover {\n background: #e0e0e0;\n }\n\n /* Pins */\n .aa-pin {\n position: absolute;\n width: 28px;\n height: 28px;\n border-radius: 50% 50% 50% 0;\n background: #e94560;\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n transform: rotate(-45deg);\n box-shadow: 0 2px 8px rgba(233, 69, 96, 0.4);\n transition: transform 0.15s, box-shadow 0.15s;\n z-index: 2147483645;\n }\n\n .aa-pin:hover {\n transform: rotate(-45deg) scale(1.15);\n box-shadow: 0 4px 12px rgba(233, 69, 96, 0.5);\n }\n\n .aa-pin.aa-resolved {\n background: #2ecc71;\n box-shadow: 0 2px 8px rgba(46, 204, 113, 0.4);\n }\n\n .aa-pin-number {\n transform: rotate(45deg);\n }\n\n /* Pin Detail Popup */\n .aa-pin-detail {\n position: fixed;\n z-index: 2147483647;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n width: 320px;\n overflow: hidden;\n }\n\n .aa-pin-detail-header {\n background: #1a1a2e;\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .aa-pin-detail-meta {\n font-size: 11px;\n opacity: 0.7;\n }\n\n .aa-pin-detail-body {\n padding: 16px;\n }\n\n .aa-pin-detail-text {\n font-size: 14px;\n margin-bottom: 12px;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .aa-pin-detail-info {\n font-size: 12px;\n color: #888;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .aa-pin-detail-selector {\n font-family: 'SF Mono', Monaco, monospace;\n font-size: 11px;\n color: #666;\n background: #f5f5f5;\n padding: 4px 8px;\n border-radius: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .aa-pin-detail-actions {\n padding: 0 16px 16px;\n display: flex;\n gap: 8px;\n }\n\n .aa-status-btn {\n padding: 4px 12px;\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid #e0e0e0;\n background: #fff;\n transition: all 0.15s;\n }\n\n .aa-status-btn:hover {\n background: #f5f5f5;\n }\n\n .aa-status-btn.aa-resolve {\n color: #2ecc71;\n border-color: #2ecc71;\n }\n\n .aa-status-btn.aa-resolve:hover {\n background: #2ecc71;\n color: #fff;\n }\n\n .aa-status-btn.aa-reopen {\n color: #e94560;\n border-color: #e94560;\n }\n\n .aa-status-btn.aa-reopen:hover {\n background: #e94560;\n color: #fff;\n }\n\n /* Dark mode */\n @media (prefers-color-scheme: dark) {\n .aa-form-container, .aa-pin-detail {\n background: #2d2d3f;\n color: #e0e0e0;\n }\n\n .aa-input, .aa-textarea {\n background: #1a1a2e;\n border-color: #404060;\n color: #e0e0e0;\n }\n\n .aa-btn-secondary {\n background: #404060;\n color: #e0e0e0;\n }\n\n .aa-btn-secondary:hover {\n background: #505070;\n }\n\n .aa-pin-detail-selector {\n background: #1a1a2e;\n color: #aaa;\n }\n\n .aa-status-btn {\n background: #2d2d3f;\n border-color: #404060;\n }\n\n .aa-status-btn:hover {\n background: #404060;\n }\n }\n`;\n","const ASTRO_CLASS_RE = /^astro-[a-zA-Z0-9]+$/;\nconst IGNORED_TAGS = new Set(['html', 'body', 'head']);\n\nfunction isAstroClass(cls: string): boolean {\n return ASTRO_CLASS_RE.test(cls);\n}\n\nfunction getStableClasses(el: Element): string[] {\n return Array.from(el.classList).filter((cls) => !isAstroClass(cls));\n}\n\nfunction isUnique(selector: string): boolean {\n try {\n return document.querySelectorAll(selector).length === 1;\n } catch {\n return false;\n }\n}\n\nfunction escapeSelector(value: string): string {\n return CSS.escape(value);\n}\n\nexport function generateSelector(target: Element): string {\n if (IGNORED_TAGS.has(target.tagName.toLowerCase())) {\n return target.tagName.toLowerCase();\n }\n\n // 1. ID (if stable — not auto-generated)\n if (target.id && !/^[0-9]/.test(target.id) && !target.id.includes(':')) {\n const sel = `#${escapeSelector(target.id)}`;\n if (isUnique(sel)) return sel;\n }\n\n // 2. data-testid\n const testId = target.getAttribute('data-testid');\n if (testId) {\n const sel = `[data-testid=\"${escapeSelector(testId)}\"]`;\n if (isUnique(sel)) return sel;\n }\n\n // 3. Tag + stable classes\n const tag = target.tagName.toLowerCase();\n const classes = getStableClasses(target);\n if (classes.length > 0) {\n const classSel = classes.map((c) => `.${escapeSelector(c)}`).join('');\n const sel = `${tag}${classSel}`;\n if (isUnique(sel)) return sel;\n }\n\n // 4. Build path upward with nth-child\n const path: string[] = [];\n let current: Element | null = target;\n\n while (current && !IGNORED_TAGS.has(current.tagName.toLowerCase())) {\n let segment = current.tagName.toLowerCase();\n\n // Try id first\n if (current.id && !/^[0-9]/.test(current.id) && !current.id.includes(':')) {\n segment = `#${escapeSelector(current.id)}`;\n path.unshift(segment);\n const sel = path.join(' > ');\n if (isUnique(sel)) return sel;\n // ID should be unique, break here\n break;\n }\n\n // Try tag + classes\n const cls = getStableClasses(current);\n if (cls.length > 0) {\n segment += cls.map((c) => `.${escapeSelector(c)}`).join('');\n }\n\n // Add nth-child if not unique enough\n const parent: Element | null = current.parentElement;\n if (parent) {\n const currentTag = current.tagName;\n const siblings = Array.from(parent.children).filter(\n (s: Element) => s.tagName === currentTag,\n );\n if (siblings.length > 1) {\n const index = siblings.indexOf(current) + 1;\n segment += `:nth-child(${index})`;\n }\n }\n\n path.unshift(segment);\n const sel = path.join(' > ');\n if (isUnique(sel)) return sel;\n\n current = parent;\n }\n\n return path.join(' > ') || tag;\n}\n\nexport function getElementLabel(el: Element): string {\n const tag = el.tagName.toLowerCase();\n const classes = getStableClasses(el);\n const classStr = classes.length > 0 ? `.${classes.slice(0, 3).join('.')}` : '';\n return `<${tag}${classStr}>`;\n}\n\nexport function getElementText(el: Element): string {\n const text = el.textContent?.trim() || '';\n return text.slice(0, 200);\n}\n","import { SHADOW_ROOT_ID } from '../constants.js';\nimport { getElementLabel } from './selector.js';\n\nexport class Highlighter {\n private highlight: HTMLElement;\n private label: HTMLElement;\n private currentTarget: Element | null = null;\n\n constructor(private shadowRoot: ShadowRoot) {\n this.highlight = document.createElement('div');\n this.highlight.className = 'aa-highlight';\n this.highlight.style.display = 'none';\n\n this.label = document.createElement('div');\n this.label.className = 'aa-highlight-label';\n this.highlight.appendChild(this.label);\n\n this.shadowRoot.appendChild(this.highlight);\n }\n\n private isOwnElement(el: Element): boolean {\n const root = document.getElementById(SHADOW_ROOT_ID);\n return root !== null && (root === el || root.contains(el));\n }\n\n onMouseMove = (e: MouseEvent): void => {\n const target = e.target as Element;\n\n if (!target || this.isOwnElement(target)) {\n this.hide();\n return;\n }\n\n if (target === this.currentTarget) return;\n this.currentTarget = target;\n\n const rect = target.getBoundingClientRect();\n this.highlight.style.display = 'block';\n this.highlight.style.top = `${rect.top}px`;\n this.highlight.style.left = `${rect.left}px`;\n this.highlight.style.width = `${rect.width}px`;\n this.highlight.style.height = `${rect.height}px`;\n this.label.textContent = getElementLabel(target);\n };\n\n hide(): void {\n this.highlight.style.display = 'none';\n this.currentTarget = null;\n }\n\n getTarget(): Element | null {\n return this.currentTarget;\n }\n\n destroy(): void {\n this.highlight.remove();\n }\n}\n","import type { CreateAnnotationPayload } from '../types.js';\nimport { API_ANNOTATIONS } from '../constants.js';\nimport { generateSelector, getElementText } from './selector.js';\n\nexport class AnnotationForm {\n private container: HTMLElement;\n private onSubmitted: () => void;\n private onClosed: () => void;\n\n constructor(\n private shadowRoot: ShadowRoot,\n onSubmitted: () => void,\n onClosed: () => void,\n private devMode: boolean,\n ) {\n this.container = document.createElement('div');\n this.container.className = 'aa-form-container';\n this.container.style.display = 'none';\n this.shadowRoot.appendChild(this.container);\n this.onSubmitted = onSubmitted;\n this.onClosed = onClosed;\n }\n\n show(target: Element): void {\n const selector = generateSelector(target);\n const elementTag = target.tagName.toLowerCase();\n const elementText = getElementText(target);\n const rect = target.getBoundingClientRect();\n\n // Position: below and to the right of the element, or above if not enough space\n let top = rect.bottom + 8;\n let left = rect.left;\n\n if (top + 300 > window.innerHeight) {\n top = rect.top - 308;\n }\n if (left + 340 > window.innerWidth) {\n left = window.innerWidth - 350;\n }\n if (top < 10) top = 10;\n if (left < 10) left = 10;\n\n this.container.style.top = `${top}px`;\n this.container.style.left = `${left}px`;\n this.container.style.display = 'block';\n\n this.container.innerHTML = `\n <div class=\"aa-form-header\">\n <div>\n <div class=\"aa-form-header-title\">New Annotation</div>\n <div class=\"aa-form-header-selector\">${this.escapeHtml(selector)}</div>\n </div>\n <button class=\"aa-form-close\" data-action=\"close\">&times;</button>\n </div>\n <div class=\"aa-form-body\">\n ${this.devMode ? '' : '<input class=\"aa-input\" type=\"text\" placeholder=\"Your name\" data-field=\"author\" value=\"\" />'}\n <textarea class=\"aa-textarea\" placeholder=\"What should be changed?\" data-field=\"text\"></textarea>\n <div class=\"aa-form-actions\">\n <button class=\"aa-btn aa-btn-secondary\" data-action=\"close\">Cancel</button>\n <button class=\"aa-btn aa-btn-primary\" data-action=\"submit\">Submit</button>\n </div>\n </div>\n `;\n\n // Focus textarea\n const textarea = this.container.querySelector('[data-field=\"text\"]') as HTMLTextAreaElement;\n setTimeout(() => textarea?.focus(), 50);\n\n // Event listeners\n this.container.querySelectorAll('[data-action=\"close\"]').forEach((btn) => {\n btn.addEventListener('click', () => this.hide());\n });\n\n const submitBtn = this.container.querySelector('[data-action=\"submit\"]') as HTMLButtonElement;\n submitBtn.addEventListener('click', () => {\n this.submit(selector, elementTag, elementText);\n });\n\n // Submit on Ctrl+Enter\n textarea.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n this.submit(selector, elementTag, elementText);\n }\n });\n }\n\n private async submit(selector: string, elementTag: string, elementText: string): Promise<void> {\n const text = (this.container.querySelector('[data-field=\"text\"]') as HTMLTextAreaElement)?.value?.trim();\n const author = this.devMode\n ? 'Developer'\n : (this.container.querySelector('[data-field=\"author\"]') as HTMLInputElement)?.value?.trim();\n\n if (!text) return;\n\n const submitBtn = this.container.querySelector('[data-action=\"submit\"]') as HTMLButtonElement;\n submitBtn.disabled = true;\n submitBtn.textContent = 'Saving...';\n\n const payload: CreateAnnotationPayload = {\n page: window.location.pathname,\n selector,\n elementTag,\n elementText,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n device: window.innerWidth < 768 ? 'mobile' : window.innerWidth < 1024 ? 'tablet' : 'desktop',\n text,\n author: author || 'Anonymous',\n };\n\n try {\n const res = await fetch(API_ANNOTATIONS, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) throw new Error('Failed to save');\n\n this.hide();\n this.onSubmitted();\n } catch (err) {\n submitBtn.disabled = false;\n submitBtn.textContent = 'Submit';\n console.error('[astro-annotate] Failed to save annotation:', err);\n }\n }\n\n hide(): void {\n if (this.container.style.display === 'none') return;\n this.container.style.display = 'none';\n this.container.innerHTML = '';\n this.onClosed();\n }\n\n isVisible(): boolean {\n return this.container.style.display !== 'none';\n }\n\n private escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n destroy(): void {\n this.container.remove();\n }\n}\n","import type { Annotation } from '../types.js';\nimport { API_ANNOTATIONS } from '../constants.js';\n\nexport class PinManager {\n private pins: HTMLElement[] = [];\n private detailPopup: HTMLElement;\n private onChanged: () => void;\n\n constructor(\n private shadowRoot: ShadowRoot,\n onChanged: () => void,\n ) {\n this.detailPopup = document.createElement('div');\n this.detailPopup.className = 'aa-pin-detail';\n this.detailPopup.style.display = 'none';\n this.shadowRoot.appendChild(this.detailPopup);\n this.onChanged = onChanged;\n }\n\n render(annotations: Annotation[]): void {\n this.clearPins();\n\n annotations.forEach((annotation, index) => {\n const el = document.querySelector(annotation.selector);\n if (!el) return;\n\n const pin = document.createElement('div');\n pin.className = `aa-pin${annotation.status === 'resolved' ? ' aa-resolved' : ''}`;\n pin.innerHTML = `<span class=\"aa-pin-number\">${index + 1}</span>`;\n\n // Position pin at top-right corner of element\n const updatePosition = () => {\n const rect = el.getBoundingClientRect();\n pin.style.position = 'fixed';\n pin.style.top = `${Math.max(0, rect.top - 10)}px`;\n pin.style.left = `${Math.max(0, rect.right - 24)}px`;\n };\n updatePosition();\n\n pin.addEventListener('click', (e) => {\n e.stopPropagation();\n this.showDetail(annotation, index, el);\n });\n\n this.shadowRoot.appendChild(pin);\n this.pins.push(pin);\n\n // Update position on scroll/resize\n const observer = new IntersectionObserver(() => updatePosition(), { threshold: 0 });\n observer.observe(el);\n });\n }\n\n private showDetail(annotation: Annotation, index: number, el: Element): void {\n const rect = el.getBoundingClientRect();\n let top = rect.bottom + 8;\n let left = rect.left;\n\n if (top + 250 > window.innerHeight) {\n top = rect.top - 258;\n }\n if (left + 320 > window.innerWidth) {\n left = window.innerWidth - 330;\n }\n if (top < 10) top = 10;\n if (left < 10) left = 10;\n\n this.detailPopup.style.top = `${top}px`;\n this.detailPopup.style.left = `${left}px`;\n this.detailPopup.style.display = 'block';\n\n const date = new Date(annotation.timestamp).toLocaleString();\n\n this.detailPopup.innerHTML = `\n <div class=\"aa-pin-detail-header\">\n <div>\n <div class=\"aa-form-header-title\">#${index + 1} — ${this.escapeHtml(annotation.author)}</div>\n <div class=\"aa-pin-detail-meta\">${date} · ${annotation.device} · ${annotation.status}</div>\n </div>\n <button class=\"aa-form-close\" data-action=\"close-detail\">&times;</button>\n </div>\n <div class=\"aa-pin-detail-body\">\n <div class=\"aa-pin-detail-text\">${this.escapeHtml(annotation.text)}</div>\n <div class=\"aa-pin-detail-info\">\n <div class=\"aa-pin-detail-selector\">${this.escapeHtml(annotation.selector)}</div>\n </div>\n </div>\n <div class=\"aa-pin-detail-actions\">\n ${this.getStatusButtons(annotation)}\n </div>\n `;\n\n this.detailPopup.querySelector('[data-action=\"close-detail\"]')?.addEventListener('click', () => {\n this.hideDetail();\n });\n\n this.detailPopup.querySelectorAll('[data-status]').forEach((btn) => {\n btn.addEventListener('click', () => {\n const status = (btn as HTMLElement).dataset.status!;\n this.updateStatus(annotation.id, status as Annotation['status']);\n });\n });\n }\n\n private getStatusButtons(annotation: Annotation): string {\n if (annotation.status === 'open') {\n return `\n <button class=\"aa-status-btn aa-resolve\" data-status=\"resolved\">Done</button>\n `;\n }\n return `<button class=\"aa-status-btn aa-reopen\" data-status=\"open\">Reopen</button>`;\n }\n\n private async updateStatus(id: string, status: Annotation['status']): Promise<void> {\n try {\n const res = await fetch(`${API_ANNOTATIONS}/${id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ status }),\n });\n\n if (!res.ok) throw new Error('Failed to update');\n\n this.hideDetail();\n this.onChanged();\n } catch (err) {\n console.error('[astro-annotate] Failed to update annotation:', err);\n }\n }\n\n hideDetail(): void {\n this.detailPopup.style.display = 'none';\n }\n\n private clearPins(): void {\n this.pins.forEach((pin) => pin.remove());\n this.pins = [];\n this.hideDetail();\n }\n\n private escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n destroy(): void {\n this.clearPins();\n this.detailPopup.remove();\n }\n}\n","import type { Annotation } from '../types.js';\nimport { SHADOW_ROOT_ID, API_ANNOTATIONS } from '../constants.js';\nimport { OVERLAY_STYLES } from './styles.js';\nimport { Highlighter } from './highlighter.js';\nimport { AnnotationForm } from './form.js';\nimport { PinManager } from './pin.js';\n\nexport class Overlay {\n private host: HTMLElement;\n private shadowRoot: ShadowRoot;\n private highlighter: Highlighter;\n private form: AnnotationForm;\n private pinManager: PinManager;\n private active = false;\n private devMode = !!(window as any).__ASTRO_ANNOTATE_DEV__;\n private annotations: Annotation[] = [];\n\n constructor() {\n // Create host element\n this.host = document.createElement('div');\n this.host.id = SHADOW_ROOT_ID;\n document.body.appendChild(this.host);\n\n // Attach Shadow DOM\n this.shadowRoot = this.host.attachShadow({ mode: 'open' });\n\n // Inject styles\n const style = document.createElement('style');\n style.textContent = OVERLAY_STYLES;\n this.shadowRoot.appendChild(style);\n\n // Create components\n this.highlighter = new Highlighter(this.shadowRoot);\n this.form = new AnnotationForm(\n this.shadowRoot,\n () => this.onAnnotationCreated(),\n () => this.onFormClosed(),\n this.devMode,\n );\n this.pinManager = new PinManager(this.shadowRoot, () => this.loadAnnotations());\n\n // Listen for toggle from Dev Toolbar\n window.addEventListener('aa:toggle', this.onToolbarToggle);\n\n // Load existing annotations\n this.loadAnnotations();\n\n // Close detail popup when clicking outside\n document.addEventListener('click', (e) => {\n const target = e.target as Element;\n if (!this.host.contains(target)) {\n this.pinManager.hideDetail();\n }\n });\n\n // Update pin positions on scroll\n let scrollTimeout: ReturnType<typeof setTimeout>;\n window.addEventListener('scroll', () => {\n clearTimeout(scrollTimeout);\n scrollTimeout = setTimeout(() => this.renderPins(), 50);\n }, { passive: true });\n\n window.addEventListener('resize', () => {\n this.renderPins();\n }, { passive: true });\n\n // Keyboard shortcuts\n document.addEventListener('keydown', this.onKeyDown);\n }\n\n private setActive(active: boolean): void {\n this.active = active;\n\n if (active) {\n this.form.hide();\n this.pinManager.hideDetail();\n document.addEventListener('mousemove', this.highlighter.onMouseMove);\n document.addEventListener('click', this.onElementClick);\n } else {\n document.removeEventListener('mousemove', this.highlighter.onMouseMove);\n document.removeEventListener('click', this.onElementClick);\n this.highlighter.hide();\n this.form.hide();\n }\n }\n\n private onToolbarToggle = ((e: CustomEvent) => {\n this.setActive(e.detail.active);\n }) as EventListener;\n\n private onElementClick = (e: MouseEvent): void => {\n const target = e.target as Element;\n\n // Ignore clicks on our own overlay\n if (this.host.contains(target) || this.host === target) return;\n\n // Don't switch targets while form is open\n if (this.form.isVisible()) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Hide highlight and show form\n this.highlighter.hide();\n document.removeEventListener('mousemove', this.highlighter.onMouseMove);\n\n this.form.show(target);\n };\n\n private onKeyDown = (e: KeyboardEvent): void => {\n // Don't intercept when user is typing in an external input\n const active = document.activeElement;\n const isExternalInput = active &&\n (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' ||\n (active as HTMLElement).isContentEditable) &&\n active !== this.host && !this.host.contains(active);\n\n // Alt+C: toggle annotation mode (Figma-inspired, e.code for layout-independence)\n if (e.altKey && e.code === 'KeyC' && !isExternalInput) {\n e.preventDefault();\n const newActive = !this.active;\n this.setActive(newActive);\n window.dispatchEvent(new CustomEvent('aa:state-changed', { detail: { active: newActive } }));\n return;\n }\n\n // Escape: close form or exit annotation mode\n if (e.key === 'Escape') {\n if (this.form.isVisible()) {\n this.form.hide();\n return;\n }\n if (this.active) {\n this.setActive(false);\n window.dispatchEvent(new CustomEvent('aa:state-changed', { detail: { active: false } }));\n return;\n }\n }\n };\n\n private async loadAnnotations(): Promise<void> {\n try {\n const page = window.location.pathname;\n const res = await fetch(`${API_ANNOTATIONS}?page=${encodeURIComponent(page)}`);\n if (!res.ok) return;\n\n const data = await res.json();\n this.annotations = data.annotations || [];\n const openCount = this.annotations.filter((a) => a.status === 'open').length;\n window.dispatchEvent(new CustomEvent('aa:count', { detail: { count: openCount } }));\n this.renderPins();\n } catch {\n // API not available yet, will retry\n }\n }\n\n private renderPins(): void {\n this.pinManager.render(this.annotations);\n }\n\n private onAnnotationCreated(): void {\n this.loadAnnotations();\n }\n\n private onFormClosed(): void {\n // Re-enable highlighting if still in annotation mode\n if (this.active) {\n document.addEventListener('mousemove', this.highlighter.onMouseMove);\n }\n }\n\n destroy(): void {\n document.removeEventListener('keydown', this.onKeyDown);\n window.removeEventListener('aa:toggle', this.onToolbarToggle);\n if (this.active) {\n window.dispatchEvent(new CustomEvent('aa:state-changed', { detail: { active: false } }));\n }\n this.setActive(false);\n this.highlighter.destroy();\n this.form.destroy();\n this.pinManager.destroy();\n this.host.remove();\n }\n}\n","import { SHADOW_ROOT_ID } from '../constants.js';\nimport { Overlay } from './overlay.js';\n\nlet overlay: Overlay | null = null;\n\nfunction init(): void {\n // Clean up previous instance (View Transitions)\n if (overlay) {\n overlay.destroy();\n overlay = null;\n }\n\n overlay = new Overlay();\n}\n\n// Support View Transitions (SPA navigation)\ndocument.addEventListener('astro:page-load', init);\n\n// Fallback for non-View-Transitions pages\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => {\n // Only init if astro:page-load hasn't fired yet\n if (!document.getElementById(SHADOW_ROOT_ID)) {\n init();\n }\n });\n} else {\n if (!document.getElementById(SHADOW_ROOT_ID)) {\n init();\n }\n}\n"],"mappings":";;;;;;AAAO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["sel"]}
package/dist/index.js CHANGED
@@ -184,13 +184,14 @@ function registerDevMiddleware(server, storage) {
184
184
  }
185
185
 
186
186
  // src/integration/index.ts
187
+ var ANNOTATE_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="12" y1="6" x2="12" y2="12"/></svg>`;
187
188
  function createIntegration(userConfig = {}) {
188
189
  let resolvedConfig;
189
190
  let projectRoot;
190
191
  return {
191
192
  name: "astro-annotate",
192
193
  hooks: {
193
- "astro:config:setup"({ config, command, updateConfig, injectScript, logger }) {
194
+ "astro:config:setup"({ config, command, updateConfig, injectScript, addDevToolbarApp, logger }) {
194
195
  projectRoot = fileURLToPath(config.root);
195
196
  const isDev = command === "dev" || command === "preview";
196
197
  const enabled = userConfig.enabled ?? isDev;
@@ -209,6 +210,15 @@ function createIntegration(userConfig = {}) {
209
210
  plugins: [astroAnnotateVitePlugin(resolvedConfig)]
210
211
  }
211
212
  });
213
+ if (isDev) {
214
+ const toolbarAppPath = resolve2(dirname(fileURLToPath(import.meta.url)), "toolbar-app.js");
215
+ addDevToolbarApp({
216
+ id: "astro-annotate",
217
+ name: "Annotate",
218
+ icon: ANNOTATE_ICON,
219
+ entrypoint: toolbarAppPath
220
+ });
221
+ }
212
222
  const clientPath = resolve2(dirname(fileURLToPath(import.meta.url)), "client.js");
213
223
  injectScript("page", `window.__ASTRO_ANNOTATE_DEV__=${isDev};import("${clientPath}");`);
214
224
  logger.info("Overlay enabled");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/integration/index.ts","../src/integration/vite-plugin.ts","../src/storage/local.ts","../src/server/api-handlers.ts","../src/server/dev-middleware.ts","../src/index.ts"],"sourcesContent":["import type { AstroIntegration } from 'astro';\nimport { fileURLToPath } from 'node:url';\nimport { resolve, dirname } from 'node:path';\nimport type { AstroAnnotateConfig, ResolvedConfig } from '../types.js';\nimport { DEFAULT_ANNOTATIONS_PATH } from '../constants.js';\nimport { astroAnnotateVitePlugin } from './vite-plugin.js';\nimport { LocalStorage } from '../storage/local.js';\nimport { registerDevMiddleware } from '../server/dev-middleware.js';\n\nexport function createIntegration(userConfig: AstroAnnotateConfig = {}): AstroIntegration {\n let resolvedConfig: ResolvedConfig;\n let projectRoot: string;\n\n return {\n name: 'astro-annotate',\n hooks: {\n 'astro:config:setup'({ config, command, updateConfig, injectScript, logger }) {\n projectRoot = fileURLToPath(config.root);\n\n const isDev = command === 'dev' || command === 'preview';\n const enabled = userConfig.enabled ?? isDev;\n\n if (!enabled) {\n logger.info('Disabled (set enabled: true to force)');\n return;\n }\n\n resolvedConfig = {\n enabled,\n mode: isDev ? 'dev' : 'deployed',\n storage: userConfig.storage ?? 'local',\n annotationsPath: userConfig.annotationsPath ?? DEFAULT_ANNOTATIONS_PATH,\n };\n\n // Add Vite plugin for virtual module\n updateConfig({\n vite: {\n plugins: [astroAnnotateVitePlugin(resolvedConfig)],\n },\n });\n\n // Inject client overlay script\n // At runtime, import.meta.url points to dist/index.js, client bundle is dist/client.js\n const clientPath = resolve(dirname(fileURLToPath(import.meta.url)), 'client.js');\n injectScript('page', `window.__ASTRO_ANNOTATE_DEV__=${isDev};import(\"${clientPath}\");`);\n\n logger.info('Overlay enabled');\n },\n\n 'astro:server:setup'({ server, logger }) {\n if (!resolvedConfig) return;\n\n const storage = new LocalStorage(resolvedConfig.annotationsPath, projectRoot);\n registerDevMiddleware(server, storage);\n logger.info('Dev middleware registered');\n },\n },\n };\n}\n","import type { Plugin } from 'vite';\nimport type { ResolvedConfig } from '../types.js';\nimport { VIRTUAL_MODULE_ID, RESOLVED_VIRTUAL_MODULE_ID } from '../constants.js';\n\nexport function astroAnnotateVitePlugin(config: ResolvedConfig): Plugin {\n return {\n name: 'astro-annotate-config',\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n load(id) {\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\n return `export default ${JSON.stringify(config)};`;\n }\n },\n };\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport type { Annotation, AnnotationsFile, CreateAnnotationPayload, UpdateAnnotationPayload } from '../types.js';\nimport type { AnnotationStorage } from './interface.js';\nimport { ANNOTATIONS_VERSION } from '../constants.js';\n\nfunction generateId(): string {\n return 'a_' + randomBytes(4).toString('hex');\n}\n\nfunction detectDevice(width: number): 'mobile' | 'tablet' | 'desktop' {\n if (width < 768) return 'mobile';\n if (width < 1024) return 'tablet';\n return 'desktop';\n}\n\nexport class LocalStorage implements AnnotationStorage {\n private filePath: string;\n\n constructor(annotationsPath: string, projectRoot: string) {\n this.filePath = resolve(projectRoot, annotationsPath);\n }\n\n private read(): AnnotationsFile {\n if (!existsSync(this.filePath)) {\n return { version: ANNOTATIONS_VERSION, annotations: [] };\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as AnnotationsFile;\n }\n\n private write(data: AnnotationsFile): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2) + '\\n', 'utf-8');\n }\n\n async list(page?: string): Promise<Annotation[]> {\n const data = this.read();\n if (page) {\n return data.annotations.filter((a) => a.page === page);\n }\n return data.annotations;\n }\n\n async create(payload: CreateAnnotationPayload): Promise<Annotation> {\n const data = this.read();\n const annotation: Annotation = {\n id: generateId(),\n timestamp: new Date().toISOString(),\n page: payload.page,\n selector: payload.selector,\n elementTag: payload.elementTag,\n elementText: payload.elementText.slice(0, 200),\n viewport: payload.viewport,\n device: payload.device || detectDevice(payload.viewport.width),\n text: payload.text,\n author: payload.author || 'Anonymous',\n status: 'open',\n };\n data.annotations.push(annotation);\n this.write(data);\n return annotation;\n }\n\n async update(id: string, payload: UpdateAnnotationPayload): Promise<Annotation | null> {\n const data = this.read();\n const index = data.annotations.findIndex((a) => a.id === id);\n if (index === -1) return null;\n\n if (payload.status) data.annotations[index].status = payload.status;\n if (payload.text !== undefined) data.annotations[index].text = payload.text;\n\n this.write(data);\n return data.annotations[index];\n }\n\n async delete(id: string): Promise<boolean> {\n const data = this.read();\n const before = data.annotations.length;\n data.annotations = data.annotations.filter((a) => a.id !== id);\n if (data.annotations.length === before) return false;\n this.write(data);\n return true;\n }\n}\n","import type { AnnotationStorage } from '../storage/interface.js';\nimport type { CreateAnnotationPayload, UpdateAnnotationPayload } from '../types.js';\n\nfunction json(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nexport async function handleListAnnotations(\n storage: AnnotationStorage,\n url: URL,\n): Promise<Response> {\n const page = url.searchParams.get('page') || undefined;\n const annotations = await storage.list(page);\n return json({ annotations });\n}\n\nexport async function handleCreateAnnotation(\n storage: AnnotationStorage,\n body: unknown,\n): Promise<Response> {\n const payload = body as CreateAnnotationPayload;\n\n if (!payload.selector || !payload.text || !payload.page) {\n return json({ error: 'Missing required fields: selector, text, page' }, 400);\n }\n\n const annotation = await storage.create(payload);\n return json({ annotation }, 201);\n}\n\nexport async function handleUpdateAnnotation(\n storage: AnnotationStorage,\n id: string,\n body: unknown,\n): Promise<Response> {\n const payload = body as UpdateAnnotationPayload;\n const annotation = await storage.update(id, payload);\n\n if (!annotation) {\n return json({ error: 'Annotation not found' }, 404);\n }\n\n return json({ annotation });\n}\n\nexport async function handleDeleteAnnotation(\n storage: AnnotationStorage,\n id: string,\n): Promise<Response> {\n const deleted = await storage.delete(id);\n\n if (!deleted) {\n return json({ error: 'Annotation not found' }, 404);\n }\n\n return json({ success: true });\n}\n","import type { ViteDevServer, Connect } from 'vite';\nimport type { AnnotationStorage } from '../storage/interface.js';\nimport { API_ANNOTATIONS } from '../constants.js';\nimport {\n handleListAnnotations,\n handleCreateAnnotation,\n handleUpdateAnnotation,\n handleDeleteAnnotation,\n} from './api-handlers.js';\n\nfunction collectBody(req: Connect.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nexport function registerDevMiddleware(server: ViteDevServer, storage: AnnotationStorage): void {\n server.middlewares.use(async (req, res, next) => {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n\n if (!url.pathname.startsWith(API_ANNOTATIONS)) {\n return next();\n }\n\n try {\n let response: Response;\n\n // POST /api/astro-annotate/annotations\n if (url.pathname === API_ANNOTATIONS && req.method === 'POST') {\n const raw = await collectBody(req);\n const body = JSON.parse(raw);\n response = await handleCreateAnnotation(storage, body);\n }\n // GET /api/astro-annotate/annotations\n else if (url.pathname === API_ANNOTATIONS && req.method === 'GET') {\n response = await handleListAnnotations(storage, url);\n }\n // PATCH /api/astro-annotate/annotations/:id\n else if (url.pathname.startsWith(API_ANNOTATIONS + '/') && req.method === 'PATCH') {\n const id = url.pathname.slice(API_ANNOTATIONS.length + 1);\n const raw = await collectBody(req);\n const body = JSON.parse(raw);\n response = await handleUpdateAnnotation(storage, id, body);\n }\n // DELETE /api/astro-annotate/annotations/:id\n else if (url.pathname.startsWith(API_ANNOTATIONS + '/') && req.method === 'DELETE') {\n const id = url.pathname.slice(API_ANNOTATIONS.length + 1);\n response = await handleDeleteAnnotation(storage, id);\n }\n // Unknown route\n else {\n return next();\n }\n\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const text = await response.text();\n res.end(text);\n } catch (err) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n}\n","import type { AstroAnnotateConfig } from './types.js';\nimport { createIntegration } from './integration/index.js';\n\nexport default function astroAnnotate(config: AstroAnnotateConfig = {}) {\n return createIntegration(config);\n}\n\nexport type { AstroAnnotateConfig, Annotation, AnnotationsFile } from './types.js';\n"],"mappings":"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","names":["resolve","resolve","resolve"]}
1
+ {"version":3,"sources":["../src/integration/index.ts","../src/integration/vite-plugin.ts","../src/storage/local.ts","../src/server/api-handlers.ts","../src/server/dev-middleware.ts","../src/index.ts"],"sourcesContent":["import type { AstroIntegration } from 'astro';\nimport { fileURLToPath } from 'node:url';\nimport { resolve, dirname } from 'node:path';\nimport type { AstroAnnotateConfig, ResolvedConfig } from '../types.js';\nimport { DEFAULT_ANNOTATIONS_PATH } from '../constants.js';\nimport { astroAnnotateVitePlugin } from './vite-plugin.js';\nimport { LocalStorage } from '../storage/local.js';\nimport { registerDevMiddleware } from '../server/dev-middleware.js';\n\nconst ANNOTATE_ICON = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"9\"/><line x1=\"12\" y1=\"6\" x2=\"12\" y2=\"12\"/></svg>`;\n\nexport function createIntegration(userConfig: AstroAnnotateConfig = {}): AstroIntegration {\n let resolvedConfig: ResolvedConfig;\n let projectRoot: string;\n\n return {\n name: 'astro-annotate',\n hooks: {\n 'astro:config:setup'({ config, command, updateConfig, injectScript, addDevToolbarApp, logger }) {\n projectRoot = fileURLToPath(config.root);\n\n const isDev = command === 'dev' || command === 'preview';\n const enabled = userConfig.enabled ?? isDev;\n\n if (!enabled) {\n logger.info('Disabled (set enabled: true to force)');\n return;\n }\n\n resolvedConfig = {\n enabled,\n mode: isDev ? 'dev' : 'deployed',\n storage: userConfig.storage ?? 'local',\n annotationsPath: userConfig.annotationsPath ?? DEFAULT_ANNOTATIONS_PATH,\n };\n\n // Add Vite plugin for virtual module\n updateConfig({\n vite: {\n plugins: [astroAnnotateVitePlugin(resolvedConfig)],\n },\n });\n\n // Register Dev Toolbar app (icon in Astro toolbar)\n if (isDev) {\n const toolbarAppPath = resolve(dirname(fileURLToPath(import.meta.url)), 'toolbar-app.js');\n addDevToolbarApp({\n id: 'astro-annotate',\n name: 'Annotate',\n icon: ANNOTATE_ICON,\n entrypoint: toolbarAppPath,\n });\n }\n\n // Inject client overlay script\n // At runtime, import.meta.url points to dist/index.js, client bundle is dist/client.js\n const clientPath = resolve(dirname(fileURLToPath(import.meta.url)), 'client.js');\n injectScript('page', `window.__ASTRO_ANNOTATE_DEV__=${isDev};import(\"${clientPath}\");`);\n\n logger.info('Overlay enabled');\n },\n\n 'astro:server:setup'({ server, logger }) {\n if (!resolvedConfig) return;\n\n const storage = new LocalStorage(resolvedConfig.annotationsPath, projectRoot);\n registerDevMiddleware(server, storage);\n logger.info('Dev middleware registered');\n },\n },\n };\n}\n","import type { Plugin } from 'vite';\nimport type { ResolvedConfig } from '../types.js';\nimport { VIRTUAL_MODULE_ID, RESOLVED_VIRTUAL_MODULE_ID } from '../constants.js';\n\nexport function astroAnnotateVitePlugin(config: ResolvedConfig): Plugin {\n return {\n name: 'astro-annotate-config',\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n load(id) {\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\n return `export default ${JSON.stringify(config)};`;\n }\n },\n };\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport type { Annotation, AnnotationsFile, CreateAnnotationPayload, UpdateAnnotationPayload } from '../types.js';\nimport type { AnnotationStorage } from './interface.js';\nimport { ANNOTATIONS_VERSION } from '../constants.js';\n\nfunction generateId(): string {\n return 'a_' + randomBytes(4).toString('hex');\n}\n\nfunction detectDevice(width: number): 'mobile' | 'tablet' | 'desktop' {\n if (width < 768) return 'mobile';\n if (width < 1024) return 'tablet';\n return 'desktop';\n}\n\nexport class LocalStorage implements AnnotationStorage {\n private filePath: string;\n\n constructor(annotationsPath: string, projectRoot: string) {\n this.filePath = resolve(projectRoot, annotationsPath);\n }\n\n private read(): AnnotationsFile {\n if (!existsSync(this.filePath)) {\n return { version: ANNOTATIONS_VERSION, annotations: [] };\n }\n const raw = readFileSync(this.filePath, 'utf-8');\n return JSON.parse(raw) as AnnotationsFile;\n }\n\n private write(data: AnnotationsFile): void {\n writeFileSync(this.filePath, JSON.stringify(data, null, 2) + '\\n', 'utf-8');\n }\n\n async list(page?: string): Promise<Annotation[]> {\n const data = this.read();\n if (page) {\n return data.annotations.filter((a) => a.page === page);\n }\n return data.annotations;\n }\n\n async create(payload: CreateAnnotationPayload): Promise<Annotation> {\n const data = this.read();\n const annotation: Annotation = {\n id: generateId(),\n timestamp: new Date().toISOString(),\n page: payload.page,\n selector: payload.selector,\n elementTag: payload.elementTag,\n elementText: payload.elementText.slice(0, 200),\n viewport: payload.viewport,\n device: payload.device || detectDevice(payload.viewport.width),\n text: payload.text,\n author: payload.author || 'Anonymous',\n status: 'open',\n };\n data.annotations.push(annotation);\n this.write(data);\n return annotation;\n }\n\n async update(id: string, payload: UpdateAnnotationPayload): Promise<Annotation | null> {\n const data = this.read();\n const index = data.annotations.findIndex((a) => a.id === id);\n if (index === -1) return null;\n\n if (payload.status) data.annotations[index].status = payload.status;\n if (payload.text !== undefined) data.annotations[index].text = payload.text;\n\n this.write(data);\n return data.annotations[index];\n }\n\n async delete(id: string): Promise<boolean> {\n const data = this.read();\n const before = data.annotations.length;\n data.annotations = data.annotations.filter((a) => a.id !== id);\n if (data.annotations.length === before) return false;\n this.write(data);\n return true;\n }\n}\n","import type { AnnotationStorage } from '../storage/interface.js';\nimport type { CreateAnnotationPayload, UpdateAnnotationPayload } from '../types.js';\n\nfunction json(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n\nexport async function handleListAnnotations(\n storage: AnnotationStorage,\n url: URL,\n): Promise<Response> {\n const page = url.searchParams.get('page') || undefined;\n const annotations = await storage.list(page);\n return json({ annotations });\n}\n\nexport async function handleCreateAnnotation(\n storage: AnnotationStorage,\n body: unknown,\n): Promise<Response> {\n const payload = body as CreateAnnotationPayload;\n\n if (!payload.selector || !payload.text || !payload.page) {\n return json({ error: 'Missing required fields: selector, text, page' }, 400);\n }\n\n const annotation = await storage.create(payload);\n return json({ annotation }, 201);\n}\n\nexport async function handleUpdateAnnotation(\n storage: AnnotationStorage,\n id: string,\n body: unknown,\n): Promise<Response> {\n const payload = body as UpdateAnnotationPayload;\n const annotation = await storage.update(id, payload);\n\n if (!annotation) {\n return json({ error: 'Annotation not found' }, 404);\n }\n\n return json({ annotation });\n}\n\nexport async function handleDeleteAnnotation(\n storage: AnnotationStorage,\n id: string,\n): Promise<Response> {\n const deleted = await storage.delete(id);\n\n if (!deleted) {\n return json({ error: 'Annotation not found' }, 404);\n }\n\n return json({ success: true });\n}\n","import type { ViteDevServer, Connect } from 'vite';\nimport type { AnnotationStorage } from '../storage/interface.js';\nimport { API_ANNOTATIONS } from '../constants.js';\nimport {\n handleListAnnotations,\n handleCreateAnnotation,\n handleUpdateAnnotation,\n handleDeleteAnnotation,\n} from './api-handlers.js';\n\nfunction collectBody(req: Connect.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));\n req.on('error', reject);\n });\n}\n\nexport function registerDevMiddleware(server: ViteDevServer, storage: AnnotationStorage): void {\n server.middlewares.use(async (req, res, next) => {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n\n if (!url.pathname.startsWith(API_ANNOTATIONS)) {\n return next();\n }\n\n try {\n let response: Response;\n\n // POST /api/astro-annotate/annotations\n if (url.pathname === API_ANNOTATIONS && req.method === 'POST') {\n const raw = await collectBody(req);\n const body = JSON.parse(raw);\n response = await handleCreateAnnotation(storage, body);\n }\n // GET /api/astro-annotate/annotations\n else if (url.pathname === API_ANNOTATIONS && req.method === 'GET') {\n response = await handleListAnnotations(storage, url);\n }\n // PATCH /api/astro-annotate/annotations/:id\n else if (url.pathname.startsWith(API_ANNOTATIONS + '/') && req.method === 'PATCH') {\n const id = url.pathname.slice(API_ANNOTATIONS.length + 1);\n const raw = await collectBody(req);\n const body = JSON.parse(raw);\n response = await handleUpdateAnnotation(storage, id, body);\n }\n // DELETE /api/astro-annotate/annotations/:id\n else if (url.pathname.startsWith(API_ANNOTATIONS + '/') && req.method === 'DELETE') {\n const id = url.pathname.slice(API_ANNOTATIONS.length + 1);\n response = await handleDeleteAnnotation(storage, id);\n }\n // Unknown route\n else {\n return next();\n }\n\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n const text = await response.text();\n res.end(text);\n } catch (err) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n}\n","import type { AstroAnnotateConfig } from './types.js';\nimport { createIntegration } from './integration/index.js';\n\nexport default function astroAnnotate(config: AstroAnnotateConfig = {}) {\n return createIntegration(config);\n}\n\nexport type { AstroAnnotateConfig, Annotation, AnnotationsFile } from './types.js';\n"],"mappings":";;;;;;;;;AACA,SAAS,qBAAqB;AAC9B,SAAS,WAAAA,UAAS,eAAe;;;ACE1B,SAAS,wBAAwB,QAAgC;AACtE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,IAAI;AACZ,UAAI,OAAO,mBAAmB;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,4BAA4B;AACrC,eAAO,kBAAkB,KAAK,UAAU,MAAM,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AACF;;;AClBA,SAAS,cAAc,eAAe,kBAAkB;AACxD,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAK5B,SAAS,aAAqB;AAC5B,SAAO,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK;AAC7C;AAEA,SAAS,aAAa,OAAgD;AACpE,MAAI,QAAQ,IAAK,QAAO;AACxB,MAAI,QAAQ,KAAM,QAAO;AACzB,SAAO;AACT;AAEO,IAAM,eAAN,MAAgD;AAAA,EAC7C;AAAA,EAER,YAAY,iBAAyB,aAAqB;AACxD,SAAK,WAAW,QAAQ,aAAa,eAAe;AAAA,EACtD;AAAA,EAEQ,OAAwB;AAC9B,QAAI,CAAC,WAAW,KAAK,QAAQ,GAAG;AAC9B,aAAO,EAAE,SAAS,qBAAqB,aAAa,CAAC,EAAE;AAAA,IACzD;AACA,UAAM,MAAM,aAAa,KAAK,UAAU,OAAO;AAC/C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB;AAAA,EAEQ,MAAM,MAA6B;AACzC,kBAAc,KAAK,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,OAAO;AAAA,EAC5E;AAAA,EAEA,MAAM,KAAK,MAAsC;AAC/C,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,MAAM;AACR,aAAO,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AAAA,IACvD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,SAAuD;AAClE,UAAM,OAAO,KAAK,KAAK;AACvB,UAAM,aAAyB;AAAA,MAC7B,IAAI,WAAW;AAAA,MACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,MAAM,QAAQ;AAAA,MACd,UAAU,QAAQ;AAAA,MAClB,YAAY,QAAQ;AAAA,MACpB,aAAa,QAAQ,YAAY,MAAM,GAAG,GAAG;AAAA,MAC7C,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ,UAAU,aAAa,QAAQ,SAAS,KAAK;AAAA,MAC7D,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ,UAAU;AAAA,MAC1B,QAAQ;AAAA,IACV;AACA,SAAK,YAAY,KAAK,UAAU;AAChC,SAAK,MAAM,IAAI;AACf,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,IAAY,SAA8D;AACrF,UAAM,OAAO,KAAK,KAAK;AACvB,UAAM,QAAQ,KAAK,YAAY,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAC3D,QAAI,UAAU,GAAI,QAAO;AAEzB,QAAI,QAAQ,OAAQ,MAAK,YAAY,KAAK,EAAE,SAAS,QAAQ;AAC7D,QAAI,QAAQ,SAAS,OAAW,MAAK,YAAY,KAAK,EAAE,OAAO,QAAQ;AAEvE,SAAK,MAAM,IAAI;AACf,WAAO,KAAK,YAAY,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,OAAO,IAA8B;AACzC,UAAM,OAAO,KAAK,KAAK;AACvB,UAAM,SAAS,KAAK,YAAY;AAChC,SAAK,cAAc,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAC7D,QAAI,KAAK,YAAY,WAAW,OAAQ,QAAO;AAC/C,SAAK,MAAM,IAAI;AACf,WAAO;AAAA,EACT;AACF;;;ACjFA,SAAS,KAAK,MAAe,SAAS,KAAe;AACnD,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAEA,eAAsB,sBACpB,SACA,KACmB;AACnB,QAAM,OAAO,IAAI,aAAa,IAAI,MAAM,KAAK;AAC7C,QAAM,cAAc,MAAM,QAAQ,KAAK,IAAI;AAC3C,SAAO,KAAK,EAAE,YAAY,CAAC;AAC7B;AAEA,eAAsB,uBACpB,SACA,MACmB;AACnB,QAAM,UAAU;AAEhB,MAAI,CAAC,QAAQ,YAAY,CAAC,QAAQ,QAAQ,CAAC,QAAQ,MAAM;AACvD,WAAO,KAAK,EAAE,OAAO,gDAAgD,GAAG,GAAG;AAAA,EAC7E;AAEA,QAAM,aAAa,MAAM,QAAQ,OAAO,OAAO;AAC/C,SAAO,KAAK,EAAE,WAAW,GAAG,GAAG;AACjC;AAEA,eAAsB,uBACpB,SACA,IACA,MACmB;AACnB,QAAM,UAAU;AAChB,QAAM,aAAa,MAAM,QAAQ,OAAO,IAAI,OAAO;AAEnD,MAAI,CAAC,YAAY;AACf,WAAO,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;AAAA,EACpD;AAEA,SAAO,KAAK,EAAE,WAAW,CAAC;AAC5B;AAEA,eAAsB,uBACpB,SACA,IACmB;AACnB,QAAM,UAAU,MAAM,QAAQ,OAAO,EAAE;AAEvC,MAAI,CAAC,SAAS;AACZ,WAAO,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;AAAA,EACpD;AAEA,SAAO,KAAK,EAAE,SAAS,KAAK,CAAC;AAC/B;;;ACjDA,SAAS,YAAY,KAA+C;AAClE,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACpD,QAAI,GAAG,OAAO,MAAMA,SAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC;AACpE,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEO,SAAS,sBAAsB,QAAuB,SAAkC;AAC7F,SAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEhE,QAAI,CAAC,IAAI,SAAS,WAAW,eAAe,GAAG;AAC7C,aAAO,KAAK;AAAA,IACd;AAEA,QAAI;AACF,UAAI;AAGJ,UAAI,IAAI,aAAa,mBAAmB,IAAI,WAAW,QAAQ;AAC7D,cAAM,MAAM,MAAM,YAAY,GAAG;AACjC,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,mBAAW,MAAM,uBAAuB,SAAS,IAAI;AAAA,MACvD,WAES,IAAI,aAAa,mBAAmB,IAAI,WAAW,OAAO;AACjE,mBAAW,MAAM,sBAAsB,SAAS,GAAG;AAAA,MACrD,WAES,IAAI,SAAS,WAAW,kBAAkB,GAAG,KAAK,IAAI,WAAW,SAAS;AACjF,cAAM,KAAK,IAAI,SAAS,MAAM,gBAAgB,SAAS,CAAC;AACxD,cAAM,MAAM,MAAM,YAAY,GAAG;AACjC,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,mBAAW,MAAM,uBAAuB,SAAS,IAAI,IAAI;AAAA,MAC3D,WAES,IAAI,SAAS,WAAW,kBAAkB,GAAG,KAAK,IAAI,WAAW,UAAU;AAClF,cAAM,KAAK,IAAI,SAAS,MAAM,gBAAgB,SAAS,CAAC;AACxD,mBAAW,MAAM,uBAAuB,SAAS,EAAE;AAAA,MACrD,OAEK;AACH,eAAO,KAAK;AAAA,MACd;AAEA,UAAI,aAAa,SAAS;AAC1B,eAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,YAAI,UAAU,KAAK,KAAK;AAAA,MAC1B,CAAC;AACD,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAI,IAAI,IAAI;AAAA,IACd,SAAS,KAAK;AACZ,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,CAAC;AAAA,IAC5D;AAAA,EACF,CAAC;AACH;;;AJ5DA,IAAM,gBAAgB;AAEf,SAAS,kBAAkB,aAAkC,CAAC,GAAqB;AACxF,MAAI;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACL,qBAAqB,EAAE,QAAQ,SAAS,cAAc,cAAc,kBAAkB,OAAO,GAAG;AAC9F,sBAAc,cAAc,OAAO,IAAI;AAEvC,cAAM,QAAQ,YAAY,SAAS,YAAY;AAC/C,cAAM,UAAU,WAAW,WAAW;AAEtC,YAAI,CAAC,SAAS;AACZ,iBAAO,KAAK,uCAAuC;AACnD;AAAA,QACF;AAEA,yBAAiB;AAAA,UACf;AAAA,UACA,MAAM,QAAQ,QAAQ;AAAA,UACtB,SAAS,WAAW,WAAW;AAAA,UAC/B,iBAAiB,WAAW,mBAAmB;AAAA,QACjD;AAGA,qBAAa;AAAA,UACX,MAAM;AAAA,YACJ,SAAS,CAAC,wBAAwB,cAAc,CAAC;AAAA,UACnD;AAAA,QACF,CAAC;AAGD,YAAI,OAAO;AACT,gBAAM,iBAAiBC,SAAQ,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,gBAAgB;AACxF,2BAAiB;AAAA,YACf,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,MAAM;AAAA,YACN,YAAY;AAAA,UACd,CAAC;AAAA,QACH;AAIA,cAAM,aAAaA,SAAQ,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,WAAW;AAC/E,qBAAa,QAAQ,iCAAiC,KAAK,YAAY,UAAU,KAAK;AAEtF,eAAO,KAAK,iBAAiB;AAAA,MAC/B;AAAA,MAEA,qBAAqB,EAAE,QAAQ,OAAO,GAAG;AACvC,YAAI,CAAC,eAAgB;AAErB,cAAM,UAAU,IAAI,aAAa,eAAe,iBAAiB,WAAW;AAC5E,8BAAsB,QAAQ,OAAO;AACrC,eAAO,KAAK,2BAA2B;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;;;AKpEe,SAAR,cAA+B,SAA8B,CAAC,GAAG;AACtE,SAAO,kBAAkB,MAAM;AACjC;","names":["resolve","resolve","resolve"]}
@@ -0,0 +1,25 @@
1
+ // src/client/toolbar-app.ts
2
+ var toolbar_app_default = {
3
+ init(_canvas, app, _server) {
4
+ let syncing = false;
5
+ app.onToggled(({ state }) => {
6
+ if (syncing) {
7
+ syncing = false;
8
+ return;
9
+ }
10
+ window.dispatchEvent(new CustomEvent("aa:toggle", { detail: { active: state } }));
11
+ });
12
+ window.addEventListener("aa:state-changed", ((e) => {
13
+ syncing = true;
14
+ app.toggleState({ state: e.detail.active });
15
+ }));
16
+ window.addEventListener("aa:count", ((e) => {
17
+ const count = e.detail.count;
18
+ app.toggleNotification(count > 0 ? { state: true, level: "info" } : { state: false });
19
+ }));
20
+ }
21
+ };
22
+ export {
23
+ toolbar_app_default as default
24
+ };
25
+ //# sourceMappingURL=toolbar-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/toolbar-app.ts"],"sourcesContent":["import type { DevToolbarApp } from 'astro';\n\nexport default {\n init(_canvas, app, _server) {\n let syncing = false;\n\n // Toolbar icon clicked → notify overlay\n app.onToggled(({ state }) => {\n if (syncing) {\n syncing = false;\n return;\n }\n window.dispatchEvent(new CustomEvent('aa:toggle', { detail: { active: state } }));\n });\n\n // Overlay changed state (keyboard shortcut) → sync toolbar icon\n window.addEventListener('aa:state-changed', ((e: CustomEvent) => {\n syncing = true;\n app.toggleState({ state: e.detail.active });\n }) as EventListener);\n\n // Overlay reports annotation count → toggle notification dot\n window.addEventListener('aa:count', ((e: CustomEvent) => {\n const count = e.detail.count;\n app.toggleNotification(count > 0 ? { state: true, level: 'info' } : { state: false });\n }) as EventListener);\n },\n} satisfies DevToolbarApp;\n"],"mappings":";AAEA,IAAO,sBAAQ;AAAA,EACb,KAAK,SAAS,KAAK,SAAS;AAC1B,QAAI,UAAU;AAGd,QAAI,UAAU,CAAC,EAAE,MAAM,MAAM;AAC3B,UAAI,SAAS;AACX,kBAAU;AACV;AAAA,MACF;AACA,aAAO,cAAc,IAAI,YAAY,aAAa,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,CAAC,CAAC;AAAA,IAClF,CAAC;AAGD,WAAO,iBAAiB,qBAAqB,CAAC,MAAmB;AAC/D,gBAAU;AACV,UAAI,YAAY,EAAE,OAAO,EAAE,OAAO,OAAO,CAAC;AAAA,IAC5C,EAAmB;AAGnB,WAAO,iBAAiB,aAAa,CAAC,MAAmB;AACvD,YAAM,QAAQ,EAAE,OAAO;AACvB,UAAI,mBAAmB,QAAQ,IAAI,EAAE,OAAO,MAAM,OAAO,OAAO,IAAI,EAAE,OAAO,MAAM,CAAC;AAAA,IACtF,EAAmB;AAAA,EACrB;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-annotate",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Visual annotation overlay for Astro staging sites. Clients annotate HTML elements directly in the browser. Annotations stored as structured JSON, readable by developers and LLMs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,6 +18,8 @@
18
18
  "keywords": [
19
19
  "astro-integration",
20
20
  "withastro",
21
+ "dev-toolbar",
22
+ "devtools",
21
23
  "annotations",
22
24
  "visual-feedback",
23
25
  "llm",