@unhingged/vizu-core 0.1.20 → 0.1.21

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/vizu.min.js CHANGED
@@ -95,7 +95,7 @@
95
95
  <svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="1" y="1" width="5" height="5"/><rect x="8" y="1" width="5" height="5"/><rect x="1" y="8" width="5" height="5"/><rect x="8" y="8" width="5" height="5"/></svg>
96
96
  </button>
97
97
  <button class="vz-pill-icon-btn" data-vz="disable" title="Disable Vizu (toggle with shortcut)" aria-label="Disable">\xD7</button>
98
- `}toast(e){let t=document.createElement("div");t.className="vz-toast",t.textContent=e,this.root.appendChild(t),setTimeout(()=>t.remove(),1800)}destroy(){this.el.removeEventListener("click",this.handleClick),this.el.remove()}};var J=class{constructor(e,t){this.open=!1;this.filter="open";this.lastComments=[];this.lastConfidence=new Map;this.handleClick=e=>{let t=e.target,n=t.closest('[data-vz="ref"]');if(n){e.stopPropagation();let c=n.getAttribute("data-comment-id"),p=n.getAttribute("data-ref-id");c&&p&&this.callbacks.onJumpToReference(c,p);return}let i=t.closest('[data-vz="filter"]');if(i){e.stopPropagation();let c=i.getAttribute("data-filter");c&&c!==this.filter&&(this.filter=c,this.render(this.lastComments),this.callbacks.onFilterChange?.(c));return}let r=t.closest('[data-vz="status"]');if(r){e.stopPropagation();let c=r.getAttribute("data-id"),p=r.getAttribute("data-status");c&&p&&this.callbacks.onUpdateStatus(c,p);return}let s=t.closest('[data-vz="delete"]');if(s){e.stopPropagation();let c=s.getAttribute("data-id");c&&this.callbacks.onDelete(c);return}if(t.closest('[data-vz="close"]')){this.callbacks.onClose();return}let a=t.closest('[data-vz="jump-fp"]');if(a&&a.__fp){e.stopPropagation(),this.callbacks.onJumpTo(a.__fp);return}let l=t.closest('[data-vz="jump"]');l&&l.__group&&this.callbacks.onJumpTo(l.__group.fp)};this.root=e,this.callbacks=t,this.el=document.createElement("div"),this.el.className="vz-sidebar",this.el.addEventListener("click",this.handleClick),this.root.appendChild(this.el)}toggle(e,t){this.open?this.close():this.show(e,t)}show(e,t){this.render(e,t),requestAnimationFrame(()=>this.el.classList.add("is-open")),this.open=!0}update(e,t){this.open&&this.render(e,t)}close(){this.el.classList.remove("is-open"),this.open=!1}isOpen(){return this.open}render(e,t){this.lastComments=e,t&&(this.lastConfidence=new Map(t));let n=this.lastConfidence,i=e.filter(h=>n.get(h.id)==="orphaned"),r=e.filter(h=>n.get(h.id)!=="orphaned"),s=h=>h.status??"open",a=this.filter==="all"?r:r.filter(h=>s(h)===this.filter),l=this.filter==="all"?i:[],c=Ne(e);if(a.length===0&&l.length===0){let h;if(e.length===0)h="<strong>No comments yet</strong>Click any element on the page to leave one.";else if(this.filter==="open"){let x=c.resolved+c.wontfix;h=`<strong>Nothing open.</strong>${x} closed comment${x===1?"":"s"} hidden. Switch to <em>All</em> to see them.`}else this.filter==="resolved"?h="<strong>Nothing resolved.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":this.filter==="wontfix"?h="<strong>Nothing marked won&apos;t&nbsp;fix.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":h="<strong>No comments yet</strong>Click any element on the page to leave one.";this.el.innerHTML=`
98
+ `}toast(e){let t=document.createElement("div");t.className="vz-toast",t.textContent=e,this.root.appendChild(t),setTimeout(()=>t.remove(),1800)}destroy(){this.el.removeEventListener("click",this.handleClick),this.el.remove()}};var J=class{constructor(e,t){this.open=!1;this.filter="all";this.lastComments=[];this.lastConfidence=new Map;this.handleClick=e=>{let t=e.target,n=t.closest('[data-vz="ref"]');if(n){e.stopPropagation();let c=n.getAttribute("data-comment-id"),p=n.getAttribute("data-ref-id");c&&p&&this.callbacks.onJumpToReference(c,p);return}let i=t.closest('[data-vz="filter"]');if(i){e.stopPropagation();let c=i.getAttribute("data-filter");c&&c!==this.filter&&(this.filter=c,this.render(this.lastComments),this.callbacks.onFilterChange?.(c));return}let r=t.closest('[data-vz="status"]');if(r){e.stopPropagation();let c=r.getAttribute("data-id"),p=r.getAttribute("data-status");c&&p&&this.callbacks.onUpdateStatus(c,p);return}let s=t.closest('[data-vz="delete"]');if(s){e.stopPropagation();let c=s.getAttribute("data-id");c&&this.callbacks.onDelete(c);return}if(t.closest('[data-vz="close"]')){this.callbacks.onClose();return}let a=t.closest('[data-vz="jump-fp"]');if(a&&a.__fp){e.stopPropagation(),this.callbacks.onJumpTo(a.__fp);return}let l=t.closest('[data-vz="jump"]');l&&l.__group&&this.callbacks.onJumpTo(l.__group.fp)};this.root=e,this.callbacks=t,this.el=document.createElement("div"),this.el.className="vz-sidebar",this.el.addEventListener("click",this.handleClick),this.root.appendChild(this.el)}toggle(e,t){this.open?this.close():this.show(e,t)}show(e,t){this.render(e,t),requestAnimationFrame(()=>this.el.classList.add("is-open")),this.open=!0}update(e,t){this.open&&this.render(e,t)}close(){this.el.classList.remove("is-open"),this.open=!1}isOpen(){return this.open}render(e,t){this.lastComments=e,t&&(this.lastConfidence=new Map(t));let n=this.lastConfidence,i=e.filter(h=>n.get(h.id)==="orphaned"),r=e.filter(h=>n.get(h.id)!=="orphaned"),s=h=>h.status??"open",a=this.filter==="all"?r:r.filter(h=>s(h)===this.filter),l=this.filter==="all"?i:[],c=Ne(e);if(a.length===0&&l.length===0){let h;if(e.length===0)h="<strong>No comments yet</strong>Click any element on the page to leave one.";else if(this.filter==="open"){let x=c.resolved+c.wontfix;h=`<strong>Nothing open.</strong>${x} closed comment${x===1?"":"s"} hidden. Switch to <em>All</em> to see them.`}else this.filter==="resolved"?h="<strong>Nothing resolved.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":this.filter==="wontfix"?h="<strong>Nothing marked won&apos;t&nbsp;fix.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":h="<strong>No comments yet</strong>Click any element on the page to leave one.";this.el.innerHTML=`
99
99
  ${this.headerHtml(e.length,c)}
