domma-cms 1.8.15 → 1.8.17

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.
@@ -1,8 +1,8 @@
1
- import{replaceRange as te}from"./markdown-toolbar.js";import{boolState as ne,boolValue as oe,parseAttrs as J,serialiseTag as Q,setAttrs as le}from"./shortcode-tags.js";import{SHORTCODES as se}from"./shortcode-registry.js";import{clearLookupCache as re,lookupOptions as ie}from"./shortcode-lookups.js";import{PREVIEW_RENDERERS as ce}from"./shortcode-preview.js";import{makeCheckbox as ae,makeColourInput as de,makeField as y,makeIconInput as U,makeImageInput as X,makeLivePreview as pe,makeSelect as M,makeTextInput as D}from"./shortcode-modal.js";const ue=new Map(se.map(e=>[e.name,e])),Z=6;export function templateAttrs(e){const n=String(e.template||"").match(new RegExp(`^\\[${e.name}([\\s\\S]*?)\\s*/?\\]`));return n?J(n[1]):{}}export function templateBody(e){if(e.selfClosing)return"";const n=String(e.template||"").match(new RegExp(`^\\[${e.name}[^\\]]*\\]([\\s\\S]*)\\[/${e.name}\\]$`));return n?n[1].replace(/^\n/,"").replace(/\n$/,""):""}export function templateChildren(e){if(!e.children)return[];const n=e.children.name,i=new RegExp(`\\[${n}([^\\]]*)\\]([\\s\\S]*?)\\[/${n}\\]`,"g"),d=[];for(const s of templateBody(e).matchAll(i))d.push({attrs:J(s[1]),body:s[2].replace(/^\n/,"").replace(/\n$/,"")});return d}const me=new Set(["lookup","url"]);export function seedAttrs(e,n){const i=new Map((e.attrs||[]).map(s=>[s.name,s.type])),d={};for(const[s,c]of Object.entries(n))me.has(i.get(s))||(d[s]=c);return d}export function buildSnippet(e,n,i=""){const d=Q(e.name,n,e.selfClosing);if(e.selfClosing)return d;const s=`[/${e.name}]`;return e.inline?`${d}${i}${s}`:`${d}
1
+ import{replaceRange as ne}from"./markdown-toolbar.js";import{boolState as oe,boolValue as le,parseAttrs as Q,serialiseTag as U,setAttrs as se}from"./shortcode-tags.js";import{SHORTCODES as re}from"./shortcode-registry.js";import{clearLookupCache as ie,lookupOptions as ce}from"./shortcode-lookups.js";import{PREVIEW_RENDERERS as ae}from"./shortcode-preview.js";import{makeCheckbox as de,makeColourInput as pe,makeField as x,makeIconInput as X,makeImageInput as Z,makeLivePreview as ue,makeSelect as M,makeTextInput as _}from"./shortcode-modal.js";const me=new Map(re.map(e=>[e.name,e])),ee=6;export function templateAttrs(e){const n=String(e.template||"").match(new RegExp(`^\\[${e.name}([\\s\\S]*?)\\s*/?\\]`));return n?Q(n[1]):{}}export function templateBody(e){if(e.selfClosing)return"";const n=String(e.template||"").match(new RegExp(`^\\[${e.name}[^\\]]*\\]([\\s\\S]*)\\[/${e.name}\\]$`));return n?n[1].replace(/^\n/,"").replace(/\n$/,""):""}export function templateChildren(e){if(!e.children)return[];const n=e.children.name,i=new RegExp(`\\[${n}([^\\]]*)\\]([\\s\\S]*?)\\[/${n}\\]`,"g"),a=[];for(const s of templateBody(e).matchAll(i))a.push({attrs:Q(s[1]),body:s[2].replace(/^\n/,"").replace(/\n$/,"")});return a}const fe=new Set(["lookup","url"]);export function seedAttrs(e,n){const i=new Map((e.attrs||[]).map(s=>[s.name,s.type])),a={};for(const[s,c]of Object.entries(n))fe.has(i.get(s))||(a[s]=c);return a}export function buildSnippet(e,n,i=""){const a=U(e.name,n,e.selfClosing);if(e.selfClosing)return a;const s=`[/${e.name}]`;return e.inline?`${a}${i}${s}`:`${a}
2
2
  ${i}
3
3
  ${s}`}export function buildChildrenBody(e,n){return n.map(i=>buildSnippet(e,i.attrs,i.body)).join(`
4
- `)}export function emitSnippet(e,n,i,d){const s=(e.formats||[]).find(c=>c.value===d);if(s&&typeof s.emit=="function"){const c=s.emit(n,i);if(c!=null)return c}return buildSnippet(e,n,i)}const fe="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;";function he(e,n,i,d){const s=n===!0||n==null?"":String(n);if(e.type==="checkbox"){const l=ae(e.label),r=l.querySelector("input");return r.checked=ne(n,e.default),r.addEventListener("change",i),{el:l,bare:!0,get:()=>oe(e,r.checked)}}if(e.type==="select"){const l=e.options.some(([C])=>C==="")?e.options:[["","- none -"],...e.options],r=M(l);return r.value=s,r.addEventListener("change",i),{el:y(e.label,r),get:()=>r.value||null}}if(e.type==="icon"){const{el:l,input:r}=U("icon name (optional)",s);return r.addEventListener("input",i),{el:y(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="image"){const{el:l,input:r}=X("/media/photo.jpg",s);return r.addEventListener("input",i),{el:y(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="colour"){const{el:l,input:r}=de("#0f172a or a theme colour",s);return r.addEventListener("input",i),{el:y(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="lookup"){const l=M([["","Loading\u2026"]]);l.disabled=!0,l.addEventListener("change",i);let r;const C=()=>{const h=d(),z=e.source==="collection-fields"?String(h.slug??""):e.source;z!==r&&(r=z,l.disabled=!0,ie(e.source,h).then(u=>{const v=l.value||s,b=u.some(([S])=>S===v),w=[["","- none -"],...u];v&&!b&&w.push([v,`${v} (missing)`]),l.replaceChildren();for(const[S,x]of w){const T=document.createElement("option");T.value=S,T.textContent=x,l.appendChild(T)}l.value=v,l.disabled=!1,i()}))};return C(),{el:y(e.label,l),get:()=>l.value||null,refresh:e.source==="collection-fields"?C:null}}const c=D(e.type==="url"?"/about or https://example.com":"",s);return e.type==="number"&&(c.type="number"),c.addEventListener("input",i),{el:y(e.label,c),get:()=>c.value.trim()||null}}export function openShortcodeForm(e,n,i={}){const{span:d=null,defaults:s=null}=i,c=!!d;re();const l=e.selectionStart,r=e.selectionEnd,C=e.value.slice(l,r),h=n.children?ue.get(n.children.name):null,z={...seedAttrs(n,templateAttrs(n)),...s||{}},u=document.createElement("div");u.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const v=[],b=[];let w=()=>{};const S=()=>{const t={};for(const{attr:o,w:p}of v){const a=p.get();a!=null&&a!==""&&(t[o.name]=a)}return t},x=()=>w(),T=()=>S();let O=null;!c&&n.formats?.length&&(O=M(n.formats.map(t=>[t.value,t.label])),O.addEventListener("change",x),u.appendChild(y("Format",O)));const $=n.attrs||[],P=$.length>Z+2?Z:$.length,F=(t,o)=>{const p=c?d.attrs[t.name]:z[t.name],a=he(t,p,x,T);v.push({attr:t,w:a,initial:a.get()}),o.appendChild(a.el)};for(const t of $.slice(0,P))F(t,u);if(P<$.length){const t=document.createElement("details");t.style.cssText="border-top:1px solid var(--dm-border,#333);padding-top:.6rem;";const o=document.createElement("summary");o.textContent=`More options (${$.length-P})`,o.style.cssText="cursor:pointer;font-size:.75rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",t.appendChild(o);const p=document.createElement("div");p.style.cssText="display:flex;flex-direction:column;gap:.75rem;padding-top:.75rem;",t.appendChild(p);for(const a of $.slice(P))F(a,p);u.appendChild(t)}let k=null,L=null;const N=document.createElement("div");if(!c&&h){const t=document.createElement("div");t.style.cssText=fe,t.textContent=`${h.label}s`,u.appendChild(t),N.style.cssText="display:flex;flex-direction:column;gap:.4rem;",u.appendChild(N),L=document.createElement("button"),L.type="button",L.textContent=`+ Add ${h.label.toLowerCase()}`,L.style.cssText="padding:.35rem .75rem;background:transparent;border:1px dashed var(--dm-border,#555);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;font-size:.85em;align-self:flex-start;",L.addEventListener("click",()=>{q({attrs:{},body:templateBody(h)||"Content"}),w()}),u.appendChild(L);const o=templateChildren(n),p=o.length?o:[{attrs:{},body:"Content"}];for(const a of p)q({attrs:seedAttrs(h,a.attrs),body:a.body});C&&(b[0].bodyInput.value=C)}else!c&&!n.selfClosing&&(k=document.createElement("textarea"),k.rows=n.inline?2:4,k.value=C||templateBody(n),k.style.cssText="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;font-family:inherit;box-sizing:border-box;resize:vertical;",k.addEventListener("input",x),u.appendChild(y("Content",k)));function q(t){if(b.length>=(n.children.max??20))return;const o=document.createElement("div");o.style.cssText="display:flex;gap:.35rem;align-items:center;";const p=[];for(const m of h.attrs||[])if(m.type==="icon"){const{el:f,input:B}=U(m.label.toLowerCase(),t.attrs[m.name]||"");f.style.flex=".7",B.addEventListener("input",x),p.push({attr:m,get:()=>B.value.trim()||null}),o.appendChild(f)}else if(m.type==="image"){const{el:f,input:B}=X(m.label,t.attrs[m.name]||"");f.style.flex="1",B.addEventListener("input",x),p.push({attr:m,get:()=>B.value.trim()||null}),o.appendChild(f)}else if(m.type==="select"){const f=M(m.options);f.value=t.attrs[m.name]||m.options[0][0],f.style.flex=".7",f.addEventListener("change",x),p.push({attr:m,get:()=>f.value||null}),o.appendChild(f)}else{const f=D(m.label,t.attrs[m.name]||"");f.style.flex="1",f.addEventListener("input",x),p.push({attr:m,get:()=>f.value.trim()||null}),o.appendChild(f)}const a=D("Content",t.body||"");a.style.flex="1.2",a.addEventListener("input",x),o.appendChild(a);const g=document.createElement("button");g.type="button",g.textContent="\xD7",g.style.cssText="padding:.3rem .5rem;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;font-size:.85em;flex-shrink:0;";const G={row:o,fields:p,bodyInput:a,remove:g};g.addEventListener("click",()=>{b.length<=(n.children.min??1)||(b.splice(b.indexOf(G),1),o.remove(),w())}),o.appendChild(g),b.push(G),N.appendChild(o),window.I?.scan?.(o)}const Y=ce[n.preview];let R=null;if(Y){const t=document.createElement("div");t.style.cssText="background:var(--dm-background-alt,#111);border:1px solid var(--dm-border,#333);border-radius:4px;padding:.75rem;min-height:2.5rem;display:flex;align-items:center;overflow-x:auto;",R=document.createElement("span"),t.appendChild(R),u.appendChild(y("Preview",t))}const H=pe();u.appendChild(y(c?"Tag preview":"Shortcode preview",H.el));const W=document.createElement("div");W.style.cssText="display:flex;justify-content:flex-end;gap:.75rem;padding-top:.75rem;border-top:1px solid var(--dm-border,#333);";const A=document.createElement("button");A.type="button",A.className="btn btn-ghost",A.textContent="Cancel";const I=document.createElement("button");I.type="button",I.className="btn btn-primary",I.textContent=c?"Apply":`Insert ${n.label}`,W.appendChild(A),W.appendChild(I),u.appendChild(W);const V=()=>h&&b.length?buildChildrenBody(h,b.map(t=>({attrs:Object.fromEntries(t.fields.map(o=>[o.attr.name,o.get()]).filter(([,o])=>o!=null)),body:t.bodyInput.value}))):k?k.value:templateBody(n),K=()=>emitSnippet(n,S(),V(),O?.value),ee=t=>{if(n.inline||n.selfClosing||!t.startsWith("["))return t;const o=e.value.slice(0,l),p=e.value.slice(r),a=o===""||o.endsWith(`
4
+ `)}export function emitSnippet(e,n,i,a){const s=(e.formats||[]).find(c=>c.value===a);if(s&&typeof s.emit=="function"){const c=s.emit(n,i);if(c!=null)return c}return buildSnippet(e,n,i)}const he="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;";function be(e,n,i,a){const s=n===!0||n==null?"":String(n);if(e.type==="checkbox"){const l=de(e.label),r=l.querySelector("input");return r.checked=oe(n,e.default),r.addEventListener("change",i),{el:l,bare:!0,get:()=>le(e,r.checked)}}if(e.type==="select"){const l=e.options.some(([C])=>C==="")?e.options:[["","- none -"],...e.options],r=M(l);return r.value=s,r.addEventListener("change",i),{el:x(e.label,r),get:()=>r.value||null}}if(e.type==="icon"){const{el:l,input:r}=X("icon name (optional)",s);return r.addEventListener("input",i),{el:x(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="image"){const{el:l,input:r}=Z("/media/photo.jpg",s);return r.addEventListener("input",i),{el:x(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="colour"){const{el:l,input:r}=pe("#0f172a or a theme colour",s);return r.addEventListener("input",i),{el:x(e.label,l),get:()=>r.value.trim()||null}}if(e.type==="lookup"){const l=M([["","Loading\u2026"]]);l.disabled=!0,l.addEventListener("change",i);let r;const C=()=>{const h=a(),O=e.source==="collection-fields"?String(h.slug??""):e.source;O!==r&&(r=O,l.disabled=!0,ce(e.source,h).then(u=>{const v=l.value||s,b=u.some(([S])=>S===v),k=[["","- none -"],...u];v&&!b&&k.push([v,`${v} (missing)`]),l.replaceChildren();for(const[S,y]of k){const T=document.createElement("option");T.value=S,T.textContent=y,l.appendChild(T)}l.value=v,l.disabled=!1,i()}))};return C(),{el:x(e.label,l),get:()=>l.value||null,refresh:e.source==="collection-fields"?C:null}}const c=_(e.placeholder||(e.type==="url"?"/about or https://example.com":""),s);return e.type==="number"&&(c.type="number"),c.addEventListener("input",i),{el:x(e.label,c),get:()=>c.value.trim()||null}}export function openShortcodeForm(e,n,i={}){const{span:a=null,defaults:s=null}=i,c=!!a;ie();const l=e.selectionStart,r=e.selectionEnd,C=e.value.slice(l,r),h=n.children?me.get(n.children.name):null,O={...seedAttrs(n,templateAttrs(n)),...s||{}},u=document.createElement("div");u.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const v=[],b=[];let k=()=>{};const S=()=>{const t={};for(const{attr:o,w:p}of v){const d=p.get();d!=null&&d!==""&&(t[o.name]=d)}return t},y=()=>k(),T=()=>S();let z=null;!c&&n.formats?.length&&(z=M(n.formats.map(t=>[t.value,t.label])),z.addEventListener("change",y),u.appendChild(x("Format",z)));const $=n.attrs||[],P=$.length>ee+2?ee:$.length,D=(t,o)=>{const p=c?a.attrs[t.name]:O[t.name],d=be(t,p,y,T);v.push({attr:t,w:d,initial:d.get()}),o.appendChild(d.el)};for(const t of $.slice(0,P))D(t,u);if(P<$.length){const t=document.createElement("details");t.style.cssText="border-top:1px solid var(--dm-border,#333);padding-top:.6rem;";const o=document.createElement("summary");o.textContent=`More options (${$.length-P})`,o.style.cssText="cursor:pointer;font-size:.75rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",t.appendChild(o);const p=document.createElement("div");p.style.cssText="display:flex;flex-direction:column;gap:.75rem;padding-top:.75rem;",t.appendChild(p);for(const d of $.slice(P))D(d,p);u.appendChild(t)}let w=null,L=null;const N=document.createElement("div");if(!c&&h){const t=document.createElement("div");t.style.cssText=he,t.textContent=`${h.label}s`,u.appendChild(t),N.style.cssText="display:flex;flex-direction:column;gap:.4rem;",u.appendChild(N),L=document.createElement("button"),L.type="button",L.textContent=`+ Add ${h.label.toLowerCase()}`,L.style.cssText="padding:.35rem .75rem;background:transparent;border:1px dashed var(--dm-border,#555);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;font-size:.85em;align-self:flex-start;",L.addEventListener("click",()=>{F({attrs:{},body:templateBody(h)||"Content"}),k()}),u.appendChild(L);const o=templateChildren(n),p=o.length?o:[{attrs:{},body:"Content"}];for(const d of p)F({attrs:seedAttrs(h,d.attrs),body:d.body});C&&(b[0].bodyInput.value=C)}else!c&&!n.selfClosing&&(w=document.createElement("textarea"),w.rows=n.inline?2:4,w.value=C||templateBody(n),w.style.cssText="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;font-family:inherit;box-sizing:border-box;resize:vertical;",w.addEventListener("input",y),u.appendChild(x("Content",w)));function F(t){if(b.length>=(n.children.max??20))return;const o=document.createElement("div");o.style.cssText="display:flex;gap:.35rem;align-items:center;";const p=[];for(const m of h.attrs||[])if(m.type==="icon"){const{el:f,input:B}=X(m.label.toLowerCase(),t.attrs[m.name]||"");f.style.flex=".7",B.addEventListener("input",y),p.push({attr:m,get:()=>B.value.trim()||null}),o.appendChild(f)}else if(m.type==="image"){const{el:f,input:B}=Z(m.label,t.attrs[m.name]||"");f.style.flex="1",B.addEventListener("input",y),p.push({attr:m,get:()=>B.value.trim()||null}),o.appendChild(f)}else if(m.type==="select"){const f=M(m.options);f.value=t.attrs[m.name]||m.options[0][0],f.style.flex=".7",f.addEventListener("change",y),p.push({attr:m,get:()=>f.value||null}),o.appendChild(f)}else{const f=_(m.label,t.attrs[m.name]||"");f.style.flex="1",f.addEventListener("input",y),p.push({attr:m,get:()=>f.value.trim()||null}),o.appendChild(f)}const d=_("Content",t.body||"");d.style.flex="1.2",d.addEventListener("input",y),o.appendChild(d);const g=document.createElement("button");g.type="button",g.textContent="\xD7",g.style.cssText="padding:.3rem .5rem;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;font-size:.85em;flex-shrink:0;";const J={row:o,fields:p,bodyInput:d,remove:g};g.addEventListener("click",()=>{b.length<=(n.children.min??1)||(b.splice(b.indexOf(J),1),o.remove(),k())}),o.appendChild(g),b.push(J),N.appendChild(o),window.I?.scan?.(o)}const q=ae[n.preview];let R=null;if(q){const t=document.createElement("div");t.style.cssText="background:var(--dm-background-alt,#111);border:1px solid var(--dm-border,#333);border-radius:4px;padding:.75rem;min-height:2.5rem;display:flex;align-items:center;overflow-x:auto;",R=document.createElement("span"),t.appendChild(R),u.appendChild(x("Preview",t))}const Y=ue();u.appendChild(x(c?"Tag preview":"Shortcode preview",Y.el));const j=document.createElement("div");j.style.cssText="display:flex;justify-content:flex-end;gap:.75rem;padding-top:.75rem;border-top:1px solid var(--dm-border,#333);";const A=document.createElement("button");A.type="button",A.className="btn btn-ghost",A.textContent="Cancel";const I=document.createElement("button");I.type="button",I.className="btn btn-primary",I.textContent=c?"Apply":`Insert ${n.label}`,j.appendChild(A),j.appendChild(I),u.appendChild(j);const H=()=>h&&b.length?buildChildrenBody(h,b.map(t=>({attrs:Object.fromEntries(t.fields.map(o=>[o.attr.name,o.get()]).filter(([,o])=>o!=null)),body:t.bodyInput.value}))):w?w.value:templateBody(n),V=()=>emitSnippet(n,S(),H(),z?.value),te=t=>{if(n.inline||n.selfClosing||!t.startsWith("["))return t;const o=e.value.slice(0,l),p=e.value.slice(r),d=o===""||o.endsWith(`
5
5
  `)?"":`
