@nuniapp/widget 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -18,13 +18,13 @@ Get a project ID by running: npx @nuniapp/cli@latest init (it prints the ID and
18
18
  Steps:
19
19
  1. Detect the framework and package manager.
20
20
  2. If the app uses React (Next.js, Vite, Remix, React Router, TanStack Start, etc.):
21
- - Install @nuniapp/react.
21
+ - Install @nuniapp/react@latest.
22
22
  - Render <Nuni project="PROJECT_ID" /> once, globally, in the root layout or app entry (for Next.js App Router: app/layout.tsx, inside <body>). It is already a client component.
23
23
  3. Otherwise, if there is a bundler (Vue, Svelte, Astro, Angular, etc.):
24
- - Install @nuniapp/widget.
24
+ - Install @nuniapp/widget@latest.
25
25
  - Call init({ project: "PROJECT_ID" }) once on the client, in the app entry or root layout. import { init } from "@nuniapp/widget".
26
26
  4. If there is no bundler (plain HTML, WordPress, Webflow): add this before </body> on every page:
27
- <script src="https://cdn.jsdelivr.net/npm/@nuniapp/widget@0/dist/nuni.global.js" data-project="PROJECT_ID" defer></script>
27
+ <script src="https://cdn.jsdelivr.net/npm/@nuniapp/widget@latest/dist/nuni.global.js" data-project="PROJECT_ID" defer></script>
28
28
  5. Commit the project ID directly in the code. It is public, not a secret. Do not put it in an env var.
29
29
  6. Do not add Nuni to server-only code, and do not load it more than once.
30
30
  7. If the site sends a Content-Security-Policy, allow connect-src https://*.convex.cloud wss://*.convex.cloud https://*.convex.site (and script-src cdn.jsdelivr.net for the script tag).
@@ -38,8 +38,8 @@ Docs: https://nuni.praveenjuge.com/quickstart
38
38
  Or do it yourself:
39
39
 
40
40
  ```sh
41
- npx @nuniapp/cli init # prints your project ID and the snippet for your framework
42
- npm install @nuniapp/widget
41
+ npx @nuniapp/cli@latest init # prints your project ID and the snippet for your framework
42
+ npm install @nuniapp/widget@latest
43
43
  ```
44
44
 
45
45
  ```ts
@@ -52,7 +52,7 @@ Using React? Use [`@nuniapp/react`](https://www.npmjs.com/package/@nuniapp/react
52
52
 
53
53
  ```html
54
54
  <script
55
- src="https://cdn.jsdelivr.net/npm/@nuniapp/widget@0/dist/nuni.global.js"
55
+ src="https://cdn.jsdelivr.net/npm/@nuniapp/widget@latest/dist/nuni.global.js"
56
56
  data-project="nuni_..."
57
57
  defer
58
58
  ></script>
package/dist/index.js CHANGED
@@ -73,7 +73,7 @@ function describeLocation(input) {
73
73
  }
74
74
  //#endregion
75
75
  //#region src/config.ts
76
- const VERSION = "0.1.2";
76
+ const VERSION = "0.1.3";
77
77
  function resolveConfig(options) {
78
78
  return {
79
79
  project: options.project,
@@ -4632,6 +4632,7 @@ const STYLES = `
4632
4632
  font-size: 14px;
4633
4633
  line-height: 1.45;
4634
4634
  -webkit-font-smoothing: antialiased;
4635
+ -moz-osx-font-smoothing: grayscale;
4635
4636
  }
4636
4637
  @media (prefers-color-scheme: dark) {
4637
4638
  :host {
@@ -5889,7 +5890,7 @@ function init(options) {
5889
5890
  const noop = { destroy() {} };
5890
5891
  if (typeof window === "undefined" || typeof document === "undefined") return noop;
5891
5892
  if (!isProjectId(options.project)) {
5892
- console.warn(`[nuni] "${options.project}" is not a valid project ID. Run \`npx @nuniapp/cli init\` to get one.`);
5893
+ console.warn(`[nuni] "${options.project}" is not a valid project ID. Run \`npx @nuniapp/cli@latest init\` to get one.`);
5893
5894
  return noop;
5894
5895
  }
5895
5896
  if (active?.project === options.project) return handleFor(active);
@@ -24,6 +24,7 @@
24
24
  font-size: 14px;
25
25
  line-height: 1.45;
26
26
  -webkit-font-smoothing: antialiased;
27
+ -moz-osx-font-smoothing: grayscale;
27
28
  }
