domma-cms 0.9.1 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/admin/js/templates/block-editor.html +163 -163
  2. package/admin/js/templates/form-editor.html +245 -245
  3. package/admin/js/views/action-editor.js +1 -1
  4. package/admin/js/views/block-editor.js +8 -8
  5. package/admin/js/views/collection-editor.js +4 -4
  6. package/admin/js/views/collections.js +1 -1
  7. package/admin/js/views/form-editor.js +7 -7
  8. package/admin/js/views/forms.js +1 -1
  9. package/admin/js/views/navigation.js +14 -14
  10. package/admin/js/views/page-editor.js +35 -35
  11. package/admin/js/views/pages.js +5 -5
  12. package/admin/js/views/plugins.js +13 -10
  13. package/admin/js/views/view-editor.js +1 -1
  14. package/config/plugins.json +25 -0
  15. package/package.json +1 -1
  16. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +4 -4
  17. package/plugins/docs/data/folders.json +3 -3
  18. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +5 -0
  19. package/plugins/garage/admin/templates/garage.html +30 -0
  20. package/plugins/garage/admin/views/garage.js +62 -1
  21. package/plugins/garage/plugin.json +1 -1
  22. package/plugins/notes/admin/templates/notes.html +2 -11
  23. package/plugins/notes/admin/views/notes.js +108 -129
  24. package/plugins/notes/collections/user-notes/schema.json +2 -1
  25. package/plugins/notes/plugin.json +1 -1
  26. package/plugins/site-search/admin/templates/site-search.html +174 -46
  27. package/plugins/site-search/admin/views/site-search.js +72 -1
  28. package/plugins/site-search/config.js +6 -1
  29. package/plugins/site-search/plugin.json +1 -1
  30. package/plugins/site-search/public/inject-head.html +1 -1
  31. package/plugins/site-search/public/search.css +1 -1
  32. package/plugins/site-search/public/search.js +1 -1
  33. package/plugins/todo/admin/templates/todo.html +2 -8
  34. package/plugins/todo/admin/views/todo.js +123 -106
  35. package/plugins/todo/collections/todos/schema.json +2 -1
  36. package/plugins/todo/plugin.json +1 -1
  37. package/server/routes/api/media.js +127 -118
  38. package/server/routes/api/plugins.js +15 -4
  39. package/server/server.js +288 -285
  40. package/server/services/collections.js +17 -10
  41. package/server/services/plugins.js +77 -67
  42. package/server/services/renderer.js +3 -3
  43. package/plugins/docs/data/documents/452f49b7-9c93-4a67-874d-27f882891ad2.json +0 -11
@@ -7,61 +7,189 @@
7
7
  </div>
8
8
  </div>
9
9
 
10
- <div class="card mb-4">
11
- <div class="card-header"><h2>Settings</h2></div>
12
- <div class="card-body">
13
-
14
- <div class="row mb-3">
15
- <div class="col">
16
- <label class="form-label">Search placeholder text</label>
17
- <input id="field-placeholder" type="text" class="form-input" placeholder="Search pages...">
18
- <span class="form-hint">Displayed inside the search input when empty.</span>
19
- </div>
20
- </div>
10
+ <div id="ss-settings-tabs" class="tabs">
11
+ <div class="tab-list">
12
+ <button class="tab-item active">Settings</button>
13
+ <button class="tab-item">Trigger &amp; Position</button>
14
+ <button class="tab-item">How it works</button>
15
+ </div>
16
+ <div class="tab-content">
17
+
18
+ <!-- Settings Tab -->
19
+ <div class="tab-panel active" id="tab-search-settings">
20
+ <div class="card mb-4" style="margin-top:1rem;">
21
+ <div class="card-body">
22
+
23
+ <div class="row mb-3">
24
+ <div class="col">
25
+ <label class="form-label">Search placeholder text</label>
26
+ <input id="field-placeholder" type="text" class="form-input" placeholder="Search pages...">
27
+ <span class="form-hint">Displayed inside the search input when empty.</span>
28
+ </div>
29
+ </div>
30
+
31
+ <div class="row mb-3">
32
+ <div class="col">
33
+ <label class="form-check-label">
34
+ <input id="field-keyboard-shortcut" type="checkbox">
35
+ Enable keyboard shortcut (Ctrl+K / ⌘K)
36
+ </label>
37
+ <span class="form-hint">Allows visitors to open the search overlay using a keyboard shortcut.</span>
38
+ </div>
39
+ </div>
21
40
 
22
- <div class="row mb-3">
23
- <div class="col">
24
- <label class="form-check-label">
25
- <input id="field-keyboard-shortcut" type="checkbox">
26
- Enable keyboard shortcut (Ctrl+K / ⌘K)
27
- </label>
28
- <span class="form-hint">Allows visitors to open the search overlay using a keyboard shortcut.</span>
41
+ <div class="row mb-3">
42
+ <div class="col-6">
43
+ <label class="form-label">Max results</label>
44
+ <input id="field-max-results" type="number" class="form-input" min="1" max="50"
45
+ placeholder="10">
46
+ <span class="form-hint">Maximum number of results to return per search (1–50).</span>
47
+ </div>
48
+ <div class="col-6">
49
+ <label class="form-label">Minimum query length</label>
50
+ <input id="field-min-query-length" type="number" class="form-input" min="1" max="5"
51
+ placeholder="2">
52
+ <span class="form-hint">Minimum characters required before searching (1–5).</span>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="row">
57
+ <div class="col-6">
58
+ <label class="form-label">Debounce delay (ms)</label>
59
+ <input id="field-debounce-ms" type="number" class="form-input" min="100" max="1000"
60
+ placeholder="300">
61
+ <span class="form-hint">Delay after typing stops before search fires (100–1000ms).</span>
62
+ </div>
63
+ </div>
64
+
65
+ </div>
29
66
  </div>
