domma-cms 0.14.1 → 0.14.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"@fastify/jwt": "^10.0.0",
|
|
72
72
|
"@fastify/multipart": "^9.3.0",
|
|
73
73
|
"@fastify/rate-limit": "^10.3.0",
|
|
74
|
-
"@fastify/static": "
|
|
74
|
+
"@fastify/static": "9.1.1",
|
|
75
75
|
"bcryptjs": "^3.0.3",
|
|
76
|
-
"domma-js": "^0.22.
|
|
76
|
+
"domma-js": "^0.22.6",
|
|
77
77
|
"dotenv": "^17.2.3",
|
|
78
|
-
"fastify": "5.8.
|
|
78
|
+
"fastify": "5.8.5",
|
|
79
79
|
"gray-matter": "^4.0.3",
|
|
80
80
|
"marked": "^15.0.0",
|
|
81
81
|
"nodemailer": "8.0.5",
|
|
@@ -98,7 +98,10 @@ export function requirePermission(resource, action) {
|
|
|
98
98
|
export {getPermissionsForRole};
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
* Shorthand preHandler —
|
|
101
|
+
* Shorthand preHandler — admin-tier role (level ≤ 1) or above.
|
|
102
|
+
* Matches the base role hierarchy documented in roles.js:
|
|
103
|
+
* super-admin (0), admin (1), user (2).
|
|
104
|
+
* Both super-admin and admin pass; regular users and anything below do not.
|
|
102
105
|
*
|
|
103
106
|
* @param {FastifyRequest} request
|
|
104
107
|
* @param {FastifyReply} reply
|
|
@@ -108,7 +111,7 @@ export async function requireAdmin(request, reply) {
|
|
|
108
111
|
if (!request.user) {
|
|
109
112
|
return reply.code(401).send({ statusCode: 401, error: 'Unauthorised', message: 'Authentication required' });
|
|
110
113
|
}
|
|
111
|
-
if (getRoleLevel(request.user.role)
|
|
114
|
+
if (getRoleLevel(request.user.role) > 1) {
|
|
112
115
|
return reply.code(403).send({ statusCode: 403, error: 'Forbidden', message: 'Admin access required' });
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -413,6 +413,43 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
413
413
|
return `<div class="dm-collection-display accordion"${multiAttr}>\n${items}\n</div>`;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Render a collection as a Domma progression (timeline / roadmap).
|
|
418
|
+
* Output mirrors processTimelineBlocks so the same styling applies.
|
|
419
|
+
*
|
|
420
|
+
* @param {object[]} entries
|
|
421
|
+
* @param {object} opts
|
|
422
|
+
* @param {string} opts.titleField - entry data field used as item title
|
|
423
|
+
* @param {string} [opts.dateField] - optional field surfaced as data-date
|
|
424
|
+
* @param {string} [opts.statusField]- optional field surfaced as data-status
|
|
425
|
+
* @param {string} [opts.iconField] - optional field surfaced as data-icon
|
|
426
|
+
* @param {string} [opts.bodyField] - optional Markdown body field
|
|
427
|
+
* @param {string} opts.layout - vertical | centred | horizontal
|
|
428
|
+
* @param {string} opts.theme - minimal | corporate | modern
|
|
429
|
+
* @param {string} opts.mode - timeline | roadmap
|
|
430
|
+
* @param {string} opts.emptyMsg
|
|
431
|
+
* @returns {string}
|
|
432
|
+
*/
|
|
433
|
+
function renderCollectionTimeline(entries, opts) {
|
|
434
|
+
const {titleField, dateField, statusField, iconField, bodyField, layout, theme, mode, emptyMsg} = opts;
|
|
435
|
+
if (!entries.length) {
|
|
436
|
+
return `<div class="dm-collection-display dm-collection-empty"><p>${escapeHtmlText(emptyMsg)}</p></div>`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const items = entries.map(e => {
|
|
440
|
+
const title = escapeHtmlText(String(e.data?.[titleField] ?? e.id ?? '(untitled)'));
|
|
441
|
+
const date = dateField && e.data?.[dateField] != null ? ` data-date="${escapeAttr(String(e.data[dateField]))}"` : '';
|
|
442
|
+
const status = statusField && e.data?.[statusField] != null ? ` data-status="${escapeAttr(String(e.data[statusField]))}"` : '';
|
|
443
|
+
const icon = iconField && e.data?.[iconField] != null ? ` data-icon="${escapeAttr(String(e.data[iconField]))}"` : '';
|
|
444
|
+
const bodyHtml = bodyField
|
|
445
|
+
? marked.parse(String(e.data?.[bodyField] ?? ''))
|
|
446
|
+
: '';
|
|
447
|
+
return `<div class="dm-progression-item"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
448
|
+
}).join('\n');
|
|
449
|
+
|
|
450
|
+
return `<div class="dm-collection-display dm-progression" data-layout="${layout}" data-theme="${theme}" data-mode="${mode}">\n${items}\n</div>`;
|
|
451
|
+
}
|
|
452
|
+
|
|
416
453
|
/**
|
|
417
454
|
* Process [view slug="..." display="table|cards|list" /] shortcodes.
|
|
418
455
|
* Executes the View's aggregation pipeline and renders results using the
|
|
@@ -488,6 +525,21 @@ async function processViewBlocks(markdown) {
|
|
|
488
525
|
const bodyField = attrs['body-field'] || 'description';
|
|
489
526
|
const multiple = attrs.multiple === 'true';
|
|
490
527
|
replacement = renderCollectionAccordion(entries, accordionTitleField, bodyField, multiple, emptyMsg);
|
|
528
|
+
} else if (display === 'timeline') {
|
|
529
|
+
const timelineLayout = ['vertical', 'centred', 'horizontal'].includes(attrs.layout) ? attrs.layout : 'vertical';
|
|
530
|
+
const timelineTheme = ['minimal', 'corporate', 'modern'].includes(attrs.theme) ? attrs.theme : 'minimal';
|
|
531
|
+
const timelineMode = ['timeline', 'roadmap'].includes(attrs.mode) ? attrs.mode : 'timeline';
|
|
532
|
+
replacement = renderCollectionTimeline(entries, {
|
|
533
|
+
titleField: attrs['title-field'] || 'title',
|
|
534
|
+
dateField: attrs['date-field'] || '',
|
|
535
|
+
statusField: attrs['status-field'] || '',
|
|
536
|
+
iconField: attrs['icon-field'] || '',
|
|
537
|
+
bodyField: attrs['body-field'] || '',
|
|
538
|
+
layout: timelineLayout,
|
|
539
|
+
theme: timelineTheme,
|
|
540
|
+
mode: timelineMode,
|
|
541
|
+
emptyMsg,
|
|
542
|
+
});
|
|
491
543
|
} else if (display === 'block') {
|
|
492
544
|
const blockName = attrs.block || viewConfig?.display?.block || '';
|
|
493
545
|
if (blockName) {
|
|
@@ -607,6 +659,21 @@ async function processCollectionBlocks(markdown) {
|
|
|
607
659
|
const bodyField = attrs['body-field'] || 'description';
|
|
608
660
|
const multiple = attrs.multiple === 'true';
|
|
609
661
|
replacement = renderCollectionAccordion(entries, accordionTitleField, bodyField, multiple, emptyMsg);
|
|
662
|
+
} else if (display === 'timeline') {
|
|
663
|
+
const timelineLayout = ['vertical', 'centred', 'horizontal'].includes(attrs.layout) ? attrs.layout : 'vertical';
|
|
664
|
+
const timelineTheme = ['minimal', 'corporate', 'modern'].includes(attrs.theme) ? attrs.theme : 'minimal';
|
|
665
|
+
const timelineMode = ['timeline', 'roadmap'].includes(attrs.mode) ? attrs.mode : 'timeline';
|
|
666
|
+
replacement = renderCollectionTimeline(entries, {
|
|
667
|
+
titleField: attrs['title-field'] || 'title',
|
|
668
|
+
dateField: attrs['date-field'] || '',
|
|
669
|
+
statusField: attrs['status-field'] || '',
|
|
670
|
+
iconField: attrs['icon-field'] || '',
|
|
671
|
+
bodyField: attrs['body-field'] || '',
|
|
672
|
+
layout: timelineLayout,
|
|
673
|
+
theme: timelineTheme,
|
|
674
|
+
mode: timelineMode,
|
|
675
|
+
emptyMsg,
|
|
676
|
+
});
|
|
610
677
|
} else if (display === 'block') {
|
|
611
678
|
const blockName = attrs.block || '';
|
|
612
679
|
if (blockName) {
|
|
@@ -2265,22 +2332,31 @@ function processTableBlocks(markdown) {
|
|
|
2265
2332
|
* [/hero]
|
|
2266
2333
|
*
|
|
2267
2334
|
* Supported attributes:
|
|
2268
|
-
* title
|
|
2269
|
-
* tagline
|
|
2270
|
-
* size
|
|
2271
|
-
* variant
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
* image
|
|
2280
|
-
* overlay
|
|
2281
|
-
* align
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2335
|
+
* title - Hero heading (.hero-title)
|
|
2336
|
+
* tagline - Subtitle text (.hero-subtitle)
|
|
2337
|
+
* size - "sm", "lg", "full" → .hero-sm / .hero-lg / .hero-full
|
|
2338
|
+
* variant - "dark", "primary",
|
|
2339
|
+
* upstream 8: "gradient-purple", "gradient-blue", "gradient-green",
|
|
2340
|
+
* "gradient-sunset", "gradient-ocean", "gradient-rose",
|
|
2341
|
+
* "gradient-forest", "gradient-night"
|
|
2342
|
+
* theme-specific (26): "gradient-{theme}-{mode}" where theme is one of
|
|
2343
|
+
* ocean, forest, sunset, royal, lemon, silver, charcoal, christmas,
|
|
2344
|
+
* unicorn, dreamy, grayve, mint, wedding — and mode is light or dark
|
|
2345
|
+
* → .hero-{variant}
|
|
2346
|
+
* image - URL for background-image + adds .hero-cover
|
|
2347
|
+
* overlay - "light", "dark", "darker", "gradient", "gradient-reverse" → .hero-overlay-{overlay}
|
|
2348
|
+
* align - "center" (default) or "left" → .hero-center / .hero-left
|
|
2349
|
+
* bg / color - Background colour (any safe CSS colour value)
|
|
2350
|
+
* min-height - Minimum height (px, em, rem, vh, vw, %)
|
|
2351
|
+
* fullwidth - "true" breaks out of the page container to span the full viewport width (adds .hero-breakout).
|
|
2352
|
+
* Must be the literal string "true" — the attribute is otherwise ignored.
|
|
2353
|
+
* twinkle - Flag attribute — adds particle overlay (requires Effects plugin)
|
|
2354
|
+
* twinkle-count - Number of twinkle particles
|
|
2355
|
+
* twinkle-colour - Particle colour (CSS value)
|
|
2356
|
+
* blobs - Flag attribute — adds ambient blob background
|
|
2357
|
+
* blobs-type - Blob animation type (default: "float-blobs")
|
|
2358
|
+
* class - Extra classes appended to .hero
|
|
2359
|
+
* id - Element id attribute
|
|
2284
2360
|
*
|
|
2285
2361
|
* @param {string} markdown
|
|
2286
2362
|
* @returns {string}
|
|
@@ -2391,8 +2467,12 @@ function processCenterBlocks(markdown) {
|
|
|
2391
2467
|
(_, attrStr, body) => {
|
|
2392
2468
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
2393
2469
|
const classAttr = attrs.class ? ` class="${escapeAttr(attrs.class)}"` : '';
|
|
2394
|
-
|
|
2395
|
-
|
|
2470
|
+
// Emit the wrapper with blank lines around the body so CommonMark
|
|
2471
|
+
// parses markdown inside the div. DO NOT eagerly call marked.parse
|
|
2472
|
+
// here — that HTML-escapes attribute quotes on any unprocessed
|
|
2473
|
+
// shortcodes inside (e.g. [button href="..."] becomes href="...),
|
|
2474
|
+
// which breaks every later shortcode processor in the pipeline.
|
|
2475
|
+
return `\n<div style="text-align:center;"${classAttr}>\n\n${body.trim()}\n\n</div>\n`;
|
|
2396
2476
|
}
|
|
2397
2477
|
));
|
|
2398
2478
|
}
|