domma-cms 0.2.1 → 0.5.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/README.md +3 -3
- package/admin/css/admin.css +1 -1200
- 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 -242
- package/admin/js/app.js +9 -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 +54 -421
- 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/admin/js/templates/form-editor.html +238 -0
- 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/layouts.html +44 -7
- package/admin/js/templates/login.html +29 -4
- package/admin/js/templates/my-profile.html +17 -0
- package/admin/js/templates/page-editor.html +48 -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 +137 -18
- package/admin/js/templates/tutorials.html +81 -0
- package/admin/js/templates/user-editor.html +7 -0
- package/admin/js/templates/users.html +3 -1
- 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 -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/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 -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/my-profile.js +1 -0
- package/admin/js/views/navigation.js +14 -212
- package/admin/js/views/page-editor.js +72 -661
- package/admin/js/views/pages.js +5 -72
- package/admin/js/views/plugins.js +13 -90
- 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 -199
- package/admin/js/views/tutorials.js +1 -12
- package/admin/js/views/user-editor.js +1 -88
- package/admin/js/views/users.js +4 -76
- 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 +2 -17
- package/config/connections.json.bak +9 -0
- package/config/connections.json.example +9 -0
- package/config/navigation.json +15 -0
- package/config/plugins.json +19 -29
- package/config/server.json +6 -6
- package/config/site.json +17 -6
- package/package.json +24 -10
- 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 +21 -12
- 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 -302
- package/public/js/btt.js +1 -90
- package/public/js/cookie-consent.js +1 -61
- package/public/js/form-logic-engine.js +1 -0
- package/public/js/forms.js +1 -0
- package/public/js/site.js +1 -204
- package/scripts/build.js +194 -129
- package/scripts/pro.js +254 -0
- package/scripts/reset.js +33 -8
- package/scripts/seed.js +343 -78
- package/scripts/setup.js +5 -4
- package/server/middleware/auth.js +136 -97
- package/server/routes/api/actions.js +200 -0
- package/server/routes/api/auth.js +292 -116
- package/server/routes/api/blocks.js +84 -0
- package/server/routes/api/collections.js +88 -23
- package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +483 -505
- package/server/routes/api/layouts.js +49 -25
- package/server/routes/api/media.js +118 -93
- package/server/routes/api/navigation.js +40 -37
- package/server/routes/api/pages.js +132 -118
- package/server/routes/api/plugins.js +6 -3
- package/server/routes/api/settings.js +104 -89
- package/server/routes/api/users.js +27 -21
- package/server/routes/api/views.js +148 -0
- package/server/routes/public.js +124 -108
- package/server/server.js +269 -173
- 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/{plugins/form-builder → server/services}/email.js +126 -103
- package/server/services/forms.js +173 -0
- package/server/services/markdown.js +1378 -648
- package/server/services/permissionRegistry.js +173 -0
- package/server/services/presetCollections.js +251 -0
- package/server/services/renderer.js +75 -1
- 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 +119 -113
- package/plugins/form-builder/admin/templates/form-editor.html +0 -171
- package/plugins/form-builder/admin/templates/form-settings.html +0 -29
- package/plugins/form-builder/admin/views/form-editor.js +0 -1442
- 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 -63
- package/plugins/form-builder/data/forms/contacts.json +0 -66
- package/plugins/form-builder/data/submissions/consent.json +0 -13
- package/plugins/form-builder/data/submissions/contact-details.json +0 -1
- package/plugins/form-builder/data/submissions/contacts.json +0 -26
- package/plugins/form-builder/plugin.json +0 -52
- package/plugins/form-builder/public/form-logic-engine.js +0 -568
- 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/admin/index.html
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<link rel="stylesheet" href="/dist/domma/grid.css">
|
|
11
11
|
<link rel="stylesheet" href="/dist/domma/elements.css">
|
|
12
12
|
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
13
|
+
<link rel="stylesheet" href="/admin/dist/domma/domma-tools.css">
|
|
13
14
|
|
|
14
15
|
<!-- Cropper.js - image editor -->
|
|
15
16
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cropperjs@1/dist/cropper.min.css">
|
|
@@ -60,6 +61,9 @@
|
|
|
60
61
|
<!-- DommaJS Syntax Highlighter (adds Domma.syntax) -->
|
|
61
62
|
<script src="/dist/domma/domma-syntax.min.js"></script>
|
|
62
63
|
|
|
64
|
+
<!-- DommaJS Tools (adds E.editor, E.schemaBuilder etc.) -->
|
|
65
|
+
<script src="/admin/dist/domma/domma-tools.min.js"></script>
|
|
66
|
+
|
|
63
67
|
<!-- Cropper.js - image editor -->
|
|
64
68
|
<script src="https://cdn.jsdelivr.net/npm/cropperjs@1/dist/cropper.min.js"></script>
|
|
65
69
|
|
package/admin/js/api.js
CHANGED
|
@@ -1,242 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Admin API Client
|
|
3
|
-
* Authenticated requests using Bearer tokens with automatic refresh on 401.
|
|
4
|
-
* All existing api.* namespaces are preserved — views need no changes.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const BASE = '/api';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Token helpers
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
function getToken() { return S.get('auth_token'); }
|
|
14
|
-
function getRefreshToken() { return S.get('auth_refresh_token'); }
|
|
15
|
-
function setToken(t) { S.set('auth_token', t); }
|
|
16
|
-
function clearAuth() {
|
|
17
|
-
S.remove('auth_token');
|
|
18
|
-
S.remove('auth_refresh_token');
|
|
19
|
-
S.remove('auth_user');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Attempt to refresh the access token using the stored refresh token.
|
|
24
|
-
* Clears auth and redirects to login if refresh fails.
|
|
25
|
-
*
|
|
26
|
-
* @returns {Promise<string>} New access token
|
|
27
|
-
*/
|
|
28
|
-
async function refreshAccessToken() {
|
|
29
|
-
const refreshToken = getRefreshToken();
|
|
30
|
-
if (!refreshToken) throw new Error('No refresh token');
|
|
31
|
-
|
|
32
|
-
const res = await fetch(`${BASE}/auth/refresh`, {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
headers: { 'Content-Type': 'application/json' },
|
|
35
|
-
body: JSON.stringify({ refreshToken })
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
clearAuth();
|
|
40
|
-
R.navigate('/login');
|
|
41
|
-
throw new Error('Token refresh failed');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const { token } = await res.json();
|
|
45
|
-
setToken(token);
|
|
46
|
-
return token;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Core request function
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Make an authenticated API request.
|
|
55
|
-
* Automatically retries once after refreshing the access token on 401.
|
|
56
|
-
*
|
|
57
|
-
* @param {string} endpoint
|
|
58
|
-
* @param {RequestInit} options
|
|
59
|
-
* @returns {Promise<any>}
|
|
60
|
-
*/
|
|
61
|
-
async function apiRequest(endpoint, options = {}) {
|
|
62
|
-
let token = getToken();
|
|
63
|
-
|
|
64
|
-
const buildHeaders = (t) => ({
|
|
65
|
-
...(options.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
66
|
-
...options.headers,
|
|
67
|
-
...(t ? { Authorization: `Bearer ${t}` } : {})
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
let res = await fetch(`${BASE}${endpoint}`, { ...options, headers: buildHeaders(token) });
|
|
71
|
-
|
|
72
|
-
// On 401, try refreshing once
|
|
73
|
-
if (res.status === 401 && getRefreshToken()) {
|
|
74
|
-
try {
|
|
75
|
-
token = await refreshAccessToken();
|
|
76
|
-
res = await fetch(`${BASE}${endpoint}`, { ...options, headers: buildHeaders(token) });
|
|
77
|
-
} catch {
|
|
78
|
-
return; // Already redirected to login
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!res.ok) {
|
|
83
|
-
const body = await res.json().catch(() => ({ error: 'Request failed' }));
|
|
84
|
-
throw new Error(body.error || body.message || `HTTP ${res.status}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 204 No Content
|
|
88
|
-
if (res.status === 204) return null;
|
|
89
|
-
|
|
90
|
-
return res.json();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Upload a file with Bearer token (multipart — no Content-Type override).
|
|
95
|
-
*
|
|
96
|
-
* @param {string} endpoint
|
|
97
|
-
* @param {FormData} formData
|
|
98
|
-
* @returns {Promise<any>}
|
|
99
|
-
*/
|
|
100
|
-
async function uploadRequest(endpoint, formData) {
|
|
101
|
-
const token = getToken();
|
|
102
|
-
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
|
103
|
-
|
|
104
|
-
const res = await fetch(`${BASE}${endpoint}`, { method: 'POST', headers, body: formData });
|
|
105
|
-
if (!res.ok) {
|
|
106
|
-
const body = await res.json().catch(() => ({ error: 'Upload failed' }));
|
|
107
|
-
throw new Error(body.error || body.message || `HTTP ${res.status}`);
|
|
108
|
-
}
|
|
109
|
-
return res.json();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
// Domain API namespaces
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
|
|
116
|
-
export const api = {
|
|
117
|
-
// Auth
|
|
118
|
-
auth: {
|
|
119
|
-
setupStatus: () => apiRequest('/auth/setup-status', { method: 'GET' }),
|
|
120
|
-
setup: (data) => apiRequest('/auth/setup', { method: 'POST', body: JSON.stringify(data) }),
|
|
121
|
-
login: (data) => apiRequest('/auth/login', { method: 'POST', body: JSON.stringify(data) }),
|
|
122
|
-
me: () => apiRequest('/auth/me', { method: 'GET' }),
|
|
123
|
-
refresh: (refreshToken) => apiRequest('/auth/refresh', { method: 'POST', body: JSON.stringify({ refreshToken }) })
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// Pages
|
|
127
|
-
pages: {
|
|
128
|
-
list: () => apiRequest('/pages', { method: 'GET' }),
|
|
129
|
-
get: (urlPath) => apiRequest(`/pages${urlPath}`, { method: 'GET' }),
|
|
130
|
-
create: (payload) => apiRequest('/pages', { method: 'POST', body: JSON.stringify(payload) }),
|
|
131
|
-
update: (urlPath, payload) => apiRequest(`/pages${urlPath}`, { method: 'PUT', body: JSON.stringify(payload) }),
|
|
132
|
-
delete: (urlPath) => apiRequest(`/pages${urlPath}`, {method: 'DELETE'}),
|
|
133
|
-
preview: (markdown) => apiRequest('/pages/preview', {method: 'POST', body: JSON.stringify({markdown})})
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
// Settings
|
|
137
|
-
settings: {
|
|
138
|
-
get: () => apiRequest('/settings', { method: 'GET' }),
|
|
139
|
-
save: (data) => apiRequest('/settings', { method: 'PUT', body: JSON.stringify(data) })
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// Navigation
|
|
143
|
-
navigation: {
|
|
144
|
-
get: () => apiRequest('/navigation', { method: 'GET' }),
|
|
145
|
-
save: (data) => apiRequest('/navigation', { method: 'PUT', body: JSON.stringify(data) })
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// Layouts (presets)
|
|
149
|
-
layouts: {
|
|
150
|
-
get: () => apiRequest('/layouts', { method: 'GET' }),
|
|
151
|
-
save: (data) => apiRequest('/layouts', { method: 'PUT', body: JSON.stringify(data) })
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
// Media
|
|
155
|
-
media: {
|
|
156
|
-
list: () => apiRequest('/media', { method: 'GET' }),
|
|
157
|
-
upload: (formData) => uploadRequest('/media', formData),
|
|
158
|
-
delete: (name) => apiRequest(`/media/${encodeURIComponent(name)}`, {method: 'DELETE'}),
|
|
159
|
-
rename: (name, newName) => apiRequest(`/media/${encodeURIComponent(name)}`, {
|
|
160
|
-
method: 'PATCH',
|
|
161
|
-
body: JSON.stringify({newName})
|
|
162
|
-
}),
|
|
163
|
-
info: (name) => apiRequest(`/media/${encodeURIComponent(name)}/info`, {method: 'GET'}),
|
|
164
|
-
transform: (name, body) => apiRequest(`/media/${encodeURIComponent(name)}/transform`, {
|
|
165
|
-
method: 'POST',
|
|
166
|
-
body: JSON.stringify(body)
|
|
167
|
-
}),
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
// Users
|
|
171
|
-
users: {
|
|
172
|
-
list: () => apiRequest('/users', { method: 'GET' }),
|
|
173
|
-
get: (id) => apiRequest(`/users/${id}`, { method: 'GET' }),
|
|
174
|
-
create: (data) => apiRequest('/users', { method: 'POST', body: JSON.stringify(data) }),
|
|
175
|
-
update: (id, data) => apiRequest(`/users/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
176
|
-
delete: (id) => apiRequest(`/users/${id}`, { method: 'DELETE' })
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// Plugins
|
|
180
|
-
plugins: {
|
|
181
|
-
list: () => apiRequest('/plugins', { method: 'GET' }),
|
|
182
|
-
update: (name, data) => apiRequest(`/plugins/${name}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
183
|
-
adminConfig: () => apiRequest('/plugins/admin-config', { method: 'GET' })
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
// Collections
|
|
187
|
-
collections: {
|
|
188
|
-
list: () => apiRequest('/collections', { method: 'GET' }),
|
|
189
|
-
get: (slug) => apiRequest(`/collections/${slug}`, { method: 'GET' }),
|
|
190
|
-
create: (data) => apiRequest('/collections', { method: 'POST', body: JSON.stringify(data) }),
|
|
191
|
-
update: (slug, data) => apiRequest(`/collections/${slug}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
192
|
-
delete: (slug) => apiRequest(`/collections/${slug}`, { method: 'DELETE' }),
|
|
193
|
-
|
|
194
|
-
listEntries: (slug, params = {}) => {
|
|
195
|
-
const qs = new URLSearchParams(params).toString();
|
|
196
|
-
return apiRequest(`/collections/${slug}/entries${qs ? '?' + qs : ''}`, { method: 'GET' });
|
|
197
|
-
},
|
|
198
|
-
getEntry: (slug, id) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'GET' }),
|
|
199
|
-
createEntry: (slug, data) => apiRequest(`/collections/${slug}/entries`, { method: 'POST', body: JSON.stringify({ data }) }),
|
|
200
|
-
updateEntry: (slug, id, data) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'PUT', body: JSON.stringify({ data }) }),
|
|
201
|
-
deleteEntry: (slug, id) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'DELETE' }),
|
|
202
|
-
clearEntries: (slug) => apiRequest(`/collections/${slug}/entries`, { method: 'DELETE' }),
|
|
203
|
-
|
|
204
|
-
import: (slug, entries) => apiRequest(`/collections/${slug}/import`, { method: 'POST', body: JSON.stringify({ entries }) }),
|
|
205
|
-
|
|
206
|
-
// Public API (no auth needed when collection is public)
|
|
207
|
-
publicList: (slug, params = {}) => {
|
|
208
|
-
const qs = new URLSearchParams(params).toString();
|
|
209
|
-
return apiRequest(`/collections/${slug}/public${qs ? '?' + qs : ''}`, { method: 'GET' });
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
// Settings (extended)
|
|
214
|
-
settingsExt: {
|
|
215
|
-
testEmail: (to) => apiRequest('/settings/test-email', { method: 'POST', body: JSON.stringify({ to }) })
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// Auth state helpers (used by app.js)
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
export function isAuthenticated() {
|
|
224
|
-
return !!getToken();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function getUser() {
|
|
228
|
-
return S.get('auth_user');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function setAuthData({ token, refreshToken, user }) {
|
|
232
|
-
if (token) setToken(token);
|
|
233
|
-
if (refreshToken) S.set('auth_refresh_token', refreshToken);
|
|
234
|
-
if (user) S.set('auth_user', user);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function logout() {
|
|
238
|
-
clearAuth();
|
|
239
|
-
R.navigate('/login');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export {apiRequest};
|
|
1
|
+
const r="/api";function a(){return S.get("auth_token")}function h(){return S.get("auth_refresh_token")}function m(e){S.set("auth_token",e)}function c(){S.remove("auth_token"),S.remove("auth_refresh_token"),S.remove("auth_user")}async function l(){const e=h();if(!e)throw new Error("No refresh token");const o=await fetch(`${r}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw c(),R.navigate("/login"),new Error("Token refresh failed");const{token:s}=await o.json();return m(s),s}async function t(e,o={}){let s=a();const d=i=>({...o.body!==void 0?{"Content-Type":"application/json"}:{},...o.headers,...i?{Authorization:`Bearer ${i}`}:{}});let n=await fetch(`${r}${e}`,{...o,headers:d(s)});if(n.status===401&&h())try{s=await l(),n=await fetch(`${r}${e}`,{...o,headers:d(s)})}catch{return}if(!n.ok){const i=await n.json().catch(()=>({error:"Request failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.status===204?null:n.json()}async function u(e,o){const s=a(),d=s?{Authorization:`Bearer ${s}`}:{},n=await fetch(`${r}${e}`,{method:"POST",headers:d,body:o});if(!n.ok){const i=await n.json().catch(()=>({error:"Upload failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.json()}export const api={auth:{setupStatus:()=>t("/auth/setup-status",{method:"GET"}),setup:e=>t("/auth/setup",{method:"POST",body:JSON.stringify(e)}),login:e=>t("/auth/login",{method:"POST",body:JSON.stringify(e)}),me:()=>t("/auth/me",{method:"GET"}),updateMe:e=>t("/auth/me",{method:"PUT",body:JSON.stringify(e)}),refresh:e=>t("/auth/refresh",{method:"POST",body:JSON.stringify({refreshToken:e})}),forgotPassword:e=>t("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e})}),resetPassword:(e,o)=>t("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e,password:o})})},pages:{list:()=>t("/pages",{method:"GET"}),get:e=>t(`/pages${e}`,{method:"GET"}),create:e=>t("/pages",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/pages${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/pages${e}`,{method:"DELETE"}),preview:e=>t("/pages/preview",{method:"POST",body:JSON.stringify({markdown:e})}),tags:()=>t("/pages/tags",{method:"GET"}).then(e=>e.tags||[])},settings:{get:()=>t("/settings",{method:"GET"}),save:e=>t("/settings",{method:"PUT",body:JSON.stringify(e)})},navigation:{get:()=>t("/navigation",{method:"GET"}),save:e=>t("/navigation",{method:"PUT",body:JSON.stringify(e)})},layouts:{get:()=>t("/layouts",{method:"GET"}),save:e=>t("/layouts",{method:"PUT",body:JSON.stringify(e)}),getOptions:()=>t("/layouts/options",{method:"GET"}),saveOptions:e=>t("/layouts/options",{method:"PUT",body:JSON.stringify(e)})},media:{list:()=>t("/media",{method:"GET"}),upload:e=>u("/media",e),delete:e=>t(`/media/${encodeURIComponent(e)}`,{method:"DELETE"}),rename:(e,o)=>t(`/media/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({newName:o})}),info:e=>t(`/media/${encodeURIComponent(e)}/info`,{method:"GET"}),transform:(e,o)=>t(`/media/${encodeURIComponent(e)}/transform`,{method:"POST",body:JSON.stringify(o)})},users:{list:()=>t("/users",{method:"GET"}),get:e=>t(`/users/${e}`,{method:"GET"}),create:e=>t("/users",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/users/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/users/${e}`,{method:"DELETE"})},plugins:{list:()=>t("/plugins",{method:"GET"}),update:(e,o)=>t(`/plugins/${e}`,{method:"PUT",body:JSON.stringify(o)}),adminConfig:()=>t("/plugins/admin-config",{method:"GET"})},collections:{list:()=>t("/collections",{method:"GET"}),proStatus:()=>t("/collections/pro-status",{method:"GET"}),get:e=>t(`/collections/${e}`,{method:"GET"}),create:e=>t("/collections",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/collections/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/collections/${e}`,{method:"DELETE"}),listEntries:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/entries${s?"?"+s:""}`,{method:"GET"})},getEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"GET"}),createEntry:(e,o)=>t(`/collections/${e}/entries`,{method:"POST",body:JSON.stringify({data:o})}),updateEntry:(e,o,s)=>t(`/collections/${e}/entries/${o}`,{method:"PUT",body:JSON.stringify({data:s})}),deleteEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"DELETE"}),clearEntries:e=>t(`/collections/${e}/entries`,{method:"DELETE"}),import:(e,o)=>t(`/collections/${e}/import`,{method:"POST",body:JSON.stringify({entries:o})}),publicList:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/public${s?"?"+s:""}`,{method:"GET"})},getConnections:()=>t("/collections/connections",{method:"GET"}),saveConnections:e=>t("/collections/connections",{method:"PUT",body:JSON.stringify(e)})},forms:{list:()=>t("/forms",{method:"GET"}),create:e=>t("/forms",{method:"POST",body:JSON.stringify(e)}),get:e=>t(`/forms/${e}`,{method:"GET"}),update:(e,o)=>t(`/forms/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/forms/${e}`,{method:"DELETE"}),listSubmissions:e=>t(`/forms/${e}/submissions`,{method:"GET"}),clearSubmissions:e=>t(`/forms/${e}/submissions`,{method:"DELETE"}),deleteSubmission:(e,o)=>t(`/forms/${e}/submissions/${o}`,{method:"DELETE"}),testEmail:e=>t("/forms/test-email",{method:"POST",body:JSON.stringify({to:e})})},views:{list:()=>t("/views",{method:"GET"}),get:e=>t(`/views/${e}`,{method:"GET"}),create:e=>t("/views",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/views/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/views/${e}`,{method:"DELETE"}),execute:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/views/${e}/execute${s?"?"+s:""}`,{method:"GET"})},forCollection:e=>t(`/views/collection/${e}`,{method:"GET"})},actions:{list:()=>t("/actions",{method:"GET"}),get:e=>t(`/actions/${e}`,{method:"GET"}),create:e=>t("/actions",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/actions/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/actions/${e}`,{method:"DELETE"}),execute:(e,o)=>t(`/actions/${e}/execute`,{method:"POST",body:JSON.stringify({entryId:o})}),forCollection:e=>t(`/actions/collection/${e}`,{method:"GET"}),checkAccess:(e,o)=>t(`/actions/${e}/check-access`,{method:"POST",body:JSON.stringify({entryIds:o})})},blocks:{list:()=>t("/blocks",{method:"GET"}),get:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"GET"}),put:(e,o)=>t(`/blocks/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"DELETE"})},get:e=>t(e,{method:"GET"}),post:(e,o)=>t(e,{method:"POST",body:JSON.stringify(o)}),put:(e,o)=>t(e,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(e,{method:"DELETE"}),themes:{list:()=>t("/plugins/theme-roller/themes",{method:"GET"})},settingsExt:{testEmail:e=>t("/settings/test-email",{method:"POST",body:JSON.stringify({to:e})})}};export function isAuthenticated(){return!!a()}export function getUser(){return S.get("auth_user")}export function setAuthData({token:e,refreshToken:o,user:s}){e&&m(e),o&&S.set("auth_refresh_token",o),s&&S.set("auth_user",s)}export function logout(){const e=h();e&&fetch(`${r}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),c(),R.navigate("/login")}export{t as apiRequest};
|
package/admin/js/app.js
CHANGED
|
@@ -1,231 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
$(() => {
|
|
11
|
-
// Fetch admin theme preference from server config; fall back to charcoal-dark
|
|
12
|
-
(async () => {
|
|
13
|
-
try {
|
|
14
|
-
const site = await api.settings.get();
|
|
15
|
-
Domma.theme.init({ theme: site.adminTheme || 'charcoal-dark', persist: true });
|
|
16
|
-
} catch {
|
|
17
|
-
Domma.theme.init({ theme: 'charcoal-dark', persist: true });
|
|
18
|
-
}
|
|
19
|
-
})();
|
|
20
|
-
|
|
21
|
-
// -------------------------------------------------------------------------
|
|
22
|
-
// Auth guard — redirect to login unless already on /login
|
|
23
|
-
// -------------------------------------------------------------------------
|
|
24
|
-
R.use(async (to, from, next) => {
|
|
25
|
-
if (to.path === '/login') return next();
|
|
26
|
-
|
|
27
|
-
if (!isAuthenticated()) {
|
|
28
|
-
R.navigate('/login');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
next();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// -------------------------------------------------------------------------
|
|
36
|
-
// Sidebar (role-aware — re-built after login lands on dashboard)
|
|
37
|
-
// -------------------------------------------------------------------------
|
|
38
|
-
let sidebar = null;
|
|
39
|
-
|
|
40
|
-
async function fetchSidebarCounts() {
|
|
41
|
-
if (!isAuthenticated()) return {};
|
|
42
|
-
try {
|
|
43
|
-
const [pages, media, users, plugins, collections] = await Promise.all([
|
|
44
|
-
api.pages.list().catch(() => []),
|
|
45
|
-
api.media.list().catch(() => []),
|
|
46
|
-
api.users.list().catch(() => []),
|
|
47
|
-
api.plugins.list().catch(() => []),
|
|
48
|
-
api.collections.list().catch(() => [])
|
|
49
|
-
]);
|
|
50
|
-
return {
|
|
51
|
-
pages: pages.length,
|
|
52
|
-
media: media.length,
|
|
53
|
-
users: users.length,
|
|
54
|
-
plugins: plugins.filter(p => p.enabled).length || plugins.length,
|
|
55
|
-
collections: collections.length
|
|
56
|
-
};
|
|
57
|
-
} catch {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function buildSidebar(role) {
|
|
63
|
-
if (sidebar) {
|
|
64
|
-
$('#admin-sidebar').empty();
|
|
65
|
-
}
|
|
66
|
-
const counts = await fetchSidebarCounts();
|
|
67
|
-
sidebar = Domma.elements.sidebar('#admin-sidebar', {
|
|
68
|
-
header: { title: 'CMS Admin', icon: 'layout' },
|
|
69
|
-
items: getSidebarConfig(role, counts, pluginSidebarItems),
|
|
70
|
-
collapsible: true,
|
|
71
|
-
collapseAt: 992,
|
|
72
|
-
push: true,
|
|
73
|
-
contentSelector: '.dashboard-main',
|
|
74
|
-
top: '60px'
|
|
75
|
-
});
|
|
76
|
-
attachSidebarTooltips();
|
|
77
|
-
attachCollapsibleHeadings();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function attachSidebarTooltips() {
|
|
81
|
-
$('#admin-sidebar .sidebar-link').each(function () {
|
|
82
|
-
const label = $(this).find('.sidebar-text').text().trim();
|
|
83
|
-
if (label) $(this).attr('data-tooltip', label);
|
|
84
|
-
});
|
|
85
|
-
E.tooltip('#admin-sidebar [data-tooltip]', { placement: 'right' });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function attachCollapsibleHeadings() {
|
|
89
|
-
$('#admin-sidebar .sidebar-heading').each(function () {
|
|
90
|
-
const $heading = $(this);
|
|
91
|
-
$heading.addClass('sidebar-heading--collapsible');
|
|
92
|
-
$heading.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>');
|
|
93
|
-
|
|
94
|
-
$heading.on('click', function () {
|
|
95
|
-
const collapsing = !$heading.hasClass('is-collapsed');
|
|
96
|
-
$heading.toggleClass('is-collapsed', collapsing);
|
|
97
|
-
|
|
98
|
-
let $el = $heading.next();
|
|
99
|
-
while ($el.length && !$el.hasClass('sidebar-heading') && !$el.hasClass('sidebar-divider')) {
|
|
100
|
-
$el.toggle(!collapsing);
|
|
101
|
-
$el = $el.next();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
Domma.icons.scan('#admin-sidebar');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Build with no role initially (login page has no sidebar)
|
|
110
|
-
buildSidebar(null);
|
|
111
|
-
|
|
112
|
-
// Keep sidebar active item in sync with route
|
|
113
|
-
M.subscribe('router:afterChange', ({ to }) => {
|
|
114
|
-
if (sidebar && to.path !== '/login') {
|
|
115
|
-
sidebar.setActive('#' + to.path);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// -------------------------------------------------------------------------
|
|
120
|
-
// Router — core routes + login
|
|
121
|
-
// -------------------------------------------------------------------------
|
|
122
|
-
const coreRoutes = [
|
|
123
|
-
{ path: '/', view: 'dashboard', title: 'Dashboard - Domma CMS' },
|
|
124
|
-
{ path: '/pages', view: 'pages', title: 'Pages - Domma CMS' },
|
|
125
|
-
{ path: '/pages/new', view: 'pageEditor', title: 'New Page - Domma CMS' },
|
|
126
|
-
{ path: '/pages/edit/*', view: 'pageEditor', title: 'Edit Page - Domma CMS' },
|
|
127
|
-
{ path: '/media', view: 'media', title: 'Media - Domma CMS' },
|
|
128
|
-
{ path: '/navigation', view: 'navigation', title: 'Navigation - Domma CMS' },
|
|
129
|
-
{ path: '/layouts', view: 'layouts', title: 'Layouts - Domma CMS' },
|
|
130
|
-
{ path: '/settings', view: 'settings', title: 'Settings - Domma CMS' },
|
|
131
|
-
{ path: '/users', view: 'users', title: 'Users - Domma CMS' },
|
|
132
|
-
{ path: '/users/new', view: 'userEditor', title: 'New User - Domma CMS' },
|
|
133
|
-
{ path: '/users/edit/:id', view: 'userEditor', title: 'Edit User - Domma CMS' },
|
|
134
|
-
{path: '/plugins', view: 'plugins', title: 'Plugins - Domma CMS'},
|
|
135
|
-
{path: '/documentation', view: 'documentation', title: 'Usage - Domma CMS'},
|
|
136
|
-
{path: '/tutorials', view: 'tutorials', title: 'Tutorials - Domma CMS'},
|
|
137
|
-
{path: '/collections', view: 'collections', title: 'Collections - Domma CMS'},
|
|
138
|
-
{path: '/collections/new', view: 'collectionEditor', title: 'New Collection - Domma CMS'},
|
|
139
|
-
{path: '/collections/edit/:slug', view: 'collectionEditor', title: 'Edit Collection - Domma CMS'},
|
|
140
|
-
{path: '/collections/:slug/entries',view: 'collectionEntries', title: 'Entries - Domma CMS'},
|
|
141
|
-
{ path: '/login', view: 'login', title: 'Sign in - Domma CMS',
|
|
142
|
-
onEnter: () => {
|
|
143
|
-
$('#admin-sidebar').hide();
|
|
144
|
-
$('#admin-topbar').hide();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
// After login, re-show chrome and rebuild sidebar with correct role
|
|
150
|
-
M.subscribe('router:afterChange', ({ to, from }) => {
|
|
151
|
-
if (to.path !== '/login') {
|
|
152
|
-
$('#admin-sidebar').show();
|
|
153
|
-
$('#admin-topbar').show();
|
|
154
|
-
if (from?.path === '/login') {
|
|
155
|
-
const user = getUser();
|
|
156
|
-
if (user) buildSidebar(user.role);
|
|
157
|
-
}
|
|
158
|
-
injectUserMenu();
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Reveal primary/danger buttons after each view renders
|
|
163
|
-
M.subscribe('router:afterChange', () => {
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
if ($('.btn-primary, .btn-danger').length) {
|
|
166
|
-
Domma.effects.reveal('.btn-primary, .btn-danger', {animation: 'fade', stagger: 40, duration: 300});
|
|
167
|
-
}
|
|
168
|
-
}, 50);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Collapsible cards — event delegation handles all views including plugins
|
|
172
|
-
$('#view-container').on('click', '.card-collapsible .card-header', function (e) {
|
|
173
|
-
if ($(e.target).closest('button, a').length) return;
|
|
174
|
-
$(this).closest('.card').toggleClass('card-collapsed');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Global Ctrl+S / Cmd+S — click the primary save button in any view
|
|
178
|
-
document.addEventListener('keydown', (e) => {
|
|
179
|
-
if (!(e.ctrlKey || e.metaKey) || e.key !== 's') return;
|
|
180
|
-
if (window.location.hash === '#/login') return;
|
|
181
|
-
const saveBtn = document.querySelector('#view-container .view-header button.btn-primary');
|
|
182
|
-
if (saveBtn) {
|
|
183
|
-
e.preventDefault();
|
|
184
|
-
saveBtn.click();
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// -------------------------------------------------------------------------
|
|
189
|
-
// Dynamic plugin routes (loaded before R.init)
|
|
190
|
-
// -------------------------------------------------------------------------
|
|
191
|
-
const allViews = { ...coreViews };
|
|
192
|
-
const allRoutes = [...coreRoutes];
|
|
193
|
-
let pluginSidebarItems = [];
|
|
194
|
-
|
|
195
|
-
async function loadPlugins() {
|
|
196
|
-
if (!isAuthenticated()) return;
|
|
197
|
-
try {
|
|
198
|
-
const pluginConfig = await api.plugins.adminConfig();
|
|
199
|
-
pluginSidebarItems = pluginConfig.sidebar || [];
|
|
200
|
-
if (pluginConfig.routes?.length) allRoutes.push(...pluginConfig.routes);
|
|
201
|
-
for (const [viewName, viewDef] of Object.entries(pluginConfig.views || {})) {
|
|
202
|
-
try {
|
|
203
|
-
const mod = await import(`/plugins/${viewDef.entry}`);
|
|
204
|
-
allViews[viewName] = mod[viewDef.exportName];
|
|
205
|
-
} catch { /* plugin view load failure is non-fatal */ }
|
|
206
|
-
}
|
|
207
|
-
} catch { /* plugins endpoint unreachable — continue without plugins */ }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// -------------------------------------------------------------------------
|
|
211
|
-
// Inject user info + sign out into topbar
|
|
212
|
-
// -------------------------------------------------------------------------
|
|
213
|
-
function injectUserMenu() {
|
|
214
|
-
const user = getUser();
|
|
215
|
-
if (!user) return;
|
|
216
|
-
if ($('#topbar-user-name').length) return; // already injected
|
|
217
|
-
|
|
218
|
-
const roleLabelMap = {
|
|
219
|
-
admin: 'Admin', manager: 'Manager', editor: 'Editor', subscriber: 'Subscriber'
|
|
220
|
-
};
|
|
221
|
-
const roleLabel = roleLabelMap[user.role] || user.role;
|
|
222
|
-
|
|
223
|
-
$('#topbar-user').html(`
|
|
224
|
-
<span id="topbar-user-name" class="topbar-user-name">${escapeHtml(user.name)}</span>
|
|
225
|
-
<span class="topbar-role-badge topbar-role-badge--${escapeHtml(user.role)}">${roleLabel}</span>
|
|
226
|
-
`);
|
|
227
|
-
|
|
228
|
-
$('#topbar-actions').html(`
|
|
1
|
+
import{getSidebarConfig as L}from"./config/sidebar-config.js";import{views as U}from"./views/index.js";import{api as s,getUser as b,isAuthenticated as n,logout as V}from"./api.js";$(()=>{(async()=>{try{const t=n()?await s.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})(),R.use(async(t,a,e)=>{if(t.path==="/login"||t.path==="/reset-password")return e();if(!n()){R.navigate("/login");return}e()});let r=null;async function f(){if(!n())return{};try{const[t,a,e,i,o,c,D,y,k,P,N,x,A]=await Promise.all([s.pages.list().catch(()=>[]),s.media.list().catch(()=>[]),s.users.list().catch(()=>[]),s.plugins.list().catch(()=>[]),s.collections.list().catch(()=>[]),s.forms.list().catch(()=>[]),s.themes.list().catch(()=>[]),s.views.list().catch(()=>[]),s.actions.list().catch(()=>[]),s.blocks.list().catch(()=>[]),s.navigation.get().catch(()=>({})),s.layouts.get().catch(()=>({})),s.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);return{pages:t.length,media:a.length,users:e.length,plugins:i.filter(W=>W.enabled).length||i.length,collections:o.length,forms:c.length,themes:D.length,views:y.length,actions:k.length,blocks:P.length,navigation:(N.items||[]).length,layouts:Object.keys(x).length,roles:(A.entries||[]).length}}catch{return{}}}async function m(){try{return(await s.get("/auth/permissions")).permissions||[]}catch{return[]}}async function d(t){r&&$("#admin-sidebar").empty();const a=await f(),e=h.map(i=>{if(!i.countKey)return i;const o=a[i.countKey];return{...i,badge:o!=null&&o>0?String(o):null}});r=Domma.elements.sidebar("#admin-sidebar",{header:{title:"CMS Admin",icon:"layout"},items:L(t,a,e),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),v(),C()}function v(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&$(this).attr("data-tooltip",t)}),E.tooltip("#admin-sidebar [data-tooltip]",{placement:"right"})}function C(){$("#admin-sidebar .sidebar-heading").each(function(){const t=$(this);t.addClass("sidebar-heading--collapsible"),t.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>'),t.on("click",function(){const a=!t.hasClass("is-collapsed");t.toggleClass("is-collapsed",a);let e=t.next();for(;e.length&&!e.hasClass("sidebar-heading")&&!e.hasClass("sidebar-divider");)e.toggle(!a),e=e.next()})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{r&&t.path!=="/login"&&t.path!=="/reset-password"&&r.setActive("#"+t.path)});const S=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/navigation",view:"navigation",title:"Navigation - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:a})=>{if(t.path!=="/login"&&t.path!=="/reset-password"){if($("#admin-sidebar").show(),$("#admin-topbar").show(),a?.path==="/login"||a?.path==="/reset-password"){await u();const e=await m();d(e)}g()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){$(t.target).closest("button, a").length||$(this).closest(".card").toggleClass("card-collapsed")}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const a=document.querySelector("#view-container .view-header button.btn-primary");a&&(t.preventDefault(),a.click())});const p={...U},l=[...S];let h=[];async function u(){if(n())try{const t=await s.plugins.adminConfig();h=t.sidebar||[],t.routes?.length&&l.push(...t.routes);for(const[a,e]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${e.entry}`);p[a]=i[e.exportName]}catch{}}catch{}}function g(){const t=b();if(!t||$("#topbar-user-name").length)return;const e={admin:"Admin",manager:"Manager",editor:"Editor",subscriber:"Subscriber"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
|
+
<span id="topbar-user-name" class="topbar-user-name">${w(t.name)}</span>
|
|
3
|
+
<span class="topbar-role-badge topbar-role-badge--${w(t.role)}">${e}</span>
|
|
4
|
+
`),$("#topbar-actions").html(`
|
|
5
|
+
<a href="#/my-profile" class="topbar-action-link" data-tooltip="My Profile" data-tooltip-placement="bottom">
|
|
6
|
+
<span data-icon="user"></span>
|
|
7
|
+
<span>My Profile</span>
|
|
8
|
+
</a>
|
|
229
9
|
<a href="#/settings" class="topbar-action-link" data-tooltip="Settings" data-tooltip-placement="bottom">
|
|
230
10
|
<span data-icon="settings"></span>
|
|
231
11
|
<span>Settings</span>
|
|
@@ -234,54 +14,4 @@ $(() => {
|
|
|
234
14
|
<span data-icon="log-out"></span>
|
|
235
15
|
<span>Sign out</span>
|
|
236
16
|
</a>
|
|
237
|
-
`);
|
|
238
|
-
|
|
239
|
-
$('#topbar-logout-btn').on('click', (e) => { e.preventDefault(); logout(); });
|
|
240
|
-
Domma.icons.scan('#admin-topbar');
|
|
241
|
-
E.tooltip('#topbar-actions [data-tooltip]', { placement: 'bottom' });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function escapeHtml(str) {
|
|
245
|
-
return String(str)
|
|
246
|
-
.replace(/&/g, '&')
|
|
247
|
-
.replace(/</g, '<')
|
|
248
|
-
.replace(/>/g, '>');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// -------------------------------------------------------------------------
|
|
252
|
-
// Boot sequence
|
|
253
|
-
// -------------------------------------------------------------------------
|
|
254
|
-
(async () => {
|
|
255
|
-
await loadPlugins();
|
|
256
|
-
|
|
257
|
-
const user = getUser();
|
|
258
|
-
if (user) {
|
|
259
|
-
await buildSidebar(user.role);
|
|
260
|
-
injectUserMenu();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
R.init({
|
|
264
|
-
container: '#view-container',
|
|
265
|
-
routes: allRoutes,
|
|
266
|
-
views: allViews,
|
|
267
|
-
default: '/',
|
|
268
|
-
transitions: { enter: 'fadeIn', leave: 'fadeOut', duration: 150 }
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Patch Domma router to support /* wildcard routes (multi-segment catch-all)
|
|
272
|
-
const origExtract = R._extractParams.bind(R);
|
|
273
|
-
R._extractParams = function(routePath, actualPath) {
|
|
274
|
-
if (routePath.endsWith('/*')) {
|
|
275
|
-
const prefix = routePath.slice(0, -2);
|
|
276
|
-
return actualPath.startsWith(prefix + '/') ? {} : null;
|
|
277
|
-
}
|
|
278
|
-
return origExtract(routePath, actualPath);
|
|
279
|
-
};
|
|
280
|
-
// Only re-trigger if the current URL needs the wildcard patch (e.g. /pages/edit/about)
|
|
281
|
-
const currentPath = (window.location.hash || '#/').slice(1) || '/';
|
|
282
|
-
const needsWildcard = allRoutes
|
|
283
|
-
.filter(r => r.path.endsWith('/*'))
|
|
284
|
-
.some(r => currentPath.startsWith(r.path.slice(0, -2) + '/'));
|
|
285
|
-
if (needsWildcard) R._handleRouteChange();
|
|
286
|
-
})();
|
|
287
|
-
});
|
|
17
|
+
`),$("#topbar-logout-btn").on("click",i=>{i.preventDefault(),V()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"})}function w(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}(async()=>{if(!n()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else if(await u(),b()){const o=await m();await d(o),g()}R.init({container:"#view-container",routes:l,views:p,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,o){if(i.endsWith("/*")){const c=i.slice(0,-2);return o.startsWith(c+"/")?{}:null}return t(i,o)};const a=(window.location.hash||"#/").slice(1)||"/";l.filter(i=>i.path.endsWith("/*")).some(i=>a.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
|