domma-cms 0.10.0 → 0.13.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.
Files changed (121) hide show
  1. package/CLAUDE.md +248 -159
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/api.js +1 -1
  4. package/admin/js/app.js +7 -3
  5. package/admin/js/config/sidebar-config.js +1 -1
  6. package/admin/js/http-interceptor.js +1 -0
  7. package/admin/js/lib/safe-html.js +1 -0
  8. package/admin/js/templates/documentation.html +611 -2
  9. package/admin/js/templates/layouts.html +5 -4
  10. package/admin/js/templates/notifications.html +14 -0
  11. package/admin/js/templates/plugin-marketplace.html +16 -0
  12. package/admin/js/templates/plugins.html +17 -5
  13. package/admin/js/views/index.js +1 -1
  14. package/admin/js/views/layouts.js +1 -16
  15. package/admin/js/views/notifications.js +1 -0
  16. package/admin/js/views/plugin-marketplace.js +1 -0
  17. package/admin/js/views/plugins.js +16 -16
  18. package/config/navigation.json +5 -72
  19. package/config/plugins.json +10 -14
  20. package/config/presets.json +50 -13
  21. package/config/site.json +11 -63
  22. package/package.json +2 -1
  23. package/plugins/_template/admin/templates/index.html +17 -0
  24. package/plugins/_template/admin/views/index.js +19 -0
  25. package/plugins/_template/config.js +8 -0
  26. package/plugins/_template/plugin.js +23 -0
  27. package/plugins/_template/plugin.json +34 -0
  28. package/plugins/analytics/plugin.json +41 -31
  29. package/plugins/blog/admin/templates/blog.html +22 -0
  30. package/plugins/blog/admin/templates/categories.html +7 -0
  31. package/plugins/blog/admin/templates/comments.html +11 -0
  32. package/plugins/blog/admin/templates/post-editor.html +97 -0
  33. package/plugins/blog/admin/templates/settings.html +11 -0
  34. package/plugins/blog/admin/views/blog.js +183 -0
  35. package/plugins/blog/admin/views/categories.js +235 -0
  36. package/plugins/blog/admin/views/comments.js +187 -0
  37. package/plugins/blog/admin/views/post-editor.js +291 -0
  38. package/plugins/blog/admin/views/settings.js +100 -0
  39. package/plugins/blog/collections/categories/schema.json +12 -0
  40. package/plugins/blog/collections/comments/schema.json +16 -0
  41. package/plugins/blog/collections/posts/schema.json +19 -0
  42. package/plugins/blog/config.js +8 -0
  43. package/plugins/blog/plugin.js +352 -0
  44. package/plugins/blog/plugin.json +96 -0
  45. package/plugins/blog/roles/blog-author.json +10 -0
  46. package/plugins/blog/roles/blog-editor.json +12 -0
  47. package/plugins/blog/templates/author.html +9 -0
  48. package/plugins/blog/templates/category.html +9 -0
  49. package/plugins/blog/templates/index.html +9 -0
  50. package/plugins/blog/templates/post.html +17 -0
  51. package/plugins/blog/templates/tag.html +9 -0
  52. package/plugins/contacts/collections/user-contact-groups/schema.json +1 -1
  53. package/plugins/contacts/collections/user-contacts/schema.json +1 -1
  54. package/plugins/contacts/plugin.js +4 -10
  55. package/plugins/contacts/plugin.json +13 -3
  56. package/plugins/notes/collections/user-notes/schema.json +1 -1
  57. package/plugins/notes/plugin.js +3 -9
  58. package/plugins/notes/plugin.json +13 -3
  59. package/plugins/site-search/plugin.json +5 -2
  60. package/plugins/theme-switcher/plugin.json +1 -1
  61. package/plugins/todo/collections/todos/schema.json +1 -1
  62. package/plugins/todo/plugin.js +3 -9
  63. package/plugins/todo/plugin.json +13 -3
  64. package/public/css/site.css +1 -1
  65. package/scripts/build.js +48 -0
  66. package/scripts/create-plugin.js +113 -0
  67. package/scripts/fresh.js +6 -7
  68. package/scripts/gen-instance-secret.js +46 -0
  69. package/scripts/reset.js +3 -3
  70. package/scripts/setup.js +31 -13
  71. package/server/middleware/auth.js +48 -0
  72. package/server/middleware/managerAuth.js +36 -0
  73. package/server/routes/api/actions.js +1 -1
  74. package/server/routes/api/auth.js +4 -3
  75. package/server/routes/api/layouts.js +173 -49
  76. package/server/routes/api/notifications.js +155 -0
  77. package/server/routes/api/plugin-marketplace.js +75 -0
  78. package/server/routes/api/users.js +1 -1
  79. package/server/routes/api/views.js +1 -1
  80. package/server/routes/public.js +4 -9
  81. package/server/server.js +32 -3
  82. package/server/services/actions.js +1 -1
  83. package/server/services/managerClient.js +182 -0
  84. package/server/services/markdown.js +52 -14
  85. package/server/services/permissionRegistry.js +245 -173
  86. package/server/services/pluginInstaller.js +301 -0
  87. package/server/services/plugins.js +117 -10
  88. package/server/services/presetCollections.js +66 -251
  89. package/server/services/renderer.js +99 -0
  90. package/server/services/roles.js +191 -39
  91. package/server/services/users.js +1 -1
  92. package/server/services/views.js +1 -1
  93. package/server/templates/page.html +2 -2
  94. package/plugins/docs/admin/templates/docs.html +0 -69
  95. package/plugins/docs/admin/views/docs.js +0 -276
  96. package/plugins/docs/config.js +0 -8
  97. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +0 -11
  98. package/plugins/docs/data/folders.json +0 -9
  99. package/plugins/docs/data/templates.json +0 -1
  100. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +0 -5
  101. package/plugins/docs/plugin.js +0 -375
  102. package/plugins/docs/plugin.json +0 -23
  103. package/plugins/form-builder/data/forms/contacts.json +0 -66
  104. package/plugins/form-builder/data/forms/enquiries.json +0 -103
  105. package/plugins/form-builder/data/forms/feedback.json +0 -131
  106. package/plugins/form-builder/data/forms/notes.json +0 -79
  107. package/plugins/form-builder/data/forms/to-do.json +0 -100
  108. package/plugins/form-builder/data/submissions/contacts.json +0 -1
  109. package/plugins/form-builder/data/submissions/enquiries.json +0 -1
  110. package/plugins/form-builder/data/submissions/feedback.json +0 -1
  111. package/plugins/form-builder/data/submissions/notes.json +0 -1
  112. package/plugins/form-builder/data/submissions/to-do.json +0 -1
  113. package/plugins/garage/admin/templates/garage.html +0 -111
  114. package/plugins/garage/admin/views/garage.js +0 -622
  115. package/plugins/garage/collections/garage-vehicles/schema.json +0 -101
  116. package/plugins/garage/config.js +0 -18
  117. package/plugins/garage/data/vehicles.json +0 -70
  118. package/plugins/garage/plugin.js +0 -398
  119. package/plugins/garage/plugin.json +0 -33
  120. package/scripts/seed.js +0 -1996
  121. package/server/services/userTypes.js +0 -227
