domma-cms 0.3.0 → 0.5.2
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/README.md +3 -3
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +2313 -0
- package/admin/dist/domma/domma-tools.min.js +10 -0
- package/admin/index.html +4 -0
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +8 -4
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +18 -10
- package/admin/js/templates/action-editor.html +171 -0
- package/admin/js/templates/actions-list.html +19 -0
- package/admin/js/templates/api-reference.html +1411 -0
- package/admin/js/templates/block-editor.html +158 -0
- package/admin/js/templates/blocks.html +8 -0
- package/admin/js/templates/collection-editor.html +47 -0
- package/admin/js/templates/collection-entries.html +3 -0
- package/admin/js/templates/collections.html +51 -4
- package/admin/js/templates/documentation.html +258 -0
- package/{plugins/form-builder/admin → admin/js}/templates/form-editor.html +238 -199
- package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
- package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
- package/admin/js/templates/login.html +29 -4
- package/admin/js/templates/my-profile.html +17 -0
- package/admin/js/templates/page-editor.html +39 -0
- package/admin/js/templates/pages.html +6 -1
- package/admin/js/templates/pro-docs.html +259 -0
- package/admin/js/templates/role-editor.html +59 -0
- package/admin/js/templates/roles.html +10 -0
- package/admin/js/templates/settings.html +167 -23
- package/admin/js/templates/tutorials.html +81 -0
- package/admin/js/templates/user-editor.html +7 -0
- package/admin/js/templates/users.html +3 -26
- package/admin/js/templates/view-editor.html +201 -0
- package/admin/js/templates/view-preview.html +51 -0
- package/admin/js/templates/views-list.html +19 -0
- package/admin/js/views/action-editor.js +1 -0
- package/admin/js/views/actions-list.js +1 -0
- package/admin/js/views/api-reference.js +1 -0
- package/admin/js/views/block-editor.js +8 -0
- package/admin/js/views/blocks.js +4 -0
- package/admin/js/views/collection-editor.js +3 -3
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/dashboard.js +1 -1
- package/admin/js/views/form-editor.js +8 -0
- package/admin/js/views/form-submissions.js +1 -0
- package/admin/js/views/forms.js +1 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/media.js +1 -1
- package/admin/js/views/my-profile.js +1 -0
- package/admin/js/views/page-editor.js +34 -15
- package/admin/js/views/pages.js +5 -5
- package/admin/js/views/plugins.js +10 -10
- package/admin/js/views/pro-docs.js +1 -0
- package/admin/js/views/role-editor.js +1 -0
- package/admin/js/views/roles.js +4 -0
- package/admin/js/views/settings.js +3 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +4 -7
- package/admin/js/views/view-editor.js +1 -0
- package/admin/js/views/view-preview.js +1 -0
- package/admin/js/views/views-list.js +1 -0
- package/bin/cli.js +1 -1
- package/config/auth.json +1 -0
- package/config/connections.json.bak +9 -0
- package/config/connections.json.example +9 -0
- package/config/navigation.json +5 -15
- package/config/plugins.json +19 -29
- package/config/server.json +6 -6
- package/config/site.json +16 -6
- package/package.json +25 -10
- package/plugins/example-analytics/stats.json +17 -12
- package/plugins/form-builder/data/forms/contacts.json +62 -62
- package/plugins/form-builder/data/forms/enquiries.json +103 -0
- package/plugins/form-builder/data/forms/feedback.json +17 -16
- package/plugins/form-builder/data/forms/notes.json +79 -0
- package/plugins/form-builder/data/forms/to-do.json +100 -0
- package/plugins/form-builder/data/submissions/contacts.json +1 -26
- package/plugins/form-builder/data/submissions/notes.json +1 -0
- package/plugins/form-builder/data/submissions/to-do.json +1 -0
- package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
- package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
- package/plugins/theme-roller/config.js +1 -0
- package/plugins/theme-roller/plugin.js +233 -0
- package/plugins/theme-roller/plugin.json +31 -0
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +1 -0
- package/public/css/forms.css +1 -0
- package/public/css/site.css +1 -1
- package/public/js/forms.js +1 -0
- package/public/js/site.js +1 -1
- package/scripts/build.js +194 -129
- package/scripts/pro.js +254 -0
- package/scripts/reset.js +33 -8
- package/scripts/seed.js +677 -128
- package/scripts/setup.js +1 -0
- package/server/middleware/auth.js +136 -120
- package/server/routes/api/actions.js +200 -0
- package/server/routes/api/auth.js +292 -146
- package/server/routes/api/blocks.js +84 -0
- package/server/routes/api/collections.js +79 -27
- package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +491 -505
- package/server/routes/api/layouts.js +49 -39
- package/server/routes/api/media.js +118 -92
- package/server/routes/api/navigation.js +40 -36
- package/server/routes/api/pages.js +132 -118
- package/server/routes/api/plugins.js +6 -3
- package/server/routes/api/settings.js +104 -88
- package/server/routes/api/users.js +27 -19
- package/server/routes/api/views.js +148 -0
- package/server/routes/public.js +124 -108
- package/server/server.js +269 -181
- package/server/services/actions.js +387 -0
- package/server/services/adapterRegistry.js +98 -0
- package/server/services/adapters/FileAdapter.js +192 -0
- package/server/services/adapters/MongoAdapter.js +220 -0
- package/server/services/blocks.js +162 -0
- package/server/services/collections.js +74 -86
- package/server/services/connectionManager.js +102 -0
- package/server/services/content.js +312 -307
- package/server/services/email.js +126 -0
- package/server/services/forms.js +173 -0
- package/server/services/markdown.js +1378 -747
- package/server/services/permissionRegistry.js +173 -0
- package/server/services/presetCollections.js +251 -0
- package/server/services/renderer.js +98 -2
- package/server/services/roles.js +227 -0
- package/server/services/rowAccess.js +104 -0
- package/server/services/userProfiles.js +199 -0
- package/server/services/users.js +281 -212
- package/server/services/views.js +280 -0
- package/server/templates/page.html +124 -113
- package/plugins/form-builder/admin/templates/form-settings.html +0 -29
- package/plugins/form-builder/admin/views/form-editor.js +0 -1444
- package/plugins/form-builder/admin/views/form-settings.js +0 -38
- package/plugins/form-builder/admin/views/form-submissions.js +0 -295
- package/plugins/form-builder/admin/views/forms-list.js +0 -164
- package/plugins/form-builder/config.js +0 -9
- package/plugins/form-builder/data/forms/consent.json +0 -104
- package/plugins/form-builder/data/forms/contact-details.json +0 -99
- package/plugins/form-builder/data/submissions/consent.json +0 -13
- package/plugins/form-builder/plugin.json +0 -52
- package/plugins/form-builder/public/inject-body.html +0 -352
- package/plugins/form-builder/public/inject-head.html +0 -58
- package/plugins/form-builder/public/package.json +0 -1
- package/scripts/copy-domma.js +0 -48
- package/server/services/userTypes.js +0 -167
- /package/plugins/form-builder/data/submissions/{contact-details.json → enquiries.json} +0 -0
- /package/{plugins/form-builder/public → public/js}/form-logic-engine.js +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import{api as
|
|
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 T(i){const d=document.createElement("code");return d.style.cssText=be,d.textContent=i,d}function C(i){const d=document.createElement("h3");return d.style.cssText=ge,d.textContent=i,d}const se=document.createElement("div");se.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 v="display:flex;flex-direction:column;gap:1rem;",ne=document.createElement("div");ne.className="tab-panel active",ne.style.cssText=v;const Q=document.createElement("div");Q.className="tab-panel",Q.style.cssText=v;const O=document.createElement("div");O.className="tab-panel",O.style.cssText=v;const G=document.createElement("div");G.className="tab-panel",G.style.cssText=v;const ae=document.createElement("div");ae.className="tab-panel",ae.style.cssText=v,J.appendChild(ne),J.appendChild(Q),J.appendChild(O),J.appendChild(G),J.appendChild(ae),Z.appendChild(ce),Z.appendChild(J),se.appendChild(Z);const q=document.createElement("div");q.appendChild(C("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(T(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),q.appendChild(ee),ne.appendChild(q);const _=document.createElement("div");_.appendChild(C("Grid & Columns"));function k(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 re=document.createDocumentFragment();return re.appendChild(n),re.appendChild(a),t&&re.appendChild(k(t)),re}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,re)=>{const ue=document.createElement("td");ue.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",re===0?ue.appendChild(T(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]
|
|
5
|
-
[/grid]`,"cols sets the track count; each [col] fills one track.")),
|
|
5
|
+
[/grid]`,"cols sets the track count; each [col] fills one track.")),_.appendChild(e("Column spanning (12-col base)",`[grid cols="12" gap="3"]
|
|
6
6
|
[col span="8"]Main content[/col]
|
|
7
7
|
[col span="4"]Sidebar[/col]
|
|
8
|
-
[/grid]`,"span values must add up to cols. Common splits: 8/4, 6/6, 9/3.")),
|
|
8
|
+
[/grid]`,"span values must add up to cols. Common splits: 8/4, 6/6, 9/3.")),_.appendChild(e("Row (flexbox, equal-width)",`[row gap="4"]
|
|
9
9
|
[col]Left[/col]
|
|
10
10
|
[col]Right[/col]
|
|
11
|
-
[/row]`,"Use [row] when you want equal-width columns without specifying a count.")),
|
|
11
|
+
[/row]`,"Use [row] when you want equal-width columns without specifying a count.")),_.appendChild(e("Card in a grid",`[grid cols="3" gap="4"]
|
|
12
12
|
[col]
|
|
13
13
|
[card title="One"]Content[/card]
|
|
14
14
|
[/col]
|
|
@@ -18,36 +18,55 @@ import{api as x}from"../api.js";import{createToolbar as _,insertAtCursor as j,wr
|
|
|
18
18
|
[col]
|
|
19
19
|
[card title="Three"]Content[/card]
|
|
20
20
|
[/col]
|
|
21
|
-
[/grid]`,null)),
|
|
21
|
+
[/grid]`,null)),Q.appendChild(_);const l=document.createElement("div");l.appendChild(C("Cards")),l.appendChild(e("Basic card",`[card title="Optional Title"]
|
|
22
22
|
Markdown **works** here.
|
|
23
|
-
[/card]`,"Omit title for a card with no header.")),
|
|
23
|
+
[/card]`,"Omit title for a card with no header.")),l.appendChild(e("Icon \u2014 inline (default)",`[card title="Feature" icon="star"]
|
|
24
|
+
Icon sits left of the title in a flex row.
|
|
25
|
+
[/card]`,"icon accepts any Domma icon name. Default layout when icon is set.")),l.appendChild(e("Icon \u2014 stacked (centred)",`[card title="Feature" icon="star" icon-layout="stacked" hover]
|
|
26
|
+
Icon is centred above the title. Great for feature tiles.
|
|
27
|
+
[/card]`,'icon-layout="stacked" centres the icon above the title. Combine with hover for a lift effect.')),l.appendChild(e("Icon with subtitle",`[card title="Feature" subtitle="Tagline here" icon="star" icon-layout="stacked"]
|
|
28
|
+
Body content.
|
|
29
|
+
[/card]`,"subtitle adds a secondary line beneath the title in both layouts.")),l.appendChild(e("Collapsible card",`[card title="Click to expand" collapsible="true"]
|
|
24
30
|
Hidden by default.
|
|
25
|
-
[/card]`,null)),
|
|
31
|
+
[/card]`,null)),l.appendChild(e("Full attribute reference",`[card title="Title" subtitle="Sub" icon="star" icon-layout="stacked"
|
|
32
|
+
variant="primary" hover footer="Footer text"
|
|
33
|
+
collapsible="true" class="extra" id="my-card"]
|
|
34
|
+
Body content.
|
|
35
|
+
[/card]`,'All attributes are optional. variant accepts "primary". icon-layout accepts "inline" (default) or "stacked".')),O.appendChild(l);const c=document.createElement("div");c.appendChild(C("Badge")),c.appendChild(k("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(T(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)}),c.appendChild(h),c.appendChild(e("Basic badge",'[badge variant="success"]New[/badge]',null)),c.appendChild(e("Pill outline badge",'[badge variant="danger" outline pill]Deprecated[/badge]',null)),O.appendChild(c);const r=document.createElement("div");r.appendChild(C("Spacer")),r.appendChild(k("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(T(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)}),r.appendChild(b),r.appendChild(e("32px gap",'[spacer size="32" /]',null)),Q.appendChild(r);const p=document.createElement("div");p.appendChild(C("Icon")),p.appendChild(k("Self-closing shortcode that renders any Domma icon inline."));const m=document.createElement("table");m.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(T(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),m.appendChild(t)}),p.appendChild(m),p.appendChild(e("Coloured icon",'[icon name="star" size="24" color="#f5a623" /]',null)),O.appendChild(p);const y=document.createElement("div");y.appendChild(C("Center")),y.appendChild(k("Wraps content in a centred div. Works with any content including cards, grids, and text.")),y.appendChild(e("Example","[center]Centred content here[/center]",'Accepts an optional class="..." attribute for extra styling.')),Q.appendChild(y);const s=document.createElement("div");s.appendChild(C("Hero")),s.appendChild(k("Full-width hero sections \u2014 no plugin required. Uses Domma's built-in Hero CSS component."));const g=document.createElement("table");g.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(T(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),g.appendChild(t)}),s.appendChild(g),s.appendChild(e("Basic hero",'[hero title="Welcome" tagline="Build something great"][/hero]',null)),s.appendChild(e("Gradient with body content",`[hero title="Get Started" tagline="Everything you need." size="lg" variant="gradient-blue" align="center"]
|
|
26
36
|
Some introductory **Markdown** content here.
|
|
27
|
-
[/hero]`,null)),
|
|
37
|
+
[/hero]`,null)),s.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(s);const S=document.createElement("div");S.appendChild(C("Interactive Components")),S.appendChild(k("Tabs, accordion, carousel, and countdown \u2014 no plugin required.")),S.appendChild(e("Tabs",`[tabs]
|
|
28
38
|
[tab title="First"]Content **A**[/tab]
|
|
29
39
|
[tab title="Second"]Content **B**[/tab]
|
|
30
|
-
[/tabs]`,'Add style="pills" for pill-style navigation.')),
|
|
40
|
+
[/tabs]`,'Add style="pills" for pill-style navigation.')),S.appendChild(e("Accordion",`[accordion]
|
|
31
41
|
[item title="Question 1"]Answer here.[/item]
|
|
32
42
|
[item title="Question 2"]Answer here.[/item]
|
|
33
|
-
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),
|
|
43
|
+
[/accordion]`,'Add multiple="true" to allow several panels open at once.')),S.appendChild(e("Carousel",`[carousel autoplay="true" interval="5000"]
|
|
34
44
|
[slide title="Slide 1"]Description[/slide]
|
|
35
45
|
[slide image="/media/photo.jpg" title="Slide 2"]Caption[/slide]
|
|
36
|
-
[/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),
|
|
46
|
+
[/carousel]`,'Omit autoplay for a manual carousel. loop="false" disables wrapping.')),S.appendChild(e("Countdown (to date)",'[countdown to="2026-12-31" format="DD:HH:mm:ss" /]',null)),S.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")),O.appendChild(S);const u=document.createElement("div");u.appendChild(C("Timeline")),u.appendChild(k("Renders a Domma Progression component with event items."));const w=document.createElement("table");w.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(T(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)}),u.appendChild(w),u.appendChild(e("Vertical timeline",`[timeline layout="vertical" theme="modern"]
|
|
47
|
+
[event title="Kickoff" date="Jan 2025" status="completed" icon="flag"]
|
|
48
|
+
Project launched.
|
|
49
|
+
[/event]
|
|
50
|
+
[event title="Beta" date="Jun 2025" status="in-progress" icon="rocket"]
|
|
51
|
+
In active development.
|
|
52
|
+
[/event]
|
|
53
|
+
[/timeline]`,null)),O.appendChild(u);const B=document.createElement("div");B.appendChild(C("Embedding a Form"));const x=document.createElement("table");x.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(T(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),x.appendChild(t)}),B.appendChild(x),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)),G.appendChild(B);const z=document.createElement("div");z.appendChild(C("Displaying a Collection"));const L=document.createElement("pre");L.style.cssText=be+"display:block;padding:.5rem .75rem;white-space:pre;overflow-x:auto;",L.textContent='[collection slug="enquiries" display="table" /]',z.appendChild(L);const A=[["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."]],H=document.createElement("table");H.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",A.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(T(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)}),z.appendChild(H),z.appendChild(k("Table display uses Domma Table \u2014 search, sort, pagination, column selector, and CSV export are all built in. Cards and lists are static.")),G.appendChild(z);const M=document.createElement("div");M.appendChild(C("View \u2014 Pro")),M.appendChild(k("Renders a Collection with full filtering, pagination, and display options. Requires a Pro MongoDB collection."));const V=document.createElement("table");V.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(T(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)}),M.appendChild(V),M.appendChild(e("Table display",'[view slug="products" display="table" limit="50" /]',null)),M.appendChild(e("Cards display",'[view slug="team" display="cards" columns="3" title-field="name" /]',null)),G.appendChild(M);const P=document.createElement("div");P.appendChild(C("CTA \u2014 Pro")),P.appendChild(k("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(T(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)}),P.appendChild(K),P.appendChild(e("Wrapping form",'[cta action="approve" entry="abc123" style="success" confirm="Approve this entry?"]Approve[/cta]',null)),P.appendChild(e("Self-closing",'[cta action="archive" entry="abc123" icon="archive" label="Archive" /]',null)),G.appendChild(P);const F=document.createElement("div");F.appendChild(C("Effects")),F.appendChild(k("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(T(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)}),F.appendChild(le),F.appendChild(e("Scribe script \u2014 looping multi-phrase typewriter",`[scribe loop="true" loop-delay="2000" delete-speed="20"]
|
|
37
54
|
[render]We build things that matter.[/render]
|
|
38
55
|
[wait]1200[/wait]
|
|
39
56
|
[undo all="true" /]
|
|
40
57
|
[render]We design for humans.[/render]
|
|
41
58
|
[wait]1200[/wait]
|
|
42
59
|
[undo all="true" /]
|
|
43
|
-
[/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.')),F.appendChild(k("Enable the Domma Effects plugin to activate these shortcodes on your site.")),ae.appendChild(F);const W=document.createElement("div");W.appendChild(C("Tables")),W.appendChild(k("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(T(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)}),W.appendChild(Y),W.appendChild(e("Striped + bordered table",`[table striped="true" bordered="true" caption="Product Pricing"]
|
|
44
61
|
| Product | Price |
|
|
45
62
|
| ------- | ----: |
|
|
46
63
|
| Widget | $9.99 |
|
|
47
64
|
| Gadget | $14.99 |
|
|
48
|
-
[/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.")),O.appendChild(W);const oe=document.createElement("div");oe.appendChild(C("Slideover")),oe.appendChild(k("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(T(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"]
|
|
49
66
|
## Details
|
|
50
67
|
Markdown content here.
|
|
51
|
-
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),
|
|
68
|
+
[/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),O.appendChild(oe);const X=document.createElement("div");X.appendChild(C("DConfig \u2014 Declarative Behaviour")),X.appendChild(k("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]
|
|
52
69
|
{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
|
|
53
|
-
[/dconfig]`,null)),
|
|
70
|
+
[/dconfig]`,null)),ae.appendChild(X);const D=document.createElement("div");D.appendChild(C("Tips"));const N=document.createElement("table");N.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(T(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),N.appendChild(t)}),D.appendChild(N),D.appendChild(k("Most shortcodes accept a class attribute for custom styling.")),ne.appendChild(D),o.setContent(se),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(\/.*)/),T=ge?ge[1]:null,C=!!T,se=E.loader(o.get(0),{type:"dots"}),Z=[U.layouts.get().catch(()=>({})),U.settings.get().catch(()=>({}))];C&&Z.push(U.pages.get(T).catch(()=>null));const[ce,J,v]=await Promise.all(Z);se.destroy();const ne=J?.layoutOptions?.spacerSize??40;if(C&&!v){E.toast("Page not found.",{type:"error"}),R.navigate("/pages");return}o.find("#editor-title").text(C?`Edit Page \u2014 ${v.title||T}`:"New Page"),C&&o.find("#page-url-path").val(T),v&&(o.find("#field-title").val(v.title||""),o.find("#field-description").val(v.description||""),o.find("#field-status").val(v.status||"draft"),o.find("#field-sort-order").val(v.sortOrder??99),o.find("#field-show-in-nav").prop("checked",!!v.showInNav),o.find("#field-sidebar").prop("checked",!!v.sidebar),o.find("#field-show-breadcrumbs").prop("checked",v.breadcrumbs!==!1),o.find("#field-category").val(v.category||""),o.find("#field-visibility").val(v.visibility||"public"),o.find("#field-theme").val(v.theme||""),o.find("#field-seo-title").val(v.seo?.title||""),o.find("#field-seo-desc").val(v.seo?.description||""),v.dconfig&&o.find("#field-dconfig").val(JSON.stringify(v.dconfig,null,2)));const Q=await U.pages.tags().catch(()=>[]),O=o.find("#field-tags").get(0),G=O?E.pillbox(O,{data:Q,value:v?.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=(v?.layout||"default")===e?"selected":"";ae.append(`<option value="${e}" ${l}>${f.label||e}</option>`)}),C){const e=o.find("#view-page-btn").get(0);e.href=T,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)),C){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=T,l=!0)})}const q=o.find("#markdown-editor"),ee=o.find("#markdown-preview");v&&q.val(v.content||"");const j=q.get(0),te=document.createElement("div");te.className="editor-line-numbers",j.parentElement.insertBefore(te,j);const pe=()=>{const e=j.value.split(`
|
|
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()}};
|
package/admin/js/views/pages.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<a href="#/pages/edit${
|
|
3
|
-
<a href="${
|
|
4
|
-
<button class="btn btn-sm btn-danger btn-delete" data-path="${
|
|
5
|
-
`}],emptyMessage:'No pages found. <a href="#/pages/new">Create one</a>.'}),Domma.icons.scan(),document.querySelectorAll("#pages-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),Domma.effects.reveal(".card",{animation:"fade",duration:350})};
|
|
1
|
+
import{api as d}from"../api.js";export const pagesView={templateUrl:"/admin/js/templates/pages.html",async onMount(e){const c=E.loader(e.get(0),{type:"dots"});let n=await d.pages.list().catch(()=>[]);c.destroy();const l=s=>{T.create("#pages-table",{data:s,columns:[{key:"title",title:"Title",render:(t,a)=>`<a href="#/pages/edit${a.urlPath}">${t}</a>`},{key:"urlPath",title:"URL",render:t=>`<code>${t}</code>`},{key:"layout",title:"Layout"},{key:"status",title:"Status",render:t=>`<span class="badge badge-${t==="published"?"success":"warning"}">${t}</span>`},{key:"tags",title:"Tags",render:t=>Array.isArray(t)&&t.length?t.map(a=>`<span class="badge badge-info badge-pill badge-sm">${a}</span>`).join(" "):"\u2014"},{key:"updatedAt",title:"Updated",render:t=>t?D(t).format("DD MMM YYYY"):"\u2014"},{key:"actions",title:"Actions",render:(t,a)=>`
|
|
2
|
+
<a href="#/pages/edit${a.urlPath}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
|
+
<a href="${a.urlPath}" target="_blank" class="btn btn-sm btn-ghost" data-tooltip="View"><span data-icon="external-link"></span></a>
|
|
4
|
+
<button class="btn btn-sm btn-danger btn-delete" data-path="${a.urlPath}">Delete</button>
|
|
5
|
+
`}],emptyMessage:'No pages found. <a href="#/pages/new">Create one</a>.'}),Domma.icons.scan(),document.querySelectorAll("#pages-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),Domma.effects.reveal(".card",{animation:"fade",duration:350})};l(n);const p=s=>{const t=e.find("#pages-tree").empty().get(0);if(!s.length){t.textContent="No pages found.";return}const a=s.map(i=>{const r=i.urlPath.split("/").filter(Boolean),o=r.length>1?"/"+r.slice(0,-1).join("/"):null,g=o&&s.some(b=>b.urlPath===o);return{id:i.urlPath,parent_id:g?o:null,name:i.title||i.urlPath,icon:i.status==="published"?"check-circle":"file-text"}});E.treeView(t,{data:a,idKey:"id",parentKey:"parent_id",labelKey:"name",iconKey:"icon",expandedByDefault:!0,onSelect:i=>{R.navigate(`/pages/edit${i}`)}}),Domma.icons.scan(t)};e.find("#view-table-btn").on("click",function(){e.find("#pages-table").show(),e.find("#pages-tree").hide(),$(this).addClass("btn-primary").removeClass("btn-ghost"),e.find("#view-tree-btn").addClass("btn-ghost").removeClass("btn-primary")}),e.find("#view-tree-btn").on("click",function(){e.find("#pages-table").hide(),e.find("#pages-tree").show(),$(this).addClass("btn-primary").removeClass("btn-ghost"),e.find("#view-table-btn").addClass("btn-ghost").removeClass("btn-primary"),p(n)}),e.find("#view-table-btn, #view-tree-btn").each(function(){E.tooltip(this,{content:this.getAttribute("data-tooltip"),position:"top"})}),e.find("#status-filter").off("change").on("change",function(){const s=$(this).val(),t=s?n.filter(a=>a.status===s):n;l(t)}),e.off("click",".btn-delete").on("click",".btn-delete",async function(){const s=$(this).data("path");if(await E.confirm(`Delete page at <strong>${s}</strong>? This cannot be undone.`))try{await d.pages.delete(s),E.toast("Page deleted.",{type:"success"}),n=n.filter(a=>a.urlPath!==s),l(n)}catch{E.toast("Failed to delete page.",{type:"error"})}})}};
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="card plugin-card" data-plugin="${
|
|
1
|
+
import{api as o}from"../api.js";export const pluginsView={templateUrl:"/admin/js/templates/plugins.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"});let t=[];try{t=await o.plugins.list()}catch(e){E.toast(`Failed to load plugins: ${e.message}`,{type:"error"})}if(l.destroy(),!t.length){a.find("#plugins-empty").show();return}p(a,t),Domma.icons.scan()}};function p(a,l){const t=a.find("#plugins-grid").empty();l.forEach(e=>{const i=e.enabled?"badge-success":"badge-secondary",s=e.enabled?"Enabled":"Disabled",n=e.enabled?"Disable":"Enable",g=e.enabled?"btn-ghost":"btn-primary",c=e.author?`by ${d(e.author)}`:"",r=e.date?d(e.date):"",b=`
|
|
2
|
+
<div class="card plugin-card" data-plugin="${e.name}">
|
|
3
3
|
<div class="card-body">
|
|
4
4
|
<div class="plugin-header">
|
|
5
|
-
<div class="plugin-icon"><span data-icon="${
|
|
5
|
+
<div class="plugin-icon"><span data-icon="${e.icon||"package"}"></span></div>
|
|
6
6
|
<div class="plugin-meta">
|
|
7
|
-
<div class="plugin-name">${d(
|
|
8
|
-
<div class="plugin-version">v${d(
|
|
7
|
+
<div class="plugin-name">${d(e.displayName)}</div>
|
|
8
|
+
<div class="plugin-version">v${d(e.version)}${c?` · ${c}`:""}</div>
|
|
9
9
|
</div>
|
|
10
10
|
<span class="badge ${i}">${s}</span>
|
|
11
11
|
</div>
|
|
12
|
-
<p class="plugin-desc">${d(
|
|
12
|
+
<p class="plugin-desc">${d(e.description)}</p>
|
|
13
13
|
${r?`<p class="plugin-date text-muted" style="font-size:.8rem;margin:0">Released ${r}</p>`:""}
|
|
14
14
|
</div>
|
|
15
15
|
<div class="plugin-footer">
|
|
16
16
|
<span class="text-muted" style="font-size:.8rem">Restart server after changes</span>
|
|
17
|
-
<button class="btn btn-sm ${
|
|
18
|
-
data-name="${
|
|
19
|
-
data-enabled="${
|
|
17
|
+
<button class="btn btn-sm ${g} btn-toggle-plugin"
|
|
18
|
+
data-name="${e.name}"
|
|
19
|
+
data-enabled="${e.enabled?"1":"0"}">
|
|
20
20
|
${n}
|
|
21
21
|
</button>
|
|
22
22
|
</div>
|
|
23
|
-
</div>`;
|
|
23
|
+
</div>`;t.append(b)}),Domma.icons.scan(),Domma.effects.reveal(".plugin-card",{animation:"fade",stagger:60,duration:400}),Domma.effects.ripple(".btn-toggle-plugin"),t.on("click",".btn-toggle-plugin",async function(){const e=$(this),i=e.data("name"),s=e.data("enabled")!==1&&e.data("enabled")!=="1";e.prop("disabled",!0).text(s?"Enabling\u2026":"Disabling\u2026");try{await o.plugins.update(i,{enabled:s}),E.toast(`Plugin ${s?"enabled":"disabled"}. Restart the server to apply changes.`,{type:"success"});const n=await o.plugins.list();p(a,n)}catch(n){E.toast(`Failed: ${n.message}`,{type:"error"}),e.prop("disabled",!1)}})}function d(a){return String(a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const proDocsView={templateUrl:"/admin/js/templates/pro-docs.html",async onMount(o){E.tabs(o.find("#pro-docs-tabs").get(0)),Domma.icons.scan(),Domma.syntax.scan()}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as O,getUser as K}from"../api.js";const Q=[{value:"badge-danger",label:"Red (Admin)"},{value:"badge-warning",label:"Orange (Manager)"},{value:"badge-info",label:"Blue (Editor)"},{value:"badge-secondary",label:"Grey (Subscriber)"},{value:"badge-success",label:"Green"},{value:"badge-primary",label:"Primary"}];export const roleEditorView={templateUrl:"/admin/js/templates/role-editor.html",async onMount(i){if(!K()){R.navigate("/");return}const N=window.location.hash.match(/\/roles\/edit\/([^/?#]+)/)?.[1];if(!N){R.navigate("/roles");return}E.tabs(i.find("#role-tabs").get(0));const U=E.loader(i.get(0),{type:"dots"});let j,y;try{[j,y]=await Promise.all([O.get(`/collections/roles/entries/${N}`),O.get("/auth/permissions-registry")])}catch(e){U.destroy(),E.toast(`Failed to load role: ${e.message}`,{type:"error"}),R.navigate("/roles");return}U.destroy();const c=j.data||{},L=c.level===0,I=Object.fromEntries(y.resources.map(e=>[e.key,e])),x={};y.groups.forEach(e=>{x[e]=[]}),y.resources.forEach(e=>{x[e.group]&&x[e.group].push(e.key)}),i.find("#role-title").text(`Edit Role: ${c.label||c.name}`);const S=i.find("#role-name"),M=i.find("#role-label"),A=i.find("#role-level"),B=i.find("#role-badge");Q.forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.label,c.badgeClass===e.value&&(t.selected=!0),B.get(0).appendChild(t)}),S.val(c.name||""),M.val(c.label||""),A.val(c.level??""),L&&(S.prop("disabled",!0),A.prop("disabled",!0));const _=i.find("#permissions-container").get(0),F=c.permissions||[],C={},q={},H={},V=(e,t)=>F.includes(e)||F.includes(`${e}.${t}`);y.groups.forEach(e=>{const t=x[e];if(!t.length)return;const l=document.createElement("div");l.className="card mb-3";const r=document.createElement("div");r.className="card-header",r.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:default;";const g=document.createElement("strong");g.textContent=e;const d=document.createElement("label");d.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.85rem;font-weight:normal;cursor:pointer;";const o=document.createElement("input");o.type="checkbox",o.style.cursor="pointer",d.appendChild(o),d.appendChild(document.createTextNode("Select All")),H[e]=o,r.appendChild(g),r.appendChild(d),l.appendChild(r);const p=document.createElement("div");p.className="card-body",p.style.cssText="padding:.75rem 1rem;",t.forEach((a,v)=>{const n=I[a],m=n.actions.map(s=>s.key),b=v===t.length-1,T=document.createElement("div");T.style.cssText=`margin-bottom:${b?"0":".75rem"};padding-bottom:${b?"0":".75rem"};${b?"":"border-bottom:1px solid var(--dm-border);"}`;const k=document.createElement("div");k.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const W=document.createElement("span");W.dataset.icon=n.icon;const G=document.createElement("strong");G.textContent=n.label,G.style.fontSize=".9rem";const w=document.createElement("label");w.style.cssText="margin-left:auto;font-size:.8rem;display:flex;align-items:center;gap:.35rem;cursor:pointer;";const u=document.createElement("input");u.type="checkbox",u.style.cursor="pointer";const J=m.every(s=>V(a,s));u.checked=J,q[a]=u,w.appendChild(u),w.appendChild(document.createTextNode("Full Access")),k.appendChild(W),k.appendChild(G),k.appendChild(w),T.appendChild(k);const P=document.createElement("div");P.style.cssText="display:flex;gap:1.5rem;padding-left:1.5rem;flex-wrap:wrap;";const $={};n.actions.forEach(s=>{const f=document.createElement("label");f.style.cssText="display:flex;align-items:center;gap:.3rem;font-size:.875rem;cursor:pointer;";const h=document.createElement("input");h.type="checkbox",h.style.cursor="pointer",h.checked=V(a,s.key),h.dataset.res=a,h.dataset.action=s.key,C[`${a}.${s.key}`]=h,$[s.key]=h,f.appendChild(h),f.appendChild(document.createTextNode(s.label)),P.appendChild(f)}),u.addEventListener("change",()=>{m.forEach(s=>{$[s].checked=u.checked}),z(e,t)}),m.forEach(s=>{$[s].addEventListener("change",()=>{u.checked=m.every(f=>$[f].checked),z(e,t)})}),T.appendChild(P),p.appendChild(T)}),o.addEventListener("change",()=>{t.forEach(a=>{I[a].actions.map(m=>m.key).forEach(m=>{const b=C[`${a}.${m}`];b&&(b.checked=o.checked)});const n=q[a];n&&(n.checked=o.checked)})}),z(e,t),l.appendChild(p),_.appendChild(l)}),Domma.icons.scan("#permissions-container"),i.find("#btn-save-role").on("click",async()=>{const e=S.val().trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),t=M.val().trim(),l=parseInt(A.val(),10),r=B.val();if(!t){E.toast("Display label is required.",{type:"error"});return}const g=[];y.resources.forEach(o=>{const p=o.key,a=o.actions.map(n=>n.key),v=a.filter(n=>C[`${p}.${n}`]?.checked);v.length===a.length?g.push(p):v.forEach(n=>g.push(`${p}.${n}`))});const d={data:{name:L?c.name:e||c.name,label:t,level:L||isNaN(l)?c.level:l,permissions:g,badgeClass:r}};try{await O.put(`/collections/roles/entries/${N}`,d),E.toast("Role saved.",{type:"success"}),R.navigate("/roles")}catch(o){E.toast(`Save failed: ${o.message}`,{type:"error"})}});function z(e,t){const l=H[e];l&&(l.checked=t.every(r=>I[r].actions.map(d=>d.key).every(d=>C[`${r}.${d}`]?.checked)))}}};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{api as y,getUser as h}from"../api.js";const C=[{value:"badge-danger",label:"Red (Admin)"},{value:"badge-warning",label:"Orange (Manager)"},{value:"badge-info",label:"Blue (Editor)"},{value:"badge-secondary",label:"Grey (Subscriber)"},{value:"badge-success",label:"Green"},{value:"badge-primary",label:"Primary"}];function N(t){if(!t||!t.length)return"<em>None</em>";const f=new Set(t.map(o=>o.split(".")[0])),b=t.every(o=>!o.includes(".")),i=f.size;return`${i} resource${i!==1?"s":""} ${b?"(full)":"(partial)"}`}function $(t){return String(t).replace(/"/g,""")}export const rolesView={templateUrl:"/admin/js/templates/roles.html",async onMount(t){if(!h()){R.navigate("/");return}const b=E.loader(t.get(0),{type:"dots"});let i=[];const o=async()=>{i=(await y.get("/collections/roles/entries?limit=100")).entries||[]},v=()=>{T.create("#roles-table",{data:i,columns:[{key:"data",title:"Label",render:e=>`<span class="badge ${e.badgeClass||"badge-secondary"}">${e.label||""}</span>`},{key:"data",title:"Name",render:e=>`<code>${e.name||""}</code>`},{key:"data",title:"Level",render:e=>e.level??""},{key:"data",title:"Permissions",render:e=>N(e.permissions)},{key:"id",title:"Actions",render:(e,a)=>{const n=a.data?.level===0;return`
|
|
2
|
+
<a href="#/roles/edit/${e}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
|
+
${n?"":`<button class="btn btn-sm btn-danger btn-delete-role" data-id="${e}" data-name="${$(a.data?.label)}">Delete</button>`}
|
|
4
|
+
`}}],emptyMessage:"No roles found."}),Domma.icons.scan()};try{await o()}finally{b.destroy()}v(),document.addEventListener("click",async function e(a){if(!document.contains(t.get(0))){document.removeEventListener("click",e);return}const n=a.target.closest(".btn-delete-role");if(!n)return;const m=n.dataset.id,u=n.dataset.name;if(await E.confirm(`Delete role <strong>${u}</strong>? This cannot be undone.`))try{await y.delete(`/collections/roles/entries/${m}`),E.toast("Role deleted.",{type:"success"}),await o(),v()}catch(p){E.toast(`Failed: ${p.message}`,{type:"error"})}}),t.find("#btn-add-role").on("click",()=>{const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const a=(l,c)=>{const s=document.createElement("div"),r=document.createElement("label");return r.textContent=l,r.style.cssText="display:block;font-size:.85rem;margin-bottom:.25rem;font-weight:600;",s.appendChild(r),s.appendChild(c),s},n=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. moderator"}),m=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. Moderator"}),u=Object.assign(document.createElement("input"),{type:"number",className:"form-input",value:"5",min:"1"}),d=document.createElement("select");d.className="form-input",C.forEach(l=>{const c=Object.assign(document.createElement("option"),{value:l.value,textContent:l.label});d.appendChild(c)});const p=Object.assign(document.createElement("button"),{className:"btn btn-primary",textContent:"Create Role"});e.appendChild(a("Name (slug)",n)),e.appendChild(a("Display Label",m)),e.appendChild(a("Level",u)),e.appendChild(a("Badge Colour",d)),e.appendChild(p);const g=E.modal({title:"Add Role",size:"sm"});g.element.appendChild(e),g.open(),p.addEventListener("click",async()=>{const l=n.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),c=m.value.trim(),s=parseInt(u.value,10);if(!l||!c||isNaN(s)||s<1){E.toast("Name, label and level (\u2265 1) are required.",{type:"error"});return}try{const r=await y.post("/collections/roles/entries",{data:{name:l,label:c,level:s,permissions:[],badgeClass:d.value}});g.close(),E.toast("Role created. Set permissions in the editor.",{type:"success"}),R.navigate(`/roles/edit/${r.id}`)}catch(r){E.toast(`Error: ${r.message}`,{type:"error"})}})})}};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as v,apiRequest as u}from"../api.js";export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const x=E.loader(e.get(0),{type:"dots"}),t=await v.settings.get().catch(()=>({}));if(x.destroy(),e.find("#field-site-title").val(t.title||""),e.find("#field-tagline").val(t.tagline||""),e.find("#field-font-family").val(t.fontFamily||"Roboto"),e.find("#field-font-size").val(t.fontSize||16),e.find("#field-theme").val(t.baseTheme||t.theme||"charcoal-dark"),t.baseTheme){e.find("#field-theme").prop("disabled",!0);const l=document.createElement("p");l.className="form-hint",l.style.cssText="margin-top:.4rem;font-size:.8rem;color:var(--dm-info);";const s=document.createElement("a");s.href="#/plugins/theme-roller",s.textContent="Theme Roller",l.appendChild(document.createTextNode(`Custom theme \u201C${t.theme}\u201D is active (based on ${t.baseTheme}). Manage via `)),l.appendChild(s),l.appendChild(document.createTextNode(".")),e.find("#field-theme").get(0).closest(".col-6").appendChild(l)}e.find("#field-admin-theme").val(t.adminTheme||"charcoal-dark");const m=t.autoTheme||{},y=!!m.enabled,n=e.find("#field-auto-theme-enabled"),g=e.find("#auto-theme-fields"),b=e.find("#field-theme"),k=b.get(0).innerHTML;e.find("#field-day-theme").html(k),e.find("#field-night-theme").html(k),t.baseTheme?(n.prop("disabled",!0),e.find("#auto-theme-roller-hint").show()):(n.prop("checked",y),y&&(g.show(),b.prop("disabled",!0)),n.on("change",function(){const l=this.checked;g.toggle(l),b.prop("disabled",l)})),e.find("#field-day-theme").val(m.dayTheme||"charcoal-light"),e.find("#field-night-theme").val(m.nightTheme||"charcoal-dark"),e.find("#field-day-start").val(m.dayStart||"07:00"),e.find("#field-night-start").val(m.nightStart||"19:00"),e.find("#field-spacer-size").val(t.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(t.seo?.defaultTitle||""),e.find("#field-seo-separator").val(t.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(t.seo?.defaultDescription||""),e.find("#field-footer-copy").val(t.footer?.copyright||""),e.find("#field-social-twitter").val(t.social?.twitter||""),e.find("#field-social-facebook").val(t.social?.facebook||""),e.find("#field-social-instagram").val(t.social?.instagram||""),e.find("#field-social-linkedin").val(t.social?.linkedin||""),e.find("#field-social-github").val(t.social?.github||""),e.find("#field-social-youtube").val(t.social?.youtube||""),e.find("#field-smtp-host").val(t.smtp?.host||""),e.find("#field-smtp-port").val(t.smtp?.port||587),e.find("#field-smtp-user").val(t.smtp?.user||""),e.find("#field-smtp-pass").val(t.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",t.smtp?.secure||!1),e.find("#field-smtp-from-address").val(t.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(t.smtp?.fromName||"");const a=t.backToTop||{};e.find("#field-btt-enabled").prop("checked",a.enabled!==!1),e.find("#field-btt-threshold").val(a.scrollThreshold??300),e.find("#field-btt-position").val(a.position||"bottom-right"),e.find("#field-btt-offset").val(a.offset??32),e.find("#field-btt-bottom-offset").val(a.bottomOffset??a.offset??32),e.find("#field-btt-label").val(a.label||""),e.find("#field-btt-smooth").prop("checked",a.smooth!==!1);const i=t.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",i.enabled!==!1),e.find("#field-cc-message").val(i.message||""),e.find("#field-cc-accept-all").val(i.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(i.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(i.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(i.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(i.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(i.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(i.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(i.cookiePolicyUrl||""),e.find("#field-cc-position").val(i.position||"bottom"),e.find("#field-cc-layout").val(i.layout||"bar"),e.find("#field-cc-theme").val(i.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",i.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",i.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",i.showMarketing!==!1),e.find("#field-cc-version").val(i.consentVersion||"1.0");const c=t.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",c.enabled===!0),e.find("#field-breadcrumbs-home-label").val(c.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(c.position||"TL"),e.find("#field-bc-offset-x").val(c.offsetX??8),e.find("#field-bc-offset-y").val(c.offsetY??8);function T(l){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===l;this.style.background=s?"var(--primary, #5b8cff)":"",this.style.color=s?"#fff":""})}T(c.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const l=this.dataset.pos;e.find("#field-breadcrumbs-position").val(l),T(l)});let o=null;try{const{css:l}=await u("/settings/custom-css");e.find("#field-custom-css").val(l||""),E.editor&&(o=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}o&&o._editorEl&&o._editorEl.addEventListener("keydown",l=>{if(!l.ctrlKey&&!l.metaKey)return;if(l.key==="s"){l.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=o._editorEl,f=s.selectionStart!==s.selectionEnd;if((l.key==="c"||l.key==="x")&&!f){l.preventDefault();const d=s.value,p=s.selectionStart,r=d.lastIndexOf(`
|
|
2
|
+
`,p-1)+1,h=d.indexOf(`
|
|
3
|
+
`,p),w=h===-1?d.slice(r):d.slice(r,h+1);if(navigator.clipboard.writeText(w),l.key==="x"){const S=d.slice(0,r),C=h===-1?"":d.slice(h+1);s.value=S+C,s.selectionStart=s.selectionEnd=r,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const l=e.find("#field-admin-theme").val(),s=!t.baseTheme&&e.find("#field-auto-theme-enabled").prop("checked"),f={enabled:s,dayTheme:e.find("#field-day-theme").val()||"charcoal-light",nightTheme:e.find("#field-night-theme").val()||"charcoal-dark",dayStart:e.find("#field-day-start").val()||"07:00",nightStart:e.find("#field-night-start").val()||"19:00"},d={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),fontFamily:e.find("#field-font-family").val()||"Roboto",fontSize:parseInt(e.find("#field-font-size").val(),10)||16,theme:t.baseTheme?t.theme:s?f.dayTheme:e.find("#field-theme").val(),...t.baseTheme?{baseTheme:t.baseTheme}:{},autoTheme:f,adminTheme:l,layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),links:t.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},p=e.find("#save-settings-btn");p.prop("disabled",!0);try{await v.settings.save(d),Domma.theme.set(l),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{p.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const l=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),f=e.find("#send-test-email-btn");f.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const d=await u("/settings/test-email",{method:"POST",body:JSON.stringify({to:l||void 0})});s&&(s.textContent=d.message||"Test email sent.",s.style.color="var(--success,#4ade80)")}catch(d){s&&(s.textContent=d.message||"Failed to send test email.",s.style.color="var(--danger,#f87171)")}finally{f.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const l=e.find("#save-breadcrumbs-btn");l.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await v.settings.save({...t,breadcrumbs:s}),t.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{l.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const l=o?o.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await u("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:l})}),E.toast("Custom CSS saved.",{type:"success"})}catch(f){E.toast(f.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as n}from"../api.js";function k(c){return{string:"string",email:"email",tel:"string",number:"number",textarea:"textarea",select:"select",radio:"select",checkbox:"boolean","checkbox-group":"select",date:"string",time:"string",url:"string",hidden:"string"}[c]||"string"}export const userEditorView={templateUrl:"/admin/js/templates/user-editor.html",async onMount(c){const d=window.location.hash.match(/#\/users\/edit\/([^/]+)/),u=d?d[1]:null,r=!!u,[o,m,f]=await Promise.all([r?n.users.get(u).catch(()=>null):Promise.resolve(null),n.collections.listEntries("roles",{limit:100}).catch(()=>null),n.collections.get("user-profiles").catch(()=>null)]);if(r&&!o){E.toast("User not found.",{type:"error"}),R.navigate("/users");return}c.find("#editor-title").text(r?"Edit User":"New User"),c.find("#cancel-btn").on("click",()=>R.navigate("/users"));const h=(m?.entries||[]).map(t=>({value:t.data.name,label:t.data.label})).sort((t,a)=>{const e=l=>m.entries.find(s=>s.data.name===l)?.data?.level??99;return e(t.value)-e(a.value)}),y=[{value:"admin",label:"Admin"},{value:"manager",label:"Manager"},{value:"editor",label:"Editor"},{value:"subscriber",label:"Subscriber"}],v=h.length>0?h:y,w={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Jane Smith"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"jane@example.com"}},role:{type:"select",required:!0,label:"Role",options:v},isActive:{type:"boolean",label:"Active account"},password:{type:"password",required:!r,minLength:r?0:8,label:r?"New Password":"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:r?"Leave blank to keep current password":"Minimum 8 characters"}}},U=o?{name:o.name||"",email:o.email||"",role:o.role||"editor",isActive:o.isActive!==!1}:{role:"editor",isActive:!0};F.render("#user-form-container",w,U,{layout:"stacked",submitText:r?"Update User":"Create User",onSubmit:async t=>{if(r&&t.password&&t.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const a={name:t.name,email:t.email,role:t.role,isActive:!!t.isActive};t.password&&(a.password=t.password);try{let e;r?e=await n.users.update(u,a):e=await n.users.create(a);const l=f?.fields||[];if(l.length>0){const s={};for(const p of l)p.name in t&&(s[p.name]=t[p.name]);const i=r?u:e?.id;i&&Object.keys(s).length>0&&await n.users.update(i,{profile:s})}E.toast("User saved successfully.",{type:"success"}),R.navigate("/users")}catch(e){return E.toast(`Save failed: ${e.message||"Unknown error"}`,{type:"error"}),!1}}});const b=f?.fields||[];if(b.length>0){c.find("#profile-card").show();const t={},a={};for(const e of b){const s={type:k(e.type),label:e.label||e.name};e.required&&(s.required=!0),e.placeholder&&(s.formConfig={placeholder:e.placeholder}),e.options&&Array.isArray(e.options)&&(s.options=e.options.map(i=>typeof i=="string"?{value:i,label:i}:i)),t[e.name]=s,a[e.name]=o?.profile?.[e.name]??""}F.render("#profile-form-container",t,a,{layout:"stacked",submitText:r?"Update Profile":"Save Profile",onSubmit:async e=>{const l=u;if(!l)return E.toast("Save the user first, then update the profile.",{type:"info"}),!1;try{await n.users.update(l,{profile:e}),E.toast("Profile saved.",{type:"success"})}catch(s){return E.toast(`Profile save failed: ${s.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
|
package/admin/js/views/users.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-
|
|
3
|
-
${s?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${
|
|
4
|
-
|
|
5
|
-
<button class="btn btn-sm btn-outline btn-edit-ut" data-id="${e}">Edit</button>
|
|
6
|
-
${s?"":`<button class="btn btn-sm btn-danger btn-delete-ut" data-id="${e}" data-name="${N(t.data?.label)}">Delete</button>`}
|
|
7
|
-
`}}],emptyMessage:"No user types found."}),Domma.icons.scan()},v=async()=>{f=(await b.get("/collections/user-types/entries?limit=100")).entries||[],w(f)};await v();const L=(a=null)=>{const e=a?.data||{},t=document.createElement("div");t.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const s=(c,n)=>{const o=document.createElement("div"),y=document.createElement("label");return y.textContent=c,y.style.cssText="display:block;font-size:.85rem;margin-bottom:.25rem;font-weight:600;",o.appendChild(y),o.appendChild(n),o},l=document.createElement("input");l.type="text",l.value=e.name||"",l.placeholder="e.g. moderator",l.className="form-control",e.level===0&&(l.disabled=!0);const r=document.createElement("input");r.type="text",r.value=e.label||"",r.placeholder="e.g. Moderator",r.className="form-control";const d=document.createElement("input");d.type="number",d.value=e.level??"",d.min=e.level===0?0:1,d.className="form-control",e.level===0&&(d.disabled=!0);const u=document.createElement("select");u.className="form-control",I.forEach(c=>{const n=document.createElement("option");n.value=c.value,n.textContent=c.label,e.badgeClass===c.value&&(n.selected=!0),u.appendChild(n)});const m=document.createElement("div");m.style.cssText="display:grid;grid-template-columns:1fr 1fr;gap:.25rem;",A.forEach(c=>{const n=document.createElement("label");n.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.875rem;";const o=document.createElement("input");o.type="checkbox",o.name="perm",o.value=c,(e.permissions||[]).includes(c)&&(o.checked=!0),n.appendChild(o),n.appendChild(document.createTextNode(c)),m.appendChild(n)});const p=document.createElement("button");return p.className="btn btn-primary",p.textContent=a?"Update User Type":"Create User Type",t.appendChild(s("Name (slug)",l)),t.appendChild(s("Label",r)),t.appendChild(s("Level",d)),t.appendChild(s("Badge Colour",u)),t.appendChild(s("Permissions",m)),t.appendChild(p),{form:t,nameInput:l,labelInput:r,levelInput:d,badgeSelect:u,permWrap:m,saveBtn:p}},C=(a=null)=>{const{form:e,nameInput:t,labelInput:s,levelInput:l,badgeSelect:r,permWrap:d,saveBtn:u}=L(a),m=E.modal({title:a?"Edit User Type":"Add User Type",size:"md"});m.element.appendChild(e),m.open(),u.addEventListener("click",async()=>{const p=t.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),c=s.value.trim(),n=parseInt(l.value,10),o=[...d.querySelectorAll('input[name="perm"]:checked')].map(h=>h.value),y=r.value;if(!p||!c||isNaN(n)){E.toast("Name, label and level are required.",{type:"error"});return}const U={name:p,label:c,level:n,permissions:o,badgeClass:y};try{a?(await b.put(`/collections/user-types/entries/${a.id}`,{data:U}),E.toast("User type updated.",{type:"success"})):(await b.post("/collections/user-types/entries",{data:U}),E.toast("User type created.",{type:"success"})),m.close(),await v()}catch(h){E.toast(`Error: ${h.message}`,{type:"error"})}})};i.find("#btn-add-user-type").on("click",()=>C()),document.addEventListener("click",async function a(e){if(!document.contains(i.get(0))){document.removeEventListener("click",a);return}const t=e.target.closest(".btn-edit-ut"),s=e.target.closest(".btn-delete-ut");if(t){const l=t.dataset.id,r=f.find(d=>d.id===l);r&&C(r)}if(s){const l=s.dataset.id,r=s.dataset.name;if(!await E.confirm(`Delete user type <strong>${r}</strong>?`))return;try{await b.delete(`/collections/user-types/entries/${l}`),E.toast("User type deleted.",{type:"success"}),await v()}catch(u){E.toast(`Failed: ${u.message}`,{type:"error"})}}})}};function N(i){return String(i).replace(/"/g,""")}
|
|
1
|
+
import{api as c,getUser as p}from"../api.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(a){const i=p(),b=E.loader(a.get(0),{type:"dots"});let[n,g]=await Promise.all([c.users.list().catch(()=>[]),c.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);b.destroy();const o=g.entries||[],d=r=>{T.create("#users-table",{data:r,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:e=>{const t=o.find(m=>m.data?.name===e),s=t?.data?.badgeClass||"badge-secondary",l=t?.data?.label||e;return`<span class="badge ${s}">${l}</span>`}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const s=t.id===i?.id,l=Object.fromEntries(o.map(u=>[u.data?.name,u.data?.level??99]));return(l[i?.role]??99)<(l[t.role]??99)?`
|
|
2
|
+
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
|
+
${s?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${y(t.name)}">Delete</button>`}
|
|
4
|
+
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan()};d(n),a.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const r=$(this).data("id"),e=$(this).data("name");if(await E.confirm(`Delete user <strong>${e}</strong>? This cannot be undone.`))try{await c.delete(`/users/${r}`),E.toast("User deleted.",{type:"success"}),n=n.filter(s=>s.id!==r),d(n)}catch(s){E.toast(`Failed to delete: ${s.message}`,{type:"error"})}})}};function y(a){return String(a).replace(/"/g,""")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as h}from"../api.js";let b=null,f=null;export const viewEditorView={templateUrl:"/admin/js/templates/view-editor.html",async onMount(e){b=null,f=null;const l=window.location.hash.match(/\/views\/edit\/([^/?#]+)/);l&&(b=l[1]),E.tabs(e.find("#view-editor-tabs").get(0)),await O(e),await D(e),await j(e),await T(e),e.find("#view-source").get(0)?.addEventListener("change",async()=>{await x(e)}),b&&(e.find("#view-editor-title").text("Edit View"),await I(e,b)),e.find("#add-stage-btn").off("click").on("click",()=>{const n=e.find("#add-stage-type").val()||"$match";q(e,{type:n,config:{}})}),e.find("#save-view-btn").off("click").on("click",async()=>{await Q(e)}),H(e),Y(e),Domma.icons.scan()}};async function O(e){const t=e.find("#view-source").get(0);if(t)try{(await h.collections.list()).forEach(n=>{const a=document.createElement("option");a.value=n.slug,a.textContent=`${n.title} (${n.slug})`,t.appendChild(a)})}catch{}}async function T(e){const t=e.find("#view-block-name").get(0);if(t)try{(await h.blocks.list()).forEach(n=>{const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name}.html`,t.appendChild(a)})}catch{}}async function D(e){const t=e.find("#view-connection").get(0);if(t)try{const l=await h.collections.getConnections();Object.keys(l).forEach(n=>{if(!t.querySelector(`option[value="${n}"]`)){const a=document.createElement("option");a.value=n,a.textContent=n,t.appendChild(a)}})}catch{}}async function j(e){const t=e.find("#view-roles-checkboxes").get(0);if(!t)return;["admin","manager","editor","subscriber"].forEach(n=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const s=document.createElement("input");s.type="checkbox",s.value=n,s.dataset.role=n,s.className="view-role-cb",s.checked=n==="admin",a.appendChild(s),a.appendChild(document.createTextNode(n)),t.appendChild(a)})}async function x(e){const t=e.find("#view-source").val();if(!t){f=null;return}try{f=(await h.collections.get(t))?.fields||[]}catch{f=null}e.find(".stage-card[data-guided]").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");n&&(l==="$match"&&M(n),l==="$sort"&&z(n))}),A(e,null)}async function I(e,t){try{const l=await h.views.get(t);if(!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}const n=l.pipeline?.source;n&&(e.find("#view-source").val(n),await x(e)),F(e,l)}catch(l){E.toast(l.message||"Failed to load view.",{type:"error"}),R.navigate("/views")}}function F(e,t){e.find("#view-title").val(t.title||""),e.find("#view-slug").val(t.slug||""),e.find("#view-description").val(t.description||""),e.find("#view-source").val(t.pipeline?.source||""),e.find("#view-connection").val(t.connection||"default"),e.find("#view-display-mode").val(t.display?.mode||"table"),e.find("#view-page-size").val(t.display?.pageSize||25),e.find("#view-block-name").val(t.display?.block||"");const l=t.access?.roles||["admin"];e.find(".view-role-cb").each(function(){this.checked=l.includes(this.value)}),e.find("#view-public").prop("checked",t.access?.public||!1);const n=t.access?.rowLevel;n&&(e.find("#view-rowlevel-enabled").prop("checked",!0),e.find("#view-rowlevel-config").css("display","flex"),e.find("#view-rowlevel-mode").val(n.mode||"owner"),e.find("#view-rowlevel-userkey").val(n.userKey||"id"),n.mode==="field"&&(e.find("#view-rowlevel-field-group").css("display",""),e.find("#view-rowlevel-field").val(n.field||"")));const a=e.find("#pipeline-stages-list").get(0);if(a)for(;a.firstChild;)a.removeChild(a.firstChild);(t.pipeline?.stages||[]).forEach(s=>q(e,s)),A(e,t.display?.columns||[]),S(e)}const _=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains (regex)"},{value:"in",label:"\u2208 in (comma list)"}];function g(e=!1){const t=[];return e&&t.push({value:"",label:"\u2014 select field \u2014"}),(f||[]).forEach(l=>{t.push({value:`data.${l.name}`,label:`${l.label||l.name} (${l.name})`})}),t.length===0||t.length===1&&e?(t.push({value:"__meta.createdAt",label:"Created At"}),t.push({value:"__meta.updatedAt",label:"Updated At"})):(t.push({value:"__meta.createdAt",label:"Created At (meta)"}),t.push({value:"__meta.updatedAt",label:"Updated At (meta)"})),t}function v(e,t){const l=document.createElement("select");return l.className="form-input form-input--sm",e.forEach(n=>{const a=document.createElement("option");a.value=n.value,a.textContent=n.label,String(n.value)===String(t)&&(a.selected=!0),l.appendChild(a)}),l}function N(e,t,l="text"){const n=document.createElement("input");return n.type=l,n.className="form-input form-input--sm",n.placeholder=e,n.value=t??"",n}function B(e){if(!f?.length)return null;const t=e?.startsWith("data.")?e.slice(5):null;return t&&f.find(l=>l.name===t)||null}function y(e,t,l=""){const n=B(e);if(n?.type==="select"&&(t==="eq"||t==="ne")&&n.options?.length){const i=document.createElement("select");i.className="form-input form-input--sm match-val",i.style.flex="1";const o=document.createElement("option");return o.value="",o.textContent="\u2014 select value \u2014",i.appendChild(o),(n.options||[]).forEach(c=>{const d=typeof c=="string"?c:c.value??"",u=typeof c=="string"?c:c.label||d;if(!d||d==="undefined")return;const m=document.createElement("option");m.value=d,m.textContent=u,d===l&&(m.selected=!0),i.appendChild(m)}),i}const s=t==="in"?"val1, val2, val3":t==="contains"?"search pattern (regex)":t==="gt"||t==="lt"||t==="gte"||t==="lte"?"0":"value",r=document.createElement("input");return r.type="text",r.className="form-input form-input--sm match-val",r.style.flex="1",r.placeholder=s,r.value=l,r}function k(e){const t=[],l={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"};return Object.entries(e||{}).forEach(([n,a])=>{if(typeof a=="object"&&a!==null&&!Array.isArray(a)){if("$regex"in a){t.push({field:n,op:"contains",val:String(a.$regex??"")});return}Object.entries(a).forEach(([s,r])=>{const i=l[s];i&&t.push({field:n,op:i,val:Array.isArray(r)?r.join(", "):String(r)})})}else t.push({field:n,op:"eq",val:String(a??"")})}),t}function C(e,t,l="",n="eq",a=""){const s=document.createElement("div");s.className="match-condition-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;flex-wrap:wrap;";const r=v([{value:"",label:"\u2014 field \u2014"},...g()],l);r.className+=" match-field",r.style.minWidth="140px";const i=v(_,n);i.className+=" match-op",i.style.minWidth="160px";const o=document.createElement("div");o.className="match-val-wrap",o.style.cssText="flex:1;min-width:120px;display:flex;",o.appendChild(y(l,n,a));const c=()=>{const m=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(r.value,i.value,m))};r.addEventListener("change",c),i.addEventListener("change",c);const d=document.createElement("button");d.type="button",d.className="btn btn-sm btn-ghost",d.title="Remove";const u=document.createElement("span");u.setAttribute("data-icon","x"),d.appendChild(u),d.addEventListener("click",()=>s.remove()),s.appendChild(r),s.appendChild(i),s.appendChild(o),s.appendChild(d),e.insertBefore(s,t),Domma.icons.scan(s)}function W(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$match||t||{},n="$or"in l;let a=[];n?(l.$or||[]).forEach(d=>k(d).forEach(u=>a.push(u))):a=k(l);const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem;";const r=document.createElement("label");r.className="form-label",r.style.marginBottom="0",r.textContent="Match:";const i=document.createElement("select");i.className="form-input form-input--sm match-logic",i.style.width="auto",[{value:"and",label:"ALL conditions (AND)"},{value:"or",label:"ANY condition (OR)"}].forEach(d=>{const u=document.createElement("option");u.value=d.value,u.textContent=d.label,(n?"or":"and")===d.value&&(u.selected=!0),i.appendChild(u)}),s.appendChild(r),s.appendChild(i),e.appendChild(s);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm match-add-btn";const c=document.createElement("span");c.setAttribute("data-icon","plus"),o.appendChild(c),o.appendChild(document.createTextNode(" Add Condition")),o.addEventListener("click",()=>{C(e,o),Domma.icons.scan(e)}),e.appendChild(o),a.forEach(d=>C(e,o,d.field,d.op,d.val)),a.length===0&&C(e,o),Domma.icons.scan(e)}function M(e){e.querySelectorAll(".match-condition-row").forEach(t=>{const l=t.querySelector(".match-field");if(l){const r=l.value,i=v([{value:"",label:"\u2014 field \u2014"},...g()],r);i.className=l.className,i.style.cssText=l.style.cssText;const o=t.querySelector(".match-val-wrap"),c=t.querySelector(".match-op");o&&c&&i.addEventListener("change",()=>{const d=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(i.value,c.value,d))}),l.parentNode.replaceChild(i,l)}const n=t.querySelector(".match-val-wrap"),a=t.querySelector(".match-field")?.value,s=t.querySelector(".match-op")?.value;if(n&&a&&s){const r=n.querySelector(".match-val")?.value??"";for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(y(a,s,r))}})}function U(e){const t=e.querySelector(".match-logic")?.value||"and",l=a=>{const s=a.querySelector(".match-field")?.value?.trim(),r=a.querySelector(".match-op")?.value,i=a.querySelector(".match-val")?.value?.trim()??"";if(!s)return null;const o=s.startsWith("__")?s.slice(2):s,c={};switch(r){case"eq":c[o]=i;break;case"ne":c[o]={$ne:i};break;case"gt":c[o]={$gt:isNaN(i)?i:Number(i)};break;case"lt":c[o]={$lt:isNaN(i)?i:Number(i)};break;case"gte":c[o]={$gte:isNaN(i)?i:Number(i)};break;case"lte":c[o]={$lte:isNaN(i)?i:Number(i)};break;case"contains":c[o]={$regex:i,$options:"i"};break;case"in":c[o]={$in:i.split(",").map(d=>d.trim()).filter(Boolean)};break}return Object.keys(c).length?c:null},n=[];return e.querySelectorAll(".match-condition-row").forEach(a=>{const s=l(a);s&&n.push(s)}),n.length===0?{}:t==="or"?{$or:n}:Object.assign({},...n)}function V(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$sort||t||{},n=Object.entries(l),a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm";const s=document.createElement("span");s.setAttribute("data-icon","plus"),a.appendChild(s),a.appendChild(document.createTextNode(" Add Sort")),e.appendChild(a);const r=(i="",o=1)=>{const c=document.createElement("div");c.className="sort-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=v([{value:"",label:"\u2014 field \u2014"},...g()],i);d.className+=" sort-field";const u=v([{value:"1",label:"\u2191 Ascending"},{value:"-1",label:"\u2193 Descending"}],String(o));u.className+=" sort-dir";const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove()}),c.appendChild(d),c.appendChild(u),c.appendChild(m),e.insertBefore(c,a),Domma.icons.scan(c)};a.addEventListener("click",()=>{r()}),n.forEach(([i,o])=>r(i,o)),n.length===0&&r(),Domma.icons.scan(e)}function z(e){e.querySelectorAll(".sort-row").forEach(t=>{const l=t.querySelector(".sort-field"),n=l?.value,a=v([{value:"",label:"\u2014 field \u2014"},...g()],n);a.className=l.className,l.parentNode.replaceChild(a,l)})}function J(e){const t={};return e.querySelectorAll(".sort-row").forEach(l=>{const n=l.querySelector(".sort-field")?.value?.trim(),a=parseInt(l.querySelector(".sort-dir")?.value,10)||1;if(!n)return;const s=n.startsWith("__")?n.slice(2):n;t[s]=a}),t}function q(e,t){const l=e.find("#pipeline-stages-list").get(0);if(!l)return;const n=l.querySelector(".stage-empty-placeholder");n&&n.remove();const a=["$match","$sort"].includes(t.type),s=document.createElement("div");s.className="card mb-2 stage-card",s.dataset.stageType=t.type,a&&(s.dataset.guided="true");const r=document.createElement("div");r.className="card-header",r.style.cssText="display:flex;align-items:center;gap:.5rem;";const i=document.createElement("code");i.textContent=t.type,i.style.cssText="flex:1;font-size:.85rem;";const o=document.createElement("button");o.type="button",o.className="btn btn-sm btn-danger";const c=document.createElement("span");c.setAttribute("data-icon","trash-2"),o.appendChild(c),o.addEventListener("click",()=>{s.remove(),l.querySelector(".stage-card")||l.appendChild(G())}),r.appendChild(i),r.appendChild(o);const d=document.createElement("div");if(d.className="card-body",a){const u=document.createElement("div");u.className="stage-guided",d.appendChild(u),t.type==="$match"&&W(u,t.config),t.type==="$sort"&&V(u,t.config)}else{const u=document.createElement("label");u.className="form-label",u.textContent="Stage Config (JSON)";const m=document.createElement("small");m.className="text-muted",m.style.cssText="display:block;margin-bottom:.4rem;",m.textContent=`Enter the inner config for ${t.type} \u2014 e.g. for $lookup: { from, localField, foreignField, as }. Do not wrap in { "${t.type}": ... }.`;const p=document.createElement("textarea");p.className="form-input stage-config",p.rows=5,p.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",p.placeholder="{}",p.value=Object.keys(t.config||{}).length?JSON.stringify(t.config,null,2):"",d.appendChild(u),d.appendChild(m),d.appendChild(p)}s.appendChild(r),s.appendChild(d),l.appendChild(s),Domma.icons.scan(s)}function G(){const e=document.createElement("p");return e.className="text-muted stage-empty-placeholder",e.textContent="No stages yet. Add a stage to filter, join, or transform your data.",e.style.cssText="text-align:center;padding:2rem 0;",e}function K(e){const t=[];return e.find(".stage-card").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");let a;if(n)l==="$match"&&(a=U(n)),l==="$sort"&&(a=J(n));else{const s=this.querySelector(".stage-config")?.value?.trim()||"{}";try{a=JSON.parse(s)}catch{throw new Error(`Invalid JSON in ${l} stage config`)}}t.push({type:l,config:a})}),t}function A(e,t){const l=e.find("#view-columns-builder").get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);const n=t??(f||[]).map(i=>({key:`data.${i.name}`,label:i.label||i.name})),a=(i="",o="")=>{const c=document.createElement("div");c.className="col-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=f?.length?v([{value:"",label:"\u2014 field \u2014"},...g()],i):N("data.fieldName",i);d.className+=" col-key";const u=N("Display label",o);u.className+=" col-label",u.style.flex="1",f?.length&&d.tagName==="SELECT"&&d.addEventListener("change",()=>{if(!u.value){const w=(f||[]).find(L=>`data.${L.name}`===d.value);w&&(u.value=w.label||w.name)}});const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove column";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove(),Domma.icons.scan(l)}),c.appendChild(d),c.appendChild(u),c.appendChild(m),l.insertBefore(c,l.lastChild),Domma.icons.scan(c)};n.forEach(i=>a(i.key,i.label));const s=document.createElement("button");s.type="button",s.className="btn btn-ghost btn-sm";const r=document.createElement("span");r.setAttribute("data-icon","plus"),s.appendChild(r),s.appendChild(document.createTextNode(" Add Column")),s.addEventListener("click",()=>{a(),Domma.icons.scan(l)}),l.appendChild(s),Domma.icons.scan(l)}function P(e){const t=[];return e.find(".col-row").each(function(){const l=this.querySelector(".col-key")?.value?.trim(),n=this.querySelector(".col-label")?.value?.trim();l&&t.push({key:l,label:n||l})}),t}function Y(e){const t=e.find("#view-display-mode").get(0);t&&(t.addEventListener("change",()=>S(e)),S(e))}function S(e){const t=e.find("#view-display-mode").val()||"table",l=e.find("#view-columns-section").get(0),n=e.find("#view-block-section").get(0);l&&(l.style.display=t==="table"?"":"none"),n&&(n.style.display=t==="block"?"":"none")}function H(e){const t=e.find("#view-rowlevel-enabled").get(0),l=e.find("#view-rowlevel-config").get(0),n=e.find("#view-rowlevel-mode").get(0),a=e.find("#view-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{l&&(l.style.display=t.checked?"flex":"none")}),n&&n.addEventListener("change",()=>{a&&(a.style.display=n.value==="field"?"":"none")}))}async function Q(e){const t=e.find("#view-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const l=e.find("#view-source").val();if(!l){E.toast("Source collection is required (Source tab).",{type:"warning"});return}let n;try{n=K(e)}catch(d){E.toast(d.message,{type:"error"});return}const a=P(e),s=[];e.find(".view-role-cb:checked").each(function(){s.push(this.value)});const r=e.find("#view-rowlevel-enabled").is(":checked");let i=null;if(r){const d=e.find("#view-rowlevel-mode").val()||"owner",u=e.find("#view-rowlevel-userkey").val()||"id";if(i={mode:d,userKey:u},d==="field"){const m=e.find("#view-rowlevel-field").val().trim();if(!m){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}i.field=m}}const o={title:t,slug:e.find("#view-slug").val().trim()||void 0,description:e.find("#view-description").val().trim(),connection:e.find("#view-connection").val()||"default",pipeline:{source:l,stages:n},display:{mode:e.find("#view-display-mode").val()||"table",columns:a,pageSize:parseInt(e.find("#view-page-size").val(),10)||25,block:e.find("#view-block-name").val()||""},access:{roles:s,public:e.find("#view-public").is(":checked"),rowLevel:i}},c=e.find("#save-view-btn").get(0);c&&(c.disabled=!0);try{if(b)await h.views.update(b,o),E.toast("View updated.",{type:"success"});else{const d=await h.views.create(o);E.toast("View created.",{type:"success"}),R.navigate(`/views/edit/${d.slug}`)}}catch(d){E.toast(d.message||"Failed to save view.",{type:"error"})}finally{c&&(c.disabled=!1)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as m}from"../api.js";let l=null,r=1,c=0;export const viewPreviewView={templateUrl:"/admin/js/templates/view-preview.html",async onMount(e){l=null,r=1,c=0;const i=window.location.hash.match(/\/views\/([^/?#]+)\/preview/),s=i?i[1]:null;if(!s){E.toast("No view selected.",{type:"error"}),R.navigate("/views");return}try{if(l=await m.views.get(s),!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}}catch(t){E.toast(t.message||"Failed to load view.",{type:"error"});return}e.find("#preview-title").text(l.title),e.find("#preview-edit-btn").attr("href",`#/views/edit/${l.slug}`),e.find("#preview-source").text(l.pipeline?.source||"\u2014"),e.find("#preview-connection").text(l.connection||"default"),e.find("#preview-mode").text(l.display?.mode||"table"),e.find("#preview-meta-card").show(),e.find("#preview-run-btn").off("click").on("click",async()=>{r=1,await v(e)}),e.find("#preview-prev").off("click").on("click",async()=>{r>1&&(r--,await v(e))}),e.find("#preview-next").off("click").on("click",async()=>{const t=l.display?.pageSize||25;r*t<c&&(r++,await v(e))}),Domma.icons.scan()}};async function v(e){const n=l.display?.pageSize||25,i=e.find("#preview-run-btn").get(0);i&&(i.disabled=!0,i.textContent="Running\u2026"),e.find("#preview-placeholder").hide(),e.find("#preview-table").hide(),e.find("#preview-list").hide(),e.find("#preview-empty").hide();try{const s=await m.views.execute(l.slug,{page:r,limit:n});c=s.total||0;const t=Math.max(1,Math.ceil(c/n));if(e.find("#preview-count").text(`${c} result${c!==1?"s":""}`),e.find("#preview-page-label").text(`Page ${r} of ${t}`),e.find("#preview-prev").prop("disabled",r<=1),e.find("#preview-next").prop("disabled",r>=t),e.find("#preview-pagination").show(),!s.results||s.results.length===0){e.find("#preview-empty").show();return}(l.display?.mode||"table")==="list"?g(s.results,e):y(s.results,e)}catch(s){E.toast(s.message||"Execution failed.",{type:"error"})}finally{i&&(i.disabled=!1,i.textContent="Run")}}function y(e,n){const i=l.display?.columns||[],s=i.length?i:Object.keys(u(e[0])).slice(0,6).map(t=>({key:t,label:t}));T.create("#preview-table",{data:e,columns:s.map(t=>({key:t.key,title:t.label||t.key,render:(d,f)=>{const w=t.key.split(".");let a=f;for(const h of w)a=a?.[h];const o=a!=null?String(a):"",p=document.createElement("span");return p.title=o,p.textContent=o.length>80?o.slice(0,80)+"\u2026":o,p.outerHTML}})),emptyMessage:"No results."}),n.find("#preview-table").show(),Domma.icons.scan()}function g(e,n){const i=n.find("#preview-list").get(0);if(i){for(;i.firstChild;)i.removeChild(i.firstChild);e.forEach(s=>{const t=document.createElement("div");t.style.cssText="padding:.75rem;border-bottom:1px solid var(--dm-border,#333);";const d=u(s);Object.entries(d).slice(0,6).forEach(([f,w])=>{const a=document.createElement("div");a.style.cssText="display:flex;gap:.5rem;font-size:.875rem;";const o=document.createElement("strong");o.textContent=f+": ",o.style.cssText="color:var(--dm-text-muted,#888);min-width:120px;flex-shrink:0;";const p=document.createElement("span");p.textContent=String(w??""),a.appendChild(o),a.appendChild(p),t.appendChild(a)}),i.appendChild(t)}),n.find("#preview-list").show()}}function u(e,n="",i={}){for(const[s,t]of Object.entries(e||{})){const d=n?`${n}.${s}`:s;t&&typeof t=="object"&&!Array.isArray(t)&&Object.keys(t).length<8?u(t,d,i):i[d]=t}return i}
|