@hazem3500/komo 0.8.0 → 0.9.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/NODE.md +20 -0
- package/README.md +12 -0
- package/dist/accent-picker-F5UI2NP7.js +1 -0
- package/dist/chunk-JQVBZ2BB.js +1 -0
- package/dist/{chunk-RPKLGTEU.js → chunk-Q7WSWLMV.js} +1 -1
- package/dist/{chunk-E5X4IQJ2.js → chunk-YZP3KX4Y.js} +1 -1
- package/dist/design-comment-QPTW66JO.js +1 -0
- package/dist/design-comment.d.ts +42 -0
- package/dist/design-mode.js +1 -1
- package/dist/dom.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/node.mjs +218 -65
- package/dist/{onboarding-YA63RUKK.js → onboarding-NO3RQWR7.js} +1 -1
- package/dist/react.js +1 -1
- package/dist/setup.js +1 -1
- package/dist/toolbar-runtime-2SIUE6SQ.js +1 -0
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
- package/server/index.ts +108 -17
- package/server/secrets.d.ts +6 -0
- package/server/setup-client.txt +66 -38
- package/src/types.ts +13 -0
- package/dist/accent-picker-HSUCA4KE.js +0 -1
- package/dist/chunk-ZUNTYWJF.js +0 -1
- package/dist/toolbar-runtime-MYOLQPTJ.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as e,c as t}from"./chunk-RPKLGTEU.js";import{d as a,e as n,f as s}from"./chunk-E5X4IQJ2.js";import"./chunk-I7AUKTXE.js";async function r(e,t=""){let r;try{let a=await e.request(`project/sites${t}`);r=[...a.fixed,...a.sites]}catch{return null}let i=a("div","approved-sites-editor");i.dataset.open="false";let o=a("span","approved-sites-count"),l=s("Sites with comments",()=>{let e="true"!==i.dataset.open;i.dataset.open=String(e),l.setAttribute("aria-expanded",String(e)),c.inert=!e},"approved-sites-toggle");l.replaceChildren(a("span","","Sites with comments"),o,n("chevron")),l.setAttribute("aria-expanded","false");let c=a("div","approved-sites-reveal");c.id=`approved-sites-${crypto.randomUUID()}`,c.inert=!0,l.setAttribute("aria-controls",c.id);let p=a("div","approved-sites-body"),d=a("ul","approved-site-list"),u=a("p","account-usage-status");u.setAttribute("role","status");let v=a("div","approved-site-add"),m=a("input");m.type="text",m.inputMode="url",m.spellcheck=!1,m.placeholder="your-site.com",m.setAttribute("aria-label","Site to approve"),m.setAttribute("autocomplete","off");let b=a("div","account-usage-note account-sites-note");b.append(n("info"),a("span","","Only these sites can show comments. Add * to cover preview links."));let h=async a=>{let n=r;r=a,f(),u.textContent="";try{let n=await e.request(`project/sites${t}`,"PATCH",{sites:a});return r=[...n.fixed,...n.sites],f(),!0}catch(e){return r=n,f(),u.textContent=e instanceof Error?e.message:"Try again.",!1}},y=s("Add",async()=>{let e;try{e=function(e){let t=e.trim().replace(/\/$/,"").toLowerCase();if(/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(t))return t;let a=/^https:\/\/([^/?#@\s]+)$/.exec(t.includes("://")?t:`https://${t}`),[n="",...s]=(a?.[1]??"").split(".");if(!a||s.length<1||s.join(".").includes("*")||n.split("*").length>2||n.includes("*")&&s.length<2)throw new Error("That doesn’t look like a site. Try your-site.com or *-preview.your-site.com.");return`https://${a[1]}`}(m.value)}catch(e){return void(u.textContent=e.message)}if(r.includes(e))return m.value="",void(u.textContent="Already on the list.");let t=m.value;m.value="",y.disabled=!0,await h([...r,e])||(m.value||=t),y.disabled=!1,m.focus()},"secondary");m.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),y.click())}),v.append(m,y);let g=e=>{let t=a("li","approved-site"),n=a("span","",e.replace(/^https:\/\//,""));return n.title=e,t.append(n,s(`Remove ${e}`,()=>{h(r.filter(t=>t!==e))},"icon","close")),t},f=()=>{o.textContent=String(r.length),d.replaceChildren(...r.map(g))};return f(),p.append(d,v,b,u),c.append(p),i.append(l,c),i}var i=new WeakMap;function o(e,n="usage",o=!0,l=!1){let c=a("section","account-usage");c.setAttribute("aria-label","Account usage");let p=(e="")=>{let t=a("span",`usage-skeleton ${e}`);return t.setAttribute("aria-hidden","true"),t},d=e.user?.id,u=e.token,v=()=>e.user?.id===d&&e.token===u,m=i.get(e);m||i.set(e,m=new Map);let b=JSON.stringify([d,u,n]),h=m.get(b);h||m.set(b,h={});let y=e=>{c.setAttribute("aria-busy","false"),c.replaceChildren(a("h3","",`${e.hosted?"Starter plan":"Self-hosted"}${l?" · Local":""}`));let{used:t,limit:n}=e.comments,s=a("div","account-usage-label account-comments"),r=a("span","account-comment-count");if(r.append(a("span","",null===n?`${t.toLocaleString()} / Unlimited`:`${t.toLocaleString()} / ${n.toLocaleString()}`)),s.title="Includes replies and deleted comments.",null!==n){let e=a("span","account-usage-ring");e.setAttribute("role","meter"),e.setAttribute("aria-label","Project comments"),e.setAttribute("aria-valuemin","0"),e.setAttribute("aria-valuemax",String(n)),e.setAttribute("aria-valuenow",String(Math.min(t,n))),e.setAttribute("aria-valuetext",`${t} of ${n} comments used, including replies and deleted comments`),e.style.setProperty("--usage",(n>0?100*Math.min(t/n,1):0)+"%"),e.dataset.full=String(t>=n),r.append(e)}if(s.append(a("span","","Project comments"),r),c.append(s),e.projects){let{used:t,limit:n}=e.projects;if(null===n){let e=a("div","account-usage-label");return e.append(a("span","","Projects"),a("span","",`${t.toLocaleString()} / Unlimited`)),void c.append(e)}let s=a("div","account-projects"),r=a("div","account-usage-label"),i=a("span","account-project-count");i.append(a("strong","",String(t)),a("span","",`of ${n}`)),r.append(a("span","","Projects"),i);let o=a("div","account-project-slots");o.setAttribute("role","meter"),o.setAttribute("aria-label","Projects"),o.setAttribute("aria-valuemin","0"),o.setAttribute("aria-valuemax",String(n)),o.setAttribute("aria-valuenow",String(t)),o.setAttribute("aria-valuetext",`${t} of ${n} projects used`);for(let e=0;e<n;e++){let n=a("span");n.dataset.used=String(e<t),o.append(n)}s.append(r,o),c.append(s)}},g=async()=>{if(!v())return c.replaceChildren(),void c.setAttribute("aria-busy","false");h.value?y(h.value):(()=>{let e=a("div","usage-skeleton-heading");e.append(p("usage-skeleton-title"));let t=a("div","account-usage-label account-comments"),n=a("span","account-comment-count");n.append(p("usage-skeleton-count"),p("usage-skeleton-ring")),t.append(p("usage-skeleton-label"),n);let s=a("div","account-projects"),r=a("div","account-usage-label");r.append(p("usage-skeleton-label"),p("usage-skeleton-count"));let i=a("div","account-project-slots");i.setAttribute("aria-hidden","true"),i.append(a("span"),a("span"),a("span")),s.append(r,i);let o=a("span","sr-only","Loading account usage");o.setAttribute("role","status"),c.setAttribute("aria-busy","true"),c.replaceChildren(e,t,s,o)})(),c.setAttribute("aria-busy","true");try{let t,a=h.pending??=e.request(n);try{t=await a}finally{h.pending===a&&(h.pending=void 0)}if(!v())return delete h.value,c.replaceChildren(),void c.setAttribute("aria-busy","false");if(h.value=t,!c.isConnected)return;y(t),o&&e.user?.verified&&(async()=>{let t=n.includes("?")?n.slice(n.indexOf("?")):"",a=await r(e,t);a&&h.value&&c.isConnected&&v()&&c.append(a)})().catch(()=>{})}catch(e){if(e instanceof t&&(401===e.status||403===e.status)&&delete h.value,!v())return delete h.value,c.replaceChildren(),void c.setAttribute("aria-busy","false");if(!c.isConnected)return;h.value?y(h.value):c.replaceChildren(),c.setAttribute("aria-busy","false");let n=a("span","account-usage-status",h.value?"Couldn’t refresh usage":"Usage unavailable");n.setAttribute("role","status"),c.append(n,s("Retry",()=>{g()},"secondary"))}};return g(),c}function l(t,r,i){let o=a("section","account-sites project-management");o.dataset.open="false";let l=s("Project settings",()=>{let e="true"!==o.dataset.open;o.dataset.open=String(e),l.setAttribute("aria-expanded",String(e)),c.inert=!e,c.setAttribute("aria-hidden",String(!e))},"account-sites-summary");l.append(n("chevron")),l.setAttribute("aria-expanded","false");let c=a("div","account-sites-reveal");c.id=`project-settings-${crypto.randomUUID()}`,l.setAttribute("aria-controls",c.id),c.inert=!0,c.setAttribute("aria-hidden","true");let p=a("div","account-sites-clip");c.append(p);let d=a("div","account-sites-content"),u=a("p","account-usage-status");u.setAttribute("role","status");let v=r?`?workspace=${encodeURIComponent(r)}`:"",m=(e,a="GET",n)=>t.request(`project${e}${v}`,a,n);o.hidden=!0,p.append(d,u),o.append(l,c);let b,h=async e=>{let t=Array.from(o.querySelectorAll("button"));t.forEach(e=>e.disabled=!0),u.textContent="";try{await e()}catch(e){u.textContent=e instanceof Error?e.message:"Try again."}finally{t.forEach(e=>e.disabled=!1)}},y=async()=>{let e,n={format:"komo-export",version:1,project:b.project,exportedAt:(new Date).toISOString(),tables:{}};for(let a of["threads","comments","reactions","users"]){n.tables[a]=[];let s=0;for(;null!==s;){let i=await t.request(`project/export?table=${a}&offset=${s}${void 0===e?"":`&revision=${e}`}${r?`&workspace=${encodeURIComponent(r)}`:""}`);e=i.revision,n.tables[a].push(...i.rows),s=i.next}}let s=URL.createObjectURL(new Blob([JSON.stringify(n,null,2)],{type:"application/json"})),i=a("a");i.href=s,i.download=`${b.project}-comments.json`,i.click(),setTimeout(()=>URL.revokeObjectURL(s),1e3),u.textContent="Export downloaded."},g=async()=>{b=await m(""),o.hidden=!1,d.replaceChildren();let t=a("div","account-name-label");t.append(a("span","","Access"));let n,r=[["public","Anyone with the link"],["private","Invited Google accounts"]],l=()=>{let a=e("Project access",r,b.access,e=>{h(async()=>{let t=b.access;b.access=e,l();try{await m("","PATCH",{access:e}),u.textContent="Access updated."}catch(e){throw b.access=t,l(),e}})});n?n.replaceWith(a):t.append(a),n=a};l(),d.append(t);let c=await m("/webhook"),p=a("details","project-webhook");p.append(a("summary","","Comment webhooks"));let v=a("p","project-help","Send signed events to your HTTPS endpoint. Signing secrets are encrypted and shown only when entered.");p.append(v),c.deliveryAvailable||(v.textContent="Secure webhook delivery is not available on this server yet.");let f=a("label","account-name-label","HTTPS endpoint"),w=a("input");w.type="url",w.autocomplete="off",w.placeholder="https://example.com/komo-webhook",w.value=c.url,f.append(w),p.append(f);let A=a("label","account-name-label",c.secretConfigured?"Replace signing secret":"Signing secret"),k=a("input");k.type="password",k.autocomplete="new-password",k.minLength=32,k.maxLength=256,A.append(k),p.append(A);let C=new Map;for(let e of c.supportedEvents){let t=a("label","account-name-label",e),n=a("input");n.type="checkbox",n.checked=c.events.includes(e),C.set(e,n),t.prepend(n),p.append(t)}let x=a("label","account-name-label","Enable delivery"),j=a("input");j.type="checkbox",j.checked=c.enabled,j.disabled=!c.deliveryAvailable&&!c.enabled,x.prepend(j),p.append(x),p.append(s("Save webhook settings",()=>{h(async()=>{await m("/webhook","PATCH",{url:w.value,secret:k.value,events:[...C].filter(([,e])=>e.checked).map(([e])=>e),enabled:j.checked}),k.value="",u.textContent="Webhook settings saved."})},"secondary")),d.append(p);let S=a("label","account-name-label","Invite someone"),$=a("input");$.type="email",$.placeholder="name@example.com",$.autocomplete="email",S.append($);let E=a("input");E.readOnly=!0,E.hidden=!0,E.setAttribute("aria-label","Invitation link");let T=s("Create invitation",()=>{h(async()=>{if(!$.checkValidity()||!$.value)throw Error("Enter an email address.");let e=await m("/invites","POST",{email:$.value});E.value=e.url,E.hidden=!1,P.hidden=!1,u.textContent="Share this link. It expires in 7 days."})},"secondary"),P=s("Copy invitation",()=>{h(async()=>{await navigator.clipboard.writeText(E.value),u.textContent="Invitation copied."})},"secondary");P.hidden=!0,d.append(S,T,E,P);for(let e of b.members){let t=a("div","project-member");t.append(a("span","",e.name),s(`Remove ${e.name}`,()=>{h(async()=>{await m("/members","DELETE",{user:e.id}),await g(),u.textContent="Member removed."})},"secondary")),d.append(t)}for(let e of b.invites){let t=a("div","project-member");t.append(a("span","",e.email),s("Revoke invitation",()=>{h(async()=>{await m("/invites","DELETE",{email:e.email}),await g(),u.textContent="Invitation revoked."})},"secondary")),d.append(t)}d.append(s("Download comments",()=>{h(y)},"secondary"));let L=a("a","project-help","Move to self-hosting");L.href="https://komo.offbr.co/hosting/#migration",L.target="_blank",L.rel="noopener noreferrer",d.append(L);let U=a("details","project-danger");U.append(a("summary","","Free space or delete project")),U.append(a("p","",`Export first. These actions permanently remove comments. Type ${b.project} to confirm.`));let R=a("input");R.placeholder=b.project,R.setAttribute("aria-label","Confirm project key"),U.append(R),U.append(s("Delete resolved threads",()=>{h(async()=>{await m("/clear-resolved","POST",{confirm:R.value}),i?.(!1),u.textContent="Resolved threads deleted.",R.value=""})},"secondary destructive")),b.hosted&&U.append(s("Delete project",()=>{h(async()=>{await m("","DELETE",{confirm:R.value}),i?.(!0),d.replaceChildren(),u.textContent="Project deleted. Its project slot is available again."})},"secondary destructive")),d.append(U)};return g().catch(e=>{403!==e?.status&&(o.hidden=!1,u.textContent="Project settings unavailable.",d.append(s("Retry",()=>{h(g)},"secondary")))}),o}function c(e,t){let r=a("section","onboarding-panel"),i=a("p","account-usage-status");i.setAttribute("role","status");let l=e=>{i.textContent=e instanceof Error?e.message:"Try again."},c=async()=>{let p=encodeURIComponent(t.workspace),d=a("div","account-sites"),u=a("div","account-sites-summary");u.append(a("span","","Approved sites"),n("chevron")),d.append(u),d.setAttribute("aria-busy","true"),i.textContent="",r.replaceChildren(o(e,`usage?workspace=${p}`,!1),d,i);try{let o=await e.request(`workspace?workspace=${p}`);if(!r.isConnected)return;i.textContent="";let c=a("section","account-sites");c.dataset.open="false";let u=s("",()=>{let e="true"!==c.dataset.open;c.dataset.open=String(e),u.setAttribute("aria-expanded",String(e)),v.inert=!e,v.setAttribute("aria-hidden",String(!e))},"account-sites-summary");u.setAttribute("aria-expanded","false");let v=a("div","account-sites-reveal");v.id=`approved-sites-${crypto.randomUUID()}`,v.inert=!0,v.setAttribute("aria-hidden","true"),u.setAttribute("aria-controls",v.id);let m=a("div","account-sites-clip"),b=a("span","account-sites-count");u.append(a("span","","Approved sites"),b,n("chevron"));let h=a("div","account-sites-content"),y=a("div","approved-sites"),g=()=>{b.textContent=String(o.sites.length),y.replaceChildren(...o.sites.map(e=>a("span","",new URL(e).host)))};g();let f=a("label","account-name-label","Website"),w=a("input");w.type="url",w.placeholder="https://your-site.com",w.setAttribute("autocomplete","url"),w.value=t.site||o.suggested.find(e=>!o.sites.includes(e))||"",f.append(w);let A=s("Approve site",async()=>{let a;try{let e=new URL(w.value.trim());if("https:"!==e.protocol||e.username||e.password)throw Error("Enter an HTTPS website address.");a=e.origin}catch(e){return void l(e)}let n=[...o.sites],s=w.value;o.sites.includes(a)||o.sites.push(a),g(),w.value="",A.disabled=!0;try{await e.request("workspace/sites","POST",{project:t.workspace,origin:a}),t.site=void 0,i.textContent="Site approved"}catch(e){o.sites=n,g(),w.value||(w.value=s),l(e)}finally{A.disabled=!1}},"secondary");w.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),A.click())});let k=a("div","account-usage-note account-sites-note");k.append(n("info"),a("span","","Approved sites can show and share comments. Localhost comments are visible by default.")),h.append(y,f,A,k),m.append(h),v.append(m),c.append(u,v),d.replaceWith(c)}catch(e){l(e),r.append(s("Retry",()=>{c()},"secondary"))}};if(t.invite){let n=s("Accept invitation",async()=>{n.disabled=!0;try{await e.request("project/join","POST",{invite:t.invite}),t.invite=void 0,r.replaceChildren(a("p","","You’re in. Return to the website to leave comments."))}catch(e){l(e),n.disabled=!1}},"primary");r.append(n,i)}else if(t.workspace)c();else if(t.code||t.claimKey){let a=s(t.code?"Create project":"Enable comments",async()=>{a.disabled=!0;let n=a.textContent;a.textContent=t.code?"Creating project…":"Enabling comments…";try{if(t.code){let a=await e.request("setup/complete","POST",{code:t.code});t.workspace=a.project,t.code=void 0,history.replaceState(null,"",`/setup?workspace=${encodeURIComponent(a.project)}`),await c(),i.textContent="Project ready. Return to your terminal."}else await e.request("owner/claim","POST",{key:t.claimKey}),t.claimKey=void 0,r.replaceChildren(o(e),i),i.textContent="Comments are ready."}catch(e){l(e),a.disabled=!1,a.textContent=n}},"primary");r.append(a,i)}else r.append(o(e));return r}export{o as accountUsage,c as onboardingPanel,l as projectManagement};
|
|
1
|
+
import{b as e,c as t}from"./chunk-Q7WSWLMV.js";import{h as a,i as n,j as s}from"./chunk-YZP3KX4Y.js";import"./chunk-I7AUKTXE.js";async function r(e,t=""){let r;try{let a=await e.request(`project/sites${t}`);r=[...a.fixed,...a.sites]}catch{return null}let i=a("div","approved-sites-editor");i.dataset.open="false";let o=a("span","approved-sites-count"),l=s("Sites with comments",()=>{let e="true"!==i.dataset.open;i.dataset.open=String(e),l.setAttribute("aria-expanded",String(e)),c.inert=!e},"approved-sites-toggle");l.replaceChildren(a("span","","Sites with comments"),o,n("chevron")),l.setAttribute("aria-expanded","false");let c=a("div","approved-sites-reveal");c.id=`approved-sites-${crypto.randomUUID()}`,c.inert=!0,l.setAttribute("aria-controls",c.id);let p=a("div","approved-sites-body"),d=a("ul","approved-site-list"),u=a("p","account-usage-status");u.setAttribute("role","status");let v=a("div","approved-site-add"),m=a("input");m.type="text",m.inputMode="url",m.spellcheck=!1,m.placeholder="your-site.com",m.setAttribute("aria-label","Site to approve"),m.setAttribute("autocomplete","off");let b=a("div","account-usage-note account-sites-note");b.append(n("info"),a("span","","Only these sites can show comments. Add * to cover preview links."));let h=async a=>{let n=r;r=a,f(),u.textContent="";try{let n=await e.request(`project/sites${t}`,"PATCH",{sites:a});return r=[...n.fixed,...n.sites],f(),!0}catch(e){return r=n,f(),u.textContent=e instanceof Error?e.message:"Try again.",!1}},y=s("Add",async()=>{let e;try{e=function(e){let t=e.trim().replace(/\/$/,"").toLowerCase();if(/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(t))return t;let a=/^https:\/\/([^/?#@\s]+)$/.exec(t.includes("://")?t:`https://${t}`),[n="",...s]=(a?.[1]??"").split(".");if(!a||s.length<1||s.join(".").includes("*")||n.split("*").length>2||n.includes("*")&&s.length<2)throw new Error("That doesn’t look like a site. Try your-site.com or *-preview.your-site.com.");return`https://${a[1]}`}(m.value)}catch(e){return void(u.textContent=e.message)}if(r.includes(e))return m.value="",void(u.textContent="Already on the list.");let t=m.value;m.value="",y.disabled=!0,await h([...r,e])||(m.value||=t),y.disabled=!1,m.focus()},"secondary");m.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),y.click())}),v.append(m,y);let g=e=>{let t=a("li","approved-site"),n=a("span","",e.replace(/^https:\/\//,""));return n.title=e,t.append(n,s(`Remove ${e}`,()=>{h(r.filter(t=>t!==e))},"icon","close")),t},f=()=>{o.textContent=String(r.length),d.replaceChildren(...r.map(g))};return f(),p.append(d,v,b,u),c.append(p),i.append(l,c),i}var i=new WeakMap;function o(e,n="usage",o=!0,l=!1){let c=a("section","account-usage");c.setAttribute("aria-label","Account usage");let p=(e="")=>{let t=a("span",`usage-skeleton ${e}`);return t.setAttribute("aria-hidden","true"),t},d=e.user?.id,u=e.token,v=()=>e.user?.id===d&&e.token===u,m=i.get(e);m||i.set(e,m=new Map);let b=JSON.stringify([d,u,n]),h=m.get(b);h||m.set(b,h={});let y=e=>{c.setAttribute("aria-busy","false"),c.replaceChildren(a("h3","",`${e.hosted?"Starter plan":"Self-hosted"}${l?" · Local":""}`));let{used:t,limit:n}=e.comments,s=a("div","account-usage-label account-comments"),r=a("span","account-comment-count");if(r.append(a("span","",null===n?`${t.toLocaleString()} / Unlimited`:`${t.toLocaleString()} / ${n.toLocaleString()}`)),s.title="Includes replies and deleted comments.",null!==n){let e=a("span","account-usage-ring");e.setAttribute("role","meter"),e.setAttribute("aria-label","Project comments"),e.setAttribute("aria-valuemin","0"),e.setAttribute("aria-valuemax",String(n)),e.setAttribute("aria-valuenow",String(Math.min(t,n))),e.setAttribute("aria-valuetext",`${t} of ${n} comments used, including replies and deleted comments`),e.style.setProperty("--usage",(n>0?100*Math.min(t/n,1):0)+"%"),e.dataset.full=String(t>=n),r.append(e)}if(s.append(a("span","","Project comments"),r),c.append(s),e.projects){let{used:t,limit:n}=e.projects;if(null===n){let e=a("div","account-usage-label");return e.append(a("span","","Projects"),a("span","",`${t.toLocaleString()} / Unlimited`)),void c.append(e)}let s=a("div","account-projects"),r=a("div","account-usage-label"),i=a("span","account-project-count");i.append(a("strong","",String(t)),a("span","",`of ${n}`)),r.append(a("span","","Projects"),i);let o=a("div","account-project-slots");o.setAttribute("role","meter"),o.setAttribute("aria-label","Projects"),o.setAttribute("aria-valuemin","0"),o.setAttribute("aria-valuemax",String(n)),o.setAttribute("aria-valuenow",String(t)),o.setAttribute("aria-valuetext",`${t} of ${n} projects used`);for(let e=0;e<n;e++){let n=a("span");n.dataset.used=String(e<t),o.append(n)}s.append(r,o),c.append(s)}},g=async()=>{if(!v())return c.replaceChildren(),void c.setAttribute("aria-busy","false");h.value?y(h.value):(()=>{let e=a("div","usage-skeleton-heading");e.append(p("usage-skeleton-title"));let t=a("div","account-usage-label account-comments"),n=a("span","account-comment-count");n.append(p("usage-skeleton-count"),p("usage-skeleton-ring")),t.append(p("usage-skeleton-label"),n);let s=a("div","account-projects"),r=a("div","account-usage-label");r.append(p("usage-skeleton-label"),p("usage-skeleton-count"));let i=a("div","account-project-slots");i.setAttribute("aria-hidden","true"),i.append(a("span"),a("span"),a("span")),s.append(r,i);let o=a("span","sr-only","Loading account usage");o.setAttribute("role","status"),c.setAttribute("aria-busy","true"),c.replaceChildren(e,t,s,o)})(),c.setAttribute("aria-busy","true");try{let t,a=h.pending??=e.request(n);try{t=await a}finally{h.pending===a&&(h.pending=void 0)}if(!v())return delete h.value,c.replaceChildren(),void c.setAttribute("aria-busy","false");if(h.value=t,!c.isConnected)return;y(t),o&&e.user?.verified&&(async()=>{let t=n.includes("?")?n.slice(n.indexOf("?")):"",a=await r(e,t);a&&h.value&&c.isConnected&&v()&&c.append(a)})().catch(()=>{})}catch(e){if(e instanceof t&&(401===e.status||403===e.status)&&delete h.value,!v())return delete h.value,c.replaceChildren(),void c.setAttribute("aria-busy","false");if(!c.isConnected)return;h.value?y(h.value):c.replaceChildren(),c.setAttribute("aria-busy","false");let n=a("span","account-usage-status",h.value?"Couldn’t refresh usage":"Usage unavailable");n.setAttribute("role","status"),c.append(n,s("Retry",()=>{g()},"secondary"))}};return g(),c}function l(t,r,i){let o=a("section","account-sites project-management");o.dataset.open="false";let l=s("Project settings",()=>{let e="true"!==o.dataset.open;o.dataset.open=String(e),l.setAttribute("aria-expanded",String(e)),c.inert=!e,c.setAttribute("aria-hidden",String(!e))},"account-sites-summary");l.append(n("chevron")),l.setAttribute("aria-expanded","false");let c=a("div","account-sites-reveal");c.id=`project-settings-${crypto.randomUUID()}`,l.setAttribute("aria-controls",c.id),c.inert=!0,c.setAttribute("aria-hidden","true");let p=a("div","account-sites-clip");c.append(p);let d=a("div","account-sites-content"),u=a("p","account-usage-status");u.setAttribute("role","status");let v=r?`?workspace=${encodeURIComponent(r)}`:"",m=(e,a="GET",n)=>t.request(`project${e}${v}`,a,n);o.hidden=!0,p.append(d,u),o.append(l,c);let b,h=async e=>{let t=Array.from(o.querySelectorAll("button"));t.forEach(e=>e.disabled=!0),u.textContent="";try{await e()}catch(e){u.textContent=e instanceof Error?e.message:"Try again."}finally{t.forEach(e=>e.disabled=!1)}},y=async()=>{let e,n={format:"komo-export",version:1,project:b.project,exportedAt:(new Date).toISOString(),tables:{}};for(let a of["threads","comments","reactions","users"]){n.tables[a]=[];let s=0;for(;null!==s;){let i=await t.request(`project/export?table=${a}&offset=${s}${void 0===e?"":`&revision=${e}`}${r?`&workspace=${encodeURIComponent(r)}`:""}`);e=i.revision,n.tables[a].push(...i.rows),s=i.next}}let s=URL.createObjectURL(new Blob([JSON.stringify(n,null,2)],{type:"application/json"})),i=a("a");i.href=s,i.download=`${b.project}-comments.json`,i.click(),setTimeout(()=>URL.revokeObjectURL(s),1e3),u.textContent="Export downloaded."},g=async()=>{b=await m(""),o.hidden=!1,d.replaceChildren();let t=a("div","account-name-label");t.append(a("span","","Access"));let n,r=[["public","Anyone with the link"],["private","Invited Google accounts"]],l=()=>{let a=e("Project access",r,b.access,e=>{h(async()=>{let t=b.access;b.access=e,l();try{await m("","PATCH",{access:e}),u.textContent="Access updated."}catch(e){throw b.access=t,l(),e}})});n?n.replaceWith(a):t.append(a),n=a};l(),d.append(t);let c=await m("/webhook"),p=a("details","project-webhook");p.append(a("summary","","Comment webhooks"));let v=a("p","project-help","Send signed events to your HTTPS endpoint. Signing secrets are encrypted and shown only when entered.");p.append(v),c.deliveryAvailable||(v.textContent="Secure webhook delivery is not available on this server yet.");let f=a("label","account-name-label","HTTPS endpoint"),w=a("input");w.type="url",w.autocomplete="off",w.placeholder="https://example.com/komo-webhook",w.value=c.url,f.append(w),p.append(f);let A=a("label","account-name-label",c.secretConfigured?"Replace signing secret":"Signing secret"),k=a("input");k.type="password",k.autocomplete="new-password",k.minLength=32,k.maxLength=256,A.append(k),p.append(A);let C=new Map;for(let e of c.supportedEvents){let t=a("label","account-name-label",e),n=a("input");n.type="checkbox",n.checked=c.events.includes(e),C.set(e,n),t.prepend(n),p.append(t)}let x=a("label","account-name-label","Enable delivery"),j=a("input");j.type="checkbox",j.checked=c.enabled,j.disabled=!c.deliveryAvailable&&!c.enabled,x.prepend(j),p.append(x),p.append(s("Save webhook settings",()=>{h(async()=>{await m("/webhook","PATCH",{url:w.value,secret:k.value,events:[...C].filter(([,e])=>e.checked).map(([e])=>e),enabled:j.checked}),k.value="",u.textContent="Webhook settings saved."})},"secondary")),d.append(p);let S=a("label","account-name-label","Invite someone"),$=a("input");$.type="email",$.placeholder="name@example.com",$.autocomplete="email",S.append($);let E=a("input");E.readOnly=!0,E.hidden=!0,E.setAttribute("aria-label","Invitation link");let T=s("Create invitation",()=>{h(async()=>{if(!$.checkValidity()||!$.value)throw Error("Enter an email address.");let e=await m("/invites","POST",{email:$.value});E.value=e.url,E.hidden=!1,P.hidden=!1,u.textContent="Share this link. It expires in 7 days."})},"secondary"),P=s("Copy invitation",()=>{h(async()=>{await navigator.clipboard.writeText(E.value),u.textContent="Invitation copied."})},"secondary");P.hidden=!0,d.append(S,T,E,P);for(let e of b.members){let t=a("div","project-member");t.append(a("span","",e.name),s(`Remove ${e.name}`,()=>{h(async()=>{await m("/members","DELETE",{user:e.id}),await g(),u.textContent="Member removed."})},"secondary")),d.append(t)}for(let e of b.invites){let t=a("div","project-member");t.append(a("span","",e.email),s("Revoke invitation",()=>{h(async()=>{await m("/invites","DELETE",{email:e.email}),await g(),u.textContent="Invitation revoked."})},"secondary")),d.append(t)}d.append(s("Download comments",()=>{h(y)},"secondary"));let L=a("a","project-help","Move to self-hosting");L.href="https://komo.offbr.co/hosting/#migration",L.target="_blank",L.rel="noopener noreferrer",d.append(L);let U=a("details","project-danger");U.append(a("summary","","Free space or delete project")),U.append(a("p","",`Export first. These actions permanently remove comments. Type ${b.project} to confirm.`));let R=a("input");R.placeholder=b.project,R.setAttribute("aria-label","Confirm project key"),U.append(R),U.append(s("Delete resolved threads",()=>{h(async()=>{await m("/clear-resolved","POST",{confirm:R.value}),i?.(!1),u.textContent="Resolved threads deleted.",R.value=""})},"secondary destructive")),b.hosted&&U.append(s("Delete project",()=>{h(async()=>{await m("","DELETE",{confirm:R.value}),i?.(!0),d.replaceChildren(),u.textContent="Project deleted. Its project slot is available again."})},"secondary destructive")),d.append(U)};return g().catch(e=>{403!==e?.status&&(o.hidden=!1,u.textContent="Project settings unavailable.",d.append(s("Retry",()=>{h(g)},"secondary")))}),o}function c(e,t){let r=a("section","onboarding-panel"),i=a("p","account-usage-status");i.setAttribute("role","status");let l=e=>{i.textContent=e instanceof Error?e.message:"Try again."},c=async()=>{let p=encodeURIComponent(t.workspace),d=a("div","account-sites"),u=a("div","account-sites-summary");u.append(a("span","","Approved sites"),n("chevron")),d.append(u),d.setAttribute("aria-busy","true"),i.textContent="",r.replaceChildren(o(e,`usage?workspace=${p}`,!1),d,i);try{let o=await e.request(`workspace?workspace=${p}`);if(!r.isConnected)return;i.textContent="";let c=a("section","account-sites");c.dataset.open="false";let u=s("",()=>{let e="true"!==c.dataset.open;c.dataset.open=String(e),u.setAttribute("aria-expanded",String(e)),v.inert=!e,v.setAttribute("aria-hidden",String(!e))},"account-sites-summary");u.setAttribute("aria-expanded","false");let v=a("div","account-sites-reveal");v.id=`approved-sites-${crypto.randomUUID()}`,v.inert=!0,v.setAttribute("aria-hidden","true"),u.setAttribute("aria-controls",v.id);let m=a("div","account-sites-clip"),b=a("span","account-sites-count");u.append(a("span","","Approved sites"),b,n("chevron"));let h=a("div","account-sites-content"),y=a("div","approved-sites"),g=()=>{b.textContent=String(o.sites.length),y.replaceChildren(...o.sites.map(e=>a("span","",new URL(e).host)))};g();let f=a("label","account-name-label","Website"),w=a("input");w.type="url",w.placeholder="https://your-site.com",w.setAttribute("autocomplete","url"),w.value=t.site||o.suggested.find(e=>!o.sites.includes(e))||"",f.append(w);let A=s("Approve site",async()=>{let a;try{let e=new URL(w.value.trim());if("https:"!==e.protocol||e.username||e.password)throw Error("Enter an HTTPS website address.");a=e.origin}catch(e){return void l(e)}let n=[...o.sites],s=w.value;o.sites.includes(a)||o.sites.push(a),g(),w.value="",A.disabled=!0;try{await e.request("workspace/sites","POST",{project:t.workspace,origin:a}),t.site=void 0,i.textContent="Site approved"}catch(e){o.sites=n,g(),w.value||(w.value=s),l(e)}finally{A.disabled=!1}},"secondary");w.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),A.click())});let k=a("div","account-usage-note account-sites-note");k.append(n("info"),a("span","","Approved sites can show and share comments. Localhost comments are visible by default.")),h.append(y,f,A,k),m.append(h),v.append(m),c.append(u,v),d.replaceWith(c)}catch(e){l(e),r.append(s("Retry",()=>{c()},"secondary"))}};if(t.invite){let n=s("Accept invitation",async()=>{n.disabled=!0;try{await e.request("project/join","POST",{invite:t.invite}),t.invite=void 0,r.replaceChildren(a("p","","You’re in. Return to the website to leave comments."))}catch(e){l(e),n.disabled=!1}},"primary");r.append(n,i)}else if(t.workspace)c();else if(t.code||t.claimKey){let a=s(t.code?"Create project":"Enable comments",async()=>{a.disabled=!0;let n=a.textContent;a.textContent=t.code?"Creating project…":"Enabling comments…";try{if(t.code){let a=await e.request("setup/complete","POST",{code:t.code});t.workspace=a.project,t.code=void 0,history.replaceState(null,"",`/setup?workspace=${encodeURIComponent(a.project)}`),await c(),i.textContent="Project ready. Return to your terminal."}else await e.request("owner/claim","POST",{key:t.claimKey}),t.claimKey=void 0,r.replaceChildren(o(e),i),i.textContent="Comments are ready."}catch(e){l(e),a.disabled=!1,a.textContent=n}},"primary");r.append(a,i)}else r.append(o(e));return r}export{o as accountUsage,c as onboardingPanel,l as projectManagement};
|
package/dist/react.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{b as e,c as n,e as r}from"./chunk-Z2ABBESO.js";import{b as t}from"./chunk-
|
|
1
|
+
"use client";import{b as e,c as n,e as r}from"./chunk-Z2ABBESO.js";import{b as t}from"./chunk-JQVBZ2BB.js";import"./chunk-Q7WSWLMV.js";import"./chunk-Y2GRLW4Q.js";import"./chunk-7YUXYVQA.js";import"./chunk-YZP3KX4Y.js";import"./chunk-I7AUKTXE.js";import{useEffect as o,useRef as s}from"react";function c(e){let n=s(null);o(()=>{if(n.current&&function(e,n){return[...new Set([...Object.keys(e),...Object.keys(n)])].every(r=>{if("shortcuts"===r||"features"===r){let t=e[r]??{},o=n[r]??{};return Object.keys({...t,...o}).every(e=>Object.is(t[e],o[e]))}if("onboarding"!==r)return Object.is(e[r],n[r]);let t=e.onboarding,o=n.onboarding;return t&&o?Object.keys({...t,...o}).every(e=>"sites"===e?t.sites?.length===o.sites?.length&&(t.sites??[]).every((e,n)=>e===o.sites?.[n]):Object.is(t[e],o[e])):t===o})}(n.current.config,e)||(n.current?.controller.destroy(),n.current=null,!1===e.enabled))return;let r={...e,...e.onboarding?{onboarding:{...e.onboarding,...e.onboarding.sites?{sites:[...e.onboarding.sites]}:{}}}:{}};n.current={config:r,controller:t(r)}}),o(()=>()=>{n.current?.controller.destroy(),n.current=null},[])}function u(r){let t=s(null),c=s(null);return c.current||(c.current=e=>{t.current=e}),o(()=>{if(!0===r.enabled&&t.current)return e(t.current,r.component,r.props,r.onChange)},[r.enabled,r.component]),o(()=>{!0===r.enabled&&n(t.current,r.component,r.props,r.onChange)},[r.enabled,r.component,r.props,r.onChange]),{ref:c.current,"data-komo-inspectable":r.component.id}}function i(e){o(()=>{if(!0!==e.enabled)return;let n=e.root??document.documentElement;return r(n,e.tokens)},[e.root,e.tokens,e.enabled])}export{c as useKomo,i as useKomoDesignTokens,u as useKomoInspectable};
|
package/dist/setup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as o,c as n}from"./chunk-
|
|
1
|
+
import{a as o,c as n}from"./chunk-JQVBZ2BB.js";import"./chunk-Q7WSWLMV.js";import"./chunk-Y2GRLW4Q.js";import"./chunk-7YUXYVQA.js";import"./chunk-YZP3KX4Y.js";import"./chunk-I7AUKTXE.js";function r(r){return(s={})=>n(o({...r,...s}))}export{r as defineKomo};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as t,A as e,B as n,C as r,D as i,E as o,F as s,G as a,H as l,I as u,J as c,K as d,L as h,M as m,N as p,O as f,P as g,Q as v,R as y,S as w,T as x,U as E,V as P,W as S,X as b,Y as C,Z as M,_ as k,a as T,aa as A,b as L,ba as D,c as I,ca as j,da as V,ea as _,fa as O,ga as R,ha as W,ia as N,j as B,ja as F,k as H,ka as U,l as G,m as Y,n as $,o as X,p as z,q,r as K,s as Z,t as J,u as Q,v as tt,w as et,x as nt,y as rt,z as it}from"./chunk-7YUXYVQA.js";import{c as ot,g as st,k as at}from"./chunk-YZP3KX4Y.js";import{a as lt}from"./chunk-I7AUKTXE.js";import{createRoot as ut}from"react-dom/client";import{flushSync as ct}from"react-dom";import{createElement as dt}from"react";import*as ht from"react";var mt=({size:t=24,color:e="currentColor",...n})=>ht.createElement("svg",{viewBox:"0 0 24 24",width:t,height:t,stroke:e,strokeWidth:"2",fill:"none",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",...n},ht.createElement("path",{d:"m7 15 5 5 5-5M7 9l5-5 5 5"}));mt.displayName="ChevronSelectorVertical";import*as pt from"react";var ft=({size:t=24,color:e="currentColor",...n})=>pt.createElement("svg",{viewBox:"0 0 24 24",width:t,height:t,stroke:e,strokeWidth:"2",fill:"none",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",...n},pt.createElement("path",{d:"M18 6 6 18M6 6l12 12"}));ft.displayName="XClose";import*as gt from"react";var vt=({size:t=24,color:e="currentColor",...n})=>gt.createElement("svg",{viewBox:"0 0 24 24",width:t,height:t,stroke:e,strokeWidth:"2",fill:"none",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",...n},gt.createElement("path",{d:"M19 12H5m0 0 7 7m-7-7 7-7"}));vt.displayName="ArrowLeft";import*as yt from"react";var wt=({size:t=24,color:e="currentColor",...n})=>yt.createElement("svg",{viewBox:"0 0 24 24",width:t,height:t,stroke:e,strokeWidth:"2",fill:"none",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",...n},yt.createElement("path",{d:"m9 18 6-6-6-6"}));wt.displayName="ChevronRight";import{useEffect as xt,useId as Et,useLayoutEffect as Pt,useRef as St,useState as bt}from"react";import{createContext as Ct}from"react";var Mt=Ct({});import{useRef as kt}from"react";import{useLayoutEffect as Tt,useEffect as At}from"react";var Lt=typeof window<"u"?Tt:At;import{createContext as Dt}from"react";var It=Dt(null);import{createContext as jt}from"react";var Vt=jt({transformPagePoint:t=>t,isStatic:!1,reducedMotion:"never"});import{useContext as _t,useId as Ot,useEffect as Rt,useCallback as Wt}from"react";import{createContext as Nt}from"react";var Bt=Nt({strict:!1}),Ft={animation:["animate","variants","whileHover","whileTap","exit","whileInView","whileFocus","whileDrag"],exit:["exit"],drag:["drag","dragControls"],focus:["whileFocus"],hover:["whileHover","onHoverStart","onHoverEnd"],tap:["whileTap","onTap","onTapStart","onTapCancel"],pan:["onPan","onPanStart","onPanSessionStart","onPanEnd"],inView:["whileInView","onViewportEnter","onViewportLeave"],layout:["layout","layoutId"]},Ht=!1;function Ut(){return function(){if(Ht)return;let t={};for(let e in Ft)t[e]={isEnabled:t=>Ft[e].some(e=>!!t[e])};v(t),Ht=!0}(),y()}var Gt=new Set(["animate","exit","variants","initial","style","values","variants","transition","transformTemplate","custom","inherit","onBeforeLayoutMeasure","onAnimationStart","onAnimationComplete","onUpdate","onDragStart","onDrag","onDragEnd","onMeasureDragConstraints","onDirectionLock","onDragTransitionEnd","_dragX","_dragY","onHoverStart","onHoverEnd","onViewportEnter","onViewportLeave","globalTapTarget","propagate","ignoreStrict","viewport"]);function Yt(t){return t.startsWith("while")||t.startsWith("drag")&&"draggable"!==t||t.startsWith("layout")||t.startsWith("onTap")||t.startsWith("onPan")||t.startsWith("onLayout")||Gt.has(t)}var $t,Xt=t=>!Yt(t);try{"function"==typeof($t=lt("@emotion/is-prop-valid").default)&&(Xt=t=>t.startsWith("on")?!Yt(t):$t(t))}catch{}import{jsxs as zt,jsx as qt}from"react/jsx-runtime";import{forwardRef as Kt,useContext as Zt}from"react";import{createContext as Jt}from"react";var Qt=Jt({});import{useContext as te,useMemo as ee}from"react";function ne(t){let{initial:e,animate:n}=function(t,e){if(h(t)){let{initial:e,animate:n}=t;return{initial:!1===e||d(e)?e:void 0,animate:d(n)?n:void 0}}return!1!==t.inherit?e:{}}(t,te(Qt));return ee(()=>({initial:e,animate:n}),[re(e),re(n)])}function re(t){return Array.isArray(t)?t.join(" "):t}import{Fragment as ie,useMemo as oe,createElement as se}from"react";import{useMemo as ae}from"react";var le=()=>({style:{},transform:{},transformOrigin:{},vars:{}});function ue(t,e,n){for(let r in e)!rt(e[r])&&!b(r,n)&&(t[r]=e[r])}function ce(t,e){let n={};return ue(n,t.style||{},t),Object.assign(n,function({transformTemplate:t},e){return ae(()=>{let n={style:{},transform:{},transformOrigin:{},vars:{}};return S(n,e,t),Object.assign({},n.vars,n.style)},[e])}(t,e)),n}function de(t,e){let n={},r=ce(t,e);return t.drag&&!1!==t.dragListener&&(n.draggable=!1,r.userSelect=r.WebkitUserSelect=r.WebkitTouchCallout="none",r.touchAction=!0===t.drag?"none":"pan-"+("x"===t.drag?"y":"x")),void 0===t.tabIndex&&(t.onTap||t.onTapStart||t.whileTap)&&(n.tabIndex=0),n.style=r,n}import{useMemo as he}from"react";var me=()=>({style:{},transform:{},transformOrigin:{},vars:{},attrs:{}});function pe(e,n,r,i){let o=he(()=>{let r={style:{},transform:{},transformOrigin:{},vars:{},attrs:{}};return k(r,n,t(i),e.transformTemplate,e.style),{...r.attrs,style:{...r.style}}},[n]);if(e.style){let t={};ue(t,e.style,e),o.style={...t,...o.style}}return o}var fe=["animate","circle","defs","desc","ellipse","g","image","line","filter","marker","mask","metadata","path","pattern","polygon","polyline","rect","stop","switch","symbol","svg","text","tspan","use","view"];function ge(t){return"string"==typeof t&&!t.includes("-")&&!!(fe.indexOf(t)>-1||/[A-Z]/u.test(t))}function ve(t,e,n,{latestValues:r},i,o=!1,s){let a=(s??ge(t)?pe:de)(e,r,i,t),l=function(t,e,n){let r={};for(let i in t)"values"===i&&"object"==typeof t.values||rt(t[i])||(Xt(i)||!0===n&&Yt(i)||!e&&!Yt(i)||t.draggable&&i.startsWith("onDrag"))&&(r[i]=t[i]);return r}(e,"string"==typeof t,o),u=t!==ie?{...l,...a,ref:n}:{},{children:c}=e,d=oe(()=>rt(c)?c.get():c,[c]);return se(t,{...u,children:d})}import{useContext as ye}from"react";function we(t,e,n,r){let i={},o=r(t,{});for(let t in o)i[t]=R(o[t]);let{initial:s,animate:a}=t,l=h(t),u=m(t);e&&u&&!l&&!1!==t.inherit&&(void 0===s&&(s=e.initial),void 0===a&&(a=e.animate));let d=!!n&&!1===n.initial;d=d||!1===s;let p=d?a:s;if(p&&"boolean"!=typeof p&&!c(p)){let e=Array.isArray(p)?p:[p];for(let n=0;n<e.length;n++){let r=et(t,e[n]);if(r){let{transitionEnd:t,transition:e,...n}=r;for(let t in n){let e=n[t];if(Array.isArray(e)){e=e[d?e.length-1:0]}null!==e&&(i[t]=e)}for(let e in t)i[e]=t[e]}}}return i}var xe=t=>(e,n)=>{let r=ye(Qt),i=ye(It),o=()=>function({scrapeMotionValuesFromProps:t,createRenderState:e},n,r,i){return{latestValues:we(n,r,i,t),renderState:e()}}(t,e,r,i);return n?o():function(t){let e=kt(null);return null===e.current&&(e.current=t()),e.current}(o)},Ee=xe({scrapeMotionValuesFromProps:C,createRenderState:le}),Pe=xe({scrapeMotionValuesFromProps:A,createRenderState:me}),Se=Symbol.for("motionComponentSymbol");import{useRef as be,useInsertionEffect as Ce,useCallback as Me}from"react";function ke(t,e,n){let r=be(n);Ce(()=>{r.current=n});let i=be(null);return Me(n=>{n&&t.onMount?.(n),e&&(n?e.mount(n):e.unmount());let o=r.current;if("function"==typeof o)if(n){let t=o(n);"function"==typeof t&&(i.current=t)}else i.current?(i.current(),i.current=null):o(n);else o&&(o.current=n)},[e])}import{useContext as Te,useRef as Ae,useInsertionEffect as Le,useEffect as De}from"react";import{createContext as Ie}from"react";var je=Ie({});function Ve(t){return t&&"object"==typeof t&&Object.prototype.hasOwnProperty.call(t,"current")}function _e(t,n,r,i,o,s){let{visualElement:a}=Te(Qt),l=Te(Bt),u=Te(It),c=Te(Vt),d=c.reducedMotion,h=c.skipAnimations,m=Ae(null),p=Ae(!1);i=i||l.renderer,!m.current&&i&&(m.current=i(t,{visualState:n,parent:a,props:r,presenceContext:u,blockInitialAnimation:!!u&&!1===u.initial,reducedMotionConfig:d,skipAnimations:h,isSVG:s}),p.current&&m.current&&(m.current.manuallyAnimateOnMount=!0));let f=m.current,g=Te(je);f&&!f.projection&&o&&("html"===f.type||"svg"===f.type)&&function(t,e,n,r){let{layoutId:i,layout:o,drag:s,dragConstraints:a,layoutScroll:l,layoutRoot:u,layoutAnchor:c,layoutCrossfade:d}=e;t.projection=new n(t.latestValues,e["data-framer-portal-id"]?void 0:Oe(t.parent)),t.projection.setOptions({layoutId:i,layout:o,alwaysMeasureLayout:!!s||a&&Ve(a),visualElement:t,animationType:"string"==typeof o?o:"both",initialPromotionConfig:r,crossfade:d,layoutScroll:l,layoutRoot:u,layoutAnchor:c})}(m.current,r,o,g);let v=Ae(!1);Le(()=>{f&&v.current&&f.update(r,u)});let y=r[e],w=Ae(!!y&&typeof window<"u"&&!window.MotionHandoffIsComplete?.(y)&&window.MotionHasOptimisedAnimation?.(y));return Lt(()=>{p.current=!0,f&&(v.current=!0,window.MotionIsMounted=!0,f.updateFeatures(),f.scheduleRenderMicrotask(),w.current&&f.animationState&&f.animationState.animateChanges())}),De(()=>{f&&(!w.current&&f.animationState&&f.animationState.animateChanges(),w.current&&(queueMicrotask(()=>{window.MotionHandoffMarkAsComplete?.(y)}),w.current=!1),f.enteringChildren=void 0)}),f}function Oe(t){if(t)return!1!==t.options.allowProjection?t.projection:Oe(t.parent)}function Re(t,{forwardMotionProps:e=!1,type:n}={},r,i){r&&function(t){let e=Ut();for(let n in t)e[n]={...e[n],...t[n]};v(e)}(r);let o=n?"svg"===n:ge(t),s=o?Pe:Ee;function a(n,r){let a,l={...Zt(Vt),...n,layoutId:We(n)},{isStatic:u}=l,c=ne(n),d=s(n,u);if(!u&&typeof window<"u"){Zt(Bt).strict;let e=function(t){let e=Ut(),{drag:n,layout:r}=e;if(!n&&!r)return{};let i={...n,...r};return{MeasureLayout:n?.isEnabled(t)||r?.isEnabled(t)?i.MeasureLayout:void 0,ProjectionNode:i.ProjectionNode}}(l);a=e.MeasureLayout,c.visualElement=_e(t,d,l,i,e.ProjectionNode,o)}return zt(Qt.Provider,{value:c,children:[a&&c.visualElement?qt(a,{visualElement:c.visualElement,...l}):null,ve(t,n,ke(d,c.visualElement,r),d,u,e,o)]})}a.displayName=`motion.${"string"==typeof t?t:`create(${t.displayName??t.name??""})`}`;let l=Kt(a);return l[Se]=t,l}function We({layoutId:t}){let e=Zt(Mt).id;return e&&void 0!==t?e+"-"+t:t}import{Fragment as Ne}from"react";var Be=0,Fe={animation:{Feature:class extends w{constructor(t){super(t),t.animationState||(t.animationState=j(t))}updateAnimationControlsSubscription(){let{animate:t}=this.node.getProps();c(t)&&(this.unmountControls=t.subscribe(this.node))}mount(){this.updateAnimationControlsSubscription()}update(){let{animate:t}=this.node.getProps(),{animate:e}=this.node.prevProps||{};t!==e&&this.updateAnimationControlsSubscription()}unmount(){this.node.animationState.reset(),this.unmountControls?.()}}},exit:{Feature:class extends w{constructor(){super(...arguments),this.id=Be++,this.isExitComplete=!1}update(){if(!this.node.presenceContext)return;let{isPresent:t,onExitComplete:e}=this.node.presenceContext,{isPresent:n}=this.node.prevPresenceContext||{};if(!this.node.animationState||t===n)return;if(t&&!1===n){if(this.isExitComplete){let{initial:t,custom:e}=this.node.getProps();if("string"==typeof t||"object"==typeof t&&null!==t&&!Array.isArray(t)){let n=nt(this.node,t,e);if(n){let{transition:t,transitionEnd:e,...r}=n;for(let t in r)this.node.getValue(t)?.jump(r[t])}}this.node.animationState.reset(),this.node.animationState.animateChanges()}else this.node.animationState.setActive("exit",!1);return void(this.isExitComplete=!1)}let r=this.node.animationState.setActive("exit",!t);e&&!t&&r.then(()=>{this.isExitComplete=!0,e(this.id)})}mount(){let{register:t,onExitComplete:e}=this.node.presenceContext||{};e&&e(this.id),t&&(this.unmount=t(this.id))}unmount(){}}}};function He(t){return{point:{x:t.pageX,y:t.pageY}}}function Ue(t,e,n,r){return O(t,e,(t=>e=>o(e)&&t(e,He(e)))(n),r)}var Ge=({current:t})=>t?t.ownerDocument.defaultView:null,Ye=new Set(["auto","scroll"]),$e=class{constructor(t,e,{transformPagePoint:n,contextWindow:r=window,dragSnapToOrigin:i=!1,distanceThreshold:s=3,element:a}={}){if(this.startEvent=null,this.lastMoveEvent=null,this.lastMoveEventInfo=null,this.lastRawMoveEventInfo=null,this.handlers={},this.contextWindow=window,this.scrollPositions=new Map,this.removeScrollListeners=null,this.onElementScroll=t=>{this.handleScroll(t.target)},this.onWindowScroll=()=>{this.handleScroll(window)},this.updatePoint=()=>{if(!this.lastMoveEvent||!this.lastMoveEventInfo)return;this.lastRawMoveEventInfo&&(this.lastMoveEventInfo=Xe(this.lastRawMoveEventInfo,this.transformPagePoint));let t=qe(this.lastMoveEventInfo,this.history),e=null!==this.startEvent,n=U(t.offset,{x:0,y:0})>=this.distanceThreshold;if(!e&&!n)return;let{point:r}=t,{timestamp:i}=Z;this.history.push({...r,timestamp:i});let{onStart:o,onMove:s}=this.handlers;e||(o&&o(this.lastMoveEvent,t),this.startEvent=this.lastMoveEvent),s&&s(this.lastMoveEvent,t)},this.handlePointerMove=(t,e)=>{this.lastMoveEvent=t,this.lastRawMoveEventInfo=e,this.lastMoveEventInfo=Xe(e,this.transformPagePoint),q.update(this.updatePoint,!0)},this.handlePointerUp=(t,e)=>{this.end();let{onEnd:n,onSessionEnd:r,resumeAnimation:i}=this.handlers;if((this.dragSnapToOrigin||!this.startEvent)&&i&&i(),!this.lastMoveEvent||!this.lastMoveEventInfo)return;let o=qe("pointercancel"===t.type?this.lastMoveEventInfo:Xe(e,this.transformPagePoint),this.history);this.startEvent&&n&&n(t,o),r&&r(t,o)},!o(t))return;this.dragSnapToOrigin=i,this.handlers=e,this.transformPagePoint=n,this.distanceThreshold=s,this.contextWindow=r||window;let l=Xe(He(t),this.transformPagePoint),{point:u}=l,{timestamp:c}=Z;this.history=[{...u,timestamp:c}];let{onSessionStart:d}=e;d&&d(t,qe(l,this.history));let h={passive:!0,capture:!0};this.removeListeners=Y(Ue(this.contextWindow,"pointermove",this.handlePointerMove,h),Ue(this.contextWindow,"pointerup",this.handlePointerUp,h),Ue(this.contextWindow,"pointercancel",this.handlePointerUp,h)),a&&this.startScrollTracking(a)}startScrollTracking(t){let e=t.parentElement;for(;e;){let t=getComputedStyle(e);(Ye.has(t.overflowX)||Ye.has(t.overflowY))&&this.scrollPositions.set(e,{x:e.scrollLeft,y:e.scrollTop}),e=e.parentElement}this.scrollPositions.set(window,{x:window.scrollX,y:window.scrollY}),window.addEventListener("scroll",this.onElementScroll,{capture:!0}),window.addEventListener("scroll",this.onWindowScroll),this.removeScrollListeners=()=>{window.removeEventListener("scroll",this.onElementScroll,{capture:!0}),window.removeEventListener("scroll",this.onWindowScroll)}}handleScroll(t){let e=this.scrollPositions.get(t);if(!e)return;let n=t===window,r=n?{x:window.scrollX,y:window.scrollY}:{x:t.scrollLeft,y:t.scrollTop},i=r.x-e.x,o=r.y-e.y;0===i&&0===o||(n?this.lastMoveEventInfo&&(this.lastMoveEventInfo.point.x+=i,this.lastMoveEventInfo.point.y+=o):this.history.length>0&&(this.history[0].x-=i,this.history[0].y-=o),this.scrollPositions.set(t,r),q.update(this.updatePoint,!0))}updateHandlers(t){this.handlers=t}end(){this.removeListeners&&this.removeListeners(),this.removeScrollListeners&&this.removeScrollListeners(),this.scrollPositions.clear(),K(this.updatePoint)}};function Xe(t,e){return e?{point:e(t.point)}:t}function ze(t,e){return{x:t.x-e.x,y:t.y-e.y}}function qe({point:t},e){return{point:t,delta:ze(t,Ze(e)),offset:ze(t,Ke(e)),velocity:Je(e,.1)}}function Ke(t){return t[0]}function Ze(t){return t[t.length-1]}function Je(t,e){if(t.length<2)return{x:0,y:0};let n=t.length-1,r=null,i=Ze(t);for(;n>=0&&(r=t[n],!(i.timestamp-r.timestamp>X(e)));)n--;if(!r)return{x:0,y:0};r===t[0]&&t.length>2&&i.timestamp-r.timestamp>2*X(e)&&(r=t[1]);let o=z(i.timestamp-r.timestamp);if(0===o)return{x:0,y:0};let s={x:(i.x-r.x)/o,y:(i.y-r.y)/o};return s.x===1/0&&(s.x=0),s.y===1/0&&(s.y=0),s}function Qe(t,e,n){return{min:void 0!==e?t.min+e:void 0,max:void 0!==n?t.max+n-(t.max-t.min):void 0}}function tn(t,e){let n=e.min-t.min,r=e.max-t.max;return e.max-e.min<t.max-t.min&&([n,r]=[r,n]),{min:n,max:r}}var en=.35;function nn(t,e,n){return{min:rn(t,e),max:rn(t,n)}}function rn(t,e){return"number"==typeof t?t:t[e]||0}var on=new WeakMap,sn=class{constructor(t){this.openDragLock=null,this.isDragging=!1,this.currentDirection=null,this.originPoint={x:0,y:0},this.constraints=!1,this.hasMutatedConstraints=!1,this.elastic=u(),this.latestPointerEvent=null,this.latestPanInfo=null,this.visualElement=t}start(t,{snapToCursor:e=!1,distanceThreshold:n}={}){let{presenceContext:i}=this.visualElement;if(i&&!1===i.isPresent)return;let{dragSnapToOrigin:o}=this.getProps();this.panSession=new $e(t,{onSessionStart:t=>{e&&this.snapToCursor(He(t).point),this.stopAnimation()},onStart:(t,e)=>{let{drag:n,dragPropagation:i,onDragStart:o}=this.getProps();if(n&&!i&&(this.openDragLock&&this.openDragLock(),this.openDragLock=r(n),!this.openDragLock))return;this.latestPointerEvent=t,this.latestPanInfo=e,this.isDragging=!0,this.currentDirection=null,this.resolveConstraints(),this.visualElement.projection&&(this.visualElement.projection.isAnimationBlocked=!0,this.visualElement.projection.target=void 0),_(t=>{let e=this.getAxisMotionValue(t).get()||0;if(J.test(e)){let{projection:n}=this.visualElement;if(n&&n.layout){let r=n.layout.layoutBox[t];r&&(e=V(r)*(parseFloat(e)/100))}}this.originPoint[t]=e}),o&&q.update(()=>o(t,e),!1,!0),it(this.visualElement,"transform");let{animationState:s}=this.visualElement;s&&s.setActive("whileDrag",!0)},onMove:(t,e)=>{this.latestPointerEvent=t,this.latestPanInfo=e;let{dragPropagation:n,dragDirectionLock:r,onDirectionLock:i,onDrag:o}=this.getProps();if(!n&&!this.openDragLock)return;let{offset:s}=e;if(r&&null===this.currentDirection)return this.currentDirection=function(t,e=10){let n=null;return Math.abs(t.y)>e?n="y":Math.abs(t.x)>e&&(n="x"),n}(s),void(null!==this.currentDirection&&i&&i(this.currentDirection));this.updateAxis("x",e.point,s),this.updateAxis("y",e.point,s),this.visualElement.render(),o&&q.update(()=>o(t,e),!1,!0)},onSessionEnd:(t,e)=>{this.latestPointerEvent=t,this.latestPanInfo=e,this.stop(t,e),this.latestPointerEvent=null,this.latestPanInfo=null},resumeAnimation:()=>{let{dragSnapToOrigin:t}=this.getProps();(t||this.constraints)&&this.startAnimation({x:0,y:0})}},{transformPagePoint:this.visualElement.getTransformPagePoint(),dragSnapToOrigin:o,distanceThreshold:n,contextWindow:Ge(this.visualElement),element:this.visualElement.current})}stop(t,e){let n=t||this.latestPointerEvent,r=e||this.latestPanInfo,i=this.isDragging;if(this.cancel(),!i||!r||!n)return;let{velocity:o}=r;this.startAnimation(o);let{onDragEnd:s}=this.getProps();s&&q.postRender(()=>s(n,r))}cancel(){this.isDragging=!1;let{projection:t,animationState:e}=this.visualElement;t&&(t.isAnimationBlocked=!1),this.endPanSession();let{dragPropagation:n}=this.getProps();!n&&this.openDragLock&&(this.openDragLock(),this.openDragLock=null),e&&e.setActive("whileDrag",!1)}endPanSession(){this.panSession&&this.panSession.end(),this.panSession=void 0}updateAxis(t,e,n){let{drag:r}=this.getProps();if(!n||!ln(t,r,this.currentDirection))return;let i=this.getAxisMotionValue(t),o=this.originPoint[t]+n[t];this.constraints&&this.constraints[t]&&(o=function(t,{min:e,max:n},r){return void 0!==e&&t<e?t=r?Q(e,t,r.min):Math.max(t,e):void 0!==n&&t>n&&(t=r?Q(n,t,r.max):Math.min(t,n)),t}(o,this.constraints[t],this.elastic[t])),i.set(o)}resolveConstraints(){let{dragConstraints:t,dragElastic:e}=this.getProps(),n=this.visualElement.projection&&!this.visualElement.projection.layout?this.visualElement.projection.measure(!1):this.visualElement.projection?.layout,r=this.constraints;t&&Ve(t)?this.constraints||(this.constraints=this.resolveRefConstraints()):this.constraints=!(!t||!n)&&function(t,{top:e,left:n,bottom:r,right:i}){return{x:Qe(t.x,n,i),y:Qe(t.y,e,r)}}(n.layoutBox,t),this.elastic=function(t=en){return!1===t?t=0:!0===t&&(t=en),{x:nn(t,"left","right"),y:nn(t,"top","bottom")}}(e),r!==this.constraints&&!Ve(t)&&n&&this.constraints&&!this.hasMutatedConstraints&&_(t=>{!1!==this.constraints&&this.getAxisMotionValue(t)&&(this.constraints[t]=function(t,e){let n={};return void 0!==e.min&&(n.min=e.min-t.min),void 0!==e.max&&(n.max=e.max-t.min),n}(n.layoutBox[t],this.constraints[t]))})}resolveRefConstraints(){let{dragConstraints:t,onMeasureDragConstraints:e}=this.getProps();if(!t||!Ve(t))return!1;let n=t.current;H(null!==n,"If `dragConstraints` is set as a React ref, that ref must be passed to another component's `ref` prop.","drag-constraints-ref");let{projection:r}=this.visualElement;if(!r||!r.layout)return!1;r.root&&(r.root.scroll=void 0,r.root.updateScroll());let i=P(n,r.root,this.visualElement.getTransformPagePoint()),o=function(t,e){return{x:tn(t.x,e.x),y:tn(t.y,e.y)}}(r.layout.layoutBox,i);if(e){let t=e(E(o));this.hasMutatedConstraints=!!t,t&&(o=x(t))}return o}startAnimation(t){let{drag:e,dragMomentum:n,dragElastic:r,dragTransition:i,dragSnapToOrigin:o,onDragTransitionEnd:s}=this.getProps(),a=this.constraints||{},l=_(s=>{if(!ln(s,e,this.currentDirection))return;let l=a&&a[s]||{};(!0===o||o===s)&&(l={min:0,max:0});let u=r?200:1e6,c=r?40:1e7,d={type:"inertia",velocity:n?t[s]:0,bounceStiffness:u,bounceDamping:c,timeConstant:750,restDelta:1,restSpeed:10,...i,...l};return this.startAxisValueAnimation(s,d)});return Promise.all(l).then(s)}startAxisValueAnimation(t,e){let n=this.getAxisMotionValue(t);return it(this.visualElement,t),n.start(tt(t,n,0,e,this.visualElement,!1))}stopAnimation(){_(t=>this.getAxisMotionValue(t).stop())}getAxisMotionValue(t){let e=`_drag${t.toUpperCase()}`;return this.visualElement.getProps()[e]||this.visualElement.getValue(t,this.visualElement.latestValues[t]??0)}snapToCursor(t){_(e=>{let{drag:n}=this.getProps();if(!ln(e,n,this.currentDirection))return;let{projection:r}=this.visualElement,i=this.getAxisMotionValue(e);if(r&&r.layout){let{min:n,max:o}=r.layout.layoutBox[e],s=i.get()||0;i.set(t[e]-Q(n,o,.5)+s)}})}scalePositionWithinConstraints(){if(!this.visualElement.current)return;let{drag:t,dragConstraints:e}=this.getProps(),{projection:n}=this.visualElement;if(!Ve(e)||!n||!this.constraints)return;this.stopAnimation();let r={x:0,y:0};_(t=>{let e=this.getAxisMotionValue(t);if(e&&!1!==this.constraints){let n=e.get();r[t]=function(t,e){let n=.5,r=V(t),i=V(e);return i>r?n=$(e.min,e.max-r,t.min):r>i&&(n=$(t.min,t.max-i,e.min)),B(0,1,n)}({min:n,max:n},this.constraints[t])}});let{transformTemplate:i}=this.visualElement.getProps();this.visualElement.current.style.transform=i?i({},""):"none",n.root&&n.root.updateScroll(),n.updateLayout(),this.constraints=!1,this.resolveConstraints(),_(e=>{if(!ln(e,t,null))return;let n=this.getAxisMotionValue(e),{min:i,max:o}=this.constraints[e];n.set(Q(i,o,r[e]))}),this.visualElement.render()}addListeners(){if(!this.visualElement.current)return;on.set(this.visualElement,this);let t,e=this.visualElement.current,n=Ue(e,"pointerdown",t=>{let{drag:n,dragListener:r=!0}=this.getProps(),i=t.target,o=i!==e&&s(i);n&&r&&!o&&this.start(t)}),r=()=>{let{dragConstraints:n}=this.getProps();Ve(n)&&n.current&&(this.constraints=this.resolveRefConstraints(),t||(t=function(t,e,n){let r=l(t,an(n)),i=l(e,an(n));return()=>{r(),i()}}(e,n.current,()=>this.scalePositionWithinConstraints())))},{projection:i}=this.visualElement,o=i.addEventListener("measure",r);i&&!i.layout&&(i.root&&i.root.updateScroll(),i.updateLayout()),q.read(r);let a=O(window,"resize",()=>this.scalePositionWithinConstraints()),u=i.addEventListener("didUpdate",({delta:t,hasLayoutChanged:e})=>{this.isDragging&&e&&(_(e=>{let n=this.getAxisMotionValue(e);n&&(this.originPoint[e]+=t[e].translate,n.set(n.get()+t[e].translate))}),this.visualElement.render())});return()=>{a(),n(),o(),u&&u(),t&&t()}}getProps(){let t=this.visualElement.getProps(),{drag:e=!1,dragDirectionLock:n=!1,dragPropagation:r=!1,dragConstraints:i=!1,dragElastic:o=en,dragMomentum:s=!0}=t;return{...t,drag:e,dragDirectionLock:n,dragPropagation:r,dragConstraints:i,dragElastic:o,dragMomentum:s}}};function an(t){let e=!0;return()=>{e?e=!1:t()}}function ln(t,e,n){return!(!0!==e&&e!==t||null!==n&&n!==t)}var un=t=>(e,n)=>{t&&q.update(()=>t(e,n),!1,!0)};import{jsx as cn}from"react/jsx-runtime";import{useContext as dn,Component as hn}from"react";var mn=!1,pn=class extends hn{componentDidMount(){let{visualElement:t,layoutGroup:e,switchLayoutGroup:n,layoutId:r}=this.props,{projection:i}=t;i&&(e.group&&e.group.add(i),n&&n.register&&r&&n.register(i),mn&&i.root.didUpdate(),i.addEventListener("animationComplete",()=>{this.safeToRemove()}),i.setOptions({...i.options,layoutDependency:this.props.layoutDependency,onExitComplete:()=>this.safeToRemove()})),W.hasEverUpdated=!0}getSnapshotBeforeUpdate(t){let{layoutDependency:e,visualElement:n,drag:r,isPresent:i}=this.props,{projection:o}=n;return o&&(o.isPresent=i,t.layoutDependency!==e&&o.setOptions({...o.options,layoutDependency:e}),mn=!0,r||t.layoutDependency!==e||void 0===e||t.isPresent!==i?o.willUpdate():this.safeToRemove(),t.isPresent!==i&&(i?o.promote():o.relegate()||q.postRender(()=>{let t=o.getStack();(!t||!t.members.length)&&this.safeToRemove()}))),null}componentDidUpdate(){let{visualElement:t,layoutAnchor:e}=this.props,{projection:r}=t;r&&(r.options.layoutAnchor=e,r.root.didUpdate(),n.postRender(()=>{!r.currentAnimation&&r.isLead()&&this.safeToRemove()}))}componentWillUnmount(){let{visualElement:t,layoutGroup:e,switchLayoutGroup:n}=this.props,{projection:r}=t;mn=!0,r&&(r.scheduleCheckAfterUnmount(),e&&e.group&&e.group.remove(r),n&&n.deregister&&n.deregister(r))}safeToRemove(){let{safeToRemove:t}=this.props;t&&t()}render(){return null}};function fn(t){let[e,n]=function(t=!0){let e=_t(It);if(null===e)return[!0,null];let{isPresent:n,onExitComplete:r,register:i}=e,o=Ot();Rt(()=>{if(t)return i(o)},[t]);let s=Wt(()=>t&&r&&r(o),[o,r,t]);return!n&&r?[!1,s]:[!0]}(),r=dn(Mt);return cn(pn,{...t,layoutGroup:r,switchLayoutGroup:dn(je),isPresent:e,safeToRemove:n})}var gn={pan:{Feature:class extends w{constructor(){super(...arguments),this.removePointerDownListener=G}onPointerDown(t){this.session=new $e(t,this.createPanHandlers(),{transformPagePoint:this.node.getTransformPagePoint(),contextWindow:Ge(this.node)})}createPanHandlers(){let{onPanSessionStart:t,onPanStart:e,onPan:n,onPanEnd:r}=this.node.getProps();return{onSessionStart:un(t),onStart:un(e),onMove:un(n),onEnd:(t,e)=>{delete this.session,r&&q.postRender(()=>r(t,e))}}}mount(){this.removePointerDownListener=Ue(this.node.current,"pointerdown",t=>this.onPointerDown(t))}update(){this.session&&this.session.updateHandlers(this.createPanHandlers())}unmount(){this.removePointerDownListener(),this.session&&this.session.end()}}},drag:{Feature:class extends w{constructor(t){super(t),this.removeGroupControls=G,this.removeListeners=G,this.controls=new sn(t)}mount(){let{dragControls:t}=this.node.getProps();t&&(this.removeGroupControls=t.subscribe(this.controls)),this.removeListeners=this.controls.addListeners()||G}update(){let{dragControls:t}=this.node.getProps(),{dragControls:e}=this.node.prevProps||{};t!==e&&(this.removeGroupControls(),t&&(this.removeGroupControls=t.subscribe(this.controls)))}unmount(){this.removeGroupControls(),this.removeListeners(),this.controls.isDragging||this.controls.endPanSession()}},ProjectionNode:N,MeasureLayout:fn}};function vn(t,e,n){let{props:r}=t;t.animationState&&r.whileHover&&t.animationState.setActive("whileHover","Start"===n);let i=r["onHover"+n];i&&q.postRender(()=>i(e,He(e)))}function yn(t,e,n){let{props:r}=t;if(t.current instanceof HTMLButtonElement&&t.current.disabled)return;t.animationState&&r.whileTap&&t.animationState.setActive("whileTap","Start"===n);let i=r["onTap"+("End"===n?"":n)];i&&q.postRender(()=>i(e,He(e)))}var wn=new WeakMap,xn=new WeakMap,En=t=>{let e=wn.get(t.target);e&&e(t)},Pn=t=>{t.forEach(En)};function Sn(t,e,n){let r=function({root:t,...e}){let n=t||document;xn.has(n)||xn.set(n,{});let r=xn.get(n),i=JSON.stringify(e);return r[i]||(r[i]=new IntersectionObserver(Pn,{root:t,...e})),r[i]}(e);return wn.set(t,n),r.observe(t),()=>{wn.delete(t),r.unobserve(t)}}var bn={some:0,all:1};var Cn={inView:{Feature:class extends w{constructor(){super(...arguments),this.hasEnteredView=!1,this.isInView=!1}startObserver(){this.stopObserver?.();let{viewport:t={}}=this.node.getProps(),{root:e,margin:n,amount:r="some",once:i}=t,o={root:e?e.current:void 0,rootMargin:n,threshold:"number"==typeof r?r:bn[r]};this.stopObserver=Sn(this.node.current,o,t=>{let{isIntersecting:e}=t;if(this.isInView===e||(this.isInView=e,i&&!e&&this.hasEnteredView))return;e&&(this.hasEnteredView=!0),this.node.animationState&&this.node.animationState.setActive("whileInView",e);let{onViewportEnter:n,onViewportLeave:r}=this.node.getProps(),o=e?n:r;o&&o(t)})}mount(){this.startObserver()}update(){if(typeof IntersectionObserver>"u")return;let{props:t,prevProps:e}=this.node;["amount","margin","root"].some(function({viewport:t={}},{viewport:e={}}={}){return n=>t[n]!==e[n]}(t,e))&&this.startObserver()}unmount(){this.stopObserver?.(),this.hasEnteredView=!1,this.isInView=!1}}},tap:{Feature:class extends w{mount(){let{current:t}=this.node;if(!t)return;let{globalTapTarget:e,propagate:n}=this.node.props;this.unmount=a(t,(t,e)=>(yn(this.node,e,"Start"),(t,{success:e})=>yn(this.node,t,e?"End":"Cancel")),{useGlobalTarget:e,stopPropagation:!1===n?.tap})}unmount(){}}},focus:{Feature:class extends w{constructor(){super(...arguments),this.isActive=!1}onFocus(){let t=!1;try{t=this.node.current.matches(":focus-visible")}catch{t=!0}!t||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!0),this.isActive=!0)}onBlur(){!this.isActive||!this.node.animationState||(this.node.animationState.setActive("whileFocus",!1),this.isActive=!1)}mount(){this.unmount=Y(O(this.node.current,"focus",()=>this.onFocus()),O(this.node.current,"blur",()=>this.onBlur()))}unmount(){}}},hover:{Feature:class extends w{mount(){let{current:t}=this.node;t&&(this.unmount=i(t,(t,e)=>(vn(this.node,e,"Start"),t=>vn(this.node,t,"End"))))}unmount(){}}}},Mn=function(t,e){if(typeof Proxy>"u")return Re;let n=new Map,r=(n,r)=>Re(n,r,t,e);return new Proxy((t,e)=>r(t,e),{get:(i,o)=>"create"===o?r:(n.has(o)||n.set(o,Re(o,void 0,t,e)),n.get(o))})}({...Fe,...Cn,...gn,...{layout:{ProjectionNode:N,MeasureLayout:fn}}},(t,e)=>e.isSVG??ge(t)?new D(e):new M(e,{allowProjection:t!==Ne}));import{useState as kn}from"react";var Tn=Mn;import{Fragment as An,jsx as Ln,jsxs as Dn}from"react/jsx-runtime";function In({items:t,defaultActiveId:e,activeId:n,edge:r="bottom",alignEnd:i=!1,onActiveChange:o,label:s="App navigation",moreLabel:a="More",backLabel:l="Back",className:u="",style:c}){let d=Et(),[h,m]=bt({kind:"collapsed"}),[v,y]=bt(e??t[0]?.id),[w,x]=bt(null),E=St(null),P=St(null),S=St(r),b="left"===r||"right"===r;Pt(()=>{let t=P.current;if(!t||!w)return;let e=t.getBoundingClientRect(),n=Math.max(8-e.left,Math.min(0,window.innerWidth-8-e.right)),r=Math.max(8-e.top,Math.min(0,window.innerHeight-8-e.bottom));t.style.left=`${w.x+n}px`,t.style.top=`${w.y+r}px`},[w]);let C=St(null),M=St(null),k=St(h),A=St("more"),D=St(!1),j=St(!1),V=St(!1),_=St(null),O=St(void 0),R=St(void 0),W=St(!1),N=St(!1),B=function(){!f.current&&g();let[t]=kn(p.current);return t}(),H=n??v,U="collapsed"!==h.kind,G=t.filter(t=>!1!==t.showInBar),Y=G.find(t=>"dismiss"===t.id),$=G.filter(t=>"dismiss"!==t.id),X=t.filter(t=>t.children?.length);function z(){clearTimeout(O.current),x(null)}function q(){z(),clearTimeout(R.current),W.current=!1}function K(t,e,n=!1){clearTimeout(O.current);let i=E.current,o=M.current;if(!i||!o||U||j.current||V.current&&!n)return;let s=i.getBoundingClientRect(),a=t.getBoundingClientRect(),l={label:e,x:"left"===r?s.width+8:"right"===r?-8:a.left-s.left+a.width/2,y:"top"===r?s.height+8:"bottom"===r?-8:a.top-s.top+a.height/2};clearTimeout(R.current);let u=()=>{W.current=!0,x(l)};n||W.current?u():O.current=setTimeout(u,400)}function Z(t,e,n){q(),U||(A.current=e,D.current=n),_.current="first",m(t)}function J(t=!0,e=A.current){q(),V.current=!0,_.current=t&&D.current?e:null,m({kind:"collapsed"})}function Q(){"group"===h.kind&&(_.current=h.id,m({kind:"main"}))}function tt(t,e){let n=!!t.children?.length,r=H===t.id||!!t.children?.some(t=>t.id===H),i=!e&&t.keepOpenOnSelect,s=(e,n=!1)=>Ln(Vn,{label:t.label,icon:n,reduced:!!B,children:e}),a=r&&t.activeIcon?t.activeIcon:t.icon,l=Dn(An,{children:[Ln("span",{className:"morphing-menu__icon","aria-hidden":"true",style:i?{display:"grid"}:void 0,children:i?s(a,!0):a}),!e&&Ln("span",{className:"morphing-menu__label",style:i?{display:"grid"}:void 0,children:i?s(t.label):t.label}),!e&&n&&Ln(jn,{})]}),u={className:e?"morphing-menu__shortcut":"morphing-menu__row","data-menu-item":t.id,"aria-label":t.label,"aria-current":r?n?"true":"page":void 0,"aria-expanded":n?"group"===h.kind&&h.id===t.id:void 0,"aria-controls":n?`${d}-group-${t.id}`:void 0,onClick:e=>n?Z({kind:"group",id:t.id},t.id,0===e.detail):function(t){y(t.id),o?.(t.id),U&&!t.keepOpenOnSelect?J(!0,"group"===h.kind?h.id:t.id):q(),t.onSelect?.()}(t),onPointerEnter:n=>{e&&"mouse"===n.pointerType&&K(n.currentTarget,t.label)},onPointerLeave:z,onPointerDown:q,onFocus:n=>{e&&n.currentTarget.matches(":focus-visible")&&K(n.currentTarget,t.label,!0)},onBlur:z};return t.href&&!n?Ln("a",{href:t.href,...u,children:l},t.id):Ln(Tn.button,{layout:!!e&&"position",transition:B?{duration:0}:T,type:"button",...u,children:l},t.id)}Pt(()=>{let t=E.current,e=C.current,n=M.current;if(!t||!e||!n)return;let i=S.current!==r;S.current=r;let o=k.current;k.current=h;let s=[...t.querySelectorAll(".morphing-menu__panel")],a=s.find(t=>"false"===t.getAttribute("aria-hidden")),l=[],u=!1,c=t=>(l.push(t),t),d=()=>({width:a?.offsetWidth??n.offsetWidth,height:a?.offsetHeight??n.offsetHeight}),p=i||o.kind!==h.kind||"group"===o.kind&&"group"===h.kind&&o.id!==h.id,f="collapsed"===o.kind!=("collapsed"===h.kind),g=B||!p;if(g)(()=>{let t=d();e.style.width=`${t.width}px`,e.style.height=`${t.height}px`})();else if(f&&!U)c(F(e,d(),I));else if(f){let t=Math.min(n.offsetWidth,n.offsetWidth>250?280:200);c(F(e,{width:t,height:n.offsetHeight>52?32:28},L)).finished.then(()=>{u||c(F(e,d(),{...T,bounce:U?.24:.15}))}).catch(()=>{})}else c(F(e,d(),{...T,duration:.25,bounce:.1}));c(F(n,{opacity:U?0:1,scale:U&&!B?.8:1,filter:U&&!B?"blur(8px)":"blur(0px)"},{duration:g?0:U?.15:.22,delay:g||U?0:.08,...U?{}:{ease:"easeOut"}}));for(let t of s){let e=t===a;for(let[n,r]of[...t.querySelectorAll(".morphing-menu__row")].entries())e&&f&&!g&&Object.assign(r.style,{opacity:"0",transform:"translateY(48px)",filter:"blur(4px)"}),c(F(r,{opacity:e?1:0,y:e||B?0:16,filter:e||B?"blur(0px)":"blur(2px)"},{...T,duration:g?0:e?f?.4:.25:.12,bounce:f?.3:0,delay:!g&&e?(f?.2:0)+.02*n:0}))}let v=[...(U?a:n)?.querySelectorAll("[data-menu-item]")??[]],y=_.current;_.current=null,y&&(j.current=!0,(("first"===y?v[0]:v.find(t=>t.dataset.menuItem===y))??(U?void 0:v.find(t=>"more"===t.dataset.menuItem)))?.focus({preventScroll:!0}),j.current=!1);let w=()=>m(t=>({...t}));return window.addEventListener("resize",w),()=>{u=!0,l.forEach(t=>t.stop()),window.removeEventListener("resize",w)}},[h,B,r,t.length,G.length]),xt(()=>{if(!U)return;let t=new AbortController;return st(E.current,"pointerdown",t=>{t.target instanceof Node&&!t.composedPath().includes(E.current)&&J(!1)},{signal:t.signal}),()=>t.abort()},[U]),xt(()=>()=>{clearTimeout(O.current),clearTimeout(R.current)},[]);let et=Dn("button",{type:"button",className:"morphing-menu__row","data-menu-item":"close",onClick:()=>J(!0,"more"),children:[Ln(ft,{"aria-hidden":"true"}),Ln("span",{className:"morphing-menu__label",children:"Close drawer"})]});return Dn("nav",{ref:E,"aria-label":s,className:["morphing-menu",u].filter(Boolean).join(" "),"data-view":h.kind,"data-edge":r,"data-vertical":b,"data-align-end":i,style:{"--mm-count":G.length+1,...c},onPointerMove:t=>{if(!V.current||"mouse"!==t.pointerType||!t.movementX&&!t.movementY)return;V.current=!1;let e=t.target instanceof Element?t.target.closest(".morphing-menu__shortcut"):null;e&&K(e,e.getAttribute("aria-label")??"")},onKeyDown:t=>{"Escape"===t.key&&(V.current=!0,q(),U&&(t.preventDefault(),t.stopPropagation(),"group"===h.kind?Q():J()))},onBlur:t=>{t.relatedTarget instanceof Node&&t.currentTarget.contains(t.relatedTarget)||(U?t.relatedTarget instanceof Node&&J(!1):N.current||q())},children:[Dn("div",{ref:C,className:"morphing-menu__shell",children:[Dn("div",{ref:M,className:"morphing-menu__bar","aria-hidden":U,inert:U,onPointerEnter:t=>{"mouse"===t.pointerType&&(N.current=!0,clearTimeout(R.current))},onPointerLeave:t=>{"mouse"===t.pointerType&&(N.current=!1,z(),clearTimeout(R.current),R.current=setTimeout(()=>{W.current=!1},300))},children:[$.map(t=>tt(t,!0)),Ln(Tn.button,{layout:"position",transition:B?{duration:0}:T,type:"button",className:"morphing-menu__shortcut","data-menu-item":"more","aria-label":a,"aria-expanded":U,"aria-controls":`${d}-main`,onClick:t=>Z({kind:"main"},"more",0===t.detail),onPointerEnter:t=>{"mouse"===t.pointerType&&K(t.currentTarget,a)},onPointerLeave:z,onPointerDown:q,onBlur:z,onFocus:t=>{t.currentTarget.matches(":focus-visible")&&K(t.currentTarget,a,!0)},children:Ln(mt,{"aria-hidden":"true"})}),Y&&tt(Y,!0)]}),Dn("div",{id:`${d}-main`,className:"morphing-menu__panel","aria-hidden":"main"!==h.kind,inert:"main"!==h.kind,children:[[...t].sort((t,e)=>(t.expandedOrder??0)-(e.expandedOrder??0)).map(t=>tt(t,!1)),et]}),X.map(t=>{let e="group"===h.kind&&h.id===t.id;return Dn("div",{id:`${d}-group-${t.id}`,className:"morphing-menu__panel","aria-label":t.label,"aria-hidden":!e,inert:!e,children:[Dn("button",{type:"button",className:"morphing-menu__row","data-menu-item":"back",onClick:Q,children:[Ln(vt,{"aria-hidden":"true"}),Ln("span",{children:l})]}),t.children.map(t=>tt(t,!1)),et]},t.id)})]}),w&&!U&&Ln("div",{ref:P,className:"morphing-menu__tooltip","aria-hidden":"true",style:{left:w.x,top:w.y},children:w.label})]})}function jn(){return Ln(wt,{className:"morphing-menu__chevron","aria-hidden":"true"})}function Vn({label:t,icon:e,reduced:n,children:r}){let i=St(null),o=St(null);return Pt(()=>{let r=i.current,s=o.current;if(o.current={label:t,node:r.cloneNode(!0)},!s||s.label===t||n)return;let a=s.node;a.setAttribute("aria-hidden","true"),r.parentElement.append(a);let l={duration:200,easing:"cubic-bezier(.22,1,.36,1)"},u=r.animate([{opacity:0,transform:e?"scale(.7)":"translateY(4px)",filter:"blur(2px)"},{opacity:1,transform:"none",filter:"blur(0px)"}],l),c=a.animate([{opacity:1,transform:"none",filter:"blur(0px)"},{opacity:0,transform:e?"scale(.7)":"translateY(-4px)",filter:"blur(2px)"}],l);return c.finished.then(()=>a.remove()).catch(()=>{}),()=>{u.cancel(),c.cancel(),a.remove()}},[t,e,n]),Ln("span",{ref:i,style:{gridArea:"1 / 1",display:"flex",alignItems:"center"},children:r})}function _n(t){return"glyph"in t?dt("span",{style:{display:"contents"},dangerouslySetInnerHTML:{__html:ot[t.glyph]}}):dt("span",{className:"review-avatar"},t.user?.avatarUrl?dt("img",{src:t.user.avatarUrl,alt:"",referrerPolicy:"no-referrer"}):t.user?at(t.user.name):_n({glyph:"person"}))}var On=t=>dt(In,{...t,items:t.items.map(t=>({...t,icon:_n(t.icon)}))});function Rn(t,e){t.replaceChildren();let n=ut(t);return ct(()=>n.render(On(e))),{render:t=>n.render(On(t)),unmount:()=>n.unmount()}}export{Rn as mountToolbar};
|
package/dist/types.d.ts
CHANGED
|
@@ -115,6 +115,19 @@ export interface CommentsOptions {
|
|
|
115
115
|
drawerContainer?: HTMLElement;
|
|
116
116
|
/** Mounted content root for Frame. Defaults to a sole body content element; otherwise Floating only. */
|
|
117
117
|
pageRoot?: HTMLElement;
|
|
118
|
+
/**
|
|
119
|
+
* Roots, besides pageRoot, whose elements can be picked for comments and
|
|
120
|
+
* Design mode, such as a portal container. Defaults to all of body except
|
|
121
|
+
* komo, so body-level dialogs and popovers are pickable. Return [] to limit
|
|
122
|
+
* picking to pageRoot. Called on every pick; keep it cheap.
|
|
123
|
+
*/
|
|
124
|
+
pickRoots?: () => Element[];
|
|
125
|
+
/**
|
|
126
|
+
* Stop komo's own pointer, focus, wheel and typing events at its host
|
|
127
|
+
* element, so page shortcuts and open modals don't react to komo's UI.
|
|
128
|
+
* Defaults to true; set false if the host needs to observe them.
|
|
129
|
+
*/
|
|
130
|
+
isolateEvents?: boolean;
|
|
118
131
|
/** Resolve an anchor to a repository-relative source path. */
|
|
119
132
|
source?: (element: Element) => string | undefined;
|
|
120
133
|
/** Defaults to GitHub's source viewer. */
|
package/package.json
CHANGED
package/server/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
requireMember,
|
|
6
6
|
} from "./project-management";
|
|
7
7
|
import { setupPage } from "./setup-page";
|
|
8
|
+
import { authPrefix, iapIdentity, type IapIdentity } from "./iap";
|
|
8
9
|
import setupClient from "./setup-client.txt";
|
|
9
10
|
import {
|
|
10
11
|
dispatchWebhook,
|
|
@@ -338,7 +339,7 @@ async function oauthAuthorize(
|
|
|
338
339
|
authorize.search = new URLSearchParams({
|
|
339
340
|
client_id:
|
|
340
341
|
provider === "google" ? env.GOOGLE_CLIENT_ID : env.GITHUB_CLIENT_ID,
|
|
341
|
-
redirect_uri: `${url.origin}/auth/${provider}/callback`,
|
|
342
|
+
redirect_uri: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/auth/${provider}/callback`,
|
|
342
343
|
...(provider === "google"
|
|
343
344
|
? { response_type: "code", scope: "openid profile email" }
|
|
344
345
|
: {}),
|
|
@@ -413,7 +414,7 @@ async function oauthCallback(
|
|
|
413
414
|
: env.GITHUB_CLIENT_SECRET,
|
|
414
415
|
grant_type: "authorization_code",
|
|
415
416
|
code,
|
|
416
|
-
redirect_uri: `${url.origin}/auth/${provider}/callback`,
|
|
417
|
+
redirect_uri: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/auth/${provider}/callback`,
|
|
417
418
|
code_verifier: saved.verifier,
|
|
418
419
|
}),
|
|
419
420
|
}
|
|
@@ -495,16 +496,23 @@ async function oauthCallback(
|
|
|
495
496
|
)
|
|
496
497
|
: undefined;
|
|
497
498
|
const accessToken = await session(env, created?.project ?? saved.project, id);
|
|
499
|
+
return signedInPage(
|
|
500
|
+
{
|
|
501
|
+
type: created ? "komo:setup" : "branch-comments:auth",
|
|
502
|
+
...(created ? { ...created, code: setupCode } : {}),
|
|
503
|
+
token: accessToken,
|
|
504
|
+
user,
|
|
505
|
+
},
|
|
506
|
+
saved.origin
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
// The popup posts the session back to the site that opened it, then closes.
|
|
510
|
+
function signedInPage(message: Record<string, unknown>, origin: string) {
|
|
498
511
|
const nonce = crypto.randomUUID();
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
...(created ? { ...created, code: setupCode } : {}),
|
|
502
|
-
token: accessToken,
|
|
503
|
-
user,
|
|
504
|
-
}).replace(/</g, "\\u003c");
|
|
505
|
-
const target = JSON.stringify(saved.origin).replace(/</g, "\\u003c");
|
|
512
|
+
const body = JSON.stringify(message).replace(/</g, "\\u003c");
|
|
513
|
+
const target = JSON.stringify(origin).replace(/</g, "\\u003c");
|
|
506
514
|
return new Response(
|
|
507
|
-
`<!doctype html><meta charset="utf-8"><title>Signed in</title><p>You’re signed in. You can close this window and return to your comments.</p><script nonce="${nonce}">if(window.opener){window.opener.postMessage(${
|
|
515
|
+
`<!doctype html><meta charset="utf-8"><title>Signed in</title><p>You’re signed in. You can close this window and return to your comments.</p><script nonce="${nonce}">if(window.opener){window.opener.postMessage(${body},${target});window.close();}</script>`,
|
|
508
516
|
{
|
|
509
517
|
headers: {
|
|
510
518
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -516,6 +524,44 @@ async function oauthCallback(
|
|
|
516
524
|
}
|
|
517
525
|
);
|
|
518
526
|
}
|
|
527
|
+
// Records the IAP-verified Google account as a verified reviewer.
|
|
528
|
+
async function iapUser(env: Env, signedIn: IapIdentity) {
|
|
529
|
+
await env.DB.prepare(
|
|
530
|
+
"INSERT INTO users(id,name,verified) VALUES(?,?,1) ON CONFLICT(id) DO NOTHING"
|
|
531
|
+
)
|
|
532
|
+
.bind(signedIn.id, signedIn.email.split("@")[0].slice(0, 60))
|
|
533
|
+
.run();
|
|
534
|
+
await env.DB.prepare("UPDATE users SET email=?,verified=1 WHERE id=?")
|
|
535
|
+
.bind(signedIn.email, signedIn.id)
|
|
536
|
+
.run();
|
|
537
|
+
return identity(
|
|
538
|
+
(await env.DB.prepare("SELECT * FROM users WHERE id=?")
|
|
539
|
+
.bind(signedIn.id)
|
|
540
|
+
.first<UserRow>())!
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
// Behind IAP the authorize step is the whole sign-in: IAP has already
|
|
544
|
+
// authenticated the browser, so there is no Google redirect or callback.
|
|
545
|
+
async function iapSignIn(request: Request, env: Env) {
|
|
546
|
+
const url = new URL(request.url);
|
|
547
|
+
const state = string(url.searchParams.get("state"), 200, "state");
|
|
548
|
+
const signedIn = await iapIdentity(request, env.KOMO_IAP_AUDIENCE!);
|
|
549
|
+
const saved = await env.DB.prepare(
|
|
550
|
+
"DELETE FROM oauth_states WHERE state_hash=? AND expires_at>? AND provider=? RETURNING project,origin"
|
|
551
|
+
)
|
|
552
|
+
.bind(await hash(state), Date.now(), "google")
|
|
553
|
+
.first<{ project: string; origin: string }>();
|
|
554
|
+
check(saved, 400, "Sign-in expired. Return to the site and try again.");
|
|
555
|
+
const user = await iapUser(env, signedIn);
|
|
556
|
+
return signedInPage(
|
|
557
|
+
{
|
|
558
|
+
type: "branch-comments:auth",
|
|
559
|
+
token: await session(env, saved.project, user.id),
|
|
560
|
+
user,
|
|
561
|
+
},
|
|
562
|
+
saved.origin
|
|
563
|
+
);
|
|
564
|
+
}
|
|
519
565
|
async function route(
|
|
520
566
|
request: Request,
|
|
521
567
|
env: Env,
|
|
@@ -640,7 +686,7 @@ async function route(
|
|
|
640
686
|
)
|
|
641
687
|
.bind(id, await hash(secret), JSON.stringify(config), Date.now() + 600000)
|
|
642
688
|
.run();
|
|
643
|
-
return json({ id, secret, url: `${url.origin}/setup?code=${id}` });
|
|
689
|
+
return json({ id, secret, url: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}/setup?code=${id}` });
|
|
644
690
|
}
|
|
645
691
|
if (
|
|
646
692
|
url.pathname === "/setup/poll" &&
|
|
@@ -668,7 +714,7 @@ async function route(
|
|
|
668
714
|
? {
|
|
669
715
|
project: row.project,
|
|
670
716
|
repo: JSON.parse(row.config).repo,
|
|
671
|
-
endpoint: url.origin
|
|
717
|
+
endpoint: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}`,
|
|
672
718
|
}
|
|
673
719
|
: { pending: true }
|
|
674
720
|
);
|
|
@@ -676,6 +722,33 @@ async function route(
|
|
|
676
722
|
const oauthPath = url.pathname.match(
|
|
677
723
|
/^\/auth\/(github|google)\/(authorize|callback)$/
|
|
678
724
|
);
|
|
725
|
+
if (env.KOMO_IAP_AUDIENCE) {
|
|
726
|
+
// IAP sign-in replaces OAuth. Its handlers live only under KOMO_AUTH_PREFIX,
|
|
727
|
+
// the path the load balancer puts behind IAP.
|
|
728
|
+
const prefix = authPrefix(env.KOMO_AUTH_PREFIX);
|
|
729
|
+
const signInPath =
|
|
730
|
+
!prefix || url.pathname.startsWith(`${prefix}/auth/`)
|
|
731
|
+
? url.pathname.slice(prefix.length)
|
|
732
|
+
: "";
|
|
733
|
+
if (signInPath === "/auth/google/authorize") return iapSignIn(request, env);
|
|
734
|
+
// For agents: an IAP-authenticated caller exchanges its identity for a
|
|
735
|
+
// project session, the non-interactive counterpart of the popup.
|
|
736
|
+
if (signInPath === "/auth/iap/session" && request.method === "POST") {
|
|
737
|
+
const signedIn = await iapIdentity(request, env.KOMO_IAP_AUDIENCE);
|
|
738
|
+
const target = string(url.searchParams.get("project"), 100, "project");
|
|
739
|
+
check(await projectConfig(env, target), 404, "Unknown comments project.");
|
|
740
|
+
const user = await iapUser(env, signedIn);
|
|
741
|
+
return json({ token: await session(env, target, user.id), user }, 201);
|
|
742
|
+
}
|
|
743
|
+
check(!oauthPath || oauthPath[1] === "google", 403, "Sign in with your Google account.");
|
|
744
|
+
check(
|
|
745
|
+
!oauthPath &&
|
|
746
|
+
url.pathname !== "/auth/iap/session" &&
|
|
747
|
+
!(prefix && url.pathname.startsWith(`${prefix}/`)),
|
|
748
|
+
404,
|
|
749
|
+
"Not found."
|
|
750
|
+
);
|
|
751
|
+
}
|
|
679
752
|
if (oauthPath)
|
|
680
753
|
return oauthPath[2] === "authorize"
|
|
681
754
|
? oauthAuthorize(request, env, oauthPath[1] as "github" | "google")
|
|
@@ -861,8 +934,12 @@ async function route(
|
|
|
861
934
|
return json({
|
|
862
935
|
repo: config.repo,
|
|
863
936
|
github: !!(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET),
|
|
864
|
-
google: !!(
|
|
937
|
+
google: !!(
|
|
938
|
+
env.KOMO_IAP_AUDIENCE ||
|
|
939
|
+
(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET)
|
|
940
|
+
),
|
|
865
941
|
guests:
|
|
942
|
+
!env.KOMO_IAP_AUDIENCE &&
|
|
866
943
|
config.allowGuests !== false &&
|
|
867
944
|
(!config.requireOwner || !!owner) &&
|
|
868
945
|
!(await privateAccess(env, project)),
|
|
@@ -931,6 +1008,7 @@ async function route(
|
|
|
931
1008
|
});
|
|
932
1009
|
}
|
|
933
1010
|
if (url.pathname === "/auth/guest" && request.method === "POST") {
|
|
1011
|
+
check(!env.KOMO_IAP_AUDIENCE, 403, "Guest comments are disabled.");
|
|
934
1012
|
check(
|
|
935
1013
|
!(await privateAccess(env, project)),
|
|
936
1014
|
403,
|
|
@@ -958,9 +1036,11 @@ async function route(
|
|
|
958
1036
|
) {
|
|
959
1037
|
const provider = url.pathname.includes("/google/") ? "google" : "github";
|
|
960
1038
|
check(
|
|
961
|
-
|
|
962
|
-
?
|
|
963
|
-
:
|
|
1039
|
+
env.KOMO_IAP_AUDIENCE
|
|
1040
|
+
? provider === "google"
|
|
1041
|
+
: provider === "google"
|
|
1042
|
+
? env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
|
|
1043
|
+
: env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET,
|
|
964
1044
|
503,
|
|
965
1045
|
"Sign-in is not configured."
|
|
966
1046
|
);
|
|
@@ -984,7 +1064,8 @@ async function route(
|
|
|
984
1064
|
)
|
|
985
1065
|
.run();
|
|
986
1066
|
return json({
|
|
987
|
-
|
|
1067
|
+
// KOMO_PUBLIC_PATH covers apps served under a path behind a proxy that strips it.
|
|
1068
|
+
url: `${url.origin}${env.KOMO_PUBLIC_PATH ?? ""}${env.KOMO_IAP_AUDIENCE ? authPrefix(env.KOMO_AUTH_PREFIX) : ""}/auth/${provider}/authorize?state=${encodeURIComponent(state)}`,
|
|
988
1069
|
});
|
|
989
1070
|
}
|
|
990
1071
|
if (url.pathname === "/me") {
|
|
@@ -1047,6 +1128,16 @@ async function route(
|
|
|
1047
1128
|
check(request.method === "GET", 405, "Method not allowed.");
|
|
1048
1129
|
return json({ user });
|
|
1049
1130
|
}
|
|
1131
|
+
// Behind IAP, comments are never readable or writable without a session
|
|
1132
|
+
// minted from an IAP-verified Google account.
|
|
1133
|
+
if (env.KOMO_IAP_AUDIENCE) {
|
|
1134
|
+
const reader = await authenticate(request, env, project);
|
|
1135
|
+
check(
|
|
1136
|
+
reader.verified && reader.id.startsWith("google:"),
|
|
1137
|
+
403,
|
|
1138
|
+
"Sign in with your Google account to see comments."
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1050
1141
|
if (await privateAccess(env, project)) {
|
|
1051
1142
|
await requireMember(
|
|
1052
1143
|
env,
|
package/server/secrets.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ interface Env {
|
|
|
8
8
|
) => Promise<void>;
|
|
9
9
|
KOMO_HOSTED?: string;
|
|
10
10
|
KOMO_PAUSED?: string;
|
|
11
|
+
/** Enables Google Cloud IAP sign-in: the IAP backend service audience. */
|
|
12
|
+
KOMO_IAP_AUDIENCE?: string;
|
|
13
|
+
/** Path prefix for IAP sign-in handlers, e.g. "/backoffice". */
|
|
14
|
+
KOMO_AUTH_PREFIX?: string;
|
|
15
|
+
/** Path the app is served under behind a proxy that strips it, e.g. "/komo". */
|
|
16
|
+
KOMO_PUBLIC_PATH?: string;
|
|
11
17
|
EDGE_LIMIT?: RateLimit;
|
|
12
18
|
KOMO_VERSION?: { id: string };
|
|
13
19
|
}
|