domma-cms 1.8.21 → 1.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.23] - 2026-10-17
4
+
5
+ - **Every block opens in the page editor.** Picking a block in the page editor's bar - the **Blocks** list, the search or a recent one - now opens it in a tab beside the page instead of going to the Blocks editor, so any block can be edited there, not only the ones the page uses. A block that is not on the page says so (the preview cannot show it until it is added - right-click › Add block). Picking a page still opens that page.
6
+
7
+ ## [1.8.22] - 2026-10-17
8
+
9
+ - **Edit a page's blocks in the page editor.** Every block the page uses now has a tab above the editor - the blocks in its text first, then the footer's. Open one and edit the block's HTML and CSS beside the page, with the preview showing the whole page with the block as you type (outlined, and scrolled to). **Save** saves the page and every block you changed together ("Page and block "cta-banner" saved."); a block that is also used on other pages, in a view or in the footer says so before the first save that changes it, and the tab bar shows where else it is. **Discard** undoes block changes too, and leaving keeps them as before. Right-click a block's tag › **Edit the block** opens its tab. Changes made in the Blocks editor come back with the page as unsaved work in the tab, so either editor can finish them. The switcher bar's "On this page" chips are gone - the tabs replace them.
10
+
3
11
  ## [1.8.21] - 2026-10-17
4
12
 
5
13
  - `[scribe]` fade, bounce and glow now follow `speed` and `delete-speed`: a slow speed fades each unit in over that time, and undone text fades out over `delete-speed` before the next action (domma-js 0.53.2, see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.53.2)).
@@ -1,4 +1,4 @@
1
- import{draftKey as B}from"./page-editor-arrange.js";import{draftKey as $,draftOlderThan as H}from"./block-editor-arrange.js";import{hopItems as P,hopMatches as F,hopTarget as K,keyOf as l,orderHop as D,pathFor as u,pushRecent as q,recentChips as _}from"./editor-hop-arrange.js";const C="editor-hop.recent",f=new Set;let T=!1;function J(){T||!window.M?.subscribe||(T=!0,M.subscribe("router:afterChange",({to:e}={})=>{K(e?.path)||f.clear()}))}export const hold=e=>{f.add(l(e))},takeHeld=e=>f.delete(l(e));export function hasDraft(e){try{return!!S.get(e.kind==="page"?B(e.id):$(e.id))}catch{return!1}}function g(){try{const e=S.get(C);return Array.isArray(e)?e:[]}catch{return[]}}export function noteRecent(e){const t=q(g(),e);try{S.set(C,t)}catch{}return t}const z=`
1
+ import{draftKey as F}from"./page-editor-arrange.js";import{draftKey as $,draftOlderThan as K}from"./block-editor-arrange.js";import{hopItems as A,hopMatches as D,hopTarget as C,keyOf as l,orderHop as q,pathFor as m,pushRecent as _,recentChips as z}from"./editor-hop-arrange.js";const T="editor-hop.recent",f=new Set;let L=!1;function U(){L||!window.M?.subscribe||(L=!0,M.subscribe("router:afterChange",({to:e}={})=>{C(e?.path)||f.clear()}))}export const hold=e=>{f.add(l(e))},takeHeld=e=>f.delete(l(e));export function hasDraft(e){try{return!!S.get(e.kind==="page"?F(e.id):$(e.id))}catch{return!1}}function k(){try{const e=S.get(T);return Array.isArray(e)?e:[]}catch{return[]}}export function noteRecent(e){const t=_(k(),e);try{S.set(T,t)}catch{}return t}const W=`
2
2
  <div class="dm-hop">
3
3
  <div class="dm-hop-find">
4
4
  <span class="dm-hop-find-icon" data-icon="search" data-icon-size="14"></span>
