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 +8 -0
- package/admin/js/lib/editor-hop.js +3 -7
- package/admin/js/lib/page-blocks-arrange.js +1 -0
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/templates/page-editor.html +63 -7
- package/admin/js/views/page-editor.js +10 -8
- package/docs/markdown-shortcodes.md +10 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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>`,
|
|
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
|
-
[/${
|
|
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
|
|
5
|
-
|
|
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="
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {} [] () => ==
|
|
|
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
|
|
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
|
|
78
|
-
`).map(
|
|
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
|
|
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==="<"?"<":"&")+"</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 {} [] () => ==
|
|
|
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
|
-
|
|
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,``),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 **
|
|
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
|
```
|