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.
- package/admin/css/admin.css +1 -1200
- package/admin/js/api.js +1 -242
- package/admin/js/app.js +5 -279
- package/admin/js/config/sidebar-config.js +1 -115
- package/admin/js/lib/card.js +1 -63
- package/admin/js/lib/image-editor.js +1 -869
- package/admin/js/lib/markdown-toolbar.js +46 -421
- package/admin/js/templates/layouts.html +44 -7
- package/admin/js/templates/page-editor.html +9 -0
- package/admin/js/templates/settings.html +18 -1
- package/admin/js/templates/users.html +29 -4
- package/admin/js/views/collection-editor.js +3 -487
- package/admin/js/views/collection-entries.js +1 -484
- package/admin/js/views/collections.js +1 -153
- package/admin/js/views/dashboard.js +1 -56
- package/admin/js/views/documentation.js +1 -12
- package/admin/js/views/index.js +1 -39
- package/admin/js/views/layouts.js +9 -42
- package/admin/js/views/login.js +7 -251
- package/admin/js/views/media.js +1 -240
- package/admin/js/views/navigation.js +14 -212
- package/admin/js/views/page-editor.js +53 -661
- package/admin/js/views/pages.js +5 -72
- package/admin/js/views/plugins.js +13 -90
- package/admin/js/views/settings.js +1 -199
- package/admin/js/views/tutorials.js +1 -12
- package/admin/js/views/user-editor.js +1 -88
- package/admin/js/views/users.js +7 -76
- package/config/auth.json +1 -17
- package/config/navigation.json +15 -0
- package/config/site.json +5 -4
- package/package.json +1 -1
- package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
- package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
- package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
- package/plugins/domma-effects/public/celebrations/index.js +1 -535
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
- package/plugins/example-analytics/stats.json +16 -12
- package/plugins/form-builder/admin/templates/form-editor.html +158 -130
- package/plugins/form-builder/admin/views/form-editor.js +3 -1
- package/plugins/form-builder/data/forms/contact-details.json +71 -35
- package/plugins/form-builder/data/forms/feedback.json +130 -0
- package/plugins/form-builder/data/submissions/feedback.json +1 -0
- package/plugins/form-builder/public/form-logic-engine.js +1 -568
- package/public/css/site.css +1 -302
- package/public/js/btt.js +1 -90
- package/public/js/cookie-consent.js +1 -61
- package/public/js/site.js +1 -204
- package/scripts/setup.js +4 -4
- package/server/middleware/auth.js +44 -21
- package/server/routes/api/auth.js +38 -8
- package/server/routes/api/collections.js +18 -5
- package/server/routes/api/layouts.js +18 -4
- package/server/routes/api/media.js +2 -3
- package/server/routes/api/navigation.js +2 -3
- package/server/routes/api/pages.js +3 -3
- package/server/routes/api/settings.js +2 -3
- package/server/routes/api/users.js +4 -6
- package/server/routes/public.js +3 -3
- package/server/server.js +8 -0
- package/server/services/markdown.js +102 -3
- package/server/services/userTypes.js +167 -0
- 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()}};
|
package/admin/js/views/index.js
CHANGED
|
@@ -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
|
-
|
|
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>${
|
|
18
|
-
<span class="badge">${
|
|
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">${
|
|
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" ${
|
|
24
|
-
<label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${
|
|
25
|
-
<label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${
|
|
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"})}})}};
|
package/admin/js/views/login.js
CHANGED
|
@@ -1,255 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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>${
|
|
198
|
-
<span class="theme-swatch-mode">${
|
|
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}}})}
|