@obelism/content 0.18.0 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.global.js +30 -6
- package/dist/bridge.js +173 -18
- package/dist/bridge.mjs +173 -18
- package/package.json +1 -1
package/dist/bridge.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
!/*! Obelism Content bridge (@obelism/content), MIT. https://content.obelism.studio/docs/guides/visual-editor */function(){"use strict";var e;let t="data-cms-uid",r="data-cms-entry",
|
|
1
|
+
!/*! Obelism Content bridge (@obelism/content), MIT. https://content.obelism.studio/docs/guides/visual-editor */function(){"use strict";var e;let t="data-cms-uid",r="data-cms-entry",n="data-cms-product",o="cms_environment",a={clicked:"{rate}% clicked",title:"{clickers} of {viewers} visitors of its page clicked it in the last 30 days ({clicks} clicks)"},i=(e,t)=>e.replace(/\{(\w+)\}/g,(e,r)=>r in t?String(t[r]):e),s=`
|
|
2
2
|
:host { all: initial; }
|
|
3
3
|
.layer {
|
|
4
4
|
position: fixed; inset: 0; pointer-events: none;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
.badge[data-place='beside'] { transform: translateY(-50%); }
|
|
23
23
|
.badge[data-place='above'] { transform: translate(-100%, -100%); }
|
|
24
24
|
.badge:focus-visible { outline: 2px solid rgb(212 130 30); outline-offset: 2px; }
|
|
25
|
-
`,l=(e,t)=>e.height>=64?{place:"inside",top:Math.max(e.top,0)+8,left:e.right-8}:e.right+8+140<=t?{place:"beside",top:e.top+e.height/2,left:e.right+8}:{place:"above",top:e.top-4,left:e.right},d="obelism-content-bridge",c="#d58a1f",p=(e="")=>`${e}[${t}]{cursor:pointer;transition:outline-color .15s}${e}[${t}]:hover{outline:2px dashed ${c};outline-offset:2px}${e}[${t}][data-cms-selected]{outline:2px solid ${c};outline-offset:2px}`,g={features:[],selectedUid:null,prompting:!1,canUndo:!1,unsaved:!1,saving:!1,publishing:!1,selecting:!1,stats:{},statsVisible:!1},u=()=>{let e={update:new Set,saved:new Set,published:new Set,change:new Set};return{listeners:e,on:(t,r)=>(e[t].add(r),()=>{e[t].delete(r)}),emit(t,r){for(let
|
|
25
|
+
`,l=(e,t)=>e.height>=64?{place:"inside",top:Math.max(e.top,0)+8,left:e.right-8}:e.right+8+140<=t?{place:"beside",top:e.top+e.height/2,left:e.right+8}:{place:"above",top:e.top-4,left:e.right},d="obelism-content-bridge",c="#d58a1f",p=(e="")=>`${e}[${t}]{cursor:pointer;transition:outline-color .15s}${e}[${t}]:hover{outline:2px dashed ${c};outline-offset:2px}${e}[${t}][data-cms-selected]{outline:2px solid ${c};outline-offset:2px}`,g={features:[],selectedUid:null,prompting:!1,canUndo:!1,unsaved:!1,saving:!1,publishing:!1,selecting:!1,stats:{},statsVisible:!1},u=()=>{let e={update:new Set,saved:new Set,published:new Set,change:new Set};return{listeners:e,on:(t,r)=>(e[t].add(r),()=>{e[t].delete(r)}),emit(t,r){for(let n of e[t])try{n(r)}catch(e){console.error("[@obelism/content bridge]",e)}}}},h=()=>Promise.reject(Error("Save and publish in the visual editor itself")),m=()=>Promise.reject(Error("Only while signed in to edit on the site")),b={prompt:()=>Promise.reject(Error("Prompts only work inside the visual editor")),createPage:()=>Promise.reject(Error("New pages are made on the site itself")),createProductPage:()=>Promise.reject(Error("Product pages are made on the site itself")),view:m,applyJson:m,openForm:(e,t={})=>(queueMicrotask(()=>t.onError?.("Only while signed in to edit on the site")),{close:()=>{}}),pages:m,openPage:()=>{},undo:()=>{},save:h,publish:h,signOut:()=>{},select:()=>{},setSelecting:()=>{}},f=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0,v=e=>`[${t}="${e.replace(/["\\]/g,"\\$&")}"]`,y=(e,t)=>{let r=u();e.on("saved",()=>r.emit("saved",{})),e.on("published",()=>r.emit("published",{}));let n=!1;return{isEditing:e.isEditing,mode:e.isEditing?"editor":null,entryId:e.entryId,environment:t.get(o),product:null,on:(t,o)=>("update"!==t||n||(n=!0,e.on("update",e=>{let t=e?.entry;t&&r.emit("update",{entry:t,selectedUid:null})})),r.on(t,o)),getState:()=>g,...b,destroy:()=>{}}},x={open:"Edit with a prompt",placeholder:"Describe a change, e.g. “make the heading shorter”",send:"Apply",undo:"Undo",close:"Close",minimize:"Minimize, to see the page",expand:"Expand",scopeEntry:"Whole page",scopeBlock:"Selected block",clearBlock:"Edit the whole page instead",working:"Working on it…",save:"Save",publish:"Publish",saving:"Saving…",publishing:"Publishing…",saved:"Saved as a draft",published:"Published",unsaved:"Unsaved changes",signOut:"Exit editing",viewingDrafts:"Viewing drafts",selectHint:"Click a block to change only that block",newPage:"New page",newPageCancel:"Edit this page instead",newPagePlaceholder:"Describe the new page, e.g. “a pricing page for agencies”",path:"Path",pathPlaceholder:"/pricing",creating:"Writing the page…",created:"Opening the new page…",modes:"Edit with",modePrompt:"Prompt",form:"Form",formTitle:"The content form",formLoading:"Opening the form…",pages:"Pages",pagesSearch:"Find a page",pagesLoading:"Loading pages…",pagesEmpty:"No pages match",pageCurrent:"This page",pageDraft:"Draft",pageChanged:"Changed",addProductPage:"Add a product page",addingProductPage:"Making the product page…",addedProductPage:"Opening the product page…",productPageHint:"This product has no page yet. Add one for what the store doesn’t say."},w="obelism-content-prompt-side",k={page:'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',newPage:'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 18v-6"/>',block:'<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 8h10"/><path d="M7 12h6"/>',undo:'<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',close:'<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',minimize:'<path d="m6 9 6 6 6-6"/>',expand:'<path d="m18 15-6-6-6 6"/>',chevrons:'<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',search:'<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',check:'<path d="M20 6 9 17l-5-5"/>',mark:'<path d="M12 3v18M3 12h18"/><g fill="currentColor" stroke="none"><circle cx="7" cy="7" r="1.75"/><circle cx="17" cy="7" r="1.75"/><circle cx="7" cy="17" r="1.75"/><circle cx="17" cy="17" r="1.75"/></g>',send:'<path d="M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"/><path d="M6 12h16"/>'},E=`
|
|
26
26
|
:host { all: initial; }
|
|
27
27
|
* { box-sizing: border-box; font: inherit; color: inherit; margin: 0; }
|
|
28
28
|
.root {
|
|
@@ -43,10 +43,20 @@
|
|
|
43
43
|
--font-inter: 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
44
44
|
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
45
45
|
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
|
|
46
|
+
transition: translate .45s cubic-bezier(0.34, 1.3, 0.64, 1);
|
|
46
47
|
z-index: 2147483647; width: min(640px, calc(100vw - 32px)); color-scheme: light dark;
|
|
47
48
|
font: 400 14px/1.4 var(--font-inter); color: rgb(var(--theme-foreground-rgb));
|
|
48
49
|
-webkit-font-smoothing: antialiased;
|
|
49
50
|
}
|
|
51
|
+
/* Dragged to a side of the screen, it stays there, springing into place. */
|
|
52
|
+
.root[data-side='left'] { left: 16px; transform: none; }
|
|
53
|
+
.root[data-side='right'] { left: auto; right: 16px; transform: none; }
|
|
54
|
+
.root[data-side='left'] .bar { justify-content: flex-start; }
|
|
55
|
+
.root[data-side='right'] .bar { justify-content: flex-end; }
|
|
56
|
+
.root.dragging { transition: none; user-select: none; -webkit-user-select: none; }
|
|
57
|
+
.root.dragging, .root.dragging * { cursor: grabbing !important; }
|
|
58
|
+
.toolbar, .footer, .status { cursor: grab; touch-action: none; }
|
|
59
|
+
.pill { touch-action: none; }
|
|
50
60
|
@media (prefers-color-scheme: dark) {
|
|
51
61
|
.root {
|
|
52
62
|
--theme-foreground-rgb: var(--color-white-rgb);
|
|
@@ -82,7 +92,8 @@ button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-
|
|
|
82
92
|
}
|
|
83
93
|
button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translateY(-2px); }
|
|
84
94
|
.pill > span:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
|
-
.mark { color: rgb(var(--theme-accent-rgb));
|
|
95
|
+
.mark { color: rgb(var(--theme-accent-rgb)); }
|
|
96
|
+
.mark svg { width: 20px; height: 20px; }
|
|
86
97
|
.pill.compact { width: auto; height: 40px; padding: 0 18px; font-size: 14px; gap: 4px; }
|
|
87
98
|
|
|
88
99
|
/* Open: a floating card, the text on top and a row of pill buttons below. */
|
|
@@ -100,7 +111,7 @@ button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translat
|
|
|
100
111
|
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(.98); } }
|
|
101
112
|
@keyframes spin { to { transform: rotate(1turn); } }
|
|
102
113
|
@media (prefers-reduced-motion: reduce) {
|
|
103
|
-
.panel { animation: none; }
|
|
114
|
+
.root, .panel { animation: none; transition: none; }
|
|
104
115
|
.pill { transition: none; }
|
|
105
116
|
}
|
|
106
117
|
.status {
|
|
@@ -125,7 +136,20 @@ textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
|
125
136
|
display: block; width: 100%; height: min(560px, 60vh); border: none; border-radius: 12px;
|
|
126
137
|
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
127
138
|
}
|
|
128
|
-
|
|
139
|
+
/* Loading, it keeps the form's own height, the spinner in the middle,
|
|
140
|
+
whatever the form says meanwhile; the frame shows once the form does. */
|
|
141
|
+
.form.loading { position: relative; height: calc(min(560px, 60vh) + 12px); }
|
|
142
|
+
.form.loading iframe { position: absolute; visibility: hidden; }
|
|
143
|
+
.form-loader {
|
|
144
|
+
position: absolute; inset: 12px 12px 0; display: flex; align-items: center; justify-content: center;
|
|
145
|
+
gap: 10px; border-radius: 12px; color: rgb(var(--theme-foreground-muted-rgb));
|
|
146
|
+
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
147
|
+
}
|
|
148
|
+
.form-loader::before {
|
|
149
|
+
content: ''; width: 16px; height: 16px; flex: none; border-radius: 50%;
|
|
150
|
+
border: 2px solid rgb(var(--theme-border-rgb) / .3); border-top-color: rgb(var(--theme-accent-rgb));
|
|
151
|
+
animation: spin .7s linear infinite;
|
|
152
|
+
}
|
|
129
153
|
/* New page: where it goes, above what it is for. */
|
|
130
154
|
.path {
|
|
131
155
|
display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
|
|
@@ -233,4 +257,4 @@ button.chip:disabled { opacity: .5; }
|
|
|
233
257
|
.empty { padding: 8px 12px; color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
234
258
|
.empty.error { color: var(--theme-error); }
|
|
235
259
|
[hidden] { display: none !important; }
|
|
236
|
-
`,E=(e,t,r={},...o)=>{let n=e.createElement(t);for(let t of(Object.assign(n,r),o))n.appendChild("string"==typeof t?e.createTextNode(t):t);return n},C=(e,t)=>{let r=E(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${w[t]}</svg>`,r},P=(e,t,r={})=>{let o={...x,...r.labels},n=E(e,"div");n.setAttribute("data-obelism-content-prompt","");let a=n.attachShadow({mode:"open"}),i="site"===t.mode,s=E(e,"span",{textContent:o.open}),l=E(e,"button",{className:"pill",type:"button"},s,E(e,"span",{className:"mark",textContent:"⁜"})),d=E(e,"span",{className:"pill compact"},`${o.viewingDrafts} \xb7 `),c=E(e,"button",{className:"link",type:"button",textContent:o.signOut});d.appendChild(c);let p=E(e,"p",{className:"status"});p.setAttribute("role","status");let g=E(e,"textarea",{placeholder:o.placeholder,rows:1});g.setAttribute("aria-label",o.open);let u=E(e,"div",{className:"form"}),h=C(e,"page"),b=C(e,"block"),m=E(e,"span"),f=E(e,"button",{type:"button",title:o.clearBlock});f.setAttribute("aria-label",o.clearBlock),f.appendChild(C(e,"close"));let v=E(e,"span",{className:"chip scope"},h,b,m,f),y=E(e,"button",{className:"action chip",type:"button"},C(e,"undo"),o.undo),w=t=>{let r=E(e,"button",{type:"button",textContent:t});return r.setAttribute("aria-pressed","false"),r},P=w(o.modePrompt),A=w(o.form),S=E(e,"span",{className:"modes"},P,A);S.setAttribute("role","group"),S.setAttribute("aria-label",o.modes);let L=E(e,"button",{className:"action chip",type:"button"},C(e,"newPage"),o.newPage),N=E(e,"button",{className:"action chip product",type:"button"},C(e,"newPage"),o.addProductPage),U=E(e,"button",{type:"button",title:o.newPageCancel});U.setAttribute("aria-label",o.newPageCancel),U.appendChild(C(e,"close"));let I=E(e,"span",{className:"chip scope"},C(e,"newPage"),E(e,"span",{textContent:o.newPage}),U),$=E(e,"input",{type:"text",placeholder:o.pathPlaceholder,autocomplete:"off",spellcheck:!1}),M=E(e,"label",{className:"path"},E(e,"span",{textContent:o.path}),$),O=E(e,"button",{className:"close icon-button",type:"button",title:o.close},C(e,"close"));O.setAttribute("aria-label",o.close);let T=C(e,"minimize"),j=C(e,"expand"),z=E(e,"button",{className:"icon-button",type:"button"},T,j),R=E(e,"button",{className:"action primary icon-button",type:"submit"},E(e,"span",{className:"visually-hidden",textContent:o.send}),C(e,"send"));R.title=o.send;let D=E(e,"span",{className:"divider"}),F=E(e,"div",{className:"toolbar"},v,I,S,y,N,L,E(e,"span",{className:"spacer"}),z,O,D,R),q=E(e,"span",{textContent:o.pages}),B=E(e,"button",{className:"chip page-chip",type:"button",title:o.pages},C(e,"page"),q,C(e,"chevrons"));B.setAttribute("aria-haspopup","listbox"),B.setAttribute("aria-expanded","false"),B.setAttribute("aria-controls","obelism-pages");let H=E(e,"input",{type:"text",placeholder:o.pagesSearch,autocomplete:"off",spellcheck:!1});H.setAttribute("role","combobox"),H.setAttribute("aria-label",o.pagesSearch),H.setAttribute("aria-autocomplete","list"),H.setAttribute("aria-expanded","true"),H.setAttribute("aria-controls","obelism-pages");let V=E(e,"ul",{className:"options",id:"obelism-pages"});V.setAttribute("role","listbox"),V.setAttribute("aria-label",o.pages);let Y=E(e,"p",{className:"empty"}),W=E(e,"div",{className:"pages"},E(e,"label",{className:"search"},C(e,"search"),H),V,Y),_=E(e,"span",{className:"unsaved"});_.appendChild(E(e,"span",{className:"dot"})),_.appendChild(e.createTextNode(o.unsaved));let J=E(e,"button",{className:"link",type:"button",textContent:o.signOut}),X=E(e,"button",{className:"action chip",type:"button",textContent:o.save}),Z=E(e,"button",{className:"action chip",type:"button",textContent:o.publish}),K=E(e,"div",{className:"footer"},B,_,E(e,"span",{className:"spacer"}),J,X,Z),G=E(e,"form",{className:"panel"},p,M,g,u,F,W,K),Q=E(e,"div",{className:"root"},E(e,"div",{className:"bar"},l,d),G);a.append(E(e,"style",{textContent:k}),Q);let ee=!1,et=!1,er=!1,eo=null,en=!1,ea=null,ei=!1,es=!1,el=null,ed=null,ec=null,ep=0,eg=e=>({text:e instanceof Error?e.message:String(e),error:!0}),eu=e=>{let r=e.features.includes("prompt"),n=e.saving||e.publishing,a=e.prompting||n;Q.hidden=!r&&!i,l.hidden=ee||!r,d.hidden=r||!i,G.hidden=!ee||!r;let c=i&&e.features.includes("create"),x=i&&null===t.entryId,w=x&&c&&null!==t.product,k=x&&c&&!w;c||(er=!1),k&&(er=!0),(er||x||!i)&&(en=!1);let E=ee&&r&&en;E&&!ea?em():!E&&ea&&ef();let C=w&&!er;s.textContent=C?o.addProductPage:k?o.newPage:o.open,U.hidden=k,v.hidden=er||C,I.hidden=!er,N.hidden=!C,N.disabled=a,L.hidden=er||en||!c,L.disabled=a,et&&(es=!1),M.hidden=!er||es||et,$.disabled=a,g.hidden=en||es||et||C,u.hidden=!en||es||et,u.classList.toggle("loading",ei),F.hidden=es,W.hidden=!es,g.placeholder=er?o.newPagePlaceholder:o.placeholder,m.textContent=e.selectedUid?o.scopeBlock:o.scopeEntry,f.hidden=!e.selectedUid,h.hidden=!!e.selectedUid,b.hidden=!e.selectedUid,S.hidden=!i||er||x||!e.features.includes("form"),P.setAttribute("aria-pressed",String(!en)),A.setAttribute("aria-pressed",String(en)),S.querySelectorAll("button").forEach(e=>{e.disabled=a}),y.hidden=er||!e.canUndo,y.disabled=a,R.hidden=en||et||C,D.hidden=en||et||C;let O=et?o.expand:o.minimize;z.title=O,z.setAttribute("aria-label",O),z.setAttribute("aria-expanded",String(!et)),T.hidden=et,j.hidden=!et,R.disabled=a||""===g.value.trim()||er&&""===$.value.trim(),g.disabled=a,X.hidden=!e.features.includes("save")||x,X.disabled=a||!e.unsaved,X.textContent=e.saving?o.saving:o.save,Z.hidden=!e.features.includes("publish")||x,Z.disabled=a,Z.textContent=e.publishing?o.publishing:o.publish,J.hidden=!i,K.hidden=!i,_.hidden=!e.unsaved,B.hidden=!i,B.setAttribute("aria-expanded",String(es)),q.textContent=el?.find(({entryId:e})=>e===t.entryId)?.name??o.pages;let H=en&&ei&&!es,V=e.prompting||H,Y=e.prompting?er?o.creating:C?o.addingProductPage:o.working:H?o.formLoading:eo?.text??(C?o.productPageHint:!i||er||es||e.selectedUid?"":o.selectHint);p.textContent=Y,p.hidden=!Y||et,p.className=`status${V?" working":""}${!a&&!H&&eo?.error?" error":""}`},eh=()=>eu(t.getState()),eb=()=>{g.style.height="auto",g.style.height=`${g.scrollHeight}px`},em=()=>{ei=!0,u.classList.add("loading");let e=t.openForm(u,{title:o.formTitle,onReady:()=>{ea===e&&(ei=!1,eo=null,eh())},onError:t=>{ea===e&&(ei=!1,eo={text:t,error:!0},eh())}});ea=e},ef=()=>{ea?.close(),ea=null,ei=!1},ev=e=>{en!==e&&(en=e,eo=null,eh(),en||g.focus())},ey=async()=>{if(en)return;let e=g.value.trim();if(e&&!t.getState().prompting){eo=null;try{if(er){let r=$.value.trim();if(!r)return $.focus();await t.createPage(r,e),el=null,ed=null,eo={text:o.created,error:!1}}else{let{summary:r}=await t.prompt(e);eo={text:r,error:!1}}g.value="",eb()}catch(e){eo=eg(e)}eh(),g.focus()}},ex=()=>(ed??(ed=t.pages().then(e=>{el=e,ec=null,ek(),eh()},e=>{ed=null,ec=eg(e).text,ek()})),ed),ew=()=>{let e=H.value.trim().toLowerCase();return(el??[]).filter(t=>!e||t.name.toLowerCase().includes(e)||t.url.toLowerCase().includes(e)||`/${t.path}`.toLowerCase().includes(e))},ek=()=>{let r=ew();ep=Math.max(0,Math.min(ep,r.length-1)),V.textContent="",r.forEach((r,n)=>{let a=r.entryId===t.entryId,i=E(e,"li",{className:`option${n===ep?" active":""}`,id:`obelism-page-${n}`},E(e,"span",{className:"option-text"},E(e,"span",{textContent:r.name}),E(e,"span",{className:"option-path",textContent:r.url})));if(i.setAttribute("role","option"),i.setAttribute("aria-selected",String(n===ep)),"published"!==r.status&&i.appendChild(E(e,"span",{className:"badge",textContent:"draft"===r.status?o.pageDraft:o.pageChanged})),a){i.setAttribute("aria-current","page");let t=C(e,"check");t.removeAttribute("aria-hidden"),t.setAttribute("role","img"),t.setAttribute("aria-label",o.pageCurrent),i.appendChild(t)}i.addEventListener("click",()=>eC(r)),V.appendChild(i)});let n=V.children[ep];n?(H.setAttribute("aria-activedescendant",n.id),n.scrollIntoView?.({block:"nearest"})):H.removeAttribute("aria-activedescendant"),Y.textContent=(ec??(el?"":o.pagesLoading))||(0===r.length?o.pagesEmpty:""),Y.hidden=!Y.textContent,Y.className=`empty${ec?" error":""}`},eE=e=>{eo=null,(es=e)&&(H.value="",ep=Math.max(0,(el??[]).findIndex(({entryId:e})=>e===t.entryId)),ex(),ek()),eh(),(es?H:B).focus()},eC=e=>{if(e.entryId===t.entryId)return eE(!1);t.openPage(e)},eP=e=>{(ee=e)||(es=!1,et=!1),eh(),i&&t.setSelecting(ee&&!er),ee&&i&&ex()},eA=e=>{er=e,eo=null,t.setSelecting(ee&&!er),er&&t.select(null),eh(),(er?$:g).focus()},eS=async e=>{eo=null;try{await t[e](),eo={text:"save"===e?o.saved:o.published,error:!1}}catch(e){eo=eg(e)}eh()};l.addEventListener("click",()=>{eP(!0),en||(er?$:g).focus()}),O.addEventListener("click",()=>eP(!1)),z.addEventListener("click",()=>{et=!et,eh(),et||(en?z:g).focus()}),X.addEventListener("click",()=>void eS("save")),Z.addEventListener("click",()=>void eS("publish")),J.addEventListener("click",()=>t.signOut()),c.addEventListener("click",()=>t.signOut()),f.addEventListener("click",()=>t.select(null)),L.addEventListener("click",()=>eA(!0)),N.addEventListener("click",async()=>{eo=null;try{await t.createProductPage(),eo={text:o.addedProductPage,error:!1}}catch(e){eo=eg(e)}eh()}),U.addEventListener("click",()=>eA(!1)),P.addEventListener("click",()=>ev(!1)),A.addEventListener("click",()=>ev(!0)),B.addEventListener("click",()=>eE(!es)),$.addEventListener("input",eh),$.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&eP(!1):(e.preventDefault(),g.focus())}),H.addEventListener("input",()=>{ep=0,ek()}),H.addEventListener("keydown",e=>{let t=V.children.length;if("ArrowDown"===e.key||"ArrowUp"===e.key){if(e.preventDefault(),0===t)return;ep=(ep+("ArrowDown"===e.key?1:-1)+t)%t,ek()}else if("Enter"!==e.key||e.isComposing)"Escape"===e.key&&(e.preventDefault(),eE(!1));else{e.preventDefault();let t=ew()[ep];t&&eC(t)}}),y.addEventListener("click",()=>{eo=null,t.undo()}),G.addEventListener("submit",e=>{e.preventDefault(),ey()}),g.addEventListener("input",()=>{eb(),eh()}),g.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&eP(!1):(e.preventDefault(),ey())});let eL=t.on("change",eu);return eh(),e.body.appendChild(n),()=>{eL(),ef(),n.remove()}},A="https://content.obelism.studio",S=(e,t)=>JSON.stringify(e)===JSON.stringify(t),L=Symbol.for("@obelism/content/bridge"),N=y({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),U=({url:e,prompt:c=!0,pagePath:h,productPath:b}={})=>{let x;if("u"<typeof window)return N;let w=window;if(w[L])return w[L];if(w.ObelismBridge)x=y(w.ObelismBridge,new URLSearchParams(w.location.search));else{let y=(x=((e,r)=>{let o=new URLSearchParams(e.location.search),c=e.parent!==e&&o.has("cms_preview"),h=u(),b=[],y={...g,selecting:!0},x=e=>{y={...y,...e},h.emit("change",y)},w={isEditing:c,mode:c?"editor":null,entryId:o.get("cms_entry"),environment:o.get(n),product:null,on:h.on,getState:()=>y,...m,destroy:()=>{b.splice(0).forEach(e=>e())}};if(!c)return w;let k=e.document,E=(t,o={})=>{e.parent.postMessage({source:d,type:t,...o},r)},C=k.createElement("style");C.textContent=p(),k.head.appendChild(C),b.push(()=>C.remove());let P=(e,t)=>{if(k.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),y.selectedUid!==(e??null)&&x({selectedUid:e??null}),!e)return;let r=k.querySelector(v(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},A=e=>{let r=e.target,o=r?.closest?.(`[${t}]`);if(!o)return;e.preventDefault(),e.stopPropagation();let n=o.getAttribute(t);P(n,!1),E("select",{uid:n})};k.addEventListener("click",A,!0),b.push(()=>k.removeEventListener("click",A,!0));let S=()=>E("ready",{url:e.location.href}),L=new Map,N=0,U=e=>{let t=L.get(e);return t?(clearTimeout(t.timer),L.delete(e),0===L.size&&x({prompting:!1}),t):null};w.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!y.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let o=`prompt_${++N}`,n=void 0===t.uid?y.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{U(o)?.reject(Error("The editor did not answer in time"))},12e4);L.set(o,{resolve:e,reject:t,timer:a}),x({prompting:!0}),E("prompt",{id:o,prompt:r,uid:n})})},w.undo=()=>E("undo"),w.select=e=>{P(e,!1),E("select",{uid:e})};let I=null;b.push(()=>I?.destroy()),b.push(()=>{for(let e of L.keys())U(e)?.reject(Error("The bridge was destroyed"))});let $=t=>{if(t.origin!==r||t.source!==e.parent)return;let o=t.data;if(o&&"obelism-content-editor"===o.source){if("connect"===o.type)S();else if("update"===o.type&&o.entry)h.emit("update",{entry:o.entry,selectedUid:o.selectedUid??null}),e.requestAnimationFrame(()=>P(o.selectedUid,!1)),I?.refresh();else if("select"===o.type)P(o.uid,!0);else if("saved"===o.type||"published"===o.type)h.emit(o.type,{}),0===h.listeners.update.size&&e.location.reload();else if("features"===o.type&&Array.isArray(o.features))x({features:o.features.filter(e=>"string"==typeof e)});else if("stats"===o.type)x({stats:(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};let t={};for(let[r,o]of Object.entries(e)){let{clicks:e,clickers:n,viewers:a,rate:i}=o??{};f(e)&&f(n)&&f(a)&&f(i)&&(t[r]={clicks:e,clickers:n,viewers:a,rate:i})}return t})(o.stats),statsVisible:!1!==o.visible}),(I||Object.keys(y.stats).length)&&(I??(I=((e,{selector:t,onSelect:r,labels:o})=>{let n=e.document,d={...a,...o},c=n.createElement("div");c.setAttribute("data-obelism-stats","");let p=c.attachShadow({mode:"open"}),g=n.createElement("style");g.textContent=s;let u=n.createElement("div");u.className="layer",p.append(g,u),n.body.appendChild(c);let h={},b=!1,m=null,f=()=>{if(m=null,u.replaceChildren(),u.hidden=!b,!b)return;let o=e.innerHeight;for(let[a,s]of Object.entries(h)){let c=n.querySelector(t(a));if(!c)continue;let p=c.getBoundingClientRect();if(!p.width&&!p.height||p.bottom<0||p.top>o)continue;let g=n.createElement("button");g.type="button",g.className="badge",g.dataset.uid=a,g.textContent=i(d.clicked,{rate:Math.round(100*s.rate)}),g.title=i(d.title,{clickers:s.clickers,viewers:s.viewers,clicks:s.clicks});let{place:h,top:b,left:m}=l(p,e.innerWidth);g.dataset.place=h,g.style.top=`${b}px`,g.style.left=`${m}px`,g.addEventListener("click",()=>r(a)),u.appendChild(g)}},v=()=>{null===m&&b&&(m=e.requestAnimationFrame(f))};e.addEventListener("scroll",v,{capture:!0,passive:!0}),e.addEventListener("resize",v);let y=e.MutationObserver,x="function"==typeof y?new y(v):null;return x?.observe(n.body,{childList:!0,subtree:!0}),{update(t,r){h=t,b=r,null!==m&&(e.cancelAnimationFrame?.(m),m=null),f()},refresh:v,destroy(){null!==m&&e.cancelAnimationFrame?.(m),e.removeEventListener("scroll",v,{capture:!0}),e.removeEventListener("resize",v),x?.disconnect(),c.remove()}}})(e,{selector:v,onSelect:e=>w.select(e)})),I.update(y.stats,y.statsVisible));else if("promptState"===o.type)x({canUndo:!0===o.canUndo});else if("promptResult"===o.type&&"string"==typeof o.id){let e=U(o.id);if(!e)return;!0===o.ok?e.resolve({summary:"string"==typeof o.summary?o.summary:""}):e.reject(Error("string"==typeof o.error?o.error:"The prompt failed"))}}};return e.addEventListener("message",$),b.push(()=>e.removeEventListener("message",$)),"loading"===k.readyState?(k.addEventListener("DOMContentLoaded",S,{once:!0}),b.push(()=>k.removeEventListener("DOMContentLoaded",S))):S(),w})(w,new URL(e||A).origin)).isEditing||w.parent!==w?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(w.document);if(y&&(x=((e,n,{pagePath:a=e=>`/${e}`,productPath:i}={})=>{let s=e.document,l=u(),c=[],h=new Map,b=[],m=new Set,f=null,y=null,x=-1/0,w=new Map,k=g,E=e=>{k={...k,...e},l.emit("change",k)},C=()=>E({unsaved:[...h.values()].some(({content:e,saved:t})=>!S(e,t)),canUndo:b.length>0}),P=async(t,r)=>{let o=await e.fetch(`${n}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await o.json().catch(()=>({}));if(401===o.status)throw E({features:[]}),Error("You were signed out. Sign in again to edit.");if(!o.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${o.status}`);return a},A=()=>s.querySelector(`[${r}]`)?.getAttribute(r)??null,L=()=>{let e=s.querySelector(`[${o}]`),t=e?.getAttribute(o),r=e?.getAttribute("data-cms-product-handle");return t&&r?{id:t,handle:r}:null},N=e=>{let t=e?s.querySelector(v(e)):null;return t?.closest(`[${r}]`)?.getAttribute(r)??A()},U=()=>{if(!f)return;let e=f.space.locales.map(({code:e})=>e),t=s.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:f.space.defaultLocale},I=new Map,$=async e=>{let t=h.get(e);if(t)return t;let r=I.get(e);return r||(r=P(`entries/${encodeURIComponent(e)}`).then(t=>{let r=h.get(e)??{content:t.content,saved:t.content,revision:t.draftRevision};return h.set(e,r),r}).finally(()=>I.delete(e)),I.set(e,r)),r},M=e=>{let t=void 0===e?k.selectedUid:e,r=N(t);if(!r)throw Error("Nothing on this page is marked as an entry: add editable(entry)");return{uid:t,entryId:r}},O=e=>{s.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),k.selectedUid!==e&&E({selectedUid:e}),e&&s.querySelector(v(e))?.setAttribute("data-cms-selected","")},T=async t=>{if(0===l.listeners.update.size)return;let r=h.get(t)?.content,{entry:o}=await P(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:U()});l.emit("update",{entry:o,selectedUid:k.selectedUid}),e.requestAnimationFrame(()=>O(k.selectedUid))},j=e=>console.error("[@obelism/content bridge]",e),z=t=>{l.emit(t,{}),0===l.listeners.update.size&&e.location.reload()},R=async()=>{for(let[e,t]of h){if(S(t.content,t.saved))continue;let{draftRevision:r}=await P(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,m.add(e)}b.length=0,x=-1/0,C()},D={isEditing:!0,mode:"site",get entryId(){return A()},get product(){return L()},environment:null,on:l.on,getState:()=>k,async prompt(e,t={}){let r=e.trim();if(!r)throw Error("Say what to change");if(!k.features.includes("prompt"))throw Error("You may not edit content here");let{uid:o,entryId:n}=M(t.uid);E({prompting:!0});try{let e=await $(n),t=await P(`entries/${encodeURIComponent(n)}/prompt`,{prompt:r,content:e.content,locale:U(),uid:o});return b.push({entryId:n,content:e.content}),x=-1/0,e.content=t.content,C(),await T(n),{summary:t.summary}}finally{E({prompting:!1})}},async createPage(t,r){let o=r.trim();if(!o)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!k.features.includes("create"))throw Error("You may not add pages here");if(k.unsaved)throw Error("Save or undo your changes before adding a page");E({prompting:!0});try{let r=await P("entries",{path:t,prompt:o,locale:U(),from:A()}),n=a(r.path,U()??"");return e.location.assign(n),{summary:r.summary,entryId:r.entryId,path:r.path,url:n}}finally{E({prompting:!1})}},async createProductPage(t=L()??void 0){if(!t)throw Error("Nothing on this page is marked as a product: add editableProduct(product)");if(!k.features.includes("create"))throw Error("You may not add pages here");if(k.unsaved)throw Error("Save or undo your changes before adding a page");E({prompting:!0});try{let r=await P("product-pages",{product:{id:t.id,handle:t.handle}});return e.location.reload(),r}finally{E({prompting:!1})}},async view(e={}){let{uid:t,entryId:r}=M(e.uid),o=await $(r),{view:n}=await P(`entries/${encodeURIComponent(r)}/view`,{content:o.content,locale:U(),uid:t});return n},async applyJson(e,t={}){if(!k.features.includes("prompt"))throw Error("You may not edit content here");if(!e||"object"!=typeof e||Array.isArray(e))throw Error("Send the fields to change as an object");let{uid:r,entryId:o}=M(t.uid);E({prompting:!0});try{let t=await $(o),n=await P(`entries/${encodeURIComponent(o)}/apply`,{changes:e,content:t.content,locale:U(),uid:r});return S(n.content,t.content)||(b.push({entryId:o,content:t.content}),x=-1/0,t.content=n.content,C(),await T(o)),{changed:n.changed??[],...n.view&&{view:n.view}}}finally{E({prompting:!1})}},openForm(t,r={}){let{title:o="Content form",onReady:n,onError:a}=r;y?.();let i=!1,s=null,c=null,p=()=>{},g=e=>{i||a?.(e instanceof Error?e.message:String(e))},u=()=>{i||(i=!0,p(),s?.destroy(),y===u&&(y=null))};y=u;let m=()=>{if(i||!s)return;let e=N(k.selectedUid);if(!e)return;let t=h.get(e);if(!t)return void $(e).then(m,g);let r={entryId:e,content:t.content,locale:U(),selectedUid:k.selectedUid};c&&c.entryId===r.entryId&&c.content===r.content&&c.locale===r.locale&&c.selectedUid===r.selectedUid||(c=r,s.show(r))},f=(e,t)=>{let r=t??c?.entryId,o=void 0===r?void 0:h.get(r);if(i||void 0===r||!o||S(e,o.content))return;let n=Date.now();n-x>=1e3&&b.push({entryId:r,content:o.content}),x=n,o.content=e,c?.entryId===r&&(c={...c,content:e}),C(),clearTimeout(w.get(r)),w.set(r,setTimeout(()=>{w.delete(r),T(r).catch(j)},300))};return(async()=>{if(!k.features.includes("form"))throw Error("You may not edit content here");await $(M(void 0).entryId);let{url:r}=await P("form",{});i||(s=((e,t,r,o,n)=>{let a=null;try{a=new URL(r)}catch{}if(!a||!/^https?:$/.test(a.protocol))throw Error("Obelism Content sent no form to open");let i=a.origin,s=t.ownerDocument.createElement("iframe");s.title=o,s.src=r;let l=!1,c=null,p=()=>{l&&c&&s.contentWindow?.postMessage({source:d,type:"content",...c},i)},g=setTimeout(()=>{l||n.onError("The form did not open")},3e4),u=e=>{if(e.origin!==i||!s.contentWindow||e.source!==s.contentWindow)return;let t=e.data;if(t&&"obelism-content-form"===t.source)if("ready"===t.type)l=!0,clearTimeout(g),n.onReady(),p();else{let e;if("change"!==t.type||!(e=t.content)||"object"!=typeof e||Array.isArray(e))if("height"===t.type){let e=t.height;if("number"==typeof e&&Number.isFinite(e)){let t=Math.round(Math.min(Math.max(e,80),4e3));s.style.height=`min(${t}px, 60vh)`}}else"error"===t.type&&n.onError("string"==typeof t.message&&t.message?t.message:"The form failed");else n.onChange(t.content,"string"==typeof t.entryId?t.entryId:void 0)}};return e.addEventListener("message",u),t.appendChild(s),{show(e){c=e,p()},destroy(){clearTimeout(g),e.removeEventListener("message",u),s.remove()}}})(e,t,"string"==typeof r?r:"",o,{onReady:()=>n?.(),onChange:f,onError:e=>a?.(e)}),p=l.on("change",m),m())})().catch(g),{close:u}},async pages(){let{pages:e}=await P("pages"),t=[U(),f?.space.defaultLocale];return e.flatMap(e=>{let r,o=t.find(t=>void 0!==t&&void 0!==e.paths[t])??Object.keys(e.paths)[0],n=void 0===o?void 0:e.paths[o];return void 0===o||void 0===n?[]:[{entryId:e.entryId,name:e.name,contentType:e.contentType,status:e.status,path:n,url:e.product?(r=e.product.handle,i?i(r,o):(f?.productPath??"/products/{handle}").replaceAll("{handle}",encodeURIComponent(r))):a(n,o),product:e.product??null}]})},openPage(t){(!k.unsaved||e.confirm("Leave this page and lose the changes you have not saved?"))&&(h.clear(),b.length=0,C(),e.location.assign(t.url))},undo(){let e=b.pop(),t=e&&h.get(e.entryId);e&&t&&(x=-1/0,t.content=e.content,C(),T(e.entryId).catch(j))},async save(){if(!k.features.includes("save"))throw Error("You may not edit content here");E({saving:!0});try{await R()}finally{E({saving:!1})}z("saved")},async publish(){if(!k.features.includes("publish"))throw Error("You may not publish here");E({publishing:!0});try{await R();let e=A(),t=m.size>0?[...m]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await P(`entries/${encodeURIComponent(e)}/publish`,{}),m.delete(e)}finally{E({publishing:!1})}z("published")},signOut(){if(k.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;h.clear(),C();let t=e.location.pathname+e.location.search;e.location.href=`${n}/logout?return_to=${encodeURIComponent(t)}`},select(e){O(e)},setSelecting(e){e?s.documentElement.setAttribute("data-cms-selecting",""):s.documentElement.removeAttribute("data-cms-selecting"),E({selecting:e})},destroy(){y?.(),w.forEach(e=>clearTimeout(e)),w.clear(),c.splice(0).forEach(e=>e())}},F=s.createElement("style");F.textContent=p("html[data-cms-selecting] "),s.head.appendChild(F),c.push(()=>F.remove()),c.push(()=>s.documentElement.removeAttribute("data-cms-selecting"));let q=e=>{if(!k.selecting)return;let r=e.target,o=r?.closest?.(`[${t}]`);o&&(e.preventDefault(),e.stopPropagation(),O(o.getAttribute(t)))};s.addEventListener("click",q,!0),c.push(()=>s.removeEventListener("click",q,!0));let B=e=>{k.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",B),c.push(()=>e.removeEventListener("beforeunload",B)),P("session").then(e=>{f=e,E({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),D})(w,y,{pagePath:h,productPath:b})),x.isEditing&&c){let e=P(w.document,x,!0===c?{}:c),t=x.destroy;x.destroy=()=>{e(),t()}}}return w[L]=x,x};(e=window).ObelismBridge=U((e=>{let t=e?.dataset.url,r=t||A;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:U,mountPromptBox:P}}();
|
|
260
|
+
`,C=(e,t,r={},...n)=>{let o=e.createElement(t);for(let t of(Object.assign(o,r),n))o.appendChild("string"==typeof t?e.createTextNode(t):t);return o},P=(e,t)=>{let r=C(e,"span",{className:"icon"});return r.setAttribute("aria-hidden","true"),r.innerHTML=`<svg viewBox="0 0 24 24">${k[t]}</svg>`,r},L=(e,t,r={})=>{let n,o={...x,...r.labels},a=C(e,"div");a.setAttribute("data-obelism-content-prompt","");let i=a.attachShadow({mode:"open"}),s="site"===t.mode,l=C(e,"span",{textContent:o.open}),d=C(e,"button",{className:"pill",type:"button"},l,((n=P(e,"mark")).classList.add("mark"),n)),c=C(e,"span",{className:"pill compact"},`${o.viewingDrafts} \xb7 `),p=C(e,"button",{className:"link",type:"button",textContent:o.signOut});c.appendChild(p);let g=C(e,"p",{className:"status"});g.setAttribute("role","status");let u=C(e,"textarea",{placeholder:o.placeholder,rows:1});u.setAttribute("aria-label",o.open);let h=C(e,"div",{className:"form-loader",textContent:o.formLoading});h.setAttribute("role","status");let m=C(e,"div",{className:"form"},h),b=P(e,"page"),f=P(e,"block"),v=C(e,"span"),y=C(e,"button",{type:"button",title:o.clearBlock});y.setAttribute("aria-label",o.clearBlock),y.appendChild(P(e,"close"));let k=C(e,"span",{className:"chip scope"},b,f,v,y),L=C(e,"button",{className:"action chip",type:"button"},P(e,"undo"),o.undo),S=t=>{let r=C(e,"button",{type:"button",textContent:t});return r.setAttribute("aria-pressed","false"),r},A=S(o.modePrompt),N=S(o.form),I=C(e,"span",{className:"modes"},A,N);I.setAttribute("role","group"),I.setAttribute("aria-label",o.modes);let U=C(e,"button",{className:"action chip",type:"button"},P(e,"newPage"),o.newPage),M=C(e,"button",{className:"action chip product",type:"button"},P(e,"newPage"),o.addProductPage),$=C(e,"button",{type:"button",title:o.newPageCancel});$.setAttribute("aria-label",o.newPageCancel),$.appendChild(P(e,"close"));let O=C(e,"span",{className:"chip scope"},P(e,"newPage"),C(e,"span",{textContent:o.newPage}),$),j=C(e,"input",{type:"text",placeholder:o.pathPlaceholder,autocomplete:"off",spellcheck:!1}),R=C(e,"label",{className:"path"},C(e,"span",{textContent:o.path}),j),T=C(e,"button",{className:"close icon-button",type:"button",title:o.close},P(e,"close"));T.setAttribute("aria-label",o.close);let z=P(e,"minimize"),D=P(e,"expand"),F=C(e,"button",{className:"icon-button",type:"button"},z,D),B=C(e,"button",{className:"action primary icon-button",type:"submit"},C(e,"span",{className:"visually-hidden",textContent:o.send}),P(e,"send"));B.title=o.send;let q=C(e,"span",{className:"divider"}),V=C(e,"div",{className:"toolbar"},k,O,I,L,M,U,C(e,"span",{className:"spacer"}),F,T,q,B),W=C(e,"span",{textContent:o.pages}),H=C(e,"button",{className:"chip page-chip",type:"button",title:o.pages},P(e,"page"),W,P(e,"chevrons"));H.setAttribute("aria-haspopup","listbox"),H.setAttribute("aria-expanded","false"),H.setAttribute("aria-controls","obelism-pages");let Y=C(e,"input",{type:"text",placeholder:o.pagesSearch,autocomplete:"off",spellcheck:!1});Y.setAttribute("role","combobox"),Y.setAttribute("aria-label",o.pagesSearch),Y.setAttribute("aria-autocomplete","list"),Y.setAttribute("aria-expanded","true"),Y.setAttribute("aria-controls","obelism-pages");let X=C(e,"ul",{className:"options",id:"obelism-pages"});X.setAttribute("role","listbox"),X.setAttribute("aria-label",o.pages);let _=C(e,"p",{className:"empty"}),J=C(e,"div",{className:"pages"},C(e,"label",{className:"search"},P(e,"search"),Y),X,_),Z=C(e,"span",{className:"unsaved"});Z.appendChild(C(e,"span",{className:"dot"})),Z.appendChild(e.createTextNode(o.unsaved));let K=C(e,"button",{className:"link",type:"button",textContent:o.signOut}),G=C(e,"button",{className:"action chip",type:"button",textContent:o.save}),Q=C(e,"button",{className:"action chip",type:"button",textContent:o.publish}),ee=C(e,"div",{className:"footer"},H,Z,C(e,"span",{className:"spacer"}),K,G,Q),et=C(e,"form",{className:"panel"},g,R,u,m,V,J,ee),er=C(e,"div",{className:"root"},C(e,"div",{className:"bar"},d,c),et);i.append(C(e,"style",{textContent:E}),er);let en=e.defaultView,eo=e=>{"center"===e?delete er.dataset.side:er.dataset.side=e};eo((()=>{try{let e=en?.localStorage.getItem(w);return"left"===e||"right"===e?e:"center"}catch{return"center"}})());let ea=null,ei=!1;er.addEventListener("pointerdown",e=>{if(ei=!1,0!==e.button||!e.isPrimary)return;let t=e.target.closest("button, input, textarea, iframe, a, label, .pages, .form");if(t&&t!==d)return;let{left:r,right:n}=er.getBoundingClientRect();ea={id:e.pointerId,startX:e.clientX,x:e.clientX,velocity:0,time:e.timeStamp,moved:!1,min:Math.min(0,16-r),max:Math.max(0,(en?.innerWidth??0)-16-n)}}),er.addEventListener("pointermove",e=>{if(!ea||e.pointerId!==ea.id)return;let t=e.clientX-ea.startX;if(!ea.moved){if(6>Math.abs(t))return;ea.moved=!0,er.classList.add("dragging");try{er.setPointerCapture(e.pointerId)}catch{}}let r=e.timeStamp-ea.time;r>0&&(ea.velocity=(e.clientX-ea.x)/r),ea.x=e.clientX,ea.time=e.timeStamp;let n=Math.min(Math.max(t,ea.min),ea.max);er.style.translate=`${n+(t-n)/3}px 0`});let es=e=>{if(!ea||e.pointerId!==ea.id)return;let{moved:t,x:r,velocity:n,time:o}=ea;if(ea=null,!t)return;ei=!0;let a=er.getBoundingClientRect().left,i=en?.innerWidth??0,s=r+(e.timeStamp-o>80?0:Math.min(Math.max(150*n,-120),120)),l=s<i/3?"left":s>2*i/3?"right":"center";eo(l),er.style.translate="";let d=er.getBoundingClientRect().left;er.style.translate=`${a-d}px 0`,er.offsetWidth,er.classList.remove("dragging"),er.style.translate="";try{en?.localStorage.setItem(w,l)}catch{}};er.addEventListener("pointerup",es),er.addEventListener("pointercancel",es),er.addEventListener("click",e=>{ei&&(ei=!1,e.preventDefault(),e.stopPropagation())},!0);let el=!1,ed=!1,ec=!1,ep=null,eg=!1,eu=null,eh=!1,em=!1,eb=null,ef=null,ev=null,ey=0,ex=e=>({text:e instanceof Error?e.message:String(e),error:!0}),ew=e=>{let r=e.features.includes("prompt"),n=e.saving||e.publishing,a=e.prompting||n;er.hidden=!r&&!s,d.hidden=el||!r,c.hidden=r||!s,et.hidden=!el||!r;let i=s&&e.features.includes("create"),p=s&&null===t.entryId,x=p&&i&&null!==t.product,w=p&&i&&!x;i||(ec=!1),w&&(ec=!0),(ec||p||!s)&&(eg=!1);let E=el&&r&⪚E&&!eu?eC():!E&&eu&&eP();let C=x&&!ec;l.textContent=C?o.addProductPage:w?o.newPage:o.open,$.hidden=w,k.hidden=ec||C,O.hidden=!ec,M.hidden=!C,M.disabled=a,U.hidden=ec||eg||!i,U.disabled=a,ed&&(em=!1),R.hidden=!ec||em||ed,j.disabled=a,u.hidden=eg||em||ed||C,m.hidden=!eg||em||ed,m.classList.toggle("loading",eh),h.hidden=!eh,V.hidden=em,J.hidden=!em,u.placeholder=ec?o.newPagePlaceholder:o.placeholder,v.textContent=e.selectedUid?o.scopeBlock:o.scopeEntry,y.hidden=!e.selectedUid,b.hidden=!!e.selectedUid,f.hidden=!e.selectedUid,I.hidden=!s||ec||p||!e.features.includes("form"),A.setAttribute("aria-pressed",String(!eg)),N.setAttribute("aria-pressed",String(eg)),I.querySelectorAll("button").forEach(e=>{e.disabled=a}),L.hidden=ec||!e.canUndo,L.disabled=a,B.hidden=eg||ed||C,q.hidden=eg||ed||C;let P=ed?o.expand:o.minimize;F.title=P,F.setAttribute("aria-label",P),F.setAttribute("aria-expanded",String(!ed)),z.hidden=ed,D.hidden=!ed,B.disabled=a||""===u.value.trim()||ec&&""===j.value.trim(),u.disabled=a,G.hidden=!e.features.includes("save")||p,G.disabled=a||!e.unsaved,G.textContent=e.saving?o.saving:o.save,Q.hidden=!e.features.includes("publish")||p,Q.disabled=a,Q.textContent=e.publishing?o.publishing:o.publish,K.hidden=!s,ee.hidden=!s,Z.hidden=!e.unsaved,H.hidden=!s,H.setAttribute("aria-expanded",String(em)),W.textContent=eb?.find(({entryId:e})=>e===t.entryId)?.name??o.pages;let S=eg&&eh&&!em,T=e.prompting,Y=e.prompting?ec?o.creating:C?o.addingProductPage:o.working:S?"":ep?.text??(C?o.productPageHint:!s||ec||em||e.selectedUid?"":o.selectHint);g.textContent=Y,g.hidden=!Y||ed,g.className=`status${T?" working":""}${!a&&!S&&ep?.error?" error":""}`},ek=()=>ew(t.getState()),eE=()=>{u.style.height="auto",u.style.height=`${u.scrollHeight}px`},eC=()=>{eh=!0,m.classList.add("loading");let e=t.openForm(m,{title:o.formTitle,onReady:()=>{eu===e&&(eh=!1,ep=null,ek())},onError:t=>{eu===e&&(eh=!1,ep={text:t,error:!0},ek())}});eu=e},eP=()=>{eu?.close(),eu=null,eh=!1},eL=e=>{eg!==e&&(eg=e,ep=null,ek(),eg||u.focus())},eS=async()=>{if(eg)return;let e=u.value.trim();if(e&&!t.getState().prompting){ep=null;try{if(ec){let r=j.value.trim();if(!r)return j.focus();await t.createPage(r,e),eb=null,ef=null,ep={text:o.created,error:!1}}else{let{summary:r}=await t.prompt(e);ep={text:r,error:!1}}u.value="",eE()}catch(e){ep=ex(e)}ek(),u.focus()}},eA=()=>(ef??(ef=t.pages().then(e=>{eb=e,ev=null,eI(),ek()},e=>{ef=null,ev=ex(e).text,eI()})),ef),eN=()=>{let e=Y.value.trim().toLowerCase();return(eb??[]).filter(t=>!e||t.name.toLowerCase().includes(e)||t.url.toLowerCase().includes(e)||`/${t.path}`.toLowerCase().includes(e))},eI=()=>{let r=eN();ey=Math.max(0,Math.min(ey,r.length-1)),X.textContent="",r.forEach((r,n)=>{let a=r.entryId===t.entryId,i=C(e,"li",{className:`option${n===ey?" active":""}`,id:`obelism-page-${n}`},C(e,"span",{className:"option-text"},C(e,"span",{textContent:r.name}),C(e,"span",{className:"option-path",textContent:r.url})));if(i.setAttribute("role","option"),i.setAttribute("aria-selected",String(n===ey)),"published"!==r.status&&i.appendChild(C(e,"span",{className:"badge",textContent:"draft"===r.status?o.pageDraft:o.pageChanged})),a){i.setAttribute("aria-current","page");let t=P(e,"check");t.removeAttribute("aria-hidden"),t.setAttribute("role","img"),t.setAttribute("aria-label",o.pageCurrent),i.appendChild(t)}i.addEventListener("click",()=>eM(r)),X.appendChild(i)});let n=X.children[ey];n?(Y.setAttribute("aria-activedescendant",n.id),n.scrollIntoView?.({block:"nearest"})):Y.removeAttribute("aria-activedescendant"),_.textContent=(ev??(eb?"":o.pagesLoading))||(0===r.length?o.pagesEmpty:""),_.hidden=!_.textContent,_.className=`empty${ev?" error":""}`},eU=e=>{ep=null,(em=e)&&(Y.value="",ey=Math.max(0,(eb??[]).findIndex(({entryId:e})=>e===t.entryId)),eA(),eI()),ek(),(em?Y:H).focus()},eM=e=>{if(e.entryId===t.entryId)return eU(!1);t.openPage(e)},e$=e=>{(el=e)||(em=!1,ed=!1),ek(),s&&t.setSelecting(el&&!ec),el&&s&&eA()},eO=e=>{ec=e,ep=null,t.setSelecting(el&&!ec),ec&&t.select(null),ek(),(ec?j:u).focus()},ej=async e=>{ep=null;try{await t[e](),ep={text:"save"===e?o.saved:o.published,error:!1}}catch(e){ep=ex(e)}ek()};d.addEventListener("click",()=>{e$(!0),eg||(ec?j:u).focus()}),T.addEventListener("click",()=>e$(!1)),F.addEventListener("click",()=>{ed=!ed,ek(),ed||(eg?F:u).focus()}),et.addEventListener("click",e=>{if(!ed)return;let t=e.target.closest("button");t&&t!==F&&t!==T&&(ed=!1,ek())},!0),G.addEventListener("click",()=>void ej("save")),Q.addEventListener("click",()=>void ej("publish")),K.addEventListener("click",()=>t.signOut()),p.addEventListener("click",()=>t.signOut()),y.addEventListener("click",()=>t.select(null)),U.addEventListener("click",()=>eO(!0)),M.addEventListener("click",async()=>{ep=null;try{await t.createProductPage(),ep={text:o.addedProductPage,error:!1}}catch(e){ep=ex(e)}ek()}),$.addEventListener("click",()=>eO(!1)),A.addEventListener("click",()=>eL(!1)),N.addEventListener("click",()=>eL(!0)),H.addEventListener("click",()=>eU(!em)),j.addEventListener("input",ek),j.addEventListener("keydown",e=>{"Enter"!==e.key||e.isComposing?"Escape"===e.key&&e$(!1):(e.preventDefault(),u.focus())}),Y.addEventListener("input",()=>{ey=0,eI()}),Y.addEventListener("keydown",e=>{let t=X.children.length;if("ArrowDown"===e.key||"ArrowUp"===e.key){if(e.preventDefault(),0===t)return;ey=(ey+("ArrowDown"===e.key?1:-1)+t)%t,eI()}else if("Enter"!==e.key||e.isComposing)"Escape"===e.key&&(e.preventDefault(),eU(!1));else{e.preventDefault();let t=eN()[ey];t&&eM(t)}}),L.addEventListener("click",()=>{ep=null,t.undo()}),et.addEventListener("submit",e=>{e.preventDefault(),eS()}),u.addEventListener("input",()=>{eE(),ek()}),u.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||e.isComposing?"Escape"===e.key&&e$(!1):(e.preventDefault(),eS())});let eR=t.on("change",ew);return ek(),e.body.appendChild(a),()=>{eR(),eP(),a.remove()}},S="https://content.obelism.studio",A=(e,t)=>JSON.stringify(e)===JSON.stringify(t),N=Symbol.for("@obelism/content/bridge"),I=y({isEditing:!1,entryId:null,on:()=>{}},new URLSearchParams),U=({url:e,prompt:c=!0,pagePath:h,productPath:m}={})=>{let x;if("u"<typeof window)return I;let w=window;if(w[N])return w[N];if(w.ObelismBridge)x=y(w.ObelismBridge,new URLSearchParams(w.location.search));else{let y=(x=((e,r)=>{let n=new URLSearchParams(e.location.search),c=e.parent!==e&&n.has("cms_preview"),h=u(),m=[],y={...g,selecting:!0},x=e=>{y={...y,...e},h.emit("change",y)},w={isEditing:c,mode:c?"editor":null,entryId:n.get("cms_entry"),environment:n.get(o),product:null,on:h.on,getState:()=>y,...b,destroy:()=>{m.splice(0).forEach(e=>e())}};if(!c)return w;let k=e.document,E=(t,n={})=>{e.parent.postMessage({source:d,type:t,...n},r)},C=k.createElement("style");C.textContent=p(),k.head.appendChild(C),m.push(()=>C.remove());let P=(e,t)=>{if(k.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),y.selectedUid!==(e??null)&&x({selectedUid:e??null}),!e)return;let r=k.querySelector(v(e));r&&(r.setAttribute("data-cms-selected",""),t&&r.scrollIntoView({behavior:"smooth",block:"center"}))},L=e=>{let r=e.target,n=r?.closest?.(`[${t}]`);if(!n)return;e.preventDefault(),e.stopPropagation();let o=n.getAttribute(t);P(o,!1),E("select",{uid:o})};k.addEventListener("click",L,!0),m.push(()=>k.removeEventListener("click",L,!0));let S=()=>E("ready",{url:e.location.href}),A=new Map,N=0,I=e=>{let t=A.get(e);return t?(clearTimeout(t.timer),A.delete(e),0===A.size&&x({prompting:!1}),t):null};w.prompt=(e,t={})=>{let r=e.trim();if(!r)return Promise.reject(Error("Say what to change"));if(!y.features.includes("prompt"))return Promise.reject(Error("The editor does not offer prompts here"));let n=`prompt_${++N}`,o=void 0===t.uid?y.selectedUid:t.uid;return new Promise((e,t)=>{let a=setTimeout(()=>{I(n)?.reject(Error("The editor did not answer in time"))},12e4);A.set(n,{resolve:e,reject:t,timer:a}),x({prompting:!0}),E("prompt",{id:n,prompt:r,uid:o})})},w.undo=()=>E("undo"),w.select=e=>{P(e,!1),E("select",{uid:e})};let U=null;m.push(()=>U?.destroy()),m.push(()=>{for(let e of A.keys())I(e)?.reject(Error("The bridge was destroyed"))});let M=t=>{if(t.origin!==r||t.source!==e.parent)return;let n=t.data;if(n&&"obelism-content-editor"===n.source){if("connect"===n.type)S();else if("update"===n.type&&n.entry)h.emit("update",{entry:n.entry,selectedUid:n.selectedUid??null}),e.requestAnimationFrame(()=>P(n.selectedUid,!1)),U?.refresh();else if("select"===n.type)P(n.uid,!0);else if("saved"===n.type||"published"===n.type)h.emit(n.type,{}),0===h.listeners.update.size&&e.location.reload();else if("features"===n.type&&Array.isArray(n.features))x({features:n.features.filter(e=>"string"==typeof e)});else if("stats"===n.type)x({stats:(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return{};let t={};for(let[r,n]of Object.entries(e)){let{clicks:e,clickers:o,viewers:a,rate:i}=n??{};f(e)&&f(o)&&f(a)&&f(i)&&(t[r]={clicks:e,clickers:o,viewers:a,rate:i})}return t})(n.stats),statsVisible:!1!==n.visible}),(U||Object.keys(y.stats).length)&&(U??(U=((e,{selector:t,onSelect:r,labels:n})=>{let o=e.document,d={...a,...n},c=o.createElement("div");c.setAttribute("data-obelism-stats","");let p=c.attachShadow({mode:"open"}),g=o.createElement("style");g.textContent=s;let u=o.createElement("div");u.className="layer",p.append(g,u),o.body.appendChild(c);let h={},m=!1,b=null,f=()=>{if(b=null,u.replaceChildren(),u.hidden=!m,!m)return;let n=e.innerHeight;for(let[a,s]of Object.entries(h)){let c=o.querySelector(t(a));if(!c)continue;let p=c.getBoundingClientRect();if(!p.width&&!p.height||p.bottom<0||p.top>n)continue;let g=o.createElement("button");g.type="button",g.className="badge",g.dataset.uid=a,g.textContent=i(d.clicked,{rate:Math.round(100*s.rate)}),g.title=i(d.title,{clickers:s.clickers,viewers:s.viewers,clicks:s.clicks});let{place:h,top:m,left:b}=l(p,e.innerWidth);g.dataset.place=h,g.style.top=`${m}px`,g.style.left=`${b}px`,g.addEventListener("click",()=>r(a)),u.appendChild(g)}},v=()=>{null===b&&m&&(b=e.requestAnimationFrame(f))};e.addEventListener("scroll",v,{capture:!0,passive:!0}),e.addEventListener("resize",v);let y=e.MutationObserver,x="function"==typeof y?new y(v):null;return x?.observe(o.body,{childList:!0,subtree:!0}),{update(t,r){h=t,m=r,null!==b&&(e.cancelAnimationFrame?.(b),b=null),f()},refresh:v,destroy(){null!==b&&e.cancelAnimationFrame?.(b),e.removeEventListener("scroll",v,{capture:!0}),e.removeEventListener("resize",v),x?.disconnect(),c.remove()}}})(e,{selector:v,onSelect:e=>w.select(e)})),U.update(y.stats,y.statsVisible));else if("promptState"===n.type)x({canUndo:!0===n.canUndo});else if("promptResult"===n.type&&"string"==typeof n.id){let e=I(n.id);if(!e)return;!0===n.ok?e.resolve({summary:"string"==typeof n.summary?n.summary:""}):e.reject(Error("string"==typeof n.error?n.error:"The prompt failed"))}}};return e.addEventListener("message",M),m.push(()=>e.removeEventListener("message",M)),"loading"===k.readyState?(k.addEventListener("DOMContentLoaded",S,{once:!0}),m.push(()=>k.removeEventListener("DOMContentLoaded",S))):S(),w})(w,new URL(e||S).origin)).isEditing||w.parent!==w?null:(e=>{for(let t of e.cookie.split(";")){let[e,...r]=t.trim().split("=");if("obelism_content_editing"===e)try{let e=decodeURIComponent(r.join("="));return e.startsWith("/")&&!e.startsWith("//")?e:null}catch{break}}return null})(w.document);if(y&&(x=((e,o,{pagePath:a=e=>`/${e}`,productPath:i}={})=>{let s=e.document,l=u(),c=[],h=new Map,m=[],b=new Set,f=null,y=null,x=-1/0,w=new Map,k=g,E=e=>{k={...k,...e},l.emit("change",k)},C=()=>E({unsaved:[...h.values()].some(({content:e,saved:t})=>!A(e,t)),canUndo:m.length>0}),P=async(t,r)=>{let n=await e.fetch(`${o}/api/${t}`,void 0===r?{credentials:"same-origin"}:{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","x-obelism-content":"1"},body:JSON.stringify(r)}),a=await n.json().catch(()=>({}));if(401===n.status)throw E({features:[]}),Error("You were signed out. Sign in again to edit.");if(!n.ok)throw Error("string"==typeof a.error?a.error:`Obelism Content answered ${n.status}`);return a},L=()=>s.querySelector(`[${r}]`)?.getAttribute(r)??null,S=()=>{let e=s.querySelector(`[${n}]`),t=e?.getAttribute(n),r=e?.getAttribute("data-cms-product-handle");return t&&r?{id:t,handle:r}:null},N=e=>{let t=e?s.querySelector(v(e)):null;return t?.closest(`[${r}]`)?.getAttribute(r)??L()},I=()=>{if(!f)return;let e=f.space.locales.map(({code:e})=>e),t=s.documentElement.lang;if(e.includes(t))return t;let r=t.split("-")[0]??"";return e.includes(r)?r:f.space.defaultLocale},U=new Map,M=async e=>{let t=h.get(e);if(t)return t;let r=U.get(e);return r||(r=P(`entries/${encodeURIComponent(e)}`).then(t=>{let r=h.get(e)??{content:t.content,saved:t.content,revision:t.draftRevision};return h.set(e,r),r}).finally(()=>U.delete(e)),U.set(e,r)),r},$=e=>{let t=void 0===e?k.selectedUid:e,r=N(t);if(!r)throw Error("Nothing on this page is marked as an entry: add editable(entry)");return{uid:t,entryId:r}},O=e=>{s.querySelectorAll("[data-cms-selected]").forEach(e=>{e.removeAttribute("data-cms-selected")}),k.selectedUid!==e&&E({selectedUid:e}),e&&s.querySelector(v(e))?.setAttribute("data-cms-selected","")},j=async t=>{if(0===l.listeners.update.size)return;let r=h.get(t)?.content,{entry:n}=await P(`entries/${encodeURIComponent(t)}/preview`,{content:r,locale:I()});l.emit("update",{entry:n,selectedUid:k.selectedUid}),e.requestAnimationFrame(()=>O(k.selectedUid))},R=e=>console.error("[@obelism/content bridge]",e),T=t=>{l.emit(t,{}),0===l.listeners.update.size&&e.location.reload()},z=async()=>{for(let[e,t]of h){if(A(t.content,t.saved))continue;let{draftRevision:r}=await P(`entries/${encodeURIComponent(e)}/save`,{content:t.content,baseRevision:t.revision});t.saved=t.content,t.revision=r,b.add(e)}m.length=0,x=-1/0,C()},D={isEditing:!0,mode:"site",get entryId(){return L()},get product(){return S()},environment:null,on:l.on,getState:()=>k,async prompt(e,t={}){let r=e.trim();if(!r)throw Error("Say what to change");if(!k.features.includes("prompt"))throw Error("You may not edit content here");let{uid:n,entryId:o}=$(t.uid);E({prompting:!0});try{let e=await M(o),t=await P(`entries/${encodeURIComponent(o)}/prompt`,{prompt:r,content:e.content,locale:I(),uid:n});return m.push({entryId:o,content:e.content}),x=-1/0,e.content=t.content,C(),await j(o),{summary:t.summary}}finally{E({prompting:!1})}},async createPage(t,r){let n=r.trim();if(!n)throw Error("Say what the page is for");if(!t.trim())throw Error("Say where the page goes, like /pricing");if(!k.features.includes("create"))throw Error("You may not add pages here");if(k.unsaved)throw Error("Save or undo your changes before adding a page");E({prompting:!0});try{let r=await P("entries",{path:t,prompt:n,locale:I(),from:L()}),o=a(r.path,I()??"");return e.location.assign(o),{summary:r.summary,entryId:r.entryId,path:r.path,url:o}}finally{E({prompting:!1})}},async createProductPage(t=S()??void 0){if(!t)throw Error("Nothing on this page is marked as a product: add editableProduct(product)");if(!k.features.includes("create"))throw Error("You may not add pages here");if(k.unsaved)throw Error("Save or undo your changes before adding a page");E({prompting:!0});try{let r=await P("product-pages",{product:{id:t.id,handle:t.handle}});return e.location.reload(),r}finally{E({prompting:!1})}},async view(e={}){let{uid:t,entryId:r}=$(e.uid),n=await M(r),{view:o}=await P(`entries/${encodeURIComponent(r)}/view`,{content:n.content,locale:I(),uid:t});return o},async applyJson(e,t={}){if(!k.features.includes("prompt"))throw Error("You may not edit content here");if(!e||"object"!=typeof e||Array.isArray(e))throw Error("Send the fields to change as an object");let{uid:r,entryId:n}=$(t.uid);E({prompting:!0});try{let t=await M(n),o=await P(`entries/${encodeURIComponent(n)}/apply`,{changes:e,content:t.content,locale:I(),uid:r});return A(o.content,t.content)||(m.push({entryId:n,content:t.content}),x=-1/0,t.content=o.content,C(),await j(n)),{changed:o.changed??[],...o.view&&{view:o.view}}}finally{E({prompting:!1})}},openForm(t,r={}){let{title:n="Content form",onReady:o,onError:a}=r;y?.();let i=!1,s=null,c=null,p=()=>{},g=e=>{i||a?.(e instanceof Error?e.message:String(e))},u=()=>{i||(i=!0,p(),s?.destroy(),y===u&&(y=null))};y=u;let b=()=>{if(i||!s)return;let e=N(k.selectedUid);if(!e)return;let t=h.get(e);if(!t)return void M(e).then(b,g);let r={entryId:e,content:t.content,locale:I(),selectedUid:k.selectedUid};c&&c.entryId===r.entryId&&c.content===r.content&&c.locale===r.locale&&c.selectedUid===r.selectedUid||(c=r,s.show(r))},f=(e,t)=>{let r=t??c?.entryId,n=void 0===r?void 0:h.get(r);if(i||void 0===r||!n||A(e,n.content))return;let o=Date.now();o-x>=1e3&&m.push({entryId:r,content:n.content}),x=o,n.content=e,c?.entryId===r&&(c={...c,content:e}),C(),clearTimeout(w.get(r)),w.set(r,setTimeout(()=>{w.delete(r),j(r).catch(R)},300))};return(async()=>{if(!k.features.includes("form"))throw Error("You may not edit content here");await M($(void 0).entryId);let{url:r}=await P("form",{});i||(s=((e,t,r,n,o)=>{let a=null;try{a=new URL(r)}catch{}if(!a||!/^https?:$/.test(a.protocol))throw Error("Obelism Content sent no form to open");let i=a.origin,s=t.ownerDocument.createElement("iframe");s.title=n,s.src=r;let l=!1,c=!1,p=null,g=()=>{l&&p&&s.contentWindow?.postMessage({source:d,type:"content",...p},i)},u=setTimeout(()=>{c||o.onError("The form did not open")},3e4),h=()=>{c=!0,clearTimeout(u),o.onReady()},m=e=>{if(e.origin!==i||!s.contentWindow||e.source!==s.contentWindow)return;let t=e.data;if(t&&"obelism-content-form"===t.source)if("ready"===t.type)l=!0,g(),!0!==t.loading&&h();else if("loaded"===t.type)l&&h();else{let e;if("change"!==t.type||!(e=t.content)||"object"!=typeof e||Array.isArray(e))if("height"===t.type){let e=t.height;if("number"==typeof e&&Number.isFinite(e)){let t=Math.round(Math.min(Math.max(e,80),4e3));s.style.height=`min(${t}px, 60vh)`}}else"error"===t.type&&o.onError("string"==typeof t.message&&t.message?t.message:"The form failed");else o.onChange(t.content,"string"==typeof t.entryId?t.entryId:void 0)}};return e.addEventListener("message",m),t.appendChild(s),{show(e){p=e,g()},destroy(){clearTimeout(u),e.removeEventListener("message",m),s.remove()}}})(e,t,"string"==typeof r?r:"",n,{onReady:()=>o?.(),onChange:f,onError:e=>a?.(e)}),p=l.on("change",b),b())})().catch(g),{close:u}},async pages(){let{pages:e}=await P("pages"),t=[I(),f?.space.defaultLocale];return e.flatMap(e=>{let r,n=t.find(t=>void 0!==t&&void 0!==e.paths[t])??Object.keys(e.paths)[0],o=void 0===n?void 0:e.paths[n];return void 0===n||void 0===o?[]:[{entryId:e.entryId,name:e.name,contentType:e.contentType,status:e.status,path:o,url:e.product?(r=e.product.handle,i?i(r,n):(f?.productPath??"/products/{handle}").replaceAll("{handle}",encodeURIComponent(r))):a(o,n),product:e.product??null}]})},openPage(t){(!k.unsaved||e.confirm("Leave this page and lose the changes you have not saved?"))&&(h.clear(),m.length=0,C(),e.location.assign(t.url))},undo(){let e=m.pop(),t=e&&h.get(e.entryId);e&&t&&(x=-1/0,t.content=e.content,C(),j(e.entryId).catch(R))},async save(){if(!k.features.includes("save"))throw Error("You may not edit content here");E({saving:!0});try{await z()}finally{E({saving:!1})}T("saved")},async publish(){if(!k.features.includes("publish"))throw Error("You may not publish here");E({publishing:!0});try{await z();let e=L(),t=b.size>0?[...b]:e?[e]:[];if(0===t.length)throw Error("Nothing on this page is marked as an entry: add editable(entry)");for(let e of t)await P(`entries/${encodeURIComponent(e)}/publish`,{}),b.delete(e)}finally{E({publishing:!1})}T("published")},signOut(){if(k.unsaved&&!e.confirm("Sign out and lose the changes you have not saved?"))return;h.clear(),C();let t=e.location.pathname+e.location.search;e.location.href=`${o}/logout?return_to=${encodeURIComponent(t)}`},select(e){O(e)},setSelecting(e){e?s.documentElement.setAttribute("data-cms-selecting",""):s.documentElement.removeAttribute("data-cms-selecting"),E({selecting:e})},destroy(){y?.(),w.forEach(e=>clearTimeout(e)),w.clear(),c.splice(0).forEach(e=>e())}},F=s.createElement("style");F.textContent=p("html[data-cms-selecting] "),s.head.appendChild(F),c.push(()=>F.remove()),c.push(()=>s.documentElement.removeAttribute("data-cms-selecting"));let B=e=>{if(!k.selecting)return;let r=e.target,n=r?.closest?.(`[${t}]`);n&&(e.preventDefault(),e.stopPropagation(),O(n.getAttribute(t)))};s.addEventListener("click",B,!0),c.push(()=>s.removeEventListener("click",B,!0));let q=e=>{k.unsaved&&(e.preventDefault(),e.returnValue="")};return e.addEventListener("beforeunload",q),c.push(()=>e.removeEventListener("beforeunload",q)),P("session").then(e=>{f=e,E({features:e.features})}).catch(e=>console.error("[@obelism/content bridge]",e)),D})(w,y,{pagePath:h,productPath:m})),x.isEditing&&c){let e=L(w.document,x,!0===c?{}:c),t=x.destroy;x.destroy=()=>{e(),t()}}}return w[N]=x,x};(e=window).ObelismBridge=U((e=>{let t=e?.dataset.url,r=t||S;if(!t&&e?.src){let t=new URL(e.src);"/bridge.js"===t.pathname&&(r=t.origin)}return{url:r,prompt:e?.dataset.prompt!=="false"}})(document.currentScript)),e.ObelismContent={getContentBridge:U,mountPromptBox:L}}();
|
package/dist/bridge.js
CHANGED
|
@@ -546,6 +546,18 @@ const DEFAULT_PROMPT_LABELS = {
|
|
|
546
546
|
addedProductPage: 'Opening the product page…',
|
|
547
547
|
productPageHint: 'This product has no page yet. Add one for what the store doesn’t say.'
|
|
548
548
|
};
|
|
549
|
+
const SIDE_KEY = 'obelism-content-prompt-side';
|
|
550
|
+
// How far the pointer moves before it drags the box, not clicks it.
|
|
551
|
+
const DRAG_THRESHOLD_PX = 6;
|
|
552
|
+
// How far ahead a flick carries the box: its speed for this long.
|
|
553
|
+
const FLICK_MS = 150;
|
|
554
|
+
// …at most this far, so one coarse pointer event isn't a flick across the
|
|
555
|
+
// screen…
|
|
556
|
+
const FLICK_MAX_PX = 120;
|
|
557
|
+
// …and none when the pointer stood still this long before letting go.
|
|
558
|
+
const FLICK_STALE_MS = 80;
|
|
559
|
+
// Its distance from the side of the screen, as in the CSS.
|
|
560
|
+
const EDGE_PX = 16;
|
|
549
561
|
// Icons (Lucide), drawn in the text colour.
|
|
550
562
|
const ICONS = {
|
|
551
563
|
page: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
@@ -558,6 +570,7 @@ const ICONS = {
|
|
|
558
570
|
chevrons: '<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',
|
|
559
571
|
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
560
572
|
check: '<path d="M20 6 9 17l-5-5"/>',
|
|
573
|
+
mark: '<path d="M12 3v18M3 12h18"/><g fill="currentColor" stroke="none"><circle cx="7" cy="7" r="1.75"/><circle cx="17" cy="7" r="1.75"/><circle cx="7" cy="17" r="1.75"/><circle cx="17" cy="17" r="1.75"/></g>',
|
|
561
574
|
send: '<path d="M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"/><path d="M6 12h16"/>'
|
|
562
575
|
};
|
|
563
576
|
// Obelism's design tokens (obelism.studio/design.md), under their own names:
|
|
@@ -583,10 +596,20 @@ const CSS = `
|
|
|
583
596
|
--font-inter: 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
584
597
|
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
585
598
|
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
|
|
599
|
+
transition: translate .45s cubic-bezier(0.34, 1.3, 0.64, 1);
|
|
586
600
|
z-index: 2147483647; width: min(640px, calc(100vw - 32px)); color-scheme: light dark;
|
|
587
601
|
font: 400 14px/1.4 var(--font-inter); color: rgb(var(--theme-foreground-rgb));
|
|
588
602
|
-webkit-font-smoothing: antialiased;
|
|
589
603
|
}
|
|
604
|
+
/* Dragged to a side of the screen, it stays there, springing into place. */
|
|
605
|
+
.root[data-side='left'] { left: 16px; transform: none; }
|
|
606
|
+
.root[data-side='right'] { left: auto; right: 16px; transform: none; }
|
|
607
|
+
.root[data-side='left'] .bar { justify-content: flex-start; }
|
|
608
|
+
.root[data-side='right'] .bar { justify-content: flex-end; }
|
|
609
|
+
.root.dragging { transition: none; user-select: none; -webkit-user-select: none; }
|
|
610
|
+
.root.dragging, .root.dragging * { cursor: grabbing !important; }
|
|
611
|
+
.toolbar, .footer, .status { cursor: grab; touch-action: none; }
|
|
612
|
+
.pill { touch-action: none; }
|
|
590
613
|
@media (prefers-color-scheme: dark) {
|
|
591
614
|
.root {
|
|
592
615
|
--theme-foreground-rgb: var(--color-white-rgb);
|
|
@@ -622,7 +645,8 @@ button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-
|
|
|
622
645
|
}
|
|
623
646
|
button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translateY(-2px); }
|
|
624
647
|
.pill > span:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
625
|
-
.mark { color: rgb(var(--theme-accent-rgb));
|
|
648
|
+
.mark { color: rgb(var(--theme-accent-rgb)); }
|
|
649
|
+
.mark svg { width: 20px; height: 20px; }
|
|
626
650
|
.pill.compact { width: auto; height: 40px; padding: 0 18px; font-size: 14px; gap: 4px; }
|
|
627
651
|
|
|
628
652
|
/* Open: a floating card, the text on top and a row of pill buttons below. */
|
|
@@ -640,7 +664,7 @@ button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translat
|
|
|
640
664
|
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(.98); } }
|
|
641
665
|
@keyframes spin { to { transform: rotate(1turn); } }
|
|
642
666
|
@media (prefers-reduced-motion: reduce) {
|
|
643
|
-
.panel { animation: none; }
|
|
667
|
+
.root, .panel { animation: none; transition: none; }
|
|
644
668
|
.pill { transition: none; }
|
|
645
669
|
}
|
|
646
670
|
.status {
|
|
@@ -665,7 +689,20 @@ textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
|
665
689
|
display: block; width: 100%; height: min(560px, 60vh); border: none; border-radius: 12px;
|
|
666
690
|
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
667
691
|
}
|
|
668
|
-
|
|
692
|
+
/* Loading, it keeps the form's own height, the spinner in the middle,
|
|
693
|
+
whatever the form says meanwhile; the frame shows once the form does. */
|
|
694
|
+
.form.loading { position: relative; height: calc(min(560px, 60vh) + 12px); }
|
|
695
|
+
.form.loading iframe { position: absolute; visibility: hidden; }
|
|
696
|
+
.form-loader {
|
|
697
|
+
position: absolute; inset: 12px 12px 0; display: flex; align-items: center; justify-content: center;
|
|
698
|
+
gap: 10px; border-radius: 12px; color: rgb(var(--theme-foreground-muted-rgb));
|
|
699
|
+
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
700
|
+
}
|
|
701
|
+
.form-loader::before {
|
|
702
|
+
content: ''; width: 16px; height: 16px; flex: none; border-radius: 50%;
|
|
703
|
+
border: 2px solid rgb(var(--theme-border-rgb) / .3); border-top-color: rgb(var(--theme-accent-rgb));
|
|
704
|
+
animation: spin .7s linear infinite;
|
|
705
|
+
}
|
|
669
706
|
/* New page: where it goes, above what it is for. */
|
|
670
707
|
.path {
|
|
671
708
|
display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
|
|
@@ -792,6 +829,11 @@ const icon = (doc, name)=>{
|
|
|
792
829
|
span.innerHTML = `<svg viewBox="0 0 24 24">${ICONS[name]}</svg>`;
|
|
793
830
|
return span;
|
|
794
831
|
};
|
|
832
|
+
const mark = (doc)=>{
|
|
833
|
+
const span = icon(doc, 'mark');
|
|
834
|
+
span.classList.add('mark');
|
|
835
|
+
return span;
|
|
836
|
+
};
|
|
795
837
|
// Mounts the box on the page; returns a function that removes it.
|
|
796
838
|
const mountPromptBox = (doc, bridge, options = {})=>{
|
|
797
839
|
const labels = {
|
|
@@ -810,11 +852,8 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
810
852
|
const pill = element(doc, 'button', {
|
|
811
853
|
className: 'pill',
|
|
812
854
|
type: 'button'
|
|
813
|
-
}, pillText, // The Obelism mark.
|
|
814
|
-
|
|
815
|
-
className: 'mark',
|
|
816
|
-
textContent: '⁜'
|
|
817
|
-
}));
|
|
855
|
+
}, pillText, // The Obelism mark (⁜), drawn: the glyph sits low in most fonts.
|
|
856
|
+
mark(doc));
|
|
818
857
|
// Someone signed in who may only read: a note that drafts show, and a
|
|
819
858
|
// way out.
|
|
820
859
|
const viewing = element(doc, 'span', {
|
|
@@ -836,9 +875,14 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
836
875
|
});
|
|
837
876
|
input.setAttribute('aria-label', labels.open);
|
|
838
877
|
// Form mode (on the site): the CMS's form, mounted while shown.
|
|
878
|
+
const formLoader = element(doc, 'div', {
|
|
879
|
+
className: 'form-loader',
|
|
880
|
+
textContent: labels.formLoading
|
|
881
|
+
});
|
|
882
|
+
formLoader.setAttribute('role', 'status');
|
|
839
883
|
const formBox = element(doc, 'div', {
|
|
840
884
|
className: 'form'
|
|
841
|
-
});
|
|
885
|
+
}, formLoader);
|
|
842
886
|
// What the prompt acts on: the whole page, or the selected block with a
|
|
843
887
|
// way back to the page.
|
|
844
888
|
const pageIcon = icon(doc, 'page');
|
|
@@ -1013,6 +1057,95 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1013
1057
|
shadow.append(element(doc, 'style', {
|
|
1014
1058
|
textContent: CSS
|
|
1015
1059
|
}), root);
|
|
1060
|
+
// Dragged sideways, the box moves to the left, the middle or the right
|
|
1061
|
+
// of the screen, whichever third it is let go (or flicked) in; this
|
|
1062
|
+
// browser remembers where. From the pill, or the open box's bars, not its
|
|
1063
|
+
// fields and buttons.
|
|
1064
|
+
const win = doc.defaultView;
|
|
1065
|
+
const readSide = ()=>{
|
|
1066
|
+
try {
|
|
1067
|
+
const side = win?.localStorage.getItem(SIDE_KEY);
|
|
1068
|
+
return side === 'left' || side === 'right' ? side : 'center';
|
|
1069
|
+
} catch {
|
|
1070
|
+
return 'center';
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
const placeAt = (side)=>{
|
|
1074
|
+
if (side === 'center') delete root.dataset.side;
|
|
1075
|
+
else root.dataset.side = side;
|
|
1076
|
+
};
|
|
1077
|
+
placeAt(readSide());
|
|
1078
|
+
let drag = null;
|
|
1079
|
+
// A drag that ends on the pill isn't a click on it.
|
|
1080
|
+
let dragged = false;
|
|
1081
|
+
root.addEventListener('pointerdown', (event)=>{
|
|
1082
|
+
dragged = false;
|
|
1083
|
+
if (event.button !== 0 || !event.isPrimary) return;
|
|
1084
|
+
const target = event.target;
|
|
1085
|
+
const control = target.closest('button, input, textarea, iframe, a, label, .pages, .form');
|
|
1086
|
+
if (control && control !== pill) return;
|
|
1087
|
+
const { left, right } = root.getBoundingClientRect();
|
|
1088
|
+
drag = {
|
|
1089
|
+
id: event.pointerId,
|
|
1090
|
+
startX: event.clientX,
|
|
1091
|
+
x: event.clientX,
|
|
1092
|
+
velocity: 0,
|
|
1093
|
+
time: event.timeStamp,
|
|
1094
|
+
moved: false,
|
|
1095
|
+
min: Math.min(0, EDGE_PX - left),
|
|
1096
|
+
max: Math.max(0, (win?.innerWidth ?? 0) - EDGE_PX - right)
|
|
1097
|
+
};
|
|
1098
|
+
});
|
|
1099
|
+
root.addEventListener('pointermove', (event)=>{
|
|
1100
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
1101
|
+
const dx = event.clientX - drag.startX;
|
|
1102
|
+
if (!drag.moved) {
|
|
1103
|
+
if (Math.abs(dx) < DRAG_THRESHOLD_PX) return;
|
|
1104
|
+
drag.moved = true;
|
|
1105
|
+
root.classList.add('dragging');
|
|
1106
|
+
try {
|
|
1107
|
+
root.setPointerCapture(event.pointerId);
|
|
1108
|
+
} catch {}
|
|
1109
|
+
}
|
|
1110
|
+
const elapsed = event.timeStamp - drag.time;
|
|
1111
|
+
if (elapsed > 0) drag.velocity = (event.clientX - drag.x) / elapsed;
|
|
1112
|
+
drag.x = event.clientX;
|
|
1113
|
+
drag.time = event.timeStamp;
|
|
1114
|
+
// Past the edge, a third as far.
|
|
1115
|
+
const clamped = Math.min(Math.max(dx, drag.min), drag.max);
|
|
1116
|
+
root.style.translate = `${clamped + (dx - clamped) / 3}px 0`;
|
|
1117
|
+
});
|
|
1118
|
+
const drop = (event)=>{
|
|
1119
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
1120
|
+
const { moved, x, velocity, time } = drag;
|
|
1121
|
+
drag = null;
|
|
1122
|
+
if (!moved) return;
|
|
1123
|
+
dragged = true;
|
|
1124
|
+
// Springs from where it was let go to its new place.
|
|
1125
|
+
const before = root.getBoundingClientRect().left;
|
|
1126
|
+
const width = win?.innerWidth ?? 0;
|
|
1127
|
+
const flick = event.timeStamp - time > FLICK_STALE_MS ? 0 : Math.min(Math.max(velocity * FLICK_MS, -FLICK_MAX_PX), FLICK_MAX_PX);
|
|
1128
|
+
const projected = x + flick;
|
|
1129
|
+
const side = projected < width / 3 ? 'left' : projected > width * 2 / 3 ? 'right' : 'center';
|
|
1130
|
+
placeAt(side);
|
|
1131
|
+
root.style.translate = '';
|
|
1132
|
+
const after = root.getBoundingClientRect().left;
|
|
1133
|
+
root.style.translate = `${before - after}px 0`;
|
|
1134
|
+
void root.offsetWidth;
|
|
1135
|
+
root.classList.remove('dragging');
|
|
1136
|
+
root.style.translate = '';
|
|
1137
|
+
try {
|
|
1138
|
+
win?.localStorage.setItem(SIDE_KEY, side);
|
|
1139
|
+
} catch {}
|
|
1140
|
+
};
|
|
1141
|
+
root.addEventListener('pointerup', drop);
|
|
1142
|
+
root.addEventListener('pointercancel', drop);
|
|
1143
|
+
root.addEventListener('click', (event)=>{
|
|
1144
|
+
if (!dragged) return;
|
|
1145
|
+
dragged = false;
|
|
1146
|
+
event.preventDefault();
|
|
1147
|
+
event.stopPropagation();
|
|
1148
|
+
}, true);
|
|
1016
1149
|
let open = false;
|
|
1017
1150
|
let minimized = false;
|
|
1018
1151
|
let creating = false;
|
|
@@ -1070,6 +1203,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1070
1203
|
input.hidden = form || picking || minimized || offerProduct;
|
|
1071
1204
|
formBox.hidden = !form || picking || minimized;
|
|
1072
1205
|
formBox.classList.toggle('loading', formLoading);
|
|
1206
|
+
formLoader.hidden = !formLoading;
|
|
1073
1207
|
toolbar.hidden = picking;
|
|
1074
1208
|
pagesSection.hidden = !picking;
|
|
1075
1209
|
input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
|
|
@@ -1110,8 +1244,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1110
1244
|
pageChip.setAttribute('aria-expanded', String(picking));
|
|
1111
1245
|
pageChipText.textContent = pages?.find(({ entryId })=>entryId === bridge.entryId)?.name ?? labels.pages;
|
|
1112
1246
|
const loadingForm = form && formLoading && !picking;
|
|
1113
|
-
const working = state.prompting
|
|
1114
|
-
|
|
1247
|
+
const working = state.prompting;
|
|
1248
|
+
// A form opening says so in its own place.
|
|
1249
|
+
const note = state.prompting ? creating ? labels.creating : offerProduct ? labels.addingProductPage : labels.working : loadingForm ? '' : message?.text ?? (offerProduct ? labels.productPageHint : site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
|
|
1115
1250
|
status.textContent = note;
|
|
1116
1251
|
status.hidden = !note || minimized;
|
|
1117
1252
|
status.className = `status${working ? ' working' : ''}${!busy && !loadingForm && message?.error ? ' error' : ''}`;
|
|
@@ -1315,6 +1450,15 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1315
1450
|
refresh();
|
|
1316
1451
|
if (!minimized) (form ? minimize : input).focus();
|
|
1317
1452
|
});
|
|
1453
|
+
// Any other button of the minimized box expands it first (capture, so
|
|
1454
|
+
// its own handler then sees the box open), e.g. the pages or New page.
|
|
1455
|
+
panel.addEventListener('click', (event)=>{
|
|
1456
|
+
if (!minimized) return;
|
|
1457
|
+
const button = event.target.closest('button');
|
|
1458
|
+
if (!button || button === minimize || button === close) return;
|
|
1459
|
+
minimized = false;
|
|
1460
|
+
refresh();
|
|
1461
|
+
}, true);
|
|
1318
1462
|
save.addEventListener('click', ()=>void store('save'));
|
|
1319
1463
|
publish.addEventListener('click', ()=>void store('publish'));
|
|
1320
1464
|
signOut.addEventListener('click', ()=>bridge.signOut());
|
|
@@ -1406,13 +1550,15 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1406
1550
|
// Messages are only exchanged with the URL's origin, and only with the
|
|
1407
1551
|
// iframe's window:
|
|
1408
1552
|
// - form → bridge: { source: 'obelism-content-form', type: 'ready' |
|
|
1409
|
-
// 'change' | 'error' | 'height', content?, message?,
|
|
1410
|
-
// `
|
|
1411
|
-
//
|
|
1553
|
+
// 'loaded' | 'change' | 'error' | 'height', loading?, content?, message?,
|
|
1554
|
+
// height? } — `ready` with `loading: true` when the form still loads the
|
|
1555
|
+
// entry after it and says `loaded` once it shows (a CMS from before says
|
|
1556
|
+
// only `ready`), `change` carries the whole entry content after an edit,
|
|
1557
|
+
// `height` the form's own height in px, which the iframe takes (up to 60vh)
|
|
1412
1558
|
// - bridge → form: { source: 'obelism-content-bridge', type: 'content',
|
|
1413
1559
|
// entryId, content, locale, selectedUid }
|
|
1414
1560
|
const FORM_SOURCE = 'obelism-content-form';
|
|
1415
|
-
// How long the form may take to
|
|
1561
|
+
// How long the form may take to show.
|
|
1416
1562
|
const FORM_READY_TIMEOUT_MS = 30_000;
|
|
1417
1563
|
const isObject = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
|
|
1418
1564
|
// Mounts the form at `url` in `container`. The URL is single use: mount a
|
|
@@ -1430,6 +1576,7 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1430
1576
|
iframe.title = title;
|
|
1431
1577
|
iframe.src = url;
|
|
1432
1578
|
let ready = false;
|
|
1579
|
+
let shown = false;
|
|
1433
1580
|
let latest = null;
|
|
1434
1581
|
const post = ()=>{
|
|
1435
1582
|
if (!ready || !latest) return;
|
|
@@ -1440,8 +1587,13 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1440
1587
|
}, origin);
|
|
1441
1588
|
};
|
|
1442
1589
|
const timer = setTimeout(()=>{
|
|
1443
|
-
if (!
|
|
1590
|
+
if (!shown) handlers.onError('The form did not open');
|
|
1444
1591
|
}, FORM_READY_TIMEOUT_MS);
|
|
1592
|
+
const show = ()=>{
|
|
1593
|
+
shown = true;
|
|
1594
|
+
clearTimeout(timer);
|
|
1595
|
+
handlers.onReady();
|
|
1596
|
+
};
|
|
1445
1597
|
const onMessage = (event)=>{
|
|
1446
1598
|
if (event.origin !== origin || !iframe.contentWindow || event.source !== iframe.contentWindow) {
|
|
1447
1599
|
return;
|
|
@@ -1451,9 +1603,12 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1451
1603
|
if (data.type === 'ready') {
|
|
1452
1604
|
// Again after the form reloads itself: it gets the content again.
|
|
1453
1605
|
ready = true;
|
|
1454
|
-
clearTimeout(timer);
|
|
1455
|
-
handlers.onReady();
|
|
1456
1606
|
post();
|
|
1607
|
+
// Until the form loaded the entry, the box's own loading state
|
|
1608
|
+
// stands in for the form's.
|
|
1609
|
+
if (data.loading !== true) show();
|
|
1610
|
+
} else if (data.type === 'loaded') {
|
|
1611
|
+
if (ready) show();
|
|
1457
1612
|
} else if (data.type === 'change' && isObject(data.content)) {
|
|
1458
1613
|
handlers.onChange(data.content, typeof data.entryId === 'string' ? data.entryId : undefined);
|
|
1459
1614
|
} else if (data.type === 'height') {
|
package/dist/bridge.mjs
CHANGED
|
@@ -544,6 +544,18 @@ const DEFAULT_PROMPT_LABELS = {
|
|
|
544
544
|
addedProductPage: 'Opening the product page…',
|
|
545
545
|
productPageHint: 'This product has no page yet. Add one for what the store doesn’t say.'
|
|
546
546
|
};
|
|
547
|
+
const SIDE_KEY = 'obelism-content-prompt-side';
|
|
548
|
+
// How far the pointer moves before it drags the box, not clicks it.
|
|
549
|
+
const DRAG_THRESHOLD_PX = 6;
|
|
550
|
+
// How far ahead a flick carries the box: its speed for this long.
|
|
551
|
+
const FLICK_MS = 150;
|
|
552
|
+
// …at most this far, so one coarse pointer event isn't a flick across the
|
|
553
|
+
// screen…
|
|
554
|
+
const FLICK_MAX_PX = 120;
|
|
555
|
+
// …and none when the pointer stood still this long before letting go.
|
|
556
|
+
const FLICK_STALE_MS = 80;
|
|
557
|
+
// Its distance from the side of the screen, as in the CSS.
|
|
558
|
+
const EDGE_PX = 16;
|
|
547
559
|
// Icons (Lucide), drawn in the text colour.
|
|
548
560
|
const ICONS = {
|
|
549
561
|
page: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
@@ -556,6 +568,7 @@ const ICONS = {
|
|
|
556
568
|
chevrons: '<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',
|
|
557
569
|
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
558
570
|
check: '<path d="M20 6 9 17l-5-5"/>',
|
|
571
|
+
mark: '<path d="M12 3v18M3 12h18"/><g fill="currentColor" stroke="none"><circle cx="7" cy="7" r="1.75"/><circle cx="17" cy="7" r="1.75"/><circle cx="7" cy="17" r="1.75"/><circle cx="17" cy="17" r="1.75"/></g>',
|
|
559
572
|
send: '<path d="M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"/><path d="M6 12h16"/>'
|
|
560
573
|
};
|
|
561
574
|
// Obelism's design tokens (obelism.studio/design.md), under their own names:
|
|
@@ -581,10 +594,20 @@ const CSS = `
|
|
|
581
594
|
--font-inter: 'InterVariable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
582
595
|
Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
583
596
|
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
|
|
597
|
+
transition: translate .45s cubic-bezier(0.34, 1.3, 0.64, 1);
|
|
584
598
|
z-index: 2147483647; width: min(640px, calc(100vw - 32px)); color-scheme: light dark;
|
|
585
599
|
font: 400 14px/1.4 var(--font-inter); color: rgb(var(--theme-foreground-rgb));
|
|
586
600
|
-webkit-font-smoothing: antialiased;
|
|
587
601
|
}
|
|
602
|
+
/* Dragged to a side of the screen, it stays there, springing into place. */
|
|
603
|
+
.root[data-side='left'] { left: 16px; transform: none; }
|
|
604
|
+
.root[data-side='right'] { left: auto; right: 16px; transform: none; }
|
|
605
|
+
.root[data-side='left'] .bar { justify-content: flex-start; }
|
|
606
|
+
.root[data-side='right'] .bar { justify-content: flex-end; }
|
|
607
|
+
.root.dragging { transition: none; user-select: none; -webkit-user-select: none; }
|
|
608
|
+
.root.dragging, .root.dragging * { cursor: grabbing !important; }
|
|
609
|
+
.toolbar, .footer, .status { cursor: grab; touch-action: none; }
|
|
610
|
+
.pill { touch-action: none; }
|
|
588
611
|
@media (prefers-color-scheme: dark) {
|
|
589
612
|
.root {
|
|
590
613
|
--theme-foreground-rgb: var(--color-white-rgb);
|
|
@@ -620,7 +643,8 @@ button:focus-visible { outline: 2px solid rgb(var(--theme-accent-rgb)); outline-
|
|
|
620
643
|
}
|
|
621
644
|
button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translateY(-2px); }
|
|
622
645
|
.pill > span:first-child { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
623
|
-
.mark { color: rgb(var(--theme-accent-rgb));
|
|
646
|
+
.mark { color: rgb(var(--theme-accent-rgb)); }
|
|
647
|
+
.mark svg { width: 20px; height: 20px; }
|
|
624
648
|
.pill.compact { width: auto; height: 40px; padding: 0 18px; font-size: 14px; gap: 4px; }
|
|
625
649
|
|
|
626
650
|
/* Open: a floating card, the text on top and a row of pill buttons below. */
|
|
@@ -638,7 +662,7 @@ button.pill:hover { color: rgb(var(--theme-foreground-rgb)); transform: translat
|
|
|
638
662
|
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(.98); } }
|
|
639
663
|
@keyframes spin { to { transform: rotate(1turn); } }
|
|
640
664
|
@media (prefers-reduced-motion: reduce) {
|
|
641
|
-
.panel { animation: none; }
|
|
665
|
+
.root, .panel { animation: none; transition: none; }
|
|
642
666
|
.pill { transition: none; }
|
|
643
667
|
}
|
|
644
668
|
.status {
|
|
@@ -663,7 +687,20 @@ textarea::placeholder { color: rgb(var(--theme-foreground-muted-rgb)); }
|
|
|
663
687
|
display: block; width: 100%; height: min(560px, 60vh); border: none; border-radius: 12px;
|
|
664
688
|
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
665
689
|
}
|
|
666
|
-
|
|
690
|
+
/* Loading, it keeps the form's own height, the spinner in the middle,
|
|
691
|
+
whatever the form says meanwhile; the frame shows once the form does. */
|
|
692
|
+
.form.loading { position: relative; height: calc(min(560px, 60vh) + 12px); }
|
|
693
|
+
.form.loading iframe { position: absolute; visibility: hidden; }
|
|
694
|
+
.form-loader {
|
|
695
|
+
position: absolute; inset: 12px 12px 0; display: flex; align-items: center; justify-content: center;
|
|
696
|
+
gap: 10px; border-radius: 12px; color: rgb(var(--theme-foreground-muted-rgb));
|
|
697
|
+
background: rgb(var(--theme-background-actions-rgb) / .45);
|
|
698
|
+
}
|
|
699
|
+
.form-loader::before {
|
|
700
|
+
content: ''; width: 16px; height: 16px; flex: none; border-radius: 50%;
|
|
701
|
+
border: 2px solid rgb(var(--theme-border-rgb) / .3); border-top-color: rgb(var(--theme-accent-rgb));
|
|
702
|
+
animation: spin .7s linear infinite;
|
|
703
|
+
}
|
|
667
704
|
/* New page: where it goes, above what it is for. */
|
|
668
705
|
.path {
|
|
669
706
|
display: flex; align-items: center; gap: 10px; padding: 16px 22px 0;
|
|
@@ -790,6 +827,11 @@ const icon = (doc, name)=>{
|
|
|
790
827
|
span.innerHTML = `<svg viewBox="0 0 24 24">${ICONS[name]}</svg>`;
|
|
791
828
|
return span;
|
|
792
829
|
};
|
|
830
|
+
const mark = (doc)=>{
|
|
831
|
+
const span = icon(doc, 'mark');
|
|
832
|
+
span.classList.add('mark');
|
|
833
|
+
return span;
|
|
834
|
+
};
|
|
793
835
|
// Mounts the box on the page; returns a function that removes it.
|
|
794
836
|
const mountPromptBox = (doc, bridge, options = {})=>{
|
|
795
837
|
const labels = {
|
|
@@ -808,11 +850,8 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
808
850
|
const pill = element(doc, 'button', {
|
|
809
851
|
className: 'pill',
|
|
810
852
|
type: 'button'
|
|
811
|
-
}, pillText, // The Obelism mark.
|
|
812
|
-
|
|
813
|
-
className: 'mark',
|
|
814
|
-
textContent: '⁜'
|
|
815
|
-
}));
|
|
853
|
+
}, pillText, // The Obelism mark (⁜), drawn: the glyph sits low in most fonts.
|
|
854
|
+
mark(doc));
|
|
816
855
|
// Someone signed in who may only read: a note that drafts show, and a
|
|
817
856
|
// way out.
|
|
818
857
|
const viewing = element(doc, 'span', {
|
|
@@ -834,9 +873,14 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
834
873
|
});
|
|
835
874
|
input.setAttribute('aria-label', labels.open);
|
|
836
875
|
// Form mode (on the site): the CMS's form, mounted while shown.
|
|
876
|
+
const formLoader = element(doc, 'div', {
|
|
877
|
+
className: 'form-loader',
|
|
878
|
+
textContent: labels.formLoading
|
|
879
|
+
});
|
|
880
|
+
formLoader.setAttribute('role', 'status');
|
|
837
881
|
const formBox = element(doc, 'div', {
|
|
838
882
|
className: 'form'
|
|
839
|
-
});
|
|
883
|
+
}, formLoader);
|
|
840
884
|
// What the prompt acts on: the whole page, or the selected block with a
|
|
841
885
|
// way back to the page.
|
|
842
886
|
const pageIcon = icon(doc, 'page');
|
|
@@ -1011,6 +1055,95 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1011
1055
|
shadow.append(element(doc, 'style', {
|
|
1012
1056
|
textContent: CSS
|
|
1013
1057
|
}), root);
|
|
1058
|
+
// Dragged sideways, the box moves to the left, the middle or the right
|
|
1059
|
+
// of the screen, whichever third it is let go (or flicked) in; this
|
|
1060
|
+
// browser remembers where. From the pill, or the open box's bars, not its
|
|
1061
|
+
// fields and buttons.
|
|
1062
|
+
const win = doc.defaultView;
|
|
1063
|
+
const readSide = ()=>{
|
|
1064
|
+
try {
|
|
1065
|
+
const side = win?.localStorage.getItem(SIDE_KEY);
|
|
1066
|
+
return side === 'left' || side === 'right' ? side : 'center';
|
|
1067
|
+
} catch {
|
|
1068
|
+
return 'center';
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
const placeAt = (side)=>{
|
|
1072
|
+
if (side === 'center') delete root.dataset.side;
|
|
1073
|
+
else root.dataset.side = side;
|
|
1074
|
+
};
|
|
1075
|
+
placeAt(readSide());
|
|
1076
|
+
let drag = null;
|
|
1077
|
+
// A drag that ends on the pill isn't a click on it.
|
|
1078
|
+
let dragged = false;
|
|
1079
|
+
root.addEventListener('pointerdown', (event)=>{
|
|
1080
|
+
dragged = false;
|
|
1081
|
+
if (event.button !== 0 || !event.isPrimary) return;
|
|
1082
|
+
const target = event.target;
|
|
1083
|
+
const control = target.closest('button, input, textarea, iframe, a, label, .pages, .form');
|
|
1084
|
+
if (control && control !== pill) return;
|
|
1085
|
+
const { left, right } = root.getBoundingClientRect();
|
|
1086
|
+
drag = {
|
|
1087
|
+
id: event.pointerId,
|
|
1088
|
+
startX: event.clientX,
|
|
1089
|
+
x: event.clientX,
|
|
1090
|
+
velocity: 0,
|
|
1091
|
+
time: event.timeStamp,
|
|
1092
|
+
moved: false,
|
|
1093
|
+
min: Math.min(0, EDGE_PX - left),
|
|
1094
|
+
max: Math.max(0, (win?.innerWidth ?? 0) - EDGE_PX - right)
|
|
1095
|
+
};
|
|
1096
|
+
});
|
|
1097
|
+
root.addEventListener('pointermove', (event)=>{
|
|
1098
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
1099
|
+
const dx = event.clientX - drag.startX;
|
|
1100
|
+
if (!drag.moved) {
|
|
1101
|
+
if (Math.abs(dx) < DRAG_THRESHOLD_PX) return;
|
|
1102
|
+
drag.moved = true;
|
|
1103
|
+
root.classList.add('dragging');
|
|
1104
|
+
try {
|
|
1105
|
+
root.setPointerCapture(event.pointerId);
|
|
1106
|
+
} catch {}
|
|
1107
|
+
}
|
|
1108
|
+
const elapsed = event.timeStamp - drag.time;
|
|
1109
|
+
if (elapsed > 0) drag.velocity = (event.clientX - drag.x) / elapsed;
|
|
1110
|
+
drag.x = event.clientX;
|
|
1111
|
+
drag.time = event.timeStamp;
|
|
1112
|
+
// Past the edge, a third as far.
|
|
1113
|
+
const clamped = Math.min(Math.max(dx, drag.min), drag.max);
|
|
1114
|
+
root.style.translate = `${clamped + (dx - clamped) / 3}px 0`;
|
|
1115
|
+
});
|
|
1116
|
+
const drop = (event)=>{
|
|
1117
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
1118
|
+
const { moved, x, velocity, time } = drag;
|
|
1119
|
+
drag = null;
|
|
1120
|
+
if (!moved) return;
|
|
1121
|
+
dragged = true;
|
|
1122
|
+
// Springs from where it was let go to its new place.
|
|
1123
|
+
const before = root.getBoundingClientRect().left;
|
|
1124
|
+
const width = win?.innerWidth ?? 0;
|
|
1125
|
+
const flick = event.timeStamp - time > FLICK_STALE_MS ? 0 : Math.min(Math.max(velocity * FLICK_MS, -FLICK_MAX_PX), FLICK_MAX_PX);
|
|
1126
|
+
const projected = x + flick;
|
|
1127
|
+
const side = projected < width / 3 ? 'left' : projected > width * 2 / 3 ? 'right' : 'center';
|
|
1128
|
+
placeAt(side);
|
|
1129
|
+
root.style.translate = '';
|
|
1130
|
+
const after = root.getBoundingClientRect().left;
|
|
1131
|
+
root.style.translate = `${before - after}px 0`;
|
|
1132
|
+
void root.offsetWidth;
|
|
1133
|
+
root.classList.remove('dragging');
|
|
1134
|
+
root.style.translate = '';
|
|
1135
|
+
try {
|
|
1136
|
+
win?.localStorage.setItem(SIDE_KEY, side);
|
|
1137
|
+
} catch {}
|
|
1138
|
+
};
|
|
1139
|
+
root.addEventListener('pointerup', drop);
|
|
1140
|
+
root.addEventListener('pointercancel', drop);
|
|
1141
|
+
root.addEventListener('click', (event)=>{
|
|
1142
|
+
if (!dragged) return;
|
|
1143
|
+
dragged = false;
|
|
1144
|
+
event.preventDefault();
|
|
1145
|
+
event.stopPropagation();
|
|
1146
|
+
}, true);
|
|
1014
1147
|
let open = false;
|
|
1015
1148
|
let minimized = false;
|
|
1016
1149
|
let creating = false;
|
|
@@ -1068,6 +1201,7 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1068
1201
|
input.hidden = form || picking || minimized || offerProduct;
|
|
1069
1202
|
formBox.hidden = !form || picking || minimized;
|
|
1070
1203
|
formBox.classList.toggle('loading', formLoading);
|
|
1204
|
+
formLoader.hidden = !formLoading;
|
|
1071
1205
|
toolbar.hidden = picking;
|
|
1072
1206
|
pagesSection.hidden = !picking;
|
|
1073
1207
|
input.placeholder = creating ? labels.newPagePlaceholder : labels.placeholder;
|
|
@@ -1108,8 +1242,9 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1108
1242
|
pageChip.setAttribute('aria-expanded', String(picking));
|
|
1109
1243
|
pageChipText.textContent = pages?.find(({ entryId })=>entryId === bridge.entryId)?.name ?? labels.pages;
|
|
1110
1244
|
const loadingForm = form && formLoading && !picking;
|
|
1111
|
-
const working = state.prompting
|
|
1112
|
-
|
|
1245
|
+
const working = state.prompting;
|
|
1246
|
+
// A form opening says so in its own place.
|
|
1247
|
+
const note = state.prompting ? creating ? labels.creating : offerProduct ? labels.addingProductPage : labels.working : loadingForm ? '' : message?.text ?? (offerProduct ? labels.productPageHint : site && !creating && !picking && !state.selectedUid ? labels.selectHint : '');
|
|
1113
1248
|
status.textContent = note;
|
|
1114
1249
|
status.hidden = !note || minimized;
|
|
1115
1250
|
status.className = `status${working ? ' working' : ''}${!busy && !loadingForm && message?.error ? ' error' : ''}`;
|
|
@@ -1313,6 +1448,15 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1313
1448
|
refresh();
|
|
1314
1449
|
if (!minimized) (form ? minimize : input).focus();
|
|
1315
1450
|
});
|
|
1451
|
+
// Any other button of the minimized box expands it first (capture, so
|
|
1452
|
+
// its own handler then sees the box open), e.g. the pages or New page.
|
|
1453
|
+
panel.addEventListener('click', (event)=>{
|
|
1454
|
+
if (!minimized) return;
|
|
1455
|
+
const button = event.target.closest('button');
|
|
1456
|
+
if (!button || button === minimize || button === close) return;
|
|
1457
|
+
minimized = false;
|
|
1458
|
+
refresh();
|
|
1459
|
+
}, true);
|
|
1316
1460
|
save.addEventListener('click', ()=>void store('save'));
|
|
1317
1461
|
publish.addEventListener('click', ()=>void store('publish'));
|
|
1318
1462
|
signOut.addEventListener('click', ()=>bridge.signOut());
|
|
@@ -1404,13 +1548,15 @@ const mountPromptBox = (doc, bridge, options = {})=>{
|
|
|
1404
1548
|
// Messages are only exchanged with the URL's origin, and only with the
|
|
1405
1549
|
// iframe's window:
|
|
1406
1550
|
// - form → bridge: { source: 'obelism-content-form', type: 'ready' |
|
|
1407
|
-
// 'change' | 'error' | 'height', content?, message?,
|
|
1408
|
-
// `
|
|
1409
|
-
//
|
|
1551
|
+
// 'loaded' | 'change' | 'error' | 'height', loading?, content?, message?,
|
|
1552
|
+
// height? } — `ready` with `loading: true` when the form still loads the
|
|
1553
|
+
// entry after it and says `loaded` once it shows (a CMS from before says
|
|
1554
|
+
// only `ready`), `change` carries the whole entry content after an edit,
|
|
1555
|
+
// `height` the form's own height in px, which the iframe takes (up to 60vh)
|
|
1410
1556
|
// - bridge → form: { source: 'obelism-content-bridge', type: 'content',
|
|
1411
1557
|
// entryId, content, locale, selectedUid }
|
|
1412
1558
|
const FORM_SOURCE = 'obelism-content-form';
|
|
1413
|
-
// How long the form may take to
|
|
1559
|
+
// How long the form may take to show.
|
|
1414
1560
|
const FORM_READY_TIMEOUT_MS = 30_000;
|
|
1415
1561
|
const isObject = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
|
|
1416
1562
|
// Mounts the form at `url` in `container`. The URL is single use: mount a
|
|
@@ -1428,6 +1574,7 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1428
1574
|
iframe.title = title;
|
|
1429
1575
|
iframe.src = url;
|
|
1430
1576
|
let ready = false;
|
|
1577
|
+
let shown = false;
|
|
1431
1578
|
let latest = null;
|
|
1432
1579
|
const post = ()=>{
|
|
1433
1580
|
if (!ready || !latest) return;
|
|
@@ -1438,8 +1585,13 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1438
1585
|
}, origin);
|
|
1439
1586
|
};
|
|
1440
1587
|
const timer = setTimeout(()=>{
|
|
1441
|
-
if (!
|
|
1588
|
+
if (!shown) handlers.onError('The form did not open');
|
|
1442
1589
|
}, FORM_READY_TIMEOUT_MS);
|
|
1590
|
+
const show = ()=>{
|
|
1591
|
+
shown = true;
|
|
1592
|
+
clearTimeout(timer);
|
|
1593
|
+
handlers.onReady();
|
|
1594
|
+
};
|
|
1443
1595
|
const onMessage = (event)=>{
|
|
1444
1596
|
if (event.origin !== origin || !iframe.contentWindow || event.source !== iframe.contentWindow) {
|
|
1445
1597
|
return;
|
|
@@ -1449,9 +1601,12 @@ const mountFormFrame = (win, container, url, title, handlers)=>{
|
|
|
1449
1601
|
if (data.type === 'ready') {
|
|
1450
1602
|
// Again after the form reloads itself: it gets the content again.
|
|
1451
1603
|
ready = true;
|
|
1452
|
-
clearTimeout(timer);
|
|
1453
|
-
handlers.onReady();
|
|
1454
1604
|
post();
|
|
1605
|
+
// Until the form loaded the entry, the box's own loading state
|
|
1606
|
+
// stands in for the form's.
|
|
1607
|
+
if (data.loading !== true) show();
|
|
1608
|
+
} else if (data.type === 'loaded') {
|
|
1609
|
+
if (ready) show();
|
|
1455
1610
|
} else if (data.type === 'change' && isObject(data.content)) {
|
|
1456
1611
|
handlers.onChange(data.content, typeof data.entryId === 'string' ? data.entryId : undefined);
|
|
1457
1612
|
} else if (data.type === 'height') {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obelism/content",
|
|
3
3
|
"description": "Obelism Content SDK: the delivery API client, the visual editor bridge with prompt editing, editing on your own site, webhook verification, and the content model CLI",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
7
7
|
"headless-cms",
|