@@ -13,11 +13,7 @@ import{draftKey as B}from"./page-editor-arrange.js";import{draftKey as $,draftOl
13
13
  <span class="dm-hop-label">Recent</span>
14
14
  <div class="dm-hop-chips dm-hop-recent" data-each="recent.value key=key"></div>
15
15
  </div>
16
- <div class="dm-hop-group" hidden data-bind-hidden="!related.value.length">
17
- <span class="dm-hop-label">On this page</span>
18
- <div class="dm-hop-chips dm-hop-related" data-each="related.value key=key"></div>
19
- </div>
20
- </div>`,U=`
16
+ </div>`,X=`
21
17
  <button type="button" class="dm-hop-chip is-{{kind}}" data-path="{{path}}" title="{{title}}">
22
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>
23
- </button>`,k=e=>e==="page"?"Page":"Block";function L(e){const t=e.unsaved??hasDraft(e);return{...e,key:l(e),path:u(e),kindText:k(e.kind),title:`${k(e.kind)}: ${e.kind==="page"?`${e.label} (${e.id})`:e.id}${t?" - unsaved changes kept":""}`,dot:t?"\u2022":"",dotLabel:t?"unsaved changes":""}}function W(e){const t=document.createElement("div");t.className="dm-hop-result";const r=document.createElement("span");r.className=`dm-hop-kind is-${e.kind}`,r.textContent=k(e.kind);const c=document.createElement("span");c.className="dm-hop-name",c.textContent=e.label;const o=document.createElement("small");if(o.className="dm-hop-meta",o.textContent=e.meta,t.append(r,c,o),hasDraft(e)){const s=document.createElement("span");s.className="dm-hop-dot",s.textContent="\u2022",s.title="Unsaved changes kept",t.appendChild(s)}return t}export function mountEditorHop(e,{current:t,pages:r=[],blocks:c=[]}){J(),e.innerHTML=z,e.querySelectorAll("[data-each]").forEach(n=>{n.innerHTML=U});const o=t?.id?noteRecent(t):g(),s=P(r,c),d=new Set(s.map(l)),p={recent:M.observable(_(o.filter(n=>d.has(l(n))),t,hasDraft).map(L)),related:M.observable([])},O=M.applyBindings(p,e),v=e.querySelector(".dm-hop-input"),w=E.autocomplete?E.autocomplete(v,{data:s,minChars:0,maxResults:0,debounce:60,highlightMatches:!1,clearOnSelect:!0,emptyMessage:"No page or block matches",filterFn:F,onFilter:(n,a)=>D(n,a,{recent:o,current:t}),renderItem:n=>W(n),onSelect:n=>{v.blur(),R.navigate(u(n))}}):null,A=[...e.querySelectorAll(".dm-hop-pick")];for(const n of A){const a=n.dataset.kind,h=s.filter(i=>i.kind===a);n.append(new Option(`${a==="page"?"Pages":"Blocks"} (${h.length})\u2026`,""));for(const i of h){const m=t?.id&&l(i)===l(t),x=new Option(`${!m&&hasDraft(i)?"\u2022 ":""}${a==="page"?`${i.label} (${i.id})`:i.label}${m?" - editing":""}`,u(i));x.disabled=!!m,n.append(x)}n.hidden=h.length===0}const b=n=>{const a=n.target.closest(".dm-hop-pick");if(!a?.value)return;const h=a.value;a.value="",R.navigate(h)};e.addEventListener("change",b);const y=n=>{const a=n.target.closest(".dm-hop-chip");a?.dataset.path&&R.navigate(a.dataset.path)};return e.addEventListener("click",y),I.scan(e),{setRelated(n){const a=(Array.isArray(n)?n:[]).map(L);JSON.stringify(a)!==JSON.stringify(p.related.peek())&&(p.related.value=a)},dispose(){e.removeEventListener("click",y),e.removeEventListener("change",b);try{w?.destroy?.()}catch{}O.dispose()}}}export function lastPage(){return g().find(e=>e.kind==="page")?.id||""}const N=2e5;export function heldBlockEdits(e,t={}){const r=o=>t instanceof Map?t.get(o):t[o],c={};for(const o of e){let s=null;try{s=S.get($(o))}catch{}const d=s?.form;if(!d||typeof d.content!="string"||H(s,r(o)))continue;const p=typeof d.css=="string"?d.css:"";d.content.length>N||p.length>N||(c[o]={content:d.content,css:p})}return c}
19
+ </button>`,g=e=>e==="page"?"Page":"Block";function Y(e){const t=e.unsaved??hasDraft(e);return{...e,key:l(e),path:m(e),kindText:g(e.kind),title:`${g(e.kind)}: ${e.kind==="page"?`${e.label} (${e.id})`:e.id}${t?" - unsaved changes kept":""}`,dot:t?"\u2022":"",dotLabel:t?"unsaved changes":""}}function j(e){const t=document.createElement("div");t.className="dm-hop-result";const r=document.createElement("span");r.className=`dm-hop-kind is-${e.kind}`,r.textContent=g(e.kind);const d=document.createElement("span");d.className="dm-hop-name",d.textContent=e.label;const o=document.createElement("small");if(o.className="dm-hop-meta",o.textContent=e.meta,t.append(r,d,o),hasDraft(e)){const s=document.createElement("span");s.className="dm-hop-dot",s.textContent="\u2022",s.title="Unsaved changes kept",t.appendChild(s)}return t}export function mountEditorHop(e,{current:t,pages:r=[],blocks:d=[],open:o=null}){const s=n=>{const a=C(n);a?.id&&o?.(a)||R.navigate(n)};U(),e.innerHTML=W,e.querySelectorAll("[data-each]").forEach(n=>{n.innerHTML=X});const c=t?.id?noteRecent(t):k(),p=A(r,d),N=new Set(p.map(l)),O={recent:M.observable(z(c.filter(n=>N.has(l(n))),t,hasDraft).map(Y))},B=M.applyBindings(O,e),b=e.querySelector(".dm-hop-input"),H=E.autocomplete?E.autocomplete(b,{data:p,minChars:0,maxResults:0,debounce:60,highlightMatches:!1,clearOnSelect:!0,emptyMessage:"No page or block matches",filterFn:D,onFilter:(n,a)=>q(n,a,{recent:c,current:t}),renderItem:n=>j(n),onSelect:n=>{b.blur(),s(m(n))}}):null,P=[...e.querySelectorAll(".dm-hop-pick")];for(const n of P){const a=n.dataset.kind,h=p.filter(i=>i.kind===a);n.append(new Option(`${a==="page"?"Pages":"Blocks"} (${h.length})\u2026`,""));for(const i of h){const u=t?.id&&l(i)===l(t),x=new Option(`${!u&&hasDraft(i)?"\u2022 ":""}${a==="page"?`${i.label} (${i.id})`:i.label}${u?" - editing":""}`,m(i));x.disabled=!!u,n.append(x)}n.hidden=h.length===0}const v=n=>{const a=n.target.closest(".dm-hop-pick");if(!a?.value)return;const h=a.value;a.value="",s(h)};e.addEventListener("change",v);const y=n=>{const a=n.target.closest(".dm-hop-chip");a?.dataset.path&&s(a.dataset.path)};return e.addEventListener("click",y),I.scan(e),{dispose(){e.removeEventListener("click",y),e.removeEventListener("change",v);try{H?.destroy?.()}catch{}B.dispose()}}}export function lastPage(){return k().find(e=>e.kind==="page")?.id||""}const w=2e5;export function heldBlockEdits(e,t={}){const r=o=>t instanceof Map?t.get(o):t[o],d={};for(const o of e){let s=null;try{s=S.get($(o))}catch{}const c=s?.form;if(!c||typeof c.content!="string"||K(s,r(o)))continue;const p=typeof c.css=="string"?c.css:"";c.content.length>w||p.length>w||(d[o]={content:c.content,css:p})}return d}
@@ -0,0 +1 @@
1
+ export function blockTabs(e,t=[],i=null){const n=i?i instanceof Set?i:new Set(i):null,s=[],o=new Set;for(const r of Array.isArray(e)?e:[])typeof r!="string"||o.has(r)||(o.add(r),s.push({name:r,footer:!1}));for(const r of Array.isArray(t)?t:[])typeof r!="string"||o.has(r)||n&&!n.has(r)||(o.add(r),s.push({name:r,footer:!0}));return s}export function otherUses(e,t=""){return!e||typeof e!="object"?null:{pages:(Array.isArray(e.pages)?e.pages:[]).filter(i=>i!==t),views:Array.isArray(e.views)?e.views.filter(Boolean):[],footer:e.footer===!0}}const a=(e,t,i=`${t}s`)=>`${e} ${e===1?t:i}`;export function describeUses(e){if(!e)return"";const t=[];return e.pages.length&&t.push(a(e.pages.length,"other page")),e.views.length&&t.push(a(e.views.length,"view")),e.footer&&t.push("the footer"),t.length<2?t.join(""):`${t.slice(0,-1).join(", ")} and ${t.at(-1)}`}export function usesLine(e){if(!e)return"Where else it is used could not be read - saving changes it everywhere it is.";if(e.footer&&!e.pages.length&&!e.views.length)return"In the footer - on every page. Saving changes it everywhere.";const t=describeUses(e);return t?`Also used by ${t} - saving changes it there too.`:"Only used on this page."}export function notHereLine(e){const t="Not on this page, so the preview cannot show it (right-click \u203A Add block puts it in).";if(!e||typeof e!="object")return`${t} Where else it is used could not be read.`;const i=Array.isArray(e.pages)?e.pages.length:0,n=Array.isArray(e.views)?e.views.filter(Boolean).length:0,s=[];if(i&&s.push(a(i,"page")),n&&s.push(a(n,"view")),e.footer===!0&&s.push("the footer"),!s.length)return`${t} Not used anywhere yet.`;const o=s.length<2?s[0]:`${s.slice(0,-1).join(", ")} and ${s.at(-1)}`;return`${t} Used by ${o} - saving changes it there.`}export function saveQuestion(e,t={},i=new Set){const n=[];for(const s of e){if(i.has(s))continue;if(t[s]==null){n.push(`Where else "${s}" is used could not be read.`);continue}const o=describeUses(t[s]);o&&n.push(`"${s}" is also used by ${o}.`)}return n.length?`${n.join(" ")} Saving changes ${n.length===1?"it":"them"} everywhere ${n.length===1?"it is":"they are"} used. Save anyway?`:""}export function previewBlocks(e={},t=[],i=2e5){const n={...e};for(const s of t){if(!s.dirty){delete n[s.name];continue}s.content.length>i||s.css.length>i||(n[s.name]={content:s.content,css:s.css})}return n}export function savedMessage({page:e=!1,css:t=!1,blocks:i=[]}={}){const n=[];if(e&&n.push("Page"),t&&n.push("CSS"),i.length===1?n.push(`block "${i[0]}"`):i.length&&n.push(`${i.length} blocks`),!n.length)return"Nothing to save.";const s=n.length===1?n[0]:`${n.slice(0,-1).join(", ")} and ${n.at(-1)}`;return`${s[0].toUpperCase()}${s.slice(1)} saved.`}export function outlineBlock(e,t){const i=String(e??"");if(!t||!/^[a-z0-9][a-z0-9-]*$/.test(t))return i;const n=`<style>[data-block="${t}"]{outline:2px dashed var(--dm-primary,#4f6bed);outline-offset:6px}</style>`;return/<\/head>/i.test(i)?i.replace(/<\/head>/i,`${n}</head>`):n+i}
@@ -1,3 +1,3 @@
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}]
1
+ import{SHORTCODES as M}from"./shortcode-registry.js";import{EFFECTS as L,CELEBRATION_THEMES as J,getEffectAttrs as D}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(L.map(e=>[e.name,e])),pe=L.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;",T="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=()=>[],openBlock:n=null}={}){const o=()=>{const i=t()||[],s=i.filter(c=>c.shortTag&&!j.has(c.name)&&!z.has(c.name));return{defs:i,specs:[...ue,...s.map(c=>({name:c.name,selfClosing:!0}))]}},a=(i,s)=>{p();const{defs:c,specs:r}=o();V(e,ee(e,r),i,s,c,n)};return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",i=>{i.preventDefault(),a(i.clientX,i.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:i=>{a(i.x,i.y)}})}function p(){l&&(k(),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))&&p()},U=e=>{l&&!l.root.contains(e.target)&&!(l.submenu&&l.submenu.contains(e.target))&&p()},K=e=>{e.key==="Escape"&&p()};function V(e,t,n,o,a=[],i=null){oe();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=Y,l={root:s,textarea:e,span:t,submenu:null,blocks:a,openBlock:i,keepEmpty:!1};const c=new Map(a.map(b=>[b.name,b])),r=t&&(j.get(t.name)||z.get(t.name)),d=t&&(r?t.name==="block"?c.get(String(t.attrs.template||"")):null:c.get(t.name)),x=t&&F.has(t.name)?L.filter(b=>b.name in t.attrs):[];r&&fe(s,r),d&&he(s,d,!!r);for(const b of x)ve(s,b);(r||d||x.length)&&s.appendChild(_()),s.appendChild(y("Celebrations\u2026",()=>we())),s.appendChild(y("Add effect\u2026",()=>Te())),s.appendChild(y("Insert\u2026",()=>ke()));const u=e.selectionStart!==e.selectionEnd;(t||u)&&s.appendChild(y(u?"Wrap selection in\u2026":"Wrap in\u2026",()=>_e())),a.length&&(s.appendChild(_()),s.appendChild(y("Add block\u2026",()=>Le()))),document.body.appendChild(s),X(s,n,o),document.addEventListener("mousedown",q,!0),document.addEventListener("keydown",K,!0),window.addEventListener("scroll",U,!0),window.I&&I.scan(s)}function _(){const e=document.createElement("div");return e.style.cssText=me,e}function v(e){const t=document.createElement("div");return t.style.cssText=T,t.textContent=e,t}function fe(e,t){e.appendChild(v(`${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:i,span:s}=l;p(),A(i,{...t,attrs:D(t,s.attrs)},{span:s})}),e.appendChild(n);for(const i of t.actions||[]){const s=h(document.createElement("button"));s.type="button",s.style.cssText=m,s.innerHTML=`<span data-icon="${i.icon||"zap"}" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>`,s.appendChild(document.createTextNode(i.label)),s.addEventListener("click",()=>{const{textarea:c,span:r}=l;if(i.remove){ne(c,r.openTagStart,r.openTagEnd,""),p();return}l.span=W(c,r,i.set||{}),p()}),e.appendChild(s)}const o=Array.isArray(t.quickAttrs)?new Set(t.quickAttrs):null,a=D(t,l.span.attrs).filter(i=>!o||o.has(i.name));(t.actions||[]).length&&a.length&&e.appendChild(_());for(const i of a)e.appendChild(B(i))}function he(e,t,n){const{span:o}=l;if(l.keepEmpty=!0,n&&e.appendChild(_()),e.appendChild(v(n?`${t.name} parameters`:`${t.name} block`)),t.description&&!n){const s=document.createElement("div");s.style.cssText="padding:0 .55rem .3rem;font-size:.75rem;color:var(--dm-text-muted,#9a9aac);max-width:260px;",s.textContent=t.description,e.appendChild(s)}const a=(s,c,r)=>{const d=h(document.createElement("button"));d.type="button",d.style.cssText=m,d.innerHTML=`<span data-icon="${s}" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>`,d.appendChild(document.createTextNode(c)),d.addEventListener("click",r),e.appendChild(d)};t.attrs.length&&a("edit","Edit parameters\u2026",()=>{const{textarea:s}=l;p(),A(s,{...t,name:o.name,label:t.name},{span:o})});const{openBlock:i}=l;a(i?"layout":"external-link",i?"Edit the block":"Open the block",()=>{p(),i?i(t.name):location.hash=`#/blocks/edit/${encodeURIComponent(t.name)}`});for(const s of t.attrs)e.appendChild(B(s));if(!t.attrs.length){const s=document.createElement("div");s.style.cssText=T+"text-transform:none;letter-spacing:0;",s.textContent="This block takes no parameters.",e.appendChild(s)}}function B(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 w(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:o,root:a}=l,i=a.getBoundingClientRect(),{blocks:s,openBlock:c}=l;p(),V(n,o,i.left,i.top,s,c)}}function xe(e,t,n){const o=h(document.createElement("label"));o.style.cssText=m;const a=document.createElement("input");return a.type="checkbox",a.checked=n,a.addEventListener("change",()=>w(t,Z(e,a.checked))),o.appendChild(a),o.appendChild(document.createTextNode(e.label)),o}function be(e,t,n){const o=document.createElement("div");o.style.cssText=m+"cursor:default;";const a=document.createElement("span");a.textContent=e.label,a.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=n===!0||n==null?"":n,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=P;let s=null;if(i.addEventListener("input",()=>{clearTimeout(s),s=setTimeout(()=>w(t,i.value.trim()),180)}),i.addEventListener("mousedown",c=>c.stopPropagation()),o.appendChild(a),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",r=>r.stopPropagation()),c.addEventListener("click",async()=>{const{textarea:r,span:d}=l;p();const x=await ce({current:i.value.trim()});x&&r.isConnected&&N(r,d,t,x)}),o.appendChild(c)}return o}function ye(e,t,n){const o=document.createElement("div");o.style.cssText=m+"cursor:default;";const a=document.createElement("span");a.textContent=e.label,a.style.flex="1";const i=document.createElement("input");return i.type="color",typeof n=="string"&&/^#/.test(n)?i.value=n:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>w(t,i.value)),o.appendChild(a),o.appendChild(i),o}function Ee(e,t,n){const o=n==null||n===!0?e.default??"":n;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const a=g();for(const[i,s]of e.options||[]){const c=h(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent=s,i===o&&(c.style.fontWeight="700"),c.addEventListener("click",()=>{w(t,i),k()}),a.appendChild(c)}return a})}function ge(e,t,n){const o=n==null||n===!0?"":String(n),a=o?`${e.label}: ${o}`:e.label;return y(a,()=>{const i=g();i.style.width="232px";const s=document.createElement("input");s.type="text",s.placeholder="Search icons\u2026",s.style.cssText=P+"margin:6px;width:calc(100% - 12px);",s.addEventListener("mousedown",b=>b.stopPropagation()),i.appendChild(s);const c=h(document.createElement("button"));c.type="button",c.style.cssText=m,c.textContent="- none -",o||(c.style.fontWeight="700"),c.addEventListener("click",()=>{w(t,null),k()}),i.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;",i.appendChild(r);const d=96,x=()=>{const b=typeof I<"u"&&typeof I.list=="function"?I.list():[],O=s.value.trim().toLowerCase(),S=O?b.filter(f=>f.toLowerCase().includes(O)):b;r.replaceChildren();for(const f of S.slice(0,d)){const C=h(document.createElement("button"));C.type="button",C.title=f,C.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===o&&(C.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;",C.appendChild($),C.addEventListener("click",()=>{w(t,f),k()}),r.appendChild(C)}if(S.length>d){const f=document.createElement("div");f.style.cssText=T,f.textContent=`${S.length-d} more - refine search`,r.appendChild(f)}if(!S.length){const f=document.createElement("div");f.style.cssText=T,f.textContent="No icons match",r.appendChild(f)}window.I&&I.scan(r)};let u=null;return s.addEventListener("input",()=>{clearTimeout(u),u=setTimeout(x,120)}),x(),setTimeout(()=>s.focus(),0),i})}function Ce(e,t,n){const o=n==null||n===!0?e.default??"":n;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const a=g();a.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(a,{type:"dots"}):null;return re(e.source).then(s=>{if(i&&i.destroy(),!l||l.submenu!==a)return;if(a.style.minHeight="",!s.length){const d=document.createElement("div");d.style.cssText=T,d.textContent="Nothing found",a.appendChild(d)}const c=s.some(([d])=>d===o),r=[["","- none -"],...s];o!==""&&!c&&r.push([o,`${o} (missing)`]);for(const[d,x]of r){const u=h(document.createElement("button"));u.type="button",u.style.cssText=m,u.textContent=x,d===o&&(u.style.fontWeight="700"),u.addEventListener("click",()=>{w(t,d===""?null:d),k()}),a.appendChild(u)}}),a})}function ve(e,t){e.appendChild(v(`Effect: ${t.label}`));for(const o of t.attrs||[])e.appendChild(B(o,`${t.name}-${o.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 o={};for(const a of te(l.span.attrs,t.name))o[a]=null;W(l.textarea,l.span,o),p()}),e.appendChild(n)}function we(){const e=g();e.appendChild(v("Seasonal themes"));for(const[t,n]of[["auto","Auto (date-detected)"],...Object.entries(J).map(([o,a])=>[o,a.label])]){const o=h(document.createElement("button"));o.type="button",o.style.cssText=m,o.textContent=n,o.addEventListener("click",()=>{R(l.textarea,`[celebrate theme="${t}" /]`),p()}),e.appendChild(o)}e.appendChild(_()),e.appendChild(v("Celebration effects"));for(const t of["butterflies","ticker-tape","firework","fireworks"]){const n=z.get(t),o=h(document.createElement("button"));o.type="button",o.style.cssText=m,o.textContent=n.label,o.addEventListener("click",()=>{const a=l.textarea;p(),A(a,n)}),e.appendChild(o)}return e}function Te(){const e=g(),t=l.span,n=t&&F.has(t.name)?t.name:null;for(const o of[...new Set(L.map(a=>a.category))]){const a=L.filter(i=>i.category===o);if(a.length){e.appendChild(v(o));for(const i of a){const s=n&&(i.hosts||[]).includes(n)&&!(i.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(i.label)),s){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(s)N(l.textarea,l.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
2
2
  Content
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`}
3
+ [/${i.name}]`;R(l.textarea,r)}p()}),e.appendChild(c)}}}return e}function y(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 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;",n.appendChild(o),n.addEventListener("mouseenter",()=>G(n,t)),n.addEventListener("click",a=>{a.stopPropagation(),G(n,t)}),n}function g(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=Y,e}function G(e,t){k();const n=t();l.submenu=n,document.body.appendChild(n);const o=e.getBoundingClientRect();X(n,o.right+2,o.top,o.left-2),window.I&&I.scan(n)}function k(){l&&l.submenu&&(l.submenu.remove(),l.submenu=null)}function H(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=g();for(const{category:t,items:n}of ie()){e.appendChild(v(t));for(const o of n){const a=H(o);a.addEventListener("click",()=>{const i=l.textarea;p(),ae(o.name,i)}),e.appendChild(a)}}return e}function Le(){const e=g();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",a=>a.stopPropagation());const n=document.createElement("div");e.append(t,n);const o=()=>{const a=t.value.trim().toLowerCase(),i=l.blocks.filter(s=>!a||s.name.includes(a)||s.description.toLowerCase().includes(a));n.replaceChildren();for(const s of i){const c=H(s);if(s.description&&(c.title=s.description),!s.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;p();const d=r.selectionStart,x=le(s);R(r,x);const u=x.indexOf('=""');u>=0&&r.setSelectionRange(d+u+2,d+u+2),r.focus()}),n.appendChild(c)}if(!i.length){const s=document.createElement("div");s.style.cssText=T,s.textContent="No blocks match",n.appendChild(s)}window.I&&I.scan(n)};return t.addEventListener("input",o),o(),setTimeout(()=>t.focus(),0),e}function _e(){const e=g();for(const t of M.filter(n=>n.wrappable||n.container)){const n=H(t);n.addEventListener("click",()=>{const o=l.textarea;p(),A(o,t)}),e.appendChild(n)}return e}function X(e,t,n,o){e.style.left="0px",e.style.top="0px";const a=e.getBoundingClientRect();let i=t;t+a.width>window.innerWidth-8&&(i=o!=null?o-a.width:t-a.width);let s=n;n+a.height>window.innerHeight-8&&(s=Math.max(8,window.innerHeight-a.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,s)}px`}
@@ -1,18 +1,20 @@
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
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.
4
+ above it (#page-hop, lib/editor-hop.js) finds any page or block and lists recent edits - a page opens
5
+ in its own editor (this page's changes kept without asking), a block opens HERE. Blocks are edited in
6
+ this editor: a tab each above it (.pe-files) - the page's, the footer's, any opened from the bar - their
7
+ HTML and CSS in .pe-block-pane, saved with the page.
6
8
  Bound with M.applyBindings (see the view): every field is a data-model, and what shows - unsaved,
7
9
  the problem note, the view mode, the CSS pane, Live Preview, History - is read from the state. The
8
10
  router renders this file through _.render, which eats any {{…}}: bindings only, never mustache. -->
9
11
  <div id="page-hop" class="dm-hop-slot"></div>
10
12
  <div class="pex-bar">
11
- <span data-help-title="Pages and blocks" data-help="Open any page or block from the Pages and Blocks lists above, find one by name in the search (it looks through both), 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>
13
+ <span data-help-title="Pages and blocks" data-help="The blocks this page uses have a tab each above the editor - edit their HTML and CSS right here, with the preview showing the page as you type. Save saves the page and the blocks you changed (a block used elsewhere changes there too - you are asked first). Any other block opens here in a tab too - pick it from the Blocks list, the search or a recent one. Picking a page opens that page; your unsaved changes here are kept while you go. A dot marks anything with changes not saved yet."></span>
12
14
  <span class="pex-chip" hidden data-bind-hidden="!dirty.value" title="Changes not saved yet">Unsaved</span>
13
15
  <small class="pex-problem" hidden data-bind-hidden="!(dirty.value && problem.value)" data-bind-text="problem.value"></small>
14
16
  <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>
17
+ title="Those blocks' changes were kept from the block editor - open the block's tab to see them, Save saves them"></small>
16
18
  <span class="pex-spacer"></span>
17
19
  <a class="btn btn-ghost btn-sm" target="_blank" rel="noopener" hidden data-bind-hidden="!isEdit" data-bind-href="savedPath">
18
20
  <span data-icon="external-link"></span> View page
@@ -37,14 +39,50 @@
37
39
  <div class="tab-panel active">
38
40
  <div class="card editor-card" data-bind-class="fullscreen.value && 'editor-fullscreen'">
39
41
  <div class="card-body p-0">
40
- <div class="editor-toolbar" id="editor-toolbar"></div>
42
+ <!-- The page and the blocks it uses (its body's, then the footer's), one tab each. The
43
+ tabs are buttons with data-name; the view listens on the strip. -->
44
+ <div class="pe-files" role="tablist" aria-label="The page and its blocks" hidden data-bind-hidden="!blockTabs.value.length">
45
+ <button type="button" class="pe-file" role="tab" data-name="" data-bind-class="!blockTab.value && 'active'"
46
+ data-bind-aria-selected="!blockTab.value">
47
+ <span class="pe-file-kind">Page</span><span class="pe-file-dot" hidden data-bind-hidden="!pageDirty.value">•</span>
48
+ </button>
49
+ <span class="pe-files-list" data-each="blockTabs.value key=name">
50
+ <button type="button" class="pe-file is-block" role="tab" data-bind-data-name="name" data-bind-title="title"
51
+ data-bind-class="active.value && 'active'" data-bind-aria-selected="active.value">
52
+ <span class="pe-file-kind" data-bind-text="kindText"></span><span class="pe-file-name" data-bind-text="name"></span><span class="pe-file-dot" hidden data-bind-hidden="!dirty.value">•</span>
53
+ </button>
54
+ </span>
55
+ </div>
56
+ <!-- On a block's tab the Markdown tools step aside; the view modes and fullscreen stay. -->
57
+ <div class="editor-toolbar" id="editor-toolbar" data-bind-class="blockTab.value && 'pe-toolbar--block'"></div>
41
58
  <div class="editor-body" id="editor-body" data-bind-class="'editor-mode-' + viewMode.value">
42
59
  <div class="editor-pane editor-pane--write">
43
60
  <!-- The body is not a data-model: the folding manager owns what the textarea shows. -->
44
- <textarea id="markdown-editor" class="editor-textarea" data-bind-hidden="cssMode.value"
61
+ <textarea id="markdown-editor" class="editor-textarea" data-bind-hidden="cssMode.value || blockTab.value"
45
62
  placeholder="Write your page content in Markdown..."></textarea>
46
63
  <textarea id="css-editor" class="editor-textarea" hidden
47
- data-bind-hidden="!cssMode.value" data-model="css.value" placeholder="/* Custom CSS… */"></textarea>
64
+ data-bind-hidden="!cssMode.value || blockTab.value" data-model="css.value" placeholder="/* Custom CSS… */"></textarea>
65
+ <!-- The open block: its HTML or its CSS (two simple-editors, mounted by the view). The
66
+ bar is moved into the toolbar row by the view, where the Markdown tools step aside. -->
67
+ <div class="pe-block-pane" hidden data-bind-hidden="!blockTab.value">
68
+ <div class="pe-block-bar" hidden data-bind-hidden="!blockTab.value">
69
+ <div class="pe-block-parts" role="group" aria-label="Part of the block">
70
+ <button type="button" class="editor-view-btn" data-on-click="setBlockPart('html')"
71
+ data-bind-class="blockPart.value === 'html' && 'active'">HTML</button>
72
+ <button type="button" class="editor-view-btn" data-on-click="setBlockPart('css')"
73
+ data-bind-class="blockPart.value === 'css' && 'active'">CSS</button>
74
+ </div>
75
+ <small class="pe-block-uses" data-bind-text="blockUses.value"></small>
76
+ <span class="pex-spacer"></span>
77
+ <button type="button" class="editor-view-btn" title="Tidy the HTML's indentation" data-on-click="formatBlock"
78
+ data-bind-disabled="blockPart.value !== 'html' || !blockReady.value"><span data-icon="code"></span></button>
79
+ <button type="button" class="editor-view-btn pe-block-open" title="Open in the Blocks editor - samples, parameters, every page it is on"
80
+ data-on-click="openBlockEditor"><span data-icon="external-link"></span></button>
81
+ </div>
82
+ <p class="pe-block-note" hidden data-bind-hidden="!blockNote.value" data-bind-text="blockNote.value"></p>
83
+ <div class="pe-block-edit" data-part="html" hidden data-bind-hidden="!blockReady.value || blockPart.value !== 'html'"></div>
84
+ <div class="pe-block-edit" data-part="css" hidden data-bind-hidden="!blockReady.value || blockPart.value !== 'css'"></div>
85
+ </div>
48
86
  </div>
49
87
  <div class="editor-divider" id="editor-divider" role="separator" aria-orientation="vertical"
50
88
  tabindex="0" aria-label="Resize editor panes" aria-valuemin="15" aria-valuemax="85"
@@ -292,6 +330,24 @@
292
330
  background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
293
331
  .pex-problem { color: var(--dm-danger); font-size: var(--dm-text-sm); }
294
332
  .pex-held { color: var(--dm-warning); font-size: var(--dm-text-sm); }
333
+ .pe-files { display: flex; flex-wrap: wrap; gap: .25rem; padding: .4rem .5rem 0; border-bottom: 1px solid var(--dm-border); }
334
+ .pe-files-list { display: contents; }
335
+ .pe-file { display: inline-flex; align-items: center; gap: .35rem; padding: .3rem .7rem; font-size: var(--dm-text-sm); cursor: pointer;
336
+ border: 1px solid transparent; border-bottom: 0; border-radius: var(--dm-radius-sm) var(--dm-radius-sm) 0 0;
337
+ background: none; color: var(--dm-text-muted); margin-bottom: -1px; }
338
+ .pe-file:hover { color: var(--dm-text); }
339
+ .pe-file.active { color: var(--dm-text); background: var(--dm-surface, var(--dm-background)); border-color: var(--dm-border); }
340
+ .pe-file-kind { font-size: var(--dm-text-xs); text-transform: uppercase; letter-spacing: .04em; opacity: .7; }
341
+ .pe-file:not(.is-block) .pe-file-kind { opacity: 1; text-transform: none; letter-spacing: 0; font-size: var(--dm-text-sm); font-weight: 600; }
342
+ .pe-file-name { font-family: var(--dm-font-mono, ui-monospace, monospace); }
343
+ .pe-file-dot { color: var(--dm-warning); font-weight: 700; }
344
+ .pe-toolbar--block > :not(.editor-toolbar-right):not(.pe-block-bar), .pe-toolbar--block .pe-toolbar-extras { display: none !important; }
345
+ .pe-block-pane { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; }
346
+ .pe-block-bar { display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: center; gap: .5rem; min-width: 0; }
347
+ .pe-block-parts { display: inline-flex; gap: .15rem; }
348
+ .pe-block-uses { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
349
+ .pe-block-note { margin: 0; padding: .75rem 1rem; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
350
+ .pe-block-edit { display: flex; flex: 1 1 auto; min-height: 0; }
295
351
  .pex-bar [hidden], .editor-card [hidden], #editor-meta-tabs [hidden] { display: none !important; }
296
352
  .pe-hist-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
297
353
  .pe-hist-head h3 { margin: 0; font-size: 1rem; }
@@ -1,4 +1,4 @@
1
- import{api as v}from"../api.js";import{CardBuilder as xt}from"../lib/card-builder.js";import{TimelineBuilder as Pt}from"../lib/timeline-builder.js";import{attachEditorKeybindings as At,createToolbar as Lt,insertAtCursor as Dt}from"../lib/markdown-toolbar.js";import{EffectsBuilder as Ot}from"../lib/effects-builder.js";import{attachContextMenu as Rt}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as Nt}from"../lib/shortcode-insert.js";import{populateThemeSelect as Bt}from"../lib/themes.js";import{createFoldingManager as It}from"../lib/editor-folding.js";import{openScribeComposer as Ft}from"../lib/scribe-composer.js";import{openCrudTutorial as zt}from"../lib/crud-tutorial.js";import{addBannerAction as V}from"../lib/plugin-chrome.js";import{helpPopovers as We}from"../lib/help-popover.js";import{onceClosed as Se,openMenuAt as Ye}from"../lib/tool-kit.js";import{pickMedia as $t}from"../lib/media-picker.js";import{openPageClone as jt}from"../lib/page-clone.js";import{openEditorReference as Ge}from"../lib/page-editor-reference.js";import{cleanType as Q,DEFAULT_TYPE as ee,EDITOR_FONTS as Vt,fontHref as qt,isDefaultType as Ht,LINE_HEIGHTS as Kt,SIZES as Ut,typeVars as Xe}from"../lib/editor-type.js";import{getProjectFromHash as _t}from"../lib/project-context.js";import{attachSmoothCaret as Ze,getCaretPreference as Jt,setCaretPreference as Wt}from"../lib/smooth-caret.js";import{SCREEN as Yt,bannerSection as Gt,draftIsStale as Qe,draftKey as Xt,initialProject as Zt,makeDraft as Qt,problems as Te,readDraft as ea,snapshot as j}from"../lib/page-editor-arrange.js";import{fmtDateTime as te}from"../lib/dates.js";import{heldBlockEdits as ta,hold as et,mountEditorHop as aa,noteRecent as sa,takeHeld as oa}from"../lib/editor-hop.js";import{isHop as na}from"../lib/editor-hop-arrange.js";import{blockTagDefs as ra,blocksInBody as ia}from"../lib/block-tags.js";function ae(o){const l=document.createElement("template");return l.innerHTML=o.trim(),l.content.firstElementChild}const la=te,tt=o=>te(o.createdAt)+(o.label?` - ${o.label}`:""),ca=`
1
+ import{api as g}from"../api.js";import{CardBuilder as ma}from"../lib/card-builder.js";import{TimelineBuilder as ga}from"../lib/timeline-builder.js";import{attachEditorKeybindings as ha,createToolbar as ya,insertAtCursor as ka}from"../lib/markdown-toolbar.js";import{EffectsBuilder as wa}from"../lib/effects-builder.js";import{attachContextMenu as Ea}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as Ma}from"../lib/shortcode-insert.js";import{populateThemeSelect as Sa}from"../lib/themes.js";import{createFoldingManager as Ta}from"../lib/editor-folding.js";import{openScribeComposer as Ca}from"../lib/scribe-composer.js";import{openCrudTutorial as xa}from"../lib/crud-tutorial.js";import{addBannerAction as Z}from"../lib/plugin-chrome.js";import{helpPopovers as Pt}from"../lib/help-popover.js";import{onceClosed as Je,openMenuAt as At,sidebarCount as Pa}from"../lib/tool-kit.js";import{pickMedia as Aa}from"../lib/media-picker.js";import{openPageClone as La}from"../lib/page-clone.js";import{openEditorReference as Lt}from"../lib/page-editor-reference.js";import{cleanType as me,DEFAULT_TYPE as ge,EDITOR_FONTS as Ra,fontHref as Da,isDefaultType as Ba,LINE_HEIGHTS as Oa,SIZES as Na,typeVars as Rt}from"../lib/editor-type.js";import{getProjectFromHash as $a}from"../lib/project-context.js";import{attachSmoothCaret as Dt,getCaretPreference as Ia,setCaretPreference as Fa}from"../lib/smooth-caret.js";import{SCREEN as za,bannerSection as ja,draftIsStale as Bt,draftKey as Va,initialProject as qa,makeDraft as Ha,problems as Ye,readDraft as Ua,snapshot as H}from"../lib/page-editor-arrange.js";import{fmtDateTime as ee}from"../lib/dates.js";import{heldBlockEdits as Ka,hold as Ot,mountEditorHop as _a,noteRecent as Nt,takeHeld as $t}from"../lib/editor-hop.js";import{isHop as Wa}from"../lib/editor-hop-arrange.js";import{blockTagDefs as Ja,blocksInBody as Ya}from"../lib/block-tags.js";import{draftKey as te,draftOffer as Ga,formatHtml as Xa,formFromBlock as Qa,payloadFromForm as Za,snapshot as Ge}from"../lib/block-editor-arrange.js";import{blockTabs as eo,notHereLine as to,otherUses as It,outlineBlock as ao,previewBlocks as oo,saveQuestion as so,savedMessage as no,usesLine as ro}from"../lib/page-blocks-arrange.js";import{createSimpleEditor as Ft}from"../lib/simple-editor.js";function he(r){const d=document.createElement("template");return d.innerHTML=r.trim(),d.content.firstElementChild}const lo=ee,zt=r=>ee(r.createdAt)+(r.label?` - ${r.label}`:""),io=`
2
2
  <div class="pe-share">
3
3
  <p class="form-hint" style="margin-top:0">Anyone with the link can view this page even while it is unpublished, without
4
4
  signing in. The link works for this page only, expires on its own, and can be revoked at any time.</p>
@@ -30,7 +30,7 @@ import{api as v}from"../api.js";import{CardBuilder as xt}from"../lib/card-builde
30
30
  .pe-share [hidden] { display: none !important; }
31
31
  .pe-share-row { display: flex; align-items: center; gap: .6rem; padding: .5rem 0; border-bottom: 1px solid var(--dm-border-light); }
32
32
  .pe-share-text { flex: 1 1 auto; min-width: 0; font-size: .82rem; }
33
- </style>`;function at(o){const l=qt(o);if(!l||document.querySelector(`link[data-pe-font="${CSS.escape(o)}"]`))return;const c=Object.assign(document.createElement("link"),{rel:"stylesheet",href:l});c.dataset.peFont=o,document.head.appendChild(c)}const da=`
33
+ </style>`;function jt(r){const d=Da(r);if(!d||document.querySelector(`link[data-pe-font="${CSS.escape(r)}"]`))return;const p=Object.assign(document.createElement("link"),{rel:"stylesheet",href:d});p.dataset.peFont=r,document.head.appendChild(p)}const co=`
34
34
  <div class="pe-settings">
35
35
  <p class="pe-settings-note">How the editor looks to you - kept in this browser, for every page.</p>
36
36
  <label class="form-label" for="pe-set-font">Font <span data-help="The lettering in the editor (not on the site). Monospace keeps every character the same width, so shortcodes and Markdown line up."></span></label>
@@ -68,17 +68,17 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
68
68
  .pe-settings-sample { white-space: pre-wrap; padding: .75rem 1rem; border-radius: var(--dm-radius-md); border: 1px solid var(--dm-border);
69
69
  background: var(--dm-background-alt); font-family: var(--pe-font); font-size: var(--pe-size); line-height: var(--pe-line); }
70
70
  .pe-settings-actions { display: flex; gap: .5rem; border-top: 1px solid var(--dm-border); padding-top: .75rem; }
71
- </style>`;function pa(o){const l=E.slideover({title:"Editor settings",size:"sm",position:"right"}),c=ae(`<div>${da}</div>`),s=Q(o.peek()),i={font:M.observable(s.font),size:M.observable(String(s.size)),line:M.observable(String(s.line)),wrap:M.observable(s.wrap),caret:M.observable(Jt())},m=M.computed(()=>Q({font:i.font.value,size:i.size.value,line:i.line.value,wrap:i.wrap.value}));i.isDefault=M.computed(()=>Ht(m.value)&&i.caret.value==="auto");const p=M.effect(()=>Wt(i.caret.value)),r=c.querySelector("#pe-set-font");for(const[g,L]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const D=Vt.filter(O=>O.kind===g),f=L?Object.assign(document.createElement("optgroup"),{label:L}):r.querySelector("optgroup:last-of-type");f.append(...D.map(O=>new Option(O.label,O.id))),L&&r.appendChild(f)}c.querySelector("#pe-set-size").append(...Ut.map(g=>new Option(`${g}px`,String(g)))),c.querySelector("#pe-set-line").append(...Kt.map(g=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[g]||String(g),String(g))));const d=c.querySelector(".pe-settings-sample"),C=M.effect(()=>{const g=m.value;at(g.font);for(const[L,D]of Object.entries(Xe(g)))d.style.setProperty(L,D);d.style.whiteSpace=g.wrap?"pre-wrap":"pre",d.style.overflowX=g.wrap?"":"auto",JSON.stringify(g)!==JSON.stringify(Q(o.peek()))&&(o.value=g,se("type",g))});i.reset=()=>{i.font.value=ee.font,i.size.value=String(ee.size),i.line.value=String(ee.line),i.wrap.value=ee.wrap,i.caret.value="auto"},i.done=()=>l.close(),l.element.appendChild(c);const b=M.applyBindings(i,c),h=We(c);return Se(l,()=>{b.dispose(),h(),C?.(),p?.()}),Domma.icons?.scan?.(c),l.open(),l}async function ua(o){const l=E.slideover({title:"Share preview link",size:"md",position:"right"}),c=ae(`<div>${ca}</div>`),s={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},i=async()=>{try{const p=await v.pages.previewLinks.list(o);s.links.value=p.filter(r=>!r.revokedAt&&!r.expired).map(r=>({id:r.id,title:r.label||"Untitled link",meta:`Expires ${la(r.expiresAt)} - ${r.useCount===1?"1 view":`${r.useCount||0} views`}`})),s.listError.value=""}catch(p){s.listError.value="Could not load existing links: "+(p.message||"unknown error")}finally{s.loaded.value=!0}};s.create=async()=>{s.creating.value=!0;try{const p=await v.pages.previewLinks.create(o,s.expiry.peek(),s.note.peek().trim());s.url.value=p.url,s.note.value="",E.toast("Share link created.",{type:"success"}),await i(),c.querySelector(".pe-share-url")?.select()}catch(p){E.toast("Could not create link: "+(p.message||"unknown error"),{type:"error"})}finally{s.creating.value=!1}},s.copy=async()=>{try{await navigator.clipboard.writeText(s.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{c.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},s.revoke=async p=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await v.pages.previewLinks.revoke(p),E.toast("Link revoked.",{type:"success"}),await i()}catch(r){E.toast("Could not revoke: "+(r.message||"unknown error"),{type:"error"})}};const m=M.applyBindings(s,c);Se(l,()=>m.dispose()),l.element.appendChild(c),l.open(),await i()}function ma(o){return new Promise(l=>{const c=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),s=ae(`
71
+ </style>`;function po(r){const d=E.slideover({title:"Editor settings",size:"sm",position:"right"}),p=he(`<div>${co}</div>`),s=me(r.peek()),c={font:M.observable(s.font),size:M.observable(String(s.size)),line:M.observable(String(s.line)),wrap:M.observable(s.wrap),caret:M.observable(Ia())},v=M.computed(()=>me({font:c.font.value,size:c.size.value,line:c.line.value,wrap:c.wrap.value}));c.isDefault=M.computed(()=>Ba(v.value)&&c.caret.value==="auto");const f=M.effect(()=>Fa(c.caret.value)),b=p.querySelector("#pe-set-font");for(const[u,I]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const J=Ra.filter(m=>m.kind===u),U=I?Object.assign(document.createElement("optgroup"),{label:I}):b.querySelector("optgroup:last-of-type");U.append(...J.map(m=>new Option(m.label,m.id))),I&&b.appendChild(U)}p.querySelector("#pe-set-size").append(...Na.map(u=>new Option(`${u}px`,String(u)))),p.querySelector("#pe-set-line").append(...Oa.map(u=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[u]||String(u),String(u))));const l=p.querySelector(".pe-settings-sample"),h=M.effect(()=>{const u=v.value;jt(u.font);for(const[I,J]of Object.entries(Rt(u)))l.style.setProperty(I,J);l.style.whiteSpace=u.wrap?"pre-wrap":"pre",l.style.overflowX=u.wrap?"":"auto",JSON.stringify(u)!==JSON.stringify(me(r.peek()))&&(r.value=u,ye("type",u))});c.reset=()=>{c.font.value=ge.font,c.size.value=String(ge.size),c.line.value=String(ge.line),c.wrap.value=ge.wrap,c.caret.value="auto"},c.done=()=>d.close(),d.element.appendChild(p);const y=M.applyBindings(c,p),C=Pt(p);return Je(d,()=>{y.dispose(),C(),h?.(),f?.()}),Domma.icons?.scan?.(p),d.open(),d}async function uo(r){const d=E.slideover({title:"Share preview link",size:"md",position:"right"}),p=he(`<div>${io}</div>`),s={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},c=async()=>{try{const f=await g.pages.previewLinks.list(r);s.links.value=f.filter(b=>!b.revokedAt&&!b.expired).map(b=>({id:b.id,title:b.label||"Untitled link",meta:`Expires ${lo(b.expiresAt)} - ${b.useCount===1?"1 view":`${b.useCount||0} views`}`})),s.listError.value=""}catch(f){s.listError.value="Could not load existing links: "+(f.message||"unknown error")}finally{s.loaded.value=!0}};s.create=async()=>{s.creating.value=!0;try{const f=await g.pages.previewLinks.create(r,s.expiry.peek(),s.note.peek().trim());s.url.value=f.url,s.note.value="",E.toast("Share link created.",{type:"success"}),await c(),p.querySelector(".pe-share-url")?.select()}catch(f){E.toast("Could not create link: "+(f.message||"unknown error"),{type:"error"})}finally{s.creating.value=!1}},s.copy=async()=>{try{await navigator.clipboard.writeText(s.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{p.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},s.revoke=async f=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await g.pages.previewLinks.revoke(f),E.toast("Link revoked.",{type:"success"}),await c()}catch(b){E.toast("Could not revoke: "+(b.message||"unknown error"),{type:"error"})}};const v=M.applyBindings(s,p);Je(d,()=>v.dispose()),d.element.appendChild(p),d.open(),await c()}function fo(r){return new Promise(d=>{const p=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),s=he(`
72
72
  <div style="padding:1rem">
73
73
  <label class="form-label" for="pe-version-label">Version Label</label>
74
74
  <input id="pe-version-label" type="text" class="form-input" style="margin-bottom:.75rem"
75
75
  placeholder="e.g. Initial design, Before restructure" data-model="label.value" data-on-keydown="key">
76
76
  <button type="button" class="btn btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save Version</button>
77
- </div>`);let i=!1;const m={label:M.observable(""),saving:M.observable(!1)};m.save=async()=>{m.saving.value=!0;try{await v.versions.create(o,m.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),i=!0,c.close()}catch(r){E.toast("Failed to save version: "+r.message,{type:"error"})}finally{m.saving.value=!1}},m.key=r=>{r.key==="Enter"&&(r.preventDefault(),m.save())};const p=M.applyBindings(m,s);Se(c,()=>{p.dispose(),l(i)}),c.element.appendChild(s),c.open(),setTimeout(()=>s.querySelector("input")?.focus(),100)})}let k=null,st=!1;function fa(){st||!window.R?.use||(st=!0,R.use((o,l,c)=>{const s=l?.path||"",i=o?.path||"",m=Yt.test(s)&&i!==s;return m&&k?.path===s&&k.cloneTarget===i?(k.cloneTarget=null,k.keepDraft(),et(k.item),c()):!m||!k||k.path!==s||!k.dirty()?c():na(s,i)?(k.keepDraft(),et(k.item),c()):E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(p=>{p?(k?.keepDraft(),c()):history.replaceState(null,"","#"+s)})}),M.subscribe("router:afterChange",({to:o}={})=>{if(k&&(o?.path||"")!==k.path){const l=k;k=null,l.dispose()}}))}function va(o){return o.split(`
78
- `).map(l=>l.trimEnd()).join(`
77
+ </div>`);let c=!1;const v={label:M.observable(""),saving:M.observable(!1)};v.save=async()=>{v.saving.value=!0;try{await g.versions.create(r,v.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),c=!0,p.close()}catch(b){E.toast("Failed to save version: "+b.message,{type:"error"})}finally{v.saving.value=!1}},v.key=b=>{b.key==="Enter"&&(b.preventDefault(),v.save())};const f=M.applyBindings(v,s);Je(p,()=>{f.dispose(),d(c)}),p.element.appendChild(s),p.open(),setTimeout(()=>s.querySelector("input")?.focus(),100)})}let T=null,Vt=!1;function bo(){Vt||!window.R?.use||(Vt=!0,R.use((r,d,p)=>{const s=d?.path||"",c=r?.path||"",v=za.test(s)&&c!==s;return v&&T?.path===s&&T.cloneTarget===c?(T.cloneTarget=null,T.keepDraft(),T.hold(),p()):!v||!T||T.path!==s||!T.dirty()?p():Wa(s,c)?(T.keepDraft(),T.hold(),p()):E.confirm("You have unsaved changes to this page. Leave anyway? They are kept as a draft for your next visit.").then(f=>{f?(T?.keepDraft(),p()):history.replaceState(null,"","#"+s)})}),M.subscribe("router:afterChange",({to:r}={})=>{if(T&&(r?.path||"")!==T.path){const d=T;T=null,d.dispose()}}))}function vo(r){return r.split(`
78
+ `).map(d=>d.trimEnd()).join(`
79
79
  `).replace(/\n{3,}/g,`
80
80
 
81
- `)}const ot="editor_prefs";function q(){return S.get(ot)||{viewMode:"split",fullscreen:!1}}function se(o,l){const c=q();c[o]=l,S.set(ot,c)}const ba=["edit","details","seo","dconfig","live","history"],nt=["split","write","preview"],ga=o=>o.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function rt(o,l,c=()=>{}){let s=0;try{s=o.contentWindow.scrollY||0}catch{}o.addEventListener("load",()=>{try{o.contentWindow.scrollTo(0,s)}catch{}c()},{once:!0}),o.srcdoc=l}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(o){if(fa(),k){const e=k;k=null,e.dispose()}const l=window.location.hash.replace(/^#/,""),c=[],s=e=>(c.push(e),e);let i=()=>!1,m=()=>{};const p=window.location.hash.match(/#\/pages\/edit(\/[^?]*)/),r=p?p[1]:null,d=!!r,C={path:l,item:{kind:"page",id:r||""},dirty:()=>i(),keepDraft:()=>m(),dispose:()=>{c.splice(0).forEach(e=>{try{e()}catch{}})}};k=C;const b=o.get(0),h=()=>k===C&&b.isConnected,g=E.loader(b,{type:"dots"}),[L,D,f,O,Ce,it,lt,H]=await Promise.all([v.layouts.get().catch(()=>({})),v.settings.get().catch(()=>({})),d?v.pages.get(r).catch(()=>null):null,v.pages.list().catch(()=>[]),v.projects.list().catch(()=>[]),v.pages.tags().catch(()=>[]),v.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),v.blocks.list().catch(()=>[])]);if(!h()){g.destroy();return}if(g.destroy(),d&&!f){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const w=e=>b.querySelector(e);if(Bt(w("#field-theme"),{includeDefault:!0}),await Ea(w("#field-visibility"),f?.visibility),!h())return;const ct=wa(w("#field-layout"),L,f?.layout||"default");for(const e of Ce)w("#field-project").append(new Option(e.name||e.slug,e.slug));const oe=ra(H),K=q(),a={urlPath:M.observable(r||""),title:M.observable(f?.title||""),description:M.observable(f?.description||""),layout:M.observable(ct),status:M.observable(f?.status||"draft"),sortOrder:M.observable(String(f?.sortOrder??99)),showInNav:M.observable(!!f?.showInNav),sidebar:M.observable(!!f?.sidebar),breadcrumbs:M.observable(f?f.breadcrumbs!==!1:!0),bundled:M.observable(!!f?.bundled),tags:M.observable(Array.isArray(f?.tags)?f.tags:[]),adminTags:M.observable(Array.isArray(f?.adminTags)?f.adminTags:[]),category:M.observable(f?.category||""),visibility:M.observable(typeof f?.visibility=="string"&&f.visibility?f.visibility:"public"),project:M.observable(Zt({isEdit:d,page:f,projects:Ce,fromHash:_t()||"",urlPath:r||""})),theme:M.observable(f?.theme||""),seoTitle:M.observable(f?.seo?.title||""),seoDesc:M.observable(f?.seo?.description||""),dconfig:M.observable(f?.dconfig?JSON.stringify(f.dconfig,null,2):""),css:M.observable(""),cssOpened:M.observable(!1)},n={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),tab:M.observable("edit"),viewMode:M.observable(nt.includes(K.viewMode)?K.viewMode:"split"),fullscreen:M.observable(!!K.fullscreen),cssMode:M.observable(!1),folded:M.observable(!1),liveStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null),heldNote:M.observable("")},T=w("#markdown-editor"),F=w("#css-editor");T.value=f?.content||"";const dt=Ze(T),pt=Ze(F);s(()=>dt.destroy()),s(()=>pt.destroy());const N=document.createElement("div");N.className="editor-line-numbers editor-line-numbers--foldable",T.parentElement.insertBefore(N,T);const x=It(T,N,{onFoldChange:()=>{}});let z=!1;x.setGuard(e=>{z=e});const $=()=>x.getUnfoldedContent(),xe=M.observable(Q(K.type));s(M.effect(()=>{const e=xe.value,t=w("#editor-body");if(t){at(e.font);for(const[u,y]of Object.entries(Xe(e)))t.style.setProperty(u,y);t.classList.toggle("pe-nowrap",!e.wrap),T.wrap=e.wrap?"soft":"off",F.wrap=e.wrap?"soft":"off"}}));let Pe=null;s(()=>{try{Pe?.close()}catch{}});const B=()=>(n.bodyRev.value,{urlPath:a.urlPath.value.trim(),title:a.title.value,description:a.description.value,layout:a.layout.value,status:a.status.value,sortOrder:a.sortOrder.value,showInNav:a.showInNav.value,sidebar:a.sidebar.value,breadcrumbs:a.breadcrumbs.value,bundled:a.bundled.value,tags:a.tags.value,adminTags:a.adminTags.value,category:a.category.value,visibility:a.visibility.value,project:a.project.value,theme:a.theme.value,seoTitle:a.seoTitle.value,seoDesc:a.seoDesc.value,dconfig:a.dconfig.value,body:$(),css:a.cssOpened.value?a.css.value:null}),ut=M.computed(()=>j(B())!==n.base.value),mt=M.computed(()=>Te({urlPath:a.urlPath.value,dconfig:a.dconfig.value})[0]?.text||""),ft=M.computed(()=>n.versions.value.some(e=>e.checked.value)),vt=M.computed(()=>n.versions.value.length>0&&n.versions.value.every(e=>e.checked.value)),bt=M.computed(()=>{const e=n.versions.value.filter(t=>t.checked.value).length;return e>0&&e<n.versions.value.length});i=()=>j(B())!==n.base.peek();let Ae=JSON.stringify(a.adminTags.peek());const U=e=>({title:a.title.peek().trim()||"Untitled",description:a.description.peek().trim(),layout:a.layout.peek(),status:a.status.peek(),sortOrder:parseInt(String(a.sortOrder.peek()),10)||99,showInNav:a.showInNav.peek(),sidebar:a.sidebar.peek(),...a.breadcrumbs.peek()?{}:{breadcrumbs:!1},...a.bundled.peek()?{bundled:!0}:{},tags:a.tags.peek(),...d&&JSON.stringify(a.adminTags.peek())===Ae?{}:{adminTags:a.adminTags.peek()},category:a.category.peek().trim()||null,visibility:a.visibility.peek()||"public",project:a.project.peek()||"",seo:{title:a.seoTitle.peek().trim(),description:a.seoDesc.peek().trim()},dconfig:e,...a.theme.peek()?{theme:a.theme.peek()}:{}}),ne=()=>{const e=a.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},Le=()=>a.urlPath.peek().trim()||r||"/";let re=[];const gt=Array.isArray(D?.footer?.blocks)?D.footer.blocks.filter(e=>typeof e=="string"):[],ht=new Map((Array.isArray(H)?H:[]).map(e=>[e.name,e.updatedAt])),ie=()=>{const e=ta([...new Set([...re,...gt])],ht),t=Object.keys(e),u=t.length?`Preview shows unsaved changes to ${t.join(", ")}`:"";return u!==n.heldNote.peek()&&(n.heldNote.value=u),e},_=w("#markdown-preview");let De="";v.settings.getCustomCss().then(({css:e})=>{De=e||""}).catch(()=>{});const Oe=()=>{const e=_?.contentDocument;if(!e?.head)return;const t=a.cssOpened.peek()?a.css.peek():"";if(!t)return;let u=e.getElementById("preview-custom-css");u||(u=e.createElement("style"),u.id="preview-custom-css",e.head.appendChild(u)),u.textContent=t};let le=null;const J=()=>{clearTimeout(le),le=setTimeout(async()=>{if(!(!_||n.viewMode.peek()==="write"))try{const{html:e}=await v.pages.previewFull(Le(),U(ne()),$(),ie());h()&&rt(_,ga(e),Oe)}catch(e){_.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(e?.message||"unknown error").replace(/[<&]/g,t=>t==="<"?"&lt;":"&amp;")+"</body>"}},500)},Re=w("#live-preview-frame");let ce=!0,de=null,pe=!1;const ue=async()=>{if(!(pe||!Re)){pe=!0,n.liveStatus.value="Rendering\u2026";try{const{html:e}=await v.pages.previewFull(Le(),U(ne()),$(),ie());if(!h())return;rt(Re,e),ce=!1,n.liveStatus.value="Showing unsaved changes"}catch(e){n.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{pe=!1}}},Ne=()=>{ce=!0,n.tab.peek()==="live"&&(clearTimeout(de),de=setTimeout(ue,600))};s(()=>{clearTimeout(le),clearTimeout(de)});const me=Lt(o.find("#markdown-editor"),o.find("#editor-toolbar"),{spacerDefault:D?.layoutOptions?.spacerSize??40});Rt(T,{blocks:()=>oe});const fe=b.querySelector(".editor-toolbar-right");fe.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),w("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const yt=ae(`
81
+ `)}const qt="editor_prefs";function ae(){return S.get(qt)||{viewMode:"split",fullscreen:!1}}function ye(r,d){const p=ae();p[r]=d,S.set(qt,p)}const mo=["edit","details","seo","dconfig","live","history"],Ht=["split","write","preview"],go=r=>r.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function Ut(r,d,p=()=>{}){let s=0;try{s=r.contentWindow.scrollY||0}catch{}r.addEventListener("load",()=>{try{r.contentWindow.scrollTo(0,s)}catch{}p()},{once:!0}),r.srcdoc=d}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(r){if(bo(),T){const e=T;T=null,e.dispose()}const d=window.location.hash.replace(/^#/,""),p=[],s=e=>(p.push(e),e);let c=()=>!1,v=()=>{},f=()=>{};const b=window.location.hash.match(/#\/pages\/edit(\/[^?]*)/),l=b?b[1]:null,h=!!l,y={path:d,item:{kind:"page",id:l||""},dirty:()=>c(),keepDraft:()=>v(),hold:()=>f(),dispose:()=>{p.splice(0).forEach(e=>{try{e()}catch{}})}};T=y;const C=r.get(0),u=()=>T===y&&C.isConnected,I=E.loader(C,{type:"dots"}),[J,U,m,Xe,Qe,Kt,_t,oe]=await Promise.all([g.layouts.get().catch(()=>({})),g.settings.get().catch(()=>({})),h?g.pages.get(l).catch(()=>null):null,g.pages.list().catch(()=>[]),g.projects.list().catch(()=>[]),g.pages.tags().catch(()=>[]),g.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),g.get("/blocks?usage=1").catch(()=>g.blocks.list()).catch(()=>[])]);if(!u()){I.destroy();return}if(I.destroy(),h&&!m){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const k=e=>C.querySelector(e);if(Sa(k("#field-theme"),{includeDefault:!0}),await Eo(k("#field-visibility"),m?.visibility),!u())return;const Wt=wo(k("#field-layout"),J,m?.layout||"default");for(const e of Qe)k("#field-project").append(new Option(e.name||e.slug,e.slug));const ke=Ja(oe),se=ae(),n={urlPath:M.observable(l||""),title:M.observable(m?.title||""),description:M.observable(m?.description||""),layout:M.observable(Wt),status:M.observable(m?.status||"draft"),sortOrder:M.observable(String(m?.sortOrder??99)),showInNav:M.observable(!!m?.showInNav),sidebar:M.observable(!!m?.sidebar),breadcrumbs:M.observable(m?m.breadcrumbs!==!1:!0),bundled:M.observable(!!m?.bundled),tags:M.observable(Array.isArray(m?.tags)?m.tags:[]),adminTags:M.observable(Array.isArray(m?.adminTags)?m.adminTags:[]),category:M.observable(m?.category||""),visibility:M.observable(typeof m?.visibility=="string"&&m.visibility?m.visibility:"public"),project:M.observable(qa({isEdit:h,page:m,projects:Qe,fromHash:$a()||"",urlPath:l||""})),theme:M.observable(m?.theme||""),seoTitle:M.observable(m?.seo?.title||""),seoDesc:M.observable(m?.seo?.description||""),dconfig:M.observable(m?.dconfig?JSON.stringify(m.dconfig,null,2):""),css:M.observable(""),cssOpened:M.observable(!1)},a={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),tab:M.observable("edit"),viewMode:M.observable(Ht.includes(se.viewMode)?se.viewMode:"split"),fullscreen:M.observable(!!se.fullscreen),cssMode:M.observable(!1),folded:M.observable(!1),liveStatus:M.observable("Showing unsaved changes"),liveWidth:M.observable("full"),versions:M.observable([]),histLoading:M.observable(!1),histError:M.observable(""),compare:M.observable(null),heldNote:M.observable(""),blockTab:M.observable(""),blockPart:M.observable("html"),blockReady:M.observable(!1),blockNote:M.observable(""),blockUses:M.observable(""),blockTabs:M.observable([]),blockRev:M.observable(0)},x=k("#markdown-editor"),K=k("#css-editor");x.value=m?.content||"";const Jt=Dt(x),Yt=Dt(K);s(()=>Jt.destroy()),s(()=>Yt.destroy());const F=document.createElement("div");F.className="editor-line-numbers editor-line-numbers--foldable",x.parentElement.insertBefore(F,x);const B=Ta(x,F,{onFoldChange:()=>{}});let _=!1;B.setGuard(e=>{_=e});const W=()=>B.getUnfoldedContent(),Ze=M.observable(me(se.type));s(M.effect(()=>{const e=Ze.value,t=k("#editor-body");if(t){jt(e.font);for(const[o,i]of Object.entries(Rt(e)))t.style.setProperty(o,i);t.classList.toggle("pe-nowrap",!e.wrap),x.wrap=e.wrap?"soft":"off",K.wrap=e.wrap?"soft":"off"}}));let et=null;s(()=>{try{et?.close()}catch{}});const z=()=>(a.bodyRev.value,{urlPath:n.urlPath.value.trim(),title:n.title.value,description:n.description.value,layout:n.layout.value,status:n.status.value,sortOrder:n.sortOrder.value,showInNav:n.showInNav.value,sidebar:n.sidebar.value,breadcrumbs:n.breadcrumbs.value,bundled:n.bundled.value,tags:n.tags.value,adminTags:n.adminTags.value,category:n.category.value,visibility:n.visibility.value,project:n.project.value,theme:n.theme.value,seoTitle:n.seoTitle.value,seoDesc:n.seoDesc.value,dconfig:n.dconfig.value,body:W(),css:n.cssOpened.value?n.css.value:null}),P=new Map,ne=e=>Ge(e.form)!==e.base,Y=()=>[...P.entries()].filter(([,e])=>ne(e)).map(([e])=>e),Gt=M.computed(()=>H(z())!==a.base.value),Xt=M.computed(()=>(a.blockRev.value,H(z())!==a.base.value||Y().length>0)),Qt=M.computed(()=>Ye({urlPath:n.urlPath.value,dconfig:n.dconfig.value})[0]?.text||""),Zt=M.computed(()=>a.versions.value.some(e=>e.checked.value)),ea=M.computed(()=>a.versions.value.length>0&&a.versions.value.every(e=>e.checked.value)),ta=M.computed(()=>{const e=a.versions.value.filter(t=>t.checked.value).length;return e>0&&e<a.versions.value.length}),we=()=>H(z())!==a.base.peek();c=()=>we()||Y().length>0;let tt=JSON.stringify(n.adminTags.peek());const re=e=>({title:n.title.peek().trim()||"Untitled",description:n.description.peek().trim(),layout:n.layout.peek(),status:n.status.peek(),sortOrder:parseInt(String(n.sortOrder.peek()),10)||99,showInNav:n.showInNav.peek(),sidebar:n.sidebar.peek(),...n.breadcrumbs.peek()?{}:{breadcrumbs:!1},...n.bundled.peek()?{bundled:!0}:{},tags:n.tags.peek(),...h&&JSON.stringify(n.adminTags.peek())===tt?{}:{adminTags:n.adminTags.peek()},category:n.category.peek().trim()||null,visibility:n.visibility.peek()||"public",project:n.project.peek()||"",seo:{title:n.seoTitle.peek().trim(),description:n.seoDesc.peek().trim()},dconfig:e,...n.theme.peek()?{theme:n.theme.peek()}:{}}),Ee=()=>{const e=n.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},at=()=>n.urlPath.peek().trim()||l||"/";let le=[];const Me=Array.isArray(U?.footer?.blocks)?U.footer.blocks.filter(e=>typeof e=="string"):[],Se=Array.isArray(oe)?oe:[],ot=new Map(Se.map(e=>[e.name,e.updatedAt])),Te=new Map(Se.map(e=>[e.name,e.usage]));let G=new Set;const Ce=()=>{const e=[...new Set([...le,...Me,...P.keys()])],t=Ka(e.filter(i=>!P.has(i)),ot);G=new Set(Object.keys(t));const o=G.size?`Preview shows unsaved changes to ${[...G].join(", ")}`:"";return o!==a.heldNote.peek()&&(a.heldNote.value=o),oo(t,[...P.entries()].map(([i,w])=>({name:i,content:w.form.content,css:w.form.css,dirty:ne(w)})))};let xe="";const aa=()=>{nt();const e=xe;if(e){xe="";try{X.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},X=k("#markdown-preview");let st="";g.settings.getCustomCss().then(({css:e})=>{st=e||""}).catch(()=>{});const nt=()=>{const e=X?.contentDocument;if(!e?.head)return;const t=n.cssOpened.peek()?n.css.peek():"";if(!t)return;let o=e.getElementById("preview-custom-css");o||(o=e.createElement("style"),o.id="preview-custom-css",e.head.appendChild(o)),o.textContent=t};let Pe=null;const O=()=>{clearTimeout(Pe),Pe=setTimeout(async()=>{if(!(!X||a.viewMode.peek()==="write"))try{const{html:e}=await g.pages.previewFull(at(),re(Ee()),W(),Ce());u()&&Ut(X,ao(go(e),a.blockTab.peek()),aa)}catch(e){X.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(e?.message||"unknown error").replace(/[<&]/g,t=>t==="<"?"&lt;":"&amp;")+"</body>"}},500)},rt=k("#live-preview-frame");let Ae=!0,Le=null,Re=!1;const De=async()=>{if(!(Re||!rt)){Re=!0,a.liveStatus.value="Rendering\u2026";try{const{html:e}=await g.pages.previewFull(at(),re(Ee()),W(),Ce());if(!u())return;Ut(rt,e),Ae=!1,a.liveStatus.value="Showing unsaved changes"}catch(e){a.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{Re=!1}}},Q=()=>{Ae=!0,a.tab.peek()==="live"&&(clearTimeout(Le),Le=setTimeout(De,600))};s(()=>{clearTimeout(Pe),clearTimeout(Le)});const Be=ya(r.find("#markdown-editor"),r.find("#editor-toolbar"),{spacerDefault:U?.layoutOptions?.spacerSize??40});Ea(x,{blocks:()=>ke,openBlock:e=>He(e)});const ie=C.querySelector(".editor-toolbar-right");ie.querySelectorAll(".editor-view-btn[data-mode]").forEach(e=>{const t=e.getAttribute("data-mode");e.classList.remove("active"),e.setAttribute("data-bind-class",`viewMode.value === '${t}' && 'active'`),e.setAttribute("data-on-click",`setMode('${t}')`)}),k("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const oa=he(`
82
82
  <span class="pe-toolbar-extras" style="display:contents">
83
83
  <button type="button" class="editor-view-btn" data-tip="Edit Custom CSS" data-on-click="toggleCss"
84
84
  data-bind-class="cssMode.value && 'active'"><span>CSS</span></button>
@@ -90,5 +90,7 @@ The quick brown fox - 0O 1lI {} [] () =&gt; ==
90
90
  <span class="pe-fold-icon" hidden data-bind-hidden="!folded.value"><span data-icon="chevrons-right" data-icon-size="10"></span></span>
91
91
  </button>
92
92
  <span class="editor-toolbar-sep"></span>
93
- </span>`);fe.prepend(yt);const Be=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),Ie=w("#field-tags");let W=null;W=Ie?E.pillbox(Ie,{data:Be(it),value:a.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{W&&(a.tags.value=[...W.getValue()])}}):null;const Fe=w("#field-admin-tags");let Y=null;Y=Fe?E.pillbox(Fe,{data:Be(lt),value:a.adminTags.peek(),creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{Y&&(a.adminTags.value=[...Y.getValue()])}}):null;let ve=!1,be=null;const G=async()=>{ve=!0,n.histLoading.value=!0,n.histError.value="";try{const e=(await v.versions.list(r)).versions||[];if(!h())return;n.versions.value=e.map(t=>({...t,when:te(t.createdAt),author:t.author||"",label:t.label||"",checked:M.observable(!1)}))}catch(e){n.histError.value="Failed to load versions: "+e.message}finally{n.histLoading.value=!1}},ge=e=>n.versions.peek().find(t=>t.filename===e),he=async e=>{try{const t=await v.versions.get(r,e.filename);be=e.filename,n.compare.value={label:tt(e),old:t.content,current:$()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},ze=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await v.versions.restore(r,e),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${r}`),300)}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},kt=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await v.versions.delete(r,e.filename),E.toast("Version deleted.",{type:"success"}),await G()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},$e=()=>{n.bodyRev.value=n.bodyRev.peek()+1},je=()=>{a.cssOpened.peek()||(a.css.value=De,a.cssOpened.value=!0,At(F))},ye=e=>{for(const t of["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"])a[t].value=e[t]==null?"":String(e[t]);for(const t of["showInNav","sidebar","breadcrumbs","bundled"])a[t].value=!!e[t];a.tags.value=Array.isArray(e.tags)?e.tags:[],W?.setValue(a.tags.peek()),Array.isArray(e.adminTags)&&(a.adminTags.value=e.adminTags),Y?.setValue(a.adminTags.peek()),x.unfoldAll(),z=!0,T.value=e.body==null?"":String(e.body),z=!1,x.refresh(),e.css!=null&&(je(),a.css.value=String(e.css)),$e()};n.base.value=j(B());const X=Xt(r||"");m=()=>{if(i())try{S.set(X,Qt(B(),n.base.peek()))}catch{}};const ke=()=>{try{S.remove?S.remove(X):S.set(X,null)}catch{}},Ve=e=>{i()&&(m(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Ve),s(()=>window.removeEventListener("beforeunload",Ve));const qe=async()=>{if(n.saving.peek())return;const e=B(),t=Te(e);if(t.length){E.toast(t[0].text,{type:"warning"}),b.querySelectorAll("#editor-meta-tabs .tab-item")[ba.indexOf(t[0].field==="dconfig"?"dconfig":"details")]?.click();return}const u=(e.dconfig||"").trim(),y=U(u?JSON.parse(u):null),A=a.cssOpened.peek();n.saving.value=!0;try{if(d){const I={frontmatter:y,body:e.body};e.urlPath!==r&&(I.newUrlPath=e.urlPath),await v.pages.update(r,I)}else await v.pages.create({urlPath:e.urlPath,frontmatter:y,body:e.body});if(A&&await v.settings.saveCustomCss(a.css.peek()),E.toast(A?"Page and CSS saved.":"Page saved.",{type:"success"}),!h())return;ke(),d&&sa({kind:"page",id:e.urlPath,label:a.title.peek().trim()||"Untitled"}),Ae=JSON.stringify(a.adminTags.peek()),n.base.value=j(B()),d?e.urlPath!==r&&R.navigate(`/pages/edit${e.urlPath}`):R.navigate("/pages")}catch(I){E.toast(`Save failed: ${I.message||"Unknown error"}`,{type:"error"})}finally{n.saving.value=!1}},He=b.querySelector(".dm-plugin-body")||b,wt={...a,...n,dirty:ut,problem:mt,anySelected:ft,allSelected:vt,someSelected:bt,isEdit:d,savedPath:r||""},Et={save:()=>qe(),discard:async()=>{!i()||!await E.confirm("Discard the changes you have not saved?")||(ye(JSON.parse(n.base.peek())),ke())},showTab:e=>{n.tab.value=e,e==="live"&&ce&&ue(),e==="history"&&d&&!ve&&G()},setMode:e=>{n.cssMode.peek()&&(n.cssMode.value=!1),n.viewMode.value=e,se("viewMode",e),e!=="write"&&J()},toggleFullscreen:()=>{n.fullscreen.value=!n.fullscreen.peek(),se("fullscreen",n.fullscreen.peek())},toggleCss:()=>{n.cssMode.peek()?(n.cssMode.value=!1,n.viewMode.value=nt.includes(q().viewMode)?q().viewMode:"split",x.refresh()):(je(),n.cssMode.value=!0,n.viewMode.value="split")},formatMarkdown:()=>{const e=T.selectionStart,t=va($());x.unfoldAll(),z=!0,T.value=t,z=!1,T.selectionStart=T.selectionEnd=Math.min(e,t.length),T.dispatchEvent(new Event("input")),x.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{n.folded.peek()?x.unfoldAll():x.foldAll(),n.folded.value=!n.folded.peek()},setWidth:e=>{n.liveWidth.value=e;const t=w("#live-preview-stage");t&&(t.scrollTop=0)},refreshLive:()=>ue(),share:async()=>{if(!d){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await ua(r)},toggleAll:e=>{const t=!!e?.target?.checked;n.versions.peek().forEach(u=>{u.checked.value=t})},deleteSelected:async()=>{const e=n.versions.peek().filter(u=>u.checked.peek()).map(u=>u.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await v.versions.bulkDelete(r,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await G()}catch(u){E.toast("Delete failed: "+u.message,{type:"error"})}},saveNamed:async()=>{await ma(r)&&h()&&(n.tab.peek()==="history"?await G():ve=!1)},closeCompare:()=>{n.compare.value=null,be=null},restoreCompared:()=>ze(be)};fe.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const Mt=M.applyBindings(wt,He,{methods:Et});s(()=>Mt.dispose());const Ke=aa(w("#page-hop"),{current:{kind:"page",id:r||"",label:f?.title||r||""},pages:O,blocks:H});s(()=>Ke.dispose()),E.tabs(w("#editor-meta-tabs"));let we=null;s(()=>clearTimeout(we)),T.addEventListener("input",()=>{x.refresh(),N.scrollTop=T.scrollTop,!z&&(clearTimeout(we),we=setTimeout($e,120),J(),Ne())}),T.addEventListener("scroll",()=>{N.scrollTop=T.scrollTop}),F.addEventListener("scroll",()=>{N.scrollTop=F.scrollTop});let Ue=!0;s(M.effect(()=>{for(const e of Object.keys(a))e!=="css"&&e!=="cssOpened"&&a[e].value;if(n.bodyRev.value,Ue){Ue=!1,J();return}J(),Ne()}));const St=oe.map(e=>e.name),Tt=oe.filter(e=>e.shortTag).map(e=>e.name);s(M.effect(()=>{n.bodyRev.value,re=ia($(),St,Tt),Ke.setRelated(re.map(e=>({kind:"block",id:e,label:e}))),ie()}));let Ee=null;if(s(()=>clearTimeout(Ee)),s(M.effect(()=>{const e=a.css.value;n.cssMode.value&&(ha(N,e,F),clearTimeout(Ee),Ee=setTimeout(Oe,400))})),d){const e=w(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:u=>{const y=ge(u.dataset.file);return y?[{header:tt(y)},{label:"Compare with the editor",icon:"columns",action:()=>he(y)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>ze(y.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>kt(y)}]:[]}});s(()=>t?.destroy?.()),e.addEventListener("click",u=>{const y=u.target.closest("tr[data-file]");if(!y||u.target.matches("input"))return;const A=u.target.closest(".pe-kebab");if(A){Ye(A);return}const I=ge(y.dataset.file);I&&he(I)}),e.addEventListener("keydown",u=>{const y=u.target.closest("tr[data-file]");if(y){if(u.key==="F10"&&u.shiftKey)u.preventDefault(),Ye(y);else if(u.key==="Enter"&&u.target===y){const A=ge(y.dataset.file);A&&he(A)}}})}ya(w("#editor-body"),w("#editor-divider"),s),Nt({image:e=>(n.cssMode.value=!1,ka(e)),scribe:e=>Ft(e),card:e=>xt.open(e,{onMediaBrowse:()=>v.media.list().catch(()=>[])}),timeline:e=>Pt.open(e,{onPreview:async t=>{try{return await v.pages.preview(t)}catch{return null}},onListCollections:()=>v.collections.list().catch(()=>[]),onGetCollection:t=>v.collections.get(t).catch(()=>null)})}),me.onEffects(e=>Ot.open(e)),me.onHelp(()=>Ge()),me.onTutorial(e=>zt(e));const _e=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&h()&&(e.preventDefault(),qe())};document.addEventListener("keydown",_e),s(()=>document.removeEventListener("keydown",_e));const Z=b.querySelector(".dm-plugin-banner-name");s(M.effect(()=>{const e=Gt(d,a.title.value,r);if(!h()||(document.title=`${e} - Domma CMS`,!Z))return;const t=Z.lastChild;t&&t.nodeType===3&&Z.querySelector(".dm-plugin-banner-sep")?t.textContent=e:Z.textContent=`Pages \u203A ${e}`})),V(o,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),V(o,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Ge()}),d&&V(o,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(a.urlPath.peek()||r,"_blank","noopener")});const Me=V(o,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:()=>{if(!d||n.saving.peek()||!h())return;const e=B(),t=Te({...e,urlPath:r});if(t.length){E.toast(t[0].text,{type:"warning"});return}const u=jt({page:f,frontmatter:{...U(ne()),adminTags:a.adminTags.peek()},body:e.body,source:r,pages:O,onCloned:y=>{h()&&(C.cloneTarget=`/pages/edit${y}`,R.navigate(C.cloneTarget))}});s(()=>u.close())}});Me&&(Me.disabled=!d,d||(Me.title="Save the page before cloning it")),V(o,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Pe=pa(xe)}}),s(We(He,{position:"bottom"})),Domma.icons.scan();let Je=null;try{Je=S.get(X)}catch{}const P=ea(Je),Ct=oa(C.item);if(P){if(ke(),j(P.state)!==n.base.peek()&&Ct&&!Qe(P,n.base.peek()))ye(P.state),E.toast("Your unsaved changes to this page are back.",{type:"info"});else if(j(P.state)!==n.base.peek()){const e=P.at?` (${te(P.at)})`:"",t=Qe(P,n.base.peek())?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${e}. Restore them?${t}`)&&h()&&ye(P.state)}}}};function ha(o,l,c){o.replaceChildren(...String(l).split(`
94
- `).map((s,i)=>{const m=document.createElement("span");m.className="editor-line-number-row";const p=document.createElement("span");p.className="fold-toggle fold-toggle--empty";const r=document.createElement("span");return r.className="editor-line-num",r.textContent=String(i+1),m.append(p,r),m})),o.scrollTop=c.scrollTop}function ya(o,l,c){if(!o||!l)return;const s=15,i=85,m=b=>Math.min(i,Math.max(s,b));let p=m(parseFloat(q().splitRatio)||50);const r=b=>{o.style.setProperty("--dm-editor-split",p+"%"),l.setAttribute("aria-valuenow",String(Math.round(p))),b&&se("splitRatio",p)};r(!1);const d=b=>{const h=o.getBoundingClientRect();h.width&&(p=m((b.clientX-h.left)/h.width*100)),r(!1)},C=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",C),o.classList.remove("is-resizing"),l.classList.remove("is-dragging"),r(!0)};c(C),l.addEventListener("pointerdown",b=>{window.matchMedia("(max-width: 768px)").matches||(b.preventDefault(),o.classList.add("is-resizing"),l.classList.add("is-dragging"),document.addEventListener("pointermove",d),document.addEventListener("pointerup",C))}),l.addEventListener("dblclick",()=>{p=50,r(!0)}),l.addEventListener("keydown",b=>{const h=b.shiftKey?10:2;let g=p;if(b.key==="ArrowLeft")g=p-h;else if(b.key==="ArrowRight")g=p+h;else if(b.key==="Home")g=s;else if(b.key==="End")g=i;else if(b.key==="Enter"||b.key===" ")g=50;else return;b.preventDefault(),p=m(g),r(!0)})}async function ka(o){const l=o.selectionStart,c=o.selectionEnd,s=await $t({title:"Upload or insert an image"});if(!s||!o.isConnected)return;let i=s.split("/").pop().split("?")[0];try{i=decodeURIComponent(i)}catch{}const m=i.replace(/[\\[\]]/g,"\\$&"),p=s.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");o.setSelectionRange(l,c),Dt(o,`![${m}](${p})`),o.dispatchEvent(new Event("input",{bubbles:!0}))}function wa(o,l,c){const s=Object.entries(l||{});return o?(o.replaceChildren(...s.map(([i,m])=>new Option(m?.label||i,i))),s.some(([i])=>i===c)?c:s[0]?.[0]||c):c}async function Ea(o,l){if(!o)return;let c=[];try{const d=await v.get("/collections/roles/entries?limit=100");c=Array.isArray(d)?d:d?.entries||[]}catch{}const s=c.map(d=>({name:d.data?.name,label:d.data?.label||d.data?.name,level:d.data?.level??99})).filter(d=>d.name&&d.level!==0).sort((d,C)=>C.level-d.level),i=(d,C)=>Object.assign(document.createElement("option"),{value:d,textContent:C}),m=()=>{typeof l=="string"&&l&&![...o.options].some(d=>d.value===l)&&o.append(i(l,`${l} (custom)`))};if(!s.length)return m();const p=document.createElement("optgroup");p.label="Role and above",s.forEach(d=>p.append(i(d.name,`${d.label} and above`)));const r=document.createElement("optgroup");r.label="Role only",s.forEach(d=>r.append(i(`=${d.name}`,`${d.label} only`))),o.replaceChildren(i("public","Public - everyone"),p,r,i("private","Private - super-admin only")),m()}
93
+ </span>`);ie.prepend(oa),ie.before(k(".pe-block-bar"));const lt=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),it=k("#field-tags");let ce=null;ce=it?E.pillbox(it,{data:lt(Kt),value:n.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{ce&&(n.tags.value=[...ce.getValue()])}}):null;const ct=k("#field-admin-tags");let de=null;de=ct?E.pillbox(ct,{data:lt(_t),value:n.adminTags.peek(),creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{de&&(n.adminTags.value=[...de.getValue()])}}):null;let Oe=!1,Ne=null;const pe=async()=>{Oe=!0,a.histLoading.value=!0,a.histError.value="";try{const e=(await g.versions.list(l)).versions||[];if(!u())return;a.versions.value=e.map(t=>({...t,when:ee(t.createdAt),author:t.author||"",label:t.label||"",checked:M.observable(!1)}))}catch(e){a.histError.value="Failed to load versions: "+e.message}finally{a.histLoading.value=!1}},$e=e=>a.versions.peek().find(t=>t.filename===e),Ie=async e=>{try{const t=await g.versions.get(l,e.filename);Ne=e.filename,a.compare.value={label:zt(e),old:t.content,current:W()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},dt=async e=>{if(!(!e||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await g.versions.restore(l,e),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${l}`),300)}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},sa=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await g.versions.delete(l,e.filename),E.toast("Version deleted.",{type:"success"}),await pe()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},pt=()=>{a.bodyRev.value=a.bodyRev.peek()+1},ut=()=>{n.cssOpened.peek()||(n.css.value=st,n.cssOpened.value=!0,ha(K))},Fe=e=>{for(const t of["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","dconfig"])n[t].value=e[t]==null?"":String(e[t]);for(const t of["showInNav","sidebar","breadcrumbs","bundled"])n[t].value=!!e[t];n.tags.value=Array.isArray(e.tags)?e.tags:[],ce?.setValue(n.tags.peek()),Array.isArray(e.adminTags)&&(n.adminTags.value=e.adminTags),de?.setValue(n.adminTags.peek()),B.unfoldAll(),_=!0,x.value=e.body==null?"":String(e.body),_=!1,B.refresh(),e.css!=null&&(ut(),n.css.value=String(e.css)),pt()};a.base.value=H(z());const ze=Va(l||""),ue=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}};v=()=>{try{we()&&S.set(ze,Ha(z(),a.base.peek()));const e=new Date().toISOString();for(const t of Y()){const o=P.get(t);S.set(te(t),{form:{...o.form},base:o.base,at:e})}}catch{}},f=()=>{Ot(y.item);for(const e of Y())Ot({kind:"block",id:e})};const je=()=>ue(ze),ft=e=>{c()&&(v(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ft),s(()=>window.removeEventListener("beforeunload",ft));const bt=new Set,na=async e=>{const t=[];for(const o of e){const i=P.get(o);if(!i)continue;const w={...i.form};try{const A=Za(w,i.block?.meta);await g.blocks.put(o,A),i.block={...i.block,meta:A.meta},i.saved=w,i.base=Ge(w),ot.set(o,new Date().toISOString()),u()&&ue(te(o)),t.push(o)}catch(A){E.toast(`Block "${o}" not saved: ${A.message||"Unknown error"}`,{type:"error"})}}return t.forEach(o=>bt.add(o)),t.length&&(a.blockRev.value=a.blockRev.peek()+1,Pa("#/blocks"),u()&&q()),t};let Ve=!1;const vt=async()=>{if(a.saving.peek()||Ve)return;Ve=!0;let e,t,o;try{if(await kt(),!u())return;e=z(),t=Y(),o=!h||we()||!t.length;const D=o?Ye(e):[];if(D.length){E.toast(D[0].text,{type:"warning"}),C.querySelectorAll("#editor-meta-tabs .tab-item")[mo.indexOf(D[0].field==="dconfig"?"dconfig":"details")]?.click();return}const L=so(t,Object.fromEntries(t.map(ve=>[ve,It(Te.get(ve),l||"")])),bt);if(L&&(!await E.confirm(L)||!u()))return}finally{Ve=!1}const i=(e.dconfig||"").trim(),w=re(i?JSON.parse(i):null),A=o&&n.cssOpened.peek();a.saving.value=!0;try{if(o&&h){const L={frontmatter:w,body:e.body};e.urlPath!==l&&(L.newUrlPath=e.urlPath),await g.pages.update(l,L)}else o&&await g.pages.create({urlPath:e.urlPath,frontmatter:w,body:e.body});o&&u()&&(tt=JSON.stringify(e.adminTags),a.base.value=H(e)),A&&await g.settings.saveCustomCss(n.css.peek());const D=await na(t);if((o||D.length)&&E.toast(no({page:o,css:A,blocks:D}),{type:"success"}),!o||!u())return;je(),h&&Nt({kind:"page",id:e.urlPath,label:n.title.peek().trim()||"Untitled"}),h?e.urlPath!==l&&R.navigate(`/pages/edit${e.urlPath}`):R.navigate("/pages")}catch(D){E.toast(`Save failed: ${D.message||"Unknown error"}`,{type:"error"})}finally{a.saving.value=!1}};let N=null,j=null;const V=new Map,qe=new Map,fe=()=>{a.blockRev.value=a.blockRev.peek()+1},mt=new Set(Se.map(e=>e.name)),ra={body:"a block on this page",footer:"in the footer, on every page",open:"not on this page - opened here"},la=(e,t)=>{const o=`${t}:${e}`;return qe.has(o)||qe.set(o,{id:o,name:e,kindText:t==="footer"?"Footer":"Block",title:`${e} - ${ra[t]}`,active:M.observable(!1),dirty:M.observable(!1)}),qe.get(o)},gt=()=>{for(const e of a.blockTabs.peek()){const t=P.get(e.name),o=e.name===a.blockTab.peek(),i=t?ne(t):G.has(e.name);e.active.peek()!==o&&(e.active.value=o),e.dirty.peek()!==i&&(e.dirty.value=i)}},q=()=>{const e=eo(le,Me,mt).map(i=>({name:i.name,kind:i.footer?"footer":"body"})),t=[...P.keys(),...V.keys(),a.blockTab.peek()].filter(i=>i&&!e.some(w=>w.name===i));for(const i of new Set(t))e.push({name:i,kind:"open"});const o=e.map(i=>la(i.name,i.kind));o.map(i=>i.id).join("|")!==a.blockTabs.peek().map(i=>i.id).join("|")&&(a.blockTabs.value=o),gt()},ht=()=>{const e=P.get(a.blockTab.peek());!e||!N||(N.setValue(e.form.content),j.setValue(e.form.css),a.blockNote.value="",a.blockReady.value=!0)},yt=async(e,{quiet:t=!1}={})=>{for(;V.has(e);)if(await V.get(e),P.has(e)||t)return P.get(e)||null;if(P.has(e))return P.get(e);const o=(async()=>{try{const i=await g.blocks.get(e);if(!u())return null;const w=Qa({...i,name:e}),A={block:i,saved:w,base:Ge(w),form:{...w}};let D=null;try{D=S.get(te(e))}catch{}const L=Ga(D,A.base);if(t&&L?.stale)return null;if($t({kind:"block",id:e}),L){const ve=L.at?` (${ee(L.at)})`:"",va=!L.stale||await E.confirm(`You left unsaved changes to "${e}"${ve}, but the block has been saved since. Restore your version anyway?`);if(!u())return null;va&&(A.form={...A.form,...L.form,name:e},L.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return D&&ue(te(e)),P.set(e,A),fe(),A}catch(i){return u()&&a.blockTab.peek()===e&&(a.blockNote.value=`Could not open "${e}": ${i.message||"unknown error"}`),null}finally{V.delete(e)}})();return V.set(e,o),o},kt=async()=>{for(;V.size;)await Promise.all([...V.values()])},He=async e=>{if(!e){if(!a.blockTab.peek())return;a.blockTab.value="",q(),B.refresh(),O(),x.focus();return}a.tab.peek()!=="edit"&&C.querySelectorAll("#editor-meta-tabs .tab-item")[0]?.click(),a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.peek()==="preview"&&(a.viewMode.value="split"),a.blockTab.value=e;const t=le.includes(e)||Me.includes(e);a.blockUses.value=t?ro(It(Te.get(e),l||"")):to(Te.get(e)),Nt({kind:"block",id:e,label:e}),q();let o=P.get(e);o||(a.blockReady.value=!1,a.blockNote.value=`Opening ${e}\u2026`,o=await yt(e)),!(!o||!u()||a.blockTab.peek()!==e)&&(ht(),q(),xe=e,O(),(a.blockPart.peek()==="css"?j:N)?.focus())};let Ue=null;s(()=>clearTimeout(Ue));const wt=(e,t)=>{const o=P.get(a.blockTab.peek());!o||!a.blockReady.peek()||(o.form[e]=t,clearTimeout(Ue),Ue=setTimeout(fe,120),O(),Q())},Et=C.querySelector(".dm-plugin-body")||C,ia={...n,...a,dirty:Xt,pageDirty:Gt,problem:Qt,anySelected:Zt,allSelected:ea,someSelected:ta,isEdit:h,savedPath:l||""},ca={save:()=>vt(),discard:async()=>{if(await kt(),!(!u()||!c()||!await E.confirm("Discard the changes you have not saved?"))){Fe(JSON.parse(a.base.peek())),je();for(const[e,t]of P)ne(t)&&(t.form={...t.saved},ue(te(e)));ht(),fe(),q(),O(),Q()}},setBlockPart:e=>{a.blockPart.value=e==="css"?"css":"html",(e==="css"?j:N)?.focus()},formatBlock:()=>{const e=P.get(a.blockTab.peek());!e||!N||(e.form.content=Xa(e.form.content),N.setValue(e.form.content),fe(),O(),Q())},openBlockEditor:()=>{const e=a.blockTab.peek();e&&R.navigate(`/blocks/edit/${encodeURIComponent(e)}`)},showTab:e=>{a.tab.value=e,e==="live"&&Ae&&De(),e==="history"&&h&&!Oe&&pe()},setMode:e=>{a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.value=e,ye("viewMode",e),e!=="write"&&O()},toggleFullscreen:()=>{a.fullscreen.value=!a.fullscreen.peek(),ye("fullscreen",a.fullscreen.peek())},toggleCss:()=>{a.cssMode.peek()?(a.cssMode.value=!1,a.viewMode.value=Ht.includes(ae().viewMode)?ae().viewMode:"split",B.refresh()):(ut(),a.cssMode.value=!0,a.viewMode.value="split")},formatMarkdown:()=>{const e=x.selectionStart,t=vo(W());B.unfoldAll(),_=!0,x.value=t,_=!1,x.selectionStart=x.selectionEnd=Math.min(e,t.length),x.dispatchEvent(new Event("input")),B.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{a.folded.peek()?B.unfoldAll():B.foldAll(),a.folded.value=!a.folded.peek()},setWidth:e=>{a.liveWidth.value=e;const t=k("#live-preview-stage");t&&(t.scrollTop=0)},refreshLive:()=>De(),share:async()=>{if(!h){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await uo(l)},toggleAll:e=>{const t=!!e?.target?.checked;a.versions.peek().forEach(o=>{o.checked.value=t})},deleteSelected:async()=>{const e=a.versions.peek().filter(o=>o.checked.peek()).map(o=>o.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await g.versions.bulkDelete(l,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await pe()}catch(o){E.toast("Delete failed: "+o.message,{type:"error"})}},saveNamed:async()=>{await fo(l)&&u()&&(a.tab.peek()==="history"?await pe():Oe=!1)},closeCompare:()=>{a.compare.value=null,Ne=null},restoreCompared:()=>dt(Ne)};ie.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const da=M.applyBindings(ia,Et,{methods:ca});s(()=>da.dispose());const pa=_a(k("#page-hop"),{current:{kind:"page",id:l||"",label:m?.title||l||""},pages:Xe,blocks:oe,open:e=>e.kind!=="block"||!mt.has(e.id)?!1:(He(e.id),!0)});s(()=>pa.dispose()),N=Ft(k('.pe-block-edit[data-part="html"]'),{initialValue:"",language:"html",autoIndent:!0,onChange:e=>wt("content",e)}),j=Ft(k('.pe-block-edit[data-part="css"]'),{initialValue:"",language:"css",autoIndent:!0,onChange:e=>wt("css",e)}),N.textarea.setAttribute("aria-label","Block template HTML"),j.textarea.setAttribute("aria-label","Block CSS"),j.textarea.placeholder=`/* Scoped to this block - e.g.
94
+ color: var(--dm-primary);
95
+ h3 { font-size: 1.25rem; } */`,s(()=>{N.destroy(),j.destroy()});const Mt=k(".pe-files"),St=e=>{const t=e.target.closest(".pe-file");t&&He(t.dataset.name||"")};Mt.addEventListener("click",St),s(()=>Mt.removeEventListener("click",St)),E.tabs(k("#editor-meta-tabs"));let Ke=null;s(()=>clearTimeout(Ke)),x.addEventListener("input",()=>{B.refresh(),F.scrollTop=x.scrollTop,!_&&(clearTimeout(Ke),Ke=setTimeout(pt,120),O(),Q())}),x.addEventListener("scroll",()=>{F.scrollTop=x.scrollTop}),K.addEventListener("scroll",()=>{F.scrollTop=K.scrollTop});let Tt=!0;s(M.effect(()=>{for(const e of Object.keys(n))e!=="css"&&e!=="cssOpened"&&n[e].value;if(a.bodyRev.value,Tt){Tt=!1,O();return}O(),Q()}));const ua=ke.map(e=>e.name),fa=ke.filter(e=>e.shortTag).map(e=>e.name);s(M.effect(()=>{a.bodyRev.value,le=Ya(W(),ua,fa),Ce(),q()})),s(M.effect(()=>{a.blockRev.value,a.blockTab.value,gt()})),s(M.effect(()=>{F.hidden=!!a.blockTab.value}));let _e=null;if(s(()=>clearTimeout(_e)),s(M.effect(()=>{const e=n.css.value;a.cssMode.value&&(ho(F,e,K),clearTimeout(_e),_e=setTimeout(nt,400))})),h){const e=k(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:o=>{const i=$e(o.dataset.file);return i?[{header:zt(i)},{label:"Compare with the editor",icon:"columns",action:()=>Ie(i)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>dt(i.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>sa(i)}]:[]}});s(()=>t?.destroy?.()),e.addEventListener("click",o=>{const i=o.target.closest("tr[data-file]");if(!i||o.target.matches("input"))return;const w=o.target.closest(".pe-kebab");if(w){At(w);return}const A=$e(i.dataset.file);A&&Ie(A)}),e.addEventListener("keydown",o=>{const i=o.target.closest("tr[data-file]");if(i){if(o.key==="F10"&&o.shiftKey)o.preventDefault(),At(i);else if(o.key==="Enter"&&o.target===i){const w=$e(i.dataset.file);w&&Ie(w)}}})}yo(k("#editor-body"),k("#editor-divider"),s),Ma({image:e=>(a.cssMode.value=!1,ko(e)),scribe:e=>Ca(e),card:e=>ma.open(e,{onMediaBrowse:()=>g.media.list().catch(()=>[])}),timeline:e=>ga.open(e,{onPreview:async t=>{try{return await g.pages.preview(t)}catch{return null}},onListCollections:()=>g.collections.list().catch(()=>[]),onGetCollection:t=>g.collections.get(t).catch(()=>null)})}),Be.onEffects(e=>wa.open(e)),Be.onHelp(()=>Lt()),Be.onTutorial(e=>xa(e));const Ct=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&u()&&(e.preventDefault(),vt())};document.addEventListener("keydown",Ct),s(()=>document.removeEventListener("keydown",Ct));const be=C.querySelector(".dm-plugin-banner-name");s(M.effect(()=>{const e=ja(h,n.title.value,l);if(!u()||(document.title=`${e} - Domma CMS`,!be))return;const t=be.lastChild;t&&t.nodeType===3&&be.querySelector(".dm-plugin-banner-sep")?t.textContent=e:be.textContent=`Pages \u203A ${e}`})),Z(r,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),Z(r,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Lt()}),h&&Z(r,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>window.open(n.urlPath.peek()||l,"_blank","noopener")});const We=Z(r,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:()=>{if(!h||a.saving.peek()||!u())return;const e=z(),t=Ye({...e,urlPath:l});if(t.length){E.toast(t[0].text,{type:"warning"});return}const o=La({page:m,frontmatter:{...re(Ee()),adminTags:n.adminTags.peek()},body:e.body,source:l,pages:Xe,onCloned:i=>{u()&&(y.cloneTarget=`/pages/edit${i}`,R.navigate(y.cloneTarget))}});s(()=>o.close())}});We&&(We.disabled=!h,h||(We.title="Save the page before cloning it")),Z(r,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{et=po(Ze)}}),s(Pt(Et,{position:"bottom"})),Domma.icons.scan();for(const e of G)yt(e,{quiet:!0}).then(()=>{u()&&q()});let xt=null;try{xt=S.get(ze)}catch{}const $=Ua(xt),ba=$t(y.item);if($){if(je(),H($.state)!==a.base.peek()&&ba&&!Bt($,a.base.peek()))Fe($.state),E.toast("Your unsaved changes to this page are back.",{type:"info"});else if(H($.state)!==a.base.peek()){const e=$.at?` (${ee($.at)})`:"",t=Bt($,a.base.peek())?" The saved page has changed since - restoring puts your version back over it (the saved one stays in History).":"";await E.confirm(`You left unsaved changes to this page${e}. Restore them?${t}`)&&u()&&Fe($.state)}}}};function ho(r,d,p){r.replaceChildren(...String(d).split(`
96
+ `).map((s,c)=>{const v=document.createElement("span");v.className="editor-line-number-row";const f=document.createElement("span");f.className="fold-toggle fold-toggle--empty";const b=document.createElement("span");return b.className="editor-line-num",b.textContent=String(c+1),v.append(f,b),v})),r.scrollTop=p.scrollTop}function yo(r,d,p){if(!r||!d)return;const s=15,c=85,v=y=>Math.min(c,Math.max(s,y));let f=v(parseFloat(ae().splitRatio)||50);const b=y=>{r.style.setProperty("--dm-editor-split",f+"%"),d.setAttribute("aria-valuenow",String(Math.round(f))),y&&ye("splitRatio",f)};b(!1);const l=y=>{const C=r.getBoundingClientRect();C.width&&(f=v((y.clientX-C.left)/C.width*100)),b(!1)},h=()=>{document.removeEventListener("pointermove",l),document.removeEventListener("pointerup",h),r.classList.remove("is-resizing"),d.classList.remove("is-dragging"),b(!0)};p(h),d.addEventListener("pointerdown",y=>{window.matchMedia("(max-width: 768px)").matches||(y.preventDefault(),r.classList.add("is-resizing"),d.classList.add("is-dragging"),document.addEventListener("pointermove",l),document.addEventListener("pointerup",h))}),d.addEventListener("dblclick",()=>{f=50,b(!0)}),d.addEventListener("keydown",y=>{const C=y.shiftKey?10:2;let u=f;if(y.key==="ArrowLeft")u=f-C;else if(y.key==="ArrowRight")u=f+C;else if(y.key==="Home")u=s;else if(y.key==="End")u=c;else if(y.key==="Enter"||y.key===" ")u=50;else return;y.preventDefault(),f=v(u),b(!0)})}async function ko(r){const d=r.selectionStart,p=r.selectionEnd,s=await Aa({title:"Upload or insert an image"});if(!s||!r.isConnected)return;let c=s.split("/").pop().split("?")[0];try{c=decodeURIComponent(c)}catch{}const v=c.replace(/[\\[\]]/g,"\\$&"),f=s.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");r.setSelectionRange(d,p),ka(r,`![${v}](${f})`),r.dispatchEvent(new Event("input",{bubbles:!0}))}function wo(r,d,p){const s=Object.entries(d||{});return r?(r.replaceChildren(...s.map(([c,v])=>new Option(v?.label||c,c))),s.some(([c])=>c===p)?p:s[0]?.[0]||p):p}async function Eo(r,d){if(!r)return;let p=[];try{const l=await g.get("/collections/roles/entries?limit=100");p=Array.isArray(l)?l:l?.entries||[]}catch{}const s=p.map(l=>({name:l.data?.name,label:l.data?.label||l.data?.name,level:l.data?.level??99})).filter(l=>l.name&&l.level!==0).sort((l,h)=>h.level-l.level),c=(l,h)=>Object.assign(document.createElement("option"),{value:l,textContent:h}),v=()=>{typeof d=="string"&&d&&![...r.options].some(l=>l.value===d)&&r.append(c(d,`${d} (custom)`))};if(!s.length)return v();const f=document.createElement("optgroup");f.label="Role and above",s.forEach(l=>f.append(c(l.name,`${l.label} and above`)));const b=document.createElement("optgroup");b.label="Role only",s.forEach(l=>b.append(c(`=${l.name}`,`${l.label} only`))),r.replaceChildren(c("public","Public - everyone"),f,b,c("private","Private - super-admin only")),v()}
@@ -1268,7 +1268,7 @@ do not clash with a built-in shortcode name (a block called `card` or `menu`, fo
1268
1268
  out (`[cta-banner title="" tagline="" … /]`), the cursor in the first one. Right-clicking inside a block's tag - the
1269
1269
  short tag or `[block template="…"]` - lists that block's own parameters to fill in there and then; a parameter the
1270
1270
  block's doc comment writes as `dark | light | gold` is offered as a choice. **Edit parameters…** opens them in a
1271
- form, and **Open the block** goes to the block editor. The parameters come from the template's `{{placeholders}}`;
1271
+ form, and **Edit the block** opens it in its tab (below). The parameters come from the template's `{{placeholders}}`;
1272
1272
  the hints and choices from the "Parameters:" list in the block's opening comment:
1273
1273
 
1274
1274
  ```
@@ -1281,6 +1281,15 @@ the hints and choices from the "Parameters:" list in the block's opening comment
1281
1281
  -->
1282
1282
  ```
1283
1283
 
1284
+ **Editing a block from its page:** every block the page uses has a tab above the page editor - its tag's blocks first,
1285
+ then the footer's (they are on every page). Open one to edit the block's HTML and CSS right there; the preview shows
1286
+ the whole page with the block as you type it, outlined. **Save** saves the page and every block you changed - a block
1287
+ is shared, so if it is also used on other pages, in a view or in the footer, the first save that changes it says so
1288
+ and asks. **Discard** undoes the block changes too. A dot on a tab marks unsaved changes. Any other block opens in a tab too: pick it from the
1289
+ **Blocks** list, the search or a recent one in the bar above the editor. The Blocks editor is still
1290
+ there for what the tabs leave out - new blocks, sample values, and every page a block is on (the tab's open button
1291
+ takes you there with your changes kept).
1292
+
1284
1293
  **Example:**
1285
1294
 
1286
1295
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.21",
3
+ "version": "1.8.23",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",