domma-cms 0.13.5 → 0.14.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 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +24 -18
- package/admin/js/lib/scribe-composer.js +4 -0
- package/admin/js/lib/simple-editor.js +49 -0
- package/admin/js/templates/block-editor.html +76 -18
- package/admin/js/templates/blocks.html +18 -8
- package/admin/js/templates/component-editor.html +141 -0
- package/admin/js/templates/components.html +18 -0
- package/admin/js/views/block-editor-enhance.js +1 -0
- package/admin/js/views/block-editor.js +8 -8
- package/admin/js/views/blocks.js +11 -4
- package/admin/js/views/component-editor.js +28 -0
- package/admin/js/views/components.js +11 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/page-editor.js +6 -6
- package/admin/js/views/pages.js +5 -2
- package/config/navigation.json +5 -0
- package/config/plugins.json +5 -5
- package/config/presets.json +92 -40
- package/config/site.json +75 -8
- package/package.json +2 -2
- package/public/css/site.css +1 -1
- package/server/routes/api/blocks.js +128 -60
- package/server/routes/api/components.js +115 -0
- package/server/routes/api/layouts.js +24 -0
- package/server/routes/api/pages.js +135 -132
- package/server/routes/api/versions.js +16 -0
- package/server/server.js +6 -0
- package/server/services/blocks.js +387 -284
- package/server/services/components.js +653 -0
- package/server/services/content.js +334 -334
- package/server/services/hooks.js +28 -0
- package/server/services/markdown.js +2836 -2629
- package/server/services/permissionRegistry.js +13 -0
- package/server/services/renderer.js +13 -3
- package/server/services/versions.js +37 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components API
|
|
3
|
+
* CRUD + compilation + export/import for .dmc components.
|
|
4
|
+
*
|
|
5
|
+
* GET /api/components - list
|
|
6
|
+
* GET /api/components/:name - raw source + parsed props
|
|
7
|
+
* GET /api/components/:name.js - compiled JS module (public)
|
|
8
|
+
* POST /api/components/compile - transient compile for preview
|
|
9
|
+
* GET /api/components/:name/export - download .dmcomponent.json
|
|
10
|
+
* POST /api/components/import - import .dmcomponent.json
|
|
11
|
+
* PUT /api/components/:name - create/update (compiles first)
|
|
12
|
+
* DELETE /api/components/:name - delete
|
|
13
|
+
*/
|
|
14
|
+
import {authenticate, requirePermission} from '../../middleware/auth.js';
|
|
15
|
+
import {
|
|
16
|
+
compileComponent,
|
|
17
|
+
deleteComponent,
|
|
18
|
+
exportBundle,
|
|
19
|
+
getCompiledJs,
|
|
20
|
+
getComponent,
|
|
21
|
+
importBundle,
|
|
22
|
+
listComponents,
|
|
23
|
+
saveComponent
|
|
24
|
+
} from '../../services/components.js';
|
|
25
|
+
|
|
26
|
+
export async function componentsRoutes(fastify) {
|
|
27
|
+
const canRead = {preHandler: [authenticate, requirePermission('components', 'read')]};
|
|
28
|
+
const canUpdate = {preHandler: [authenticate, requirePermission('components', 'update')]};
|
|
29
|
+
const canDelete = {preHandler: [authenticate, requirePermission('components', 'delete')]};
|
|
30
|
+
|
|
31
|
+
// Public: compiled JS module (served to every page visitor).
|
|
32
|
+
fastify.get('/components/:name.js', async (request, reply) => {
|
|
33
|
+
const {name} = request.params;
|
|
34
|
+
try {
|
|
35
|
+
const {js, errors, mtime} = await getCompiledJs(name);
|
|
36
|
+
if (errors.length) return reply.status(500).send({error: 'Compile failed', errors});
|
|
37
|
+
reply.header('Content-Type', 'application/javascript; charset=utf-8');
|
|
38
|
+
reply.header('Cache-Control', 'public, max-age=60, must-revalidate');
|
|
39
|
+
reply.header('ETag', `"${name}-${mtime}"`);
|
|
40
|
+
return js;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
43
|
+
if (err.code === 'ENOENT') return reply.status(404).send({error: 'Component not found'});
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
fastify.get('/components', canRead, () => listComponents());
|
|
49
|
+
|
|
50
|
+
fastify.get('/components/:name', canRead, async (request, reply) => {
|
|
51
|
+
try { return await getComponent(request.params.name); }
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
54
|
+
if (err.code === 'ENOENT') return reply.status(404).send({error: 'Component not found'});
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
fastify.post('/components/compile', canUpdate, async (request, reply) => {
|
|
60
|
+
const {source, name} = request.body || {};
|
|
61
|
+
if (typeof source !== 'string' || typeof name !== 'string') {
|
|
62
|
+
return reply.status(400).send({error: 'source and name are required'});
|
|
63
|
+
}
|
|
64
|
+
return compileComponent(source, name);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
fastify.get('/components/:name/export', canRead, async (request, reply) => {
|
|
68
|
+
const {name} = request.params;
|
|
69
|
+
try {
|
|
70
|
+
const bundle = await exportBundle(name);
|
|
71
|
+
reply.header('Content-Type', 'application/json; charset=utf-8');
|
|
72
|
+
reply.header('Content-Disposition', `attachment; filename="${name}.dmcomponent.json"`);
|
|
73
|
+
return JSON.stringify(bundle, null, 2);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
76
|
+
if (err.code === 'ENOENT') return reply.status(404).send({error: 'Component not found'});
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
fastify.post('/components/import', canUpdate, async (request, reply) => {
|
|
82
|
+
try { return await importBundle(request.body, {overwrite: !!request.body?.overwrite}); }
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err.code === 'CONFLICT') return reply.status(409).send({error: err.message, name: err.name});
|
|
85
|
+
if (err.code === 'INVALID_BUNDLE') return reply.status(400).send({error: err.message});
|
|
86
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
fastify.put('/components/:name', canUpdate, async (request, reply) => {
|
|
92
|
+
const {name} = request.params;
|
|
93
|
+
const {source, bundled} = request.body || {};
|
|
94
|
+
if (typeof source !== 'string') return reply.status(400).send({error: 'source (string) is required'});
|
|
95
|
+
try {
|
|
96
|
+
const result = await saveComponent(name, source, {bundled: !!bundled});
|
|
97
|
+
if (!result.success) return reply.status(400).send({error: 'Compile failed', errors: result.errors});
|
|
98
|
+
return result;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
101
|
+
if (err.code === 'PLUGIN_OWNED') return reply.status(409).send({error: err.message});
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
fastify.delete('/components/:name', canDelete, async (request, reply) => {
|
|
107
|
+
try { await deleteComponent(request.params.name); return reply.status(204).send(); }
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (err.code === 'INVALID_NAME') return reply.status(400).send({error: err.message});
|
|
110
|
+
if (err.code === 'ENOENT') return reply.status(404).send({error: 'Component not found'});
|
|
111
|
+
if (err.code === 'PLUGIN_OWNED') return reply.status(409).send({error: err.message});
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -22,12 +22,36 @@ const BUILTIN_PRESETS = {
|
|
|
22
22
|
builtin: true, navbar: true, footer: true, sidebar: false,
|
|
23
23
|
width: 'normal', bgColor: '', bgImage: '', class: ''
|
|
24
24
|
},
|
|
25
|
+
article: {
|
|
26
|
+
key: 'article', label: 'Article',
|
|
27
|
+
description: 'Narrow reading column for long-form prose and blog posts.',
|
|
28
|
+
builtin: true, navbar: true, footer: true, sidebar: false,
|
|
29
|
+
width: 'narrow', bgColor: '', bgImage: '', class: ''
|
|
30
|
+
},
|
|
31
|
+
wide: {
|
|
32
|
+
key: 'wide', label: 'Wide',
|
|
33
|
+
description: 'Standard page with navbar and footer, using a wide content column (~75% of viewport).',
|
|
34
|
+
builtin: true, navbar: true, footer: true, sidebar: false,
|
|
35
|
+
width: 'wide', bgColor: '', bgImage: '', class: ''
|
|
36
|
+
},
|
|
25
37
|
landing: {
|
|
26
38
|
key: 'landing', label: 'Landing Page',
|
|
27
39
|
description: 'Full-width landing page with navbar, no footer.',
|
|
28
40
|
builtin: true, navbar: true, footer: false, sidebar: false,
|
|
29
41
|
width: 'full', bgColor: '', bgImage: '', class: ''
|
|
30
42
|
},
|
|
43
|
+
product: {
|
|
44
|
+
key: 'product', label: 'Product Page',
|
|
45
|
+
description: 'Full-width marketing page that keeps the footer.',
|
|
46
|
+
builtin: true, navbar: true, footer: true, sidebar: false,
|
|
47
|
+
width: 'full', bgColor: '', bgImage: '', class: ''
|
|
48
|
+
},
|
|
49
|
+
dashboard: {
|
|
50
|
+
key: 'dashboard', label: 'Dashboard',
|
|
51
|
+
description: 'Wide layout for data-heavy or app-like pages; no footer.',
|
|
52
|
+
builtin: true, navbar: true, footer: false, sidebar: false,
|
|
53
|
+
width: 'wide', bgColor: '', bgImage: '', class: ''
|
|
54
|
+
},
|
|
31
55
|
blank: {
|
|
32
56
|
key: 'blank', label: 'Blank',
|
|
33
57
|
description: 'Minimal page with no navbar or footer.',
|
|
@@ -1,132 +1,135 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pages API
|
|
3
|
-
* GET /api/pages - list all pages
|
|
4
|
-
* GET /api/pages/* - get single page by URL path
|
|
5
|
-
* POST /api/pages - create page
|
|
6
|
-
* PUT /api/pages/* - update page
|
|
7
|
-
* DELETE /api/pages/* - delete page
|
|
8
|
-
*/
|
|
9
|
-
import {createPage, deletePage, getPage, listPages, renamePage, updatePage} from '../../services/content.js';
|
|
10
|
-
import {parseMarkdown} from '../../services/markdown.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
return reply.status(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await
|
|
107
|
-
return {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Pages API
|
|
3
|
+
* GET /api/pages - list all pages
|
|
4
|
+
* GET /api/pages/* - get single page by URL path
|
|
5
|
+
* POST /api/pages - create page
|
|
6
|
+
* PUT /api/pages/* - update page
|
|
7
|
+
* DELETE /api/pages/* - delete page
|
|
8
|
+
*/
|
|
9
|
+
import {createPage, deletePage, getPage, listPages, renamePage, updatePage} from '../../services/content.js';
|
|
10
|
+
import {parseMarkdown} from '../../services/markdown.js';
|
|
11
|
+
import {getVersionCount} from '../../services/versions.js';
|
|
12
|
+
import {authenticate, requirePermission} from '../../middleware/auth.js';
|
|
13
|
+
import {getConfig, saveConfig} from '../../config.js';
|
|
14
|
+
|
|
15
|
+
export async function pagesRoutes(fastify) {
|
|
16
|
+
const canRead = {preHandler: [authenticate, requirePermission('pages', 'read')]};
|
|
17
|
+
const canCreate = {preHandler: [authenticate, requirePermission('pages', 'create')]};
|
|
18
|
+
const canUpdate = {preHandler: [authenticate, requirePermission('pages', 'update')]};
|
|
19
|
+
const canDelete = {preHandler: [authenticate, requirePermission('pages', 'delete')]};
|
|
20
|
+
|
|
21
|
+
// Render markdown preview (shortcodes + sanitize, no frontmatter)
|
|
22
|
+
fastify.post('/pages/preview', canRead, async (request, reply) => {
|
|
23
|
+
const {markdown} = request.body;
|
|
24
|
+
if (typeof markdown !== 'string') return reply.status(400).send({error: 'markdown must be a string'});
|
|
25
|
+
const {html} = await parseMarkdown(`---\ntitle: preview\n---\n${markdown}`);
|
|
26
|
+
return {html};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Aggregate unique tags from all pages — must be registered before the wildcard /pages/* route
|
|
30
|
+
fastify.get('/pages/tags', canRead, async (request, reply) => {
|
|
31
|
+
const pages = await listPages();
|
|
32
|
+
const tagSet = new Set();
|
|
33
|
+
pages.forEach(p => {
|
|
34
|
+
if (Array.isArray(p.tags)) p.tags.forEach(t => { if (t) tagSet.add(t); });
|
|
35
|
+
});
|
|
36
|
+
return { tags: [...tagSet].sort() };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// List all pages
|
|
40
|
+
fastify.get('/pages', canRead, async (request, reply) => {
|
|
41
|
+
const pages = await listPages();
|
|
42
|
+
const counts = await Promise.all(pages.map(p => getVersionCount(p.urlPath)));
|
|
43
|
+
return pages.map((p, i) => ({
|
|
44
|
+
urlPath: p.urlPath,
|
|
45
|
+
title: p.title,
|
|
46
|
+
slug: p.slug,
|
|
47
|
+
status: p.status,
|
|
48
|
+
layout: p.layout,
|
|
49
|
+
showInNav: p.showInNav,
|
|
50
|
+
sortOrder: p.sortOrder,
|
|
51
|
+
category: p.category || null,
|
|
52
|
+
visibility: p.visibility || 'public',
|
|
53
|
+
tags: p.tags || [],
|
|
54
|
+
updatedAt: p.updatedAt,
|
|
55
|
+
createdAt: p.createdAt,
|
|
56
|
+
versionCount: counts[i]
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Get single page
|
|
61
|
+
fastify.get('/pages/*', canRead, async (request, reply) => {
|
|
62
|
+
const urlPath = '/' + request.params['*'];
|
|
63
|
+
const page = await getPage(urlPath);
|
|
64
|
+
if (!page) return reply.status(404).send({ error: 'Page not found' });
|
|
65
|
+
return page;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Create page
|
|
69
|
+
fastify.post('/pages', canCreate, async (request, reply) => {
|
|
70
|
+
const { urlPath, frontmatter, body } = request.body;
|
|
71
|
+
if (!urlPath) return reply.status(400).send({ error: 'urlPath is required' });
|
|
72
|
+
|
|
73
|
+
const existing = await getPage(urlPath);
|
|
74
|
+
if (existing) return reply.status(409).send({ error: 'Page already exists at that path' });
|
|
75
|
+
|
|
76
|
+
const page = await createPage(urlPath, frontmatter || {}, body || '');
|
|
77
|
+
return reply.status(201).send(page);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Update page (optionally rename to a new URL path)
|
|
81
|
+
fastify.put('/pages/*', canUpdate, async (request, reply) => {
|
|
82
|
+
const urlPath = '/' + request.params['*'];
|
|
83
|
+
const { frontmatter, body, newUrlPath } = request.body;
|
|
84
|
+
|
|
85
|
+
const existing = await getPage(urlPath);
|
|
86
|
+
if (!existing) return reply.status(404).send({ error: 'Page not found' });
|
|
87
|
+
|
|
88
|
+
if (newUrlPath && newUrlPath !== urlPath) {
|
|
89
|
+
const conflict = await getPage(newUrlPath);
|
|
90
|
+
if (conflict) return reply.status(409).send({ error: 'A page already exists at that path' });
|
|
91
|
+
|
|
92
|
+
await renamePage(urlPath, newUrlPath);
|
|
93
|
+
await rewriteNavLinks(urlPath, newUrlPath);
|
|
94
|
+
|
|
95
|
+
const page = await updatePage(newUrlPath, frontmatter || {}, body, {author: request.user.username});
|
|
96
|
+
return page;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const page = await updatePage(urlPath, frontmatter || {}, body, {author: request.user.username});
|
|
100
|
+
return page;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Delete page
|
|
104
|
+
fastify.delete('/pages/*', canDelete, async (request, reply) => {
|
|
105
|
+
const urlPath = '/' + request.params['*'];
|
|
106
|
+
const existing = await getPage(urlPath);
|
|
107
|
+
if (!existing) return reply.status(404).send({ error: 'Page not found' });
|
|
108
|
+
|
|
109
|
+
await deletePage(urlPath);
|
|
110
|
+
return { success: true };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Rewrite any navigation item URLs that match oldUrlPath to newUrlPath,
|
|
116
|
+
* then persist the updated navigation config.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} oldUrlPath
|
|
119
|
+
* @param {string} newUrlPath
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
122
|
+
async function rewriteNavLinks(oldUrlPath, newUrlPath) {
|
|
123
|
+
const nav = getConfig('navigation');
|
|
124
|
+
if (!nav?.items?.length) return;
|
|
125
|
+
|
|
126
|
+
let changed = false;
|
|
127
|
+
for (const item of nav.items) {
|
|
128
|
+
if (item.url === oldUrlPath) { item.url = newUrlPath; changed = true; }
|
|
129
|
+
for (const child of item.children || []) {
|
|
130
|
+
if (child.url === oldUrlPath) { child.url = newUrlPath; changed = true; }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (changed) await saveConfig('navigation', nav);
|
|
135
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
deleteVersions,
|
|
15
15
|
getVersion,
|
|
16
16
|
listVersions,
|
|
17
|
+
pruneVersions,
|
|
17
18
|
restoreVersion
|
|
18
19
|
} from '../../services/versions.js';
|
|
19
20
|
import {authenticate, requirePermission} from '../../middleware/auth.js';
|
|
@@ -82,6 +83,21 @@ export async function versionsRoutes(fastify) {
|
|
|
82
83
|
}
|
|
83
84
|
});
|
|
84
85
|
|
|
86
|
+
// Prune versions — keep the most recent N (policy decided in pruneVersions)
|
|
87
|
+
fastify.post('/versions/prune/*', canDelete, async (request, reply) => {
|
|
88
|
+
const urlPath = '/' + request.params['*'];
|
|
89
|
+
const keep = Number.parseInt(request.body?.keep, 10);
|
|
90
|
+
if (!Number.isFinite(keep) || keep < 0) {
|
|
91
|
+
return reply.status(400).send({error: 'keep must be a non-negative integer'});
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = await pruneVersions(urlPath, {keep});
|
|
95
|
+
return result;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return reply.status(400).send({error: err.message});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
85
101
|
// Bulk delete versions (POST used as DELETE with body is poorly supported)
|
|
86
102
|
fastify.post('/versions/bulk-delete/*', canDelete, async (request, reply) => {
|
|
87
103
|
const urlPath = '/' + request.params['*'];
|
package/server/server.js
CHANGED
|
@@ -22,6 +22,8 @@ import {load as loadRoles, seed as seedRoles} from './services/roles.js';
|
|
|
22
22
|
import {ensureAllProfiles, seed as seedUserProfiles} from './services/userProfiles.js';
|
|
23
23
|
import {seedAll as seedPresetCollections} from './services/presetCollections.js';
|
|
24
24
|
import {seedDefaultBlocks} from './services/blocks.js';
|
|
25
|
+
import {seedDefaultComponents} from './services/components.js';
|
|
26
|
+
import {refreshComponentTagAllowlist} from './services/markdown.js';
|
|
25
27
|
|
|
26
28
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
29
|
const ROOT = path.resolve(__dirname, '..');
|
|
@@ -167,6 +169,8 @@ await seedUserProfiles();
|
|
|
167
169
|
await ensureAllProfiles();
|
|
168
170
|
await seedPresetCollections();
|
|
169
171
|
await seedDefaultBlocks();
|
|
172
|
+
await seedDefaultComponents();
|
|
173
|
+
await refreshComponentTagAllowlist();
|
|
170
174
|
|
|
171
175
|
// Serve uploaded media files — nosniff prevents browsers rendering spoofed content types
|
|
172
176
|
await app.register(staticPlugin, {
|
|
@@ -241,6 +245,7 @@ const { formsRoutes } = await import('./routes/api/forms.js');
|
|
|
241
245
|
const { viewsRoutes } = await import('./routes/api/views.js');
|
|
242
246
|
const { actionsRoutes } = await import('./routes/api/actions.js');
|
|
243
247
|
const {blocksRoutes} = await import('./routes/api/blocks.js');
|
|
248
|
+
const {componentsRoutes} = await import('./routes/api/components.js');
|
|
244
249
|
const {versionsRoutes} = await import('./routes/api/versions.js');
|
|
245
250
|
const {effectsRoutes} = await import('./routes/api/effects.js');
|
|
246
251
|
const {notificationsRoutes} = await import('./routes/api/notifications.js');
|
|
@@ -258,6 +263,7 @@ await app.register(formsRoutes, { prefix: '/api' });
|
|
|
258
263
|
await app.register(viewsRoutes, { prefix: '/api' });
|
|
259
264
|
await app.register(actionsRoutes, { prefix: '/api' });
|
|
260
265
|
await app.register(blocksRoutes, {prefix: '/api'});
|
|
266
|
+
await app.register(componentsRoutes, {prefix: '/api'});
|
|
261
267
|
await app.register(versionsRoutes, {prefix: '/api'});
|
|
262
268
|
await app.register(effectsRoutes, {prefix: '/api'});
|
|
263
269
|
await app.register(notificationsRoutes, {prefix: '/api'});
|