30
67
  </div>
31
68
 
32
- <div class="row mb-3">
33
- <div class="col-6">
34
- <label class="form-label">Max results</label>
35
- <input id="field-max-results" type="number" class="form-input" min="1" max="50" placeholder="10">
36
- <span class="form-hint">Maximum number of results to return per search (1–50).</span>
37
- </div>
38
- <div class="col-6">
39
- <label class="form-label">Minimum query length</label>
40
- <input id="field-min-query-length" type="number" class="form-input" min="1" max="5" placeholder="2">
41
- <span class="form-hint">Minimum characters required before searching (1–5).</span>
69
+ <!-- Trigger & Position Tab -->
70
+ <div class="tab-panel" id="tab-trigger-position">
71
+ <div class="card mb-4" style="margin-top:1rem;">
72
+ <div class="card-body">
73
+
74
+ <div class="row mb-3">
75
+ <div class="col">
76
+ <label class="form-label">Display Mode</label>
77
+ <div id="ss-display-mode" style="display:flex;gap:8px;flex-wrap:wrap;">
78
+ <button type="button" class="btn ss-mode-btn" data-mode="navbar">
79
+ <span data-icon="menu" data-icon-size="14" style="margin-right:5px;"></span>Navbar
80
+ </button>
81
+ <button type="button" class="btn ss-mode-btn" data-mode="floating">
82
+ <span data-icon="search" data-icon-size="14" style="margin-right:5px;"></span>Floating
83
+ Button
84
+ </button>
85
+ </div>
86
+ <input type="hidden" id="field-display-mode" value="navbar">
87
+ <span class="form-hint">Navbar adds the icon to the site navigation. Floating button shows a fixed button on every page.</span>
88
+ </div>
89
+ </div>
90
+
91
+ <div id="ss-pos-section" style="display:none;">
92
+
93
+ <div class="row mb-3">
94
+ <div class="col">
95
+ <label class="form-check-label">
96
+ <input id="field-icon-only" type="checkbox" checked>
97
+ Icon only
98
+ </label>
99
+ <span class="form-hint">When unchecked, shows a "Search" label alongside the icon.</span>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="row" style="align-items:flex-start;gap:0;">
104
+
105
+ <div class="col-auto" style="margin-right:1.5rem;margin-bottom:1rem;">
106
+ <label class="form-label" style="margin-bottom:.6rem;">Position</label>
107
+ <div id="ss-pos-grid"
108
+ style="display:grid;grid-template-columns:1fr 1fr;gap:2px;border:1px solid var(--border-color,rgba(255,255,255,.1));border-radius:10px;overflow:hidden;background:var(--border-color,rgba(255,255,255,.07));width:fit-content;">
109
+
110
+ <div class="ss-pos-btn" data-pos="top-left"
111
+ style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
112
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
113
+ <span style="font-weight:600;">↖ Left</span>
114
+ </div>
115
+ <div class="ss-pos-btn" data-pos="top-right"
116
+ style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
117
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
118
+ <span style="font-weight:600;">Right ↗</span>
119
+ </div>
120
+
121
+ <div class="ss-pos-btn" data-pos="left-center"
122
+ style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
123
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
124
+ <span style="font-weight:600;">← Left</span>
125
+ </div>
126
+ <div class="ss-pos-btn" data-pos="right-center"
127
+ style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
128
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
129
+ <span style="font-weight:600;">Right →</span>
130
+ </div>
131
+
132
+ <div class="ss-pos-btn" data-pos="bottom-left"
133
+ style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
134
+ <span style="font-weight:600;">↙ Left</span>
135
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
136
+ </div>
137
+ <div class="ss-pos-btn" data-pos="bottom-right"
138
+ style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
139
+ <span style="font-weight:600;">Right ↘</span>
140
+ <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
141
+ </div>
142
+
143
+ </div>
144
+ <input type="hidden" id="field-position" value="bottom-right">
145
+ </div>
146
+
147
+ <div class="col-sm-3">
148
+ <label class="form-label">Edge offset</label>
149
+ <div style="display:flex;flex-direction:column;gap:.55rem;">
150
+ <div style="display:flex;align-items:center;gap:.4rem;">
151
+ <span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">H</span>
152
+ <input id="field-offset-x" type="number" class="form-input" min="0" max="500"
153
+ placeholder="32" style="max-width:80px;">
154
+ <span class="text-muted" style="font-size:.8rem;">px</span>
155
+ </div>
156
+ <div style="display:flex;align-items:center;gap:.4rem;">
157
+ <span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">V</span>
158
+ <input id="field-offset-y" type="number" class="form-input" min="0" max="500"
159
+ placeholder="32" style="max-width:80px;">
160
+ <span class="text-muted" style="font-size:.8rem;">px</span>
161
+ </div>
162
+ </div>
163
+ <p class="form-hint text-muted" style="margin-top:.35rem;font-size:.75rem;">Distance
164
+ from the screen edge.</p>
165
+ </div>
166
+
167
+ </div>
168
+ </div>
169
+
170
+ </div>
42
171
  </div>
43
172
  </div>
44
173
 
