domma-cms 0.36.17 → 0.36.19

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 +1 @@
1
- export const EFFECTS=[{name:"reveal",label:"Reveal",category:"Entrance",description:"Scroll-triggered entrance animation when the element enters the viewport.",attrs:[{name:"animation",label:"Animation",type:"select",default:"fade",options:[["fade","Fade"],["slide-up","Slide up"],["slide-down","Slide down"],["zoom","Zoom"],["flip","Flip"]]},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"threshold",label:"Threshold",type:"number",default:.1,step:.05,min:0,max:1},{name:"once",label:"Animate once",type:"checkbox",default:!0}],hosts:["card","hero","banner","col"],previewable:!0},{name:"breathe",label:"Breathe",category:"Animation",description:"Continuous gentle scaling \u2014 makes elements feel alive.",attrs:[{name:"amplitude",label:"Amplitude (px)",type:"number",default:8},{name:"duration",label:"Duration (ms)",type:"number",default:3e3}],hosts:["card","hero","col","button"],previewable:!0},{name:"pulse",label:"Pulse",category:"Animation",description:"A repeating scale pulse \u2014 stronger than breathe.",attrs:[{name:"scale",label:"Scale",type:"number",default:1.05,step:.01},{name:"duration",label:"Duration (ms)",type:"number",default:1e3}],hosts:["card","hero","banner","button"],previewable:!0},{name:"shake",label:"Shake",category:"Animation",description:"One-shot horizontal or vertical shake.",attrs:[{name:"intensity",label:"Intensity (px)",type:"number",default:5},{name:"duration",label:"Duration (ms)",type:"number",default:500},{name:"direction",label:"Direction",type:"select",default:"horizontal",options:[["horizontal","Horizontal"],["vertical","Vertical"]]}],hosts:["card","banner","button"],previewable:!0},{name:"animate",label:"CSS Animate",category:"Animation",description:"Applies a Domma CSS animation utility class \u2014 no JavaScript.",attrs:[{name:"type",label:"Type",type:"select",default:"fade-in-up",options:[["fade-in","fade-in"],["fade-in-up","fade-in-up"],["fade-in-down","fade-in-down"],["fade-in-left","fade-in-left"],["fade-in-right","fade-in-right"],["slide-in-up","slide-in-up"],["slide-in-down","slide-in-down"],["zoom-in","zoom-in"],["zoom-out","zoom-out"],["bounce","bounce"],["pulse","pulse"],["shake","shake"],["spin","spin"],["flip","flip"]]},{name:"duration",label:"Duration",type:"select",default:"normal",options:[["fast","fast"],["normal","normal"],["slow","slow"],["slower","slower"]]},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"repeat",label:"Repeat",type:"select",default:"once",options:[["once","Once"],["infinite","Infinite"]]}],hosts:["card","hero","banner","col","button"],previewable:!0},{name:"scribe",label:"Scribe (typewriter)",category:"Text",description:"Types text character by character. Simple mode only \u2014 for the action-script composer use Insert \u2192 Scribe.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"cursor",label:"Show cursor",type:"checkbox",default:!0},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"scramble",label:"Scramble",category:"Text",description:"Reveals text through a character-scramble animation.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"counter",label:"Counter",category:"Text",description:"Animates a number from a starting value to a target. Self-closing shortcode.",attrs:[{name:"to",label:"To",type:"number",default:100},{name:"from",label:"From",type:"number",default:0},{name:"duration",label:"Duration (ms)",type:"number",default:2e3},{name:"prefix",label:"Prefix",type:"text",default:""},{name:"suffix",label:"Suffix",type:"text",default:""},{name:"decimals",label:"Decimal places",type:"number",default:0},{name:"separator",label:"Thousand separator",type:"text",default:""}],hosts:[],previewable:!0,selfClosing:!0},{name:"ripple",label:"Ripple",category:"Visual",description:"Click-triggered ripple effect on the element.",attrs:[{name:"colour",label:"Colour",type:"colour",default:"#7c6af7"},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"opacity",label:"Opacity",type:"number",default:.3,step:.05,min:0,max:1}],hosts:["card","col","button"],previewable:!0},{name:"twinkle",label:"Twinkle",category:"Visual",description:"Generates animated particle effects inside the element.",attrs:[{name:"count",label:"Particle count",type:"number",default:80},{name:"shape",label:"Shape",type:"select",default:"star",options:[["star","Star"],["circle","Circle"],["square","Square"]]},{name:"colour",label:"Colour",type:"colour",default:"#ffffff"},{name:"min-size",label:"Min size (px)",type:"number",default:2},{name:"max-size",label:"Max size (px)",type:"number",default:6}],hosts:["card","hero"],previewable:!0},{name:"ambient",label:"Ambient background",category:"Visual",description:"Applies an animated CSS background class to the element.",attrs:[{name:"type",label:"Type",type:"select",default:"float-blobs",options:[["float-blobs","Float blobs"],["float-orbs","Float orbs"],["float-particles","Float particles"],["rotate-glow","Rotate glow"],["rotate-dual","Rotate dual"],["rotate-spotlight","Rotate spotlight"],["wave","Wave"],["ripple","Ripple"],["aurora","Aurora"]]},{name:"speed",label:"Speed",type:"select",default:"normal",options:[["slow","Slow"],["normal","Normal"],["fast","Fast"]]},{name:"intensity",label:"Intensity",type:"select",default:"subtle",options:[["subtle","Subtle"],["intense","Intense"]]}],hosts:["card","hero","banner"],previewable:!0},{name:"firework",label:"Firework",category:"Celebrations",description:"CSS-only firework particle. Self-closing or wrapping form.",attrs:[{name:"type",label:"Type",type:"select",default:"burst",options:[["burst","Burst"],["sparkle","Sparkle"],["trail","Trail"]]},{name:"colour",label:"Colour theme",type:"select",default:"rainbow",options:[["rainbow","Rainbow"],["primary","Primary"],["info","Info"],["success","Success"],["warning","Warning"],["danger","Danger"]]},{name:"size",label:"Size",type:"select",default:"md",options:[["sm","Small"],["md","Medium"],["lg","Large"]]},{name:"continuous",label:"Continuous",type:"checkbox",default:!1},{name:"hover",label:"On hover only",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"fireworks",label:"Fireworks show",category:"Celebrations",description:"A container with multiple firework particles inside.",attrs:[],hosts:[],previewable:!1},{name:"celebrate",label:"Celebrate",category:"Celebrations",description:"Canvas-based seasonal particle system. Runs full-page on the published site.",attrs:[{name:"theme",label:"Theme",type:"select",default:"auto",options:[["auto","Auto (date-detected)"],["christmas","Christmas"],["halloween","Halloween"],["valentines","Valentines"],["guy-fawkes","Guy Fawkes"],["st-patricks","St Patrick's"],["st-davids","St David's"],["st-georges","St George's"],["st-andrews","St Andrew's"]]},{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]}],hosts:[],previewable:!1,selfClosing:!0},{name:"ticker-tape",label:"Ticker tape",category:"Celebrations",description:"Coloured rectangular strips fall from the top, sway, rotate, and fade. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"theme",options:[["theme","Theme"],["rainbow","Rainbow"],["gold","Gold"],["silver","Silver"],["festive","Festive"],["pastel","Pastel"],["mono","Mono"]]},{name:"density",label:"Density",type:"number",default:50},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sway",label:"Sway (px)",type:"number",default:60},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:150}],hosts:["hero"],previewable:!0},{name:"butterflies",label:"Butterflies",category:"Celebrations",description:"Procedurally drawn butterflies wander and rise with flapping wings. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"meadow",options:[["meadow","Meadow"],["theme","Theme"],["rainbow","Rainbow"],["pastel","Pastel"],["festive","Festive"],["gold","Gold"],["silver","Silver"],["mono","Mono"],["sunset","Sunset"],["ocean","Ocean"],["forest","Forest"],["bridal","Bridal"],["firefly","Firefly"]]},{name:"density",label:"Density",type:"number",default:18},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"wander",label:"Wander",type:"number",default:1,step:.1,hint:"Steering erraticness"},{name:"rise-speed",label:"Rise speed",type:"number",default:.4,step:.1,hint:"Upward drift bias"},{name:"flap-speed",label:"Flap speed",type:"number",default:1,step:.1},{name:"min-size",label:"Min wingspan (px)",type:"number",default:14},{name:"max-size",label:"Max wingspan (px)",type:"number",default:26},{name:"two-tone",label:"Two-tone wings",type:"checkbox",default:!0},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:40}],hosts:["hero"],previewable:!0},{name:"strobe",label:"Strobe lighting",category:"Celebrations",description:"Light beams sweep from the corners and brighten where they cross \u2014 club, concert, or searchlight moods. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"preset",label:"Preset",type:"select",default:"club",options:[["club","Club"],["concert","Concert"],["police","Police"],["searchlight","Searchlight"],["scanner","Scanner"],["mood","Mood"]],hint:"Other fields override the preset when changed"},{name:"motion",label:"Motion",type:"select",default:"sweep",options:[["sweep","Sweep (fan back and forth)"],["rotate","Rotate (full circle)"]]},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sweep-arc",label:"Sweep arc (deg)",type:"number",default:100},{name:"beam-width",label:"Beam width (deg)",type:"number",default:16},{name:"intensity",label:"Intensity",type:"number",default:.7,step:.05,min:0,max:1},{name:"flicker",label:"Flicker",type:"checkbox",default:!0},{name:"hz",label:"Flicker rate (Hz)",type:"number",default:10,hint:"Rates above 5 flashes/sec can trigger photosensitive seizures"},{name:"colours",label:"Colours",type:"text",default:"",placeholder:"#ffffff, #38bdf8",hint:"Comma-separated hex colours \u2014 overrides the preset palette"},{name:"duration",label:"Auto-stop (ms)",type:"number",default:"",hint:"Stop after this many ms \u2014 blank runs forever"}],hosts:["hero"],previewable:!0}];export function getEffect(t){return EFFECTS.find(e=>e.name===t)||null}export function effectsByCategory(){return["Entrance","Animation","Text","Visual","Celebrations"].map(e=>({category:e,effects:EFFECTS.filter(a=>a.category===e)}))}export function effectsInjectableInto(t){return EFFECTS.filter(e=>e.hosts.includes(t))}
1
+ export const EFFECTS=[{name:"reveal",label:"Reveal",category:"Entrance",description:"Scroll-triggered entrance animation when the element enters the viewport.",attrs:[{name:"animation",label:"Animation",type:"select",default:"fade",options:[["fade","Fade"],["slide-up","Slide up"],["slide-down","Slide down"],["zoom","Zoom"],["flip","Flip"]]},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"threshold",label:"Threshold",type:"number",default:.1,step:.05,min:0,max:1},{name:"once",label:"Animate once",type:"checkbox",default:!0}],hosts:["card","hero","banner","col"],previewable:!0},{name:"breathe",label:"Breathe",category:"Animation",description:"Continuous gentle scaling \u2014 makes elements feel alive.",attrs:[{name:"amplitude",label:"Amplitude (px)",type:"number",default:8},{name:"duration",label:"Duration (ms)",type:"number",default:3e3}],hosts:["card","hero","col","button"],previewable:!0},{name:"pulse",label:"Pulse",category:"Animation",description:"A repeating scale pulse \u2014 stronger than breathe.",attrs:[{name:"scale",label:"Scale",type:"number",default:1.05,step:.01},{name:"duration",label:"Duration (ms)",type:"number",default:1e3}],hosts:["card","hero","banner","button"],previewable:!0},{name:"shake",label:"Shake",category:"Animation",description:"One-shot horizontal or vertical shake.",attrs:[{name:"intensity",label:"Intensity (px)",type:"number",default:5},{name:"duration",label:"Duration (ms)",type:"number",default:500},{name:"direction",label:"Direction",type:"select",default:"horizontal",options:[["horizontal","Horizontal"],["vertical","Vertical"]]}],hosts:["card","banner","button"],previewable:!0},{name:"animate",label:"CSS Animate",category:"Animation",description:"Applies a Domma CSS animation utility class \u2014 no JavaScript.",attrs:[{name:"type",label:"Type",type:"select",default:"fade-in-up",options:[["fade-in","fade-in"],["fade-in-up","fade-in-up"],["fade-in-down","fade-in-down"],["fade-in-left","fade-in-left"],["fade-in-right","fade-in-right"],["slide-in-up","slide-in-up"],["slide-in-down","slide-in-down"],["zoom-in","zoom-in"],["zoom-out","zoom-out"],["bounce","bounce"],["pulse","pulse"],["shake","shake"],["spin","spin"],["flip","flip"]]},{name:"duration",label:"Duration",type:"select",default:"normal",options:[["fast","fast"],["normal","normal"],["slow","slow"],["slower","slower"]]},{name:"delay",label:"Delay (ms)",type:"number",default:0},{name:"repeat",label:"Repeat",type:"select",default:"once",options:[["once","Once"],["infinite","Infinite"]]}],hosts:["card","hero","banner","col","button"],previewable:!0},{name:"scribe",label:"Scribe (typewriter)",category:"Text",description:"Types text character by character. Simple mode only \u2014 for the action-script composer use Insert \u2192 Scribe.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"cursor",label:"Show cursor",type:"checkbox",default:!0},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"scramble",label:"Scramble",category:"Text",description:"Reveals text through a character-scramble animation.",attrs:[{name:"speed",label:"Speed (ms/char)",type:"number",default:50},{name:"loop",label:"Loop",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"counter",label:"Counter",category:"Text",description:"Animates a number from a starting value to a target. Self-closing shortcode.",attrs:[{name:"to",label:"To",type:"number",default:100},{name:"from",label:"From",type:"number",default:0},{name:"duration",label:"Duration (ms)",type:"number",default:2e3},{name:"prefix",label:"Prefix",type:"text",default:""},{name:"suffix",label:"Suffix",type:"text",default:""},{name:"decimals",label:"Decimal places",type:"number",default:0},{name:"separator",label:"Thousand separator",type:"text",default:""}],hosts:[],previewable:!0,selfClosing:!0},{name:"ripple",label:"Ripple",category:"Visual",description:"Click-triggered ripple effect on the element.",attrs:[{name:"colour",label:"Colour",type:"colour",default:"#7c6af7"},{name:"duration",label:"Duration (ms)",type:"number",default:600},{name:"opacity",label:"Opacity",type:"number",default:.3,step:.05,min:0,max:1}],hosts:["card","col","button"],previewable:!0},{name:"twinkle",label:"Twinkle",category:"Visual",description:"Generates animated particle effects inside the element.",attrs:[{name:"count",label:"Particle count",type:"number",default:80},{name:"shape",label:"Shape",type:"select",default:"star",options:[["star","Star"],["circle","Circle"],["square","Square"]]},{name:"colour",label:"Colour",type:"colour",default:"#ffffff"},{name:"min-size",label:"Min size (px)",type:"number",default:2},{name:"max-size",label:"Max size (px)",type:"number",default:6}],hosts:["card","hero"],previewable:!0},{name:"ambient",label:"Ambient background",category:"Visual",description:"Applies an animated CSS background class to the element.",attrs:[{name:"type",label:"Type",type:"select",default:"float-blobs",options:[["float-blobs","Float blobs"],["float-orbs","Float orbs"],["float-particles","Float particles"],["rotate-glow","Rotate glow"],["rotate-dual","Rotate dual"],["rotate-spotlight","Rotate spotlight"],["wave","Wave"],["ripple","Ripple"],["aurora","Aurora"]]},{name:"speed",label:"Speed",type:"select",default:"normal",options:[["slow","Slow"],["normal","Normal"],["fast","Fast"]]},{name:"intensity",label:"Intensity",type:"select",default:"subtle",options:[["subtle","Subtle"],["intense","Intense"]]}],hosts:["card","hero","banner"],previewable:!0},{name:"firework",label:"Firework",category:"Celebrations",description:"CSS-only firework particle. Self-closing or wrapping form.",attrs:[{name:"type",label:"Type",type:"select",default:"burst",options:[["burst","Burst"],["sparkle","Sparkle"],["trail","Trail"]]},{name:"colour",label:"Colour theme",type:"select",default:"rainbow",options:[["rainbow","Rainbow"],["primary","Primary"],["info","Info"],["success","Success"],["warning","Warning"],["danger","Danger"]]},{name:"size",label:"Size",type:"select",default:"md",options:[["sm","Small"],["md","Medium"],["lg","Large"]]},{name:"continuous",label:"Continuous",type:"checkbox",default:!1},{name:"hover",label:"On hover only",type:"checkbox",default:!1}],hosts:[],previewable:!0},{name:"fireworks",label:"Fireworks show",category:"Celebrations",description:"A container with multiple firework particles inside.",attrs:[],hosts:[],previewable:!1},{name:"celebrate",label:"Celebrate",category:"Celebrations",description:"Canvas-based seasonal particle system. Runs full-page on the published site.",attrs:[{name:"theme",label:"Theme",type:"select",default:"auto",options:[["auto","Auto (date-detected)"],["christmas","Christmas"],["halloween","Halloween"],["valentines","Valentines"],["guy-fawkes","Guy Fawkes"],["st-patricks","St Patrick's"],["st-davids","St David's"],["st-georges","St George's"],["st-andrews","St Andrew's"]]},{name:"intensity",label:"Intensity",type:"select",default:"medium",options:[["light","Light"],["medium","Medium"],["heavy","Heavy"]]}],hosts:[],previewable:!1,selfClosing:!0},{name:"ticker-tape",label:"Ticker tape",category:"Celebrations",description:"Coloured rectangular strips fall from the top, sway, rotate, and fade. Inserted self-closing (full-page); hand-write the wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"theme",options:[["theme","Theme"],["rainbow","Rainbow"],["gold","Gold"],["silver","Silver"],["festive","Festive"],["pastel","Pastel"],["mono","Mono"]]},{name:"density",label:"Density",type:"number",default:50},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sway",label:"Sway (px)",type:"number",default:60},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:150}],hosts:["hero"],previewable:!0,selfClosing:!0},{name:"butterflies",label:"Butterflies",category:"Celebrations",description:"Procedurally drawn butterflies wander and rise with flapping wings. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"palette",label:"Palette",type:"select",default:"meadow",options:[["meadow","Meadow"],["theme","Theme"],["rainbow","Rainbow"],["pastel","Pastel"],["festive","Festive"],["gold","Gold"],["silver","Silver"],["mono","Mono"],["sunset","Sunset"],["ocean","Ocean"],["forest","Forest"],["bridal","Bridal"],["firefly","Firefly"]]},{name:"density",label:"Density",type:"number",default:18},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"wander",label:"Wander",type:"number",default:1,step:.1,hint:"Steering erraticness"},{name:"rise-speed",label:"Rise speed",type:"number",default:.4,step:.1,hint:"Upward drift bias"},{name:"flap-speed",label:"Flap speed",type:"number",default:1,step:.1},{name:"min-size",label:"Min wingspan (px)",type:"number",default:14},{name:"max-size",label:"Max wingspan (px)",type:"number",default:26},{name:"two-tone",label:"Two-tone wings",type:"checkbox",default:!0},{name:"burst",label:"Burst mode (one-shot)",type:"checkbox",default:!1},{name:"burst-count",label:"Burst count",type:"number",default:40}],hosts:["hero"],previewable:!0},{name:"strobe",label:"Strobe lighting",category:"Celebrations",description:"Light beams sweep from the corners and brighten where they cross \u2014 club, concert, or searchlight moods. Self-closing for full-page; wrapping form for container-scoped.",attrs:[{name:"preset",label:"Preset",type:"select",default:"club",options:[["club","Club"],["concert","Concert"],["police","Police"],["searchlight","Searchlight"],["scanner","Scanner"],["mood","Mood"]],hint:"Other fields override the preset when changed"},{name:"motion",label:"Motion",type:"select",default:"sweep",options:[["sweep","Sweep (fan back and forth)"],["rotate","Rotate (full circle)"]]},{name:"speed",label:"Speed multiplier",type:"number",default:1,step:.1},{name:"sweep-arc",label:"Sweep arc (deg)",type:"number",default:100},{name:"beam-width",label:"Beam width (deg)",type:"number",default:16},{name:"intensity",label:"Intensity",type:"number",default:.7,step:.05,min:0,max:1},{name:"flicker",label:"Flicker",type:"checkbox",default:!0},{name:"hz",label:"Flicker rate (Hz)",type:"number",default:10,hint:"Rates above 5 flashes/sec can trigger photosensitive seizures"},{name:"colours",label:"Colours",type:"text",default:"",placeholder:"#ffffff, #38bdf8",hint:"Comma-separated hex colours \u2014 overrides the preset palette"},{name:"duration",label:"Auto-stop (ms)",type:"number",default:"",hint:"Stop after this many ms \u2014 blank runs forever"}],hosts:["hero"],previewable:!0}];export function getEffect(t){return EFFECTS.find(e=>e.name===t)||null}export function effectsByCategory(){return["Entrance","Animation","Text","Visual","Celebrations"].map(e=>({category:e,effects:EFFECTS.filter(a=>a.category===e)}))}export function effectsInjectableInto(t){return EFFECTS.filter(e=>e.hosts.includes(t))}
@@ -1,4 +1,4 @@
1
- function w(){if(document.getElementById("dm-simple-editor-style"))return;const r=document.createElement("style");r.id="dm-simple-editor-style",r.textContent=`
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
- `,document.head.appendChild(r)}export function createSimpleEditor(r,g={}){if(!r)throw new Error("createSimpleEditor: mountEl is required.");const{initialValue:v="",onChange:h=null}=g;for(w();r.firstChild;)r.removeChild(r.firstChild);const a=document.createElement("div"),l=document.createElement("div"),e=document.createElement("textarea");a.className="dm-edit-wrap",l.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(l),a.appendChild(e),r.appendChild(a);function u(){const t=e.value.split(`
47
- `).length||1,n=new Array(t);for(let i=0;i<t;i++)n[i]=String(i+1);l.textContent=n.join(`
48
- `);const o=String(t).length;l.style.minWidth=`calc(${o}ch + 1.5rem)`}function s(){requestAnimationFrame(()=>{l.scrollTop=e.scrollTop})}function b(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(`
49
- `,n-1)+1,p=i.slice(d,d+2),f=p===" "?2:p[0]===" "?1:0;f>0&&(e.value=i.slice(0,d)+i.slice(d+f),e.selectionStart=e.selectionEnd=Math.max(d,n-f))}e.dispatchEvent(new Event("input",{bubbles:!0}))}const m=[];function c(t,n,o){t.addEventListener(n,o),m.push([t,n,o])}return c(e,"input",()=>{u(),s(),h&&h(e.value)}),c(e,"scroll",s),c(e,"keydown",b),c(e,"keyup",s),e.value=v,u(),{textarea:e,gutter:l,getValue(){return e.value},setValue(t){e.value!==t&&(e.value=t??"",u(),s())},focus(){e.focus()},destroy(){for(const[t,n,o]of m)t.removeEventListener(n,o);m.length=0,a.parentNode===r&&r.removeChild(a)}}}
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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 240px; overflow-y: auto; }
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: .35rem .5rem; border: 0; background: none; cursor: pointer;
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-textarea {
27
- flex: 1; width: 100%; border: 0; resize: none; outline: none;
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
- <textarea id="pcode-textarea" spellcheck="false" disabled placeholder="Select a file on the left to start editing."></textarea>
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 c}from"../api.js";export const pluginCodeView={templateUrl:"/admin/js/templates/plugin-code.html",async onMount(n){const y=window.location.hash.match(/\/plugins\/code\/([a-z0-9-]+)/),i=y?y[1]:null;if(!i){E.toast("No plugin specified.",{type:"error"}),R.navigate("/plugins");return}n.find("#pcode-title").text(`${i} \u2014 Code`),document.title=`${i} Code - Domma CMS`;const r=n.find("#pcode-textarea").get(0),p=n.find("#pcode-save").get(0),h=n.find("#pcode-delete").get(0),C=n.find("#pcode-status").get(0),g=n.find("#pcode-current").get(0),w=n.find("#pcode-filelist");let u=[],a=null,l=!1;function o(e){C.textContent=e}function d(e){l=e,p.disabled=!e||!a,b()}function b(){w.empty(),u.forEach(e=>{const t=document.createElement("button");if(t.className="pcode-file"+(e.path===a?" active":""),t.textContent=e.path,e.path===a&&l){const s=document.createElement("span");s.className="pcode-dirty",s.textContent="\u25CF",t.appendChild(s)}t.addEventListener("click",()=>v(e.path)),w.get(0).appendChild(t)})}async function f(){try{u=(await c.plugins.files(i)).files||[],b()}catch(e){E.toast(`Failed to list files: ${e.message}`,{type:"error"})}}async function v(e){if(!(l&&!await E.confirm("Discard unsaved changes?")))try{const t=await c.plugins.readFile(i,e);a=e,r.value=t.content,r.disabled=!1,h.disabled=!1,g.textContent=e,d(!1),o("Ctrl+S saves the open file."),r.focus()}catch(t){E.toast(`Failed to open ${e}: ${t.message}`,{type:"error"})}}async function k(){if(!(!a||!l)){p.disabled=!0;try{const e=await c.plugins.writeFile(i,a,r.value);d(!1),e.restartRequired?(o(`Saved ${e.path} \u2014 server file: restart to apply.`),E.toast("Saved. Restart the server to apply this change.",{type:"warning"})):(o(`Saved ${e.path} \u2014 hard-refresh the admin tab to see it.`),E.toast("Saved. Hard-refresh to see admin changes.",{type:"success"})),await f()}catch(e){p.disabled=!1,E.toast(`Save failed: ${e.message}`,{type:"error"})}}}r.addEventListener("input",()=>{l||d(!0)}),r.addEventListener("keydown",e=>{if(e.key==="Tab"){e.preventDefault();const{selectionStart:t,selectionEnd:s}=r;r.setRangeText(" ",t,s,"end"),l||d(!0)}(e.ctrlKey||e.metaKey)&&e.key==="s"&&(e.preventDefault(),k())}),p.addEventListener("click",k),h.addEventListener("click",async()=>{if(a&&await E.confirm(`Delete ${a}? This cannot be undone.`))try{await c.plugins.deleteFile(i,a),E.toast(`Deleted ${a}.`,{type:"success"}),a=null,r.value="",r.disabled=!0,h.disabled=!0,g.textContent="Select a file",d(!1),await f()}catch(e){E.toast(`Delete failed: ${e.message}`,{type:"error"})}}),n.find("#pcode-newfile").on("click",async()=>{if(!await E.confirm(`<form id="pcode-newfile-form"><div class="form-group">
2
- <label class="form-label">New file path (inside the plugin)</label>
3
- <input class="form-control" type="text" name="path" placeholder="admin/views/cart.js">
4
- </div></form>`,{title:"New File",confirmText:"Create",html:!0}))return;const s=document.querySelector('#pcode-newfile-form input[name="path"]')?.value.trim();if(s)try{await c.plugins.writeFile(i,s,""),await f(),await v(s)}catch(m){E.toast(`Could not create file: ${m.message}`,{type:"error"})}}),n.find("#pcode-back").on("click",async()=>{l&&!await E.confirm("Discard unsaved changes?")||R.navigate("/plugins")}),n.find("#pcode-restart").on("click",async()=>{if(await E.confirm("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 c.plugins.restart()).supervised||E.toast("No supervisor detected \u2014 if the server does not come back, start it manually.",{type:"warning"}),o("Restarting\u2026 the page will report back when the server returns.");const s=Date.now(),m=setInterval(async()=>{try{await H.get("/api/auth/setup-status"),clearInterval(m),o("Server is back. Server-side changes are live."),E.toast("Server restarted \u2014 changes are live.",{type:"success"})}catch{Date.now()-s>3e4&&(clearInterval(m),o("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 f();const S=u.find(e=>e.path==="plugin.js")||u[0];S&&await v(S.path),I.scan()}};
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,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;");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()}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.36.17",
3
+ "version": "0.36.19",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -71,8 +71,8 @@ const SEED_ENTRIES = [
71
71
  label: 'Admin',
72
72
  level: 1,
73
73
  permissions: [
74
- 'pages', 'media', 'blocks', 'navigation', 'layouts',
75
- 'collections', 'views', 'actions',
74
+ 'pages', 'media', 'blocks', 'components', 'navigation', 'menus',
75
+ 'projects', 'layouts', 'collections', 'views', 'actions',
76
76
  'users', 'settings', 'notifications', 'plugins',
77
77
  'api-tokens', 'api-endpoints'
78
78
  ],