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