45
- <div class="row">
46
- <div class="col-6">
47
- <label class="form-label">Debounce delay (ms)</label>
48
- <input id="field-debounce-ms" type="number" class="form-input" min="100" max="1000" placeholder="300">
49
- <span class="form-hint">Delay after typing stops before search fires (100–1000ms).</span>
174
+ <!-- How it works Tab -->
175
+ <div class="tab-panel" id="tab-how-it-works">
176
+ <div class="card mb-4" style="margin-top:1rem;">
177
+ <div class="card-body">
178
+ <p class="text-muted mb-2">Site Search adds a search icon to the public navbar. Visitors can click
179
+ it or use
180
+ the keyboard shortcut to open a full-screen overlay with live results.</p>
181
+ <ul class="text-muted" style="padding-left:1.25rem;line-height:1.8;">
182
+ <li>Results are weighted: title matches score highest, followed by tags, description, and page
183
+ content.
184
+ </li>
185
+ <li>Draft pages and private pages are excluded from results.</li>
186
+ <li>Use <kbd>↑</kbd> <kbd>↓</kbd> to navigate results, <kbd>↵</kbd> to open, <kbd>Esc</kbd> to
187
+ close.
188
+ </li>
189
+ </ul>
190
+ </div>
50
191
  </div>
51
192
  </div>
52
193
 
53
194
  </div>
54
195
  </div>
55
-
56
- <div class="card mb-4">
57
- <div class="card-header"><h2>How it works</h2></div>
58
- <div class="card-body">
59
- <p class="text-muted mb-2">Site Search adds a search icon to the public navbar. Visitors can click it or use
60
- the keyboard shortcut to open a full-screen overlay with live results.</p>
61
- <ul class="text-muted" style="padding-left:1.25rem;line-height:1.8;">
62
- <li>Results are weighted: title matches score highest, followed by tags, description, and page content.</li>
63
- <li>Draft pages and private pages are excluded from results.</li>
64
- <li>Use <kbd>↑</kbd> <kbd>↓</kbd> to navigate results, <kbd>↵</kbd> to open, <kbd>Esc</kbd> to close.</li>
65
- </ul>
66
- </div>
67
- </div>
@@ -14,19 +14,86 @@ export const siteSearchView = {
14
14
  E.toast('Could not load settings.', {type: 'error'});
15
15
  }
16
16
 
17
+ // ---- Basic settings ----
17
18
  $container.find('#field-placeholder').val(settings.placeholder || 'Search pages...');
18
19
  $container.find('#field-keyboard-shortcut').prop('checked', settings.keyboardShortcut !== false);
19
20
  $container.find('#field-max-results').val(settings.maxResults ?? 10);
20
21
  $container.find('#field-min-query-length').val(settings.minQueryLength ?? 2);
21
22
  $container.find('#field-debounce-ms').val(settings.debounceMs ?? 300);
22
23
 
24
+ // ---- Position settings ----
25
+ const displayMode = settings.displayMode || 'navbar';
26
+ const fieldMode = document.getElementById('field-display-mode');
27
+ const fieldPos = document.getElementById('field-position');
28
+ const fieldOffX = document.getElementById('field-offset-x');
29
+ const fieldOffY = document.getElementById('field-offset-y');
30
+ const fieldIconOnly = document.getElementById('field-icon-only');
31
+ const posSection = document.getElementById('ss-pos-section');
32
+ const posGrid = document.getElementById('ss-pos-grid');
33
+ const modeBar = document.getElementById('ss-display-mode');
34
+
35
+ if (fieldMode) fieldMode.value = displayMode;
36
+ if (fieldPos) fieldPos.value = settings.position ?? 'bottom-right';
37
+ if (fieldOffX) fieldOffX.value = settings.offsetX ?? 32;
38
+ if (fieldOffY) fieldOffY.value = settings.offsetY ?? 32;
39
+ if (fieldIconOnly) fieldIconOnly.checked = settings.iconOnly !== false;
40
+
41
+ function applyMode(mode) {
42
+ if (fieldMode) fieldMode.value = mode;
43
+ if (posSection) posSection.style.display = mode === 'floating' ? '' : 'none';
44
+ if (modeBar) {
45
+ modeBar.querySelectorAll('.ss-mode-btn').forEach(function (btn) {
46
+ const active = btn.getAttribute('data-mode') === mode;
47
+ btn.classList.toggle('btn-primary', active);
48
+ btn.classList.toggle('btn-secondary', !active);
49
+ });
50
+ }
51
+ }
52
+
53
+ function updatePosBtns(pos) {
54
+ document.querySelectorAll('.ss-pos-btn').forEach(function (btn) {
55
+ const active = btn.getAttribute('data-pos') === pos;
56
+ btn.style.background = active ? 'var(--dm-primary, #5b8cff)' : '';
57
+ btn.style.color = active ? '#fff' : '';
58
+ });
59
+ }
60
+
61
+ applyMode(displayMode);
62
+ updatePosBtns(settings.position || 'bottom-right');
63
+
64
+ // Mode toggle buttons
65
+ if (modeBar) {
66
+ modeBar.addEventListener('click', function (e) {
67
+ const btn = e.target.closest('.ss-mode-btn');
68
+ if (!btn) return;
69
+ applyMode(btn.getAttribute('data-mode'));
70
+ });
71
+ }
72
+
73
+ // Position grid
74
+ if (posGrid) {
75
+ posGrid.addEventListener('click', function (e) {
76
+ const btn = e.target.closest('[data-pos]');
77
+ if (!btn) return;
78
+ const pos = btn.getAttribute('data-pos');
79
+ if (fieldPos) fieldPos.value = pos;
80
+ updatePosBtns(pos);
81
+ });
82
+ }
83
+
84
+ // ---- Save ----
23
85
  $container.find('#save-settings-btn').off('click').on('click', async () => {
24
86
  const data = {
25
87
  placeholder: $container.find('#field-placeholder').val() || 'Search pages...',
26
88
  keyboardShortcut: $container.find('#field-keyboard-shortcut').prop('checked'),
27
89
  maxResults: parseInt($container.find('#field-max-results').val(), 10) || 10,
28
90
  minQueryLength: parseInt($container.find('#field-min-query-length').val(), 10) || 2,
29
- debounceMs: parseInt($container.find('#field-debounce-ms').val(), 10) || 300
91
+ debounceMs: parseInt($container.find('#field-debounce-ms').val(), 10) || 300,
92
+ displayMode: fieldMode ? fieldMode.value : 'navbar',
93
+ position: fieldPos ? fieldPos.value : 'bottom-right',
94
+ offsetX: fieldOffX ? (parseInt(fieldOffX.value, 10) || 32) : 32,
95
+ offsetY: fieldOffY ? (parseInt(fieldOffY.value, 10) || 32) : 32,
96
+ iconOnly: fieldIconOnly ? fieldIconOnly.checked : true
30
97
  };
31
98
 
32
99
  try {
@@ -40,6 +107,10 @@ export const siteSearchView = {
40
107
  }
41
108
  });
42
109
 
110
+ // Initialise tabs
111
+ const tabsEl = $container.find('#ss-settings-tabs').get(0);
112
+ if (tabsEl) E.tabs(tabsEl);
113
+
43
114
  Domma.icons.scan();
44
115
  }