28
29
  @media (prefers-color-scheme: dark) {
29
30
  :host {
@@ -323,4 +324,4 @@ a.item { color: inherit; text-decoration: none; }
323
324
  @media (prefers-reduced-motion: reduce) {
324
325
  * { transition: none !important; animation: none !important; }
325
326
  }
326
- `,this.pinLayer=J(`div`,{class:`pins`}),this.overlayLayer=J(`div`,{class:`overlay`}),this.uiLayer=J(`div`,{class:`ui`}),this.root.append(n,this.pinLayer,this.overlayLayer,this.uiLayer),this.api=new Qn(e)}start(){document.body.appendChild(this.host),ar(this.secret).then(e=>{this.secretHash=e,this.render()}),this.api.touch(location.origin),this.cleanups.push(this.api.onStatus(e=>{this.status=e,this.render()})),this.cleanups.push(this.api.onPages(e=>{this.pages=e,this.render()})),this.ownerToken&&this.watchSession(this.ownerToken),this.readDeepLink(),this.subscribePage(),this.cleanups.push(ir(()=>{this.readDeepLink(),this.subscribePage()}));let e=e=>this.onKeyDown(e),t=()=>this.scheduleLayout(),n=e=>this.onMessage(e);window.addEventListener(`keydown`,e,!0),window.addEventListener(`scroll`,t,{capture:!0,passive:!0}),window.addEventListener(`resize`,t,{passive:!0}),window.addEventListener(`message`,n),this.cleanups.push(()=>{window.removeEventListener(`keydown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,t),window.removeEventListener(`message`,n)});let r=new MutationObserver(e=>{e.every(e=>this.host.contains(e.target)||e.target===this.host)||this.scheduleResolve()});r.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:[`class`,`style`,`hidden`,`open`,`id`]});let i=new ResizeObserver(()=>this.scheduleLayout());i.observe(document.documentElement),this.cleanups.push(()=>{r.disconnect(),i.disconnect()}),this.render()}destroy(){this.setPicking(!1),this.pageUnsub?.(),this.sessionUnsub?.();for(let e of this.cleanups)e();cancelAnimationFrame(this.layoutFrame),clearTimeout(this.resolveTimer),clearTimeout(this.toastTimer),this.host.remove(),this.api.close()}currentPageKey(){let e=new URL(location.href);return this.config.getPageKey?.(e)??o(e)}subscribePage(){let e=this.currentPageKey();e===this.pageKey&&this.pageUnsub||(this.pageKey=e,this.pageUnsub?.(),this.comments=[],this.extraComment=null,this.loaded=!1,this.placements.clear(),this.card=null,this.activeId=null,this.pageUnsub=this.api.onPage(e,e=>{let t=this.extraComment,n=t&&!e.some(e=>e._id===t._id)?[...e,t]:e;this.comments=n.sort((e,t)=>e.createdAt-t.createdAt),this.loaded=!0,this.resolvePlacements(!1),this.openDeepLink(),this.render()},()=>{}),this.render())}watchSession(e){this.sessionUnsub?.(),this.sessionUnsub=this.api.onSession(e,e=>{e.valid?this.ownerName=e.ownerName??`Owner`:(this.ownerName=null,this.ownerToken=null,dr(Q.session(this.config.project),null),this.sessionUnsub?.(),this.sessionUnsub=null),this.render()})}get isOwner(){return!!(this.ownerToken&&this.ownerName)}isMine(e){return!!this.secretHash&&e.authorKeyHash===this.secretHash}visibleComments(){return this.comments.filter(e=>e.status===(this.panelOpen&&this.tab===`resolved`?`resolved`:`open`)||this.card?.kind===`thread`&&this.card.id===e._id)}scheduleResolve(){clearTimeout(this.resolveTimer),this.resolveTimer=window.setTimeout(()=>this.resolvePlacements(!0),200)}resolvePlacements(e){let t=new Set(this.comments.map(e=>e._id));for(let e of this.placements.keys())t.has(e)||this.placements.delete(e);for(let t of this.comments){let n=this.placements.get(t._id);if(e&&n?.element&&n.element.isConnected){let e=w(n.element),r=t.anchor.text;if(!r||e===r||ye(e,r)>.8)continue}let r=Fe(t.anchor,document,{isIgnored:this.isIgnored});this.placements.set(t._id,{element:r.element,confidence:r.confidence})}this.renderPins(),this.layout(),e&&this.panelOpen&&this.render()}pinPoint(e){let t=this.placements.get(e._id)?.element;if(!t||!h(t))return null;let n=t.getBoundingClientRect();return{x:n.left+e.anchor.offset.x*n.width,y:n.top+e.anchor.offset.y*n.height}}scheduleLayout(){this.layoutFrame||(this.layoutFrame=requestAnimationFrame(()=>{this.layoutFrame=0,this.layout()}))}layout(){let e=window.innerWidth,t=window.innerHeight,n=[];this.pinPositions.clear();for(let r of this.visibleComments()){let i=this.pins.get(r._id);if(!i)continue;let a=this.pinPoint(r);if(i.hidden=!(a&&a.x>=-10&&a.y>=-10&&a.x<=e+10&&a.y<=t+10),!a)continue;let o=a.y,s=a.x;for(;n.some(e=>Math.abs(e.x-s)<14&&Math.abs(e.y-o)<14);)s+=18;n.push({x:s,y:o}),this.pinPositions.set(r._id,{x:s,y:o}),i.style.transform=`translate(${Math.round(s)}px, ${Math.round(o)}px)`}this.positionCard(),this.picking&&this.hoverEl&&this.renderHighlight()}setPicking(e){if(this.picking===e)return;this.picking=e;let t=document.documentElement;if(e){this.card=null,window.matchMedia(pr).matches&&(this.panelOpen=!1),t.classList.add(fr),this.injectCursorStyle(),document.addEventListener(`pointermove`,this.onPickMove,!0),document.addEventListener(`click`,this.onPickClick,!0);for(let e of[`pointerdown`,`mousedown`,`pointerup`,`mouseup`])document.addEventListener(e,this.swallow,{capture:!0,passive:!1})}else{t.classList.remove(fr),this.hoverEl=null,document.removeEventListener(`pointermove`,this.onPickMove,!0),document.removeEventListener(`click`,this.onPickClick,!0);for(let e of[`pointerdown`,`mousedown`,`pointerup`,`mouseup`])document.removeEventListener(e,this.swallow,!0)}this.render()}injectCursorStyle(){if(document.getElementById(`nuni-cursor-style`))return;let e=document.createElement(`style`);e.id=`nuni-cursor-style`,e.textContent=`html.${fr}, html.${fr} * { cursor: crosshair !important; }`,document.head.appendChild(e),this.cleanups.push(()=>e.remove())}fromPage(e){if(e.composedPath().includes(this.host))return null;let t=e.target;return t instanceof Element?t:null}renderHighlight(){if(this.overlayLayer.replaceChildren(),!this.picking)return;this.overlayLayer.append(J(`div`,{class:`pick-hint`},`Click anything to comment · Esc to cancel`));let e=this.hoverEl;if(!e||e===document.documentElement)return;let t=e.getBoundingClientRect(),n=J(`div`,{class:`highlight`},J(`span`,{class:`highlight-label`},hr(e)));Object.assign(n.style,{left:`${t.left-2}px`,top:`${t.top-2}px`,width:`${t.width+4}px`,height:`${t.height+4}px`}),this.overlayLayer.append(n)}onKeyDown(e){let t=e.composedPath().includes(this.host);if(e.key===`Escape`){if(this.picking)this.setPicking(!1);else if(this.card)this.closeCard();else if(this.panelOpen)this.togglePanel(!1);else return;e.stopPropagation();return}t||mr(e.target)||e.metaKey||e.ctrlKey||e.altKey||e.repeat||(e.key===`c`||e.key===`C`)&&(e.preventDefault(),this.setPicking(!this.picking))}openClaim(){let e=new URL(`${this.config.appUrl}/dashboard/claim`);e.searchParams.set(`project`,this.config.project),e.searchParams.set(`origin`,location.origin),window.open(e.toString(),`nuni-claim`,`popup,width=480,height=680`)||(location.href=e.toString())}onMessage(e){let t;try{t=new URL(this.config.appUrl).origin}catch{return}if(e.origin!==t)return;let n=e.data;n&&n.type===`nuni:session`&&n.project===this.config.project&&typeof n.token==`string`&&n.token.startsWith(`nuni_s_`)&&(this.ownerToken=n.token,dr(Q.session(this.config.project),n.token),this.watchSession(n.token),this.showToast(`You're signed in as the owner`))}readDeepLink(){let e=new URL(location.href),t=e.searchParams.get(`nuni`);t&&(this.deepLinkId=t,e.searchParams.delete(`nuni`),history.replaceState(history.state,``,e.toString()))}openDeepLink(){let e=this.deepLinkId;if(!e||!this.loaded)return;this.deepLinkId=null;let t=this.comments.find(t=>t._id===e);if(t){this.focusDeepLinked(t);return}this.api.getComment(e).then(e=>{if(!e||e.page.path!==this.pageKey){this.showToast(`That comment was deleted or is on another page`);return}this.extraComment=e,this.comments=[...this.comments.filter(t=>t._id!==e._id),e],this.resolvePlacements(!1),this.focusDeepLinked(e)})}focusDeepLinked(e){this.focusComment(e._id)}focusComment(e){this.placements.get(e)?.element?.scrollIntoView({block:`center`,behavior:`smooth`}),this.activeId=e,this.card={kind:`thread`,id:e},window.matchMedia(pr).matches&&(this.panelOpen=!1),this.render(),setTimeout(()=>this.scheduleLayout(),400)}async submitDraft(e,t,n){let r=n.trim().slice(0,a.nameMaxLength),i=t.trim();if(!r)return this.setDraftError(e,`Enter your name`);if(!i)return this.setDraftError(e,`Write a comment first`);if(i.length>a.bodyMaxLength)return this.setDraftError(e,`Comments are limited to ${a.bodyMaxLength} characters`);this.name=r,dr(Q.name,r),e.sending=!0,e.error=void 0,this.render();let o=c(location.href);try{let t=await this.api.createComment({body:i,authorName:r,authorSecret:this.secret,page:{...o,path:this.pageKey,title:document.title.slice(0,300)},anchor:e.anchor,viewport:{w:window.innerWidth,h:window.innerHeight,dpr:window.devicePixelRatio||1}});this.placements.set(t,{element:e.element,confidence:`exact`}),this.card?.kind===`composer`&&this.card.draft===e&&(this.card=null),this.showToast(`Comment added`)}catch(t){e.sending=!1,e.error=t instanceof Xn?t.message:`Couldn't post the comment`}this.render()}setDraftError(e,t){e.error=t,this.render()}async act(e,t,n){let r=this.card;r?.kind===`thread`&&(r.busy=!0,r.error=void 0,this.render());try{return await t(),n&&this.showToast(n),this.card?.kind===`thread`&&this.card.id===e&&(this.card.busy=!1,this.card.editing=!1),this.render(),!0}catch(t){return this.card?.kind===`thread`&&this.card.id===e&&(this.card.busy=!1,this.card.error=t instanceof Error?t.message:`Something went wrong`),this.render(),!1}}resolveComment(e){let t=this.ownerToken;t&&this.act(e._id,()=>this.api.resolve(e._id,t),`Resolved`).then(t=>{t&&!(this.panelOpen&&this.tab===`resolved`)&&this.card?.kind===`thread`&&this.card.id===e._id&&this.closeCard()})}reopenComment(e){let t=this.ownerToken;t&&this.act(e._id,()=>this.api.reopen(e._id,t),`Reopened`)}deleteComment(e){if(!window.confirm(`Delete this comment? This can't be undone.`))return;let t=this.ownerToken,n=this.isMine(e)?()=>this.api.deleteOwn(e._id,this.secret):t?()=>this.api.remove(e._id,t):null;n&&this.act(e._id,n,`Deleted`).then(t=>{t&&(this.extraComment?._id===e._id&&(this.extraComment=null),this.comments=this.comments.filter(t=>t._id!==e._id),this.placements.delete(e._id),this.closeCard())})}saveEdit(e,t){this.act(e._id,()=>this.api.editOwn(e._id,this.secret,t),`Saved`)}showToast(e){this.toast=e,clearTimeout(this.toastTimer),this.toastTimer=window.setTimeout(()=>{this.toast=null,this.render()},2400),this.render()}togglePanel(e=!this.panelOpen){this.panelOpen=e,e&&window.matchMedia(pr).matches&&(this.card=null),this.render()}closeCard(){this.card=null,this.activeId=null,this.render()}render(){this.renderPins(),this.renderUi(),this.renderHighlight(),this.layout()}renderPins(){let e=this.visibleComments(),t=new Set(e.map(e=>e._id));for(let[e,n]of this.pins)t.has(e)||(n.remove(),this.pins.delete(e));for(let t of e){let e=this.pins.get(t._id);if(!e){e=J(`button`,{class:`pin`,type:`button`,hidden:!0});let n=t._id;e.addEventListener(`click`,e=>{e.stopPropagation(),this.card?.kind===`thread`&&this.card.id===n?this.closeCard():(this.activeId=n,this.card={kind:`thread`,id:n},this.render())}),this.pins.set(t._id,e),this.pinLayer.append(e)}e.textContent=er(t.authorName),e.style.background=tr(t.authorName),e.setAttribute(`aria-label`,`Comment by ${t.authorName}: ${t.body.slice(0,80)}`),e.dataset.status=t.status,e.dataset.active=String(this.activeId===t._id),e.dataset.confidence=this.placements.get(t._id)?.confidence??`lost`}if(this.pinLayer.querySelector(`.pin-draft`)?.remove(),this.card?.kind===`composer`){let{draft:e}=this.card,t=e.element.getBoundingClientRect(),n=t.left+e.anchor.offset.x*t.width,r=t.top+e.anchor.offset.y*t.height,i=J(`div`,{class:`pin pin-draft pin-pending`,"aria-hidden":`true`},this.name?er(this.name):`+`);i.style.background=this.name?tr(this.name):`var(--n-accent)`,i.style.transform=`translate(${Math.round(n)}px, ${Math.round(r)}px)`,this.pinLayer.append(i)}}renderUi(){let e=[this.renderToolbar()];this.panelOpen&&e.push(this.renderPanel());let t=this.renderCard();t&&e.push(t),this.toast&&e.push(J(`div`,{class:`toast`,role:`status`},this.toast));let n=this.root.activeElement,r=n?.dataset.focusKey,i=n instanceof HTMLTextAreaElement||n instanceof HTMLInputElement?{start:n.selectionStart,end:n.selectionEnd}:null;if(this.uiLayer.replaceChildren(...e),r){let e=this.uiLayer.querySelector(`[data-focus-key="${r}"]`);e?.focus(),i&&(e instanceof HTMLTextAreaElement||e instanceof HTMLInputElement)&&e.setSelectionRange(i.start,i.end)}this.positionCard()}renderToolbar(){let e=this.comments.filter(e=>e.status===`open`).length;return J(`div`,{class:`toolbar`,role:`toolbar`,"aria-label":`Nuni comments`},J(`button`,{class:`tb-btn`,type:`button`,"aria-pressed":String(this.picking),"aria-label":this.picking?`Cancel adding a comment`:`Add a comment`,title:`Add a comment (C)`,onclick:()=>this.setPicking(!this.picking)},Y(Z.comment),J(`span`,{class:`tb-label`},this.picking?`Pick an element`:`Comment`)),J(`button`,{class:`tb-btn`,type:`button`,"aria-pressed":String(this.panelOpen),"aria-label":`Comments on this page: ${e} open`,title:`All comments`,onclick:()=>this.togglePanel()},Y(Z.list),J(`span`,{class:`tb-count`},String(e))))}renderPanel(){let e=this.comments.filter(e=>e.status===`open`),t=this.comments.filter(e=>e.status===`resolved`),n=this.tab===`open`?e:t,r=n.filter(e=>this.placements.get(e._id)?.element),i=n.filter(e=>!this.placements.get(e._id)?.element),a=this.pages.filter(e=>e.path!==this.pageKey),o=e=>J(`button`,{class:`item`,type:`button`,onclick:()=>this.focusComment(e._id)},J(`div`,{class:`row`},J(`span`,{class:`avatar`,style:`background:${tr(e.authorName)}`},er(e.authorName)),J(`span`,{class:`author`},e.authorName),J(`span`,{class:`meta`},$n(e.createdAt)),e.page.origin===location.origin?null:J(`span`,{class:`badge`,title:e.page.origin},new URL(e.page.origin).host)),J(`div`,{class:`item-text`},e.body)),s=[];if(this.loaded?n.length?(s.push(...r.map(o)),i.length&&(s.push(J(`div`,{class:`section-label`},`Couldn't find on this page`)),s.push(...i.map(o)))):s.push(J(`div`,{class:`empty`},this.tab===`open`?`No open comments on this page. Press C to add one.`:`Nothing resolved yet.`)):s.push(J(`div`,{class:`empty`},`Loading…`)),a.length){s.push(J(`div`,{class:`section-label`},`Other pages`));for(let e of a.slice(0,20)){let t=e.path.includes(`#`)?location.origin+e.path:location.origin+e.path+location.search;s.push(J(`a`,{class:`item`,href:t},J(`div`,{class:`row`},J(`span`,{class:`author`},e.path),J(`span`,{class:`spacer`}),J(`span`,{class:`badge`},`${e.count} open`))))}}return J(`div`,{class:`panel`,role:`dialog`,"aria-label":`Comments`},J(`div`,{class:`panel-head`},J(`div`,{class:`panel-title`},`Comments`),J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Close`,onclick:()=>this.togglePanel(!1)},Y(Z.close))),J(`div`,{class:`tabs`,role:`tablist`},J(`button`,{class:`tab`,role:`tab`,type:`button`,"aria-selected":String(this.tab===`open`),onclick:()=>{this.tab=`open`,this.render()}},`Open (${e.length})`),J(`button`,{class:`tab`,role:`tab`,type:`button`,"aria-selected":String(this.tab===`resolved`),onclick:()=>{this.tab=`resolved`,this.render()}},`Resolved (${t.length})`)),J(`div`,{class:`panel-list`},...s),this.status&&!this.status.claimed&&!this.isOwner?this.renderPanelFooter():null)}renderPanelFooter(){return J(`div`,{class:`panel-foot`},J(`div`,{class:`row`},J(`span`,{class:`meta`},`Own this website?`),J(`span`,{class:`spacer`}),J(`button`,{class:`btn`,type:`button`,onclick:()=>this.openClaim()},Y(Z.github),`Claim Nuni`)))}renderCard(){let e=this.card;if(!e)return null;let t=`card${window.matchMedia(pr).matches?` sheet`:``}`;if(e.kind===`composer`){let{draft:n}=e,r=!this.name,i=J(`input`,{class:`field`,placeholder:`Your name`,value:this.name,maxlength:a.nameMaxLength,autocomplete:`name`,"data-focus-key":`name`,"aria-label":`Your name`}),o=J(`textarea`,{class:`field`,placeholder:`Leave a comment`,maxlength:a.bodyMaxLength,"data-focus-key":`body`,"aria-label":`Comment`}),s=this.uiLayer.querySelector(`[data-focus-key="body"]`);s&&(o.value=s.value);let c=this.uiLayer.querySelector(`[data-focus-key="name"]`);c&&(i.value=c.value);let l=()=>void this.submitDraft(n,o.value,r?i.value:this.name);return o.addEventListener(`keydown`,e=>{e.key===`Enter`&&(e.metaKey||e.ctrlKey)&&(e.preventDefault(),l())}),J(`form`,{class:t,"data-card":`composer`,onsubmit:e=>{e.preventDefault(),l()}},J(`div`,{class:`card-body`},r?i:J(`div`,{class:`row`},J(`span`,{class:`avatar`,style:`background:${tr(this.name)}`},er(this.name)),J(`span`,{class:`author`},this.name),J(`span`,{class:`spacer`}),J(`button`,{class:`link`,type:`button`,onclick:()=>{this.name=``,this.render()}},`Not you?`)),o,n.error?J(`div`,{class:`error`,role:`alert`},n.error):null,J(`div`,{class:`row`},J(`span`,{class:`kbd`},`⌘ + Enter`),J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost`,type:`button`,onclick:()=>this.closeCard()},`Cancel`),J(`button`,{class:`btn btn-primary`,type:`submit`,disabled:!!n.sending},Y(Z.send),n.sending?`Posting…`:`Post`))))}let n=this.comments.find(t=>t._id===e.id);if(!n)return null;let r=this.isMine(n),i=this.isOwner,o=this.placements.get(n._id),s;if(e.editing){let t=J(`textarea`,{class:`field`,maxlength:a.bodyMaxLength,"data-focus-key":`edit`}),r=this.uiLayer.querySelector(`[data-focus-key="edit"]`);t.value=r?r.value:n.body,s=J(`div`,{class:`card-body`},t,J(`div`,{class:`row`},J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost`,type:`button`,onclick:()=>{e.editing=!1,this.render()}},`Cancel`),J(`button`,{class:`btn btn-primary`,type:`button`,disabled:!!e.busy,onclick:()=>this.saveEdit(n,t.value)},`Save`)))}else s=J(`div`,{class:`card-body`},J(`div`,{class:`card-head`},J(`span`,{class:`avatar`,style:`background:${tr(n.authorName)}`},er(n.authorName)),J(`span`,{class:`author`},n.authorName),J(`span`,{class:`meta`},$n(n.createdAt)+(n.editedAt?` · edited`:``)),J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Close`,onclick:()=>this.closeCard()},Y(Z.close))),J(`div`,{class:`comment-body`},n.body),J(`div`,{class:`row`},n.status===`resolved`?J(`span`,{class:`badge badge-ok`},Y(Z.check),`Resolved`):null,n.page.origin===location.origin?null:J(`span`,{class:`badge`,title:`Left on ${n.page.origin}`},new URL(n.page.origin).host),o?.confidence===`low`?J(`span`,{class:`badge`,title:`The page changed; this pin is a best guess`},`Approximate`):null,o?.element?null:J(`span`,{class:`badge`,title:`Originally on: ${n.anchor.text||n.anchor.tag}`},`Element not found`)),e.error?J(`div`,{class:`error`,role:`alert`},e.error):null);let c=[];return e.editing||(i&&c.push(n.status===`open`?J(`button`,{class:`btn`,type:`button`,disabled:!!e.busy,onclick:()=>this.resolveComment(n)},Y(Z.check),`Resolve`):J(`button`,{class:`btn`,type:`button`,disabled:!!e.busy,onclick:()=>this.reopenComment(n)},Y(Z.undo),`Reopen`)),c.push(J(`span`,{class:`spacer`})),r&&c.push(J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Edit`,title:`Edit`,onclick:()=>{e.editing=!0,this.render()}},Y(Z.edit))),(r||i)&&c.push(J(`button`,{class:`btn btn-ghost btn-icon btn-danger`,type:`button`,"aria-label":`Delete`,title:`Delete`,disabled:!!e.busy,onclick:()=>this.deleteComment(n)},Y(Z.trash)))),J(`div`,{class:t,"data-card":`thread`,role:`dialog`,"aria-label":`Comment by ${n.authorName}`},s,c.length>1||i?J(`div`,{class:`actions`},...c):null)}positionCard(){let e=this.uiLayer.querySelector(`[data-card]`);if(!e||e.classList.contains(`sheet`))return;let t=null;if(this.card?.kind===`composer`){let{draft:e}=this.card,n=e.element.getBoundingClientRect();t={x:n.left+e.anchor.offset.x*n.width,y:n.top+e.anchor.offset.y*n.height}}else if(this.card?.kind===`thread`){let e=this.comments.find(e=>this.card?.kind===`thread`&&e._id===this.card.id);t=e?this.pinPositions.get(e._id)??this.pinPoint(e):null}let n=e.offsetWidth||320,r=e.offsetHeight||160,i=window.innerWidth,a=window.innerHeight,o,s;t?(o=t.x+36,s=t.y-28,o+n>i-12&&(o=t.x-n-12)):(o=i-n-16,s=a-r-72),o=Math.max(12,Math.min(o,i-n-12)),s=Math.max(12,Math.min(s,a-r-12)),e.style.left=`${Math.round(o)}px`,e.style.top=`${Math.round(s)}px`}focusComposer(){requestAnimationFrame(()=>{this.uiLayer.querySelector(this.name?`[data-focus-key="body"]`:`[data-focus-key="name"]`)?.focus()})}};let $=null;function _r(e){let t={destroy(){}};if(typeof window>`u`||typeof document>`u`)return t;if(!r(e.project))return console.warn(`[nuni] "${e.project}" is not a valid project ID. Run \`npx @nuniapp/cli init\` to get one.`),t;if($?.project===e.project)return vr($);$&&yr($);let n=l(e),i={project:e.project,widget:null,timer:0,handles:0};$=i;let a=()=>{if($===i){if(!document.body){i.timer=window.setTimeout(a,50);return}i.widget=new gr(n),i.widget.start()}},o=window.requestIdleCallback;return o?o(a,{timeout:1500}):i.timer=window.setTimeout(a,1),vr(i)}function vr(e){e.handles++;let t=!1;return{destroy(){t||(t=!0,e.handles--,e.handles<=0&&$===e&&yr(e))}}}function yr(e){clearTimeout(e.timer),e.widget?.destroy(),e.widget=null,$===e&&($=null)}window.Nuni={init:_r,version:`0.1.2`};let br=document.currentScript??document.querySelector(`script[data-project][src*='nuni']`),xr=br?.dataset.project;xr&&_r({project:xr,convexUrl:br?.dataset.convexUrl,convexSiteUrl:br?.dataset.convexSiteUrl,appUrl:br?.dataset.appUrl})})();
327
+ `,this.pinLayer=J(`div`,{class:`pins`}),this.overlayLayer=J(`div`,{class:`overlay`}),this.uiLayer=J(`div`,{class:`ui`}),this.root.append(n,this.pinLayer,this.overlayLayer,this.uiLayer),this.api=new Qn(e)}start(){document.body.appendChild(this.host),ar(this.secret).then(e=>{this.secretHash=e,this.render()}),this.api.touch(location.origin),this.cleanups.push(this.api.onStatus(e=>{this.status=e,this.render()})),this.cleanups.push(this.api.onPages(e=>{this.pages=e,this.render()})),this.ownerToken&&this.watchSession(this.ownerToken),this.readDeepLink(),this.subscribePage(),this.cleanups.push(ir(()=>{this.readDeepLink(),this.subscribePage()}));let e=e=>this.onKeyDown(e),t=()=>this.scheduleLayout(),n=e=>this.onMessage(e);window.addEventListener(`keydown`,e,!0),window.addEventListener(`scroll`,t,{capture:!0,passive:!0}),window.addEventListener(`resize`,t,{passive:!0}),window.addEventListener(`message`,n),this.cleanups.push(()=>{window.removeEventListener(`keydown`,e,!0),window.removeEventListener(`scroll`,t,!0),window.removeEventListener(`resize`,t),window.removeEventListener(`message`,n)});let r=new MutationObserver(e=>{e.every(e=>this.host.contains(e.target)||e.target===this.host)||this.scheduleResolve()});r.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:[`class`,`style`,`hidden`,`open`,`id`]});let i=new ResizeObserver(()=>this.scheduleLayout());i.observe(document.documentElement),this.cleanups.push(()=>{r.disconnect(),i.disconnect()}),this.render()}destroy(){this.setPicking(!1),this.pageUnsub?.(),this.sessionUnsub?.();for(let e of this.cleanups)e();cancelAnimationFrame(this.layoutFrame),clearTimeout(this.resolveTimer),clearTimeout(this.toastTimer),this.host.remove(),this.api.close()}currentPageKey(){let e=new URL(location.href);return this.config.getPageKey?.(e)??o(e)}subscribePage(){let e=this.currentPageKey();e===this.pageKey&&this.pageUnsub||(this.pageKey=e,this.pageUnsub?.(),this.comments=[],this.extraComment=null,this.loaded=!1,this.placements.clear(),this.card=null,this.activeId=null,this.pageUnsub=this.api.onPage(e,e=>{let t=this.extraComment,n=t&&!e.some(e=>e._id===t._id)?[...e,t]:e;this.comments=n.sort((e,t)=>e.createdAt-t.createdAt),this.loaded=!0,this.resolvePlacements(!1),this.openDeepLink(),this.render()},()=>{}),this.render())}watchSession(e){this.sessionUnsub?.(),this.sessionUnsub=this.api.onSession(e,e=>{e.valid?this.ownerName=e.ownerName??`Owner`:(this.ownerName=null,this.ownerToken=null,dr(Q.session(this.config.project),null),this.sessionUnsub?.(),this.sessionUnsub=null),this.render()})}get isOwner(){return!!(this.ownerToken&&this.ownerName)}isMine(e){return!!this.secretHash&&e.authorKeyHash===this.secretHash}visibleComments(){return this.comments.filter(e=>e.status===(this.panelOpen&&this.tab===`resolved`?`resolved`:`open`)||this.card?.kind===`thread`&&this.card.id===e._id)}scheduleResolve(){clearTimeout(this.resolveTimer),this.resolveTimer=window.setTimeout(()=>this.resolvePlacements(!0),200)}resolvePlacements(e){let t=new Set(this.comments.map(e=>e._id));for(let e of this.placements.keys())t.has(e)||this.placements.delete(e);for(let t of this.comments){let n=this.placements.get(t._id);if(e&&n?.element&&n.element.isConnected){let e=w(n.element),r=t.anchor.text;if(!r||e===r||ye(e,r)>.8)continue}let r=Fe(t.anchor,document,{isIgnored:this.isIgnored});this.placements.set(t._id,{element:r.element,confidence:r.confidence})}this.renderPins(),this.layout(),e&&this.panelOpen&&this.render()}pinPoint(e){let t=this.placements.get(e._id)?.element;if(!t||!h(t))return null;let n=t.getBoundingClientRect();return{x:n.left+e.anchor.offset.x*n.width,y:n.top+e.anchor.offset.y*n.height}}scheduleLayout(){this.layoutFrame||(this.layoutFrame=requestAnimationFrame(()=>{this.layoutFrame=0,this.layout()}))}layout(){let e=window.innerWidth,t=window.innerHeight,n=[];this.pinPositions.clear();for(let r of this.visibleComments()){let i=this.pins.get(r._id);if(!i)continue;let a=this.pinPoint(r);if(i.hidden=!(a&&a.x>=-10&&a.y>=-10&&a.x<=e+10&&a.y<=t+10),!a)continue;let o=a.y,s=a.x;for(;n.some(e=>Math.abs(e.x-s)<14&&Math.abs(e.y-o)<14);)s+=18;n.push({x:s,y:o}),this.pinPositions.set(r._id,{x:s,y:o}),i.style.transform=`translate(${Math.round(s)}px, ${Math.round(o)}px)`}this.positionCard(),this.picking&&this.hoverEl&&this.renderHighlight()}setPicking(e){if(this.picking===e)return;this.picking=e;let t=document.documentElement;if(e){this.card=null,window.matchMedia(pr).matches&&(this.panelOpen=!1),t.classList.add(fr),this.injectCursorStyle(),document.addEventListener(`pointermove`,this.onPickMove,!0),document.addEventListener(`click`,this.onPickClick,!0);for(let e of[`pointerdown`,`mousedown`,`pointerup`,`mouseup`])document.addEventListener(e,this.swallow,{capture:!0,passive:!1})}else{t.classList.remove(fr),this.hoverEl=null,document.removeEventListener(`pointermove`,this.onPickMove,!0),document.removeEventListener(`click`,this.onPickClick,!0);for(let e of[`pointerdown`,`mousedown`,`pointerup`,`mouseup`])document.removeEventListener(e,this.swallow,!0)}this.render()}injectCursorStyle(){if(document.getElementById(`nuni-cursor-style`))return;let e=document.createElement(`style`);e.id=`nuni-cursor-style`,e.textContent=`html.${fr}, html.${fr} * { cursor: crosshair !important; }`,document.head.appendChild(e),this.cleanups.push(()=>e.remove())}fromPage(e){if(e.composedPath().includes(this.host))return null;let t=e.target;return t instanceof Element?t:null}renderHighlight(){if(this.overlayLayer.replaceChildren(),!this.picking)return;this.overlayLayer.append(J(`div`,{class:`pick-hint`},`Click anything to comment · Esc to cancel`));let e=this.hoverEl;if(!e||e===document.documentElement)return;let t=e.getBoundingClientRect(),n=J(`div`,{class:`highlight`},J(`span`,{class:`highlight-label`},hr(e)));Object.assign(n.style,{left:`${t.left-2}px`,top:`${t.top-2}px`,width:`${t.width+4}px`,height:`${t.height+4}px`}),this.overlayLayer.append(n)}onKeyDown(e){let t=e.composedPath().includes(this.host);if(e.key===`Escape`){if(this.picking)this.setPicking(!1);else if(this.card)this.closeCard();else if(this.panelOpen)this.togglePanel(!1);else return;e.stopPropagation();return}t||mr(e.target)||e.metaKey||e.ctrlKey||e.altKey||e.repeat||(e.key===`c`||e.key===`C`)&&(e.preventDefault(),this.setPicking(!this.picking))}openClaim(){let e=new URL(`${this.config.appUrl}/dashboard/claim`);e.searchParams.set(`project`,this.config.project),e.searchParams.set(`origin`,location.origin),window.open(e.toString(),`nuni-claim`,`popup,width=480,height=680`)||(location.href=e.toString())}onMessage(e){let t;try{t=new URL(this.config.appUrl).origin}catch{return}if(e.origin!==t)return;let n=e.data;n&&n.type===`nuni:session`&&n.project===this.config.project&&typeof n.token==`string`&&n.token.startsWith(`nuni_s_`)&&(this.ownerToken=n.token,dr(Q.session(this.config.project),n.token),this.watchSession(n.token),this.showToast(`You're signed in as the owner`))}readDeepLink(){let e=new URL(location.href),t=e.searchParams.get(`nuni`);t&&(this.deepLinkId=t,e.searchParams.delete(`nuni`),history.replaceState(history.state,``,e.toString()))}openDeepLink(){let e=this.deepLinkId;if(!e||!this.loaded)return;this.deepLinkId=null;let t=this.comments.find(t=>t._id===e);if(t){this.focusDeepLinked(t);return}this.api.getComment(e).then(e=>{if(!e||e.page.path!==this.pageKey){this.showToast(`That comment was deleted or is on another page`);return}this.extraComment=e,this.comments=[...this.comments.filter(t=>t._id!==e._id),e],this.resolvePlacements(!1),this.focusDeepLinked(e)})}focusDeepLinked(e){this.focusComment(e._id)}focusComment(e){this.placements.get(e)?.element?.scrollIntoView({block:`center`,behavior:`smooth`}),this.activeId=e,this.card={kind:`thread`,id:e},window.matchMedia(pr).matches&&(this.panelOpen=!1),this.render(),setTimeout(()=>this.scheduleLayout(),400)}async submitDraft(e,t,n){let r=n.trim().slice(0,a.nameMaxLength),i=t.trim();if(!r)return this.setDraftError(e,`Enter your name`);if(!i)return this.setDraftError(e,`Write a comment first`);if(i.length>a.bodyMaxLength)return this.setDraftError(e,`Comments are limited to ${a.bodyMaxLength} characters`);this.name=r,dr(Q.name,r),e.sending=!0,e.error=void 0,this.render();let o=c(location.href);try{let t=await this.api.createComment({body:i,authorName:r,authorSecret:this.secret,page:{...o,path:this.pageKey,title:document.title.slice(0,300)},anchor:e.anchor,viewport:{w:window.innerWidth,h:window.innerHeight,dpr:window.devicePixelRatio||1}});this.placements.set(t,{element:e.element,confidence:`exact`}),this.card?.kind===`composer`&&this.card.draft===e&&(this.card=null),this.showToast(`Comment added`)}catch(t){e.sending=!1,e.error=t instanceof Xn?t.message:`Couldn't post the comment`}this.render()}setDraftError(e,t){e.error=t,this.render()}async act(e,t,n){let r=this.card;r?.kind===`thread`&&(r.busy=!0,r.error=void 0,this.render());try{return await t(),n&&this.showToast(n),this.card?.kind===`thread`&&this.card.id===e&&(this.card.busy=!1,this.card.editing=!1),this.render(),!0}catch(t){return this.card?.kind===`thread`&&this.card.id===e&&(this.card.busy=!1,this.card.error=t instanceof Error?t.message:`Something went wrong`),this.render(),!1}}resolveComment(e){let t=this.ownerToken;t&&this.act(e._id,()=>this.api.resolve(e._id,t),`Resolved`).then(t=>{t&&!(this.panelOpen&&this.tab===`resolved`)&&this.card?.kind===`thread`&&this.card.id===e._id&&this.closeCard()})}reopenComment(e){let t=this.ownerToken;t&&this.act(e._id,()=>this.api.reopen(e._id,t),`Reopened`)}deleteComment(e){if(!window.confirm(`Delete this comment? This can't be undone.`))return;let t=this.ownerToken,n=this.isMine(e)?()=>this.api.deleteOwn(e._id,this.secret):t?()=>this.api.remove(e._id,t):null;n&&this.act(e._id,n,`Deleted`).then(t=>{t&&(this.extraComment?._id===e._id&&(this.extraComment=null),this.comments=this.comments.filter(t=>t._id!==e._id),this.placements.delete(e._id),this.closeCard())})}saveEdit(e,t){this.act(e._id,()=>this.api.editOwn(e._id,this.secret,t),`Saved`)}showToast(e){this.toast=e,clearTimeout(this.toastTimer),this.toastTimer=window.setTimeout(()=>{this.toast=null,this.render()},2400),this.render()}togglePanel(e=!this.panelOpen){this.panelOpen=e,e&&window.matchMedia(pr).matches&&(this.card=null),this.render()}closeCard(){this.card=null,this.activeId=null,this.render()}render(){this.renderPins(),this.renderUi(),this.renderHighlight(),this.layout()}renderPins(){let e=this.visibleComments(),t=new Set(e.map(e=>e._id));for(let[e,n]of this.pins)t.has(e)||(n.remove(),this.pins.delete(e));for(let t of e){let e=this.pins.get(t._id);if(!e){e=J(`button`,{class:`pin`,type:`button`,hidden:!0});let n=t._id;e.addEventListener(`click`,e=>{e.stopPropagation(),this.card?.kind===`thread`&&this.card.id===n?this.closeCard():(this.activeId=n,this.card={kind:`thread`,id:n},this.render())}),this.pins.set(t._id,e),this.pinLayer.append(e)}e.textContent=er(t.authorName),e.style.background=tr(t.authorName),e.setAttribute(`aria-label`,`Comment by ${t.authorName}: ${t.body.slice(0,80)}`),e.dataset.status=t.status,e.dataset.active=String(this.activeId===t._id),e.dataset.confidence=this.placements.get(t._id)?.confidence??`lost`}if(this.pinLayer.querySelector(`.pin-draft`)?.remove(),this.card?.kind===`composer`){let{draft:e}=this.card,t=e.element.getBoundingClientRect(),n=t.left+e.anchor.offset.x*t.width,r=t.top+e.anchor.offset.y*t.height,i=J(`div`,{class:`pin pin-draft pin-pending`,"aria-hidden":`true`},this.name?er(this.name):`+`);i.style.background=this.name?tr(this.name):`var(--n-accent)`,i.style.transform=`translate(${Math.round(n)}px, ${Math.round(r)}px)`,this.pinLayer.append(i)}}renderUi(){let e=[this.renderToolbar()];this.panelOpen&&e.push(this.renderPanel());let t=this.renderCard();t&&e.push(t),this.toast&&e.push(J(`div`,{class:`toast`,role:`status`},this.toast));let n=this.root.activeElement,r=n?.dataset.focusKey,i=n instanceof HTMLTextAreaElement||n instanceof HTMLInputElement?{start:n.selectionStart,end:n.selectionEnd}:null;if(this.uiLayer.replaceChildren(...e),r){let e=this.uiLayer.querySelector(`[data-focus-key="${r}"]`);e?.focus(),i&&(e instanceof HTMLTextAreaElement||e instanceof HTMLInputElement)&&e.setSelectionRange(i.start,i.end)}this.positionCard()}renderToolbar(){let e=this.comments.filter(e=>e.status===`open`).length;return J(`div`,{class:`toolbar`,role:`toolbar`,"aria-label":`Nuni comments`},J(`button`,{class:`tb-btn`,type:`button`,"aria-pressed":String(this.picking),"aria-label":this.picking?`Cancel adding a comment`:`Add a comment`,title:`Add a comment (C)`,onclick:()=>this.setPicking(!this.picking)},Y(Z.comment),J(`span`,{class:`tb-label`},this.picking?`Pick an element`:`Comment`)),J(`button`,{class:`tb-btn`,type:`button`,"aria-pressed":String(this.panelOpen),"aria-label":`Comments on this page: ${e} open`,title:`All comments`,onclick:()=>this.togglePanel()},Y(Z.list),J(`span`,{class:`tb-count`},String(e))))}renderPanel(){let e=this.comments.filter(e=>e.status===`open`),t=this.comments.filter(e=>e.status===`resolved`),n=this.tab===`open`?e:t,r=n.filter(e=>this.placements.get(e._id)?.element),i=n.filter(e=>!this.placements.get(e._id)?.element),a=this.pages.filter(e=>e.path!==this.pageKey),o=e=>J(`button`,{class:`item`,type:`button`,onclick:()=>this.focusComment(e._id)},J(`div`,{class:`row`},J(`span`,{class:`avatar`,style:`background:${tr(e.authorName)}`},er(e.authorName)),J(`span`,{class:`author`},e.authorName),J(`span`,{class:`meta`},$n(e.createdAt)),e.page.origin===location.origin?null:J(`span`,{class:`badge`,title:e.page.origin},new URL(e.page.origin).host)),J(`div`,{class:`item-text`},e.body)),s=[];if(this.loaded?n.length?(s.push(...r.map(o)),i.length&&(s.push(J(`div`,{class:`section-label`},`Couldn't find on this page`)),s.push(...i.map(o)))):s.push(J(`div`,{class:`empty`},this.tab===`open`?`No open comments on this page. Press C to add one.`:`Nothing resolved yet.`)):s.push(J(`div`,{class:`empty`},`Loading…`)),a.length){s.push(J(`div`,{class:`section-label`},`Other pages`));for(let e of a.slice(0,20)){let t=e.path.includes(`#`)?location.origin+e.path:location.origin+e.path+location.search;s.push(J(`a`,{class:`item`,href:t},J(`div`,{class:`row`},J(`span`,{class:`author`},e.path),J(`span`,{class:`spacer`}),J(`span`,{class:`badge`},`${e.count} open`))))}}return J(`div`,{class:`panel`,role:`dialog`,"aria-label":`Comments`},J(`div`,{class:`panel-head`},J(`div`,{class:`panel-title`},`Comments`),J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Close`,onclick:()=>this.togglePanel(!1)},Y(Z.close))),J(`div`,{class:`tabs`,role:`tablist`},J(`button`,{class:`tab`,role:`tab`,type:`button`,"aria-selected":String(this.tab===`open`),onclick:()=>{this.tab=`open`,this.render()}},`Open (${e.length})`),J(`button`,{class:`tab`,role:`tab`,type:`button`,"aria-selected":String(this.tab===`resolved`),onclick:()=>{this.tab=`resolved`,this.render()}},`Resolved (${t.length})`)),J(`div`,{class:`panel-list`},...s),this.status&&!this.status.claimed&&!this.isOwner?this.renderPanelFooter():null)}renderPanelFooter(){return J(`div`,{class:`panel-foot`},J(`div`,{class:`row`},J(`span`,{class:`meta`},`Own this website?`),J(`span`,{class:`spacer`}),J(`button`,{class:`btn`,type:`button`,onclick:()=>this.openClaim()},Y(Z.github),`Claim Nuni`)))}renderCard(){let e=this.card;if(!e)return null;let t=`card${window.matchMedia(pr).matches?` sheet`:``}`;if(e.kind===`composer`){let{draft:n}=e,r=!this.name,i=J(`input`,{class:`field`,placeholder:`Your name`,value:this.name,maxlength:a.nameMaxLength,autocomplete:`name`,"data-focus-key":`name`,"aria-label":`Your name`}),o=J(`textarea`,{class:`field`,placeholder:`Leave a comment`,maxlength:a.bodyMaxLength,"data-focus-key":`body`,"aria-label":`Comment`}),s=this.uiLayer.querySelector(`[data-focus-key="body"]`);s&&(o.value=s.value);let c=this.uiLayer.querySelector(`[data-focus-key="name"]`);c&&(i.value=c.value);let l=()=>void this.submitDraft(n,o.value,r?i.value:this.name);return o.addEventListener(`keydown`,e=>{e.key===`Enter`&&(e.metaKey||e.ctrlKey)&&(e.preventDefault(),l())}),J(`form`,{class:t,"data-card":`composer`,onsubmit:e=>{e.preventDefault(),l()}},J(`div`,{class:`card-body`},r?i:J(`div`,{class:`row`},J(`span`,{class:`avatar`,style:`background:${tr(this.name)}`},er(this.name)),J(`span`,{class:`author`},this.name),J(`span`,{class:`spacer`}),J(`button`,{class:`link`,type:`button`,onclick:()=>{this.name=``,this.render()}},`Not you?`)),o,n.error?J(`div`,{class:`error`,role:`alert`},n.error):null,J(`div`,{class:`row`},J(`span`,{class:`kbd`},`⌘ + Enter`),J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost`,type:`button`,onclick:()=>this.closeCard()},`Cancel`),J(`button`,{class:`btn btn-primary`,type:`submit`,disabled:!!n.sending},Y(Z.send),n.sending?`Posting…`:`Post`))))}let n=this.comments.find(t=>t._id===e.id);if(!n)return null;let r=this.isMine(n),i=this.isOwner,o=this.placements.get(n._id),s;if(e.editing){let t=J(`textarea`,{class:`field`,maxlength:a.bodyMaxLength,"data-focus-key":`edit`}),r=this.uiLayer.querySelector(`[data-focus-key="edit"]`);t.value=r?r.value:n.body,s=J(`div`,{class:`card-body`},t,J(`div`,{class:`row`},J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost`,type:`button`,onclick:()=>{e.editing=!1,this.render()}},`Cancel`),J(`button`,{class:`btn btn-primary`,type:`button`,disabled:!!e.busy,onclick:()=>this.saveEdit(n,t.value)},`Save`)))}else s=J(`div`,{class:`card-body`},J(`div`,{class:`card-head`},J(`span`,{class:`avatar`,style:`background:${tr(n.authorName)}`},er(n.authorName)),J(`span`,{class:`author`},n.authorName),J(`span`,{class:`meta`},$n(n.createdAt)+(n.editedAt?` · edited`:``)),J(`span`,{class:`spacer`}),J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Close`,onclick:()=>this.closeCard()},Y(Z.close))),J(`div`,{class:`comment-body`},n.body),J(`div`,{class:`row`},n.status===`resolved`?J(`span`,{class:`badge badge-ok`},Y(Z.check),`Resolved`):null,n.page.origin===location.origin?null:J(`span`,{class:`badge`,title:`Left on ${n.page.origin}`},new URL(n.page.origin).host),o?.confidence===`low`?J(`span`,{class:`badge`,title:`The page changed; this pin is a best guess`},`Approximate`):null,o?.element?null:J(`span`,{class:`badge`,title:`Originally on: ${n.anchor.text||n.anchor.tag}`},`Element not found`)),e.error?J(`div`,{class:`error`,role:`alert`},e.error):null);let c=[];return e.editing||(i&&c.push(n.status===`open`?J(`button`,{class:`btn`,type:`button`,disabled:!!e.busy,onclick:()=>this.resolveComment(n)},Y(Z.check),`Resolve`):J(`button`,{class:`btn`,type:`button`,disabled:!!e.busy,onclick:()=>this.reopenComment(n)},Y(Z.undo),`Reopen`)),c.push(J(`span`,{class:`spacer`})),r&&c.push(J(`button`,{class:`btn btn-ghost btn-icon`,type:`button`,"aria-label":`Edit`,title:`Edit`,onclick:()=>{e.editing=!0,this.render()}},Y(Z.edit))),(r||i)&&c.push(J(`button`,{class:`btn btn-ghost btn-icon btn-danger`,type:`button`,"aria-label":`Delete`,title:`Delete`,disabled:!!e.busy,onclick:()=>this.deleteComment(n)},Y(Z.trash)))),J(`div`,{class:t,"data-card":`thread`,role:`dialog`,"aria-label":`Comment by ${n.authorName}`},s,c.length>1||i?J(`div`,{class:`actions`},...c):null)}positionCard(){let e=this.uiLayer.querySelector(`[data-card]`);if(!e||e.classList.contains(`sheet`))return;let t=null;if(this.card?.kind===`composer`){let{draft:e}=this.card,n=e.element.getBoundingClientRect();t={x:n.left+e.anchor.offset.x*n.width,y:n.top+e.anchor.offset.y*n.height}}else if(this.card?.kind===`thread`){let e=this.comments.find(e=>this.card?.kind===`thread`&&e._id===this.card.id);t=e?this.pinPositions.get(e._id)??this.pinPoint(e):null}let n=e.offsetWidth||320,r=e.offsetHeight||160,i=window.innerWidth,a=window.innerHeight,o,s;t?(o=t.x+36,s=t.y-28,o+n>i-12&&(o=t.x-n-12)):(o=i-n-16,s=a-r-72),o=Math.max(12,Math.min(o,i-n-12)),s=Math.max(12,Math.min(s,a-r-12)),e.style.left=`${Math.round(o)}px`,e.style.top=`${Math.round(s)}px`}focusComposer(){requestAnimationFrame(()=>{this.uiLayer.querySelector(this.name?`[data-focus-key="body"]`:`[data-focus-key="name"]`)?.focus()})}};let $=null;function _r(e){let t={destroy(){}};if(typeof window>`u`||typeof document>`u`)return t;if(!r(e.project))return console.warn(`[nuni] "${e.project}" is not a valid project ID. Run \`npx @nuniapp/cli@latest init\` to get one.`),t;if($?.project===e.project)return vr($);$&&yr($);let n=l(e),i={project:e.project,widget:null,timer:0,handles:0};$=i;let a=()=>{if($===i){if(!document.body){i.timer=window.setTimeout(a,50);return}i.widget=new gr(n),i.widget.start()}},o=window.requestIdleCallback;return o?o(a,{timeout:1500}):i.timer=window.setTimeout(a,1),vr(i)}function vr(e){e.handles++;let t=!1;return{destroy(){t||(t=!0,e.handles--,e.handles<=0&&$===e&&yr(e))}}}function yr(e){clearTimeout(e.timer),e.widget?.destroy(),e.widget=null,$===e&&($=null)}window.Nuni={init:_r,version:`0.1.3`};let br=document.currentScript??document.querySelector(`script[data-project][src*='nuni']`),xr=br?.dataset.project;xr&&_r({project:xr,convexUrl:br?.dataset.convexUrl,convexSiteUrl:br?.dataset.convexSiteUrl,appUrl:br?.dataset.appUrl})})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuniapp/widget",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Pin comments to anything on your site. Talk it through with your team.",
5
5
  "license": "MIT",
6
6
  "type": "module",