domma-cms 0.117.0 → 0.119.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/js/lib/menu-editor-arrange.js +1 -1
- package/admin/js/lib/menu-rows.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +1 -1
- package/admin/js/templates/docs/api-forms.html +5 -0
- package/admin/js/templates/form-editor.html +11 -0
- package/admin/js/templates/menu-editor.html +6 -0
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/menu-editor.js +14 -14
- package/package.json +1 -1
- package/public/js/css-classes.mjs +1 -0
- package/public/js/form-submit.mjs +1 -1
- package/public/js/forms.js +2 -2
- package/public/js/menu-decor.mjs +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/forms.js +3 -3
- package/server/services/forms.js +2 -2
- package/server/services/menuRender.js +9 -3
- package/server/services/menus.js +4 -1
- package/server/services/renderer.js +12 -2
- package/server/templates/page.html +2 -2
|
@@ -26,7 +26,7 @@ import path from 'path';
|
|
|
26
26
|
import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm, slugify, writeForm} from '../../services/forms.js';
|
|
27
27
|
import {executeAction} from '../../services/actions.js';
|
|
28
28
|
// The same rules the browser draws the buttons by (a Cancel link must be safe).
|
|
29
|
-
import {buttonSettingsProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
|
|
29
|
+
import {buttonSettingsProblems, formClassProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
|
|
30
30
|
import {createTransport, sendFormEmail} from '../../services/email.js';
|
|
31
31
|
import {formActionFailed, formTriggerNotice} from '../../services/notification-sources.js';
|
|
32
32
|
import {
|
|
@@ -429,7 +429,7 @@ export async function formsRoutes(fastify) {
|
|
|
429
429
|
|
|
430
430
|
const now = new Date().toISOString();
|
|
431
431
|
const body = request.body || {};
|
|
432
|
-
const buttonProblems = buttonSettingsProblems(body.settings);
|
|
432
|
+
const buttonProblems = [...buttonSettingsProblems(body.settings), ...formClassProblems(body.settings, body.fields)];
|
|
433
433
|
if (buttonProblems.length) return reply.status(400).send({ error: buttonProblems[0] });
|
|
434
434
|
const form = {
|
|
435
435
|
slug,
|
|
@@ -527,7 +527,7 @@ export async function formsRoutes(fastify) {
|
|
|
527
527
|
const body = request.body || {};
|
|
528
528
|
// Only a Cancel link's address can do harm; refuse it rather than
|
|
529
529
|
// store something the page will not draw.
|
|
530
|
-
const buttonProblems = buttonSettingsProblems(body.settings);
|
|
530
|
+
const buttonProblems = [...buttonSettingsProblems(body.settings), ...formClassProblems(body.settings, body.fields)];
|
|
531
531
|
if (buttonProblems.length) return reply.status(400).send({ error: buttonProblems[0] });
|
|
532
532
|
const updated = {
|
|
533
533
|
...existing,
|
package/server/services/forms.js
CHANGED
|
@@ -7,7 +7,7 @@ import fs from 'fs/promises';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {fileURLToPath} from 'url';
|
|
9
9
|
import * as cache from './cache/index.js';
|
|
10
|
-
import {buttonSettingsProblems} from '../../public/js/form-submit.mjs';
|
|
10
|
+
import {buttonSettingsProblems, formClassProblems} from '../../public/js/form-submit.mjs';
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -134,7 +134,7 @@ export async function createForm(data) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// A Cancel link to javascript: and the like (public/js/form-submit.mjs).
|
|
137
|
-
const buttonProblems = buttonSettingsProblems(settings);
|
|
137
|
+
const buttonProblems = [...buttonSettingsProblems(settings), ...formClassProblems(settings, fields)];
|
|
138
138
|
if (buttonProblems.length) throw new Error(buttonProblems[0]);
|
|
139
139
|
|
|
140
140
|
// Throw if a form with this slug already exists
|
|
@@ -14,7 +14,7 @@ import {colourToCss, floatToCss, floatBehaviour, spacerSize} from './menus.js';
|
|
|
14
14
|
// Highlight helpers live once, in the browser module, and are imported here -
|
|
15
15
|
// the same shared-module arrangement collection-export.mjs has with
|
|
16
16
|
// collections.js. Nothing in them touches the DOM.
|
|
17
|
-
import {highlightAttrs, itemHighlightAttrs, isCurrentItem, menuAlign} from '../../public/js/menu-decor.mjs';
|
|
17
|
+
import {highlightAttrs, itemHighlightAttrs, isCurrentItem, menuAlign, cssClasses} from '../../public/js/menu-decor.mjs';
|
|
18
18
|
|
|
19
19
|
/** Attribute-safe escaping - mirrors escapeAttr in markdown.js. */
|
|
20
20
|
function esc(str) {
|
|
@@ -56,6 +56,10 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
|
|
|
56
56
|
// needs an explicit allowlist entry.
|
|
57
57
|
const current = isCurrentItem(it.url, currentPath);
|
|
58
58
|
const classAttr = current ? ' class="is-active" aria-current="page"' : '';
|
|
59
|
+
// The designer's own classes go on the <li>, so they can lay the item
|
|
60
|
+
// out (push it right, size it) as well as style its link.
|
|
61
|
+
const liClasses = cssClasses(it.class);
|
|
62
|
+
const liAttr = liClasses.length ? ` class="${esc(liClasses.join(' '))}"` : '';
|
|
59
63
|
|
|
60
64
|
// Per-item highlight override. `data-menu-item-*` rather than the
|
|
61
65
|
// menu's `data-menu-*` so the CSS can let the item win without the two
|
|
@@ -86,7 +90,7 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
|
|
|
86
90
|
badge = `<span class="dm-menu-badge"${badgeStyle}>${esc(String(it.badge.text))}</span>`;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
return `<li><a href="${href}"${classAttr}${styleAttr}${ihlAttrs}>${text}${badge}</a>${child}</li>`;
|
|
93
|
+
return `<li${liAttr}><a href="${href}"${classAttr}${styleAttr}${ihlAttrs}>${text}${badge}</a>${child}</li>`;
|
|
90
94
|
}).join('');
|
|
91
95
|
return `<ul>${lis}</ul>`;
|
|
92
96
|
}
|
|
@@ -106,7 +110,9 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
|
|
|
106
110
|
* @returns {string}
|
|
107
111
|
*/
|
|
108
112
|
export function buildMenuNav(menu, items, opts = {}) {
|
|
109
|
-
|
|
113
|
+
// The menu's own designer classes, then this placement's `[menu class=…]`.
|
|
114
|
+
const menuClasses = cssClasses(menu.class).join(' ');
|
|
115
|
+
const klass = [menuClasses, opts.class].filter(Boolean).map(c => ` ${esc(c)}`).join('');
|
|
110
116
|
const variantClass = opts.variant ? ` dm-menu--variant-${esc(opts.variant)}` : '';
|
|
111
117
|
|
|
112
118
|
// A menu that declares no orientation emits no class at all and keeps the
|
package/server/services/menus.js
CHANGED
|
@@ -19,7 +19,7 @@ import * as cache from './cache/index.js';
|
|
|
19
19
|
import {
|
|
20
20
|
HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE, MENU_ALIGN,
|
|
21
21
|
LEGACY_STYLE_HOVER, LEGACY_STYLE_ACTIVE, legacyStyleHighlight,
|
|
22
|
-
PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss
|
|
22
|
+
PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss, cssClassProblems
|
|
23
23
|
} from '../../public/js/menu-decor.mjs';
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -722,6 +722,8 @@ export function validateMenu(menu) {
|
|
|
722
722
|
}
|
|
723
723
|
}
|
|
724
724
|
}
|
|
725
|
+
// Designer classes on the menu's outer element - see cssClasses in menu-decor.mjs.
|
|
726
|
+
for (const p of cssClassProblems(menu.class)) errors.push(`class: ${p}`);
|
|
725
727
|
if (menu.align != null && menu.align !== '' && !MENU_ALIGN.includes(menu.align)) {
|
|
726
728
|
errors.push(`align must be one of: ${MENU_ALIGN.join(', ')}`);
|
|
727
729
|
}
|
|
@@ -815,6 +817,7 @@ export function validateMenu(menu) {
|
|
|
815
817
|
if (item.colour != null && !isValidColour(item.colour)) {
|
|
816
818
|
errors.push(`Item ${path}: colour must be a preset key or #rrggbb`);
|
|
817
819
|
}
|
|
820
|
+
for (const p of cssClassProblems(item.class)) errors.push(`Item ${path}: class: ${p}`);
|
|
818
821
|
// Per-item highlight override. Same shape and same value sets as
|
|
819
822
|
// the menu-level `highlight` block validated above - an item that
|
|
820
823
|
// omits a half falls through to the menu's choice for that half.
|
|
@@ -266,6 +266,8 @@ export async function renderPage(page, opts = {}) {
|
|
|
266
266
|
// Item highlighting: the navbar is built in the browser, so the
|
|
267
267
|
// config has to travel to site.js rather than being rendered here.
|
|
268
268
|
...(navMenu.highlight && {highlight: navMenu.highlight}),
|
|
269
|
+
// Designer classes for the navbar element itself (site.js adds them).
|
|
270
|
+
...(navMenu.class && {class: navMenu.class}),
|
|
269
271
|
...(navMenu.style && {style: navMenu.style})
|
|
270
272
|
}
|
|
271
273
|
: (await readLegacyNavBackup()) || {brand: site.brand || {}, items: []};
|
|
@@ -333,7 +335,10 @@ export async function renderPage(page, opts = {}) {
|
|
|
333
335
|
copyright: site.footer?.copyright || '',
|
|
334
336
|
// Highlighting is per menu, and the two footer columns are two menus.
|
|
335
337
|
...(footerPrimary?.highlight && {primaryHighlight: footerPrimary.highlight}),
|
|
336
|
-
...(footerLegal?.highlight && {legalHighlight: footerLegal.highlight})
|
|
338
|
+
...(footerLegal?.highlight && {legalHighlight: footerLegal.highlight}),
|
|
339
|
+
// Each column's menu-level designer classes, for its <nav>.
|
|
340
|
+
...(footerPrimary?.class && {primaryClass: footerPrimary.class}),
|
|
341
|
+
...(footerLegal?.class && {legalClass: footerLegal.class})
|
|
337
342
|
};
|
|
338
343
|
const footerJson = JSON.stringify(footer).replace(/<\/script>/gi, '<\\/script>');
|
|
339
344
|
|
|
@@ -993,6 +998,8 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
993
998
|
// Item highlighting: the navbar is built in the browser, so the
|
|
994
999
|
// config has to travel to site.js rather than being rendered here.
|
|
995
1000
|
...(navMenu.highlight && {highlight: navMenu.highlight}),
|
|
1001
|
+
// Designer classes for the navbar element itself (site.js adds them).
|
|
1002
|
+
...(navMenu.class && {class: navMenu.class}),
|
|
996
1003
|
...(navMenu.style && {style: navMenu.style})
|
|
997
1004
|
}
|
|
998
1005
|
: (await readLegacyNavBackup()) || {brand: site.brand || {}, items: []};
|
|
@@ -1048,7 +1055,10 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1048
1055
|
copyright: site.footer?.copyright || '',
|
|
1049
1056
|
// Highlighting is per menu, and the two footer columns are two menus.
|
|
1050
1057
|
...(footerPrimary?.highlight && {primaryHighlight: footerPrimary.highlight}),
|
|
1051
|
-
...(footerLegal?.highlight && {legalHighlight: footerLegal.highlight})
|
|
1058
|
+
...(footerLegal?.highlight && {legalHighlight: footerLegal.highlight}),
|
|
1059
|
+
// Each column's menu-level designer classes, for its <nav>.
|
|
1060
|
+
...(footerPrimary?.class && {primaryClass: footerPrimary.class}),
|
|
1061
|
+
...(footerLegal?.class && {legalClass: footerLegal.class})
|
|
1052
1062
|
};
|
|
1053
1063
|
const footerJson = JSON.stringify(footer).replace(/<\/script>/gi, '<\\/script>');
|
|
1054
1064
|
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
</script>
|
|
138
138
|
|
|
139
139
|
<!-- Site initialisation -->
|
|
140
|
-
<script src="/public/js/site.js?v=
|
|
140
|
+
<script src="/public/js/site.js?v=20261009-form-classes" type="module"></script>
|
|
141
141
|
<script src="/public/js/collection-context.js?v=20260927-ctx" type="module"></script>
|
|
142
142
|
{{ctxMenusModule}}
|
|
143
143
|
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
|
|
153
153
|
<!-- Core Forms (logic engine must load before renderer) -->
|
|
154
154
|
<script src="/public/js/form-logic-engine.js?v=20260927-submit-btn"></script>
|
|
155
|
-
<script src="/public/js/forms.js?v=
|
|
155
|
+
<script src="/public/js/forms.js?v=20261009-form-classes" type="module"></script>
|
|
156
156
|
|
|
157
157
|
<!-- Core effects runtime -->
|
|
158
158
|
<script src="/public/js/effects.js?v=20261003-characters"></script>
|