45
116
  };
@@ -6,5 +6,10 @@ export default {
6
6
  keyboardShortcut: true,
7
7
  maxResults: 10,
8
8
  minQueryLength: 2,
9
- debounceMs: 300
9
+ debounceMs: 300,
10
+ displayMode: 'navbar',
11
+ position: 'bottom-right',
12
+ offsetX: 32,
13
+ offsetY: 32,
14
+ iconOnly: true
10
15
  };
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "views": {
27
27
  "plugin-site-search": {
28
- "entry": "site-search/admin/views/site-search.js",
28
+ "entry": "site-search/admin/views/site-search.js?v=4",
29
29
  "exportName": "siteSearchView"
30
30
  }
31
31
  }
@@ -1 +1 @@
1
- <link rel="stylesheet" href="/plugins/site-search/public/search.css">
1
+ <link rel="stylesheet" href="/plugins/site-search/public/search.css?v=2">
@@ -1 +1 @@
1
- .site-search-trigger{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:6px 8px;border-radius:var(--dm-radius, 6px);color:inherit;opacity:.75;transition:opacity .15s,background .15s;font-size:var(--dm-font-size-sm, 14px)}.site-search-trigger:hover{opacity:1;background:var(--dm-surface-hover, rgba(0, 0, 0, .06))}.site-search-trigger svg,.site-search-trigger span[data-icon]{width:18px;height:18px}.site-search-shortcut-hint{display:inline-block;font-size:11px;line-height:1;padding:2px 5px;border-radius:4px;border:1px solid var(--dm-border, rgba(0, 0, 0, .15));color:var(--dm-text-muted, #888);margin-left:4px;vertical-align:middle;font-family:var(--dm-font-mono, monospace)}.site-search-overlay{position:fixed;inset:0;z-index:10000;background:#00000073;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding-top:10vh;animation:ss-overlay-in .15s ease}@keyframes ss-overlay-in{0%{opacity:0}to{opacity:1}}.site-search-panel{width:100%;max-width:600px;margin:0 16px;border-radius:var(--dm-radius-lg, 10px);box-shadow:0 20px 60px #00000059;background:var(--dm-bg, #fff);overflow:hidden;animation:ss-panel-in .15s ease}@keyframes ss-panel-in{0%{transform:translateY(-12px);opacity:0}to{transform:translateY(0);opacity:1}}.site-search-header{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid var(--dm-border, rgba(0, 0, 0, .1));gap:8px}.site-search-header span[data-icon],.site-search-header svg{width:18px;height:18px;flex-shrink:0;opacity:.5}.site-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--dm-font-size-lg, 16px);color:var(--dm-text, #111);font-family:var(--dm-font-sans, sans-serif);padding:0}.site-search-input::placeholder{color:var(--dm-text-muted, #aaa)}.site-search-close-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--dm-text-muted, #999);border-radius:var(--dm-radius, 6px);padding:4px 8px;font-size:12px;font-family:var(--dm-font-mono, monospace);border:1px solid var(--dm-border, rgba(0, 0, 0, .15));transition:color .15s;flex-shrink:0}.site-search-close-btn:hover{color:var(--dm-text, #111)}.site-search-results{max-height:60vh;overflow-y:auto;padding:8px 0}.site-search-result{display:block;padding:10px 16px;text-decoration:none;color:var(--dm-text, #111);border-left:3px solid transparent;transition:background .1s,border-color .1s;cursor:pointer}.site-search-result:hover,.site-search-result.active{background:var(--dm-surface-hover, rgba(0, 0, 0, .05));border-left-color:var(--dm-primary, #5b6af0)}.site-search-result-title{font-weight:600;font-size:var(--dm-font-size-sm, 14px);margin-bottom:2px;color:var(--dm-text, #111)}.site-search-result-snippet{font-size:12px;color:var(--dm-text-muted, #888);line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.site-search-result-snippet mark{background:var(--dm-primary-subtle, rgba(91, 106, 240, .15));color:inherit;border-radius:2px;padding:0 1px}.site-search-loading,.site-search-empty{padding:24px 16px;text-align:center;color:var(--dm-text-muted, #888);font-size:var(--dm-font-size-sm, 14px)}.site-search-loading-dots{display:inline-flex;gap:4px}.site-search-loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--dm-text-muted, #aaa);animation:ss-dot-pulse 1.2s ease-in-out infinite}.site-search-loading-dots span:nth-child(2){animation-delay:.2s}.site-search-loading-dots span:nth-child(3){animation-delay:.4s}@keyframes ss-dot-pulse{0%,80%,to{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}.site-search-results:not(:empty){border-top:1px solid var(--dm-border, rgba(0, 0, 0, .07))}.site-search-footer{padding:6px 16px;border-top:1px solid var(--dm-border, rgba(0, 0, 0, .07));display:flex;gap:12px;font-size:11px;color:var(--dm-text-muted, #aaa)}.site-search-footer kbd{display:inline-block;padding:1px 4px;border-radius:3px;border:1px solid var(--dm-border, rgba(0, 0, 0, .2));font-family:var(--dm-font-mono, monospace);font-size:10px}@media(max-width:640px){.site-search-overlay{padding-top:5vh;align-items:flex-start}.site-search-panel{margin:0 8px;border-radius:var(--dm-radius, 6px)}.site-search-results{max-height:70vh}.site-search-shortcut-hint{display:none}}
1
+ .site-search-trigger{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:6px 8px;border-radius:var(--dm-radius, 6px);color:inherit;opacity:.75;transition:opacity .15s,background .15s;font-size:var(--dm-font-size-sm, 14px)}.site-search-trigger:hover{opacity:1;background:var(--dm-surface-hover, rgba(0, 0, 0, .06))}.site-search-trigger svg,.site-search-trigger span[data-icon]{width:18px;height:18px}.site-search-shortcut-hint{display:inline-block;font-size:11px;line-height:1;padding:2px 5px;border-radius:4px;border:1px solid var(--dm-border, rgba(0, 0, 0, .15));color:var(--dm-text-muted, #888);margin-left:4px;vertical-align:middle;font-family:var(--dm-font-mono, monospace)}.site-search-overlay{position:fixed;inset:0;z-index:10000;background:#00000073;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding-top:10vh;animation:ss-overlay-in .15s ease}@keyframes ss-overlay-in{0%{opacity:0}to{opacity:1}}.site-search-panel{width:100%;max-width:600px;margin:0 16px;border-radius:var(--dm-radius-lg, 10px);background:#1e2030;color:#e8eaf0;border:1px solid rgba(255,255,255,.1);box-shadow:0 24px 64px #0009;overflow:hidden;animation:ss-panel-in .15s ease}@keyframes ss-panel-in{0%{transform:translateY(-12px);opacity:0}to{transform:translateY(0);opacity:1}}.site-search-header{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.08);gap:8px}.site-search-header span[data-icon],.site-search-header svg{width:18px;height:18px;flex-shrink:0;opacity:.45;color:#e8eaf0}.site-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--dm-font-size-lg, 16px);color:#e8eaf0;font-family:var(--dm-font-sans, sans-serif);padding:0}.site-search-input::placeholder{color:#e8eaf066;opacity:1}.site-search-close-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid rgba(255,255,255,.15);cursor:pointer;color:#e8eaf080;border-radius:var(--dm-radius, 6px);padding:4px 8px;font-size:12px;font-family:var(--dm-font-mono, monospace);transition:color .15s,border-color .15s;flex-shrink:0}.site-search-close-btn:hover{color:#e8eaf0;border-color:#ffffff4d}.site-search-results{max-height:60vh;overflow-y:auto;padding:8px 0}.site-search-result{display:block;padding:10px 16px;text-decoration:none;color:inherit;border-left:3px solid transparent;transition:background .1s,border-color .1s;cursor:pointer}.site-search-result:hover,.site-search-result.active{background:#ffffff0f;border-left-color:var(--dm-primary, #5b8cff)}.site-search-result-title{font-weight:600;font-size:var(--dm-font-size-sm, 14px);margin-bottom:2px;color:#e8eaf0}.site-search-result-snippet{font-size:12px;color:#e8eaf08c;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.site-search-result-snippet mark{background:#5b8cff40;color:#e8eaf0;border-radius:2px;padding:0 1px}.site-search-loading,.site-search-empty{padding:24px 16px;text-align:center;color:#e8eaf073;font-size:var(--dm-font-size-sm, 14px)}.site-search-loading-dots{display:inline-flex;gap:4px}.site-search-loading-dots span{width:6px;height:6px;border-radius:50%;background:#e8eaf066;animation:ss-dot-pulse 1.2s ease-in-out infinite}.site-search-loading-dots span:nth-child(2){animation-delay:.2s}.site-search-loading-dots span:nth-child(3){animation-delay:.4s}@keyframes ss-dot-pulse{0%,80%,to{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}.site-search-results:not(:empty){border-top:1px solid rgba(255,255,255,.06)}.site-search-footer{padding:6px 16px;border-top:1px solid rgba(255,255,255,.06);display:flex;gap:12px;font-size:11px;color:#e8eaf059}.site-search-footer kbd{display:inline-block;padding:1px 4px;border-radius:3px;border:1px solid rgba(255,255,255,.15);font-family:var(--dm-font-mono, monospace);font-size:10px}@media(max-width:640px){.site-search-overlay{padding-top:5vh;align-items:flex-start}.site-search-panel{margin:0 8px;border-radius:var(--dm-radius, 6px)}.site-search-results{max-height:70vh}.site-search-shortcut-hint{display:none}}.site-search-floating-trigger{position:fixed;z-index:9999;width:52px;height:52px;border-radius:50%;border:1px solid var(--dm-border, rgba(0, 0, 0, .12));cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;background:var(--dm-surface, #fff);color:var(--dm-text, #111);box-shadow:0 4px 16px #00000024;transition:background .15s,color .15s,border-color .15s,transform .15s,box-shadow .15s;outline:none;white-space:nowrap}.site-search-floating-trigger:hover{background:var(--dm-primary, #5b8cff);color:#fff;border-color:var(--dm-primary, #5b8cff);box-shadow:0 6px 20px #00000038;transform:scale(1.05)}.site-search-floating-trigger:active{transform:scale(.97)}.site-search-floating-trigger svg,.site-search-floating-trigger [data-icon]{width:20px;height:20px;flex-shrink:0;pointer-events:none}.site-search-floating-trigger--pill{width:auto;height:44px;border-radius:999px;padding:0 18px 0 14px}.site-search-floating-label{font-size:.875rem;font-weight:500;letter-spacing:.01em;pointer-events:none}
@@ -1 +1 @@
1
- (function(){"use strict";var f=window.__SITE_SEARCH__||{},S=f.placeholder||"Search pages...",b=f.keyboardShortcut!==!1,N=f.debounceMs||300,d=null,o=null,c=null,m=null,u=!1;function h(){var e=document.querySelector("#site-navbar, .navbar");if(e){var t=e.querySelector(".navbar-actions");if(!t){var a=e.querySelector(".navbar-container")||e;t=document.createElement("div"),t.className="navbar-actions",a.appendChild(t)}if(!t.querySelector(".site-search-trigger")){var s=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),r=s?"\u2318K":"Ctrl+K",n=document.createElement("button");n.className="navbar-action-link site-search-trigger",n.setAttribute("aria-label","Search"),n.setAttribute("type","button");var i=document.createElement("span");if(i.setAttribute("data-icon","search"),n.appendChild(i),b){var l=document.createElement("span");l.className="site-search-shortcut-hint",l.textContent=r,n.appendChild(l)}t.insertBefore(n,t.firstChild),n.addEventListener("click",E),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(n)}}}function g(){if(document.querySelector("#site-navbar .navbar-actions, .navbar .navbar-actions")){h();return}var e=new MutationObserver(function(){var t=document.querySelector("#site-navbar, .navbar");t&&(e.disconnect(),h())});e.observe(document.body,{childList:!0,subtree:!0}),setTimeout(function(){e.disconnect(),h()},5e3)}function w(){var e=document.createElement("div");e.className="site-search-overlay",e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),e.setAttribute("aria-label","Site search");var t=document.createElement("div");t.className="site-search-panel",t.setAttribute("role","search");var a=document.createElement("div");a.className="site-search-header";var s=document.createElement("span");s.setAttribute("data-icon","search"),a.appendChild(s),o=document.createElement("input"),o.className="site-search-input",o.type="search",o.setAttribute("autocomplete","off"),o.setAttribute("autocorrect","off"),o.setAttribute("spellcheck","false"),o.setAttribute("aria-label","Search"),o.placeholder=S,a.appendChild(o);var r=document.createElement("button");r.className="site-search-close-btn",r.setAttribute("type","button"),r.setAttribute("aria-label","Close search"),r.textContent="Esc",a.appendChild(r),t.appendChild(a),c=document.createElement("div"),c.className="site-search-results",c.setAttribute("role","listbox"),c.setAttribute("aria-live","polite"),t.appendChild(c);var n=document.createElement("div");return n.className="site-search-footer",n.innerHTML="<span><kbd>\u2191</kbd><kbd>\u2193</kbd> navigate</span><span><kbd>\u21B5</kbd> open</span><span><kbd>Esc</kbd> close</span>",t.appendChild(n),e.appendChild(t),e.addEventListener("click",function(i){i.target===e&&v()}),r.addEventListener("click",v),o.addEventListener("input",function(){clearTimeout(m);var i=o.value.trim();if(!i){C();return}m=setTimeout(function(){k(i)},N)}),o.addEventListener("keydown",D),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(a),e}function E(){u||(u=!0,d=w(),document.body.appendChild(d),document.body.style.overflow="hidden",setTimeout(function(){o&&o.focus()},30))}function v(){u&&(u=!1,clearTimeout(m),d&&d.parentNode&&d.parentNode.removeChild(d),d=null,o=null,c=null,document.body.style.overflow="")}function k(e){c&&(L(),fetch("/api/plugins/site-search/search?q="+encodeURIComponent(e)).then(function(t){return t.json()}).then(function(t){T(t.results||[],e)}).catch(function(){C();var t=document.createElement("div");t.className="site-search-empty",t.textContent="Search unavailable. Please try again.",c.appendChild(t)}))}function L(){c&&(c.innerHTML='<div class="site-search-loading"><div class="site-search-loading-dots"><span></span><span></span><span></span></div></div>')}function C(){c&&(c.innerHTML="")}function T(e,t){if(c){if(c.innerHTML="",!e.length){var a=document.createElement("div");a.className="site-search-empty",a.textContent='No results found for "'+t+'"',c.appendChild(a);return}for(var s=document.createDocumentFragment(),r=0;r<e.length;r++){var n=e[r],i=document.createElement("a");i.className="site-search-result",i.href=n.url||"/",i.setAttribute("role","option"),i.setAttribute("tabindex","-1"),i.setAttribute("data-index",String(r));var l=document.createElement("div");if(l.className="site-search-result-title",y(l,n.title||"Untitled",t),i.appendChild(l),n.snippet){var p=document.createElement("div");p.className="site-search-result-snippet",y(p,n.snippet,t),i.appendChild(p)}i.addEventListener("click",v),s.appendChild(i)}c.appendChild(s)}}function y(e,t,a){if(!a){e.textContent=t;return}var s=a.trim().split(/\s+/).filter(Boolean);if(!s.length){e.textContent=t;return}var r;try{r=new RegExp("("+s.map(function(p){return p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("|")+")","gi")}catch{e.textContent=t;return}for(var n=t.split(r),i=0;i<n.length;i++){if(r.test(n[i])){var l=document.createElement("mark");l.textContent=n[i],e.appendChild(l)}else e.appendChild(document.createTextNode(n[i]));r.lastIndex=0}}function D(e){if(e.key==="Escape"){e.preventDefault(),v();return}if(c){var t=c.querySelectorAll(".site-search-result");if(t.length){for(var a=c.querySelector(".site-search-result.active"),s=-1,r=0;r<t.length;r++)if(t[r]===a){s=r;break}if(e.key==="ArrowDown")e.preventDefault(),A(t,s<t.length-1?s+1:0);else if(e.key==="ArrowUp")e.preventDefault(),A(t,s>0?s-1:t.length-1);else if(e.key==="Enter"&&a){e.preventDefault();var n=a.getAttribute("href");v(),window.location.href=n}}}}function A(e,t){for(var a=0;a<e.length;a++)e[a].classList.toggle("active",a===t);e[t]&&e[t].scrollIntoView({block:"nearest"})}b&&document.addEventListener("keydown",function(e){if((e.metaKey||e.ctrlKey)&&e.key==="k"){var t=document.activeElement&&document.activeElement.tagName;if((t==="INPUT"||t==="TEXTAREA"||t==="SELECT")&&!u)return;e.preventDefault(),u?v():E()}}),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g()})();
1
+ (function(){"use strict";var l=window.__SITE_SEARCH__||{},N=l.placeholder||"Search pages...",g=l.keyboardShortcut!==!1,w=l.debounceMs||300,D=l.displayMode||"navbar",y=l.iconOnly!==!1,u=null,s=null,c=null,p=null,f=!1;function h(){var t=document.querySelector("#site-navbar, .navbar");if(t){var e=t.querySelector(".navbar-actions");if(!e){var n=t.querySelector(".navbar-container")||t;e=document.createElement("div"),e.className="navbar-actions",n.appendChild(e)}if(!e.querySelector(".site-search-trigger")){var r=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),o=r?"\u2318K":"Ctrl+K",a=document.createElement("button");a.className="navbar-action-link site-search-trigger",a.setAttribute("aria-label","Search"),a.setAttribute("type","button");var i=document.createElement("span");if(i.setAttribute("data-icon","search"),a.appendChild(i),g){var d=document.createElement("span");d.className="site-search-shortcut-hint",d.textContent=o,a.appendChild(d)}e.insertBefore(a,e.firstChild),a.addEventListener("click",b),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(a)}}}function L(){if(document.querySelector("#site-navbar .navbar-actions, .navbar .navbar-actions")){h();return}var t=new MutationObserver(function(){var e=document.querySelector("#site-navbar, .navbar");e&&(t.disconnect(),h())});t.observe(document.body,{childList:!0,subtree:!0}),setTimeout(function(){t.disconnect(),h()},5e3)}function k(){var t=document.createElement("div");t.className="site-search-overlay",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Site search");var e=document.createElement("div");e.className="site-search-panel",e.setAttribute("role","search");var n=document.createElement("div");n.className="site-search-header";var r=document.createElement("span");r.setAttribute("data-icon","search"),n.appendChild(r),s=document.createElement("input"),s.className="site-search-input",s.type="search",s.setAttribute("autocomplete","off"),s.setAttribute("autocorrect","off"),s.setAttribute("spellcheck","false"),s.setAttribute("aria-label","Search"),s.placeholder=N,n.appendChild(s);var o=document.createElement("button");o.className="site-search-close-btn",o.setAttribute("type","button"),o.setAttribute("aria-label","Close search"),o.textContent="Esc",n.appendChild(o),e.appendChild(n),c=document.createElement("div"),c.className="site-search-results",c.setAttribute("role","listbox"),c.setAttribute("aria-live","polite"),e.appendChild(c);var a=document.createElement("div");return a.className="site-search-footer",a.innerHTML="<span><kbd>\u2191</kbd><kbd>\u2193</kbd> navigate</span><span><kbd>\u21B5</kbd> open</span><span><kbd>Esc</kbd> close</span>",e.appendChild(a),t.appendChild(e),t.addEventListener("click",function(i){i.target===t&&v()}),o.addEventListener("click",v),s.addEventListener("input",function(){clearTimeout(p);var i=s.value.trim();if(!i){E();return}p=setTimeout(function(){T(i)},w)}),s.addEventListener("keydown",q),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(n),t}function b(){f||(f=!0,u=k(),document.body.appendChild(u),document.body.style.overflow="hidden",setTimeout(function(){s&&s.focus()},30))}function v(){f&&(f=!1,clearTimeout(p),u&&u.parentNode&&u.parentNode.removeChild(u),u=null,s=null,c=null,document.body.style.overflow="")}function T(t){c&&(x(),fetch("/api/plugins/site-search/search?q="+encodeURIComponent(t)).then(function(e){return e.json()}).then(function(e){M(e.results||[],t)}).catch(function(){E();var e=document.createElement("div");e.className="site-search-empty",e.textContent="Search unavailable. Please try again.",c.appendChild(e)}))}function x(){c&&(c.innerHTML='<div class="site-search-loading"><div class="site-search-loading-dots"><span></span><span></span><span></span></div></div>')}function E(){c&&(c.innerHTML="")}function M(t,e){if(c){if(c.innerHTML="",!t.length){var n=document.createElement("div");n.className="site-search-empty",n.textContent='No results found for "'+e+'"',c.appendChild(n);return}for(var r=document.createDocumentFragment(),o=0;o<t.length;o++){var a=t[o],i=document.createElement("a");i.className="site-search-result",i.href=a.url||"/",i.setAttribute("role","option"),i.setAttribute("tabindex","-1"),i.setAttribute("data-index",String(o));var d=document.createElement("div");if(d.className="site-search-result-title",C(d,a.title||"Untitled",e),i.appendChild(d),a.snippet){var m=document.createElement("div");m.className="site-search-result-snippet",C(m,a.snippet,e),i.appendChild(m)}i.addEventListener("click",v),r.appendChild(i)}c.appendChild(r)}}function C(t,e,n){if(!n){t.textContent=e;return}var r=n.trim().split(/\s+/).filter(Boolean);if(!r.length){t.textContent=e;return}var o;try{o=new RegExp("("+r.map(function(m){return m.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("|")+")","gi")}catch{t.textContent=e;return}for(var a=e.split(o),i=0;i<a.length;i++){if(o.test(a[i])){var d=document.createElement("mark");d.textContent=a[i],t.appendChild(d)}else t.appendChild(document.createTextNode(a[i]));o.lastIndex=0}}function q(t){if(t.key==="Escape"){t.preventDefault(),v();return}if(c){var e=c.querySelectorAll(".site-search-result");if(e.length){for(var n=c.querySelector(".site-search-result.active"),r=-1,o=0;o<e.length;o++)if(e[o]===n){r=o;break}if(t.key==="ArrowDown")t.preventDefault(),A(e,r<e.length-1?r+1:0);else if(t.key==="ArrowUp")t.preventDefault(),A(e,r>0?r-1:e.length-1);else if(t.key==="Enter"&&n){t.preventDefault();var a=n.getAttribute("href");v(),window.location.href=a}}}}function A(t,e){for(var n=0;n<t.length;n++)t[n].classList.toggle("active",n===e);t[e]&&t[e].scrollIntoView({block:"nearest"})}g&&document.addEventListener("keydown",function(t){if((t.metaKey||t.ctrlKey)&&t.key==="k"){var e=document.activeElement&&document.activeElement.tagName;if((e==="INPUT"||e==="TEXTAREA"||e==="SELECT")&&!f)return;t.preventDefault(),f?v():b()}});function O(t,e,n,r){var o=e==="left-center"||e==="bottom-left"||e==="top-left",a=e==="bottom-left"||e==="bottom-right",i=e==="top-left"||e==="top-right";Object.assign(t.style,{right:o?"auto":n+"px",left:o?n+"px":"auto",top:i?r+"px":a?"auto":"50vh",bottom:a?r+"px":"auto",transform:i||a?"none":"translateY(-50%)"})}function H(){if(!document.querySelector(".site-search-floating-trigger")){var t=l.position||"bottom-right",e=l.offsetX!=null?l.offsetX:32,n=l.offsetY!=null?l.offsetY:32,r=document.createElement("button");r.className="site-search-floating-trigger"+(y?"":" site-search-floating-trigger--pill"),r.setAttribute("type","button"),r.setAttribute("aria-label","Search");var o=document.createElement("span");if(o.setAttribute("data-icon","search"),r.appendChild(o),!y){var a=document.createElement("span");a.className="site-search-floating-label",a.textContent="Search",r.appendChild(a)}O(r,t,e,n),document.body.appendChild(r),r.addEventListener("click",b),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(r)}}function S(){D==="floating"?H():L()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S()})();
@@ -47,14 +47,8 @@
47
47
  <button class="btn btn-sm filter-btn" data-filter="completed">Completed</button>
48
48
  </div>
49
49
 
50
- <!-- Todo list -->
51
- <ul id="todo-list" style="list-style:none;padding:0;margin:0;"></ul>
52
-
53
- <!-- Empty state -->
54
- <div id="empty-state" style="display:none;text-align:center;padding:40px 0;color:var(--dm-text-muted);">
55
- <span data-icon="check-circle" data-icon-size="48" style="display:block;margin-bottom:12px;opacity:0.4;"></span>
56
- <p style="margin:0;font-size:0.95rem;">No tasks yet. Add one above to get started.</p>
57
- </div>
50
+ <!-- Todo table -->
51
+ <div id="todo-table"></div>
58
52
 
59
53
  </div>
60
54
  </div>