domma-cms 0.66.0 → 0.69.4
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 +64 -1
- package/README.md +14 -10
- package/admin/css/admin.css +1 -1
- package/admin/index.html +1 -0
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/lib/analytics-shape.js +1 -0
- package/admin/js/lib/contacts-arrange.js +1 -0
- package/admin/js/lib/help-popover.js +1 -0
- package/admin/js/lib/notes-arrange.js +1 -0
- package/admin/js/lib/plugin-accent.js +1 -1
- package/admin/js/lib/plugin-chrome.js +1 -1
- 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 +3 -3
- package/admin/js/lib/todo-arrange.js +1 -0
- package/admin/js/lib/tool-kit.js +1 -0
- package/admin/js/templates/analytics.html +138 -0
- package/admin/js/templates/contacts.html +156 -0
- package/admin/js/templates/notes.html +137 -0
- package/admin/js/templates/plugin-marketplace.html +17 -4
- package/admin/js/templates/plugins.html +13 -7
- package/admin/js/templates/theme.html +38 -0
- package/admin/js/templates/todo.html +113 -0
- package/admin/js/views/analytics.js +62 -0
- package/admin/js/views/contacts.js +174 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/notes.js +104 -0
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +24 -22
- package/admin/js/views/theme.js +1 -1
- package/admin/js/views/todo.js +96 -0
- package/bin/cli.js +4 -10
- package/bin/lib/plugin-version.js +28 -0
- package/bin/update.js +14 -0
- package/config/menus/admin-sidebar.json +1 -1
- package/config/server.json +3 -1
- package/package.json +3 -7
- package/plugins/_lib/admin/mail/reader-view.js +20 -0
- package/plugins/_lib/mail/store.js +21 -1
- package/plugins/blog/CLAUDE.md +237 -0
- package/plugins/blog/admin/css/index.css +178 -1
- package/plugins/blog/admin/templates/blog.html +35 -53
- package/plugins/blog/admin/templates/categories.html +19 -6
- package/plugins/blog/admin/templates/comments.html +26 -9
- package/plugins/blog/admin/templates/post-editor.html +36 -13
- package/plugins/blog/admin/templates/settings.html +2 -11
- package/plugins/blog/admin/views/blog.js +288 -162
- package/plugins/blog/admin/views/categories.js +212 -201
- package/plugins/blog/admin/views/comments.js +207 -158
- package/plugins/blog/admin/views/kit.js +372 -0
- package/plugins/blog/admin/views/post-editor.js +269 -21
- package/plugins/blog/admin/views/settings-cog.js +56 -0
- package/plugins/blog/admin/views/settings.js +16 -86
- package/plugins/blog/blocks/blog-card-compact.css +17 -0
- package/plugins/blog/blocks/blog-card-compact.html +8 -0
- package/plugins/blog/blocks/blog-card-row.css +22 -0
- package/plugins/blog/blocks/blog-card-row.html +11 -0
- package/plugins/blog/blocks/blog-card.css +35 -0
- package/plugins/blog/blocks/blog-card.html +16 -0
- package/plugins/blog/blocks/blog-post-classic.css +15 -0
- package/plugins/blog/blocks/blog-post-classic.html +28 -0
- package/plugins/blog/blocks/blog-post-feature.css +20 -0
- package/plugins/blog/blocks/blog-post-feature.html +30 -0
- package/plugins/blog/blocks/blog-post-minimal.css +10 -0
- package/plugins/blog/blocks/blog-post-minimal.html +23 -0
- package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
- package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
- package/plugins/blog/collections/posts/schema.json +3 -1
- package/plugins/blog/config.js +6 -0
- package/plugins/blog/lib/layouts.js +304 -0
- package/plugins/blog/lib/page.js +158 -0
- package/plugins/blog/lib/render.js +119 -0
- package/plugins/blog/lib/samples.js +234 -0
- package/plugins/blog/plugin.js +225 -15
- package/plugins/blog/plugin.json +21 -81
- package/plugins/blog/plugin.public.js +115 -210
- package/plugins/blog/public/blog.css +177 -0
- package/plugins/blog/public/blog.js +586 -0
- package/plugins/blog/public/samples/aurora.svg +7 -0
- package/plugins/blog/public/samples/dusk.svg +7 -0
- package/plugins/blog/public/samples/ember.svg +7 -0
- package/plugins/blog/public/samples/harbour.svg +7 -0
- package/plugins/blog/public/samples/meadow.svg +7 -0
- package/plugins/blog/public/samples/rosewood.svg +7 -0
- package/plugins/blog/templates/index.html +9 -8
- package/plugins/blog/templates/post.html +4 -17
- package/plugins/blog/tests/layouts.test.js +97 -0
- package/plugins/blog/tests/public.test.js +71 -0
- package/plugins/free-tier.lock.json +74 -0
- package/plugins/mail-reader/CLAUDE.md +78 -0
- package/plugins/mail-reader/plugin.json +2 -1
- package/public/css/theme-switcher.css +1 -0
- package/public/js/analytics.js +1 -0
- package/public/js/theme-switcher.js +1 -0
- package/scripts/build.js +41 -2
- package/scripts/run-tests.mjs +94 -0
- package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
- package/{plugins/contacts/plugin.js → server/routes/api/contacts.js} +71 -43
- package/server/routes/api/dashboard.js +8 -2
- package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
- package/server/routes/api/plugin-marketplace.js +78 -23
- package/server/routes/api/plugins.js +148 -3
- package/server/routes/api/todo.js +178 -0
- package/server/server.js +61 -1
- package/server/services/collections.js +13 -3
- package/server/services/managerClient.js +72 -136
- package/server/services/markdown.js +54 -1
- package/server/services/permissionRegistry.js +50 -0
- package/server/services/pluginEntitlement.js +171 -0
- package/server/services/pluginEnvelope.js +242 -0
- package/server/services/pluginInstaller.js +246 -26
- package/server/services/pluginScaffold.js +5 -0
- package/server/services/plugins.js +117 -7
- package/server/services/presetCollections.js +344 -0
- package/server/services/promoted-plugins-migration.js +254 -0
- package/server/services/publicCors.js +46 -0
- package/server/services/renderer.js +83 -3
- package/server/services/sidebarBadges.js +37 -0
- package/server/services/siteGitignore.js +310 -0
- package/server/services/themeSettings.js +39 -0
- package/config/theme.json +0 -18
- package/plugins/analytics/admin/css/index.css +0 -1
- package/plugins/analytics/admin/templates/analytics.html +0 -70
- package/plugins/analytics/admin/views/analytics.js +0 -176
- package/plugins/analytics/config.js +0 -14
- package/plugins/analytics/plugin.json +0 -60
- package/plugins/analytics/public/inject-body.html +0 -60
- package/plugins/analytics/public/inject-head.html +0 -1
- package/plugins/blog/templates/author.html +0 -9
- package/plugins/blog/templates/category.html +0 -9
- package/plugins/blog/templates/tag.html +0 -9
- package/plugins/contacts/admin/templates/contacts.html +0 -254
- package/plugins/contacts/admin/views/contacts.js +0 -943
- package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
- package/plugins/contacts/collections/user-contacts/schema.json +0 -179
- package/plugins/contacts/config.js +0 -6
- package/plugins/contacts/plugin.json +0 -36
- package/plugins/demo-viewer/config.js +0 -4
- package/plugins/demo-viewer/plugin.js +0 -36
- package/plugins/demo-viewer/plugin.json +0 -9
- package/plugins/invoice/admin/css/index.css +0 -1
- package/plugins/invoice/admin/templates/editor.html +0 -220
- package/plugins/invoice/admin/templates/index.html +0 -173
- package/plugins/invoice/admin/templates/issuers.html +0 -2
- package/plugins/invoice/admin/templates/receivers.html +0 -2
- package/plugins/invoice/admin/views/contacts-source.js +0 -266
- package/plugins/invoice/admin/views/editor.js +0 -434
- package/plugins/invoice/admin/views/export.js +0 -199
- package/plugins/invoice/admin/views/help-content.js +0 -61
- package/plugins/invoice/admin/views/index.js +0 -643
- package/plugins/invoice/admin/views/issuers.js +0 -30
- package/plugins/invoice/admin/views/media.js +0 -172
- package/plugins/invoice/admin/views/party-view.js +0 -386
- package/plugins/invoice/admin/views/payments.js +0 -127
- package/plugins/invoice/admin/views/print.js +0 -130
- package/plugins/invoice/admin/views/receivers.js +0 -55
- package/plugins/invoice/admin/views/send.js +0 -212
- package/plugins/invoice/admin/views/settings.js +0 -594
- package/plugins/invoice/admin/views/view-lifecycle.js +0 -33
- package/plugins/invoice/collections/invoice-issuers/schema.json +0 -82
- package/plugins/invoice/collections/invoice-receivers/schema.json +0 -16
- package/plugins/invoice/collections/invoices/schema.json +0 -33
- package/plugins/invoice/config.js +0 -37
- package/plugins/invoice/pdf.js +0 -164
- package/plugins/invoice/plugin.js +0 -1456
- package/plugins/invoice/plugin.json +0 -86
- package/plugins/invoice/templates/_base.css +0 -1
- package/plugins/invoice/templates/classic-nologo.html +0 -100
- package/plugins/invoice/templates/classic.html +0 -91
- package/plugins/invoice/templates/invoice-print.html +0 -237
- package/plugins/invoice/templates/minimal.html +0 -99
- package/plugins/invoice/templates/modern-nologo.html +0 -114
- package/plugins/invoice/templates/modern.html +0 -113
- package/plugins/invoice/templates/templates.json +0 -11
- package/plugins/notes/admin/templates/notes.html +0 -83
- package/plugins/notes/admin/views/notes.js +0 -281
- package/plugins/notes/collections/user-notes/schema.json +0 -54
- package/plugins/notes/config.js +0 -6
- package/plugins/notes/plugin.json +0 -33
- package/plugins/surveys/admin/templates/audience.html +0 -47
- package/plugins/surveys/admin/templates/results.html +0 -58
- package/plugins/surveys/admin/templates/survey-editor.html +0 -141
- package/plugins/surveys/admin/templates/surveys.html +0 -25
- package/plugins/surveys/admin/views/audience.js +0 -301
- package/plugins/surveys/admin/views/results.js +0 -172
- package/plugins/surveys/admin/views/survey-editor.js +0 -211
- package/plugins/surveys/admin/views/surveys.js +0 -161
- package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
- package/plugins/surveys/collections/survey-groups/schema.json +0 -11
- package/plugins/surveys/collections/survey-invites/schema.json +0 -16
- package/plugins/surveys/collections/surveys/schema.json +0 -23
- package/plugins/surveys/config.js +0 -8
- package/plugins/surveys/lib/audience.js +0 -37
- package/plugins/surveys/lib/campaigns.js +0 -43
- package/plugins/surveys/lib/ledger.js +0 -110
- package/plugins/surveys/lib/sending.js +0 -106
- package/plugins/surveys/lib/stats.js +0 -62
- package/plugins/surveys/lib/submit.js +0 -95
- package/plugins/surveys/lib/tokens.js +0 -28
- package/plugins/surveys/plugin.js +0 -174
- package/plugins/surveys/plugin.json +0 -65
- package/plugins/surveys/plugin.public.js +0 -149
- package/plugins/surveys/public/dist-shared.mjs +0 -1
- package/plugins/surveys/public/survey.css +0 -1
- package/plugins/surveys/public/survey.mjs +0 -1
- package/plugins/surveys/templates/survey-page.html +0 -63
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
- package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
- package/plugins/theme-switcher/config.js +0 -10
- package/plugins/theme-switcher/plugin.js +0 -26
- package/plugins/theme-switcher/plugin.json +0 -74
- package/plugins/theme-switcher/public/inject-body.html +0 -153
- package/plugins/theme-switcher/public/inject-head.html +0 -260
- package/plugins/todo/admin/templates/todo.html +0 -158
- package/plugins/todo/admin/views/todo.js +0 -343
- package/plugins/todo/collections/todos/schema.json +0 -60
- package/plugins/todo/config.js +0 -7
- package/plugins/todo/plugin.js +0 -102
- package/plugins/todo/plugin.json +0 -33
- /package/{plugins/contacts/admin/css/index.css → admin/css/contacts.css} +0 -0
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Modern, for an issuer with no logo: the trading name is the wordmark in
|
|
3
|
-
the band, so the band is never a dark strip with a hole in it.
|
|
4
|
-
-->
|
|
5
|
-
<!DOCTYPE html>
|
|
6
|
-
<html lang="en">
|
|
7
|
-
<head>
|
|
8
|
-
<meta charset="utf-8">
|
|
9
|
-
<title>Invoice {{number}}</title>
|
|
10
|
-
<style>
|
|
11
|
-
{{baseCss}}
|
|
12
|
-
|
|
13
|
-
/* Modern - a dark masthead band, the numbers doing the talking underneath. */
|
|
14
|
-
.invoice { padding: 0 0 48px; }
|
|
15
|
-
.band {
|
|
16
|
-
background: #1b1f23;
|
|
17
|
-
color: #fff;
|
|
18
|
-
padding: 28px 56px;
|
|
19
|
-
display: flex;
|
|
20
|
-
justify-content: space-between;
|
|
21
|
-
align-items: flex-start;
|
|
22
|
-
gap: 24px;
|
|
23
|
-
}
|
|
24
|
-
.band .wordmark { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
|
|
25
|
-
.band .logo { max-height: 56px; margin: 0; }
|
|
26
|
-
.band h1 { margin: 0; font-size: 24px; letter-spacing: 3px; font-weight: 600; }
|
|
27
|
-
.band .number { font-size: 15px; opacity: .75; margin-top: 2px; }
|
|
28
|
-
.band .meta { text-align: right; }
|
|
29
|
-
.band .meta div { opacity: .85; font-size: 13px; }
|
|
30
|
-
.band .status {
|
|
31
|
-
background: rgba(255,255,255,.15);
|
|
32
|
-
color: #fff;
|
|
33
|
-
margin-top: 8px;
|
|
34
|
-
}
|
|
35
|
-
.top { display: none; }
|
|
36
|
-
.body { padding: 32px 56px 0; }
|
|
37
|
-
.parties { grid-template-columns: 1fr 1fr; }
|
|
38
|
-
table.items th { border-bottom-color: #1b1f23; }
|
|
39
|
-
.totals .grand th, .totals .grand td { border-top-color: #1b1f23; }
|
|
40
|
-
</style>
|
|
41
|
-
</head>
|
|
42
|
-
<body>
|
|
43
|
-
|
|
44
|
-
<div class="actions">
|
|
45
|
-
<button onclick="window.print()">Print / Save as PDF</button>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div class="invoice">
|
|
49
|
-
<div class="band">
|
|
50
|
-
<div>
|
|
51
|
-
<div class="wordmark">{{issuerName}}</div>
|
|
52
|
-
<div class="lines" style="opacity:.8;font-size:12px;margin-top:6px;">{{issuerEmail}}</div>
|
|
53
|
-
</div>
|
|
54
|
-
<div class="meta">
|
|
55
|
-
<h1>INVOICE</h1>
|
|
56
|
-
<div class="number">{{number}}</div>
|
|
57
|
-
<div>{{issueDate}}</div>
|
|
58
|
-
{{dueDate}}
|
|
59
|
-
<div class="status">{{status}}</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<div class="body">
|
|
64
|
-
<div class="parties">
|
|
65
|
-
<div class="party">
|
|
66
|
-
<h3>From</h3>
|
|
67
|
-
<div class="name">{{issuerName}}</div>
|
|
68
|
-
<div class="lines">{{issuerAddress}}</div>
|
|
69
|
-
<div class="lines">{{issuerPhone}}</div>
|
|
70
|
-
{{issuerVat}}
|
|
71
|
-
</div>
|
|
72
|
-
<div class="party">
|
|
73
|
-
<h3>Billed To</h3>
|
|
74
|
-
<div class="name">{{receiverName}}</div>
|
|
75
|
-
<div class="lines">{{receiverAddress}}</div>
|
|
76
|
-
<div class="lines">{{receiverEmail}}</div>
|
|
77
|
-
{{receiverVat}}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<table class="items">
|
|
82
|
-
<thead>
|
|
83
|
-
<tr>
|
|
84
|
-
<th>Description</th>
|
|
85
|
-
<th class="num">Qty</th>
|
|
86
|
-
<th class="num">Unit</th>
|
|
87
|
-
<th class="num">Total</th>
|
|
88
|
-
</tr>
|
|
89
|
-
</thead>
|
|
90
|
-
<tbody>
|
|
91
|
-
{{items}}
|
|
92
|
-
</tbody>
|
|
93
|
-
</table>
|
|
94
|
-
|
|
95
|
-
<div class="totals">
|
|
96
|
-
<table>
|
|
97
|
-
<tr><th>Subtotal</th><td class="num">{{subtotal}}</td></tr>
|
|
98
|
-
{{vatRow}}
|
|
99
|
-
<tr class="grand"><th>Total</th><td class="num">{{total}}</td></tr>
|
|
100
|
-
{{paidRows}}
|
|
101
|
-
</table>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
{{notes}}
|
|
105
|
-
{{bankDetails}}
|
|
106
|
-
|
|
107
|
-
<div class="footer">{{footerNote}}</div>
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
{{autoPrint}}
|
|
112
|
-
|
|
113
|
-
</body>
|
|
114
|
-
</html>
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Modern - a dark masthead band carrying the logo and the invoice meta.
|
|
3
|
-
-->
|
|
4
|
-
<!DOCTYPE html>
|
|
5
|
-
<html lang="en">
|
|
6
|
-
<head>
|
|
7
|
-
<meta charset="utf-8">
|
|
8
|
-
<title>Invoice {{number}}</title>
|
|
9
|
-
<style>
|
|
10
|
-
{{baseCss}}
|
|
11
|
-
|
|
12
|
-
/* Modern - a dark masthead band, the numbers doing the talking underneath. */
|
|
13
|
-
.invoice { padding: 0 0 48px; }
|
|
14
|
-
.band {
|
|
15
|
-
background: #1b1f23;
|
|
16
|
-
color: #fff;
|
|
17
|
-
padding: 28px 56px;
|
|
18
|
-
display: flex;
|
|
19
|
-
justify-content: space-between;
|
|
20
|
-
align-items: flex-start;
|
|
21
|
-
gap: 24px;
|
|
22
|
-
}
|
|
23
|
-
.band .wordmark { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
|
|
24
|
-
.band .logo { max-height: 56px; margin: 0; }
|
|
25
|
-
.band h1 { margin: 0; font-size: 24px; letter-spacing: 3px; font-weight: 600; }
|
|
26
|
-
.band .number { font-size: 15px; opacity: .75; margin-top: 2px; }
|
|
27
|
-
.band .meta { text-align: right; }
|
|
28
|
-
.band .meta div { opacity: .85; font-size: 13px; }
|
|
29
|
-
.band .status {
|
|
30
|
-
background: rgba(255,255,255,.15);
|
|
31
|
-
color: #fff;
|
|
32
|
-
margin-top: 8px;
|
|
33
|
-
}
|
|
34
|
-
.top { display: none; }
|
|
35
|
-
.body { padding: 32px 56px 0; }
|
|
36
|
-
.parties { grid-template-columns: 1fr 1fr; }
|
|
37
|
-
table.items th { border-bottom-color: #1b1f23; }
|
|
38
|
-
.totals .grand th, .totals .grand td { border-top-color: #1b1f23; }
|
|
39
|
-
</style>
|
|
40
|
-
</head>
|
|
41
|
-
<body>
|
|
42
|
-
|
|
43
|
-
<div class="actions">
|
|
44
|
-
<button onclick="window.print()">Print / Save as PDF</button>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="invoice">
|
|
48
|
-
<div class="band">
|
|
49
|
-
<div>
|
|
50
|
-
{{issuerLogo}}
|
|
51
|
-
<div class="lines" style="opacity:.8;font-size:12px;margin-top:6px;">{{issuerEmail}}</div>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="meta">
|
|
54
|
-
<h1>INVOICE</h1>
|
|
55
|
-
<div class="number">{{number}}</div>
|
|
56
|
-
<div>{{issueDate}}</div>
|
|
57
|
-
{{dueDate}}
|
|
58
|
-
<div class="status">{{status}}</div>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div class="body">
|
|
63
|
-
<div class="parties">
|
|
64
|
-
<div class="party">
|
|
65
|
-
<h3>From</h3>
|
|
66
|
-
<div class="name">{{issuerName}}</div>
|
|
67
|
-
<div class="lines">{{issuerAddress}}</div>
|
|
68
|
-
<div class="lines">{{issuerPhone}}</div>
|
|
69
|
-
{{issuerVat}}
|
|
70
|
-
</div>
|
|
71
|
-
<div class="party">
|
|
72
|
-
<h3>Billed To</h3>
|
|
73
|
-
<div class="name">{{receiverName}}</div>
|
|
74
|
-
<div class="lines">{{receiverAddress}}</div>
|
|
75
|
-
<div class="lines">{{receiverEmail}}</div>
|
|
76
|
-
{{receiverVat}}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<table class="items">
|
|
81
|
-
<thead>
|
|
82
|
-
<tr>
|
|
83
|
-
<th>Description</th>
|
|
84
|
-
<th class="num">Qty</th>
|
|
85
|
-
<th class="num">Unit</th>
|
|
86
|
-
<th class="num">Total</th>
|
|
87
|
-
</tr>
|
|
88
|
-
</thead>
|
|
89
|
-
<tbody>
|
|
90
|
-
{{items}}
|
|
91
|
-
</tbody>
|
|
92
|
-
</table>
|
|
93
|
-
|
|
94
|
-
<div class="totals">
|
|
95
|
-
<table>
|
|
96
|
-
<tr><th>Subtotal</th><td class="num">{{subtotal}}</td></tr>
|
|
97
|
-
{{vatRow}}
|
|
98
|
-
<tr class="grand"><th>Total</th><td class="num">{{total}}</td></tr>
|
|
99
|
-
{{paidRows}}
|
|
100
|
-
</table>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
{{notes}}
|
|
104
|
-
{{bankDetails}}
|
|
105
|
-
|
|
106
|
-
<div class="footer">{{footerNote}}</div>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
{{autoPrint}}
|
|
111
|
-
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "The templates offered in Settings > Templates. A site can add its own: drop <id>.html (and optionally <id>-nologo.html) beside this file and add an entry. `nologo: false` means the style has nowhere to put a logo, so it is used as-is either way.",
|
|
3
|
-
"templates": [
|
|
4
|
-
{ "id": "classic", "name": "Classic", "nologo": true,
|
|
5
|
-
"description": "Logo top left, invoice details top right. The traditional letterhead layout." },
|
|
6
|
-
{ "id": "modern", "name": "Modern", "nologo": true,
|
|
7
|
-
"description": "A dark masthead band across the top carrying the logo and the invoice number." },
|
|
8
|
-
{ "id": "minimal", "name": "Minimal", "nologo": false,
|
|
9
|
-
"description": "Typographic and unfilled - no logo, no colour. Prints cleanly in black and white." }
|
|
10
|
-
]
|
|
11
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
<div class="notes-plugin-wrap" style="padding: 24px; max-width: 1200px;">
|
|
2
|
-
|
|
3
|
-
<!-- Toolbar -->
|
|
4
|
-
<div class="notes-toolbar" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:20px;">
|
|
5
|
-
<h2 style="margin:0;flex:0 0 auto;font-size:1.4rem;">
|
|
6
|
-
<span data-icon="file-text" data-icon-size="20" style="margin-right:8px;"></span>Notes
|
|
7
|
-
</h2>
|
|
8
|
-
<input
|
|
9
|
-
id="notes-search"
|
|
10
|
-
type="search"
|
|
11
|
-
class="form-input"
|
|
12
|
-
placeholder="Search notes…"
|
|
13
|
-
style="flex:1;min-width:180px;max-width:320px;"
|
|
14
|
-
/>
|
|
15
|
-
<button id="new-note-btn" class="btn btn-primary" style="flex-shrink:0;">
|
|
16
|
-
<span data-icon="plus" data-icon-size="15" style="margin-right:6px;"></span>New Note
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<!-- Category filter -->
|
|
21
|
-
<div id="category-filter" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:18px;"></div>
|
|
22
|
-
|
|
23
|
-
<!-- Notes table -->
|
|
24
|
-
<div id="notes-table"></div>
|
|
25
|
-
|
|
26
|
-
<!-- Editor overlay -->
|
|
27
|
-
<div
|
|
28
|
-
id="note-editor"
|
|
29
|
-
style="display:none;position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,0.45);
|
|
30
|
-
align-items:center;justify-content:center;padding:20px;"
|
|
31
|
-
>
|
|
32
|
-
<div class="card" style="width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;">
|
|
33
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;">
|
|
34
|
-
<strong style="font-size:1rem;">Edit Note</strong>
|
|
35
|
-
<button id="cancel-note-btn" class="btn btn-sm btn-secondary" title="Close">
|
|
36
|
-
<span data-icon="x" data-icon-size="15"></span>
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
<div class="card-body" style="flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:14px;">
|
|
40
|
-
<div>
|
|
41
|
-
<label for="note-title" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Title</label>
|
|
42
|
-
<input
|
|
43
|
-
id="note-title"
|
|
44
|
-
type="text"
|
|
45
|
-
class="form-input"
|
|
46
|
-
placeholder="Note title…"
|
|
47
|
-
/>
|
|
48
|
-
</div>
|
|
49
|
-
<div style="flex:1;display:flex;flex-direction:column;">
|
|
50
|
-
<label for="note-content" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Content</label>
|
|
51
|
-
<textarea
|
|
52
|
-
id="note-content"
|
|
53
|
-
class="form-input"
|
|
54
|
-
placeholder="Write your note here… (Ctrl+Enter to save)"
|
|
55
|
-
style="flex:1;min-height:220px;resize:vertical;font-family:inherit;"
|
|
56
|
-
></textarea>
|
|
57
|
-
</div>
|
|
58
|
-
<div>
|
|
59
|
-
<label for="note-categories" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Categories</label>
|
|
60
|
-
<input
|
|
61
|
-
id="note-categories"
|
|
62
|
-
type="text"
|
|
63
|
-
class="form-input"
|
|
64
|
-
placeholder="Comma-separated, e.g. work, ideas, personal"
|
|
65
|
-
/>
|
|
66
|
-
<small style="opacity:0.6;margin-top:4px;display:block;">Separate multiple categories with commas.</small>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="card-footer" style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 20px;">
|
|
70
|
-
<button id="delete-note-btn" class="btn btn-danger btn-sm" style="display:none;">
|
|
71
|
-
<span data-icon="trash" data-icon-size="13" style="margin-right:5px;"></span>Delete
|
|
72
|
-
</button>
|
|
73
|
-
<div style="display:flex;gap:8px;margin-left:auto;">
|
|
74
|
-
<button id="cancel-note-btn-footer" class="btn btn-secondary btn-sm">Cancel</button>
|
|
75
|
-
<button id="save-note-btn" class="btn btn-primary btn-sm">
|
|
76
|
-
<span data-icon="save" data-icon-size="13" style="margin-right:5px;"></span>Save
|
|
77
|
-
</button>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
</div>
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Notes plugin admin view
|
|
3
|
-
// Uses auth-aware api() helper for all API calls
|
|
4
|
-
// Uses T.create() for table rendering (Domma Table component)
|
|
5
|
-
// ============================================================
|
|
6
|
-
|
|
7
|
-
const esc = (str) => String(str ?? '')
|
|
8
|
-
.replace(/&/g, '&')
|
|
9
|
-
.replace(/</g, '<')
|
|
10
|
-
.replace(/>/g, '>')
|
|
11
|
-
.replace(/"/g, '"')
|
|
12
|
-
.replace(/'/g, ''');
|
|
13
|
-
|
|
14
|
-
function debounce(fn, delay) {
|
|
15
|
-
let timer;
|
|
16
|
-
return (...args) => {
|
|
17
|
-
clearTimeout(timer);
|
|
18
|
-
timer = setTimeout(() => fn(...args), delay);
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function api(url, method = 'GET', body) {
|
|
23
|
-
const opts = {method, headers: {'Authorization': 'Bearer ' + (S.get('auth_token') || '')}};
|
|
24
|
-
if (body !== undefined) {
|
|
25
|
-
opts.headers['Content-Type'] = 'application/json';
|
|
26
|
-
opts.body = JSON.stringify(body);
|
|
27
|
-
}
|
|
28
|
-
const res = await fetch(url, opts);
|
|
29
|
-
if (!res.ok) {
|
|
30
|
-
const err = await res.json().catch(() => ({error: res.statusText}));
|
|
31
|
-
throw new Error(err.error || res.statusText);
|
|
32
|
-
}
|
|
33
|
-
const text = await res.text();
|
|
34
|
-
return text ? JSON.parse(text) : {};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const notesView = {
|
|
38
|
-
templateUrl: '/plugins/notes/admin/templates/notes.html',
|
|
39
|
-
|
|
40
|
-
async onMount($container) {
|
|
41
|
-
// ---- State ----
|
|
42
|
-
let notes = [];
|
|
43
|
-
let categories = [];
|
|
44
|
-
let activeCategory = '';
|
|
45
|
-
let searchQuery = '';
|
|
46
|
-
let editingNoteId = null;
|
|
47
|
-
|
|
48
|
-
// ---- DOM refs ----
|
|
49
|
-
const editorEl = $container.find('#note-editor').get(0);
|
|
50
|
-
const titleInput = $container.find('#note-title').get(0);
|
|
51
|
-
const contentInput = $container.find('#note-content').get(0);
|
|
52
|
-
const catsInput = $container.find('#note-categories').get(0);
|
|
53
|
-
const deleteBtnEl = $container.find('#delete-note-btn').get(0);
|
|
54
|
-
const catFilterEl = $container.find('#category-filter').get(0);
|
|
55
|
-
|
|
56
|
-
// ---- Fetch ----
|
|
57
|
-
async function loadNotes() {
|
|
58
|
-
try {
|
|
59
|
-
const params = new URLSearchParams();
|
|
60
|
-
if (searchQuery) params.set('q', searchQuery);
|
|
61
|
-
if (activeCategory) params.set('category', activeCategory);
|
|
62
|
-
const qs = params.toString();
|
|
63
|
-
const url = '/api/plugins/notes/items' + (qs ? '?' + qs : '');
|
|
64
|
-
const res = await api(url);
|
|
65
|
-
notes = Array.isArray(res) ? res : (res.data ?? []);
|
|
66
|
-
} catch {
|
|
67
|
-
E.toast('Failed to load notes', { type: 'error' });
|
|
68
|
-
notes = [];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function loadCategories() {
|
|
73
|
-
try {
|
|
74
|
-
const res = await api('/api/plugins/notes/categories');
|
|
75
|
-
categories = Array.isArray(res) ? res : [];
|
|
76
|
-
} catch {
|
|
77
|
-
categories = [];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ---- Render table ----
|
|
82
|
-
function renderTable() {
|
|
83
|
-
const wrap = $container.find('#notes-table').get(0);
|
|
84
|
-
if (!wrap) return;
|
|
85
|
-
|
|
86
|
-
T.create(wrap, {
|
|
87
|
-
data: notes,
|
|
88
|
-
columns: [
|
|
89
|
-
{
|
|
90
|
-
key: 'title',
|
|
91
|
-
title: 'Title',
|
|
92
|
-
sortable: true,
|
|
93
|
-
render: (v) => `<strong>${esc(v || 'Untitled')}</strong>`
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
key: 'content',
|
|
97
|
-
title: 'Content',
|
|
98
|
-
sortable: false,
|
|
99
|
-
render: (v) => {
|
|
100
|
-
const preview = (v || '').slice(0, 100);
|
|
101
|
-
const ellipsis = v && v.length > 100 ? '\u2026' : '';
|
|
102
|
-
return preview
|
|
103
|
-
? `<span style="font-size:0.87rem;opacity:0.8;">${esc(preview)}${ellipsis}</span>`
|
|
104
|
-
: '<span style="opacity:0.35;">-</span>';
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
key: 'categories',
|
|
109
|
-
title: 'Categories',
|
|
110
|
-
sortable: false,
|
|
111
|
-
render: (v) => {
|
|
112
|
-
if (!Array.isArray(v) || v.length === 0) return '<span style="opacity:0.35;">-</span>';
|
|
113
|
-
return v.map((c) =>
|
|
114
|
-
`<span class="badge badge-secondary notes-cat-tag" data-cat="${esc(c)}" `
|
|
115
|
-
+ `style="font-size:0.7rem;margin-right:2px;cursor:pointer;">${esc(c)}</span>`
|
|
116
|
-
).join('');
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
key: 'updatedAt',
|
|
121
|
-
title: 'Updated',
|
|
122
|
-
sortable: true,
|
|
123
|
-
render: (v) => v
|
|
124
|
-
? `<span title="${esc(D(v).format('DD MMM YYYY HH:mm'))}">${esc(D(v).fromNow())}</span>`
|
|
125
|
-
: '-'
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
key: 'id',
|
|
129
|
-
title: 'Actions',
|
|
130
|
-
sortable: false,
|
|
131
|
-
render: (id) =>
|
|
132
|
-
`<div style="display:flex;gap:.4rem;">` +
|
|
133
|
-
`<button class="btn btn-sm btn-outline note-edit-btn" data-id="${esc(id)}">Edit</button>` +
|
|
134
|
-
`<button class="btn btn-sm btn-danger note-delete-btn" data-id="${esc(id)}">Delete</button>` +
|
|
135
|
-
`</div>`
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
emptyMessage: 'No notes yet. Click New Note to get started.'
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
wrap.querySelectorAll('.note-edit-btn').forEach((btn) => {
|
|
142
|
-
btn.addEventListener('click', () => {
|
|
143
|
-
const note = notes.find((n) => n.id === btn.dataset.id);
|
|
144
|
-
if (note) openEditor(note);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
wrap.querySelectorAll('.note-delete-btn').forEach((btn) => {
|
|
149
|
-
btn.addEventListener('click', () => deleteNote(btn.dataset.id));
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
wrap.querySelectorAll('.notes-cat-tag').forEach((tag) => {
|
|
153
|
-
tag.addEventListener('click', () => {
|
|
154
|
-
activeCategory = tag.dataset.cat;
|
|
155
|
-
renderCategoryFilter();
|
|
156
|
-
loadNotes().then(renderTable);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
Domma.icons.scan(wrap);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---- Render category filter bar (DOM methods - no innerHTML with user data) ----
|
|
164
|
-
function renderCategoryFilter() {
|
|
165
|
-
catFilterEl.textContent = '';
|
|
166
|
-
|
|
167
|
-
const makeBtn = (label, cat) => {
|
|
168
|
-
const btn = document.createElement('button');
|
|
169
|
-
btn.className = 'btn btn-sm cat-filter-btn ' + (activeCategory === cat ? 'btn-primary' : 'btn-secondary');
|
|
170
|
-
btn.dataset.cat = cat;
|
|
171
|
-
btn.style.marginRight = '4px';
|
|
172
|
-
btn.textContent = label;
|
|
173
|
-
return btn;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
catFilterEl.appendChild(makeBtn('All', ''));
|
|
177
|
-
for (const c of categories) {
|
|
178
|
-
catFilterEl.appendChild(makeBtn(c, c));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// ---- Full reload ----
|
|
183
|
-
async function reload() {
|
|
184
|
-
await Promise.all([loadNotes(), loadCategories()]);
|
|
185
|
-
renderTable();
|
|
186
|
-
renderCategoryFilter();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ---- Editor helpers ----
|
|
190
|
-
function openEditor(note = null) {
|
|
191
|
-
editingNoteId = note ? note.id : null;
|
|
192
|
-
titleInput.value = note ? (note.title || '') : '';
|
|
193
|
-
contentInput.value = note ? (note.content || '') : '';
|
|
194
|
-
catsInput.value = note && Array.isArray(note.categories) ? note.categories.join(', ') : '';
|
|
195
|
-
deleteBtnEl.style.display = note ? 'inline-flex' : 'none';
|
|
196
|
-
editorEl.style.display = 'flex';
|
|
197
|
-
titleInput.focus();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function closeEditor() {
|
|
201
|
-
editorEl.style.display = 'none';
|
|
202
|
-
editingNoteId = null;
|
|
203
|
-
titleInput.value = '';
|
|
204
|
-
contentInput.value = '';
|
|
205
|
-
catsInput.value = '';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function parseCategoriesInput() {
|
|
209
|
-
return catsInput.value.split(',').map((c) => c.trim()).filter(Boolean);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ---- Save (create or update) ----
|
|
213
|
-
async function saveNote() {
|
|
214
|
-
const title = titleInput.value.trim();
|
|
215
|
-
const content = contentInput.value.trim();
|
|
216
|
-
const cats = parseCategoriesInput();
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
if (editingNoteId) {
|
|
220
|
-
await api('/api/plugins/notes/items/' + editingNoteId, 'PUT', {title, content, categories: cats});
|
|
221
|
-
E.toast('Note updated', { type: 'success' });
|
|
222
|
-
} else {
|
|
223
|
-
await api('/api/plugins/notes/items', 'POST', {title, content, categories: cats});
|
|
224
|
-
E.toast('Note created', { type: 'success' });
|
|
225
|
-
}
|
|
226
|
-
closeEditor();
|
|
227
|
-
await reload();
|
|
228
|
-
} catch {
|
|
229
|
-
E.toast('Failed to save note', { type: 'error' });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// ---- Delete ----
|
|
234
|
-
async function deleteNote(id) {
|
|
235
|
-
const confirmed = await E.confirm('Delete this note?');
|
|
236
|
-
if (!confirmed) return;
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
await api('/api/plugins/notes/items/' + id, 'DELETE');
|
|
240
|
-
E.toast('Note deleted', { type: 'success' });
|
|
241
|
-
if (editingNoteId === id) closeEditor();
|
|
242
|
-
await reload();
|
|
243
|
-
} catch {
|
|
244
|
-
E.toast('Failed to delete note', { type: 'error' });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---- Event bindings ----
|
|
249
|
-
|
|
250
|
-
$container.find('#new-note-btn').get(0).addEventListener('click', () => openEditor());
|
|
251
|
-
$container.find('#save-note-btn').get(0).addEventListener('click', saveNote);
|
|
252
|
-
$container.find('#cancel-note-btn').get(0).addEventListener('click', closeEditor);
|
|
253
|
-
$container.find('#cancel-note-btn-footer').get(0).addEventListener('click', closeEditor);
|
|
254
|
-
deleteBtnEl.addEventListener('click', () => {
|
|
255
|
-
if (editingNoteId) deleteNote(editingNoteId);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
$container.find('#notes-search').get(0).addEventListener('input', debounce(async (e) => {
|
|
259
|
-
searchQuery = e.target.value.trim();
|
|
260
|
-
await loadNotes();
|
|
261
|
-
renderTable();
|
|
262
|
-
}, 300));
|
|
263
|
-
|
|
264
|
-
catFilterEl.addEventListener('click', async (e) => {
|
|
265
|
-
const btn = e.target.closest('.cat-filter-btn');
|
|
266
|
-
if (!btn) return;
|
|
267
|
-
activeCategory = btn.dataset.cat;
|
|
268
|
-
renderCategoryFilter();
|
|
269
|
-
await loadNotes();
|
|
270
|
-
renderTable();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
editorEl.addEventListener('keydown', (e) => {
|
|
274
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') saveNote();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// ---- Initial load ----
|
|
278
|
-
await reload();
|
|
279
|
-
Domma.icons.scan($container.get(0));
|
|
280
|
-
}
|
|
281
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"slug": "notes-notes",
|
|
3
|
-
"title": "Notes",
|
|
4
|
-
"description": "Notes managed by the Notes plugin.",
|
|
5
|
-
"plugin": "notes",
|
|
6
|
-
"fields": [
|
|
7
|
-
{
|
|
8
|
-
"name": "title",
|
|
9
|
-
"label": "Title",
|
|
10
|
-
"type": "text",
|
|
11
|
-
"required": false
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"name": "content",
|
|
15
|
-
"label": "Content",
|
|
16
|
-
"type": "textarea",
|
|
17
|
-
"required": false
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"name": "categories",
|
|
21
|
-
"label": "Categories",
|
|
22
|
-
"type": "text",
|
|
23
|
-
"required": false
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"name": "userId",
|
|
27
|
-
"label": "User ID",
|
|
28
|
-
"type": "text",
|
|
29
|
-
"required": false
|
|
30
|
-
}
|
|
31
|
-
],
|
|
32
|
-
"api": {
|
|
33
|
-
"create": {
|
|
34
|
-
"enabled": true,
|
|
35
|
-
"access": "admin"
|
|
36
|
-
},
|
|
37
|
-
"read": {
|
|
38
|
-
"enabled": true,
|
|
39
|
-
"access": "admin"
|
|
40
|
-
},
|
|
41
|
-
"update": {
|
|
42
|
-
"enabled": false,
|
|
43
|
-
"access": "admin"
|
|
44
|
-
},
|
|
45
|
-
"delete": {
|
|
46
|
-
"enabled": false,
|
|
47
|
-
"access": "admin"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"storage": {
|
|
51
|
-
"adapter": "mongodb",
|
|
52
|
-
"connection": "default"
|
|
53
|
-
}
|
|
54
|
-
}
|