domma-cms 0.2.1 → 0.3.0

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.
Files changed (70) hide show
  1. package/admin/css/admin.css +1 -1200
  2. package/admin/js/api.js +1 -242
  3. package/admin/js/app.js +5 -279
  4. package/admin/js/config/sidebar-config.js +1 -115
  5. package/admin/js/lib/card.js +1 -63
  6. package/admin/js/lib/image-editor.js +1 -869
  7. package/admin/js/lib/markdown-toolbar.js +46 -421
  8. package/admin/js/templates/layouts.html +44 -7
  9. package/admin/js/templates/page-editor.html +9 -0
  10. package/admin/js/templates/settings.html +18 -1
  11. package/admin/js/templates/users.html +29 -4
  12. package/admin/js/views/collection-editor.js +3 -487
  13. package/admin/js/views/collection-entries.js +1 -484
  14. package/admin/js/views/collections.js +1 -153
  15. package/admin/js/views/dashboard.js +1 -56
  16. package/admin/js/views/documentation.js +1 -12
  17. package/admin/js/views/index.js +1 -39
  18. package/admin/js/views/layouts.js +9 -42
  19. package/admin/js/views/login.js +7 -251
  20. package/admin/js/views/media.js +1 -240
  21. package/admin/js/views/navigation.js +14 -212
  22. package/admin/js/views/page-editor.js +53 -661
  23. package/admin/js/views/pages.js +5 -72
  24. package/admin/js/views/plugins.js +13 -90
  25. package/admin/js/views/settings.js +1 -199
  26. package/admin/js/views/tutorials.js +1 -12
  27. package/admin/js/views/user-editor.js +1 -88
  28. package/admin/js/views/users.js +7 -76
  29. package/config/auth.json +1 -17
  30. package/config/navigation.json +15 -0
  31. package/config/site.json +5 -4
  32. package/package.json +1 -1
  33. package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
  34. package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
  35. package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
  36. package/plugins/domma-effects/public/celebrations/index.js +1 -535
  37. package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
  38. package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
  39. package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
  40. package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
  41. package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
  42. package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
  43. package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
  44. package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
  45. package/plugins/example-analytics/stats.json +16 -12
  46. package/plugins/form-builder/admin/templates/form-editor.html +158 -130
  47. package/plugins/form-builder/admin/views/form-editor.js +3 -1
  48. package/plugins/form-builder/data/forms/contact-details.json +71 -35
  49. package/plugins/form-builder/data/forms/feedback.json +130 -0
  50. package/plugins/form-builder/data/submissions/feedback.json +1 -0
  51. package/plugins/form-builder/public/form-logic-engine.js +1 -568
  52. package/public/css/site.css +1 -302
  53. package/public/js/btt.js +1 -90
  54. package/public/js/cookie-consent.js +1 -61
  55. package/public/js/site.js +1 -204
  56. package/scripts/setup.js +4 -4
  57. package/server/middleware/auth.js +44 -21
  58. package/server/routes/api/auth.js +38 -8
  59. package/server/routes/api/collections.js +18 -5
  60. package/server/routes/api/layouts.js +18 -4
  61. package/server/routes/api/media.js +2 -3
  62. package/server/routes/api/navigation.js +2 -3
  63. package/server/routes/api/pages.js +3 -3
  64. package/server/routes/api/settings.js +2 -3
  65. package/server/routes/api/users.js +4 -6
  66. package/server/routes/public.js +3 -3
  67. package/server/server.js +8 -0
  68. package/server/services/markdown.js +102 -3
  69. package/server/services/userTypes.js +167 -0
  70. package/plugins/form-builder/email.js +0 -103
