astro-annotate 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/chunk-QG2SIJB2.js +18 -0
- package/dist/chunk-QG2SIJB2.js.map +1 -0
- package/dist/client.js +920 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +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 private 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 = current.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (s) => s.tagName === current!.tagName,\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\n constructor(\n private shadowRoot: ShadowRoot,\n onSubmitted: () => void,\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 }\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\">×</button>\n </div>\n <div class=\"aa-form-body\">\n <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.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 this.container.style.display = 'none';\n this.container.innerHTML = '';\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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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\">×</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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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 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(this.shadowRoot, () => this.onAnnotationCreated());\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\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 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 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 // Deactivate annotation mode, reload annotations\n this.toolbar.deactivate();\n this.setActive(false);\n this.loadAnnotations();\n }\n\n destroy(): void {\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 const existing = document.getElementById(SHADOW_ROOT_ID);\n if (existing) {\n existing.remove();\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.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as astro from 'astro';
|
|
2
|
+
|
|
3
|
+
interface Annotation {
|
|
4
|
+
id: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
page: string;
|
|
7
|
+
selector: string;
|
|
8
|
+
elementTag: string;
|
|
9
|
+
elementText: string;
|
|
10
|
+
viewport: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
device: 'mobile' | 'tablet' | 'desktop';
|
|
15
|
+
text: string;
|
|
16
|
+
author: string;
|
|
17
|
+
status: 'open' | 'resolved';
|
|
18
|
+
}
|
|
19
|
+
interface AnnotationsFile {
|
|
20
|
+
version: string;
|
|
21
|
+
annotations: Annotation[];
|
|
22
|
+
}
|
|
23
|
+
interface AstroAnnotateConfig {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
storage?: 'local';
|
|
26
|
+
annotationsPath?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare function astroAnnotate(config?: AstroAnnotateConfig): astro.AstroIntegration;
|
|
30
|
+
|
|
31
|
+
export { type Annotation, type AnnotationsFile, type AstroAnnotateConfig, astroAnnotate as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ANNOTATIONS_VERSION,
|
|
3
|
+
API_ANNOTATIONS,
|
|
4
|
+
DEFAULT_ANNOTATIONS_PATH,
|
|
5
|
+
RESOLVED_VIRTUAL_MODULE_ID,
|
|
6
|
+
VIRTUAL_MODULE_ID
|
|
7
|
+
} from "./chunk-QG2SIJB2.js";
|
|
8
|
+
|
|
9
|
+
// src/integration/index.ts
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { resolve as resolve2, dirname } from "path";
|
|
12
|
+
|
|
13
|
+
// src/integration/vite-plugin.ts
|
|
14
|
+
function astroAnnotateVitePlugin(config) {
|
|
15
|
+
return {
|
|
16
|
+
name: "astro-annotate-config",
|
|
17
|
+
resolveId(id) {
|
|
18
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
19
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
load(id) {
|
|
23
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
24
|
+
return `export default ${JSON.stringify(config)};`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/storage/local.ts
|
|
31
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
32
|
+
import { resolve } from "path";
|
|
33
|
+
import { randomBytes } from "crypto";
|
|
34
|
+
function generateId() {
|
|
35
|
+
return "a_" + randomBytes(4).toString("hex");
|
|
36
|
+
}
|
|
37
|
+
function detectDevice(width) {
|
|
38
|
+
if (width < 768) return "mobile";
|
|
39
|
+
if (width < 1024) return "tablet";
|
|
40
|
+
return "desktop";
|
|
41
|
+
}
|
|
42
|
+
var LocalStorage = class {
|
|
43
|
+
filePath;
|
|
44
|
+
constructor(annotationsPath, projectRoot) {
|
|
45
|
+
this.filePath = resolve(projectRoot, annotationsPath);
|
|
46
|
+
}
|
|
47
|
+
read() {
|
|
48
|
+
if (!existsSync(this.filePath)) {
|
|
49
|
+
return { version: ANNOTATIONS_VERSION, annotations: [] };
|
|
50
|
+
}
|
|
51
|
+
const raw = readFileSync(this.filePath, "utf-8");
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
write(data) {
|
|
55
|
+
writeFileSync(this.filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
56
|
+
}
|
|
57
|
+
async list(page) {
|
|
58
|
+
const data = this.read();
|
|
59
|
+
if (page) {
|
|
60
|
+
return data.annotations.filter((a) => a.page === page);
|
|
61
|
+
}
|
|
62
|
+
return data.annotations;
|
|
63
|
+
}
|
|
64
|
+
async create(payload) {
|
|
65
|
+
const data = this.read();
|
|
66
|
+
const annotation = {
|
|
67
|
+
id: generateId(),
|
|
68
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69
|
+
page: payload.page,
|
|
70
|
+
selector: payload.selector,
|
|
71
|
+
elementTag: payload.elementTag,
|
|
72
|
+
elementText: payload.elementText.slice(0, 200),
|
|
73
|
+
viewport: payload.viewport,
|
|
74
|
+
device: payload.device || detectDevice(payload.viewport.width),
|
|
75
|
+
text: payload.text,
|
|
76
|
+
author: payload.author || "Anonymous",
|
|
77
|
+
status: "open"
|
|
78
|
+
};
|
|
79
|
+
data.annotations.push(annotation);
|
|
80
|
+
this.write(data);
|
|
81
|
+
return annotation;
|
|
82
|
+
}
|
|
83
|
+
async update(id, payload) {
|
|
84
|
+
const data = this.read();
|
|
85
|
+
const index = data.annotations.findIndex((a) => a.id === id);
|
|
86
|
+
if (index === -1) return null;
|
|
87
|
+
if (payload.status) data.annotations[index].status = payload.status;
|
|
88
|
+
if (payload.text !== void 0) data.annotations[index].text = payload.text;
|
|
89
|
+
this.write(data);
|
|
90
|
+
return data.annotations[index];
|
|
91
|
+
}
|
|
92
|
+
async delete(id) {
|
|
93
|
+
const data = this.read();
|
|
94
|
+
const before = data.annotations.length;
|
|
95
|
+
data.annotations = data.annotations.filter((a) => a.id !== id);
|
|
96
|
+
if (data.annotations.length === before) return false;
|
|
97
|
+
this.write(data);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/server/api-handlers.ts
|
|
103
|
+
function json(data, status = 200) {
|
|
104
|
+
return new Response(JSON.stringify(data), {
|
|
105
|
+
status,
|
|
106
|
+
headers: { "Content-Type": "application/json" }
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function handleListAnnotations(storage, url) {
|
|
110
|
+
const page = url.searchParams.get("page") || void 0;
|
|
111
|
+
const annotations = await storage.list(page);
|
|
112
|
+
return json({ annotations });
|
|
113
|
+
}
|
|
114
|
+
async function handleCreateAnnotation(storage, body) {
|
|
115
|
+
const payload = body;
|
|
116
|
+
if (!payload.selector || !payload.text || !payload.page) {
|
|
117
|
+
return json({ error: "Missing required fields: selector, text, page" }, 400);
|
|
118
|
+
}
|
|
119
|
+
const annotation = await storage.create(payload);
|
|
120
|
+
return json({ annotation }, 201);
|
|
121
|
+
}
|
|
122
|
+
async function handleUpdateAnnotation(storage, id, body) {
|
|
123
|
+
const payload = body;
|
|
124
|
+
const annotation = await storage.update(id, payload);
|
|
125
|
+
if (!annotation) {
|
|
126
|
+
return json({ error: "Annotation not found" }, 404);
|
|
127
|
+
}
|
|
128
|
+
return json({ annotation });
|
|
129
|
+
}
|
|
130
|
+
async function handleDeleteAnnotation(storage, id) {
|
|
131
|
+
const deleted = await storage.delete(id);
|
|
132
|
+
if (!deleted) {
|
|
133
|
+
return json({ error: "Annotation not found" }, 404);
|
|
134
|
+
}
|
|
135
|
+
return json({ success: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/server/dev-middleware.ts
|
|
139
|
+
function collectBody(req) {
|
|
140
|
+
return new Promise((resolve3, reject) => {
|
|
141
|
+
const chunks = [];
|
|
142
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
143
|
+
req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
|
|
144
|
+
req.on("error", reject);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function registerDevMiddleware(server, storage) {
|
|
148
|
+
server.middlewares.use(async (req, res, next) => {
|
|
149
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
150
|
+
if (!url.pathname.startsWith(API_ANNOTATIONS)) {
|
|
151
|
+
return next();
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
let response;
|
|
155
|
+
if (url.pathname === API_ANNOTATIONS && req.method === "POST") {
|
|
156
|
+
const raw = await collectBody(req);
|
|
157
|
+
const body = JSON.parse(raw);
|
|
158
|
+
response = await handleCreateAnnotation(storage, body);
|
|
159
|
+
} else if (url.pathname === API_ANNOTATIONS && req.method === "GET") {
|
|
160
|
+
response = await handleListAnnotations(storage, url);
|
|
161
|
+
} else if (url.pathname.startsWith(API_ANNOTATIONS + "/") && req.method === "PATCH") {
|
|
162
|
+
const id = url.pathname.slice(API_ANNOTATIONS.length + 1);
|
|
163
|
+
const raw = await collectBody(req);
|
|
164
|
+
const body = JSON.parse(raw);
|
|
165
|
+
response = await handleUpdateAnnotation(storage, id, body);
|
|
166
|
+
} else if (url.pathname.startsWith(API_ANNOTATIONS + "/") && req.method === "DELETE") {
|
|
167
|
+
const id = url.pathname.slice(API_ANNOTATIONS.length + 1);
|
|
168
|
+
response = await handleDeleteAnnotation(storage, id);
|
|
169
|
+
} else {
|
|
170
|
+
return next();
|
|
171
|
+
}
|
|
172
|
+
res.statusCode = response.status;
|
|
173
|
+
response.headers.forEach((value, key) => {
|
|
174
|
+
res.setHeader(key, value);
|
|
175
|
+
});
|
|
176
|
+
const text = await response.text();
|
|
177
|
+
res.end(text);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
res.statusCode = 500;
|
|
180
|
+
res.setHeader("Content-Type", "application/json");
|
|
181
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/integration/index.ts
|
|
187
|
+
function createIntegration(userConfig = {}) {
|
|
188
|
+
let resolvedConfig;
|
|
189
|
+
let projectRoot;
|
|
190
|
+
return {
|
|
191
|
+
name: "astro-annotate",
|
|
192
|
+
hooks: {
|
|
193
|
+
"astro:config:setup"({ config, command, updateConfig, injectScript, logger }) {
|
|
194
|
+
projectRoot = fileURLToPath(config.root);
|
|
195
|
+
const isDev = command === "dev" || command === "preview";
|
|
196
|
+
const enabled = userConfig.enabled ?? isDev;
|
|
197
|
+
if (!enabled) {
|
|
198
|
+
logger.info("Disabled (set enabled: true to force)");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
resolvedConfig = {
|
|
202
|
+
enabled,
|
|
203
|
+
storage: userConfig.storage ?? "local",
|
|
204
|
+
annotationsPath: userConfig.annotationsPath ?? DEFAULT_ANNOTATIONS_PATH
|
|
205
|
+
};
|
|
206
|
+
updateConfig({
|
|
207
|
+
vite: {
|
|
208
|
+
plugins: [astroAnnotateVitePlugin(resolvedConfig)]
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const clientPath = resolve2(dirname(fileURLToPath(import.meta.url)), "client.js");
|
|
212
|
+
injectScript("page", `import "${clientPath}";`);
|
|
213
|
+
logger.info("Overlay enabled");
|
|
214
|
+
},
|
|
215
|
+
"astro:server:setup"({ server, logger }) {
|
|
216
|
+
if (!resolvedConfig) return;
|
|
217
|
+
const storage = new LocalStorage(resolvedConfig.annotationsPath, projectRoot);
|
|
218
|
+
registerDevMiddleware(server, storage);
|
|
219
|
+
logger.info("Dev middleware registered");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/index.ts
|
|
226
|
+
function astroAnnotate(config = {}) {
|
|
227
|
+
return createIntegration(config);
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
astroAnnotate as default
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 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', `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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "astro-annotate",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src/routes"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"astro-integration",
|
|
20
|
+
"withastro",
|
|
21
|
+
"annotations",
|
|
22
|
+
"visual-feedback",
|
|
23
|
+
"llm",
|
|
24
|
+
"staging",
|
|
25
|
+
"review"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"astro": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"astro": "^5.0.0",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.5.0"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/jan-nikolov/astro-annotate"
|
|
44
|
+
},
|
|
45
|
+
"author": "jan-nikolov"
|
|
46
|
+
}
|