domma-cms 1.8.22 → 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,9 @@
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
+
3
7
  ## [1.8.22] - 2026-10-17
4
8
 
5
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.
@@ -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 A,pathFor as m,pushRecent as D,recentChips as q}from"./editor-hop-arrange.js";const C="editor-hop.recent",f=new Set;let T=!1;function _(){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 k(){try{const e=S.get(C);return Array.isArray(e)?e:[]}catch{return[]}}export function noteRecent(e){const t=D(k(),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,7 +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>`,U=`
16
+ </div>`,X=`
17
17
  <button type="button" class="dm-hop-chip is-{{kind}}" data-path="{{path}}" title="{{title}}">
18
18
  <span class="dm-hop-kind">{{kindText}}</span><span class="dm-hop-name">{{label}}</span><span class="dm-hop-dot" aria-label="{{dotLabel}}">{{dot}}</span>
19
- </button>`,g=e=>e==="page"?"Page":"Block";function W(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 X(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 c=document.createElement("span");c.className="dm-hop-name",c.textContent=e.label;const a=document.createElement("small");if(a.className="dm-hop-meta",a.textContent=e.meta,t.append(r,c,a),hasDraft(e)){const o=document.createElement("span");o.className="dm-hop-dot",o.textContent="\u2022",o.title="Unsaved changes kept",t.appendChild(o)}return t}export function mountEditorHop(e,{current:t,pages:r=[],blocks:c=[]}){_(),e.innerHTML=z,e.querySelectorAll("[data-each]").forEach(n=>{n.innerHTML=U});const a=t?.id?noteRecent(t):k(),o=P(r,c),d=new Set(o.map(l)),h={recent:M.observable(q(a.filter(n=>d.has(l(n))),t,hasDraft).map(W))},w=M.applyBindings(h,e),b=e.querySelector(".dm-hop-input"),N=E.autocomplete?E.autocomplete(b,{data:o,minChars:0,maxResults:0,debounce:60,highlightMatches:!1,clearOnSelect:!0,emptyMessage:"No page or block matches",filterFn:F,onFilter:(n,s)=>A(n,s,{recent:a,current:t}),renderItem:n=>X(n),onSelect:n=>{b.blur(),R.navigate(m(n))}}):null,O=[...e.querySelectorAll(".dm-hop-pick")];for(const n of O){const s=n.dataset.kind,p=o.filter(i=>i.kind===s);n.append(new Option(`${s==="page"?"Pages":"Blocks"} (${p.length})\u2026`,""));for(const i of p){const u=t?.id&&l(i)===l(t),x=new Option(`${!u&&hasDraft(i)?"\u2022 ":""}${s==="page"?`${i.label} (${i.id})`:i.label}${u?" - editing":""}`,m(i));x.disabled=!!u,n.append(x)}n.hidden=p.length===0}const v=n=>{const s=n.target.closest(".dm-hop-pick");if(!s?.value)return;const p=s.value;s.value="",R.navigate(p)};e.addEventListener("change",v);const y=n=>{const s=n.target.closest(".dm-hop-chip");s?.dataset.path&&R.navigate(s.dataset.path)};return e.addEventListener("click",y),I.scan(e),{dispose(){e.removeEventListener("click",y),e.removeEventListener("change",v);try{N?.destroy?.()}catch{}w.dispose()}}}export function lastPage(){return k().find(e=>e.kind==="page")?.id||""}const L=2e5;export function heldBlockEdits(e,t={}){const r=a=>t instanceof Map?t.get(a):t[a],c={};for(const a of e){let o=null;try{o=S.get($(a))}catch{}const d=o?.form;if(!d||typeof d.content!="string"||H(o,r(a)))continue;const h=typeof d.css=="string"?d.css:"";d.content.length>L||h.length>L||(c[a]={content:d.content,css:h})}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}
@@ -1 +1 @@
1
- export function blockTabs(e,t=[],s=null){const n=s?s instanceof Set?s:new Set(s):null,i=[],o=new Set;for(const r of Array.isArray(e)?e:[])typeof r!="string"||o.has(r)||(o.add(r),i.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),i.push({name:r,footer:!0}));return i}export function otherUses(e,t=""){return!e||typeof e!="object"?null:{pages:(Array.isArray(e.pages)?e.pages:[]).filter(s=>s!==t),views:Array.isArray(e.views)?e.views.filter(Boolean):[],footer:e.footer===!0}}const a=(e,t,s=`${t}s`)=>`${e} ${e===1?t:s}`;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 saveQuestion(e,t={},s=new Set){const n=[];for(const i of e){if(s.has(i))continue;if(t[i]==null){n.push(`Where else "${i}" is used could not be read.`);continue}const o=describeUses(t[i]);o&&n.push(`"${i}" 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=[],s=2e5){const n={...e};for(const i of t){if(!i.dirty){delete n[i.name];continue}i.content.length>s||i.css.length>s||(n[i.name]={content:i.content,css:i.css})}return n}export function savedMessage({page:e=!1,css:t=!1,blocks:s=[]}={}){const n=[];if(e&&n.push("Page"),t&&n.push("CSS"),s.length===1?n.push(`block "${s[0]}"`):s.length&&n.push(`${s.length} blocks`),!n.length)return"Nothing to save.";const i=n.length===1?n[0]:`${n.slice(0,-1).join(", ")} and ${n.at(-1)}`;return`${i[0].toUpperCase()}${i.slice(1)} saved.`}export function outlineBlock(e,t){const s=String(e??"");if(!t||!/^[a-z0-9][a-z0-9-]*$/.test(t))return s;const n=`<style>[data-block="${t}"]{outline:2px dashed var(--dm-primary,#4f6bed);outline-offset:6px}</style>`;return/<\/head>/i.test(s)?s.replace(/<\/head>/i,`${n}</head>`):n+s}
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,15 +1,16 @@
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 - going to one
5
- of those keeps this page's changes without asking. The blocks this page uses are edited HERE: a tab
6
- each above the editor (.pe-files), their HTML and CSS in .pe-block-pane, saved with the page.
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.
7
8
  Bound with M.applyBindings (see the view): every field is a data-model, and what shows - unsaved,
8
9
  the problem note, the view mode, the CSS pane, Live Preview, History - is read from the state. The
9
10
  router renders this file through _.render, which eats any {{…}}: bindings only, never mustache. -->
10
11
  <div id="page-hop" class="dm-hop-slot"></div>
11
12
  <div class="pex-bar">
12
- <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). To open another page or block, use the lists above, the search, or a recent one; your unsaved changes here are kept while you go. 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>
13
14
  <span class="pex-chip" hidden data-bind-hidden="!dirty.value" title="Changes not saved yet">Unsaved</span>
14
15
  <small class="pex-problem" hidden data-bind-hidden="!(dirty.value && problem.value)" data-bind-text="problem.value"></small>
15
16
  <small class="pex-held" hidden data-bind-hidden="!heldNote.value" data-bind-text="heldNote.value"
@@ -48,7 +49,7 @@
48
49
  <span class="pe-files-list" data-each="blockTabs.value key=name">
49
50
  <button type="button" class="pe-file is-block" role="tab" data-bind-data-name="name" data-bind-title="title"
50
51
  data-bind-class="active.value && 'active'" data-bind-aria-selected="active.value">
51
- <span class="pe-file-kind" data-bind-text="footer ? 'Footer' : 'Block'"></span><span class="pe-file-name" data-bind-text="name"></span><span class="pe-file-dot" hidden data-bind-hidden="!dirty.value">•</span>
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>
52
53
  </button>
53
54
  </span>
54
55
  </div>
@@ -1,4 +1,4 @@
1
- import{api as g}from"../api.js";import{CardBuilder as ba}from"../lib/card-builder.js";import{TimelineBuilder as ma}from"../lib/timeline-builder.js";import{attachEditorKeybindings as va,createToolbar as ga,insertAtCursor as ha}from"../lib/markdown-toolbar.js";import{EffectsBuilder as ya}from"../lib/effects-builder.js";import{attachContextMenu as ka}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as wa}from"../lib/shortcode-insert.js";import{populateThemeSelect as Ea}from"../lib/themes.js";import{createFoldingManager as Ma}from"../lib/editor-folding.js";import{openScribeComposer as Sa}from"../lib/scribe-composer.js";import{openCrudTutorial as Ta}from"../lib/crud-tutorial.js";import{addBannerAction as Z}from"../lib/plugin-chrome.js";import{helpPopovers as xt}from"../lib/help-popover.js";import{onceClosed as Ke,openMenuAt as Pt,sidebarCount as Ca}from"../lib/tool-kit.js";import{pickMedia as xa}from"../lib/media-picker.js";import{openPageClone as Pa}from"../lib/page-clone.js";import{openEditorReference as At}from"../lib/page-editor-reference.js";import{cleanType as me,DEFAULT_TYPE as ve,EDITOR_FONTS as Aa,fontHref as La,isDefaultType as Ra,LINE_HEIGHTS as Da,SIZES as Ba,typeVars as Lt}from"../lib/editor-type.js";import{getProjectFromHash as Oa}from"../lib/project-context.js";import{attachSmoothCaret as Rt,getCaretPreference as Na,setCaretPreference as $a}from"../lib/smooth-caret.js";import{SCREEN as Ia,bannerSection as Fa,draftIsStale as Dt,draftKey as za,initialProject as ja,makeDraft as Va,problems as _e,readDraft as qa,snapshot as q}from"../lib/page-editor-arrange.js";import{fmtDateTime as ee}from"../lib/dates.js";import{heldBlockEdits as Ha,hold as Bt,mountEditorHop as Ua,noteRecent as Ka,takeHeld as Ot}from"../lib/editor-hop.js";import{isHop as _a}from"../lib/editor-hop-arrange.js";import{blockTagDefs as Wa,blocksInBody as Ja}from"../lib/block-tags.js";import{draftKey as te,draftOffer as Ya,formatHtml as Ga,formFromBlock as Xa,payloadFromForm as Qa,snapshot as We}from"../lib/block-editor-arrange.js";import{blockTabs as Za,otherUses as Nt,outlineBlock as eo,previewBlocks as to,saveQuestion as ao,savedMessage as oo,usesLine as so}from"../lib/page-blocks-arrange.js";import{createSimpleEditor as $t}from"../lib/simple-editor.js";function ge(r){const d=document.createElement("template");return d.innerHTML=r.trim(),d.content.firstElementChild}const no=ee,It=r=>ee(r.createdAt)+(r.label?` - ${r.label}`:""),ro=`
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 g}from"../api.js";import{CardBuilder as ba}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 Ft(r){const d=La(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 lo=`
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 io(r){const d=E.slideover({title:"Editor settings",size:"sm",position:"right"}),p=ge(`<div>${lo}</div>`),o=me(r.peek()),c={font:M.observable(o.font),size:M.observable(String(o.size)),line:M.observable(String(o.line)),wrap:M.observable(o.wrap),caret:M.observable(Na())},m=M.computed(()=>me({font:c.font.value,size:c.size.value,line:c.line.value,wrap:c.wrap.value}));c.isDefault=M.computed(()=>Ra(m.value)&&c.caret.value==="auto");const f=M.effect(()=>$a(c.caret.value)),b=p.querySelector("#pe-set-font");for(const[u,I]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const Y=Aa.filter(v=>v.kind===u),H=I?Object.assign(document.createElement("optgroup"),{label:I}):b.querySelector("optgroup:last-of-type");H.append(...Y.map(v=>new Option(v.label,v.id))),I&&b.appendChild(H)}p.querySelector("#pe-set-size").append(...Ba.map(u=>new Option(`${u}px`,String(u)))),p.querySelector("#pe-set-line").append(...Da.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=m.value;Ft(u.font);for(const[I,Y]of Object.entries(Lt(u)))l.style.setProperty(I,Y);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,he("type",u))});c.reset=()=>{c.font.value=ve.font,c.size.value=String(ve.size),c.line.value=String(ve.line),c.wrap.value=ve.wrap,c.caret.value="auto"},c.done=()=>d.close(),d.element.appendChild(p);const y=M.applyBindings(c,p),C=xt(p);return Ke(d,()=>{y.dispose(),C(),h?.(),f?.()}),Domma.icons?.scan?.(p),d.open(),d}async function co(r){const d=E.slideover({title:"Share preview link",size:"md",position:"right"}),p=ge(`<div>${ro}</div>`),o={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);o.links.value=f.filter(b=>!b.revokedAt&&!b.expired).map(b=>({id:b.id,title:b.label||"Untitled link",meta:`Expires ${no(b.expiresAt)} - ${b.useCount===1?"1 view":`${b.useCount||0} views`}`})),o.listError.value=""}catch(f){o.listError.value="Could not load existing links: "+(f.message||"unknown error")}finally{o.loaded.value=!0}};o.create=async()=>{o.creating.value=!0;try{const f=await g.pages.previewLinks.create(r,o.expiry.peek(),o.note.peek().trim());o.url.value=f.url,o.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{o.creating.value=!1}},o.copy=async()=>{try{await navigator.clipboard.writeText(o.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"})}},o.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 m=M.applyBindings(o,p);Ke(d,()=>m.dispose()),d.element.appendChild(p),d.open(),await c()}function po(r){return new Promise(d=>{const p=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),o=ge(`
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 c=!1;const m={label:M.observable(""),saving:M.observable(!1)};m.save=async()=>{m.saving.value=!0;try{await g.versions.create(r,m.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{m.saving.value=!1}},m.key=b=>{b.key==="Enter"&&(b.preventDefault(),m.save())};const f=M.applyBindings(m,o);Ke(p,()=>{f.dispose(),d(c)}),p.element.appendChild(o),p.open(),setTimeout(()=>o.querySelector("input")?.focus(),100)})}let T=null,zt=!1;function uo(){zt||!window.R?.use||(zt=!0,R.use((r,d,p)=>{const o=d?.path||"",c=r?.path||"",m=Ia.test(o)&&c!==o;return m&&T?.path===o&&T.cloneTarget===c?(T.cloneTarget=null,T.keepDraft(),T.hold(),p()):!m||!T||T.path!==o||!T.dirty()?p():_a(o,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,"","#"+o)})}),M.subscribe("router:afterChange",({to:r}={})=>{if(T&&(r?.path||"")!==T.path){const d=T;T=null,d.dispose()}}))}function fo(r){return r.split(`
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
78
  `).map(d=>d.trimEnd()).join(`
79
79
  `).replace(/\n{3,}/g,`
80
80
 
81
- `)}const jt="editor_prefs";function ae(){return S.get(jt)||{viewMode:"split",fullscreen:!1}}function he(r,d){const p=ae();p[r]=d,S.set(jt,p)}const bo=["edit","details","seo","dconfig","live","history"],Vt=["split","write","preview"],mo=r=>r.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function qt(r,d,p=()=>{}){let o=0;try{o=r.contentWindow.scrollY||0}catch{}r.addEventListener("load",()=>{try{r.contentWindow.scrollTo(0,o)}catch{}p()},{once:!0}),r.srcdoc=d}export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(r){if(uo(),T){const e=T;T=null,e.dispose()}const d=window.location.hash.replace(/^#/,""),p=[],o=e=>(p.push(e),e);let c=()=>!1,m=()=>{},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:()=>m(),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"}),[Y,H,v,Je,Ye,Ht,Ut,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&&!v){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}const k=e=>C.querySelector(e);if(Ea(k("#field-theme"),{includeDefault:!0}),await ko(k("#field-visibility"),v?.visibility),!u())return;const Kt=yo(k("#field-layout"),Y,v?.layout||"default");for(const e of Ye)k("#field-project").append(new Option(e.name||e.slug,e.slug));const ye=Wa(oe),se=ae(),n={urlPath:M.observable(l||""),title:M.observable(v?.title||""),description:M.observable(v?.description||""),layout:M.observable(Kt),status:M.observable(v?.status||"draft"),sortOrder:M.observable(String(v?.sortOrder??99)),showInNav:M.observable(!!v?.showInNav),sidebar:M.observable(!!v?.sidebar),breadcrumbs:M.observable(v?v.breadcrumbs!==!1:!0),bundled:M.observable(!!v?.bundled),tags:M.observable(Array.isArray(v?.tags)?v.tags:[]),adminTags:M.observable(Array.isArray(v?.adminTags)?v.adminTags:[]),category:M.observable(v?.category||""),visibility:M.observable(typeof v?.visibility=="string"&&v.visibility?v.visibility:"public"),project:M.observable(ja({isEdit:h,page:v,projects:Ye,fromHash:Oa()||"",urlPath:l||""})),theme:M.observable(v?.theme||""),seoTitle:M.observable(v?.seo?.title||""),seoDesc:M.observable(v?.seo?.description||""),dconfig:M.observable(v?.dconfig?JSON.stringify(v.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(Vt.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"),U=k("#css-editor");x.value=v?.content||"";const _t=Rt(x),Wt=Rt(U);o(()=>_t.destroy()),o(()=>Wt.destroy());const F=document.createElement("div");F.className="editor-line-numbers editor-line-numbers--foldable",x.parentElement.insertBefore(F,x);const B=Ma(x,F,{onFoldChange:()=>{}});let K=!1;B.setGuard(e=>{K=e});const _=()=>B.getUnfoldedContent(),Ge=M.observable(me(se.type));o(M.effect(()=>{const e=Ge.value,t=k("#editor-body");if(t){Ft(e.font);for(const[s,i]of Object.entries(Lt(e)))t.style.setProperty(s,i);t.classList.toggle("pe-nowrap",!e.wrap),x.wrap=e.wrap?"soft":"off",U.wrap=e.wrap?"soft":"off"}}));let Xe=null;o(()=>{try{Xe?.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:_(),css:n.cssOpened.value?n.css.value:null}),P=new Map,ne=e=>We(e.form)!==e.base,W=()=>[...P.entries()].filter(([,e])=>ne(e)).map(([e])=>e),Jt=M.computed(()=>q(z())!==a.base.value),Yt=M.computed(()=>(a.blockRev.value,q(z())!==a.base.value||W().length>0)),Gt=M.computed(()=>_e({urlPath:n.urlPath.value,dconfig:n.dconfig.value})[0]?.text||""),Xt=M.computed(()=>a.versions.value.some(e=>e.checked.value)),Qt=M.computed(()=>a.versions.value.length>0&&a.versions.value.every(e=>e.checked.value)),Zt=M.computed(()=>{const e=a.versions.value.filter(t=>t.checked.value).length;return e>0&&e<a.versions.value.length}),ke=()=>q(z())!==a.base.peek();c=()=>ke()||W().length>0;let Qe=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())===Qe?{}:{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()}:{}}),we=()=>{const e=n.dconfig.peek().trim();if(!e)return null;try{return JSON.parse(e)}catch{return null}},Ze=()=>n.urlPath.peek().trim()||l||"/";let Ee=[];const et=Array.isArray(H?.footer?.blocks)?H.footer.blocks.filter(e=>typeof e=="string"):[],Me=Array.isArray(oe)?oe:[],tt=new Map(Me.map(e=>[e.name,e.updatedAt])),at=new Map(Me.map(e=>[e.name,e.usage]));let G=new Set;const Se=()=>{const e=[...new Set([...Ee,...et,...P.keys()])],t=Ha(e.filter(i=>!P.has(i)),tt);G=new Set(Object.keys(t));const s=G.size?`Preview shows unsaved changes to ${[...G].join(", ")}`:"";return s!==a.heldNote.peek()&&(a.heldNote.value=s),to(t,[...P.entries()].map(([i,w])=>({name:i,content:w.form.content,css:w.form.css,dirty:ne(w)})))};let Te="";const ea=()=>{st();const e=Te;if(e){Te="";try{X.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},X=k("#markdown-preview");let ot="";g.settings.getCustomCss().then(({css:e})=>{ot=e||""}).catch(()=>{});const st=()=>{const e=X?.contentDocument;if(!e?.head)return;const t=n.cssOpened.peek()?n.css.peek():"";if(!t)return;let s=e.getElementById("preview-custom-css");s||(s=e.createElement("style"),s.id="preview-custom-css",e.head.appendChild(s)),s.textContent=t};let Ce=null;const O=()=>{clearTimeout(Ce),Ce=setTimeout(async()=>{if(!(!X||a.viewMode.peek()==="write"))try{const{html:e}=await g.pages.previewFull(Ze(),re(we()),_(),Se());u()&&qt(X,eo(mo(e),a.blockTab.peek()),ea)}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)},nt=k("#live-preview-frame");let xe=!0,Pe=null,Ae=!1;const Le=async()=>{if(!(Ae||!nt)){Ae=!0,a.liveStatus.value="Rendering\u2026";try{const{html:e}=await g.pages.previewFull(Ze(),re(we()),_(),Se());if(!u())return;qt(nt,e),xe=!1,a.liveStatus.value="Showing unsaved changes"}catch(e){a.liveStatus.value="Preview failed - "+(e?.message||"unknown error")}finally{Ae=!1}}},Q=()=>{xe=!0,a.tab.peek()==="live"&&(clearTimeout(Pe),Pe=setTimeout(Le,600))};o(()=>{clearTimeout(Ce),clearTimeout(Pe)});const Re=ga(r.find("#markdown-editor"),r.find("#editor-toolbar"),{spacerDefault:H?.layoutOptions?.spacerSize??40});ka(x,{blocks:()=>ye,openBlock:e=>yt(e)});const le=C.querySelector(".editor-toolbar-right");le.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 ta=ge(`
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,7 +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>`);le.prepend(ta),le.before(k(".pe-block-bar"));const rt=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),lt=k("#field-tags");let ie=null;ie=lt?E.pillbox(lt,{data:rt(Ht),value:n.tags.peek(),creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{ie&&(n.tags.value=[...ie.getValue()])}}):null;const it=k("#field-admin-tags");let ce=null;ce=it?E.pillbox(it,{data:rt(Ut),value:n.adminTags.peek(),creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{ce&&(n.adminTags.value=[...ce.getValue()])}}):null;let De=!1,Be=null;const de=async()=>{De=!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}},Oe=e=>a.versions.peek().find(t=>t.filename===e),Ne=async e=>{try{const t=await g.versions.get(l,e.filename);Be=e.filename,a.compare.value={label:It(e),old:t.content,current:_()}}catch(t){E.toast("Failed to load version: "+t.message,{type:"error"})}},ct=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"})}},aa=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 de()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},dt=()=>{a.bodyRev.value=a.bodyRev.peek()+1},pt=()=>{n.cssOpened.peek()||(n.css.value=ot,n.cssOpened.value=!0,va(U))},$e=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:[],ie?.setValue(n.tags.peek()),Array.isArray(e.adminTags)&&(n.adminTags.value=e.adminTags),ce?.setValue(n.adminTags.peek()),B.unfoldAll(),K=!0,x.value=e.body==null?"":String(e.body),K=!1,B.refresh(),e.css!=null&&(pt(),n.css.value=String(e.css)),dt()};a.base.value=q(z());const Ie=za(l||""),pe=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}};m=()=>{try{ke()&&S.set(Ie,Va(z(),a.base.peek()));const e=new Date().toISOString();for(const t of W()){const s=P.get(t);S.set(te(t),{form:{...s.form},base:s.base,at:e})}}catch{}},f=()=>{Bt(y.item);for(const e of W())Bt({kind:"block",id:e})};const Fe=()=>pe(Ie),ut=e=>{c()&&(m(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ut),o(()=>window.removeEventListener("beforeunload",ut));const ft=new Set,oa=async e=>{const t=[];for(const s of e){const i=P.get(s);if(!i)continue;const w={...i.form};try{const A=Qa(w,i.block?.meta);await g.blocks.put(s,A),i.block={...i.block,meta:A.meta},i.saved=w,i.base=We(w),tt.set(s,new Date().toISOString()),u()&&pe(te(s)),t.push(s)}catch(A){E.toast(`Block "${s}" not saved: ${A.message||"Unknown error"}`,{type:"error"})}}return t.forEach(s=>ft.add(s)),t.length&&(a.blockRev.value=a.blockRev.peek()+1,Ca("#/blocks"),u()&&V()),t};let ze=!1;const bt=async()=>{if(a.saving.peek()||ze)return;ze=!0;let e,t,s;try{if(await ht(),!u())return;e=z(),t=W(),s=!h||ke()||!t.length;const D=s?_e(e):[];if(D.length){E.toast(D[0].text,{type:"warning"}),C.querySelectorAll("#editor-meta-tabs .tab-item")[bo.indexOf(D[0].field==="dconfig"?"dconfig":"details")]?.click();return}const L=ao(t,Object.fromEntries(t.map(be=>[be,Nt(at.get(be),l||"")])),ft);if(L&&(!await E.confirm(L)||!u()))return}finally{ze=!1}const i=(e.dconfig||"").trim(),w=re(i?JSON.parse(i):null),A=s&&n.cssOpened.peek();a.saving.value=!0;try{if(s&&h){const L={frontmatter:w,body:e.body};e.urlPath!==l&&(L.newUrlPath=e.urlPath),await g.pages.update(l,L)}else s&&await g.pages.create({urlPath:e.urlPath,frontmatter:w,body:e.body});s&&u()&&(Qe=JSON.stringify(e.adminTags),a.base.value=q(e)),A&&await g.settings.saveCustomCss(n.css.peek());const D=await oa(t);if((s||D.length)&&E.toast(oo({page:s,css:A,blocks:D}),{type:"success"}),!s||!u())return;Fe(),h&&Ka({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 J=new Map,je=new Map,ue=()=>{a.blockRev.value=a.blockRev.peek()+1},sa=new Set(Me.map(e=>e.name)),na=(e,t)=>{const s=`${t?"footer":"body"}:${e}`;return je.has(s)||je.set(s,{id:s,name:e,footer:t,title:t?`${e} - in the footer, on every page`:`${e} - a block on this page`,active:M.observable(!1),dirty:M.observable(!1)}),je.get(s)},mt=()=>{for(const e of a.blockTabs.peek()){const t=P.get(e.name),s=e.name===a.blockTab.peek(),i=t?ne(t):G.has(e.name);e.active.peek()!==s&&(e.active.value=s),e.dirty.peek()!==i&&(e.dirty.value=i)}},V=()=>{const e=Za(Ee,et,sa),t=[...W(),a.blockTab.peek()].filter(i=>i&&!e.some(w=>w.name===i));for(const i of new Set(t))e.push({name:i,footer:!1});const s=e.map(i=>na(i.name,i.footer));s.map(i=>i.id).join("|")!==a.blockTabs.peek().map(i=>i.id).join("|")&&(a.blockTabs.value=s),mt()},vt=()=>{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)},gt=async(e,{quiet:t=!1}={})=>{for(;J.has(e);)if(await J.get(e),P.has(e)||t)return P.get(e)||null;if(P.has(e))return P.get(e);const s=(async()=>{try{const i=await g.blocks.get(e);if(!u())return null;const w=Xa({...i,name:e}),A={block:i,saved:w,base:We(w),form:{...w}};let D=null;try{D=S.get(te(e))}catch{}const L=Ya(D,A.base);if(t&&L?.stale)return null;if(Ot({kind:"block",id:e}),L){const be=L.at?` (${ee(L.at)})`:"",fa=!L.stale||await E.confirm(`You left unsaved changes to "${e}"${be}, but the block has been saved since. Restore your version anyway?`);if(!u())return null;fa&&(A.form={...A.form,...L.form,name:e},L.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return D&&pe(te(e)),P.set(e,A),ue(),A}catch(i){return u()&&a.blockTab.peek()===e&&(a.blockNote.value=`Could not open "${e}": ${i.message||"unknown error"}`),null}finally{J.delete(e)}})();return J.set(e,s),s},ht=async()=>{for(;J.size;)await Promise.all([...J.values()])},yt=async e=>{if(!e){if(!a.blockTab.peek())return;a.blockTab.value="",V(),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,a.blockUses.value=so(Nt(at.get(e),l||"")),V();let t=P.get(e);t||(a.blockReady.value=!1,a.blockNote.value=`Opening ${e}\u2026`,t=await gt(e)),!(!t||!u()||a.blockTab.peek()!==e)&&(vt(),V(),Te=e,O(),(a.blockPart.peek()==="css"?j:N)?.focus())};let Ve=null;o(()=>clearTimeout(Ve));const kt=(e,t)=>{const s=P.get(a.blockTab.peek());!s||!a.blockReady.peek()||(s.form[e]=t,clearTimeout(Ve),Ve=setTimeout(ue,120),O(),Q())},wt=C.querySelector(".dm-plugin-body")||C,ra={...n,...a,dirty:Yt,pageDirty:Jt,problem:Gt,anySelected:Xt,allSelected:Qt,someSelected:Zt,isEdit:h,savedPath:l||""},la={save:()=>bt(),discard:async()=>{if(await ht(),!(!u()||!c()||!await E.confirm("Discard the changes you have not saved?"))){$e(JSON.parse(a.base.peek())),Fe();for(const[e,t]of P)ne(t)&&(t.form={...t.saved},pe(te(e)));vt(),ue(),V(),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=Ga(e.form.content),N.setValue(e.form.content),ue(),O(),Q())},openBlockEditor:()=>{const e=a.blockTab.peek();e&&R.navigate(`/blocks/edit/${encodeURIComponent(e)}`)},showTab:e=>{a.tab.value=e,e==="live"&&xe&&Le(),e==="history"&&h&&!De&&de()},setMode:e=>{a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.value=e,he("viewMode",e),e!=="write"&&O()},toggleFullscreen:()=>{a.fullscreen.value=!a.fullscreen.peek(),he("fullscreen",a.fullscreen.peek())},toggleCss:()=>{a.cssMode.peek()?(a.cssMode.value=!1,a.viewMode.value=Vt.includes(ae().viewMode)?ae().viewMode:"split",B.refresh()):(pt(),a.cssMode.value=!0,a.viewMode.value="split")},formatMarkdown:()=>{const e=x.selectionStart,t=fo(_());B.unfoldAll(),K=!0,x.value=t,K=!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:()=>Le(),share:async()=>{if(!h){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await co(l)},toggleAll:e=>{const t=!!e?.target?.checked;a.versions.peek().forEach(s=>{s.checked.value=t})},deleteSelected:async()=>{const e=a.versions.peek().filter(s=>s.checked.peek()).map(s=>s.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 de()}catch(s){E.toast("Delete failed: "+s.message,{type:"error"})}},saveNamed:async()=>{await po(l)&&u()&&(a.tab.peek()==="history"?await de():De=!1)},closeCompare:()=>{a.compare.value=null,Be=null},restoreCompared:()=>ct(Be)};le.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const ia=M.applyBindings(ra,wt,{methods:la});o(()=>ia.dispose());const ca=Ua(k("#page-hop"),{current:{kind:"page",id:l||"",label:v?.title||l||""},pages:Je,blocks:oe});o(()=>ca.dispose()),N=$t(k('.pe-block-edit[data-part="html"]'),{initialValue:"",language:"html",autoIndent:!0,onChange:e=>kt("content",e)}),j=$t(k('.pe-block-edit[data-part="css"]'),{initialValue:"",language:"css",autoIndent:!0,onChange:e=>kt("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.
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
94
  color: var(--dm-primary);
95
- h3 { font-size: 1.25rem; } */`,o(()=>{N.destroy(),j.destroy()});const Et=k(".pe-files"),Mt=e=>{const t=e.target.closest(".pe-file");t&&yt(t.dataset.name||"")};Et.addEventListener("click",Mt),o(()=>Et.removeEventListener("click",Mt)),E.tabs(k("#editor-meta-tabs"));let qe=null;o(()=>clearTimeout(qe)),x.addEventListener("input",()=>{B.refresh(),F.scrollTop=x.scrollTop,!K&&(clearTimeout(qe),qe=setTimeout(dt,120),O(),Q())}),x.addEventListener("scroll",()=>{F.scrollTop=x.scrollTop}),U.addEventListener("scroll",()=>{F.scrollTop=U.scrollTop});let St=!0;o(M.effect(()=>{for(const e of Object.keys(n))e!=="css"&&e!=="cssOpened"&&n[e].value;if(a.bodyRev.value,St){St=!1,O();return}O(),Q()}));const da=ye.map(e=>e.name),pa=ye.filter(e=>e.shortTag).map(e=>e.name);o(M.effect(()=>{a.bodyRev.value,Ee=Ja(_(),da,pa),Se(),V()})),o(M.effect(()=>{a.blockRev.value,a.blockTab.value,mt()})),o(M.effect(()=>{F.hidden=!!a.blockTab.value}));let He=null;if(o(()=>clearTimeout(He)),o(M.effect(()=>{const e=n.css.value;a.cssMode.value&&(vo(F,e,U),clearTimeout(He),He=setTimeout(st,400))})),h){const e=k(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:s=>{const i=Oe(s.dataset.file);return i?[{header:It(i)},{label:"Compare with the editor",icon:"columns",action:()=>Ne(i)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>ct(i.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>aa(i)}]:[]}});o(()=>t?.destroy?.()),e.addEventListener("click",s=>{const i=s.target.closest("tr[data-file]");if(!i||s.target.matches("input"))return;const w=s.target.closest(".pe-kebab");if(w){Pt(w);return}const A=Oe(i.dataset.file);A&&Ne(A)}),e.addEventListener("keydown",s=>{const i=s.target.closest("tr[data-file]");if(i){if(s.key==="F10"&&s.shiftKey)s.preventDefault(),Pt(i);else if(s.key==="Enter"&&s.target===i){const w=Oe(i.dataset.file);w&&Ne(w)}}})}go(k("#editor-body"),k("#editor-divider"),o),wa({image:e=>(a.cssMode.value=!1,ho(e)),scribe:e=>Sa(e),card:e=>ba.open(e,{onMediaBrowse:()=>g.media.list().catch(()=>[])}),timeline:e=>ma.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)})}),Re.onEffects(e=>ya.open(e)),Re.onHelp(()=>At()),Re.onTutorial(e=>Ta(e));const Tt=e=>{(e.ctrlKey||e.metaKey)&&!e.shiftKey&&!e.altKey&&e.key.toLowerCase()==="s"&&u()&&(e.preventDefault(),bt())};document.addEventListener("keydown",Tt),o(()=>document.removeEventListener("keydown",Tt));const fe=C.querySelector(".dm-plugin-banner-name");o(M.effect(()=>{const e=Fa(h,n.title.value,l);if(!u()||(document.title=`${e} - Domma CMS`,!fe))return;const t=fe.lastChild;t&&t.nodeType===3&&fe.querySelector(".dm-plugin-banner-sep")?t.textContent=e:fe.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:()=>At()}),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 Ue=Z(r,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:()=>{if(!h||a.saving.peek()||!u())return;const e=z(),t=_e({...e,urlPath:l});if(t.length){E.toast(t[0].text,{type:"warning"});return}const s=Pa({page:v,frontmatter:{...re(we()),adminTags:n.adminTags.peek()},body:e.body,source:l,pages:Je,onCloned:i=>{u()&&(y.cloneTarget=`/pages/edit${i}`,R.navigate(y.cloneTarget))}});o(()=>s.close())}});Ue&&(Ue.disabled=!h,h||(Ue.title="Save the page before cloning it")),Z(r,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Xe=io(Ge)}}),o(xt(wt,{position:"bottom"})),Domma.icons.scan();for(const e of G)gt(e,{quiet:!0}).then(()=>{u()&&V()});let Ct=null;try{Ct=S.get(Ie)}catch{}const $=qa(Ct),ua=Ot(y.item);if($){if(Fe(),q($.state)!==a.base.peek()&&ua&&!Dt($,a.base.peek()))$e($.state),E.toast("Your unsaved changes to this page are back.",{type:"info"});else if(q($.state)!==a.base.peek()){const e=$.at?` (${ee($.at)})`:"",t=Dt($,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()&&$e($.state)}}}};function vo(r,d,p){r.replaceChildren(...String(d).split(`
96
- `).map((o,c)=>{const m=document.createElement("span");m.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),m.append(f,b),m})),r.scrollTop=p.scrollTop}function go(r,d,p){if(!r||!d)return;const o=15,c=85,m=y=>Math.min(c,Math.max(o,y));let f=m(parseFloat(ae().splitRatio)||50);const b=y=>{r.style.setProperty("--dm-editor-split",f+"%"),d.setAttribute("aria-valuenow",String(Math.round(f))),y&&he("splitRatio",f)};b(!1);const l=y=>{const C=r.getBoundingClientRect();C.width&&(f=m((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=o;else if(y.key==="End")u=c;else if(y.key==="Enter"||y.key===" ")u=50;else return;y.preventDefault(),f=m(u),b(!0)})}async function ho(r){const d=r.selectionStart,p=r.selectionEnd,o=await xa({title:"Upload or insert an image"});if(!o||!r.isConnected)return;let c=o.split("/").pop().split("?")[0];try{c=decodeURIComponent(c)}catch{}const m=c.replace(/[\\[\]]/g,"\\$&"),f=o.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");r.setSelectionRange(d,p),ha(r,`![${m}](${f})`),r.dispatchEvent(new Event("input",{bubbles:!0}))}function yo(r,d,p){const o=Object.entries(d||{});return r?(r.replaceChildren(...o.map(([c,m])=>new Option(m?.label||c,c))),o.some(([c])=>c===p)?p:o[0]?.[0]||p):p}async function ko(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 o=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}),m=()=>{typeof d=="string"&&d&&![...r.options].some(l=>l.value===d)&&r.append(c(d,`${d} (custom)`))};if(!o.length)return m();const f=document.createElement("optgroup");f.label="Role and above",o.forEach(l=>f.append(c(l.name,`${l.label} and above`)));const b=document.createElement("optgroup");b.label="Role only",o.forEach(l=>b.append(c(`=${l.name}`,`${l.label} only`))),r.replaceChildren(c("public","Public - everyone"),f,b,c("private","Private - super-admin only")),m()}
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()}
@@ -1285,7 +1285,8 @@ the hints and choices from the "Parameters:" list in the block's opening comment
1285
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
1286
  the whole page with the block as you type it, outlined. **Save** saves the page and every block you changed - a block
1287
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. The Blocks editor is still
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
1289
1290
  there for what the tabs leave out - new blocks, sample values, and every page a block is on (the tab's open button
1290
1291
  takes you there with your changes kept).
1291
1292
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.22",
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",