@@ -1,56 +1 @@
1
- /**
2
- * Dashboard View
3
- * Shows page count stats and a recent pages table.
4
- */
5
- import {api} from '../api.js';
6
-
7
- export const dashboardView = {
8
- templateUrl: '/admin/js/templates/dashboard.html',
9
-
10
- async onMount($container) {
11
- const pages = await api.pages.list().catch(() => []);
12
- const total = pages.length;
13
- const published = pages.filter(p => p.status === 'published').length;
14
- const drafts = pages.filter(p => p.status === 'draft').length;
15
-
16
- $container.find('#stat-total').text(total);
17
- $container.find('#stat-published').text(published);
18
- $container.find('#stat-drafts').text(drafts);
19
-
20
- Domma.effects.counter('#stat-total, #stat-published, #stat-drafts', {
21
- trigger: 'immediate',
22
- duration: 1000,
23
- stagger: 120,
24
- easing: 'ease-out'
25
- });
26
-
27
- const recent = [...pages]
28
- .sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''))
29
- .slice(0, 10);
30
-
31
- T.create('#recent-pages-table', {
32
- data: recent,
33
- columns: [
34
- {key: 'title', title: 'Title'},
35
- {key: 'urlPath', title: 'URL'},
36
- {
37
- key: 'status',
38
- title: 'Status',
39
- render: (val) => `<span class="badge badge-${val === 'published' ? 'success' : 'warning'}">${val}</span>`
40
- },
41
- {key: 'updatedAt', title: 'Updated', render: (val) => val ? D(val).format('DD MMM YYYY') : '—'},
42
- {
43
- key: 'urlPath',
44
- title: '',
45
- render: (val) => `<a href="#/pages/edit${val}" class="btn btn-sm btn-outline">Edit</a>`
46
- }
47
- ],
48
- emptyMessage: 'No pages yet. <a href="#/pages/new">Create your first page</a>.'
49
- });
50
-
51
- Domma.icons.scan();
52
-
53
- Domma.effects.reveal('.stat-card', { animation: 'fade', stagger: 80, duration: 400 });
54
- Domma.effects.reveal('.card.mt-4', { animation: 'fade', delay: 200, duration: 400 });
55
- }
56
- };
1
+ import{api as l}from"../api.js";export const dashboardView={templateUrl:"/admin/js/templates/dashboard.html",async onMount(a){const e=await l.pages.list().catch(()=>[]),s=e.length,d=e.filter(t=>t.status==="published").length,r=e.filter(t=>t.status==="draft").length;a.find("#stat-total").text(s),a.find("#stat-published").text(d),a.find("#stat-drafts").text(r),Domma.effects.counter("#stat-total, #stat-published, #stat-drafts",{trigger:"immediate",duration:1e3,stagger:120,easing:"ease-out"});const i=[...e].sort((t,n)=>(n.updatedAt||"").localeCompare(t.updatedAt||"")).slice(0,10);T.create("#recent-pages-table",{data:i,columns:[{key:"title",title:"Title"},{key:"urlPath",title:"URL"},{key:"status",title:"Status",render:t=>`<span class="badge badge-${t==="published"?"success":"warning"}">${t}</span>`},{key:"updatedAt",title:"Updated",render:t=>t?D(t).format("DD MMM YYYY"):"\u2014"},{key:"urlPath",title:"Actions",render:t=>`<a href="#/pages/edit${t}" class="btn btn-sm btn-outline">Edit</a>`}],emptyMessage:'No pages yet. <a href="#/pages/new">Create your first page</a>.'}),Domma.icons.scan(),Domma.effects.reveal(".stat-card",{animation:"fade",stagger:80,duration:400}),Domma.effects.reveal(".card.mt-4",{animation:"fade",delay:200,duration:400})}};
@@ -1,12 +1 @@
1
- /**
2
- * Documentation View
3
- * Usage reference for Domma CMS — content, structure, plugins, settings.
4
- */
5
- export const documentationView = {
6
- templateUrl: '/admin/js/templates/documentation.html',
7
-
8
- async onMount($container) {
9
- Domma.icons.scan();
10
- Domma.syntax.scan();
11
- }
12
- };
1
+ export const documentationView={templateUrl:"/admin/js/templates/documentation.html",async onMount(n){Domma.icons.scan(),Domma.syntax.scan()}};
@@ -1,39 +1 @@
1
- /**
2
- * View Registry
3
- * Maps route view names to their view modules.
4
- */
5
- import {dashboardView} from './dashboard.js';
6
- import {pagesView} from './pages.js';
7
- import {pageEditorView} from './page-editor.js';
8
- import {settingsView} from './settings.js';
9
- import {navigationView} from './navigation.js';
10
- import {layoutsView} from './layouts.js';
11
- import {mediaView} from './media.js';
12
- import {loginView} from './login.js';
13
- import {usersView} from './users.js';
14
- import {userEditorView} from './user-editor.js';
15
- import {pluginsView} from './plugins.js';
16
- import {documentationView} from './documentation.js';
17
- import {tutorialsView} from './tutorials.js';
18
- import {collectionsView} from './collections.js';
19
- import {collectionEditorView} from './collection-editor.js';
20
- import {collectionEntriesView} from './collection-entries.js';
21
-
22
- export const views = {
23
- dashboard: dashboardView,
24
- pages: pagesView,
25
- pageEditor: pageEditorView,
26
- settings: settingsView,
27
- navigation: navigationView,
28
- layouts: layoutsView,
29
- media: mediaView,
30
- login: loginView,
31
- users: usersView,
32
- userEditor: userEditorView,
33
- plugins: pluginsView,
34
- documentation: documentationView,
35
- tutorials: tutorialsView,
36
- collections: collectionsView,
37
- collectionEditor: collectionEditorView,
38
- collectionEntries: collectionEntriesView
39
- };
1
+ import{dashboardView as o}from"./dashboard.js";import{pagesView as i}from"./pages.js";import{pageEditorView as r}from"./page-editor.js";import{settingsView as t}from"./settings.js";import{navigationView as e}from"./navigation.js";import{layoutsView as m}from"./layouts.js";import{mediaView as s}from"./media.js";import{loginView as n}from"./login.js";import{usersView as p}from"./users.js";import{userEditorView as a}from"./user-editor.js";import{pluginsView as l}from"./plugins.js";import{documentationView as w}from"./documentation.js";import{tutorialsView as f}from"./tutorials.js";import{collectionsView as V}from"./collections.js";import{collectionEditorView as c}from"./collection-editor.js";import{collectionEntriesView as d}from"./collection-entries.js";export const views={dashboard:o,pages:i,pageEditor:r,settings:t,navigation:e,layouts:m,media:s,login:n,users:p,userEditor:a,plugins:l,documentation:w,tutorials:f,collections:V,collectionEditor:c,collectionEntries:d};
@@ -1,49 +1,16 @@
1
- /**
2
- * Layouts View
3
- */
4
- import { api } from '../api.js';
5
-
6
- export const layoutsView = {
7
- templateUrl: '/admin/js/templates/layouts.html',
8
-
9
- async onMount($container) {
10
- let presets = await api.layouts.get().catch(() => ({}));
11
- const $grid = $container.find('#presets-grid').empty();
12
-
13
- Object.entries(presets).forEach(([key, preset]) => {
14
- $grid.append(`
15
- <div class="preset-card card" data-key="${key}">
1
+ import{api as c}from"../api.js";export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(a){E.tabs(a.find("#layouts-tabs").get(0));let t=await c.layouts.get().catch(()=>({}));const r=a.find("#presets-grid").empty();Object.entries(t).forEach(([s,e])=>{r.append(`
2
+ <div class="preset-card card" data-key="${s}">
16
3
  <div class="card-header">
17
- <h3>${preset.label || key}</h3>
18
- <span class="badge">${key}</span>
4
+ <h3>${e.label||s}</h3>
5
+ <span class="badge">${s}</span>
19
6
  </div>
20
7
  <div class="card-body">
21
- <p class="text-muted">${preset.description || ''}</p>
8
+ <p class="text-muted">${e.description||""}</p>
22
9
  <div class="preset-toggles">
23
- <label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${preset.navbar ? 'checked' : ''}> Navbar</label>
24
- <label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${preset.footer ? 'checked' : ''}> Footer</label>
25
- <label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${preset.sidebar ? 'checked' : ''}> Sidebar</label>
10
+ <label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${e.navbar?"checked":""}> Navbar</label>
11
+ <label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${e.footer?"checked":""}> Footer</label>
12
+ <label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${e.sidebar?"checked":""}> Sidebar</label>
26
13
  </div>
27
14
  </div>
28
15
  </div>
29
- `);
30
- });
31
-
32
- $container.find('#save-layouts-btn').on('click', async () => {
33
- $container.find('.preset-card').each(function () {
34
- const key = $(this).data('key');
35
- if (presets[key]) {
36
- presets[key].navbar = $(this).find('.preset-navbar').is(':checked');
37
- presets[key].footer = $(this).find('.preset-footer').is(':checked');
38
- presets[key].sidebar = $(this).find('.preset-sidebar').is(':checked');
39
- }
40
- });
41
- try {
42
- await api.layouts.save(presets);
43
- E.toast('Layouts saved.', { type: 'success' });
44
- } catch {
45
- E.toast('Failed to save layouts.', { type: 'error' });
46
- }
47
- });
48
- }
49
- };
16
+ `)}),a.find("#save-layouts-btn").on("click",async()=>{a.find(".preset-card").each(function(){const s=$(this).data("key");t[s]&&(t[s].navbar=$(this).find(".preset-navbar").is(":checked"),t[s].footer=$(this).find(".preset-footer").is(":checked"),t[s].sidebar=$(this).find(".preset-sidebar").is(":checked"))});try{await c.layouts.save(t),E.toast("Layouts saved.",{type:"success"})}catch{E.toast("Failed to save layouts.",{type:"error"})}});const l=await c.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""})),o=a.find("#spacer-size-input"),i=a.find("#spacer-class-input");o.val(l.spacerSize??8),i.val(l.spacerClass??""),a.find("#save-options-btn").on("click",async()=>{const s=parseInt(o.val(),10)||8,e=i.val().trim();try{await c.layouts.saveOptions({spacerSize:s,spacerClass:e}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
@@ -1,255 +1,11 @@
1
- /**
2
- * Login / First-time Setup View
3
- *
4
- * Flow:
5
- * - Already logged in → redirect to dashboard
6
- * - needsSetup = true → show setup form → onboarding wizard (site → theme → done)
7
- * - needsSetup = false → show login form → redirect to dashboard
8
- */
9
- import {api, isAuthenticated, setAuthData} from '../api.js';
10
-
11
- // ---------------------------------------------------------------------------
12
- // Blueprints
13
- // ---------------------------------------------------------------------------
14
-
15
- const setupBlueprint = {
16
- name: {
17
- type: 'string',
18
- required: true,
19
- minLength: 2,
20
- label: 'Full Name',
21
- formConfig: { placeholder: 'Your name', autocomplete: 'name' }
22
- },
23
- email: {
24
- type: 'email',
25
- required: true,
26
- label: 'Email Address',
27
- formConfig: { placeholder: 'admin@example.com', autocomplete: 'email' }
28
- },
29
- password: {
30
- type: 'password',
31
- required: true,
32
- minLength: 8,
33
- label: 'Password',
34
- formConfig: { placeholder: '••••••••', autocomplete: 'new-password', tooltip: 'Minimum 8 characters' }
35
- }
36
- };
37
-
38
- const loginBlueprint = {
39
- email: {
40
- type: 'email',
41
- required: true,
42
- label: 'Email Address',
43
- formConfig: { placeholder: 'you@example.com', autocomplete: 'email' }
44
- },
45
- password: {
46
- type: 'password',
47
- required: true,
48
- label: 'Password',
49
- formConfig: { placeholder: '••••••••', autocomplete: 'current-password' }
50
- }
51
- };
52
-
53
- const THEMES = [
54
- { id: 'charcoal-dark', label: 'Charcoal', dark: true, primary: '#5b8cff', bg: '#1a1d23' },
55
- { id: 'charcoal-light', label: 'Charcoal', dark: false, primary: '#4a7aff', bg: '#f4f5f7' },
56
- { id: 'ocean-dark', label: 'Ocean', dark: true, primary: '#00b4d8', bg: '#0d1b2a' },
57
- { id: 'ocean-light', label: 'Ocean', dark: false, primary: '#0096c7', bg: '#e8f4f8' },
58
- { id: 'forest-dark', label: 'Forest', dark: true, primary: '#52b788', bg: '#1a231e' },
59
- { id: 'forest-light', label: 'Forest', dark: false, primary: '#40916c', bg: '#f0f7f4' },
60
- { id: 'sunset-dark', label: 'Sunset', dark: true, primary: '#ff6b6b', bg: '#1f1a1a' },
61
- { id: 'sunset-light', label: 'Sunset', dark: false, primary: '#e05555', bg: '#fff0f0' },
62
- { id: 'royal-dark', label: 'Royal', dark: true, primary: '#9b59b6', bg: '#1a1525' },
63
- { id: 'royal-light', label: 'Royal', dark: false, primary: '#8e44ad', bg: '#f5f0fa' },
64
- { id: 'lemon-dark', label: 'Lemon', dark: true, primary: '#f1c40f', bg: '#1a1a10' },
65
- { id: 'lemon-light', label: 'Lemon', dark: false, primary: '#d4ac0d', bg: '#fffef0' },
66
- { id: 'silver-dark', label: 'Silver', dark: true, primary: '#95a5a6', bg: '#1c1e20' },
67
- { id: 'silver-light', label: 'Silver', dark: false, primary: '#7f8c8d', bg: '#f2f3f4' },
68
- {id: 'grayve-dark', label: 'Grayve', dark: true, primary: '#00bcd4', bg: '#1a1e21'},
69
- {id: 'grayve-light', label: 'Grayve', dark: false, primary: '#00838f', bg: '#ffffff'},
70
- ];
71
-
72
- export const loginView = {
73
- templateUrl: '/admin/js/templates/login.html',
74
-
75
- async onMount($container) {
76
- if (isAuthenticated()) {
77
- R.navigate('/');
78
- return;
79
- }
80
-
81
- let needsSetup = false;
82
- try {
83
- const status = await api.auth.setupStatus();
84
- needsSetup = status.needsSetup;
85
- } catch {
86
- E.toast('Could not reach the server.', { type: 'error' });
87
- }
88
-
89
- if (needsSetup) {
90
- showStep($container, 'setup');
91
- bindSetup($container);
92
- } else {
93
- showStep($container, 'login');
94
- bindLogin($container);
95
- }
96
-
97
- Domma.icons.scan();
98
- }
99
- };
100
-
101
- // ---------------------------------------------------------------------------
102
- // Panel switching
103
- // ---------------------------------------------------------------------------
104
-
105
- const PANELS = ['setup', 'onboarding-site', 'onboarding-theme', 'onboarding-done', 'login'];
106
-
107
- function showStep($container, name) {
108
- PANELS.forEach(p => $container.find(`#${p}-panel`).hide());
109
- $container.find(`#${name}-panel`).show();
110
- Domma.icons.scan();
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Setup step
115
- // ---------------------------------------------------------------------------
116
-
117
- function bindSetup($container) {
118
- F.render('#setup-form-container', setupBlueprint, {}, {
119
- layout: 'stacked',
120
- submitText: 'Create admin account',
121
- onSubmit: async (data) => {
122
- try {
123
- const result = await api.auth.setup(data);
124
- setAuthData(result);
125
- showStep($container, 'onboarding-site');
126
- bindOnboardingSite($container);
127
- } catch (err) {
128
- E.toast(err.message || 'Setup failed. Please try again.', { type: 'error' });
129
- return false;
130
- }
131
- }
132
- });
133
- }
134
-
135
- // ---------------------------------------------------------------------------
136
- // Onboarding Step 2 — Site Identity
137
- // ---------------------------------------------------------------------------
138
-
139
- function bindOnboardingSite($container) {
140
- $container.find('#ob-site-skip').on('click', (e) => {
141
- e.preventDefault();
142
- showStep($container, 'onboarding-theme');
143
- buildThemeGrid($container);
144
- bindOnboardingTheme($container);
145
- });
146
-
147
- $container.find('#ob-site-btn').on('click', async () => {
148
- $container.find('#ob-site-error').hide();
149
- const title = $container.find('#ob-title').val().trim();
150
- const tagline = $container.find('#ob-tagline').val().trim();
151
-
152
- const $btn = $container.find('#ob-site-btn').prop('disabled', true).text('Saving…');
153
- try {
154
- // Fetch current settings then merge title + tagline
155
- const current = await api.settings.get();
156
- await api.settings.save({
157
- ...current,
158
- title: title || current.title,
159
- tagline: tagline || current.tagline,
160
- seo: {
161
- ...(current.seo || {}),
162
- defaultTitle: title || (current.seo && current.seo.defaultTitle)
163
- }
164
- });
165
-
166
- // Also update nav brand text
167
- const nav = await api.navigation.get();
168
- await api.navigation.save({
169
- ...nav,
170
- brand: { ...(nav.brand || {}), text: title || nav.brand.text }
171
- });
172
-
173
- showStep($container, 'onboarding-theme');
174
- buildThemeGrid($container);
175
- bindOnboardingTheme($container);
176
- } catch (err) {
177
- showError($container, 'ob-site-error', err.message || 'Could not save site details.');
178
- } finally {
179
- $btn.prop('disabled', false).text('Continue');
180
- }
181
- });
182
- }
183
-
184
- // ---------------------------------------------------------------------------
185
- // Onboarding Step 3 — Theme Picker
186
- // ---------------------------------------------------------------------------
187
-
188
- function buildThemeGrid($container) {
189
- const $grid = $container.find('#theme-grid').empty();
190
- THEMES.forEach(t => {
191
- const $card = $(`
192
- <div class="theme-swatch" data-theme="${t.id}" title="${t.id}">
193
- <div class="theme-swatch-preview" style="background:${t.bg}">
194
- <div class="theme-swatch-accent" style="background:${t.primary}"></div>
1
+ import{api as i,isAuthenticated as c,setAuthData as o}from"../api.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},b={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}},p=[{id:"charcoal-dark",label:"Charcoal",dark:!0,primary:"#5b8cff",bg:"#1a1d23"},{id:"charcoal-light",label:"Charcoal",dark:!1,primary:"#4a7aff",bg:"#f4f5f7"},{id:"ocean-dark",label:"Ocean",dark:!0,primary:"#00b4d8",bg:"#0d1b2a"},{id:"ocean-light",label:"Ocean",dark:!1,primary:"#0096c7",bg:"#e8f4f8"},{id:"forest-dark",label:"Forest",dark:!0,primary:"#52b788",bg:"#1a231e"},{id:"forest-light",label:"Forest",dark:!1,primary:"#40916c",bg:"#f0f7f4"},{id:"sunset-dark",label:"Sunset",dark:!0,primary:"#ff6b6b",bg:"#1f1a1a"},{id:"sunset-light",label:"Sunset",dark:!1,primary:"#e05555",bg:"#fff0f0"},{id:"royal-dark",label:"Royal",dark:!0,primary:"#9b59b6",bg:"#1a1525"},{id:"royal-light",label:"Royal",dark:!1,primary:"#8e44ad",bg:"#f5f0fa"},{id:"lemon-dark",label:"Lemon",dark:!0,primary:"#f1c40f",bg:"#1a1a10"},{id:"lemon-light",label:"Lemon",dark:!1,primary:"#d4ac0d",bg:"#fffef0"},{id:"silver-dark",label:"Silver",dark:!0,primary:"#95a5a6",bg:"#1c1e20"},{id:"silver-light",label:"Silver",dark:!1,primary:"#7f8c8d",bg:"#f2f3f4"},{id:"grayve-dark",label:"Grayve",dark:!0,primary:"#00bcd4",bg:"#1a1e21"},{id:"grayve-light",label:"Grayve",dark:!1,primary:"#00838f",bg:"#ffffff"}];export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(c()){R.navigate("/");return}let t=!1;try{t=(await i.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}t?(s(e,"setup"),g(e)):(s(e,"login"),y(e)),Domma.icons.scan()}};const u=["setup","onboarding-site","onboarding-theme","onboarding-done","login"];function s(e,t){u.forEach(a=>e.find(`#${a}-panel`).hide()),e.find(`#${t}-panel`).show(),Domma.icons.scan()}function g(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async t=>{try{const a=await i.auth.setup(t);o(a),s(e,"onboarding-site"),h(e)}catch(a){return E.toast(a.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function h(e){e.find("#ob-site-skip").on("click",t=>{t.preventDefault(),s(e,"onboarding-theme"),n(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const t=e.find("#ob-title").val().trim(),a=e.find("#ob-tagline").val().trim(),r=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const l=await i.settings.get();await i.settings.save({...l,title:t||l.title,tagline:a||l.tagline,seo:{...l.seo||{},defaultTitle:t||l.seo&&l.seo.defaultTitle}});const d=await i.navigation.get();await i.navigation.save({...d,brand:{...d.brand||{},text:t||d.brand.text}}),s(e,"onboarding-theme"),n(e),m(e)}catch(l){showError(e,"ob-site-error",l.message||"Could not save site details.")}finally{r.prop("disabled",!1).text("Continue")}})}function n(e){const t=e.find("#theme-grid").empty();p.forEach(a=>{const r=$(`
2
+ <div class="theme-swatch" data-theme="${a.id}" title="${a.id}">
3
+ <div class="theme-swatch-preview" style="background:${a.bg}">
4
+ <div class="theme-swatch-accent" style="background:${a.primary}"></div>
195
5
  </div>
196
6
  <div class="theme-swatch-label">
197
- <span>${t.label}</span>
198
- <span class="theme-swatch-mode">${t.dark ? 'Dark' : 'Light'}</span>
7
+ <span>${a.label}</span>
8
+ <span class="theme-swatch-mode">${a.dark?"Dark":"Light"}</span>
199
9
  </div>
200
10
  </div>
201
- `);
202
- $card.on('click', () => {
203
- $container.find('.theme-swatch').removeClass('selected');
204
- $card.addClass('selected');
205
- });
206
- $grid.append($card);
207
- });
208
-
209
- // Default selection
210
- $container.find('[data-theme="charcoal-dark"]').addClass('selected');
211
- }
212
-
213
- function bindOnboardingTheme($container) {
214
- $container.find('#ob-theme-skip').on('click', (e) => {
215
- e.preventDefault();
216
- showStep($container, 'onboarding-done');
217
- });
218
-
219
- $container.find('#ob-theme-btn').on('click', async () => {
220
- $container.find('#ob-theme-error').hide();
221
- const selected = $container.find('.theme-swatch.selected').attr('data-theme') || 'charcoal-dark';
222
-
223
- const $btn = $container.find('#ob-theme-btn').prop('disabled', true).text('Applying…');
224
- try {
225
- const current = await api.settings.get();
226
- await api.settings.save({ ...current, theme: selected });
227
- showStep($container, 'onboarding-done');
228
- } catch (err) {
229
- showError($container, 'ob-theme-error', err.message || 'Could not save theme.');
230
- } finally {
231
- $btn.prop('disabled', false).text('Apply theme');
232
- }
233
- });
234
- }
235
-
236
- // ---------------------------------------------------------------------------
237
- // Login step
238
- // ---------------------------------------------------------------------------
239
-
240
- function bindLogin($container) {
241
- F.render('#login-form-container', loginBlueprint, {}, {
242
- layout: 'stacked',
243
- submitText: 'Sign in',
244
- onSubmit: async (data) => {
245
- try {
246
- const result = await api.auth.login(data);
247
- setAuthData(result);
248
- R.navigate('/');
249
- } catch (err) {
250
- E.toast(err.message || 'Invalid credentials. Please try again.', { type: 'error' });
251
- return false;
252
- }
253
- }
254
- });
255
- }
11
+ `);r.on("click",()=>{e.find(".theme-swatch").removeClass("selected"),r.addClass("selected")}),t.append(r)}),e.find('[data-theme="charcoal-dark"]').addClass("selected")}function m(e){e.find("#ob-theme-skip").on("click",t=>{t.preventDefault(),s(e,"onboarding-done")}),e.find("#ob-theme-btn").on("click",async()=>{e.find("#ob-theme-error").hide();const t=e.find(".theme-swatch.selected").attr("data-theme")||"charcoal-dark",a=e.find("#ob-theme-btn").prop("disabled",!0).text("Applying\u2026");try{const r=await i.settings.get();await i.settings.save({...r,theme:t}),s(e,"onboarding-done")}catch(r){showError(e,"ob-theme-error",r.message||"Could not save theme.")}finally{a.prop("disabled",!1).text("Apply theme")}})}function y(e){F.render("#login-form-container",b,{},{layout:"stacked",submitText:"Sign in",onSubmit:async t=>{try{const a=await i.auth.login(t);o(a),R.navigate("/")}catch(a){return E.toast(a.message||"Invalid credentials. Please try again.",{type:"error"}),!1}}})}