domma-cms 1.8.16 → 1.8.18

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.
@@ -0,0 +1 @@
1
+ export const RECENT_MAX=5,RESULTS_MAX=40;export function hopTarget(t){const n=String(t||"").replace(/^#/,"").split("?")[0];if(n==="/pages/new")return{kind:"page",id:""};if(n==="/blocks/new")return{kind:"block",id:""};let r=/^\/pages\/edit(\/.*)$/.exec(n);if(r)return{kind:"page",id:r[1]};if(r=/^\/blocks\/edit\/([^/]+)\/?$/.exec(n),r)try{return{kind:"block",id:decodeURIComponent(r[1])}}catch{return{kind:"block",id:r[1]}}return null}export const isHop=(t,n)=>!!(hopTarget(t)&&hopTarget(n))&&String(t).split("?")[0]!==String(n).split("?")[0],pathFor=t=>t.kind==="page"?`/pages/edit${t.id}`:`/blocks/edit/${encodeURIComponent(t.id)}`,keyOf=t=>`${t.kind}:${t.id}`;export function pushRecent(t,n,r=5){const l=(Array.isArray(t)?t:[]).filter(i=>i&&(i.kind==="page"||i.kind==="block")&&typeof i.id=="string"&&i.id);if(!n?.id)return l.slice(0,r);const e={kind:n.kind,id:n.id,label:String(n.label||n.id).slice(0,120)};return[e,...l.filter(i=>keyOf(i)!==keyOf(e))].slice(0,r)}export function hopItems(t,n){const r=(Array.isArray(t)?t:[]).filter(e=>e?.urlPath).map(e=>({kind:"page",id:e.urlPath,label:e.title||"Untitled",meta:e.urlPath})).sort((e,i)=>e.label.localeCompare(i.label)),l=(Array.isArray(n)?n:[]).filter(e=>e?.name).map(e=>({kind:"block",id:e.name,label:e.name,meta:e.description||""})).sort((e,i)=>e.label.localeCompare(i.label));return[...r,...l]}export function hopMatches(t,n){const r=String(n||"").trim().toLowerCase();return r?[t.label,t.id,t.meta].some(l=>String(l||"").toLowerCase().includes(r)):!0}export function orderHop(t,n,{recent:r=[],current:l=null,max:e=40}={}){const i=l?.id?keyOf(l):null,a=t.filter(o=>keyOf(o)!==i),d=String(n||"").trim().toLowerCase();if(!d){const o=new Map(r.map((s,c)=>[keyOf(s),c]));return[...a.filter(s=>o.has(keyOf(s))).sort((s,c)=>o.get(keyOf(s))-o.get(keyOf(c))),...a.filter(s=>!o.has(keyOf(s)))].slice(0,e)}const p=o=>[o.label,o.kind==="page"?o.id.slice(1):o.id].some(u=>String(u).toLowerCase().startsWith(d));return[...a.filter(p),...a.filter(o=>!p(o))].slice(0,e)}export function recentChips(t,n,r=()=>!1){const l=n?.id?keyOf(n):null;return t.filter(e=>keyOf(e)!==l).map(e=>({...e,key:keyOf(e),path:pathFor(e),unsaved:!!r(e)}))}
@@ -0,0 +1,19 @@
1
+ import{draftKey as N}from"./page-editor-arrange.js";import{draftKey as g,draftOlderThan as L}from"./block-editor-arrange.js";import{hopItems as w,hopMatches as H,hopTarget as O,keyOf as l,orderHop as $,pathFor as k,pushRecent as A,recentChips as F}from"./editor-hop-arrange.js";const b="editor-hop.recent",p=new Set;let y=!1;function K(){y||!window.M?.subscribe||(y=!0,M.subscribe("router:afterChange",({to:e}={})=>{O(e?.path)||p.clear()}))}export const hold=e=>{p.add(l(e))},takeHeld=e=>p.delete(l(e));export function hasDraft(e){try{return!!S.get(e.kind==="page"?N(e.id):g(e.id))}catch{return!1}}function h(){try{const e=S.get(b);return Array.isArray(e)?e:[]}catch{return[]}}export function noteRecent(e){const t=A(h(),e);try{S.set(b,t)}catch{}return t}const B=`
2
+ <div class="dm-hop">
3
+ <div class="dm-hop-find">
4
+ <span class="dm-hop-find-icon" data-icon="search" data-icon-size="14"></span>
5
+ <input type="text" class="form-input form-input--sm dm-hop-input" placeholder="Find a page or block\u2026"
6
+ aria-label="Find a page or block to edit" autocomplete="off" spellcheck="false">
7
+ </div>
8
+ <div class="dm-hop-group" hidden data-bind-hidden="!recent.value.length">
9
+ <span class="dm-hop-label">Recent</span>
10
+ <div class="dm-hop-chips dm-hop-recent" data-each="recent.value key=key"></div>
11
+ </div>
12
+ <div class="dm-hop-group" hidden data-bind-hidden="!related.value.length">
13
+ <span class="dm-hop-label">On this page</span>
14
+ <div class="dm-hop-chips dm-hop-related" data-each="related.value key=key"></div>
15
+ </div>
16
+ </div>`,D=`
17
+ <button type="button" class="dm-hop-chip is-{{kind}}" data-path="{{path}}" title="{{title}}">
18
+ <span class="dm-hop-kind">{{kindText}}</span><span class="dm-hop-name">{{label}}</span><span class="dm-hop-dot" aria-label="{{dotLabel}}">{{dot}}</span>
19
+ </button>`,u=e=>e==="page"?"Page":"Block";function v(e){const t=e.unsaved??hasDraft(e);return{...e,key:l(e),path:k(e),kindText:u(e.kind),title:`${u(e.kind)}: ${e.kind==="page"?`${e.label} (${e.id})`:e.id}${t?" - unsaved changes kept":""}`,dot:t?"\u2022":"",dotLabel:t?"unsaved changes":""}}function P(e){const t=document.createElement("div");t.className="dm-hop-result";const d=document.createElement("span");d.className=`dm-hop-kind is-${e.kind}`,d.textContent=u(e.kind);const s=document.createElement("span");s.className="dm-hop-name",s.textContent=e.label;const a=document.createElement("small");if(a.className="dm-hop-meta",a.textContent=e.meta,t.append(d,s,a),hasDraft(e)){const o=document.createElement("span");o.className="dm-hop-dot",o.textContent="\u2022",o.title="Unsaved changes kept",t.appendChild(o)}return t}export function mountEditorHop(e,{current:t,pages:d=[],blocks:s=[]}){K(),e.innerHTML=B,e.querySelectorAll("[data-each]").forEach(n=>{n.innerHTML=D});const a=t?.id?noteRecent(t):h(),o=w(d,s),c=new Set(o.map(l)),i={recent:M.observable(F(a.filter(n=>c.has(l(n))),t,hasDraft).map(v)),related:M.observable([])},C=M.applyBindings(i,e),m=e.querySelector(".dm-hop-input"),T=E.autocomplete?E.autocomplete(m,{data:o,minChars:0,maxResults:0,debounce:60,highlightMatches:!1,clearOnSelect:!0,emptyMessage:"No page or block matches",filterFn:H,onFilter:(n,r)=>$(n,r,{recent:a,current:t}),renderItem:n=>P(n),onSelect:n=>{m.blur(),R.navigate(k(n))}}):null,f=n=>{const r=n.target.closest(".dm-hop-chip");r?.dataset.path&&R.navigate(r.dataset.path)};return e.addEventListener("click",f),I.scan(e),{setRelated(n){const r=(Array.isArray(n)?n:[]).map(v);JSON.stringify(r)!==JSON.stringify(i.related.peek())&&(i.related.value=r)},dispose(){e.removeEventListener("click",f);try{T?.destroy?.()}catch{}C.dispose()}}}export function lastPage(){return h().find(e=>e.kind==="page")?.id||""}const x=2e5;export function heldBlockEdits(e,t={}){const d=a=>t instanceof Map?t.get(a):t[a],s={};for(const a of e){let o=null;try{o=S.get(g(a))}catch{}const c=o?.form;if(!c||typeof c.content!="string"||L(o,d(a)))continue;const i=typeof c.css=="string"?c.css:"";c.content.length>x||i.length>x||(s[a]={content:c.content,css:i})}return s}
@@ -1,3 +1,3 @@
1
- import{SHORTCODES as A}from"./shortcode-registry.js";import{EFFECTS as T,CELEBRATION_THEMES as Q,getEffectAttrs as P}from"./effect-defs.js";import{boolState as Z,boolValue as ee,findEnclosingShortcode as H,setAttr as $,setAttrs as D,effectAttrKeys as te}from"./shortcode-tags.js";import{insertAtCursor as O,replaceRange as ne}from"./markdown-toolbar.js";import{clearLookupCache as oe,lookupOptions as se}from"./shortcode-lookups.js";import{insertMenuGroups as ie,insertShortcodeByName as ae}from"./shortcode-insert.js";import{openShortcodeForm as M}from"./shortcode-form.js";import{pickMedia as ce}from"./media-picker.js";function re(e){return se(e,a?.span?.attrs||{})}const le=A.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),de=new Map(A.map(e=>[e.name,e])),j=new Map(T.map(e=>[e.name,e])),ue=T.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),B=[...le,...ue],W=new Set(["card","box","hero","banner","col","button"]);let a=null;const Y="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",m="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",F="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",pe="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function f(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),d();const t=H(e,B);N(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:n=>{d();const t=H(e,B);N(e,t,n.x,n.y)}})}function d(){a&&(w(),a.root.remove(),document.removeEventListener("mousedown",q,!0),document.removeEventListener("keydown",U,!0),window.removeEventListener("scroll",K,!0),a=null)}const q=e=>{a&&!a.root.contains(e.target)&&!(a.submenu&&a.submenu.contains(e.target))&&d()},K=e=>{a&&!a.root.contains(e.target)&&!(a.submenu&&a.submenu.contains(e.target))&&d()},U=e=>{e.key==="Escape"&&d()};function N(e,n,t,o){oe();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=Y,a={root:s,textarea:e,span:n,submenu:null};const i=n&&(de.get(n.name)||j.get(n.name)),r=n&&W.has(n.name)?T.filter(l=>l.name in n.attrs):[];i&&me(s,i);for(const l of r)ge(s,l);(i||r.length)&&s.appendChild(R()),s.appendChild(g("Celebrations\u2026",()=>Ce())),s.appendChild(g("Add effect\u2026",()=>ve())),s.appendChild(g("Insert\u2026",()=>we()));const c=e.selectionStart!==e.selectionEnd;(n||c)&&s.appendChild(g(c?"Wrap selection in\u2026":"Wrap in\u2026",()=>Te())),document.body.appendChild(s),J(s,t,o),document.addEventListener("mousedown",q,!0),document.addEventListener("keydown",U,!0),window.addEventListener("scroll",K,!0),window.I&&I.scan(s)}function R(){const e=document.createElement("div");return e.style.cssText=pe,e}function v(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function me(e,n){e.appendChild(v(`${n.label} options`));const t=f(document.createElement("button"));t.type="button",t.style.cssText=m,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:i,span:r}=a;d(),M(i,{...n,attrs:P(n,r.attrs)},{span:r})}),e.appendChild(t);for(const i of n.actions||[]){const r=f(document.createElement("button"));r.type="button",r.style.cssText=m,r.innerHTML=`<span data-icon="${i.icon||"zap"}" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>`,r.appendChild(document.createTextNode(i.label)),r.addEventListener("click",()=>{const{textarea:c,span:l}=a;if(i.remove){ne(c,l.openTagStart,l.openTagEnd,""),d();return}a.span=D(c,l,i.set||{}),d()}),e.appendChild(r)}const o=Array.isArray(n.quickAttrs)?new Set(n.quickAttrs):null,s=P(n,a.span.attrs).filter(i=>!o||o.has(i.name));(n.actions||[]).length&&s.length&&e.appendChild(R());for(const i of s)e.appendChild(V(i))}function V(e,n=e.name){const t=a.span.attrs[n];switch(e.type){case"checkbox":return fe(e,n,Z(t,e.default));case"select":return xe(e,n,t);case"lookup":return Ee(e,n,t);case"colour":return be(e,n,t);case"icon":return ye(e,n,t);default:return he(e,n,t)}}function y(e,n){if(a.span=$(a.textarea,a.span,e,n),e==="theme"){const{textarea:t,span:o,root:s}=a,i=s.getBoundingClientRect();d(),N(t,o,i.left,i.top)}}function fe(e,n,t){const o=f(document.createElement("label"));o.style.cssText=m;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>y(n,ee(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function he(e,n,t){const o=document.createElement("div");o.style.cssText=m+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=F;let r=null;if(i.addEventListener("input",()=>{clearTimeout(r),r=setTimeout(()=>y(n,i.value.trim()),180)}),i.addEventListener("mousedown",c=>c.stopPropagation()),o.appendChild(s),o.appendChild(i),e.type==="image"){const c=document.createElement("button");c.type="button",c.textContent="Browse",c.title="Choose from Media",c.style.cssText="padding:2px 8px;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:inherit;cursor:pointer;flex-shrink:0;font:inherit;",c.addEventListener("mousedown",l=>l.stopPropagation()),c.addEventListener("click",async()=>{const{textarea:l,span:u}=a;d();const b=await ce({current:i.value.trim()});b&&l.isConnected&&$(l,u,n,b)}),o.appendChild(c)}return o}function be(e,n,t){const o=document.createElement("div");o.style.cssText=m+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>y(n,i.value)),o.appendChild(s),o.appendChild(i),o}function xe(e,n,t){const o=t==null||t===!0?e.default??"":t;return g(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=C();for(const[i,r]of e.options||[]){const c=f(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent=r,i===o&&(c.style.fontWeight="700"),c.addEventListener("click",()=>{y(n,i),w()}),s.appendChild(c)}return s})}function ye(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return g(s,()=>{const i=C();i.style.width="232px";const r=document.createElement("input");r.type="text",r.placeholder="Search icons\u2026",r.style.cssText=F+"margin:6px;width:calc(100% - 12px);",r.addEventListener("mousedown",L=>L.stopPropagation()),i.appendChild(r);const c=f(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent="- none -",o||(c.style.fontWeight="700"),c.addEventListener("click",()=>{y(n,null),w()}),i.appendChild(c);const l=document.createElement("div");l.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(l);const u=96,b=()=>{const L=typeof I<"u"&&typeof I.list=="function"?I.list():[],z=r.value.trim().toLowerCase(),_=z?L.filter(p=>p.toLowerCase().includes(z)):L;l.replaceChildren();for(const p of _.slice(0,u)){const x=f(document.createElement("button"));x.type="button",x.title=p,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",p===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const k=document.createElement("span");k.setAttribute("data-icon",p),k.setAttribute("data-icon-size","15"),k.style.cssText="display:inline-flex;align-items:center;",x.appendChild(k),x.addEventListener("click",()=>{y(n,p),w()}),l.appendChild(x)}if(_.length>u){const p=document.createElement("div");p.style.cssText=S,p.textContent=`${_.length-u} more - refine search`,l.appendChild(p)}if(!_.length){const p=document.createElement("div");p.style.cssText=S,p.textContent="No icons match",l.appendChild(p)}window.I&&I.scan(l)};let h=null;return r.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(b,120)}),b(),setTimeout(()=>r.focus(),0),i})}function Ee(e,n,t){const o=t==null||t===!0?e.default??"":t;return g(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=C();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return re(e.source).then(r=>{if(i&&i.destroy(),!a||a.submenu!==s)return;if(s.style.minHeight="",!r.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const c=r.some(([u])=>u===o),l=[["","- none -"],...r];o!==""&&!c&&l.push([o,`${o} (missing)`]);for(const[u,b]of l){const h=f(document.createElement("button"));h.type="button",h.style.cssText=m,h.textContent=b,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{y(n,u===""?null:u),w()}),s.appendChild(h)}}),s})}function ge(e,n){e.appendChild(v(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(V(o,`${n.name}-${o.name}`));const t=f(document.createElement("button"));t.type="button",t.style.cssText=m,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of te(a.span.attrs,n.name))o[s]=null;D(a.textarea,a.span,o),d()}),e.appendChild(t)}function Ce(){const e=C();e.appendChild(v("Seasonal themes"));for(const[n,t]of[["auto","Auto (date-detected)"],...Object.entries(Q).map(([o,s])=>[o,s.label])]){const o=f(document.createElement("button"));o.type="button",o.style.cssText=m,o.textContent=t,o.addEventListener("click",()=>{O(a.textarea,`[celebrate theme="${n}" /]`),d()}),e.appendChild(o)}e.appendChild(R()),e.appendChild(v("Celebration effects"));for(const n of["butterflies","ticker-tape","firework","fireworks"]){const t=j.get(n),o=f(document.createElement("button"));o.type="button",o.style.cssText=m,o.textContent=t.label,o.addEventListener("click",()=>{const s=a.textarea;d(),M(s,t)}),e.appendChild(o)}return e}function ve(){const e=C(),n=a.span,t=n&&W.has(n.name)?n.name:null;for(const o of[...new Set(T.map(s=>s.category))]){const s=T.filter(i=>i.category===o);if(s.length){e.appendChild(v(o));for(const i of s){const r=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),c=f(document.createElement("button"));if(c.type="button",c.style.cssText=m+"justify-content:space-between;",c.appendChild(document.createTextNode(i.label)),r){const l=document.createElement("span");l.textContent=`inject \u2192 [${t}]`,l.style.cssText="opacity:.6;font-size:.7rem;",c.appendChild(l)}c.addEventListener("click",()=>{if(r)$(a.textarea,a.span,i.name,!0);else{const l=i.selfClosing?`[${i.name} /]`:`[${i.name}]
1
+ import{SHORTCODES as M}from"./shortcode-registry.js";import{EFFECTS as k,CELEBRATION_THEMES as J,getEffectAttrs as B}from"./effect-defs.js";import{boolState as Q,boolValue as Z,findEnclosingShortcode as ee,setAttr as N,setAttrs as W,effectAttrKeys as te}from"./shortcode-tags.js";import{insertAtCursor as R,replaceRange as ne}from"./markdown-toolbar.js";import{clearLookupCache as oe,lookupOptions as se}from"./shortcode-lookups.js";import{insertMenuGroups as ie,insertShortcodeByName as ae}from"./shortcode-insert.js";import{openShortcodeForm as A}from"./shortcode-form.js";import{pickMedia as ce}from"./media-picker.js";import{blockTagSnippet as le}from"./block-tags.js";function re(e){return se(e,l?.span?.attrs||{})}const de=M.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),j=new Map(M.map(e=>[e.name,e])),z=new Map(k.map(e=>[e.name,e])),pe=k.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),ue=[...de,...pe],F=new Set(["card","box","hero","banner","col","button"]);let l=null;const Y="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",m="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",w="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",P="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",me="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function h(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e,{blocks:t=()=>[]}={}){const n=()=>{const i=t()||[],o=i.filter(a=>a.shortTag&&!j.has(a.name)&&!z.has(a.name));return{defs:i,specs:[...ue,...o.map(a=>({name:a.name,selfClosing:!0}))]}},s=(i,o)=>{u();const{defs:a,specs:c}=n();V(e,ee(e,c),i,o,a)};return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",i=>{i.preventDefault(),s(i.clientX,i.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:i=>{s(i.x,i.y)}})}function u(){l&&(T(),l.root.remove(),document.removeEventListener("mousedown",q,!0),document.removeEventListener("keydown",K,!0),window.removeEventListener("scroll",U,!0),l=null)}const q=e=>{l&&!l.root.contains(e.target)&&!(l.submenu&&l.submenu.contains(e.target))&&u()},U=e=>{l&&!l.root.contains(e.target)&&!(l.submenu&&l.submenu.contains(e.target))&&u()},K=e=>{e.key==="Escape"&&u()};function V(e,t,n,s,i=[]){oe();const o=document.createElement("div");o.className="dm-sc-menu",o.style.cssText=Y,l={root:o,textarea:e,span:t,submenu:null,blocks:i,keepEmpty:!1};const a=new Map(i.map(p=>[p.name,p])),c=t&&(j.get(t.name)||z.get(t.name)),r=t&&(c?t.name==="block"?a.get(String(t.attrs.template||"")):null:a.get(t.name)),d=t&&F.has(t.name)?k.filter(p=>p.name in t.attrs):[];c&&fe(o,c),r&&he(o,r,!!c);for(const p of d)ve(o,p);(c||r||d.length)&&o.appendChild(L()),o.appendChild(b("Celebrations\u2026",()=>we())),o.appendChild(b("Add effect\u2026",()=>Te())),o.appendChild(b("Insert\u2026",()=>ke()));const x=e.selectionStart!==e.selectionEnd;(t||x)&&o.appendChild(b(x?"Wrap selection in\u2026":"Wrap in\u2026",()=>_e())),i.length&&(o.appendChild(L()),o.appendChild(b("Add block\u2026",()=>Le()))),document.body.appendChild(o),X(o,n,s),document.addEventListener("mousedown",q,!0),document.addEventListener("keydown",K,!0),window.addEventListener("scroll",U,!0),window.I&&I.scan(o)}function L(){const e=document.createElement("div");return e.style.cssText=me,e}function C(e){const t=document.createElement("div");return t.style.cssText=w,t.textContent=e,t}function fe(e,t){e.appendChild(C(`${t.label} options`));const n=h(document.createElement("button"));n.type="button",n.style.cssText=m,n.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',n.appendChild(document.createTextNode(`Edit ${t.label}\u2026`)),n.addEventListener("click",()=>{const{textarea:o,span:a}=l;u(),A(o,{...t,attrs:B(t,a.attrs)},{span:a})}),e.appendChild(n);for(const o of t.actions||[]){const a=h(document.createElement("button"));a.type="button",a.style.cssText=m,a.innerHTML=`<span data-icon="${o.icon||"zap"}" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>`,a.appendChild(document.createTextNode(o.label)),a.addEventListener("click",()=>{const{textarea:c,span:r}=l;if(o.remove){ne(c,r.openTagStart,r.openTagEnd,""),u();return}l.span=W(c,r,o.set||{}),u()}),e.appendChild(a)}const s=Array.isArray(t.quickAttrs)?new Set(t.quickAttrs):null,i=B(t,l.span.attrs).filter(o=>!s||s.has(o.name));(t.actions||[]).length&&i.length&&e.appendChild(L());for(const o of i)e.appendChild(H(o))}function he(e,t,n){const{span:s}=l;if(l.keepEmpty=!0,n&&e.appendChild(L()),e.appendChild(C(n?`${t.name} parameters`:`${t.name} block`)),t.description&&!n){const o=document.createElement("div");o.style.cssText="padding:0 .55rem .3rem;font-size:.75rem;color:var(--dm-text-muted,#9a9aac);max-width:260px;",o.textContent=t.description,e.appendChild(o)}const i=(o,a,c)=>{const r=h(document.createElement("button"));r.type="button",r.style.cssText=m,r.innerHTML=`<span data-icon="${o}" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>`,r.appendChild(document.createTextNode(a)),r.addEventListener("click",c),e.appendChild(r)};t.attrs.length&&i("edit","Edit parameters\u2026",()=>{const{textarea:o}=l;u(),A(o,{...t,name:s.name,label:t.name},{span:s})}),i("external-link","Open the block",()=>{u(),location.hash=`#/blocks/edit/${encodeURIComponent(t.name)}`});for(const o of t.attrs)e.appendChild(H(o));if(!t.attrs.length){const o=document.createElement("div");o.style.cssText=w+"text-transform:none;letter-spacing:0;",o.textContent="This block takes no parameters.",e.appendChild(o)}}function H(e,t=e.name){const n=l.span.attrs[t];switch(e.type){case"checkbox":return xe(e,t,Q(n,e.default));case"select":return Ee(e,t,n);case"lookup":return Ce(e,t,n);case"colour":return ye(e,t,n);case"icon":return ge(e,t,n);default:return be(e,t,n)}}function v(e,t){if(l.span=N(l.textarea,l.span,e,l.keepEmpty&&t==null?"":t,{keepEmpty:l.keepEmpty}),e==="theme"){const{textarea:n,span:s,root:i}=l,o=i.getBoundingClientRect(),a=l.blocks;u(),V(n,s,o.left,o.top,a)}}function xe(e,t,n){const s=h(document.createElement("label"));s.style.cssText=m;const i=document.createElement("input");return i.type="checkbox",i.checked=n,i.addEventListener("change",()=>v(t,Z(e,i.checked))),s.appendChild(i),s.appendChild(document.createTextNode(e.label)),s}function be(e,t,n){const s=document.createElement("div");s.style.cssText=m+"cursor:default;";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const o=document.createElement("input");o.type=e.type==="number"?"number":"text",o.value=n===!0||n==null?"":n,e.default!=null&&e.default!==""&&(o.placeholder=String(e.default)),o.style.cssText=P;let a=null;if(o.addEventListener("input",()=>{clearTimeout(a),a=setTimeout(()=>v(t,o.value.trim()),180)}),o.addEventListener("mousedown",c=>c.stopPropagation()),s.appendChild(i),s.appendChild(o),e.type==="image"){const c=document.createElement("button");c.type="button",c.textContent="Browse",c.title="Choose from Media",c.style.cssText="padding:2px 8px;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:inherit;cursor:pointer;flex-shrink:0;font:inherit;",c.addEventListener("mousedown",r=>r.stopPropagation()),c.addEventListener("click",async()=>{const{textarea:r,span:d}=l;u();const x=await ce({current:o.value.trim()});x&&r.isConnected&&N(r,d,t,x)}),s.appendChild(c)}return s}function ye(e,t,n){const s=document.createElement("div");s.style.cssText=m+"cursor:default;";const i=document.createElement("span");i.textContent=e.label,i.style.flex="1";const o=document.createElement("input");return o.type="color",typeof n=="string"&&/^#/.test(n)?o.value=n:typeof e.default=="string"&&/^#/.test(e.default)&&(o.value=e.default),o.addEventListener("input",()=>v(t,o.value)),s.appendChild(i),s.appendChild(o),s}function Ee(e,t,n){const s=n==null||n===!0?e.default??"":n;return b(`${e.label}${s!==""?`: ${s}`:""}`,()=>{const i=y();for(const[o,a]of e.options||[]){const c=h(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent=a,o===s&&(c.style.fontWeight="700"),c.addEventListener("click",()=>{v(t,o),T()}),i.appendChild(c)}return i})}function ge(e,t,n){const s=n==null||n===!0?"":String(n),i=s?`${e.label}: ${s}`:e.label;return b(i,()=>{const o=y();o.style.width="232px";const a=document.createElement("input");a.type="text",a.placeholder="Search icons\u2026",a.style.cssText=P+"margin:6px;width:calc(100% - 12px);",a.addEventListener("mousedown",_=>_.stopPropagation()),o.appendChild(a);const c=h(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent="- none -",s||(c.style.fontWeight="700"),c.addEventListener("click",()=>{v(t,null),T()}),o.appendChild(c);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",o.appendChild(r);const d=96,x=()=>{const _=typeof I<"u"&&typeof I.list=="function"?I.list():[],D=a.value.trim().toLowerCase(),S=D?_.filter(f=>f.toLowerCase().includes(D)):_;r.replaceChildren();for(const f of S.slice(0,d)){const g=h(document.createElement("button"));g.type="button",g.title=f,g.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",f===s&&(g.style.borderColor="var(--dm-primary,#5b8cff)");const $=document.createElement("span");$.setAttribute("data-icon",f),$.setAttribute("data-icon-size","15"),$.style.cssText="display:inline-flex;align-items:center;",g.appendChild($),g.addEventListener("click",()=>{v(t,f),T()}),r.appendChild(g)}if(S.length>d){const f=document.createElement("div");f.style.cssText=w,f.textContent=`${S.length-d} more - refine search`,r.appendChild(f)}if(!S.length){const f=document.createElement("div");f.style.cssText=w,f.textContent="No icons match",r.appendChild(f)}window.I&&I.scan(r)};let p=null;return a.addEventListener("input",()=>{clearTimeout(p),p=setTimeout(x,120)}),x(),setTimeout(()=>a.focus(),0),o})}function Ce(e,t,n){const s=n==null||n===!0?e.default??"":n;return b(`${e.label}${s!==""?`: ${s}`:""}`,()=>{const i=y();i.style.minHeight="48px";const o=typeof E<"u"&&E.loader?E.loader(i,{type:"dots"}):null;return re(e.source).then(a=>{if(o&&o.destroy(),!l||l.submenu!==i)return;if(i.style.minHeight="",!a.length){const d=document.createElement("div");d.style.cssText=w,d.textContent="Nothing found",i.appendChild(d)}const c=a.some(([d])=>d===s),r=[["","- none -"],...a];s!==""&&!c&&r.push([s,`${s} (missing)`]);for(const[d,x]of r){const p=h(document.createElement("button"));p.type="button",p.style.cssText=m,p.textContent=x,d===s&&(p.style.fontWeight="700"),p.addEventListener("click",()=>{v(t,d===""?null:d),T()}),i.appendChild(p)}}),i})}function ve(e,t){e.appendChild(C(`Effect: ${t.label}`));for(const s of t.attrs||[])e.appendChild(H(s,`${t.name}-${s.name}`));const n=h(document.createElement("button"));n.type="button",n.style.cssText=m,n.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',n.addEventListener("click",()=>{const s={};for(const i of te(l.span.attrs,t.name))s[i]=null;W(l.textarea,l.span,s),u()}),e.appendChild(n)}function we(){const e=y();e.appendChild(C("Seasonal themes"));for(const[t,n]of[["auto","Auto (date-detected)"],...Object.entries(J).map(([s,i])=>[s,i.label])]){const s=h(document.createElement("button"));s.type="button",s.style.cssText=m,s.textContent=n,s.addEventListener("click",()=>{R(l.textarea,`[celebrate theme="${t}" /]`),u()}),e.appendChild(s)}e.appendChild(L()),e.appendChild(C("Celebration effects"));for(const t of["butterflies","ticker-tape","firework","fireworks"]){const n=z.get(t),s=h(document.createElement("button"));s.type="button",s.style.cssText=m,s.textContent=n.label,s.addEventListener("click",()=>{const i=l.textarea;u(),A(i,n)}),e.appendChild(s)}return e}function Te(){const e=y(),t=l.span,n=t&&F.has(t.name)?t.name:null;for(const s of[...new Set(k.map(i=>i.category))]){const i=k.filter(o=>o.category===s);if(i.length){e.appendChild(C(s));for(const o of i){const a=n&&(o.hosts||[]).includes(n)&&!(o.name in t.attrs),c=h(document.createElement("button"));if(c.type="button",c.style.cssText=m+"justify-content:space-between;",c.appendChild(document.createTextNode(o.label)),a){const r=document.createElement("span");r.textContent=`inject \u2192 [${n}]`,r.style.cssText="opacity:.6;font-size:.7rem;",c.appendChild(r)}c.addEventListener("click",()=>{if(a)N(l.textarea,l.span,o.name,!0);else{const r=o.selfClosing?`[${o.name} /]`:`[${o.name}]
2
2
  Content
3
- [/${i.name}]`;O(a.textarea,l)}d()}),e.appendChild(c)}}}return e}function g(e,n){const t=f(document.createElement("button"));t.type="button",t.style.cssText=m+"justify-content:space-between;",t.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(o),t.addEventListener("mouseenter",()=>G(t,n)),t.addEventListener("click",s=>{s.stopPropagation(),G(t,n)}),t}function C(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=Y,e}function G(e,n){w();const t=n();a.submenu=t,document.body.appendChild(t);const o=e.getBoundingClientRect();J(t,o.right+2,o.top,o.left-2),window.I&&I.scan(t)}function w(){a&&a.submenu&&(a.submenu.remove(),a.submenu=null)}function X(e){const n=f(document.createElement("button"));if(n.type="button",n.style.cssText=m,e.icon){const t=document.createElement("span");t.setAttribute("data-icon",e.icon),t.setAttribute("data-icon-size","14"),t.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(t)}return n.appendChild(document.createTextNode(e.label)),n}function we(){const e=C();for(const{category:n,items:t}of ie()){e.appendChild(v(n));for(const o of t){const s=X(o);s.addEventListener("click",()=>{const i=a.textarea;d(),ae(o.name,i)}),e.appendChild(s)}}return e}function Te(){const e=C();for(const n of A.filter(t=>t.wrappable||t.container)){const t=X(n);t.addEventListener("click",()=>{const o=a.textarea;d(),M(o,n)}),e.appendChild(t)}return e}function J(e,n,t,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=n;n+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:n-s.width);let r=t;t+s.height>window.innerHeight-8&&(r=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,r)}px`}
3
+ [/${o.name}]`;R(l.textarea,r)}u()}),e.appendChild(c)}}}return e}function b(e,t){const n=h(document.createElement("button"));n.type="button",n.style.cssText=m+"justify-content:space-between;",n.appendChild(document.createTextNode(e));const s=document.createElement("span");return s.setAttribute("data-icon","chevron-right"),s.setAttribute("data-icon-size","12"),s.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(s),n.addEventListener("mouseenter",()=>G(n,t)),n.addEventListener("click",i=>{i.stopPropagation(),G(n,t)}),n}function y(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=Y,e}function G(e,t){T();const n=t();l.submenu=n,document.body.appendChild(n);const s=e.getBoundingClientRect();X(n,s.right+2,s.top,s.left-2),window.I&&I.scan(n)}function T(){l&&l.submenu&&(l.submenu.remove(),l.submenu=null)}function O(e){const t=h(document.createElement("button"));if(t.type="button",t.style.cssText=m,e.icon){const n=document.createElement("span");n.setAttribute("data-icon",e.icon),n.setAttribute("data-icon-size","14"),n.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(n)}return t.appendChild(document.createTextNode(e.label)),t}function ke(){const e=y();for(const{category:t,items:n}of ie()){e.appendChild(C(t));for(const s of n){const i=O(s);i.addEventListener("click",()=>{const o=l.textarea;u(),ae(s.name,o)}),e.appendChild(i)}}return e}function Le(){const e=y();e.style.maxWidth="320px";const t=document.createElement("input");t.type="text",t.placeholder="Find a block\u2026",t.style.cssText=P+"margin:6px;width:calc(100% - 12px);",t.addEventListener("mousedown",i=>i.stopPropagation());const n=document.createElement("div");e.append(t,n);const s=()=>{const i=t.value.trim().toLowerCase(),o=l.blocks.filter(a=>!i||a.name.includes(i)||a.description.toLowerCase().includes(i));n.replaceChildren();for(const a of o){const c=O(a);if(a.description&&(c.title=a.description),!a.shortTag){const r=document.createElement("small");r.style.cssText="margin-left:auto;opacity:.6;",r.textContent="[block]",r.title='A built-in shortcode has this name, so it goes in as [block template="\u2026"]',c.appendChild(r)}c.addEventListener("click",()=>{const r=l.textarea;u();const d=r.selectionStart,x=le(a);R(r,x);const p=x.indexOf('=""');p>=0&&r.setSelectionRange(d+p+2,d+p+2),r.focus()}),n.appendChild(c)}if(!o.length){const a=document.createElement("div");a.style.cssText=w,a.textContent="No blocks match",n.appendChild(a)}window.I&&I.scan(n)};return t.addEventListener("input",s),s(),setTimeout(()=>t.focus(),0),e}function _e(){const e=y();for(const t of M.filter(n=>n.wrappable||n.container)){const n=O(t);n.addEventListener("click",()=>{const s=l.textarea;u(),A(s,t)}),e.appendChild(n)}return e}function X(e,t,n,s){e.style.left="0px",e.style.top="0px";const i=e.getBoundingClientRect();let o=t;t+i.width>window.innerWidth-8&&(o=s!=null?s-i.width:t-i.width);let a=n;n+i.height>window.innerHeight-8&&(a=Math.max(8,window.innerHeight-i.height-8)),e.style.left=`${Math.max(8,o)}px`,e.style.top=`${Math.max(8,a)}px`}
@@ -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">
@@ -94,7 +92,11 @@
94
92
  <button type="button" class="live-preview-width" title="Mobile (390px)" aria-label="Mobile width" data-on-click="setWidth('390')"
95
93
  data-bind-class="pvWidth.value === '390' && 'active'"><span data-icon="smartphone" data-icon-size="14"></span></button>
96
94
  </div>
97
- <select class="form-select form-select-sm bex-pv-select" aria-label="How many copies" data-model="pvRepeat.value">
95
+ <select id="block-preview-on" class="form-select form-select-sm bex-pv-select bex-pv-on" aria-label="Show the block on"
96
+ title="Show the block on a page that uses it" data-model="pvOn.value" data-bind-hidden="noPages">
97
+ <option value="">A blank page</option>
98
+ </select>
99
+ <select class="form-select form-select-sm bex-pv-select" aria-label="How many copies" data-model="pvRepeat.value" data-bind-hidden="pvOn.value">
98
100
  <option value="1">One</option>
99
101
  <option value="3">Three in a grid</option>
100
102
  </select>
@@ -102,7 +104,7 @@
102
104
  <option value="">Site theme</option>
103
105
  </select>
104
106
  <small class="bex-pv-status" data-bind-text="pvStatus.value"></small>
105
- <span data-help-title="Preview" data-help="The block drawn inside your site's own page - its theme, fonts, site styles and Custom CSS - exactly as a page would show it, from what is in the editor now. Try it at tablet or mobile width, as three entries in a collection grid, or in another theme. Links in the preview do not navigate."></span>
107
+ <span data-help-title="Preview" data-help="The block drawn inside your site's own page - its theme, fonts, site styles and Custom CSS - exactly as a page would show it, from what is in the editor now. Try it at tablet or mobile width, as three entries in a collection grid, or in another theme. Show on puts it on a page that uses it - the page you came from first - drawn whole, with your changes to the block (and any unsaved text on that page) and the block outlined. Links, buttons and forms in the preview do nothing."></span>
106
108
  </div>
107
109
  <div class="bex-stage">
108
110
  <iframe id="block-preview-frame" class="page-live-preview-frame" src="about:blank" title="Block preview"
@@ -186,7 +188,6 @@
186
188
  .bex-note { margin: 0; }
187
189
  .bex-cursor { font-size: var(--dm-text-xs); font-variant-numeric: tabular-nums; }
188
190
  .bex-spacer { flex: 1; }
189
- .bex-switch { width: auto; max-width: 16rem; }
190
191
  .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
192
  background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
192
193
  .bex-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
@@ -233,6 +234,7 @@
233
234
  .bex-output { flex: 1; min-height: 0; display: flex; flex-direction: column; }
234
235
  .bex-output-bar { flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; padding: .4rem .75rem; border-bottom: 1px solid var(--dm-border); }
235
236
  .bex-pv-select { width: auto; }
237
+ .bex-pv-on { max-width: 15rem; }
236
238
  .bex-pv-status { flex: 1 1 auto; color: var(--dm-text-muted); font-size: var(--dm-text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
237
239
  .bex-stage { flex: 1; min-height: 0; display: flex; justify-content: center; overflow: auto; background: var(--dm-background-alt, var(--dm-surface)); }
238
240
  .bex-stage .page-live-preview-frame { border-radius: 0; }
@@ -1,18 +1,18 @@
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>
14
+ <small class="pex-held" hidden data-bind-hidden="!heldNote.value" data-bind-text="heldNote.value"
15
+ title="Those blocks' changes are kept in the block editor - save them there"></small>
16
16
  <span class="pex-spacer"></span>
17
17
  <a class="btn btn-ghost btn-sm" target="_blank" rel="noopener" hidden data-bind-hidden="!isEdit" data-bind-href="savedPath">
18
18
  <span data-icon="external-link"></span> View page
@@ -291,6 +291,7 @@
291
291
  .pex-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px;
292
292
  background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
293
293
  .pex-problem { color: var(--dm-danger); font-size: var(--dm-text-sm); }
294
+ .pex-held { color: var(--dm-warning); font-size: var(--dm-text-sm); }
294
295
  .pex-bar [hidden], .editor-card [hidden], #editor-meta-tabs [hidden] { display: none !important; }
295
296
  .pe-hist-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
296
297
  .pe-hist-head h3 { margin: 0; font-size: 1rem; }
@@ -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 g}from"../api.js";import{addBannerAction as ee}from"../lib/plugin-chrome.js";import{helpPopovers as kt}from"../lib/help-popover.js";import{openMenuAt as ze,sidebarCount as gt}from"../lib/tool-kit.js";import{createSimpleEditor as Je}from"../lib/simple-editor.js";import{checkForm as wt,draftKey as B,draftOffer as St,escapeHtml as Ge,extractPlaceholders as Et,formFromBlock as Mt,formatHtml as Ct,draftOlderThan as xt,pageFrontmatter as At,pageInFrame as Tt,payloadFromForm as Lt,previewMessage as $t,previewPageOptions as Ot,previewShell as Rt,rawPlaceholders as Qe,renderPreview as Dt,sampleValue as Xe,scopeName as jt,shortcodeFor as Ft,snapshot as Ee}from"../lib/block-editor-arrange.js";import{draftKey as Pt,readDraft as qt}from"../lib/page-editor-arrange.js";import{fmtDateTime as Ze}from"../lib/dates.js";import{hold as It,lastPage as Bt,mountEditorHop as Ht,takeHeld as Nt}from"../lib/editor-hop.js";import{isHop as Wt}from"../lib/editor-hop-arrange.js";const _t=/^\/blocks\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,te=u=>String(u||"").split("?")[0],Me="block-editor.mode",Kt=["write","split","preview"],et="block-editor.preview",tt=["full","1024","390"],Vt="/__block-preview",Ce="dm-block-preview-root",ot={title:"Block preview",layout:"blank",breadcrumbs:!1},Yt={ADD_ATTR:["target","loading"],ALLOW_DATA_ATTR:!0,FORCE_BODY:!0},Ut=()=>window.DOMPurify?.sanitize?u=>window.DOMPurify.sanitize(u,Yt):null,zt=`
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 w=null,at=!1;function Jt(){at||!window.R?.use||(at=!0,R.use((u,m,l)=>{if(!(_t.test(m?.path||"")&&te(u?.path)!==te(m?.path))||!w?.dirty())return l();const s=w;return Wt(m?.path,u?.path)?(It(s.item),l()):E.confirm(`You have unsaved changes to ${Ge(s.label())}. Leave anyway? They are kept as a draft for your next visit.`).then(Y=>{Y?l():history.replaceState(null,"",s.hash)})}),M.subscribe("router:afterChange",({to:u}={})=>{if(w&&te(u?.path)!==w.path){const m=w;w=null,m.dispose()}}))}function Gt(u,m){const l=u.querySelector(".dm-plugin-banner-name"),O=l?.lastChild;O&&O.nodeType===3&&l.querySelector(".dm-plugin-banner-sep")&&(O.textContent=m)}function oe(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(Jt(),w){const e=w;w=null,e.dispose()}const m=u.get(0),l=m.querySelector(".bex")||m,O=[],s=e=>(O.push(e),e);let Y=!1;const f=()=>!Y&&l.isConnected,xe=location.hash.match(/^#\/blocks\/edit\/([^/?#]+)/),r=xe?decodeURIComponent(xe[1]):null,d=!r,nt=location.hash,st=te(location.hash.slice(1)),rt=d&&new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",Ae=E.loader(m,{type:"dots"});let T=null,P=[],ae=[],ne=[],Te=null;const U=Bt();try{[T,P,ae,ne]=await Promise.all([r?g.blocks.get(r):Promise.resolve(null),g.get("/blocks?usage=1").catch(()=>g.blocks.list()).catch(()=>[]),g.projects.list().catch(()=>[]),g.pages.list().catch(()=>[])]),Te=(Array.isArray(P)?P:[]).find(e=>e.name===r)?.usage||null}catch(e){Ae.destroy(),E.toast(e.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}if(Ae.destroy(),!l.isConnected)return;const lt=(Array.isArray(P)?P:[]).map(e=>e.name).sort((e,t)=>e.localeCompare(t));let Le=T?.meta||{};const v=Mt(T?{...T,name:r}:null,{project:rt}),n={};for(const[e,t]of Object.entries(v))n[e]=M.observable(t);const se=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),$e=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),H=M.observable(Ee(v)),N=M.computed(()=>Ee($e())!==H.value),W=M.observable(!1),z=M.observable(!1),J=M.computed(()=>wt($e(),{isNew:d,taken:lt}));let Oe="split";try{Oe=Kt.includes(S.get(Me))?S.get(Me):"split"}catch{}const h=M.observable(Oe);s(M.effect(()=>{const e=h.value;try{S.set(Me,e)}catch{}}));const Re=M.observable("Ln 1, Col 1");let q={};try{q=S.get(et)||{}}catch{}const re=M.observable(tt.includes(q.width)?q.width:"full"),G=M.observable(String(q.repeat)==="3"?"3":"1"),p=M.observable(typeof q.theme=="string"?q.theme:""),b=M.observable(""),le=Ot(Te,ne,U),y=M.observable(le[0]?.value===U&&U?U:"");s(M.effect(()=>{const e={width:re.value,repeat:G.value,theme:p.value};try{S.set(et,e)}catch{}}));const C={},_={},Q=M.observable(0),ce=M.observable([]),De=()=>Qe(n.content.peek());s(M.effect(()=>{const e=Et(n.content.value),t=Qe(n.content.value);let a=!1;for(const o of e){const k=t.has(o);if(!(o in C)||o in _&&_[o]!==k){const F=o in C;if(C[o]=Xe(o,new Date,{raw:k}),_[o]=k,F){a=!0;const c=l.querySelector(`.bex-samples [data-sample="${CSS.escape(o)}"]`);c&&(c.value=C[o])}}}ce.value=e.map(o=>({key:o,label:t.has(o)?`{{{${o}}}}`:`{{${o}}}`,value:C[o]})),a&&queueMicrotask(()=>Q.value++)}));const ct={...n,dirty:N,saving:W,mode:h,cursor:Re,sampleRows:ce,isNew:d,notNew:!d,pvWidth:re,pvRepeat:G,pvTheme:p,pvStatus:b,pvOn:y,noPages:le.length===0,titleText:d?"New block":r,metaText:M.computed(()=>{if(d)return"Not saved yet";const e=[];return(T?.updatedAt||T?.meta?.updatedAt)&&e.push(`changed ${Ze(T.updatedAt||T.meta.updatedAt)}`),e.push(`${n.content.value.split(`
7
+ `).length} lines`),n.css.value.trim()&&e.push(`${Math.ceil(n.css.value.length/1024)} KB CSS`),e.join(" \xB7 ")}),nameErr:M.computed(()=>(z.value||n.name.value)&&J.value.errors.name||""),cssErr:M.computed(()=>J.value.errors.css||""),scopeHint:M.computed(()=>`[data-block="${jt(n.name.value)}"]`),noSamples:M.computed(()=>ce.value.length===0),noSamplesText:"No {{placeholders}} in the template yet.",modeCls:M.computed(()=>`bex-body is-${h.value}`),cannotSave:M.computed(()=>W.value||z.value&&Object.keys(J.value.errors).length>0)},ie=l.querySelector("#block-project");for(const e of Array.isArray(ae)?ae:[]){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,ie.appendChild(t)}if(v.project&&![...ie.options].some(e=>e.value===v.project)){const e=document.createElement("option");e.value=v.project,e.textContent=`${v.project} (no longer a project)`,ie.appendChild(e)}const ue={kind:"block",id:r||"",label:r||""},it=l.querySelector("#block-preview-on");for(const e of le)it.appendChild(new Option(e.label,e.value));const x=l.querySelector(".bex-samples");x.innerHTML=zt;let D=null,K=null,i=null;function de(e){for(const[t,a]of Object.entries(e))n[t]&&(n[t].value=a);D.setValue(String(e.content??"")),K.setValue(String(e.css??""))}const A=l.querySelector("#block-preview-frame");let j="none",L=0;const ut="As your site shows it \xB7 not saved",je=()=>{if(!f()||h.peek()==="write"||j!=="ready"||y.peek())return;const e={content:n.content.peek(),css:n.css.peek(),name:n.name.peek()},t=Ut();if(!t){b.value="The preview needs the HTML cleaner, which did not load - reload the admin.";return}const a=Dt(e,C,{repeat:Number(G.peek()),sanitize:t});try{A.contentWindow?.postMessage({source:"dm-block-editor",type:"paint",tick:L,html:a},"*")}catch{}},Fe=e=>{const t=$t(e,A.contentWindow,L);if(!(!t||!f())){if(t.type==="save"){Z();return}if(!y.peek()){if(j=t.host?"ready":"failed",j==="failed"){b.value="Your site's page came back without a place for the block - the preview cannot show it.";return}b.peek()==="Loading your site\u2026"&&(b.value=ut),je()}}};window.addEventListener("message",Fe),s(()=>window.removeEventListener("message",Fe));async function pe(){const e=++L;j="loading",b.value="Loading your site\u2026";let t;try{const a=p.peek()?{...ot,theme:p.peek()}:ot;({html:t}=await g.pages.previewFull(Vt,a,`<div id="${Ce}"></div>`))}catch(a){t=`<!doctype html><html><head><meta charset="utf-8"></head><body style="font:14px system-ui;margin:0"><div id="${Ce}" style="padding:1rem"></div></body></html>`,f()&&e===L&&(b.value=`Without your site's styles - ${a?.message||"the page did not render"}`)}!f()||e!==L||(A.srcdoc=Rt(t,e,Ce))}let V=null,me=null,fe=0,ve=!0,X=null;const Pe=()=>{const e=X;if(X=null,!(!e||!y.peek()))try{const t=e.toBlock?A.contentDocument?.querySelector(`[data-block="${CSS.escape(r)}"]`):null;t?t.scrollIntoView({block:"center"}):A.contentWindow.scrollTo(0,e.y)}catch{}};A.addEventListener("load",Pe),s(()=>A.removeEventListener("load",Pe));async function dt(e){if(V?.path===e)return V;const t=await g.pages.get(e);let a=null;try{a=qt(S.get(Pt(e)))}catch{}a&&xt(a,t?.updatedAt)&&(a=null);const o=typeof a?.state?.body=="string"?a.state.body:String(t?.content??"");return V={path:e,frontmatter:At(t),body:o,withDraft:typeof a?.state?.body=="string"},V}const he=(e=!1)=>{clearTimeout(me),me=setTimeout(async()=>{const t=y.peek();if(!t||!f()||h.peek()==="write")return;const a=++fe;try{const o=await dt(t),k=p.peek()?{...o.frontmatter,theme:p.peek()}:o.frontmatter,{html:F}=await g.pages.previewFull(t,k,o.body,{[r]:{content:n.content.peek(),css:n.css.peek()}});if(!f()||a!==fe||y.peek()!==t)return;let c=0;try{c=A.contentWindow.scrollY||0}catch{}X={toBlock:ve||!!X?.toBlock,y:c},ve=!1,A.srcdoc=Tt(F,r,L),b.value=`On ${t} \xB7 not saved${o.withDraft?" \xB7 with the page's unsaved text":""}`}catch(o){f()&&a===fe&&(b.value=`Could not show ${t} - ${o?.message||"the page did not render"}`)}},e?0:450)};s(()=>clearTimeout(me));let qe=y.peek();s(M.effect(()=>{const e=y.value;e!==qe&&(qe=e,ve=!0,V=null,L++,j="none",e?(b.value=`Loading ${e}\u2026`,he(!0)):h.peek()!=="write"&&pe())}));let be=0;s(M.effect(()=>{if(n.content.value,n.css.value,n.name.value,Q.value,G.value,h.value!=="write"){if(y.peek()){he();return}if(j==="none"){pe();return}cancelAnimationFrame(be),be=requestAnimationFrame(je)}}));let Ie=p.peek();s(M.effect(()=>{const e=p.value;e!==Ie&&(Ie=e,y.peek()?he(!0):j!=="none"&&pe())})),s(()=>{cancelAnimationFrame(be),L++}),g.theme.catalog().then(({themes:e=[],custom:t=[]}={})=>{if(!f())return;const a=l.querySelector("#block-preview-theme"),o=c=>String(c).replace(/-/g," ").replace(/\b\w/g,we=>we.toUpperCase()),k=[...e.filter(c=>!String(c).startsWith("admin-")).map(c=>({id:c,label:o(c)})),...(Array.isArray(t)?t:[]).filter(c=>c?.id).map(c=>({id:c.id,label:c.label||o(c.id)}))],F=k.map(c=>c.id);for(const{id:c,label:we}of k){const Se=document.createElement("option");Se.value=c,Se.textContent=we,a.appendChild(Se)}p.peek()&&!F.includes(p.peek())?p.value="":a.value=p.peek()}).catch(()=>{l.querySelector("#block-preview-theme")?.remove(),f()&&p.peek()&&(p.value="")});const Be=e=>{const t=e.target?.dataset?.sample;t&&(C[t]=e.target.value,delete _[t],Q.value++)};x.addEventListener("input",Be),s(()=>x.removeEventListener("input",Be));function ye(e){i.focus(),i.setRangeText(e,i.selectionStart,i.selectionEnd,"end"),i.dispatchEvent(new Event("input",{bubbles:!0}))}async function He(e){switch(e){case"undo":i.focus(),document.execCommand("undo");break;case"redo":i.focus(),document.execCommand("redo");break;case"cut":i.selectionStart!==i.selectionEnd&&(await oe(i.value.slice(i.selectionStart,i.selectionEnd)),ye(""));break;case"copy":i.selectionStart!==i.selectionEnd&&await oe(i.value.slice(i.selectionStart,i.selectionEnd));break;case"paste":try{ye(await navigator.clipboard.readText())}catch{E.toast("Use Ctrl+V to paste.",{type:"info"})}break;case"select-all":i.focus(),i.select();break;case"format":{const t=Ct(i.value);D.setValue(t),n.content.value=t;break}default:break}}async function Z(){if(W.peek())return;z.value=!0;const{errors:e,name:t}=J.peek();if(Object.keys(e).length){E.toast(e.name||e.css,{type:"warning"});return}const a=d?t:r;W.value=!0;try{const o=Lt(se(),Le);if(await g.blocks.put(a,o),!f())return;Le=o.meta,H.value=Ee(se());try{S.remove?S.remove(B(r)):S.set(B(r),null)}catch{}gt("#/blocks"),E.toast(d?`Created "${a}".`:"Block saved.",{type:"success"}),d&&(location.hash=`#/blocks/edit/${encodeURIComponent(a)}`)}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{W.value=!1}}async function pt(){N.peek()&&await E.confirm("Discard the changes you have not saved?")&&(z.value=!1,de(mt(H.peek())))}function mt(e){try{const[t,a,o,k,F]=JSON.parse(e);return{name:t,project:a,bundled:o,content:k,css:F}}catch{return v}}const ft=M.applyBindings(ct,l,{methods:{save:()=>Z(),discard:()=>pt(),setWrite:()=>{h.value="write"},setSplit:()=>{h.value="split"},setPreview:()=>{h.value="preview"},setWidth:e=>{tt.includes(String(e))&&(re.value=String(e))},act:e=>He(e)}});s(()=>ft.dispose());const vt=Ht(l.querySelector("#block-hop"),{current:ue,pages:ne,blocks:P});s(()=>vt.dispose()),s(kt(l,{position:"bottom"})),D=Je(l.querySelector(".bex-write"),{initialValue:v.content,language:"html",autoIndent:!0,onChange:e=>{n.content.value=e},onCursor:({line:e,col:t})=>{Re.value=`Ln ${e}, Col ${t}`}}),K=Je(l.querySelector(".bex-css"),{initialValue:v.css,language:"css",autoIndent:!0,onChange:e=>{n.css.value=e}}),D.textarea.setAttribute("aria-label","Template HTML"),D.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>`,K.textarea.setAttribute("aria-label","Custom CSS"),K.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; } */`,s(()=>{D.destroy(),K.destroy()}),i=D.textarea;const Ne=l.querySelector(".bex-toolbar"),We=e=>{const t=e.target.closest("[data-action]");t&&He(t.dataset.action)};Ne.addEventListener("click",We),s(()=>Ne.removeEventListener("click",We));const ke=e=>De().has(e)?`{{{${e}}}}`:`{{${e}}}`,ht=e=>[{header:ke(e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>ye(ke(e))},{label:"Copy placeholder",icon:"copy",action:async()=>{const t=ke(e);E.toast(await oe(t)?"Copied.":t,{type:"info"})}},{label:"Reset sample value",icon:"rotate-ccw",action:()=>{const t=De().has(e);C[e]=Xe(e,new Date,{raw:t}),_[e]=t;const a=x.querySelector(`[data-sample="${CSS.escape(e)}"]`);a&&(a.value=C[e]),Q.value++}}],bt=E.contextMenu(x,{cursor:"glyph",match:".bex-sample",inherit:!1,density:"compact",items:e=>ht(e.dataset.key)});s(()=>bt?.destroy?.());const _e=e=>{const t=e.target.closest(".bex-kebab");t&&ze(t)},Ke=e=>{const t=e.target.closest(".bex-sample");t&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),ze(t))};x.addEventListener("click",_e),x.addEventListener("keydown",Ke),s(()=>{x.removeEventListener("click",_e),x.removeEventListener("keydown",Ke)}),Gt(m,d?"New block":r),ee(u,{id:"block-editor-list",icon:"arrow-left",title:"All blocks",onClick:()=>{location.hash="#/blocks"}}),d?m.querySelector('[data-banner-action="block-editor-shortcode"]')?.remove():ee(u,{id:"block-editor-shortcode",icon:"copy",title:"Copy shortcode",onClick:async()=>{const e=Ft(r);E.toast(await oe(e)?"Shortcode copied - set the collection slug.":e,{type:"info",duration:5e3})}}),ee(u,{id:"block-editor-guide",icon:"book",title:"Blocks guide",onClick:()=>{location.hash="#/docs/usage/blocks"}}),ee(u,{id:"block-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>Z()});const Ve=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),Z())};document.addEventListener("keydown",Ve,!0),s(()=>document.removeEventListener("keydown",Ve,!0));const Ye=()=>{if(N.peek())try{S.set(B(r),{form:se(),base:H.peek(),at:new Date().toISOString()})}catch{}},Ue=e=>{N.peek()&&(Ye(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Ue),s(()=>window.removeEventListener("beforeunload",Ue)),w={hash:nt,path:st,item:ue,dirty:()=>N.peek(),label:()=>d?"the new block":`"${r}"`,dispose(){Y=!0,Ye(),O.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(l);let ge=null;try{ge=S.get(B(r))}catch{}const $=St(ge,H.peek()),yt=Nt(ue);if(ge)try{S.remove?S.remove(B(r)):S.set(B(r),null)}catch{}if($&&yt&&!$.stale){const e={...v,...$.form};d||(e.name=r),de(e),E.toast("Your unsaved changes to this block are back.",{type:"info"})}else if($){const e=$.at?` (${Ze($.at)})`:"",t=d?"a new block":`"${Ge(r)}"`,a=$.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(a)&&f()){const o={...v,...$.form};d||(o.name=r),de(o)}}}};