domma-cms 1.6.60 → 1.7.1
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 +4349 -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/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/mail-compose.js +1 -0
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/call-new.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/admin/js/views/call-new.js +1 -1
- package/admin/js/views/contacts.js +15 -15
- package/admin/js/views/form-submissions.js +2 -2
- 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/compose-window.js +3 -1
- package/plugins/_lib/admin/mail/identity.js +18 -3
- package/plugins/_lib/admin/mail/reader-view.js +32 -3
- package/plugins/blog/CLAUDE.md +4 -0
- package/plugins/blog/admin/lib/email.js +191 -0
- package/plugins/blog/admin/views/comments.js +3 -1
- package/plugins/blog/plugin.json +2 -2
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/email.test.js +69 -0
- package/plugins/booking/CLAUDE.md +4 -0
- package/plugins/booking/admin/lib/email.js +191 -0
- package/plugins/booking/admin/views/panels.js +7 -3
- package/plugins/booking/plugin.json +1 -1
- package/plugins/booking/tests/email.test.js +69 -0
- package/plugins/free-tier.lock.json +27 -19
- package/plugins/shopping-cart/CLAUDE.md +4 -0
- package/plugins/shopping-cart/admin/lib/email.js +191 -0
- package/plugins/shopping-cart/admin/views/orders.js +11 -3
- package/plugins/shopping-cart/plugin.json +2 -2
- package/plugins/shopping-cart/tests/email.test.js +69 -0
- package/plugins/support/CLAUDE.md +4 -0
- package/plugins/support/admin/lib/email.js +191 -0
- package/plugins/support/admin/views/desk.js +4 -1
- package/plugins/support/admin/views/inbox.js +6 -2
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/email.test.js +69 -0
- 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,300 @@
|
|
|
1
|
+
# Blog Plugin - Design Spec
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-04-07
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Domma CMS ships with a clean plugin architecture where bundled plugins (Todo, Notes, Contacts, Messages) register their collections and roles at enable-time. The Blog plugin extends this pattern in two meaningful ways:
|
|
11
|
+
|
|
12
|
+
1. It is the first plugin to own **public-facing URLs** - blog posts need proper routes at `/blog/*`, not just admin API endpoints.
|
|
13
|
+
2. It is the first plugin to exercise the **plugin-registered roles** mechanism (`plugins/<name>/roles/*.json`) in a real-world scenario, with `blog-author` and `blog-editor` as distinct contributor tiers below `admin`.
|
|
14
|
+
|
|
15
|
+
The `posts`, `categories`, and `comments` collections that previously existed as static seeds or demo data are removed from the core. They exist only when the Blog plugin is enabled.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Decisions
|
|
20
|
+
|
|
21
|
+
| Concern | Decision |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Public routing | `plugin.public.js` convention - new optional file, registered at Fastify root before the catch-all |
|
|
24
|
+
| Comments | Built-in but `commentsEnabled: false` by default |
|
|
25
|
+
| Roles | Plugin-registered: `blog-author`, `blog-editor` |
|
|
26
|
+
| Base path | Configurable (`basePath: '/blog'`); restart required to apply change |
|
|
27
|
+
| Publishing workflow | Draft → Scheduled → Published; lazy scheduled-post check on every request |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Platform Extension: `plugin.public.js`
|
|
32
|
+
|
|
33
|
+
### Problem
|
|
34
|
+
|
|
35
|
+
`plugin.js` is always registered under `/api/plugins/<name>`. There is no mechanism for a plugin to own a public URL path such as `/blog/*`.
|
|
36
|
+
|
|
37
|
+
### Solution
|
|
38
|
+
|
|
39
|
+
Introduce `plugin.public.js` as an optional convention alongside `plugin.js`:
|
|
40
|
+
|
|
41
|
+
- `server/services/plugins.js` `setupPlugin` detects `plugin.public.js` and stores its resolved path in the plugin registry entry (`publicEntry`).
|
|
42
|
+
- `server/server.js` iterates enabled plugins after loading them and calls `fastify.register(publicPlugin, { prefix: settings.basePath ?? '/blog', ...opts })` for each that has a `publicEntry`. `settings` is resolved at registration time via `getPluginSettings(name)` (the same function used in `plugin.js` registration) so the value is always current at startup. This registration happens **before** `routes/public.js` is registered, guaranteeing blog routes take precedence over the catch-all.
|
|
43
|
+
- `plugin.public.js` exports a default Fastify plugin function. It receives `{ settings, auth, config }` in `options`, identical to `plugin.js`.
|
|
44
|
+
- `teardownPlugin` marks the public entry as removed; a server restart is required for route changes to take effect (Fastify does not support live route deregistration).
|
|
45
|
+
|
|
46
|
+
### Benefit
|
|
47
|
+
|
|
48
|
+
Any future plugin that needs public URL ownership - Jobs board, Events, Portfolio, Shop - drops a `plugin.public.js` and gets it for free. No further platform changes needed.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Data Model
|
|
53
|
+
|
|
54
|
+
### Collection: `posts`
|
|
55
|
+
|
|
56
|
+
Registered via `plugins/blog/collections/posts/schema.json`. Managed through `server/services/collections.js`.
|
|
57
|
+
|
|
58
|
+
| Field | Type | Required | Notes |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| `title` | text | yes | |
|
|
61
|
+
| `slug` | text | yes | URL-safe, unique; auto-derived from title on create; editable |
|
|
62
|
+
| `excerpt` | textarea | no | Falls back to first 160 chars of content for meta description |
|
|
63
|
+
| `content` | markdown | no | Full body; CMS shortcode pipeline applied on render |
|
|
64
|
+
| `featuredImage` | text | no | Media ID from `/api/media` |
|
|
65
|
+
| `categories` | array | no | Array of category IDs |
|
|
66
|
+
| `tags` | array | no | Free-form strings |
|
|
67
|
+
| `authorId` | text | yes | User ID of post author |
|
|
68
|
+
| `status` | select | yes | `draft` \| `scheduled` \| `published` |
|
|
69
|
+
| `publishedAt` | datetime | no | Required when `status: scheduled`; set to now on manual publish |
|
|
70
|
+
| `seo.title` | text | no | Overrides site title pattern in `<title>` |
|
|
71
|
+
| `seo.description` | text | no | Overrides excerpt for `<meta name="description">` |
|
|
72
|
+
|
|
73
|
+
### Collection: `categories`
|
|
74
|
+
|
|
75
|
+
| Field | Type | Notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `name` | text | Required |
|
|
78
|
+
| `slug` | text | Required, URL-safe |
|
|
79
|
+
| `description` | textarea | |
|
|
80
|
+
| `parentId` | text | Optional; enables sub-categories |
|
|
81
|
+
|
|
82
|
+
### Collection: `comments`
|
|
83
|
+
|
|
84
|
+
| Field | Type | Notes |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `postId` | text | Required; FK to posts entry |
|
|
87
|
+
| `parentId` | text | Optional; FK to parent comment (threaded) |
|
|
88
|
+
| `authorName` | text | Required |
|
|
89
|
+
| `authorEmail` | text | Required |
|
|
90
|
+
| `authorUrl` | text | Optional |
|
|
91
|
+
| `body` | textarea | Required |
|
|
92
|
+
| `status` | select | `pending` \| `approved` \| `rejected` \| `spam` |
|
|
93
|
+
| `createdAt` | datetime | Set server-side on create |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Plugin Roles
|
|
98
|
+
|
|
99
|
+
Registered via `plugins/blog/roles/`:
|
|
100
|
+
|
|
101
|
+
### `blog-author`
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"name": "blog-author",
|
|
105
|
+
"label": "Blog Author",
|
|
106
|
+
"level": 2,
|
|
107
|
+
"permissions": ["blog-posts"],
|
|
108
|
+
"badgeClass": "badge-info",
|
|
109
|
+
"resources": [
|
|
110
|
+
{ "key": "blog-posts", "label": "Blog Posts", "actions": ["read", "create", "update"] }
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
Authors can create and edit their own draft posts. **They cannot publish.** Status changes to `published` or `scheduled` are blocked server-side for anyone whose role is not `blog-editor`, `admin`, or `super-admin`. The gate uses a **named-role check** (`['blog-editor', 'admin', 'super-admin'].includes(request.user.role)`), not a level comparison - both `blog-author` and `blog-editor` are level 2, so a level-based check would incorrectly grant authors publish access. An author attempting to call `POST /posts/:id/publish` or `POST /posts/:id/schedule` receives 403 regardless of whether the post is their own.
|
|
115
|
+
|
|
116
|
+
### `blog-editor`
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"name": "blog-editor",
|
|
120
|
+
"label": "Blog Editor",
|
|
121
|
+
"level": 2,
|
|
122
|
+
"permissions": ["blog-posts", "blog-categories", "blog-comments"],
|
|
123
|
+
"badgeClass": "badge-success",
|
|
124
|
+
"resources": [
|
|
125
|
+
{ "key": "blog-posts", "label": "Blog Posts", "actions": ["read", "create", "update", "delete", "publish"] },
|
|
126
|
+
{ "key": "blog-categories", "label": "Blog Categories", "actions": ["read", "create", "update", "delete"] },
|
|
127
|
+
{ "key": "blog-comments", "label": "Blog Comments", "actions": ["read", "update", "delete"] }
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Public Routes (`plugin.public.js`)
|
|
135
|
+
|
|
136
|
+
All routes registered at the Fastify root under `basePath` (default `/blog`). Static routes (`/feed.xml`, `/page/:n`, `/category/:slug`, `/tag/:tag`, `/author/:id`) **must be registered before** `/:slug` - Fastify prefers static over parametric routes only when they are registered first for the same prefix depth.
|
|
137
|
+
|
|
138
|
+
| Method | Path | Purpose |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| GET | `/` | Blog index - paginated published posts |
|
|
141
|
+
| GET | `/feed.xml` | RSS 2.0 feed (registered before `/:slug`) |
|
|
142
|
+
| GET | `/page/:n` | Pagination |
|
|
143
|
+
| GET | `/category/:slug` | Category archive |
|
|
144
|
+
| GET | `/tag/:tag` | Tag archive |
|
|
145
|
+
| GET | `/author/:id` | Author archive |
|
|
146
|
+
| GET | `/:slug` | Individual post - lazy scheduled check (`publishedAt <= now`) - registered last |
|
|
147
|
+
|
|
148
|
+
**Scheduled post visibility**: Every `GET /:slug` request checks `status === 'published' || (status === 'scheduled' && new Date(publishedAt) <= new Date())`. No background scheduler - lazy evaluation on each request. A post whose scheduled time arrives between requests becomes visible automatically on the next hit.
|
|
149
|
+
|
|
150
|
+
**Rendering**: Public blog pages use `server/templates/page.html` (the same shell as CMS pages). The plugin extends `renderer.js` with `renderBlogPage(template, data, postMeta)` which injects post-specific SEO: `<title>`, `<meta name="description">`, Open Graph `og:title`, `og:description`, `og:image` (featured image URL). Plugin-owned HTML templates live in `plugins/blog/templates/` - `index.html`, `post.html`, `category.html`, `tag.html`, `author.html`. These render the body content that slots into the page shell.
|
|
151
|
+
|
|
152
|
+
**Comment form**: When `commentsEnabled: true`, `post.html` includes a comment submission form that POSTs to `/api/plugins/blog/comments`. When `commentModeration: true` (default), new comments land in `status: pending` and are not shown until approved.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## API Routes (`plugin.js`)
|
|
157
|
+
|
|
158
|
+
All under `/api/plugins/blog`. All routes require `authenticate` **except** `POST /comments`, which is registered separately without the global authenticate hook to allow anonymous comment submission from public visitors.
|
|
159
|
+
|
|
160
|
+
| Method | Path | Auth | Purpose |
|
|
161
|
+
|---|---|---|---|
|
|
162
|
+
| GET | `/posts` | any | List posts (authors see own; editors/admins see all) |
|
|
163
|
+
| POST | `/posts` | author+ | Create post |
|
|
164
|
+
| GET | `/posts/:id` | any | Get post |
|
|
165
|
+
| PUT | `/posts/:id` | author (own) / editor+ | Update post |
|
|
166
|
+
| DELETE | `/posts/:id` | editor+ | Delete post |
|
|
167
|
+
| POST | `/posts/:id/publish` | editor+ | Set status:published, publishedAt:now |
|
|
168
|
+
| POST | `/posts/:id/schedule` | editor+ | Set status:scheduled + publishedAt |
|
|
169
|
+
| GET | `/categories` | any | List categories |
|
|
170
|
+
| POST | `/categories` | editor+ | Create category |
|
|
171
|
+
| PUT | `/categories/:id` | editor+ | Update category |
|
|
172
|
+
| DELETE | `/categories/:id` | editor+ | Delete category |
|
|
173
|
+
| GET | `/comments` | editor+ | List comments (filterable by status, postId) |
|
|
174
|
+
| POST | `/comments` | public (no auth) | Submit comment (rate-limited, honeypot field) |
|
|
175
|
+
| PUT | `/comments/:id` | editor+ | Moderate (approve/reject/spam) |
|
|
176
|
+
| DELETE | `/comments/:id` | editor+ | Delete comment |
|
|
177
|
+
| GET | `/settings` | admin+ | Get blog plugin settings |
|
|
178
|
+
| PUT | `/settings` | admin+ | Save blog plugin settings to config/plugins.json |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Admin UI
|
|
183
|
+
|
|
184
|
+
Single "Blog" sidebar entry, with sub-navigation to four views:
|
|
185
|
+
|
|
186
|
+
### Posts list (`#/plugins/blog/posts`)
|
|
187
|
+
- `T.create()` table: title, status badge, author, categories, publishedAt, edit/delete actions
|
|
188
|
+
- Filter bar: status select, category select, author select, search input
|
|
189
|
+
- "New Post" → navigates to editor with blank state
|
|
190
|
+
|
|
191
|
+
### Post editor (`#/plugins/blog/posts/new` and `#/plugins/blog/posts/:id`)
|
|
192
|
+
- Two-column layout: main area (title input, slug input with auto-derive toggle, content textarea), sidebar (status/publishedAt picker, categories checklist, tags input, featured image picker via media modal, SEO accordion)
|
|
193
|
+
- Status transitions: Save Draft → always available; Publish Now → sets status:published; Schedule → reveals datetime picker, sets status:scheduled
|
|
194
|
+
- Slug derives from title on first edit; once manually touched it becomes independent with a "⚠ Changing slug breaks existing links" warning on published posts
|
|
195
|
+
|
|
196
|
+
### Categories (`#/plugins/blog/categories`)
|
|
197
|
+
- Inline-editable table: name, slug, description, parent, post count
|
|
198
|
+
- Create/edit/delete - no separate modal needed for simple fields
|
|
199
|
+
|
|
200
|
+
### Comments (`#/plugins/blog/comments`)
|
|
201
|
+
- Visible only when `commentsEnabled: true`
|
|
202
|
+
- Three tabs: Pending | Approved | Spam
|
|
203
|
+
- Per-row actions: Approve, Reject, Spam, Delete
|
|
204
|
+
- Post title shown as a link to the admin post editor
|
|
205
|
+
|
|
206
|
+
### Blog settings (`#/plugins/blog/settings`)
|
|
207
|
+
- `F.create()` form: basePath, postsPerPage, commentsEnabled toggle, commentModeration toggle, feedLength
|
|
208
|
+
- Save POSTs to `PUT /api/plugins/blog/settings`
|
|
209
|
+
- Banner: "Base path and posts-per-page changes require a server restart"
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## `config.js` Defaults
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
export default {
|
|
217
|
+
basePath: '/blog',
|
|
218
|
+
postsPerPage: 10,
|
|
219
|
+
commentsEnabled: false,
|
|
220
|
+
commentModeration: true,
|
|
221
|
+
feedLength: 20,
|
|
222
|
+
storage: { adapter: 'file' }
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Removing Innate Collections
|
|
229
|
+
|
|
230
|
+
`posts`, `categories`, and `comments` were previously demo-seeded or referenced as preset collections. As of the role-collapse cleanup they no longer exist in `content/collections/` or `server/services/presetCollections.js`. This spec confirms they belong exclusively to the Blog plugin. No further removal is needed - the audit step in the build sequence should grep for any lingering hardcoded references to these slugs in non-blog code.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Plugin Layout
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
plugins/blog/
|
|
238
|
+
plugin.json
|
|
239
|
+
plugin.js API routes
|
|
240
|
+
plugin.public.js Public SSR routes
|
|
241
|
+
config.js
|
|
242
|
+
CLAUDE.md
|
|
243
|
+
roles/
|
|
244
|
+
blog-author.json
|
|
245
|
+
blog-editor.json
|
|
246
|
+
collections/
|
|
247
|
+
posts/schema.json
|
|
248
|
+
categories/schema.json
|
|
249
|
+
comments/schema.json
|
|
250
|
+
templates/ Public SSR HTML (slots into page.html shell)
|
|
251
|
+
index.html
|
|
252
|
+
post.html
|
|
253
|
+
category.html
|
|
254
|
+
tag.html
|
|
255
|
+
author.html
|
|
256
|
+
admin/
|
|
257
|
+
views/
|
|
258
|
+
blog.js Entry view (posts list)
|
|
259
|
+
post-editor.js Post editor
|
|
260
|
+
categories.js
|
|
261
|
+
comments.js
|
|
262
|
+
settings.js
|
|
263
|
+
templates/
|
|
264
|
+
blog.html
|
|
265
|
+
post-editor.html
|
|
266
|
+
categories.html
|
|
267
|
+
comments.html
|
|
268
|
+
settings.html
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Build Sequence
|
|
274
|
+
|
|
275
|
+
1. **Platform extension** - extend `plugins.js` to detect and store `publicEntry`; extend `server.js` to register public plugins before `routes/public.js`
|
|
276
|
+
2. **renderer.js** - add `renderBlogPage()` helper
|
|
277
|
+
3. **Collection schemas** - write the three `schema.json` files
|
|
278
|
+
4. **Role files** - write `blog-author.json` and `blog-editor.json`
|
|
279
|
+
5. **`plugin.js`** - API routes (posts CRUD + publish/schedule, categories, comments, settings)
|
|
280
|
+
6. **`plugin.public.js`** - public SSR routes + RSS feed
|
|
281
|
+
7. **Public templates** - `index.html`, `post.html`, `category.html`, `tag.html`, `author.html`
|
|
282
|
+
8. **Admin views** - posts list, post editor, categories, comments, settings
|
|
283
|
+
9. **`plugin.json` + `config.js` + `CLAUDE.md`**
|
|
284
|
+
10. **`config/plugins.json`** - add blog entry (disabled by default; enable to test)
|
|
285
|
+
11. **Audit** - grep codebase for hardcoded `posts`/`categories`/`comments` collection slug references outside `plugins/blog/`
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Verification
|
|
290
|
+
|
|
291
|
+
- **Fresh install**: `npm run fresh` → enable Blog plugin → confirm three collections appear in `content/collections/`, two roles in `content/collections/roles/data.json`, and `/blog` returns a valid (empty) index page
|
|
292
|
+
- **Public routing**: create a published post with slug `hello-world` → `GET /blog/hello-world` returns 200 with correct title in `<title>` tag; a draft post with the same slug returns 404
|
|
293
|
+
- **Scheduled post**: create a post with `status:scheduled`, `publishedAt` 10 seconds in the future → `GET /blog/:slug` returns 404; wait 10 seconds; same URL returns 200
|
|
294
|
+
- **plugin.public.js convention**: disable Blog plugin → restart → `/blog` returns 404 (catch-all serves a CMS 404 page); re-enable → restart → `/blog` returns 200
|
|
295
|
+
- **Role gate**: log in as `blog-author` → can create/edit own draft posts → attempting to publish returns 403; log in as `blog-editor` → publish succeeds
|
|
296
|
+
- **Comments**: enable `commentsEnabled`, submit a comment on a post → lands in Pending tab in admin; approve it → appears below the post on the public page
|
|
297
|
+
- **RSS**: `GET /blog/feed.xml` returns valid XML with `<rss version="2.0">` and correct `<item>` entries for published posts only
|
|
298
|
+
- **Category archive**: assign a post to category `news` → `GET /blog/category/news` lists only posts in that category
|
|
299
|
+
- **SEO injection**: post with custom `seo.title` → `<title>` matches the custom value; post without → title follows the site pattern `{post.title} | {site.title}`
|
|
300
|
+
- **Base path change**: change `basePath` to `/news` in settings → restart → `/news` returns 200, `/blog` falls through to CMS catch-all
|