@unhingged/vizu-core 0.1.22 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto.cjs +127 -323
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +124 -320
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +117 -325
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -113
- package/dist/index.d.ts +50 -113
- package/dist/index.js +114 -319
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +205 -18
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +122 -2
- package/dist/internal.d.ts +122 -2
- package/dist/internal.js +199 -15
- package/dist/internal.js.map +1 -1
- package/dist/vizu.min.js +47 -47
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
package/dist/vizu.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var VIZU=(()=>{var
|
|
1
|
+
"use strict";var VIZU=(()=>{var B=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var me=Object.getOwnPropertyNames;var ue=Object.prototype.hasOwnProperty;var ve=(o,e)=>{for(var t in e)B(o,t,{get:e[t],enumerable:!0})},fe=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of me(e))!ue.call(o,i)&&i!==t&&B(o,i,{get:()=>e[i],enumerable:!(n=he(e,i))||n.enumerable});return o};var ge=o=>fe(B({},"__esModule",{value:!0}),o);var Oe={};ve(Oe,{Vizu:()=>A});function L(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():Math.random().toString(36).slice(2)+Date.now().toString(36)}function K(o,e){return!!o.closest(e)}function d(o){return o==null?"":String(o).replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e])}function S(o){let e=new Date(o),n=Date.now()-o;return n<6e4?"just now":n<36e5?Math.floor(n/6e4)+"m ago":n<864e5?Math.floor(n/36e5)+"h ago":e.toLocaleDateString()+" "+e.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function J(o){return o?String(o).split(/\s+/).filter(Boolean).slice(0,2).map(e=>e[0].toUpperCase()).join(""):""}function y(o,e="vz-comment-author-avatar"){if(!o)return`<span class="${e}">\xB7</span>`;let t=o.name||o.id||"User";return o.avatarUrl?`<span class="${e}"><img src="${d(o.avatarUrl)}" alt="${d(t)}" /></span>`:`<span class="${e}">${d(J(t)||"?")}</span>`}function G(){return Math.random().toString(36).slice(2,10)}var be=/\[@([^\]]+)\]\(vizu-mention:([a-zA-Z0-9_-]+)\)/g,ze=/(\[#[^\]]+\]\(vizu-ref:[a-zA-Z0-9_-]+\)|\[@[^\]]+\]\(vizu-mention:[a-zA-Z0-9_-]+\))/g;function R(o,e){return o.split(ze).map(n=>{let i=n.match(/^\[#([^\]]+)\]\(vizu-ref:([a-zA-Z0-9_-]+)\)$/);if(i)return`<a class="vz-ref" data-vz="ref" data-comment-id="${d(e)}" data-ref-id="${d(i[2])}" role="button" tabindex="0">${d(i[1])}</a>`;let r=n.match(/^\[@([^\]]+)\]\(vizu-mention:([a-zA-Z0-9_-]+)\)$/);return r?`<span class="vz-mention" data-vz="mention" data-mention-id="${d(r[2])}">@${d(r[1])}</span>`:d(n)}).join("")}function Y(o){let e=[];for(let t of o.matchAll(be))e.includes(t[2])||e.push(t[2]);return e}function $(o){return!Array.isArray(o)||o.length===0?"":`<div class="vz-attachment-grid">${o.map(t=>{let n=t.mimeType?.startsWith("image/"),i=t.filename??t.url.split("/").pop()??"file";return n?`
|
|
2
2
|
<a class="vz-attachment-display" href="${d(t.url)}" target="_blank" rel="noreferrer" title="${d(i)}">
|
|
3
3
|
<img src="${d(t.url)}" alt="${d(i)}" loading="lazy" />
|
|
4
4
|
</a>
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
<span class="vz-attachment-icon" aria-hidden="true">\u{1F4C4}</span>
|
|
8
8
|
<span class="vz-attachment-filename">${d(i)}</span>
|
|
9
9
|
</a>
|
|
10
|
-
`}).join("")}</div>`}function A(o){if(!o||typeof o!="object")return o;let e={...o};return e.id==null&&e._id!=null&&(e.id=e._id),(!Array.isArray(e.fingerprints)||e.fingerprints.length===0)&&e.fingerprint&&(e.fingerprints=[e.fingerprint]),delete e.fingerprint,e.url&&!e.pageUrl&&(e.pageUrl=e.url),(!("status"in e)||!Me(e.status))&&(e.status="open"),Array.isArray(e.replies)||(e.replies=[]),Array.isArray(e.attachments)||(e.attachments=[]),Array.isArray(e.mentions)||(e.mentions=[]),"fingerprintsRefreshedAt"in e||(e.fingerprintsRefreshedAt=null),e.schemaVersion=2,e}function Me(o){return o==="open"||o==="resolved"||o==="wontfix"}function S(o){if(!Array.isArray(o))return[];let e=[];for(let t of o)!t||typeof t!="object"||e.push(A(t));return e}function oe(o){if(!Array.isArray(o))return!1;for(let e of o){if(!e||typeof e!="object")continue;let t=e;if(t.schemaVersion!==2||"fingerprint"in t||!Array.isArray(t.replies)||!Array.isArray(t.attachments)||!Array.isArray(t.mentions))return!0}return!1}var Q=o=>`vizu:comments:${o}`,O=class{async load(e){return P(e)}async addComment(e,t){let n=P(e);n.push(t),R(e,n)}async updateComment(e,t,n){let i=P(e),r=i.findIndex(a=>a.id===t);if(r===-1)return null;let s={...i[r],...n,id:i[r].id};return i[r]=s,R(e,i),s}async removeComment(e,t){let n=P(e);R(e,n.filter(i=>i.id!==t))}async setAll(e,t){R(e,t)}async clear(e){typeof localStorage>"u"||localStorage.removeItem(Q(e))}async addReply(e,t,n){let i=P(e),r=i.findIndex(a=>a.id===t);if(r===-1)return null;let s={...i[r],replies:[...i[r].replies??[],n]};return i[r]=s,R(e,i),s}async removeReply(e,t,n){let i=P(e),r=i.findIndex(a=>a.id===t);if(r===-1)return null;let s={...i[r],replies:(i[r].replies??[]).filter(a=>a.id!==n)};return i[r]=s,R(e,i),s}};function P(o){if(typeof localStorage>"u")return[];let e=localStorage.getItem(Q(o));if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?S(t):[]}catch{return[]}}function R(o,e){typeof localStorage>"u"||localStorage.setItem(Q(o),JSON.stringify(e))}var I=class{constructor(){this.store=new Map}async load(e){return[...this.store.get(e)||[]]}async addComment(e,t){let n=this.store.get(e)??[];this.store.set(e,[...n,t])}async updateComment(e,t,n){let i=this.store.get(e)??[],r=i.findIndex(l=>l.id===t);if(r===-1)return null;let s={...i[r],...n,id:i[r].id},a=[...i];return a[r]=s,this.store.set(e,a),s}async removeComment(e,t){let n=this.store.get(e)??[];this.store.set(e,n.filter(i=>i.id!==t))}async setAll(e,t){this.store.set(e,[...t])}async clear(e){this.store.delete(e)}async addReply(e,t,n){let i=this.store.get(e)??[],r=i.findIndex(l=>l.id===t);if(r===-1)return null;let s={...i[r],replies:[...i[r].replies??[],n]},a=[...i];return a[r]=s,this.store.set(e,a),s}async removeReply(e,t,n){let i=this.store.get(e)??[],r=i.findIndex(l=>l.id===t);if(r===-1)return null;let s={...i[r],replies:(i[r].replies??[]).filter(l=>l.id!==n)},a=[...i];return a[r]=s,this.store.set(e,a),s}},_=class{async load(){return[]}async addComment(){}async updateComment(){return null}async removeComment(){}async setAll(){}async clear(){}};function ee(o){if(!o||typeof o!="object")return!1;let e=o;return typeof e.load=="function"&&typeof e.save=="function"&&typeof e.clear=="function"&&typeof e.addComment!="function"}function ae(o){if(!ee(o))return o;let e=!1,t=()=>{e||(e=!0,typeof console<"u"&&console.warn("[vizu] StorageAdapter v1 detected. Per-comment ops are emulated via full-array rewrites \u2014 upgrade your adapter to the v2 interface (addComment / updateComment / removeComment / setAll / load / clear) when you can."))};return{async load(n){return t(),o.load(n)},async addComment(n,i){t();let r=await o.load(n);r.push(i),await o.save(n,r)},async updateComment(n,i,r){t();let s=await o.load(n),a=s.findIndex(c=>c.id===i);if(a===-1)return null;let l={...s[a],...r,id:s[a].id};return s[a]=l,await o.save(n,s),l},async removeComment(n,i){t();let r=await o.load(n);await o.save(n,r.filter(s=>s.id!==i))},async setAll(n,i){t(),await o.save(n,i)},async clear(n){await o.clear(n)}}}var Te="https://vizu.unhingged.com",le=480,ce=640,E=class{constructor(e){this.cachedToken=null;this.pendingAuth=null;this.lastNotifiedTokenId=null;this.accessDenied=!1;this.accessDeniedReason=null;this.workspace=e.workspace,this.apiUrl=(e.apiUrl??Te).replace(/\/$/,""),this.autoSignIn=e.autoSignIn!==!1,this.onAuthChanged=e.onAuthChanged;let t=this.readHashToken();t?(this.cachedToken=t,this.writeStoredToken(t)):this.cachedToken=this.readStoredToken(),this.cachedToken&&!this.isExpired(this.cachedToken)&&this.fireAuthChanged(this.cachedToken)}readHashToken(){if(typeof window>"u")return null;let e=window.location.hash;if(!e||e.length<2)return null;let t=new URLSearchParams(e.slice(1)),n=t.get("vizu_auth");if(!n)return null;try{let i=Uint8Array.from(atob(n),p=>p.charCodeAt(0)),r=new TextDecoder("utf-8").decode(i),s=JSON.parse(r);if(!s.t||!s.e||!s.u||!s.w||s.w!==this.workspace)return null;let a={token:s.t,expiresAt:s.e,userId:s.u,user:typeof s.n=="string"?{id:s.u,name:s.n,avatarUrl:typeof s.a=="string"?s.a:void 0}:null};t.delete("vizu_auth");let l=t.toString(),c=l?`#${l}`:"";try{let p=window.location.pathname+window.location.search+c;window.history.replaceState(null,"",p)}catch{}return a}catch{return null}}async preflightAuth(){await this.resolveToken()}isAccessDenied(){return this.accessDenied}accessDenialReason(){return this.accessDeniedReason}async searchMentionable(){if(!this.cachedToken||this.isExpired(this.cachedToken))return[];try{let e=await this.fetchAuthed(this.apiUrl+"/api/workspaces/"+encodeURIComponent(this.workspace)+"/mentionable",{method:"GET"});if(!e.ok)return[];let t=await this.parseJson(e);return Array.isArray(t?.users)?t.users:[]}catch{return[]}}async load(e){if(!this.cachedToken||this.isExpired(this.cachedToken)){let i=this.readStoredToken();if(!i||this.isExpired(i))return[];this.cachedToken=i}let t=[],n=null;do{let i=new URL(this.apiUrl+"/api/comments");i.searchParams.set("workspace",this.workspace),i.searchParams.set("limit","100"),n&&i.searchParams.set("cursor",n);let r=await this.fetchAuthed(i.toString(),{method:"GET"}),s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);let a=Array.isArray(s.comments)?s.comments:[];t.push(...S(a)),n=typeof s.nextCursor=="string"?s.nextCursor:null}while(n);return t}async addComment(e,t){let n=await this.fetchAuthed(this.apiUrl+"/api/comments",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({workspace:this.workspace,comment:t})}),i=await this.parseJson(n);if(!n.ok)throw k(n.status,i)}async updateComment(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t));i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)});if(r.status===404)return null;let s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);return A(s.comment)}async removeComment(e,t){let n=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t));n.searchParams.set("workspace",this.workspace);let i=await this.fetchAuthed(n.toString(),{method:"DELETE"});if(i.status!==404&&!i.ok){let r=await this.parseJson(i);throw k(i.status,r)}}async setAll(e,t){await this.clear(e);for(let n of t)await this.addComment(e,n)}async clear(e){let t=new URL(this.apiUrl+"/api/comments");t.searchParams.set("workspace",this.workspace),t.searchParams.set("all","true");let n=await this.fetchAuthed(t.toString(),{method:"DELETE"});if(n.status!==404&&!n.ok){let i=await this.parseJson(n);throw k(n.status,i)}}async addReply(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t)+"/replies");i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)});if(r.status===404)return null;let s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);return s?.comment?A(s.comment):null}async removeReply(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t)+"/replies/"+encodeURIComponent(n));i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"DELETE"});if(r.status===404)return null;if(!r.ok){let s=await this.parseJson(r);throw k(r.status,s)}return null}async uploadAttachment(e,t){let n=new FormData;n.append("file",t,t.name);let i=new URL(this.apiUrl+"/api/uploads");i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"POST",body:n}),s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);if(!s?.url||typeof s.url!="string")throw k(500,{error:"upload_failed",message:"Backend did not return a URL."});return{id:F(),url:s.url,mimeType:t.type||"application/octet-stream",sizeBytes:t.size,uploadedAt:Date.now(),filename:t.name}}getAuthContext(){return this.cachedToken?{userId:this.cachedToken.userId,token:this.cachedToken.token,expiresAt:this.cachedToken.expiresAt}:null}isExpired(e){if(!e)return!0;let t=new Date(e.expiresAt).getTime();return!Number.isFinite(t)||t-Date.now()<3e4}readStoredToken(){if(typeof sessionStorage>"u")return null;let e=sessionStorage.getItem(this.sessionKey());if(!e)return null;try{let t=JSON.parse(e);return this.isExpired(t)?(sessionStorage.removeItem(this.sessionKey()),null):t}catch{return null}}writeStoredToken(e){this.cachedToken=e,typeof sessionStorage<"u"&&sessionStorage.setItem(this.sessionKey(),JSON.stringify(e))}clearStoredToken(){this.cachedToken=null,typeof sessionStorage<"u"&&sessionStorage.removeItem(this.sessionKey())}sessionKey(){return`vizu:cloud-token:v2:${this.workspace}`}async resolveToken(){if(this.cachedToken&&!this.isExpired(this.cachedToken))return this.cachedToken;let e=this.readStoredToken();if(e)return this.cachedToken=e,this.fireAuthChanged(e),e;if(this.accessDenied)throw L("access_denied",`No access to workspace "${this.workspace}" (${this.accessDeniedReason??"no_access"}).`);if(!this.autoSignIn)throw L("auth_required","Sign-in required; autoSignIn is disabled.");return this.pendingAuth||(this.pendingAuth=this.openSignInPopup().then(t=>(this.writeStoredToken(t),this.fireAuthChanged(t),t)).finally(()=>{this.pendingAuth=null})),this.pendingAuth}fireAuthChanged(e){if(this.lastNotifiedTokenId!==e.token){this.lastNotifiedTokenId=e.token;try{this.onAuthChanged?.({userId:e.userId,user:e.user})}catch(t){typeof console<"u"&&console.warn("[vizu/cloud] onAuthChanged callback threw",t)}}}openSignInPopup(){return new Promise((e,t)=>{if(typeof window>"u")return t(L("auth_unavailable","Sign-in requires a browser environment."));let n=new URL(this.apiUrl).origin,i=window.location.origin,r=this.apiUrl+"/connect?workspace="+encodeURIComponent(this.workspace)+"&origin="+encodeURIComponent(i),s=Math.max(0,Math.round((window.screen.width-le)/2)),a=Math.max(0,Math.round((window.screen.height-ce)/2)),l=window.open(r,"vizu-connect",`width=${le},height=${ce},left=${s},top=${a},noopener=no,popup=yes`);if(!l)try{let b=window.location.href,g=this.apiUrl+"/connect?workspace="+encodeURIComponent(this.workspace)+"&origin="+encodeURIComponent(i)+"&return_to="+encodeURIComponent(b);window.location.assign(g);return}catch(b){return t(L("popup_blocked","Sign-in popup was blocked and redirect fallback failed: "+(b instanceof Error?b.message:String(b))))}let c=!1,p=b=>{if(b.origin!==n)return;let g=b.data;if(!g)return;if(g.type==="vizu:auth-denied"&&g.workspace===this.workspace){c=!0,v(),this.accessDenied=!0,this.accessDeniedReason=typeof g.reason=="string"?g.reason:"no_access",t(L("access_denied",`No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`));return}if(g.type!=="vizu:auth"||g.workspace!==this.workspace||typeof g.token!="string"||typeof g.expiresAt!="string"||typeof g.userId!="string")return;c=!0,v();let z=g.user,X=z&&typeof z.name=="string"?{id:typeof z.id=="string"?z.id:g.userId,name:z.name,avatarUrl:typeof z.avatarUrl=="string"?z.avatarUrl:void 0}:null;e({token:g.token,expiresAt:g.expiresAt,userId:g.userId,user:X})},m=window.setInterval(()=>{l.closed&&(c||(v(),t(L("auth_canceled","Sign-in popup was closed before completing."))))},500);function v(){window.clearInterval(m),window.removeEventListener("message",p)}window.addEventListener("message",p)})}async fetchAuthed(e,t,n=!1){let i=await this.resolveToken(),r=new Headers(t.headers);r.set("authorization",`Bearer ${i.token}`);let s=await fetch(e,{...t,headers:r,credentials:"omit"});return s.status===401&&!n?(this.clearStoredToken(),this.fetchAuthed(e,t,!0)):s}async parseJson(e){try{let t=await e.text();return t?JSON.parse(t):null}catch{return null}}};function L(o,e){let t=new Error(e);return t.code=o,t}function k(o,e){let t=e?.error==="origin_not_allowed"?"origin_not_allowed":e?.error==="not_found"?"workspace_not_found":"http_error",n=new Error(e?.message??`HTTP ${o}`);return n.code=t,n.status=o,n.body=e,n}function de(o){let e=[],t=o,n=0;for(;t&&t!==document.documentElement&&n<8;){let i=t.tagName.toLowerCase();if(t.id){e.unshift("#"+CSS.escape(t.id));break}let r=Array.from(t.classList).filter(a=>!a.startsWith("vz-")&&!a.startsWith("vizu-")).slice(0,2);r.length&&(i+="."+r.map(a=>CSS.escape(a)).join("."));let s=t.parentElement;if(s){let a=Array.from(s.children).filter(l=>l.tagName===t.tagName);if(a.length>1){let l=a.indexOf(t);i+=":nth-of-type("+(l+1)+")"}}e.unshift(i),t=s,n++}return e.join(" > ")}function D(o){let e=o.innerText||o.textContent||"",t=o.parentElement,n=0;return t&&(n=Array.from(t.children).indexOf(o)),{selector:de(o),parentSelector:t?de(t):"",tagName:o.tagName,textSnippet:e.trim().slice(0,80),siblingIndex:n,attributes:{id:o.id||void 0,classList:Array.from(o.classList).filter(i=>!i.startsWith("vz-")&&!i.startsWith("vizu-")),role:o.getAttribute("role")||void 0,ariaLabel:o.getAttribute("aria-label")||void 0,dataKey:o.getAttribute("data-vizu-key")||void 0},ancestorChain:Pe(o,4),algorithmVersion:2}}function Pe(o,e){let t=[],n=o.parentElement;for(;n&&t.length<e;){let i=n.parentElement,r=1;i&&(r=Array.from(i.children).filter(a=>a.tagName===n.tagName).indexOf(n)+1),t.push({tag:n.tagName,nthOfType:r}),n=i}return t.length>0?{steps:t}:void 0}function j(o,e=document){if(o.attributes.dataKey){try{let s=e.querySelector(`[data-vizu-key="${CSS.escape(o.attributes.dataKey)}"]`);if(s)return{element:s,confidence:"exact"}}catch{}return{element:null,confidence:"orphaned"}}if(o.attributes.id)try{let s=e.querySelector("#"+CSS.escape(o.attributes.id));if(s)return{element:s,confidence:"exact"}}catch{}let t=[],n=o.attributes.classList;if(n&&n.length>0){let s=Re(e,o.tagName,n);s?.element&&t.push({el:s.element,rung:"class"})}try{let s=e.querySelector(o.selector);s&&t.push({el:s,rung:"selector"})}catch{}if(o.ancestorChain&&o.ancestorChain.steps.length>0){let s=Le(e,o.tagName,o.ancestorChain.steps);s?.element&&t.push({el:s.element,rung:"chain"})}else try{let s=o.parentSelector?e.querySelector(o.parentSelector):e;if(s){let a=s.children[o.siblingIndex];a&&a.tagName===o.tagName&&t.push({el:a,rung:"chain"})}}catch{}if(o.textSnippet){let s=$e(e,o.tagName,o.textSnippet);s?.element&&t.push({el:s.element,rung:"text"})}if(t.length===0)return{element:null,confidence:"orphaned"};let i=new Map;for(let s of t){let a=i.get(s.el);a||(a=new Set,i.set(s.el,a)),a.add(s.rung)}let r=null;for(let[s,a]of i)(!r||a.size>r.rungs.size)&&(r={el:s,rungs:a});return r?r.rungs.size>=3?{element:r.el,confidence:"likely"}:r.rungs.size>=2?{element:r.el,confidence:"drifted"}:r.rungs.has("class")||r.rungs.has("chain")?{element:r.el,confidence:"drifted"}:{element:null,confidence:"orphaned"}:{element:null,confidence:"orphaned"}}function Re(o,e,t){let n=new Set(t.filter(a=>!a.startsWith("vz-")&&!a.startsWith("vizu-")));if(n.size===0)return null;let i=o.querySelectorAll(e.toLowerCase()),r=null,s=0;for(let a of i){let l=new Set(Array.from(a.classList).filter(p=>!p.startsWith("vz-")&&!p.startsWith("vizu-")));if(l.size===0)continue;let c=Oe(n,l);c<.6||(!r||c>r.score?(s=r?.score??0,r={el:a,score:c}):c>s&&(s=c))}return!r||r.score-s<.1?null:{element:r.el,confidence:"likely"}}function Le(o,e,t){let n=o.querySelectorAll(e.toLowerCase()),i=null;for(let r of n){let s=Ve(r,t.length+3),a=He(s,t,3);a<2||(!i||a>i.score)&&(i={el:r,score:a})}return i?{element:i.el,confidence:i.score===t.length?"likely":"drifted"}:null}function Ve(o,e){let t=[],n=o.parentElement;for(;n&&t.length<e;){let i=n.parentElement,r=1;i&&(r=Array.from(i.children).filter(a=>a.tagName===n.tagName).indexOf(n)+1),t.push({tag:n.tagName,nthOfType:r}),n=i}return t}function $e(o,e,t){let n=pe(t);if(!n)return null;let i=o.querySelectorAll(e.toLowerCase()),r=null;for(let s of i){let a=s.innerText||s.textContent||"",l=pe(a.slice(0,160));if(!l)continue;let c=Fe(n,l);c<.75||(!r||c>r.score)&&(r={el:s,score:c})}return r?{element:r.el,confidence:"drifted"}:null}function pe(o){return o.toLowerCase().replace(/\s+/g," ").trim()}function Fe(o,e){if(o===e)return 1;if(o.length===0||e.length===0)return o.length===e.length?1:0;let t=Math.max(o.length,e.length);return 1-Ue(o,e)/t}function Ue(o,e){if(o.length>e.length){let i=o;o=e,e=i}let t=new Array(o.length+1),n=new Array(o.length+1);for(let i=0;i<=o.length;i++)t[i]=i;for(let i=1;i<=e.length;i++){n[0]=i;for(let r=1;r<=o.length;r++){let s=o.charCodeAt(r-1)===e.charCodeAt(i-1)?0:1;n[r]=Math.min(n[r-1]+1,t[r]+1,t[r-1]+s)}for(let r=0;r<=o.length;r++)t[r]=n[r]}return t[o.length]}function He(o,e,t){let n=0;for(let i=0;i<=t;i++){let r=0;for(let s=0;s<e.length;s++){let a=o[s+i];if(!a)break;a.tag===e[s].tag&&a.nthOfType===e[s].nthOfType&&r++}r>n&&(n=r)}return n}function Oe(o,e){if(o.size===0&&e.size===0)return 1;let t=0;for(let i of o)e.has(i)&&t++;let n=o.size+e.size-t;return n===0?0:t/n}function V(o,e){return j(o,e).element}function u(o){return o.selector+"|"+o.textSnippet}function $(o){let e=o.tagName.toLowerCase();if(o.textSnippet){let t=o.textSnippet.length>24?o.textSnippet.slice(0,24)+"\u2026":o.textSnippet;return e+": "+t}return o.attributes.id?e+"#"+o.attributes.id:o.attributes.classList&&o.attributes.classList.length?e+"."+o.attributes.classList[0]:e}var Ie="[data-vizu-root], [data-vizu-ignore], script, style, link, meta, head",B=class{constructor(e,t,n){this.current=null;this.active=!1;this.paused=!1;this.handleMove=e=>{if(this.paused){this.setCurrent(null),this.overlay.style.display="none";return}let t=document.elementFromPoint(e.clientX,e.clientY);if(!(!t||t===this.current)){if(this.shouldIgnore(t)){this.setCurrent(null),this.overlay.style.display="none";return}this.setCurrent(t),this.updateOverlay()}};this.handleClick=e=>{let t=document.elementFromPoint(e.clientX,e.clientY);t&&(this.shouldIgnore(t)||(e.preventDefault(),e.stopPropagation(),this.onClick(t,e)))};this.handleScroll=()=>{this.current&&this.updateOverlay()};this.root=e,this.extraIgnore=t,this.onClick=n,this.overlay=document.createElement("div"),this.overlay.className="vz-highlight",this.overlay.style.display="none",this.root.appendChild(this.overlay)}setCurrent(e){this.current!==e&&(this.current=e)}start(){this.active||(this.active=!0,document.addEventListener("mousemove",this.handleMove,!0),document.addEventListener("click",this.handleClick,!0),document.addEventListener("scroll",this.handleScroll,!0),window.addEventListener("resize",this.handleScroll))}stop(){this.active&&(this.active=!1,document.removeEventListener("mousemove",this.handleMove,!0),document.removeEventListener("click",this.handleClick,!0),document.removeEventListener("scroll",this.handleScroll,!0),window.removeEventListener("resize",this.handleScroll),this.overlay.style.display="none",this.setCurrent(null))}shouldIgnore(e){if(e===document.documentElement||e===document.body||Z(e,Ie))return!0;for(let t of this.extraIgnore)try{if(Z(e,t))return!0}catch{}return!1}setPaused(e){this.paused=e,e&&(this.setCurrent(null),this.overlay.style.display="none")}isPaused(){return this.paused}updateOverlay(){if(!this.current)return;let e=this.current.getBoundingClientRect();if(e.width===0||e.height===0){this.overlay.style.display="none";return}this.overlay.style.display="block",this.overlay.style.left=e.left+"px",this.overlay.style.top=e.top+"px",this.overlay.style.width=e.width+"px",this.overlay.style.height=e.height+"px"}destroy(){this.stop(),this.overlay.remove()}};function me(o){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(o):o.replace(/["\\]/g,"\\$&")}function De(o){let t=(o.replies??[]).map(n=>`
|
|
10
|
+
`}).join("")}</div>`}function C(o){if(!o||typeof o!="object")return o;let e={...o};return e.id==null&&e._id!=null&&(e.id=e._id),(!Array.isArray(e.fingerprints)||e.fingerprints.length===0)&&e.fingerprint&&(e.fingerprints=[e.fingerprint]),delete e.fingerprint,e.url&&!e.pageUrl&&(e.pageUrl=e.url),(!("status"in e)||!xe(e.status))&&(e.status="open"),Array.isArray(e.replies)||(e.replies=[]),Array.isArray(e.attachments)||(e.attachments=[]),Array.isArray(e.mentions)||(e.mentions=[]),"fingerprintsRefreshedAt"in e||(e.fingerprintsRefreshedAt=null),e.schemaVersion=2,e}function xe(o){return o==="open"||o==="resolved"||o==="wontfix"}function F(o){if(!Array.isArray(o))return[];let e=[];for(let t of o)!t||typeof t!="object"||e.push(C(t));return e}function Z(o){if(!Array.isArray(o))return!1;for(let e of o){if(!e||typeof e!="object")continue;let t=e;if(t.schemaVersion!==2||"fingerprint"in t||!Array.isArray(t.replies)||!Array.isArray(t.attachments)||!Array.isArray(t.mentions))return!0}return!1}var ye="https://vizu.unhingged.com",Q=480,ee=640,V=class{constructor(e){this.cachedToken=null;this.pendingAuth=null;this.lastNotifiedTokenId=null;this.accessDenied=!1;this.accessDeniedReason=null;this.workspace=e.workspace,this.apiUrl=(e.apiUrl??ye).replace(/\/$/,""),this.autoSignIn=e.autoSignIn!==!1,this.onAuthChanged=e.onAuthChanged;let t=this.readHashToken();t?(this.cachedToken=t,this.writeStoredToken(t)):this.cachedToken=this.readStoredToken(),this.cachedToken&&!this.isExpired(this.cachedToken)&&this.fireAuthChanged(this.cachedToken)}readHashToken(){if(typeof window>"u")return null;let e=window.location.hash;if(!e||e.length<2)return null;let t=new URLSearchParams(e.slice(1)),n=t.get("vizu_auth");if(!n)return null;try{let i=Uint8Array.from(atob(n),p=>p.charCodeAt(0)),r=new TextDecoder("utf-8").decode(i),s=JSON.parse(r);if(!s.t||!s.e||!s.u||!s.w||s.w!==this.workspace)return null;let a={token:s.t,expiresAt:s.e,userId:s.u,user:typeof s.n=="string"?{id:s.u,name:s.n,avatarUrl:typeof s.a=="string"?s.a:void 0}:null};t.delete("vizu_auth");let c=t.toString(),l=c?`#${c}`:"";try{let p=window.location.pathname+window.location.search+l;window.history.replaceState(null,"",p)}catch{}return a}catch{return null}}async preflightAuth(){await this.resolveToken()}isAccessDenied(){return this.accessDenied}accessDenialReason(){return this.accessDeniedReason}async searchMentionable(){if(!this.cachedToken||this.isExpired(this.cachedToken))return[];try{let e=await this.fetchAuthed(this.apiUrl+"/api/workspaces/"+encodeURIComponent(this.workspace)+"/mentionable",{method:"GET"});if(!e.ok)return[];let t=await this.parseJson(e);return Array.isArray(t?.users)?t.users:[]}catch{return[]}}async load(e){if(!this.cachedToken||this.isExpired(this.cachedToken)){let i=this.readStoredToken();if(!i||this.isExpired(i))return[];this.cachedToken=i}let t=[],n=null;do{let i=new URL(this.apiUrl+"/api/comments");i.searchParams.set("workspace",this.workspace),i.searchParams.set("limit","100"),n&&i.searchParams.set("cursor",n);let r=await this.fetchAuthed(i.toString(),{method:"GET"}),s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);let a=Array.isArray(s.comments)?s.comments:[];t.push(...F(a)),n=typeof s.nextCursor=="string"?s.nextCursor:null}while(n);return t}async addComment(e,t){let n=await this.fetchAuthed(this.apiUrl+"/api/comments",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({workspace:this.workspace,comment:t})}),i=await this.parseJson(n);if(!n.ok)throw k(n.status,i)}async updateComment(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t));i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify(n)});if(r.status===404)return null;let s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);return C(s.comment)}async removeComment(e,t){let n=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t));n.searchParams.set("workspace",this.workspace);let i=await this.fetchAuthed(n.toString(),{method:"DELETE"});if(i.status!==404&&!i.ok){let r=await this.parseJson(i);throw k(i.status,r)}}async setAll(e,t){await this.clear(e);for(let n of t)await this.addComment(e,n)}async clear(e){let t=new URL(this.apiUrl+"/api/comments");t.searchParams.set("workspace",this.workspace),t.searchParams.set("all","true");let n=await this.fetchAuthed(t.toString(),{method:"DELETE"});if(n.status!==404&&!n.ok){let i=await this.parseJson(n);throw k(n.status,i)}}async addReply(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t)+"/replies");i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(n)});if(r.status===404)return null;let s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);return s?.comment?C(s.comment):null}async removeReply(e,t,n){let i=new URL(this.apiUrl+"/api/comments/"+encodeURIComponent(t)+"/replies/"+encodeURIComponent(n));i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"DELETE"});if(r.status===404)return null;if(!r.ok){let s=await this.parseJson(r);throw k(r.status,s)}return null}async uploadAttachment(e,t){let n=new FormData;n.append("file",t,t.name);let i=new URL(this.apiUrl+"/api/uploads");i.searchParams.set("workspace",this.workspace);let r=await this.fetchAuthed(i.toString(),{method:"POST",body:n}),s=await this.parseJson(r);if(!r.ok)throw k(r.status,s);if(!s?.url||typeof s.url!="string")throw k(500,{error:"upload_failed",message:"Backend did not return a URL."});return{id:L(),url:s.url,mimeType:t.type||"application/octet-stream",sizeBytes:t.size,uploadedAt:Date.now(),filename:t.name}}getAuthContext(){return this.cachedToken?{userId:this.cachedToken.userId,token:this.cachedToken.token,expiresAt:this.cachedToken.expiresAt}:null}isExpired(e){if(!e)return!0;let t=new Date(e.expiresAt).getTime();return!Number.isFinite(t)||t-Date.now()<3e4}readStoredToken(){if(typeof sessionStorage>"u")return null;let e=sessionStorage.getItem(this.sessionKey());if(!e)return null;try{let t=JSON.parse(e);return this.isExpired(t)?(sessionStorage.removeItem(this.sessionKey()),null):t}catch{return null}}writeStoredToken(e){this.cachedToken=e,typeof sessionStorage<"u"&&sessionStorage.setItem(this.sessionKey(),JSON.stringify(e))}clearStoredToken(){this.cachedToken=null,typeof sessionStorage<"u"&&sessionStorage.removeItem(this.sessionKey())}sessionKey(){return`vizu:cloud-token:v2:${this.workspace}`}async resolveToken(){if(this.cachedToken&&!this.isExpired(this.cachedToken))return this.cachedToken;let e=this.readStoredToken();if(e)return this.cachedToken=e,this.fireAuthChanged(e),e;if(this.accessDenied)throw M("access_denied",`No access to workspace "${this.workspace}" (${this.accessDeniedReason??"no_access"}).`);if(!this.autoSignIn)throw M("auth_required","Sign-in required; autoSignIn is disabled.");return this.pendingAuth||(this.pendingAuth=this.openSignInPopup().then(t=>(this.writeStoredToken(t),this.fireAuthChanged(t),t)).finally(()=>{this.pendingAuth=null})),this.pendingAuth}fireAuthChanged(e){if(this.lastNotifiedTokenId!==e.token){this.lastNotifiedTokenId=e.token;try{this.onAuthChanged?.({userId:e.userId,user:e.user})}catch(t){typeof console<"u"&&console.warn("[vizu/cloud] onAuthChanged callback threw",t)}}}openSignInPopup(){return new Promise((e,t)=>{if(typeof window>"u")return t(M("auth_unavailable","Sign-in requires a browser environment."));let n=new URL(this.apiUrl).origin,i=window.location.origin,r=this.apiUrl+"/connect?workspace="+encodeURIComponent(this.workspace)+"&origin="+encodeURIComponent(i),s=Math.max(0,Math.round((window.screen.width-Q)/2)),a=Math.max(0,Math.round((window.screen.height-ee)/2)),c=window.open(r,"vizu-connect",`width=${Q},height=${ee},left=${s},top=${a},noopener=no,popup=yes`);if(!c)try{let b=window.location.href,f=this.apiUrl+"/connect?workspace="+encodeURIComponent(this.workspace)+"&origin="+encodeURIComponent(i)+"&return_to="+encodeURIComponent(b);window.location.assign(f);return}catch(b){return t(M("popup_blocked","Sign-in popup was blocked and redirect fallback failed: "+(b instanceof Error?b.message:String(b))))}let l=!1,p=b=>{if(b.origin!==n)return;let f=b.data;if(!f)return;if(f.type==="vizu:auth-denied"&&f.workspace===this.workspace){l=!0,g(),this.accessDenied=!0,this.accessDeniedReason=typeof f.reason=="string"?f.reason:"no_access",t(M("access_denied",`No access to workspace "${this.workspace}" (${this.accessDeniedReason}).`));return}if(f.type!=="vizu:auth"||f.workspace!==this.workspace||typeof f.token!="string"||typeof f.expiresAt!="string"||typeof f.userId!="string")return;l=!0,g();let z=f.user,q=z&&typeof z.name=="string"?{id:typeof z.id=="string"?z.id:f.userId,name:z.name,avatarUrl:typeof z.avatarUrl=="string"?z.avatarUrl:void 0}:null;e({token:f.token,expiresAt:f.expiresAt,userId:f.userId,user:q})},h=window.setInterval(()=>{c.closed&&(l||(g(),t(M("auth_canceled","Sign-in popup was closed before completing."))))},500);function g(){window.clearInterval(h),window.removeEventListener("message",p)}window.addEventListener("message",p)})}async fetchAuthed(e,t,n=!1){let i=await this.resolveToken(),r=new Headers(t.headers);r.set("authorization",`Bearer ${i.token}`);let s=await fetch(e,{...t,headers:r,credentials:"omit"});return s.status===401&&!n?(this.clearStoredToken(),this.fetchAuthed(e,t,!0)):s}async parseJson(e){try{let t=await e.text();return t?JSON.parse(t):null}catch{return null}}};function M(o,e){let t=new Error(e);return t.code=o,t}function k(o,e){let t=e?.error==="origin_not_allowed"?"origin_not_allowed":e?.error==="not_found"?"workspace_not_found":"http_error",n=new Error(e?.message??`HTTP ${o}`);return n.code=t,n.status=o,n.body=e,n}function te(o){let e=[],t=o,n=0;for(;t&&t!==document.documentElement&&n<8;){let i=t.tagName.toLowerCase();if(t.id){e.unshift("#"+CSS.escape(t.id));break}let r=Array.from(t.classList).filter(a=>!a.startsWith("vz-")&&!a.startsWith("vizu-")).slice(0,2);r.length&&(i+="."+r.map(a=>CSS.escape(a)).join("."));let s=t.parentElement;if(s){let a=Array.from(s.children).filter(c=>c.tagName===t.tagName);if(a.length>1){let c=a.indexOf(t);i+=":nth-of-type("+(c+1)+")"}}e.unshift(i),t=s,n++}return e.join(" > ")}function U(o){let e=o.innerText||o.textContent||"",t=o.parentElement,n=0;return t&&(n=Array.from(t.children).indexOf(o)),{selector:te(o),parentSelector:t?te(t):"",tagName:o.tagName,textSnippet:e.trim().slice(0,80),siblingIndex:n,attributes:{id:o.id||void 0,classList:Array.from(o.classList).filter(i=>!i.startsWith("vz-")&&!i.startsWith("vizu-")),role:o.getAttribute("role")||void 0,ariaLabel:o.getAttribute("aria-label")||void 0,dataKey:o.getAttribute("data-vizu-key")||void 0},ancestorChain:ke(o,4),algorithmVersion:2}}function ke(o,e){let t=[],n=o.parentElement;for(;n&&t.length<e;){let i=n.parentElement,r=1;i&&(r=Array.from(i.children).filter(a=>a.tagName===n.tagName).indexOf(n)+1),t.push({tag:n.tagName,nthOfType:r}),n=i}return t.length>0?{steps:t}:void 0}function D(o,e=document){if(o.attributes.dataKey){try{let s=e.querySelector(`[data-vizu-key="${CSS.escape(o.attributes.dataKey)}"]`);if(s)return{element:s,confidence:"exact"}}catch{}return{element:null,confidence:"orphaned"}}if(o.attributes.id)try{let s=e.querySelector("#"+CSS.escape(o.attributes.id));if(s)return{element:s,confidence:"exact"}}catch{}let t=[],n=o.attributes.classList;if(n&&n.length>0){let s=we(e,o.tagName,n);s?.element&&t.push({el:s.element,rung:"class"})}try{let s=e.querySelector(o.selector);s&&t.push({el:s,rung:"selector"})}catch{}if(o.ancestorChain&&o.ancestorChain.steps.length>0){let s=Ee(e,o.tagName,o.ancestorChain.steps);s?.element&&t.push({el:s.element,rung:"chain"})}else try{let s=o.parentSelector?e.querySelector(o.parentSelector):e;if(s){let a=s.children[o.siblingIndex];a&&a.tagName===o.tagName&&t.push({el:a,rung:"chain"})}}catch{}if(o.textSnippet){let s=Ae(e,o.tagName,o.textSnippet);s?.element&&t.push({el:s.element,rung:"text"})}if(t.length===0)return{element:null,confidence:"orphaned"};let i=new Map;for(let s of t){let a=i.get(s.el);a||(a=new Set,i.set(s.el,a)),a.add(s.rung)}let r=null;for(let[s,a]of i)(!r||a.size>r.rungs.size)&&(r={el:s,rungs:a});return r?r.rungs.size>=3?{element:r.el,confidence:"likely"}:r.rungs.size>=2?{element:r.el,confidence:"drifted"}:r.rungs.has("class")||r.rungs.has("chain")?{element:r.el,confidence:"drifted"}:{element:null,confidence:"orphaned"}:{element:null,confidence:"orphaned"}}function we(o,e,t){let n=new Set(t.filter(a=>!a.startsWith("vz-")&&!a.startsWith("vizu-")));if(n.size===0)return null;let i=o.querySelectorAll(e.toLowerCase()),r=null,s=0;for(let a of i){let c=new Set(Array.from(a.classList).filter(p=>!p.startsWith("vz-")&&!p.startsWith("vizu-")));if(c.size===0)continue;let l=Pe(n,c);l<.6||(!r||l>r.score?(s=r?.score??0,r={el:a,score:l}):l>s&&(s=l))}return!r||r.score-s<.1?null:{element:r.el,confidence:"likely"}}function Ee(o,e,t){let n=o.querySelectorAll(e.toLowerCase()),i=null;for(let r of n){let s=Ce(r,t.length+3),a=Te(s,t,3);a<2||(!i||a>i.score)&&(i={el:r,score:a})}return i?{element:i.el,confidence:i.score===t.length?"likely":"drifted"}:null}function Ce(o,e){let t=[],n=o.parentElement;for(;n&&t.length<e;){let i=n.parentElement,r=1;i&&(r=Array.from(i.children).filter(a=>a.tagName===n.tagName).indexOf(n)+1),t.push({tag:n.tagName,nthOfType:r}),n=i}return t}function Ae(o,e,t){let n=ne(t);if(!n)return null;let i=o.querySelectorAll(e.toLowerCase()),r=null;for(let s of i){let a=s.innerText||s.textContent||"",c=ne(a.slice(0,160));if(!c)continue;let l=Se(n,c);l<.75||(!r||l>r.score)&&(r={el:s,score:l})}return r?{element:r.el,confidence:"drifted"}:null}function ne(o){return o.toLowerCase().replace(/\s+/g," ").trim()}function Se(o,e){if(o===e)return 1;if(o.length===0||e.length===0)return o.length===e.length?1:0;let t=Math.max(o.length,e.length);return 1-Me(o,e)/t}function Me(o,e){if(o.length>e.length){let i=o;o=e,e=i}let t=new Array(o.length+1),n=new Array(o.length+1);for(let i=0;i<=o.length;i++)t[i]=i;for(let i=1;i<=e.length;i++){n[0]=i;for(let r=1;r<=o.length;r++){let s=o.charCodeAt(r-1)===e.charCodeAt(i-1)?0:1;n[r]=Math.min(n[r-1]+1,t[r]+1,t[r-1]+s)}for(let r=0;r<=o.length;r++)t[r]=n[r]}return t[o.length]}function Te(o,e,t){let n=0;for(let i=0;i<=t;i++){let r=0;for(let s=0;s<e.length;s++){let a=o[s+i];if(!a)break;a.tag===e[s].tag&&a.nthOfType===e[s].nthOfType&&r++}r>n&&(n=r)}return n}function Pe(o,e){if(o.size===0&&e.size===0)return 1;let t=0;for(let i of o)e.has(i)&&t++;let n=o.size+e.size-t;return n===0?0:t/n}function T(o,e){return D(o,e).element}function u(o){return o.selector+"|"+o.textSnippet}function P(o){let e=o.tagName.toLowerCase();if(o.textSnippet){let t=o.textSnippet.length>24?o.textSnippet.slice(0,24)+"\u2026":o.textSnippet;return e+": "+t}return o.attributes.id?e+"#"+o.attributes.id:o.attributes.classList&&o.attributes.classList.length?e+"."+o.attributes.classList[0]:e}var Le="[data-vizu-root], [data-vizu-ignore], script, style, link, meta, head",I=class{constructor(e,t,n){this.current=null;this.active=!1;this.paused=!1;this.handleMove=e=>{if(this.paused){this.setCurrent(null),this.overlay.style.display="none";return}let t=document.elementFromPoint(e.clientX,e.clientY);if(!(!t||t===this.current)){if(this.shouldIgnore(t)){this.setCurrent(null),this.overlay.style.display="none";return}this.setCurrent(t),this.updateOverlay()}};this.handleClick=e=>{let t=document.elementFromPoint(e.clientX,e.clientY);t&&(this.shouldIgnore(t)||(e.preventDefault(),e.stopPropagation(),this.onClick(t,e)))};this.handleScroll=()=>{this.current&&this.updateOverlay()};this.root=e,this.extraIgnore=t,this.onClick=n,this.overlay=document.createElement("div"),this.overlay.className="vz-highlight",this.overlay.style.display="none",this.root.appendChild(this.overlay)}setCurrent(e){this.current!==e&&(this.current=e)}start(){this.active||(this.active=!0,document.addEventListener("mousemove",this.handleMove,!0),document.addEventListener("click",this.handleClick,!0),document.addEventListener("scroll",this.handleScroll,!0),window.addEventListener("resize",this.handleScroll))}stop(){this.active&&(this.active=!1,document.removeEventListener("mousemove",this.handleMove,!0),document.removeEventListener("click",this.handleClick,!0),document.removeEventListener("scroll",this.handleScroll,!0),window.removeEventListener("resize",this.handleScroll),this.overlay.style.display="none",this.setCurrent(null))}shouldIgnore(e){if(e===document.documentElement||e===document.body||K(e,Le))return!0;for(let t of this.extraIgnore)try{if(K(e,t))return!0}catch{}return!1}setPaused(e){this.paused=e,e&&(this.setCurrent(null),this.overlay.style.display="none")}isPaused(){return this.paused}updateOverlay(){if(!this.current)return;let e=this.current.getBoundingClientRect();if(e.width===0||e.height===0){this.overlay.style.display="none";return}this.overlay.style.display="block",this.overlay.style.left=e.left+"px",this.overlay.style.top=e.top+"px",this.overlay.style.width=e.width+"px",this.overlay.style.height=e.height+"px"}destroy(){this.stop(),this.overlay.remove()}};function ie(o){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(o):o.replace(/["\\]/g,"\\$&")}function Re(o){let t=(o.replies??[]).map(n=>`
|
|
11
11
|
<div class="vz-reply" data-reply-id="${d(n.id)}">
|
|
12
12
|
${n.author?`<div class="vz-comment-author">${y(n.author)} <span class="vz-comment-author-name">${d(n.author.name||n.author.id||"User")}</span></div>`:""}
|
|
13
13
|
<div class="vz-reply-text">${d(n.text)}</div>
|
|
14
14
|
<div class="vz-comment-meta">
|
|
15
|
-
<span>${d(
|
|
15
|
+
<span>${d(S(typeof n.createdAt=="number"?n.createdAt:new Date(n.createdAt).getTime()))}</span>
|
|
16
16
|
<button class="vz-comment-delete" data-vz="delete-reply" data-comment-id="${d(o.id)}" data-reply-id="${d(n.id)}">delete</button>
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<button class="vz-btn vz-btn-primary vz-btn-reply" data-vz="send-reply" data-id="${d(o.id)}">Send</button>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
-
`}var
|
|
27
|
+
`}var O=class{constructor(e,t){this.currentUser=null;this.anchorTargets=[];this.anchorFingerprints=[];this.pendingRefs={};this.pendingAttachments=[];this.mentionPicker=null;this.mentionPickerDismiss=null;this.mentionPickerUsers=[];this.mentionPickerFiltered=[];this.mentionPickerSelected=0;this.mentionPickerMode="manual";this.mentionPickerEditorRange=null;this.handleEditorKey=e=>{let t=e.currentTarget;if(this.mentionPicker&&this.mentionPickerMode==="editor"){if(e.key==="ArrowDown"){e.preventDefault(),this.moveMentionPickerSelection(1);return}if(e.key==="ArrowUp"){e.preventDefault(),this.moveMentionPickerSelection(-1);return}if((e.key==="Enter"||e.key==="Tab")&&this.mentionPickerFiltered.length>0){e.preventDefault(),this.commitMentionPickerSelection();return}if(e.key==="Escape"){e.preventDefault(),this.closeMentionPicker();return}}if(e.key==="#"){e.preventDefault();let n=this.saveRange();this.callbacks.onStartReferencePick(i=>this.insertChip(i,n),()=>{t.focus(),this.restoreRange(n)});return}if(e.key==="Enter"&&(e.metaKey||e.ctrlKey)){e.preventDefault(),this.save();return}e.key==="Escape"&&(e.preventDefault(),this.callbacks.onClose())};this.handleEditorInput=()=>{let e=this.detectMentionTrigger();e?this.openOrUpdateEditorMentionPicker(e.range,e.query):this.mentionPicker&&this.mentionPickerMode==="editor"&&this.closeMentionPicker()};this.handleEditorPaste=e=>{if(this.callbacks.canUploadAttachments()){let i=e.clipboardData?.items??null;if(i){let r=[];for(let s=0;s<i.length;s++){let a=i[s];if(a.kind==="file"&&a.type.startsWith("image/")){let c=a.getAsFile();c&&r.push(c)}}if(r.length>0){e.preventDefault();for(let s of r)this.uploadAndAppend(s);return}}}e.preventDefault();let t=e.clipboardData?.getData("text/plain")??"",n=window.getSelection();if(n&&n.rangeCount>0){let i=n.getRangeAt(0);i.deleteContents(),i.insertNode(document.createTextNode(t)),i.collapse(!1),n.removeAllRanges(),n.addRange(i)}};this.handleDragOver=e=>{if(!e.dataTransfer||!Array.from(e.dataTransfer.types).includes("Files"))return;e.preventDefault(),this.el.querySelector('[data-vz="drop-hint"]')?.removeAttribute("hidden")};this.handleDragLeave=e=>{let t=e.currentTarget;if(e.relatedTarget instanceof Node&&t.contains(e.relatedTarget))return;this.el.querySelector('[data-vz="drop-hint"]')?.setAttribute("hidden","")};this.handleDrop=e=>{if(e.preventDefault(),this.el.querySelector('[data-vz="drop-hint"]')?.setAttribute("hidden",""),!e.dataTransfer)return;let n=Array.from(e.dataTransfer.files).filter(i=>i.type.startsWith("image/"));for(let i of n)this.uploadAndAppend(i)};this.handleFileChange=e=>{let t=e.currentTarget,n=t.files?Array.from(t.files):[];for(let i of n)this.uploadAndAppend(i);t.value=""};this.handleClick=e=>{let t=e.target,n=t.closest('[data-vz="remove-anchor"]');if(n){e.stopPropagation();let s=n.getAttribute("data-fp-key");s&&this.removeAnchor(s);return}let i=t.closest('[data-vz="ref"]');if(i){let s=i.getAttribute("data-comment-id"),a=i.getAttribute("data-ref-id");s&&a&&(e.stopPropagation(),this.callbacks.onJumpToReference(s,a));return}let r=t.getAttribute("data-vz");if(r==="close")this.callbacks.onClose();else if(r==="save")this.save();else if(r==="mention")e.preventDefault(),this.openMentionPicker(t);else if(r==="upload")e.preventDefault(),this.el.querySelector('[data-vz="file-input"]')?.click();else if(r==="remove-attachment"){e.preventDefault();let s=t.getAttribute("data-attachment-id");s&&(this.pendingAttachments=this.pendingAttachments.filter(a=>a.id!==s),this.renderAttachmentList())}else if(r==="delete"){let s=t.getAttribute("data-id");s&&this.callbacks.onDelete(s)}else if(r==="toggle-reply"){e.preventDefault();let s=t.getAttribute("data-id");if(!s)return;let a=this.el.querySelector(`.vz-replies[data-comment-id="${ie(s)}"]`);if(!a)return;a.hasAttribute("hidden")?(a.removeAttribute("hidden"),a.querySelector(".vz-reply-input")?.focus()):a.setAttribute("hidden","")}else if(r==="send-reply"){e.preventDefault();let s=t.getAttribute("data-id");if(!s)return;let a=this.el.querySelector(`.vz-reply-input[data-comment-id="${ie(s)}"]`),c=a?.value.trim()??"";if(!c)return;this.callbacks.onAddReply(s,c),a&&(a.value="")}else if(r==="delete-reply"){e.preventDefault();let s=t.getAttribute("data-comment-id"),a=t.getAttribute("data-reply-id");s&&a&&this.callbacks.onDeleteReply(s,a)}};this.root=e,this.callbacks=t,this.el=document.createElement("div"),this.el.className="vz-popover",this.el.style.display="none",this.el.addEventListener("click",this.handleClick),this.root.appendChild(this.el)}setUser(e){this.currentUser=e}open(e,t,n){this.anchorTargets=[...e],this.anchorFingerprints=[...t],this.pendingRefs={},this.render(n),this.position()}update(e){this.anchorTargets.length&&(this.render(e),this.position())}addAnchor(e,t){let n=u(e);return this.anchorFingerprints.some(i=>u(i)===n)?!1:(this.anchorFingerprints.push(e),t&&this.anchorTargets.push(t),this.callbacks.onAnchorsChanged([...this.anchorFingerprints]),this.renderAnchorList(),this.updateHeader(),!0)}removeAnchor(e){if(this.anchorFingerprints.length<=1)return!1;let t=this.anchorFingerprints.findIndex(n=>u(n)===e);return t<0?!1:(this.anchorFingerprints.splice(t,1),this.anchorTargets.splice(t,1),this.callbacks.onAnchorsChanged([...this.anchorFingerprints]),this.renderAnchorList(),this.updateHeader(),!0)}updateHeader(){let e=this.el.querySelector(".vz-popover-header span:first-child");e&&(e.textContent=this.anchorFingerprints.length>1?`Comment on ${this.anchorFingerprints.length} elements`:"Comment")}reposition(){this.anchorTargets.length&&this.position()}close(){this.closeMentionPicker(),this.el.style.display="none",this.anchorTargets=[],this.anchorFingerprints=[],this.pendingRefs={}}isOpen(){return this.anchorTargets.length>0}getAnchors(){return[...this.anchorFingerprints]}getAnchorTargets(){return[...this.anchorTargets]}render(e){if(!this.anchorTargets.length)return;let t=this.anchorTargets[0],n=Array.from(t.classList).filter(h=>!h.startsWith("vz-")&&!h.startsWith("vizu-")).slice(0,2),i=t.tagName.toLowerCase()+(t.id?"#"+t.id:"")+(n.length?"."+n.join("."):""),r=(t.innerText||"").trim(),s=r.slice(0,60),a=this.currentUser?`<div class="vz-comment-author">${y(this.currentUser)} <span class="vz-comment-author-name">${d(this.currentUser.name)}</span> <span>\xB7 you</span></div>`:"";this.el.innerHTML=`
|
|
28
28
|
<div class="vz-popover-header">
|
|
29
29
|
<span>${this.anchorFingerprints.length>1?`Comment on ${this.anchorFingerprints.length} elements`:"Comment"}</span>
|
|
30
30
|
<button class="vz-popover-close" data-vz="close" aria-label="Close">\xD7</button>
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
<div class="vz-target-label">${d(i)}${s?" \xB7 “"+d(s)+(r.length>60?"\u2026":"")+"”":""}</div>
|
|
33
33
|
<div class="vz-anchor-list-wrap"></div>
|
|
34
34
|
<div class="vz-comment-list">
|
|
35
|
-
${e.length===0?'<div class="vz-empty">No comments yet.</div>':e.map(
|
|
36
|
-
<div class="vz-comment" data-comment-id="${d(
|
|
37
|
-
${
|
|
38
|
-
<div>${
|
|
39
|
-
${
|
|
35
|
+
${e.length===0?'<div class="vz-empty">No comments yet.</div>':e.map(h=>`
|
|
36
|
+
<div class="vz-comment" data-comment-id="${d(h.id)}">
|
|
37
|
+
${h.author?`<div class="vz-comment-author">${y(h.author)} <span class="vz-comment-author-name">${d(h.author.name||h.author.id||"User")}</span></div>`:""}
|
|
38
|
+
<div>${R(h.text,h.id)}</div>
|
|
39
|
+
${$(h.attachments)}
|
|
40
40
|
<div class="vz-comment-meta">
|
|
41
|
-
<span>${d(
|
|
41
|
+
<span>${d(S(h.createdAt))}</span>
|
|
42
42
|
<span class="vz-comment-actions">
|
|
43
|
-
<button class="vz-comment-reply-toggle" data-vz="toggle-reply" data-id="${d(
|
|
44
|
-
<button class="vz-comment-delete" data-vz="delete" data-id="${d(
|
|
43
|
+
<button class="vz-comment-reply-toggle" data-vz="toggle-reply" data-id="${d(h.id)}">${(h.replies?.length??0)>0?`${h.replies.length} ${h.replies.length===1?"reply":"replies"}`:"reply"}</button>
|
|
44
|
+
<button class="vz-comment-delete" data-vz="delete" data-id="${d(h.id)}">delete</button>
|
|
45
45
|
</span>
|
|
46
46
|
</div>
|
|
47
|
-
${
|
|
47
|
+
${Re(h)}
|
|
48
48
|
</div>
|
|
49
49
|
`).join("")}
|
|
50
50
|
</div>
|
|
@@ -61,15 +61,15 @@
|
|
|
61
61
|
<button class="vz-btn vz-btn-primary" data-vz="save">Save</button>
|
|
62
62
|
</div>
|
|
63
63
|
<input type="file" class="vz-file-input" data-vz="file-input" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" hidden />
|
|
64
|
-
`,this.el.style.display="flex",this.renderAnchorList(),this.renderAttachmentList();let
|
|
64
|
+
`,this.el.style.display="flex",this.renderAnchorList(),this.renderAttachmentList();let c=this.el.querySelector(".vz-textarea");c.focus(),c.addEventListener("keydown",this.handleEditorKey),c.addEventListener("input",this.handleEditorInput),c.addEventListener("paste",this.handleEditorPaste);let l=this.el.querySelector(".vz-editor-wrap");l&&this.callbacks.canUploadAttachments()&&(l.addEventListener("dragover",this.handleDragOver),l.addEventListener("dragleave",this.handleDragLeave),l.addEventListener("drop",this.handleDrop));let p=this.el.querySelector('[data-vz="file-input"]');p&&p.addEventListener("change",this.handleFileChange)}renderAnchorList(){let e=this.el.querySelector(".vz-anchor-list-wrap");if(!e)return;if(this.anchorFingerprints.length<=1){e.innerHTML="";return}let t=this.anchorFingerprints.map(n=>{let i=u(n),r=P(n);return`<span class="vz-anchor-chip" data-fp-key="${d(i)}">${d(r)}<button class="vz-anchor-chip-remove" data-vz="remove-anchor" data-fp-key="${d(i)}" aria-label="Remove anchor">\xD7</button></span>`}).join("");e.innerHTML=`
|
|
65
65
|
<div class="vz-anchor-hint">Anchored to ${this.anchorFingerprints.length} elements:</div>
|
|
66
66
|
<div class="vz-anchor-list">${t}</div>
|
|
67
|
-
`}detectMentionTrigger(){let e=window.getSelection();if(!e||e.rangeCount===0||!e.isCollapsed)return null;let t=e.getRangeAt(0),n=t.startContainer;if(n.nodeType!==Node.TEXT_NODE)return null;let i=n.textContent??"",r=t.startOffset;for(let s=r-1;s>=0;s--){let a=i[s];if(a==="@"){let
|
|
67
|
+
`}detectMentionTrigger(){let e=window.getSelection();if(!e||e.rangeCount===0||!e.isCollapsed)return null;let t=e.getRangeAt(0),n=t.startContainer;if(n.nodeType!==Node.TEXT_NODE)return null;let i=n.textContent??"",r=t.startOffset;for(let s=r-1;s>=0;s--){let a=i[s];if(a==="@"){let c=s>0?i[s-1]:"";if(c!==""&&!/\s/.test(c))return null;let l=i.slice(s+1,r);if(/\s/.test(l))return null;let p=document.createRange();return p.setStart(n,s),p.setEnd(n,r),{range:p,query:l}}if(/\s/.test(a))return null}return null}async uploadAndAppend(e){if(!this.callbacks.canUploadAttachments())return;let t="upload-"+Math.random().toString(36).slice(2,9),n={id:t,url:"",mimeType:e.type||"application/octet-stream",sizeBytes:e.size,uploadedAt:Date.now(),filename:e.name};this.pendingAttachments.push(n),this.renderAttachmentList(t);try{let i=await this.callbacks.onUploadAttachment(e),r=this.pendingAttachments.findIndex(s=>s.id===t);r>=0?this.pendingAttachments[r]=i:this.pendingAttachments.push(i),this.renderAttachmentList()}catch(i){this.pendingAttachments=this.pendingAttachments.filter(r=>r.id!==t),this.renderAttachmentList(),typeof console<"u"&&console.warn("[vizu] attachment upload failed",i)}}renderAttachmentList(e){let t=this.el.querySelector('[data-vz="attachment-list"]');if(t){if(this.pendingAttachments.length===0){t.innerHTML="";return}t.innerHTML=this.pendingAttachments.map(n=>{let r=n.id===e||n.url===""?'<div class="vz-attachment-thumb vz-attachment-thumb-loading">\u2026</div>':n.mimeType.startsWith("image/")?`<img class="vz-attachment-thumb" src="${d(n.url)}" alt="${d(n.filename??"")}" />`:'<div class="vz-attachment-thumb">\u{1F4C4}</div>';return`
|
|
68
68
|
<div class="vz-attachment-item" data-attachment-id="${d(n.id)}">
|
|
69
69
|
${r}
|
|
70
70
|
<button class="vz-attachment-remove" data-vz="remove-attachment" data-attachment-id="${d(n.id)}" aria-label="Remove attachment">\xD7</button>
|
|
71
71
|
</div>
|
|
72
|
-
`}).join("")}}saveRange(){let e=window.getSelection();return!e||e.rangeCount===0?null:e.getRangeAt(0).cloneRange()}restoreRange(e){if(!e)return;let t=window.getSelection();t?.removeAllRanges(),t?.addRange(e)}insertChip(e,t){let n=
|
|
72
|
+
`}).join("")}}saveRange(){let e=window.getSelection();return!e||e.rangeCount===0?null:e.getRangeAt(0).cloneRange()}restoreRange(e){if(!e)return;let t=window.getSelection();t?.removeAllRanges(),t?.addRange(e)}insertChip(e,t){let n=G();this.pendingRefs[n]=e;let i=P(e),r=document.createElement("span");r.className="vz-chip",r.contentEditable="false",r.setAttribute("data-ref-id",n),r.textContent=i;let s=this.el.querySelector(".vz-textarea");s.focus(),this.restoreRange(t);let a=window.getSelection();if(!a||a.rangeCount===0){s.appendChild(r),s.appendChild(document.createTextNode(" ")),this.moveCursorToEnd(s);return}let c=a.getRangeAt(0);c.deleteContents(),c.insertNode(r);let l=document.createTextNode(" ");r.after(l);let p=document.createRange();p.setStartAfter(l),p.collapse(!0),a.removeAllRanges(),a.addRange(p)}moveCursorToEnd(e){let t=document.createRange();t.selectNodeContents(e),t.collapse(!1);let n=window.getSelection();n?.removeAllRanges(),n?.addRange(t)}serializeEditor(e){let t=[],n=i=>{if(i.nodeType===Node.TEXT_NODE){t.push(i.textContent||"");return}if(i.nodeType!==Node.ELEMENT_NODE)return;let r=i;if(r.classList.contains("vz-chip")){let s=r.getAttribute("data-mention-id");if(s){let l=r.textContent||"",p=l.startsWith("@")?l.slice(1):l;t.push(`[@${p}](vizu-mention:${s})`);return}let a=r.getAttribute("data-ref-id"),c=r.textContent||"";a&&t.push(`[#${c}](vizu-ref:${a})`);return}if(r.tagName==="BR"){t.push(`
|
|
73
73
|
`);return}r.tagName==="DIV"&&t.length&&!t[t.length-1].endsWith(`
|
|
74
74
|
`)&&t.push(`
|
|
75
75
|
`);for(let s of r.childNodes)n(s)};for(let i of e.childNodes)n(i);return t.join("").trim()}async openMentionPicker(e){if(this.closeMentionPicker(),!this.callbacks.onMentionSearch)return;this.mentionPickerMode="manual",this.mentionPickerEditorRange=null;let t=this.createMentionPickerShell();this.positionMentionPickerByElement(t,e);let n=await this.fetchMentionables();if(this.mentionPicker!==t)return;this.mentionPickerUsers=n,this.renderMentionPickerBody(""),this.positionMentionPickerByElement(t,e),t.querySelector(".vz-mention-search")?.focus(),this.wireMentionPickerDismiss()}async openOrUpdateEditorMentionPicker(e,t){if(this.mentionPickerEditorRange=e,!this.mentionPicker||this.mentionPickerMode!=="editor"){this.closeMentionPicker(),this.mentionPickerMode="editor";let n=this.createMentionPickerShell({showSearch:!1});this.positionMentionPickerByRange(n,e);let i=await this.fetchMentionables();if(this.mentionPicker!==n)return;this.mentionPickerUsers=i,this.renderMentionPickerBody(t),this.positionMentionPickerByRange(n,e),this.wireMentionPickerDismiss();return}this.renderMentionPickerBody(t),this.positionMentionPickerByRange(this.mentionPicker,e)}createMentionPickerShell({showSearch:e=!0}={}){let t=document.createElement("div");if(t.className="vz-mention-picker",t.setAttribute("data-vz-ignore",""),t.innerHTML=`
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
<div class="vz-mention-list" data-vz="mention-list">
|
|
78
78
|
<div class="vz-mention-loading">Loading\u2026</div>
|
|
79
79
|
</div>
|
|
80
|
-
`,this.el.appendChild(t),this.mentionPicker=t,this.mentionPickerFiltered=[],this.mentionPickerSelected=0,t.addEventListener("mousemove",n=>{let i=n.target?.closest(".vz-mention-item");if(!i)return;let r=Number(i.getAttribute("data-mention-index")??-1);r>=0&&r!==this.mentionPickerSelected&&(this.mentionPickerSelected=r,this.refreshMentionPickerHighlight())}),t.addEventListener("click",n=>{let i=n.target?.closest(".vz-mention-item");if(!i)return;n.preventDefault(),n.stopPropagation();let r=Number(i.getAttribute("data-mention-index")??-1);r>=0&&(this.mentionPickerSelected=r,this.commitMentionPickerSelection())}),e){let n=t.querySelector(".vz-mention-search");n?.addEventListener("input",()=>this.renderMentionPickerBody(n.value)),n?.addEventListener("keydown",i=>{i.key==="ArrowDown"?(i.preventDefault(),this.moveMentionPickerSelection(1)):i.key==="ArrowUp"?(i.preventDefault(),this.moveMentionPickerSelection(-1)):i.key==="Enter"||i.key==="Tab"?this.mentionPickerFiltered.length>0&&(i.preventDefault(),this.commitMentionPickerSelection()):i.key==="Escape"&&(i.preventDefault(),this.closeMentionPicker())})}return t}async fetchMentionables(){if(!this.callbacks.onMentionSearch)return[];try{return await this.callbacks.onMentionSearch()}catch{return[]}}renderMentionPickerBody(e){let t=this.mentionPicker;if(!t)return;let n=t.querySelector('[data-vz="mention-list"]');if(!n)return;let i=e.toLowerCase().trim();if(this.mentionPickerFiltered=i?this.mentionPickerUsers.filter(r=>r.name.toLowerCase().includes(i)):this.mentionPickerUsers.slice(),this.mentionPickerSelected=0,this.mentionPickerFiltered.length===0){n.innerHTML=`<div class="vz-mention-empty">${this.mentionPickerUsers.length===0?"No teammates to mention yet.":"No teammates match."}</div>`;return}n.innerHTML=this.mentionPickerFiltered.map((r,s)=>{let a=r.avatarUrl?`<img class="vz-mention-avatar" src="${d(r.avatarUrl)}" alt="" />`:`<span class="vz-mention-avatar vz-mention-avatar-initials">${d(
|
|
80
|
+
`,this.el.appendChild(t),this.mentionPicker=t,this.mentionPickerFiltered=[],this.mentionPickerSelected=0,t.addEventListener("mousemove",n=>{let i=n.target?.closest(".vz-mention-item");if(!i)return;let r=Number(i.getAttribute("data-mention-index")??-1);r>=0&&r!==this.mentionPickerSelected&&(this.mentionPickerSelected=r,this.refreshMentionPickerHighlight())}),t.addEventListener("click",n=>{let i=n.target?.closest(".vz-mention-item");if(!i)return;n.preventDefault(),n.stopPropagation();let r=Number(i.getAttribute("data-mention-index")??-1);r>=0&&(this.mentionPickerSelected=r,this.commitMentionPickerSelection())}),e){let n=t.querySelector(".vz-mention-search");n?.addEventListener("input",()=>this.renderMentionPickerBody(n.value)),n?.addEventListener("keydown",i=>{i.key==="ArrowDown"?(i.preventDefault(),this.moveMentionPickerSelection(1)):i.key==="ArrowUp"?(i.preventDefault(),this.moveMentionPickerSelection(-1)):i.key==="Enter"||i.key==="Tab"?this.mentionPickerFiltered.length>0&&(i.preventDefault(),this.commitMentionPickerSelection()):i.key==="Escape"&&(i.preventDefault(),this.closeMentionPicker())})}return t}async fetchMentionables(){if(!this.callbacks.onMentionSearch)return[];try{return await this.callbacks.onMentionSearch()}catch{return[]}}renderMentionPickerBody(e){let t=this.mentionPicker;if(!t)return;let n=t.querySelector('[data-vz="mention-list"]');if(!n)return;let i=e.toLowerCase().trim();if(this.mentionPickerFiltered=i?this.mentionPickerUsers.filter(r=>r.name.toLowerCase().includes(i)):this.mentionPickerUsers.slice(),this.mentionPickerSelected=0,this.mentionPickerFiltered.length===0){n.innerHTML=`<div class="vz-mention-empty">${this.mentionPickerUsers.length===0?"No teammates to mention yet.":"No teammates match."}</div>`;return}n.innerHTML=this.mentionPickerFiltered.map((r,s)=>{let a=r.avatarUrl?`<img class="vz-mention-avatar" src="${d(r.avatarUrl)}" alt="" />`:`<span class="vz-mention-avatar vz-mention-avatar-initials">${d(J(r.name)||"?")}</span>`;return`<button class="vz-mention-item${s===0?" is-selected":""}" type="button" data-mention-index="${s}">${a}<span class="vz-mention-item-name">${d(r.name)}</span></button>`}).join("")}moveMentionPickerSelection(e){if(this.mentionPickerFiltered.length===0)return;let t=this.mentionPickerFiltered.length;this.mentionPickerSelected=(this.mentionPickerSelected+e+t)%t,this.refreshMentionPickerHighlight()}refreshMentionPickerHighlight(){let e=this.mentionPicker;if(!e)return;let t=e.querySelectorAll(".vz-mention-item");t.forEach((n,i)=>n.classList.toggle("is-selected",i===this.mentionPickerSelected)),t[this.mentionPickerSelected]?.scrollIntoView({block:"nearest"})}commitMentionPickerSelection(){let e=this.mentionPickerFiltered[this.mentionPickerSelected];e&&(this.mentionPickerMode==="editor"&&this.mentionPickerEditorRange?this.replaceRangeWithMentionChip(this.mentionPickerEditorRange,e):this.insertMentionChip(e),this.closeMentionPicker())}replaceRangeWithMentionChip(e,t){let n=this.el.querySelector(".vz-textarea");if(!n)return;let i=document.createElement("span");i.className="vz-chip vz-chip-mention",i.contentEditable="false",i.setAttribute("data-mention-id",t.id),i.textContent="@"+t.name,e.deleteContents(),e.insertNode(i);let r=document.createTextNode(" ");i.after(r);let s=window.getSelection();if(s){let a=document.createRange();a.setStartAfter(r),a.collapse(!0),s.removeAllRanges(),s.addRange(a)}n.focus()}wireMentionPickerDismiss(){if(this.mentionPickerDismiss)return;let e=n=>{n.key==="Escape"&&(n.preventDefault(),this.closeMentionPicker())},t=n=>{if(this.mentionPicker&&!this.mentionPicker.contains(n.target)){if(this.mentionPickerMode==="editor"){let i=this.el.querySelector(".vz-textarea");if(i&&i.contains(n.target))return}this.closeMentionPicker()}};document.addEventListener("keydown",e,!0),document.addEventListener("mousedown",t,!0),this.mentionPickerDismiss=()=>{document.removeEventListener("keydown",e,!0),document.removeEventListener("mousedown",t,!0)}}closeMentionPicker(){this.mentionPickerDismiss&&(this.mentionPickerDismiss(),this.mentionPickerDismiss=null),this.mentionPicker&&(this.mentionPicker.remove(),this.mentionPicker=null),this.mentionPickerFiltered=[],this.mentionPickerSelected=0,this.mentionPickerEditorRange=null}positionMentionPickerByElement(e,t){let n=this.el.getBoundingClientRect(),i=t.getBoundingClientRect();e.style.left=`${i.left-n.left}px`,e.style.top=`${i.bottom-n.top+4}px`}positionMentionPickerByRange(e,t){let n=this.el.getBoundingClientRect(),i=t.getBoundingClientRect();e.style.left=`${i.left-n.left}px`,e.style.top=`${i.bottom-n.top+4}px`}insertMentionChip(e){if(!e||!e.id)return;let t=this.el.querySelector(".vz-textarea");if(!t)return;let n=document.createElement("span");n.className="vz-chip vz-chip-mention",n.contentEditable="false",n.setAttribute("data-mention-id",e.id),n.textContent="@"+e.name,t.focus();let i=window.getSelection();if(!i||i.rangeCount===0){t.appendChild(n),t.appendChild(document.createTextNode(" ")),this.moveCursorToEnd(t);return}let r=i.getRangeAt(0);r.deleteContents(),r.insertNode(n);let s=document.createTextNode(" ");n.after(s);let a=document.createRange();a.setStartAfter(s),a.collapse(!0),i.removeAllRanges(),i.addRange(a)}save(){let e=this.el.querySelector(".vz-textarea");if(!e)return;let t=this.serializeEditor(e),n=this.pendingAttachments.filter(l=>l.url).length;if(!t&&n===0)return;let i={},r=/\[#[^\]]+\]\(vizu-ref:([a-zA-Z0-9_-]+)\)/g,s;for(;(s=r.exec(t))!==null;){let l=s[1];this.pendingRefs[l]&&(i[l]=this.pendingRefs[l])}let a=Y(t),c=this.pendingAttachments.filter(l=>l.url);this.callbacks.onAdd(t,[...this.anchorFingerprints],Object.keys(i).length?i:void 0,a.length?a:void 0,c.length?c:void 0),e.innerHTML="",this.pendingRefs={},this.pendingAttachments=[],this.renderAttachmentList()}position(){let e=this.anchorTargets[0];if(!e)return;let t=e.getBoundingClientRect(),n=this.el.getBoundingClientRect(),i=8,r=t.bottom+i,s=t.left;r+n.height>window.innerHeight-i&&(r=t.top-n.height-i,r<i&&(r=i)),s+n.width>window.innerWidth-i&&(s=window.innerWidth-n.width-i),s<i&&(s=i),this.el.style.top=r+"px",this.el.style.left=s+"px"}destroy(){this.closeMentionPicker(),this.el.removeEventListener("click",this.handleClick),this.el.remove()}};var _=class{constructor(e,t){this.sidebarOpen=!1;this.actions=[];this.commentsCount=0;this.user=null;this.multiSelectMode=!1;this.selectionCount=0;this.handleClick=e=>{let t=e.target.closest("[data-vz]");if(!t)return;let n=t.getAttribute("data-vz");if(n==="sidebar")this.callbacks.onToggleSidebar();else if(n==="disable")this.callbacks.onDisable();else if(n==="multi")this.callbacks.onToggleMultiSelect();else if(n==="commit-selection")this.callbacks.onCommitSelection();else if(n==="clear-selection")this.callbacks.onClearSelection();else if(n==="action"){let i=t.getAttribute("data-id");i&&this.callbacks.onAction(i)}};this.root=e,this.callbacks=t,this.el=document.createElement("div"),this.el.className="vz-pill",this.el.style.display="none",this.el.addEventListener("click",this.handleClick),this.root.appendChild(this.el)}setActions(e){this.actions=e,this.render()}setComments(e){this.commentsCount=e.length,this.render()}setUser(e){this.user=e,this.render()}setSidebarOpen(e){this.sidebarOpen=e;let t=this.el.querySelector('[data-vz="sidebar"]');t&&t.classList.toggle("is-active",e)}setMultiSelectState(e,t){this.multiSelectMode=e,this.selectionCount=t,this.el.classList.toggle("is-selecting",e&&t>0),this.render()}show(){this.el.style.display="flex",this.render()}hide(){this.el.style.display="none"}render(){if(this.el.style.display==="none")return;let e=this.actions.filter(s=>s.visibleWhen?s.visibleWhen({commentsCount:this.commentsCount}):!0),t=this.user?`<div class="vz-pill-user" title="${d(this.user.name)}">${y(this.user,"vz-pill-user-avatar")} ${d(this.user.name.split(" ")[0])}</div><span class="vz-pill-divider"></span>`:"",n=e.length?e.map(s=>`<button class="vz-pill-btn${s.variant==="primary"?" primary":""}" data-vz="action" data-id="${d(s.id)}"${s.title?` title="${d(s.title)}"`:""}>${d(s.label)}</button>`).join("")+'<span class="vz-pill-divider"></span>':"",i="";if(this.multiSelectMode&&this.selectionCount>0)i=`
|
|
81
81
|
<span class="vz-pill-count-btn is-active" title="Selected ${this.selectionCount} element${this.selectionCount===1?"":"s"}">${this.selectionCount} selected</span>
|
|
82
82
|
<button class="vz-pill-btn primary" data-vz="commit-selection">Comment on ${this.selectionCount} \u2192</button>
|
|
83
83
|
<button class="vz-pill-btn" data-vz="clear-selection">Cancel</button>
|
|
@@ -95,44 +95,44 @@
|
|
|
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
|
|
99
|
-
${this.headerHtml(e.length,
|
|
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 N=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 l=n.getAttribute("data-comment-id"),p=n.getAttribute("data-ref-id");l&&p&&this.callbacks.onJumpToReference(l,p);return}let i=t.closest('[data-vz="filter"]');if(i){e.stopPropagation();let l=i.getAttribute("data-filter");l&&l!==this.filter&&(this.filter=l,this.render(this.lastComments),this.callbacks.onFilterChange?.(l));return}let r=t.closest('[data-vz="status"]');if(r){e.stopPropagation();let l=r.getAttribute("data-id"),p=r.getAttribute("data-status");l&&p&&this.callbacks.onUpdateStatus(l,p);return}let s=t.closest('[data-vz="delete"]');if(s){e.stopPropagation();let l=s.getAttribute("data-id");l&&this.callbacks.onDelete(l);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 c=t.closest('[data-vz="jump"]');c&&c.__group&&this.callbacks.onJumpTo(c.__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(m=>n.get(m.id)==="orphaned"),r=e.filter(m=>n.get(m.id)!=="orphaned"),s=m=>m.status??"open",a=this.filter==="all"?r:r.filter(m=>s(m)===this.filter),c=this.filter==="all"?i:[],l=$e(e);if(a.length===0&&c.length===0){let m;if(e.length===0)m="<strong>No comments yet</strong>Click any element on the page to leave one.";else if(this.filter==="open"){let x=l.resolved+l.wontfix;m=`<strong>Nothing open.</strong>${x} closed comment${x===1?"":"s"} hidden. Switch to <em>All</em> to see them.`}else this.filter==="resolved"?m="<strong>Nothing resolved.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":this.filter==="wontfix"?m="<strong>Nothing marked won't fix.</strong>Switch to <em>Open</em> or <em>All</em> to see more.":m="<strong>No comments yet</strong>Click any element on the page to leave one.";this.el.innerHTML=`
|
|
99
|
+
${this.headerHtml(e.length,l)}
|
|
100
100
|
<div class="vz-sidebar-body">
|
|
101
|
-
<div class="vz-sidebar-empty">${
|
|
101
|
+
<div class="vz-sidebar-empty">${m}</div>
|
|
102
102
|
</div>
|
|
103
|
-
`;return}let p=new Map;for(let
|
|
104
|
-
<div class="vz-sidebar-group" data-group="${
|
|
103
|
+
`;return}let p=new Map;for(let m of a){let w=(m.fingerprints||[])[0];if(!w)continue;let v=u(w),E=p.get(v);E?E.comments.push(m):p.set(v,{fp:w,comments:[m]})}let h=[],g=0;for(let m of p.values()){let x=m.fp.tagName.toLowerCase()+(m.fp.attributes.id?"#"+m.fp.attributes.id:""),w=m.fp.textSnippet.slice(0,60);h.push(`
|
|
104
|
+
<div class="vz-sidebar-group" data-group="${g}">
|
|
105
105
|
<div class="vz-sidebar-group-head">
|
|
106
106
|
${d(x)}
|
|
107
|
-
${w?`<div class="vz-sidebar-group-text">“${d(w)}${
|
|
107
|
+
${w?`<div class="vz-sidebar-group-text">“${d(w)}${m.fp.textSnippet.length>60?"\u2026":""}”</div>`:""}
|
|
108
108
|
</div>
|
|
109
|
-
${
|
|
110
|
-
<div class="vz-sidebar-item ${
|
|
109
|
+
${m.comments.map(v=>{let E=v.status??"open",W=(v.fingerprints||[]).slice(1),ce=W.length?`<div class="vz-anchor-list" style="margin-top:6px">${W.map((X,pe)=>`<span class="vz-anchor-chip" data-vz="jump-fp" data-fp-key="${d(u(X))}" title="Jump to this element">+${pe+2}/${(v.fingerprints||[]).length} ${d(P(X))}</span>`).join("")}</div>`:"",de=n.get(v.id)==="drifted"?'<span class="vz-status-pill vz-status-drifted" title="Anchored via a fallback match \u2014 the original element may have moved or changed.">drifted</span>':"";return`
|
|
110
|
+
<div class="vz-sidebar-item ${E!=="open"?"vz-sidebar-item-dim":""}" data-comment-id="${d(v.id)}" data-vz="jump" data-group="${g}" role="button" tabindex="0">
|
|
111
111
|
<div class="vz-sidebar-item-row">
|
|
112
|
-
${
|
|
113
|
-
${
|
|
114
|
-
${
|
|
112
|
+
${v.author?`<div class="vz-sidebar-item-author">${y(v.author)} <span class="vz-sidebar-item-author-name">${d(v.author.name)}</span></div>`:""}
|
|
113
|
+
${de}
|
|
114
|
+
${E!=="open"?`<span class="vz-status-pill vz-status-${E}">${E}</span>`:""}
|
|
115
115
|
</div>
|
|
116
|
-
<span class="vz-sidebar-item-text">${
|
|
117
|
-
${
|
|
118
|
-
${
|
|
116
|
+
<span class="vz-sidebar-item-text">${R(v.text,v.id)}</span>
|
|
117
|
+
${$(v.attachments)}
|
|
118
|
+
${ce}
|
|
119
119
|
<div class="vz-sidebar-item-meta">
|
|
120
|
-
<span>${d(
|
|
120
|
+
<span>${d(S(v.createdAt))}${(v.fingerprints||[]).length>1?` \xB7 grouped (${(v.fingerprints||[]).length})`:""}${(v.replies?.length??0)>0?` \xB7 ${v.replies.length} ${v.replies.length===1?"reply":"replies"}`:""}</span>
|
|
121
121
|
<span class="vz-sidebar-item-actions">
|
|
122
|
-
${
|
|
123
|
-
<button class="vz-sidebar-item-delete" data-vz="delete" data-id="${d(
|
|
122
|
+
${Fe(v.id,E)}
|
|
123
|
+
<button class="vz-sidebar-item-delete" data-vz="delete" data-id="${d(v.id)}" title="Delete comment">delete</button>
|
|
124
124
|
</span>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
127
|
`}).join("")}
|
|
128
128
|
</div>
|
|
129
|
-
`),
|
|
130
|
-
${this.headerHtml(e.length,
|
|
129
|
+
`),g++}let b=c.length>0?Ue(c):"";this.el.innerHTML=`
|
|
130
|
+
${this.headerHtml(e.length,l)}
|
|
131
131
|
<div class="vz-sidebar-body">
|
|
132
|
-
${
|
|
132
|
+
${h.join("")}
|
|
133
133
|
${b}
|
|
134
134
|
</div>
|
|
135
|
-
`;let
|
|
135
|
+
`;let f=this.el.querySelectorAll('[data-vz="jump"]'),z=[...p.values()];for(let m of f){let x=Number(m.dataset.group);m.__group=z[x]}let q=this.el.querySelectorAll('[data-vz="jump-fp"]');for(let m of q){let x=m.getAttribute("data-fp-key"),w=He(a,x||"");m.__fp=w}}headerHtml(e,t){return`
|
|
136
136
|
<div class="vz-sidebar-header">
|
|
137
137
|
<div class="vz-sidebar-title">
|
|
138
138
|
Comments
|
|
@@ -154,10 +154,10 @@
|
|
|
154
154
|
All <span class="vz-sidebar-filter-count">${e}</span>
|
|
155
155
|
</button>
|
|
156
156
|
</div>
|
|
157
|
-
`}destroy(){this.el.removeEventListener("click",this.handleClick),this.el.remove()}};function
|
|
157
|
+
`}destroy(){this.el.removeEventListener("click",this.handleClick),this.el.remove()}};function $e(o){let e={open:0,resolved:0,wontfix:0};for(let t of o){let n=t.status??"open";e[n]=(e[n]??0)+1}return e}function Fe(o,e){let t=d(o);return e==="open"?`
|
|
158
158
|
<button class="vz-sidebar-item-status" data-vz="status" data-id="${t}" data-status="resolved" title="Mark resolved">resolve</button>
|
|
159
159
|
<button class="vz-sidebar-item-status" data-vz="status" data-id="${t}" data-status="wontfix" title="Won't fix">won't fix</button>
|
|
160
|
-
`:`<button class="vz-sidebar-item-status" data-vz="status" data-id="${t}" data-status="open" title="Reopen">reopen</button>`}function
|
|
160
|
+
`:`<button class="vz-sidebar-item-status" data-vz="status" data-id="${t}" data-status="open" title="Reopen">reopen</button>`}function Ue(o){let e=o.map(t=>{let n=t.status??"open",i=(t.fingerprints||[])[0],r=i?i.tagName.toLowerCase():"comment",s=i?.textSnippet?.slice(0,60)??"";return`
|
|
161
161
|
<div class="vz-sidebar-item vz-sidebar-item-orphan" data-comment-id="${d(t.id)}">
|
|
162
162
|
<div class="vz-sidebar-item-row">
|
|
163
163
|
${t.author?`<div class="vz-sidebar-item-author">${y(t.author)} <span class="vz-sidebar-item-author-name">${d(t.author.name)}</span></div>`:""}
|
|
@@ -166,10 +166,10 @@
|
|
|
166
166
|
<div class="vz-sidebar-orphan-anchor">
|
|
167
167
|
was on <code>${d(r)}</code>${s?` “${d(s)}${(i?.textSnippet?.length??0)>60?"\u2026":""}”`:""}
|
|
168
168
|
</div>
|
|
169
|
-
<span class="vz-sidebar-item-text">${
|
|
170
|
-
${
|
|
169
|
+
<span class="vz-sidebar-item-text">${R(t.text,t.id)}</span>
|
|
170
|
+
${$(t.attachments)}
|
|
171
171
|
<div class="vz-sidebar-item-meta">
|
|
172
|
-
<span>${d(
|
|
172
|
+
<span>${d(S(t.createdAt))}${n!=="open"?` \xB7 ${n}`:""}</span>
|
|
173
173
|
<span class="vz-sidebar-item-actions">
|
|
174
174
|
<button class="vz-sidebar-item-delete" data-vz="delete" data-id="${d(t.id)}" title="Delete comment">delete</button>
|
|
175
175
|
</span>
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
</div>
|
|
184
184
|
${e}
|
|
185
185
|
</div>
|
|
186
|
-
`}function
|
|
186
|
+
`}function He(o,e){for(let t of o)for(let n of t.fingerprints||[])if(u(n)===e)return n;return null}var Ve=`
|
|
187
187
|
[data-vizu-root] {
|
|
188
188
|
position: fixed; top: 0; left: 0; width: 0; height: 0;
|
|
189
189
|
z-index: 2147483647;
|
|
@@ -1199,12 +1199,12 @@
|
|
|
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="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.requestAuth()}};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}requestAuth(){this.storage instanceof E&&(this.user||this.storage.isAccessDenied()||this.storage.preflightAuth().catch(e=>{e?.code==="auth_canceled"||e?.code==="popup_blocked"||typeof console<"u"&&console.warn("[vizu] preflight auth failed:",e)}))}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 re(){if(typeof document>"u"||document.getElementById("vizu-styles"))return;let o=document.createElement("style");o.id="vizu-styles",o.textContent=Ve,(document.head||document.documentElement).appendChild(o)}function se(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 oe(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 j=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 ae={shortcut:"mod+shift+e",accent:"#FF6647"},A=class{constructor(e,t){this.cloud=null;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 j;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(oe(e,this.parsedShortcut)){e.preventDefault();let t=this.enabled;this.toggle(),!t&&this.enabled&&this.requestAuth()}};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=T(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()}))};if(!t&&!e?.workspace)throw new Error("[vizu] `workspace` is required \u2014 create one at https://vizu.unhingged.com and pass new Vizu({ workspace: 'your-slug' }).");this.opts={...e},this.opts.shortcut=e.shortcut??ae.shortcut,this.opts.accent=e.accent??ae.accent,this.ns=e.workspace??"default",this.parsedShortcut=se(this.opts.shortcut),this.user=e.user??null,t?(this.storage=t,this.cloud=null):(this.cloud=new V({workspace:e.workspace,apiUrl:e.apiUrl,autoSignIn:e.autoSignIn,onAuthChanged:n=>{n.user&&this.setUser(n.user),this.hasLoadedComments&&this.loadComments()}}),this.storage=this.cloud),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.cloud?this.cloud.searchMentionable():[]}shouldMount(){return this.cloud?this.user!==null:!0}requireUserOrAuth(){return!this.cloud||this.user?!0:(this.cloud.isAccessDenied()||this.cloud.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}requestAuth(){this.cloud&&(this.user||this.cloud.isAccessDenied()||this.cloud.preflightAuth().catch(e=>{e?.code==="auth_canceled"||e?.code==="popup_blocked"||e?.code==="auth_required"||typeof console<"u"&&console.warn("[vizu] preflight auth failed:",e)}))}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=F(e),t?.persist!==!1&&await this.storage.setAll(this.ns,this.comments),this.bus.emit("comments:set",{comments:this.getComments()}),this.refreshUi()}async updateComment(e,t){let n=this.comments.findIndex(l=>l.id===e);if(n===-1)return null;let{id:i,schemaVersion:r,...s}=t,a={...this.comments[n],...s};this.comments[n]=a;let c=await this.storage.updateComment(this.ns,e,s);return this.bus.emit("comment:updated",{comment:c??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 c=t.get(u(a));if(c?.target){let l=U(c.target);n.push(l),!i&&u(l)!==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.ns,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");return this.storage.uploadAttachment(this.ns,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:L(),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.ns,e,r):await this.storage.updateComment(this.ns,e,{replies:s.replies})}catch(a){let c=this.comments[i];this.comments[i]={...c,replies:(c.replies??[]).filter(g=>g.id!==r.id)};let l=a?.code??"",p=a?.status,h=p===403||l==="forbidden"?"Reply failed \u2014 you don't have permission.":p===401||l==="auth_required"?"Reply failed \u2014 please sign in again.":"Reply failed. Please try again.";if(this.pill?.toast(h),this.refreshUi(),this.popover?.isOpen()){let g=this.popover.getAnchors();g[0]&&this.popover.update(this.commentsForElement(g[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.ns,e,t):await this.storage.updateComment(this.ns,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.ns),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)??T(t[0]))&&this.openPopoverFor(e.filter(Boolean),t)}getSelection(){return[...this.pendingFingerprints]}async loadComments(){let e=await this.storage.load(this.ns);this.comments=F(e),Z(e)&&this.storage.setAll(this.ns,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.ns,e=>{if(e.type==="added"){let t=C(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=C(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||(re(),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 O(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 N(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 _(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 I(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=U(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=U(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:L(),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.ns,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.ns,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 c=this.popover.getAnchors();c[0]&&this.popover.update(this.commentsForElement(c[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>
|
|
1206
|
-
`,this.pickingBanner.addEventListener("click",e=>{e.target.closest('[data-vz="cancel-pick"]')&&this.cancelPicking()}),this.root.appendChild(this.pickingBanner))}hidePickingBanner(){this.pickingBanner?.remove(),this.pickingBanner=null}jumpToReference(e,t){let i=this.comments.find(r=>r.id===e)?.references?.[t];i&&this.jumpToFingerprint(i)}jumpToFingerprint(e){let t=
|
|
1206
|
+
`,this.pickingBanner.addEventListener("click",e=>{e.target.closest('[data-vz="cancel-pick"]')&&this.cancelPicking()}),this.root.appendChild(this.pickingBanner))}hidePickingBanner(){this.pickingBanner?.remove(),this.pickingBanner=null}jumpToReference(e,t){let i=this.comments.find(r=>r.id===e)?.references?.[t];i&&this.jumpToFingerprint(i)}jumpToFingerprint(e){let t=T(e);if(!t){this.pill?.toast("Element not found on this page");return}if(t.scrollIntoView({behavior:"smooth",block:"center"}),this.root){let r=document.createElement("div");r.className="vz-jump-spotlight",this.root.appendChild(r),this.positionOutline(r,t);let s={el:r,target:t};this.spotlights.add(s),setTimeout(()=>{r.remove(),this.spotlights.delete(s)},1800)}let n=u(e),i=this.outlines.get(n)?.outline;i&&setTimeout(()=>{i.classList.remove("is-pulsing"),i.offsetWidth,i.classList.add("is-pulsing"),setTimeout(()=>i.classList.remove("is-pulsing"),1500)},250)}renderAllMarkers(){if(!this.root)return;for(let{marker:r}of this.markers.values())r.remove();for(let{outline:r}of this.outlines.values())r.remove();this.markers.clear(),this.outlines.clear();let e=new Map,t=new Map,n=r=>r==="exact"?3:r==="likely"?2:r==="drifted"?1:0,i=(r,s)=>r===void 0||n(s)>n(r)?s:r;for(let r of this.comments)for(let s of r.fingerprints||[]){let a=u(s),c=e.get(a);if(!c){let l=D(s);c={fp:s,target:l.element,confidence:l.confidence,commentIds:[]},e.set(a,c)}c.commentIds.push(r.id),t.set(r.id,i(t.get(r.id),c.confidence))}this.resolvedConfidence.clear();for(let r of this.comments){let s=t.get(r.id)??"orphaned";this.resolvedConfidence.set(r.id,s);let a=(r.fingerprints||[])[0],c=a?e.get(u(a))?.target??null:null;this.bus.emit("anchor:resolved",{comment:r,confidence:s,element:c})}for(let r of this.comments)this.selfHealedThisSession.has(r.id)||this.resolvedConfidence.get(r.id)==="drifted"&&this.selfHealComment(r,e);for(let[r,s]of e){if(s.confidence==="orphaned"||!s.target)continue;let a=s.commentIds.filter(h=>this.commentMatchesStatusFilter(h));if(a.length===0)continue;let c=document.createElement("div");c.className="vz-comment-outline",s.confidence==="drifted"&&c.classList.add("vz-comment-outline-drifted"),this.root.appendChild(c),this.outlines.set(r,{outline:c,target:s.target,fp:s.fp});let l=document.createElement("button");l.className="vz-marker",s.confidence==="drifted"&&l.classList.add("vz-marker-drifted"),l.setAttribute("data-element-key",r),l.setAttribute("data-confidence",s.confidence);let p=a.length;if(p>1)l.textContent=String(p),l.title=p+" comments";else{l.textContent="";let h=this.comments.find(g=>g.id===a[0]);l.title=h?.author?"Comment by "+h.author.name:"Comment"}l.addEventListener("click",h=>{if(h.preventDefault(),h.stopPropagation(),s.target){let g=this.commentsForElement(s.fp);this.openPopoverFor([s.target],[s.fp],g)}}),l.addEventListener("mouseenter",()=>this.pulseRelatedElements(a,r)),this.root.appendChild(l),this.markers.set(r,{marker:l,target:s.target,fp:s.fp,commentIds:a}),s.target?(this.positionMarker(l,s.target),this.positionOutline(c,s.target)):(l.style.display="none",c.style.display="none")}}pulseRelatedElements(e,t){let n=new Set;for(let i of e){let r=this.comments.find(a=>a.id===i);if(!r)continue;let s=r.fingerprints||[];if(!(s.length<=1))for(let a of s){let c=u(a);c!==t&&n.add(c)}}for(let i of n){let r=this.outlines.get(i)?.outline;r&&(r.classList.remove("is-pulsing"),r.offsetWidth,r.classList.add("is-pulsing"),setTimeout(()=>r.classList.remove("is-pulsing"),900))}}positionOutline(e,t){let n=t.getBoundingClientRect();if(n.width===0&&n.height===0){e.style.display="none";return}e.style.display="",e.style.left=n.left+"px",e.style.top=n.top+"px",e.style.width=n.width+"px",e.style.height=n.height+"px"}positionMarker(e,t){let n=t.getBoundingClientRect();if(n.width===0&&n.height===0){e.style.display="none";return}e.style.display="",e.style.left=n.right+"px",e.style.top=n.top+"px"}syncActiveAnchorOutlines(e){if(!this.root)return;let t=new Set(e.map(n=>u(n)));for(let[n,i]of this.activeAnchorOutlines)t.has(n)||(i.el.remove(),this.activeAnchorOutlines.delete(n));for(let n of e){let i=u(n);if(this.activeAnchorOutlines.has(i))continue;let r=document.createElement("div");r.className="vz-comment-outline is-selected-pending",this.root.appendChild(r);let s=T(n);s?this.positionOutline(r,s):r.style.display="none",this.activeAnchorOutlines.set(i,{el:r,fp:n})}}clearActiveAnchorOutlines(){for(let e of this.activeAnchorOutlines.values())e.el.remove();this.activeAnchorOutlines.clear()}actionContext(){return{comments:this.getComments(),pageHtml:this.snapshotHtml(),pageUrl:typeof location<"u"?location.href:void 0,pageVersion:this.opts.pageVersion,user:this.user??void 0,timestamp:Date.now(),copyToClipboard:e=>this.copyToClipboard(e),toast:e=>this.pill?.toast(e)}}snapshotHtml(){let e=document.documentElement.cloneNode(!0);for(let n of e.querySelectorAll("[data-vizu-root], #vizu-styles"))n.remove();return`<!DOCTYPE html>
|
|
1207
1207
|
<html`+document.documentElement.getAttributeNames().map(n=>` ${n}="${(document.documentElement.getAttribute(n)||"").replace(/"/g,""")}"`).join("")+`>
|
|
1208
1208
|
`+e.innerHTML+`
|
|
1209
|
-
</html>`}copyToClipboard(e){navigator.clipboard?.writeText?navigator.clipboard.writeText(e).catch(()=>
|
|
1209
|
+
</html>`}copyToClipboard(e){navigator.clipboard?.writeText?navigator.clipboard.writeText(e).catch(()=>le(e)):le(e)}};function le(o){let e=document.createElement("textarea");e.value=o,e.style.position="fixed",e.style.opacity="0",document.body.appendChild(e),e.select();try{document.execCommand("copy")}catch{}e.remove()}function De(){if(typeof document>"u")return null;let o=document.currentScript;if(o)return o;let e=Array.from(document.scripts);return e.find(t=>t.dataset&&t.dataset.vizuAuto!==void 0)||e.find(t=>t.src&&/vizu(\.min)?\.js/.test(t.src))||null}function Ie(){if(typeof window>"u"||window.__vizu)return;let e=De()?.dataset||{};if(!e.workspace){console.error('[vizu] data-workspace is required. Get your install snippet at https://vizu.unhingged.com \u2014 <script src="\u2026/vizu.min.js" data-workspace="your-slug"></script>');return}let t=e.userName?{id:e.userId,name:e.userName,avatarUrl:e.userAvatar,email:e.userEmail}:void 0,n={workspace:e.workspace,apiUrl:e.apiUrl,autoSignIn:e.autoSignIn==="false"?!1:void 0,shortcut:e.shortcut||"mod+shift+e",pageVersion:e.version,accent:e.accent,startEnabled:e.startEnabled==="true"||e.startEnabled===""||e.startEnabled==="1",ignoreSelectors:e.ignore?e.ignore.split(",").map(r=>r.trim()).filter(Boolean):[],user:t},i=new A(n);window.__vizu=i,window.Vizu=A}Ie();return ge(Oe);})();
|
|
1210
1210
|
//# sourceMappingURL=vizu.min.js.map
|