6
6
  `,g=p===""||p.startsWith(`
7
7
  `)?"":`
8
- `;return`${a}${t}${g}`};w=()=>{L&&(L.disabled=b.length>=(n.children.max??20));for(const{w:t}of v)t.refresh?.();if(R){const t=Y(c?{...d.attrs,..._()}:S(),c?"":V());R.style.cssText=t.css,R.textContent=t.text}H.update(c?Q(n.name,{...d.attrs,..._()},d.selfClosing):K())};function _(){const t={};for(const{attr:o,w:p,initial:a}of v){const g=p.get();g!==a&&(t[o.name]=g)}return t}const j=E.slideover({title:c?`Edit ${n.label}`:`Insert ${n.label}`,size:h?"lg":"md",position:"right",content:u});return j.open(),window.I?.scan?.(u),w(),A.addEventListener("click",()=>j.close()),I.addEventListener("click",()=>{if(j.close(),c)le(e,d,_());else{const t=ee(K());te(e,l,r,t),e.setSelectionRange(l+t.length,l+t.length)}e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()}),requestAnimationFrame(()=>{u.querySelector("input:not([type=checkbox]):not([type=color]), select, textarea")?.focus()}),j}
8
+ `;return`${d}${t}${g}`};k=()=>{L&&(L.disabled=b.length>=(n.children.max??20));for(const{w:t}of v)t.refresh?.();if(R){const t=q(c?{...a.attrs,...K()}:S(),c?"":H());R.style.cssText=t.css,R.textContent=t.text}Y.update(c?U(a.name,{...a.attrs,...G()},a.selfClosing,{keepEmpty:!!n.block}):V())};function K(){const t={};for(const{attr:o,w:p,initial:d}of v){const g=p.get();g!==d&&(t[o.name]=g)}return t}function G(){const t=K();if(n.block)for(const o of Object.keys(t))t[o]==null&&(t[o]="");return t}const W=E.slideover({title:c?`Edit ${n.label}`:`Insert ${n.label}`,size:h?"lg":"md",position:"right",content:u});return W.open(),window.I?.scan?.(u),k(),A.addEventListener("click",()=>W.close()),I.addEventListener("click",()=>{if(W.close(),c)se(e,a,G(),{keepEmpty:!!n.block});else{const t=te(V());ne(e,l,r,t),e.setSelectionRange(l+t.length,l+t.length)}e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()}),requestAnimationFrame(()=>{u.querySelector("input:not([type=checkbox]):not([type=color]), select, textarea")?.focus()}),W}
@@ -1 +1 @@
1
- import{encodeMessageAttribute as p,decodeMessageAttribute as g}from"../../../public/js/celebrations/message-attrs.mjs";import{replaceRange as d}from"./markdown-toolbar.js";export function parseAttrs(n){const t=String(n??""),r={};for(const[,f,o,e]of t.matchAll(/([\w-]+)=(?:"([^"]*)"|'([^']*)')/g))r[f]=f==="message"?g(o??e??""):o??e??"";const c=t.replace(/([\w-]+)=(?:"[^"]*"|'[^']*')/g,f=>" ".repeat(f.length));for(const[,f]of c.matchAll(/\b([\w-]+)\b/g))f in r||(r[f]=!0);return r}export function serialiseTag(n,t,r){const c=[];for(const[o,e]of Object.entries(t))if(e===!0)c.push(o);else{if(e===!1||e===null||e===void 0||e==="")continue;c.push(`${o}="${o==="message"?p(e):String(e).replace(/"/g,"&quot;")}"`)}const f=c.length?` ${c.join(" ")}`:"";return r?`[${n}${f} /]`:`[${n}${f}]`}function a(n,t,r,c){const f=n.slice(t,r),o=/\/\s*\]$/.test(f),e=f.match(new RegExp(`^\\[${c}([\\s\\S]*?)\\s*/?\\]$`)),s=e?e[1]:"";return{name:c,openTagStart:t,openTagEnd:r,attrStr:s,attrs:parseAttrs(s),selfClosing:o}}function u(n,t,r){const c=new RegExp(`\\[${r}(?=[\\s\\]/])([^\\]]*)\\]`,"g"),f=new RegExp(`\\[/${r}\\]`,"g"),o=[];for(const l of n.matchAll(c)){if(l.index>=t)break;o.push({type:"open",start:l.index,end:l.index+l[0].length})}for(const l of n.matchAll(f)){if(l.index>=t)break;o.push({type:"close",start:l.index})}o.sort((l,i)=>l.start-i.start);let e=0,s=null;for(const l of o)l.type==="open"?(e++,s=l):(e--,e<0&&(e=0,s=null));return e>0&&s&&new RegExp(`\\[/${r}\\]`).test(n.slice(t))?{start:s.start,end:s.end}:null}export function findEnclosingShortcode(n,t){const r=n.value,c=n.selectionStart,f=new Map(t.map(s=>[s.name,s]));let o=null;for(const s of r.matchAll(/\[(\/)?([a-zA-Z][\w-]*)((?:[^\]]*?))\]/g)){const l=s.index,i=l+s[0].length;c<l||c>i||f.has(s[2])&&(o={name:s[2],start:l,end:i,close:!!s[1]})}if(o)if(o.close){const s=u(r,o.start,o.name);if(s)return a(r,s.start,s.end,o.name)}else return a(r,o.start,o.end,o.name);let e=null;for(const s of t){if(s.selfClosing)continue;const l=u(r,c,s.name);l&&(!e||l.start>e.start)&&(e={...l,name:s.name})}return e?a(r,e.start,e.end,e.name):null}export function setAttr(n,t,r,c){return setAttrs(n,t,{[r]:c})}export function setAttrs(n,t,r){const c={...t.attrs};for(const[s,l]of Object.entries(r))l===!1||l===null||l===void 0||l===""?delete c[s]:c[s]=l;const f=serialiseTag(t.name,c,t.selfClosing),o=typeof document<"u"?document.activeElement:null;d(n,t.openTagStart,t.openTagEnd,f),o&&o!==n&&typeof o.focus=="function"&&o.focus();const e=t.openTagStart+f.length;return a(n.value,t.openTagStart,e,t.name)}export function effectAttrKeys(n,t){const r=`${t}-`;return Object.keys(n).filter(c=>c===t||c.startsWith(r))}export function boolState(n,t){return n==null?!!t:!(n===!1||n==="false"||n==="0")}export function boolValue(n,t){return t===!!n.default?null:t?"true":"false"}
1
+ import{encodeMessageAttribute as p,decodeMessageAttribute as g}from"../../../public/js/celebrations/message-attrs.mjs";import{replaceRange as d}from"./markdown-toolbar.js";export function parseAttrs(n){const e=String(n??""),s={};for(const[,r,l,o]of e.matchAll(/([\w-]+)=(?:"([^"]*)"|'([^']*)')/g))s[r]=r==="message"?g(l??o??""):l??o??"";const f=e.replace(/([\w-]+)=(?:"[^"]*"|'[^']*')/g,r=>" ".repeat(r.length));for(const[,r]of f.matchAll(/\b([\w-]+)\b/g))r in s||(s[r]=!0);return s}export function serialiseTag(n,e,s,{keepEmpty:f=!1}={}){const r=[];for(const[o,t]of Object.entries(e))if(t===!0)r.push(o);else{if(t===!1||t===null||t===void 0||t===""&&!f)continue;r.push(`${o}="${o==="message"?p(t):String(t).replace(/"/g,"&quot;")}"`)}const l=r.length?` ${r.join(" ")}`:"";return s?`[${n}${l} /]`:`[${n}${l}]`}function a(n,e,s,f){const r=n.slice(e,s),l=/\/\s*\]$/.test(r),o=r.match(new RegExp(`^\\[${f}([\\s\\S]*?)\\s*/?\\]$`)),t=o?o[1]:"";return{name:f,openTagStart:e,openTagEnd:s,attrStr:t,attrs:parseAttrs(t),selfClosing:l}}function u(n,e,s){const f=new RegExp(`\\[${s}(?=[\\s\\]/])([^\\]]*)\\]`,"g"),r=new RegExp(`\\[/${s}\\]`,"g"),l=[];for(const c of n.matchAll(f)){if(c.index>=e)break;l.push({type:"open",start:c.index,end:c.index+c[0].length})}for(const c of n.matchAll(r)){if(c.index>=e)break;l.push({type:"close",start:c.index})}l.sort((c,i)=>c.start-i.start);let o=0,t=null;for(const c of l)c.type==="open"?(o++,t=c):(o--,o<0&&(o=0,t=null));return o>0&&t&&new RegExp(`\\[/${s}\\]`).test(n.slice(e))?{start:t.start,end:t.end}:null}export function findEnclosingShortcode(n,e){const s=n.value,f=n.selectionStart,r=new Map(e.map(t=>[t.name,t]));let l=null;for(const t of s.matchAll(/\[(\/)?([a-zA-Z0-9][\w-]*)((?:[^\]]*?))\]/g)){const c=t.index,i=c+t[0].length;f<c||f>i||r.has(t[2])&&(l={name:t[2],start:c,end:i,close:!!t[1]})}if(l)if(l.close){const t=u(s,l.start,l.name);if(t)return a(s,t.start,t.end,l.name)}else return a(s,l.start,l.end,l.name);let o=null;for(const t of e){if(t.selfClosing)continue;const c=u(s,f,t.name);c&&(!o||c.start>o.start)&&(o={...c,name:t.name})}return o?a(s,o.start,o.end,o.name):null}export function setAttr(n,e,s,f,r){return setAttrs(n,e,{[s]:f},r)}export function setAttrs(n,e,s,{keepEmpty:f=!1}={}){const r={...e.attrs};for(const[c,i]of Object.entries(s))i===!1||i===null||i===void 0||i===""&&!f?delete r[c]:r[c]=i;const l=serialiseTag(e.name,r,e.selfClosing,{keepEmpty:f}),o=typeof document<"u"?document.activeElement:null;d(n,e.openTagStart,e.openTagEnd,l),o&&o!==n&&typeof o.focus=="function"&&o.focus();const t=e.openTagStart+l.length;return a(n.value,e.openTagStart,t,e.name)}export function effectAttrKeys(n,e){const s=`${e}-`;return Object.keys(n).filter(f=>f===e||f.startsWith(s))}export function boolState(n,e){return n==null?!!e:!(n===!1||n==="false"||n==="0")}export function boolValue(n,e){return e===!!n.default?null:e?"true":"false"}
@@ -5,16 +5,14 @@
5
5
  #block-editor-body stays: admin.css lifts the view-width cap on it. -->
6
6
  <div class="bex-page">
7
7
  <div class="bex">
8
+ <!-- lib/editor-hop.js: find any page or block, recent edits; going there keeps this block's changes. -->
9
+ <div id="block-hop" class="dm-hop-slot"></div>
8
10
  <div class="bex-bar">
9
11
  <div class="bex-ident">
10
12
  <strong data-bind-text="titleText"></strong>
11
13
  <small data-bind-text="metaText.value"></small>
12
14
  <span class="bex-chip is-warn" data-bind-hidden="!dirty.value" title="Changes not saved yet">Unsaved</span>
13
15
  </div>
14
- <span class="bex-spacer"></span>
15
- <select id="block-quick-switch" class="form-input form-input--sm bex-switch" aria-label="Open another block" title="Open another block" data-bind-hidden="isNew">
16
- <option value="">Open another block…</option>
17
- </select>
18
16
  </div>
19
17
 
20
18
  <div class="bex-unsaved" data-bind-hidden="!dirty.value">
@@ -186,7 +184,6 @@
186
184
  .bex-note { margin: 0; }
187
185
  .bex-cursor { font-size: var(--dm-text-xs); font-variant-numeric: tabular-nums; }
188
186
  .bex-spacer { flex: 1; }
189
- .bex-switch { width: auto; max-width: 16rem; }
190
187
  .bex-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
191
188
  background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
192
189
  .bex-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
@@ -1,16 +1,14 @@
1
1
  <!-- Page editor. No page heading: the Tool banner above is the heading ("Pages › …", set by
2
2
  admin/js/views/page-editor.js), with Back, Editor reference, View on the site and Save in it. This bar
3
- carries the page switcher and the unsaved state; Ctrl+S saves; leaving with unsaved changes asks and
4
- keeps a draft.
3
+ carries the unsaved state; Ctrl+S saves; leaving with unsaved changes asks and keeps a draft. The row
4
+ above it (#page-hop, lib/editor-hop.js) finds any page or block, and lists recent edits and the blocks
5
+ this page uses - going to one of those keeps this page's changes without asking.
5
6
  Bound with M.applyBindings (see the view): every field is a data-model, and what shows - unsaved,
6
7
  the problem note, the view mode, the CSS pane, Live Preview, History - is read from the state. The
7
8
  router renders this file through _.render, which eats any {{…}}: bindings only, never mustache. -->
9
+ <div id="page-hop" class="dm-hop-slot"></div>
8
10
  <div class="pex-bar">
9
- <select id="quick-switch" class="form-select form-select-sm" style="width:auto;" aria-label="Switch to another page"
10
- hidden data-bind-hidden="!hasPages" data-bind-disabled="dirty.value" data-on-change="switchPage">
11
- <option value="">Switch page...</option>
12
- </select>
13
- <span data-help-title="Switching pages" data-help="Opens another page in the editor. It is locked while this page has unsaved changes - save or discard them first."></span>
11
+ <span data-help-title="Pages and blocks" data-help="Find any page or block in the box above, or pick a recent one. Your unsaved changes here are kept while you go - come back and they are still in place. A dot marks anything with changes not saved yet."></span>
14
12
  <span class="pex-chip" hidden data-bind-hidden="!dirty.value" title="Changes not saved yet">Unsaved</span>
15
13
  <small class="pex-problem" hidden data-bind-hidden="!(dirty.value && problem.value)" data-bind-text="problem.value"></small>
16
14
  <span class="pex-spacer"></span>
@@ -1,14 +1,14 @@
1
- import{api as $}from"../api.js";import{addBannerAction as J}from"../lib/plugin-chrome.js";import{helpPopovers as at}from"../lib/help-popover.js";import{openMenuAt as Fe,sidebarCount as nt}from"../lib/tool-kit.js";import{createSimpleEditor as Ie}from"../lib/simple-editor.js";import{checkForm as st,draftKey as j,draftOffer as rt,escapeHtml as Ne,extractPlaceholders as ct,formFromBlock as lt,formatHtml as it,payloadFromForm as ut,previewMessage as dt,previewShell as pt,rawPlaceholders as We,renderPreview as mt,sampleValue as _e,scopeName as vt,shortcodeFor as ht,snapshot as de}from"../lib/block-editor-arrange.js";import{fmtDateTime as Be}from"../lib/dates.js";const ft=/^\/blocks\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,G=u=>String(u||"").split("?")[0],pe="block-editor.mode",bt=["write","split","preview"],He="block-editor.preview",Ve=["full","1024","390"],yt="/__block-preview",me="dm-block-preview-root",Ke={title:"Block preview",layout:"blank",breadcrumbs:!1},kt={ADD_ATTR:["target","loading"],ALLOW_DATA_ATTR:!0,FORCE_BODY:!0},wt=()=>window.DOMPurify?.sanitize?u=>window.DOMPurify.sanitize(u,kt):null,gt=`
1
+ import{api as T}from"../api.js";import{addBannerAction as J}from"../lib/plugin-chrome.js";import{helpPopovers as st}from"../lib/help-popover.js";import{openMenuAt as qe,sidebarCount as rt}from"../lib/tool-kit.js";import{createSimpleEditor as Fe}from"../lib/simple-editor.js";import{checkForm as ct,draftKey as O,draftOffer as lt,escapeHtml as He,extractPlaceholders as it,formFromBlock as ut,formatHtml as dt,payloadFromForm as pt,previewMessage as mt,previewShell as vt,rawPlaceholders as Ie,renderPreview as ft,sampleValue as Ne,scopeName as ht,shortcodeFor as bt,snapshot as pe}from"../lib/block-editor-arrange.js";import{fmtDateTime as We}from"../lib/dates.js";import{hold as yt,mountEditorHop as kt,takeHeld as wt}from"../lib/editor-hop.js";import{isHop as gt}from"../lib/editor-hop-arrange.js";const St=/^\/blocks\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,G=u=>String(u||"").split("?")[0],me="block-editor.mode",Et=["write","split","preview"],_e="block-editor.preview",Be=["full","1024","390"],Mt="/__block-preview",ve="dm-block-preview-root",Ve={title:"Block preview",layout:"blank",breadcrumbs:!1},Ct={ADD_ATTR:["target","loading"],ALLOW_DATA_ATTR:!0,FORCE_BODY:!0},xt=()=>window.DOMPurify?.sanitize?u=>window.DOMPurify.sanitize(u,Ct):null,At=`
2
2
  <div class="bex-sample" data-key="{{key}}" tabindex="0">
3
3
  <label class="bex-sample-label" for="bex-sample-{{key}}">{{label}}</label>
4
4
  <input id="bex-sample-{{key}}" type="text" class="form-input form-input--sm" data-bind-value="value" data-sample="{{key}}" placeholder="Sample {{key}}">
5
5
  <button type="button" class="bex-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
6
- </div>`;let h=null,Ue=!1;function St(){Ue||!window.R?.use||(Ue=!0,R.use((u,p,s)=>{if(!(ft.test(p?.path||"")&&G(u?.path)!==G(p?.path))||!h?.dirty())return s();const c=h;return E.confirm(`You have unsaved changes to ${Ne(c.label())}. Leave anyway? They are kept as a draft for your next visit.`).then(H=>{H?s():history.replaceState(null,"",c.hash)})}),M.subscribe("router:afterChange",({to:u}={})=>{if(h&&G(u?.path)!==h.path){const p=h;h=null,p.dispose()}}))}function Et(u,p){const s=u.querySelector(".dm-plugin-banner-name"),g=s?.lastChild;g&&g.nodeType===3&&s.querySelector(".dm-plugin-banner-sep")&&(g.textContent=p)}function Q(u){return navigator.clipboard?.writeText(u).then(()=>!0,()=>!1)??Promise.resolve(!1)}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(u){if(St(),h){const e=h;h=null,e.dispose()}const p=u.get(0),s=p.querySelector(".bex")||p,g=[],c=e=>(g.push(e),e);let H=!1;const C=()=>!H&&s.isConnected,ve=location.hash.match(/^#\/blocks\/edit\/([^/?#]+)/),l=ve?decodeURIComponent(ve[1]):null,d=!l,Ye=location.hash,ze=G(location.hash.slice(1)),Je=d&&new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",he=E.loader(p,{type:"dots"});let y=null,X=[],Z=[];try{[y,X,Z]=await Promise.all([l?$.blocks.get(l):Promise.resolve(null),$.blocks.list().catch(()=>[]),$.projects.list().catch(()=>[])])}catch(e){he.destroy(),E.toast(e.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}if(he.destroy(),!s.isConnected)return;const fe=(Array.isArray(X)?X:[]).map(e=>e.name).sort((e,t)=>e.localeCompare(t));let be=y?.meta||{};const v=lt(y?{...y,name:l}:null,{project:Je}),a={};for(const[e,t]of Object.entries(v))a[e]=M.observable(t);const ee=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].peek()])),ye=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].value])),O=M.observable(de(v)),D=M.computed(()=>de(ye())!==O.value),q=M.observable(!1),V=M.observable(!1),K=M.computed(()=>st(ye(),{isNew:d,taken:fe}));let ke="split";try{ke=bt.includes(S.get(pe))?S.get(pe):"split"}catch{}const k=M.observable(ke);c(M.effect(()=>{const e=k.value;try{S.set(pe,e)}catch{}}));const we=M.observable("Ln 1, Col 1");let L={};try{L=S.get(He)||{}}catch{}const te=M.observable(Ve.includes(L.width)?L.width:"full"),U=M.observable(String(L.repeat)==="3"?"3":"1"),m=M.observable(typeof L.theme=="string"?L.theme:""),x=M.observable("");c(M.effect(()=>{const e={width:te.value,repeat:U.value,theme:m.value};try{S.set(He,e)}catch{}}));const f={},P={},Y=M.observable(0),oe=M.observable([]),ge=()=>We(a.content.peek());c(M.effect(()=>{const e=ct(a.content.value),t=We(a.content.value);let n=!1;for(const o of e){const w=t.has(o);if(!(o in f)||o in P&&P[o]!==w){const B=o in f;if(f[o]=_e(o,new Date,{raw:w}),P[o]=w,B){n=!0;const i=s.querySelector(`.bex-samples [data-sample="${CSS.escape(o)}"]`);i&&(i.value=f[o])}}}oe.value=e.map(o=>({key:o,label:t.has(o)?`{{{${o}}}}`:`{{${o}}}`,value:f[o]})),n&&queueMicrotask(()=>Y.value++)}));const Ge={...a,dirty:D,saving:q,mode:k,cursor:we,sampleRows:oe,isNew:d,notNew:!d,pvWidth:te,pvRepeat:U,pvTheme:m,pvStatus:x,titleText:d?"New block":l,metaText:M.computed(()=>{if(d)return"Not saved yet";const e=[];return(y?.updatedAt||y?.meta?.updatedAt)&&e.push(`changed ${Be(y.updatedAt||y.meta.updatedAt)}`),e.push(`${a.content.value.split(`
7
- `).length} lines`),a.css.value.trim()&&e.push(`${Math.ceil(a.css.value.length/1024)} KB CSS`),e.join(" \xB7 ")}),nameErr:M.computed(()=>(V.value||a.name.value)&&K.value.errors.name||""),cssErr:M.computed(()=>K.value.errors.css||""),scopeHint:M.computed(()=>`[data-block="${vt(a.name.value)}"]`),noSamples:M.computed(()=>oe.value.length===0),noSamplesText:"No {{placeholders}} in the template yet.",modeCls:M.computed(()=>`bex-body is-${k.value}`),cannotSave:M.computed(()=>q.value||V.value&&Object.keys(K.value.errors).length>0)},ae=s.querySelector("#block-project");for(const e of Array.isArray(Z)?Z:[]){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,ae.appendChild(t)}if(v.project&&![...ae.options].some(e=>e.value===v.project)){const e=document.createElement("option");e.value=v.project,e.textContent=`${v.project} (no longer a project)`,ae.appendChild(e)}const F=s.querySelector("#block-quick-switch");for(const e of fe){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),F.appendChild(t)}const b=s.querySelector(".bex-samples");b.innerHTML=gt;let A=null,N=null,r=null;function Se(e){for(const[t,n]of Object.entries(e))a[t]&&(a[t].value=n);A.setValue(String(e.content??"")),N.setValue(String(e.css??""))}const ne=s.querySelector("#block-preview-frame");let T="none",W=0;const Qe="As your site shows it \xB7 not saved",Ee=()=>{if(!C()||k.peek()==="write"||T!=="ready")return;const e={content:a.content.peek(),css:a.css.peek(),name:a.name.peek()},t=wt();if(!t){x.value="The preview needs the HTML cleaner, which did not load - reload the admin.";return}const n=mt(e,f,{repeat:Number(U.peek()),sanitize:t});try{ne.contentWindow?.postMessage({source:"dm-block-editor",type:"paint",tick:W,html:n},"*")}catch{}},Me=e=>{const t=dt(e,ne.contentWindow,W);if(!(!t||!C())){if(t.type==="save"){z();return}if(T=t.host?"ready":"failed",T==="failed"){x.value="Your site's page came back without a place for the block - the preview cannot show it.";return}x.peek()==="Loading your site\u2026"&&(x.value=Qe),Ee()}};window.addEventListener("message",Me),c(()=>window.removeEventListener("message",Me));async function Ce(){const e=++W;T="loading",x.value="Loading your site\u2026";let t;try{const n=m.peek()?{...Ke,theme:m.peek()}:Ke;({html:t}=await $.pages.previewFull(yt,n,`<div id="${me}"></div>`))}catch(n){t=`<!doctype html><html><head><meta charset="utf-8"></head><body style="font:14px system-ui;margin:0"><div id="${me}" style="padding:1rem"></div></body></html>`,x.value=`Without your site's styles - ${n?.message||"the page did not render"}`}!C()||e!==W||(ne.srcdoc=pt(t,e,me))}let se=0;c(M.effect(()=>{if(a.content.value,a.css.value,a.name.value,Y.value,U.value,k.value!=="write"){if(T==="none"){Ce();return}cancelAnimationFrame(se),se=requestAnimationFrame(Ee)}}));let xe=m.peek();c(M.effect(()=>{const e=m.value;e!==xe&&(xe=e,T!=="none"&&Ce())})),c(()=>{cancelAnimationFrame(se),W++}),$.theme.catalog().then(({themes:e=[],custom:t=[]}={})=>{if(!C())return;const n=s.querySelector("#block-preview-theme"),o=i=>String(i).replace(/-/g," ").replace(/\b\w/g,ie=>ie.toUpperCase()),w=[...e.filter(i=>!String(i).startsWith("admin-")).map(i=>({id:i,label:o(i)})),...(Array.isArray(t)?t:[]).filter(i=>i?.id).map(i=>({id:i.id,label:i.label||o(i.id)}))],B=w.map(i=>i.id);for(const{id:i,label:ie}of w){const ue=document.createElement("option");ue.value=i,ue.textContent=ie,n.appendChild(ue)}m.peek()&&!B.includes(m.peek())?m.value="":n.value=m.peek()}).catch(()=>{s.querySelector("#block-preview-theme")?.remove(),C()&&m.peek()&&(m.value="")});const Ae=e=>{const t=e.target?.dataset?.sample;t&&(f[t]=e.target.value,delete P[t],Y.value++)};b.addEventListener("input",Ae),c(()=>b.removeEventListener("input",Ae));function re(e){r.focus(),r.setRangeText(e,r.selectionStart,r.selectionEnd,"end"),r.dispatchEvent(new Event("input",{bubbles:!0}))}async function Le(e){switch(e){case"undo":r.focus(),document.execCommand("undo");break;case"redo":r.focus(),document.execCommand("redo");break;case"cut":r.selectionStart!==r.selectionEnd&&(await Q(r.value.slice(r.selectionStart,r.selectionEnd)),re(""));break;case"copy":r.selectionStart!==r.selectionEnd&&await Q(r.value.slice(r.selectionStart,r.selectionEnd));break;case"paste":try{re(await navigator.clipboard.readText())}catch{E.toast("Use Ctrl+V to paste.",{type:"info"})}break;case"select-all":r.focus(),r.select();break;case"format":{const t=it(r.value);A.setValue(t),a.content.value=t;break}default:break}}async function z(){if(q.peek())return;V.value=!0;const{errors:e,name:t}=K.peek();if(Object.keys(e).length){E.toast(e.name||e.css,{type:"warning"});return}const n=d?t:l;q.value=!0;try{const o=ut(ee(),be);if(await $.blocks.put(n,o),!C())return;be=o.meta,O.value=de(ee());try{S.remove?S.remove(j(l)):S.set(j(l),null)}catch{}nt("#/blocks"),E.toast(d?`Created "${n}".`:"Block saved.",{type:"success"}),d&&(location.hash=`#/blocks/edit/${encodeURIComponent(n)}`)}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{q.value=!1}}async function Xe(){D.peek()&&await E.confirm("Discard the changes you have not saved?")&&(V.value=!1,Se(Ze(O.peek())))}function Ze(e){try{const[t,n,o,w,B]=JSON.parse(e);return{name:t,project:n,bundled:o,content:w,css:B}}catch{return v}}const et=M.applyBindings(Ge,s,{methods:{save:()=>z(),discard:()=>Xe(),setWrite:()=>{k.value="write"},setSplit:()=>{k.value="split"},setPreview:()=>{k.value="preview"},setWidth:e=>{Ve.includes(String(e))&&(te.value=String(e))},act:e=>Le(e)}});c(()=>et.dispose()),c(at(s,{position:"bottom"})),A=Ie(s.querySelector(".bex-write"),{initialValue:v.content,language:"html",autoIndent:!0,onChange:e=>{a.content.value=e},onCursor:({line:e,col:t})=>{we.value=`Ln ${e}, Col ${t}`}}),N=Ie(s.querySelector(".bex-css"),{initialValue:v.css,language:"css",autoIndent:!0,onChange:e=>{a.css.value=e}}),A.textarea.setAttribute("aria-label","Template HTML"),A.textarea.placeholder=`<div class="card mb-3">
6
+ </div>`;let f=null,Ye=!1;function Lt(){Ye||!window.R?.use||(Ye=!0,R.use((u,p,s)=>{if(!(St.test(p?.path||"")&&G(u?.path)!==G(p?.path))||!f?.dirty())return s();const r=f;return gt(p?.path,u?.path)?(yt(r.item),s()):E.confirm(`You have unsaved changes to ${He(r.label())}. Leave anyway? They are kept as a draft for your next visit.`).then(_=>{_?s():history.replaceState(null,"",r.hash)})}),M.subscribe("router:afterChange",({to:u}={})=>{if(f&&G(u?.path)!==f.path){const p=f;f=null,p.dispose()}}))}function Tt(u,p){const s=u.querySelector(".dm-plugin-banner-name"),C=s?.lastChild;C&&C.nodeType===3&&s.querySelector(".dm-plugin-banner-sep")&&(C.textContent=p)}function Q(u){return navigator.clipboard?.writeText(u).then(()=>!0,()=>!1)??Promise.resolve(!1)}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(u){if(Lt(),f){const e=f;f=null,e.dispose()}const p=u.get(0),s=p.querySelector(".bex")||p,C=[],r=e=>(C.push(e),e);let _=!1;const x=()=>!_&&s.isConnected,fe=location.hash.match(/^#\/blocks\/edit\/([^/?#]+)/),l=fe?decodeURIComponent(fe[1]):null,d=!l,Ke=location.hash,Ue=G(location.hash.slice(1)),ze=d&&new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",he=E.loader(p,{type:"dots"});let y=null,B=[],X=[],be=[];try{[y,B,X,be]=await Promise.all([l?T.blocks.get(l):Promise.resolve(null),T.blocks.list().catch(()=>[]),T.projects.list().catch(()=>[]),T.pages.list().catch(()=>[])])}catch(e){he.destroy(),E.toast(e.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}if(he.destroy(),!s.isConnected)return;const Je=(Array.isArray(B)?B:[]).map(e=>e.name).sort((e,t)=>e.localeCompare(t));let ye=y?.meta||{};const v=ut(y?{...y,name:l}:null,{project:ze}),a={};for(const[e,t]of Object.entries(v))a[e]=M.observable(t);const Z=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].peek()])),ke=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].value])),D=M.observable(pe(v)),P=M.computed(()=>pe(ke())!==D.value),q=M.observable(!1),V=M.observable(!1),Y=M.computed(()=>ct(ke(),{isNew:d,taken:Je}));let we="split";try{we=Et.includes(S.get(me))?S.get(me):"split"}catch{}const k=M.observable(we);r(M.effect(()=>{const e=k.value;try{S.set(me,e)}catch{}}));const ge=M.observable("Ln 1, Col 1");let $={};try{$=S.get(_e)||{}}catch{}const ee=M.observable(Be.includes($.width)?$.width:"full"),K=M.observable(String($.repeat)==="3"?"3":"1"),m=M.observable(typeof $.theme=="string"?$.theme:""),A=M.observable("");r(M.effect(()=>{const e={width:ee.value,repeat:K.value,theme:m.value};try{S.set(_e,e)}catch{}}));const h={},F={},U=M.observable(0),te=M.observable([]),Se=()=>Ie(a.content.peek());r(M.effect(()=>{const e=it(a.content.value),t=Ie(a.content.value);let n=!1;for(const o of e){const g=t.has(o);if(!(o in h)||o in F&&F[o]!==g){const W=o in h;if(h[o]=Ne(o,new Date,{raw:g}),F[o]=g,W){n=!0;const i=s.querySelector(`.bex-samples [data-sample="${CSS.escape(o)}"]`);i&&(i.value=h[o])}}}te.value=e.map(o=>({key:o,label:t.has(o)?`{{{${o}}}}`:`{{${o}}}`,value:h[o]})),n&&queueMicrotask(()=>U.value++)}));const Ge={...a,dirty:P,saving:q,mode:k,cursor:ge,sampleRows:te,isNew:d,notNew:!d,pvWidth:ee,pvRepeat:K,pvTheme:m,pvStatus:A,titleText:d?"New block":l,metaText:M.computed(()=>{if(d)return"Not saved yet";const e=[];return(y?.updatedAt||y?.meta?.updatedAt)&&e.push(`changed ${We(y.updatedAt||y.meta.updatedAt)}`),e.push(`${a.content.value.split(`
7
+ `).length} lines`),a.css.value.trim()&&e.push(`${Math.ceil(a.css.value.length/1024)} KB CSS`),e.join(" \xB7 ")}),nameErr:M.computed(()=>(V.value||a.name.value)&&Y.value.errors.name||""),cssErr:M.computed(()=>Y.value.errors.css||""),scopeHint:M.computed(()=>`[data-block="${ht(a.name.value)}"]`),noSamples:M.computed(()=>te.value.length===0),noSamplesText:"No {{placeholders}} in the template yet.",modeCls:M.computed(()=>`bex-body is-${k.value}`),cannotSave:M.computed(()=>q.value||V.value&&Object.keys(Y.value.errors).length>0)},oe=s.querySelector("#block-project");for(const e of Array.isArray(X)?X:[]){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,oe.appendChild(t)}if(v.project&&![...oe.options].some(e=>e.value===v.project)){const e=document.createElement("option");e.value=v.project,e.textContent=`${v.project} (no longer a project)`,oe.appendChild(e)}const ae={kind:"block",id:l||"",label:l||""},b=s.querySelector(".bex-samples");b.innerHTML=At;let L=null,H=null,c=null;function ne(e){for(const[t,n]of Object.entries(e))a[t]&&(a[t].value=n);L.setValue(String(e.content??"")),H.setValue(String(e.css??""))}const se=s.querySelector("#block-preview-frame");let j="none",N=0;const Qe="As your site shows it \xB7 not saved",Ee=()=>{if(!x()||k.peek()==="write"||j!=="ready")return;const e={content:a.content.peek(),css:a.css.peek(),name:a.name.peek()},t=xt();if(!t){A.value="The preview needs the HTML cleaner, which did not load - reload the admin.";return}const n=ft(e,h,{repeat:Number(K.peek()),sanitize:t});try{se.contentWindow?.postMessage({source:"dm-block-editor",type:"paint",tick:N,html:n},"*")}catch{}},Me=e=>{const t=mt(e,se.contentWindow,N);if(!(!t||!x())){if(t.type==="save"){z();return}if(j=t.host?"ready":"failed",j==="failed"){A.value="Your site's page came back without a place for the block - the preview cannot show it.";return}A.peek()==="Loading your site\u2026"&&(A.value=Qe),Ee()}};window.addEventListener("message",Me),r(()=>window.removeEventListener("message",Me));async function Ce(){const e=++N;j="loading",A.value="Loading your site\u2026";let t;try{const n=m.peek()?{...Ve,theme:m.peek()}:Ve;({html:t}=await T.pages.previewFull(Mt,n,`<div id="${ve}"></div>`))}catch(n){t=`<!doctype html><html><head><meta charset="utf-8"></head><body style="font:14px system-ui;margin:0"><div id="${ve}" style="padding:1rem"></div></body></html>`,A.value=`Without your site's styles - ${n?.message||"the page did not render"}`}!x()||e!==N||(se.srcdoc=vt(t,e,ve))}let re=0;r(M.effect(()=>{if(a.content.value,a.css.value,a.name.value,U.value,K.value,k.value!=="write"){if(j==="none"){Ce();return}cancelAnimationFrame(re),re=requestAnimationFrame(Ee)}}));let xe=m.peek();r(M.effect(()=>{const e=m.value;e!==xe&&(xe=e,j!=="none"&&Ce())})),r(()=>{cancelAnimationFrame(re),N++}),T.theme.catalog().then(({themes:e=[],custom:t=[]}={})=>{if(!x())return;const n=s.querySelector("#block-preview-theme"),o=i=>String(i).replace(/-/g," ").replace(/\b\w/g,ue=>ue.toUpperCase()),g=[...e.filter(i=>!String(i).startsWith("admin-")).map(i=>({id:i,label:o(i)})),...(Array.isArray(t)?t:[]).filter(i=>i?.id).map(i=>({id:i.id,label:i.label||o(i.id)}))],W=g.map(i=>i.id);for(const{id:i,label:ue}of g){const de=document.createElement("option");de.value=i,de.textContent=ue,n.appendChild(de)}m.peek()&&!W.includes(m.peek())?m.value="":n.value=m.peek()}).catch(()=>{s.querySelector("#block-preview-theme")?.remove(),x()&&m.peek()&&(m.value="")});const Ae=e=>{const t=e.target?.dataset?.sample;t&&(h[t]=e.target.value,delete F[t],U.value++)};b.addEventListener("input",Ae),r(()=>b.removeEventListener("input",Ae));function ce(e){c.focus(),c.setRangeText(e,c.selectionStart,c.selectionEnd,"end"),c.dispatchEvent(new Event("input",{bubbles:!0}))}async function Le(e){switch(e){case"undo":c.focus(),document.execCommand("undo");break;case"redo":c.focus(),document.execCommand("redo");break;case"cut":c.selectionStart!==c.selectionEnd&&(await Q(c.value.slice(c.selectionStart,c.selectionEnd)),ce(""));break;case"copy":c.selectionStart!==c.selectionEnd&&await Q(c.value.slice(c.selectionStart,c.selectionEnd));break;case"paste":try{ce(await navigator.clipboard.readText())}catch{E.toast("Use Ctrl+V to paste.",{type:"info"})}break;case"select-all":c.focus(),c.select();break;case"format":{const t=dt(c.value);L.setValue(t),a.content.value=t;break}default:break}}async function z(){if(q.peek())return;V.value=!0;const{errors:e,name:t}=Y.peek();if(Object.keys(e).length){E.toast(e.name||e.css,{type:"warning"});return}const n=d?t:l;q.value=!0;try{const o=pt(Z(),ye);if(await T.blocks.put(n,o),!x())return;ye=o.meta,D.value=pe(Z());try{S.remove?S.remove(O(l)):S.set(O(l),null)}catch{}rt("#/blocks"),E.toast(d?`Created "${n}".`:"Block saved.",{type:"success"}),d&&(location.hash=`#/blocks/edit/${encodeURIComponent(n)}`)}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{q.value=!1}}async function Xe(){P.peek()&&await E.confirm("Discard the changes you have not saved?")&&(V.value=!1,ne(Ze(D.peek())))}function Ze(e){try{const[t,n,o,g,W]=JSON.parse(e);return{name:t,project:n,bundled:o,content:g,css:W}}catch{return v}}const et=M.applyBindings(Ge,s,{methods:{save:()=>z(),discard:()=>Xe(),setWrite:()=>{k.value="write"},setSplit:()=>{k.value="split"},setPreview:()=>{k.value="preview"},setWidth:e=>{Be.includes(String(e))&&(ee.value=String(e))},act:e=>Le(e)}});r(()=>et.dispose());const tt=kt(s.querySelector("#block-hop"),{current:ae,pages:be,blocks:B});r(()=>tt.dispose()),r(st(s,{position:"bottom"})),L=Fe(s.querySelector(".bex-write"),{initialValue:v.content,language:"html",autoIndent:!0,onChange:e=>{a.content.value=e},onCursor:({line:e,col:t})=>{ge.value=`Ln ${e}, Col ${t}`}}),H=Fe(s.querySelector(".bex-css"),{initialValue:v.css,language:"css",autoIndent:!0,onChange:e=>{a.css.value=e}}),L.textarea.setAttribute("aria-label","Template HTML"),L.textarea.placeholder=`<div class="card mb-3">
8
8
  <div class="card-body">
9
9
  <h3>{{title}}</h3>
10
10
  <p>{{message}}</p>
11
11
  </div>
12
- </div>`,N.textarea.setAttribute("aria-label","Custom CSS"),N.textarea.placeholder=`/* Scoped to this block - e.g.
12
+ </div>`,H.textarea.setAttribute("aria-label","Custom CSS"),H.textarea.placeholder=`/* Scoped to this block - e.g.
13
13
  color: var(--dm-primary);
14
- h3 { font-size: 1.25rem; } */`,c(()=>{A.destroy(),N.destroy()}),r=A.textarea;const Te=s.querySelector(".bex-toolbar"),Re=e=>{const t=e.target.closest("[data-action]");t&&Le(t.dataset.action)};Te.addEventListener("click",Re),c(()=>Te.removeEventListener("click",Re));const $e=()=>{const e=F.value;e&&e!==l?location.hash=`#/blocks/edit/${encodeURIComponent(e)}`:F.value=l||""};F.addEventListener("change",$e),c(()=>F.removeEventListener("change",$e));const ce=e=>ge().has(e)?`{{{${e}}}}`:`{{${e}}}`,tt=e=>[{header:ce(e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>re(ce(e))},{label:"Copy placeholder",icon:"copy",action:async()=>{const t=ce(e);E.toast(await Q(t)?"Copied.":t,{type:"info"})}},{label:"Reset sample value",icon:"rotate-ccw",action:()=>{const t=ge().has(e);f[e]=_e(e,new Date,{raw:t}),P[e]=t;const n=b.querySelector(`[data-sample="${CSS.escape(e)}"]`);n&&(n.value=f[e]),Y.value++}}],ot=E.contextMenu(b,{cursor:"glyph",match:".bex-sample",inherit:!1,density:"compact",items:e=>tt(e.dataset.key)});c(()=>ot?.destroy?.());const je=e=>{const t=e.target.closest(".bex-kebab");t&&Fe(t)},Oe=e=>{const t=e.target.closest(".bex-sample");t&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),Fe(t))};b.addEventListener("click",je),b.addEventListener("keydown",Oe),c(()=>{b.removeEventListener("click",je),b.removeEventListener("keydown",Oe)}),Et(p,d?"New block":l),J(u,{id:"block-editor-list",icon:"arrow-left",title:"All blocks",onClick:()=>{location.hash="#/blocks"}}),d?p.querySelector('[data-banner-action="block-editor-shortcode"]')?.remove():J(u,{id:"block-editor-shortcode",icon:"copy",title:"Copy shortcode",onClick:async()=>{const e=ht(l);E.toast(await Q(e)?"Shortcode copied - set the collection slug.":e,{type:"info",duration:5e3})}}),J(u,{id:"block-editor-guide",icon:"book",title:"Blocks guide",onClick:()=>{location.hash="#/docs/usage/blocks"}}),J(u,{id:"block-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>z()});const De=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),z())};document.addEventListener("keydown",De,!0),c(()=>document.removeEventListener("keydown",De,!0));const qe=()=>{if(D.peek())try{S.set(j(l),{form:ee(),base:O.peek(),at:new Date().toISOString()})}catch{}},Pe=e=>{D.peek()&&(qe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Pe),c(()=>window.removeEventListener("beforeunload",Pe)),h={hash:Ye,path:ze,dirty:()=>D.peek(),label:()=>d?"the new block":`"${l}"`,dispose(){H=!0,qe(),g.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(s);let le=null;try{le=S.get(j(l))}catch{}const _=rt(le,O.peek());if(le)try{S.remove?S.remove(j(l)):S.set(j(l),null)}catch{}if(_){const e=_.at?` (${Be(_.at)})`:"",t=d?"a new block":`"${Ne(l)}"`,n=_.stale?`You left unsaved changes to ${t}${e}, but the block has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${e}. Restore them?`;if(await E.confirm(n)&&C()){const o={...v,..._.form};d||(o.name=l),Se(o)}}}};
14
+ h3 { font-size: 1.25rem; } */`,r(()=>{L.destroy(),H.destroy()}),c=L.textarea;const Te=s.querySelector(".bex-toolbar"),Re=e=>{const t=e.target.closest("[data-action]");t&&Le(t.dataset.action)};Te.addEventListener("click",Re),r(()=>Te.removeEventListener("click",Re));const le=e=>Se().has(e)?`{{{${e}}}}`:`{{${e}}}`,ot=e=>[{header:le(e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>ce(le(e))},{label:"Copy placeholder",icon:"copy",action:async()=>{const t=le(e);E.toast(await Q(t)?"Copied.":t,{type:"info"})}},{label:"Reset sample value",icon:"rotate-ccw",action:()=>{const t=Se().has(e);h[e]=Ne(e,new Date,{raw:t}),F[e]=t;const n=b.querySelector(`[data-sample="${CSS.escape(e)}"]`);n&&(n.value=h[e]),U.value++}}],at=E.contextMenu(b,{cursor:"glyph",match:".bex-sample",inherit:!1,density:"compact",items:e=>ot(e.dataset.key)});r(()=>at?.destroy?.());const $e=e=>{const t=e.target.closest(".bex-kebab");t&&qe(t)},je=e=>{const t=e.target.closest(".bex-sample");t&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),qe(t))};b.addEventListener("click",$e),b.addEventListener("keydown",je),r(()=>{b.removeEventListener("click",$e),b.removeEventListener("keydown",je)}),Tt(p,d?"New block":l),J(u,{id:"block-editor-list",icon:"arrow-left",title:"All blocks",onClick:()=>{location.hash="#/blocks"}}),d?p.querySelector('[data-banner-action="block-editor-shortcode"]')?.remove():J(u,{id:"block-editor-shortcode",icon:"copy",title:"Copy shortcode",onClick:async()=>{const e=bt(l);E.toast(await Q(e)?"Shortcode copied - set the collection slug.":e,{type:"info",duration:5e3})}}),J(u,{id:"block-editor-guide",icon:"book",title:"Blocks guide",onClick:()=>{location.hash="#/docs/usage/blocks"}}),J(u,{id:"block-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>z()});const Oe=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),z())};document.addEventListener("keydown",Oe,!0),r(()=>document.removeEventListener("keydown",Oe,!0));const De=()=>{if(P.peek())try{S.set(O(l),{form:Z(),base:D.peek(),at:new Date().toISOString()})}catch{}},Pe=e=>{P.peek()&&(De(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Pe),r(()=>window.removeEventListener("beforeunload",Pe)),f={hash:Ke,path:Ue,item:ae,dirty:()=>P.peek(),label:()=>d?"the new block":`"${l}"`,dispose(){_=!0,De(),C.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(s);let ie=null;try{ie=S.get(O(l))}catch{}const w=lt(ie,D.peek()),nt=wt(ae);if(ie)try{S.remove?S.remove(O(l)):S.set(O(l),null)}catch{}if(w&&nt&&!w.stale){const e={...v,...w.form};d||(e.name=l),ne(e),E.toast("Your unsaved changes to this block are back.",{type:"info"})}else if(w){const e=w.at?` (${We(w.at)})`:"",t=d?"a new block":`"${He(l)}"`,n=w.stale?`You left unsaved changes to ${t}${e}, but the block has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${e}. Restore them?`;if(await E.confirm(n)&&x()){const o={...v,...w.form};d||(o.name=l),ne(o)}}}};
@@ -1,4 +1,4 @@
1
- import{api as g}from"../api.js";import{CardBuilder as gt}from"../lib/card-builder.js";import{TimelineBuilder as bt}from"../lib/timeline-builder.js";import{attachEditorKeybindings as ht,createToolbar as yt,insertAtCursor as wt}from"../lib/markdown-toolbar.js";import{EffectsBuilder as kt}from"../lib/effects-builder.js";import{attachContextMenu as Et}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as Mt}from"../lib/shortcode-insert.js";import{populateThemeSelect as St}from"../lib/themes.js";import{createFoldingManager as Tt}from"../lib/editor-folding.js";import{openScribeComposer as Ct}from"../lib/scribe-composer.js";import{openCrudTutorial as xt}from"../lib/crud-tutorial.js";import{addBannerAction as j}from"../lib/plugin-chrome.js";import{helpPopovers as Ke}from"../lib/help-popover.js";import{onceClosed as ye,openMenuAt as Ue}from"../lib/tool-kit.js";import{pickMedia as Pt}from"../lib/media-picker.js";import{openPageClone as At}from"../lib/page-clone.js";import{openEditorReference as _e}from"../lib/page-editor-reference.js";import{cleanType as Z,DEFAULT_TYPE as Q,EDITOR_FONTS as Lt,fontHref as Ot,isDefaultType as Dt,LINE_HEIGHTS as Rt,SIZES as Nt,typeVars as Je}from"../lib/editor-type.js";import{getProjectFromHash as Ft}from"../lib/project-context.js";import{attachSmoothCaret as We,getCaretPreference as It,setCaretPreference as Bt}from"../lib/smooth-caret.js";import{SCREEN as $t,bannerSection as zt,draftIsStale as jt,draftKey as Vt,initialProject as qt,makeDraft as Ht,problems as we,readDraft as Kt,snapshot as V}from"../lib/page-editor-arrange.js";import{fmtDateTime as ee}from"../lib/dates.js";function te(o){const i=document.createElement("template");return i.innerHTML=o.trim(),i.content.firstElementChild}const Ut=ee,Ge=o=>ee(o.createdAt)+(o.label?` - ${o.label}`:""),_t=`
1
+ import{api as v}from"../api.js";import{CardBuilder as Mt}from"../lib/card-builder.js";import{TimelineBuilder as St}from"../lib/timeline-builder.js";import{attachEditorKeybindings as Tt,createToolbar as Ct,insertAtCursor as xt}from"../lib/markdown-toolbar.js";import{EffectsBuilder as Pt}from"../lib/effects-builder.js";import{attachContextMenu as Lt}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as At}from"../lib/shortcode-insert.js";import{populateThemeSelect as Dt}from"../lib/themes.js";import{createFoldingManager as Ot}from"../lib/editor-folding.js";import{openScribeComposer as Rt}from"../lib/scribe-composer.js";import{openCrudTutorial as Nt}from"../lib/crud-tutorial.js";import{addBannerAction as V}from"../lib/plugin-chrome.js";import{helpPopovers as _e}from"../lib/help-popover.js";import{onceClosed as we,openMenuAt as Je}from"../lib/tool-kit.js";import{pickMedia as It}from"../lib/media-picker.js";import{openPageClone as Ft}from"../lib/page-clone.js";import{openEditorReference as We}from"../lib/page-editor-reference.js";import{cleanType as Z,DEFAULT_TYPE as Q,EDITOR_FONTS as Bt,fontHref as zt,isDefaultType as $t,LINE_HEIGHTS as jt,SIZES as Vt,typeVars as Ye}from"../lib/editor-type.js";import{getProjectFromHash as qt}from"../lib/project-context.js";import{attachSmoothCaret as Ge,getCaretPreference as Ht,setCaretPreference as Kt}from"../lib/smooth-caret.js";import{SCREEN as Ut,bannerSection as _t,draftIsStale as Xe,draftKey as Jt,initialProject as Wt,makeDraft as Yt,problems as Ee,readDraft as Gt,snapshot as $}from"../lib/page-editor-arrange.js";import{fmtDateTime as ee}from"../lib/dates.js";import{hold as Ze,mountEditorHop as Xt,noteRecent as Zt,takeHeld as Qt}from"../lib/editor-hop.js";import{isHop as ea}from"../lib/editor-hop-arrange.js";import{blockTagDefs as ta,blocksInBody as aa}from"../lib/block-tags.js";function te(o){const l=document.createElement("template");return l.innerHTML=o.trim(),l.content.firstElementChild}const sa=ee,Qe=o=>ee(o.createdAt)+(o.label?` - ${o.label}`:""),oa=`
2
2
  <div class="pe-share">
3
3
  <p class="form-hint" style="margin-top:0">Anyone with the link can view this page even while it is unpublished, without
4
4
  signing in. The link works for this page only, expires on its own, and can be revoked at any time.</p>
@@ -30,7 +30,7 @@ import{api as g}from"../api.js";import{CardBuilder as gt}from"../lib/card-builde
30
30
  .pe-share [hidden] { display: none !important; }
31
31
  .pe-share-row { display: flex; align-items: center; gap: .6rem; padding: .5rem 0; border-bottom: 1px solid var(--dm-border-light); }
32
32
  .pe-share-text { flex: 1 1 auto; min-width: 0; font-size: .82rem; }
33
- </style>`;function Ye(o){const i=Ot(o);if(!i||document.querySelector(`link[data-pe-font="${CSS.escape(o)}"]`))return;const l=Object.assign(document.createElement("link"),{rel:"stylesheet",href:i});l.dataset.peFont=o,document.head.appendChild(l)}const Jt=`
33
+ </style>`;function et(o){const l=zt(o);if(!l||document.querySelector(`link[data-pe-font="${CSS.escape(o)}"]`))return;const c=Object.assign(document.createElement("link"),{rel:"stylesheet",href:l});c.dataset.peFont=o,document.head.appendChild(c)}const na=`
34
34
  <div class="pe-settings">
35
35
  <p class="pe-settings-note">How the editor looks to you - kept in this browser, for every page.</p>
36
36
  <label class="form-label" for="pe-set-font">Font <span data-help="The lettering in the editor (not on the site). Monospace keeps every character the same width, so shortcodes and Markdown line up."></span></label>
@@ -68,17 +68,17 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
68
68
  .pe-settings-sample { white-space: pre-wrap; padding: .75rem 1rem; border-radius: var(--dm-radius-md); border: 1px solid var(--dm-border);
69
69
  background: var(--dm-background-alt); font-family: var(--pe-font); font-size: var(--pe-size); line-height: var(--pe-line); }
70
70
  .pe-settings-actions { display: flex; gap: .5rem; border-top: 1px solid var(--dm-border); padding-top: .75rem; }
71
- </style>`;function Wt(o){const i=E.slideover({title:"Editor settings",size:"sm",position:"right"}),l=te(`<div>${Jt}</div>`),s=Z(o.peek()),r={font:M.observable(s.font),size:M.observable(String(s.size)),line:M.observable(String(s.line)),wrap:M.observable(s.wrap),caret:M.observable(It())},v=M.computed(()=>Z({font:r.font.value,size:r.size.value,line:r.line.value,wrap:r.wrap.value}));r.isDefault=M.computed(()=>Dt(v.value)&&r.caret.value==="auto");const u=M.effect(()=>Bt(r.caret.value)),p=l.querySelector("#pe-set-font");for(const[b,L]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const $=Lt.filter(P=>P.kind===b),m=L?Object.assign(document.createElement("optgroup"),{label:L}):p.querySelector("optgroup:last-of-type");m.append(...$.map(P=>new Option(P.label,P.id))),L&&p.appendChild(m)}l.querySelector("#pe-set-size").append(...Nt.map(b=>new Option(`${b}px`,String(b)))),l.querySelector("#pe-set-line").append(...Rt.map(b=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[b]||String(b),String(b))));const c=l.querySelector(".pe-settings-sample"),C=M.effect(()=>{const b=v.value;Ye(b.font);for(const[L,$]of Object.entries(Je(b)))c.style.setProperty(L,$);c.style.whiteSpace=b.wrap?"pre-wrap":"pre",c.style.overflowX=b.wrap?"":"auto",JSON.stringify(b)!==JSON.stringify(Z(o.peek()))&&(o.value=b,ae("type",b))});r.reset=()=>{r.font.value=Q.font,r.size.value=String(Q.size),r.line.value=String(Q.line),r.wrap.value=Q.wrap,r.caret.value="auto"},r.done=()=>i.close(),i.element.appendChild(l);const d=M.applyBindings(r,l),h=Ke(l);return ye(i,()=>{d.dispose(),h(),C?.(),u?.()}),Domma.icons?.scan?.(l),i.open(),i}async function Gt(o){const i=E.slideover({title:"Share preview link",size:"md",position:"right"}),l=te(`<div>${_t}</div>`),s={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},r=async()=>{try{const u=await g.pages.previewLinks.list(o);s.links.value=u.filter(p=>!p.revokedAt&&!p.expired).map(p=>({id:p.id,title:p.label||"Untitled link",meta:`Expires ${Ut(p.expiresAt)} - ${p.useCount===1?"1 view":`${p.useCount||0} views`}`})),s.listError.value=""}catch(u){s.listError.value="Could not load existing links: "+(u.message||"unknown error")}finally{s.loaded.value=!0}};s.create=async()=>{s.creating.value=!0;try{const u=await g.pages.previewLinks.create(o,s.expiry.peek(),s.note.peek().trim());s.url.value=u.url,s.note.value="",E.toast("Share link created.",{type:"success"}),await r(),l.querySelector(".pe-share-url")?.select()}catch(u){E.toast("Could not create link: "+(u.message||"unknown error"),{type:"error"})}finally{s.creating.value=!1}},s.copy=async()=>{try{await navigator.clipboard.writeText(s.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{l.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},s.revoke=async u=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await g.pages.previewLinks.revoke(u),E.toast("Link revoked.",{type:"success"}),await r()}catch(p){E.toast("Could not revoke: "+(p.message||"unknown error"),{type:"error"})}};const v=M.applyBindings(s,l);ye(i,()=>v.dispose()),i.element.appendChild(l),i.open(),await r()}function Yt(o){return new Promise(i=>{const l=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),s=te(`
71
+ </style>`;function ra(o){const l=E.slideover({title:"Editor settings",size:"sm",position:"right"}),c=te(`<div>${na}</div>`),s=Z(o.peek()),i={font:M.observable(s.font),size:M.observable(String(s.size)),line:M.observable(String(s.line)),wrap:M.observable(s.wrap),caret:M.observable(Ht())},u=M.computed(()=>Z({font:i.font.value,size:i.size.value,line:i.line.value,wrap:i.wrap.value}));i.isDefault=M.computed(()=>$t(u.value)&&i.caret.value==="auto");const p=M.effect(()=>Kt(i.caret.value)),r=c.querySelector("#pe-set-font");for(const[g,A]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const j=Bt.filter(D=>D.kind===g),f=A?Object.assign(document.createElement("optgroup"),{label:A}):r.querySelector("optgroup:last-of-type");f.append(...j.map(D=>new Option(D.label,D.id))),A&&r.appendChild(f)}c.querySelector("#pe-set-size").append(...Vt.map(g=>new Option(`${g}px`,String(g)))),c.querySelector("#pe-set-line").append(...jt.map(g=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[g]||String(g),String(g))));const d=c.querySelector(".pe-settings-sample"),C=M.effect(()=>{const g=u.value;et(g.font);for(const[A,j]of Object.entries(Ye(g)))d.style.setProperty(A,j);d.style.whiteSpace=g.wrap?"pre-wrap":"pre",d.style.overflowX=g.wrap?"":"auto",JSON.stringify(g)!==JSON.stringify(Z(o.peek()))&&(o.value=g,ae("type",g))});i.reset=()=>{i.font.value=Q.font,i.size.value=String(Q.size),i.line.value=String(Q.line),i.wrap.value=Q.wrap,i.caret.value="auto"},i.done=()=>l.close(),l.element.appendChild(c);const b=M.applyBindings(i,c),h=_e(c);return we(l,()=>{b.dispose(),h(),C?.(),p?.()}),Domma.icons?.scan?.(c),l.open(),l}async function ia(o){const l=E.slideover({title:"Share preview link",size:"md",position:"right"}),c=te(`<div>${oa}</div>`),s={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},i=async()=>{try{const p=await v.pages.previewLinks.list(o);s.links.value=p.filter(r=>!r.revokedAt&&!r.expired).map(r=>({id:r.id,title:r.label||"Untitled link",meta:`Expires ${sa(r.expiresAt)} - ${r.useCount===1?"1 view":`${r.useCount||0} views`}`})),s.listError.value=""}catch(p){s.listError.value="Could not load existing links: "+(p.message||"unknown error")}finally{s.loaded.value=!0}};s.create=async()=>{s.creating.value=!0;try{const p=await v.pages.previewLinks.create(o,s.expiry.peek(),s.note.peek().trim());s.url.value=p.url,s.note.value="",E.toast("Share link created.",{type:"success"}),await i(),c.querySelector(".pe-share-url")?.select()}catch(p){E.toast("Could not create link: "+(p.message||"unknown error"),{type:"error"})}finally{s.creating.value=!1}},s.copy=async()=>{try{await navigator.clipboard.writeText(s.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{c.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},s.revoke=async p=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await v.pages.previewLinks.revoke(p),E.toast("Link revoked.",{type:"success"}),await i()}catch(r){E.toast("Could not revoke: "+(r.message||"unknown error"),{type:"error"})}};const u=M.applyBindings(s,c);we(l,()=>u.dispose()),l.element.appendChild(c),l.open(),await i()}function la(o){return new Promise(l=>{const c=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),s=te(`
72
72
  <div style="padding:1rem">
73
73
  <label class="form-label" for="pe-version-label">Version Label</label>
74
74
  <input id="pe-version-label" type="text" class="form-input" style="margin-bottom:.75rem"
75
75
  placeholder="e.g. Initial design, Before restructure" data-model="label.value" data-on-keydown="key">
76
76
  <button type="button" class="btn btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save Version</button>
77
- </div>`);let r=!1;const v={label:M.observable(""),saving:M.observable(!1)};v.save=async()=>{v.saving.value=!0;try{await g.versions.create(o,v.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),r=!0,l.close()}catch(p){E.toast("Failed to save version: "+p.message,{type:"error"})}finally{v.saving.value=!1}},v.key=p=>{p.key==="Enter"&&(p.preventDefault(),v.save())};const u=M.applyBindings(v,s);ye(l,()=>{u.dispose(),i(r)}),l.element.appendChild(s),l.open(),setTimeout(()=>s.querySelector("input")?.focus(),100)})}let T=null,Xe=!1;function Xt(){Xe||!window.R?.use||(Xe=!0,R.use((o,i,l)=>{const s=i?.path||"",r=o?.path||"",v=$t.test(s)&&r!==s;return v&&T?.path===s&&T.cloneTarget===r?(T.cloneTarget=null,T.keepDraft(),l()):!v||!T||T.path!==s||!T.dirty()?l():E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(u=>{u?(T?.keepDraft(),l()):history.replaceState(null,"","#"+s)})}),M.subscribe("router:afterChange",({to:o}={})=>{if(T&&(o?.path||"")!==T.path){const i=T;T=null,i.dispose()}}))}function Zt(o){return o.split(`
78
- `).map(i=>i.trimEnd()).join(`
77
+ </div>`);let i=!1;const u={label:M.observable(""),saving:M.observable(!1)};u.save=async()=>{u.saving.value=!0;try{await v.versions.create(o,u.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),i=!0,c.close()}catch(r){E.toast("Failed to save version: "+r.message,{type:"error"})}finally{u.saving.value=!1}},u.key=r=>{r.key==="Enter"&&(r.preventDefault(),u.save())};const p=M.applyBindings(u,s);we(c,()=>{p.dispose(),l(i)}),c.element.appendChild(s),c.open(),setTimeout(()=>s.querySelector("input")?.focus(),100)})}let k=null,tt=!1;function ca(){tt||!window.R?.use||(tt=!0,R.use((o,l,c)=>{const s=l?.path||"",i=o?.path||"",u=Ut.test(s)&&i!==s;return u&&k?.path===s&&k.cloneTarget===i?(k.cloneTarget=null,k.keepDraft(),Ze(k.item),c()):!u||!k||k.path!==s||!k.dirty()?c():ea(s,i)?(k.keepDraft(),Ze(k.item),c()):E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(p=>{p?(k?.keepDraft(),c()):history.replaceState(null,"","#"+s)})}),M.subscribe("router:afterChange",({to:o}={})=>{if(k&&(o?.path||"")!==k.path){const l=k;k=null,l.dispose()}}))}function da(o){return o.split(`
78
+ `).map(l=>l.trimEnd()).join(`
79
79
  `).replace(/\n{3,}/g,`
80
80
 
81
- `)}const Ze="editor_prefs";function q(){return S.get(Ze)||{viewMode:"split",fullscreen:!1}}function ae(o,i){const l=q();l[o]=i,S.set(Ze,l)}const Qt=["edit","details","seo","dconfig","live","history"],Qe=["split","write","preview"],ea=o=>o.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function et(o,i,l=()=>{}){let s=0;try{s=o.contentWindow.scrollY||0}catch{}o.addEventListener("load",()=>{try{o.contentWindow.scrollTo(0,s)}catch{}l()},{once:!0}),o.srcdoc=i}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(o){if(Xt(),T){const e=T;T=null,e.dispose()}const i=window.location.hash.replace(/^#/,""),l=[],s=e=>(l.push(e),e);let r=()=>!1,v=()=>{};const u={path:i,dirty:()=>r(),keepDraft:()=>v(),dispose:()=>{l.splice(0).forEach(e=>{try{e()}catch{}})}};T=u;const p=o.get(0),c=()=>T===u&&p.isConnected,C=window.location.hash.match(/#\/pages\/edit(\/[^?]*)/),d=C?C[1]:null,h=!!d,b=E.loader(p,{type:"dots"}),[L,$,m,P,ke,tt,at]=await Promise.all([g.layouts.get().catch(()=>({})),g.settings.get().catch(()=>({})),h?g.pages.get(d).catch(()=>null):null,h?g.pages.list().catch(()=>[]):[],g.projects.list().catch(()=>[]),g.pages.tags().catch(()=>[]),g.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[])]);if(!c()){b.destroy();return}if(b.destroy(),h&&!m){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const w=e=>p.querySelector(e);if(St(w("#field-theme"),{includeDefault:!0}),await na(w("#field-visibility"),m?.visibility),!c())return;const st=oa(w("#field-layout"),L,m?.layout||"default");for(const e of ke)w("#field-project").append(new Option(e.name||e.slug,e.slug));const Ee=[...Array.isArray(P)?P:[]].sort((e,t)=>(e.title||e.urlPath).localeCompare(t.title||t.urlPath));for(const e of Ee)w("#quick-switch").append(new Option(`${e.title||"Untitled"} (${e.urlPath})`,e.urlPath,!1,e.urlPath===d));const H=q(),a={urlPath:M.observable(d||""),title:M.observable(m?.title||""),description:M.observable(m?.description||""),layout:M.observable(st),status:M.observable(m?.status||"draft"),sortOrder:M.observable(String(m?.sortOrder??99)),showInNav:M.observable(!!m?.showInNav),sidebar:M.observable(!!m?.sidebar),breadcrumbs:M.observable(m?m.breadcrumbs!==!1:!0),bundled:M.observable(!!m?.bundled),tags:M.observable(Array.isArray(m?.tags)?m.tags:[]),adminTags:M.observable(Array.isArray(m?.adminTags)?m.adminTags:[]),category:M.observable(m?.category||""),visibility:M.observable(typeof m?.visibility=="string"&&m.visibility?m.visibility:"public"),project:M.observable(qt({isEdit:h,page:m,projects:ke,fromHash:Ft()||"",urlPath:d||""})),theme:M.observable(m?.theme||""),seoTitle:M.observable(m?.seo?.title||""),seoDesc:M.observable(m?.seo?.description||""),dconfig:M.observable(m?.dconfig?JSON.stringify(m.dconfig,null,2):""),css:M.observable(""),cssOpened:M.observable(!1)},n={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),tab:M.observable("edit"),viewMode:M.observable(Qe.includes(H.viewMode)?H.viewMode:"split"),fullscreen:M.observable(!!H.fullscreen),cssMode:M.observable(!1),folded:M.observable(!1),liveStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null)},k=w("#markdown-editor"),F=w("#css-editor");k.value=m?.content||"";const ot=We(k),nt=We(F);s(()=>ot.destroy()),s(()=>nt.destroy());const O=document.createElement("div");O.className="editor-line-numbers editor-line-numbers--foldable",k.parentElement.insertBefore(O,k);const x=Tt(k,O,{onFoldChange:()=>{}});let I=!1;x.setGuard(e=>{I=e});const z=()=>x.getUnfoldedContent(),Me=M.observable(Z(H.type));s(M.effect(()=>{const e=Me.value,t=w("#editor-body");if(t){Ye(e.font);for(const[f,y]of Object.entries(Je(e)))t.style.setProperty(f,y);t.classList.toggle("pe-nowrap",!e.wrap),k.wrap=e.wrap?"soft":"off",F.wrap=e.wrap?"soft":"off"}}));let Se=null;s(()=>{try{Se?.close()}catch{}});const D=()=>(n.bodyRev.value,{urlPath:a.urlPath.value.trim(),title:a.title.value,description:a.description.value,layout:a.layout.value,status:a.status.value,sortOrder:a.sortOrder.value,showInNav:a.showInNav.value,sidebar:a.sidebar.value,breadcrumbs:a.breadcrumbs.value,bundled:a.bundled.value,tags:a.tags.value,adminTags:a.adminTags.value,category:a.category.value,visibility:a.visibility.value,project:a.project.value,theme:a.theme.value,seoTitle:a.seoTitle.value,seoDesc:a.seoDesc.value,dconfig:a.dconfig.value,body:z(),css:a.cssOpened.value?a.css.value:null}),rt=M.computed(()=>V(D())!==n.base.value),it=M.computed(()=>we({urlPath:a.urlPath.value,dconfig:a.dconfig.value})[0]?.text||""),lt=M.computed(()=>n.versions.value.some(e=>e.checked.value)),ct=M.computed(()=>n.versions.value.length>0&&n.versions.value.every(e=>e.checked.value)),dt=M.computed(()=>{const e=n.versions.value.filter(t=>t.checked.value).length;return e>0&&e<n.versions.value.length});r=()=>V(D())!==n.base.peek();let Te=JSON.stringify(a.adminTags.peek());const K=e=>({title:a.title.peek().trim()||"Untitled",description:a.description.peek().trim(),layout:a.layout.peek(),status:a.status.peek(),sortOrder:parseInt(String(a.sortOrder.peek()),10)||99,showInNav:a.showInNav.peek(),sidebar:a.sidebar.peek(),...a.breadcrumbs.peek()?{}:{breadcrumbs:!1},...a.bundled.peek()?{bundled:!0}:{},tags:a.tags.peek(),...h&&JSON.stringify(a.adminTags.peek())===Te?{}:{adminTags:a.adminTags.peek()},category:a.category.peek().trim()||null,visibility:a.visibility.peek()||"public",project:a.project.peek()||"",seo:{title:a.seoTitle.peek().trim(),description:a.seoDesc.peek().trim()},dconfig:e,...a.theme.peek()?{theme:a.theme.peek()}:{}}),se=()=>{const e=a.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},Ce=()=>a.urlPath.peek().trim()||d||"/",U=w("#markdown-preview");let xe="";g.settings.getCustomCss().then(({css:e})=>{xe=e||""}).catch(()=>{});const Pe=()=>{const e=U?.contentDocument;if(!e?.head)return;const t=a.cssOpened.peek()?a.css.peek():"";if(!t)return;let f=e.getElementById("preview-custom-css");f||(f=e.createElement("style"),f.id="preview-custom-css",e.head.appendChild(f)),f.textContent=t};let oe=null;const _=()=>{clearTimeout(oe),oe=setTimeout(async()=>{if(!(!U||n.viewMode.peek()==="write"))try{const{html:e}=await g.pages.previewFull(Ce(),K(se()),z());c()&&et(U,ea(e),Pe)}catch(e){U.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(e?.message||"unknown error").replace(/[<&]/g,t=>t==="<"?"&lt;":"&amp;")+"</body>"}},500)},Ae=w("#live-preview-frame");let ne=!0,re=null,ie=!1;const le=async()=>{if(!(ie||!Ae)){ie=!0,n.liveStatus.value="Rendering\u2026";try{const{html:e}=await g.pages.previewFull(Ce(),K(se()),z());if(!c())return;et(Ae,e),ne=!1,n.liveStatus.value="Showing unsaved changes"}catch(e){n.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{ie=!1}}},Le=()=>{ne=!0,n.tab.peek()==="live"&&(clearTimeout(re),re=setTimeout(le,600))};s(()=>{clearTimeout(oe),clearTimeout(re)});const ce=yt(o.find("#markdown-editor"),o.find("#editor-toolbar"),{spacerDefault:$?.layoutOptions?.spacerSize??40});Et(k);const de=p.querySelector(".editor-toolbar-right");de.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),w("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const pt=te(`
81
+ `)}const at="editor_prefs";function q(){return S.get(at)||{viewMode:"split",fullscreen:!1}}function ae(o,l){const c=q();c[o]=l,S.set(at,c)}const pa=["edit","details","seo","dconfig","live","history"],st=["split","write","preview"],ua=o=>o.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function ot(o,l,c=()=>{}){let s=0;try{s=o.contentWindow.scrollY||0}catch{}o.addEventListener("load",()=>{try{o.contentWindow.scrollTo(0,s)}catch{}c()},{once:!0}),o.srcdoc=l}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(o){if(ca(),k){const e=k;k=null,e.dispose()}const l=window.location.hash.replace(/^#/,""),c=[],s=e=>(c.push(e),e);let i=()=>!1,u=()=>{};const p=window.location.hash.match(/#\/pages\/edit(\/[^?]*)/),r=p?p[1]:null,d=!!r,C={path:l,item:{kind:"page",id:r||""},dirty:()=>i(),keepDraft:()=>u(),dispose:()=>{c.splice(0).forEach(e=>{try{e()}catch{}})}};k=C;const b=o.get(0),h=()=>k===C&&b.isConnected,g=E.loader(b,{type:"dots"}),[A,j,f,D,Me,nt,rt,Se]=await Promise.all([v.layouts.get().catch(()=>({})),v.settings.get().catch(()=>({})),d?v.pages.get(r).catch(()=>null):null,v.pages.list().catch(()=>[]),v.projects.list().catch(()=>[]),v.pages.tags().catch(()=>[]),v.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),v.blocks.list().catch(()=>[])]);if(!h()){g.destroy();return}if(g.destroy(),d&&!f){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const w=e=>b.querySelector(e);if(Dt(w("#field-theme"),{includeDefault:!0}),await ga(w("#field-visibility"),f?.visibility),!h())return;const it=ba(w("#field-layout"),A,f?.layout||"default");for(const e of Me)w("#field-project").append(new Option(e.name||e.slug,e.slug));const se=ta(Se),H=q(),a={urlPath:M.observable(r||""),title:M.observable(f?.title||""),description:M.observable(f?.description||""),layout:M.observable(it),status:M.observable(f?.status||"draft"),sortOrder:M.observable(String(f?.sortOrder??99)),showInNav:M.observable(!!f?.showInNav),sidebar:M.observable(!!f?.sidebar),breadcrumbs:M.observable(f?f.breadcrumbs!==!1:!0),bundled:M.observable(!!f?.bundled),tags:M.observable(Array.isArray(f?.tags)?f.tags:[]),adminTags:M.observable(Array.isArray(f?.adminTags)?f.adminTags:[]),category:M.observable(f?.category||""),visibility:M.observable(typeof f?.visibility=="string"&&f.visibility?f.visibility:"public"),project:M.observable(Wt({isEdit:d,page:f,projects:Me,fromHash:qt()||"",urlPath:r||""})),theme:M.observable(f?.theme||""),seoTitle:M.observable(f?.seo?.title||""),seoDesc:M.observable(f?.seo?.description||""),dconfig:M.observable(f?.dconfig?JSON.stringify(f.dconfig,null,2):""),css:M.observable(""),cssOpened:M.observable(!1)},n={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),tab:M.observable("edit"),viewMode:M.observable(st.includes(H.viewMode)?H.viewMode:"split"),fullscreen:M.observable(!!H.fullscreen),cssMode:M.observable(!1),folded:M.observable(!1),liveStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null)},T=w("#markdown-editor"),F=w("#css-editor");T.value=f?.content||"";const lt=Ge(T),ct=Ge(F);s(()=>lt.destroy()),s(()=>ct.destroy());const O=document.createElement("div");O.className="editor-line-numbers editor-line-numbers--foldable",T.parentElement.insertBefore(O,T);const x=Ot(T,O,{onFoldChange:()=>{}});let B=!1;x.setGuard(e=>{B=e});const z=()=>x.getUnfoldedContent(),Te=M.observable(Z(H.type));s(M.effect(()=>{const e=Te.value,t=w("#editor-body");if(t){et(e.font);for(const[m,y]of Object.entries(Ye(e)))t.style.setProperty(m,y);t.classList.toggle("pe-nowrap",!e.wrap),T.wrap=e.wrap?"soft":"off",F.wrap=e.wrap?"soft":"off"}}));let Ce=null;s(()=>{try{Ce?.close()}catch{}});const N=()=>(n.bodyRev.value,{urlPath:a.urlPath.value.trim(),title:a.title.value,description:a.description.value,layout:a.layout.value,status:a.status.value,sortOrder:a.sortOrder.value,showInNav:a.showInNav.value,sidebar:a.sidebar.value,breadcrumbs:a.breadcrumbs.value,bundled:a.bundled.value,tags:a.tags.value,adminTags:a.adminTags.value,category:a.category.value,visibility:a.visibility.value,project:a.project.value,theme:a.theme.value,seoTitle:a.seoTitle.value,seoDesc:a.seoDesc.value,dconfig:a.dconfig.value,body:z(),css:a.cssOpened.value?a.css.value:null}),dt=M.computed(()=>$(N())!==n.base.value),pt=M.computed(()=>Ee({urlPath:a.urlPath.value,dconfig:a.dconfig.value})[0]?.text||""),ut=M.computed(()=>n.versions.value.some(e=>e.checked.value)),mt=M.computed(()=>n.versions.value.length>0&&n.versions.value.every(e=>e.checked.value)),ft=M.computed(()=>{const e=n.versions.value.filter(t=>t.checked.value).length;return e>0&&e<n.versions.value.length});i=()=>$(N())!==n.base.peek();let xe=JSON.stringify(a.adminTags.peek());const K=e=>({title:a.title.peek().trim()||"Untitled",description:a.description.peek().trim(),layout:a.layout.peek(),status:a.status.peek(),sortOrder:parseInt(String(a.sortOrder.peek()),10)||99,showInNav:a.showInNav.peek(),sidebar:a.sidebar.peek(),...a.breadcrumbs.peek()?{}:{breadcrumbs:!1},...a.bundled.peek()?{bundled:!0}:{},tags:a.tags.peek(),...d&&JSON.stringify(a.adminTags.peek())===xe?{}:{adminTags:a.adminTags.peek()},category:a.category.peek().trim()||null,visibility:a.visibility.peek()||"public",project:a.project.peek()||"",seo:{title:a.seoTitle.peek().trim(),description:a.seoDesc.peek().trim()},dconfig:e,...a.theme.peek()?{theme:a.theme.peek()}:{}}),oe=()=>{const e=a.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},Pe=()=>a.urlPath.peek().trim()||r||"/",U=w("#markdown-preview");let Le="";v.settings.getCustomCss().then(({css:e})=>{Le=e||""}).catch(()=>{});const Ae=()=>{const e=U?.contentDocument;if(!e?.head)return;const t=a.cssOpened.peek()?a.css.peek():"";if(!t)return;let m=e.getElementById("preview-custom-css");m||(m=e.createElement("style"),m.id="preview-custom-css",e.head.appendChild(m)),m.textContent=t};let ne=null;const _=()=>{clearTimeout(ne),ne=setTimeout(async()=>{if(!(!U||n.viewMode.peek()==="write"))try{const{html:e}=await v.pages.previewFull(Pe(),K(oe()),z());h()&&ot(U,ua(e),Ae)}catch(e){U.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(e?.message||"unknown error").replace(/[<&]/g,t=>t==="<"?"&lt;":"&amp;")+"</body>"}},500)},De=w("#live-preview-frame");let re=!0,ie=null,le=!1;const ce=async()=>{if(!(le||!De)){le=!0,n.liveStatus.value="Rendering\u2026";try{const{html:e}=await v.pages.previewFull(Pe(),K(oe()),z());if(!h())return;ot(De,e),re=!1,n.liveStatus.value="Showing unsaved changes"}catch(e){n.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{le=!1}}},Oe=()=>{re=!0,n.tab.peek()==="live"&&(clearTimeout(ie),ie=setTimeout(ce,600))};s(()=>{clearTimeout(ne),clearTimeout(ie)});const de=Ct(o.find("#markdown-editor"),o.find("#editor-toolbar"),{spacerDefault:j?.layoutOptions?.spacerSize??40});Lt(T,{blocks:()=>se});const pe=b.querySelector(".editor-toolbar-right");pe.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),w("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const vt=te(`
82
82
  <span class="pe-toolbar-extras" style="display:contents">
83
83
  <button type="button" class="editor-view-btn" data-tip="Edit Custom CSS" data-on-click="toggleCss"
84
84
  data-bind-class="cssMode.value && 'active'"><span>CSS</span></button>
@@ -90,5 +90,5 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
90
90
  <span class="pe-fold-icon" hidden data-bind-hidden="!folded.value"><span data-icon="chevrons-right" data-icon-size="10"></span></span>
91
91
  </button>
92
92
  <span class="editor-toolbar-sep"></span>
93
- </span>`);de.prepend(pt);const Oe=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),De=w("#field-tags");let J=null;J=De?E.pillbox(De,{data:Oe(tt),value:a.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{J&&(a.tags.value=[...J.getValue()])}}):null;const Re=w("#field-admin-tags");let W=null;W=Re?E.pillbox(Re,{data:Oe(at),value:a.adminTags.peek(),creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{W&&(a.adminTags.value=[...W.getValue()])}}):null;let pe=!1,ue=null;const G=async()=>{pe=!0,n.histLoading.value=!0,n.histError.value="";try{const e=(await g.versions.list(d)).versions||[];if(!c())return;n.versions.value=e.map(t=>({...t,when:ee(t.createdAt),author:t.author||"",label:t.label||"",checked:M.observable(!1)}))}catch(e){n.histError.value="Failed to load versions: "+e.message}finally{n.histLoading.value=!1}},ve=e=>n.versions.peek().find(t=>t.filename===e),fe=async e=>{try{const t=await g.versions.get(d,e.filename);ue=e.filename,n.compare.value={label:Ge(e),old:t.content,current:z()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},Ne=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await g.versions.restore(d,e),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${d}`),300)}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},ut=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await g.versions.delete(d,e.filename),E.toast("Version deleted.",{type:"success"}),await G()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},Fe=()=>{n.bodyRev.value=n.bodyRev.peek()+1},Ie=()=>{a.cssOpened.peek()||(a.css.value=xe,a.cssOpened.value=!0,ht(F))},Be=e=>{for(const t of["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"])a[t].value=e[t]==null?"":String(e[t]);for(const t of["showInNav","sidebar","breadcrumbs","bundled"])a[t].value=!!e[t];a.tags.value=Array.isArray(e.tags)?e.tags:[],J?.setValue(a.tags.peek()),Array.isArray(e.adminTags)&&(a.adminTags.value=e.adminTags),W?.setValue(a.adminTags.peek()),x.unfoldAll(),I=!0,k.value=e.body==null?"":String(e.body),I=!1,x.refresh(),e.css!=null&&(Ie(),a.css.value=String(e.css)),Fe()};n.base.value=V(D());const Y=Vt(d||"");v=()=>{if(r())try{S.set(Y,Ht(D(),n.base.peek()))}catch{}};const me=()=>{try{S.remove?S.remove(Y):S.set(Y,null)}catch{}},$e=e=>{r()&&(v(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",$e),s(()=>window.removeEventListener("beforeunload",$e));const ze=async()=>{if(n.saving.peek())return;const e=D(),t=we(e);if(t.length){E.toast(t[0].text,{type:"warning"}),p.querySelectorAll("#editor-meta-tabs .tab-item")[Qt.indexOf(t[0].field==="dconfig"?"dconfig":"details")]?.click();return}const f=(e.dconfig||"").trim(),y=K(f?JSON.parse(f):null),A=a.cssOpened.peek();n.saving.value=!0;try{if(h){const N={frontmatter:y,body:e.body};e.urlPath!==d&&(N.newUrlPath=e.urlPath),await g.pages.update(d,N)}else await g.pages.create({urlPath:e.urlPath,frontmatter:y,body:e.body});A&&await g.settings.saveCustomCss(a.css.peek()),E.toast(A?"Page and CSS saved.":"Page saved.",{type:"success"}),me(),Te=JSON.stringify(a.adminTags.peek()),n.base.value=V(D()),h?e.urlPath!==d&&R.navigate(`/pages/edit${e.urlPath}`):R.navigate("/pages")}catch(N){E.toast(`Save failed: ${N.message||"Unknown error"}`,{type:"error"})}finally{n.saving.value=!1}},je=p.querySelector(".dm-plugin-body")||p,vt={...a,...n,dirty:rt,problem:it,anySelected:lt,allSelected:ct,someSelected:dt,isEdit:h,savedPath:d||"",hasPages:Ee.length>0},ft={save:()=>ze(),discard:async()=>{!r()||!await E.confirm("Discard the changes you have not saved?")||(Be(JSON.parse(n.base.peek())),me())},switchPage:e=>{const t=e?.target?.value;t&&R.navigate("/pages/edit"+t)},showTab:e=>{n.tab.value=e,e==="live"&&ne&&le(),e==="history"&&h&&!pe&&G()},setMode:e=>{n.cssMode.peek()&&(n.cssMode.value=!1),n.viewMode.value=e,ae("viewMode",e),e!=="write"&&_()},toggleFullscreen:()=>{n.fullscreen.value=!n.fullscreen.peek(),ae("fullscreen",n.fullscreen.peek())},toggleCss:()=>{n.cssMode.peek()?(n.cssMode.value=!1,n.viewMode.value=Qe.includes(q().viewMode)?q().viewMode:"split",x.refresh()):(Ie(),n.cssMode.value=!0,n.viewMode.value="split")},formatMarkdown:()=>{const e=k.selectionStart,t=Zt(z());x.unfoldAll(),I=!0,k.value=t,I=!1,k.selectionStart=k.selectionEnd=Math.min(e,t.length),k.dispatchEvent(new Event("input")),x.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{n.folded.peek()?x.unfoldAll():x.foldAll(),n.folded.value=!n.folded.peek()},setWidth:e=>{n.liveWidth.value=e;const t=w("#live-preview-stage");t&&(t.scrollTop=0)},refreshLive:()=>le(),share:async()=>{if(!h){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await Gt(d)},toggleAll:e=>{const t=!!e?.target?.checked;n.versions.peek().forEach(f=>{f.checked.value=t})},deleteSelected:async()=>{const e=n.versions.peek().filter(f=>f.checked.peek()).map(f=>f.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await g.versions.bulkDelete(d,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await G()}catch(f){E.toast("Delete failed: "+f.message,{type:"error"})}},saveNamed:async()=>{await Yt(d)&&c()&&(n.tab.peek()==="history"?await G():pe=!1)},closeCompare:()=>{n.compare.value=null,ue=null},restoreCompared:()=>Ne(ue)};de.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const mt=M.applyBindings(vt,je,{methods:ft});s(()=>mt.dispose()),E.tabs(w("#editor-meta-tabs"));let ge=null;s(()=>clearTimeout(ge)),k.addEventListener("input",()=>{x.refresh(),O.scrollTop=k.scrollTop,!I&&(clearTimeout(ge),ge=setTimeout(Fe,120),_(),Le())}),k.addEventListener("scroll",()=>{O.scrollTop=k.scrollTop}),F.addEventListener("scroll",()=>{O.scrollTop=F.scrollTop});let Ve=!0;s(M.effect(()=>{for(const e of Object.keys(a))e!=="css"&&e!=="cssOpened"&&a[e].value;if(n.bodyRev.value,Ve){Ve=!1,_();return}_(),Le()}));let be=null;if(s(()=>clearTimeout(be)),s(M.effect(()=>{const e=a.css.value;n.cssMode.value&&(ta(O,e,F),clearTimeout(be),be=setTimeout(Pe,400))})),h){const e=w(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:f=>{const y=ve(f.dataset.file);return y?[{header:Ge(y)},{label:"Compare with the editor",icon:"columns",action:()=>fe(y)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>Ne(y.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>ut(y)}]:[]}});s(()=>t?.destroy?.()),e.addEventListener("click",f=>{const y=f.target.closest("tr[data-file]");if(!y||f.target.matches("input"))return;const A=f.target.closest(".pe-kebab");if(A){Ue(A);return}const N=ve(y.dataset.file);N&&fe(N)}),e.addEventListener("keydown",f=>{const y=f.target.closest("tr[data-file]");if(y){if(f.key==="F10"&&f.shiftKey)f.preventDefault(),Ue(y);else if(f.key==="Enter"&&f.target===y){const A=ve(y.dataset.file);A&&fe(A)}}})}aa(w("#editor-body"),w("#editor-divider"),s),Mt({image:e=>(n.cssMode.value=!1,sa(e)),scribe:e=>Ct(e),card:e=>gt.open(e,{onMediaBrowse:()=>g.media.list().catch(()=>[])}),timeline:e=>bt.open(e,{onPreview:async t=>{try{return await g.pages.preview(t)}catch{return null}},onListCollections:()=>g.collections.list().catch(()=>[]),onGetCollection:t=>g.collections.get(t).catch(()=>null)})}),ce.onEffects(e=>kt.open(e)),ce.onHelp(()=>_e()),ce.onTutorial(e=>xt(e));const qe=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&c()&&(e.preventDefault(),ze())};document.addEventListener("keydown",qe),s(()=>document.removeEventListener("keydown",qe));const X=p.querySelector(".dm-plugin-banner-name");s(M.effect(()=>{const e=zt(h,a.title.value,d);if(!c()||(document.title=`${e} - Domma CMS`,!X))return;const t=X.lastChild;t&&t.nodeType===3&&X.querySelector(".dm-plugin-banner-sep")?t.textContent=e:X.textContent=`Pages \u203A ${e}`})),j(o,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),j(o,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>_e()}),h&&j(o,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(a.urlPath.peek()||d,"_blank","noopener")});const he=j(o,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:()=>{if(!h||n.saving.peek()||!c())return;const e=D(),t=we({...e,urlPath:d});if(t.length){E.toast(t[0].text,{type:"warning"});return}const f=At({page:m,frontmatter:{...K(se()),adminTags:a.adminTags.peek()},body:e.body,source:d,pages:P,onCloned:y=>{c()&&(u.cloneTarget=`/pages/edit${y}`,R.navigate(u.cloneTarget))}});s(()=>f.close())}});he&&(he.disabled=!h,h||(he.title="Save the page before cloning it")),j(o,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Se=Wt(Me)}}),s(Ke(je,{position:"bottom"})),Domma.icons.scan();let He=null;try{He=S.get(Y)}catch{}const B=Kt(He);if(B&&(me(),V(B.state)!==n.base.peek())){const e=B.at?` (${ee(B.at)})`:"",t=jt(B,n.base.peek())?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${e}. Restore them?${t}`)&&c()&&Be(B.state)}}};function ta(o,i,l){o.replaceChildren(...String(i).split(`
94
- `).map((s,r)=>{const v=document.createElement("span");v.className="editor-line-number-row";const u=document.createElement("span");u.className="fold-toggle fold-toggle--empty";const p=document.createElement("span");return p.className="editor-line-num",p.textContent=String(r+1),v.append(u,p),v})),o.scrollTop=l.scrollTop}function aa(o,i,l){if(!o||!i)return;const s=15,r=85,v=d=>Math.min(r,Math.max(s,d));let u=v(parseFloat(q().splitRatio)||50);const p=d=>{o.style.setProperty("--dm-editor-split",u+"%"),i.setAttribute("aria-valuenow",String(Math.round(u))),d&&ae("splitRatio",u)};p(!1);const c=d=>{const h=o.getBoundingClientRect();h.width&&(u=v((d.clientX-h.left)/h.width*100)),p(!1)},C=()=>{document.removeEventListener("pointermove",c),document.removeEventListener("pointerup",C),o.classList.remove("is-resizing"),i.classList.remove("is-dragging"),p(!0)};l(C),i.addEventListener("pointerdown",d=>{window.matchMedia("(max-width: 768px)").matches||(d.preventDefault(),o.classList.add("is-resizing"),i.classList.add("is-dragging"),document.addEventListener("pointermove",c),document.addEventListener("pointerup",C))}),i.addEventListener("dblclick",()=>{u=50,p(!0)}),i.addEventListener("keydown",d=>{const h=d.shiftKey?10:2;let b=u;if(d.key==="ArrowLeft")b=u-h;else if(d.key==="ArrowRight")b=u+h;else if(d.key==="Home")b=s;else if(d.key==="End")b=r;else if(d.key==="Enter"||d.key===" ")b=50;else return;d.preventDefault(),u=v(b),p(!0)})}async function sa(o){const i=o.selectionStart,l=o.selectionEnd,s=await Pt({title:"Upload or insert an image"});if(!s||!o.isConnected)return;let r=s.split("/").pop().split("?")[0];try{r=decodeURIComponent(r)}catch{}const v=r.replace(/[\\[\]]/g,"\\$&"),u=s.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");o.setSelectionRange(i,l),wt(o,`![${v}](${u})`),o.dispatchEvent(new Event("input",{bubbles:!0}))}function oa(o,i,l){const s=Object.entries(i||{});return o?(o.replaceChildren(...s.map(([r,v])=>new Option(v?.label||r,r))),s.some(([r])=>r===l)?l:s[0]?.[0]||l):l}async function na(o,i){if(!o)return;let l=[];try{const c=await g.get("/collections/roles/entries?limit=100");l=Array.isArray(c)?c:c?.entries||[]}catch{}const s=l.map(c=>({name:c.data?.name,label:c.data?.label||c.data?.name,level:c.data?.level??99})).filter(c=>c.name&&c.level!==0).sort((c,C)=>C.level-c.level),r=(c,C)=>Object.assign(document.createElement("option"),{value:c,textContent:C}),v=()=>{typeof i=="string"&&i&&![...o.options].some(c=>c.value===i)&&o.append(r(i,`${i} (custom)`))};if(!s.length)return v();const u=document.createElement("optgroup");u.label="Role and above",s.forEach(c=>u.append(r(c.name,`${c.label} and above`)));const p=document.createElement("optgroup");p.label="Role only",s.forEach(c=>p.append(r(`=${c.name}`,`${c.label} only`))),o.replaceChildren(r("public","Public - everyone"),u,p,r("private","Private - super-admin only")),v()}
93
+ </span>`);pe.prepend(vt);const Re=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),Ne=w("#field-tags");let J=null;J=Ne?E.pillbox(Ne,{data:Re(nt),value:a.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{J&&(a.tags.value=[...J.getValue()])}}):null;const Ie=w("#field-admin-tags");let W=null;W=Ie?E.pillbox(Ie,{data:Re(rt),value:a.adminTags.peek(),creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{W&&(a.adminTags.value=[...W.getValue()])}}):null;let ue=!1,me=null;const Y=async()=>{ue=!0,n.histLoading.value=!0,n.histError.value="";try{const e=(await v.versions.list(r)).versions||[];if(!h())return;n.versions.value=e.map(t=>({...t,when:ee(t.createdAt),author:t.author||"",label:t.label||"",checked:M.observable(!1)}))}catch(e){n.histError.value="Failed to load versions: "+e.message}finally{n.histLoading.value=!1}},fe=e=>n.versions.peek().find(t=>t.filename===e),ve=async e=>{try{const t=await v.versions.get(r,e.filename);me=e.filename,n.compare.value={label:Qe(e),old:t.content,current:z()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},Fe=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await v.versions.restore(r,e),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${r}`),300)}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},bt=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await v.versions.delete(r,e.filename),E.toast("Version deleted.",{type:"success"}),await Y()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},Be=()=>{n.bodyRev.value=n.bodyRev.peek()+1},ze=()=>{a.cssOpened.peek()||(a.css.value=Le,a.cssOpened.value=!0,Tt(F))},be=e=>{for(const t of["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"])a[t].value=e[t]==null?"":String(e[t]);for(const t of["showInNav","sidebar","breadcrumbs","bundled"])a[t].value=!!e[t];a.tags.value=Array.isArray(e.tags)?e.tags:[],J?.setValue(a.tags.peek()),Array.isArray(e.adminTags)&&(a.adminTags.value=e.adminTags),W?.setValue(a.adminTags.peek()),x.unfoldAll(),B=!0,T.value=e.body==null?"":String(e.body),B=!1,x.refresh(),e.css!=null&&(ze(),a.css.value=String(e.css)),Be()};n.base.value=$(N());const G=Jt(r||"");u=()=>{if(i())try{S.set(G,Yt(N(),n.base.peek()))}catch{}};const ge=()=>{try{S.remove?S.remove(G):S.set(G,null)}catch{}},$e=e=>{i()&&(u(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",$e),s(()=>window.removeEventListener("beforeunload",$e));const je=async()=>{if(n.saving.peek())return;const e=N(),t=Ee(e);if(t.length){E.toast(t[0].text,{type:"warning"}),b.querySelectorAll("#editor-meta-tabs .tab-item")[pa.indexOf(t[0].field==="dconfig"?"dconfig":"details")]?.click();return}const m=(e.dconfig||"").trim(),y=K(m?JSON.parse(m):null),L=a.cssOpened.peek();n.saving.value=!0;try{if(d){const I={frontmatter:y,body:e.body};e.urlPath!==r&&(I.newUrlPath=e.urlPath),await v.pages.update(r,I)}else await v.pages.create({urlPath:e.urlPath,frontmatter:y,body:e.body});if(L&&await v.settings.saveCustomCss(a.css.peek()),E.toast(L?"Page and CSS saved.":"Page saved.",{type:"success"}),!h())return;ge(),d&&Zt({kind:"page",id:e.urlPath,label:a.title.peek().trim()||"Untitled"}),xe=JSON.stringify(a.adminTags.peek()),n.base.value=$(N()),d?e.urlPath!==r&&R.navigate(`/pages/edit${e.urlPath}`):R.navigate("/pages")}catch(I){E.toast(`Save failed: ${I.message||"Unknown error"}`,{type:"error"})}finally{n.saving.value=!1}},Ve=b.querySelector(".dm-plugin-body")||b,gt={...a,...n,dirty:dt,problem:pt,anySelected:ut,allSelected:mt,someSelected:ft,isEdit:d,savedPath:r||""},ht={save:()=>je(),discard:async()=>{!i()||!await E.confirm("Discard the changes you have not saved?")||(be(JSON.parse(n.base.peek())),ge())},showTab:e=>{n.tab.value=e,e==="live"&&re&&ce(),e==="history"&&d&&!ue&&Y()},setMode:e=>{n.cssMode.peek()&&(n.cssMode.value=!1),n.viewMode.value=e,ae("viewMode",e),e!=="write"&&_()},toggleFullscreen:()=>{n.fullscreen.value=!n.fullscreen.peek(),ae("fullscreen",n.fullscreen.peek())},toggleCss:()=>{n.cssMode.peek()?(n.cssMode.value=!1,n.viewMode.value=st.includes(q().viewMode)?q().viewMode:"split",x.refresh()):(ze(),n.cssMode.value=!0,n.viewMode.value="split")},formatMarkdown:()=>{const e=T.selectionStart,t=da(z());x.unfoldAll(),B=!0,T.value=t,B=!1,T.selectionStart=T.selectionEnd=Math.min(e,t.length),T.dispatchEvent(new Event("input")),x.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{n.folded.peek()?x.unfoldAll():x.foldAll(),n.folded.value=!n.folded.peek()},setWidth:e=>{n.liveWidth.value=e;const t=w("#live-preview-stage");t&&(t.scrollTop=0)},refreshLive:()=>ce(),share:async()=>{if(!d){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await ia(r)},toggleAll:e=>{const t=!!e?.target?.checked;n.versions.peek().forEach(m=>{m.checked.value=t})},deleteSelected:async()=>{const e=n.versions.peek().filter(m=>m.checked.peek()).map(m=>m.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await v.versions.bulkDelete(r,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await Y()}catch(m){E.toast("Delete failed: "+m.message,{type:"error"})}},saveNamed:async()=>{await la(r)&&h()&&(n.tab.peek()==="history"?await Y():ue=!1)},closeCompare:()=>{n.compare.value=null,me=null},restoreCompared:()=>Fe(me)};pe.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const yt=M.applyBindings(gt,Ve,{methods:ht});s(()=>yt.dispose());const qe=Xt(w("#page-hop"),{current:{kind:"page",id:r||"",label:f?.title||r||""},pages:D,blocks:Se});s(()=>qe.dispose()),E.tabs(w("#editor-meta-tabs"));let he=null;s(()=>clearTimeout(he)),T.addEventListener("input",()=>{x.refresh(),O.scrollTop=T.scrollTop,!B&&(clearTimeout(he),he=setTimeout(Be,120),_(),Oe())}),T.addEventListener("scroll",()=>{O.scrollTop=T.scrollTop}),F.addEventListener("scroll",()=>{O.scrollTop=F.scrollTop});let He=!0;s(M.effect(()=>{for(const e of Object.keys(a))e!=="css"&&e!=="cssOpened"&&a[e].value;if(n.bodyRev.value,He){He=!1,_();return}_(),Oe()}));const kt=se.map(e=>e.name),wt=se.filter(e=>e.shortTag).map(e=>e.name);s(M.effect(()=>{n.bodyRev.value,qe.setRelated(aa(z(),kt,wt).map(e=>({kind:"block",id:e,label:e})))}));let ye=null;if(s(()=>clearTimeout(ye)),s(M.effect(()=>{const e=a.css.value;n.cssMode.value&&(ma(O,e,F),clearTimeout(ye),ye=setTimeout(Ae,400))})),d){const e=w(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:m=>{const y=fe(m.dataset.file);return y?[{header:Qe(y)},{label:"Compare with the editor",icon:"columns",action:()=>ve(y)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>Fe(y.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>bt(y)}]:[]}});s(()=>t?.destroy?.()),e.addEventListener("click",m=>{const y=m.target.closest("tr[data-file]");if(!y||m.target.matches("input"))return;const L=m.target.closest(".pe-kebab");if(L){Je(L);return}const I=fe(y.dataset.file);I&&ve(I)}),e.addEventListener("keydown",m=>{const y=m.target.closest("tr[data-file]");if(y){if(m.key==="F10"&&m.shiftKey)m.preventDefault(),Je(y);else if(m.key==="Enter"&&m.target===y){const L=fe(y.dataset.file);L&&ve(L)}}})}fa(w("#editor-body"),w("#editor-divider"),s),At({image:e=>(n.cssMode.value=!1,va(e)),scribe:e=>Rt(e),card:e=>Mt.open(e,{onMediaBrowse:()=>v.media.list().catch(()=>[])}),timeline:e=>St.open(e,{onPreview:async t=>{try{return await v.pages.preview(t)}catch{return null}},onListCollections:()=>v.collections.list().catch(()=>[]),onGetCollection:t=>v.collections.get(t).catch(()=>null)})}),de.onEffects(e=>Pt.open(e)),de.onHelp(()=>We()),de.onTutorial(e=>Nt(e));const Ke=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&h()&&(e.preventDefault(),je())};document.addEventListener("keydown",Ke),s(()=>document.removeEventListener("keydown",Ke));const X=b.querySelector(".dm-plugin-banner-name");s(M.effect(()=>{const e=_t(d,a.title.value,r);if(!h()||(document.title=`${e} - Domma CMS`,!X))return;const t=X.lastChild;t&&t.nodeType===3&&X.querySelector(".dm-plugin-banner-sep")?t.textContent=e:X.textContent=`Pages \u203A ${e}`})),V(o,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),V(o,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>We()}),d&&V(o,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(a.urlPath.peek()||r,"_blank","noopener")});const ke=V(o,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:()=>{if(!d||n.saving.peek()||!h())return;const e=N(),t=Ee({...e,urlPath:r});if(t.length){E.toast(t[0].text,{type:"warning"});return}const m=Ft({page:f,frontmatter:{...K(oe()),adminTags:a.adminTags.peek()},body:e.body,source:r,pages:D,onCloned:y=>{h()&&(C.cloneTarget=`/pages/edit${y}`,R.navigate(C.cloneTarget))}});s(()=>m.close())}});ke&&(ke.disabled=!d,d||(ke.title="Save the page before cloning it")),V(o,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Ce=ra(Te)}}),s(_e(Ve,{position:"bottom"})),Domma.icons.scan();let Ue=null;try{Ue=S.get(G)}catch{}const P=Gt(Ue),Et=Qt(C.item);if(P){if(ge(),$(P.state)!==n.base.peek()&&Et&&!Xe(P,n.base.peek()))be(P.state),E.toast("Your unsaved changes to this page are back.",{type:"info"});else if($(P.state)!==n.base.peek()){const e=P.at?` (${ee(P.at)})`:"",t=Xe(P,n.base.peek())?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${e}. Restore them?${t}`)&&h()&&be(P.state)}}}};function ma(o,l,c){o.replaceChildren(...String(l).split(`
94
+ `).map((s,i)=>{const u=document.createElement("span");u.className="editor-line-number-row";const p=document.createElement("span");p.className="fold-toggle fold-toggle--empty";const r=document.createElement("span");return r.className="editor-line-num",r.textContent=String(i+1),u.append(p,r),u})),o.scrollTop=c.scrollTop}function fa(o,l,c){if(!o||!l)return;const s=15,i=85,u=b=>Math.min(i,Math.max(s,b));let p=u(parseFloat(q().splitRatio)||50);const r=b=>{o.style.setProperty("--dm-editor-split",p+"%"),l.setAttribute("aria-valuenow",String(Math.round(p))),b&&ae("splitRatio",p)};r(!1);const d=b=>{const h=o.getBoundingClientRect();h.width&&(p=u((b.clientX-h.left)/h.width*100)),r(!1)},C=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",C),o.classList.remove("is-resizing"),l.classList.remove("is-dragging"),r(!0)};c(C),l.addEventListener("pointerdown",b=>{window.matchMedia("(max-width: 768px)").matches||(b.preventDefault(),o.classList.add("is-resizing"),l.classList.add("is-dragging"),document.addEventListener("pointermove",d),document.addEventListener("pointerup",C))}),l.addEventListener("dblclick",()=>{p=50,r(!0)}),l.addEventListener("keydown",b=>{const h=b.shiftKey?10:2;let g=p;if(b.key==="ArrowLeft")g=p-h;else if(b.key==="ArrowRight")g=p+h;else if(b.key==="Home")g=s;else if(b.key==="End")g=i;else if(b.key==="Enter"||b.key===" ")g=50;else return;b.preventDefault(),p=u(g),r(!0)})}async function va(o){const l=o.selectionStart,c=o.selectionEnd,s=await It({title:"Upload or insert an image"});if(!s||!o.isConnected)return;let i=s.split("/").pop().split("?")[0];try{i=decodeURIComponent(i)}catch{}const u=i.replace(/[\\[\]]/g,"\\$&"),p=s.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");o.setSelectionRange(l,c),xt(o,`![${u}](${p})`),o.dispatchEvent(new Event("input",{bubbles:!0}))}function ba(o,l,c){const s=Object.entries(l||{});return o?(o.replaceChildren(...s.map(([i,u])=>new Option(u?.label||i,i))),s.some(([i])=>i===c)?c:s[0]?.[0]||c):c}async function ga(o,l){if(!o)return;let c=[];try{const d=await v.get("/collections/roles/entries?limit=100");c=Array.isArray(d)?d:d?.entries||[]}catch{}const s=c.map(d=>({name:d.data?.name,label:d.data?.label||d.data?.name,level:d.data?.level??99})).filter(d=>d.name&&d.level!==0).sort((d,C)=>C.level-d.level),i=(d,C)=>Object.assign(document.createElement("option"),{value:d,textContent:C}),u=()=>{typeof l=="string"&&l&&![...o.options].some(d=>d.value===l)&&o.append(i(l,`${l} (custom)`))};if(!s.length)return u();const p=document.createElement("optgroup");p.label="Role and above",s.forEach(d=>p.append(i(d.name,`${d.label} and above`)));const r=document.createElement("optgroup");r.label="Role only",s.forEach(d=>r.append(i(`=${d.name}`,`${d.label} only`))),o.replaceChildren(i("public","Public - everyone"),p,r,i("private","Private - super-admin only")),u()}
@@ -1264,6 +1264,23 @@ Short tags are self-closing only, and only work for block names made of lowercas
1264
1264
  do not clash with a built-in shortcode name (a block called `card` or `menu`, for example, can only be used through
1265
1265
  `[block template="…"]`).
1266
1266
 
1267
+ **In the page editor:** right-click and choose **Add block…** to insert any block with all its parameters written
1268
+ out (`[cta-banner title="" tagline="" … /]`), the cursor in the first one. Right-clicking inside a block's tag - the
1269
+ short tag or `[block template="…"]` - lists that block's own parameters to fill in there and then; a parameter the
1270
+ block's doc comment writes as `dark | light | gold` is offered as a choice. **Edit parameters…** opens them in a
1271
+ form, and **Open the block** goes to the block editor. The parameters come from the template's `{{placeholders}}`;
1272
+ the hints and choices from the "Parameters:" list in the block's opening comment:
1273
+
1274
+ ```
1275
+ <!--
1276
+ cta-banner - Reusable statement call-to-action block.
1277
+
1278
+ Parameters (all optional):
1279
+ title - Heading text (default: empty)
1280
+ variant - dark | light | gold (default: dark)
1281
+ -->
1282
+ ```
1283
+
1267
1284
  **Example:**
1268
1285
 
1269
1286
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.15",
3
+ "version": "1.8.17",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",