domma-cms 0.36.17 → 0.36.18
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{highlight as S}from"./syntax-highlight.js";function T(){if(document.getElementById("dm-simple-editor-style"))return;const r=document.createElement("style");r.id="dm-simple-editor-style",r.textContent=`
|
|
2
2
|
.dm-edit-wrap {
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: row;
|
|
@@ -43,7 +43,53 @@ function w(){if(document.getElementById("dm-simple-editor-style"))return;const r
|
|
|
43
43
|
overflow: auto;
|
|
44
44
|
box-sizing: border-box;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
/* \u2500\u2500 Highlight overlay (language mode only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
47
|
+
.dm-edit-host {
|
|
48
|
+
position: relative;
|
|
49
|
+
flex: 1;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
display: flex;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
}
|
|
54
|
+
.dm-edit-host .dm-edit-area {
|
|
55
|
+
position: relative;
|
|
56
|
+
z-index: 1;
|
|
57
|
+
width: 100%;
|
|
58
|
+
color: transparent;
|
|
59
|
+
caret-color: var(--dm-text, #ddd);
|
|
60
|
+
}
|
|
61
|
+
.dm-edit-host .dm-edit-area::selection { background: rgba(102, 148, 255, .35); }
|
|
62
|
+
.dm-edit-highlight {
|
|
63
|
+
position: absolute;
|
|
64
|
+
inset: 0;
|
|
65
|
+
z-index: 0;
|
|
66
|
+
margin: 0;
|
|
67
|
+
padding: .75rem;
|
|
68
|
+
font-family: var(--dm-font-mono, 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
69
|
+
font-size: .85rem;
|
|
70
|
+
line-height: 1.55;
|
|
71
|
+
tab-size: 2;
|
|
72
|
+
-moz-tab-size: 2;
|
|
73
|
+
white-space: pre;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
color: var(--dm-text, #ddd);
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
}
|
|
79
|
+
/* Token palette \u2014 single set chosen to stay legible on light and
|
|
80
|
+
dark themes alike. */
|
|
81
|
+
.dm-tok-comment { color: #8b949e; font-style: italic; }
|
|
82
|
+
.dm-tok-string { color: #4f9e4f; }
|
|
83
|
+
.dm-tok-keyword { color: #b559c8; }
|
|
84
|
+
.dm-tok-literal { color: #2f9bb3; }
|
|
85
|
+
.dm-tok-number { color: #c98a2c; }
|
|
86
|
+
.dm-tok-func { color: #4078f2; }
|
|
87
|
+
.dm-tok-key { color: #b8762e; }
|
|
88
|
+
.dm-tok-tag { color: #cc5c66; }
|
|
89
|
+
`,document.head.appendChild(r)}export function createSimpleEditor(r,w={}){if(!r)throw new Error("createSimpleEditor: mountEl is required.");const{initialValue:y="",onChange:v=null,language:x=null,autoIndent:C=!1,onCursor:k=null}=w;for(T();r.firstChild;)r.removeChild(r.firstChild);const a=document.createElement("div"),s=document.createElement("div"),e=document.createElement("textarea");a.className="dm-edit-wrap",s.className="dm-edit-gutter",e.className="dm-edit-area",e.spellcheck=!1,e.setAttribute("autocomplete","off"),e.setAttribute("autocorrect","off"),e.setAttribute("autocapitalize","off"),a.appendChild(s);let l=null;if(x){const t=document.createElement("div");t.className="dm-edit-host",l=document.createElement("pre"),l.className="dm-edit-highlight",t.appendChild(l),t.appendChild(e),a.appendChild(t)}else a.appendChild(e);r.appendChild(a);function f(){l&&(l.innerHTML=S(e.value,x))}function p(){const t=e.value.split(`
|
|
90
|
+
`).length||1,n=new Array(t);for(let i=0;i<t;i++)n[i]=String(i+1);s.textContent=n.join(`
|
|
91
|
+
`);const o=String(t).length;s.style.minWidth=`calc(${o}ch + 1.5rem)`}function u(){requestAnimationFrame(()=>{s.scrollTop=e.scrollTop,l&&(l.scrollTop=e.scrollTop,l.scrollLeft=e.scrollLeft)})}function g(){if(!k)return;const t=e.value.slice(0,e.selectionStart),n=(t.match(/\n/g)||[]).length+1,o=t.length-(t.lastIndexOf(`
|
|
92
|
+
`)+1)+1;k({line:n,col:o})}function E(t){if(t.key!=="Enter"||t.isComposing)return;t.preventDefault();const{selectionStart:n,value:o}=e,i=o.lastIndexOf(`
|
|
93
|
+
`,n-1)+1,d=(o.slice(i,n).match(/^[ \t]*/)||[""])[0],h=o.slice(0,n).trimEnd().slice(-1),m="{[(".includes(h)?" ":"";e.setRangeText(`
|
|
94
|
+
`+d+m,n,e.selectionEnd,"end"),e.dispatchEvent(new Event("input",{bubbles:!0}))}function z(t){if(t.key!=="Tab")return;t.preventDefault();const{selectionStart:n,selectionEnd:o,value:i}=e;if(!t.shiftKey)e.value=i.slice(0,n)+" "+i.slice(o),e.selectionStart=e.selectionEnd=n+2;else{const d=i.lastIndexOf(`
|
|
95
|
+
`,n-1)+1,h=i.slice(d,d+2),m=h===" "?2:h[0]===" "?1:0;m>0&&(e.value=i.slice(0,d)+i.slice(d+m),e.selectionStart=e.selectionEnd=Math.max(d,n-m))}e.dispatchEvent(new Event("input",{bubbles:!0}))}const b=[];function c(t,n,o){t.addEventListener(n,o),b.push([t,n,o])}return c(e,"input",()=>{p(),f(),u(),g(),v&&v(e.value)}),c(e,"scroll",u),c(e,"keydown",z),C&&c(e,"keydown",E),c(e,"keyup",()=>{u(),g()}),c(e,"click",g),e.value=y,p(),f(),{textarea:e,gutter:s,getValue(){return e.value},setValue(t){e.value!==t&&(e.value=t??"",p(),f(),u())},focus(){e.focus()},destroy(){for(const[t,n,o]of b)t.removeEventListener(n,o);b.length=0,a.parentNode===r&&r.removeChild(a)}}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export function escapeHtml(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const i=(e,t)=>`<span class="dm-tok-${e}">${escapeHtml(t)}</span>`;function m(e,t){let n="",s=0;const o=t.map(r=>({cls:r.cls,re:new RegExp(r.re.source,r.re.flags.replace("y","")+"y")}));for(;s<e.length;){let r=!1;for(const{cls:l,re:a}of o){a.lastIndex=s;const c=a.exec(e);if(c&&c[0].length>0){n+=i(l,c[0]),s+=c[0].length,r=!0;break}}if(!r){let l=s+1;if(/[A-Za-z0-9_]/.test(e[s]))for(;l<e.length&&/[A-Za-z0-9_]/.test(e[l]);)l++;n+=escapeHtml(e.slice(s,l)),s=l}}return n}const d=/\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/,g={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:d},{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:"literal",re:/\*\*[^*\n]+\*\*|\*[^*\n]+\*/},{cls:"func",re:/\[[^\]\n]*\]\([^)\n]*\)/},{cls:"tag",re:/^\s*[-*+]\s|^\s*\d+\.\s/m}]};export function languageForFile(e){const t=String(e||"").toLowerCase().split(".").pop();return{js:"js",json:"json",css:"css",html:"html",htm:"html",svg:"html",md:"md",markdown:"md"}[t]||null}export function highlight(e,t){const n=g[t];return(n?m(String(e??""),n):escapeHtml(String(e??"")))+`
|
|
2
|
+
`}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<style>
|
|
2
2
|
.pcode-layout { display: flex; gap: 1rem; align-items: stretch; min-height: 70vh; }
|
|
3
|
-
.pcode-files { flex: 0 0
|
|
3
|
+
.pcode-files { flex: 0 0 250px; overflow-y: auto; }
|
|
4
4
|
.pcode-files .card-body { padding: .5rem; }
|
|
5
|
+
.pcode-dir {
|
|
6
|
+
padding: .5rem .5rem .2rem; font-size: .7rem; font-weight: 700;
|
|
7
|
+
text-transform: uppercase; letter-spacing: .04em;
|
|
8
|
+
color: var(--dm-text-muted, #6b7280);
|
|
9
|
+
}
|
|
5
10
|
.pcode-file {
|
|
6
11
|
display: flex; align-items: center; gap: .4rem; width: 100%;
|
|
7
|
-
padding: .
|
|
12
|
+
padding: .3rem .5rem .3rem 1rem; border: 0; background: none; cursor: pointer;
|
|
8
13
|
border-radius: var(--dm-radius-md, 6px); text-align: left;
|
|
9
14
|
color: var(--dm-text, inherit); font-size: .82rem;
|
|
10
15
|
font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
|
|
@@ -23,18 +28,14 @@
|
|
|
23
28
|
font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
|
|
24
29
|
font-size: .85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
25
30
|
}
|
|
26
|
-
#pcode-
|
|
27
|
-
|
|
28
|
-
padding: .75rem 1rem; background: transparent; color: var(--dm-text, inherit);
|
|
29
|
-
font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
|
|
30
|
-
font-size: .85rem; line-height: 1.55; tab-size: 4; white-space: pre;
|
|
31
|
-
overflow: auto; min-height: 55vh;
|
|
32
|
-
}
|
|
31
|
+
#pcode-editor { flex: 1; display: flex; min-height: 55vh; }
|
|
32
|
+
#pcode-editor > .dm-edit-wrap { flex: 1; }
|
|
33
33
|
.pcode-statusbar {
|
|
34
34
|
display: flex; align-items: center; gap: .75rem; padding: .4rem .75rem;
|
|
35
35
|
border-top: 1px solid var(--dm-border, rgba(128,128,128,.25));
|
|
36
36
|
font-size: .78rem; color: var(--dm-text-muted, #6b7280);
|
|
37
37
|
}
|
|
38
|
+
#pcode-cursor { margin-left: auto; font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace); }
|
|
38
39
|
</style>
|
|
39
40
|
|
|
40
41
|
<div class="view-header d-flex justify-content-between align-items-center mb-4">
|
|
@@ -66,12 +67,15 @@
|
|
|
66
67
|
<div class="pcode-toolbar">
|
|
67
68
|
<span class="pcode-current" id="pcode-current">Select a file</span>
|
|
68
69
|
<span style="flex:1"></span>
|
|
70
|
+
<button id="pcode-rename" class="btn btn-sm btn-ghost" title="Rename file" disabled><span data-icon="edit-3"></span></button>
|
|
69
71
|
<button id="pcode-delete" class="btn btn-sm btn-ghost" title="Delete file" disabled><span data-icon="trash-2"></span></button>
|
|
72
|
+
<button id="pcode-saveall" class="btn btn-sm btn-ghost" disabled>Save All</button>
|
|
70
73
|
<button id="pcode-save" class="btn btn-sm btn-primary" disabled><span data-icon="save"></span> Save</button>
|
|
71
74
|
</div>
|
|
72
|
-
<
|
|
75
|
+
<div id="pcode-editor"></div>
|
|
73
76
|
<div class="pcode-statusbar">
|
|
74
|
-
<span id="pcode-status">Ctrl+S saves the open file.</span>
|
|
77
|
+
<span id="pcode-status">Ctrl+S saves the open file. Unsaved edits survive switching files.</span>
|
|
78
|
+
<span id="pcode-cursor"></span>
|
|
75
79
|
</div>
|
|
76
80
|
</div>
|
|
77
81
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<label class="form-label">
|
|
3
|
-
<input class="form-control" type="text" name="path" placeholder="admin/views/cart.js">
|
|
4
|
-
</div></form>`,{title:
|
|
1
|
+
import{api as d}from"../api.js";import{createSimpleEditor as V}from"../lib/simple-editor.js";import{languageForFile as q}from"../lib/syntax-highlight.js";export const pluginCodeView={templateUrl:"/admin/js/templates/plugin-code.html",async onMount(o){const b=window.location.hash.match(/\/plugins\/code\/([a-z0-9-]+)/),l=b?b[1]:null;if(!l){E.toast("No plugin specified.",{type:"error"}),R.navigate("/plugins");return}o.find("#pcode-title").text(`${l} \u2014 Code`),document.title=`${l} Code - Domma CMS`;const k=o.find("#pcode-save").get(0),S=o.find("#pcode-saveall").get(0),C=o.find("#pcode-delete").get(0),x=o.find("#pcode-rename").get(0),K=o.find("#pcode-status").get(0),F=o.find("#pcode-cursor").get(0),L=o.find("#pcode-current").get(0),$=o.find("#pcode-filelist"),P=o.find("#pcode-editor").get(0);let m=[],n=null,c=null;const r=new Map,v=e=>r.has(e)&&r.get(e).content!==r.get(e).saved,p=()=>[...r.keys()].filter(v);function f(e){K.textContent=e}function g(){k.disabled=!n||!v(n),S.disabled=p().length===0,C.disabled=!n,x.disabled=!n}function h(){$.empty();const e=$.get(0),a=new Map;m.forEach(t=>{const s=t.path.includes("/")?t.path.slice(0,t.path.lastIndexOf("/")):"";a.has(s)||a.set(s,[]),a.get(s).push(t)}),[...a.keys()].sort((t,s)=>t.localeCompare(s)).forEach(t=>{if(t!==""){const s=document.createElement("div");s.className="pcode-dir",s.textContent=t+"/",e.appendChild(s)}a.get(t).forEach(s=>{const i=document.createElement("button");if(i.className="pcode-file"+(s.path===n?" active":""),i.style.paddingLeft=t===""?".5rem":"1rem",i.textContent=s.path.slice(t===""?0:t.length+1),i.title=s.path,v(s.path)){const w=document.createElement("span");w.className="pcode-dirty",w.textContent="\u25CF",i.appendChild(w)}i.addEventListener("click",()=>y(s.path)),e.appendChild(i)})})}async function u(){try{m=(await d.plugins.files(l)).files||[],h()}catch(e){E.toast(`Failed to list files: ${e.message}`,{type:"error"})}}function O(e,a){c&&c.destroy(),c=V(P,{initialValue:a,language:q(e),autoIndent:!0,onChange:t=>{r.get(e).content=t,g(),h()},onCursor:({line:t,col:s})=>{F.textContent=`Ln ${t}, Col ${s}`}}),c.textarea.addEventListener("keydown",t=>{(t.ctrlKey||t.metaKey)&&t.key==="s"&&(t.preventDefault(),t.shiftKey?N():j())}),c.focus()}async function y(e){if(e!==n){if(!r.has(e))try{const a=await d.plugins.readFile(l,e);r.set(e,{content:a.content,saved:a.content})}catch(a){E.toast(`Failed to open ${e}: ${a.message}`,{type:"error"});return}n=e,L.textContent=e+(q(e)?"":" (plain text)"),O(e,r.get(e).content),F.textContent="Ln 1, Col 1",g(),h()}}async function D(e){const a=r.get(e),t=await d.plugins.writeFile(l,e,a.content);return a.saved=a.content,t.restartRequired}async function j(){if(!(!n||!v(n))){try{const e=await D(n);f(e?`Saved ${n} \u2014 server file: restart to apply.`:`Saved ${n} \u2014 hard-refresh the admin tab to see it.`),E.toast(e?"Saved. Restart the server to apply.":"Saved.",{type:e?"warning":"success"})}catch(e){E.toast(`Save failed: ${e.message}`,{type:"error"})}g(),h(),u()}}async function N(){const e=p();if(!e.length)return;let a=!1;const t=[];for(const s of e)try{a=await D(s)||a}catch(i){t.push(`${s}: ${i.message}`)}t.length?E.toast(`Some saves failed \u2014 ${t.join("; ")}`,{type:"error"}):(f(a?`Saved ${e.length} file(s) \u2014 server files included: restart to apply.`:`Saved ${e.length} file(s) \u2014 hard-refresh the admin tab to see them.`),E.toast(`Saved ${e.length} file(s).`,{type:a?"warning":"success"})),g(),h(),u()}k.addEventListener("click",j),S.addEventListener("click",N);async function B(e,a){const t=i=>String(i||"").replace(/&/g,"&").replace(/</g,"<").replace(/"/g,""");return await E.confirm(`<form id="pcode-path-form"><div class="form-group">
|
|
2
|
+
<label class="form-label">File path (inside the plugin)</label>
|
|
3
|
+
<input class="form-control" type="text" name="path" value="${t(a)}" placeholder="admin/views/cart.js">
|
|
4
|
+
</div></form>`,{title:e,confirmText:"OK",html:!0})&&document.querySelector('#pcode-path-form input[name="path"]')?.value.trim()||null}o.find("#pcode-newfile").on("click",async()=>{const e=await B("New File");if(e)try{await d.plugins.writeFile(l,e,""),r.set(e,{content:"",saved:""}),await u(),await y(e)}catch(a){E.toast(`Could not create file: ${a.message}`,{type:"error"})}}),x.addEventListener("click",async()=>{if(!n)return;if(["plugin.json","plugin.js","config.js"].includes(n)){E.toast(`${n} is required and cannot be renamed.`,{type:"error"});return}const e=await B("Rename File",n);if(!(!e||e===n))try{await d.plugins.writeFile(l,e,r.get(n)?.content??""),await d.plugins.deleteFile(l,n),r.set(e,{content:r.get(n).content,saved:r.get(n).content}),r.delete(n),n=null,await u(),await y(e),E.toast("Renamed.",{type:"success"})}catch(a){E.toast(`Rename failed: ${a.message}`,{type:"error"}),u()}}),C.addEventListener("click",async()=>{if(n&&await E.confirm(`Delete ${n}? This cannot be undone.`))try{await d.plugins.deleteFile(l,n),r.delete(n),E.toast(`Deleted ${n}.`,{type:"success"}),n=null,L.textContent="Select a file",c&&(c.destroy(),c=null),g(),await u()}catch(e){E.toast(`Delete failed: ${e.message}`,{type:"error"})}}),o.find("#pcode-back").on("click",async()=>{p().length&&!await E.confirm(`Discard unsaved changes in ${p().length} file(s)?`)||R.navigate("/plugins")});const M=e=>{p().length&&(e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",M),window.addEventListener("hashchange",function e(){window.removeEventListener("beforeunload",M),window.removeEventListener("hashchange",e)}),o.find("#pcode-restart").on("click",async()=>{const e=p().length;if(await E.confirm((e?`${e} file(s) have unsaved changes \u2014 they will NOT be included. `:"")+"Restart the server now? Server-side plugin changes apply on boot. The admin will be unreachable for a few seconds.",{title:"Restart Server",confirmText:"Restart"}))try{(await d.plugins.restart()).supervised||E.toast("No supervisor detected \u2014 if the server does not come back, start it manually.",{type:"warning"}),f("Restarting\u2026 the page will report back when the server returns.");const s=Date.now(),i=setInterval(async()=>{try{await H.get("/api/auth/setup-status"),clearInterval(i),f("Server is back. Server-side changes are live."),E.toast("Server restarted \u2014 changes are live.",{type:"success"})}catch{Date.now()-s>3e4&&(clearInterval(i),f("Server did not come back within 30s \u2014 check it manually."),E.toast("Server not responding after 30s.",{type:"error"}))}},1500)}catch(t){E.toast(`Restart failed: ${t.message}`,{type:"error"})}}),await u();const T=m.find(e=>e.path==="plugin.js")||m[0];T&&await y(T.path),I.scan()}};
|