domma-cms 0.55.1 → 0.66.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/CLAUDE.md +130 -2
- package/README.md +4 -4
- package/admin/css/admin.css +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 +3 -3
- package/admin/js/lib/page-picker.js +1 -0
- package/admin/js/lib/plugin-accent.js +1 -0
- package/admin/js/lib/plugin-chrome.js +1 -0
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/sidebar-grouping.js +1 -1
- package/admin/js/lib/sidebar-grouping.test.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/lib/slideover-resizable.js +1 -0
- package/admin/js/templates/context-menu-editor.html +212 -0
- package/admin/js/templates/context-menus.html +16 -0
- package/admin/js/templates/plugin-code.html +1 -1
- package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
- package/admin/js/templates/settings.html +16 -97
- package/admin/js/templates/theme.html +173 -0
- package/admin/js/views/context-menu-editor.js +55 -0
- package/admin/js/views/context-menus.js +5 -0
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +25 -23
- package/admin/js/views/search.js +1 -0
- package/admin/js/views/settings.js +3 -3
- package/admin/js/views/theme.js +1 -0
- package/bin/cli.js +2 -0
- package/bin/update.js +13 -2
- package/config/menus/admin-sidebar.json +129 -23
- package/config/plugins.json +5 -5
- package/config/search.json +13 -0
- package/config/theme.json +18 -0
- package/package.json +12 -4
- package/plugins/_lib/admin/mail/compose-window.js +914 -0
- package/plugins/_lib/admin/mail/contacts.js +301 -0
- package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
- package/plugins/_lib/admin/mail/folder-tree.js +254 -0
- package/plugins/_lib/admin/mail/identity.js +480 -0
- package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
- package/plugins/_lib/admin/mail/keyboard.js +136 -0
- package/plugins/_lib/admin/mail/mail.css +1 -0
- package/plugins/_lib/admin/mail/mail.html +71 -0
- package/plugins/_lib/admin/mail/panes.js +253 -0
- package/plugins/_lib/admin/mail/reader-view.js +4453 -0
- package/plugins/_lib/admin/mail/resizable.js +26 -0
- package/plugins/_lib/admin/mail/rules.js +343 -0
- package/plugins/_lib/admin/mail/scheduling.js +203 -0
- package/plugins/_lib/admin/mail/section-kit.js +277 -0
- package/plugins/_lib/admin/mail/templates.js +238 -0
- package/plugins/_lib/admin/mail/threads.js +200 -0
- package/plugins/_lib/admin/mail/vacation.js +269 -0
- package/plugins/_lib/admin/ui/help.css +1 -0
- package/plugins/_lib/admin/ui/help.js +174 -0
- package/plugins/_lib/admin/ui/resizable.js +151 -0
- package/plugins/_lib/dataStore.js +117 -0
- package/plugins/_lib/mail/accounts.js +919 -0
- package/plugins/_lib/mail/bodyTokens.js +101 -0
- package/plugins/_lib/mail/compose.js +256 -0
- package/plugins/_lib/mail/defaults.js +57 -0
- package/plugins/_lib/mail/diagnostics.js +154 -0
- package/plugins/_lib/mail/envelope.js +161 -0
- package/plugins/_lib/mail/folders.js +192 -0
- package/plugins/_lib/mail/handoff.js +274 -0
- package/plugins/_lib/mail/imapPool.js +293 -0
- package/plugins/_lib/mail/mbox.js +74 -0
- package/plugins/_lib/mail/pollSchedule.js +79 -0
- package/plugins/_lib/mail/poller.js +135 -0
- package/plugins/_lib/mail/priority.js +138 -0
- package/plugins/_lib/mail/readRoutes.js +680 -0
- package/plugins/_lib/mail/render.js +291 -0
- package/plugins/_lib/mail/ruleRunner.js +152 -0
- package/plugins/_lib/mail/scheduler.js +254 -0
- package/plugins/_lib/mail/secretbox.js +229 -0
- package/plugins/_lib/mail/send.js +396 -0
- package/plugins/_lib/mail/store.js +1002 -0
- package/plugins/_lib/mail/sync.js +292 -0
- package/plugins/_lib/mail/syncPlan.js +126 -0
- package/plugins/_lib/mail/syncSelection.js +82 -0
- package/plugins/_lib/mail/unsubscribe.js +183 -0
- package/plugins/_lib/mail/vacationRunner.js +114 -0
- package/plugins/_lib/mail/write.js +473 -0
- package/plugins/_lib/schemaSync.js +83 -0
- package/plugins/_template/admin/css/index.css +0 -0
- package/plugins/_template/admin/templates/index.html +4 -4
- package/plugins/_template/admin/views/index.js +7 -0
- package/plugins/analytics/admin/css/index.css +1 -0
- package/plugins/analytics/admin/templates/analytics.html +22 -13
- package/plugins/analytics/plugin.json +3 -0
- package/plugins/blog/admin/css/index.css +1 -0
- package/plugins/blog/admin/templates/blog.html +30 -18
- package/plugins/blog/admin/templates/categories.html +2 -2
- package/plugins/blog/admin/templates/comments.html +2 -2
- package/plugins/blog/admin/templates/post-editor.html +34 -34
- package/plugins/blog/admin/templates/settings.html +6 -3
- package/plugins/blog/admin/views/blog.js +8 -5
- package/plugins/blog/admin/views/categories.js +5 -10
- package/plugins/blog/admin/views/comments.js +5 -5
- package/plugins/blog/admin/views/post-editor.js +39 -20
- package/plugins/blog/admin/views/settings.js +52 -50
- package/plugins/blog/collections/categories/schema.json +7 -6
- package/plugins/blog/collections/comments/schema.json +11 -10
- package/plugins/blog/collections/posts/schema.json +14 -13
- package/plugins/blog/plugin.js +36 -13
- package/plugins/blog/plugin.json +13 -5
- package/plugins/blog/plugin.public.js +312 -0
- package/plugins/contacts/admin/css/index.css +1 -0
- package/plugins/contacts/admin/templates/contacts.html +128 -0
- package/plugins/contacts/admin/views/contacts.js +237 -4
- package/plugins/contacts/collections/user-contacts/schema.json +108 -0
- package/plugins/contacts/plugin.js +214 -27
- package/plugins/contacts/plugin.json +4 -1
- package/plugins/invoice/admin/css/index.css +1 -0
- package/plugins/invoice/admin/templates/editor.html +140 -49
- package/plugins/invoice/admin/templates/index.html +153 -23
- package/plugins/invoice/admin/templates/issuers.html +2 -5
- package/plugins/invoice/admin/templates/receivers.html +2 -5
- package/plugins/invoice/admin/views/contacts-source.js +266 -0
- package/plugins/invoice/admin/views/editor.js +366 -199
- package/plugins/invoice/admin/views/export.js +199 -0
- package/plugins/invoice/admin/views/help-content.js +61 -0
- package/plugins/invoice/admin/views/index.js +582 -94
- package/plugins/invoice/admin/views/issuers.js +24 -17
- package/plugins/invoice/admin/views/media.js +172 -0
- package/plugins/invoice/admin/views/party-view.js +305 -67
- package/plugins/invoice/admin/views/payments.js +127 -0
- package/plugins/invoice/admin/views/print.js +130 -0
- package/plugins/invoice/admin/views/receivers.js +49 -16
- package/plugins/invoice/admin/views/send.js +212 -0
- package/plugins/invoice/admin/views/settings.js +594 -0
- package/plugins/invoice/admin/views/view-lifecycle.js +33 -0
- package/plugins/invoice/collections/invoice-issuers/schema.json +77 -11
- package/plugins/invoice/collections/invoice-receivers/schema.json +10 -9
- package/plugins/invoice/collections/invoices/schema.json +19 -13
- package/plugins/invoice/config.js +27 -6
- package/plugins/invoice/pdf.js +164 -0
- package/plugins/invoice/plugin.js +1217 -44
- package/plugins/invoice/plugin.json +10 -9
- package/plugins/invoice/templates/_base.css +1 -0
- package/plugins/invoice/templates/classic-nologo.html +100 -0
- package/plugins/invoice/templates/classic.html +91 -0
- package/plugins/invoice/templates/invoice-print.html +24 -0
- package/plugins/invoice/templates/minimal.html +99 -0
- package/plugins/invoice/templates/modern-nologo.html +114 -0
- package/plugins/invoice/templates/modern.html +113 -0
- package/plugins/invoice/templates/templates.json +11 -0
- package/plugins/mail-reader/admin/views/mail.js +19 -0
- package/plugins/mail-reader/config.js +7 -0
- package/plugins/mail-reader/plugin.js +48 -0
- package/plugins/mail-reader/plugin.json +33 -0
- package/plugins/notes/admin/views/notes.js +1 -1
- package/plugins/notes/plugin.json +2 -2
- package/plugins/surveys/lib/audience.js +37 -0
- package/plugins/surveys/lib/campaigns.js +43 -0
- package/plugins/surveys/lib/ledger.js +110 -0
- package/plugins/surveys/lib/sending.js +106 -0
- package/plugins/surveys/lib/stats.js +62 -0
- package/plugins/surveys/lib/submit.js +95 -0
- package/plugins/surveys/lib/tokens.js +28 -0
- package/plugins/surveys/plugin.public.js +149 -0
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +1 -1
- package/public/css/forms.css +1 -1
- package/public/css/search.css +1 -0
- package/public/css/site.css +1 -1
- package/public/js/collection-context.js +2 -2
- package/public/js/context-menus.js +1 -0
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -2
- package/public/js/search.js +1 -0
- package/public/js/site.js +1 -1
- package/scripts/build.js +37 -3
- package/scripts/copy-domma.js +48 -0
- package/scripts/seed.js +1996 -0
- package/server/routes/api/collections.js +34 -0
- package/server/routes/api/context-menus.js +104 -0
- package/server/routes/api/forms.js +42 -3
- package/server/routes/api/notifications.js +69 -19
- package/server/routes/api/plugins.js +50 -6
- package/server/routes/api/search.js +43 -0
- package/server/routes/api/theme.js +69 -0
- package/server/routes/public.js +42 -7
- package/server/server.js +74 -0
- package/server/services/adapters/FileAdapter.js +6 -1
- package/server/services/content.js +26 -0
- package/server/services/contextMenus.js +477 -0
- package/server/services/markdown.js +70 -9
- package/server/services/permissionRegistry.js +24 -0
- package/server/services/pluginFiles.js +52 -11
- package/server/services/plugins.js +229 -6
- package/server/services/renderer.js +144 -22
- package/server/services/roles.js +1 -1
- package/server/services/search-migration.js +82 -0
- package/server/services/search.js +413 -0
- package/server/services/sidebar-migration.js +1 -0
- package/server/services/themeSettings.js +541 -0
- package/server/services/users.js +8 -0
- package/server/templates/page.html +4 -2
- package/plugins/contacts/data/contacts.json +0 -20
- package/plugins/notes/data/notes.json +0 -1
- package/plugins/site-search/admin/views/site-search.js +0 -116
- package/plugins/site-search/config.js +0 -15
- package/plugins/site-search/plugin.js +0 -188
- package/plugins/site-search/plugin.json +0 -40
- package/plugins/site-search/public/inject-body.html +0 -17
- package/plugins/site-search/public/inject-head.html +0 -1
- package/plugins/site-search/public/search.css +0 -1
- package/plugins/site-search/public/search.js +0 -1
- package/plugins/todo/data/todos.json +0 -1
|
@@ -14,6 +14,13 @@ export const indexView = {
|
|
|
14
14
|
// Confirm: if (!await E.confirm('Are you sure?')) return;
|
|
15
15
|
// Create table: T.create(el, { data, columns: [...] });
|
|
16
16
|
// Create form: F.create({ fields }, defaults, { showSubmitButton: false }).renderTo(el);
|
|
17
|
+
//
|
|
18
|
+
// Route params: onMount is handed ONE argument and never the route
|
|
19
|
+
// params, and R.current is a METHOD - R.current.params
|
|
20
|
+
// is always undefined. An editor view reads its id off
|
|
21
|
+
// the hash:
|
|
22
|
+
// const m = location.hash.match(/^#\/plugins\/PLUGIN_SLUG\/edit\/([^?#]+)/);
|
|
23
|
+
// const id = m ? decodeURIComponent(m[1]) : null;
|
|
17
24
|
I.scan();
|
|
18
25
|
}
|
|
19
26
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.analytics-filter{max-width:240px}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="view-header">
|
|
2
2
|
<h1><span data-icon="chart-bar"></span> Analytics</h1>
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="view-header-actions">
|
|
4
4
|
<button id="export-btn" class="btn btn-ghost btn-sm" type="button">
|
|
5
5
|
<span data-icon="download"></span> Export CSV
|
|
6
6
|
</button>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<div class="card mb-4">
|
|
14
14
|
<div class="card-body">
|
|
15
|
-
<div id="range-tabs" class="
|
|
15
|
+
<div id="range-tabs" class="flex gap-2 flex-wrap">
|
|
16
16
|
<button class="btn btn-ghost btn-sm range-btn" data-range="today">Today</button>
|
|
17
17
|
<button class="btn btn-ghost btn-sm range-btn active" data-range="7d">Last 7 days</button>
|
|
18
18
|
<button class="btn btn-ghost btn-sm range-btn" data-range="30d">Last 30 days</button>
|
|
@@ -23,22 +23,31 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="grid grid-cols-3 gap-4 mb-4">
|
|
26
|
-
<div class="card">
|
|
26
|
+
<div class="card stat-card">
|
|
27
27
|
<div class="card-body">
|
|
28
|
-
<div class="
|
|
29
|
-
<div
|
|
28
|
+
<div class="stat-icon"><span data-icon="activity"></span></div>
|
|
29
|
+
<div>
|
|
30
|
+
<div class="stat-value" id="kpi-range">-</div>
|
|
31
|
+
<div class="stat-label">Hits in range</div>
|
|
32
|
+
</div>
|
|
30
33
|
</div>
|
|
31
34
|
</div>
|
|
32
|
-
<div class="card">
|
|
35
|
+
<div class="card stat-card">
|
|
33
36
|
<div class="card-body">
|
|
34
|
-
<div class="
|
|
35
|
-
<div
|
|
37
|
+
<div class="stat-icon"><span data-icon="file-text"></span></div>
|
|
38
|
+
<div>
|
|
39
|
+
<div class="stat-value" id="kpi-pages">-</div>
|
|
40
|
+
<div class="stat-label">Unique pages</div>
|
|
41
|
+
</div>
|
|
36
42
|
</div>
|
|
37
43
|
</div>
|
|
38
|
-
<div class="card">
|
|
44
|
+
<div class="card stat-card">
|
|
39
45
|
<div class="card-body">
|
|
40
|
-
<div class="
|
|
41
|
-
<div
|
|
46
|
+
<div class="stat-icon stat-icon--success"><span data-icon="trending-up"></span></div>
|
|
47
|
+
<div>
|
|
48
|
+
<div class="stat-value" id="kpi-lifetime">-</div>
|
|
49
|
+
<div class="stat-label">Lifetime hits</div>
|
|
50
|
+
</div>
|
|
42
51
|
</div>
|
|
43
52
|
</div>
|
|
44
53
|
</div>
|
|
@@ -51,9 +60,9 @@
|
|
|
51
60
|
</div>
|
|
52
61
|
|
|
53
62
|
<div class="card">
|
|
54
|
-
<div class="card-header
|
|
63
|
+
<div class="card-header flex justify-between items-center">
|
|
55
64
|
<strong>Top pages</strong>
|
|
56
|
-
<input id="filter-input" type="search" class="form-input form-input-sm" placeholder="Filter URL…"
|
|
65
|
+
<input id="filter-input" type="search" class="form-input form-input-sm analytics-filter" placeholder="Filter URL…">
|
|
57
66
|
</div>
|
|
58
67
|
<div class="card-body">
|
|
59
68
|
<div id="analytics-table"></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.blog-hidden{display:none!important}.blog-clickable{cursor:pointer}.blog-nowrap{white-space:nowrap}.blog-editor{align-items:flex-start}.blog-editor-main{flex:1;min-width:0}.blog-editor-side{width:280px;flex-shrink:0}@media(max-width:900px){.blog-editor{flex-wrap:wrap}.blog-editor-side{width:100%}}.blog-filter-select{width:auto}.blog-filter-search{width:240px}.blog-settings-card{max-width:600px}.blog-media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;padding:.5rem}.blog-media-tile{border:1px solid var(--dm-border, #333);background:transparent;border-radius:.25rem;padding:.25rem;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:.25rem}.blog-media-tile img{width:100%;height:90px;object-fit:cover;border-radius:.15rem}.blog-media-tile span{font-size:.7rem;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%}.blog-modal-form{display:flex;flex-direction:column;gap:1rem}.blog-modal-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem}.blog-image-preview{max-width:100%;border-radius:.25rem;display:block;margin-bottom:.5rem}.blog-title-link{font-weight:600}.blog-empty-cell{opacity:.4}.blog-row-actions{display:flex;gap:.4rem}.blog-comment-body{font-size:.87rem}.blog-moderation-actions{display:flex;gap:.3rem;flex-wrap:wrap}
|
|
@@ -1,42 +1,54 @@
|
|
|
1
|
-
<div class="view-header
|
|
2
|
-
<h1
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="file-text"></span> Blog Posts</h1>
|
|
3
3
|
<button id="btn-new-post" class="btn btn-primary">
|
|
4
4
|
<span data-icon="plus"></span> New Post
|
|
5
5
|
</button>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div class="grid grid-cols-4 gap-3 mb-4">
|
|
9
|
-
<div class="card"><div class="card-body">
|
|
10
|
-
<div class="
|
|
11
|
-
<div
|
|
9
|
+
<div class="card stat-card"><div class="card-body">
|
|
10
|
+
<div class="stat-icon"><span data-icon="file-text"></span></div>
|
|
11
|
+
<div>
|
|
12
|
+
<div class="stat-value" id="kpi-total">-</div>
|
|
13
|
+
<div class="stat-label">Total</div>
|
|
14
|
+
</div>
|
|
12
15
|
</div></div>
|
|
13
|
-
<div class="card"><div class="card-body">
|
|
14
|
-
<div class="
|
|
15
|
-
<div
|
|
16
|
+
<div class="card stat-card"><div class="card-body">
|
|
17
|
+
<div class="stat-icon stat-icon--success"><span data-icon="check-circle"></span></div>
|
|
18
|
+
<div>
|
|
19
|
+
<div class="stat-value" id="kpi-published">-</div>
|
|
20
|
+
<div class="stat-label">Published</div>
|
|
21
|
+
</div>
|
|
16
22
|
</div></div>
|
|
17
|
-
<div class="card"><div class="card-body">
|
|
18
|
-
<div class="
|
|
19
|
-
<div
|
|
23
|
+
<div class="card stat-card"><div class="card-body">
|
|
24
|
+
<div class="stat-icon"><span data-icon="edit"></span></div>
|
|
25
|
+
<div>
|
|
26
|
+
<div class="stat-value" id="kpi-drafts">-</div>
|
|
27
|
+
<div class="stat-label">Drafts</div>
|
|
28
|
+
</div>
|
|
20
29
|
</div></div>
|
|
21
|
-
<div class="card"><div class="card-body">
|
|
22
|
-
<div class="
|
|
23
|
-
<div
|
|
30
|
+
<div class="card stat-card"><div class="card-body">
|
|
31
|
+
<div class="stat-icon stat-icon--warning"><span data-icon="clock"></span></div>
|
|
32
|
+
<div>
|
|
33
|
+
<div class="stat-value" id="kpi-scheduled">-</div>
|
|
34
|
+
<div class="stat-label">Scheduled</div>
|
|
35
|
+
</div>
|
|
24
36
|
</div></div>
|
|
25
37
|
</div>
|
|
26
38
|
|
|
27
39
|
<div class="card mb-3">
|
|
28
40
|
<div class="card-body">
|
|
29
|
-
<div class="
|
|
30
|
-
<select id="filter-status" class="form-select
|
|
41
|
+
<div class="flex gap-2 flex-wrap items-center">
|
|
42
|
+
<select id="filter-status" class="form-select blog-filter-select">
|
|
31
43
|
<option value="">All statuses</option>
|
|
32
44
|
<option value="draft">Draft</option>
|
|
33
45
|
<option value="scheduled">Scheduled</option>
|
|
34
46
|
<option value="published">Published</option>
|
|
35
47
|
</select>
|
|
36
|
-
<select id="filter-category" class="form-select
|
|
48
|
+
<select id="filter-category" class="form-select blog-filter-select">
|
|
37
49
|
<option value="">All categories</option>
|
|
38
50
|
</select>
|
|
39
|
-
<input id="filter-search" type="
|
|
51
|
+
<input id="filter-search" type="search" class="form-input blog-filter-search" placeholder="Search title or excerpt…">
|
|
40
52
|
<button id="filter-reset" class="btn btn-ghost btn-sm">Reset</button>
|
|
41
53
|
</div>
|
|
42
54
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<div class="view-header
|
|
2
|
-
<h1
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="folder"></span> Blog Categories</h1>
|
|
3
3
|
<button id="btn-new-category" class="btn btn-primary">
|
|
4
4
|
<span data-icon="plus"></span> New Category
|
|
5
5
|
</button>
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
<div class="view-header
|
|
2
|
-
<h1
|
|
3
|
-
<div class="
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1 id="editor-title">New Post</h1>
|
|
3
|
+
<div class="view-header-actions">
|
|
4
4
|
<button id="btn-save-draft" class="btn btn-secondary">Save Draft</button>
|
|
5
|
-
<button id="btn-schedule" class="btn btn-warning
|
|
5
|
+
<button id="btn-schedule" class="btn btn-warning blog-hidden">Schedule</button>
|
|
6
6
|
<button id="btn-publish" class="btn btn-primary">Publish Now</button>
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<div class="
|
|
10
|
+
<div class="flex gap-4 blog-editor">
|
|
11
11
|
<!-- Main content area -->
|
|
12
|
-
<div
|
|
12
|
+
<div class="blog-editor-main">
|
|
13
13
|
<div class="card mb-3">
|
|
14
14
|
<div class="card-body">
|
|
15
15
|
<div class="mb-3">
|
|
16
|
-
<label class="form-label
|
|
17
|
-
<input type="text" id="post-title" class="form-
|
|
16
|
+
<label class="form-label font-bold" for="post-title">Title</label>
|
|
17
|
+
<input type="text" id="post-title" class="form-input form-input-lg" placeholder="Post title…">
|
|
18
18
|
</div>
|
|
19
19
|
<div class="mb-3">
|
|
20
|
-
<label class="form-label">Slug</label>
|
|
21
|
-
<div class="
|
|
22
|
-
<input type="text" id="post-slug" class="form-
|
|
23
|
-
<label class="
|
|
24
|
-
<input type="checkbox" id="slug-auto"> Auto
|
|
20
|
+
<label class="form-label" for="post-slug">Slug</label>
|
|
21
|
+
<div class="flex gap-2 items-center">
|
|
22
|
+
<input type="text" id="post-slug" class="form-input font-mono">
|
|
23
|
+
<label class="flex items-center gap-1 text-muted text-sm blog-nowrap" for="slug-auto">
|
|
24
|
+
<input type="checkbox" id="slug-auto" class="form-checkbox"> Auto
|
|
25
25
|
</label>
|
|
26
26
|
</div>
|
|
27
|
-
<div id="slug-warning" class="text-warning
|
|
27
|
+
<div id="slug-warning" class="text-warning text-sm mt-1 blog-hidden">
|
|
28
28
|
⚠ Changing slug breaks existing links
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
31
31
|
<div class="mb-3">
|
|
32
|
-
<label class="form-label">Excerpt</label>
|
|
33
|
-
<textarea id="post-excerpt" class="form-
|
|
32
|
+
<label class="form-label" for="post-excerpt">Excerpt</label>
|
|
33
|
+
<textarea id="post-excerpt" class="form-textarea" rows="2" placeholder="Short summary…"></textarea>
|
|
34
34
|
</div>
|
|
35
35
|
<div class="mb-3">
|
|
36
|
-
<label class="form-label">Content</label>
|
|
37
|
-
<textarea id="post-content" class="form-
|
|
36
|
+
<label class="form-label" for="post-content">Content</label>
|
|
37
|
+
<textarea id="post-content" class="form-textarea font-mono" rows="20" placeholder="Write in Markdown…"></textarea>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
42
|
<!-- SEO accordion -->
|
|
43
43
|
<div class="card mb-3">
|
|
44
|
-
<div class="card-header" id="seo-toggle"
|
|
45
|
-
SEO <span class="text-muted
|
|
44
|
+
<div class="card-header blog-clickable" id="seo-toggle">
|
|
45
|
+
SEO <span class="text-muted text-sm">(click to expand)</span>
|
|
46
46
|
</div>
|
|
47
|
-
<div class="card-body" id="seo-panel"
|
|
47
|
+
<div class="card-body blog-hidden" id="seo-panel">
|
|
48
48
|
<div class="mb-3">
|
|
49
|
-
<label class="form-label">SEO Title</label>
|
|
50
|
-
<input type="text" id="seo-title" class="form-
|
|
49
|
+
<label class="form-label" for="seo-title">SEO Title</label>
|
|
50
|
+
<input type="text" id="seo-title" class="form-input" placeholder="Overrides page title">
|
|
51
51
|
</div>
|
|
52
52
|
<div class="mb-3">
|
|
53
|
-
<label class="form-label">SEO Description</label>
|
|
54
|
-
<textarea id="seo-description" class="form-
|
|
53
|
+
<label class="form-label" for="seo-description">SEO Description</label>
|
|
54
|
+
<textarea id="seo-description" class="form-textarea" rows="2" placeholder="Overrides excerpt for meta description"></textarea>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
60
|
<!-- Sidebar -->
|
|
61
|
-
<div
|
|
61
|
+
<div class="blog-editor-side">
|
|
62
62
|
<div class="card mb-3">
|
|
63
63
|
<div class="card-header">Status</div>
|
|
64
64
|
<div class="card-body">
|
|
65
|
-
<div id="schedule-picker"
|
|
66
|
-
<label class="form-label">Scheduled for</label>
|
|
67
|
-
<input type="datetime-local" id="post-scheduled-at" class="form-
|
|
65
|
+
<div id="schedule-picker" class="mb-3 blog-hidden">
|
|
66
|
+
<label class="form-label" for="post-scheduled-at">Scheduled for</label>
|
|
67
|
+
<input type="datetime-local" id="post-scheduled-at" class="form-input">
|
|
68
68
|
</div>
|
|
69
|
-
<span id="post-status-badge" class="badge
|
|
69
|
+
<span id="post-status-badge" class="badge badge-secondary">draft</span>
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
73
|
<div class="card mb-3">
|
|
74
74
|
<div class="card-header">Categories</div>
|
|
75
75
|
<div class="card-body" id="categories-checklist">
|
|
76
|
-
<p class="text-muted
|
|
76
|
+
<p class="text-muted text-sm">Loading…</p>
|
|
77
77
|
</div>
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
80
|
<div class="card mb-3">
|
|
81
81
|
<div class="card-header">Tags</div>
|
|
82
82
|
<div class="card-body">
|
|
83
|
-
<input type="text" id="post-tags" class="form-
|
|
84
|
-
<div class="text-muted
|
|
83
|
+
<input type="text" id="post-tags" class="form-input" placeholder="tag1, tag2, …">
|
|
84
|
+
<div class="text-muted text-sm mt-1">Comma-separated</div>
|
|
85
85
|
</div>
|
|
86
86
|
</div>
|
|
87
87
|
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
<div class="card-body">
|
|
91
91
|
<div id="featured-image-preview" class="mb-2"></div>
|
|
92
92
|
<button id="btn-pick-image" class="btn btn-sm btn-secondary">Pick Image</button>
|
|
93
|
-
<button id="btn-clear-image" class="btn btn-sm btn-outline-
|
|
93
|
+
<button id="btn-clear-image" class="btn btn-sm btn-outline blog-hidden">Clear</button>
|
|
94
94
|
</div>
|
|
95
95
|
</div>
|
|
96
96
|
</div>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
<div class="view-header
|
|
2
|
-
<h1
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="settings"></span> Blog Settings</h1>
|
|
3
|
+
<button id="btn-save-settings" class="btn btn-primary">
|
|
4
|
+
<span data-icon="save"></span> Save
|
|
5
|
+
</button>
|
|
3
6
|
</div>
|
|
4
7
|
|
|
5
8
|
<div class="alert alert-warning mb-4">
|
|
6
9
|
Base path and posts-per-page changes require a server restart to take effect.
|
|
7
10
|
</div>
|
|
8
11
|
|
|
9
|
-
<div class="card
|
|
12
|
+
<div class="card blog-settings-card">
|
|
10
13
|
<div class="card-body" id="settings-form"></div>
|
|
11
14
|
</div>
|
|
@@ -44,7 +44,7 @@ export const blogView = {
|
|
|
44
44
|
title: 'Title',
|
|
45
45
|
sortable: true,
|
|
46
46
|
render: (v, row) =>
|
|
47
|
-
`<a href="#/plugins/blog/posts/${escapeHtml(row.id)}"
|
|
47
|
+
`<a href="#/plugins/blog/posts/${escapeHtml(row.id)}" class="blog-title-link">${escapeHtml(v || 'Untitled')}</a>`
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
key: 'status',
|
|
@@ -61,7 +61,7 @@ export const blogView = {
|
|
|
61
61
|
sortable: false,
|
|
62
62
|
render: (val) => Array.isArray(val) && val.length
|
|
63
63
|
? val.map((id) => `<span class="badge badge-secondary mr-1">${escapeHtml(catNameById(id) || id)}</span>`).join('')
|
|
64
|
-
: '<span
|
|
64
|
+
: '<span class="blog-empty-cell">-</span>'
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
key: 'publishedAt',
|
|
@@ -69,7 +69,7 @@ export const blogView = {
|
|
|
69
69
|
sortable: true,
|
|
70
70
|
render: (v) => v
|
|
71
71
|
? `<span title="${escapeHtml(D(v).format('DD MMM YYYY HH:mm'))}">${escapeHtml(D(v).format('DD MMM YYYY'))}</span>`
|
|
72
|
-
: '<span
|
|
72
|
+
: '<span class="blog-empty-cell">-</span>'
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
key: 'id',
|
|
@@ -80,7 +80,7 @@ export const blogView = {
|
|
|
80
80
|
const viewLink = row.status === 'published' && slug
|
|
81
81
|
? `<a href="/blog/${slug}" target="_blank" class="btn btn-sm btn-outline" title="View on site"><span data-icon="external-link" data-icon-size="14"></span></a>`
|
|
82
82
|
: '';
|
|
83
|
-
return `<div
|
|
83
|
+
return `<div class="blog-row-actions">` +
|
|
84
84
|
`<button class="btn btn-sm btn-outline post-edit-btn" data-id="${escapeHtml(id)}"><span data-icon="edit" data-icon-size="14"></span></button>` +
|
|
85
85
|
viewLink +
|
|
86
86
|
`<button class="btn btn-sm btn-danger post-delete-btn" data-id="${escapeHtml(id)}"><span data-icon="trash" data-icon-size="14"></span></button>` +
|
|
@@ -165,7 +165,10 @@ export const blogView = {
|
|
|
165
165
|
H.get(`${BASE}/categories`).catch(() => ({ data: [] }))
|
|
166
166
|
]);
|
|
167
167
|
if (postsRes.error) throw new Error(postsRes.error);
|
|
168
|
-
|
|
168
|
+
// GET /posts sends a BARE ARRAY, so `.data` is undefined and this
|
|
169
|
+
// read emptied the whole screen - no rows, all four KPIs zero.
|
|
170
|
+
// Same guard as categories.js and comments.js.
|
|
171
|
+
allPosts = postsRes.data ?? (Array.isArray(postsRes) ? postsRes : []);
|
|
169
172
|
categories = catsRes.data ?? catsRes ?? [];
|
|
170
173
|
} catch {
|
|
171
174
|
allPosts = [];
|
|
@@ -33,9 +33,7 @@ export const categoriesView = {
|
|
|
33
33
|
|
|
34
34
|
function buildModal() {
|
|
35
35
|
const form = document.createElement('div');
|
|
36
|
-
form.
|
|
37
|
-
form.style.flexDirection = 'column';
|
|
38
|
-
form.style.gap = '1rem';
|
|
36
|
+
form.className = 'blog-modal-form';
|
|
39
37
|
|
|
40
38
|
const mkField = (labelText, id, tag = 'input', rows) => {
|
|
41
39
|
const group = document.createElement('div');
|
|
@@ -48,7 +46,7 @@ export const categoriesView = {
|
|
|
48
46
|
|
|
49
47
|
const el = document.createElement(tag);
|
|
50
48
|
el.id = id;
|
|
51
|
-
el.className = 'form-
|
|
49
|
+
el.className = tag === 'textarea' ? 'form-textarea' : 'form-input';
|
|
52
50
|
if (tag === 'textarea' && rows) el.rows = rows;
|
|
53
51
|
group.appendChild(el);
|
|
54
52
|
|
|
@@ -65,10 +63,7 @@ export const categoriesView = {
|
|
|
65
63
|
|
|
66
64
|
// Footer buttons
|
|
67
65
|
const footer = document.createElement('div');
|
|
68
|
-
footer.
|
|
69
|
-
footer.style.justifyContent = 'flex-end';
|
|
70
|
-
footer.style.gap = '0.5rem';
|
|
71
|
-
footer.style.marginTop = '0.5rem';
|
|
66
|
+
footer.className = 'blog-modal-actions';
|
|
72
67
|
|
|
73
68
|
const cancelBtn = document.createElement('button');
|
|
74
69
|
cancelBtn.className = 'btn btn-secondary';
|
|
@@ -159,14 +154,14 @@ export const categoriesView = {
|
|
|
159
154
|
key: 'description',
|
|
160
155
|
title: 'Description',
|
|
161
156
|
sortable: false,
|
|
162
|
-
render: (v) => escapeHtml(v) || '<span
|
|
157
|
+
render: (v) => escapeHtml(v) || '<span class="blog-empty-cell">-</span>'
|
|
163
158
|
},
|
|
164
159
|
{
|
|
165
160
|
key: 'id',
|
|
166
161
|
title: 'Actions',
|
|
167
162
|
sortable: false,
|
|
168
163
|
render: (id) =>
|
|
169
|
-
`<div
|
|
164
|
+
`<div class="blog-row-actions">` +
|
|
170
165
|
`<button class="btn btn-sm btn-outline cat-edit-btn" data-id="${escapeHtml(id)}">` +
|
|
171
166
|
`<span data-icon="edit" data-icon-size="14"></span> Edit</button>` +
|
|
172
167
|
`<button class="btn btn-sm btn-danger cat-delete-btn" data-id="${escapeHtml(id)}">` +
|
|
@@ -65,7 +65,7 @@ export const commentsView = {
|
|
|
65
65
|
sortable: false,
|
|
66
66
|
render: (v) => v
|
|
67
67
|
? `<a href="mailto:${escapeHtml(v)}">${escapeHtml(v)}</a>`
|
|
68
|
-
: '<span
|
|
68
|
+
: '<span class="blog-empty-cell">-</span>'
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
key: 'body',
|
|
@@ -74,7 +74,7 @@ export const commentsView = {
|
|
|
74
74
|
render: (v) => {
|
|
75
75
|
const preview = String(v ?? '').slice(0, 80);
|
|
76
76
|
const ellipsis = String(v ?? '').length > 80 ? '\u2026' : '';
|
|
77
|
-
return `<span
|
|
77
|
+
return `<span class="blog-comment-body">${escapeHtml(preview)}${ellipsis}</span>`;
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
{
|
|
@@ -83,7 +83,7 @@ export const commentsView = {
|
|
|
83
83
|
sortable: false,
|
|
84
84
|
render: (v) => v
|
|
85
85
|
? `<a href="#/plugins/blog/posts/${escapeHtml(v)}">${escapeHtml(v)}</a>`
|
|
86
|
-
: '<span
|
|
86
|
+
: '<span class="blog-empty-cell">-</span>'
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
key: 'createdAt',
|
|
@@ -91,7 +91,7 @@ export const commentsView = {
|
|
|
91
91
|
sortable: true,
|
|
92
92
|
render: (v) => v
|
|
93
93
|
? `<span title="${escapeHtml(D(v).format('DD MMM YYYY HH:mm'))}">${escapeHtml(D(v).fromNow())}</span>`
|
|
94
|
-
: '<span
|
|
94
|
+
: '<span class="blog-empty-cell">-</span>'
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
key: 'id',
|
|
@@ -110,7 +110,7 @@ export const commentsView = {
|
|
|
110
110
|
}
|
|
111
111
|
btns.push(`<button class="btn btn-sm btn-danger comment-delete-btn" data-id="${escapeHtml(id)}">` +
|
|
112
112
|
`<span data-icon="trash" data-icon-size="14"></span></button>`);
|
|
113
|
-
return `<div
|
|
113
|
+
return `<div class="blog-moderation-actions">${btns.join('')}</div>`;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
],
|