@@ -0,0 +1,352 @@
1
+ import defaultConfig from './config.js';
2
+ import {createEntry, deleteEntry, getEntry, listEntries, updateEntry} from '../../server/services/collections.js';
3
+ import {getConfig, saveConfig} from '../../server/config.js';
4
+
5
+ const POSTS_SLUG = 'blog-posts';
6
+ const CATEGORIES_SLUG = 'blog-categories';
7
+ const COMMENTS_SLUG = 'blog-comments';
8
+
9
+ const PUBLISH_ROLES = ['blog-editor', 'admin', 'super-admin'];
10
+
11
+ function canPublish(user) {
12
+ return PUBLISH_ROLES.includes(user?.role);
13
+ }
14
+
15
+ function isEditorOrAdmin(user) {
16
+ return canPublish(user);
17
+ }
18
+
19
+ /** Derive a URL slug from a title string. */
20
+ function slugify(str) {
21
+ return String(str)
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, '-')
24
+ .replace(/^-+|-+$/g, '');
25
+ }
26
+
27
+ function toPost(entry) {
28
+ return {
29
+ id: entry.id,
30
+ ...entry.data,
31
+ createdAt: entry.meta.createdAt,
32
+ updatedAt: entry.meta.updatedAt
33
+ };
34
+ }
35
+
36
+ function toCategory(entry) {
37
+ return {
38
+ id: entry.id,
39
+ ...entry.data,
40
+ createdAt: entry.meta.createdAt,
41
+ updatedAt: entry.meta.updatedAt
42
+ };
43
+ }
44
+
45
+ function toComment(entry) {
46
+ return {
47
+ id: entry.id,
48
+ ...entry.data,
49
+ createdAt: entry.meta.createdAt,
50
+ updatedAt: entry.meta.updatedAt
51
+ };
52
+ }
53
+
54
+ export default async function blogPlugin(fastify, options) {
55
+ const { authenticate, requireAdmin } = options.auth;
56
+ const settings = { ...defaultConfig, ...(options.settings || {}) };
57
+
58
+ // -------------------------------------------------------------------------
59
+ // Posts
60
+ // -------------------------------------------------------------------------
61
+
62
+ /** GET /api/plugins/blog/posts */
63
+ fastify.get('/posts', { preHandler: [authenticate] }, async (request, reply) => {
64
+ const user = request.user;
65
+ const { entries } = await listEntries(POSTS_SLUG, { limit: 100000, sort: 'createdAt', order: 'desc' });
66
+ let posts = entries.map(toPost);
67
+
68
+ if (!isEditorOrAdmin(user) && user?.role === 'blog-author') {
69
+ const uid = user?.id ?? user?.sub ?? null;
70
+ posts = posts.filter(p => p.authorId === uid);
71
+ }
72
+
73
+ return reply.send(posts);
74
+ });
75
+
76
+ /** POST /api/plugins/blog/posts */
77
+ fastify.post('/posts', { preHandler: [authenticate] }, async (request, reply) => {
78
+ const user = request.user;
79
+ const uid = user?.id ?? user?.sub ?? null;
80
+ const body = request.body ?? {};
81
+
82
+ const {
83
+ title, content, excerpt, featuredImage,
84
+ categories, tags, status, slug
85
+ } = body;
86
+
87
+ const derivedSlug = slug
88
+ ? String(slug).trim()
89
+ : slugify(title ?? '');
90
+
91
+ const entry = await createEntry(POSTS_SLUG, {
92
+ title: typeof title === 'string' ? title.trim() : '',
93
+ slug: derivedSlug,
94
+ content: typeof content === 'string' ? content : '',
95
+ excerpt: typeof excerpt === 'string' ? excerpt.trim() : '',
96
+ featuredImage: featuredImage ?? null,
97
+ categories: Array.isArray(categories) ? categories : [],
98
+ tags: Array.isArray(tags) ? tags : [],
99
+ status: status ?? 'draft',
100
+ authorId: uid
101
+ }, { createdBy: uid, source: 'admin' });
102
+
103
+ return reply.code(201).send(toPost(entry));
104
+ });
105
+
106
+ /** GET /api/plugins/blog/posts/:id */
107
+ fastify.get('/posts/:id', { preHandler: [authenticate] }, async (request, reply) => {
108
+ const entry = await getEntry(POSTS_SLUG, request.params.id);
109
+ if (!entry) return reply.code(404).send({ error: 'Post not found' });
110
+ return reply.send(toPost(entry));
111
+ });
112
+
113
+ /** PUT /api/plugins/blog/posts/:id */
114
+ fastify.put('/posts/:id', { preHandler: [authenticate] }, async (request, reply) => {
115
+ const user = request.user;
116
+ const uid = user?.id ?? user?.sub ?? null;
117
+ const { id } = request.params;
118
+
119
+ const entry = await getEntry(POSTS_SLUG, id);
120
+ if (!entry) return reply.code(404).send({ error: 'Post not found' });
121
+
122
+ if (!isEditorOrAdmin(user) && entry.data.authorId !== uid) {
123
+ return reply.code(403).send({ error: 'Forbidden' });
124
+ }
125
+
126
+ const updates = request.body ?? {};
127
+ const merged = { ...entry.data, ...updates };
128
+
129
+ const updated = await updateEntry(POSTS_SLUG, id, merged);
130
+ return reply.send(toPost(updated));
131
+ });
132
+
133
+ /** DELETE /api/plugins/blog/posts/:id */
134
+ fastify.delete('/posts/:id', { preHandler: [authenticate] }, async (request, reply) => {
135
+ const user = request.user;
136
+ if (!isEditorOrAdmin(user)) {
137
+ return reply.code(403).send({ error: 'Forbidden' });
138
+ }
139
+
140
+ const entry = await getEntry(POSTS_SLUG, request.params.id);
141
+ if (!entry) return reply.code(404).send({ error: 'Post not found' });
142
+
143
+ await deleteEntry(POSTS_SLUG, request.params.id);
144
+ return reply.send({ ok: true });
145
+ });
146
+
147
+ /** POST /api/plugins/blog/posts/:id/publish */
148
+ fastify.post('/posts/:id/publish', { preHandler: [authenticate] }, async (request, reply) => {
149
+ const user = request.user;
150
+ if (!canPublish(user)) {
151
+ return reply.code(403).send({ error: 'Forbidden' });
152
+ }
153
+
154
+ const entry = await getEntry(POSTS_SLUG, request.params.id);
155
+ if (!entry) return reply.code(404).send({ error: 'Post not found' });
156
+
157
+ const updated = await updateEntry(POSTS_SLUG, request.params.id, {
158
+ ...entry.data,
159
+ status: 'published',
160
+ publishedAt: new Date().toISOString()
161
+ });
162
+
163
+ return reply.send(toPost(updated));
164
+ });
165
+
166
+ /** POST /api/plugins/blog/posts/:id/schedule */
167
+ fastify.post('/posts/:id/schedule', { preHandler: [authenticate] }, async (request, reply) => {
168
+ const user = request.user;
169
+ if (!canPublish(user)) {
170
+ return reply.code(403).send({ error: 'Forbidden' });
171
+ }
172
+
173
+ const { publishedAt } = request.body ?? {};
174
+ if (!publishedAt) {
175
+ return reply.code(400).send({ error: 'publishedAt is required' });
176
+ }
177
+
178
+ const entry = await getEntry(POSTS_SLUG, request.params.id);
179
+ if (!entry) return reply.code(404).send({ error: 'Post not found' });
180
+
181
+ const updated = await updateEntry(POSTS_SLUG, request.params.id, {
182
+ ...entry.data,
183
+ status: 'scheduled',
184
+ publishedAt: String(publishedAt)
185
+ });
186
+
187
+ return reply.send(toPost(updated));
188
+ });
189
+
190
+ // -------------------------------------------------------------------------
191
+ // Categories
192
+ // -------------------------------------------------------------------------
193
+
194
+ /** GET /api/plugins/blog/categories */
195
+ fastify.get('/categories', { preHandler: [authenticate] }, async (request, reply) => {
196
+ const { entries } = await listEntries(CATEGORIES_SLUG, { limit: 10000, sort: 'createdAt', order: 'asc' });
197
+ return reply.send(entries.map(toCategory));
198
+ });
199
+
200
+ /** POST /api/plugins/blog/categories */
201
+ fastify.post('/categories', { preHandler: [authenticate] }, async (request, reply) => {
202
+ if (!isEditorOrAdmin(request.user)) {
203
+ return reply.code(403).send({ error: 'Forbidden' });
204
+ }
205
+
206
+ const { name, slug, description } = request.body ?? {};
207
+ if (!name || typeof name !== 'string' || !name.trim()) {
208
+ return reply.code(400).send({ error: 'name is required' });
209
+ }
210
+
211
+ const derivedSlug = slug ? String(slug).trim() : slugify(name);
212
+
213
+ const entry = await createEntry(CATEGORIES_SLUG, {
214
+ name: name.trim(),
215
+ slug: derivedSlug,
216
+ description: typeof description === 'string' ? description.trim() : ''
217
+ }, { source: 'admin' });
218
+
219
+ return reply.code(201).send(toCategory(entry));
220
+ });
221
+
222
+ /** PUT /api/plugins/blog/categories/:id */
223
+ fastify.put('/categories/:id', { preHandler: [authenticate] }, async (request, reply) => {
224
+ if (!isEditorOrAdmin(request.user)) {
225
+ return reply.code(403).send({ error: 'Forbidden' });
226
+ }
227
+
228
+ const entry = await getEntry(CATEGORIES_SLUG, request.params.id);
229
+ if (!entry) return reply.code(404).send({ error: 'Category not found' });
230
+
231
+ const updates = request.body ?? {};
232
+ const merged = { ...entry.data, ...updates };
233
+
234
+ const updated = await updateEntry(CATEGORIES_SLUG, request.params.id, merged);
235
+ return reply.send(toCategory(updated));
236
+ });
237
+
238
+ /** DELETE /api/plugins/blog/categories/:id */
239
+ fastify.delete('/categories/:id', { preHandler: [authenticate] }, async (request, reply) => {
240
+ if (!isEditorOrAdmin(request.user)) {
241
+ return reply.code(403).send({ error: 'Forbidden' });
242
+ }
243
+
244
+ const entry = await getEntry(CATEGORIES_SLUG, request.params.id);
245
+ if (!entry) return reply.code(404).send({ error: 'Category not found' });
246
+
247
+ await deleteEntry(CATEGORIES_SLUG, request.params.id);
248
+ return reply.send({ ok: true });
249
+ });
250
+
251
+ // -------------------------------------------------------------------------
252
+ // Comments
253
+ // -------------------------------------------------------------------------
254
+
255
+ /** GET /api/plugins/blog/comments */
256
+ fastify.get('/comments', { preHandler: [authenticate] }, async (request, reply) => {
257
+ const { status: statusFilter, postId } = request.query;
258
+ const { entries } = await listEntries(COMMENTS_SLUG, { limit: 100000, sort: 'createdAt', order: 'desc' });
259
+ let comments = entries.map(toComment);
260
+
261
+ if (statusFilter) {
262
+ comments = comments.filter(c => c.status === statusFilter);
263
+ }
264
+ if (postId) {
265
+ comments = comments.filter(c => c.postId === postId);
266
+ }
267
+
268
+ return reply.send(comments);
269
+ });
270
+
271
+ /** POST /api/plugins/blog/comments — public, no authenticate */
272
+ fastify.post('/comments', async (request, reply) => {
273
+ const body = request.body ?? {};
274
+
275
+ // Honeypot: non-empty `website` field = bot, silently discard
276
+ if (body.website) {
277
+ return reply.code(201).send({ ok: true });
278
+ }
279
+
280
+ const { postId, authorName, authorEmail, authorUrl, body: commentBody } = body;
281
+
282
+ if (!postId) return reply.code(400).send({ error: 'postId is required' });
283
+ if (!authorName) return reply.code(400).send({ error: 'authorName is required' });
284
+ if (!authorEmail) return reply.code(400).send({ error: 'authorEmail is required' });
285
+ if (!commentBody) return reply.code(400).send({ error: 'body is required' });
286
+
287
+ const commentStatus = settings.commentModeration !== false ? 'pending' : 'approved';
288
+
289
+ const entry = await createEntry(COMMENTS_SLUG, {
290
+ postId: String(postId),
291
+ authorName: String(authorName).trim(),
292
+ authorEmail: String(authorEmail).trim(),
293
+ authorUrl: authorUrl ? String(authorUrl).trim() : '',
294
+ body: String(commentBody),
295
+ status: commentStatus,
296
+ createdAt: new Date().toISOString()
297
+ }, { source: 'public' });
298
+
299
+ return reply.code(201).send(toComment(entry));
300
+ });
301
+
302
+ /** PUT /api/plugins/blog/comments/:id */
303
+ fastify.put('/comments/:id', { preHandler: [authenticate] }, async (request, reply) => {
304
+ const entry = await getEntry(COMMENTS_SLUG, request.params.id);
305
+ if (!entry) return reply.code(404).send({ error: 'Comment not found' });
306
+
307
+ const { status } = request.body ?? {};
308
+ const VALID_STATUSES = ['pending', 'approved', 'rejected', 'spam'];
309
+
310
+ const merged = { ...entry.data };
311
+ if (status !== undefined) {
312
+ if (!VALID_STATUSES.includes(status)) {
313
+ return reply.code(400).send({ error: `status must be one of: ${VALID_STATUSES.join(', ')}` });
314
+ }
315
+ merged.status = status;
316
+ }
317
+
318
+ const updated = await updateEntry(COMMENTS_SLUG, request.params.id, merged);
319
+ return reply.send(toComment(updated));
320
+ });
321
+
322
+ /** DELETE /api/plugins/blog/comments/:id */
323
+ fastify.delete('/comments/:id', { preHandler: [authenticate] }, async (request, reply) => {
324
+ const entry = await getEntry(COMMENTS_SLUG, request.params.id);
325
+ if (!entry) return reply.code(404).send({ error: 'Comment not found' });
326
+
327
+ await deleteEntry(COMMENTS_SLUG, request.params.id);
328
+ return reply.send({ ok: true });
329
+ });
330
+
331
+ // -------------------------------------------------------------------------
332
+ // Settings
333
+ // -------------------------------------------------------------------------
334
+
335
+ /** GET /api/plugins/blog/settings */
336
+ fastify.get('/settings', { preHandler: [authenticate] }, async (request, reply) => {
337
+ const cfg = getConfig();
338
+ const overrides = cfg.plugins?.blog?.settings ?? {};
339
+ return reply.send({ ...defaultConfig, ...overrides });
340
+ });
341
+
342
+ /** PUT /api/plugins/blog/settings */
343
+ fastify.put('/settings', { preHandler: [requireAdmin] }, async (request, reply) => {
344
+ const body = request.body ?? {};
345
+ const cfg = getConfig();
346
+ cfg.plugins ??= {};
347
+ cfg.plugins.blog ??= {};
348
+ cfg.plugins.blog.settings = body;
349
+ await saveConfig(cfg);
350
+ return reply.send({ ok: true });
351
+ });
352
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "blog",
3
+ "displayName": "Blog",
4
+ "version": "1.0.0",
5
+ "description": "Public-facing blog with posts, categories, and comments.",
6
+ "author": "Domma CMS",
7
+ "date": "2026-04-07",
8
+ "icon": "file-text",
9
+ "admin": {
10
+ "sidebar": [
11
+ {
12
+ "id": "blog",
13
+ "text": "Blog",
14
+ "icon": "file-text",
15
+ "url": "#/plugins/blog",
16
+ "section": "#/plugins/blog",
17
+ "children": [
18
+ {
19
+ "id": "blog-posts",
20
+ "text": "Posts",
21
+ "url": "#/plugins/blog/posts"
22
+ },
23
+ {
24
+ "id": "blog-categories",
25
+ "text": "Categories",
26
+ "url": "#/plugins/blog/categories"
27
+ },
28
+ {
29
+ "id": "blog-comments",
30
+ "text": "Comments",
31
+ "url": "#/plugins/blog/comments"
32
+ },
33
+ {
34
+ "id": "blog-settings",
35
+ "text": "Settings",
36
+ "url": "#/plugins/blog/settings"
37
+ }
38
+ ]
39
+ }
40
+ ],
41
+ "routes": [
42
+ {
43
+ "path": "/plugins/blog/posts",
44
+ "view": "plugin-blog-posts",
45
+ "title": "Blog Posts - Domma CMS"
46
+ },
47
+ {
48
+ "path": "/plugins/blog/posts/new",
49
+ "view": "plugin-blog-post-editor",
50
+ "title": "New Post - Domma CMS"
51
+ },
52
+ {
53
+ "path": "/plugins/blog/posts/:id",
54
+ "view": "plugin-blog-post-editor",
55
+ "title": "Edit Post - Domma CMS"
56
+ },
57
+ {
58
+ "path": "/plugins/blog/categories",
59
+ "view": "plugin-blog-categories",
60
+ "title": "Blog Categories - Domma CMS"
61
+ },
62
+ {
63
+ "path": "/plugins/blog/comments",
64
+ "view": "plugin-blog-comments",
65
+ "title": "Blog Comments - Domma CMS"
66
+ },
67
+ {
68
+ "path": "/plugins/blog/settings",
69
+ "view": "plugin-blog-settings",
70
+ "title": "Blog Settings - Domma CMS"
71
+ }
72
+ ],
73
+ "views": {
74
+ "plugin-blog-posts": {
75
+ "entry": "blog/admin/views/blog.js?v=73a4a936",
76
+ "exportName": "blogView"
77
+ },
78
+ "plugin-blog-post-editor": {
79
+ "entry": "blog/admin/views/post-editor.js?v=3288949c",
80
+ "exportName": "postEditorView"
81
+ },
82
+ "plugin-blog-categories": {
83
+ "entry": "blog/admin/views/categories.js?v=489c8dfe",
84
+ "exportName": "categoriesView"
85
+ },
86
+ "plugin-blog-comments": {
87
+ "entry": "blog/admin/views/comments.js?v=47c59ee8",
88
+ "exportName": "commentsView"
89
+ },
90
+ "plugin-blog-settings": {
91
+ "entry": "blog/admin/views/settings.js?v=3fab4235",
92
+ "exportName": "settingsView"
93
+ }
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "blog-author",
3
+ "label": "Blog Author",
4
+ "level": 2,
5
+ "permissions": ["blog-posts"],
6
+ "badgeClass": "badge-info",
7
+ "resources": [
8
+ { "key": "blog-posts", "label": "Blog Posts", "actions": ["read", "create", "update"] }
9
+ ]
10
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "blog-editor",
3
+ "label": "Blog Editor",
4
+ "level": 2,
5
+ "permissions": ["blog-posts", "blog-categories", "blog-comments"],
6
+ "badgeClass": "badge-success",
7
+ "resources": [
8
+ { "key": "blog-posts", "label": "Blog Posts", "actions": ["read", "create", "update", "delete", "publish"] },
9
+ { "key": "blog-categories", "label": "Blog Categories", "actions": ["read", "create", "update", "delete"] },
10
+ { "key": "blog-comments", "label": "Blog Comments", "actions": ["read", "update", "delete"] }
11
+ ]
12
+ }
@@ -0,0 +1,9 @@
1
+ <main class="container" style="padding: 2rem 0;">
2
+ <h1 class="mb-4">{{archiveTitle}}</h1>
3
+ <div id="blog-posts-list">
4
+ {{posts}}
5
+ </div>
6
+ <div id="blog-pagination" class="mt-4">
7
+ {{pagination}}
8
+ </div>
9
+ </main>
@@ -0,0 +1,9 @@
1
+ <main class="container" style="padding: 2rem 0;">
2
+ <h1 class="mb-4">{{archiveTitle}}</h1>
3
+ <div id="blog-posts-list">
4
+ {{posts}}
5
+ </div>
6
+ <div id="blog-pagination" class="mt-4">
7
+ {{pagination}}
8
+ </div>
9
+ </main>
@@ -0,0 +1,9 @@
1
+ <main class="container" style="padding: 2rem 0;">
2
+ <h1 class="mb-4">{{archiveTitle}}</h1>
3
+ <div id="blog-posts-list">
4
+ {{posts}}
5
+ </div>
6
+ <div id="blog-pagination" class="mt-4">
7
+ {{pagination}}
8
+ </div>
9
+ </main>
@@ -0,0 +1,17 @@
1
+ <main class="container" style="padding: 2rem 0;">
2
+ <article>
3
+ <header class="mb-4">
4
+ <h1>{{title}}</h1>
5
+ <p class="text-muted">by {{authorName}} &bull; <time>{{publishedAt}}</time></p>
6
+ <div class="mb-2">{{categories}}{{tags}}</div>
7
+ </header>
8
+ <div class="post-content mb-5">
9
+ {{content}}
10
+ </div>
11
+ </article>
12
+
13
+ <section id="comments" class="mt-5">
14
+ {{comments}}
15
+ {{commentForm}}
16
+ </section>
17
+ </main>
@@ -0,0 +1,9 @@
1
+ <main class="container" style="padding: 2rem 0;">
2
+ <h1 class="mb-4">{{archiveTitle}}</h1>
3
+ <div id="blog-posts-list">
4
+ {{posts}}
5
+ </div>
6
+ <div id="blog-pagination" class="mt-4">
7
+ {{pagination}}
8
+ </div>
9
+ </main>
@@ -1,5 +1,5 @@
1
1
  {
2
- "slug": "user-contact-groups",
2
+ "slug": "contacts-groups",
3
3
  "title": "Contact Groups",
4
4
  "description": "Groups for the Contacts plugin.",
5
5
  "plugin": "contacts",
@@ -1,5 +1,5 @@
1
1
  {
2
- "slug": "user-contacts",
2
+ "slug": "contacts-contacts",
3
3
  "title": "Contacts",
4
4
  "description": "Contacts managed by the Contacts plugin.",
5
5
  "plugin": "contacts",
@@ -1,14 +1,8 @@
1
1
  import defaultConfig from './config.js';
2
- import {
3
- listEntries,
4
- createEntry,
5
- updateEntry,
6
- deleteEntry,
7
- getEntry
8
- } from '../../server/services/collections.js';
9
-
10
- const CONTACTS_SLUG = 'user-contacts';
11
- const GROUPS_SLUG = 'user-contact-groups';
2
+ import {createEntry, deleteEntry, getEntry, listEntries, updateEntry} from '../../server/services/collections.js';
3
+
4
+ const CONTACTS_SLUG = 'contacts-contacts';
5
+ const GROUPS_SLUG = 'contacts-groups';
12
6
 
13
7
  /** Map a collection entry to the shape the admin view expects. */
14
8
  function toContact(entry) {
@@ -8,14 +8,24 @@
8
8
  "icon": "users",
9
9
  "admin": {
10
10
  "sidebar": [
11
- { "id": "contacts", "text": "Contacts", "icon": "users", "url": "#/plugins/contacts", "section": "#/plugins/contacts" }
11
+ {
12
+ "id": "contacts",
13
+ "text": "Contacts",
14
+ "icon": "users",
15
+ "url": "#/plugins/contacts",
16
+ "section": "#/plugins/contacts"
17
+ }
12
18
  ],
13
19
  "routes": [
14
- { "path": "/plugins/contacts", "view": "plugin-contacts", "title": "Contacts - Domma CMS" }
20
+ {
21
+ "path": "/plugins/contacts",
22
+ "view": "plugin-contacts",
23
+ "title": "Contacts - Domma CMS"
24
+ }
15
25
  ],
16
26
  "views": {
17
27
  "plugin-contacts": {
18
- "entry": "contacts/admin/views/contacts.js",
28
+ "entry": "contacts/admin/views/contacts.js?v=4d7ec7f7",
19
29
  "exportName": "contactsView"
20
30
  }
21
31
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "slug": "user-notes",
2
+ "slug": "notes-notes",
3
3
  "title": "Notes",
4
4
  "description": "Notes managed by the Notes plugin.",
5
5
  "plugin": "notes",
@@ -1,13 +1,7 @@
1
1
  import defaultConfig from './config.js';
2
- import {
3
- listEntries,
4
- createEntry,
5
- updateEntry,
6
- deleteEntry,
7
- getEntry
8
- } from '../../server/services/collections.js';
9
-
10
- const SLUG = 'user-notes';
2
+ import {createEntry, deleteEntry, getEntry, listEntries, updateEntry} from '../../server/services/collections.js';
3
+
4
+ const SLUG = 'notes-notes';
11
5
 
12
6
  /** Flatten a collection entry into the shape the admin view expects. */
13
7
  function toNote(entry) {
@@ -8,14 +8,24 @@
8
8
  "icon": "file-text",
9
9
  "admin": {
10
10
  "sidebar": [
11
- { "id": "notes", "text": "Notes", "icon": "file-text", "url": "#/plugins/notes", "section": "#/plugins/notes" }
11
+ {
12
+ "id": "notes",
13
+ "text": "Notes",
14
+ "icon": "file-text",
15
+ "url": "#/plugins/notes",
16
+ "section": "#/plugins/notes"
17
+ }
12
18
  ],
13
19
  "routes": [
14
- { "path": "/plugins/notes", "view": "plugin-notes", "title": "Notes - Domma CMS" }
20
+ {
21
+ "path": "/plugins/notes",
22
+ "view": "plugin-notes",
23
+ "title": "Notes - Domma CMS"
24
+ }
15
25
  ],
16
26
  "views": {
17
27
  "plugin-notes": {
18
- "entry": "notes/admin/views/notes.js?v=2",
28
+ "entry": "notes/admin/views/notes.js?v=43194b9f",
19
29
  "exportName": "notesView"
20
30
  }
21
31
  }
@@ -13,7 +13,10 @@
13
13
  "text": "Site Search",
14
14
  "icon": "search",
15
15
  "url": "#/plugins/site-search",
16
- "section": "#/plugins/site-search"
16
+ "section": "#/plugins/site-search",
17
+ "permissions": [
18
+ "settings"
19
+ ]
17
20
  }
18
21
  ],
19
22
  "routes": [
@@ -25,7 +28,7 @@
25
28
  ],
26
29
  "views": {
27
30
  "plugin-site-search": {
28
- "entry": "site-search/admin/views/site-search.js?v=4",
31
+ "entry": "site-search/admin/views/site-search.js?v=ff425f47",
29
32
  "exportName": "siteSearchView"
30
33
  }
31
34
  }