domma-cms 1.10.32 → 1.10.33

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.33] - 2026-10-26
4
+
5
+ - Group newer Tools into purpose-based submenus, including business, marketing, publishing, maintenance and games. Upgrade saved loose entries once while preserving labels, visibility, permissions and custom submenus.
6
+
3
7
  ## [1.10.32] - 2026-10-26
4
8
 
5
9
  - Add clickable (?) explanations throughout the Forms editor, including submission actions, triggers, conditions, field options and chooser controls. Newly added controls receive help immediately; help buttons preserve checkbox and panel state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.10.32",
3
+ "version": "1.10.33",
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",
@@ -98,14 +98,21 @@ const _registeredSidebarItems = [];
98
98
  * Unknown plugins remain directly in Tools until they declare their own group.
99
99
  */
100
100
  const TOOL_GROUPS = new Map(Object.entries({
101
- Productivity: ['#/notes', '#/todo', '#/calculator', '#/plugins/calendar', '#/plugins/waypoint-pro'],
101
+ Productivity: ['#/notes', '#/todo', '#/calculator', '#/plugins/calendar', '#/plugins/waypoint-pro', '#/plugins/sheets'],
102
102
  Communication: ['#/contacts', '#/plugins/contacts-pro', '#/plugins/mail-reader', '#/plugins/email-pro', '#/plugins/messages'],
103
- Marketing: ['#/analytics', '#/seo', '#/plugins/analytics-pro/dashboard', '#/plugins/social', '#/plugins/feedback', '#/plugins/surveys'],
104
- Publishing: ['#/plugins/blog', '#/plugins/blog-pro', '#/plugins/docs', '#/plugins/theme-roller'],
105
- Business: ['#/plugins/shopping-cart', '#/plugins/booking', '#/plugins/invoice', '#/job-board', '#/plugins/garage'],
106
- Maintenance: ['#/plugins/security', '#/plugins/security-pro', '#/plugins/security-pro/my-2fa', '#/plugins/backup-pro', '#/plugins/data-transfer', '#/wordpress-import']
103
+ Marketing: ['#/analytics', '#/seo', '#/plugins/analytics-pro/dashboard', '#/plugins/social', '#/plugins/feedback', '#/plugins/surveys', '#/plugins/campaigns-pro', '#/plugins/ecards'],
104
+ Publishing: ['#/plugins/blog', '#/plugins/blog-pro', '#/plugins/docs', '#/plugins/theme-roller', '#/plugins/wiki', '#/plugins/wiki-pro', '#/plugins/scraper', '#/plugins/scraper-pro'],
105
+ Business: ['#/plugins/shopping-cart', '#/plugins/booking', '#/plugins/booking-pro', '#/plugins/invoice', '#/job-board', '#/plugins/garage', '#/plugins/takeaway', '#/plugins/support', '#/plugins/onboard-pro'],
106
+ Maintenance: ['#/plugins/security', '#/plugins/security-pro', '#/plugins/security-pro/my-2fa', '#/plugins/backup-pro', '#/plugins/data-transfer', '#/wordpress-import', '#/plugins/site-manager/backups'],
107
+ Manage: ['#/plugins/site-manager/dashboard', '#/plugins/site-manager/server', '#/plugins/site-manager/injector-library'],
108
+ Games: ['#/plugins/crossword', '#/plugins/daily-word', '#/plugins/quiz', '#/plugins/word-search']
107
109
  }).flatMap(([group, urls]) => urls.map(url => [url, group])));
108
110
 
111
+ /** Default category for a known Tool URL, also used when upgrading saved menus. */
112
+ export function defaultToolGroup(url) {
113
+ return TOOL_GROUPS.get(url);
114
+ }
115
+
109
116
  /**
110
117
  * Register a Tool's sidebar entry. Entries are placed into the rendered admin
111
118
  * sidebar at render time - they are NOT persisted into menu JSON - so plugins
@@ -130,7 +137,7 @@ export function registerSidebarItem(opts) {
130
137
  }
131
138
  const {folder: itemFolder, group: itemGroup, ...item} = opts.item;
132
139
  const folder = opts.folder || itemFolder || DEFAULT_TOOL_FOLDER;
133
- const defaultGroup = folder === DEFAULT_TOOL_FOLDER && !opts.parent ? TOOL_GROUPS.get(item.url) : undefined;
140
+ const defaultGroup = folder === DEFAULT_TOOL_FOLDER && !opts.parent ? defaultToolGroup(item.url) : undefined;
134
141
  const group = [opts.group, itemGroup, defaultGroup].find(g => typeof g === 'string' && g.trim());
135
142
  _registeredSidebarItems.push({
136
143
  parent: opts.parent || null,
@@ -15,6 +15,7 @@ import fs from 'fs/promises';
15
15
  import path from 'path';
16
16
  import {fileURLToPath} from 'url';
17
17
  import {invalidateMenuIndex} from './menus.js';
18
+ import {defaultToolGroup} from './hooks.js';
18
19
 
19
20
  const __filename = fileURLToPath(import.meta.url);
20
21
  const DEFAULT_CONFIG_DIR = path.resolve(path.dirname(__filename), '..', '..', 'config');
@@ -482,6 +483,38 @@ export async function ensureFolderKeys(opts = {}) {
482
483
  changed = true;
483
484
  }
484
485
 
486
+ // Categorise only loose, known Tools once. Custom submenus and entries
487
+ // outside Tools keep their placement; later admin rearrangements win.
488
+ if (!menu.meta?.purposeToolGroups && items.length) {
489
+ const find = (nodes, test) => {
490
+ for (const node of nodes || []) {
491
+ if (test(node)) return node;
492
+ const nested = find(node?.items, test);
493
+ if (nested) return nested;
494
+ }
495
+ return null;
496
+ };
497
+ const tools = find(items, n => isFolder(n) && n.key === 'tools');
498
+ if (tools && Array.isArray(tools.items)) {
499
+ const loose = tools.items.filter(n => n?.url && defaultToolGroup(n.url));
500
+ for (const node of loose) {
501
+ const name = defaultToolGroup(node.url);
502
+ const key = `tools.${name.toLowerCase()}`;
503
+ let group = find(items, n => isFolder(n) && n.key === key)
504
+ || tools.items.find(n => isFolder(n) && !n.key && String(n.text).toLowerCase() === name.toLowerCase());
505
+ if (!group) {
506
+ group = {text: name, key, icon: 'folder', items: []};
507
+ tools.items.push(group);
508
+ }
509
+ tools.items = tools.items.filter(n => n !== node);
510
+ if (!Array.isArray(group.items)) group.items = [];
511
+ group.items.push(node);
512
+ }
513
+ }
514
+ menu.meta = {...(menu.meta || {}), purposeToolGroups: true};
515
+ changed = true;
516
+ }
517
+
485
518
  if (!changed) return {updated: false, reason: 'already up to date'};
486
519
 
487
520
  menu.meta = {...(menu.meta || {}), updatedAt: new Date().toISOString()};