domma-cms 1.8.40 → 1.8.42
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 +11 -0
- package/admin/js/lib/block-editor-arrange.js +3 -4
- package/admin/js/lib/code-format.js +42 -0
- package/admin/js/lib/markdown-toolbar.js +8 -9
- package/admin/js/lib/page-editor-reference.js +2 -2
- package/admin/js/lib/plain-editor.js +3 -6
- package/admin/js/lib/syntax-highlight.js +1 -1
- package/admin/js/templates/page-editor.html +5 -3
- package/admin/js/views/page-editor.js +33 -15
- package/docs/DommaFeatures.md +1 -0
- package/docs/configuration.md +39 -0
- package/package.json +1 -1
- package/server/server.js +3 -2
- package/server/services/embedCsp.js +19 -0
- package/server/services/markdown.js +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.42] - 2026-10-23
|
|
4
|
+
|
|
5
|
+
- **Format with Shift+Alt+F in the editor workspace.** The page body is tidied: trailing spaces off, runs of blank lines down to one, pipe tables lined up, and the lines holding only a container short tag (`[grid]`, `[col]`, `[card]`, `[tabs]`...) indented by how deeply they nest - the content between them stays at the margin, and the renderer drops a tag line's indent before parsing (`unindentShortTags()` in `markdown.js`), so the indent never reaches the page. Block and component HTML and JS are re-indented (never rewritten), CSS is laid out one declaration a line, JSON one value a line. One Ctrl+Z undoes it; the toolbar's format buttons do the same (`admin/js/lib/code-format.js`).
|
|
6
|
+
- **Auto-indent.** Enter keeps the line's indent, goes one deeper after `{ ( [`, an opening tag or `{{#if}}`, and splits a pair around the caret; in the body it carries lists and quotes on. Tab and Shift+Tab move every selected line; a closer typed on an empty line steps back.
|
|
7
|
+
- **Typing and formatting settings** (Editor settings, the cog): spaces or tabs, indent size, keep a file's own indentation, auto-indent, carry lists on, indent short tags, line up tables, and format on save (off by default).
|
|
8
|
+
- **HTML in the page body is coloured** - tag names, attributes and values, as in the block editor.
|
|
9
|
+
|
|
10
|
+
## [1.8.41] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
- **A page can show images from another site.** `csp.imgHosts` in `config/server.json` adds hosts to the Content-Security-Policy's `img-src` (`imageSources()` in `server/services/embedCsp.js`, beside `connectSources()`; bare hostnames, https only). The policy allowed only the site's own images, so an `<img>` from anywhere else - a "Featured on" badge, say - was blocked.
|
|
13
|
+
|
|
3
14
|
## [1.8.40] - 2026-10-22
|
|
4
15
|
|
|
5
16
|
- **Every collection entry is in the page for robots.** An interactive collection or view now puts every entry in the page HTML for robots that run no JavaScript, also when it loads its pages from the server, up to 1,000 (was 200, and only the first page in server mode). A collection past 1,000 shows as a warning in the SEO audit.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{fmtDate as v}from"./dates.js";export const NAME_RE=/^[a-z0-9][a-z0-9-]*$/,MAX_CSS=50*1024,FORM_FIELDS=["name","project","bundled","content","css"];export function formFromBlock(e,{project:r=""}={}){return e?{name:String(e.name||""),project:String(e.meta?.project||""),bundled:e.bundled===!0,content:String(e.content??""),css:String(e.css??"")}:{name:"",project:String(r||""),bundled:!1,content:"",css:""}}export function snapshot(e){const r=e||{};return JSON.stringify(FORM_FIELDS.map(n=>n==="bundled"?r[n]===!0:String(r[n]??"")))}export function checkForm(e,{isNew:r,taken:n=[]}={}){const t=String(e?.name||"").trim(),o={};r&&(t?NAME_RE.test(t)?n.includes(t)&&(o.name=`"${t}" is already a block - open it from the list, or choose another name.`):o.name="Lowercase letters, digits and hyphens, starting with a letter or digit.":o.name="A block needs a name.");const
|
|
1
|
+
import{fmtDate as v}from"./dates.js";export const NAME_RE=/^[a-z0-9][a-z0-9-]*$/,MAX_CSS=50*1024,FORM_FIELDS=["name","project","bundled","content","css"];export function formFromBlock(e,{project:r=""}={}){return e?{name:String(e.name||""),project:String(e.meta?.project||""),bundled:e.bundled===!0,content:String(e.content??""),css:String(e.css??"")}:{name:"",project:String(r||""),bundled:!1,content:"",css:""}}export function snapshot(e){const r=e||{};return JSON.stringify(FORM_FIELDS.map(n=>n==="bundled"?r[n]===!0:String(r[n]??"")))}export function checkForm(e,{isNew:r,taken:n=[]}={}){const t=String(e?.name||"").trim(),o={};r&&(t?NAME_RE.test(t)?n.includes(t)&&(o.name=`"${t}" is already a block - open it from the list, or choose another name.`):o.name="Lowercase letters, digits and hyphens, starting with a letter or digit.":o.name="A block needs a name.");const a=String(e?.css??"");return a.length>MAX_CSS&&(o.css=`The CSS is ${Math.ceil(a.length/1024)} KB - a block's CSS is limited to 50 KB.`),{errors:o,name:t}}export function payloadFromForm(e,r={}){const{adminTags:n,...t}=r||{};return{content:String(e?.content??""),bundled:e?.bundled===!0,css:String(e?.css??""),meta:{...t,project:String(e?.project||"")||null}}}export function extractPlaceholders(e){const r=new Set;for(const[,n]of y(e).matchAll(/\{\{([\w_]+)(?::[^{}]+)?\}\}/g))r.add(n);return[...r]}export function rawPlaceholders(e){return new Set([...y(e).matchAll(/\{\{\{(\w+)\}\}\}/g)].map(r=>r[1]))}const y=e=>String(e??"").replace(/<!--[\s\S]*?-->/g,""),u=e=>String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export{u as escapeHtml};export function formatDate(e,r){if(e==null||e==="")return null;const n=e instanceof Date?e:new Date(e);if(Number.isNaN(n.getTime()))return null;const t=(c,l=2)=>String(c).padStart(l,"0"),o=["January","February","March","April","May","June","July","August","September","October","November","December"],a=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],s=()=>(n.getHours()+11)%12+1,i={YYYY:()=>n.getFullYear(),YY:()=>t(n.getFullYear()%100),MMMM:()=>o[n.getMonth()],MMM:()=>o[n.getMonth()].slice(0,3),MM:()=>t(n.getMonth()+1),M:()=>n.getMonth()+1,DD:()=>t(n.getDate()),D:()=>n.getDate(),dddd:()=>a[n.getDay()],ddd:()=>a[n.getDay()].slice(0,3),HH:()=>t(n.getHours()),H:()=>n.getHours(),hh:()=>t(s()),h:()=>s(),mm:()=>t(n.getMinutes()),m:()=>n.getMinutes(),ss:()=>t(n.getSeconds()),s:()=>n.getSeconds(),A:()=>n.getHours()<12?"AM":"PM",a:()=>n.getHours()<12?"am":"pm"};return String(r).replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,c=>String(i[c]()))}const b={icon:"box","facebook-icon":"facebook","instagram-icon":"instagram"};export const SAMPLE_IMAGE="data:image/svg+xml,"+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500" viewBox="0 0 800 500"><rect width="800" height="500" fill="#94a3b8"/><path d="M0 500 L260 230 L420 390 L540 290 L800 500 Z" fill="#64748b"/><circle cx="610" cy="140" r="52" fill="#cbd5e1"/></svg>');const M=/(image|img|photo|picture|avatar|logo|thumb|thumbnail|cover|banner)(url|src)?$/;export function sampleValue(e,r=new Date,{raw:n=!1}={}){const t=String(e).toLowerCase();return b[t]?b[t]:/(class|style)$/.test(t)?"":t==="_id"?"a1b2c3d4-e5f6-7890-abcd-ef1234567890":t==="_createdat"||t==="_updatedat"?r.toISOString():t.replace(/update(?!d)/g,"").includes("date")||/(published|created|updated)at$/.test(t)?v(r):M.test(t)?n?t.includes("avatar")?`<img src="${SAMPLE_IMAGE}" alt="" width="40" height="40">`:`<img src="${SAMPLE_IMAGE}" alt="">`:SAMPLE_IMAGE:/(url|href|link)$/.test(t)?"#":t.includes("email")?"user@example.com":t.includes("phone")?"+44 7700 900000":t.includes("name")?"Jane Smith":t.includes("title")?"Sample Title":["message","content","description","summary","excerpt"].some(o=>t.includes(o))?"This is a sample value for preview purposes.":t.includes("rating")?"excellent":t.includes("status")?"active":t.includes("priority")?"high":t.includes("tag")?"tag1, tag2":t.includes("subject")?"Sample Subject":t.includes("categor")?n?'<a href="#">General</a>':"general":`Sample ${e}`}export const scopeName=e=>String(e||"").trim()||"preview",PREVIEW_REPEATS=[1,3];export function renderPreview(e,r={},{repeat:n=1,sanitize:t=null}={}){if(typeof t!="function")return"";const o=d=>r[d]??`[${d}]`,a=String(e?.content??"").replace(/<!--[\s\S]*?-->/g,"").replace(/\{\{\{(\w+)\}\}\}/g,(d,p)=>String(o(p))).replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(d,p,m)=>{const f=o(p);if(m){const h=formatDate(f,m);if(h!==null)return u(h)}return u(f)}),s=u(scopeName(e?.name)),i=String(e?.css??""),c=i.trim()?`<style>[data-block="${s}"] { ${i.replace(/<\/style/gi,"<\\/style")} }</style>`:"",l=PREVIEW_REPEATS.includes(Number(n))?Number(n):1,g=String(t(a)??"");if(l===1)return`${c}<div class="dm-static-block" data-block="${s}">${g}</div>`;const S=`<div data-block="${s}">${g}</div>`;return`${c}<div class="dm-collection-display dm-collection-blocks grid grid-cols-1 grid-cols-md-2 grid-cols-lg-3 gap-4">`+Array.from({length:l},()=>S).join("")+"</div>"}export function previewBridge(e,r){const n=JSON.stringify(Number(e)||0),t=JSON.stringify(String(r)).replace(/</g,"\\u003c");return`<script>(function () {
|
|
2
2
|
var P = window.parent, TICK = ${n}, HOST = ${t};
|
|
3
3
|
function host() { return document.getElementById(HOST); }
|
|
4
4
|
function up(msg) { msg.source = 'dm-block-preview'; msg.tick = TICK; P.postMessage(msg, '*'); }
|
|
@@ -33,8 +33,7 @@ function ready() {
|
|
|
33
33
|
up({type: 'ready', host: !!host()});
|
|
34
34
|
}
|
|
35
35
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ready); else ready();
|
|
36
|
-
})();<\/script>`}export function previewShell(e,r,n){const t=`<style>body.dm-cloaked{opacity:1 !important;transition:none !important}#${n}{padding:1.5rem 0}.dm-breadcrumbs{display:none !important}body.dm-vnav-left,body.dm-vnav-right{padding-left:0 !important;padding-right:0 !important}</style>`;let o=String(e??"");o=/<\/head>/i.test(o)?o.replace(/<\/head>/i,`${t}</head>`):t+o;const
|
|
37
|
-
`)}export const draftKey=e=>`block-editor.draft.${e||"new"}`;export function draftOffer(e,r){return!e||typeof e!="object"||!e.form||snapshot(e.form)===r?null:{form:e.form,stale:!!e.base&&e.base!==r,at:e.at||""}}export const shortcodeFor=e=>`[collection slug="\u2026" display="block" block="${e}" /]`;const x=new Set(["urlPath","slug","content","html","cacheTags","perViewer","hasExpiry","usedComponents","updatedAt"]);export function pageFrontmatter(e){return Object.fromEntries(Object.entries(e||{}).filter(([r])=>!x.has(r)))}export function previewPageOptions(e,r,n=""){const t=new Map((Array.isArray(r)?r:[]).map(a=>[a.urlPath,a])),o=Array.isArray(e?.pages)?e.pages.filter(a=>t.has(a)):[],i=e?.footer?[...new Set([n,"/",...o].filter(a=>a&&t.has(a)))]:o;return(n&&i.includes(n)?[n,...i.filter(a=>a!==n)]:i).map(a=>({value:a,label:`${t.get(a)?.title||"Untitled"} (${a})`}))}export function pageInFrame(e,r,n=0){const t="<style>body.dm-cloaked{opacity:1 !important;transition:none !important}"+(/^[a-z0-9-]+$/.test(r)?`[data-block="${r}"]{outline:2px dashed var(--dm-primary,#4f6bed);outline-offset:6px}`:"")+"</style>",i=`<script>(function () {
|
|
36
|
+
})();<\/script>`}export function previewShell(e,r,n){const t=`<style>body.dm-cloaked{opacity:1 !important;transition:none !important}#${n}{padding:1.5rem 0}.dm-breadcrumbs{display:none !important}body.dm-vnav-left,body.dm-vnav-right{padding-left:0 !important;padding-right:0 !important}</style>`;let o=String(e??"");o=/<\/head>/i.test(o)?o.replace(/<\/head>/i,`${t}</head>`):t+o;const a=previewBridge(r,n),s=o.toLowerCase().lastIndexOf("</body>");return s>=0?o.slice(0,s)+a+o.slice(s):o+a}export function previewMessage(e,r,n){if(!e||!r||e.source!==r)return null;const t=e.data;return!t||t.source!=="dm-block-preview"||t.tick!==n?null:["ready","save"].includes(t.type)?t:null}export const draftKey=e=>`block-editor.draft.${e||"new"}`;export function draftOffer(e,r){return!e||typeof e!="object"||!e.form||snapshot(e.form)===r?null:{form:e.form,stale:!!e.base&&e.base!==r,at:e.at||""}}export const shortcodeFor=e=>`[collection slug="\u2026" display="block" block="${e}" /]`;const w=new Set(["urlPath","slug","content","html","cacheTags","perViewer","hasExpiry","usedComponents","updatedAt"]);export function pageFrontmatter(e){return Object.fromEntries(Object.entries(e||{}).filter(([r])=>!w.has(r)))}export function previewPageOptions(e,r,n=""){const t=new Map((Array.isArray(r)?r:[]).map(i=>[i.urlPath,i])),o=Array.isArray(e?.pages)?e.pages.filter(i=>t.has(i)):[],a=e?.footer?[...new Set([n,"/",...o].filter(i=>i&&t.has(i)))]:o;return(n&&a.includes(n)?[n,...a.filter(i=>i!==n)]:a).map(i=>({value:i,label:`${t.get(i)?.title||"Untitled"} (${i})`}))}export function pageInFrame(e,r,n=0){const t="<style>body.dm-cloaked{opacity:1 !important;transition:none !important}"+(/^[a-z0-9-]+$/.test(r)?`[data-block="${r}"]{outline:2px dashed var(--dm-primary,#4f6bed);outline-offset:6px}`:"")+"</style>",a=`<script>(function () {
|
|
38
37
|
var P = window.parent, TICK = ${JSON.stringify(Number(n)||0)};
|
|
39
38
|
document.addEventListener('click', function (e) {
|
|
40
39
|
if (e.target.closest && e.target.closest('a[href], button, [type="submit"]')) e.preventDefault();
|
|
@@ -43,4 +42,4 @@ document.addEventListener('submit', function (e) { e.preventDefault(); }, true);
|
|
|
43
42
|
document.addEventListener('keydown', function (e) {
|
|
44
43
|
if ((e.ctrlKey || e.metaKey) && (e.key === 's' || e.key === 'S')) { e.preventDefault(); P.postMessage({source: 'dm-block-preview', tick: TICK, type: 'save'}, '*'); }
|
|
45
44
|
}, true);
|
|
46
|
-
})();<\/script>`;let s=String(e??"");s=/<\/head>/i.test(s)?s.replace(/<\/head>/i,`${t}</head>`):t+s;const
|
|
45
|
+
})();<\/script>`;let s=String(e??"");s=/<\/head>/i.test(s)?s.replace(/<\/head>/i,`${t}</head>`):t+s;const i=s.toLowerCase().lastIndexOf("</body>");return i>=0?s.slice(0,i)+a+s.slice(i):s+a}export function draftOlderThan(e,r){const n=Date.parse(e?.at||""),t=Date.parse(r||"");return Number.isFinite(n)&&Number.isFinite(t)&&t>n}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
let E=" ";export const DEFAULT_CODE_PREFS=Object.freeze({indent:"spaces",size:2,detect:!0,autoIndent:!0,continueLists:!0,shortTags:!0,tables:!0,formatOnSave:!1}),INDENT_SIZES=Object.freeze([2,4,8]);export function cleanCodePrefs(t={}){const e=DEFAULT_CODE_PREFS,n=s=>typeof t?.[s]=="boolean"?t[s]:e[s];return{indent:t?.indent==="tabs"?"tabs":"spaces",size:INDENT_SIZES.includes(Number(t?.size))?Number(t.size):e.size,detect:n("detect"),autoIndent:n("autoIndent"),continueLists:n("continueLists"),shortTags:n("shortTags"),tables:n("tables"),formatOnSave:n("formatOnSave")}}let S=cleanCodePrefs();export function setCodePrefs(t){S=cleanCodePrefs(t)}export const codePrefs=()=>S,prefUnit=()=>S.indent==="tabs"?" ":" ".repeat(S.size),unitFor=t=>S.detect?detectIndent(t,prefUnit()):prefUnit();export function detectIndent(t,e=" "){const n=String(t).split(`
|
|
2
|
+
`).filter(l=>l.trim()),s=n.filter(l=>l.startsWith(" ")).length,i=n.filter(l=>l.startsWith(" ")).length;if(s>i)return" ";const r=n.map(l=>l.match(/^ */)[0].length).filter(l=>l>0);return r.length?r.filter(l=>l%4===2).length>r.length/5?" ":e===" ".repeat(8)&&r.every(l=>l%8===0)?e:" ":e}export function isFormatKey(t){return!t||!t.altKey||!t.shiftKey||t.ctrlKey||t.metaKey?!1:t.code==="KeyF"||String(t.key||"").toLowerCase()==="f"}export function formatCode(t,e){const n=String(t??"").replace(/\r\n?/g,`
|
|
3
|
+
`);E=e==="md"?prefUnit():unitFor(n);try{switch(e){case"json":return{text:formatJson(n)};case"css":return{text:formatCss(n)};case"js":return{text:formatJs(n)};case"html":return{text:formatHtml(n)};case"md":return{text:formatMarkdown(n)};default:return{error:"Nothing to format here."}}}catch(s){return{error:s.message||"Could not format this."}}}export function formatJson(t){if(!t.trim())return t;try{JSON.parse(t)}catch(i){throw new Error(`Not valid JSON: ${i.message}`)}let e="",n=0;const s=()=>`
|
|
4
|
+
`+E.repeat(n);for(let i=0;i<t.length;i++){const r=t[i];if(r==='"'){let o=i+1;for(;t[o]!=='"';)o+=t[o]==="\\"?2:1;e+=t.slice(i,o+1),i=o}else if(r==="{"||r==="["){const o=r==="{"?"}":"]",l=t.slice(i+1).search(/\S/);if(t[i+1+l]===o){e+=r+o,i+=l+1;continue}n++,e+=r+s()}else r==="}"||r==="]"?(n--,e+=s()+r):r===","?e+=","+s():r===":"?e+=": ":/\s/.test(r)||(e+=r)}return e+(t.endsWith(`
|
|
5
|
+
`)?`
|
|
6
|
+
`:"")}export function formatCss(t){if(!t.trim())return t;if(/\{\{\{?\s*[#/^\w]/.test(t))throw new Error("This CSS has {{placeholders}} in it - left as it is.");const e=[];let n=0,s="",i=!1,r="",o=!1,l=0;const c=()=>E.repeat(n),a=f=>{i&&e.length&&e[e.length-1]!==""&&!e[e.length-1].endsWith("{")&&e.push(""),i=!1,e.push(f)},h=()=>{o&&n===0?(i=!0,o=!1):(r.match(/\n/g)||[]).length>=2&&(i=!0),r=""},d=f=>{const m=f.match(/^(--[\w-]+|[-\w]+)\s*:\s*([\s\S]*)$/);return!m||f.startsWith("@")?f:`${m[1]}: ${m[2]}`},u=f=>{const m=s.trim();if(s="",!!m){if(f==="{"){const g=m.startsWith("@")?[m]:M(m,",");g.forEach((p,x)=>a(c()+p.trim()+(x<g.length-1?",":" {")));return}a(c()+d(m)+";")}};for(let f=0;f<t.length;f++){const m=t[f];if(m==="/"&&t[f+1]==="*"){const g=t.indexOf("*/",f+2),p=g<0?t.length:g+2,x=t.slice(f,p);if(s.trim())s+=x;else{h();const w=x.split(`
|
|
7
|
+
`);a(c()+w[0]),e.push(...w.slice(1))}f=p-1;continue}if(m==='"'||m==="'"){s.trim()||h();let g=f+1;for(;g<t.length&&t[g]!==m&&t[g]!==`
|
|
8
|
+
`;)g+=t[g]==="\\"?2:1;s+=t.slice(f,g+1),f=g;continue}if(/\s/.test(m)){s.trim()?s.endsWith(" ")||(s+=" "):r+=m;continue}if(s.trim()||h(),m==="("?l++:m===")"&&(l=Math.max(0,l-1)),l===0&&m==="{"){u("{"),n++,r="";continue}if(l===0&&m===";"){u(";");continue}if(l===0&&m==="}"){if(u(";"),n--,n<0)throw new Error("There is a } with no { before it.");i=!1,e.push(c()+"}"),r="",n===0&&(o=!0);continue}s+=m}if(s.trim()&&u(";"),n!==0)throw new Error(`${n} { left without a closing }.`);return e.join(`
|
|
9
|
+
`)+`
|
|
10
|
+
`}function M(t,e){const n=[];let s=0,i=null,r="";for(const o of t){if(i)o===i&&(i=null);else if(o==='"'||o==="'")i=o;else if("([{".includes(o))s++;else if(")]}".includes(o))s--;else if(o===e&&s===0){n.push(r),r="";continue}r+=o}return n.push(r),n}const j=/(^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|do|else|in|of|new|delete|void|throw|yield|await))\s*$/;function A(t,e,n){let s=0;if(e.str){let i=0;for(;i<t.length&&t[i]!==e.str;)i+=t[i]==="\\"?2:1;if(i>=t.length){t.endsWith("\\")||(e.str=null);return}e.str=null,s=i+1}for(;s<t.length;){const i=e.stack[e.stack.length-1];if(e.comment){const o=t.indexOf("*/",s);if(o<0)return;e.comment=!1,s=o+2;continue}if(i==="`"){const o=t[s];if(o==="\\"){s+=2;continue}if(o==="`"){e.stack.pop(),s++;continue}if(o==="$"&&t[s+1]==="{"){e.stack.push("${"),s+=2;continue}s++;continue}const r=t[s];if(r==="/"&&t[s+1]==="/")return;if(r==="/"&&t[s+1]==="*"){e.comment=!0,s+=2;continue}if(r==='"'||r==="'"){let o=s+1;for(;o<t.length&&t[o]!==r;)o+=t[o]==="\\"?2:1;if(o>=t.length&&t.endsWith("\\")){e.str=r;return}s=o+1;continue}if(r==="`"){e.stack.push("`"),s++;continue}if(r==="/"&&j.test(t.slice(0,s))){let o=s+1,l=!1;for(;o<t.length;){const c=t[o];if(c==="\\"){o+=2;continue}if(c==="[")l=!0;else if(c==="]")l=!1;else if(c==="/"&&!l)break;o++}s=o+1;continue}if(r==="}"&&i==="${"){e.stack.pop(),s++;continue}r==="{"&&e.stack.length?e.stack.push("{"):r==="}"&&i==="{"&&e.stack.pop(),"{[(".includes(r)?n(r,s,!0):"}])".includes(r)&&n(r,s,!1),s++}}function b(t){return t.replace(/(["'`])(?:\\.|(?!\1).)*\1/g,'""').replace(/\/\*.*?(\*\/|$)/g,"").replace(/\/\/.*$/,"").trimEnd()}export function formatJs(t,e=0){const n=t.split(`
|
|
11
|
+
`),s={comment:!1,str:null,stack:[]},i=[],r=[];let o="";const l=()=>{const c=i[i.length-1];return c?c.at+1+(c.sw&&c.inCase?1:0):0};if(n.forEach((c,a)=>{const h=s.stack[s.stack.length-1]==="`"||!!s.str,d=s.comment,u=c.trim();let f=c;if(h)f=c;else if(!u)f="";else if(d)f=E.repeat(e+l())+(u.startsWith("*")?" ":"")+u;else{const g=Math.min(i.length,(u.match(/^[}\])]+/)||[""])[0].length),p=i[i.length-1],x=/^(case\b.*|default\s*):/.test(u);let w;g?w=i[i.length-g].at:p?.sw&&x?w=p.at+1:w=l();const $=b(u),W=/^(\.(?!\.\.)|\?\.|[+\-*/%]|&&|\|\||\?\?|\?(?!\.)|:(?!:))/.test($)&&!x||/(=>|[^=!<>]=|[+\-*/%]|&&|\|\||\?\?|\?)$/.test(o)&&!g&&!/^[{[(]/.test($),T=$.startsWith("/")&&j.test(o);W&&!T&&!/^[}\])]/.test(u)&&w++,f=E.repeat(e+w)+u}r.push(f);const m=Math.round(f.match(/^[ \t]*/)[0].length/E.length)-e;if(h||d||u){const g=i[i.length-1];A(c,s,(p,x,w)=>{if(w){const $=p==="{"&&/\bswitch\s*\(.*\)\s*$/.test(b(c.slice(0,x)));i.push({at:h||d?l():m,sw:$,inCase:!1})}else i.pop()}),g?.sw&&!h&&!d&&/^(case\b.*|default\s*):/.test(u)&&(g.inCase=!0),!h&&!d&&(o=b(u))}}),s.comment||s.str||s.stack.some(c=>c!=="{"))throw new Error("A comment, string or template is left open.");return r.join(`
|
|
12
|
+
`)}const O=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),R=new Set(["pre","textarea","script","style"]);export function formatHtml(t){const e=t.split(`
|
|
13
|
+
`),n=[],s=[];let i="text",r=null,o=null;const l=a=>{const h=a.lines.join(`
|
|
14
|
+
`);let d=null;try{const u=(a.open.match(/\btype\s*=\s*["']?([^"'\s>]*)/i)||[])[1]||"";a.name==="script"&&(!u||/module|javascript|ecmascript/i.test(u))?d=formatJs(h,a.level+1):a.name==="style"&&(d=N(formatCss(z(h)).replace(/\n$/,""),a.level+1))}catch{d=null}s.push(...(d??h).split(`
|
|
15
|
+
`))},c=(a,h,d)=>{for(;h<a.length;){if(i==="comment"){const u=a.indexOf("-->",h);if(u<0)return;i="text",h=u+3;continue}if(i==="tag"){const u=a[h];if(r.text+=u,h++,r.q){u===r.q&&(r.q=null);continue}if(u==='"'||u==="'"){r.q=u;continue}if(u!==">")continue;i="text";const f=r;if(r=null,O.has(f.name)||/\/\s*>$/.test(f.text))continue;if(R.has(f.name)){const m=new RegExp(`</${f.name}\\s*>`,"i").exec(a.slice(h));if(m){h+=m.index+m[0].length;continue}o={name:f.name,level:f.level,open:f.text,lines:[]},i="raw";return}n.push(f.name);continue}if(a.startsWith("<!--",h)){i="comment",h+=4;continue}if(a.startsWith("{{",h)){const u=a.indexOf("}}",h+2);if(u<0)return;const f=a.slice(h+2,u).trim();/^#\w+/.test(f)?n.push("#"+f.match(/^#(\w+)/)[1]):/^\/\w+/.test(f)&&v(n,"#"+f.slice(1).trim()),h=u+2;continue}if(a[h]==="<"&&a[h+1]==="/"){const u=/^<\/([a-zA-Z][\w:-]*)\s*>/.exec(a.slice(h));if(u){v(n,u[1].toLowerCase()),h+=u[0].length;continue}}if(a[h]==="<"&&/[a-zA-Z]/.test(a[h+1]||"")){const u=/^<([a-zA-Z][\w:-]*)/.exec(a.slice(h))[1];i="tag",r={name:u.toLowerCase(),level:d,text:"<"+u,q:null},h+=1+u.length;continue}h++}};for(const a of e){const h=a.trim();if(i==="raw"){const f=new RegExp(`</${o.name}\\s*>`,"i").exec(a);if(!f){o.lines.push(a);continue}const m=o;if(o=null,i="text",m.name==="pre"||m.name==="textarea")s.push(...m.lines,a);else{const g=a.slice(0,f.index);g.trim()&&m.lines.push(g),m.lines.some(p=>p.trim())?l(m):s.push(...m.lines),s.push(E.repeat(m.level)+a.slice(f.index).trim())}c(a,f.index+f[0].length,m.level);continue}if(i==="comment"||i==="tag"&&r.q){i==="tag"&&(r.text+=`
|
|
16
|
+
`),s.push(a),c(a,0,r?.level??0);continue}if(!h){s.push("");continue}let d;if(i==="tag")d=/^\/?>/.test(h)?r.level:r.level+1,r.text+=`
|
|
17
|
+
`;else{let f=0;const m=/(?:<\/[a-zA-Z][\w:-]*\s*>|\{\{\s*\/\w+\s*\}\}|\{\{\s*else\b[^}]*\}\})\s*/y;for(let g=0;;){m.lastIndex=g;const p=m.exec(h);if(!p||(f++,/^\{\{\s*else/.test(p[0])))break;g=m.lastIndex}d=Math.max(0,n.length-f)}const u=E.repeat(d)+h;s.push(u),c(u,d*E.length,d)}return o&&s.push(...o.lines),s.join(`
|
|
18
|
+
`)}function v(t,e){const n=t.lastIndexOf(e);n>=0&&(t.length=n)}function z(t){const e=t.split(`
|
|
19
|
+
`),n=e.filter(i=>i.trim()).map(i=>i.match(/^[ \t]*/)[0].length),s=n.length?Math.min(...n):0;return e.map(i=>i.slice(Math.min(s,i.match(/^[ \t]*/)[0].length))).join(`
|
|
20
|
+
`).replace(/^\n+|\n+$/g,"")}function N(t,e){return t.split(`
|
|
21
|
+
`).map(n=>n&&E.repeat(e)+n).join(`
|
|
22
|
+
`)}export const SHORT_TAG_CONTAINERS=new Set(["grid","row","col","card","box","header","footer","tabs","tab","accordion","item","carousel","slide","timeline","event","hero","banner","slideover","center","listgroup","table","dconfig","reveal","animate","pulse","shake","scramble","ripple","breathe","ambient","twinkle","fireworks","scribe","wait","render","redraw"]);export function shortTagLine(t){const e=String(t).trim().match(/^\[(\/?)([a-z][\w-]*)(?:\s[^\]]*)?\]$/i);return!e||!SHORT_TAG_CONTAINERS.has(e[2].toLowerCase())||!e[1]&&/\/\]$/.test(t.trim())?null:{name:e[2].toLowerCase(),close:!!e[1]}}function _(t){const e=[...t],n=new Set;let s=null,i=!1;t.forEach((l,c)=>{if(s){n.add(c);const h=l.match(/^ {0,3}(`{3,}|~{3,})\s*$/);h&&h[1][0]===s[0]&&h[1].length>=s.length&&(s=null);return}if(i||/^\s*<pre[\s>]/i.test(l)){n.add(c),i=!/<\/pre\s*>/i.test(l);return}const a=l.match(/^ {0,3}(`{3,}|~{3,})/);a&&(s=a[1],n.add(c))});const r=(l,c)=>{let a=0;const h=new RegExp(`\\[(\\/?)${l}(?:[\\s\\]])`,"gi");for(let d=c;d<t.length;d++)if(!n.has(d))for(const u of t[d].matchAll(h))if(!u[1])a++;else{if(a===0)return!0;a--}return!1},o=[];return t.forEach((l,c)=>{if(n.has(c))return;const a=shortTagLine(l);if(a)if(a.close){const h=o.lastIndexOf(a.name);if(h<0)return;o.length=h,e[c]=E.repeat(o.length)+l.trim()}else r(a.name,c+1)&&(e[c]=E.repeat(o.length)+l.trim(),o.push(a.name))}),e}export function formatMarkdown(t){if(!t.trim())return t;const e=t.split(`
|
|
23
|
+
`),n=[];let s=null,i=!1;const r=l=>/^( {4}|\t)/.test(l),o=l=>{for(let c=l+1;c<e.length;c++)if(e[c].trim())return e[c];return""};for(let l=0;l<e.length;l++){const c=e[l];if(s){n.push(c);const f=c.match(/^ {0,3}(`{3,}|~{3,})\s*$/);f&&f[1][0]===s[0]&&f[1].length>=s.length&&(s=null);continue}if(i||/^\s*<pre[\s>]/i.test(c)){n.push(c),i=!/<\/pre\s*>/i.test(c);continue}const a=c.match(/^ {0,3}(`{3,}|~{3,})/);if(a){s=a[1],n.push(c.trimEnd());continue}if(S.tables&&q(e,l)){let f=l+2;for(;f<e.length&&/\|/.test(e[f])&&e[f].trim();)f++;n.push(...B(e.slice(l,f))),l=f-1;continue}if(!c.trim()){const f=n.length?n[n.length-1]:"",m=[...n].reverse().find(g=>g.trim())||"";if(!f.trim()&&!(r(m)&&r(o(l))))continue;n.push("");continue}if(r(c)){n.push(c);continue}const h=c.match(/[ \t]*$/)[0],d=c.slice(0,c.length-h.length),u=/ {2,}$/.test(h)&&(e[l+1]||"").trim()&&!/^\s*#/.test(d);n.push(u?d+" ":d)}for(;n.length&&!n[0].trim();)n.shift();for(;n.length&&!n[n.length-1].trim();)n.pop();return(S.shortTags?_(n):n).join(`
|
|
24
|
+
`)+`
|
|
25
|
+
`}const F=/^\s*:?-+:?\s*$/;function k(t){let e=t.trim();e.startsWith("|")&&(e=e.slice(1)),e.endsWith("|")&&!e.endsWith("\\|")&&(e=e.slice(0,-1));const n=[];let s="";for(let i=0;i<e.length;i++){if(e[i]==="\\"&&e[i+1]==="|"){s+="\\|",i++;continue}if(e[i]==="|"){n.push(s.trim()),s="";continue}s+=e[i]}return n.push(s.trim()),n}function q(t,e){const n=t[e],s=t[e+1];if(!n||!s||!n.includes("|")||!s.includes("|")||/^\s{4,}/.test(n))return!1;const i=k(s);if(!(i.length>0&&i.every(r=>F.test(r))&&k(n).length===i.length))return!1;for(let r=e+2;r<t.length&&t[r].includes("|")&&t[r].trim();r++)if(k(t[r]).length>i.length)return!1;return!0}function B(t){const e=t[0].match(/^\s*/)[0],n=t.map(k),s=Math.max(...n.map(l=>l.length)),i=n[1].map(l=>l.startsWith(":")&&l.endsWith(":")?"c":l.endsWith(":")?"r":l.startsWith(":")?"l":""),r=l=>[...l].length,o=Array.from({length:s},(l,c)=>Math.max(3,...n.map((a,h)=>h===1?3:r(a[c]??""))));return n.map((l,c)=>{const a=Array.from({length:s},(h,d)=>{const u=o[d];if(c===1){const g=i[d]||"",p="-".repeat(u-(g==="c"?2:g?1:0));return g==="c"?`:${p}:`:g==="r"?`${p}:`:g==="l"?`:${p}`:p}const f=l[d]??"",m=u-r(f);return i[d]==="r"?" ".repeat(m)+f:i[d]==="c"?" ".repeat(Math.floor(m/2))+f+" ".repeat(Math.ceil(m/2)):f+" ".repeat(m)});return`${e}| ${a.join(" | ")} |`})}const y=(t,e)=>t.lastIndexOf(`
|
|
26
|
+
`,e-1)+1,C=(t,e)=>{const n=t.indexOf(`
|
|
27
|
+
`,e);return n<0?t.length:n};export function enterEdit(t,e,n,s){const i=y(t,e),r=t.slice(i,C(t,e)),o=t.slice(i,e),l=t.slice(n,C(t,n)),c=(o.match(/^[ \t]*/)||[""])[0],a=(p,x=p.length)=>({start:e,end:n,text:p,selStart:e+x,selEnd:e+x});if(s==="md"||!s){if(shortTagLine(r)&&e===n&&!l.trim()&&!I(t,i))return a(`
|
|
28
|
+
`);if(S.continueLists&&!I(t,i)){const p=o.match(/^([ \t]*)((?:>[ \t]?)+)?([ \t]*)([-*+]|(\d+)([.)]))?([ \t]+)(\[[ xX]\][ \t]+)?/),x=p?.[2]||"",w=p?.[4]||"",$=/^\s*([-*_])(\s*\1){2,}\s*$/.test(r);if(p&&(x||w)&&!$&&p[0].length<=o.length){if(!r.slice(p[0].length).trim()&&e===n&&e>=i+p[0].length)return{start:i,end:C(t,e),text:p[1],selStart:i+p[1].length,selEnd:i+p[1].length};if(e>=i+p[0].length-(p[7]?.length||0)){const T=p[5]?`${Number(p[5])+1}${p[6]}`:w,L=`
|
|
29
|
+
`+p[1]+x+(p[3]||"")+(w?T+(p[7]||" ")+(p[8]?"[ ] ":""):p[7]||"");return a(L)}}}return a(`
|
|
30
|
+
`+(S.autoIndent?c:""))}if(!S.autoIndent)return a(`
|
|
31
|
+
`);const h=(s==="html"?o:b(o)).trimEnd().slice(-1),d=l.trimStart()[0]||"",u={"{":"}","[":"]","(":")"};let f=!!u[h],m=f&&d===u[h];if(s==="html"||s==="css"){const p=o.match(/<([a-zA-Z][\w:-]*)(?:\s[^<>]*)?>\s*$/);p&&!O.has(p[1].toLowerCase())&&!/\/\s*>\s*$/.test(o)&&(f=!0,m=new RegExp(`^\\s*</${p[1]}\\s*>`,"i").test(l)),/\{\{\s*#\w+[^}]*\}\}\s*$/.test(o)&&(f=!0,m=/^\s*\{\{\s*\//.test(l))}if(!f)return a(`
|
|
32
|
+
`+c);const g=`
|
|
33
|
+
`+c+unitFor(t);if(m){const p=(t.slice(n).match(/^[ \t]*/)||[""])[0].length;return{start:e,end:n+p,text:g+`
|
|
34
|
+
`+c,selStart:e+g.length,selEnd:e+g.length}}return a(g)}function I(t,e){let n=null;for(const s of t.slice(0,e).split(`
|
|
35
|
+
`)){const i=s.match(/^\s*(`{3,}|~{3,})/);i&&(n?i[1][0]===n[0]&&i[1].length>=n.length&&!s.trim().slice(i[1].length).trim()&&(n=null):n=i[1])}return!!n}export function indentEdit(t,e,n,s,i=" "){const r=y(t,e),o=n>e&&t[n-1]===`
|
|
36
|
+
`?n-1:n,l=C(t,o);if(!t.slice(e,n).includes(`
|
|
37
|
+
`)&&!(s&&e===n))return null;const c=t.slice(r,l),a=c.split(`
|
|
38
|
+
`),h=a.map(u=>s?u.startsWith(i)?u.slice(i.length):u.startsWith(" ")?u.slice(2):u.startsWith(" ")?u.slice(1):u.replace(/^ /,""):u.trim()?i+u:u),d=h.join(`
|
|
39
|
+
`);if(d===c)return null;if(e===n){const u=a[0].length-h[0].length,f=Math.max(r,e-u);return{start:r,end:l,text:d,selStart:f,selEnd:f}}return{start:r,end:l,text:d,selStart:r,selEnd:r+d.length}}export function closerEdit(t,e,n,s,i=""){if(e!==n)return null;const r=y(t,e);let o=t.slice(r,e),l=s;if(i==="html"&&s==="/"&&/^[ \t]*<$/.test(o))o=o.slice(0,-1),l="</";else if(!"}])".includes(s))return null;if(!S.autoIndent)return null;const c=unitFor(t);if(!o||o.trim()||!o.endsWith(c))return null;const a=o.slice(0,-c.length),h=r+a.length+l.length;return{start:r,end:e,text:a+l,selStart:h,selEnd:h}}export const tabUnit=(t,e)=>e==="md"?prefUnit():unitFor(t);export function changedSpan(t,e){let n=0;const s=Math.min(t.length,e.length);for(;n<s&&t[n]===e[n];)n++;let i=0;for(;i<s-n&&t[t.length-1-i]===e[e.length-1-i];)i++;return{start:n,end:t.length-i,text:e.slice(n,e.length-i)}}export function caretAfterFormat(t,e,n){const s=t.slice(0,n).split(`
|
|
40
|
+
`).length-1,i=t.split(`
|
|
41
|
+
`)[s]??"",r=n-y(t,n),o=Math.max(0,r-i.match(/^[ \t]*/)[0].length),l=e.split(`
|
|
42
|
+
`),c=Math.min(s,l.length-1);let a=0;for(let u=0;u<c;u++)a+=l[u].length+1;const h=l[c],d=h.match(/^[ \t]*/)[0].length;return a+Math.min(h.length,d+o)}export function applyEdit(t,e){const{start:n,end:s,text:i}=e;let r=!1;if(typeof document<"u"&&typeof document.execCommand=="function"){t.focus(),t.setSelectionRange(n,s);try{r=i?document.execCommand("insertText",!1,i):n===s||document.execCommand("delete")}catch{r=!1}}r||(t.value=t.value.slice(0,n)+i+t.value.slice(s),t.dispatchEvent(new Event("input",{bubbles:!0}))),t.setSelectionRange(e.selStart,e.selEnd)}export function formatTextarea(t,e){const n=t.value,s=formatCode(n,e);if(s.error)return s;if(s.text===n)return{text:n,unchanged:!0};const i=caretAfterFormat(n,s.text,t.selectionStart),r=changedSpan(n,s.text),o=t.scrollTop;return applyEdit(t,{...r,selStart:i,selEnd:i}),t.scrollTop=o,s}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import{pageCommentEdit as
|
|
2
|
-
`,
|
|
3
|
-
`,e.selectionStart
|
|
4
|
-
`,
|
|
5
|
-
`,
|
|
6
|
-
`,
|
|
7
|
-
`,r
|
|
8
|
-
`,o);n.substring(o,s===-1?n.length:s).startsWith(t)?(replaceRange(e,o,o+t.length,""),e.setSelectionRange(Math.max(o,r-t.length),Math.max(o,r-t.length))):(replaceRange(e,o,o,t),e.setSelectionRange(r+t.length,r+t.length))}function k(e,t,r,n){const o=document.querySelector(".editor-toolbar-dropdown");if(o){o.remove();return}const s=document.createElement("div");s.className="editor-toolbar-dropdown",r.forEach(function(i){if(i.heading){const d=document.createElement("div");d.className="editor-toolbar-dropdown-heading",d.textContent=i.heading,s.appendChild(d);return}const l=document.createElement("button");if(l.type="button",l.className="editor-toolbar-dropdown-item",i.icon){const d=document.createElement("span");d.setAttribute("data-icon",i.icon),l.appendChild(d)}const c=document.createElement("span");c.textContent=i.label,l.appendChild(c),l.addEventListener("click",function(){s.remove(),document.removeEventListener("click",f,!0),m(i.action,e,t,n)}),s.appendChild(l)});const a=t.getBoundingClientRect(),h=t.closest(".editor-toolbar").getBoundingClientRect();s.style.top=a.bottom-h.top+4+"px";const p=a.left-h.left;s.style.left=Math.min(p,h.width-180-8)+"px",t.closest(".editor-toolbar").appendChild(s),Domma.icons.scan(s);function f(i){!s.contains(i.target)&&i.target!==t&&(s.remove(),document.removeEventListener("click",f,!0))}setTimeout(function(){document.addEventListener("click",f,!0)},0)}function M(){return import("./shortcode-insert.js").then(e=>[{label:"Insert Image",icon:"image-add",action:"insert:image"},...e.insertMenuGroups().map(t=>({...t,items:t.items.filter(r=>r.name!=="image")})).filter(t=>t.items.length).flatMap(t=>[{heading:t.category},...t.items.map(r=>({label:r.label,icon:r.icon,action:`insert:${r.name}`}))])])}function m(e,t,r,n){const{handlers:o}=n;typeof e=="function"?e(t):typeof e=="string"&&e.startsWith("insert:")?g(e.slice(7),t):e==="effects"?o.effects&&o.effects(t):e==="help"?o.help&&o.help(t):e==="tutorial"&&o.tutorial&&o.tutorial(t)}function g(e,t){import("./shortcode-insert.js").then(r=>r.insertShortcodeByName(e,t)).catch(r=>console.error("Insert router failed to load",r))}const v=[{icon:"bold",title:"Bold (Ctrl+B)",action:e=>wrapSelection(e,"**","**")},{icon:"italic",title:"Italic (Ctrl+I)",action:e=>wrapSelection(e,"_","_")},{icon:"strikethrough",title:"Strikethrough",action:e=>wrapSelection(e,"~~","~~")},"|",{type:"dropdown",icon:"heading-1",title:"Headings",items:[{label:"Heading 1",icon:"heading-1",action:e=>insertLine(e,"# ")},{label:"Heading 2",icon:"heading-2",action:e=>insertLine(e,"## ")},{label:"Heading 3",icon:"heading-3",action:e=>insertLine(e,"### ")}]},"|",{icon:"list-bullet",title:"Bullet list",action:e=>insertLine(e,"- ")},{icon:"list-numbered",title:"Numbered list",action:e=>insertLine(e,"1. ")},"|",{type:"dropdown",icon:"quote",title:"Paragraph",items:[{label:"Blockquote",icon:"quote",action:e=>insertLine(e,"> ")},{label:"Horizontal rule",icon:"minus-circle",action:e=>insertAtCursor(e,`
|
|
1
|
+
import{pageCommentEdit as b}from"./page-comments.js";import{applyEdit as m,closerEdit as y,enterEdit as M,formatTextarea as C,indentEdit as w,isFormatKey as S,tabUnit as x}from"./code-format.js";function B(){I.register("bold",{viewBox:"0 0 24 24",path:"M7 5H14a3 3 0 0 1 0 6H7V5zM7 11H15a3 3 0 0 1 0 6H7V11z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("italic",{viewBox:"0 0 24 24",path:"M11 5h4M9 19h4M13 5l-2 14",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("strikethrough",{viewBox:"0 0 24 24",path:"M16 4H9a3 3 0 0 0 0 6h6a3 3 0 0 1 0 6H6M3 12h18",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("quote",{viewBox:"0 0 24 24",path:"M3 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1zM15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("eye",{viewBox:"0 0 24 24",path:"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("expand",{viewBox:"0 0 24 24",path:"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("columns",{viewBox:"0 0 24 24",path:"M3 4h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM12 4v16",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("card",{viewBox:"0 0 24 24",paths:["M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z","M3 9h18"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("help-circle",{viewBox:"0 0 24 24",path:"M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 17h.01M12 13a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("book-open",{viewBox:"0 0 24 24",paths:["M2 4h7a2 2 0 0 1 2 2v14a2 2 0 0 0-2-2H2z","M22 4h-7a2 2 0 0 0-2 2v14a2 2 0 0 1 2-2h7z"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("spacer-insert",{viewBox:"0 0 24 24",paths:["M3 8h18","M3 16h18","M12 8v8"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("icon-pick",{viewBox:"0 0 24 24",paths:["M4 4h6v6H4z","M14 4h6v6h-6z","M4 14h6v6H4z","M14 14h6v6h-6z"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("colour-picker",{viewBox:"0 0 24 24",paths:["M12 2a5 5 0 0 1 5 5c0 3-5 10-5 10S7 10 7 7a5 5 0 0 1 5-5z","M12 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("btn-insert",{viewBox:"0 0 24 24",paths:["M3 7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z","M8 12h8M12 9v6"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("link-shortcode",{viewBox:"0 0 24 24",paths:["M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71","M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("layout-list",{viewBox:"0 0 24 24",paths:["M3 5h18M3 9h18","M3 14h4v6H3zM9 14h12M9 17h8"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("sparkles",{viewBox:"0 0 24 24",paths:["M12 3L13.5 8.5H19L14.5 11.5L16 17L12 14L8 17L9.5 11.5L5 8.5H10.5L12 3Z","M19 3L19.7 5.3H22L20.2 6.6L20.9 9L19 7.7L17.1 9L17.8 6.6L16 5.3H18.3L19 3Z","M5 13L5.5 14.7H7L5.8 15.5L6.3 17.2L5 16.3L3.7 17.2L4.2 15.5L3 14.7H4.5L5 13Z"],stroke:"currentColor",fill:"none",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("accordion-insert",{viewBox:"0 0 24 24",paths:["M3 4h18","M3 8h18","M8 11l4 4 4-4","M3 16h18","M3 20h18"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("banner-insert",{viewBox:"0 0 24 24",paths:["M3 6h18","M3 10h18","M3 14h12"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("hero",{viewBox:"0 0 24 24",paths:["M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5z","M7 8h10M7 11h6"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("text-style",{viewBox:"0 0 24 24",paths:["M4 7V5h16v2","M9 19h6","M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("css-code",{viewBox:"0 0 24 24",paths:["M9 9l-6 3 6 3","M15 9l6 3-6 3","M13 5l-2 14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("scribe-insert",{viewBox:"0 0 24 24",paths:["M4 20h3l10-10-3-3L4 17v3z","M14 7l3 3","M4 4h6"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("carousel-insert",{viewBox:"0 0 24 24",paths:["M7 5h10v14H7z","M3 8h2v8H3z","M19 8h2v8h-2z"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"})}export function replaceRange(e,n,s,t){if(n===s&&!t)return;const r=typeof document<"u"&&typeof e.setSelectionRange=="function"&&typeof document.execCommand=="function";let o=!1;if(r){e.focus(),e.setSelectionRange(n,s);try{o=t?document.execCommand("insertText",!1,t):document.execCommand("delete")}catch{o=!1}}if(!o){const c=e.value;e.value=c.substring(0,n)+t+c.substring(s),e.selectionStart=e.selectionEnd=n+t.length,e.dispatchEvent(new Event("input",{bubbles:!0}))}}export function wrapSelection(e,n,s){const t=e.selectionStart,r=e.selectionEnd,o=e.value.substring(t,r);replaceRange(e,t,r,n+o+s),o?e.setSelectionRange(t+n.length,r+n.length):e.setSelectionRange(t+n.length,t+n.length)}export function insertAtCursor(e,n){const s=e.selectionStart;replaceRange(e,s,s,n),e.setSelectionRange(s+n.length,s+n.length)}export function attachEditorKeybindings(e,{language:n="md",beforeFormat:s=null}={}){e.addEventListener("keydown",t=>{if(t.isComposing)return;if(S(t)){t.preventDefault(),t.stopPropagation(),s?.();const o=C(e,n);o.error?E.toast(o.error,{type:"warning"}):E.toast(o.unchanged?"Already tidy":"Formatted",{type:"info",duration:900});return}if(t.key==="Enter"&&!t.shiftKey&&!t.ctrlKey&&!t.metaKey&&!t.altKey){t.preventDefault(),m(e,M(e.value,e.selectionStart,e.selectionEnd,n));return}if(n==="css"&&t.key.length===1&&!t.ctrlKey&&!t.metaKey&&!t.altKey){const o=y(e.value,e.selectionStart,e.selectionEnd,t.key,n);if(o){t.preventDefault(),m(e,o);return}}if((t.ctrlKey||t.metaKey)&&!t.altKey&&!t.shiftKey&&(t.key==="/"||t.code==="Slash")){t.preventDefault(),t.stopPropagation();const o=b(e.value,e.selectionStart,e.selectionEnd);o&&(replaceRange(e,o.start,o.end,o.text),e.setSelectionRange(o.selectionStart,o.selectionEnd));return}if(t.key==="Tab"&&!t.ctrlKey&&!t.metaKey&&!t.altKey){t.preventDefault();const{selectionStart:o,selectionEnd:c,value:u}=e,d=x(u,n),h=w(u,o,c,t.shiftKey,d)||(t.shiftKey?null:{start:o,end:c,text:d,selStart:o+d.length,selEnd:o+d.length});h&&m(e,h);return}if(!(t.ctrlKey||t.metaKey))return;const r=t.key.toLowerCase();if(r==="z")t.preventDefault(),document.execCommand("undo"),E.toast("Undo",{type:"info",duration:1200});else if(r==="y")t.preventDefault(),document.execCommand("redo"),E.toast("Redo",{type:"info",duration:1200});else if(r==="x"&&e.selectionStart===e.selectionEnd){t.preventDefault();const o=e.value.lastIndexOf(`
|
|
2
|
+
`,e.selectionStart-1)+1,c=e.value.indexOf(`
|
|
3
|
+
`,e.selectionStart),u=c===-1?e.value.length:c+1;e.setSelectionRange(o,u),document.execCommand("cut"),e.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1200})}else if(r==="c"&&e.selectionStart===e.selectionEnd){t.preventDefault();const o=e.selectionStart,c=e.value.lastIndexOf(`
|
|
4
|
+
`,o-1)+1,u=e.value.indexOf(`
|
|
5
|
+
`,o),d=u===-1?e.value.length:u+1;e.setSelectionRange(c,d),document.execCommand("copy"),e.setSelectionRange(o,o),E.toast("Line copied",{type:"info",duration:1200})}})}export function insertLine(e,n){const s=e.selectionStart,t=e.value,r=t.lastIndexOf(`
|
|
6
|
+
`,s-1)+1,o=t.indexOf(`
|
|
7
|
+
`,r);t.substring(r,o===-1?t.length:o).startsWith(n)?(replaceRange(e,r,r+n.length,""),e.setSelectionRange(Math.max(r,s-n.length),Math.max(r,s-n.length))):(replaceRange(e,r,r,n),e.setSelectionRange(s+n.length,s+n.length))}function k(e,n,s,t){const r=document.querySelector(".editor-toolbar-dropdown");if(r){r.remove();return}const o=document.createElement("div");o.className="editor-toolbar-dropdown",s.forEach(function(i){if(i.heading){const p=document.createElement("div");p.className="editor-toolbar-dropdown-heading",p.textContent=i.heading,o.appendChild(p);return}const l=document.createElement("button");if(l.type="button",l.className="editor-toolbar-dropdown-item",i.icon){const p=document.createElement("span");p.setAttribute("data-icon",i.icon),l.appendChild(p)}const a=document.createElement("span");a.textContent=i.label,l.appendChild(a),l.addEventListener("click",function(){o.remove(),document.removeEventListener("click",h,!0),v(i.action,e,n,t)}),o.appendChild(l)});const c=n.getBoundingClientRect(),u=n.closest(".editor-toolbar").getBoundingClientRect();o.style.top=c.bottom-u.top+4+"px";const d=c.left-u.left;o.style.left=Math.min(d,u.width-180-8)+"px",n.closest(".editor-toolbar").appendChild(o),Domma.icons.scan(o);function h(i){!o.contains(i.target)&&i.target!==n&&(o.remove(),document.removeEventListener("click",h,!0))}setTimeout(function(){document.addEventListener("click",h,!0)},0)}function H(){return import("./shortcode-insert.js").then(e=>[{label:"Insert Image",icon:"image-add",action:"insert:image"},...e.insertMenuGroups().map(n=>({...n,items:n.items.filter(s=>s.name!=="image")})).filter(n=>n.items.length).flatMap(n=>[{heading:n.category},...n.items.map(s=>({label:s.label,icon:s.icon,action:`insert:${s.name}`}))])])}function v(e,n,s,t){const{handlers:r}=t;typeof e=="function"?e(n):typeof e=="string"&&e.startsWith("insert:")?g(e.slice(7),n):e==="effects"?r.effects&&r.effects(n):e==="help"?r.help&&r.help(n):e==="tutorial"&&r.tutorial&&r.tutorial(n)}function g(e,n){import("./shortcode-insert.js").then(s=>s.insertShortcodeByName(e,n)).catch(s=>console.error("Insert router failed to load",s))}const L=[{icon:"bold",title:"Bold (Ctrl+B)",action:e=>wrapSelection(e,"**","**")},{icon:"italic",title:"Italic (Ctrl+I)",action:e=>wrapSelection(e,"_","_")},{icon:"strikethrough",title:"Strikethrough",action:e=>wrapSelection(e,"~~","~~")},"|",{type:"dropdown",icon:"heading-1",title:"Headings",items:[{label:"Heading 1",icon:"heading-1",action:e=>insertLine(e,"# ")},{label:"Heading 2",icon:"heading-2",action:e=>insertLine(e,"## ")},{label:"Heading 3",icon:"heading-3",action:e=>insertLine(e,"### ")}]},"|",{icon:"list-bullet",title:"Bullet list",action:e=>insertLine(e,"- ")},{icon:"list-numbered",title:"Numbered list",action:e=>insertLine(e,"1. ")},"|",{type:"dropdown",icon:"quote",title:"Paragraph",items:[{label:"Blockquote",icon:"quote",action:e=>insertLine(e,"> ")},{label:"Horizontal rule",icon:"minus-circle",action:e=>insertAtCursor(e,`
|
|
9
8
|
---
|
|
10
|
-
`)}]},"|",{type:"dropdown",icon:"code-inline",title:"Code",items:[{label:"Inline code",icon:"code-inline",action:e=>wrapSelection(e,"`","`")},{label:"Code block",icon:"code-block",action:e=>wrapSelection(e,"\n```\n","\n```\n")}]},"|",{type:"dropdown",icon:"plus-circle",title:"Insert",insertMenu:!0,items:[]},"|",{icon:"sparkles",title:"Effects",action:"effects"},{icon:"book-open",title:"CRUD tutorial",action:"tutorial"},{icon:"image-add",title:"Upload or insert image",action:"insert:image"},{icon:"help-circle",title:"Editor help",action:"help"}];export function createToolbar(e,
|
|
9
|
+
`)}]},"|",{type:"dropdown",icon:"code-inline",title:"Code",items:[{label:"Inline code",icon:"code-inline",action:e=>wrapSelection(e,"`","`")},{label:"Code block",icon:"code-block",action:e=>wrapSelection(e,"\n```\n","\n```\n")}]},"|",{type:"dropdown",icon:"plus-circle",title:"Insert",insertMenu:!0,items:[]},"|",{icon:"sparkles",title:"Effects",action:"effects"},{icon:"book-open",title:"CRUD tutorial",action:"tutorial"},{icon:"image-add",title:"Upload or insert image",action:"insert:image"},{icon:"help-circle",title:"Editor help",action:"help"}];export function createToolbar(e,n,s={}){B();const t={help:null,tutorial:null,effects:null};s.spacerDefault&&import("./shortcode-insert.js").then(i=>i.registerInsertDefaults({spacer:{size:String(s.spacerDefault)}})).catch(()=>{});const r=e.get(0),o=n.get(0);o.textContent="",L.forEach((i,l)=>{if(i==="|"){const a=document.createElement("span");a.className="editor-toolbar-sep",o.appendChild(a)}else{const a=document.createElement("button");a.className=i.type==="dropdown"?"editor-toolbar-btn editor-toolbar-dropdown-trigger":"editor-toolbar-btn",a.setAttribute("data-tooltip",i.title),a.setAttribute("data-idx",String(l)),a.type="button";const p=document.createElement("span");if(p.setAttribute("data-icon",i.icon),a.appendChild(p),i.type==="dropdown"){const f=document.createElement("span");f.className="editor-toolbar-caret",f.setAttribute("data-icon","chevron-down"),f.setAttribute("data-icon-size","12"),f.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",a.appendChild(f)}o.appendChild(a)}});const c=document.createElement("div");c.className="editor-toolbar-right",[{mode:"split",icon:"columns",label:"Split view",active:!0},{mode:"write",icon:"file-text",label:"Write only",active:!1},{mode:"preview",icon:"eye",label:"Preview only",active:!1}].forEach(i=>{const l=document.createElement("button");l.className="editor-view-btn"+(i.active?" active":""),l.setAttribute("data-mode",i.mode),l.setAttribute("data-tooltip",i.label),l.type="button";const a=document.createElement("span");a.setAttribute("data-icon",i.icon),l.appendChild(a),c.appendChild(l)});const u=document.createElement("span");u.className="editor-toolbar-sep",c.appendChild(u);const d=document.createElement("button");d.id="fullscreen-btn",d.className="editor-toolbar-btn",d.setAttribute("data-tooltip","Toggle fullscreen"),d.type="button";const h=document.createElement("span");return h.setAttribute("data-icon","expand"),d.appendChild(h),c.appendChild(d),o.appendChild(c),Domma.icons.scan(),o.querySelectorAll("[data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})}),n.on("click",".editor-toolbar-btn[data-idx]",function(){const i=parseInt($(this).data("idx"),10),l=L[i];if(!l||l==="|")return;const a=n.get(0).querySelector(`[data-idx="${i}"]`),p={handlers:t};l.type==="dropdown"?l.insertMenu?H().then(f=>k(r,a,f,p)).catch(f=>console.error("Insert menu failed to load",f)):k(r,a,l.items,p):v(l.action,r,a,p)}),attachEditorKeybindings(r,{language:"md",beforeFormat:s.beforeFormat}),r.addEventListener("keydown",i=>{const l=i.key.toLowerCase();(i.ctrlKey||i.metaKey)&&(l==="b"?(i.preventDefault(),wrapSelection(r,"**","**")):l==="i"?(i.preventDefault(),wrapSelection(r,"_","_")):l==="k"&&(i.preventDefault(),g("link",r)))}),{$toolbar:n,onHelp(i){t.help=i},onTutorial(i){t.tutorial=i},onEffects(i){t.effects=i}}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{onceClosed as p}from"./tool-kit.js";const u=([o,a,
|
|
1
|
+
import{onceClosed as p}from"./tool-kit.js";const u=([o,a,n])=>n===void 0?{code:o,where:"",text:a}:{code:o,where:a,text:n},e=(o,a,n="")=>({title:o,code:a,note:n||""}),t=(o,{intro:a="",head:n=null,rows:s=[],block:l="",examples:r=[],outro:i=""}={})=>({title:o,intro:a,head:(n||[]).map((d,c)=>({i:c,text:d})),rows:s.map(u),block:l,examples:r,outro:i}),m=[t("Markdown Basics",{head:["Syntax","Result"],rows:[["**bold**","bold (rendered bold)"],["_italic_","italic (rendered italic)"],["## Heading","Heading (h2)"],["[text](url)","Link"],["","Image"],["- item","Bullet list"],["`code`","Inline code"],["---","Divider"]]}),t("Keyboard Shortcuts",{rows:[["Ctrl + /","Toggle a comment around selected text or the current paragraph"],["Ctrl + B","Bold selected text"],["Ctrl + I","Italic selected text"],["Ctrl + K","Insert link dialog"],["Shift + Alt + F","Format: tidy the page body (trailing spaces, blank lines, tables lined up), or re-indent a block's or component's HTML, CSS, JS or JSON"],["Enter","New line at the same indent; carries a list or quote on (Enter on an empty item ends it); in code, one deeper after { ( [ or an opening tag"],["Tab / Shift + Tab","Indent or outdent - every selected line when the selection spans lines (indent size, tabs or spaces: Editor settings)"],["Ctrl + X","Cut current line (no selection) or cut selection"],["Ctrl + C","Copy current line (no selection) or copy selection"],["Ctrl + V","Paste from clipboard"],["Ctrl + Z","Undo"],["Ctrl + Y","Redo"]]}),t("Tips",{rows:[['class="text-center"',"Centre text and inline elements on any shortcode"],['class="mx-auto"',"Centre a block element horizontally"],["[center]...[/center]","Shorthand wrapper for centred content"]],outro:"Most shortcodes accept a class attribute for custom styling."})],g=[t("Grid & Columns",{rows:[['cols="N"',"[grid] only","Number of columns (1-12)"],['gap="N"',"[grid], [row]","Gap between columns/rows (1-6)"],['span="N"',"[col] only","How many columns this cell spans"],['fullwidth="true"',"[grid] only","Break out of page container to span full viewport"],['class="x"',"all","Add extra CSS classes"]],examples:[e("Equal columns",`[grid cols="3" gap="4"]
|
|
2
2
|
[col]One[/col]
|
|
3
3
|
[col]Two[/col]
|
|
4
4
|
[col]Three[/col]
|
|
@@ -166,4 +166,4 @@ pre.per-block { padding-right: .75rem; margin-bottom: .5rem; }
|
|
|
166
166
|
.per-copy { position: absolute; top: .3rem; right: .3rem; background: none; border: none; cursor: pointer; opacity: .45; padding: .15rem;
|
|
167
167
|
line-height: 1; color: inherit; }
|
|
168
168
|
.per-copy:hover, .per-copy:focus-visible { opacity: 1; }
|
|
169
|
-
</style>`;async function w(o){try{return await navigator.clipboard.writeText(o),!0}catch{const a=document.createElement("textarea");a.value=o,a.style.cssText="position:fixed;opacity:0;",document.body.appendChild(a),a.select();let
|
|
169
|
+
</style>`;async function w(o){try{return await navigator.clipboard.writeText(o),!0}catch{const a=document.createElement("textarea");a.value=o,a.style.cssText="position:fixed;opacity:0;",document.body.appendChild(a),a.select();let n=!1;try{n=document.execCommand("copy")}catch{}return a.remove(),n}}export function openEditorReference(){const o=E.slideover({title:"Editor Reference",size:"md",position:"right"}),a=document.createElement("div");a.innerHTML=y;const n=M.observable("basics"),s=M.applyBindings({tabs:REFERENCE,tab:n,show:r=>{n.value=r},copy:async r=>{const i=await w(r);E.toast(i?"Copied!":"Could not copy - select the code and copy it.",{type:i?"success":"warning",duration:1200})}},a);o.element.appendChild(a);const l=M.effect(()=>{n.value,requestAnimationFrame(()=>I.scan(a))});p(o,()=>{try{l()}catch{}try{s.dispose()}catch{}}),o.open()}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import{attachSmoothCaret as
|
|
2
|
-
`).length||1;
|
|
3
|
-
`))};let
|
|
4
|
-
`,r-1)+1,h=u.slice(a,a+2),f=h===" "?2:h[0]===" "?1:0;f&&(e.setRangeText("",a,a+f,"start"),e.selectionStart=e.selectionEnd=Math.max(a,r-f))}e.dispatchEvent(new Event("input",{bubbles:!0}))}else if(n.key==="Enter"){n.preventDefault();const a=u.lastIndexOf(`
|
|
5
|
-
`,r-1)+1,h=(u.slice(a,r).match(/^[ \t]*/)||[""])[0],f=u.slice(0,r).trimEnd().slice(-1);e.setRangeText(`
|
|
6
|
-
`+h+("{[(".includes(f)?" ":""),r,m,"end"),e.dispatchEvent(new Event("input",{bubbles:!0}))}};e.addEventListener("input",T),e.addEventListener("scroll",v),e.addEventListener("keydown",N),e.value=E??"",L(),b();const S=g(e);return{textarea:e,gutter:i,colours:c,getValue(){return e.value},setValue(n){const r=n??"";e.value!==r&&(e.value=r,L(),b(),v(),S.refresh?.())},focus(){e.focus()},destroy(){S.destroy?.(),e.removeEventListener("input",T),e.removeEventListener("scroll",v),e.removeEventListener("keydown",N),o.parentNode===t&&t.removeChild(o)}}}
|
|
1
|
+
import{attachSmoothCaret as A}from"./smooth-caret.js";import{highlight as C}from"./syntax-highlight.js";import{applyEdit as K,closerEdit as S,enterEdit as k,formatTextarea as D,indentEdit as B,isFormatKey as H,tabUnit as I}from"./code-format.js";export function attachColourLayer(r,h){const o=document.createElement("div");o.className="pe-plain-host";const n=document.createElement("pre");n.className="editor-textarea pe-plain-colours",n.setAttribute("aria-hidden","true"),r.classList.add("pe-plain-coloured"),r.parentNode.insertBefore(o,r),o.append(n,r);let f=null;const l=()=>{const e=getComputedStyle(r);n.style.whiteSpace!==e.whiteSpace&&(n.style.whiteSpace=e.whiteSpace,n.style.overflowWrap=e.overflowWrap),f!==r.value&&(f=r.value,n.innerHTML=C(r.value,h))},i=()=>{n.scrollTop=r.scrollTop,n.scrollLeft=r.scrollLeft},s=()=>{l(),i()};return r.addEventListener("input",s),r.addEventListener("scroll",i),l(),{host:o,colours:n,paint:()=>{l(),i()},destroy(){r.removeEventListener("input",s),r.removeEventListener("scroll",i),r.classList.remove("pe-plain-coloured"),o.parentNode&&(o.parentNode.insertBefore(r,o),o.remove())}}}export function createPlainEditor(r,{initialValue:h="",onChange:o=null,ariaLabel:n="",placeholder:f="",language:l=null}={}){if(!r)throw new Error("createPlainEditor: a mount element is required.");for(;r.firstChild;)r.removeChild(r.firstChild);const i=document.createElement("div");i.className="pe-plain";const s=document.createElement("div");s.className="editor-line-numbers",s.setAttribute("aria-hidden","true");const e=document.createElement("textarea");e.className="editor-textarea",e.spellcheck=!1,e.wrap="off",e.setAttribute("autocomplete","off"),e.setAttribute("autocorrect","off"),e.setAttribute("autocapitalize","off"),n&&e.setAttribute("aria-label",n),f&&(e.placeholder=f);let c=null;if(l){const t=document.createElement("div");t.className="pe-plain-host",c=document.createElement("pre"),c.className="editor-textarea pe-plain-colours",c.setAttribute("aria-hidden","true"),e.classList.add("pe-plain-coloured"),t.append(c,e),i.append(s,t)}else i.append(s,e);r.appendChild(i);let L=0;const E=()=>{const t=e.value.split(`
|
|
2
|
+
`).length||1;t!==L&&(L=t,s.textContent=Array.from({length:t},(a,u)=>u+1).join(`
|
|
3
|
+
`))};let b=null;const y=()=>{c&&b!==e.value&&(b=e.value,c.innerHTML=C(e.value,l))},v=()=>{s.scrollTop=e.scrollTop,c&&(c.scrollTop=e.scrollTop,c.scrollLeft=e.scrollLeft)},w=()=>{E(),y(),v(),o&&o(e.value)},N=t=>{if(t.isComposing)return;const{selectionStart:a,selectionEnd:u,value:m}=e;if(H(t)){if(t.preventDefault(),!l)return;const d=D(e,l);d.error&&globalThis.E?.toast?.(d.error,{type:"warning"});return}if(t.ctrlKey||t.metaKey||t.altKey)return;let p=null;if(t.key==="Tab"){t.preventDefault();const d=I(m,l);p=B(m,a,u,t.shiftKey,d),!p&&!t.shiftKey&&(p={start:a,end:u,text:d,selStart:a+d.length,selEnd:a+d.length})}else t.key==="Enter"&&!t.shiftKey?(t.preventDefault(),p=k(m,a,u,l||"text")):t.key.length===1&&(p=S(m,a,u,t.key,l),p&&t.preventDefault());p&&K(e,p)};e.addEventListener("input",w),e.addEventListener("scroll",v),e.addEventListener("keydown",N),e.value=h??"",E(),y();const T=A(e);return{textarea:e,gutter:s,colours:c,getValue(){return e.value},setValue(t){const a=t??"";e.value!==a&&(e.value=a,E(),y(),v(),T.refresh?.())},focus(){e.focus()},destroy(){T.destroy?.(),e.removeEventListener("input",w),e.removeEventListener("scroll",v),e.removeEventListener("keydown",N),i.parentNode===r&&r.removeChild(i)}}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export function escapeHtml(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const
|
|
1
|
+
export function escapeHtml(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const g=(e,n)=>`<span class="dm-tok-${e}">${escapeHtml(n)}</span>`;function o(e,n){let r="",s=0;const d=n.map(t=>({cls:t.cls,inner:t.inner,re:new RegExp(t.re.source,t.re.flags.replace("y","")+"y")}));for(;s<e.length;){let t=!1;for(const{cls:l,inner:a,re:i}of d){i.lastIndex=s;const c=i.exec(e);if(c&&c[0].length>0){r+=a?o(c[0],m[a]):g(l,c[0]),s+=c[0].length,t=!0;break}}if(!t){let l=s+1;if(/[A-Za-z0-9_]/.test(e[s]))for(;l<e.length&&/[A-Za-z0-9_]/.test(e[l]);)l++;r+=escapeHtml(e.slice(s,l)),s=l}}return r}const f=/\b(?:async|await|break|case|catch|class|const|continue|default|delete|do|else|export|extends|finally|for|from|function|if|import|in|instanceof|let|new|of|return|static|super|switch|this|throw|try|typeof|var|void|while|yield)\b/,m={js:[{cls:"comment",re:/\/\/[^\n]*/},{cls:"comment",re:/\/\*[\s\S]*?(?:\*\/|$)/},{cls:"string",re:/`(?:[^`\\]|\\[\s\S])*(?:`|$)/},{cls:"string",re:/'(?:[^'\\\n]|\\[\s\S])*(?:'|$)/},{cls:"string",re:/"(?:[^"\\\n]|\\[\s\S])*(?:"|$)/},{cls:"keyword",re:f},{cls:"literal",re:/\b(?:true|false|null|undefined|NaN|Infinity)\b/},{cls:"number",re:/\b0[xXbBoO]?[\da-fA-F_]+n?\b|\b\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?\b/},{cls:"func",re:/[A-Za-z_$][\w$]*(?=\s*\()/}],json:[{cls:"key",re:/"(?:[^"\\]|\\[\s\S])*"(?=\s*:)/},{cls:"string",re:/"(?:[^"\\]|\\[\s\S])*(?:"|$)/},{cls:"literal",re:/\b(?:true|false|null)\b/},{cls:"number",re:/-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/}],css:[{cls:"comment",re:/\/\*[\s\S]*?(?:\*\/|$)/},{cls:"string",re:/'(?:[^'\\\n]|\\[\s\S])*(?:'|$)|"(?:[^"\\\n]|\\[\s\S])*(?:"|$)/},{cls:"number",re:/-?\b\d+(?:\.\d+)?(?:px|rem|em|vh|vw|s|ms|deg|fr|%)?\b/},{cls:"key",re:/--?[a-zA-Z][\w-]*(?=\s*:)/},{cls:"keyword",re:/@[a-zA-Z-]+/},{cls:"func",re:/[a-zA-Z-]+(?=\()/},{cls:"literal",re:/#[0-9a-fA-F]{3,8}\b/},{cls:"tag",re:/\.[a-zA-Z_-][\w-]*|#[a-zA-Z_-][\w-]*/}],html:[{cls:"comment",re:/<!--[\s\S]*?(?:-->|$)/},{cls:"string",re:/"(?:[^"]*)(?:"|$)|'(?:[^']*)(?:'|$)/},{cls:"tag",re:/<\/?[a-zA-Z][\w-]*|\/?>/},{cls:"key",re:/\b[a-zA-Z-]+(?==)/}],md:[{cls:"keyword",re:/^#{1,6}[^\n]*/m},{cls:"comment",re:/```[\s\S]*?(?:```|$)/},{cls:"string",re:/`[^`\n]*(?:`|$)/},{cls:"comment",re:/<!--[\s\S]*?(?:-->|$)/},{cls:"comment",re:/^ {0,3}\/\/[^\n]*/m},{inner:"html",re:/<\/?[a-zA-Z][\w:-]*(?:\s(?:"[^"]*"|'[^']*'|[^'"<>])*)?\/?>/},{cls:"literal",re:/\*\*[^*\n]+\*\*|\*[^*\n]+\*/},{cls:"func",re:/\[[^\]\n]*\]\([^)\n]*\)/},{cls:"key",re:/\[\/?[a-z][\w-]*(?:\s[^\]\n]*)?\/?\]/},{cls:"tag",re:/^\s*[-*+]\s|^\s*\d+\.\s/m}]};export function languageForFile(e){const n=String(e||"").toLowerCase().split(".").pop();return{js:"js",json:"json",css:"css",html:"html",htm:"html",svg:"html",md:"md",markdown:"md"}[n]||null}export function highlight(e,n){const r=m[n];return(r?o(String(e??""),r):escapeHtml(String(e??"")))+`
|
|
2
2
|
`}
|
|
@@ -136,8 +136,8 @@
|
|
|
136
136
|
data-bind-class="blockPart.value === 'css' && 'active'">CSS</button>
|
|
137
137
|
</div>
|
|
138
138
|
<span class="pex-spacer"></span>
|
|
139
|
-
<button type="button" class="editor-view-btn" title="
|
|
140
|
-
data-bind-disabled="
|
|
139
|
+
<button type="button" class="editor-view-btn" title="Format (Shift+Alt+F)" data-on-click="formatBlock"
|
|
140
|
+
data-bind-disabled="!blockReady.value"><span data-icon="code"></span></button>
|
|
141
141
|
</div>
|
|
142
142
|
<p class="pe-block-note" hidden data-bind-hidden="!blockNote.value" data-bind-text="blockNote.value"></p>
|
|
143
143
|
<div class="pe-block-edit" data-part="html" hidden data-bind-hidden="!blockReady.value || blockPart.value !== 'html'"></div>
|
|
@@ -545,7 +545,9 @@
|
|
|
545
545
|
The colours are a <pre> behind the textarea with the SAME .editor-textarea class - the lettering rules reach
|
|
546
546
|
both by one selector, so they cannot differ; only what is set here (position, colour) tells them apart. */
|
|
547
547
|
.pe-plain { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; }
|
|
548
|
-
.pe-plain .editor-textarea { flex: 1 1 auto; min-width: 0; white-space: pre; overflow: auto;
|
|
548
|
+
.pe-plain .editor-textarea { flex: 1 1 auto; min-width: 0; white-space: pre; overflow: auto; margin: 0; box-sizing: border-box; }
|
|
549
|
+
/* A tab shows as wide as an indent (Editor settings) - on every layer alike, by the shared class. */
|
|
550
|
+
#editor-body .editor-textarea { tab-size: var(--pe-tab, 2); -moz-tab-size: var(--pe-tab, 2); }
|
|
549
551
|
.pe-plain-host { position: relative; flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; overflow: hidden; }
|
|
550
552
|
/* The layer scrolls like the textarea (its scrollbar sits under the textarea's), so the two wrap at the same width. */
|
|
551
553
|
.pe-plain-host .pe-plain-colours { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: auto; scrollbar-gutter: stable; color: var(--dm-text); margin: 0; box-sizing: border-box; }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as h}from"../api.js";import{CardBuilder as en}from"../lib/card-builder.js";import{TimelineBuilder as tn}from"../lib/timeline-builder.js";import{attachEditorKeybindings as on,createToolbar as an,insertAtCursor as sn}from"../lib/markdown-toolbar.js";import{EffectsBuilder as nn}from"../lib/effects-builder.js";import{attachContextMenu as rn}from"../lib/shortcode-context-menu.js";import{registerCustomInserters as ln}from"../lib/shortcode-insert.js";import{populateThemeSelect as cn}from"../lib/themes.js";import{createFoldingManager as dn}from"../lib/editor-folding.js";import{openScribeComposer as pn}from"../lib/scribe-composer.js";import{openCrudTutorial as un}from"../lib/crud-tutorial.js";import{addBannerAction as dt}from"../lib/plugin-chrome.js";import{exampleJson as mn,parseSchema as Po,schemaText as fn}from"../lib/seo-structured-data.js";import{helpPopovers as qa}from"../lib/help-popover.js";import{onceClosed as jt,openMenuAt as Ce,sidebarCount as pt}from"../lib/tool-kit.js";import{pickMedia as bn}from"../lib/media-picker.js";import{openPageClone as vn}from"../lib/page-clone.js";import{openEditorReference as Va}from"../lib/page-editor-reference.js";import{cleanType as Bt,DEFAULT_TYPE as It,EDITOR_FONTS as gn,fontHref as hn,isDefaultType as kn,LINE_HEIGHTS as yn,SIZES as wn,typeVars as _a}from"../lib/editor-type.js";import{getProjectFromHash as Ft}from"../lib/project-context.js";import{attachSmoothCaret as Ka,getCaretPreference as Sn,setCaretPreference as En}from"../lib/smooth-caret.js";import{SCREEN as Oo,bannerSection as Tn,draftIsStale as Mn,draftKey as ze,initialProject as Cn,makeDraft as Ua,problems as Ro,readDraft as xn,snapshot as W}from"../lib/page-editor-arrange.js";import{fmtDateTime as qe}from"../lib/dates.js";import{hasDraft as xe,heldBlockEdits as Pn,hold as Ao,takeHeld as $o,wireHeldRouter as On}from"../lib/editor-hop.js";import{hopTarget as Ha}from"../lib/editor-hop-arrange.js";import{checkNewName as Rn,copyName as Lo,usageText as An}from"../lib/blocks-arrange.js";import{isCollapsed as Ja,setCollapsed as Wa}from"../lib/sidebar-size.js";import{blockTagDefs as Ya,blocksInBody as $n}from"../lib/block-tags.js";import{draftKey as Ve,draftOffer as Ln,extractPlaceholders as Ga,formFromBlock as Xa,payloadFromForm as Za,rawPlaceholders as No,shortcodeFor as Nn,snapshot as Do}from"../lib/block-editor-arrange.js";import{blockTabs as Dn,notHereLine as jn,otherUses as Qa,outlineBlock as Bn,previewBlocks as In,saveQuestion as Fn,savedMessage as zn,usesLine as qn}from"../lib/page-blocks-arrange.js";import{addressFor as es,blockAloneBody as Vn,blockRows as _n,cleanLayout as jo,closeTab as Kn,componentRows as Un,componentStatusText as Hn,componentsInBody as Jn,moveTab as Wn,nextLayout as Yn,openTab as Bo,pageRows as Gn,pageStatusText as Xn,rememberedTabs as Zn,renameTab as Qn,sampleRows as ts,saveAllLabel as er,startingTabs as tr,tabId as X,tabItem as I,tabsToClose as Io,workspaceKey as or}from"../lib/editor-workspace-arrange.js";import{PANES as zt,STARTER_SOURCE as ar,assembleSource as os,attributeFor as sr,draftKey as _e,draftStatus as nr,nameProblem as rr,previewRows as as,readValue as ir,snapshot as Fo,splitSource as ut,writeValue as ss}from"../lib/component-editor-arrange.js";import{attachColourLayer as ns,createPlainEditor as zo}from"../lib/plain-editor.js";import{cleanCodePrefs as qt,codePrefs as lr,DEFAULT_CODE_PREFS as Vt,formatTextarea as Ke,INDENT_SIZES as cr,setCodePrefs as dr}from"../lib/code-format.js";function mt(d){const b=document.createElement("template");return b.innerHTML=d.trim(),b.content.firstElementChild}const pr=qe,rs=d=>qe(d.createdAt)+(d.label?` - ${d.label}`:""),ur=`
|
|
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 k}from"../api.js";import{CardBuilder as Ws}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 is(d){const b=hn(d);if(!b||document.querySelector(`link[data-pe-font="${CSS.escape(d)}"]`))return;const v=Object.assign(document.createElement("link"),{rel:"stylesheet",href:b});v.dataset.peFont=d,document.head.appendChild(v)}const mr=`
|
|
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>
|
|
@@ -52,6 +52,27 @@ import{api as k}from"../api.js";import{CardBuilder as Ws}from"../lib/card-builde
|
|
|
52
52
|
<option value="native">Native</option>
|
|
53
53
|
</select>
|
|
54
54
|
<p class="pe-settings-note">Smooth enables caret animation across the admin and public site forms in this browser. Automatic follows your system's reduced-motion preference.</p>
|
|
55
|
+
<h4 class="pe-settings-head">Typing and formatting</h4>
|
|
56
|
+
<div class="pe-settings-grid">
|
|
57
|
+
<div><label class="form-label" for="pe-set-indent">Indent with</label>
|
|
58
|
+
<select id="pe-set-indent" class="form-select" data-model="indent.value">
|
|
59
|
+
<option value="spaces">Spaces</option>
|
|
60
|
+
<option value="tabs">Tabs</option>
|
|
61
|
+
</select></div>
|
|
62
|
+
<div><label class="form-label" for="pe-set-indent-size" data-bind-text="indent.value === 'tabs' ? 'Tab width' : 'Indent size'">Indent size</label>
|
|
63
|
+
<select id="pe-set-indent-size" class="form-select" data-model="indentSize.value"></select></div>
|
|
64
|
+
</div>
|
|
65
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="detect.value"> Keep a file's own indentation
|
|
66
|
+
<span data-help="A block, component or CSS already indented its own way (tabs, or four spaces) keeps that way when you type and format. Off: everything takes the setting above. The page body always takes the setting - its indentation means something in Markdown."></span></label>
|
|
67
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="autoIndent.value"> Auto-indent
|
|
68
|
+
<span data-help="Enter keeps the line's indentation, and in code goes one deeper after { ( [ or an opening tag; a closing } or </ typed on an empty line steps back."></span></label>
|
|
69
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="continueLists.value"> Carry lists and quotes on
|
|
70
|
+
<span data-help="Enter on a list item starts the next one (numbers count up, a task box comes back empty); Enter on an empty item ends the list. Shift+Enter is always a plain new line."></span></label>
|
|
71
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="shortTags.value"> Indent short tags when formatting
|
|
72
|
+
<span data-help="Shift+Alt+F indents the lines that hold only a short tag - [grid], [col], [card]\u2026 - by how deeply they nest. The content between them stays at the margin. The site never shows the indent."></span></label>
|
|
73
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="tables.value"> Line up tables when formatting</label>
|
|
74
|
+
<label class="pe-settings-switch"><input type="checkbox" class="form-switch" data-model="formatOnSave.value"> Format on save
|
|
75
|
+
<span data-help="Save formats the tab you are on first, as Shift+Alt+F would. Ctrl+Z still undoes it."></span></label>
|
|
55
76
|
<div class="pe-settings-sample" aria-label="Sample"># A heading
|
|
56
77
|
[card title="Shortcodes line up"]
|
|
57
78
|
The quick brown fox - 0O 1lI {} [] () => ==
|
|
@@ -67,20 +88,21 @@ The quick brown fox - 0O 1lI {} [] () => ==
|
|
|
67
88
|
.pe-settings-note { margin: 0; font-size: .85rem; opacity: .75; }
|
|
68
89
|
.pe-settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
|
|
69
90
|
.pe-settings-switch { display: flex; align-items: center; gap: .5rem; }
|
|
91
|
+
.pe-settings-head { margin: .5rem 0 0; font-size: .95rem; border-top: 1px solid var(--dm-border); padding-top: .75rem; }
|
|
70
92
|
.pe-settings-sample { white-space: pre-wrap; padding: .75rem 1rem; border-radius: var(--dm-radius-md); border: 1px solid var(--dm-border);
|
|
71
93
|
background: var(--dm-background-alt); font-family: var(--pe-font); font-size: var(--pe-size); line-height: var(--pe-line); }
|
|
72
94
|
.pe-settings-actions { display: flex; gap: .5rem; border-top: 1px solid var(--dm-border); padding-top: .75rem; }
|
|
73
|
-
</style>`;function
|
|
95
|
+
</style>`;function fr(d,b){const v=E.slideover({title:"Editor settings",size:"sm",position:"right"}),i=mt(`<div>${mr}</div>`),y=Bt(d.peek()),u={font:M.observable(y.font),size:M.observable(String(y.size)),line:M.observable(String(y.line)),wrap:M.observable(y.wrap),caret:M.observable(Sn()),sidebar:M.observable(Pe().closeSidebar===!0)},w=qt(b.peek());u.indent=M.observable(w.indent),u.indentSize=M.observable(String(w.size));for(const T of["detect","autoIndent","continueLists","shortTags","tables","formatOnSave"])u[T]=M.observable(w[T]);const k=M.computed(()=>qt({indent:u.indent.value,size:u.indentSize.value,detect:u.detect.value,autoIndent:u.autoIndent.value,continueLists:u.continueLists.value,shortTags:u.shortTags.value,tables:u.tables.value,formatOnSave:u.formatOnSave.value})),x=M.effect(()=>{const T=k.value;JSON.stringify(T)!==JSON.stringify(qt(b.peek()))&&(b.value=T,Ue("code",T))}),j=M.effect(()=>{const T=u.sidebar.value;T!==(Pe().closeSidebar===!0)&&Ue("closeSidebar",T)}),c=M.computed(()=>Bt({font:u.font.value,size:u.size.value,line:u.line.value,wrap:u.wrap.value}));u.isDefault=M.computed(()=>kn(c.value)&&u.caret.value==="auto"&&!u.sidebar.value&&JSON.stringify(k.value)===JSON.stringify(Vt));const A=M.effect(()=>En(u.caret.value)),V=i.querySelector("#pe-set-font");for(const[T,ne]of[["mono","Monospace"],["sans","Other"],["serif",null]]){const Oe=gn.filter(re=>re.kind===T),O=ne?Object.assign(document.createElement("optgroup"),{label:ne}):V.querySelector("optgroup:last-of-type");O.append(...Oe.map(re=>new Option(re.label,re.id))),ne&&V.appendChild(O)}i.querySelector("#pe-set-indent-size").append(...cr.map(T=>new Option(String(T),String(T)))),i.querySelector("#pe-set-size").append(...wn.map(T=>new Option(`${T}px`,String(T)))),i.querySelector("#pe-set-line").append(...yn.map(T=>new Option({1.3:"Tight",1.45:"Snug",1.6:"Normal",1.8:"Relaxed",2:"Double"}[T]||String(T),String(T))));const Y=i.querySelector(".pe-settings-sample"),D=M.effect(()=>{const T=c.value;is(T.font);for(const[ne,Oe]of Object.entries(_a(T)))Y.style.setProperty(ne,Oe);Y.style.whiteSpace=T.wrap?"pre-wrap":"pre",Y.style.overflowX=T.wrap?"":"auto",JSON.stringify(T)!==JSON.stringify(Bt(d.peek()))&&(d.value=T,Ue("type",T))});u.reset=()=>{u.font.value=It.font,u.size.value=String(It.size),u.line.value=String(It.line),u.wrap.value=It.wrap,u.caret.value="auto",u.sidebar.value=!1,u.indent.value=Vt.indent,u.indentSize.value=String(Vt.size);for(const T of["detect","autoIndent","continueLists","shortTags","tables","formatOnSave"])u[T].value=Vt[T]},u.done=()=>v.close(),v.element.appendChild(i);const se=M.applyBindings(u,i),_t=qa(i);return jt(v,()=>{se.dispose(),_t(),D?.(),A?.(),j?.(),x?.()}),Domma.icons?.scan?.(i),v.open(),v}async function br(d){const b=E.slideover({title:"Share preview link",size:"md",position:"right"}),v=mt(`<div>${ur}</div>`),i={expiry:M.observable("7d"),note:M.observable(""),creating:M.observable(!1),url:M.observable(""),links:M.observable([]),loaded:M.observable(!1),listError:M.observable("")},y=async()=>{try{const w=await h.pages.previewLinks.list(d);i.links.value=w.filter(k=>!k.revokedAt&&!k.expired).map(k=>({id:k.id,title:k.label||"Untitled link",meta:`Expires ${pr(k.expiresAt)} - ${k.useCount===1?"1 view":`${k.useCount||0} views`}`})),i.listError.value=""}catch(w){i.listError.value="Could not load existing links: "+(w.message||"unknown error")}finally{i.loaded.value=!0}};i.create=async()=>{i.creating.value=!0;try{const w=await h.pages.previewLinks.create(d,i.expiry.peek(),i.note.peek().trim());i.url.value=w.url,i.note.value="",E.toast("Share link created.",{type:"success"}),await y(),v.querySelector(".pe-share-url")?.select()}catch(w){E.toast("Could not create link: "+(w.message||"unknown error"),{type:"error"})}finally{i.creating.value=!1}},i.copy=async()=>{try{await navigator.clipboard.writeText(i.url.peek()),E.toast("Link copied to clipboard.",{type:"success"})}catch{v.querySelector(".pe-share-url")?.select(),E.toast("Press Ctrl+C to copy the selected link.",{type:"info"})}},i.revoke=async w=>{if(await E.confirm("Revoke this link? Anyone holding it loses access immediately."))try{await h.pages.previewLinks.revoke(w),E.toast("Link revoked.",{type:"success"}),await y()}catch(k){E.toast("Could not revoke: "+(k.message||"unknown error"),{type:"error"})}};const u=M.applyBindings(i,v);jt(b,()=>u.dispose()),b.element.appendChild(v),b.open(),await y()}function vr(d){return new Promise(b=>{const v=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),i=mt(`
|
|
74
96
|
<div style="padding:1rem">
|
|
75
97
|
<label class="form-label" for="pe-version-label">Version Label</label>
|
|
76
98
|
<input id="pe-version-label" type="text" class="form-input" style="margin-bottom:.75rem"
|
|
77
99
|
placeholder="e.g. Initial design, Before restructure" data-model="label.value" data-on-keydown="key">
|
|
78
100
|
<button type="button" class="btn btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save Version</button>
|
|
79
|
-
</div>`);let
|
|
101
|
+
</div>`);let y=!1;const u={label:M.observable(""),saving:M.observable(!1)};u.save=async()=>{u.saving.value=!0;try{await h.versions.create(d,u.label.peek().trim()||null),E.toast("Named version saved.",{type:"success"}),y=!0,v.close()}catch(k){E.toast("Failed to save version: "+k.message,{type:"error"})}finally{u.saving.value=!1}},u.key=k=>{k.key==="Enter"&&(k.preventDefault(),u.save())};const w=M.applyBindings(u,i);jt(v,()=>{w.dispose(),b(y)}),v.element.appendChild(i),v.open(),setTimeout(()=>i.querySelector("input")?.focus(),100)})}const gr=`<div class="card">
|
|
80
102
|
<h3>{{title}}</h3>
|
|
81
103
|
<p>{{description}}</p>
|
|
82
104
|
</div>
|
|
83
|
-
`,
|
|
105
|
+
`,hr=`
|
|
84
106
|
<div class="pe-new">
|
|
85
107
|
<p class="form-hint" style="margin-top:0" data-bind-text="hint.value"></p>
|
|
86
108
|
<label class="form-label" for="pe-new-name">Name</label>
|
|
@@ -100,15 +122,11 @@ The quick brown fox - 0O 1lI {} [] () => ==
|
|
|
100
122
|
.pe-new [hidden] { display: none !important; }
|
|
101
123
|
.pe-new-err { display: block; margin-top: .3rem; color: var(--dm-danger); font-size: var(--dm-text-xs); }
|
|
102
124
|
.pe-new-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.25rem; }
|
|
103
|
-
</style>`;function os({kind:d,projects:m,taken:v,project:l="",name:b="",check:y,create:w,register:h=()=>{}}){return new Promise(C=>{const L=E.slideover({title:d==="block"?"New block":"New component",size:"sm",position:"right"});h(()=>{try{L.close()}catch{}});const c=ct(`<div>${cr}</div>`);for(const D of m)c.querySelector("#pe-new-project").append(new Option(D.name||D.slug,D.slug));const P={name:M.observable(b),project:M.observable(l),busy:M.observable(!1),hint:M.observable(d==="block"?"A block is a reusable piece of HTML with {{placeholders}}, used on pages with its short tag or by a collection display. Its name is its tag - fixed once created.":"A component is a custom element - <dm-name> - with a template, props, a script and a style. Its name is its tag - fixed once created."),placeholder:M.observable(d==="block"?"e.g. team-card":"e.g. pricing-table")};P.error=M.computed(()=>P.name.value.trim()?y(P.name.value.trim(),v):""),P.canCreate=M.computed(()=>!P.busy.value&&!!P.name.value.trim()&&!y(P.name.value.trim(),v));let H="";P.create=async()=>{const D=P.name.peek().trim();if(!(!D||P.error.peek()||P.busy.peek())){P.busy.value=!0;try{await w(D,P.project.peek()),H=D,L.close()}catch(J){E.toast(`Not created: ${J.message||J}`,{type:"error"})}finally{P.busy.value=!1}}},P.cancel=()=>L.close(),P.key=D=>{D.key==="Enter"&&(D.preventDefault(),P.create())};const A=M.applyBindings(P,c);Nt(L,()=>{A.dispose(),C(H)}),L.element.appendChild(c),c.querySelector("#pe-new-project").value=l,L.open(),setTimeout(()=>{const D=c.querySelector("#pe-new-name");D?.focus(),D?.select()},100)})}let q=null,as=!1;function dr(){as||!window.R?.use||(as=!0,En(),R.use((d,m,v)=>{if(q&&!q.alive()){const y=q;q=null,y.dispose()}const l=m?.path||"",b=d?.path||"";if(!q||!So.test(l))return v();if(So.test(b)){const y=qa(b);if(y){q.open(y,b);return}return v()}return q.dirty()?E.confirm("You have unsaved changes. Leave anyway? They are kept as drafts for your next visit.").then(y=>{y?(q?.keepDraft(),v()):q&&history.replaceState({pe:1},"","#"+q.address)}):v()}),M.subscribe("router:afterChange",({to:d}={})=>{if(q&&!So.test(d?.path||"")){const m=q;q=null,m.dispose()}}))}function pr(d){return d.split(`
|
|
104
|
-
`).map(m=>m.trimEnd()).join(`
|
|
105
|
-
`).replace(/\n{3,}/g,`
|
|
106
|
-
|
|
107
|
-
`)}const ss="editor_prefs";function Te(){return S.get(ss)||{viewMode:"split",fullscreen:!1}}const ur=()=>{try{return Te()||{}}catch{return{}}};function dt(d,m){const v=Te();v[d]=m,S.set(ss,v)}const ns="editor-workspace.tabs",rs="editor-workspace.folded",pt="editor-workspace.css",mr="/__block-preview",fr=d=>d.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function br(d,m,v=()=>{}){let l=0;try{l=d.contentWindow.scrollY||0}catch{}d.addEventListener("load",()=>{try{d.contentWindow.scrollTo(0,l)}catch{}v()},{once:!0}),d.srcdoc=m}const vr=["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","seoSchema","dconfig"],gr=["showInNav","sidebar","breadcrumbs","bundled"],ls=()=>({urlPath:"",title:"",description:"",layout:"",status:"draft",sortOrder:"99",showInNav:!1,sidebar:!1,breadcrumbs:!0,bundled:!1,tags:[],adminTags:[],category:"",visibility:"public",project:"",theme:"",seoTitle:"",seoDesc:"",seoSchema:"",dconfig:"",body:"",css:null});export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(d){if(dr(),q){const e=q;q=null,e.dispose()}const m=window.location.hash.replace(/^#/,""),v=[],l=e=>(v.push(e),e);let b=()=>!1,y=()=>{},w=()=>{};const h=d.get(0),C=h.querySelector("#pe-workspace"),L={address:m,alive:()=>C?.isConnected===!0,dirty:()=>b(),keepDraft:()=>y(),open:(e,t)=>w(e,t),dispose:()=>{v.splice(0).forEach(e=>{try{e()}catch{}})}};q=L;const c=()=>q===L&&L.alive();if(ur().closeSidebar===!0){const e=document.querySelector("#admin-sidebar");e&&!za(e)&&!window.matchMedia("(max-width: 768px)").matches&&(Va(e,!0,!1),l(()=>{e.isConnected&&za(e)&&Va(e,!1,!1)}))}const P=E.loader(h,{type:"dots"});let[H,A,D,J,ut,Ce,Bo,jo]=await Promise.all([k.layouts.get().catch(()=>({})),k.settings.get().catch(()=>({})),k.pages.list().catch(()=>[]),k.projects.list().catch(()=>[]),k.pages.tags().catch(()=>[]),k.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),k.get("/blocks?usage=1").catch(()=>k.blocks.list()).catch(()=>[]),k.components.list().catch(()=>[])]);if(!c()){P.destroy();return}P.destroy();const N=e=>h.querySelector(e);if(D=Array.isArray(D)?D:[],tn(N("#field-theme"),{includeDefault:!0}),await Er(N("#field-visibility")),!c())return;const Ft=Object.keys(H||{});wr(N("#field-layout"),H,Ft[0]||"default");const Io=e=>Ft.includes(e)?e:Ft[0]||e||"default";for(const e of J)for(const t of["#field-project","#block-project","#comp-project"])N(t).append(new Option(e.name||e.slug,e.slug));let ae=Array.isArray(Bo)?Bo:[],mt=Ha(ae);const ft=new Map(ae.map(e=>[e.name,e.updatedAt])),ze=new Map(ae.map(e=>[e.name,e.usage])),pe=new Set(ae.map(e=>e.name)),Fo=async()=>{const e=await k.get("/blocks?usage=1").catch(()=>null);if(!(!c()||!Array.isArray(e))){ae=e,mt=Ha(ae);for(const t of ae)pe.add(t.name),ze.set(t.name,t.usage),ft.has(t.name)||ft.set(t.name,t.updatedAt)}},qt=Array.isArray(A?.footer?.blocks)?A.footer.blocks.filter(e=>typeof e=="string"):[];let Ve=Array.isArray(jo)?jo.filter(e=>typeof e?.name=="string"):[];const se=new Set(Ve.map(e=>e.name)),qo=async()=>{const e=await k.components.list().catch(()=>null);if(!(!c()||!Array.isArray(e))){Ve=e.filter(t=>typeof t?.name=="string");for(const t of Ve)se.add(t.name)}},te=Te(),p={urlPath:M.observable(""),title:M.observable(""),description:M.observable(""),layout:M.observable(Io("default")),status:M.observable("draft"),sortOrder:M.observable("99"),showInNav:M.observable(!1),sidebar:M.observable(!1),breadcrumbs:M.observable(!0),bundled:M.observable(!1),tags:M.observable([]),adminTags:M.observable([]),category:M.observable(""),visibility:M.observable("public"),project:M.observable(""),theme:M.observable(""),seoTitle:M.observable(""),seoDesc:M.observable(""),seoSchema:M.observable(""),dconfig:M.observable(""),css:M.observable(""),cssOpened:M.observable(!1)},o={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),viewMode:M.observable(Ro(te.viewMode)),fullscreen:M.observable(!!te.fullscreen),explorerOn:M.observable(te.explorer??!window.matchMedia("(max-width: 1024px)").matches),dockOn:M.observable(!!te.dock),dockTab:M.observable("details"),seoSchemaNote:M.observable(""),cssMode:M.observable(!1),folded:M.observable(!1),previewStatus: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(""),tabs:M.observable([]),active:M.observable(""),curId:M.observable(null),blockTab:M.observable(""),blockPart:M.observable("html"),blockReady:M.observable(!1),blockNote:M.observable(""),blockUses:M.observable(""),blockProject:M.observable(""),blockBundled:M.observable(!1),blockWhere:M.observable(""),compTab:M.observable(""),compPart:M.observable("template"),compReady:M.observable(!1),compNote:M.observable(""),compReadOnly:M.observable(!1),compState:M.observable(""),compErrors:M.observable(""),compProject:M.observable(""),compBundled:M.observable(!1),blockRev:M.observable(0),compRev:M.observable(0),docRev:M.observable(0),filter:M.observable(""),secHere:M.observable(te.secHere!==!1),secPages:M.observable(te.secPages!==!1),secBlocks:M.observable(te.secBlocks!==!1),secComps:M.observable(te.secComps!==!1),thisPage:M.observable([]),pageRows:M.observable([]),blockRows:M.observable([]),compRows:M.observable([])},F=N("#markdown-editor"),ue=N("#css-editor"),is=Ia(F),cs=Ia(ue);l(()=>is.destroy()),l(()=>cs.destroy());const ne=document.createElement("div");ne.className="editor-line-numbers editor-line-numbers--foldable",F.parentElement.insertBefore(ne,F);const He=Za(F,"md"),zt=Za(ue,"css");l(()=>{He.destroy(),zt.destroy()});const zo=()=>{He.paint(),zt.paint()},G=on(F,ne,{onFoldChange:()=>He.paint()});let xe=!1;G.setGuard(e=>{xe=e});const bt=()=>G.getUnfoldedContent(),Vo=M.observable(Lt(te.type));l(M.effect(()=>{const e=Vo.value,t=N("#editor-body");if(t){ts(e.font);for(const[a,s]of Object.entries(ja(e)))t.style.setProperty(a,s);t.classList.toggle("pe-nowrap",!e.wrap),F.wrap=e.wrap?"soft":"off",ue.wrap=e.wrap?"soft":"off",requestAnimationFrame(()=>{c()&&zo()})}}));let Ho=null;l(()=>{try{Ho?.close()}catch{}});const K=()=>(o.bodyRev.value,{urlPath:p.urlPath.value.trim(),title:p.title.value,description:p.description.value,layout:p.layout.value,status:p.status.value,sortOrder:p.sortOrder.value,showInNav:p.showInNav.value,sidebar:p.sidebar.value,breadcrumbs:p.breadcrumbs.value,bundled:p.bundled.value,tags:p.tags.value,adminTags:p.adminTags.value,category:p.category.value,visibility:p.visibility.value,project:p.project.value,theme:p.theme.value,seoTitle:p.seoTitle.value,seoDesc:p.seoDesc.value,seoSchema:p.seoSchema.value,dconfig:p.dconfig.value,body:bt(),css:null}),Ko=()=>M.untracked?M.untracked(K):K(),Z=M.observable(""),vt=()=>p.cssOpened.peek()&&p.css.peek()!==Z.peek(),_=new Map;let u=null;const $=new Map,B=new Map;let x=[];const re=e=>Oo(e.form)!==e.base,me=()=>[...$.entries()].filter(([,e])=>re(e)).map(([e])=>e),fe=e=>!e.readOnly&&Do({name:e.name,...e.form})!==e.base,Ke=()=>[...B.values()].filter(fe).map(e=>e.name),Vt=()=>Y(Ko())!==o.base.peek(),Ht=e=>e.loaded&&(e===u?Vt():Y(e.state)!==e.base),Pe=()=>[..._.values()].filter(Ht),ds=M.computed(()=>Y(K())!==o.base.value),ps=M.computed(()=>p.cssOpened.value&&p.css.value!==Z.value),us=M.computed(()=>(o.blockRev.value,o.compRev.value,o.docRev.value,Y(K())!==o.base.value||p.cssOpened.value&&p.css.value!==Z.value||Pe().some(e=>e!==u)||me().length>0||Ke().length>0)),ms=M.computed(()=>{o.blockRev.value,o.compRev.value;const e=I(o.active.value);if(e?.kind==="block"){const t=$.get(e.id);return!!(t&&re(t))}if(e?.kind==="component"){const t=B.get(e.id);return!!(t&&fe(t))}return Y(K())!==o.base.value||p.cssOpened.value&&p.css.value!==Z.value}),fs=M.computed(()=>o.curId.value!==null),Uo=M.computed(()=>I(o.active.value)?.kind||""),_o=M.computed(()=>!!o.curId.value&&I(o.active.value)?.kind==="page"),bs=M.computed(()=>p.urlPath.value.trim()||o.curId.value||""),vs=M.computed(()=>Mo({urlPath:p.urlPath.value,dconfig:p.dconfig.value})[0]?.text||""),gs=M.computed(()=>{o.docRev.value;const e=I(o.active.value);return e?e.kind==="block"?o.blockUses.value:e.kind==="component"?qn({name:e.id,readOnly:o.compReadOnly.value,state:o.compReady.value?o.compState.value:""}):Un({urlPath:p.urlPath.value,status:p.status.value,isEdit:!!o.curId.value}):""}),Wo=M.computed(()=>o.compTab.value?"component":o.blockTab.value?"block":o.curId.value!==null?"page":""),ks=M.computed(()=>o.blockTab.value?`[${o.blockTab.value} /]`:""),hs=M.computed(()=>({compiling:"Compiling\u2026",ok:"Compiles",error:"Does not compile"})[o.compState.value]||""),ys=M.computed(()=>o.compTab.value?`<dm-${o.compTab.value}>`:""),ws=M.computed(()=>o.versions.value.some(e=>e.checked.value)),Es=M.computed(()=>o.versions.value.length>0&&o.versions.value.every(e=>e.checked.value)),Ss=M.computed(()=>{const e=o.versions.value.filter(t=>t.checked.value).length;return e>0&&e<o.versions.value.length});b=()=>Pe().length>0||me().length>0||Ke().length>0||vt();const Kt=(e,t,a)=>({title:String(e.title||"").trim()||"Untitled",description:String(e.description||"").trim(),layout:e.layout,status:e.status,sortOrder:parseInt(String(e.sortOrder),10)||99,showInNav:!!e.showInNav,sidebar:!!e.sidebar,...e.breadcrumbs?{}:{breadcrumbs:!1},...e.bundled?{bundled:!0}:{},tags:Array.isArray(e.tags)?e.tags:[],...t?.isEdit&&JSON.stringify(e.adminTags)===t.savedAdminTags?{}:{adminTags:Array.isArray(e.adminTags)?e.adminTags:[]},category:String(e.category||"").trim()||null,visibility:e.visibility||"public",project:e.project||"",seo:{title:String(e.seoTitle||"").trim(),description:String(e.seoDesc||"").trim(),...Yo(e)?{schema:Yo(e)}:t?.isEdit?{schema:null}:{}},dconfig:a,...e.theme?{theme:e.theme}:{}}),Yo=e=>Eo(e.seoSchema).value,Ut=e=>{const t=String(e.dconfig||"").trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}},Ms=()=>p.urlPath.peek().trim()||u?.id||"/";let gt=[],Jo=[];const kt=e=>gt.includes(e)||qt.includes(e);let Oe=new Set;const ht=()=>{const e=[...new Set([...gt,...qt,...$.keys()])],t=wn(e.filter(s=>!$.has(s)),ft);Oe=new Set(Object.keys(t));const a=Oe.size?`Preview shows unsaved changes to ${[...Oe].join(", ")}`:"";return a!==o.heldNote.peek()&&(o.heldNote.value=a),An(t,[...$.entries()].map(([s,r])=>({name:s,content:r.form.content,css:r.form.css,dirty:re(r)})))};let _t="";const Ts=()=>{Go();const e=_t;if(e){_t="";try{Re.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},Re=N("#markdown-preview");k.settings.getCustomCss().then(({css:e})=>{if(!c())return;Z.value=e||"";let t=null;try{t=S.get(pt)}catch{}typeof t=="string"&&t!==Z.peek()?(ta(),p.css.value=t,E.toast("Your unsaved custom CSS is back - the CSS button shows it.",{type:"info"})):p.cssOpened.peek()||(p.css.value=Z.peek()),W(pt)}).catch(()=>{});const Go=()=>{const e=Re?.contentDocument;if(!e?.head)return;const t=p.cssOpened.peek()?p.css.peek():"";if(!t)return;let a=e.getElementById("preview-custom-css");a||(a=e.createElement("style"),a.id="preview-custom-css",e.head.appendChild(a)),a.textContent=t};let Wt=null,Yt=0;const U=(e=!1)=>{clearTimeout(Wt),Wt=setTimeout(async()=>{if(!Re||o.viewMode.peek()==="write"||!o.active.peek()||o.compTab.peek())return;const t=++Yt,a=o.blockTab.peek();o.previewStatus.value="Rendering\u2026";try{let s;if(a&&(!u||!kt(a)))({html:s}=await k.pages.previewFull(mr,{title:a,layout:"blank",breadcrumbs:!1},Bn(a,Bs(a)),ht()));else if(u){const r=K();({html:s}=await k.pages.previewFull(Ms(),Kt(r,u,Ut(r)),r.body,ht()))}else{Re.srcdoc="";return}if(!c()||t!==Yt)return;br(Re,$n(fr(s),a),Ts),o.previewStatus.value="Showing unsaved changes"}catch(s){if(!c()||t!==Yt)return;o.previewStatus.value="Preview failed - "+(s?.message||"unknown error"),Re.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(s?.message||"unknown error").replace(/[<&]/g,r=>r==="<"?"<":"&")+"</body>"}},e?0:500)};l(()=>clearTimeout(Wt));const Jt=Gs(d.find("#markdown-editor"),d.find("#editor-toolbar"),{spacerDefault:A?.layoutOptions?.spacerSize??40});Zs(F,{blocks:()=>mt,openBlock:e=>Ne(e)});const Ue=h.querySelector(".editor-toolbar-right");Ue.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}')`)}),N("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const Cs=ct(`
|
|
125
|
+
</style>`;function ls({kind:d,projects:b,taken:v,project:i="",name:y="",check:u,create:w,register:k=()=>{}}){return new Promise(x=>{const j=E.slideover({title:d==="block"?"New block":"New component",size:"sm",position:"right"});k(()=>{try{j.close()}catch{}});const c=mt(`<div>${hr}</div>`);for(const D of b)c.querySelector("#pe-new-project").append(new Option(D.name||D.slug,D.slug));const A={name:M.observable(y),project:M.observable(i),busy:M.observable(!1),hint:M.observable(d==="block"?"A block is a reusable piece of HTML with {{placeholders}}, used on pages with its short tag or by a collection display. Its name is its tag - fixed once created.":"A component is a custom element - <dm-name> - with a template, props, a script and a style. Its name is its tag - fixed once created."),placeholder:M.observable(d==="block"?"e.g. team-card":"e.g. pricing-table")};A.error=M.computed(()=>A.name.value.trim()?u(A.name.value.trim(),v):""),A.canCreate=M.computed(()=>!A.busy.value&&!!A.name.value.trim()&&!u(A.name.value.trim(),v));let V="";A.create=async()=>{const D=A.name.peek().trim();if(!(!D||A.error.peek()||A.busy.peek())){A.busy.value=!0;try{await w(D,A.project.peek()),V=D,j.close()}catch(se){E.toast(`Not created: ${se.message||se}`,{type:"error"})}finally{A.busy.value=!1}}},A.cancel=()=>j.close(),A.key=D=>{D.key==="Enter"&&(D.preventDefault(),A.create())};const Y=M.applyBindings(A,c);jt(j,()=>{Y.dispose(),x(V)}),j.element.appendChild(c),c.querySelector("#pe-new-project").value=i,j.open(),setTimeout(()=>{const D=c.querySelector("#pe-new-name");D?.focus(),D?.select()},100)})}let F=null,cs=!1;function kr(){cs||!window.R?.use||(cs=!0,On(),R.use((d,b,v)=>{if(F&&!F.alive()){const u=F;F=null,u.dispose()}const i=b?.path||"",y=d?.path||"";if(!F||!Oo.test(i))return v();if(Oo.test(y)){const u=Ha(y);if(u){F.open(u,y);return}return v()}return F.dirty()?E.confirm("You have unsaved changes. Leave anyway? They are kept as drafts for your next visit.").then(u=>{u?(F?.keepDraft(),v()):F&&history.replaceState({pe:1},"","#"+F.address)}):v()}),M.subscribe("router:afterChange",({to:d}={})=>{if(F&&!Oo.test(d?.path||"")){const b=F;F=null,b.dispose()}}))}const ds="editor_prefs";function Pe(){return S.get(ds)||{viewMode:"split",fullscreen:!1}}const yr=()=>{try{return Pe()||{}}catch{return{}}};function Ue(d,b){const v=Pe();v[d]=b,S.set(ds,v)}const ps="editor-workspace.tabs",us="editor-workspace.folded",ft="editor-workspace.css",wr="/__block-preview",Sr=d=>d.replace(/<\/head>/i,"<style>body.dm-cloaked{opacity:1 !important;transition:none !important}</style></head>");function Er(d,b,v=()=>{}){let i=0;try{i=d.contentWindow.scrollY||0}catch{}d.addEventListener("load",()=>{try{d.contentWindow.scrollTo(0,i)}catch{}v()},{once:!0}),d.srcdoc=b}const Tr=["urlPath","title","description","layout","status","sortOrder","category","visibility","project","theme","seoTitle","seoDesc","seoSchema","dconfig"],Mr=["showInNav","sidebar","breadcrumbs","bundled"],ms=()=>({urlPath:"",title:"",description:"",layout:"",status:"draft",sortOrder:"99",showInNav:!1,sidebar:!1,breadcrumbs:!0,bundled:!1,tags:[],adminTags:[],category:"",visibility:"public",project:"",theme:"",seoTitle:"",seoDesc:"",seoSchema:"",dconfig:"",body:"",css:null});export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(d){if(kr(),F){const e=F;F=null,e.dispose()}const b=window.location.hash.replace(/^#/,""),v=[],i=e=>(v.push(e),e);let y=()=>!1,u=()=>{},w=()=>{};const k=d.get(0),x=k.querySelector("#pe-workspace"),j={address:b,alive:()=>x?.isConnected===!0,dirty:()=>y(),keepDraft:()=>u(),open:(e,t)=>w(e,t),dispose:()=>{v.splice(0).forEach(e=>{try{e()}catch{}})}};F=j;const c=()=>F===j&&j.alive();if(yr().closeSidebar===!0){const e=document.querySelector("#admin-sidebar");e&&!Ja(e)&&!window.matchMedia("(max-width: 768px)").matches&&(Wa(e,!0,!1),i(()=>{e.isConnected&&Ja(e)&&Wa(e,!1,!1)}))}const A=E.loader(k,{type:"dots"});let[V,Y,D,se,_t,T,ne,Oe]=await Promise.all([h.layouts.get().catch(()=>({})),h.settings.get().catch(()=>({})),h.pages.list().catch(()=>[]),h.projects.list().catch(()=>[]),h.pages.tags().catch(()=>[]),h.listTags.list("pages").then(e=>e.map(t=>t.name)).catch(()=>[]),h.get("/blocks?usage=1").catch(()=>h.blocks.list()).catch(()=>[]),h.components.list().catch(()=>[])]);if(!c()){A.destroy();return}A.destroy();const O=e=>k.querySelector(e);if(D=Array.isArray(D)?D:[],cn(O("#field-theme"),{includeDefault:!0}),await Rr(O("#field-visibility")),!c())return;const re=Object.keys(V||{});Or(O("#field-layout"),V,re[0]||"default");const Vo=e=>re.includes(e)?e:re[0]||e||"default";for(const e of se)for(const t of["#field-project","#block-project","#comp-project"])O(t).append(new Option(e.name||e.slug,e.slug));let ie=Array.isArray(ne)?ne:[],bt=Ya(ie);const vt=new Map(ie.map(e=>[e.name,e.updatedAt])),He=new Map(ie.map(e=>[e.name,e.usage])),be=new Set(ie.map(e=>e.name)),_o=async()=>{const e=await h.get("/blocks?usage=1").catch(()=>null);if(!(!c()||!Array.isArray(e))){ie=e,bt=Ya(ie);for(const t of ie)be.add(t.name),He.set(t.name,t.usage),vt.has(t.name)||vt.set(t.name,t.updatedAt)}},Kt=Array.isArray(Y?.footer?.blocks)?Y.footer.blocks.filter(e=>typeof e=="string"):[];let Je=Array.isArray(Oe)?Oe.filter(e=>typeof e?.name=="string"):[];const le=new Set(Je.map(e=>e.name)),Ko=async()=>{const e=await h.components.list().catch(()=>null);if(!(!c()||!Array.isArray(e))){Je=e.filter(t=>typeof t?.name=="string");for(const t of Je)le.add(t.name)}},Z=Pe(),p={urlPath:M.observable(""),title:M.observable(""),description:M.observable(""),layout:M.observable(Vo("default")),status:M.observable("draft"),sortOrder:M.observable("99"),showInNav:M.observable(!1),sidebar:M.observable(!1),breadcrumbs:M.observable(!0),bundled:M.observable(!1),tags:M.observable([]),adminTags:M.observable([]),category:M.observable(""),visibility:M.observable("public"),project:M.observable(""),theme:M.observable(""),seoTitle:M.observable(""),seoDesc:M.observable(""),seoSchema:M.observable(""),dconfig:M.observable(""),css:M.observable(""),cssOpened:M.observable(!1)},a={bodyRev:M.observable(0),base:M.observable(""),saving:M.observable(!1),viewMode:M.observable(jo(Z.viewMode)),fullscreen:M.observable(!!Z.fullscreen),explorerOn:M.observable(Z.explorer??!window.matchMedia("(max-width: 1024px)").matches),dockOn:M.observable(!!Z.dock),dockTab:M.observable("details"),seoSchemaNote:M.observable(""),cssMode:M.observable(!1),folded:M.observable(!1),previewStatus: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(""),tabs:M.observable([]),active:M.observable(""),curId:M.observable(null),blockTab:M.observable(""),blockPart:M.observable("html"),blockReady:M.observable(!1),blockNote:M.observable(""),blockUses:M.observable(""),blockProject:M.observable(""),blockBundled:M.observable(!1),blockWhere:M.observable(""),compTab:M.observable(""),compPart:M.observable("template"),compReady:M.observable(!1),compNote:M.observable(""),compReadOnly:M.observable(!1),compState:M.observable(""),compErrors:M.observable(""),compProject:M.observable(""),compBundled:M.observable(!1),blockRev:M.observable(0),compRev:M.observable(0),docRev:M.observable(0),filter:M.observable(""),secHere:M.observable(Z.secHere!==!1),secPages:M.observable(Z.secPages!==!1),secBlocks:M.observable(Z.secBlocks!==!1),secComps:M.observable(Z.secComps!==!1),thisPage:M.observable([]),pageRows:M.observable([]),blockRows:M.observable([]),compRows:M.observable([])},_=O("#markdown-editor"),ve=O("#css-editor"),fs=Ka(_),bs=Ka(ve);i(()=>fs.destroy()),i(()=>bs.destroy());const ce=document.createElement("div");ce.className="editor-line-numbers editor-line-numbers--foldable",_.parentElement.insertBefore(ce,_);const We=ns(_,"md"),Ut=ns(ve,"css");i(()=>{We.destroy(),Ut.destroy()});const Uo=()=>{We.paint(),Ut.paint()},Q=dn(_,ce,{onFoldChange:()=>We.paint()});let gt=!1;Q.setGuard(e=>{gt=e});const Ht=()=>Q.getUnfoldedContent(),Jt=()=>{Q.unfoldAll(),a.folded.value=!1,We.paint()},vs=e=>{if(e?.kind==="page")Jt(),Ke(_,"md");else if(e?.kind==="block")oe&&Ke(oe.textarea,"html"),me&&Ke(me.textarea,"css");else if(e?.kind==="component"){if(N.get(e.id)?.readOnly)return;for(const[o,s]of Object.entries({template:"html",props:"json",script:"js",style:"css"}))ae[o]&&Ke(ae[o].textarea,s)}},Ho=e=>{e.error?E.toast(e.error,{type:"warning"}):E.toast(e.unchanged?"Already tidy":"Formatted",{type:"info",duration:900})},Jo=M.observable(Bt(Z.type)),Wo=M.observable(qt(Z.code));i(M.effect(()=>{const e=Wo.value;dr(e),O("#editor-body")?.style.setProperty("--pe-tab",String(e.size))})),i(M.effect(()=>{const e=Jo.value,t=O("#editor-body");if(t){is(e.font);for(const[o,s]of Object.entries(_a(e)))t.style.setProperty(o,s);t.classList.toggle("pe-nowrap",!e.wrap),_.wrap=e.wrap?"soft":"off",ve.wrap=e.wrap?"soft":"off",requestAnimationFrame(()=>{c()&&Uo()})}}));let Yo=null;i(()=>{try{Yo?.close()}catch{}});const K=()=>(a.bodyRev.value,{urlPath:p.urlPath.value.trim(),title:p.title.value,description:p.description.value,layout:p.layout.value,status:p.status.value,sortOrder:p.sortOrder.value,showInNav:p.showInNav.value,sidebar:p.sidebar.value,breadcrumbs:p.breadcrumbs.value,bundled:p.bundled.value,tags:p.tags.value,adminTags:p.adminTags.value,category:p.category.value,visibility:p.visibility.value,project:p.project.value,theme:p.theme.value,seoTitle:p.seoTitle.value,seoDesc:p.seoDesc.value,seoSchema:p.seoSchema.value,dconfig:p.dconfig.value,body:Ht(),css:null}),Go=()=>M.untracked?M.untracked(K):K(),ee=M.observable(""),ht=()=>p.cssOpened.peek()&&p.css.peek()!==ee.peek(),U=new Map;let m=null;const L=new Map,N=new Map;let P=[];const de=e=>Do(e.form)!==e.base,ge=()=>[...L.entries()].filter(([,e])=>de(e)).map(([e])=>e),he=e=>!e.readOnly&&Fo({name:e.name,...e.form})!==e.base,Ye=()=>[...N.values()].filter(he).map(e=>e.name),Wt=()=>W(Go())!==a.base.peek(),Yt=e=>e.loaded&&(e===m?Wt():W(e.state)!==e.base),Re=()=>[...U.values()].filter(Yt),gs=M.computed(()=>W(K())!==a.base.value),hs=M.computed(()=>p.cssOpened.value&&p.css.value!==ee.value),ks=M.computed(()=>(a.blockRev.value,a.compRev.value,a.docRev.value,W(K())!==a.base.value||p.cssOpened.value&&p.css.value!==ee.value||Re().some(e=>e!==m)||ge().length>0||Ye().length>0)),ys=M.computed(()=>{a.blockRev.value,a.compRev.value;const e=I(a.active.value);if(e?.kind==="block"){const t=L.get(e.id);return!!(t&&de(t))}if(e?.kind==="component"){const t=N.get(e.id);return!!(t&&he(t))}return W(K())!==a.base.value||p.cssOpened.value&&p.css.value!==ee.value}),ws=M.computed(()=>a.curId.value!==null),Xo=M.computed(()=>I(a.active.value)?.kind||""),Zo=M.computed(()=>!!a.curId.value&&I(a.active.value)?.kind==="page"),Ss=M.computed(()=>p.urlPath.value.trim()||a.curId.value||""),Es=M.computed(()=>Ro({urlPath:p.urlPath.value,dconfig:p.dconfig.value})[0]?.text||""),Ts=M.computed(()=>{a.docRev.value;const e=I(a.active.value);return e?e.kind==="block"?a.blockUses.value:e.kind==="component"?Hn({name:e.id,readOnly:a.compReadOnly.value,state:a.compReady.value?a.compState.value:""}):Xn({urlPath:p.urlPath.value,status:p.status.value,isEdit:!!a.curId.value}):""}),Qo=M.computed(()=>a.compTab.value?"component":a.blockTab.value?"block":a.curId.value!==null?"page":""),Ms=M.computed(()=>a.blockTab.value?`[${a.blockTab.value} /]`:""),Cs=M.computed(()=>({compiling:"Compiling\u2026",ok:"Compiles",error:"Does not compile"})[a.compState.value]||""),xs=M.computed(()=>a.compTab.value?`<dm-${a.compTab.value}>`:""),Ps=M.computed(()=>a.versions.value.some(e=>e.checked.value)),Os=M.computed(()=>a.versions.value.length>0&&a.versions.value.every(e=>e.checked.value)),Rs=M.computed(()=>{const e=a.versions.value.filter(t=>t.checked.value).length;return e>0&&e<a.versions.value.length});y=()=>Re().length>0||ge().length>0||Ye().length>0||ht();const Gt=(e,t,o)=>({title:String(e.title||"").trim()||"Untitled",description:String(e.description||"").trim(),layout:e.layout,status:e.status,sortOrder:parseInt(String(e.sortOrder),10)||99,showInNav:!!e.showInNav,sidebar:!!e.sidebar,...e.breadcrumbs?{}:{breadcrumbs:!1},...e.bundled?{bundled:!0}:{},tags:Array.isArray(e.tags)?e.tags:[],...t?.isEdit&&JSON.stringify(e.adminTags)===t.savedAdminTags?{}:{adminTags:Array.isArray(e.adminTags)?e.adminTags:[]},category:String(e.category||"").trim()||null,visibility:e.visibility||"public",project:e.project||"",seo:{title:String(e.seoTitle||"").trim(),description:String(e.seoDesc||"").trim(),...ea(e)?{schema:ea(e)}:t?.isEdit?{schema:null}:{}},dconfig:o,...e.theme?{theme:e.theme}:{}}),ea=e=>Po(e.seoSchema).value,Xt=e=>{const t=String(e.dconfig||"").trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}},As=()=>p.urlPath.peek().trim()||m?.id||"/";let kt=[],ta=[];const yt=e=>kt.includes(e)||Kt.includes(e);let Ae=new Set;const wt=()=>{const e=[...new Set([...kt,...Kt,...L.keys()])],t=Pn(e.filter(s=>!L.has(s)),vt);Ae=new Set(Object.keys(t));const o=Ae.size?`Preview shows unsaved changes to ${[...Ae].join(", ")}`:"";return o!==a.heldNote.peek()&&(a.heldNote.value=o),In(t,[...L.entries()].map(([s,r])=>({name:s,content:r.form.content,css:r.form.css,dirty:de(r)})))};let Zt="";const $s=()=>{oa();const e=Zt;if(e){Zt="";try{$e.contentDocument?.querySelector(`[data-block="${CSS.escape(e)}"]`)?.scrollIntoView({block:"center"})}catch{}}},$e=O("#markdown-preview");h.settings.getCustomCss().then(({css:e})=>{if(!c())return;ee.value=e||"";let t=null;try{t=S.get(ft)}catch{}typeof t=="string"&&t!==ee.peek()?(ia(),p.css.value=t,E.toast("Your unsaved custom CSS is back - the CSS button shows it.",{type:"info"})):p.cssOpened.peek()||(p.css.value=ee.peek()),J(ft)}).catch(()=>{});const oa=()=>{const e=$e?.contentDocument;if(!e?.head)return;const t=p.cssOpened.peek()?p.css.peek():"";if(!t)return;let o=e.getElementById("preview-custom-css");o||(o=e.createElement("style"),o.id="preview-custom-css",e.head.appendChild(o)),o.textContent=t};let Qt=null,eo=0;const H=(e=!1)=>{clearTimeout(Qt),Qt=setTimeout(async()=>{if(!$e||a.viewMode.peek()==="write"||!a.active.peek()||a.compTab.peek())return;const t=++eo,o=a.blockTab.peek();a.previewStatus.value="Rendering\u2026";try{let s;if(o&&(!m||!yt(o)))({html:s}=await h.pages.previewFull(wr,{title:o,layout:"blank",breadcrumbs:!1},Vn(o,_s(o)),wt()));else if(m){const r=K();({html:s}=await h.pages.previewFull(As(),Gt(r,m,Xt(r)),r.body,wt()))}else{$e.srcdoc="";return}if(!c()||t!==eo)return;Er($e,Bn(Sr(s),o),$s),a.previewStatus.value="Showing unsaved changes"}catch(s){if(!c()||t!==eo)return;a.previewStatus.value="Preview failed - "+(s?.message||"unknown error"),$e.srcdoc='<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;padding:1rem;color:#b91c1c;">Preview failed: '+String(s?.message||"unknown error").replace(/[<&]/g,r=>r==="<"?"<":"&")+"</body>"}},e?0:500)};i(()=>clearTimeout(Qt));const to=an(d.find("#markdown-editor"),d.find("#editor-toolbar"),{spacerDefault:Y?.layoutOptions?.spacerSize??40,beforeFormat:()=>Jt()});rn(_,{blocks:()=>bt,openBlock:e=>Be(e)});const Ge=k.querySelector(".editor-toolbar-right");Ge.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}')`)}),O("#fullscreen-btn")?.setAttribute("data-on-click","toggleFullscreen");const Ls=mt(`
|
|
108
126
|
<span class="pe-toolbar-extras" style="display:contents">
|
|
109
127
|
<button type="button" class="editor-view-btn" data-tip="Edit Custom CSS" data-on-click="toggleCss"
|
|
110
128
|
data-bind-class="cssMode.value && 'active'"><span>CSS</span></button>
|
|
111
|
-
<button type="button" class="editor-view-btn" data-tip="Format
|
|
129
|
+
<button type="button" class="editor-view-btn" data-tip="Format (Shift+Alt+F)" data-on-click="formatMarkdown"><span data-icon="code"></span></button>
|
|
112
130
|
<button type="button" class="editor-view-btn" data-tip="Fold / unfold all shortcodes" data-on-click="toggleFold"
|
|
113
131
|
style="font-size:9px;letter-spacing:-1px;" data-bind-class="folded.value && 'active'">
|
|
114
132
|
<!-- I.scan swaps an icon span for an <svg>: the bindings go on a wrapper that stays. -->
|
|
@@ -116,8 +134,8 @@ The quick brown fox - 0O 1lI {} [] () => ==
|
|
|
116
134
|
<span class="pe-fold-icon" hidden data-bind-hidden="!folded.value"><span data-icon="chevrons-right" data-icon-size="10"></span></span>
|
|
117
135
|
</button>
|
|
118
136
|
<span class="editor-toolbar-sep"></span>
|
|
119
|
-
</span>`);Ue.prepend(Cs),Ue.before(N(".pe-block-bar")),Ue.before(N(".pe-comp-bar"));const Xo=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),Qo=N("#field-tags");let yt=null;yt=Qo?E.pillbox(Qo,{data:Xo(ut),value:[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{yt&&(p.tags.value=[...yt.getValue()])}}):null;const Zo=N("#field-admin-tags");let wt=null;wt=Zo?E.pillbox(Zo,{data:Xo(Ce),value:[],creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{wt&&(p.adminTags.value=[...wt.getValue()])}}):null;const ea=()=>{o.bodyRev.value=o.bodyRev.peek()+1},be=()=>{o.blockRev.value=o.blockRev.peek()+1},ve=()=>{o.compRev.value=o.compRev.peek()+1},_e=()=>{o.docRev.value=o.docRev.peek()+1},ta=()=>{p.cssOpened.peek()||(p.css.peek()||(p.css.value=Z.peek()),p.cssOpened.value=!0,Js(ue))},We=e=>{for(const t of vr)p[t].value=e[t]==null?"":String(e[t]);for(const t of gr)p[t].value=!!e[t];p.tags.value=Array.isArray(e.tags)?e.tags:[],yt?.setValue(p.tags.peek()),Array.isArray(e.adminTags)&&(p.adminTags.value=e.adminTags),wt?.setValue(p.adminTags.peek()),Lo(N("#field-visibility"),p.visibility.peek()),G.unfoldAll(),o.folded.value=!1,xe=!0,F.value=e.body==null?"":String(e.body),xe=!1,G.refresh(),zo(),ea()},oa=(e,t)=>({urlPath:t||"",title:e?.title||"",description:e?.description||"",layout:Io(e?.layout||"default"),status:e?.status||"draft",sortOrder:String(e?.sortOrder??99),showInNav:!!e?.showInNav,sidebar:!!e?.sidebar,breadcrumbs:e?e.breadcrumbs!==!1:!0,bundled:!!e?.bundled,tags:Array.isArray(e?.tags)?e.tags:[],adminTags:Array.isArray(e?.adminTags)?e.adminTags:[],category:e?.category||"",visibility:typeof e?.visibility=="string"&&e.visibility?e.visibility:"public",project:hn({isEdit:!!t,page:e,projects:J,fromHash:t?"":jt()||"",urlPath:t||""}),theme:e?.theme||"",seoTitle:e?.seo?.title||"",seoDesc:e?.seo?.description||"",seoSchema:rn(e?.seo?.schema),dconfig:e?.dconfig?JSON.stringify(e.dconfig,null,2):"",body:e?.content||"",css:null}),aa=()=>{u&&(u.state=K(),u.at=Date.now())},sa=e=>{u!==e&&(aa(),u=e,We(e.state),o.base.value=e.base,o.curId.value=e.id,e.at=Date.now(),St(),_e())},xs=()=>{aa(),u=null;const e=ls();We(e),o.base.value=Y(e),o.curId.value=null,St(),_e()};We(ls()),o.base.value=Y(K());const W=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}},na=(e,t)=>({name:e.name,...e.form,base:e.base,at:t});y=()=>{try{for(const t of Pe())S.set(je(t.id),Fa(t===u?Ko():t.state,t.base));vt()&&S.set(pt,p.css.peek());const e=new Date().toISOString();for(const t of me()){const a=$.get(t);S.set(Fe(t),{form:{...a.form},base:a.base,at:e})}for(const t of Ke())S.set(qe(t),na(B.get(t),e))}catch{}};const ra=e=>{b()&&(y(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ra),l(()=>window.removeEventListener("beforeunload",ra));let Ye=!1,Et=null;const St=()=>{Ye=!1,Et=null,o.versions.value=[],o.compare.value=null,o.histError.value="",o.dockOn.peek()&&o.dockTab.peek()==="history"&&(u?.isEdit?le():o.dockTab.value="details")},le=async()=>{const e=u;if(e?.isEdit){Ye=!0,o.histLoading.value=!0,o.histError.value="";try{const t=(await k.versions.list(e.id)).versions||[];if(!c()||u!==e)return;o.versions.value=t.map(a=>({...a,when:Ie(a.createdAt),author:a.author||"",label:a.label||"",who:[a.author,a.label].filter(Boolean).join(" - "),checked:M.observable(!1)}))}catch(t){u===e&&(o.histError.value="Failed to load versions: "+t.message)}finally{u===e&&(o.histLoading.value=!1)}}},Gt=e=>o.versions.peek().find(t=>t.filename===e),Xt=async e=>{const t=u;try{const a=await k.versions.get(t.id,e.filename);if(!c()||u!==t)return;Et=e.filename,o.compare.value={label:es(e),old:a.content,current:bt()}}catch(a){E.toast("Failed to load version: "+a.message,{type:"error"})}},Ps=async()=>{const e=u;if(!e?.isEdit)return;const t=await k.pages.get(e.id).catch(()=>null);if(!c()||u!==e||!t)return;e.page=t;const a=oa(t,e.id);e.base=Y(a),e.savedAdminTags=JSON.stringify(a.adminTags),We(a),o.base.value=e.base,St(),o.dockOn.peek()&&o.dockTab.peek()==="history"&&le(),U()},la=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 k.versions.restore(u.id,e),E.toast("Page restored.",{type:"success"}),await Ps()}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},Os=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await k.versions.delete(u.id,e.filename),E.toast("Version deleted.",{type:"success"}),await le()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},$e=new Map,Rs=e=>{if(!$e.has(e)){const t=I(e);$e.set(e,{id:e,kind:t.kind,kindText:t.kind==="page"?"Page":t.kind==="component"?"Component":"Block",label:M.observable(""),title:M.observable(""),active:M.observable(!1),dirty:M.observable(!1)})}return $e.get(e)},ia=e=>{const t=e===u?p.title.peek():e?.loaded?e.state.title:D.find(a=>a.urlPath===e?.id)?.title;return String(t||"").trim()||e?.id||"New page"};let ca=!0,da="";const $s=()=>{if(ca)return;const e={ids:x,active:o.active.peek()},t=JSON.stringify(e);if(t!==da){da=t;try{S.set(ns,e)}catch{}}},V=()=>{const e=x.map(Rs);for(const t of e){const a=I(t.id);let s,r;if(a.kind==="page"){const i=_.get(a.id);s=ia(i),r=i?.loaded?Ht(i):Me(a),t.title.value=a.id?`${s} (${a.id})`:"New page"}else if(a.kind==="component"){const i=B.get(a.id);s=a.id,r=i?fe(i):Me(a),t.title.value=`Component <dm-${a.id}>`}else{const i=$.get(a.id);s=a.id,r=i?re(i):Oe.has(a.id)||Me(a),t.title.value=`Block ${a.id}`}t.label.peek()!==s&&(t.label.value=s),t.dirty.peek()!==r&&(t.dirty.value=r);const n=t.id===o.active.peek();t.active.peek()!==n&&(t.active.value=n)}e.map(t=>t.id).join("|")!==o.tabs.peek().map(t=>t.id).join("|")&&(o.tabs.value=e),$s()};let ge=new Set;try{const e=S.get(rs);Array.isArray(e)&&(ge=new Set(e.filter(t=>typeof t=="string")))}catch{}const pa=new Map,Qt=e=>{let t=pa.get(e.key);return t?(t.label.peek()!==e.label&&(t.label.value=e.label),t.meta.peek()!==(e.meta||"")&&(t.meta.value=e.meta||""),t.status.peek()!==(e.status||"")&&(t.status.value=e.status||""),t.folder.peek()!==!!e.folder&&(t.folder.value=!!e.folder)):(t={...e,label:M.observable(e.label),meta:M.observable(e.meta||""),status:M.observable(e.status||""),folder:M.observable(!!e.folder),active:M.observable(!1),dirty:M.observable(!1),folded:M.observable(!1),title:""},pa.set(e.key,t)),t.title=e.kind==="block"||e.kind==="component"?e.meta?`${e.id} - ${e.meta}`:e.id:e.kind==="folder"?e.meta:`${e.label} (${e.meta})`,t},Zt=new Map,z=()=>{const e=o.filter.peek(),t=I(o.active.peek()),a=t?.kind==="page"?t.id:null,s=t?.kind==="block"?t.id:null,r=t?.kind==="component"?t.id:null,n=new Set(Pe().map(f=>f.id)),i=Kn(D,{filter:e,folded:ge}).map(Qt);for(const f of i){const T=f.kind==="page"&&f.id===a,j=f.kind==="page"&&(n.has(f.id)||!_.get(f.id)?.loaded&&Me({kind:"page",id:f.id})),de=ge.has(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j),f.folded.peek()!==de&&(f.folded.value=de)}const g=jn(ae,e).map(Qt),O=f=>{const T=$.get(f);return T?re(T):Oe.has(f)||Me({kind:"block",id:f})};for(const f of g){const T=f.id===s,j=O(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j)}const X=Fn(Ve,e).map(Qt),Ee=f=>{const T=B.get(f);return T?fe(T):Me({kind:"component",id:f})};for(const f of X){const T=f.id===r,j=Ee(f.id);f.active.peek()!==T&&(f.active.value=T),f.dirty.peek()!==j&&(f.dirty.value=j)}const yo=u?On(gt,qt,pe):[],Na=(f,T,j)=>(Zt.has(f)||Zt.set(f,{key:f,id:T,label:j,kindText:"",title:"",active:M.observable(!1),dirty:M.observable(!1)}),Zt.get(f)),wo=yo.map(f=>{const T=Na(`block:${f.name}`,f.name,f.name);T.kindText=f.footer?"Footer":"Block",T.title=`${f.name} - ${f.footer?"in the footer, on every page":"a block on this page"}`;const j=f.name===s,de=O(f.name);return T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),T});for(const f of Jo){const T=Na(`component:${f}`,f,f);T.kindText="Component",T.title=`<dm-${f}> - a component on this page`;const j=f===r,de=Ee(f);T.active.peek()!==j&&(T.active.value=j),T.dirty.peek()!==de&&(T.dirty.value=de),wo.push(T)}const Dt=(f,T)=>f.map(j=>j.key).join("|")===T.map(j=>j.key).join("|");Dt(i,o.pageRows.peek())||(o.pageRows.value=i),Dt(g,o.blockRows.peek())||(o.blockRows.value=g),Dt(X,o.compRows.peek())||(o.compRows.value=X),Dt(wo,o.thisPage.peek())||(o.thisPage.value=wo)},ua=async()=>{const e=await k.pages.list().catch(()=>null);!c()||!Array.isArray(e)||(D=e,z(),V())},ee=()=>{const e=I(o.active.peek()),t=e?Ya(e):"/pages/new",a=window.location.hash.replace(/^#/,"");L.address=a.split("?")[0]===t?a:t,a.split("?")[0]!==t?history.replaceState({pe:1},"","#"+t):history.state?.pe||history.replaceState({pe:1},"",window.location.hash)},As=async e=>{if(e.loading)return e.loading;e.loading=(async()=>{let t=null;if(e.isEdit){if(t=await k.pages.get(e.id).catch(()=>null),!c())return!1;if(!t)return E.toast(`Page ${e.id} not found.`,{type:"error"}),!1}e.page=t;const a=oa(t,e.id);e.state=a,e.base=Y(a),e.savedAdminTags=JSON.stringify(a.adminTags),e.loaded=!0;let s=null;try{s=S.get(je(e.id))}catch{}const r=yn(s),n=Co({kind:"page",id:e.id});if(r){W(je(e.id));const i=Y(r.state)!==e.base,g=kn(r,e.base),O={...a,...r.state,adminTags:a.adminTags};if(i&&n&&!g)e.state=O,E.toast(`Your unsaved changes to ${e.id||"the new page"} are back.`,{type:"info"});else if(i){const X=r.at?` (${Ie(r.at)})`:"",Ee=g?" 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 ${e.id||"a new page"}${X}. Restore them?${Ee}`)&&c()&&(e.state=O)}}return!0})();try{return await e.loading}finally{e.loading=null}},ma=e=>(_.has(e)||_.set(e,{kind:"page",id:e,isEdit:!!e,loaded:!1,page:null,state:null,base:"",savedAdminTags:"[]",at:0}),_.get(e)),ie=async e=>{const t=Q({kind:"page",id:e});({ids:x}=$o(x,t)),o.active.value=t,o.blockTab.value="",co();const a=ma(e);if(V(),!a.loaded){o.previewStatus.value=`Opening ${e||"a new page"}\u2026`;const s=await As(a);if(!c()||!x.includes(t)||_.get(e)!==a)return;if(!s){fa(t);return}if(o.active.peek()!==t){V(),z();return}}sa(a),ee(),V(),z(),U(),G.refresh(),F.focus()};w=async(e,t)=>{if(L.address=t,e.kind==="page"){ie(e.id);return}if(!e.id){(e.kind==="block"?$t:tt)({project:jt(t)||""});return}const a=e.kind==="block"?pe:se;if(a.has(e.id)||await(e.kind==="block"?Fo:qo)(),!!c()){if(!a.has(e.id)){E.toast(`There is no ${e.kind} called "${e.id}".`,{type:"warning"}),ee(),z();return}e.kind==="block"?Ne(e.id):et(e.id)}};const fa=e=>{const t=o.active.peek(),a=In(x,e,t);x=a.ids,$e.delete(e);const s=I(e),r=a.active!==t?I(a.active):null;if(s.kind==="page"){if(_.delete(s.id),u?.id===s.id){const n=[..._.values()].filter(i=>i.loaded&&i.id!==s.id).sort((i,g)=>g.at-i.at)[0];n?sa(n):xs()}}else s.kind==="component"?(B.delete(s.id),o.compTab.peek()===s.id&&co()):($.delete(s.id),o.blockTab.peek()===s.id&&(o.blockTab.value="",o.blockReady.value=!1));if(o.active.value=a.active,r?.kind==="page"){ie(r.id);return}if(r?.kind==="block"){Ne(r.id);return}if(r?.kind==="component"){et(r.id);return}ee(),_e(),be(),ve(),V(),z(),U()},Je=e=>{const t=I(e);if(!t||!x.includes(e))return;let a="";try{if(t.kind==="page"){const s=_.get(t.id);s&&Ht(s)&&(S.set(je(s.id),Fa(s===u?K():s.state,s.base)),To({kind:"page",id:s.id}),a=ia(s))}else if(t.kind==="component"){const s=B.get(t.id);s&&fe(s)&&(S.set(qe(t.id),na(s,new Date().toISOString())),To({kind:"component",id:t.id}),a=t.id)}else{const s=$.get(t.id);s&&re(s)&&(S.set(Fe(t.id),{form:{...s.form},base:s.base,at:new Date().toISOString()}),To({kind:"block",id:t.id}),a=t.id)}}catch{}fa(e),a&&E.toast(`Changes to "${a}" kept as a draft - back when you open it again.`,{type:"info"})},Mt=e=>{const t=I(e);t&&(t.kind==="page"?ie(t.id):t.kind==="component"?et(t.id):Ne(t.id))},ba=new Set,Ds=async e=>{const t=[];for(const a of e){const s=$.get(a);if(!s)continue;const r={...s.form};try{const n=_a(r,s.block?.meta);await k.blocks.put(a,n),s.block={...s.block,meta:n.meta},s.saved=r,s.base=Oo(r),ft.set(a,new Date().toISOString()),c()&&W(Fe(a)),t.push(a)}catch(n){E.toast(`Block "${a}" not saved: ${n.message||"Unknown error"}`,{type:"error"})}}return t.forEach(a=>ba.add(a)),t.length&&(be(),lt("#/blocks")),t},Ns=async e=>{const t=e===u?K():e.state,a=Eo(t.seoSchema).error;if(a)return E.toast(`${e.id||"The new page"} not saved - structured data: ${a}`,{type:"error"}),e===u&&(o.seoSchemaNote.value=a,o.dockOn.value=!0,o.dockTab.value="seo"),!1;const s=Kt(t,e,Ut(t)),r=e.id;try{if(e.isEdit){const n={frontmatter:s,body:t.body};t.urlPath!==e.id&&(n.newUrlPath=t.urlPath),await k.pages.update(e.id,n)}else await k.pages.create({urlPath:t.urlPath,frontmatter:s,body:t.body})}catch(n){return E.toast(`${e.id||"The new page"} not saved: ${n.message||"Unknown error"}`,{type:"error"}),!1}if(!c())return!0;if(e.savedAdminTags=JSON.stringify(t.adminTags),e.base=Y(t),e===u?o.base.value=e.base:e.state=t,W(je(r)),!e.isEdit||t.urlPath!==e.id){_.delete(r),e.id=t.urlPath,e.isEdit=!0,_.set(e.id,e);const n=Q({kind:"page",id:r}),i=Q({kind:"page",id:e.id});$e.delete(n),{ids:x}=Wn(x,n,i,o.active.peek()),o.active.peek()===n&&(o.active.value=i),e===u&&(o.curId.value=e.id,ee())}return!0},Ls=async e=>{const t=[];for(const a of e){const s=B.get(a);if(!s||s.readOnly)continue;if(Ot===s?(clearTimeout(Pt),Ot=null,ve(),Ze(s)):s.state||Ze(s),s.compiling&&await s.compiling,!c())return t;if(s.state!=="ok"){E.toast(`Component "${a}" not saved: ${s.state==="error"?"it does not compile - fix the errors first.":"it has not compiled yet - try again in a moment."}`,{type:"warning"});continue}const r={...s.form},n={...s.meta||{}};delete n.bundled,r.project?n.project=r.project:delete n.project;try{await k.components.put(a,{source:r.source,bundled:r.bundled,meta:n}),s.meta=n,s.saved=r,s.base=Do({name:a,...r});const i=Ve.find(g=>g.name===a);i&&(i.bundled=r.bundled,i.meta=n),c()&&W(qe(a)),t.push(a)}catch(i){E.toast(`Component "${a}" not saved: ${i.message||"Unknown error"}`,{type:"error"})}}return t.length&&(ve(),lt("#/components")),t};let eo=!1;const to=async(e=!1)=>{if(o.saving.peek()||eo)return;eo=!0;let t,a,s=[];try{if(await ga(),!c())return;const n=I(o.active.peek());if(e)t=Pe(),a=me(),s=Ke();else if(n?.kind==="block")t=[],a=$.has(n.id)&&re($.get(n.id))?[n.id]:[];else if(n?.kind==="component")t=[],a=[],s=B.has(n.id)&&fe(B.get(n.id))?[n.id]:[];else if(n?.kind==="page"&&u)a=me().filter(kt),t=!u.isEdit||Vt()||!a.length?[u]:[];else return;if(!t.length&&!a.length&&!s.length){E.toast("Nothing to save.",{type:"info"});return}for(const g of t){const O=Mo(g===u?K():g.state);if(O.length){E.toast(`${g.id||"New page"}: ${O[0].text}`,{type:"warning"}),g!==u&&await ie(g.id),o.dockOn.value=!0,o.dockTab.value=O[0].field==="dconfig"?"dconfig":"details",dt("dock",!0);return}}const i=Dn(a,Object.fromEntries(a.map(g=>[g,Wa(ze.get(g),u?.id||"")])),ba);if(i&&(!await E.confirm(i)||!c()))return}finally{eo=!1}const r=t.length>0&&vt();o.saving.value=!0;try{let n=0;for(const O of t)await Ns(O)&&n++;if(!c())return;if(r){const O=p.css.peek();await k.settings.saveCustomCss(O),Z.value=O,W(pt)}const i=await Ds(a);if(!c())return;const g=await Ls(s);if((n||i.length||g.length)&&E.toast(Nn({pages:n,css:r,blocks:i,components:g}),{type:"success"}),_e(),!c())return;V(),z(),n&&(lt("#/pages"),St(),ua())}catch(n){E.toast(`Save failed: ${n.message||"Unknown error"}`,{type:"error"})}finally{o.saving.value=!1}};let oe=null,Ge=null;const Ae=new Map,De=new Map,va=()=>{const e=$.get(o.blockTab.peek());!e||!oe||(o.blockReady.value=!1,oe.setValue(e.form.content),Ge.setValue(e.form.css),o.blockProject.value=e.form.project,Lo(N("#block-project"),e.form.project),o.blockBundled.value=e.form.bundled,o.blockWhere.value=Mn({usage:ze.get(e.form.name)})||"Where it is used could not be read.",o.blockNote.value="",o.blockReady.value=!0,no())},Bs=e=>{const t=$.get(e);if(!t)return{};const a=t.form.content;return Object.fromEntries(Ja(Ka(a),Po(a),t.samples||{}).map(s=>[s.name,s.value]))};l(M.effect(()=>{const e=o.blockProject.value,t=o.blockBundled.value,a=$.get(o.blockTab.peek());!a||!o.blockReady.peek()||a.form.project===e&&a.form.bundled===t||(a.form.project=e,a.form.bundled=t,be())}));const oo=async(e,{quiet:t=!1}={})=>{for(;Ae.has(e);)if(await Ae.get(e),$.has(e)||t)return $.get(e)||null;if($.has(e))return $.get(e);const a=(async()=>{try{const s=await k.blocks.get(e);if(!c()||!t&&!x.includes(Q({kind:"block",id:e})))return null;const r=Ua({...s,name:e}),n={block:s,saved:r,base:Oo(r),form:{...r},samples:{}};let i=null;try{i=S.get(Fe(e))}catch{}const g=Cn(i,n.base);if(t&&g?.stale)return null;if(Co({kind:"block",id:e}),g){const O=g.at?` (${Ie(g.at)})`:"",X=!g.stale||await E.confirm(`You left unsaved changes to "${e}"${O}, but the block has been saved since. Restore your version anyway?`);if(!c())return null;X&&(n.form={...n.form,...g.form,name:e},g.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return i&&W(Fe(e)),$.set(e,n),be(),n}catch(s){return c()&&o.blockTab.peek()===e&&(o.blockNote.value=`Could not open "${e}": ${s.message||"unknown error"}`),null}finally{Ae.delete(e)}})();return Ae.set(e,a),a},ga=async()=>{for(;Ae.size||De.size;)await Promise.all([...Ae.values(),...De.values()])},Ne=async e=>{if(!e||!pe.has(e))return;const t=Q({kind:"block",id:e});({ids:x}=$o(x,t)),o.active.value=t,co(),o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.peek()==="preview"&&(o.viewMode.value="split"),o.blockTab.peek()!==e&&(o.blockReady.value=!1),o.blockTab.value=e,o.blockUses.value=u&&kt(e)?Ln(Wa(ze.get(e),u.id||"")):Rn(ze.get(e)),ee(),V(),z();let a=$.get(e);a||(o.blockReady.value=!1,o.blockNote.value=`Opening ${e}\u2026`,a=await oo(e)),!(!a||!c()||o.blockTab.peek()!==e)&&(va(),V(),z(),_t=e,U(),(o.blockPart.peek()==="css"?Ge:oe)?.focus())};let ao=null;l(()=>clearTimeout(ao));const ka=(e,t)=>{const a=$.get(o.blockTab.peek());!a||!o.blockReady.peek()||(a.form[e]=t,clearTimeout(ao),ao=setTimeout(()=>{be(),e==="content"&&no()},120),U())},ke=N("#pe-block-samples"),ha=e=>{ke.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,ke.appendChild(t)};let Xe=[],so=null;function no(){const e=$.get(o.blockTab.peek());if(!e){Xe=[],so=null,ha("Open a block to see its placeholders.");return}const t=Ja(Ka(e.form.content),Po(e.form.content),e.samples);if(!(so===e&&t.map(a=>a.name).join("|")===Xe.join("|"))){if(Xe=t.map(a=>a.name),so=e,!t.length){ha("No {{placeholders}} in the template yet.");return}ke.replaceChildren();for(const a of t){const s=document.createElement("div");s.className="pe-comp-prop",s.tabIndex=0,s.dataset.sample=a.name;const r=document.createElement("label");r.className="pe-comp-prop-label",r.textContent=a.name,r.title=a.raw?`{{{${a.name}}}} - raw markup`:`{{${a.name}}}`;const n=document.createElement("input");n.type="text",n.className="form-input form-input--sm pe-comp-prop-input",n.value=a.value,n.setAttribute("aria-label",`Sample value for ${a.name}`),n.addEventListener("input",()=>{e.samples[a.name]=n.value,U()});const i=document.createElement("button");i.type="button",i.className="pe-kebab",i.setAttribute("aria-label",`Actions for ${a.name}`),i.title="Actions",i.textContent="\u22EE",s.append(r,n,i),ke.appendChild(s)}}}const ro=(e,t)=>Po(e.form.content).has(t)?`{{{${t}}}}`:`{{${t}}}`,js=e=>{const t=$.get(o.blockTab.peek());return!t||!Xe.includes(e)?[]:[{header:ro(t,e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>{const a=oe?.textarea;a&&(o.blockPart.value="html",a.focus(),a.setRangeText(ro(t,e),a.selectionStart,a.selectionEnd,"end"),a.dispatchEvent(new Event("input",{bubbles:!0})))}},{label:"Copy the placeholder",icon:"copy",action:()=>Le(ro(t,e),"Placeholder")},{type:"divider"},{label:"Reset the sample",icon:"rotate-ccw",action:()=>{delete t.samples[e],Xe=[],no(),U()}}]};{const e=E.contextMenu(ke,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>js(t.dataset.sample)});l(()=>e?.destroy?.()),ke.addEventListener("click",t=>{const a=t.target.closest(".pe-kebab");a&&Se(a)}),ke.addEventListener("keydown",t=>{const a=t.target.closest(".pe-comp-prop");a&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Se(a))})}const ce={},he=N("#component-preview"),Tt=e=>{try{he?.contentWindow?.postMessage(e,"*")}catch{}};let lo=he.contentDocument?.readyState==="complete"&&/component-preview/.test(he.contentDocument?.URL||""),Ct=!1,Qe=null;const ya=()=>{lo=!0,Qe&&c()&&(Tt({type:"mount",payload:Qe}),Ct=!0),Qe=null};he.addEventListener("load",ya),l(()=>he.removeEventListener("load",ya));const Is=e=>{if(!lo){Qe=e;return}if(!Ct){Ct=!0,Tt({type:"mount",payload:e});return}Qe=e,lo=!1,Ct=!1,he.src=he.src};let xt="";const io=(e=!1)=>{const t=B.get(o.compTab.peek());if(!(!t||t.state!=="ok"||!t.js)){if(o.viewMode.peek()==="write"){t.previewStale=!0;return}!e&&xt===t.name&&!t.previewStale||(t.previewStale=!1,xt=t.name,Is({compiledJs:t.js,tagName:`dm-${t.name}`,props:t.previewProps}))}},Ze=e=>{const t=++e.seq;return e.state="compiling",o.compTab.peek()===e.name&&(o.compState.value="compiling"),e.compiling=(async()=>{let a;try{a=await k.components.compile(e.name,e.form.source)}catch(s){a={errors:[{type:"request",message:s.message||"Compile request failed."}]}}if(!(t!==e.seq||!c())){if(a.errors?.length?(e.state="error",e.errors=a.errors.map(s=>`[${s.type}] ${s.message}`).join(`
|
|
120
|
-
`),e.js=null):(e.state="ok",e.errors="",e.js=a.js),o.compTab.peek()!==e.name){e.previewStale=!0;return}o.compState.value=e.state,o.compErrors.value=e.errors,e.state==="ok"&&(fo(),io(!0))}})().finally(()=>{e.compiling&&t===e.seq&&(e.compiling=null)}),e.compiling};let Pt=null,Ot=null;l(()=>clearTimeout(Pt));const wa=()=>{const e=B.get(o.compTab.peek());if(!e||!ce.template)return;o.compReady.value=!1;const t=it(e.form.source);for(const a of It)ce[a].setValue(t[a]),ce[a].textarea.readOnly=e.readOnly;o.compReadOnly.value=e.readOnly,o.compProject.value=e.form.project,Lo(N("#comp-project"),e.form.project),o.compBundled.value=e.form.bundled,o.compState.value=e.state,o.compErrors.value=e.errors,o.compNote.value="",o.compReady.value=!0,fo(),e.state==="ok"?io():e.state||Ze(e)},co=()=>{o.compTab.peek()&&(o.compTab.value="",o.compReady.value=!1)},Ea={page:"details",block:"details",component:"details"};let Rt="";l(M.effect(()=>{const e=Wo.value;e!==Rt&&(Rt&&(Ea[Rt]=o.dockTab.peek()),Rt=e,e&&(o.dockTab.value=Ea[e]||"details",e==="page"&&o.dockTab.peek()==="history"&&o.dockOn.peek()&&u?.isEdit&&!Ye&&le()))}));const po=async(e,{quiet:t=!1}={})=>{if(De.has(e))return De.get(e);if(B.has(e))return B.get(e);const a=(async()=>{try{const s=await k.components.get(e);if(!c()||!x.includes(Q({kind:"component",id:e})))return null;const r=s.readOnly===!0||s.origin==="plugin",n={project:s.meta?.project||"",bundled:!!s.bundled,source:Ga(it(s.source))},i={name:e,comp:s,saved:n,base:Do({name:e,...n}),form:{...n},meta:s.meta||{},readOnly:r,state:"",errors:"",js:null,seq:0,compiling:null,previewProps:{},previewStale:!1};let g=null;try{g=S.get(qe(e))}catch{}const O=Zn(g,i.base);if(t&&(!O.ok||O.stale||r))return null;const X=Co({kind:"component",id:e});if(t&&!X)return null;if(O.ok&&!r){const Ee=g.at?` (${Ie(g.at)})`:"",yo=X&&!O.stale||await E.confirm(O.stale?`You left unsaved changes to the component "${e}"${Ee}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to the component "${e}"${Ee}. Restore them?`);if(!c())return null;yo&&(i.form={project:String(g.project??""),bundled:g.bundled===!0,source:String(g.source)},X&&!O.stale&&E.toast(`Your unsaved changes to the component "${e}" are back.`,{type:"info"}))}return g&&W(qe(e)),B.set(e,i),ve(),i}catch(s){return c()&&o.compTab.peek()===e&&(o.compNote.value=`Could not open <dm-${e}>: ${s.message||"unknown error"}`),null}finally{De.delete(e)}})();return De.set(e,a),a},et=async e=>{if(!e||!se.has(e))return;const t=Q({kind:"component",id:e});({ids:x}=$o(x,t)),o.active.value=t,o.blockTab.value="",o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.peek()==="preview"&&(o.viewMode.value="split"),o.compTab.value=e,ee(),V(),z();let a=B.get(e);a||(o.compReady.value=!1,o.compReadOnly.value=!1,o.compState.value="",o.compNote.value=`Opening <dm-${e}>\u2026`,a=await po(e)),!(!a||!c()||o.compTab.peek()!==e)&&(wa(),V(),z(),ce[o.compPart.peek()]?.focus())},Fs=(e,t)=>{const a=B.get(o.compTab.peek());if(!a||!o.compReady.peek()||a.readOnly)return;const s=it(a.form.source);s[e]=t,a.form.source=Ga(s),clearTimeout(Pt),Ot=a,Pt=setTimeout(()=>{Ot=null,ve(),Ze(a)},300)};l(M.effect(()=>{const e=o.compProject.value,t=o.compBundled.value,a=B.get(o.compTab.peek());!a||!o.compReady.peek()||a.readOnly||a.form.project===e&&a.form.bundled===t||(a.form.project=e,a.form.bundled=t,ve())}));const ye=N("#pe-comp-props");let uo=[];const mo=e=>{ye.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,ye.appendChild(t)};function fo(){const e=B.get(o.compTab.peek());if(!e){uo=[],mo("Open a component to try its props.");return}const{rows:t,error:a}=Xa(it(e.form.source).props,e.previewProps);if(uo=t,a){mo("The <props> block is not valid JSON - fix it to see the props here.");return}if(!t.length){e.previewProps={},mo("No props declared. Add them in the <props> block.");return}e.previewProps=Object.fromEntries(t.map(s=>[s.name,s.value])),ye.replaceChildren();for(const s of t){const r=document.createElement("div");r.className="pe-comp-prop",r.tabIndex=0,r.dataset.prop=s.name;const n=document.createElement("label");n.className="pe-comp-prop-label",n.textContent=s.label,n.title=`${s.name} (${s.type})`;const i=s.type==="array"||s.type==="object"?document.createElement("textarea"):document.createElement("input");s.type==="boolean"?(i.type="checkbox",i.className="form-check",i.checked=Qa(s.type,s.value)):(i.tagName==="INPUT"?i.type=s.type==="number"?"number":"text":i.rows=2,i.className="form-input form-input--sm pe-comp-prop-input",i.value=Qa(s.type,s.value)),i.setAttribute("aria-label",s.label);const g=()=>{e.previewProps[s.name]=tr(s.type,s.type==="boolean"?i.checked:i.value),xt===e.name&&Tt({type:"update",payload:{props:e.previewProps}})};i.addEventListener("input",g),i.addEventListener("change",g);const O=document.createElement("button");O.type="button",O.className="pe-kebab",O.setAttribute("aria-label",`Actions for ${s.label}`),O.title="Actions",O.textContent="\u22EE",r.append(n,i,O),ye.appendChild(r)}}const Le=async(e,t)=>{try{await navigator.clipboard.writeText(e),E.toast(`${t} copied.`,{type:"success"})}catch{E.toast(e,{type:"info",duration:8e3})}},qs=e=>{const t=B.get(o.compTab.peek()),a=uo.find(n=>n.name===e);if(!t||!a)return[];const s=Qn(a.name,a.type,t.previewProps[a.name]),r=`dm-${t.name}`;return[{header:a.label},{label:"Reset to its default",icon:"rotate-ccw",action:()=>{delete t.previewProps[a.name];const n=Xa(it(t.form.source).props,{}).rows.find(i=>i.name===a.name);t.previewProps[a.name]=n?n.value:null,fo(),xt===t.name&&Tt({type:"update",payload:{props:t.previewProps}})}},{label:"Copy as an attribute",icon:"copy",disabled:!s,action:()=>Le(s,"Attribute")},{label:"Copy the tag with this value",icon:"code",action:()=>Le(`<${r}${s?` ${s}`:""}></${r}>`,"Tag")},{label:"Copy the prop name",icon:"copy",action:()=>Le(a.name,"Name")},{type:"divider"},{label:"Edit in <props>",icon:"edit",action:()=>{o.compPart.value="props";const n=ce.props?.textarea;if(!n)return;const i=n.value.indexOf(`"${a.name}"`);n.focus(),i>=0&&n.setSelectionRange(i,i+a.name.length+2)}}]};{const e=E.contextMenu(ye,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>qs(t.dataset.prop)});l(()=>e?.destroy?.()),ye.addEventListener("click",t=>{const a=t.target.closest(".pe-kebab");a&&Se(a)}),ye.addEventListener("keydown",t=>{const a=t.target.closest(".pe-comp-prop");a&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Se(a))})}let Be=!1;const Sa=()=>jt()||"",$t=async({project:e=Sa(),seed:t="",content:a=ir,css:s=""}={})=>{if(!Be){Be=!0;try{const n=await os({kind:"block",projects:J,taken:[...pe],project:e,name:t,check:Sn,register:l,create:async(i,g)=>{await k.blocks.put(i,_a({name:i,project:g,bundled:!1,content:a,css:s}))}});if(!c())return;if(!n){ee();return}if(await Fo(),!c())return;lt("#/blocks"),E.toast(`Created the block "${n}".`,{type:"success"}),Ne(n)}finally{Be=!1}}},tt=async({name:e="",source:t=Xn,project:a=Sa()}={})=>{if(!Be){Be=!0;try{const s=await os({kind:"component",projects:J,taken:[...se],project:a,name:e,register:l,check:(r,n)=>er(r,{isNew:!0,taken:n}),create:async(r,n)=>{await k.components.put(r,{source:t,bundled:!1,meta:n?{project:n}:{}})}});if(!c())return;if(!s){ee();return}if(await qo(),!c())return;lt("#/components"),E.toast(`Created the component "${s}".`,{type:"success"}),et(s)}finally{Be=!1}}},Ma=h.querySelector(".dm-plugin-body")||h,zs=M.computed(()=>{o.blockRev.value,o.compRev.value,o.docRev.value,Y(K()),o.base.value;const e=I(o.active.value),t=[...Pe().map(s=>Q({kind:"page",id:s.id})),...me().map(s=>Q({kind:"block",id:s})),...Ke().map(s=>Q({kind:"component",id:s}))];let a=[];return e?.kind==="block"||e?.kind==="component"?a=[o.active.value]:e?.kind==="page"&&u&&(a=[o.active.value,...me().filter(kt).map(s=>Q({kind:"block",id:s}))]),Yn(t,a)}),Vs={...p,...o,dirty:us,pageDirty:ds,activeDirty:ms,cssUnsaved:ps,hasPage:fs,activeKind:Uo,canView:_o,viewHref:bs,problem:vs,statusText:gs,saveAll:zs,dockKind:Wo,blockTagText:ks,compStateText:hs,compTagText:ys,anySelected:ws,allSelected:Es,someSelected:Ss},ot=(e,t)=>dt(e,t),bo={save:()=>to(!1),saveAllOpen:()=>to(!0),discard:async()=>{if(await ga(),!c())return;const e=I(o.active.peek());if(e?.kind==="block"){const t=$.get(e.id);if(!t||!re(t)||!await E.confirm(`Discard the changes to "${e.id}" you have not saved?`))return;t.form={...t.saved},W(Fe(e.id)),va(),be()}else if(e?.kind==="component"){const t=B.get(e.id);if(!t||!fe(t)||!await E.confirm(`Discard the changes to the component "${e.id}" you have not saved?`))return;t.form={...t.saved},t.state="",W(qe(e.id)),wa(),ve()}else{if(!u||e?.kind==="page"&&e.id!==u.id||!(Vt()||vt())||!await E.confirm("Discard the changes to this page you have not saved?"))return;We(JSON.parse(o.base.peek())),p.cssOpened.peek()&&(p.css.value=Z.peek()),W(je(u.id)),W(pt),_e()}V(),z(),U()},newPage:()=>ie(""),newBlock:()=>$t(),newComponent:()=>tt(),toggleSection:e=>{const t={here:"secHere",pages:"secPages",blocks:"secBlocks",components:"secComps"}[e];t&&(o[t].value=!o[t].peek(),ot(t,o[t].peek()))},toggleExplorer:()=>{o.explorerOn.value=!o.explorerOn.peek(),ot("explorer",o.explorerOn.peek())},toggleDock:()=>{o.dockOn.value=!o.dockOn.peek(),ot("dock",o.dockOn.peek()),o.dockOn.peek()&&o.dockTab.peek()==="history"&&!Ye&&le()},showDock:e=>{o.dockTab.value=e,e==="history"&&u?.isEdit&&!Ye&&le()},schemaChanged:()=>{o.seoSchemaNote.value=Eo(p.seoSchema.peek()).error},insertSchemaExample:async e=>{const t=e?.target,a=t?.value||"";if(t&&(t.value=""),!a||p.seoSchema.peek().trim()&&!await E.confirm("Replace the structured data with the example?"))return;const s=p.urlPath.peek().trim()||u?.id||"/";p.seoSchema.value=nn(a,{title:p.seoTitle.peek().trim()||p.title.peek().trim(),description:p.seoDesc.peek().trim()||p.description.peek().trim(),url:`${location.origin}${s}`,siteName:A?.title||A?.seo?.defaultTitle||""}),o.seoSchemaNote.value=""},setBlockPart:e=>{o.blockPart.value=e==="css"?"css":"html",(e==="css"?Ge:oe)?.focus()},formatBlock:()=>{const e=$.get(o.blockTab.peek());!e||!oe||(e.form.content=xn(e.form.content),oe.setValue(e.form.content),be(),U())},copyShortcode:async()=>{const e=o.blockTab.peek();e&&await Le(Pn(e),"Shortcode")},setCompPart:e=>{o.compPart.value=It.includes(e)?e:"template",ce[o.compPart.peek()]?.focus()},copyComponent:()=>{const e=B.get(o.compTab.peek());e&&tt({name:xo(e.name,[...se]),source:e.form.source})},setMode:e=>{o.cssMode.peek()&&(o.cssMode.value=!1),o.viewMode.value=Ro(e),ot("viewMode",o.viewMode.peek()),e!=="write"&&(U(!0),io())},toggleFullscreen:()=>{o.fullscreen.value=!o.fullscreen.peek(),ot("fullscreen",o.fullscreen.peek())},toggleCss:()=>{o.blockTab.peek()||o.compTab.peek()||(o.cssMode.peek()?(o.cssMode.value=!1,o.viewMode.value=Ro(Te().viewMode),G.refresh()):(ta(),o.cssMode.value=!0,o.viewMode.value="split"))},formatMarkdown:()=>{const e=F.selectionStart,t=pr(bt());G.unfoldAll(),xe=!0,F.value=t,xe=!1,F.selectionStart=F.selectionEnd=Math.min(e,t.length),F.dispatchEvent(new Event("input")),He.paint(),G.refresh(),E.toast("Markdown formatted.",{type:"success"})},toggleFold:()=>{o.folded.peek()?G.unfoldAll():G.foldAll(),o.folded.value=!o.folded.peek(),He.paint()},setWidth:e=>{o.liveWidth.value=e},refreshPreview:()=>{if(o.compTab.peek()){const e=B.get(o.compTab.peek());e&&Ze(e)}else U(!0)},share:async()=>{if(!u?.isEdit){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await rr(u.id)},toggleAll:e=>{const t=!!e?.target?.checked;o.versions.peek().forEach(a=>{a.checked.value=t})},deleteSelected:async()=>{const e=o.versions.peek().filter(a=>a.checked.peek()).map(a=>a.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await k.versions.bulkDelete(u.id,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await le()}catch(a){E.toast("Delete failed: "+a.message,{type:"error"})}},saveNamed:async()=>{if(!u?.isEdit)return;const e=u;await lr(e.id)&&c()&&u===e&&await le()},closeCompare:()=>{o.compare.value=null,Et=null},restoreCompared:()=>la(Et)};Ue.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const Hs=M.applyBindings(Vs,Ma,{methods:bo});l(()=>Hs.dispose()),oe=No(N('.pe-block-edit[data-part="html"]'),{ariaLabel:"Block template HTML",language:"html",onChange:e=>ka("content",e)}),Ge=No(N('.pe-block-edit[data-part="css"]'),{ariaLabel:"Block CSS",language:"css",placeholder:`/* Scoped to this block - e.g.
|
|
137
|
+
</span>`);Ge.prepend(Ls),Ge.before(O(".pe-block-bar")),Ge.before(O(".pe-comp-bar"));const aa=e=>(Array.isArray(e)?e:[]).map(t=>({value:t,label:t})),sa=O("#field-tags");let St=null;St=sa?E.pillbox(sa,{data:aa(_t),value:[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026",onChange:()=>{St&&(p.tags.value=[...St.getValue()])}}):null;const na=O("#field-admin-tags");let Et=null;Et=na?E.pillbox(na,{data:aa(T),value:[],creatable:!0,searchable:!0,placeholder:"Add admin tag\u2026",onChange:()=>{Et&&(p.adminTags.value=[...Et.getValue()])}}):null;const ra=()=>{a.bodyRev.value=a.bodyRev.peek()+1},Le=()=>{a.blockRev.value=a.blockRev.peek()+1},ke=()=>{a.compRev.value=a.compRev.peek()+1},Xe=()=>{a.docRev.value=a.docRev.peek()+1},ia=()=>{p.cssOpened.peek()||(p.css.peek()||(p.css.value=ee.peek()),p.cssOpened.value=!0,on(ve,{language:"css"}))},Ze=e=>{for(const t of Tr)p[t].value=e[t]==null?"":String(e[t]);for(const t of Mr)p[t].value=!!e[t];p.tags.value=Array.isArray(e.tags)?e.tags:[],St?.setValue(p.tags.peek()),Array.isArray(e.adminTags)&&(p.adminTags.value=e.adminTags),Et?.setValue(p.adminTags.peek()),qo(O("#field-visibility"),p.visibility.peek()),Q.unfoldAll(),a.folded.value=!1,gt=!0,_.value=e.body==null?"":String(e.body),gt=!1,Q.refresh(),Uo(),ra()},la=(e,t)=>({urlPath:t||"",title:e?.title||"",description:e?.description||"",layout:Vo(e?.layout||"default"),status:e?.status||"draft",sortOrder:String(e?.sortOrder??99),showInNav:!!e?.showInNav,sidebar:!!e?.sidebar,breadcrumbs:e?e.breadcrumbs!==!1:!0,bundled:!!e?.bundled,tags:Array.isArray(e?.tags)?e.tags:[],adminTags:Array.isArray(e?.adminTags)?e.adminTags:[],category:e?.category||"",visibility:typeof e?.visibility=="string"&&e.visibility?e.visibility:"public",project:Cn({isEdit:!!t,page:e,projects:se,fromHash:t?"":Ft()||"",urlPath:t||""}),theme:e?.theme||"",seoTitle:e?.seo?.title||"",seoDesc:e?.seo?.description||"",seoSchema:fn(e?.seo?.schema),dconfig:e?.dconfig?JSON.stringify(e.dconfig,null,2):"",body:e?.content||"",css:null}),ca=()=>{m&&(m.state=K(),m.at=Date.now())},da=e=>{m!==e&&(ca(),m=e,Ze(e.state),a.base.value=e.base,a.curId.value=e.id,e.at=Date.now(),Mt(),Xe())},Ns=()=>{ca(),m=null;const e=ms();Ze(e),a.base.value=W(e),a.curId.value=null,Mt(),Xe()};Ze(ms()),a.base.value=W(K());const J=e=>{try{S.remove?S.remove(e):S.set(e,null)}catch{}},pa=(e,t)=>({name:e.name,...e.form,base:e.base,at:t});u=()=>{try{for(const t of Re())S.set(ze(t.id),Ua(t===m?Go():t.state,t.base));ht()&&S.set(ft,p.css.peek());const e=new Date().toISOString();for(const t of ge()){const o=L.get(t);S.set(Ve(t),{form:{...o.form},base:o.base,at:e})}for(const t of Ye())S.set(_e(t),pa(N.get(t),e))}catch{}};const ua=e=>{y()&&(u(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ua),i(()=>window.removeEventListener("beforeunload",ua));let Qe=!1,Tt=null;const Mt=()=>{Qe=!1,Tt=null,a.versions.value=[],a.compare.value=null,a.histError.value="",a.dockOn.peek()&&a.dockTab.peek()==="history"&&(m?.isEdit?pe():a.dockTab.value="details")},pe=async()=>{const e=m;if(e?.isEdit){Qe=!0,a.histLoading.value=!0,a.histError.value="";try{const t=(await h.versions.list(e.id)).versions||[];if(!c()||m!==e)return;a.versions.value=t.map(o=>({...o,when:qe(o.createdAt),author:o.author||"",label:o.label||"",who:[o.author,o.label].filter(Boolean).join(" - "),checked:M.observable(!1)}))}catch(t){m===e&&(a.histError.value="Failed to load versions: "+t.message)}finally{m===e&&(a.histLoading.value=!1)}}},oo=e=>a.versions.peek().find(t=>t.filename===e),ao=async e=>{const t=m;try{const o=await h.versions.get(t.id,e.filename);if(!c()||m!==t)return;Tt=e.filename,a.compare.value={label:rs(e),old:o.content,current:Ht()}}catch(o){E.toast("Failed to load version: "+o.message,{type:"error"})}},Ds=async()=>{const e=m;if(!e?.isEdit)return;const t=await h.pages.get(e.id).catch(()=>null);if(!c()||m!==e||!t)return;e.page=t;const o=la(t,e.id);e.base=W(o),e.savedAdminTags=JSON.stringify(o.adminTags),Ze(o),a.base.value=e.base,Mt(),a.dockOn.peek()&&a.dockTab.peek()==="history"&&pe(),H()},ma=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 h.versions.restore(m.id,e),E.toast("Page restored.",{type:"success"}),await Ds()}catch(t){E.toast("Restore failed: "+t.message,{type:"error"})}},js=async e=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await h.versions.delete(m.id,e.filename),E.toast("Version deleted.",{type:"success"}),await pe()}catch(t){E.toast("Failed to delete: "+t.message,{type:"error"})}},Ne=new Map,Bs=e=>{if(!Ne.has(e)){const t=I(e);Ne.set(e,{id:e,kind:t.kind,kindText:t.kind==="page"?"Page":t.kind==="component"?"Component":"Block",label:M.observable(""),title:M.observable(""),active:M.observable(!1),dirty:M.observable(!1)})}return Ne.get(e)},fa=e=>{const t=e===m?p.title.peek():e?.loaded?e.state.title:D.find(o=>o.urlPath===e?.id)?.title;return String(t||"").trim()||e?.id||"New page"};let ba=!0,va="";const Is=()=>{if(ba)return;const e={ids:P,active:a.active.peek()},t=JSON.stringify(e);if(t!==va){va=t;try{S.set(ps,e)}catch{}}},q=()=>{const e=P.map(Bs);for(const t of e){const o=I(t.id);let s,r;if(o.kind==="page"){const l=U.get(o.id);s=fa(l),r=l?.loaded?Yt(l):xe(o),t.title.value=o.id?`${s} (${o.id})`:"New page"}else if(o.kind==="component"){const l=N.get(o.id);s=o.id,r=l?he(l):xe(o),t.title.value=`Component <dm-${o.id}>`}else{const l=L.get(o.id);s=o.id,r=l?de(l):Ae.has(o.id)||xe(o),t.title.value=`Block ${o.id}`}t.label.peek()!==s&&(t.label.value=s),t.dirty.peek()!==r&&(t.dirty.value=r);const n=t.id===a.active.peek();t.active.peek()!==n&&(t.active.value=n)}e.map(t=>t.id).join("|")!==a.tabs.peek().map(t=>t.id).join("|")&&(a.tabs.value=e),Is()};let ye=new Set;try{const e=S.get(us);Array.isArray(e)&&(ye=new Set(e.filter(t=>typeof t=="string")))}catch{}const ga=new Map,so=e=>{let t=ga.get(e.key);return t?(t.label.peek()!==e.label&&(t.label.value=e.label),t.meta.peek()!==(e.meta||"")&&(t.meta.value=e.meta||""),t.status.peek()!==(e.status||"")&&(t.status.value=e.status||""),t.folder.peek()!==!!e.folder&&(t.folder.value=!!e.folder)):(t={...e,label:M.observable(e.label),meta:M.observable(e.meta||""),status:M.observable(e.status||""),folder:M.observable(!!e.folder),active:M.observable(!1),dirty:M.observable(!1),folded:M.observable(!1),title:""},ga.set(e.key,t)),t.title=e.kind==="block"||e.kind==="component"?e.meta?`${e.id} - ${e.meta}`:e.id:e.kind==="folder"?e.meta:`${e.label} (${e.meta})`,t},no=new Map,z=()=>{const e=a.filter.peek(),t=I(a.active.peek()),o=t?.kind==="page"?t.id:null,s=t?.kind==="block"?t.id:null,r=t?.kind==="component"?t.id:null,n=new Set(Re().map(f=>f.id)),l=Gn(D,{filter:e,folded:ye}).map(so);for(const f of l){const C=f.kind==="page"&&f.id===o,B=f.kind==="page"&&(n.has(f.id)||!U.get(f.id)?.loaded&&xe({kind:"page",id:f.id})),fe=ye.has(f.id);f.active.peek()!==C&&(f.active.value=C),f.dirty.peek()!==B&&(f.dirty.value=B),f.folded.peek()!==fe&&(f.folded.value=fe)}const g=_n(ie,e).map(so),$=f=>{const C=L.get(f);return C?de(C):Ae.has(f)||xe({kind:"block",id:f})};for(const f of g){const C=f.id===s,B=$(f.id);f.active.peek()!==C&&(f.active.value=C),f.dirty.peek()!==B&&(f.dirty.value=B)}const G=Un(Je,e).map(so),Me=f=>{const C=N.get(f);return C?he(C):xe({kind:"component",id:f})};for(const f of G){const C=f.id===r,B=Me(f.id);f.active.peek()!==C&&(f.active.value=C),f.dirty.peek()!==B&&(f.dirty.value=B)}const Co=m?Dn(kt,Kt,be):[],za=(f,C,B)=>(no.has(f)||no.set(f,{key:f,id:C,label:B,kindText:"",title:"",active:M.observable(!1),dirty:M.observable(!1)}),no.get(f)),xo=Co.map(f=>{const C=za(`block:${f.name}`,f.name,f.name);C.kindText=f.footer?"Footer":"Block",C.title=`${f.name} - ${f.footer?"in the footer, on every page":"a block on this page"}`;const B=f.name===s,fe=$(f.name);return C.active.peek()!==B&&(C.active.value=B),C.dirty.peek()!==fe&&(C.dirty.value=fe),C});for(const f of ta){const C=za(`component:${f}`,f,f);C.kindText="Component",C.title=`<dm-${f}> - a component on this page`;const B=f===r,fe=Me(f);C.active.peek()!==B&&(C.active.value=B),C.dirty.peek()!==fe&&(C.dirty.value=fe),xo.push(C)}const Dt=(f,C)=>f.map(B=>B.key).join("|")===C.map(B=>B.key).join("|");Dt(l,a.pageRows.peek())||(a.pageRows.value=l),Dt(g,a.blockRows.peek())||(a.blockRows.value=g),Dt(G,a.compRows.peek())||(a.compRows.value=G),Dt(xo,a.thisPage.peek())||(a.thisPage.value=xo)},ha=async()=>{const e=await h.pages.list().catch(()=>null);!c()||!Array.isArray(e)||(D=e,z(),q())},te=()=>{const e=I(a.active.peek()),t=e?es(e):"/pages/new",o=window.location.hash.replace(/^#/,"");j.address=o.split("?")[0]===t?o:t,o.split("?")[0]!==t?history.replaceState({pe:1},"","#"+t):history.state?.pe||history.replaceState({pe:1},"",window.location.hash)},Fs=async e=>{if(e.loading)return e.loading;e.loading=(async()=>{let t=null;if(e.isEdit){if(t=await h.pages.get(e.id).catch(()=>null),!c())return!1;if(!t)return E.toast(`Page ${e.id} not found.`,{type:"error"}),!1}e.page=t;const o=la(t,e.id);e.state=o,e.base=W(o),e.savedAdminTags=JSON.stringify(o.adminTags),e.loaded=!0;let s=null;try{s=S.get(ze(e.id))}catch{}const r=xn(s),n=$o({kind:"page",id:e.id});if(r){J(ze(e.id));const l=W(r.state)!==e.base,g=Mn(r,e.base),$={...o,...r.state,adminTags:o.adminTags};if(l&&n&&!g)e.state=$,E.toast(`Your unsaved changes to ${e.id||"the new page"} are back.`,{type:"info"});else if(l){const G=r.at?` (${qe(r.at)})`:"",Me=g?" 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 ${e.id||"a new page"}${G}. Restore them?${Me}`)&&c()&&(e.state=$)}}return!0})();try{return await e.loading}finally{e.loading=null}},ka=e=>(U.has(e)||U.set(e,{kind:"page",id:e,isEdit:!!e,loaded:!1,page:null,state:null,base:"",savedAdminTags:"[]",at:0}),U.get(e)),ue=async e=>{const t=X({kind:"page",id:e});({ids:P}=Bo(P,t)),a.active.value=t,a.blockTab.value="",vo();const o=ka(e);if(q(),!o.loaded){a.previewStatus.value=`Opening ${e||"a new page"}\u2026`;const s=await Fs(o);if(!c()||!P.includes(t)||U.get(e)!==o)return;if(!s){ya(t);return}if(a.active.peek()!==t){q(),z();return}}da(o),te(),q(),z(),H(),Q.refresh(),_.focus()};w=async(e,t)=>{if(j.address=t,e.kind==="page"){ue(e.id);return}if(!e.id){(e.kind==="block"?Lt:nt)({project:Ft(t)||""});return}const o=e.kind==="block"?be:le;if(o.has(e.id)||await(e.kind==="block"?_o:Ko)(),!!c()){if(!o.has(e.id)){E.toast(`There is no ${e.kind} called "${e.id}".`,{type:"warning"}),te(),z();return}e.kind==="block"?Be(e.id):st(e.id)}};const ya=e=>{const t=a.active.peek(),o=Kn(P,e,t);P=o.ids,Ne.delete(e);const s=I(e),r=o.active!==t?I(o.active):null;if(s.kind==="page"){if(U.delete(s.id),m?.id===s.id){const n=[...U.values()].filter(l=>l.loaded&&l.id!==s.id).sort((l,g)=>g.at-l.at)[0];n?da(n):Ns()}}else s.kind==="component"?(N.delete(s.id),a.compTab.peek()===s.id&&vo()):(L.delete(s.id),a.blockTab.peek()===s.id&&(a.blockTab.value="",a.blockReady.value=!1));if(a.active.value=o.active,r?.kind==="page"){ue(r.id);return}if(r?.kind==="block"){Be(r.id);return}if(r?.kind==="component"){st(r.id);return}te(),Xe(),Le(),ke(),q(),z(),H()},et=e=>{const t=I(e);if(!t||!P.includes(e))return;let o="";try{if(t.kind==="page"){const s=U.get(t.id);s&&Yt(s)&&(S.set(ze(s.id),Ua(s===m?K():s.state,s.base)),Ao({kind:"page",id:s.id}),o=fa(s))}else if(t.kind==="component"){const s=N.get(t.id);s&&he(s)&&(S.set(_e(t.id),pa(s,new Date().toISOString())),Ao({kind:"component",id:t.id}),o=t.id)}else{const s=L.get(t.id);s&&de(s)&&(S.set(Ve(t.id),{form:{...s.form},base:s.base,at:new Date().toISOString()}),Ao({kind:"block",id:t.id}),o=t.id)}}catch{}ya(e),o&&E.toast(`Changes to "${o}" kept as a draft - back when you open it again.`,{type:"info"})},Ct=e=>{const t=I(e);t&&(t.kind==="page"?ue(t.id):t.kind==="component"?st(t.id):Be(t.id))},wa=new Set,zs=async e=>{const t=[];for(const o of e){const s=L.get(o);if(!s)continue;const r={...s.form};try{const n=Za(r,s.block?.meta);await h.blocks.put(o,n),s.block={...s.block,meta:n.meta},s.saved=r,s.base=Do(r),vt.set(o,new Date().toISOString()),c()&&J(Ve(o)),t.push(o)}catch(n){E.toast(`Block "${o}" not saved: ${n.message||"Unknown error"}`,{type:"error"})}}return t.forEach(o=>wa.add(o)),t.length&&(Le(),pt("#/blocks")),t},qs=async e=>{const t=e===m?K():e.state,o=Po(t.seoSchema).error;if(o)return E.toast(`${e.id||"The new page"} not saved - structured data: ${o}`,{type:"error"}),e===m&&(a.seoSchemaNote.value=o,a.dockOn.value=!0,a.dockTab.value="seo"),!1;const s=Gt(t,e,Xt(t)),r=e.id;try{if(e.isEdit){const n={frontmatter:s,body:t.body};t.urlPath!==e.id&&(n.newUrlPath=t.urlPath),await h.pages.update(e.id,n)}else await h.pages.create({urlPath:t.urlPath,frontmatter:s,body:t.body})}catch(n){return E.toast(`${e.id||"The new page"} not saved: ${n.message||"Unknown error"}`,{type:"error"}),!1}if(!c())return!0;if(e.savedAdminTags=JSON.stringify(t.adminTags),e.base=W(t),e===m?a.base.value=e.base:e.state=t,J(ze(r)),!e.isEdit||t.urlPath!==e.id){U.delete(r),e.id=t.urlPath,e.isEdit=!0,U.set(e.id,e);const n=X({kind:"page",id:r}),l=X({kind:"page",id:e.id});Ne.delete(n),{ids:P}=Qn(P,n,l,a.active.peek()),a.active.peek()===n&&(a.active.value=l),e===m&&(a.curId.value=e.id,te())}return!0},Vs=async e=>{const t=[];for(const o of e){const s=N.get(o);if(!s||s.readOnly)continue;if(At===s?(clearTimeout(Rt),At=null,ke(),at(s)):s.state||at(s),s.compiling&&await s.compiling,!c())return t;if(s.state!=="ok"){E.toast(`Component "${o}" not saved: ${s.state==="error"?"it does not compile - fix the errors first.":"it has not compiled yet - try again in a moment."}`,{type:"warning"});continue}const r={...s.form},n={...s.meta||{}};delete n.bundled,r.project?n.project=r.project:delete n.project;try{await h.components.put(o,{source:r.source,bundled:r.bundled,meta:n}),s.meta=n,s.saved=r,s.base=Fo({name:o,...r});const l=Je.find(g=>g.name===o);l&&(l.bundled=r.bundled,l.meta=n),c()&&J(_e(o)),t.push(o)}catch(l){E.toast(`Component "${o}" not saved: ${l.message||"Unknown error"}`,{type:"error"})}}return t.length&&(ke(),pt("#/components")),t};let ro=!1;const io=async(e=!1)=>{if(a.saving.peek()||ro)return;ro=!0;let t,o,s=[];try{if(await Ea(),!c())return;const n=I(a.active.peek());if(lr().formatOnSave&&vs(n),e)t=Re(),o=ge(),s=Ye();else if(n?.kind==="block")t=[],o=L.has(n.id)&&de(L.get(n.id))?[n.id]:[];else if(n?.kind==="component")t=[],o=[],s=N.has(n.id)&&he(N.get(n.id))?[n.id]:[];else if(n?.kind==="page"&&m)o=ge().filter(yt),t=!m.isEdit||Wt()||!o.length?[m]:[];else return;if(!t.length&&!o.length&&!s.length){E.toast("Nothing to save.",{type:"info"});return}for(const g of t){const $=Ro(g===m?K():g.state);if($.length){E.toast(`${g.id||"New page"}: ${$[0].text}`,{type:"warning"}),g!==m&&await ue(g.id),a.dockOn.value=!0,a.dockTab.value=$[0].field==="dconfig"?"dconfig":"details",Ue("dock",!0);return}}const l=Fn(o,Object.fromEntries(o.map(g=>[g,Qa(He.get(g),m?.id||"")])),wa);if(l&&(!await E.confirm(l)||!c()))return}finally{ro=!1}const r=t.length>0&&ht();a.saving.value=!0;try{let n=0;for(const $ of t)await qs($)&&n++;if(!c())return;if(r){const $=p.css.peek();await h.settings.saveCustomCss($),ee.value=$,J(ft)}const l=await zs(o);if(!c())return;const g=await Vs(s);if((n||l.length||g.length)&&E.toast(zn({pages:n,css:r,blocks:l,components:g}),{type:"success"}),Xe(),!c())return;q(),z(),n&&(pt("#/pages"),Mt(),ha())}catch(n){E.toast(`Save failed: ${n.message||"Unknown error"}`,{type:"error"})}finally{a.saving.value=!1}};let oe=null,me=null;const De=new Map,je=new Map,Sa=()=>{const e=L.get(a.blockTab.peek());!e||!oe||(a.blockReady.value=!1,oe.setValue(e.form.content),me.setValue(e.form.css),a.blockProject.value=e.form.project,qo(O("#block-project"),e.form.project),a.blockBundled.value=e.form.bundled,a.blockWhere.value=An({usage:He.get(e.form.name)})||"Where it is used could not be read.",a.blockNote.value="",a.blockReady.value=!0,uo())},_s=e=>{const t=L.get(e);if(!t)return{};const o=t.form.content;return Object.fromEntries(ts(Ga(o),No(o),t.samples||{}).map(s=>[s.name,s.value]))};i(M.effect(()=>{const e=a.blockProject.value,t=a.blockBundled.value,o=L.get(a.blockTab.peek());!o||!a.blockReady.peek()||o.form.project===e&&o.form.bundled===t||(o.form.project=e,o.form.bundled=t,Le())}));const lo=async(e,{quiet:t=!1}={})=>{for(;De.has(e);)if(await De.get(e),L.has(e)||t)return L.get(e)||null;if(L.has(e))return L.get(e);const o=(async()=>{try{const s=await h.blocks.get(e);if(!c()||!t&&!P.includes(X({kind:"block",id:e})))return null;const r=Xa({...s,name:e}),n={block:s,saved:r,base:Do(r),form:{...r},samples:{}};let l=null;try{l=S.get(Ve(e))}catch{}const g=Ln(l,n.base);if(t&&g?.stale)return null;if($o({kind:"block",id:e}),g){const $=g.at?` (${qe(g.at)})`:"",G=!g.stale||await E.confirm(`You left unsaved changes to "${e}"${$}, but the block has been saved since. Restore your version anyway?`);if(!c())return null;G&&(n.form={...n.form,...g.form,name:e},g.stale||E.toast(`Your unsaved changes to "${e}" are back.`,{type:"info"}))}return l&&J(Ve(e)),L.set(e,n),Le(),n}catch(s){return c()&&a.blockTab.peek()===e&&(a.blockNote.value=`Could not open "${e}": ${s.message||"unknown error"}`),null}finally{De.delete(e)}})();return De.set(e,o),o},Ea=async()=>{for(;De.size||je.size;)await Promise.all([...De.values(),...je.values()])},Be=async e=>{if(!e||!be.has(e))return;const t=X({kind:"block",id:e});({ids:P}=Bo(P,t)),a.active.value=t,vo(),a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.peek()==="preview"&&(a.viewMode.value="split"),a.blockTab.peek()!==e&&(a.blockReady.value=!1),a.blockTab.value=e,a.blockUses.value=m&&yt(e)?qn(Qa(He.get(e),m.id||"")):jn(He.get(e)),te(),q(),z();let o=L.get(e);o||(a.blockReady.value=!1,a.blockNote.value=`Opening ${e}\u2026`,o=await lo(e)),!(!o||!c()||a.blockTab.peek()!==e)&&(Sa(),q(),z(),Zt=e,H(),(a.blockPart.peek()==="css"?me:oe)?.focus())};let co=null;i(()=>clearTimeout(co));const Ta=(e,t)=>{const o=L.get(a.blockTab.peek());!o||!a.blockReady.peek()||(o.form[e]=t,clearTimeout(co),co=setTimeout(()=>{Le(),e==="content"&&uo()},120),H())},we=O("#pe-block-samples"),Ma=e=>{we.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,we.appendChild(t)};let tt=[],po=null;function uo(){const e=L.get(a.blockTab.peek());if(!e){tt=[],po=null,Ma("Open a block to see its placeholders.");return}const t=ts(Ga(e.form.content),No(e.form.content),e.samples);if(!(po===e&&t.map(o=>o.name).join("|")===tt.join("|"))){if(tt=t.map(o=>o.name),po=e,!t.length){Ma("No {{placeholders}} in the template yet.");return}we.replaceChildren();for(const o of t){const s=document.createElement("div");s.className="pe-comp-prop",s.tabIndex=0,s.dataset.sample=o.name;const r=document.createElement("label");r.className="pe-comp-prop-label",r.textContent=o.name,r.title=o.raw?`{{{${o.name}}}} - raw markup`:`{{${o.name}}}`;const n=document.createElement("input");n.type="text",n.className="form-input form-input--sm pe-comp-prop-input",n.value=o.value,n.setAttribute("aria-label",`Sample value for ${o.name}`),n.addEventListener("input",()=>{e.samples[o.name]=n.value,H()});const l=document.createElement("button");l.type="button",l.className="pe-kebab",l.setAttribute("aria-label",`Actions for ${o.name}`),l.title="Actions",l.textContent="\u22EE",s.append(r,n,l),we.appendChild(s)}}}const mo=(e,t)=>No(e.form.content).has(t)?`{{{${t}}}}`:`{{${t}}}`,Ks=e=>{const t=L.get(a.blockTab.peek());return!t||!tt.includes(e)?[]:[{header:mo(t,e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>{const o=oe?.textarea;o&&(a.blockPart.value="html",o.focus(),o.setRangeText(mo(t,e),o.selectionStart,o.selectionEnd,"end"),o.dispatchEvent(new Event("input",{bubbles:!0})))}},{label:"Copy the placeholder",icon:"copy",action:()=>Ie(mo(t,e),"Placeholder")},{type:"divider"},{label:"Reset the sample",icon:"rotate-ccw",action:()=>{delete t.samples[e],tt=[],uo(),H()}}]};{const e=E.contextMenu(we,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>Ks(t.dataset.sample)});i(()=>e?.destroy?.()),we.addEventListener("click",t=>{const o=t.target.closest(".pe-kebab");o&&Ce(o)}),we.addEventListener("keydown",t=>{const o=t.target.closest(".pe-comp-prop");o&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Ce(o))})}const ae={},Se=O("#component-preview"),xt=e=>{try{Se?.contentWindow?.postMessage(e,"*")}catch{}};let fo=Se.contentDocument?.readyState==="complete"&&/component-preview/.test(Se.contentDocument?.URL||""),Pt=!1,ot=null;const Ca=()=>{fo=!0,ot&&c()&&(xt({type:"mount",payload:ot}),Pt=!0),ot=null};Se.addEventListener("load",Ca),i(()=>Se.removeEventListener("load",Ca));const Us=e=>{if(!fo){ot=e;return}if(!Pt){Pt=!0,xt({type:"mount",payload:e});return}ot=e,fo=!1,Pt=!1,Se.src=Se.src};let Ot="";const bo=(e=!1)=>{const t=N.get(a.compTab.peek());if(!(!t||t.state!=="ok"||!t.js)){if(a.viewMode.peek()==="write"){t.previewStale=!0;return}!e&&Ot===t.name&&!t.previewStale||(t.previewStale=!1,Ot=t.name,Us({compiledJs:t.js,tagName:`dm-${t.name}`,props:t.previewProps}))}},at=e=>{const t=++e.seq;return e.state="compiling",a.compTab.peek()===e.name&&(a.compState.value="compiling"),e.compiling=(async()=>{let o;try{o=await h.components.compile(e.name,e.form.source)}catch(s){o={errors:[{type:"request",message:s.message||"Compile request failed."}]}}if(!(t!==e.seq||!c())){if(o.errors?.length?(e.state="error",e.errors=o.errors.map(s=>`[${s.type}] ${s.message}`).join(`
|
|
138
|
+
`),e.js=null):(e.state="ok",e.errors="",e.js=o.js),a.compTab.peek()!==e.name){e.previewStale=!0;return}a.compState.value=e.state,a.compErrors.value=e.errors,e.state==="ok"&&(yo(),bo(!0))}})().finally(()=>{e.compiling&&t===e.seq&&(e.compiling=null)}),e.compiling};let Rt=null,At=null;i(()=>clearTimeout(Rt));const xa=()=>{const e=N.get(a.compTab.peek());if(!e||!ae.template)return;a.compReady.value=!1;const t=ut(e.form.source);for(const o of zt)ae[o].setValue(t[o]),ae[o].textarea.readOnly=e.readOnly;a.compReadOnly.value=e.readOnly,a.compProject.value=e.form.project,qo(O("#comp-project"),e.form.project),a.compBundled.value=e.form.bundled,a.compState.value=e.state,a.compErrors.value=e.errors,a.compNote.value="",a.compReady.value=!0,yo(),e.state==="ok"?bo():e.state||at(e)},vo=()=>{a.compTab.peek()&&(a.compTab.value="",a.compReady.value=!1)},Pa={page:"details",block:"details",component:"details"};let $t="";i(M.effect(()=>{const e=Qo.value;e!==$t&&($t&&(Pa[$t]=a.dockTab.peek()),$t=e,e&&(a.dockTab.value=Pa[e]||"details",e==="page"&&a.dockTab.peek()==="history"&&a.dockOn.peek()&&m?.isEdit&&!Qe&&pe()))}));const go=async(e,{quiet:t=!1}={})=>{if(je.has(e))return je.get(e);if(N.has(e))return N.get(e);const o=(async()=>{try{const s=await h.components.get(e);if(!c()||!P.includes(X({kind:"component",id:e})))return null;const r=s.readOnly===!0||s.origin==="plugin",n={project:s.meta?.project||"",bundled:!!s.bundled,source:os(ut(s.source))},l={name:e,comp:s,saved:n,base:Fo({name:e,...n}),form:{...n},meta:s.meta||{},readOnly:r,state:"",errors:"",js:null,seq:0,compiling:null,previewProps:{},previewStale:!1};let g=null;try{g=S.get(_e(e))}catch{}const $=nr(g,l.base);if(t&&(!$.ok||$.stale||r))return null;const G=$o({kind:"component",id:e});if(t&&!G)return null;if($.ok&&!r){const Me=g.at?` (${qe(g.at)})`:"",Co=G&&!$.stale||await E.confirm($.stale?`You left unsaved changes to the component "${e}"${Me}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to the component "${e}"${Me}. Restore them?`);if(!c())return null;Co&&(l.form={project:String(g.project??""),bundled:g.bundled===!0,source:String(g.source)},G&&!$.stale&&E.toast(`Your unsaved changes to the component "${e}" are back.`,{type:"info"}))}return g&&J(_e(e)),N.set(e,l),ke(),l}catch(s){return c()&&a.compTab.peek()===e&&(a.compNote.value=`Could not open <dm-${e}>: ${s.message||"unknown error"}`),null}finally{je.delete(e)}})();return je.set(e,o),o},st=async e=>{if(!e||!le.has(e))return;const t=X({kind:"component",id:e});({ids:P}=Bo(P,t)),a.active.value=t,a.blockTab.value="",a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.peek()==="preview"&&(a.viewMode.value="split"),a.compTab.value=e,te(),q(),z();let o=N.get(e);o||(a.compReady.value=!1,a.compReadOnly.value=!1,a.compState.value="",a.compNote.value=`Opening <dm-${e}>\u2026`,o=await go(e)),!(!o||!c()||a.compTab.peek()!==e)&&(xa(),q(),z(),ae[a.compPart.peek()]?.focus())},Hs=(e,t)=>{const o=N.get(a.compTab.peek());if(!o||!a.compReady.peek()||o.readOnly)return;const s=ut(o.form.source);s[e]=t,o.form.source=os(s),clearTimeout(Rt),At=o,Rt=setTimeout(()=>{At=null,ke(),at(o)},300)};i(M.effect(()=>{const e=a.compProject.value,t=a.compBundled.value,o=N.get(a.compTab.peek());!o||!a.compReady.peek()||o.readOnly||o.form.project===e&&o.form.bundled===t||(o.form.project=e,o.form.bundled=t,ke())}));const Ee=O("#pe-comp-props");let ho=[];const ko=e=>{Ee.replaceChildren();const t=document.createElement("p");t.className="pe-dock-none",t.textContent=e,Ee.appendChild(t)};function yo(){const e=N.get(a.compTab.peek());if(!e){ho=[],ko("Open a component to try its props.");return}const{rows:t,error:o}=as(ut(e.form.source).props,e.previewProps);if(ho=t,o){ko("The <props> block is not valid JSON - fix it to see the props here.");return}if(!t.length){e.previewProps={},ko("No props declared. Add them in the <props> block.");return}e.previewProps=Object.fromEntries(t.map(s=>[s.name,s.value])),Ee.replaceChildren();for(const s of t){const r=document.createElement("div");r.className="pe-comp-prop",r.tabIndex=0,r.dataset.prop=s.name;const n=document.createElement("label");n.className="pe-comp-prop-label",n.textContent=s.label,n.title=`${s.name} (${s.type})`;const l=s.type==="array"||s.type==="object"?document.createElement("textarea"):document.createElement("input");s.type==="boolean"?(l.type="checkbox",l.className="form-check",l.checked=ss(s.type,s.value)):(l.tagName==="INPUT"?l.type=s.type==="number"?"number":"text":l.rows=2,l.className="form-input form-input--sm pe-comp-prop-input",l.value=ss(s.type,s.value)),l.setAttribute("aria-label",s.label);const g=()=>{e.previewProps[s.name]=ir(s.type,s.type==="boolean"?l.checked:l.value),Ot===e.name&&xt({type:"update",payload:{props:e.previewProps}})};l.addEventListener("input",g),l.addEventListener("change",g);const $=document.createElement("button");$.type="button",$.className="pe-kebab",$.setAttribute("aria-label",`Actions for ${s.label}`),$.title="Actions",$.textContent="\u22EE",r.append(n,l,$),Ee.appendChild(r)}}const Ie=async(e,t)=>{try{await navigator.clipboard.writeText(e),E.toast(`${t} copied.`,{type:"success"})}catch{E.toast(e,{type:"info",duration:8e3})}},Js=e=>{const t=N.get(a.compTab.peek()),o=ho.find(n=>n.name===e);if(!t||!o)return[];const s=sr(o.name,o.type,t.previewProps[o.name]),r=`dm-${t.name}`;return[{header:o.label},{label:"Reset to its default",icon:"rotate-ccw",action:()=>{delete t.previewProps[o.name];const n=as(ut(t.form.source).props,{}).rows.find(l=>l.name===o.name);t.previewProps[o.name]=n?n.value:null,yo(),Ot===t.name&&xt({type:"update",payload:{props:t.previewProps}})}},{label:"Copy as an attribute",icon:"copy",disabled:!s,action:()=>Ie(s,"Attribute")},{label:"Copy the tag with this value",icon:"code",action:()=>Ie(`<${r}${s?` ${s}`:""}></${r}>`,"Tag")},{label:"Copy the prop name",icon:"copy",action:()=>Ie(o.name,"Name")},{type:"divider"},{label:"Edit in <props>",icon:"edit",action:()=>{a.compPart.value="props";const n=ae.props?.textarea;if(!n)return;const l=n.value.indexOf(`"${o.name}"`);n.focus(),l>=0&&n.setSelectionRange(l,l+o.name.length+2)}}]};{const e=E.contextMenu(Ee,{cursor:"glyph",match:".pe-comp-prop",inherit:!1,density:"compact",items:t=>Js(t.dataset.prop)});i(()=>e?.destroy?.()),Ee.addEventListener("click",t=>{const o=t.target.closest(".pe-kebab");o&&Ce(o)}),Ee.addEventListener("keydown",t=>{const o=t.target.closest(".pe-comp-prop");o&&t.key==="F10"&&t.shiftKey&&(t.preventDefault(),Ce(o))})}let Fe=!1;const Oa=()=>Ft()||"",Lt=async({project:e=Oa(),seed:t="",content:o=gr,css:s=""}={})=>{if(!Fe){Fe=!0;try{const n=await ls({kind:"block",projects:se,taken:[...be],project:e,name:t,check:Rn,register:i,create:async(l,g)=>{await h.blocks.put(l,Za({name:l,project:g,bundled:!1,content:o,css:s}))}});if(!c())return;if(!n){te();return}if(await _o(),!c())return;pt("#/blocks"),E.toast(`Created the block "${n}".`,{type:"success"}),Be(n)}finally{Fe=!1}}},nt=async({name:e="",source:t=ar,project:o=Oa()}={})=>{if(!Fe){Fe=!0;try{const s=await ls({kind:"component",projects:se,taken:[...le],project:o,name:e,register:i,check:(r,n)=>rr(r,{isNew:!0,taken:n}),create:async(r,n)=>{await h.components.put(r,{source:t,bundled:!1,meta:n?{project:n}:{}})}});if(!c())return;if(!s){te();return}if(await Ko(),!c())return;pt("#/components"),E.toast(`Created the component "${s}".`,{type:"success"}),st(s)}finally{Fe=!1}}},Ra=k.querySelector(".dm-plugin-body")||k,Ws=M.computed(()=>{a.blockRev.value,a.compRev.value,a.docRev.value,W(K()),a.base.value;const e=I(a.active.value),t=[...Re().map(s=>X({kind:"page",id:s.id})),...ge().map(s=>X({kind:"block",id:s})),...Ye().map(s=>X({kind:"component",id:s}))];let o=[];return e?.kind==="block"||e?.kind==="component"?o=[a.active.value]:e?.kind==="page"&&m&&(o=[a.active.value,...ge().filter(yt).map(s=>X({kind:"block",id:s}))]),er(t,o)}),Ys={...p,...a,dirty:ks,pageDirty:gs,activeDirty:ys,cssUnsaved:hs,hasPage:ws,activeKind:Xo,canView:Zo,viewHref:Ss,problem:Es,statusText:Ts,saveAll:Ws,dockKind:Qo,blockTagText:Ms,compStateText:Cs,compTagText:xs,anySelected:Ps,allSelected:Os,someSelected:Rs},rt=(e,t)=>Ue(e,t),wo={save:()=>io(!1),saveAllOpen:()=>io(!0),discard:async()=>{if(await Ea(),!c())return;const e=I(a.active.peek());if(e?.kind==="block"){const t=L.get(e.id);if(!t||!de(t)||!await E.confirm(`Discard the changes to "${e.id}" you have not saved?`))return;t.form={...t.saved},J(Ve(e.id)),Sa(),Le()}else if(e?.kind==="component"){const t=N.get(e.id);if(!t||!he(t)||!await E.confirm(`Discard the changes to the component "${e.id}" you have not saved?`))return;t.form={...t.saved},t.state="",J(_e(e.id)),xa(),ke()}else{if(!m||e?.kind==="page"&&e.id!==m.id||!(Wt()||ht())||!await E.confirm("Discard the changes to this page you have not saved?"))return;Ze(JSON.parse(a.base.peek())),p.cssOpened.peek()&&(p.css.value=ee.peek()),J(ze(m.id)),J(ft),Xe()}q(),z(),H()},newPage:()=>ue(""),newBlock:()=>Lt(),newComponent:()=>nt(),toggleSection:e=>{const t={here:"secHere",pages:"secPages",blocks:"secBlocks",components:"secComps"}[e];t&&(a[t].value=!a[t].peek(),rt(t,a[t].peek()))},toggleExplorer:()=>{a.explorerOn.value=!a.explorerOn.peek(),rt("explorer",a.explorerOn.peek())},toggleDock:()=>{a.dockOn.value=!a.dockOn.peek(),rt("dock",a.dockOn.peek()),a.dockOn.peek()&&a.dockTab.peek()==="history"&&!Qe&&pe()},showDock:e=>{a.dockTab.value=e,e==="history"&&m?.isEdit&&!Qe&&pe()},schemaChanged:()=>{a.seoSchemaNote.value=Po(p.seoSchema.peek()).error},insertSchemaExample:async e=>{const t=e?.target,o=t?.value||"";if(t&&(t.value=""),!o||p.seoSchema.peek().trim()&&!await E.confirm("Replace the structured data with the example?"))return;const s=p.urlPath.peek().trim()||m?.id||"/";p.seoSchema.value=mn(o,{title:p.seoTitle.peek().trim()||p.title.peek().trim(),description:p.seoDesc.peek().trim()||p.description.peek().trim(),url:`${location.origin}${s}`,siteName:Y?.title||Y?.seo?.defaultTitle||""}),a.seoSchemaNote.value=""},setBlockPart:e=>{a.blockPart.value=e==="css"?"css":"html",(e==="css"?me:oe)?.focus()},formatBlock:()=>{const e=a.blockPart.peek()==="css",t=e?me:oe;!t||!a.blockReady.peek()||Ho(Ke(t.textarea,e?"css":"html"))},copyShortcode:async()=>{const e=a.blockTab.peek();e&&await Ie(Nn(e),"Shortcode")},setCompPart:e=>{a.compPart.value=zt.includes(e)?e:"template",ae[a.compPart.peek()]?.focus()},copyComponent:()=>{const e=N.get(a.compTab.peek());e&&nt({name:Lo(e.name,[...le]),source:e.form.source})},setMode:e=>{a.cssMode.peek()&&(a.cssMode.value=!1),a.viewMode.value=jo(e),rt("viewMode",a.viewMode.peek()),e!=="write"&&(H(!0),bo())},toggleFullscreen:()=>{a.fullscreen.value=!a.fullscreen.peek(),rt("fullscreen",a.fullscreen.peek())},toggleCss:()=>{a.blockTab.peek()||a.compTab.peek()||(a.cssMode.peek()?(a.cssMode.value=!1,a.viewMode.value=jo(Pe().viewMode),Q.refresh()):(ia(),a.cssMode.value=!0,a.viewMode.value="split"))},formatMarkdown:()=>{Jt(),Ho(Ke(_,"md"))},toggleFold:()=>{a.folded.peek()?Q.unfoldAll():Q.foldAll(),a.folded.value=!a.folded.peek(),We.paint()},setWidth:e=>{a.liveWidth.value=e},refreshPreview:()=>{if(a.compTab.peek()){const e=N.get(a.compTab.peek());e&&at(e)}else H(!0)},share:async()=>{if(!m?.isEdit){E.toast("Save the page first - a share link needs a page to point at.",{type:"warning"});return}await br(m.id)},toggleAll:e=>{const t=!!e?.target?.checked;a.versions.peek().forEach(o=>{o.checked.value=t})},deleteSelected:async()=>{const e=a.versions.peek().filter(o=>o.checked.peek()).map(o=>o.filename),t=e.length;if(!(!t||!await E.confirm(`Delete ${t} version${t>1?"s":""}? This cannot be undone.`)))try{await h.versions.bulkDelete(m.id,e),E.toast(`${t} version${t>1?"s":""} deleted.`,{type:"success"}),await pe()}catch(o){E.toast("Delete failed: "+o.message,{type:"error"})}},saveNamed:async()=>{if(!m?.isEdit)return;const e=m;await vr(e.id)&&c()&&m===e&&await pe()},closeCompare:()=>{a.compare.value=null,Tt=null},restoreCompared:()=>ma(Tt)};Ge.querySelectorAll("[data-tip]").forEach(e=>E.tooltip(e,{content:e.getAttribute("data-tip"),position:"top"}));const Gs=M.applyBindings(Ys,Ra,{methods:wo});i(()=>Gs.dispose()),oe=zo(O('.pe-block-edit[data-part="html"]'),{ariaLabel:"Block template HTML",language:"html",onChange:e=>Ta("content",e)}),me=zo(O('.pe-block-edit[data-part="css"]'),{ariaLabel:"Block CSS",language:"css",placeholder:`/* Scoped to this block - e.g.
|
|
121
139
|
color: var(--dm-primary);
|
|
122
|
-
h3 { font-size: 1.25rem; } */`,onChange:e=>
|
|
123
|
-
`).map((
|
|
140
|
+
h3 { font-size: 1.25rem; } */`,onChange:e=>Ta("css",e)}),i(()=>{oe.destroy(),me.destroy()});for(const e of zt)ae[e]=zo(O(`.pe-comp-edit[data-part="${e}"]`),{ariaLabel:`Component <${e}> block`,language:{template:"html",props:"json",script:"js",style:"css"}[e],onChange:t=>Hs(e,t)});i(()=>{for(const e of zt)ae[e].destroy()});const Te=O(".pe-tabs-list"),Nt=e=>e.target.closest(".pe-tab")?.dataset.id||"",Aa=e=>{const t=Nt(e);t&&(e.target.closest(".pe-tab-close")?et(t):Ct(t))},$a=e=>{e.button===1&&Nt(e)&&(e.preventDefault(),et(Nt(e)))},La=e=>{const t=Nt(e);!t||e.target.closest(".pe-tab-close")||(e.key==="Enter"||e.key===" "?(e.preventDefault(),Ct(t)):e.key==="Delete"?(e.preventDefault(),et(t)):e.key==="F10"&&e.shiftKey&&(e.preventDefault(),Ce(e.target.closest(".pe-tab"))))};Te.addEventListener("click",Aa),Te.addEventListener("auxclick",$a),Te.addEventListener("keydown",La),i(()=>{Te.removeEventListener("click",Aa),Te.removeEventListener("auxclick",$a),Te.removeEventListener("keydown",La)});const So=e=>{for(const t of e)et(t)},Xs=async e=>{const t=I(e);if(t?.id)if(t.kind==="page")a.active.peek()!==e&&await ue(t.id),c()&&m?.id===t.id&&Ba();else if(t.kind==="block"){const s=(L.get(t.id)||await lo(t.id,{quiet:!0}))?.form||Xa(await h.blocks.get(t.id).catch(()=>null));c()&&Lt({seed:Lo(t.id,[...be]),content:s.content,css:s.css})}else{const s=(N.get(t.id)||await go(t.id,{quiet:!0}))?.form.source??(await h.components.get(t.id).catch(()=>null))?.source??"";c()&&nt({name:Lo(t.id,[...le]),source:s})}},Zs=e=>{const t=I(e);if(!t||!P.includes(e))return[];const o=P.indexOf(e),s=Ne.get(e)?.label.peek()||t.id,r=n=>{P=Wn(P,e,n),q()};return[{header:s},{label:"Close",icon:"x",action:()=>et(e)},{label:"Close others",icon:"x-circle",disabled:P.length<2,action:()=>So(Io(P,e,"others"))},{label:"Close to the right",icon:"chevrons-right",disabled:o>=P.length-1,action:()=>So(Io(P,e,"right"))},{label:"Close all",icon:"layers",action:()=>So(Io(P,e,"all"))},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:o===0,action:()=>r(-1)},{label:"Move right",icon:"arrow-right",disabled:o>=P.length-1,action:()=>r(1)},{type:"divider"},{label:t.kind==="page"?"Clone page\u2026":`Duplicate ${t.kind}\u2026`,icon:"copy",disabled:!t.id,action:()=>Xs(e)},{label:"Copy link",icon:"link",disabled:!t.id,action:()=>Ie(`${location.origin}${location.pathname}#${es(t)}`,"Link")}]};{const e=E.contextMenu(Te,{cursor:"glyph",match:".pe-tab",inherit:!1,density:"compact",items:t=>Zs(t.dataset.id)});i(()=>e?.destroy?.())}const Na=O(".pe-ex-scroll"),Da=e=>{const t=e.target.closest(".pe-ex-item"),o=t?.dataset.id||"";if(!o)return;const s=o.indexOf(":"),r=o.slice(0,s),n=o.slice(s+1),l=()=>{ye.has(n)?ye.delete(n):ye.add(n);try{S.set(us,[...ye])}catch{}z()};if(r==="folder"){l();return}if(r==="page"&&e.target.closest(".pe-ex-chev")&&t.classList.contains("is-folder")){l();return}r==="page"?ue(n):r==="block"?Be(n):r==="component"&&st(n)};Na.addEventListener("click",Da),i(()=>Na.removeEventListener("click",Da));let Eo=null;i(()=>clearTimeout(Eo)),_.addEventListener("input",()=>{Q.refresh(),ce.scrollTop=_.scrollTop,!gt&&(clearTimeout(Eo),Eo=setTimeout(ra,120),H())}),_.addEventListener("scroll",()=>{ce.scrollTop=_.scrollTop}),ve.addEventListener("scroll",()=>{ce.scrollTop=ve.scrollTop}),i(M.effect(()=>{for(const e of Object.keys(p))e!=="css"&&e!=="cssOpened"&&p[e].value;a.bodyRev.value,H()})),i(M.effect(()=>{a.bodyRev.value,a.docRev.value;const e=m?Ht():"";kt=m?$n(e,bt.map(t=>t.name),bt.filter(t=>t.shortTag).map(t=>t.name)):[],ta=m?Jn(e,le):[],wt(),z()})),i(M.effect(()=>{a.blockRev.value,a.compRev.value,a.docRev.value,a.active.value,a.filter.value,p.title.value,q(),z()})),i(M.effect(()=>{ce.hidden=!!(a.blockTab.value||a.compTab.value)}));let To=null;i(()=>clearTimeout(To)),i(M.effect(()=>{const e=p.css.value;a.cssMode.value&&(Ut.paint(),Cr(ce,e,ve),clearTimeout(To),To=setTimeout(oa,400))}));{const e=O(".pe-version-list"),t=E.contextMenu(e,{cursor:"glyph",match:"tr[data-file]",inherit:!1,density:"compact",items:o=>{const s=oo(o.dataset.file);return s?[{header:rs(s)},{label:"Show beside the editor",icon:"columns",action:()=>ao(s)},{label:"Restore this version\u2026",icon:"rotate-ccw",action:()=>ma(s.filename)},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,action:()=>js(s)}]:[]}});i(()=>t?.destroy?.()),e.addEventListener("click",o=>{const s=o.target.closest("tr[data-file]");if(!s||o.target.matches("input"))return;const r=o.target.closest(".pe-kebab");if(r){Ce(r);return}const n=oo(s.dataset.file);n&&ao(n)}),e.addEventListener("keydown",o=>{const s=o.target.closest("tr[data-file]");if(s){if(o.key==="F10"&&o.shiftKey)o.preventDefault(),Ce(s);else if(o.key==="Enter"&&o.target===s){const r=oo(s.dataset.file);r&&ao(r)}}})}xr(O("#editor-body"),O("#editor-divider"),i),ln({image:e=>(a.cssMode.value=!1,Pr(e)),scribe:e=>pn(e),card:e=>en.open(e,{onMediaBrowse:()=>h.media.list().catch(()=>[])}),timeline:e=>tn.open(e,{onPreview:async t=>{try{return await h.pages.preview(t)}catch{return null}},onListCollections:()=>h.collections.list().catch(()=>[]),onGetCollection:t=>h.collections.get(t).catch(()=>null)})}),to.onEffects(e=>nn.open(e)),to.onHelp(()=>Va()),to.onTutorial(e=>un(e));const ja=e=>{if(!c())return;const t=or(e);t&&(e.preventDefault(),t==="save"?io(!1):t==="layout"?wo.setMode(Yn(a.viewMode.peek())):wo.toggleExplorer())};document.addEventListener("keydown",ja),i(()=>document.removeEventListener("keydown",ja));const it=k.querySelector(".dm-plugin-banner-name");i(M.effect(()=>{const e=a.curId.value,t=Xo.value,o=a.blockTab.value||a.compTab.value,s=t==="block"?"Blocks":t==="component"?"Components":"Pages",r=t==="block"||t==="component"?o:e===null?"Editor":Tn(!!e,p.title.value,e);if(!c()||(document.title=`${r} - Domma CMS`,!it))return;const n=it.querySelector(".dm-plugin-banner-parent"),l=it.lastChild;n&&l&&l.nodeType===3&&it.querySelector(".dm-plugin-banner-sep")?(n.textContent=s,l.textContent=r):it.textContent=`${s} \u203A ${r}`})),dt(d,{id:"page-editor-back",icon:"arrow-left",title:"Back to Pages",onClick:()=>R.navigate("/pages")}),dt(d,{id:"page-editor-help",icon:"help-circle",title:"Editor reference",onClick:()=>Va()});const Qs=dt(d,{id:"page-editor-view",icon:"external-link",title:"View on the site",onClick:()=>{m?.isEdit&&window.open(p.urlPath.peek().trim()||m.id,"_blank","noopener")}}),Ba=()=>{if(!m?.isEdit||a.saving.peek()||!c())return;const e=m,t=K(),o=Ro({...t,urlPath:e.id});if(o.length){E.toast(o[0].text,{type:"warning"});return}const s=vn({page:e.page,frontmatter:{...Gt(t,e,Xt(t)),adminTags:t.adminTags},body:t.body,source:e.id,pages:D,onCloned:async r=>{c()&&(await ha(),c()&&ue(r))}});i(()=>s.close())},Mo=dt(d,{id:"page-editor-clone",icon:"copy",title:"Clone page",onClick:Ba});i(M.effect(()=>{const e=Zo.value;for(const t of[Qs,Mo])t&&(t.disabled=!e);Mo&&(Mo.title=e?"Clone page":"Open a saved page to clone it")})),dt(d,{id:"page-editor-settings",icon:"settings",title:"Editor settings - font, size, spacing",onClick:()=>{Yo=fr(Jo,Wo)}}),i(qa(Ra,{position:"bottom"})),Domma.icons.scan();let Ia={ids:[],active:""};try{Ia=Zn(S.get(ps))}catch{}const lt=Ha(b),ct=tr(lt,Ia,history.state?.pe===1);P=ct.ids.filter(e=>{const t=I(e);return t.kind==="page"||(t.kind==="component"?le:be).has(t.id)}),ba=!1;for(const e of P){const t=I(e);t.kind==="page"&&ka(t.id)}q(),z(),wt();for(const e of Ae)lo(e,{quiet:!0}).then(()=>{c()&&(q(),z())});for(const e of P){const t=I(e);t.kind==="component"&&e!==ct.active&&xe(t)&&go(t.id,{quiet:!0}).then(()=>{c()&&(q(),z())})}const Fa=lt&&!lt.id&<.kind!=="page";ct.active&&P.includes(ct.active)?await Ct(ct.active):P.length?await Ct(P[0]):Fa||te(),Fa&&c()&&(await(lt.kind==="block"?Lt:nt)({project:Ft(b)||""}),c()&&te())}};function qo(d,b){!d||typeof b!="string"||!b||[...d.options].some(v=>v.value===b)||(d.append(Object.assign(document.createElement("option"),{value:b,textContent:`${b} (custom)`})),d.value=b)}function Cr(d,b,v){d.replaceChildren(...String(b).split(`
|
|
141
|
+
`).map((i,y)=>{const u=document.createElement("span");u.className="editor-line-number-row";const w=document.createElement("span");w.className="fold-toggle fold-toggle--empty";const k=document.createElement("span");return k.className="editor-line-num",k.textContent=String(y+1),u.append(w,k),u})),d.scrollTop=v.scrollTop}function xr(d,b,v){if(!d||!b)return;const i=15,y=85,u=c=>Math.min(y,Math.max(i,c));let w=u(parseFloat(Pe().splitRatio)||50);const k=c=>{d.style.setProperty("--dm-editor-split",w+"%"),b.setAttribute("aria-valuenow",String(Math.round(w))),c&&Ue("splitRatio",w)};k(!1);const x=c=>{const A=d.getBoundingClientRect();A.width&&(w=u((c.clientX-A.left)/A.width*100)),k(!1)},j=()=>{document.removeEventListener("pointermove",x),document.removeEventListener("pointerup",j),d.classList.remove("is-resizing"),b.classList.remove("is-dragging"),k(!0)};v(j),b.addEventListener("pointerdown",c=>{window.matchMedia("(max-width: 768px)").matches||(c.preventDefault(),d.classList.add("is-resizing"),b.classList.add("is-dragging"),document.addEventListener("pointermove",x),document.addEventListener("pointerup",j))}),b.addEventListener("dblclick",()=>{w=50,k(!0)}),b.addEventListener("keydown",c=>{const A=c.shiftKey?10:2;let V=w;if(c.key==="ArrowLeft")V=w-A;else if(c.key==="ArrowRight")V=w+A;else if(c.key==="Home")V=i;else if(c.key==="End")V=y;else if(c.key==="Enter"||c.key===" ")V=50;else return;c.preventDefault(),w=u(V),k(!0)})}async function Pr(d){const b=d.selectionStart,v=d.selectionEnd,i=await bn({title:"Upload or insert an image"});if(!i||!d.isConnected)return;let y=i.split("/").pop().split("?")[0];try{y=decodeURIComponent(y)}catch{}const u=y.replace(/[\\[\]]/g,"\\$&"),w=i.replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/ /g,"%20");d.setSelectionRange(b,v),sn(d,``),d.dispatchEvent(new Event("input",{bubbles:!0}))}function Or(d,b,v){const i=Object.entries(b||{});return d?(d.replaceChildren(...i.map(([y,u])=>new Option(u?.label||y,y))),i.some(([y])=>y===v)?v:i[0]?.[0]||v):v}async function Rr(d,b){if(!d)return;let v=[];try{const x=await h.get("/collections/roles/entries?limit=100");v=Array.isArray(x)?x:x?.entries||[]}catch{}const i=v.map(x=>({name:x.data?.name,label:x.data?.label||x.data?.name,level:x.data?.level??99})).filter(x=>x.name&&x.level!==0).sort((x,j)=>j.level-x.level),y=(x,j)=>Object.assign(document.createElement("option"),{value:x,textContent:j}),u=()=>{typeof b=="string"&&b&&![...d.options].some(x=>x.value===b)&&d.append(y(b,`${b} (custom)`))};if(!i.length)return u();const w=document.createElement("optgroup");w.label="Role and above",i.forEach(x=>w.append(y(x.name,`${x.label} and above`)));const k=document.createElement("optgroup");k.label="Role only",i.forEach(x=>k.append(y(`=${x.name}`,`${x.label} only`))),d.replaceChildren(y("public","Public - everyone"),w,k,y("private","Private - super-admin only")),u()}
|
package/docs/DommaFeatures.md
CHANGED
|
@@ -1334,6 +1334,7 @@ The four Tools below were plugins until 0.67.0 and are now part of the CMS. They
|
|
|
1334
1334
|
- **`TRUST_PROXY`** - Takes Fastify's own values: `loopback` (recommended behind Apache or the manager, and the default for new sites the manager creates), a list of addresses or CIDR ranges, or a hop count. `1` or `true` trusts every hop, which lets a visitor spoof their IP address.
|
|
1335
1335
|
Where: `server/services/network.js` - Since: 0.83.0.
|
|
1336
1336
|
- **Reading other sites from a page** - `csp.connectHosts` in `config/server.json` lets public pages fetch from the listed hosts (added to the CSP's `connect-src`, https only); domma-cms.com's Pricing page reads the Pro Tool prices from dcm this way.
|
|
1337
|
+
- **Showing images from other sites** - `csp.imgHosts` in `config/server.json` lets public pages show images from the listed hosts (added to the CSP's `img-src`, https only); without it only the site's own images load.
|
|
1337
1338
|
- **Public asset CORS** - `cors.publicPrefixes` in `config/server.json` adds `Access-Control-Allow-Origin: *` to GET and HEAD requests under the listed paths only, so other sites can import scripts or assets from them.
|
|
1338
1339
|
Where: `server/services/publicCors.js`.
|
|
1339
1340
|
- **Email service** - A Nodemailer transport built from the SMTP settings in `site.json`, used for form emails, password resets and notices. With no SMTP host set, mail goes to a throwaway Ethereal test inbox, which reports success but delivers to nobody. The CMS makes this visible in Settings, in health checks and with an "Email not set up" notification. New sites ship with an SMTP host, defaulting to a local mail server on port 25 or taken from the environment.
|
package/docs/configuration.md
CHANGED
|
@@ -27,6 +27,7 @@ Controls the HTTP server.
|
|
|
27
27
|
| `cors.origin` | boolean / string / string[] | `false` | Passed directly to `@fastify/cors`. `false` disables CORS. Set to `"*"` or a domain to allow cross-origin requests |
|
|
28
28
|
| `cors.publicPrefixes` | string[] | `[]` | Absolute path prefixes whose **GET and HEAD** responses carry `Access-Control-Allow-Origin: *`, so other origins can hot-link those assets. A module script is fetched in CORS mode, so this is what makes e.g. `/public/demos/` importable from elsewhere. `@fastify/cors` decides per origin and cannot express a path, hence the separate key. Reads only - it never widens a write, and it applies to listed prefixes only. A value that is not an absolute path is ignored |
|
|
29
29
|
| `csp.connectHosts` | string[] | `[]` | Other sites a public page may read from in the browser (`fetch`), added to the Content-Security-Policy's `connect-src` as `https://<host>`. The site itself and cdn.jsdelivr.net are always allowed. Bare hostnames only (`dcm.dommajs.org`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
|
|
30
|
+
| `csp.imgHosts` | string[] | `[]` | Other sites a public page may show images from (an `<img>` hosted elsewhere, such as a badge or partner logo), added to the Content-Security-Policy's `img-src` as `https://<host>`. The site itself, `data:` and `blob:` are always allowed; without this an image from another site is blocked - upload it to Media instead if you can. Bare hostnames only (`statics.startupbase.io`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
|
|
30
31
|
| `uploads.maxFileSize` | number | `10485760` (10 MB) | Maximum allowed upload size in bytes |
|
|
31
32
|
| `logging.level` | string | Log verbosity: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent`. Defaults to `info` in development, `warn` otherwise. `warn` hides 4xx responses and plugin `log.info` output, so a failed admin action leaves no trace - raise it to `info` if you need to diagnose one. Overridden by the `LOG_LEVEL` env var. |
|
|
32
33
|
| `restartOnPluginToggle` | boolean | `true` | Restart the server automatically when an admin enables or disables a plugin. See **Plugin toggles restart the server** below |
|
|
@@ -1528,6 +1529,44 @@ absent here: the editor is already telling you what you are looking at.
|
|
|
1528
1529
|
Backed by `POST /api/pages/preview/full` (`pages.read`), which takes
|
|
1529
1530
|
`{urlPath, frontmatter, body}` and returns `{html}`.
|
|
1530
1531
|
|
|
1532
|
+
### Formatting and indenting in the editor
|
|
1533
|
+
|
|
1534
|
+
**Shift+Alt+F** (Option+Shift+F on a Mac) formats what you are editing, as one
|
|
1535
|
+
step Ctrl+Z undoes. The toolbar's format button does the same.
|
|
1536
|
+
|
|
1537
|
+
- **Page body (Markdown)**: trailing spaces off (a two-space line break before
|
|
1538
|
+
more text stays), runs of blank lines down to one, pipe tables lined up.
|
|
1539
|
+
Indentation is never changed - four spaces would turn text into a code block -
|
|
1540
|
+
and fenced code is left exactly as written. Folded shortcodes are unfolded first.
|
|
1541
|
+
The exception is a line holding only a container short tag (`[grid]`, `[col]`,
|
|
1542
|
+
`[card]`, `[tabs]`, `[tab]`…, `SHORT_TAG_CONTAINERS`): those lines are indented
|
|
1543
|
+
by nesting, so the structure reads at a glance. The content between them stays
|
|
1544
|
+
at the margin. The renderer takes a tag line's indent off before anything else
|
|
1545
|
+
(`unindentShortTags()` in `markdown.js`), so the indent never reaches the page.
|
|
1546
|
+
- **Block and component HTML, JS**: re-indented by nesting only; the code itself
|
|
1547
|
+
is never rewritten. `<pre>` and `<textarea>` keep their lines; a `<script>` or
|
|
1548
|
+
`<style>` inside HTML is indented as JS or CSS.
|
|
1549
|
+
- **CSS** (blocks, components, the page's custom CSS): one declaration a line,
|
|
1550
|
+
`prop: value;`, a blank line between rules.
|
|
1551
|
+
- **JSON** (component props): parsed and printed; invalid JSON is reported, not changed.
|
|
1552
|
+
|
|
1553
|
+
**Editor settings** (the banner cog), under *Typing and formatting*, kept in the
|
|
1554
|
+
browser with the rest of the editor's settings: indent with spaces or tabs, the
|
|
1555
|
+
indent size (2, 4 or 8 - also how wide a tab shows), *Keep a file's own
|
|
1556
|
+
indentation* (on: a block, component or CSS already in tabs or fours keeps them;
|
|
1557
|
+
the page body always takes the setting), *Auto-indent*, *Carry lists and quotes
|
|
1558
|
+
on*, *Indent short tags when formatting*, *Line up tables when formatting*, and
|
|
1559
|
+
*Format on save* (off by default: Save formats the tab on show first; text that
|
|
1560
|
+
cannot be formatted is saved as it is).
|
|
1561
|
+
|
|
1562
|
+
While typing, **Enter** keeps the line's indentation. In the body it carries a
|
|
1563
|
+
list or quote on (numbers count up, a task box comes back empty) and Enter on an
|
|
1564
|
+
empty item ends the list; Shift+Enter is a plain new line. After a short tag line, the
|
|
1565
|
+
next line starts at the margin, where content belongs. In code it goes one
|
|
1566
|
+
deeper after `{`, `(`, `[`, an opening tag or `{{#if}}`, and with the caret
|
|
1567
|
+
between a pair the closer moves to its own line. **Tab / Shift+Tab** indent or
|
|
1568
|
+
outdent every selected line when the selection spans lines.
|
|
1569
|
+
|
|
1531
1570
|
### Share links
|
|
1532
1571
|
|
|
1533
1572
|
**Share link** in the preview-only layout's bar mints a URL that renders one unpublished
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -33,7 +33,7 @@ import * as cache from './services/cache/index.js';
|
|
|
33
33
|
import {parseTrustProxy} from './services/network.js';
|
|
34
34
|
import {requestSerializer} from './services/request-log.js';
|
|
35
35
|
import {rateLimitOptions} from './services/rate-limit.js';
|
|
36
|
-
import {connectSources, frameSources} from './services/embedCsp.js';
|
|
36
|
+
import {connectSources, frameSources, imageSources} from './services/embedCsp.js';
|
|
37
37
|
import {cspSources as gaSources} from './services/googleAnalytics.js';
|
|
38
38
|
|
|
39
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -128,7 +128,8 @@ await app.register(helmet, {
|
|
|
128
128
|
// (services/googleAnalytics.js) - a site without one keeps exactly this policy.
|
|
129
129
|
scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", 'https://cdn.jsdelivr.net', gaSources('script')],
|
|
130
130
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net', 'https://fonts.googleapis.com'],
|
|
131
|
-
|
|
131
|
+
// config/server.json csp.imgHosts: other sites a page may show images from (services/embedCsp.js).
|
|
132
|
+
imgSrc: [...imageSources(serverConfig.csp?.imgHosts), gaSources('img')],
|
|
132
133
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
|
|
133
134
|
// config/server.json csp.connectHosts: other sites a page may read from (services/embedCsp.js).
|
|
134
135
|
connectSrc: [...connectSources(serverConfig.csp?.connectHosts), gaSources('connect')],
|
|
@@ -50,3 +50,22 @@ export function connectSources(connectHosts = []) {
|
|
|
50
50
|
.map(h => `https://${h}`);
|
|
51
51
|
return [...new Set([...ALWAYS_CONNECTED, ...extra])];
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/** What a page may always show images from: the site itself, and inline/data and blob images. */
|
|
55
|
+
export const ALWAYS_IMAGED = ["'self'", 'data:', 'blob:'];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The img-src: the site, data: and blob:, and every host in config/server.json `csp.imgHosts` - for a page
|
|
59
|
+
* that shows an image hosted elsewhere (a badge, a partner logo). Bare hostnames only, served over https;
|
|
60
|
+
* anything else is dropped. Read at boot, like connect-src.
|
|
61
|
+
*
|
|
62
|
+
* @param {string[]} imgHosts - config/server.json csp.imgHosts
|
|
63
|
+
* @returns {string[]} img-src sources
|
|
64
|
+
*/
|
|
65
|
+
export function imageSources(imgHosts = []) {
|
|
66
|
+
const extra = (Array.isArray(imgHosts) ? imgHosts : [])
|
|
67
|
+
.map(h => String(h).trim().toLowerCase())
|
|
68
|
+
.filter(h => HOST_RE.test(h))
|
|
69
|
+
.map(h => `https://${h}`);
|
|
70
|
+
return [...new Set([...ALWAYS_IMAGED, ...extra])];
|
|
71
|
+
}
|
|
@@ -1857,6 +1857,28 @@ export function parseShortcodeAttrs(attrStr) {
|
|
|
1857
1857
|
*/
|
|
1858
1858
|
let scrubCallId = 0;
|
|
1859
1859
|
|
|
1860
|
+
/**
|
|
1861
|
+
* The container short tags whose own lines the editor's format indents by nesting
|
|
1862
|
+
* (SHORT_TAG_CONTAINERS in admin/js/lib/code-format.js - keep the two lists the same;
|
|
1863
|
+
* tests/admin/code-format.test.js checks). Their processors replace only the tag, so
|
|
1864
|
+
* the indent before it would stay in front of the HTML, and marked reads four
|
|
1865
|
+
* spaces as a code block. A line holding nothing but one of these tags loses its
|
|
1866
|
+
* indent here, before anything else runs, so the indent is only ever for the reader.
|
|
1867
|
+
*/
|
|
1868
|
+
export const SHORT_TAG_CONTAINERS = Object.freeze([
|
|
1869
|
+
'grid', 'row', 'col', 'card', 'box', 'header', 'footer', 'tabs', 'tab', 'accordion', 'item', 'carousel', 'slide',
|
|
1870
|
+
'timeline', 'event', 'hero', 'banner', 'slideover', 'center', 'listgroup', 'table', 'dconfig', 'reveal', 'animate',
|
|
1871
|
+
'pulse', 'shake', 'scramble', 'ripple', 'breathe', 'ambient', 'twinkle', 'fireworks', 'scribe', 'wait', 'render', 'redraw'
|
|
1872
|
+
]);
|
|
1873
|
+
const INDENTED_SHORT_TAG = new RegExp(`^[ \\t]+(?=\\[\\/?(?:${SHORT_TAG_CONTAINERS.join('|')})(?:\\s[^\\]\\n]*)?\\][ \\t]*$)`, 'gim');
|
|
1874
|
+
|
|
1875
|
+
/** A short tag line's indent off (above); fenced code keeps its own. */
|
|
1876
|
+
export function unindentShortTags(markdown) {
|
|
1877
|
+
if (!/^[ \t]+\[/m.test(markdown)) return markdown;
|
|
1878
|
+
const {scrubbed, restore} = scrubCodeRegions(markdown);
|
|
1879
|
+
return restore(scrubbed.replace(INDENTED_SHORT_TAG, ''));
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1860
1882
|
export function scrubCodeRegions(markdown, preserveIndented = false) {
|
|
1861
1883
|
const callId = ++scrubCallId;
|
|
1862
1884
|
const store = [];
|
|
@@ -4827,7 +4849,8 @@ export function sanitizePageHtml(html) {
|
|
|
4827
4849
|
* (a row-scoped `[view]`), so it must not share a role-keyed cache entry.
|
|
4828
4850
|
*/
|
|
4829
4851
|
export async function parseMarkdown(raw, opts = {}) {
|
|
4830
|
-
const {data, content} = matter(raw);
|
|
4852
|
+
const {data, content: written} = matter(raw);
|
|
4853
|
+
const content = unindentShortTags(written);
|
|
4831
4854
|
|
|
4832
4855
|
// Pipeline (earlier stages are already HTML when later ones run; keep in step
|
|
4833
4856
|
// with "Shortcode Nesting Rules" in CLAUDE.md and docs/markdown-shortcodes.md):
|