domma-cms 0.6.2 → 0.6.5
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/admin/js/views/page-editor.js +13 -13
- package/package.json +2 -2
- package/plugins/example-analytics/stats.json +5 -5
- package/plugins/form-builder/data/forms/contacts.json +2 -2
- package/plugins/form-builder/data/forms/enquiries.json +2 -2
- package/plugins/form-builder/data/forms/feedback.json +2 -2
- package/plugins/form-builder/data/forms/notes.json +2 -2
- package/plugins/form-builder/data/forms/to-do.json +2 -2
- package/scripts/seed.js +271 -0
- package/server/services/markdown.js +36 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as U}from"../api.js";import{createToolbar as ye,insertAtCursor as he}from"../lib/markdown-toolbar.js";function xe(){const o=E.slideover({title:"Editor Reference",size:"md",position:"right"}),be="background:var(--dm-surface-subtle,#1a1a2e);padding:.2rem .4rem;border-radius:3px;font-size:.85em;font-family:monospace;",ge="margin:1rem 0 .5rem;font-size:1rem;";function
|
|
1
|
+
import{api as U}from"../api.js";import{createToolbar as ye,insertAtCursor as he}from"../lib/markdown-toolbar.js";function xe(){const o=E.slideover({title:"Editor Reference",size:"md",position:"right"}),be="background:var(--dm-surface-subtle,#1a1a2e);padding:.2rem .4rem;border-radius:3px;font-size:.85em;font-family:monospace;",ge="margin:1rem 0 .5rem;font-size:1rem;";function S(i){const d=document.createElement("code");return d.style.cssText=be,d.textContent=i,d}function y(i){const d=document.createElement("h3");return d.style.cssText=ge,d.textContent=i,d}const re=document.createElement("div");re.style.cssText="padding:1rem;";const Z=document.createElement("div");Z.className="tabs";const ce=document.createElement("div");ce.className="tab-list",["Basics","Layout","Components","Data","Advanced"].forEach((i,d)=>{const t=document.createElement("button");t.className="tab-item"+(d===0?" active":""),t.textContent=i,ce.appendChild(t)});const J=document.createElement("div");J.className="tab-content";const x="display:flex;flex-direction:column;gap:1rem;",ne=document.createElement("div");ne.className="tab-panel active",ne.style.cssText=x;const Q=document.createElement("div");Q.className="tab-panel",Q.style.cssText=x;const M=document.createElement("div");M.className="tab-panel",M.style.cssText=x;const V=document.createElement("div");V.className="tab-panel",V.style.cssText=x;const ae=document.createElement("div");ae.className="tab-panel",ae.style.cssText=x,J.appendChild(ne),J.appendChild(Q),J.appendChild(M),J.appendChild(V),J.appendChild(ae),Z.appendChild(ce),Z.appendChild(J),re.appendChild(Z);const O=document.createElement("div");O.appendChild(y("Markdown Basics"));const ee=document.createElement("table");ee.style.cssText="width:100%;border-collapse:collapse;font-size:.9em;";const j=document.createElement("thead"),te=document.createElement("tr");["Syntax","Result"].forEach(i=>{const d=document.createElement("th");d.style.cssText="text-align:left;padding:.4rem .5rem;border-bottom:1px solid var(--dm-border,#333);",d.textContent=i,te.appendChild(d)}),j.appendChild(te),ee.appendChild(j);const pe=document.createElement("tbody");[["**bold**","bold (rendered bold)"],["_italic_","italic (rendered italic)"],["## Heading","Heading (h2)"],["[text](url)","Link"],["","Image"],["- item","Bullet list"],["`code`","Inline code"],["---","Divider"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.35rem .5rem;vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.35rem .5rem;vertical-align:top;color:var(--dm-text-muted,#aaa);font-size:.85em;",a.textContent=d,t.appendChild(n),t.appendChild(a),pe.appendChild(t)}),ee.appendChild(pe),O.appendChild(ee),ne.appendChild(O);const _=document.createElement("div");_.appendChild(y("Grid & Columns"));function z(i){const d=document.createElement("p");return d.style.cssText="margin:.3rem 0 .6rem;font-size:.82em;color:var(--dm-text-muted,#aaa);",d.textContent=i,d}function e(i,d,t){const n=document.createElement("p");n.style.cssText="margin:.75rem 0 .25rem;font-size:.85em;font-weight:600;",n.textContent=i;const a=document.createElement("pre");a.style.cssText=be+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;margin:0;",a.textContent=d;const se=document.createDocumentFragment();return se.appendChild(n),se.appendChild(a),t&&se.appendChild(z(t)),se}const f=document.createElement("table");f.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['cols="N"',"[grid] only","Number of columns (1\u201312)"],['gap="N"',"[grid], [row]","Gap between columns/rows (1\u20136)"],['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"]].forEach(([i,d,t])=>{const n=document.createElement("tr");[i,d,t].forEach((a,se)=>{const ue=document.createElement("td");ue.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",se===0?ue.appendChild(S(i)):(ue.style.color="var(--dm-text-muted,#aaa)",ue.textContent=a),n.appendChild(ue)}),f.appendChild(n)}),_.appendChild(f),_.appendChild(e("Equal columns",`[grid cols="3" gap="4"]
|
|
2
2
|
[col]One[/col]
|
|
3
3
|
[col]Two[/col]
|
|
4
4
|
[col]Three[/col]
|
|
@@ -18,7 +18,7 @@ import{api as U}from"../api.js";import{createToolbar as ye,insertAtCursor as he}
|
|
|
18
18
|
[col]
|
|
19
19
|
[card title="Three"]Content[/card]
|
|
20
20
|
[/col]
|
|
21
|
-
[/grid]`,null)),Q.appendChild(_);const l=document.createElement("div");l.appendChild(
|
|
21
|
+
[/grid]`,null)),Q.appendChild(_);const l=document.createElement("div");l.appendChild(y("Cards")),l.appendChild(e("Basic card",`[card title="Optional Title"]
|
|
22
22
|
Markdown **works** here.
|
|
23
23
|
[/card]`,"Omit title for a card with no header.")),l.appendChild(e("Icon \u2014 inline (default)",`[card title="Feature" icon="star"]
|
|
24
24
|
Icon sits left of the title in a flex row.
|
|
@@ -32,41 +32,41 @@ Hidden by default.
|
|
|
32
32
|
variant="primary" hover footer="Footer text"
|
|
33
33
|
collapsible="true" class="extra" id="my-card"]
|
|
34
34
|
Body content.
|
|
35
|
-
[/card]`,'All attributes are optional. variant accepts "primary". icon-layout accepts "inline" (default) or "stacked".')),
|
|
35
|
+
[/card]`,'All attributes are optional. variant accepts "primary". icon-layout accepts "inline" (default) or "stacked".')),M.appendChild(l);const p=document.createElement("div");p.appendChild(y("Badge")),p.appendChild(z("Inline badge/label elements. Self-closing renders an empty coloured dot."));const h=document.createElement("table");h.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['variant="..."',"primary (default), secondary, success, danger, warning, info, light, dark"],["pill","Flag: rounded pill shape (.badge-pill)"],["outline","Flag: outlined style (.badge-outline)"],['size="small|large"',"Reduced or enlarged badge"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),h.appendChild(t)}),p.appendChild(h),p.appendChild(e("Basic badge",'[badge variant="success"]New[/badge]',null)),p.appendChild(e("Pill outline badge",'[badge variant="danger" outline pill]Deprecated[/badge]',null)),M.appendChild(p);const s=document.createElement("div");s.appendChild(y("Spacer")),s.appendChild(z("Self-closing shortcode that inserts a fixed-height blank gap."));const b=document.createElement("table");b.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['size="N"',"Height in px (default: 8)"],['class="..."',"Extra CSS class on the spacer div"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),b.appendChild(t)}),s.appendChild(b),s.appendChild(e("32px gap",'[spacer size="32" /]',null)),Q.appendChild(s);const m=document.createElement("div");m.appendChild(y("Icon")),m.appendChild(z("Self-closing shortcode that renders any Domma icon inline."));const u=document.createElement("table");u.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Domma icon name (required). Also accepted as src=."],['size="N"',"Width and height in px"],['color="..."',"CSS colour applied via style"],['class="..."',"Extra CSS classes"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),u.appendChild(t)}),m.appendChild(u),m.appendChild(e("Coloured icon",'[icon name="star" size="24" color="#f5a623" /]',null)),M.appendChild(m);const w=document.createElement("div");w.appendChild(y("Center")),w.appendChild(z("Wraps content in a centred div. Works with any content including cards, grids, and text.")),w.appendChild(e("Example","[center]Centred content here[/center]",'Accepts an optional class="..." attribute for extra styling.')),Q.appendChild(w);const c=document.createElement("div");c.appendChild(y("Hero")),c.appendChild(z("Full-width hero sections \u2014 no plugin required. Uses Domma's built-in Hero CSS component."));const v=document.createElement("table");v.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Heading text"],['tagline="..."',"Subtitle text"],['size="sm|lg|full"',"Height variant (default: normal)"],['variant="dark|primary|gradient-blue|gradient-purple|gradient-sunset|gradient-ocean"',"Colour / gradient preset"],['image="url"',"Background image URL (adds cover mode)"],['overlay="light|dark|darker|gradient|gradient-reverse"',"Image overlay style"],['align="center|left"',"Content alignment (default: center)"],['fullwidth="true"',"Break out of the page container to span full viewport width"],['bg="..."',"Background colour CSS value"],["twinkle","Flag: adds particle overlay (requires Effects plugin)"],['twinkle-count="N"',"Number of particles"],['twinkle-colour="..."',"Particle colour CSS value"],["blobs","Flag: adds ambient blob background"],['blobs-type="..."',"Blob animation type (default: float-blobs)"],['class="..."',"Extra CSS classes"],['id="..."',"Element id"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),v.appendChild(t)}),c.appendChild(v),c.appendChild(e("Basic hero",'[hero title="Welcome" tagline="Build something great"][/hero]',null)),c.appendChild(e("Gradient with body content",`[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]
|
|
36
36
|
Some introductory **Markdown** content here.
|
|
37
|
-
[/hero]`,null)),
|
|
37
|
+
[/hero]`,null)),c.appendChild(e("Background image with overlay",'[hero title="Our Story" image="/media/hero.jpg" overlay="dark" size="full"][/hero]',"Combine image + overlay for text legibility over photos.")),Q.appendChild(c);const T=document.createElement("div");T.appendChild(y("Interactive Components")),T.appendChild(z("Tabs, accordion, carousel, and countdown \u2014 no plugin required.")),T.appendChild(e("Tabs",`[tabs]
|
|
38
38
|
[tab title="First"]Content **A**[/tab]
|
|
39
39
|
[tab title="Second"]Content **B**[/tab]
|
|
40
|
-
[/tabs]`,'Add style="pills" for pill-style navigation.')),
|
|
40
|
+
[/tabs]`,'Add style="pills" for pill-style navigation.')),T.appendChild(e("Accordion",`[accordion]
|
|
41
41
|
[item title="Question 1"]Answer here.[/item]
|
|
42
42
|
[item title="Question 2"]Answer here.[/item]
|
|
43
|
-
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),
|
|
43
|
+
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),T.appendChild(e("Carousel",`[carousel autoplay="true" interval="5000"]
|
|
44
44
|
[slide title="Slide 1"]Description[/slide]
|
|
45
45
|
[slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]
|
|
46
|
-
[/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),
|
|
46
|
+
[/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),T.appendChild(e("Countdown (to date)",'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',null)),T.appendChild(e("Countdown (duration)",'[countdown duration="300" format="mm:ss" /]',"duration is in seconds. format options: mm:ss \xB7 HH:mm:ss \xB7 DD:HH:mm:ss")),M.appendChild(T);const g=document.createElement("div");g.appendChild(y("Timeline")),g.appendChild(z("Renders a Domma Progression component with event items."));const C=document.createElement("table");C.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['layout="vertical|centred|horizontal"',"[timeline] \u2014 layout orientation (default: vertical)"],['theme="minimal|corporate|modern"',"[timeline] \u2014 visual theme (default: minimal)"],['mode="timeline|roadmap"',"[timeline] \u2014 display mode (default: timeline)"],['class="..."',"[timeline] \u2014 extra CSS classes"],['id="..."',"[timeline] \u2014 element id"],['title="..."',"[event] \u2014 event heading"],['date="..."',"[event] \u2014 display date string"],['status="planned|in-progress|completed|blocked"',"[event] \u2014 progress status"],['icon="..."',"[event] \u2014 Domma icon name"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),C.appendChild(t)}),g.appendChild(C),g.appendChild(e("Vertical timeline",`[timeline layout="vertical" theme="modern"]
|
|
47
47
|
[event title="Kickoff" date="Jan 2025" status="completed" icon="flag"]
|
|
48
48
|
Project launched.
|
|
49
49
|
[/event]
|
|
50
50
|
[event title="Beta" date="Jun 2025" status="in-progress" icon="rocket"]
|
|
51
51
|
In active development.
|
|
52
52
|
[/event]
|
|
53
|
-
[/timeline]`,null)),
|
|
53
|
+
[/timeline]`,null)),M.appendChild(g);const B=document.createElement("div");B.appendChild(y("Embedding a Form"));const r=document.createElement("table");r.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['name="..."',"Form slug (required). Also accepted as slug=."],['class="..."',"Extra CSS classes on the wrapper div"],['id="..."',"id attribute on the wrapper div"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),r.appendChild(t)}),B.appendChild(r),B.appendChild(e("Basic",'[form name="contact" /]',"Forms are managed under Forms in the sidebar.")),B.appendChild(e("With wrapper styling",'[form name="newsletter" class="mt-4" id="newsletter-form" /]',null)),V.appendChild(B);const k=document.createElement("div");k.appendChild(y("Displaying a Collection"));const D=document.createElement("pre");D.style.cssText=be+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;",D.textContent='[collection slug="enquiries" display="table" /]',k.appendChild(D);const N=[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["fields","Comma-separated field keys to show. Defaults to all."],["limit","Maximum number of entries to display."],["sort","Field key to sort by."],["order","asc or desc (default asc)."],["title-field","Field used as the card/list title (cards + list)."],["columns","Grid columns for cards display (2\u20134, default 3)."],["search","true/false \u2014 enable search in table mode (default true)."],["sortable","true/false \u2014 enable column sorting in table mode (default true)."],["exportable","true/false \u2014 show CSV export button in table mode (default false)."],["page-size","Rows per page in table mode (default 25)."],["empty","Text shown when there are no entries."],["cta","Action slug: enables per-entry CTA buttons."],["cta-label",'CTA button label (default: "Run").'],["cta-icon","CTA button icon name."],["cta-style","CTA button variant (default: primary)."],["cta-confirm","CTA confirmation prompt text."]],P=document.createElement("table");P.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",N.forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),P.appendChild(t)}),k.appendChild(P),k.appendChild(z("Table display uses Domma Table \u2014 search, sort, pagination, column selector, and CSV export are all built in. Cards and lists are static.")),V.appendChild(k);const H=document.createElement("div");H.appendChild(y("View \u2014 Pro")),H.appendChild(z("Renders a Collection with full filtering, pagination, and display options. Requires a Pro MongoDB collection."));const W=document.createElement("table");W.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["slug","Required. The collection slug."],["display","table (default), cards, or list."],["limit","Maximum number of entries to display."],["fields","Comma-separated field keys to show."],["title-field","Field used as the card/list title."],["columns","Grid columns for cards display (2\u20134, default 3)."],["empty","Text shown when there are no entries."]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),W.appendChild(t)}),H.appendChild(W),H.appendChild(e("Table display",'[view slug="products" display="table" limit="50" /]',null)),H.appendChild(e("Cards display",'[view slug="team" display="cards" columns="3" title-field="name" /]',null)),V.appendChild(H);const F=document.createElement("div");F.appendChild(y("CTA \u2014 Pro")),F.appendChild(z("Renders an action button that triggers a Pro Action against an entry. Requires the Pro Actions feature."));const K=document.createElement("table");K.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["action","Required. The action slug."],["entry","Required. The entry id to act on."],["style","Button variant (default: primary)."],["icon","Domma icon name for the button."],["size","Button size (sm, lg)."],["confirm","Confirmation prompt text before running."],["label","Self-closing only: button label text."]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;width:35%;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),K.appendChild(t)}),F.appendChild(K),F.appendChild(e("Wrapping form",'[cta action="approve" entry="abc123" style="success" confirm="Approve this entry?"]Approve[/cta]',null)),F.appendChild(e("Self-closing",'[cta action="archive" entry="abc123" icon="archive" label="Archive" /]',null)),V.appendChild(F);const q=document.createElement("div");q.appendChild(y("Effects")),q.appendChild(z("Shortcodes for scroll-triggered animations, counters, typewriter text, and more. Requires the Domma Effects plugin."));const me=[['[reveal animation="fade"]...[/reveal]',"Fade/slide/zoom on scroll"],["[breathe]...[/breathe]","Continuous gentle scale loop"],["[pulse]...[/pulse]","Repeating scale pulse"],["[shake]...[/shake]","One-shot shake"],['[scribe speed="50"]...[/scribe]',"Typewriter \u2014 simple mode (text typed letter by letter)"],['[scribe loop="true"][render]Text[/render][wait]1500[/wait][undo /][/scribe]',"Typewriter \u2014 script mode (sequenced render/wait/undo actions)"],["[scramble]...[/scramble]","Character-scramble reveal"],['[counter to="100" /]',"Animated number counter (self-closing)"],["[ripple]...[/ripple]","Click-triggered ripple"],['[twinkle count="50"]...[/twinkle]',"Particle/star overlay"],['[animate type="fade-in-up"]...[/animate]',"CSS-only animation (no plugin needed)"],['[ambient type="aurora"]...[/ambient]',"CSS-only animated background"],['[firework type="burst" colour="rainbow" /]',"CSS firework (self-closing)"],["[fireworks]...[/fireworks]","Fireworks display container"],['[celebrate theme="auto" /]',"Seasonal canvas celebration"]],le=document.createElement("table");le.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",me.forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),le.appendChild(t)}),q.appendChild(le),q.appendChild(e("Scribe script \u2014 looping multi-phrase typewriter",`[scribe loop="true" loop-delay="2000" delete-speed="20"]
|
|
54
54
|
[render]We build things that matter.[/render]
|
|
55
55
|
[wait]1200[/wait]
|
|
56
56
|
[undo all="true" /]
|
|
57
57
|
[render]We design for humans.[/render]
|
|
58
58
|
[wait]1200[/wait]
|
|
59
59
|
[undo all="true" /]
|
|
60
|
-
[/scribe]`,'Use [render]...[/render] to type text, [wait]Ms[/wait] to pause, and [undo /] to delete. loop="true" replays the sequence. Only [render], [wait], and [undo] shortcodes are parsed \u2014 plain text between actions is ignored.')),
|
|
60
|
+
[/scribe]`,'Use [render]...[/render] to type text, [wait]Ms[/wait] to pause, and [undo /] to delete. loop="true" replays the sequence. Only [render], [wait], and [undo] shortcodes are parsed \u2014 plain text between actions is ignored.')),q.appendChild(z("Enable the Domma Effects plugin to activate these shortcodes on your site.")),ae.appendChild(q);const G=document.createElement("div");G.appendChild(y("Tables")),G.appendChild(z("Wrap a standard Markdown (GFM) table with Domma CSS classes and responsive horizontal scrolling."));const Y=document.createElement("table");Y.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['striped="true"',"Alternate row background (.table-striped)"],['bordered="true"',"Borders on all cells (.table-bordered)"],['compact="true"',"Reduced cell padding (.table-compact)"],['caption="..."',"Caption text above the table"],['class="..."',"Extra CSS classes appended to .table"],['id="..."',"id attribute on the <table> element"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),Y.appendChild(t)}),G.appendChild(Y),G.appendChild(e("Striped + bordered table",`[table striped="true" bordered="true" caption="Product Pricing"]
|
|
61
61
|
| Product | Price |
|
|
62
62
|
| ------- | ----: |
|
|
63
63
|
| Widget | $9.99 |
|
|
64
64
|
| Gadget | $14.99 |
|
|
65
|
-
[/table]`,"The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.")),
|
|
65
|
+
[/table]`,"The inner content must be a valid GFM Markdown table. The shortcode adds Domma CSS classes and wraps in a responsive scroll container.")),M.appendChild(G);const oe=document.createElement("div");oe.appendChild(y("Slideover")),oe.appendChild(z("A trigger button that opens a slide-in panel with Markdown content. No plugin required."));const ie=document.createElement("table");ie.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['title="..."',"Panel header text"],['trigger="..."','Button label (default: "Open")'],['size="sm|md|lg"',"Panel width (default: md)"],['position="right|left"',"Slide direction (default: right)"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),ie.appendChild(t)}),oe.appendChild(ie),oe.appendChild(e("Example",`[slideover title="More Info" trigger="Read more" size="md"]
|
|
66
66
|
## Details
|
|
67
67
|
Markdown content here.
|
|
68
|
-
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),
|
|
68
|
+
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),M.appendChild(oe);const X=document.createElement("div");X.appendChild(y("DConfig \u2014 Declarative Behaviour")),X.appendChild(z("Define click handlers and class toggles without writing JavaScript. Use the DConfig section above or embed inline with [dconfig]...[/dconfig] in the content body. Inline shortcodes win on selector conflict.")),X.appendChild(e("Toggle a class on click",'{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }','Selector keys use standard CSS selectors. "target" is optional \u2014 defaults to the element itself.')),X.appendChild(e("Inline shortcode syntax",`[dconfig]
|
|
69
69
|
{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
|
|
70
|
-
[/dconfig]`,null)),ae.appendChild(X);const
|
|
70
|
+
[/dconfig]`,null)),ae.appendChild(X);const A=document.createElement("div");A.appendChild(y("Tips"));const L=document.createElement("table");L.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['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"]].forEach(([i,d])=>{const t=document.createElement("tr"),n=document.createElement("td");n.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",n.appendChild(S(i));const a=document.createElement("td");a.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",a.textContent=d,t.appendChild(n),t.appendChild(a),L.appendChild(t)}),A.appendChild(L),A.appendChild(z("Most shortcodes accept a class attribute for custom styling.")),ne.appendChild(A),o.setContent(re),o.open(),E.tabs(Z)}let de=!1,fe=null,ve=!1;export const pageEditorView={templateUrl:"/admin/js/templates/page-editor.html",async onMount(o){const ge=window.location.hash.match(/#\/pages\/edit(\/.*)/),S=ge?ge[1]:null,y=!!S,re=E.loader(o.get(0),{type:"dots"}),Z=[U.layouts.get().catch(()=>({})),U.settings.get().catch(()=>({}))];y&&Z.push(U.pages.get(S).catch(()=>null));const[ce,J,x]=await Promise.all(Z);re.destroy();const ne=J?.layoutOptions?.spacerSize??40;if(y&&!x){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}o.find("#editor-title").text(y?`Edit Page \u2014 ${x.title||S}`:"New Page"),y&&o.find("#page-url-path").val(S),x&&(o.find("#field-title").val(x.title||""),o.find("#field-description").val(x.description||""),o.find("#field-status").val(x.status||"draft"),o.find("#field-sort-order").val(x.sortOrder??99),o.find("#field-show-in-nav").prop("checked",!!x.showInNav),o.find("#field-sidebar").prop("checked",!!x.sidebar),o.find("#field-show-breadcrumbs").prop("checked",x.breadcrumbs!==!1),o.find("#field-category").val(x.category||""),o.find("#field-visibility").val(x.visibility||"public"),o.find("#field-theme").val(x.theme||""),o.find("#field-seo-title").val(x.seo?.title||""),o.find("#field-seo-desc").val(x.seo?.description||""),x.dconfig&&o.find("#field-dconfig").val(JSON.stringify(x.dconfig,null,2)));const Q=await U.pages.tags().catch(()=>[]),M=o.find("#field-tags").get(0),V=M?E.pillbox(M,{data:Q,value:x?.tags||[],creatable:!0,searchable:!0,placeholder:"Add tag\u2026"}):null,ae=o.find("#field-layout").empty();if(Object.entries(ce).forEach(([e,f])=>{const l=(x?.layout||"default")===e?"selected":"";ae.append(`<option value="${e}" ${l}>${f.label||e}</option>`)}),y){const e=o.find("#view-page-btn").get(0);e.href=S,e.style.display="";const f=o.find("#live-preview-tab").get(0);f.style.display=""}if(E.tabs(o.find("#editor-meta-tabs").get(0)),y){const e=o.find("#live-preview-tab").get(0),f=o.find("#live-preview-frame").get(0);let l=!1;e.addEventListener("click",function(){l||(f.src=S,l=!0)})}const O=o.find("#markdown-editor"),ee=o.find("#markdown-preview");x&&O.val(x.content||"");const j=O.get(0),te=document.createElement("div");te.className="editor-line-numbers",j.parentElement.insertBefore(te,j);const pe=()=>{const e=j.value.split(`
|
|
71
71
|
`).length;te.textContent=Array.from({length:e},(f,l)=>l+1).join(`
|
|
72
|
-
`),te.scrollTop=j.scrollTop};pe(),j.addEventListener("input",pe),j.addEventListener("scroll",()=>{te.scrollTop=j.scrollTop});let Ce=null;const _=()=>{clearTimeout(Ce),Ce=setTimeout(async()=>{try{const{html:e}=await U.pages.preview(q.val());ee.html(e,{safe:!1}),I.scan(ee.get(0))}catch{window.marked&&ee.html(marked.parse(q.val()))}},400)},k=ye(q,o.find("#editor-toolbar"),{spacerDefault:ne});k.onLink(async e=>{const l=(await U.pages.list().catch(()=>[])).map(g=>({label:g.title||g.urlPath,value:g.urlPath})),c=document.createElement("div");c.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const h=document.createElement("label");h.className="form-label",h.textContent="URL";const r=document.createElement("input");r.type="text",r.className="form-input",r.placeholder="/about or https://example.com";const b=e.value.substring(e.selectionStart,e.selectionEnd);b&&b.startsWith("/")&&(r.value=b),c.appendChild(h),c.appendChild(r);const p=document.createElement("label");p.className="form-label",p.textContent="Link text";const m=document.createElement("input");m.type="text",m.className="form-input",m.placeholder="Display text",b&&!b.startsWith("/")&&(m.value=b),c.appendChild(p),c.appendChild(m);const y=document.createElement("button");y.type="button",y.className="btn btn-primary",y.textContent="Insert Link",c.appendChild(y);const s=E.modal({title:"Insert Link",size:"sm"});s.element.appendChild(c),s.open(),requestAnimationFrame(()=>{E.autocomplete(r,{data:l,minChars:1,onSelect:g=>{r.value=g.value,m.value||(m.value=g.label)}}),r.focus()}),y.addEventListener("click",()=>{const g=r.value.trim(),S=m.value.trim();if(!g)return;s.close();const u=`[${S||g}](${g})`,w=e.selectionStart,B=e.selectionEnd;e.value=e.value.substring(0,w)+u+e.value.substring(B),e.selectionStart=e.selectionEnd=w+u.length,e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onImage(async e=>{const l=(await U.media.list().catch(()=>[])).filter(r=>/\.(png|jpe?g|gif|webp|svg)$/i.test(r.name)),c=document.createElement("div");if(c.className="media-picker-grid",l.length)l.forEach(r=>{const b=document.createElement("div");b.className="media-picker-item",b.dataset.url=r.url;const p=document.createElement("img");p.src=r.url,p.alt=r.name;const m=document.createElement("span");m.textContent=r.name,b.appendChild(p),b.appendChild(m),c.appendChild(b)});else{const r=document.createElement("p");r.className="text-muted p-3",r.textContent="No images uploaded yet.",c.appendChild(r)}const h=E.modal({title:"Insert Image",size:"lg"});h.element.appendChild(c),$(h.element).on("click",".media-picker-item",function(){const r=$(this).data("url"),b=$(this).find("span").text();he(e,``),h.close(),q.get(0).dispatchEvent(new Event("input",{bubbles:!0}))}),h.open()}),k.onCollection(async e=>{const f=await U.collections.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="Collection";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(D=>{const N=document.createElement("option");N.value=D.slug,N.textContent=D.title||D.slug,p.appendChild(N)});else{const D=document.createElement("option");D.value="",D.textContent="No collections found",p.appendChild(D)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("div"),y=document.createElement("label");y.style.cssText=c,y.textContent="Display";const s=document.createElement("select");s.style.cssText=h,["table","cards","list"].forEach(D=>{const N=document.createElement("option");N.value=D,N.textContent=D.charAt(0).toUpperCase()+D.slice(1),s.appendChild(N)}),m.appendChild(y),m.appendChild(s),l.appendChild(m);const g=document.createElement("div");g.style.display="none";const S=document.createElement("label");S.style.cssText=c,S.textContent="Columns (2\u20134)";const u=document.createElement("input");u.type="number",u.min="2",u.max="4",u.value="3",u.style.cssText=h,g.appendChild(S),g.appendChild(u),l.appendChild(g);const w=document.createElement("div"),B=document.createElement("label");B.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const x=document.createElement("input");x.type="checkbox",x.checked=!0,B.appendChild(x),B.appendChild(document.createTextNode("Enable search")),w.appendChild(B),l.appendChild(w);const z=document.createElement("div"),L=document.createElement("label");L.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const A=document.createElement("input");A.type="checkbox",A.checked=!0,L.appendChild(A),L.appendChild(document.createTextNode("Sortable columns")),z.appendChild(L),l.appendChild(z);const H=document.createElement("div"),M=document.createElement("label");M.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const V=document.createElement("input");V.type="checkbox",V.checked=!1,M.appendChild(V),M.appendChild(document.createTextNode("CSV export")),H.appendChild(M),l.appendChild(H);const P=document.createElement("div"),K=document.createElement("label");K.style.cssText=c,K.textContent="Rows per page";const F=document.createElement("input");F.type="number",F.min="5",F.max="100",F.value="25",F.style.cssText=h,P.appendChild(K),P.appendChild(F),l.appendChild(P);const me=document.createElement("div"),le=document.createElement("label");le.style.cssText=c,le.textContent="Limit (optional)";const W=document.createElement("input");W.type="number",W.placeholder="All",W.style.cssText=h,me.appendChild(le),me.appendChild(W),l.appendChild(me);const Y=document.createElement("button");Y.type="button",Y.className="btn btn-primary",Y.textContent="Insert",l.appendChild(Y);const oe=[w,z,H,P],ie=()=>{const D=s.value==="table";oe.forEach(N=>{N.style.display=D?"":"none"}),g.style.display=s.value==="cards"?"":"none"};s.addEventListener("change",ie),ie();const X=E.modal({title:"Insert Collection",size:"sm"});X.element.appendChild(l),X.open(),Y.addEventListener("click",()=>{const D=p.value;if(!D)return;const N=s.value;let i=`[collection slug="${D}" display="${N}"`;N==="cards"&&(i+=` columns="${u.value}"`),N==="table"&&!x.checked&&(i+=' search="false"'),N==="table"&&!A.checked&&(i+=' sortable="false"'),N==="table"&&V.checked&&(i+=' exportable="true"'),N==="table"&&F.value!=="25"&&(i+=` page-size="${F.value}"`);const d=W.value.trim();d&&(i+=` limit="${d}"`),i+=" /]",X.close(),he(e,i),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onForm(async e=>{const f=await U.forms.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="Form";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(s=>{const g=document.createElement("option");g.value=s.slug,g.textContent=s.title||s.slug,p.appendChild(g)});else{const s=document.createElement("option");s.value="",s.textContent="No forms found",p.appendChild(s)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("button");m.className="btn btn-primary btn-sm",m.style.cssText="align-self:flex-end;margin-top:.5rem;",m.textContent="Insert Form",l.appendChild(m);const y=E.modal({title:"Insert Form",size:"sm"});y.element.appendChild(l),y.open(),m.addEventListener("click",()=>{const s=p.value;s&&(he(e,`[form slug="${s}" /]`),y.close(),q.get(0).dispatchEvent(new Event("input",{bubbles:!0})))})}),k.onView(async e=>{const f=await U.views.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",r=document.createElement("div"),b=document.createElement("label");b.style.cssText=c,b.textContent="View";const p=document.createElement("select");if(p.style.cssText=h,f.length)f.forEach(u=>{const w=document.createElement("option");w.value=u.slug,w.textContent=u.title||u.slug,p.appendChild(w)});else{const u=document.createElement("option");u.value="",u.textContent="No Views configured yet \u2014 create one under Data \u2192 Views",p.appendChild(u)}r.appendChild(b),r.appendChild(p),l.appendChild(r);const m=document.createElement("div"),y=document.createElement("label");y.style.cssText=c,y.textContent="Display";const s=document.createElement("select");s.style.cssText=h,["table","cards","list"].forEach(u=>{const w=document.createElement("option");w.value=u,w.textContent=u.charAt(0).toUpperCase()+u.slice(1),s.appendChild(w)}),m.appendChild(y),m.appendChild(s),l.appendChild(m);const g=document.createElement("button");g.type="button",g.className="btn btn-primary",g.textContent="Insert",l.appendChild(g);const S=E.modal({title:"Insert View",size:"sm"});S.element.appendChild(l),S.open(),g.addEventListener("click",()=>{const u=p.value;if(!u)return;const w=`[view slug="${u}" display="${s.value}" /]`;S.close(),he(e,w),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onCta(async e=>{const f=await U.actions.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const c="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;";function r(x,z){const L=document.createElement("div"),A=document.createElement("label");return A.style.cssText=c,A.textContent=x,L.appendChild(A),L.appendChild(z),L}function b(x){const z=document.createElement("select");return z.style.cssText=h,x.forEach(([L,A])=>{const H=document.createElement("option");H.value=L,H.textContent=A,z.appendChild(H)}),z}function p(x,z,L){const A=document.createElement("input");return A.type=x,A.placeholder=z||"",A.value=L||"",A.style.cssText=h,A}const m=b(f.length?f.map(x=>[x.slug,x.title||x.slug]):[["","No Actions configured yet \u2014 create one under Data \u2192 Actions"]]);l.appendChild(r("Action",m));const y=p("text","Button label","Run");if(l.appendChild(r("Label",y)),m.addEventListener("change",()=>{const x=f.find(z=>z.slug===m.value);x?.trigger?.label&&(y.value=x.trigger.label)}),f.length){const x=f[0];x?.trigger?.label&&(y.value=x.trigger.label)}const s=p("text","Paste entry UUID\u2026","");l.appendChild(r("Entry ID",s));const g=b([["primary","Primary"],["secondary","Secondary"],["ghost","Ghost"],["danger","Danger"]]);l.appendChild(r("Style",g));const S=p("text","e.g. check, zap, send (optional)","");l.appendChild(r("Icon",S));const u=p("text","Confirmation message (optional)","");l.appendChild(r("Confirm prompt",u));const w=document.createElement("button");w.type="button",w.className="btn btn-primary",w.textContent="Insert",l.appendChild(w);const B=E.modal({title:"Insert CTA Button",size:"sm"});B.element.appendChild(l),B.open(),w.addEventListener("click",()=>{const x=m.value;if(!x)return;const z=s.value.trim(),L=(y.value.trim()||"Run").replace(/\[\/cta\]/gi,""),A=g.value,H=S.value.trim().replace(/"/g,""),M=u.value.trim().replace(/"/g,""),V=z.replace(/"/g,"");let P=`action="${x}" style="${A}"`;V&&(P+=` entry="${V}"`),H&&(P+=` icon="${H}"`),M&&(P+=` confirm="${M}"`);const K=`[cta ${P}]${L}[/cta]`;B.close(),he(e,K),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),k.onHelp(()=>{xe()}),o.find(".editor-view-btn").on("click",function(){const e=$(this).data("mode");o.find(".editor-view-btn").removeClass("active"),$(this).addClass("active"),o.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${e}`)}),o.find("#fullscreen-btn").on("click",function(){o.find(".editor-card").toggleClass("editor-fullscreen")}),de=!1,fe&&window.removeEventListener("beforeunload",fe),fe=e=>{de&&e.preventDefault()},window.addEventListener("beforeunload",fe),ve||(ve=!0,R.use((e,f,l)=>{const c=window.location.hash.startsWith("#/pages/edit")||window.location.hash==="#/pages/new";if(!de||!c)return l();E.confirm("You have unsaved changes. Leave this page?").then(h=>{h&&(de=!1,l())})})),q.on("input",()=>{de=!0,_()}),_(),o.find("#save-btn").on("click",async()=>{const e=o.find("#page-url-path").val().trim();if(!e){E.toast("URL path is required.",{type:"warning"});return}const f=o.find("#field-dconfig").val().trim();let l=null;if(f)try{l=JSON.parse(f)}catch{E.toast("DConfig JSON is invalid. Please check the format before saving.",{type:"warning"});return}const c={title:o.find("#field-title").val().trim()||"Untitled",description:o.find("#field-description").val().trim(),layout:o.find("#field-layout").val(),status:o.find("#field-status").val(),sortOrder:parseInt(o.find("#field-sort-order").val(),10)||99,showInNav:o.find("#field-show-in-nav").is(":checked"),sidebar:o.find("#field-sidebar").is(":checked"),...o.find("#field-show-breadcrumbs").is(":checked")?{}:{breadcrumbs:!1},tags:G?G.getValue():[],category:o.find("#field-category").val().trim()||null,visibility:o.find("#field-visibility").val()||"public",seo:{title:o.find("#field-seo-title").val().trim(),description:o.find("#field-seo-desc").val().trim()},dconfig:l,...o.find("#field-theme").val()?{theme:o.find("#field-theme").val()}:{}};try{if(o.find("#save-btn").prop("disabled",!0).text("Saving\u2026"),C){const h={frontmatter:c,body:q.val()};e!==T&&(h.newUrlPath=e),await U.pages.update(T,h),E.toast("Page saved successfully.",{type:"success"}),de=!1,e!==T&&R.navigate(`/pages/edit${e}`)}else await U.pages.create({urlPath:e,frontmatter:c,body:q.val()}),E.toast("Page saved successfully.",{type:"success"}),de=!1,R.navigate("/pages")}catch(h){E.toast(`Save failed: ${h.message||"Unknown error"}`,{type:"error"})}finally{o.find("#save-btn").prop("disabled",!1).text("Save")}}),o.find("#cancel-btn").on("click",()=>R.navigate("/pages")),Domma.icons.scan()}};
|
|
72
|
+
`),te.scrollTop=j.scrollTop};pe(),j.addEventListener("input",pe),j.addEventListener("scroll",()=>{te.scrollTop=j.scrollTop});let Ce=null;const _=()=>{clearTimeout(Ce),Ce=setTimeout(async()=>{try{const{html:e}=await U.pages.preview(O.val());ee.html(e,{safe:!1}),I.scan(ee.get(0))}catch{window.marked&&ee.html(marked.parse(O.val()))}},400)},z=ye(O,o.find("#editor-toolbar"),{spacerDefault:ne});z.onLink(async e=>{const l=(await U.pages.list().catch(()=>[])).map(r=>({label:r.title||r.urlPath,value:r.urlPath})),p=document.createElement("div");p.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const h=document.createElement("label");h.className="form-label",h.textContent="URL";const s=document.createElement("input");s.type="text",s.className="form-input",s.placeholder="/about or https://example.com";const b=e.value.substring(e.selectionStart,e.selectionEnd);b&&b.startsWith("/")&&(s.value=b),p.appendChild(h),p.appendChild(s);const m=document.createElement("label");m.className="form-label",m.textContent="Link text";const u=document.createElement("input");u.type="text",u.className="form-input",u.placeholder="Display text",b&&!b.startsWith("/")&&(u.value=b),p.appendChild(m),p.appendChild(u);const w=document.createElement("label");w.className="form-label",w.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const c=document.createElement("input");c.type="checkbox",c.style.cssText="width:1rem;height:1rem;cursor:pointer;",w.appendChild(c),w.appendChild(document.createTextNode("Display as button")),p.appendChild(w);const v=document.createElement("label");v.className="form-label",v.textContent="Button colour",v.style.display="none";const T=document.createElement("select");T.className="form-select",T.style.display="none",[["primary","Primary"],["secondary","Secondary"],["success","Success (Green)"],["danger","Danger (Red)"],["warning","Warning (Orange)"],["info","Info (Blue)"],["outline","Outline"],["outline-success","Outline Success"],["outline-danger","Outline Danger"],["ghost","Ghost"]].forEach(([r,k])=>{const D=document.createElement("option");D.value=r,D.textContent=k,T.appendChild(D)}),p.appendChild(v),p.appendChild(T),c.addEventListener("change",()=>{const r=c.checked;v.style.display=r?"":"none",T.style.display=r?"":"none"});const C=document.createElement("button");C.type="button",C.className="btn btn-primary",C.textContent="Insert Link",p.appendChild(C);const B=E.modal({title:"Insert Link",size:"sm"});B.element.appendChild(p),B.open(),requestAnimationFrame(()=>{E.autocomplete(s,{data:l,minChars:1,onSelect:r=>{s.value=r.value,u.value||(u.value=r.label)}}),s.focus()}),C.addEventListener("click",()=>{const r=s.value.trim(),k=u.value.trim();if(!r)return;B.close();const D=c.checked?`<a href="${r}" class="btn btn-${T.value}">${k||r}</a>`:`[${k||r}](${r})`,N=e.selectionStart,P=e.selectionEnd;e.value=e.value.substring(0,N)+D+e.value.substring(P),e.selectionStart=e.selectionEnd=N+D.length,e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),z.onImage(async e=>{const l=(await U.media.list().catch(()=>[])).filter(s=>/\.(png|jpe?g|gif|webp|svg)$/i.test(s.name)),p=document.createElement("div");if(p.className="media-picker-grid",l.length)l.forEach(s=>{const b=document.createElement("div");b.className="media-picker-item",b.dataset.url=s.url;const m=document.createElement("img");m.src=s.url,m.alt=s.name;const u=document.createElement("span");u.textContent=s.name,b.appendChild(m),b.appendChild(u),p.appendChild(b)});else{const s=document.createElement("p");s.className="text-muted p-3",s.textContent="No images uploaded yet.",p.appendChild(s)}const h=E.modal({title:"Insert Image",size:"lg"});h.element.appendChild(p),$(h.element).on("click",".media-picker-item",function(){const s=$(this).data("url"),b=$(this).find("span").text();he(e,``),h.close(),O.get(0).dispatchEvent(new Event("input",{bubbles:!0}))}),h.open()}),z.onCollection(async e=>{const f=await U.collections.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const p="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",s=document.createElement("div"),b=document.createElement("label");b.style.cssText=p,b.textContent="Collection";const m=document.createElement("select");if(m.style.cssText=h,f.length)f.forEach(A=>{const L=document.createElement("option");L.value=A.slug,L.textContent=A.title||A.slug,m.appendChild(L)});else{const A=document.createElement("option");A.value="",A.textContent="No collections found",m.appendChild(A)}s.appendChild(b),s.appendChild(m),l.appendChild(s);const u=document.createElement("div"),w=document.createElement("label");w.style.cssText=p,w.textContent="Display";const c=document.createElement("select");c.style.cssText=h,["table","cards","list"].forEach(A=>{const L=document.createElement("option");L.value=A,L.textContent=A.charAt(0).toUpperCase()+A.slice(1),c.appendChild(L)}),u.appendChild(w),u.appendChild(c),l.appendChild(u);const v=document.createElement("div");v.style.display="none";const T=document.createElement("label");T.style.cssText=p,T.textContent="Columns (2\u20134)";const g=document.createElement("input");g.type="number",g.min="2",g.max="4",g.value="3",g.style.cssText=h,v.appendChild(T),v.appendChild(g),l.appendChild(v);const C=document.createElement("div"),B=document.createElement("label");B.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const r=document.createElement("input");r.type="checkbox",r.checked=!0,B.appendChild(r),B.appendChild(document.createTextNode("Enable search")),C.appendChild(B),l.appendChild(C);const k=document.createElement("div"),D=document.createElement("label");D.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const N=document.createElement("input");N.type="checkbox",N.checked=!0,D.appendChild(N),D.appendChild(document.createTextNode("Sortable columns")),k.appendChild(D),l.appendChild(k);const P=document.createElement("div"),H=document.createElement("label");H.style.cssText="display:flex;align-items:center;gap:.5rem;font-size:.9em;cursor:pointer;";const W=document.createElement("input");W.type="checkbox",W.checked=!1,H.appendChild(W),H.appendChild(document.createTextNode("CSV export")),P.appendChild(H),l.appendChild(P);const F=document.createElement("div"),K=document.createElement("label");K.style.cssText=p,K.textContent="Rows per page";const q=document.createElement("input");q.type="number",q.min="5",q.max="100",q.value="25",q.style.cssText=h,F.appendChild(K),F.appendChild(q),l.appendChild(F);const me=document.createElement("div"),le=document.createElement("label");le.style.cssText=p,le.textContent="Limit (optional)";const G=document.createElement("input");G.type="number",G.placeholder="All",G.style.cssText=h,me.appendChild(le),me.appendChild(G),l.appendChild(me);const Y=document.createElement("button");Y.type="button",Y.className="btn btn-primary",Y.textContent="Insert",l.appendChild(Y);const oe=[C,k,P,F],ie=()=>{const A=c.value==="table";oe.forEach(L=>{L.style.display=A?"":"none"}),v.style.display=c.value==="cards"?"":"none"};c.addEventListener("change",ie),ie();const X=E.modal({title:"Insert Collection",size:"sm"});X.element.appendChild(l),X.open(),Y.addEventListener("click",()=>{const A=m.value;if(!A)return;const L=c.value;let i=`[collection slug="${A}" display="${L}"`;L==="cards"&&(i+=` columns="${g.value}"`),L==="table"&&!r.checked&&(i+=' search="false"'),L==="table"&&!N.checked&&(i+=' sortable="false"'),L==="table"&&W.checked&&(i+=' exportable="true"'),L==="table"&&q.value!=="25"&&(i+=` page-size="${q.value}"`);const d=G.value.trim();d&&(i+=` limit="${d}"`),i+=" /]",X.close(),he(e,i),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),z.onForm(async e=>{const f=await U.forms.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const p="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",s=document.createElement("div"),b=document.createElement("label");b.style.cssText=p,b.textContent="Form";const m=document.createElement("select");if(m.style.cssText=h,f.length)f.forEach(c=>{const v=document.createElement("option");v.value=c.slug,v.textContent=c.title||c.slug,m.appendChild(v)});else{const c=document.createElement("option");c.value="",c.textContent="No forms found",m.appendChild(c)}s.appendChild(b),s.appendChild(m),l.appendChild(s);const u=document.createElement("button");u.className="btn btn-primary btn-sm",u.style.cssText="align-self:flex-end;margin-top:.5rem;",u.textContent="Insert Form",l.appendChild(u);const w=E.modal({title:"Insert Form",size:"sm"});w.element.appendChild(l),w.open(),u.addEventListener("click",()=>{const c=m.value;c&&(he(e,`[form slug="${c}" /]`),w.close(),O.get(0).dispatchEvent(new Event("input",{bubbles:!0})))})}),z.onView(async e=>{const f=await U.views.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const p="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;",s=document.createElement("div"),b=document.createElement("label");b.style.cssText=p,b.textContent="View";const m=document.createElement("select");if(m.style.cssText=h,f.length)f.forEach(g=>{const C=document.createElement("option");C.value=g.slug,C.textContent=g.title||g.slug,m.appendChild(C)});else{const g=document.createElement("option");g.value="",g.textContent="No Views configured yet \u2014 create one under Data \u2192 Views",m.appendChild(g)}s.appendChild(b),s.appendChild(m),l.appendChild(s);const u=document.createElement("div"),w=document.createElement("label");w.style.cssText=p,w.textContent="Display";const c=document.createElement("select");c.style.cssText=h,["table","cards","list"].forEach(g=>{const C=document.createElement("option");C.value=g,C.textContent=g.charAt(0).toUpperCase()+g.slice(1),c.appendChild(C)}),u.appendChild(w),u.appendChild(c),l.appendChild(u);const v=document.createElement("button");v.type="button",v.className="btn btn-primary",v.textContent="Insert",l.appendChild(v);const T=E.modal({title:"Insert View",size:"sm"});T.element.appendChild(l),T.open(),v.addEventListener("click",()=>{const g=m.value;if(!g)return;const C=`[view slug="${g}" display="${c.value}" /]`;T.close(),he(e,C),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),z.onCta(async e=>{const f=await U.actions.list().catch(()=>[]),l=document.createElement("div");l.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:.75rem;";const p="display:block;font-size:.85em;font-weight:600;margin-bottom:.25rem;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;",h="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;";function s(r,k){const D=document.createElement("div"),N=document.createElement("label");return N.style.cssText=p,N.textContent=r,D.appendChild(N),D.appendChild(k),D}function b(r){const k=document.createElement("select");return k.style.cssText=h,r.forEach(([D,N])=>{const P=document.createElement("option");P.value=D,P.textContent=N,k.appendChild(P)}),k}function m(r,k,D){const N=document.createElement("input");return N.type=r,N.placeholder=k||"",N.value=D||"",N.style.cssText=h,N}const u=b(f.length?f.map(r=>[r.slug,r.title||r.slug]):[["","No Actions configured yet \u2014 create one under Data \u2192 Actions"]]);l.appendChild(s("Action",u));const w=m("text","Button label","Run");if(l.appendChild(s("Label",w)),u.addEventListener("change",()=>{const r=f.find(k=>k.slug===u.value);r?.trigger?.label&&(w.value=r.trigger.label)}),f.length){const r=f[0];r?.trigger?.label&&(w.value=r.trigger.label)}const c=m("text","Paste entry UUID\u2026","");l.appendChild(s("Entry ID",c));const v=b([["primary","Primary"],["secondary","Secondary"],["ghost","Ghost"],["danger","Danger"]]);l.appendChild(s("Style",v));const T=m("text","e.g. check, zap, send (optional)","");l.appendChild(s("Icon",T));const g=m("text","Confirmation message (optional)","");l.appendChild(s("Confirm prompt",g));const C=document.createElement("button");C.type="button",C.className="btn btn-primary",C.textContent="Insert",l.appendChild(C);const B=E.modal({title:"Insert CTA Button",size:"sm"});B.element.appendChild(l),B.open(),C.addEventListener("click",()=>{const r=u.value;if(!r)return;const k=c.value.trim(),D=(w.value.trim()||"Run").replace(/\[\/cta\]/gi,""),N=v.value,P=T.value.trim().replace(/"/g,""),H=g.value.trim().replace(/"/g,""),W=k.replace(/"/g,"");let F=`action="${r}" style="${N}"`;W&&(F+=` entry="${W}"`),P&&(F+=` icon="${P}"`),H&&(F+=` confirm="${H}"`);const K=`[cta ${F}]${D}[/cta]`;B.close(),he(e,K),e.dispatchEvent(new Event("input",{bubbles:!0})),e.focus()})}),z.onHelp(()=>{xe()}),o.find(".editor-view-btn").on("click",function(){const e=$(this).data("mode");o.find(".editor-view-btn").removeClass("active"),$(this).addClass("active"),o.find("#editor-body").removeClass("editor-mode-split editor-mode-write editor-mode-preview").addClass(`editor-mode-${e}`)}),o.find("#fullscreen-btn").on("click",function(){o.find(".editor-card").toggleClass("editor-fullscreen")}),de=!1,fe&&window.removeEventListener("beforeunload",fe),fe=e=>{de&&e.preventDefault()},window.addEventListener("beforeunload",fe),ve||(ve=!0,R.use((e,f,l)=>{const p=window.location.hash.startsWith("#/pages/edit")||window.location.hash==="#/pages/new";if(!de||!p)return l();E.confirm("You have unsaved changes. Leave this page?").then(h=>{h&&(de=!1,l())})})),O.on("input",()=>{de=!0,_()}),_(),o.find("#save-btn").on("click",async()=>{const e=o.find("#page-url-path").val().trim();if(!e){E.toast("URL path is required.",{type:"warning"});return}const f=o.find("#field-dconfig").val().trim();let l=null;if(f)try{l=JSON.parse(f)}catch{E.toast("DConfig JSON is invalid. Please check the format before saving.",{type:"warning"});return}const p={title:o.find("#field-title").val().trim()||"Untitled",description:o.find("#field-description").val().trim(),layout:o.find("#field-layout").val(),status:o.find("#field-status").val(),sortOrder:parseInt(o.find("#field-sort-order").val(),10)||99,showInNav:o.find("#field-show-in-nav").is(":checked"),sidebar:o.find("#field-sidebar").is(":checked"),...o.find("#field-show-breadcrumbs").is(":checked")?{}:{breadcrumbs:!1},tags:V?V.getValue():[],category:o.find("#field-category").val().trim()||null,visibility:o.find("#field-visibility").val()||"public",seo:{title:o.find("#field-seo-title").val().trim(),description:o.find("#field-seo-desc").val().trim()},dconfig:l,...o.find("#field-theme").val()?{theme:o.find("#field-theme").val()}:{}};try{if(o.find("#save-btn").prop("disabled",!0).text("Saving\u2026"),y){const h={frontmatter:p,body:O.val()};e!==S&&(h.newUrlPath=e),await U.pages.update(S,h),E.toast("Page saved successfully.",{type:"success"}),de=!1,e!==S&&R.navigate(`/pages/edit${e}`)}else await U.pages.create({urlPath:e,frontmatter:p,body:O.val()}),E.toast("Page saved successfully.",{type:"success"}),de=!1,R.navigate("/pages")}catch(h){E.toast(`Save failed: ${h.message||"Unknown error"}`,{type:"error"})}finally{o.find("#save-btn").prop("disabled",!1).text("Save")}}),o.find("#cancel-btn").on("click",()=>R.navigate("/pages")),Domma.icons.scan()}};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@fastify/rate-limit": "^10.3.0",
|
|
69
69
|
"@fastify/static": "^8.1.0",
|
|
70
70
|
"bcryptjs": "^3.0.3",
|
|
71
|
-
"domma-js": "^0.
|
|
71
|
+
"domma-js": "^0.20.2",
|
|
72
72
|
"dotenv": "^17.2.3",
|
|
73
73
|
"fastify": "5.8.1",
|
|
74
74
|
"gray-matter": "^4.0.3",
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"/":
|
|
2
|
+
"/": 138,
|
|
3
3
|
"/about": 71,
|
|
4
4
|
"/blog": 30,
|
|
5
5
|
"/contact": 30,
|
|
6
6
|
"/resources/typography": 4,
|
|
7
7
|
"/resources": 13,
|
|
8
8
|
"/resources/shortcodes": 14,
|
|
9
|
-
"/resources/cards":
|
|
9
|
+
"/resources/cards": 15,
|
|
10
10
|
"/resources/interactive": 13,
|
|
11
|
-
"/resources/grid":
|
|
11
|
+
"/resources/grid": 6,
|
|
12
12
|
"/forms": 14,
|
|
13
13
|
"/resources/effects": 6,
|
|
14
14
|
"/blog/hello-world": 20,
|
|
15
|
-
"/feedback":
|
|
15
|
+
"/feedback": 38,
|
|
16
16
|
"/resources/dependencies": 2,
|
|
17
17
|
"/resources/components": 6,
|
|
18
18
|
"/gdpr": 3,
|
|
19
19
|
"/scratch": 51,
|
|
20
20
|
"/getting-started": 3,
|
|
21
21
|
"/resources/pro": 1,
|
|
22
|
-
"/todo":
|
|
22
|
+
"/todo": 23,
|
|
23
23
|
"/thank-you": 1
|
|
24
24
|
}
|
package/scripts/seed.js
CHANGED
|
@@ -1197,6 +1197,277 @@ See the [Shortcode Reference](/resources/shortcodes) for full documentation.
|
|
|
1197
1197
|
---
|
|
1198
1198
|
|
|
1199
1199
|
← [Back to Resources](/resources)
|
|
1200
|
+
`
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
file: 'privacy.md',
|
|
1204
|
+
fm: {
|
|
1205
|
+
title: 'Privacy Policy', slug: 'privacy',
|
|
1206
|
+
description: 'How we collect, use, and protect your personal data',
|
|
1207
|
+
layout: 'default', status: 'published',
|
|
1208
|
+
sortOrder: 90, showInNav: false, sidebar: false,
|
|
1209
|
+
seo: {title: 'Privacy Policy', description: 'How we collect, use, and protect your personal data'},
|
|
1210
|
+
createdAt: now, updatedAt: now, visibility: 'public'
|
|
1211
|
+
},
|
|
1212
|
+
body: `[hero variant="dark" size="sm" fullwidth="true" twinkle twinkle-count="20" blobs]
|
|
1213
|
+
|
|
1214
|
+
# Privacy Policy
|
|
1215
|
+
|
|
1216
|
+
How [Your Organisation] collects, uses, and protects your personal data.
|
|
1217
|
+
|
|
1218
|
+
[/hero]
|
|
1219
|
+
|
|
1220
|
+
**Last updated: ${new Date().toLocaleDateString('en-GB', {day: 'numeric', month: 'long', year: 'numeric'})}**
|
|
1221
|
+
|
|
1222
|
+
[Your Organisation] ("we", "us", "our") is committed to protecting and respecting your privacy. This policy explains
|
|
1223
|
+
what personal data we collect, how we use it, and your rights under applicable data protection law, including the UK
|
|
1224
|
+
GDPR.
|
|
1225
|
+
|
|
1226
|
+
---
|
|
1227
|
+
|
|
1228
|
+
## 1. Who We Are
|
|
1229
|
+
|
|
1230
|
+
**Data Controller:** [Your Organisation]
|
|
1231
|
+
|
|
1232
|
+
If you have any questions about this policy or how we handle your data, please [contact us](/contact).
|
|
1233
|
+
|
|
1234
|
+
---
|
|
1235
|
+
|
|
1236
|
+
## 2. What Data We Collect
|
|
1237
|
+
|
|
1238
|
+
We may collect and process the following categories of personal data:
|
|
1239
|
+
|
|
1240
|
+
**Information you provide directly:**
|
|
1241
|
+
|
|
1242
|
+
- Name and contact details (e.g. email address) when you submit a contact or feedback form
|
|
1243
|
+
- Any other information you voluntarily provide through the site
|
|
1244
|
+
|
|
1245
|
+
**Information collected automatically:**
|
|
1246
|
+
|
|
1247
|
+
- Server access logs (IP address, browser type, pages visited, timestamps) for security and performance monitoring
|
|
1248
|
+
- Session data stored in your browser (e.g. theme preferences)
|
|
1249
|
+
|
|
1250
|
+
We do not use third-party analytics services or advertising trackers on this site.
|
|
1251
|
+
|
|
1252
|
+
---
|
|
1253
|
+
|
|
1254
|
+
## 3. How We Use Your Data
|
|
1255
|
+
|
|
1256
|
+
We use the personal data we collect for the following purposes:
|
|
1257
|
+
|
|
1258
|
+
| Purpose | Legal Basis |
|
|
1259
|
+
|----------------------------------------------------------|----------------------|
|
|
1260
|
+
| Responding to enquiries submitted via our contact form | Legitimate interests |
|
|
1261
|
+
| Monitoring site security and diagnosing technical issues | Legitimate interests |
|
|
1262
|
+
| Complying with legal obligations | Legal obligation |
|
|
1263
|
+
|
|
1264
|
+
We will never sell, rent, or share your personal data with third parties for marketing purposes.
|
|
1265
|
+
|
|
1266
|
+
---
|
|
1267
|
+
|
|
1268
|
+
## 4. How Long We Keep Your Data
|
|
1269
|
+
|
|
1270
|
+
We retain personal data only for as long as necessary for the purposes described above:
|
|
1271
|
+
|
|
1272
|
+
- **Contact form submissions:** Up to 12 months from the date of receipt, unless an ongoing relationship requires longer
|
|
1273
|
+
retention
|
|
1274
|
+
- **Server access logs:** Up to 30 days, then automatically purged
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1278
|
+
## 5. Your Rights
|
|
1279
|
+
|
|
1280
|
+
Under the UK GDPR, you have the following rights regarding your personal data:
|
|
1281
|
+
|
|
1282
|
+
- **Right of access** — request a copy of the data we hold about you
|
|
1283
|
+
- **Right to rectification** — ask us to correct inaccurate data
|
|
1284
|
+
- **Right to erasure** — ask us to delete your data where we have no lawful basis to retain it
|
|
1285
|
+
- **Right to restrict processing** — ask us to pause processing while a dispute is resolved
|
|
1286
|
+
- **Right to data portability** — receive your data in a structured, machine-readable format
|
|
1287
|
+
- **Right to object** — object to processing based on legitimate interests
|
|
1288
|
+
|
|
1289
|
+
To exercise any of these rights, please [contact us](/contact). We will respond within one calendar month.
|
|
1290
|
+
|
|
1291
|
+
If you are not satisfied with our response, you have the right to lodge a complaint with the **Information
|
|
1292
|
+
Commissioner's Office (ICO)**: [ico.org.uk](https://ico.org.uk) | 0303 123 1113.
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
## 6. Cookies
|
|
1297
|
+
|
|
1298
|
+
This site uses only essential cookies necessary for its operation (e.g. storing your preferred theme). No tracking or
|
|
1299
|
+
advertising cookies are used. See our [Cookie Policy](/cookies) for full details.
|
|
1300
|
+
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
## 7. Security
|
|
1304
|
+
|
|
1305
|
+
We take appropriate technical and organisational measures to protect your personal data against unauthorised access,
|
|
1306
|
+
disclosure, alteration, or destruction.
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
|
|
1310
|
+
## 8. Changes to This Policy
|
|
1311
|
+
|
|
1312
|
+
We may update this policy from time to time. The "Last updated" date at the top of this page will always reflect the
|
|
1313
|
+
most recent revision.
|
|
1314
|
+
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
## 9. Contact Us
|
|
1318
|
+
|
|
1319
|
+
For any privacy-related enquiries, please use our [contact page](/contact).
|
|
1320
|
+
`
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
file: 'cookies.md',
|
|
1324
|
+
fm: {
|
|
1325
|
+
title: 'Cookie Policy', slug: 'cookies',
|
|
1326
|
+
description: 'Our approach to cookies, tracking, and your rights under UK GDPR',
|
|
1327
|
+
layout: 'default', status: 'published',
|
|
1328
|
+
sortOrder: 91, showInNav: false, sidebar: false,
|
|
1329
|
+
seo: {
|
|
1330
|
+
title: 'Cookie Policy',
|
|
1331
|
+
description: 'Our approach to cookies, tracking, and your rights under UK GDPR'
|
|
1332
|
+
},
|
|
1333
|
+
createdAt: now, updatedAt: now, visibility: 'public'
|
|
1334
|
+
},
|
|
1335
|
+
body: `[hero variant="dark" size="sm" fullwidth="true" twinkle twinkle-count="20" blobs]
|
|
1336
|
+
|
|
1337
|
+
# Cookie Policy
|
|
1338
|
+
|
|
1339
|
+
Your rights and our obligations under UK GDPR & PECR.
|
|
1340
|
+
|
|
1341
|
+
[/hero]
|
|
1342
|
+
|
|
1343
|
+
**Last updated: ${new Date().toLocaleDateString('en-GB', {day: 'numeric', month: 'long', year: 'numeric'})}**
|
|
1344
|
+
|
|
1345
|
+
This page explains how [Your Organisation] handles cookies and your rights under the UK General Data Protection
|
|
1346
|
+
Regulation (UK GDPR) and the Privacy and Electronic Communications Regulations (PECR).
|
|
1347
|
+
|
|
1348
|
+
---
|
|
1349
|
+
|
|
1350
|
+
## 1. What Are Cookies?
|
|
1351
|
+
|
|
1352
|
+
Cookies are small text files placed on your device by a website. They allow the site to remember your preferences and
|
|
1353
|
+
settings between visits.
|
|
1354
|
+
|
|
1355
|
+
---
|
|
1356
|
+
|
|
1357
|
+
## 2. Cookies We Use
|
|
1358
|
+
|
|
1359
|
+
This site uses a minimal, privacy-first approach to cookies. We do **not** use advertising, tracking, or profiling
|
|
1360
|
+
cookies of any kind.
|
|
1361
|
+
|
|
1362
|
+
| Cookie | Type | Purpose | Duration |
|
|
1363
|
+
|-------------|------------|---------------------------------------|------------------------|
|
|
1364
|
+
| \`dm_theme\` | Functional | Stores your preferred colour theme | Session / localStorage |
|
|
1365
|
+
| \`dm_motion\` | Functional | Stores your reduced-motion preference | Session / localStorage |
|
|
1366
|
+
|
|
1367
|
+
**Note:** Theme and motion preferences are stored in your browser's \`localStorage\`, not as HTTP cookies. They never
|
|
1368
|
+
leave your device and are not transmitted to our servers.
|
|
1369
|
+
|
|
1370
|
+
We do not embed third-party content (e.g. social media widgets, YouTube iframes) that would set third-party cookies
|
|
1371
|
+
without your consent.
|
|
1372
|
+
|
|
1373
|
+
---
|
|
1374
|
+
|
|
1375
|
+
## 3. Your Cookie Choices
|
|
1376
|
+
|
|
1377
|
+
Because we only use functional, non-tracking storage, we do not require a cookie consent banner under PECR.
|
|
1378
|
+
|
|
1379
|
+
You can clear locally stored preferences at any time through your browser settings:
|
|
1380
|
+
|
|
1381
|
+
- **Chrome / Edge:** Settings → Privacy and security → Clear browsing data → Cookies and other site data
|
|
1382
|
+
- **Firefox:** Settings → Privacy & Security → Cookies and Site Data → Clear Data
|
|
1383
|
+
- **Safari:** Settings → Safari → Clear History and Website Data
|
|
1384
|
+
|
|
1385
|
+
Clearing these will reset your theme and motion preferences to the site defaults.
|
|
1386
|
+
|
|
1387
|
+
---
|
|
1388
|
+
|
|
1389
|
+
## 4. Your Rights Under UK GDPR
|
|
1390
|
+
|
|
1391
|
+
As a UK resident (or EU resident accessing our site), you have rights under UK GDPR:
|
|
1392
|
+
|
|
1393
|
+
### Right to Be Informed
|
|
1394
|
+
|
|
1395
|
+
We tell you how we use your data through this policy and our [Privacy Policy](/privacy).
|
|
1396
|
+
|
|
1397
|
+
### Right of Access
|
|
1398
|
+
|
|
1399
|
+
You can request a copy of any personal data we hold about you by [contacting us](/contact).
|
|
1400
|
+
|
|
1401
|
+
### Right to Erasure ("Right to be Forgotten")
|
|
1402
|
+
|
|
1403
|
+
Where we hold personal data about you and have no overriding legal reason to retain it, you can ask us to delete it.
|
|
1404
|
+
|
|
1405
|
+
### Right to Restrict Processing
|
|
1406
|
+
|
|
1407
|
+
If you dispute the accuracy of your data or our right to process it, you can ask us to pause processing while we
|
|
1408
|
+
investigate.
|
|
1409
|
+
|
|
1410
|
+
### Right to Object
|
|
1411
|
+
|
|
1412
|
+
You can object to processing of your personal data where we rely on legitimate interests as the legal basis.
|
|
1413
|
+
|
|
1414
|
+
### Right to Data Portability
|
|
1415
|
+
|
|
1416
|
+
Where processing is based on your consent or a contract, you can receive your data in a portable format.
|
|
1417
|
+
|
|
1418
|
+
### How to Exercise Your Rights
|
|
1419
|
+
|
|
1420
|
+
Submit a request via our [contact page](/contact). We will acknowledge your request within 72 hours and respond fully
|
|
1421
|
+
within one calendar month.
|
|
1422
|
+
|
|
1423
|
+
---
|
|
1424
|
+
|
|
1425
|
+
## 5. Lawful Bases for Processing
|
|
1426
|
+
|
|
1427
|
+
We only process personal data where we have a valid lawful basis. The primary bases we rely on are:
|
|
1428
|
+
|
|
1429
|
+
- **Legitimate interests** — operating a secure, functional website and responding to enquiries
|
|
1430
|
+
- **Legal obligation** — where required by UK law
|
|
1431
|
+
|
|
1432
|
+
We do not rely on consent for any processing on this site, meaning you do not need to opt in or out for the site to
|
|
1433
|
+
function normally.
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
## 6. International Transfers
|
|
1438
|
+
|
|
1439
|
+
All data processed by this site is stored and processed within the UK and/or EEA. We do not transfer personal data to
|
|
1440
|
+
countries outside these territories.
|
|
1441
|
+
|
|
1442
|
+
---
|
|
1443
|
+
|
|
1444
|
+
## 7. Data Retention
|
|
1445
|
+
|
|
1446
|
+
We retain personal data only as long as necessary. See our [Privacy Policy](/privacy) for specific retention periods.
|
|
1447
|
+
|
|
1448
|
+
---
|
|
1449
|
+
|
|
1450
|
+
## 8. The ICO
|
|
1451
|
+
|
|
1452
|
+
The UK supervisory authority for data protection is the **Information Commissioner's Office (ICO)**:
|
|
1453
|
+
|
|
1454
|
+
- Website: [ico.org.uk](https://ico.org.uk)
|
|
1455
|
+
- Phone: 0303 123 1113
|
|
1456
|
+
|
|
1457
|
+
You have the right to lodge a complaint with the ICO at any time if you believe we have not handled your data lawfully.
|
|
1458
|
+
|
|
1459
|
+
---
|
|
1460
|
+
|
|
1461
|
+
## 9. Changes to This Policy
|
|
1462
|
+
|
|
1463
|
+
We will update this page whenever our practices change. The "Last updated" date at the top reflects the current
|
|
1464
|
+
revision.
|
|
1465
|
+
|
|
1466
|
+
---
|
|
1467
|
+
|
|
1468
|
+
## 10. Contact
|
|
1469
|
+
|
|
1470
|
+
Privacy queries: use our [contact page](/contact).
|
|
1200
1471
|
`
|
|
1201
1472
|
}
|
|
1202
1473
|
];
|
|
@@ -64,7 +64,7 @@ async function loadBlockTemplate(blockName) {
|
|
|
64
64
|
* @param {object|null} ctaOpts - Optional CTA button options
|
|
65
65
|
* @returns {string}
|
|
66
66
|
*/
|
|
67
|
-
function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts) {
|
|
67
|
+
function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols) {
|
|
68
68
|
if (!entries.length) {
|
|
69
69
|
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
70
70
|
}
|
|
@@ -88,7 +88,12 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts) {
|
|
|
88
88
|
return html;
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
const validCols = ['2', '3', '4', '5', '6'].includes(String(cols)) ? cols : '';
|
|
92
|
+
const wrapperClass = validCols
|
|
93
|
+
? `dm-collection-display dm-collection-blocks grid grid-cols-${validCols} gap-4`
|
|
94
|
+
: 'dm-collection-display dm-collection-blocks';
|
|
95
|
+
|
|
96
|
+
return `<div class="${wrapperClass}">\n${items.join('\n')}\n</div>`;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
function renderCollectionTable(slug, entries, visibleFields, attrs, ctaOpts) {
|
|
@@ -333,7 +338,8 @@ async function processCollectionBlocks(markdown) {
|
|
|
333
338
|
if (blockName) {
|
|
334
339
|
try {
|
|
335
340
|
const tpl = await loadBlockTemplate(blockName);
|
|
336
|
-
|
|
341
|
+
const cols = attrs.cols || '';
|
|
342
|
+
replacement = renderCollectionBlocks(entries, tpl, emptyMsg, ctaOpts, cols);
|
|
337
343
|
} catch {
|
|
338
344
|
replacement = `<div class="dm-collection-display dm-collection-empty"><p>Block template “${escapeHtmlText(blockName)}” not found.</p></div>`;
|
|
339
345
|
}
|
|
@@ -550,9 +556,30 @@ function processCardBlocks(markdown) {
|
|
|
550
556
|
|
|
551
557
|
const iconLayout = (attrs['icon-layout'] || 'inline').trim(); // 'inline' | 'stacked'
|
|
552
558
|
|
|
553
|
-
|
|
559
|
+
// Extract [header]...[/header] and [footer]...[/footer] sub-tags (Pattern B)
|
|
560
|
+
let headerContent = null;
|
|
561
|
+
let footerContent = null;
|
|
562
|
+
let remaining = body;
|
|
563
|
+
remaining = remaining.replace(
|
|
564
|
+
/\[header\]([\s\S]*?)\[\/header\]/i,
|
|
565
|
+
(_, inner) => {
|
|
566
|
+
headerContent = inner.trim();
|
|
567
|
+
return '';
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
remaining = remaining.replace(
|
|
571
|
+
/\[footer\]([\s\S]*?)\[\/footer\]/i,
|
|
572
|
+
(_, inner) => {
|
|
573
|
+
footerContent = inner.trim();
|
|
574
|
+
return '';
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// Header — sub-tag wins over attributes; attributes only used when no sub-tag
|
|
554
579
|
let headerHtml = '';
|
|
555
|
-
|
|
580
|
+
if (headerContent !== null) {
|
|
581
|
+
headerHtml = `<div class="card-header">${marked.parse(headerContent)}</div>`;
|
|
582
|
+
} else if (title || icon) {
|
|
556
583
|
let inner = '';
|
|
557
584
|
if (icon && iconLayout === 'stacked') {
|
|
558
585
|
// Stacked: icon centred above title, all centred
|
|
@@ -574,8 +601,10 @@ function processCardBlocks(markdown) {
|
|
|
574
601
|
}
|
|
575
602
|
}
|
|
576
603
|
|
|
577
|
-
|
|
578
|
-
|
|
604
|
+
const bodyHtml = `<div class="card-body">${marked.parse(remaining.trim())}</div>`;
|
|
605
|
+
const footerHtml = footerContent !== null
|
|
606
|
+
? `<div class="card-footer">${marked.parse(footerContent)}</div>`
|
|
607
|
+
: footer ? `<div class="card-footer">${footer}</div>` : '';
|
|
579
608
|
|
|
580
609
|
return `<div class="${classes.join(' ')}"${coll}${id}>${headerHtml}${bodyHtml}${footerHtml}</div>\n`;
|
|
581
610
|
}
|