100
100
  <div class="vz-sidebar-body">
101
101
  <div class="vz-sidebar-empty">${h}</div>
@@ -1199,7 +1199,7 @@
1199
1199
  from { opacity: 0; transform: translateX(-50%) translateY(8px); }
1200
1200
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
1201
1201
  }
1202
- `;function he(){if(typeof document>"u"||document.getElementById("vizu-styles"))return;let o=document.createElement("style");o.id="vizu-styles",o.textContent=qe,(document.head||document.documentElement).appendChild(o)}function ue(o){let e=o.toLowerCase().split("+").map(n=>n.trim());return{key:e.pop()||"",mod:e.includes("mod")||e.includes("cmdorctrl"),shift:e.includes("shift"),alt:e.includes("alt")||e.includes("option"),ctrl:e.includes("ctrl")||e.includes("control")}}function fe(o,e){let t=typeof navigator<"u"&&/Mac|iPhone|iPad/.test(navigator.platform);if(o.key.toLowerCase()!==e.key)return!1;if(e.mod){if(!(t?o.metaKey:o.ctrlKey))return!1}else if(e.ctrl&&!o.ctrlKey)return!1;return!(e.shift!==o.shiftKey||e.alt!==o.altKey)}var W=class{constructor(){this.listeners=new Map}on(e,t){let n=this.listeners.get(e);return n||(n=new Set,this.listeners.set(e,n)),n.add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){let n=this.listeners.get(e);if(n)for(let i of[...n])try{i(t)}catch(r){console.error("[vizu] event handler error for",e,r)}}clear(){this.listeners.clear()}};var te={shortcut:"mod+shift+e",namespace:"default",accent:"#FF6647"};function Ke(o,e,t,n){return e?(o&&typeof console<"u"&&console.warn("[vizu] Both `cloud` and `storage` set; `cloud` wins. Drop `storage` to silence this."),new E({workspace:e.workspace,apiUrl:e.apiUrl,autoSignIn:e.autoSignIn,onAuthChanged:n})):o?o==="local"?new O:o==="memory"?new I:o==="none"?new _:ee(o)?ae(o):o:t==="local"?new O:new I}var M=class{constructor(e={},t="memory"){this.root=null;this.highlighter=null;this.popover=null;this.pill=null;this.sidebar=null;this.markers=new Map;this.outlines=new Map;this.activeAnchorOutlines=new Map;this.spotlights=new Set;this.comments=[];this.resolvedConfidence=new Map;this.selfHealedThisSession=new Set;this.actions=[];this.user=null;this.statusFilter="open";this.hasLoadedComments=!1;this.enabled=!1;this.rafScheduled=!1;this.bus=new W;this.picking=null;this.pickingBanner=null;this.multiSelectMode=!1;this.pendingFingerprints=[];this.pendingTargets=[];this.unsubscribeStorage=null;this.onKeyDown=e=>{if(this.picking&&e.key==="Escape"){e.preventDefault(),this.cancelPicking();return}if(this.pendingFingerprints.length>0&&e.key==="Escape"&&!this.popover?.isOpen()){e.preventDefault(),this.clearSelection();return}if(fe(e,this.parsedShortcut)){e.preventDefault();let t=this.enabled;this.toggle(),!t&&this.enabled&&this.storage instanceof E&&this.storage.preflightAuth().catch(n=>{n?.code==="auth_canceled"||n?.code==="popup_blocked"||typeof console<"u"&&console.warn("[vizu] preflight auth failed:",n)})}};this.scheduleReposition=()=>{this.rafScheduled||(this.rafScheduled=!0,requestAnimationFrame(()=>{this.rafScheduled=!1;for(let[,e]of this.outlines)e.target&&this.positionOutline(e.outline,e.target);for(let[,e]of this.markers)e.target&&this.positionMarker(e.marker,e.target);for(let[,e]of this.activeAnchorOutlines){let t=V(e.fp);t&&this.positionOutline(e.el,t)}for(let e of this.spotlights)e.target&&this.positionOutline(e.el,e.target);this.popover?.reposition()}))};this.opts={...e},this.opts.shortcut=e.shortcut??te.shortcut,this.opts.namespace=e.namespace??te.namespace,this.opts.accent=e.accent??te.accent,this.parsedShortcut=ue(this.opts.shortcut),this.user=e.user??null,this.storage=Ke(e.storage,e.cloud,t,n=>{n.user&&this.setUser(n.user),this.hasLoadedComments&&this.loadComments()}),e.actions&&(this.actions=[...e.actions]),e.onCommentAdded&&this.bus.on("comment:added",n=>e.onCommentAdded(n.comment)),e.onCommentRemoved&&this.bus.on("comment:removed",n=>e.onCommentRemoved(n.id)),typeof window<"u"&&(window.addEventListener("keydown",this.onKeyDown),this.loadComments().catch(()=>{}).finally(()=>{this.hasLoadedComments=!0,this.subscribeStorage()}),e.startEnabled&&this.deferred(()=>this.enable()))}on(e,t){return this.bus.on(e,t)}off(e,t){this.bus.off(e,t)}setUser(e){this.user=e,this.popover?.setUser(e),this.pill?.setUser(e),this.bus.emit("user:changed",{user:e}),this.enabled&&!this.root&&this.shouldMount()&&this.deferred(()=>this.mount())}getUser(){return this.user}async searchMentionable(){if(this.opts.mentionable)try{return await this.opts.mentionable()}catch(e){return typeof console<"u"&&console.warn("[vizu] options.mentionable threw",e),[]}return this.storage instanceof E?this.storage.searchMentionable():[]}shouldMount(){return this.storage instanceof E?this.user!==null:!0}requireUserOrAuth(){return!(this.storage instanceof E)||this.user?!0:(this.storage.isAccessDenied()||this.storage.preflightAuth().catch(()=>{}),!1)}enable(){this.enabled||(this.enabled=!0,this.bus.emit("enabled",{}),this.shouldMount()&&this.deferred(()=>this.mount()))}disable(){this.enabled&&(this.enabled=!1,this.unmount(),this.bus.emit("disabled",{}))}toggle(){this.enabled?this.disable():this.enable()}isEnabled(){return this.enabled}destroy(){this.disable(),typeof window<"u"&&window.removeEventListener("keydown",this.onKeyDown),this.unsubscribeStorage?.(),this.unsubscribeStorage=null,this.selfHealedThisSession.clear(),this.bus.clear()}addAction(e){let t=this.actions.findIndex(n=>n.id===e.id);t>=0?this.actions[t]=e:this.actions.push(e),this.pill?.setActions(this.actions)}removeAction(e){this.actions=this.actions.filter(t=>t.id!==e),this.pill?.setActions(this.actions)}getActions(){return[...this.actions]}invokeAction(e){let t=this.actions.find(i=>i.id===e);if(!t)return;let n=this.actionContext();this.bus.emit("action:invoked",{id:e}),t.onClick(n)}getComments(){return[...this.comments]}async setComments(e,t){this.comments=S(e),t?.persist!==!1&&await this.storage.setAll(this.opts.namespace,this.comments),this.bus.emit("comments:set",{comments:this.getComments()}),this.refreshUi()}async updateComment(e,t){let n=this.comments.findIndex(c=>c.id===e);if(n===-1)return null;let{id:i,schemaVersion:r,...s}=t,a={...this.comments[n],...s};this.comments[n]=a;let l=await this.storage.updateComment(this.opts.namespace,e,s);return this.bus.emit("comment:updated",{comment:l??a}),this.refreshUi(),a}getAuthContext(){return this.storage.getAuthContext?.()??null}async selfHealComment(e,t){this.selfHealedThisSession.add(e.id);let n=[],i=!1;for(let a of e.fingerprints||[]){let l=t.get(u(a));if(l?.target){let c=D(l.target);n.push(c),!i&&u(c)!==u(a)&&(i=!0)}else n.push(a)}if(!i)return;let r={fingerprints:n,fingerprintsRefreshedAt:Date.now()},s=this.comments.findIndex(a=>a.id===e.id);s!==-1&&(this.comments[s]={...this.comments[s],...r});try{await this.storage.updateComment(this.opts.namespace,e.id,r)}catch(a){typeof console<"u"&&console.warn("[vizu] self-heal write failed for",e.id,a)}}getCommentConfidence(e){return this.resolvedConfidence.get(e)}getResolvedConfidence(){return new Map(this.resolvedConfidence)}canUploadAttachments(){return typeof this.storage.uploadAttachment=="function"}async uploadAttachment(e){if(!this.storage.uploadAttachment)throw new Error("[vizu] active storage adapter does not support uploadAttachment; only the CloudStorageAdapter does in v1");return this.storage.uploadAttachment(this.opts.namespace,e)}async addReply(e,t,n){if(!this.requireUserOrAuth())return null;let i=this.comments.findIndex(a=>a.id===e);if(i===-1)return null;let r={id:F(),text:t.trim(),createdAt:Date.now(),author:this.user??void 0,authorId:this.user?.id,mentions:n&&n.length?n:void 0};if(!r.text)return null;let s={...this.comments[i],replies:[...this.comments[i].replies??[],r]};this.comments[i]=s;try{this.storage.addReply?await this.storage.addReply(this.opts.namespace,e,r):await this.storage.updateComment(this.opts.namespace,e,{replies:s.replies})}catch(a){let l=this.comments[i];this.comments[i]={...l,replies:(l.replies??[]).filter(v=>v.id!==r.id)};let c=a?.code??"",p=a?.status,m=p===403||c==="forbidden"?"Reply failed \u2014 you don't have permission.":p===401||c==="auth_required"?"Reply failed \u2014 please sign in again.":"Reply failed. Please try again.";if(this.pill?.toast(m),this.refreshUi(),this.popover?.isOpen()){let v=this.popover.getAnchors();v[0]&&this.popover.update(this.commentsForElement(v[0]))}return typeof console<"u"&&console.warn("[vizu] addReply failed",a),null}if(this.bus.emit("comment:updated",{comment:s}),this.refreshUi(),this.popover?.isOpen()){let a=this.popover.getAnchors();a[0]&&this.popover.update(this.commentsForElement(a[0]))}return s}async removeReply(e,t){let n=this.comments.findIndex(s=>s.id===e);if(n===-1)return null;let i=(this.comments[n].replies??[]).filter(s=>s.id!==t);if(i.length===(this.comments[n].replies??[]).length)return null;let r={...this.comments[n],replies:i};if(this.comments[n]=r,this.storage.removeReply?await this.storage.removeReply(this.opts.namespace,e,t):await this.storage.updateComment(this.opts.namespace,e,{replies:i}),this.bus.emit("comment:updated",{comment:r}),this.refreshUi(),this.popover?.isOpen()){let s=this.popover.getAnchors();s[0]&&this.popover.update(this.commentsForElement(s[0]))}return r}async clearAll(){let e=this.comments;this.comments=[],await this.storage.clear(this.opts.namespace),this.bus.emit("comments:cleared",{previous:e}),this.refreshUi(),this.popover?.close(),this.clearActiveAnchorOutlines(),this.highlighter?.setPaused(!1)}toggleMultiSelectMode(){this.requireUserOrAuth()&&(this.multiSelectMode=!this.multiSelectMode,this.multiSelectMode||this.clearSelection(),this.pill?.setMultiSelectState(this.multiSelectMode,this.pendingFingerprints.length))}clearSelection(){this.pendingFingerprints=[],this.pendingTargets=[],this.multiSelectMode=!1,this.clearActiveAnchorOutlines(),this.pill?.setMultiSelectState(!1,0)}commitSelection(){if(this.pendingFingerprints.length===0)return;let e=[...this.pendingTargets],t=[...this.pendingFingerprints];this.pendingFingerprints=[],this.pendingTargets=[],this.multiSelectMode=!1,this.pill?.setMultiSelectState(!1,0),(e.find(i=>!!i)??V(t[0]))&&this.openPopoverFor(e.filter(Boolean),t)}getSelection(){return[...this.pendingFingerprints]}async loadComments(){let e=await this.storage.load(this.opts.namespace);this.comments=S(e),oe(e)&&this.storage.setAll(this.opts.namespace,this.comments).catch(()=>{}),this.bus.emit("comments:loaded",{comments:this.getComments()}),this.enabled&&this.refreshUi()}subscribeStorage(){this.unsubscribeStorage||this.storage.subscribe&&(this.unsubscribeStorage=this.storage.subscribe(this.opts.namespace,e=>{if(e.type==="added"){let t=A(e.comment);this.comments.some(n=>n.id===t.id)||(this.comments.push(t),this.bus.emit("comment:added",{comment:t}),this.refreshUi())}else if(e.type==="updated"){let t=this.comments.findIndex(i=>i.id===e.comment.id),n=A(e.comment);t>=0&&(this.comments[t]=n,this.bus.emit("comment:updated",{comment:n}),this.refreshUi())}else if(e.type==="removed"){let t=this.comments.findIndex(n=>n.id===e.id);if(t>=0){let[n]=this.comments.splice(t,1);this.bus.emit("comment:removed",{id:e.id,comment:n}),this.refreshUi()}}else if(e.type==="cleared"){let t=this.comments;this.comments=[],this.bus.emit("comments:cleared",{previous:t}),this.refreshUi()}}))}deferred(e){typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e())}mount(){this.root||(he(),this.root=document.createElement("div"),this.root.setAttribute("data-vizu-root",""),this.root.style.setProperty("--vz-accent",this.opts.accent),document.body.appendChild(this.root),this.popover=new q(this.root,{onAdd:(e,t,n,i,r)=>{this.addCommentFromPopover(e,t,n,i,r)},onUploadAttachment:e=>this.uploadAttachment(e),canUploadAttachments:()=>this.canUploadAttachments(),onDelete:e=>{this.removeComment(e)},onAddReply:(e,t)=>{this.addReply(e,t)},onDeleteReply:(e,t)=>{this.removeReply(e,t)},onClose:()=>this.closePopover(),onStartReferencePick:(e,t)=>this.startPicking(e,t),onJumpToReference:(e,t)=>this.jumpToReference(e,t),onAnchorsChanged:e=>this.syncActiveAnchorOutlines(e),onMentionSearch:()=>this.searchMentionable()}),this.popover.setUser(this.user),this.sidebar=new J(this.root,{onJumpTo:e=>this.jumpToFingerprint(e),onJumpToReference:(e,t)=>this.jumpToReference(e,t),onDelete:e=>{this.removeComment(e)},onUpdateStatus:(e,t)=>{this.updateComment(e,{status:t})},onClose:()=>this.closeSidebar(),onFilterChange:e=>{this.statusFilter=e,this.renderAllMarkers()}}),this.pill=new K(this.root,{onToggleSidebar:()=>this.toggleSidebar(),onDisable:()=>this.disable(),onAction:e=>this.invokeAction(e),onToggleMultiSelect:()=>this.toggleMultiSelectMode(),onCommitSelection:()=>this.commitSelection(),onClearSelection:()=>this.clearSelection()}),this.pill.setActions(this.actions),this.pill.setUser(this.user),this.pill.setComments(this.comments),this.pill.setMultiSelectState(this.multiSelectMode,this.pendingFingerprints.length),this.pill.show(),this.highlighter=new B(this.root,this.opts.ignoreSelectors??[],(e,t)=>this.onElementClick(e,t)),this.highlighter.start(),this.renderAllMarkers(),window.addEventListener("scroll",this.scheduleReposition,!0),window.addEventListener("resize",this.scheduleReposition),this.bus.emit("mounted",{}))}unmount(){window.removeEventListener("scroll",this.scheduleReposition,!0),window.removeEventListener("resize",this.scheduleReposition),this.highlighter?.destroy(),this.popover?.destroy(),this.pill?.destroy(),this.sidebar?.destroy(),this.root?.remove(),this.root=null,this.highlighter=null,this.popover=null,this.pill=null,this.sidebar=null,this.markers.clear(),this.outlines.clear(),this.activeAnchorOutlines.clear();for(let e of this.spotlights)e.el.remove();this.spotlights.clear(),this.pendingFingerprints=[],this.pendingTargets=[],this.bus.emit("unmounted",{})}onElementClick(e,t){if(this.picking){let r=D(e),s=this.picking;this.picking=null,this.hidePickingBanner(),this.highlighter?.setPaused(this.popover?.isOpen()??!1),s.onSelect(r);return}if(!this.requireUserOrAuth())return;let n=D(e);if(this.popover?.isOpen()&&t.shiftKey){this.popover.addAnchor(n,e);return}if(!this.popover?.isOpen()&&(this.multiSelectMode||t.shiftKey)){this.addToSelection(n,e);return}this.bus.emit("element:selected",{target:e,fingerprint:n});let i=this.commentsForElement(n);this.openPopoverFor([e],[n],i)}openPopoverFor(e,t,n){if(!this.popover)return;let i=n??this.commentsForElement(t[0]);this.popover.open(e,t,i),this.syncActiveAnchorOutlines(t),this.highlighter?.setPaused(!0)}closePopover(){this.popover?.close(),this.clearActiveAnchorOutlines(),this.highlighter?.setPaused(!1),this.bus.emit("element:deselected",{})}addToSelection(e,t){let n=u(e);this.pendingFingerprints.some(i=>u(i)===n)?(this.pendingFingerprints=this.pendingFingerprints.filter(i=>u(i)!==n),this.pendingTargets=this.pendingTargets.filter(i=>i!==t)):(this.pendingFingerprints.push(e),this.pendingTargets.push(t)),this.syncActiveAnchorOutlines(this.pendingFingerprints),this.pill?.setMultiSelectState(!0,this.pendingFingerprints.length),this.pendingFingerprints.length>0?this.multiSelectMode=!0:this.multiSelectMode=!1}commentMatchesStatusFilter(e){if(this.statusFilter==="all")return!0;let t=this.comments.find(n=>n.id===e);return t?(t.status??"open")===this.statusFilter:!0}commentsForElement(e){let t=u(e);return this.comments.filter(n=>(n.fingerprints||[]).some(i=>u(i)===t))}async addCommentFromPopover(e,t,n,i,r){if(!this.requireUserOrAuth()||t.length===0)return;let s=typeof location<"u"?location.href:void 0,a={id:F(),schemaVersion:2,fingerprints:t,text:e,createdAt:Date.now(),pageVersion:this.opts.pageVersion,pageUrl:s,author:this.user??void 0,authorId:this.user?.id,references:n&&Object.keys(n).length?n:void 0,status:"open",replies:[],attachments:r??[],mentions:i??[]};this.comments.push(a),await this.storage.addComment(this.opts.namespace,a),this.bus.emit("comment:added",{comment:a}),this.popover?.update(this.commentsForElement(t[0])),this.refreshUi()}async removeComment(e){let t=this.comments.findIndex(i=>i.id===e);if(t===-1)return;let n=this.comments[t];this.comments=this.comments.filter(i=>i.id!==e);try{await this.storage.removeComment(this.opts.namespace,e)}catch(i){this.comments.splice(t,0,n);let r=i?.status,s=i?.code??"",a=r===403||s==="forbidden"?"Delete failed \u2014 you don't have permission.":r===401||s==="auth_required"?"Delete failed \u2014 please sign in again.":"Delete failed. Please try again.";if(this.pill?.toast(a),this.popover?.isOpen()){let l=this.popover.getAnchors();l[0]&&this.popover.update(this.commentsForElement(l[0]))}this.refreshUi(),typeof console<"u"&&console.warn("[vizu] removeComment failed",i);return}if(this.bus.emit("comment:removed",{id:e,comment:n}),this.popover?.isOpen()){let i=this.popover.getAnchors();i[0]&&this.popover.update(this.commentsForElement(i[0]))}this.refreshUi()}refreshUi(){this.pill?.setComments(this.comments),this.renderAllMarkers(),this.sidebar?.update(this.comments,this.resolvedConfidence)}toggleSidebar(){if(!this.sidebar)return;this.sidebar.isOpen()||this.renderAllMarkers(),this.sidebar.toggle(this.comments,this.resolvedConfidence);let e=this.sidebar.isOpen();this.pill?.setSidebarOpen(e),this.bus.emit(e?"sidebar:opened":"sidebar:closed",{})}closeSidebar(){this.sidebar?.close(),this.pill?.setSidebarOpen(!1),this.bus.emit("sidebar:closed",{})}startPicking(e,t){this.picking&&this.picking.onCancel(),this.picking={onSelect:e,onCancel:t},this.highlighter?.setPaused(!1),this.showPickingBanner()}cancelPicking(){if(!this.picking)return;let e=this.picking;this.picking=null,this.hidePickingBanner(),this.highlighter?.setPaused(this.popover?.isOpen()??!1),e.onCancel()}showPickingBanner(){this.root&&(this.pickingBanner=document.createElement("div"),this.pickingBanner.className="vz-picking-banner",this.pickingBanner.innerHTML=`
1202
+ `;function he(){if(typeof document>"u"||document.getElementById("vizu-styles"))return;let o=document.createElement("style");o.id="vizu-styles",o.textContent=qe,(document.head||document.documentElement).appendChild(o)}function ue(o){let e=o.toLowerCase().split("+").map(n=>n.trim());return{key:e.pop()||"",mod:e.includes("mod")||e.includes("cmdorctrl"),shift:e.includes("shift"),alt:e.includes("alt")||e.includes("option"),ctrl:e.includes("ctrl")||e.includes("control")}}function fe(o,e){let t=typeof navigator<"u"&&/Mac|iPhone|iPad/.test(navigator.platform);if(o.key.toLowerCase()!==e.key)return!1;if(e.mod){if(!(t?o.metaKey:o.ctrlKey))return!1}else if(e.ctrl&&!o.ctrlKey)return!1;return!(e.shift!==o.shiftKey||e.alt!==o.altKey)}var W=class{constructor(){this.listeners=new Map}on(e,t){let n=this.listeners.get(e);return n||(n=new Set,this.listeners.set(e,n)),n.add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){let n=this.listeners.get(e);if(n)for(let i of[...n])try{i(t)}catch(r){console.error("[vizu] event handler error for",e,r)}}clear(){this.listeners.clear()}};var te={shortcut:"mod+shift+e",namespace:"default",accent:"#FF6647"};function Ke(o,e,t,n){return e?(o&&typeof console<"u"&&console.warn("[vizu] Both `cloud` and `storage` set; `cloud` wins. Drop `storage` to silence this."),new E({workspace:e.workspace,apiUrl:e.apiUrl,autoSignIn:e.autoSignIn,onAuthChanged:n})):o?o==="local"?new O:o==="memory"?new I:o==="none"?new _:ee(o)?ae(o):o:t==="local"?new O:new I}var M=class{constructor(e={},t="memory"){this.root=null;this.highlighter=null;this.popover=null;this.pill=null;this.sidebar=null;this.markers=new Map;this.outlines=new Map;this.activeAnchorOutlines=new Map;this.spotlights=new Set;this.comments=[];this.resolvedConfidence=new Map;this.selfHealedThisSession=new Set;this.actions=[];this.user=null;this.statusFilter="all";this.hasLoadedComments=!1;this.enabled=!1;this.rafScheduled=!1;this.bus=new W;this.picking=null;this.pickingBanner=null;this.multiSelectMode=!1;this.pendingFingerprints=[];this.pendingTargets=[];this.unsubscribeStorage=null;this.onKeyDown=e=>{if(this.picking&&e.key==="Escape"){e.preventDefault(),this.cancelPicking();return}if(this.pendingFingerprints.length>0&&e.key==="Escape"&&!this.popover?.isOpen()){e.preventDefault(),this.clearSelection();return}if(fe(e,this.parsedShortcut)){e.preventDefault();let t=this.enabled;this.toggle(),!t&&this.enabled&&this.storage instanceof E&&this.storage.preflightAuth().catch(n=>{n?.code==="auth_canceled"||n?.code==="popup_blocked"||typeof console<"u"&&console.warn("[vizu] preflight auth failed:",n)})}};this.scheduleReposition=()=>{this.rafScheduled||(this.rafScheduled=!0,requestAnimationFrame(()=>{this.rafScheduled=!1;for(let[,e]of this.outlines)e.target&&this.positionOutline(e.outline,e.target);for(let[,e]of this.markers)e.target&&this.positionMarker(e.marker,e.target);for(let[,e]of this.activeAnchorOutlines){let t=V(e.fp);t&&this.positionOutline(e.el,t)}for(let e of this.spotlights)e.target&&this.positionOutline(e.el,e.target);this.popover?.reposition()}))};this.opts={...e},this.opts.shortcut=e.shortcut??te.shortcut,this.opts.namespace=e.namespace??te.namespace,this.opts.accent=e.accent??te.accent,this.parsedShortcut=ue(this.opts.shortcut),this.user=e.user??null,this.storage=Ke(e.storage,e.cloud,t,n=>{n.user&&this.setUser(n.user),this.hasLoadedComments&&this.loadComments()}),e.actions&&(this.actions=[...e.actions]),e.onCommentAdded&&this.bus.on("comment:added",n=>e.onCommentAdded(n.comment)),e.onCommentRemoved&&this.bus.on("comment:removed",n=>e.onCommentRemoved(n.id)),typeof window<"u"&&(window.addEventListener("keydown",this.onKeyDown),this.loadComments().catch(()=>{}).finally(()=>{this.hasLoadedComments=!0,this.subscribeStorage()}),e.startEnabled&&this.deferred(()=>this.enable()))}on(e,t){return this.bus.on(e,t)}off(e,t){this.bus.off(e,t)}setUser(e){this.user=e,this.popover?.setUser(e),this.pill?.setUser(e),this.bus.emit("user:changed",{user:e}),this.enabled&&!this.root&&this.shouldMount()&&this.deferred(()=>this.mount())}getUser(){return this.user}async searchMentionable(){if(this.opts.mentionable)try{return await this.opts.mentionable()}catch(e){return typeof console<"u"&&console.warn("[vizu] options.mentionable threw",e),[]}return this.storage instanceof E?this.storage.searchMentionable():[]}shouldMount(){return this.storage instanceof E?this.user!==null:!0}requireUserOrAuth(){return!(this.storage instanceof E)||this.user?!0:(this.storage.isAccessDenied()||this.storage.preflightAuth().catch(()=>{}),!1)}enable(){this.enabled||(this.enabled=!0,this.bus.emit("enabled",{}),this.shouldMount()&&this.deferred(()=>this.mount()))}disable(){this.enabled&&(this.enabled=!1,this.unmount(),this.bus.emit("disabled",{}))}toggle(){this.enabled?this.disable():this.enable()}isEnabled(){return this.enabled}destroy(){this.disable(),typeof window<"u"&&window.removeEventListener("keydown",this.onKeyDown),this.unsubscribeStorage?.(),this.unsubscribeStorage=null,this.selfHealedThisSession.clear(),this.bus.clear()}addAction(e){let t=this.actions.findIndex(n=>n.id===e.id);t>=0?this.actions[t]=e:this.actions.push(e),this.pill?.setActions(this.actions)}removeAction(e){this.actions=this.actions.filter(t=>t.id!==e),this.pill?.setActions(this.actions)}getActions(){return[...this.actions]}invokeAction(e){let t=this.actions.find(i=>i.id===e);if(!t)return;let n=this.actionContext();this.bus.emit("action:invoked",{id:e}),t.onClick(n)}getComments(){return[...this.comments]}async setComments(e,t){this.comments=S(e),t?.persist!==!1&&await this.storage.setAll(this.opts.namespace,this.comments),this.bus.emit("comments:set",{comments:this.getComments()}),this.refreshUi()}async updateComment(e,t){let n=this.comments.findIndex(c=>c.id===e);if(n===-1)return null;let{id:i,schemaVersion:r,...s}=t,a={...this.comments[n],...s};this.comments[n]=a;let l=await this.storage.updateComment(this.opts.namespace,e,s);return this.bus.emit("comment:updated",{comment:l??a}),this.refreshUi(),a}getAuthContext(){return this.storage.getAuthContext?.()??null}async selfHealComment(e,t){this.selfHealedThisSession.add(e.id);let n=[],i=!1;for(let a of e.fingerprints||[]){let l=t.get(u(a));if(l?.target){let c=D(l.target);n.push(c),!i&&u(c)!==u(a)&&(i=!0)}else n.push(a)}if(!i)return;let r={fingerprints:n,fingerprintsRefreshedAt:Date.now()},s=this.comments.findIndex(a=>a.id===e.id);s!==-1&&(this.comments[s]={...this.comments[s],...r});try{await this.storage.updateComment(this.opts.namespace,e.id,r)}catch(a){typeof console<"u"&&console.warn("[vizu] self-heal write failed for",e.id,a)}}getCommentConfidence(e){return this.resolvedConfidence.get(e)}getResolvedConfidence(){return new Map(this.resolvedConfidence)}canUploadAttachments(){return typeof this.storage.uploadAttachment=="function"}async uploadAttachment(e){if(!this.storage.uploadAttachment)throw new Error("[vizu] active storage adapter does not support uploadAttachment; only the CloudStorageAdapter does in v1");return this.storage.uploadAttachment(this.opts.namespace,e)}async addReply(e,t,n){if(!this.requireUserOrAuth())return null;let i=this.comments.findIndex(a=>a.id===e);if(i===-1)return null;let r={id:F(),text:t.trim(),createdAt:Date.now(),author:this.user??void 0,authorId:this.user?.id,mentions:n&&n.length?n:void 0};if(!r.text)return null;let s={...this.comments[i],replies:[...this.comments[i].replies??[],r]};this.comments[i]=s;try{this.storage.addReply?await this.storage.addReply(this.opts.namespace,e,r):await this.storage.updateComment(this.opts.namespace,e,{replies:s.replies})}catch(a){let l=this.comments[i];this.comments[i]={...l,replies:(l.replies??[]).filter(v=>v.id!==r.id)};let c=a?.code??"",p=a?.status,m=p===403||c==="forbidden"?"Reply failed \u2014 you don't have permission.":p===401||c==="auth_required"?"Reply failed \u2014 please sign in again.":"Reply failed. Please try again.";if(this.pill?.toast(m),this.refreshUi(),this.popover?.isOpen()){let v=this.popover.getAnchors();v[0]&&this.popover.update(this.commentsForElement(v[0]))}return typeof console<"u"&&console.warn("[vizu] addReply failed",a),null}if(this.bus.emit("comment:updated",{comment:s}),this.refreshUi(),this.popover?.isOpen()){let a=this.popover.getAnchors();a[0]&&this.popover.update(this.commentsForElement(a[0]))}return s}async removeReply(e,t){let n=this.comments.findIndex(s=>s.id===e);if(n===-1)return null;let i=(this.comments[n].replies??[]).filter(s=>s.id!==t);if(i.length===(this.comments[n].replies??[]).length)return null;let r={...this.comments[n],replies:i};if(this.comments[n]=r,this.storage.removeReply?await this.storage.removeReply(this.opts.namespace,e,t):await this.storage.updateComment(this.opts.namespace,e,{replies:i}),this.bus.emit("comment:updated",{comment:r}),this.refreshUi(),this.popover?.isOpen()){let s=this.popover.getAnchors();s[0]&&this.popover.update(this.commentsForElement(s[0]))}return r}async clearAll(){let e=this.comments;this.comments=[],await this.storage.clear(this.opts.namespace),this.bus.emit("comments:cleared",{previous:e}),this.refreshUi(),this.popover?.close(),this.clearActiveAnchorOutlines(),this.highlighter?.setPaused(!1)}toggleMultiSelectMode(){this.requireUserOrAuth()&&(this.multiSelectMode=!this.multiSelectMode,this.multiSelectMode||this.clearSelection(),this.pill?.setMultiSelectState(this.multiSelectMode,this.pendingFingerprints.length))}clearSelection(){this.pendingFingerprints=[],this.pendingTargets=[],this.multiSelectMode=!1,this.clearActiveAnchorOutlines(),this.pill?.setMultiSelectState(!1,0)}commitSelection(){if(this.pendingFingerprints.length===0)return;let e=[...this.pendingTargets],t=[...this.pendingFingerprints];this.pendingFingerprints=[],this.pendingTargets=[],this.multiSelectMode=!1,this.pill?.setMultiSelectState(!1,0),(e.find(i=>!!i)??V(t[0]))&&this.openPopoverFor(e.filter(Boolean),t)}getSelection(){return[...this.pendingFingerprints]}async loadComments(){let e=await this.storage.load(this.opts.namespace);this.comments=S(e),oe(e)&&this.storage.setAll(this.opts.namespace,this.comments).catch(()=>{}),this.bus.emit("comments:loaded",{comments:this.getComments()}),this.enabled&&this.refreshUi()}subscribeStorage(){this.unsubscribeStorage||this.storage.subscribe&&(this.unsubscribeStorage=this.storage.subscribe(this.opts.namespace,e=>{if(e.type==="added"){let t=A(e.comment);this.comments.some(n=>n.id===t.id)||(this.comments.push(t),this.bus.emit("comment:added",{comment:t}),this.refreshUi())}else if(e.type==="updated"){let t=this.comments.findIndex(i=>i.id===e.comment.id),n=A(e.comment);t>=0&&(this.comments[t]=n,this.bus.emit("comment:updated",{comment:n}),this.refreshUi())}else if(e.type==="removed"){let t=this.comments.findIndex(n=>n.id===e.id);if(t>=0){let[n]=this.comments.splice(t,1);this.bus.emit("comment:removed",{id:e.id,comment:n}),this.refreshUi()}}else if(e.type==="cleared"){let t=this.comments;this.comments=[],this.bus.emit("comments:cleared",{previous:t}),this.refreshUi()}}))}deferred(e){typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e())}mount(){this.root||(he(),this.root=document.createElement("div"),this.root.setAttribute("data-vizu-root",""),this.root.style.setProperty("--vz-accent",this.opts.accent),document.body.appendChild(this.root),this.popover=new q(this.root,{onAdd:(e,t,n,i,r)=>{this.addCommentFromPopover(e,t,n,i,r)},onUploadAttachment:e=>this.uploadAttachment(e),canUploadAttachments:()=>this.canUploadAttachments(),onDelete:e=>{this.removeComment(e)},onAddReply:(e,t)=>{this.addReply(e,t)},onDeleteReply:(e,t)=>{this.removeReply(e,t)},onClose:()=>this.closePopover(),onStartReferencePick:(e,t)=>this.startPicking(e,t),onJumpToReference:(e,t)=>this.jumpToReference(e,t),onAnchorsChanged:e=>this.syncActiveAnchorOutlines(e),onMentionSearch:()=>this.searchMentionable()}),this.popover.setUser(this.user),this.sidebar=new J(this.root,{onJumpTo:e=>this.jumpToFingerprint(e),onJumpToReference:(e,t)=>this.jumpToReference(e,t),onDelete:e=>{this.removeComment(e)},onUpdateStatus:(e,t)=>{this.updateComment(e,{status:t})},onClose:()=>this.closeSidebar(),onFilterChange:e=>{this.statusFilter=e,this.renderAllMarkers()}}),this.pill=new K(this.root,{onToggleSidebar:()=>this.toggleSidebar(),onDisable:()=>this.disable(),onAction:e=>this.invokeAction(e),onToggleMultiSelect:()=>this.toggleMultiSelectMode(),onCommitSelection:()=>this.commitSelection(),onClearSelection:()=>this.clearSelection()}),this.pill.setActions(this.actions),this.pill.setUser(this.user),this.pill.setComments(this.comments),this.pill.setMultiSelectState(this.multiSelectMode,this.pendingFingerprints.length),this.pill.show(),this.highlighter=new B(this.root,this.opts.ignoreSelectors??[],(e,t)=>this.onElementClick(e,t)),this.highlighter.start(),this.renderAllMarkers(),window.addEventListener("scroll",this.scheduleReposition,!0),window.addEventListener("resize",this.scheduleReposition),this.bus.emit("mounted",{}))}unmount(){window.removeEventListener("scroll",this.scheduleReposition,!0),window.removeEventListener("resize",this.scheduleReposition),this.highlighter?.destroy(),this.popover?.destroy(),this.pill?.destroy(),this.sidebar?.destroy(),this.root?.remove(),this.root=null,this.highlighter=null,this.popover=null,this.pill=null,this.sidebar=null,this.markers.clear(),this.outlines.clear(),this.activeAnchorOutlines.clear();for(let e of this.spotlights)e.el.remove();this.spotlights.clear(),this.pendingFingerprints=[],this.pendingTargets=[],this.bus.emit("unmounted",{})}onElementClick(e,t){if(this.picking){let r=D(e),s=this.picking;this.picking=null,this.hidePickingBanner(),this.highlighter?.setPaused(this.popover?.isOpen()??!1),s.onSelect(r);return}if(!this.requireUserOrAuth())return;let n=D(e);if(this.popover?.isOpen()&&t.shiftKey){this.popover.addAnchor(n,e);return}if(!this.popover?.isOpen()&&(this.multiSelectMode||t.shiftKey)){this.addToSelection(n,e);return}this.bus.emit("element:selected",{target:e,fingerprint:n});let i=this.commentsForElement(n);this.openPopoverFor([e],[n],i)}openPopoverFor(e,t,n){if(!this.popover)return;let i=n??this.commentsForElement(t[0]);this.popover.open(e,t,i),this.syncActiveAnchorOutlines(t),this.highlighter?.setPaused(!0)}closePopover(){this.popover?.close(),this.clearActiveAnchorOutlines(),this.highlighter?.setPaused(!1),this.bus.emit("element:deselected",{})}addToSelection(e,t){let n=u(e);this.pendingFingerprints.some(i=>u(i)===n)?(this.pendingFingerprints=this.pendingFingerprints.filter(i=>u(i)!==n),this.pendingTargets=this.pendingTargets.filter(i=>i!==t)):(this.pendingFingerprints.push(e),this.pendingTargets.push(t)),this.syncActiveAnchorOutlines(this.pendingFingerprints),this.pill?.setMultiSelectState(!0,this.pendingFingerprints.length),this.pendingFingerprints.length>0?this.multiSelectMode=!0:this.multiSelectMode=!1}commentMatchesStatusFilter(e){if(this.statusFilter==="all")return!0;let t=this.comments.find(n=>n.id===e);return t?(t.status??"open")===this.statusFilter:!0}commentsForElement(e){let t=u(e);return this.comments.filter(n=>(n.fingerprints||[]).some(i=>u(i)===t))}async addCommentFromPopover(e,t,n,i,r){if(!this.requireUserOrAuth()||t.length===0)return;let s=typeof location<"u"?location.href:void 0,a={id:F(),schemaVersion:2,fingerprints:t,text:e,createdAt:Date.now(),pageVersion:this.opts.pageVersion,pageUrl:s,author:this.user??void 0,authorId:this.user?.id,references:n&&Object.keys(n).length?n:void 0,status:"open",replies:[],attachments:r??[],mentions:i??[]};this.comments.push(a),await this.storage.addComment(this.opts.namespace,a),this.bus.emit("comment:added",{comment:a}),this.popover?.update(this.commentsForElement(t[0])),this.refreshUi()}async removeComment(e){let t=this.comments.findIndex(i=>i.id===e);if(t===-1)return;let n=this.comments[t];this.comments=this.comments.filter(i=>i.id!==e);try{await this.storage.removeComment(this.opts.namespace,e)}catch(i){this.comments.splice(t,0,n);let r=i?.status,s=i?.code??"",a=r===403||s==="forbidden"?"Delete failed \u2014 you don't have permission.":r===401||s==="auth_required"?"Delete failed \u2014 please sign in again.":"Delete failed. Please try again.";if(this.pill?.toast(a),this.popover?.isOpen()){let l=this.popover.getAnchors();l[0]&&this.popover.update(this.commentsForElement(l[0]))}this.refreshUi(),typeof console<"u"&&console.warn("[vizu] removeComment failed",i);return}if(this.bus.emit("comment:removed",{id:e,comment:n}),this.popover?.isOpen()){let i=this.popover.getAnchors();i[0]&&this.popover.update(this.commentsForElement(i[0]))}this.refreshUi()}refreshUi(){this.pill?.setComments(this.comments),this.renderAllMarkers(),this.sidebar?.update(this.comments,this.resolvedConfidence)}toggleSidebar(){if(!this.sidebar)return;this.sidebar.isOpen()||this.renderAllMarkers(),this.sidebar.toggle(this.comments,this.resolvedConfidence);let e=this.sidebar.isOpen();this.pill?.setSidebarOpen(e),this.bus.emit(e?"sidebar:opened":"sidebar:closed",{})}closeSidebar(){this.sidebar?.close(),this.pill?.setSidebarOpen(!1),this.bus.emit("sidebar:closed",{})}startPicking(e,t){this.picking&&this.picking.onCancel(),this.picking={onSelect:e,onCancel:t},this.highlighter?.setPaused(!1),this.showPickingBanner()}cancelPicking(){if(!this.picking)return;let e=this.picking;this.picking=null,this.hidePickingBanner(),this.highlighter?.setPaused(this.popover?.isOpen()??!1),e.onCancel()}showPickingBanner(){this.root&&(this.pickingBanner=document.createElement("div"),this.pickingBanner.className="vz-picking-banner",this.pickingBanner.innerHTML=`
1203
1203
  <span>Click any element to reference</span>
1204
1204
  <span class="vz-picking-banner-hint">or press <span class="vz-picking-banner-kbd">Esc</span></span>
1205
1205
  <button class="vz-picking-banner-cancel" data-vz="cancel-pick">Cancel</button>