domma-cms 0.66.0 → 0.69.3
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 +92 -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 +307 -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,343 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Todo 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 STATUS = {
|
|
8
|
-
NOT_STARTED: 'not-started',
|
|
9
|
-
IN_PROGRESS: 'in-progress',
|
|
10
|
-
COMPLETED: 'completed'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const PRIORITY = {
|
|
14
|
-
HIGHEST: 'highest',
|
|
15
|
-
HIGH: 'high',
|
|
16
|
-
MEDIUM: 'medium',
|
|
17
|
-
LOW: 'low',
|
|
18
|
-
LOWEST: 'lowest'
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function formatStatus(status) {
|
|
22
|
-
return status.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function formatPriority(priority) {
|
|
26
|
-
return priority.charAt(0).toUpperCase() + priority.slice(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const esc = (str) => String(str ?? '')
|
|
30
|
-
.replace(/&/g, '&')
|
|
31
|
-
.replace(/</g, '<')
|
|
32
|
-
.replace(/>/g, '>')
|
|
33
|
-
.replace(/"/g, '"')
|
|
34
|
-
.replace(/'/g, ''');
|
|
35
|
-
|
|
36
|
-
async function api(url, method = 'GET', body) {
|
|
37
|
-
const opts = {method, headers: {'Authorization': 'Bearer ' + (S.get('auth_token') || '')}};
|
|
38
|
-
if (body !== undefined) {
|
|
39
|
-
opts.headers['Content-Type'] = 'application/json';
|
|
40
|
-
opts.body = JSON.stringify(body);
|
|
41
|
-
}
|
|
42
|
-
const res = await fetch(url, opts);
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
const err = await res.json().catch(() => ({error: res.statusText}));
|
|
45
|
-
throw new Error(err.error || res.statusText);
|
|
46
|
-
}
|
|
47
|
-
const text = await res.text();
|
|
48
|
-
return text ? JSON.parse(text) : {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const todoView = {
|
|
52
|
-
templateUrl: '/plugins/todo/admin/templates/todo.html',
|
|
53
|
-
|
|
54
|
-
async onMount($container) {
|
|
55
|
-
// ---- State ----
|
|
56
|
-
let todos = [];
|
|
57
|
-
let activeFilter = 'all';
|
|
58
|
-
|
|
59
|
-
// ---- Fetch ----
|
|
60
|
-
async function loadTodos() {
|
|
61
|
-
try {
|
|
62
|
-
const res = await api('/api/plugins/todo/items');
|
|
63
|
-
todos = Array.isArray(res) ? res : (res.data ?? []);
|
|
64
|
-
} catch {
|
|
65
|
-
E.toast('Failed to load todos', { type: 'error' });
|
|
66
|
-
todos = [];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ---- Render table ----
|
|
71
|
-
function render() {
|
|
72
|
-
const wrap = $container.find('#todo-table').get(0);
|
|
73
|
-
if (!wrap) return;
|
|
74
|
-
|
|
75
|
-
const filtered = activeFilter === 'all'
|
|
76
|
-
? todos
|
|
77
|
-
: todos.filter((t) => t.status === activeFilter);
|
|
78
|
-
|
|
79
|
-
T.create(wrap, {
|
|
80
|
-
data: filtered,
|
|
81
|
-
columns: [
|
|
82
|
-
{
|
|
83
|
-
key: 'status',
|
|
84
|
-
title: '',
|
|
85
|
-
sortable: false,
|
|
86
|
-
render: (status, row) => {
|
|
87
|
-
const checked = status === STATUS.COMPLETED ? 'checked' : '';
|
|
88
|
-
return `<input type="checkbox" class="form-check-input todo-checkbox" data-id="${esc(row.id)}" ${checked} style="cursor:pointer;" />`;
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
key: 'text',
|
|
93
|
-
title: 'Task',
|
|
94
|
-
sortable: true,
|
|
95
|
-
render: (text, row) => {
|
|
96
|
-
const isCompleted = row.status === STATUS.COMPLETED;
|
|
97
|
-
const style = isCompleted ? 'text-decoration:line-through;opacity:0.45;' : '';
|
|
98
|
-
let dueBadge = '';
|
|
99
|
-
if (row.dueAt) {
|
|
100
|
-
const due = new Date(row.dueAt);
|
|
101
|
-
const today = new Date();
|
|
102
|
-
today.setHours(0, 0, 0, 0);
|
|
103
|
-
const overdue = !isCompleted && due < today;
|
|
104
|
-
const dueLabel = D(row.dueAt).format('D MMM');
|
|
105
|
-
dueBadge = ` <span class="badge ${overdue ? 'badge-danger' : 'badge-outline'}" `
|
|
106
|
-
+ `style="font-size:0.68rem;padding:1px 6px;" title="Due ${esc(dueLabel)}">`
|
|
107
|
-
+ `${overdue ? '\u26a0 ' : ''}${esc(dueLabel)}</span>`;
|
|
108
|
-
}
|
|
109
|
-
return `<span style="font-size:0.9rem;${style}">${esc(text)}${dueBadge}</span>`;
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
key: 'status',
|
|
114
|
-
title: 'Status',
|
|
115
|
-
sortable: true,
|
|
116
|
-
render: (status, row) => {
|
|
117
|
-
const opts = [
|
|
118
|
-
{v: 'not-started', l: 'Not Started'},
|
|
119
|
-
{v: 'in-progress', l: 'In Progress'},
|
|
120
|
-
{v: 'completed', l: 'Completed'}
|
|
121
|
-
].map(({v, l}) =>
|
|
122
|
-
`<option value="${v}"${status === v ? ' selected' : ''}>${l}</option>`
|
|
123
|
-
).join('');
|
|
124
|
-
return `<select class="todo-select-pill status-select status-pill-${esc(status)}" data-id="${esc(row.id)}">${opts}</select>`;
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
key: 'priority',
|
|
129
|
-
title: 'Priority',
|
|
130
|
-
sortable: true,
|
|
131
|
-
render: (priority, row) => {
|
|
132
|
-
const opts = [
|
|
133
|
-
{v: 'highest', l: 'Highest'},
|
|
134
|
-
{v: 'high', l: 'High'},
|
|
135
|
-
{v: 'medium', l: 'Medium'},
|
|
136
|
-
{v: 'low', l: 'Low'},
|
|
137
|
-
{v: 'lowest', l: 'Lowest'}
|
|
138
|
-
].map(({v, l}) =>
|
|
139
|
-
`<option value="${v}"${priority === v ? ' selected' : ''}>${l}</option>`
|
|
140
|
-
).join('');
|
|
141
|
-
return `<select class="todo-select-pill priority-select priority-pill-${esc(priority)}" data-id="${esc(row.id)}">${opts}</select>`;
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
key: 'id',
|
|
146
|
-
title: 'Actions',
|
|
147
|
-
sortable: false,
|
|
148
|
-
render: (id) =>
|
|
149
|
-
`<div style="display:flex;gap:.4rem;">` +
|
|
150
|
-
`<button class="btn btn-sm btn-outline edit-todo" data-id="${esc(id)}">Edit</button>` +
|
|
151
|
-
`<button class="btn btn-sm btn-danger delete-todo" data-id="${esc(id)}">Delete</button>` +
|
|
152
|
-
`</div>`
|
|
153
|
-
}
|
|
154
|
-
],
|
|
155
|
-
emptyMessage: 'No tasks yet. Add one above to get started.'
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
wrap.querySelectorAll('.edit-todo').forEach((btn) => {
|
|
159
|
-
btn.addEventListener('click', () => editTodo(btn.dataset.id));
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
wrap.querySelectorAll('.delete-todo').forEach((btn) => {
|
|
163
|
-
btn.addEventListener('click', () => deleteTodo(btn.dataset.id));
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
wrap.querySelectorAll('.todo-checkbox').forEach((cb) => {
|
|
167
|
-
cb.addEventListener('change', () => toggleTodo(cb.dataset.id));
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
wrap.querySelectorAll('.status-select').forEach((sel) => {
|
|
171
|
-
sel.addEventListener('change', () => {
|
|
172
|
-
sel.className = sel.className.replace(/status-pill-\S+/g, `status-pill-${sel.value}`);
|
|
173
|
-
updateStatus(sel.dataset.id, sel.value);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
wrap.querySelectorAll('.priority-select').forEach((sel) => {
|
|
178
|
-
sel.addEventListener('change', () => {
|
|
179
|
-
sel.className = sel.className.replace(/priority-pill-\S+/g, `priority-pill-${sel.value}`);
|
|
180
|
-
updatePriority(sel.dataset.id, sel.value);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
Domma.icons.scan(wrap);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ---- Add todo ----
|
|
188
|
-
async function addTodo() {
|
|
189
|
-
const $input = $container.find('#todo-input');
|
|
190
|
-
const $priority = $container.find('#priority-input');
|
|
191
|
-
const $due = $container.find('#due-date-input');
|
|
192
|
-
const text = $input.get(0).value.trim();
|
|
193
|
-
const priority = $priority.get(0).value;
|
|
194
|
-
const dueAt = $due.get(0)?.value || null;
|
|
195
|
-
|
|
196
|
-
if (!text) {
|
|
197
|
-
E.toast('Please enter a task', { type: 'warning' });
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
const item = await api('/api/plugins/todo/items', 'POST', {
|
|
203
|
-
text,
|
|
204
|
-
priority,
|
|
205
|
-
status: STATUS.NOT_STARTED,
|
|
206
|
-
dueAt
|
|
207
|
-
});
|
|
208
|
-
todos.push(item);
|
|
209
|
-
$input.get(0).value = '';
|
|
210
|
-
$priority.get(0).value = PRIORITY.MEDIUM;
|
|
211
|
-
if ($due.get(0)) $due.get(0).value = '';
|
|
212
|
-
render();
|
|
213
|
-
E.toast('Task added', { type: 'success' });
|
|
214
|
-
} catch {
|
|
215
|
-
E.toast('Failed to add task', { type: 'error' });
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---- Toggle (checkbox) ----
|
|
220
|
-
async function toggleTodo(id) {
|
|
221
|
-
const todo = todos.find((t) => t.id === id);
|
|
222
|
-
if (!todo) return;
|
|
223
|
-
|
|
224
|
-
const newStatus = todo.status === STATUS.COMPLETED ? STATUS.NOT_STARTED : STATUS.COMPLETED;
|
|
225
|
-
try {
|
|
226
|
-
const updated = await api(`/api/plugins/todo/items/${id}`, 'PUT', {status: newStatus});
|
|
227
|
-
Object.assign(todo, updated);
|
|
228
|
-
render();
|
|
229
|
-
E.toast(newStatus === STATUS.COMPLETED ? 'Task completed' : 'Task reopened', { type: 'success' });
|
|
230
|
-
} catch {
|
|
231
|
-
E.toast('Failed to update task', { type: 'error' });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// ---- Edit todo text ----
|
|
236
|
-
async function editTodo(id) {
|
|
237
|
-
const todo = todos.find((t) => t.id === id);
|
|
238
|
-
if (!todo) return;
|
|
239
|
-
|
|
240
|
-
const newText = await E.prompt('Edit your task:', { inputValue: todo.text });
|
|
241
|
-
if (!newText || !newText.trim()) return;
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
const updated = await api(`/api/plugins/todo/items/${id}`, 'PUT', {text: newText.trim()});
|
|
245
|
-
Object.assign(todo, updated);
|
|
246
|
-
render();
|
|
247
|
-
E.toast('Task updated', { type: 'success' });
|
|
248
|
-
} catch {
|
|
249
|
-
E.toast('Failed to update task', { type: 'error' });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ---- Delete single ----
|
|
254
|
-
async function deleteTodo(id) {
|
|
255
|
-
const confirmed = await E.confirm('Delete this task?');
|
|
256
|
-
if (!confirmed) return;
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
await api(`/api/plugins/todo/items/${id}`, 'DELETE');
|
|
260
|
-
todos = todos.filter((t) => t.id !== id);
|
|
261
|
-
render();
|
|
262
|
-
E.toast('Task deleted', { type: 'success' });
|
|
263
|
-
} catch {
|
|
264
|
-
E.toast('Failed to delete task', { type: 'error' });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ---- Update status ----
|
|
269
|
-
async function updateStatus(id, status) {
|
|
270
|
-
const todo = todos.find((t) => t.id === id);
|
|
271
|
-
if (!todo) return;
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const updated = await api(`/api/plugins/todo/items/${id}`, 'PUT', {status});
|
|
275
|
-
Object.assign(todo, updated);
|
|
276
|
-
render();
|
|
277
|
-
E.toast(`Status: ${formatStatus(status)}`, { type: 'success' });
|
|
278
|
-
} catch {
|
|
279
|
-
E.toast('Failed to update status', { type: 'error' });
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ---- Update priority ----
|
|
284
|
-
async function updatePriority(id, priority) {
|
|
285
|
-
const todo = todos.find((t) => t.id === id);
|
|
286
|
-
if (!todo) return;
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
const updated = await api(`/api/plugins/todo/items/${id}`, 'PUT', {priority});
|
|
290
|
-
Object.assign(todo, updated);
|
|
291
|
-
render();
|
|
292
|
-
E.toast(`Priority: ${formatPriority(priority)}`, { type: 'success' });
|
|
293
|
-
} catch {
|
|
294
|
-
E.toast('Failed to update priority', { type: 'error' });
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ---- Clear completed ----
|
|
299
|
-
async function clearCompleted() {
|
|
300
|
-
const hasCompleted = todos.some((t) => t.status === STATUS.COMPLETED);
|
|
301
|
-
if (!hasCompleted) {
|
|
302
|
-
E.toast('No completed tasks to clear', { type: 'warning' });
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const confirmed = await E.confirm('Clear all completed tasks?');
|
|
307
|
-
if (!confirmed) return;
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
await api('/api/plugins/todo/completed', 'DELETE');
|
|
311
|
-
todos = todos.filter((t) => t.status !== STATUS.COMPLETED);
|
|
312
|
-
render();
|
|
313
|
-
E.toast('Completed tasks cleared', { type: 'success' });
|
|
314
|
-
} catch {
|
|
315
|
-
E.toast('Failed to clear completed tasks', { type: 'error' });
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ---- Event bindings ----
|
|
320
|
-
|
|
321
|
-
$container.find('#add-todo-btn').get(0).addEventListener('click', addTodo);
|
|
322
|
-
|
|
323
|
-
$container.find('#todo-input').get(0).addEventListener('keydown', (e) => {
|
|
324
|
-
if (e.key === 'Enter') addTodo();
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
$container.find('#clear-completed-btn').get(0).addEventListener('click', clearCompleted);
|
|
328
|
-
|
|
329
|
-
$container.find('#filter-bar').get(0).querySelectorAll('.filter-btn').forEach((btn) => {
|
|
330
|
-
btn.addEventListener('click', () => {
|
|
331
|
-
activeFilter = btn.dataset.filter;
|
|
332
|
-
$container.find('#filter-bar').get(0).querySelectorAll('.filter-btn').forEach((b) => b.classList.remove('active'));
|
|
333
|
-
btn.classList.add('active');
|
|
334
|
-
render();
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// ---- Initial load ----
|
|
339
|
-
await loadTodos();
|
|
340
|
-
render();
|
|
341
|
-
Domma.icons.scan($container.get(0));
|
|
342
|
-
}
|
|
343
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"slug": "todo-items",
|
|
3
|
-
"title": "Todos",
|
|
4
|
-
"description": "Todo items managed by the Todo plugin.",
|
|
5
|
-
"plugin": "todo",
|
|
6
|
-
"fields": [
|
|
7
|
-
{
|
|
8
|
-
"name": "text",
|
|
9
|
-
"label": "Task",
|
|
10
|
-
"type": "text",
|
|
11
|
-
"required": true
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"name": "status",
|
|
15
|
-
"label": "Status",
|
|
16
|
-
"type": "text",
|
|
17
|
-
"required": false
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"name": "priority",
|
|
21
|
-
"label": "Priority",
|
|
22
|
-
"type": "text",
|
|
23
|
-
"required": false
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"name": "dueAt",
|
|
27
|
-
"label": "Due Date",
|
|
28
|
-
"type": "text",
|
|
29
|
-
"required": false
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"name": "userId",
|
|
33
|
-
"label": "User ID",
|
|
34
|
-
"type": "text",
|
|
35
|
-
"required": false
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
"api": {
|
|
39
|
-
"create": {
|
|
40
|
-
"enabled": true,
|
|
41
|
-
"access": "admin"
|
|
42
|
-
},
|
|
43
|
-
"read": {
|
|
44
|
-
"enabled": true,
|
|
45
|
-
"access": "admin"
|
|
46
|
-
},
|
|
47
|
-
"update": {
|
|
48
|
-
"enabled": false,
|
|
49
|
-
"access": "admin"
|
|
50
|
-
},
|
|
51
|
-
"delete": {
|
|
52
|
-
"enabled": false,
|
|
53
|
-
"access": "admin"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"storage": {
|
|
57
|
-
"adapter": "mongodb",
|
|
58
|
-
"connection": "default"
|
|
59
|
-
}
|
|
60
|
-
}
|
package/plugins/todo/config.js
DELETED
package/plugins/todo/plugin.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import defaultConfig from './config.js';
|
|
2
|
-
import {createEntry, deleteEntry, getEntry, listEntries, updateEntry} from '../../server/services/collections.js';
|
|
3
|
-
|
|
4
|
-
const SLUG = 'todo-items';
|
|
5
|
-
|
|
6
|
-
/** Flatten a collection entry into the shape the admin view expects. */
|
|
7
|
-
function toTodo(entry) {
|
|
8
|
-
return {
|
|
9
|
-
id: entry.id,
|
|
10
|
-
...entry.data,
|
|
11
|
-
createdAt: entry.meta.createdAt,
|
|
12
|
-
updatedAt: entry.meta.updatedAt
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default async function todoPlugin(fastify, options) {
|
|
17
|
-
const { authenticate } = options.auth;
|
|
18
|
-
const config = {...defaultConfig, ...(options.settings || {})};
|
|
19
|
-
const scope = config.scope ?? 'user';
|
|
20
|
-
|
|
21
|
-
function userId(request) {
|
|
22
|
-
return scope === 'user' ? (request.user?.id ?? request.user?.sub ?? null) : null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function loadAll(uid) {
|
|
26
|
-
const {entries} = await listEntries(SLUG, {limit: 10000, sort: 'createdAt', order: 'asc'});
|
|
27
|
-
let items = entries.map(toTodo);
|
|
28
|
-
if (uid) items = items.filter(t => t.userId === uid);
|
|
29
|
-
return items;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** GET /api/plugins/todo/items */
|
|
33
|
-
fastify.get('/items', { preHandler: [authenticate] }, async (request, reply) => {
|
|
34
|
-
const uid = userId(request);
|
|
35
|
-
const { status } = request.query;
|
|
36
|
-
let items = await loadAll(uid);
|
|
37
|
-
if (status) items = items.filter(t => t.status === status);
|
|
38
|
-
return reply.send(items);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/** POST /api/plugins/todo/items */
|
|
42
|
-
fastify.post('/items', { preHandler: [authenticate] }, async (request, reply) => {
|
|
43
|
-
const uid = userId(request);
|
|
44
|
-
const { text, priority, status } = request.body ?? {};
|
|
45
|
-
|
|
46
|
-
if (!text || typeof text !== 'string' || !text.trim()) {
|
|
47
|
-
return reply.code(400).send({ error: 'text is required' });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const {dueAt} = request.body ?? {};
|
|
51
|
-
const entry = await createEntry(SLUG, {
|
|
52
|
-
text: text.trim(),
|
|
53
|
-
status: status ?? 'not-started',
|
|
54
|
-
priority: priority ?? config.defaultPriority ?? 'medium',
|
|
55
|
-
dueAt: dueAt ?? null,
|
|
56
|
-
userId: uid ?? null
|
|
57
|
-
}, {createdBy: uid, source: 'admin'});
|
|
58
|
-
|
|
59
|
-
return reply.code(201).send(toTodo(entry));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
/** PUT /api/plugins/todo/items/:id */
|
|
63
|
-
fastify.put('/items/:id', { preHandler: [authenticate] }, async (request, reply) => {
|
|
64
|
-
const uid = userId(request);
|
|
65
|
-
const { id } = request.params;
|
|
66
|
-
const patch = request.body ?? {};
|
|
67
|
-
|
|
68
|
-
const entry = await getEntry(SLUG, id);
|
|
69
|
-
if (!entry) return reply.code(404).send({error: 'Todo not found'});
|
|
70
|
-
if (uid && entry.data.userId !== uid) return reply.code(404).send({error: 'Todo not found'});
|
|
71
|
-
|
|
72
|
-
const merged = {...entry.data};
|
|
73
|
-
for (const key of ['text', 'status', 'priority', 'dueAt']) {
|
|
74
|
-
if (key in patch) merged[key] = key === 'text' ? String(patch[key]).trim() : patch[key];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const updated = await updateEntry(SLUG, id, merged);
|
|
78
|
-
return reply.send(toTodo(updated));
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
/** DELETE /api/plugins/todo/completed - must be registered before /:id */
|
|
82
|
-
fastify.delete('/completed', {preHandler: [authenticate]}, async (request, reply) => {
|
|
83
|
-
const uid = userId(request);
|
|
84
|
-
const all = await loadAll(uid);
|
|
85
|
-
const completed = all.filter(t => t.status === 'completed');
|
|
86
|
-
await Promise.all(completed.map(t => deleteEntry(SLUG, t.id)));
|
|
87
|
-
return reply.send({success: true, removed: completed.length});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
/** DELETE /api/plugins/todo/items/:id */
|
|
91
|
-
fastify.delete('/items/:id', { preHandler: [authenticate] }, async (request, reply) => {
|
|
92
|
-
const uid = userId(request);
|
|
93
|
-
const { id } = request.params;
|
|
94
|
-
|
|
95
|
-
const entry = await getEntry(SLUG, id);
|
|
96
|
-
if (!entry) return reply.code(404).send({error: 'Todo not found'});
|
|
97
|
-
if (uid && entry.data.userId !== uid) return reply.code(404).send({error: 'Todo not found'});
|
|
98
|
-
|
|
99
|
-
await deleteEntry(SLUG, id);
|
|
100
|
-
return reply.send({ success: true });
|
|
101
|
-
});
|
|
102
|
-
}
|
package/plugins/todo/plugin.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "todo",
|
|
3
|
-
"displayName": "Todo",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Personal task manager with priorities and status tracking.",
|
|
6
|
-
"author": "Darryl Waterhouse",
|
|
7
|
-
"date": "2026-03-24",
|
|
8
|
-
"icon": "check-square",
|
|
9
|
-
"admin": {
|
|
10
|
-
"sidebar": [
|
|
11
|
-
{
|
|
12
|
-
"id": "todo",
|
|
13
|
-
"text": "Todo",
|
|
14
|
-
"icon": "check-square",
|
|
15
|
-
"url": "#/plugins/todo",
|
|
16
|
-
"section": "#/plugins/todo"
|
|
17
|
-
}
|
|
18
|
-
],
|
|
19
|
-
"routes": [
|
|
20
|
-
{
|
|
21
|
-
"path": "/plugins/todo",
|
|
22
|
-
"view": "plugin-todo",
|
|
23
|
-
"title": "Todo - Domma CMS"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"views": {
|
|
27
|
-
"plugin-todo": {
|
|
28
|
-
"entry": "todo/admin/views/todo.js?v=5ab5af5f",
|
|
29
|
-
"exportName": "todoView"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
File without changes
|