domma-cms 0.7.6 → 0.7.8
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/config/plugins.json +0 -8
- package/package.json +1 -1
- package/plugins/job-board/admin/templates/application-detail.html +0 -40
- package/plugins/job-board/admin/templates/applications.html +0 -10
- package/plugins/job-board/admin/templates/companies.html +0 -24
- package/plugins/job-board/admin/templates/dashboard.html +0 -36
- package/plugins/job-board/admin/templates/job-editor.html +0 -17
- package/plugins/job-board/admin/templates/jobs.html +0 -15
- package/plugins/job-board/admin/templates/profile.html +0 -17
- package/plugins/job-board/admin/views/application-detail.js +0 -62
- package/plugins/job-board/admin/views/applications.js +0 -47
- package/plugins/job-board/admin/views/companies.js +0 -104
- package/plugins/job-board/admin/views/dashboard.js +0 -88
- package/plugins/job-board/admin/views/job-editor.js +0 -86
- package/plugins/job-board/admin/views/jobs.js +0 -53
- package/plugins/job-board/admin/views/profile.js +0 -47
- package/plugins/job-board/config.js +0 -6
- package/plugins/job-board/plugin.js +0 -466
- package/plugins/job-board/plugin.json +0 -40
- package/plugins/job-board/schemas/jb-agent-companies.json +0 -17
- package/plugins/job-board/schemas/jb-applications.json +0 -20
- package/plugins/job-board/schemas/jb-candidate-profiles.json +0 -20
- package/plugins/job-board/schemas/jb-companies.json +0 -21
- package/plugins/job-board/schemas/jb-jobs.json +0 -23
- package/plugins/theme-roller/admin/templates/theme-roller.html +0 -71
- package/plugins/theme-roller/admin/views/theme-roller-view.js +0 -403
- package/plugins/theme-roller/config.js +0 -1
- package/plugins/theme-roller/plugin.js +0 -233
- package/plugins/theme-roller/plugin.json +0 -31
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +0 -1
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Roller Plugin — Server
|
|
3
|
-
* Manages custom themes: CRUD on JSON files, CSS generation, and site config updates.
|
|
4
|
-
*
|
|
5
|
-
* Endpoints (prefix: /api/plugins/theme-roller):
|
|
6
|
-
* GET /themes — admin: list saved themes
|
|
7
|
-
* GET /themes/:name — admin: get single theme
|
|
8
|
-
* POST /themes — admin: save new theme
|
|
9
|
-
* PUT /themes/:name — admin: update existing theme
|
|
10
|
-
* DELETE /themes/:name — admin: delete theme
|
|
11
|
-
* POST /themes/:name/activate — admin: activate theme (generate CSS + update site config)
|
|
12
|
-
* POST /deactivate — admin: revert to a built-in theme
|
|
13
|
-
*/
|
|
14
|
-
import fs from 'node:fs/promises';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import {fileURLToPath} from 'url';
|
|
17
|
-
import {getConfig, saveConfig} from '../../server/config.js';
|
|
18
|
-
|
|
19
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
const THEMES_DIR = path.join(__dirname, 'data', 'themes');
|
|
21
|
-
const CSS_FILE = path.join(__dirname, 'public', 'active-theme.css');
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Helpers
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
function themePath(name) {
|
|
28
|
-
return path.join(THEMES_DIR, `${name}.json`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function readTheme(name) {
|
|
32
|
-
return JSON.parse(await fs.readFile(themePath(name), 'utf8'));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function writeTheme(name, data) {
|
|
36
|
-
await fs.writeFile(themePath(name), JSON.stringify(data, null, 4) + '\n', 'utf8');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function listThemes() {
|
|
40
|
-
let entries;
|
|
41
|
-
try {
|
|
42
|
-
entries = await fs.readdir(THEMES_DIR);
|
|
43
|
-
} catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
const themes = [];
|
|
47
|
-
for (const f of entries) {
|
|
48
|
-
if (!f.endsWith('.json')) continue;
|
|
49
|
-
try {
|
|
50
|
-
themes.push(JSON.parse(await fs.readFile(path.join(THEMES_DIR, f), 'utf8')));
|
|
51
|
-
} catch { /* skip corrupt files */ }
|
|
52
|
-
}
|
|
53
|
-
return themes.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Generate CSS for a custom theme.
|
|
58
|
-
* Produces `.dm-theme-{name} { --var: value; }` matching exportCSS() format.
|
|
59
|
-
*
|
|
60
|
-
* @param {Object} themeData - The theme JSON object
|
|
61
|
-
* @returns {string} CSS string
|
|
62
|
-
*/
|
|
63
|
-
function generateCSS(themeData) {
|
|
64
|
-
const {name, changes} = themeData;
|
|
65
|
-
if (!changes || Object.keys(changes).length === 0) return '';
|
|
66
|
-
|
|
67
|
-
const date = new Date().toISOString().split('T')[0];
|
|
68
|
-
let css = `/**\n * Custom Domma Theme: ${name}\n * Generated: ${date}\n */\n\n`;
|
|
69
|
-
// Use :root so overrides apply regardless of which built-in theme class is on <body>.
|
|
70
|
-
// active-theme.css loads last (headInjectLate), so same-specificity :root wins by source order.
|
|
71
|
-
css += `:root {\n`;
|
|
72
|
-
for (const [variable, value] of Object.entries(changes)) {
|
|
73
|
-
css += ` ${variable}: ${value};\n`;
|
|
74
|
-
}
|
|
75
|
-
css += '}\n';
|
|
76
|
-
return css;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Write the active-theme.css file.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} css - CSS content (empty string = no custom theme)
|
|
83
|
-
*/
|
|
84
|
-
async function writeActiveCSS(css) {
|
|
85
|
-
const content = css || '/* Domma CMS — active custom theme (empty until a custom theme is activated) */\n';
|
|
86
|
-
await fs.writeFile(CSS_FILE, content, 'utf8');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Plugin registration
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
export default async function themeRollerPlugin(fastify, opts) {
|
|
94
|
-
const {authenticate, requireAdmin} = opts.auth;
|
|
95
|
-
|
|
96
|
-
// -----------------------------------------------------------------------
|
|
97
|
-
// GET /themes — list all saved themes
|
|
98
|
-
// -----------------------------------------------------------------------
|
|
99
|
-
fastify.get('/themes', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
100
|
-
return listThemes();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// -----------------------------------------------------------------------
|
|
104
|
-
// GET /themes/:name — get single theme
|
|
105
|
-
// -----------------------------------------------------------------------
|
|
106
|
-
fastify.get('/themes/:name', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
107
|
-
try {
|
|
108
|
-
return await readTheme(req.params.name);
|
|
109
|
-
} catch {
|
|
110
|
-
return reply.code(404).send({error: 'Theme not found'});
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// -----------------------------------------------------------------------
|
|
115
|
-
// POST /themes — save a new theme
|
|
116
|
-
// -----------------------------------------------------------------------
|
|
117
|
-
fastify.post('/themes', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
118
|
-
const {name, baseTheme, changes} = req.body || {};
|
|
119
|
-
|
|
120
|
-
if (!name || typeof name !== 'string') {
|
|
121
|
-
return reply.code(400).send({error: 'name is required'});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Sanitise name: lowercase alphanumeric + hyphens only
|
|
125
|
-
const safeName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
126
|
-
if (!safeName) return reply.code(400).send({error: 'Invalid theme name'});
|
|
127
|
-
|
|
128
|
-
const now = new Date().toISOString();
|
|
129
|
-
const theme = {
|
|
130
|
-
name: safeName,
|
|
131
|
-
baseTheme: baseTheme || 'charcoal-dark',
|
|
132
|
-
changes: changes || {},
|
|
133
|
-
createdAt: now,
|
|
134
|
-
updatedAt: now
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
await writeTheme(safeName, theme);
|
|
138
|
-
return reply.code(201).send(theme);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// -----------------------------------------------------------------------
|
|
142
|
-
// PUT /themes/:name — update an existing theme
|
|
143
|
-
// -----------------------------------------------------------------------
|
|
144
|
-
fastify.put('/themes/:name', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
145
|
-
const {name} = req.params;
|
|
146
|
-
let existing;
|
|
147
|
-
try {
|
|
148
|
-
existing = await readTheme(name);
|
|
149
|
-
} catch {
|
|
150
|
-
return reply.code(404).send({error: 'Theme not found'});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const {baseTheme, changes} = req.body || {};
|
|
154
|
-
const updated = {
|
|
155
|
-
...existing,
|
|
156
|
-
baseTheme: baseTheme ?? existing.baseTheme,
|
|
157
|
-
changes: changes ?? existing.changes,
|
|
158
|
-
updatedAt: new Date().toISOString()
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
await writeTheme(name, updated);
|
|
162
|
-
|
|
163
|
-
// If this is the currently active theme, regenerate CSS
|
|
164
|
-
const siteConfig = getConfig('site');
|
|
165
|
-
if (siteConfig.theme === name) {
|
|
166
|
-
await writeActiveCSS(generateCSS(updated));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return updated;
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// -----------------------------------------------------------------------
|
|
173
|
-
// DELETE /themes/:name — delete a theme
|
|
174
|
-
// -----------------------------------------------------------------------
|
|
175
|
-
fastify.delete('/themes/:name', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
176
|
-
const {name} = req.params;
|
|
177
|
-
try {
|
|
178
|
-
await fs.unlink(themePath(name));
|
|
179
|
-
} catch {
|
|
180
|
-
return reply.code(404).send({error: 'Theme not found'});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// If deleting the active theme, revert to charcoal-dark
|
|
184
|
-
const siteConfig = getConfig('site');
|
|
185
|
-
if (siteConfig.theme === name) {
|
|
186
|
-
await writeActiveCSS('');
|
|
187
|
-
await saveConfig('site', {...siteConfig, theme: 'charcoal-dark'});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return {success: true};
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// -----------------------------------------------------------------------
|
|
194
|
-
// POST /themes/:name/activate — activate a custom theme
|
|
195
|
-
// -----------------------------------------------------------------------
|
|
196
|
-
fastify.post('/themes/:name/activate', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
197
|
-
const {name} = req.params;
|
|
198
|
-
let theme;
|
|
199
|
-
try {
|
|
200
|
-
theme = await readTheme(name);
|
|
201
|
-
} catch {
|
|
202
|
-
return reply.code(404).send({error: 'Theme not found'});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Generate and write CSS
|
|
206
|
-
const css = generateCSS(theme);
|
|
207
|
-
await writeActiveCSS(css);
|
|
208
|
-
|
|
209
|
-
// Update site config so renderer applies the theme class to <body>
|
|
210
|
-
// baseTheme is saved so the renderer can apply both the base and custom class
|
|
211
|
-
const siteConfig = getConfig('site');
|
|
212
|
-
await saveConfig('site', {...siteConfig, theme: name, baseTheme: theme.baseTheme});
|
|
213
|
-
|
|
214
|
-
return {success: true, theme: name, css};
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// -----------------------------------------------------------------------
|
|
218
|
-
// POST /deactivate — revert to a built-in theme
|
|
219
|
-
// -----------------------------------------------------------------------
|
|
220
|
-
fastify.post('/deactivate', {preHandler: [authenticate, requireAdmin]}, async (req, reply) => {
|
|
221
|
-
const {theme: builtInTheme = 'charcoal-dark'} = req.body || {};
|
|
222
|
-
|
|
223
|
-
// Clear custom CSS
|
|
224
|
-
await writeActiveCSS('');
|
|
225
|
-
|
|
226
|
-
// Update site config to the chosen built-in theme, clearing custom baseTheme
|
|
227
|
-
const siteConfig = getConfig('site');
|
|
228
|
-
const {baseTheme: _, ...restConfig} = siteConfig;
|
|
229
|
-
await saveConfig('site', {...restConfig, theme: builtInTheme});
|
|
230
|
-
|
|
231
|
-
return {success: true, theme: builtInTheme};
|
|
232
|
-
});
|
|
233
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "theme-roller",
|
|
3
|
-
"displayName": "Theme Roller",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Visual theme customisation with live preview. Create, save, and apply custom themes.",
|
|
6
|
-
"author": "Darryl Waterhouse",
|
|
7
|
-
"date": "2026-03-06",
|
|
8
|
-
"icon": "palette",
|
|
9
|
-
"admin": {
|
|
10
|
-
"sidebar": [{
|
|
11
|
-
"id": "theme-roller",
|
|
12
|
-
"text": "Theme Roller",
|
|
13
|
-
"icon": "palette",
|
|
14
|
-
"url": "#/plugins/theme-roller",
|
|
15
|
-
"section": "#/plugins/theme-roller",
|
|
16
|
-
"countKey": "themes"
|
|
17
|
-
}],
|
|
18
|
-
"routes": [
|
|
19
|
-
{ "path": "/plugins/theme-roller", "view": "plugin-theme-roller", "title": "Theme Roller - Domma CMS" }
|
|
20
|
-
],
|
|
21
|
-
"views": {
|
|
22
|
-
"plugin-theme-roller": {
|
|
23
|
-
"entry": "theme-roller/admin/views/theme-roller-view.js",
|
|
24
|
-
"exportName": "themeRollerView"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"inject": {
|
|
29
|
-
"headLate": "public/inject-head-late.html"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<link rel="stylesheet" href="/plugins/theme-roller/public/active-theme.css">
|