domma-cms 1.6.59 → 1.7.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/CHANGELOG.md +4344 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/draft-owner.js +1 -0
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/admin/js/views/user-editor.js +1 -1
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/keyboard.js +49 -0
- package/plugins/_lib/admin/mail/reader-view.js +4 -2
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# Nav Hidden Items Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or
|
|
4
|
+
> superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
5
|
+
|
|
6
|
+
**Goal:** Add a per-item "hidden" toggle to the navigation editor so items and footer links can be suppressed from the
|
|
7
|
+
public site without deleting them.
|
|
8
|
+
|
|
9
|
+
**Architecture:** A `hidden: true` flag is stored optionally on each nav item and footer link in their respective config
|
|
10
|
+
files. The admin editor toggles this flag in-memory and saves it via the existing Save button. At render time,
|
|
11
|
+
`renderer.js` strips hidden items before serialising `__CMS_NAV__` into the public page - hidden data never reaches the
|
|
12
|
+
browser.
|
|
13
|
+
|
|
14
|
+
**Tech Stack:** Fastify 5 (ESM) backend, Domma SPA admin (Domma JS framework), CSS in `admin/css/admin.css`.
|
|
15
|
+
|
|
16
|
+
**Spec:** `docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## File Map
|
|
21
|
+
|
|
22
|
+
| File | What changes |
|
|
23
|
+
|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
|
24
|
+
| `server/services/renderer.js` | Add `filterHiddenNavItems()` helper; apply before building `navJson` and `siteJson` |
|
|
25
|
+
| `admin/css/admin.css` | Add `.nav-item-row--hidden` and `.btn-toggle-hidden.active` styles |
|
|
26
|
+
| `admin/js/views/navigation.js` | `flattenItems`, `nestItems`, `footerLinks` init, `syncFromDom`, `syncFooterFromDom`, `renderItems`, `renderFooterLinks`, click handlers |
|
|
27
|
+
| `admin/js/templates/navigation.html` | Add "Actions" label to both `nav-col-action` header spans |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Task 1: Server-side filtering
|
|
32
|
+
|
|
33
|
+
**Files:**
|
|
34
|
+
|
|
35
|
+
- Modify: `server/services/renderer.js`
|
|
36
|
+
|
|
37
|
+
The goal is to strip `hidden: true` items before the nav config is JSON-serialised into the public page. The filter must
|
|
38
|
+
handle:
|
|
39
|
+
|
|
40
|
+
- Top-level items (hidden parent → entire entry gone, children included)
|
|
41
|
+
- Children of visible parents (hidden child → removed from parent's `items` array)
|
|
42
|
+
- Footer links in `site.footer.links`
|
|
43
|
+
|
|
44
|
+
The codebase only supports two levels of nesting, so no recursive traversal is needed.
|
|
45
|
+
|
|
46
|
+
- [ ] **Step 1.1: Open `server/services/renderer.js` and add the filter helper**
|
|
47
|
+
|
|
48
|
+
Add this function before the JSDoc block that documents `renderPage` (the JSDoc starts at line 33 with
|
|
49
|
+
`/** Render a page...`). Do NOT insert it between the JSDoc and the function signature - place it before line 33:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
/**
|
|
53
|
+
* Strip hidden items from the navigation config before public injection.
|
|
54
|
+
* Returns a shallow clone - never mutates the cached config object.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} nav - Raw navigation config from getConfig('navigation')
|
|
57
|
+
* @returns {object} Filtered navigation object safe for public injection
|
|
58
|
+
*/
|
|
59
|
+
function filterHiddenNavItems(nav) {
|
|
60
|
+
const items = (nav.items || [])
|
|
61
|
+
.filter(item => !item.hidden)
|
|
62
|
+
.map(item => {
|
|
63
|
+
if (!item.items?.length) return item;
|
|
64
|
+
const visibleChildren = item.items.filter(c => !c.hidden);
|
|
65
|
+
return visibleChildren.length === item.items.length
|
|
66
|
+
? item
|
|
67
|
+
: { ...item, items: visibleChildren };
|
|
68
|
+
});
|
|
69
|
+
return { ...nav, items };
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- [ ] **Step 1.2: Apply the filter in `renderPage`**
|
|
74
|
+
|
|
75
|
+
Find line 101 in `renderer.js`:
|
|
76
|
+
```js
|
|
77
|
+
navJson: JSON.stringify(navigation).replace(/<\/script>/gi, '<\\/script>'),
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Replace it with:
|
|
81
|
+
```js
|
|
82
|
+
navJson: JSON.stringify(filterHiddenNavItems(navigation)).replace(/<\/script>/gi, '<\\/script>'),
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- [ ] **Step 1.3: Filter footer links in `siteJson`**
|
|
86
|
+
|
|
87
|
+
Find the `siteJson` block (lines 102-107). Replace the opening of `Object.assign`:
|
|
88
|
+
|
|
89
|
+
From:
|
|
90
|
+
```js
|
|
91
|
+
siteJson: JSON.stringify(Object.assign(
|
|
92
|
+
{ title: site.title, footer: site.footer, social: site.social || null },
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
To:
|
|
96
|
+
```js
|
|
97
|
+
siteJson: JSON.stringify(Object.assign(
|
|
98
|
+
{
|
|
99
|
+
title: site.title,
|
|
100
|
+
footer: site.footer
|
|
101
|
+
? {
|
|
102
|
+
...site.footer,
|
|
103
|
+
links: (site.footer.links || []).filter(l => !l.hidden)
|
|
104
|
+
}
|
|
105
|
+
: site.footer,
|
|
106
|
+
social: site.social || null
|
|
107
|
+
},
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 1.4: Verify server-side filtering manually**
|
|
111
|
+
|
|
112
|
+
Temporarily edit `config/navigation.json` - add `"hidden": true` to one top-level item (e.g. "About"):
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "text": "About", "url": "/about", "icon": "info", "hidden": true }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then run (`getConfig` reads fresh from disk on every call - confirmed from `server/config.js` line 30-32:
|
|
119
|
+
`return JSON.parse(fs.readFileSync(...))` - no caching, no restart needed):
|
|
120
|
+
```bash
|
|
121
|
+
curl -s http://localhost:4096/ | grep -o '__CMS_NAV__ = [^;]*' | head -c 500
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Expected: the "About" item is **absent** from the JSON blob in the output.
|
|
125
|
+
|
|
126
|
+
- [ ] **Revert the manual edit to `navigation.json`:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git checkout config/navigation.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Confirm the file is clean:
|
|
133
|
+
```bash
|
|
134
|
+
git diff config/navigation.json
|
|
135
|
+
```
|
|
136
|
+
Expected: no output (no changes).
|
|
137
|
+
|
|
138
|
+
- [ ] **Step 1.5: Commit**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git add server/services/renderer.js
|
|
142
|
+
git commit -m "feat: strip hidden nav items/footer links at render time"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Task 2: Admin CSS
|
|
148
|
+
|
|
149
|
+
**Files:**
|
|
150
|
+
|
|
151
|
+
- Modify: `admin/css/admin.css`
|
|
152
|
+
|
|
153
|
+
- [ ] **Step 2.1: Add hidden-row styles**
|
|
154
|
+
|
|
155
|
+
Find the nav section in `admin/css/admin.css` (around line 423, just after `.nav-item-row--child`). Add:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
.nav-col-action { display: flex; align-items: center; gap: .25rem; }
|
|
159
|
+
|
|
160
|
+
.nav-item-row--hidden { opacity: 0.45; }
|
|
161
|
+
.nav-item-row--hidden .item-text,
|
|
162
|
+
.nav-item-row--hidden .footer-link-text { text-decoration: line-through; }
|
|
163
|
+
|
|
164
|
+
.btn-toggle-hidden { color: var(--text-muted, #888); }
|
|
165
|
+
.btn-toggle-hidden.active { color: var(--color-warning, #f59e0b); }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `nav-col-action` flex rule is required - the span now holds two buttons and without it they render with no
|
|
169
|
+
controlled spacing. The amber `color-warning` gives the active (item-is-hidden) button state a distinct colour.
|
|
170
|
+
|
|
171
|
+
- [ ] **Step 2.2: Visual regression check**
|
|
172
|
+
|
|
173
|
+
Open `http://localhost:4096/admin/#/navigation` in a browser. Layout should look identical to before - no new buttons
|
|
174
|
+
yet, just verifying the CSS additions don't break anything.
|
|
175
|
+
|
|
176
|
+
- [ ] **Step 2.3: Commit**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
git add admin/css/admin.css
|
|
180
|
+
git commit -m "feat: add hidden nav row styles"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Task 3: Update `navigation.js` - data model
|
|
186
|
+
|
|
187
|
+
**Files:**
|
|
188
|
+
|
|
189
|
+
- Modify: `admin/js/views/navigation.js`
|
|
190
|
+
|
|
191
|
+
**Note on `_nextId`:** This module-level counter never resets across view mounts - this is intentional. IDs only need to
|
|
192
|
+
be unique within a session (not persistent), and an ever-incrementing counter guarantees that. Do not reset it.
|
|
193
|
+
|
|
194
|
+
**Note on `.attr()` vs `.data()` - plan overrides spec:** The spec (line 41) suggests `.data('hidden') === 'true'`.
|
|
195
|
+
This plan overrides that with `.attr('data-hidden') === 'true'` because Domma's `.data()` (jQuery-compatible) auto-coerces
|
|
196
|
+
`"true"`/`"false"` attribute strings to actual booleans, making the `=== 'true'` string comparison always false. Using
|
|
197
|
+
`.attr('data-hidden')` returns the raw string and makes the comparison work correctly. The spec is wrong on this specific
|
|
198
|
+
point; follow the plan.
|
|
199
|
+
|
|
200
|
+
**Note on `.data()` for numeric IDs:** The toggle click handlers use `$(this).data('id')` and `$(this).data('idx')` to
|
|
201
|
+
read numeric IDs. `.data()` auto-coercing `"123"` → `123` is harmless (the `parseInt` call is a safe no-op on a
|
|
202
|
+
number). The `.attr()` rule applies specifically to the boolean `data-hidden` attribute.
|
|
203
|
+
|
|
204
|
+
### 3a - `flattenItems`: read `hidden` from JSON
|
|
205
|
+
|
|
206
|
+
- [ ] **Step 3a.1: Update `flattenItems` (lines 10-20)**
|
|
207
|
+
|
|
208
|
+
Replace the whole function with:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
function flattenItems(items) {
|
|
212
|
+
const flat = [];
|
|
213
|
+
(items || []).forEach(item => {
|
|
214
|
+
const id = _nextId++;
|
|
215
|
+
flat.push({ _id: id, text: item.text || '', url: item.url || '', icon: item.icon || '', hidden: item.hidden || false, parentId: null });
|
|
216
|
+
(item.items || item.children || []).forEach(child => {
|
|
217
|
+
flat.push({ _id: _nextId++, text: child.text || '', url: child.url || '', icon: child.icon || '', hidden: child.hidden || false, parentId: id });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return flat;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 3b - `nestItems`: pass `hidden` through to JSON
|
|
225
|
+
|
|
226
|
+
- [ ] **Step 3b.1: Update `nestItems` (lines 22-33)**
|
|
227
|
+
|
|
228
|
+
Replace the whole function with:
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
function nestItems(flat) {
|
|
232
|
+
return flat
|
|
233
|
+
.filter(i => i.parentId === null)
|
|
234
|
+
.map(parent => {
|
|
235
|
+
const children = flat
|
|
236
|
+
.filter(i => i.parentId === parent._id)
|
|
237
|
+
.map(c => ({
|
|
238
|
+
text: c.text,
|
|
239
|
+
url: c.url,
|
|
240
|
+
...(c.icon && { icon: c.icon }),
|
|
241
|
+
...(c.hidden && { hidden: true }),
|
|
242
|
+
}));
|
|
243
|
+
const obj = {
|
|
244
|
+
text: parent.text,
|
|
245
|
+
url: parent.url,
|
|
246
|
+
...(parent.icon && { icon: parent.icon }),
|
|
247
|
+
...(parent.hidden && { hidden: true }),
|
|
248
|
+
};
|
|
249
|
+
if (children.length) obj.items = children;
|
|
250
|
+
return obj;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 3c - `footerLinks` initialisation: preserve `hidden`
|
|
256
|
+
|
|
257
|
+
- [ ] **Step 3c.1: Update the `footerLinks` initialisation in `onMount` (line 54)**
|
|
258
|
+
|
|
259
|
+
Find:
|
|
260
|
+
```js
|
|
261
|
+
let footerLinks = (site.footer?.links || []).map(l => ({text: l.text || '', url: l.url || ''}));
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Replace with:
|
|
265
|
+
```js
|
|
266
|
+
let footerLinks = (site.footer?.links || []).map(l => ({
|
|
267
|
+
text: l.text || '',
|
|
268
|
+
url: l.url || '',
|
|
269
|
+
...(l.hidden && { hidden: true }),
|
|
270
|
+
}));
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Why this matters:** Without this, any `hidden: true` flags that already exist in `site.json` will be silently
|
|
274
|
+
stripped when the view mounts. The first save after opening the editor would then write back the links without the
|
|
275
|
+
`hidden` flag, permanently losing the state.
|
|
276
|
+
|
|
277
|
+
### 3d - `#add-nav-item` and `#add-footer-link` handlers: include `hidden`
|
|
278
|
+
|
|
279
|
+
Edit these in document order (line 136 first, then line 156).
|
|
280
|
+
|
|
281
|
+
- [ ] **Step 3d.1: Add `hidden` to the `#add-footer-link` push (line 136)**
|
|
282
|
+
|
|
283
|
+
Find:
|
|
284
|
+
```js
|
|
285
|
+
footerLinks.push({text: '', url: ''});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Replace with:
|
|
289
|
+
```js
|
|
290
|
+
footerLinks.push({ text: '', url: '', hidden: false });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
- [ ] **Step 3d.2: Add `hidden: false` to the `#add-nav-item` push (line 156)**
|
|
294
|
+
|
|
295
|
+
Find:
|
|
296
|
+
```js
|
|
297
|
+
flatItems.push({ _id: _nextId++, text: '', url: '', icon: '', parentId: null });
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Replace with:
|
|
301
|
+
```js
|
|
302
|
+
flatItems.push({ _id: _nextId++, text: '', url: '', icon: '', hidden: false, parentId: null });
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
*(New items default to visible - `hidden: false` keeps the model consistent so `renderItems`/`renderFooterLinks` never encounter `undefined`.)*
|
|
306
|
+
|
|
307
|
+
### 3e - `syncFromDom`: read `hidden` from toggle button
|
|
308
|
+
|
|
309
|
+
- [ ] **Step 3e.1: Update `syncFromDom` (lines 56-72)**
|
|
310
|
+
|
|
311
|
+
Use `.attr('data-hidden')` (raw string) not `.data('hidden')` (auto-coerces to boolean). Also scope the selector to
|
|
312
|
+
`#nav-items-list .nav-item-row` so footer link rows (which share the `.nav-item-row` class) are not accidentally
|
|
313
|
+
matched:
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
const syncFromDom = () => {
|
|
317
|
+
$container.find('#nav-items-list .nav-item-row').each(function () {
|
|
318
|
+
const id = parseInt($(this).data('id'), 10);
|
|
319
|
+
const item = flatItems.find(i => i._id === id);
|
|
320
|
+
if (!item) return;
|
|
321
|
+
item.text = $(this).find('.item-text').val();
|
|
322
|
+
item.url = $(this).find('.item-url').val();
|
|
323
|
+
item.icon = $(this).find('.item-icon').val();
|
|
324
|
+
item.hidden = $(this).find('.btn-toggle-hidden').attr('data-hidden') === 'true';
|
|
325
|
+
const parentVal = $(this).find('.item-parent').val();
|
|
326
|
+
item.parentId = parentVal ? parseInt(parentVal, 10) : null;
|
|
327
|
+
});
|
|
328
|
+
// Promote children whose parent is now itself a child
|
|
329
|
+
const childIds = new Set(flatItems.filter(i => i.parentId !== null).map(i => i._id));
|
|
330
|
+
flatItems.forEach(i => {
|
|
331
|
+
if (i.parentId !== null && childIds.has(i.parentId)) i.parentId = null;
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 3f - `syncFooterFromDom`: read `hidden` from toggle button
|
|
337
|
+
|
|
338
|
+
- [ ] **Step 3f.1: Update `syncFooterFromDom` (lines 124-132)**
|
|
339
|
+
|
|
340
|
+
Use `.attr('data-hidden')` (raw string), same reasoning as `syncFromDom`:
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
const syncFooterFromDom = () => {
|
|
344
|
+
footerLinks = [];
|
|
345
|
+
$container.find('#footer-links-list .nav-item-row').each(function () {
|
|
346
|
+
const hidden = $(this).find('.btn-toggle-hidden').attr('data-hidden') === 'true';
|
|
347
|
+
const link = {
|
|
348
|
+
text: $(this).find('.footer-link-text').val().trim(),
|
|
349
|
+
url: $(this).find('.footer-link-url').val().trim(),
|
|
350
|
+
};
|
|
351
|
+
if (hidden) link.hidden = true;
|
|
352
|
+
footerLinks.push(link);
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Note on blank+hidden links:** If a footer link has no text or URL but is marked hidden, it will still be dropped by
|
|
358
|
+
the existing `validFooterLinks` filter in the save handler (`footerLinks.filter(l => l.text || l.url)`). This is
|
|
359
|
+
acceptable - a blank link has no value regardless of its hidden state.
|
|
360
|
+
|
|
361
|
+
- [ ] **Step 3f.2: Commit the data-model changes**
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
git add admin/js/views/navigation.js
|
|
365
|
+
git commit -m "feat: propagate hidden flag through nav flattenItems/nestItems/sync"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Task 4: Update `navigation.js` - render + interaction
|
|
371
|
+
|
|
372
|
+
**Files:**
|
|
373
|
+
|
|
374
|
+
- Modify: `admin/js/views/navigation.js`
|
|
375
|
+
|
|
376
|
+
### 4a - `renderItems`: add toggle button, apply hidden class
|
|
377
|
+
|
|
378
|
+
- [ ] **Step 4a.1: Replace `renderItems` (lines 74-103)**
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
const renderItems = () => {
|
|
382
|
+
const $list = $container.find('#nav-items-list').empty();
|
|
383
|
+
const topLevel = flatItems.filter(i => i.parentId === null);
|
|
384
|
+
|
|
385
|
+
getOrderedItems(flatItems).forEach(item => {
|
|
386
|
+
const isChild = item.parentId !== null;
|
|
387
|
+
const isHidden = item.hidden === true;
|
|
388
|
+
const parentOptions = `<option value="">- top-level -</option>` +
|
|
389
|
+
topLevel
|
|
390
|
+
.filter(t => t._id !== item._id)
|
|
391
|
+
.map(t => `<option value="${t._id}"${t._id === item.parentId ? ' selected' : ''}>${esc(t.text) || '(untitled)'}</option>`)
|
|
392
|
+
.join('');
|
|
393
|
+
|
|
394
|
+
$list.append(`
|
|
395
|
+
<div class="nav-item-row${isChild ? ' nav-item-row--child' : ''}${isHidden ? ' nav-item-row--hidden' : ''}" data-id="${item._id}">
|
|
396
|
+
<span class="nav-col-indent">${isChild ? '↳' : ''}</span>
|
|
397
|
+
<input type="text" class="form-input item-text nav-col-main" value="${esc(item.text)}" placeholder="Label">
|
|
398
|
+
<input type="text" class="form-input item-url nav-col-main" value="${esc(item.url)}" placeholder="/url">
|
|
399
|
+
<input type="text" class="form-input item-icon nav-col-icon" value="${esc(item.icon)}" placeholder="icon">
|
|
400
|
+
<select class="form-select item-parent nav-col-parent">${parentOptions}</select>
|
|
401
|
+
<span class="nav-col-action">
|
|
402
|
+
<button class="btn btn-sm btn-ghost btn-toggle-hidden${isHidden ? ' active' : ''}" data-id="${item._id}" data-hidden="${isHidden}" data-tooltip="${isHidden ? 'Show' : 'Hide'}"><span data-icon="eye-off"></span></button>
|
|
403
|
+
<button class="btn btn-sm btn-danger btn-remove-item" data-id="${item._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
404
|
+
</span>
|
|
405
|
+
</div>
|
|
406
|
+
`);
|
|
407
|
+
});
|
|
408
|
+
Domma.icons.scan('#nav-items-list');
|
|
409
|
+
document.querySelectorAll('#nav-items-list [data-tooltip]').forEach(el => {
|
|
410
|
+
E.tooltip(el, {content: el.getAttribute('data-tooltip'), position: 'top'});
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 4b - `renderFooterLinks`: add toggle button, apply hidden class
|
|
416
|
+
|
|
417
|
+
- [ ] **Step 4b.1: Replace `renderFooterLinks` (lines 105-122)**
|
|
418
|
+
|
|
419
|
+
```js
|
|
420
|
+
const renderFooterLinks = () => {
|
|
421
|
+
const $list = $container.find('#footer-links-list').empty();
|
|
422
|
+
footerLinks.forEach((link, idx) => {
|
|
423
|
+
const isHidden = link.hidden === true;
|
|
424
|
+
$list.append(`
|
|
425
|
+
<div class="nav-item-row${isHidden ? ' nav-item-row--hidden' : ''}" data-footer-idx="${idx}">
|
|
426
|
+
<input type="text" class="form-input footer-link-text nav-col-main" value="${esc(link.text)}" placeholder="Label">
|
|
427
|
+
<input type="text" class="form-input footer-link-url nav-col-main" value="${esc(link.url)}" placeholder="/url">
|
|
428
|
+
<span class="nav-col-action">
|
|
429
|
+
<button class="btn btn-sm btn-ghost btn-toggle-hidden${isHidden ? ' active' : ''}" data-idx="${idx}" data-hidden="${isHidden}" data-tooltip="${isHidden ? 'Show' : 'Hide'}"><span data-icon="eye-off"></span></button>
|
|
430
|
+
<button class="btn btn-sm btn-danger btn-remove-footer" data-idx="${idx}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
431
|
+
</span>
|
|
432
|
+
</div>
|
|
433
|
+
`);
|
|
434
|
+
});
|
|
435
|
+
Domma.icons.scan('#footer-links-list');
|
|
436
|
+
document.querySelectorAll('#footer-links-list [data-tooltip]').forEach(el => {
|
|
437
|
+
E.tooltip(el, {content: el.getAttribute('data-tooltip'), position: 'top'});
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 4c - Wire up the toggle click handlers
|
|
443
|
+
|
|
444
|
+
- [ ] **Step 4c.1: Add click handlers alongside the existing `btn-remove-item` / `btn-remove-footer` handlers (after
|
|
445
|
+
line ~165)**
|
|
446
|
+
|
|
447
|
+
Use scoped selectors (`#nav-items-list` / `#footer-links-list`) to prevent cross-list double-firing:
|
|
448
|
+
|
|
449
|
+
```js
|
|
450
|
+
$container.off('click', '#nav-items-list .btn-toggle-hidden').on('click', '#nav-items-list .btn-toggle-hidden', function (e) {
|
|
451
|
+
e.stopPropagation();
|
|
452
|
+
syncFromDom();
|
|
453
|
+
const id = parseInt($(this).data('id'), 10);
|
|
454
|
+
const item = flatItems.find(i => i._id === id);
|
|
455
|
+
if (item) item.hidden = !item.hidden;
|
|
456
|
+
renderItems();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
$container.off('click', '#footer-links-list .btn-toggle-hidden').on('click', '#footer-links-list .btn-toggle-hidden', function (e) {
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
syncFooterFromDom();
|
|
462
|
+
const idx = parseInt($(this).data('idx'), 10);
|
|
463
|
+
if (footerLinks[idx]) footerLinks[idx].hidden = !footerLinks[idx].hidden;
|
|
464
|
+
renderFooterLinks();
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
- [ ] **Step 4c.2: Commit the render + interaction changes**
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
git add admin/js/views/navigation.js
|
|
472
|
+
git commit -m "feat: add hidden toggle button and visual treatment to nav editor rows"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Task 5: Update `navigation.html` - column headers
|
|
478
|
+
|
|
479
|
+
**Files:**
|
|
480
|
+
|
|
481
|
+
- Modify: `admin/js/templates/navigation.html`
|
|
482
|
+
|
|
483
|
+
**Note on header label:** The spec suggests "Vis." as a dedicated column label for the toggle. This plan uses "Actions"
|
|
484
|
+
instead because both the eye-off toggle and the trash button share the existing `nav-col-action` span - adding a
|
|
485
|
+
separate narrow "Vis." column would require restructuring the column layout. "Actions" covers both controls and requires
|
|
486
|
+
no layout changes. This is an intentional deviation from the spec's suggested label.
|
|
487
|
+
|
|
488
|
+
- [ ] **Step 5.1: Update the Menu Items header**
|
|
489
|
+
|
|
490
|
+
Find the `nav-items-header` div inside the "Menu Items" card (lines 46-53). Change:
|
|
491
|
+
```html
|
|
492
|
+
<span class="nav-col-action"></span>
|
|
493
|
+
```
|
|
494
|
+
To:
|
|
495
|
+
```html
|
|
496
|
+
<span class="nav-col-action text-muted"><small>Actions</small></span>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
- [ ] **Step 5.2: Update the Footer Links header**
|
|
500
|
+
|
|
501
|
+
Find the `nav-items-header` inside the "Footer Links" card (lines 65-70). Change:
|
|
502
|
+
```html
|
|
503
|
+
<span class="nav-col-action"></span>
|
|
504
|
+
```
|
|
505
|
+
To:
|
|
506
|
+
```html
|
|
507
|
+
<span class="nav-col-action text-muted"><small>Actions</small></span>
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
- [ ] **Step 5.3: Commit**
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
git add admin/js/templates/navigation.html
|
|
514
|
+
git commit -m "feat: label actions column in nav editor headers"
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Task 6: End-to-end verification
|
|
520
|
+
|
|
521
|
+
- [ ] **Step 6.1: Open the Navigation editor**
|
|
522
|
+
|
|
523
|
+
Open `http://localhost:4096/admin/#/navigation`.
|
|
524
|
+
|
|
525
|
+
- Confirm each nav item row has an eye-off icon button to the left of the trash button.
|
|
526
|
+
- Confirm each footer link row has the same.
|
|
527
|
+
- Confirm rows look normal (not dimmed) when hidden is off.
|
|
528
|
+
|
|
529
|
+
- [ ] **Step 6.2: Toggle a nav item hidden**
|
|
530
|
+
|
|
531
|
+
- Click the eye-off button on any nav item (e.g. "About").
|
|
532
|
+
- Row should dim and the label input should gain a strikethrough.
|
|
533
|
+
- Button should turn amber.
|
|
534
|
+
|
|
535
|
+
- [ ] **Step 6.3: Save and verify public navbar**
|
|
536
|
+
|
|
537
|
+
- Click Save. Toast: "Navigation saved."
|
|
538
|
+
- Open `http://localhost:4096/` in the browser - "About" must be absent from the navbar.
|
|
539
|
+
- Open `config/navigation.json` - "About" must still be there with `"hidden": true`.
|
|
540
|
+
|
|
541
|
+
- [ ] **Step 6.4: Toggle back and save**
|
|
542
|
+
|
|
543
|
+
- Return to the editor, click the eye-off button on "About" again.
|
|
544
|
+
- Row should un-dim, button returns to muted colour.
|
|
545
|
+
- Save. Verify "About" reappears on the public navbar.
|
|
546
|
+
|
|
547
|
+
- [ ] **Step 6.5: Test pre-existing hidden state round-trips correctly**
|
|
548
|
+
|
|
549
|
+
- Manually add `"hidden": true` to a footer link in `config/site.json` (under `footer.links`).
|
|
550
|
+
- Reload the Navigation editor (`http://localhost:4096/admin/#/navigation`).
|
|
551
|
+
- The footer link row should render dimmed with its button in amber - confirming `footerLinks` init preserves the
|
|
552
|
+
flag.
|
|
553
|
+
- Save without changing anything - verify the link still has `"hidden": true` in `site.json`.
|
|
554
|
+
|
|
555
|
+
- [ ] **Step 6.6: Test footer link hidden via UI**
|
|
556
|
+
|
|
557
|
+
- Toggle a footer link hidden via the UI. Save. Run:
|
|
558
|
+
```bash
|
|
559
|
+
curl -s http://localhost:4096/ | grep -o '__CMS_SITE__ = [^;]*' | head -c 600
|
|
560
|
+
```
|
|
561
|
+
- Confirm the hidden footer link is absent from the `links` array in `__CMS_SITE__`.
|
|
562
|
+
|
|
563
|
+
- [ ] **Step 6.7: Test a parent with hidden children**
|
|
564
|
+
|
|
565
|
+
- Mark a child item (e.g. one under "Resources") hidden. Save.
|
|
566
|
+
- Verify the "Resources" dropdown still appears in the navbar, but the hidden child does not.
|
|
567
|
+
|
|
568
|
+
- [ ] **Step 6.8: Test a hidden parent**
|
|
569
|
+
|
|
570
|
+
- Mark "Resources" (a parent with children) hidden. Save.
|
|
571
|
+
- Verify neither "Resources" nor any of its children appear in the navbar.
|
|
572
|
+
|
|
573
|
+
- [ ] **Step 6.9: Final commit if any tweaks were needed**
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
git add -p
|
|
577
|
+
git commit -m "fix: nav hidden items polish after e2e verification"
|
|
578
|
+
```
|