domma-cms 0.55.1 → 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 +194 -3
- package/README.md +18 -14
- package/admin/css/admin.css +1 -1
- package/admin/css/contacts.css +1 -0
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- 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/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/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/context-menu-editor.html +212 -0
- package/admin/js/templates/context-menus.html +16 -0
- package/admin/js/templates/notes.html +137 -0
- package/admin/js/templates/plugin-code.html +1 -1
- package/admin/js/templates/plugin-marketplace.html +17 -4
- package/admin/js/templates/plugins.html +13 -7
- 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 +211 -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/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/notes.js +104 -0
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +28 -24
- 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/admin/js/views/todo.js +96 -0
- package/bin/cli.js +6 -10
- package/bin/lib/plugin-version.js +28 -0
- package/bin/update.js +27 -2
- package/config/menus/admin-sidebar.json +129 -23
- package/config/plugins.json +5 -5
- package/config/search.json +13 -0
- package/config/server.json +3 -1
- package/package.json +14 -10
- 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 +4473 -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 +1022 -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/blog/CLAUDE.md +237 -0
- package/plugins/blog/admin/css/index.css +178 -0
- package/plugins/blog/admin/templates/blog.html +35 -41
- 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 +63 -40
- package/plugins/blog/admin/templates/settings.html +3 -9
- package/plugins/blog/admin/views/blog.js +288 -159
- package/plugins/blog/admin/views/categories.js +212 -206
- 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 +306 -39
- package/plugins/blog/admin/views/settings-cog.js +56 -0
- package/plugins/blog/admin/views/settings.js +16 -84
- 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/categories/schema.json +7 -6
- package/plugins/blog/collections/comments/schema.json +11 -10
- package/plugins/blog/collections/posts/schema.json +16 -13
- 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 +259 -26
- package/plugins/blog/plugin.json +21 -73
- package/plugins/blog/plugin.public.js +217 -0
- 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/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 +34 -0
- package/public/css/forms.css +1 -1
- package/public/css/search.css +1 -0
- package/public/css/site.css +1 -1
- package/public/css/theme-switcher.css +1 -0
- package/public/js/analytics.js +1 -0
- 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/public/js/theme-switcher.js +1 -0
- package/scripts/build.js +77 -4
- package/scripts/copy-domma.js +48 -0
- package/scripts/run-tests.mjs +92 -0
- package/scripts/seed.js +1996 -0
- package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
- package/server/routes/api/collections.js +34 -0
- package/server/routes/api/contacts.js +506 -0
- package/server/routes/api/context-menus.js +104 -0
- package/server/routes/api/dashboard.js +8 -2
- package/server/routes/api/forms.js +42 -3
- package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
- package/server/routes/api/notifications.js +69 -19
- package/server/routes/api/plugin-marketplace.js +78 -23
- package/server/routes/api/plugins.js +198 -9
- package/server/routes/api/search.js +43 -0
- package/server/routes/api/theme.js +69 -0
- package/server/routes/api/todo.js +178 -0
- package/server/routes/public.js +42 -7
- package/server/server.js +135 -1
- package/server/services/adapters/FileAdapter.js +6 -1
- package/server/services/collections.js +13 -3
- package/server/services/content.js +26 -0
- package/server/services/contextMenus.js +477 -0
- package/server/services/managerClient.js +72 -136
- package/server/services/markdown.js +124 -10
- package/server/services/permissionRegistry.js +74 -0
- package/server/services/pluginEntitlement.js +171 -0
- package/server/services/pluginEnvelope.js +242 -0
- package/server/services/pluginFiles.js +52 -11
- package/server/services/pluginInstaller.js +246 -26
- package/server/services/pluginScaffold.js +5 -0
- package/server/services/plugins.js +340 -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 +224 -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/sidebarBadges.js +37 -0
- package/server/services/siteGitignore.js +307 -0
- package/server/services/themeSettings.js +580 -0
- package/server/services/users.js +8 -0
- package/server/templates/page.html +4 -2
- package/plugins/analytics/admin/templates/analytics.html +0 -61
- package/plugins/analytics/admin/views/analytics.js +0 -176
- package/plugins/analytics/config.js +0 -14
- package/plugins/analytics/plugin.json +0 -57
- 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 -126
- package/plugins/contacts/admin/views/contacts.js +0 -710
- package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
- package/plugins/contacts/collections/user-contacts/schema.json +0 -71
- package/plugins/contacts/config.js +0 -6
- package/plugins/contacts/data/contacts.json +0 -20
- package/plugins/contacts/plugin.js +0 -291
- package/plugins/contacts/plugin.json +0 -33
- 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/templates/editor.html +0 -129
- package/plugins/invoice/admin/templates/index.html +0 -43
- package/plugins/invoice/admin/templates/issuers.html +0 -5
- package/plugins/invoice/admin/templates/receivers.html +0 -5
- package/plugins/invoice/admin/views/editor.js +0 -267
- package/plugins/invoice/admin/views/index.js +0 -155
- package/plugins/invoice/admin/views/issuers.js +0 -23
- package/plugins/invoice/admin/views/party-view.js +0 -148
- package/plugins/invoice/admin/views/receivers.js +0 -22
- package/plugins/invoice/collections/invoice-issuers/schema.json +0 -16
- package/plugins/invoice/collections/invoice-receivers/schema.json +0 -15
- package/plugins/invoice/collections/invoices/schema.json +0 -27
- package/plugins/invoice/config.js +0 -16
- package/plugins/invoice/plugin.js +0 -283
- package/plugins/invoice/plugin.json +0 -85
- package/plugins/invoice/templates/invoice-print.html +0 -213
- 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/data/notes.json +0 -1
- package/plugins/notes/plugin.json +0 -33
- 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/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/plugin.js +0 -174
- package/plugins/surveys/plugin.json +0 -65
- 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/data/todos.json +0 -1
- package/plugins/todo/plugin.js +0 -102
- package/plugins/todo/plugin.json +0 -33
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analytics Plugin - Admin View
|
|
3
|
-
* KPI cards, trend chart (Chart.js via CDN), filterable top-pages table, CSV export.
|
|
4
|
-
*/
|
|
5
|
-
const CHART_CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js';
|
|
6
|
-
|
|
7
|
-
let chartInstance = null;
|
|
8
|
-
let currentRange = '7d';
|
|
9
|
-
let currentTop = [];
|
|
10
|
-
let filterTerm = '';
|
|
11
|
-
|
|
12
|
-
export const analyticsView = {
|
|
13
|
-
templateUrl: '/plugins/analytics/admin/templates/analytics.html',
|
|
14
|
-
|
|
15
|
-
async onMount($container) {
|
|
16
|
-
await ensureChartJs();
|
|
17
|
-
await refresh($container, currentRange);
|
|
18
|
-
wireRangeTabs($container);
|
|
19
|
-
wireExport($container);
|
|
20
|
-
wireReset($container);
|
|
21
|
-
wireFilter($container);
|
|
22
|
-
Domma.icons.scan();
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function authHeaders() {
|
|
27
|
-
return { 'Authorization': 'Bearer ' + (S.get('auth_token') || '') };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function ensureChartJs() {
|
|
31
|
-
if (window.Chart) return Promise.resolve();
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const s = document.createElement('script');
|
|
34
|
-
s.src = CHART_CDN;
|
|
35
|
-
s.async = true;
|
|
36
|
-
s.onload = () => resolve();
|
|
37
|
-
s.onerror = () => reject(new Error('chart.js load failed'));
|
|
38
|
-
document.head.appendChild(s);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function refresh($container, range) {
|
|
43
|
-
currentRange = range;
|
|
44
|
-
try {
|
|
45
|
-
const [stats, series] = await Promise.all([
|
|
46
|
-
fetch('/api/plugins/analytics/stats?range=' + encodeURIComponent(range), { headers: authHeaders() })
|
|
47
|
-
.then((r) => r.json()),
|
|
48
|
-
fetch('/api/plugins/analytics/series?range=' + encodeURIComponent(range === 'all' ? '30d' : range), { headers: authHeaders() })
|
|
49
|
-
.then((r) => r.json())
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
$container.find('#kpi-range').text(stats.total.toLocaleString());
|
|
53
|
-
$container.find('#kpi-pages').text(stats.uniquePages.toLocaleString());
|
|
54
|
-
$container.find('#kpi-lifetime').text(stats.lifetimeTotal.toLocaleString());
|
|
55
|
-
|
|
56
|
-
currentTop = stats.top || [];
|
|
57
|
-
renderTable($container);
|
|
58
|
-
renderChart($container, series.series || []);
|
|
59
|
-
updateExportLink($container);
|
|
60
|
-
} catch (err) {
|
|
61
|
-
E.toast('Could not load analytics data.', { type: 'error' });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function renderTable($container) {
|
|
66
|
-
const filtered = filterTerm
|
|
67
|
-
? currentTop.filter((row) => row.url.toLowerCase().includes(filterTerm))
|
|
68
|
-
: currentTop;
|
|
69
|
-
|
|
70
|
-
T.create('#analytics-table', {
|
|
71
|
-
data: filtered,
|
|
72
|
-
columns: [
|
|
73
|
-
{ key: 'url', title: 'Page URL', render: (val) => `<code>${val}</code>` },
|
|
74
|
-
{ key: 'hits', title: 'Page views' }
|
|
75
|
-
],
|
|
76
|
-
emptyMessage: filterTerm ? 'No pages match that filter.' : 'No page views recorded yet.'
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function renderChart($container, series) {
|
|
81
|
-
const canvas = $container.find('#trend-chart').get(0);
|
|
82
|
-
if (!canvas || !window.Chart) return;
|
|
83
|
-
if (chartInstance) {
|
|
84
|
-
chartInstance.destroy();
|
|
85
|
-
chartInstance = null;
|
|
86
|
-
}
|
|
87
|
-
chartInstance = new window.Chart(canvas, {
|
|
88
|
-
type: 'line',
|
|
89
|
-
data: {
|
|
90
|
-
labels: series.map((p) => p.date),
|
|
91
|
-
datasets: [{
|
|
92
|
-
label: 'Hits',
|
|
93
|
-
data: series.map((p) => p.hits),
|
|
94
|
-
borderColor: 'rgba(99, 102, 241, 1)',
|
|
95
|
-
backgroundColor: 'rgba(99, 102, 241, 0.15)',
|
|
96
|
-
fill: true,
|
|
97
|
-
tension: 0.3,
|
|
98
|
-
borderWidth: 2,
|
|
99
|
-
pointRadius: 2
|
|
100
|
-
}]
|
|
101
|
-
},
|
|
102
|
-
options: {
|
|
103
|
-
responsive: true,
|
|
104
|
-
maintainAspectRatio: false,
|
|
105
|
-
plugins: { legend: { display: false } },
|
|
106
|
-
scales: {
|
|
107
|
-
y: { beginAtZero: true, ticks: { precision: 0 } },
|
|
108
|
-
x: { ticks: { maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } }
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function wireRangeTabs($container) {
|
|
115
|
-
$container.find('.range-btn').on('click', function () {
|
|
116
|
-
const btn = $(this);
|
|
117
|
-
$container.find('.range-btn').removeClass('active');
|
|
118
|
-
btn.addClass('active');
|
|
119
|
-
refresh($container, btn.attr('data-range'));
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function wireExport($container) {
|
|
124
|
-
$container.find('#export-btn').on('click', async () => {
|
|
125
|
-
try {
|
|
126
|
-
const res = await fetch('/api/plugins/analytics/export.csv?range=' + encodeURIComponent(currentRange), {
|
|
127
|
-
headers: authHeaders()
|
|
128
|
-
});
|
|
129
|
-
if (!res.ok) throw new Error('export failed');
|
|
130
|
-
const blob = await res.blob();
|
|
131
|
-
const cd = res.headers.get('Content-Disposition') || '';
|
|
132
|
-
const match = cd.match(/filename="?([^"]+)"?/);
|
|
133
|
-
const filename = match ? match[1] : 'analytics.csv';
|
|
134
|
-
const url = URL.createObjectURL(blob);
|
|
135
|
-
const a = document.createElement('a');
|
|
136
|
-
a.href = url;
|
|
137
|
-
a.download = filename;
|
|
138
|
-
document.body.appendChild(a);
|
|
139
|
-
a.click();
|
|
140
|
-
a.remove();
|
|
141
|
-
URL.revokeObjectURL(url);
|
|
142
|
-
} catch {
|
|
143
|
-
E.toast('Export failed.', { type: 'error' });
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function updateExportLink() {
|
|
149
|
-
// no-op; export button uses fetch+blob to preserve Bearer auth
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function wireReset($container) {
|
|
153
|
-
$container.find('#reset-btn').on('click', async () => {
|
|
154
|
-
const confirmed = await E.confirm('Reset all analytics data? This cannot be undone.');
|
|
155
|
-
if (!confirmed) return;
|
|
156
|
-
try {
|
|
157
|
-
const res = await fetch('/api/plugins/analytics/stats', {
|
|
158
|
-
method: 'DELETE',
|
|
159
|
-
headers: authHeaders()
|
|
160
|
-
});
|
|
161
|
-
if (!res.ok) throw new Error('reset failed');
|
|
162
|
-
E.toast('Analytics reset.', { type: 'success' });
|
|
163
|
-
await refresh($container, currentRange);
|
|
164
|
-
} catch {
|
|
165
|
-
E.toast('Reset failed.', { type: 'error' });
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function wireFilter($container) {
|
|
171
|
-
const input = $container.find('#filter-input');
|
|
172
|
-
input.on('input', () => {
|
|
173
|
-
filterTerm = (input.val() || '').toString().toLowerCase().trim();
|
|
174
|
-
renderTable($container);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analytics Plugin - Default Configuration
|
|
3
|
-
*
|
|
4
|
-
* excludeAdmin strip /admin pageviews before recording
|
|
5
|
-
* excludePaths array of path prefixes to ignore (e.g. ['/api', '/healthz'])
|
|
6
|
-
* honourDnt respect navigator.doNotTrack on the client
|
|
7
|
-
* sessionDedup only record one hit per URL per browser session
|
|
8
|
-
*/
|
|
9
|
-
export default {
|
|
10
|
-
excludeAdmin: true,
|
|
11
|
-
excludePaths: ['/api'],
|
|
12
|
-
honourDnt: true,
|
|
13
|
-
sessionDedup: true
|
|
14
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "analytics",
|
|
3
|
-
"displayName": "Analytics",
|
|
4
|
-
"version": "2.1.0",
|
|
5
|
-
"description": "Page view analytics with daily trends, time-range filtering, top-pages table and CSV export. Honours Do-Not-Track and dedups per session.",
|
|
6
|
-
"author": "Darryl Waterhouse",
|
|
7
|
-
"date": "2026-05-04",
|
|
8
|
-
"icon": "chart-bar",
|
|
9
|
-
"admin": {
|
|
10
|
-
"sidebar": [
|
|
11
|
-
{
|
|
12
|
-
"id": "analytics",
|
|
13
|
-
"text": "Analytics",
|
|
14
|
-
"icon": "chart-bar",
|
|
15
|
-
"url": "#/plugins/analytics",
|
|
16
|
-
"section": "#/plugins/analytics"
|
|
17
|
-
}
|
|
18
|
-
],
|
|
19
|
-
"routes": [
|
|
20
|
-
{
|
|
21
|
-
"path": "/plugins/analytics",
|
|
22
|
-
"view": "plugin-analytics",
|
|
23
|
-
"title": "Analytics - Domma CMS"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"views": {
|
|
27
|
-
"plugin-analytics": {
|
|
28
|
-
"entry": "analytics/admin/views/analytics.js?v=28fcef99",
|
|
29
|
-
"exportName": "analyticsView"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"inject": {
|
|
34
|
-
"head": "public/inject-head.html",
|
|
35
|
-
"bodyEnd": "public/inject-body.html"
|
|
36
|
-
},
|
|
37
|
-
"scaffold": {
|
|
38
|
-
"reset": [
|
|
39
|
-
{
|
|
40
|
-
"path": "data/lifetime.json",
|
|
41
|
-
"content": "{}"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"path": "data/daily.json",
|
|
45
|
-
"content": "{}"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"path": "data/journeys.json",
|
|
49
|
-
"content": "{}"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"path": "data/stats.json",
|
|
53
|
-
"content": "{}"
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
<!-- analytics: page view tracker (DNT-aware, session-deduped) -->
|
|
2
|
-
<script>
|
|
3
|
-
(function () {
|
|
4
|
-
if (typeof fetch !== 'function') return;
|
|
5
|
-
|
|
6
|
-
// Preview renders are not visits. The editor previews a page into an
|
|
7
|
-
// iframe where location.pathname is the srcdoc document rather than any
|
|
8
|
-
// real URL, so tracking one both invents a page and inflates the counts.
|
|
9
|
-
if (window.__CMS_PREVIEW__) return;
|
|
10
|
-
|
|
11
|
-
var dnt = navigator.doNotTrack === '1'
|
|
12
|
-
|| window.doNotTrack === '1'
|
|
13
|
-
|| navigator.msDoNotTrack === '1';
|
|
14
|
-
if (dnt) return;
|
|
15
|
-
|
|
16
|
-
var url = window.location.pathname;
|
|
17
|
-
|
|
18
|
-
// Session-scoped anonymous ID - clears with the tab. Cookie-less.
|
|
19
|
-
var sid = '';
|
|
20
|
-
try {
|
|
21
|
-
sid = sessionStorage.getItem('dm_sid') || '';
|
|
22
|
-
if (!sid) {
|
|
23
|
-
sid = (crypto && crypto.randomUUID)
|
|
24
|
-
? crypto.randomUUID()
|
|
25
|
-
: (Date.now().toString(36) + Math.random().toString(36).slice(2, 10));
|
|
26
|
-
sessionStorage.setItem('dm_sid', sid);
|
|
27
|
-
}
|
|
28
|
-
} catch (e) {
|
|
29
|
-
// sessionStorage unavailable - proceed without sid
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Session dedup (one hit per URL per session)
|
|
33
|
-
try {
|
|
34
|
-
var key = 'dm_analytics_seen';
|
|
35
|
-
var seen = sessionStorage.getItem(key);
|
|
36
|
-
var list = seen ? seen.split('|') : [];
|
|
37
|
-
if (list.indexOf(url) !== -1) return;
|
|
38
|
-
list.push(url);
|
|
39
|
-
sessionStorage.setItem(key, list.join('|'));
|
|
40
|
-
} catch (e) {
|
|
41
|
-
// proceed anyway
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Strip query/fragment from referrer; only keep same-origin or external origin+path
|
|
45
|
-
var ref = '';
|
|
46
|
-
try {
|
|
47
|
-
if (document.referrer) {
|
|
48
|
-
var r = new URL(document.referrer);
|
|
49
|
-
ref = r.origin === window.location.origin ? r.pathname : (r.origin + r.pathname);
|
|
50
|
-
}
|
|
51
|
-
} catch (e) { /* ignore */ }
|
|
52
|
-
|
|
53
|
-
fetch('/api/plugins/analytics/hit', {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: { 'Content-Type': 'application/json' },
|
|
56
|
-
keepalive: true,
|
|
57
|
-
body: JSON.stringify({ url: url, sid: sid, ref: ref })
|
|
58
|
-
}).catch(function () { /* silent */ });
|
|
59
|
-
})();
|
|
60
|
-
</script>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!-- analytics: head injection (empty - tracking is done via body script) -->
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
<div class="contacts-plugin">
|
|
2
|
-
|
|
3
|
-
<!-- Toolbar -->
|
|
4
|
-
<div class="toolbar mb-3" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
|
|
5
|
-
<input id="contacts-search" type="text" class="form-input" placeholder="Search contacts…" style="width:220px;">
|
|
6
|
-
<select id="group-filter" class="form-input" style="width:160px;">
|
|
7
|
-
<option value="">All groups</option>
|
|
8
|
-
</select>
|
|
9
|
-
<button id="fav-toggle" class="btn btn-outline" title="Show favourites only">
|
|
10
|
-
<span data-icon="star"></span>
|
|
11
|
-
Favourites
|
|
12
|
-
</button>
|
|
13
|
-
<span style="flex:1;"></span>
|
|
14
|
-
<button id="export-btn" class="btn btn-outline">
|
|
15
|
-
<span data-icon="download"></span>
|
|
16
|
-
Export
|
|
17
|
-
</button>
|
|
18
|
-
<button id="import-btn" class="btn btn-outline">
|
|
19
|
-
<span data-icon="upload"></span>
|
|
20
|
-
Import
|
|
21
|
-
</button>
|
|
22
|
-
<input id="import-file" type="file" accept=".json" style="display:none;">
|
|
23
|
-
<button id="add-contact-btn" class="btn btn-primary">
|
|
24
|
-
<span data-icon="plus"></span>
|
|
25
|
-
Add Contact
|
|
26
|
-
</button>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<!-- Stats bar -->
|
|
30
|
-
<div id="contacts-stats" class="mb-3" style="display:flex;gap:1.5rem;font-size:0.875rem;color:var(--dm-text-muted);">
|
|
31
|
-
<span>Total: <strong id="stat-total">0</strong></span>
|
|
32
|
-
<span>Groups: <strong id="stat-groups">0</strong></span>
|
|
33
|
-
<span>Favourites: <strong id="stat-favs">0</strong></span>
|
|
34
|
-
<span id="bulk-actions" style="display:none;margin-left:auto;">
|
|
35
|
-
<button id="bulk-delete-btn" class="btn btn-sm btn-danger">
|
|
36
|
-
<span data-icon="trash"></span>
|
|
37
|
-
Delete selected (<span id="selected-count">0</span>)
|
|
38
|
-
</button>
|
|
39
|
-
</span>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<!-- Groups panel -->
|
|
43
|
-
<div id="groups-panel" class="card mb-3">
|
|
44
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between;cursor:pointer;" id="groups-panel-toggle">
|
|
45
|
-
<span><span data-icon="folder"></span> <strong>Manage Groups</strong></span>
|
|
46
|
-
<span data-icon="chevron-down" id="groups-chevron"></span>
|
|
47
|
-
</div>
|
|
48
|
-
<div id="groups-panel-body" class="card-body" style="display:none;">
|
|
49
|
-
<div style="display:flex;gap:0.5rem;margin-bottom:1rem;">
|
|
50
|
-
<input id="new-group-input" type="text" class="form-input" placeholder="New group name…"
|
|
51
|
-
style="flex:1;">
|
|
52
|
-
<button id="add-group-btn" class="btn btn-primary">
|
|
53
|
-
<span data-icon="plus"></span>
|
|
54
|
-
Add Group
|
|
55
|
-
</button>
|
|
56
|
-
</div>
|
|
57
|
-
<div id="groups-list">
|
|
58
|
-
<p class="text-muted">No groups yet. Add one above.</p>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<!-- Contact editor overlay -->
|
|
64
|
-
<div id="contact-editor" class="card mb-3" style="display:none;">
|
|
65
|
-
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between;">
|
|
66
|
-
<strong id="editor-title">Add Contact</strong>
|
|
67
|
-
<button id="editor-cancel-btn" class="btn btn-sm btn-outline">Cancel</button>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="card-body">
|
|
70
|
-
<div class="grid grid-cols-2" style="gap:1rem;">
|
|
71
|
-
<div class="form-group">
|
|
72
|
-
<label for="contact-name" class="form-label">Name <span style="color:var(--dm-danger)">*</span></label>
|
|
73
|
-
<input id="contact-name" type="text" class="form-input" placeholder="Full name">
|
|
74
|
-
</div>
|
|
75
|
-
<div class="form-group">
|
|
76
|
-
<label for="contact-email" class="form-label">Email</label>
|
|
77
|
-
<input id="contact-email" type="email" class="form-input" placeholder="email@example.com">
|
|
78
|
-
</div>
|
|
79
|
-
<div class="form-group">
|
|
80
|
-
<label for="contact-phone" class="form-label">Phone</label>
|
|
81
|
-
<input id="contact-phone" type="text" class="form-input" placeholder="+44 …">
|
|
82
|
-
</div>
|
|
83
|
-
<div class="form-group">
|
|
84
|
-
<label class="form-label">Groups</label>
|
|
85
|
-
<div id="contact-groups-display" style="min-height:38px;padding:0.375rem 0.75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius);cursor:pointer;display:flex;flex-wrap:wrap;gap:0.25rem;align-items:center;">
|
|
86
|
-
<span class="text-muted" id="groups-placeholder">Select groups…</span>
|
|
87
|
-
</div>
|
|
88
|
-
<div id="groups-dropdown-list" class="card mt-1" style="display:none;max-height:180px;overflow-y:auto;position:absolute;z-index:100;min-width:200px;">
|
|
89
|
-
<div class="card-body" id="groups-dropdown-items" style="padding:0.25rem;"></div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="form-group" style="grid-column:1/-1;">
|
|
93
|
-
<label for="contact-notes" class="form-label">Notes</label>
|
|
94
|
-
<textarea id="contact-notes" class="form-input" rows="3" placeholder="Optional notes…"></textarea>
|
|
95
|
-
</div>
|
|
96
|
-
<div class="form-group">
|
|
97
|
-
<label class="form-label" style="display:flex;align-items:center;gap:0.5rem;cursor:pointer;">
|
|
98
|
-
<input id="contact-favourite" type="checkbox" class="form-check-input">
|
|
99
|
-
Mark as favourite
|
|
100
|
-
</label>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
|
104
|
-
<button id="editor-save-btn" class="btn btn-primary">
|
|
105
|
-
<span data-icon="save"></span>
|
|
106
|
-
<span id="editor-save-label">Save Contact</span>
|
|
107
|
-
</button>
|
|
108
|
-
<button id="editor-delete-btn" class="btn btn-danger" style="display:none;">
|
|
109
|
-
<span data-icon="trash"></span>
|
|
110
|
-
Delete
|
|
111
|
-
</button>
|
|
112
|
-
<button id="editor-cancel-btn2" class="btn btn-outline">Cancel</button>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<!-- Contacts table -->
|
|
118
|
-
<div id="contacts-table"></div>
|
|
119
|
-
|
|
120
|
-
<!-- Empty state -->
|
|
121
|
-
<div id="contacts-empty" style="display:none;text-align:center;padding:3rem 1rem;color:var(--dm-text-muted);">
|
|
122
|
-
<span data-icon="users" style="font-size:3rem;display:block;margin-bottom:1rem;"></span>
|
|
123
|
-
<p>No contacts yet. Click <strong>Add Contact</strong> to get started.</p>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
</div>
|