@a83/orbiter-admin 0.3.43 → 0.3.45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a83/orbiter-admin",
3
- "version": "0.3.43",
3
+ "version": "0.3.45",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -1158,8 +1158,8 @@ html[data-style="xfce"] .editor-shell {
1158
1158
  flex-direction: column;
1159
1159
  align-items: center;
1160
1160
  justify-content: flex-end;
1161
- width: var(--dock-item-base, 50px);
1162
- padding: 8px 7px 7px;
1161
+ min-width: var(--dock-item-base, 50px);
1162
+ padding: 10px 12px 9px;
1163
1163
  background: transparent;
1164
1164
  border: none;
1165
1165
  border-radius: 12px;
@@ -1196,21 +1196,25 @@ html[data-style="xfce"] .editor-shell {
1196
1196
  }
1197
1197
  .xfce-dock-item.active {
1198
1198
  color: var(--accent);
1199
- background: color-mix(in srgb, var(--accent) 9%, transparent);
1199
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
1200
+ box-shadow:
1201
+ 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent),
1202
+ 0 0 14px color-mix(in srgb, var(--accent) 22%, transparent),
1203
+ inset 0 1px 0 color-mix(in srgb, white 18%, transparent);
1200
1204
  }
1201
- .xfce-dock-item.active::before { opacity: .5; }
1202
- /* active indicator: glowing scan-line instead of dot */
1205
+ .xfce-dock-item.active::before { opacity: .65; }
1206
+ /* active indicator: glowing dot at bottom */
1203
1207
  .xfce-dock-item.active::after {
1204
1208
  content: '';
1205
1209
  position: absolute;
1206
- bottom: 0;
1210
+ bottom: 4px;
1207
1211
  left: 50%;
1208
1212
  transform: translateX(-50%);
1209
- width: 60%;
1210
- height: 2px;
1211
- border-radius: 1px;
1212
- background: linear-gradient(90deg, transparent, var(--accent) 30%, var(--accent) 70%, transparent);
1213
- box-shadow: 0 0 8px 1px color-mix(in srgb, var(--accent) 70%, transparent);
1213
+ width: 4px;
1214
+ height: 4px;
1215
+ border-radius: 50%;
1216
+ background: var(--accent);
1217
+ box-shadow: 0 0 6px 2px color-mix(in srgb, var(--accent) 80%, transparent);
1214
1218
  }
1215
1219
 
1216
1220
  .xfce-dock-icon {
@@ -1227,9 +1231,10 @@ html[data-style="xfce"] .editor-shell {
1227
1231
  transform: scale(1.14);
1228
1232
  }
1229
1233
  .xfce-dock-item.active .xfce-dock-icon {
1230
- opacity: .95;
1234
+ opacity: 1;
1231
1235
  color: var(--accent);
1232
- filter: drop-shadow(0 0 5px color-mix(in srgb, var(--accent) 65%, transparent));
1236
+ filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 80%, transparent));
1237
+ transform: scale(1.08);
1233
1238
  }
1234
1239
  .xfce-dock-lbl {
1235
1240
  font-family: var(--mono);
@@ -1246,8 +1251,10 @@ html[data-style="xfce"] .editor-shell {
1246
1251
  color: color-mix(in srgb, var(--accent) 80%, var(--heading));
1247
1252
  }
1248
1253
  .xfce-dock-item.active .xfce-dock-lbl {
1249
- opacity: .85;
1254
+ opacity: 1;
1250
1255
  color: var(--accent);
1256
+ font-weight: 700;
1257
+ letter-spacing: .08em;
1251
1258
  }
1252
1259
 
1253
1260
  /* ── HUD Panel ────────────────────────────────────────────── */
@@ -2150,6 +2157,20 @@ html[data-dock-pos="left"] .xfce-col-preview.visible {
2150
2157
  .xfce-pal-item-body { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
2151
2158
  .xfce-pal-snippet { font-size: 10px; color: var(--muted); font-family: var(--mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2152
2159
 
2160
+ /* palette — recent entries */
2161
+ .xfce-pal-icon-sm { width: 20px; text-align: center; font-size: 10px; color: var(--muted); flex-shrink: 0; }
2162
+ .xfce-pal-group-sub {
2163
+ font-size: 8px; font-family: var(--mono); color: var(--line);
2164
+ letter-spacing: .08em; text-transform: uppercase;
2165
+ padding: 8px 14px 4px; border-top: 1px solid var(--line);
2166
+ }
2167
+ .xfce-pal-status {
2168
+ font-size: 9px; font-family: var(--mono); border-radius: 4px;
2169
+ padding: 1px 6px; border: 1px solid currentColor; white-space: nowrap;
2170
+ }
2171
+ .xfce-pal-status-published { color: var(--jade, #5dc97e); }
2172
+ .xfce-pal-status-draft { color: var(--muted); }
2173
+
2153
2174
  /* ── Status bar build indicator ──────────────────────────── */
2154
2175
  .xfce-sb-build { font-size: 9px; font-family: var(--mono); color: var(--muted); white-space: nowrap; }
2155
2176
  .xfce-sb-g-ind {
@@ -2196,6 +2217,139 @@ html[data-style="xfce"][data-dock-pos="left"] .xfce-ws-overlay {
2196
2217
  bottom: auto; left: 110px; top: 50%; transform: translateY(-50%);
2197
2218
  }
2198
2219
 
2220
+ /* ── Zen / Focus mode ────────────────────────────────────── */
2221
+ html[data-zen="1"] .xfce-sb {
2222
+ opacity: 0; pointer-events: none;
2223
+ transform: translateY(-100%);
2224
+ transition: opacity .3s, transform .3s;
2225
+ }
2226
+ html[data-zen="1"] .xfce-dock {
2227
+ opacity: 0; pointer-events: none;
2228
+ transform: translateY(30px);
2229
+ transition: opacity .3s, transform .3s;
2230
+ }
2231
+ html[data-zen="1"][data-dock-pos="left"] .xfce-dock {
2232
+ transform: translateX(-30px);
2233
+ }
2234
+ html[data-zen="1"] .main {
2235
+ padding-top: 8px !important;
2236
+ padding-bottom: 20px !important;
2237
+ }
2238
+
2239
+ /* ── Shortcut Cheatsheet ─────────────────────────────────── */
2240
+ .xfce-cheat-overlay {
2241
+ display: none; position: fixed; inset: 0;
2242
+ background: rgba(0,0,0,.55); backdrop-filter: blur(4px);
2243
+ z-index: 100001; align-items: center; justify-content: center;
2244
+ }
2245
+ .xfce-cheat-overlay.open { display: flex; }
2246
+ .xfce-cheat-panel {
2247
+ background: var(--bg1); border: 1px solid var(--line);
2248
+ border-radius: 14px; box-shadow: 0 16px 48px rgba(0,0,0,.55);
2249
+ width: 560px; max-width: calc(100vw - 40px); font-family: var(--mono);
2250
+ overflow: hidden;
2251
+ }
2252
+ .xfce-cheat-bar {
2253
+ display: flex; align-items: center; justify-content: space-between;
2254
+ padding: 12px 16px; border-bottom: 1px solid var(--line);
2255
+ background: var(--bg2);
2256
+ }
2257
+ .xfce-cheat-title { color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: .07em; }
2258
+ .xfce-cheat-close {
2259
+ background: none; border: none; color: var(--muted); cursor: pointer;
2260
+ font-size: 13px; line-height: 1; padding: 2px 4px;
2261
+ }
2262
+ .xfce-cheat-close:hover { color: var(--text); }
2263
+ .xfce-cheat-body {
2264
+ display: grid; grid-template-columns: 1fr 1fr; gap: 0;
2265
+ padding: 16px; gap: 0 24px; max-height: 70vh; overflow-y: auto;
2266
+ }
2267
+ .xfce-cheat-section {
2268
+ font-size: 9px; letter-spacing: .1em; color: var(--accent);
2269
+ text-transform: uppercase; font-weight: 700;
2270
+ margin-bottom: 8px; margin-top: 4px;
2271
+ }
2272
+ .xfce-cheat-row {
2273
+ display: flex; align-items: baseline; gap: 10px;
2274
+ padding: 3px 0; border-bottom: 1px solid rgba(255,255,255,.04);
2275
+ }
2276
+ .xfce-cheat-row:last-child { border-bottom: none; }
2277
+ .xfce-cheat-key {
2278
+ background: var(--bg2); border: 1px solid var(--line);
2279
+ border-radius: 4px; padding: 1px 6px; font-size: 10px;
2280
+ color: var(--accent); font-family: var(--mono); white-space: nowrap;
2281
+ flex-shrink: 0; min-width: 80px; text-align: center;
2282
+ }
2283
+ .xfce-cheat-desc { font-size: 10px; color: var(--text); }
2284
+
2285
+ /* ── Bell / Notification Center ─────────────────────────── */
2286
+ .xfce-sb-bell {
2287
+ background: none; border: none; cursor: pointer; padding: 0 2px;
2288
+ color: var(--muted); font-size: 12px; line-height: 1;
2289
+ position: relative; display: flex; align-items: center; gap: 2px;
2290
+ transition: color .15s;
2291
+ }
2292
+ .xfce-sb-bell:hover { color: var(--accent); }
2293
+ .xfce-sb-bell-badge {
2294
+ position: absolute; top: -3px; right: -4px;
2295
+ background: var(--accent); color: var(--bg1);
2296
+ font-size: 8px; font-family: var(--mono); font-weight: 700;
2297
+ border-radius: 6px; padding: 0 3px; min-width: 12px; text-align: center;
2298
+ line-height: 13px; pointer-events: none;
2299
+ }
2300
+ .xfce-notif-panel {
2301
+ position: fixed; top: calc(var(--sb-h) + 6px); right: 12px;
2302
+ width: 300px; max-height: 340px;
2303
+ background: var(--bg1); border: 1px solid var(--line);
2304
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,.45);
2305
+ z-index: 100000; display: none; flex-direction: column; overflow: hidden;
2306
+ font-family: var(--mono); font-size: 11px;
2307
+ }
2308
+ .xfce-notif-panel.open { display: flex; }
2309
+ .xfce-notif-bar {
2310
+ display: flex; align-items: center; justify-content: space-between;
2311
+ padding: 8px 12px; border-bottom: 1px solid var(--line);
2312
+ background: var(--bg2); flex-shrink: 0;
2313
+ }
2314
+ .xfce-notif-title { color: var(--accent); font-weight: 700; font-size: 10px; letter-spacing: .06em; }
2315
+ .xfce-notif-clear {
2316
+ background: none; border: 1px solid var(--line); border-radius: 4px;
2317
+ color: var(--muted); font-size: 9px; font-family: var(--mono);
2318
+ padding: 1px 7px; cursor: pointer; transition: color .15s, border-color .15s;
2319
+ }
2320
+ .xfce-notif-clear:hover { color: var(--accent); border-color: var(--accent); }
2321
+ .xfce-notif-list {
2322
+ overflow-y: auto; flex: 1; padding: 4px 0;
2323
+ }
2324
+ .xfce-notif-list::-webkit-scrollbar { width: 4px; }
2325
+ .xfce-notif-list::-webkit-scrollbar-thumb { background: var(--line); border-radius: 2px; }
2326
+ .xfce-notif-item {
2327
+ display: flex; align-items: flex-start; justify-content: space-between;
2328
+ gap: 8px; padding: 6px 12px;
2329
+ border-bottom: 1px solid rgba(255,255,255,.04);
2330
+ }
2331
+ .xfce-notif-item:last-child { border-bottom: none; }
2332
+ .xfce-notif-msg { flex: 1; color: var(--text); font-size: 10px; line-height: 1.4; }
2333
+ .xfce-notif-time { color: var(--muted); font-size: 9px; white-space: nowrap; flex-shrink: 0; margin-top: 1px; }
2334
+ .xfce-notif-ok .xfce-notif-msg { color: var(--jade); }
2335
+ .xfce-notif-err .xfce-notif-msg { color: var(--red); }
2336
+ .xfce-notif-empty { padding: 20px 12px; color: var(--muted); font-size: 10px; text-align: center; }
2337
+
2338
+ /* ── HUD Drafts section ──────────────────────────────────── */
2339
+ .xfce-hud-draft-row {
2340
+ display: flex; align-items: baseline; justify-content: space-between;
2341
+ gap: 8px; padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,.04);
2342
+ }
2343
+ .xfce-hud-draft-row:last-child { border-bottom: none; }
2344
+ .xfce-hud-draft-link {
2345
+ color: var(--accent); text-decoration: none; font-size: 10px;
2346
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1;
2347
+ max-width: 160px;
2348
+ }
2349
+ .xfce-hud-draft-link:hover { text-decoration: underline; }
2350
+ .xfce-hud-draft-meta { color: var(--muted); font-size: 9px; white-space: nowrap; flex-shrink: 0; }
2351
+ .xfce-hud-empty { color: var(--muted); font-size: 10px; padding: 4px 0; }
2352
+
2199
2353
  /* ── Toast host (above dock) ─────────────────────────────── */
2200
2354
  .xfce-toast-host {
2201
2355
  position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%);
package/public/xfce.js CHANGED
@@ -13,14 +13,14 @@
13
13
  var NAV = [
14
14
  { icon: '⬡', label: 'Dashboard', href: '/dashboard.html', key: 'dashboard' },
15
15
  { icon: '◫', label: 'Media', href: '/media.html', key: 'media' },
16
- { icon: '⚙', label: 'Settings', href: '/settings.html', key: 'settings' },
17
16
  { icon: '⊛', label: 'Users', href: '/users.html', key: 'users' },
18
17
  ];
19
18
 
20
19
  var TOOLS = [
21
- { icon: '▦', label: 'Schema', href: '/schema.html', key: 'schema' },
22
- { icon: '◉', label: 'Build', href: '/build.html', key: 'build' },
23
- { icon: '↓', label: 'Import', href: '/import.html', key: 'import' },
20
+ { icon: '▦', label: 'Schema', href: '/schema.html', key: 'schema' },
21
+ { icon: '◉', label: 'Build', href: '/build.html', key: 'build' },
22
+ { icon: '↓', label: 'Import', href: '/import.html', key: 'import' },
23
+ { icon: '⚙', label: 'Settings', href: '/settings.html', key: 'settings' },
24
24
  ];
25
25
 
26
26
  var WORKSPACE = [
@@ -30,9 +30,10 @@
30
30
 
31
31
  // palette items — nav + tools pre-seeded; collections appended after /api/info
32
32
  var _palItems = NAV.concat(TOOLS).map(function (n) {
33
- return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1 } ? 'Tools' : 'Nav' };
33
+ return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1, settings:1 } ? 'Tools' : 'Nav' };
34
34
  });
35
- var _termCols = []; // collection metadata for palette commands
35
+ var _termCols = []; // collection metadata for palette commands
36
+ var _palRecents = []; // prefetched recent entries for empty palette
36
37
 
37
38
  // ── Helpers ───────────────────────────────────────────────────────────
38
39
  function el(tag, cls, html) {
@@ -64,6 +65,8 @@
64
65
  '<div class="xfce-sb-center" id="xfce-sb-title"></div>',
65
66
  '<div class="xfce-sb-right">',
66
67
  '<span id="xfce-sb-g-ind" class="xfce-sb-g-ind" style="display:none" title="g mode: d=dashboard m=media s=settings u=users b=build i=import h=schema a=account">g ›</span>',
68
+ '<button id="xfce-sb-bell" class="xfce-sb-bell" title="Notifications"><span id="xfce-sb-bell-icon">○</span><span id="xfce-sb-bell-badge" class="xfce-sb-bell-badge" style="display:none"></span></button>',
69
+ '<span class="xfce-sb-div">·</span>',
67
70
  '<button id="xfce-sb-palette-btn" class="xfce-sb-palette-btn" title="Command palette (⌘K)">⌘</button>',
68
71
  '<span class="xfce-sb-div">·</span>',
69
72
  '<span id="xfce-sb-build" class="xfce-sb-build" title="Last build"></span>',
@@ -102,6 +105,12 @@
102
105
  e.stopPropagation();
103
106
  openPalette();
104
107
  });
108
+
109
+ // Bell / notification center
110
+ document.getElementById('xfce-sb-bell').addEventListener('click', function (e) {
111
+ e.stopPropagation();
112
+ toggleNotifPanel();
113
+ });
105
114
  }
106
115
 
107
116
  // ── HUD Meta Panel ────────────────────────────────────────────────────
@@ -120,6 +129,8 @@
120
129
  '<div id="xfce-hud-pod" class="xfce-hud-rows"></div>',
121
130
  '<div class="xfce-hud-section-label" style="margin-top:16px">Collections</div>',
122
131
  '<div id="xfce-hud-cols" class="xfce-hud-rows"></div>',
132
+ '<div class="xfce-hud-section-label" style="margin-top:16px">Drafts</div>',
133
+ '<div id="xfce-hud-drafts" class="xfce-hud-rows xfce-hud-drafts"></div>',
123
134
  '<div class="xfce-hud-section-label" style="margin-top:16px">Navigation</div>',
124
135
  '<div class="xfce-hud-nav-links" id="xfce-hud-nav"></div>',
125
136
  '</div>',
@@ -152,19 +163,12 @@
152
163
  function buildToolsPopup() {
153
164
  toolsPopup = el('div', 'xfce-tools-popup');
154
165
  toolsPopup.id = 'xfce-tools-popup';
155
- TOOLS.forEach(function (t) {
166
+ TOOLS.filter(function (t) { return t.key !== 'settings'; }).forEach(function (t) {
156
167
  var a = el('a', 'xfce-tools-item' + (page === t.key ? ' active' : ''));
157
168
  a.href = t.href;
158
169
  a.innerHTML = '<span class="xfce-tools-icon">' + t.icon + '</span><span>' + t.label + '</span>';
159
170
  toolsPopup.appendChild(a);
160
171
  });
161
- var settingsSep = document.createElement('div');
162
- settingsSep.className = 'xfce-tools-sep';
163
- toolsPopup.appendChild(settingsSep);
164
- var settingsLink = el('a', 'xfce-tools-item' + (page === 'settings' ? ' active' : ''));
165
- settingsLink.href = '/settings.html';
166
- settingsLink.innerHTML = '<span class="xfce-tools-icon">⚙</span><span>Settings</span>';
167
- toolsPopup.appendChild(settingsLink);
168
172
  var sep = document.createElement('div');
169
173
  sep.className = 'xfce-tools-sep';
170
174
  toolsPopup.appendChild(sep);
@@ -436,9 +440,55 @@
436
440
  }
437
441
 
438
442
  q = q.toLowerCase();
439
- var filtered = q
440
- ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
441
- : _palItems;
443
+
444
+ // Empty input show recents + nav
445
+ if (!q) {
446
+ var html = '';
447
+ if (_palRecents.length) {
448
+ html += '<div class="xfce-pal-group">Recent</div>';
449
+ _palRecents.forEach(function (r) {
450
+ var href = '/collections/' + encodeURIComponent(r.collection) + '/entries/' + encodeURIComponent(r.slug);
451
+ html += '<div class="xfce-pal-item" data-href="' + href + '">'
452
+ + '<span class="xfce-pal-icon xfce-pal-icon-sm">◈</span>'
453
+ + '<div class="xfce-pal-item-body">'
454
+ + '<span class="xfce-pal-label">' + escHtml(r.title || r.slug) + '</span>'
455
+ + '<span class="xfce-pal-snippet">' + escHtml(r.label) + '</span>'
456
+ + '</div>'
457
+ + '<span class="xfce-pal-hint-r xfce-pal-status xfce-pal-status-' + r.status + '">' + r.status + '</span>'
458
+ + '</div>';
459
+ });
460
+ html += '<div class="xfce-pal-group">Navigation</div>';
461
+ }
462
+ var navGroups = {};
463
+ _palItems.forEach(function (it) {
464
+ var g = it.group || 'Nav';
465
+ if (!navGroups[g]) navGroups[g] = [];
466
+ navGroups[g].push(it);
467
+ });
468
+ Object.keys(navGroups).forEach(function (g) {
469
+ if (_palRecents.length) html += '<div class="xfce-pal-group-sub">' + g + '</div>';
470
+ else html += '<div class="xfce-pal-group">' + g + '</div>';
471
+ navGroups[g].forEach(function (it) {
472
+ html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
473
+ + '<span class="xfce-pal-icon">' + it.icon + '</span>'
474
+ + '<span class="xfce-pal-label">' + escHtml(it.label) + '</span>'
475
+ + (it.meta ? '<span class="xfce-pal-hint-r">' + escHtml(it.meta) + '</span>' : '')
476
+ + '</div>';
477
+ });
478
+ });
479
+ paletteResults.innerHTML = html;
480
+ paletteResults.querySelectorAll('.xfce-pal-item[data-href]').forEach(function (item) {
481
+ item.addEventListener('click', function () { location.href = item.dataset.href; closePalette(); });
482
+ item.addEventListener('mouseenter', function () {
483
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
484
+ palActive = Array.from(items).indexOf(item);
485
+ updatePalActive(items);
486
+ });
487
+ });
488
+ return;
489
+ }
490
+
491
+ var filtered = _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); });
442
492
 
443
493
  if (!filtered.length) {
444
494
  paletteResults.innerHTML = '<div class="xfce-pal-empty">No results</div>';
@@ -714,6 +764,7 @@
714
764
  t.classList.remove('show');
715
765
  setTimeout(function () { t.remove(); }, 300);
716
766
  }, 2500);
767
+ pushNotif(msg, type);
717
768
  };
718
769
 
719
770
  function observeSavedFlash() {
@@ -726,6 +777,77 @@
726
777
  }).observe(flash, { attributes: true, attributeFilter: ['style'] });
727
778
  }
728
779
 
780
+ // ── Notification Center ───────────────────────────────────────────────
781
+ var _notifications = [], _notifUnread = 0, _notifPanel = null;
782
+
783
+ function pushNotif(msg, type) {
784
+ _notifications.unshift({ msg: msg, type: type || 'info', time: Date.now() });
785
+ if (_notifications.length > 40) _notifications.pop();
786
+ _notifUnread++;
787
+ var badge = document.getElementById('xfce-sb-bell-badge');
788
+ var icon = document.getElementById('xfce-sb-bell-icon');
789
+ if (badge) { badge.textContent = _notifUnread > 9 ? '9+' : _notifUnread; badge.style.display = ''; }
790
+ if (icon) { icon.textContent = '●'; }
791
+ if (_notifPanel && _notifPanel.classList.contains('open')) renderNotifList();
792
+ }
793
+
794
+ function buildNotifPanel() {
795
+ _notifPanel = el('div', 'xfce-notif-panel');
796
+ _notifPanel.id = 'xfce-notif-panel';
797
+ _notifPanel.innerHTML = '<div class="xfce-notif-bar">'
798
+ + '<span class="xfce-notif-title">Notifications</span>'
799
+ + '<button id="xfce-notif-clear" class="xfce-notif-clear">Clear all</button>'
800
+ + '</div>'
801
+ + '<div id="xfce-notif-list" class="xfce-notif-list"></div>';
802
+ document.body.appendChild(_notifPanel);
803
+ document.getElementById('xfce-notif-clear').addEventListener('click', function () {
804
+ _notifications = []; _notifUnread = 0;
805
+ renderNotifList();
806
+ var badge = document.getElementById('xfce-sb-bell-badge');
807
+ var icon = document.getElementById('xfce-sb-bell-icon');
808
+ if (badge) badge.style.display = 'none';
809
+ if (icon) icon.textContent = '○';
810
+ });
811
+ document.addEventListener('click', function (e) {
812
+ if (_notifPanel && _notifPanel.classList.contains('open')
813
+ && !_notifPanel.contains(e.target)
814
+ && e.target.id !== 'xfce-sb-bell') closeNotifPanel();
815
+ });
816
+ }
817
+
818
+ function renderNotifList() {
819
+ var list = document.getElementById('xfce-notif-list');
820
+ if (!list) return;
821
+ if (!_notifications.length) {
822
+ list.innerHTML = '<div class="xfce-notif-empty">No notifications yet</div>';
823
+ return;
824
+ }
825
+ list.innerHTML = _notifications.map(function (n) {
826
+ var ago = Math.floor((Date.now() - n.time) / 1000);
827
+ var t = ago < 60 ? ago + 's' : ago < 3600 ? Math.floor(ago/60) + 'm' : Math.floor(ago/3600) + 'h';
828
+ var cls = n.type === 'success' ? 'ok' : n.type === 'error' ? 'err' : 'info';
829
+ return '<div class="xfce-notif-item xfce-notif-' + cls + '">'
830
+ + '<span class="xfce-notif-msg">' + escHtml(n.msg) + '</span>'
831
+ + '<span class="xfce-notif-time">' + t + '</span>'
832
+ + '</div>';
833
+ }).join('');
834
+ }
835
+
836
+ function toggleNotifPanel() {
837
+ if (!_notifPanel) buildNotifPanel();
838
+ var isOpen = _notifPanel.classList.toggle('open');
839
+ if (isOpen) {
840
+ _notifUnread = 0;
841
+ var badge = document.getElementById('xfce-sb-bell-badge');
842
+ if (badge) badge.style.display = 'none';
843
+ renderNotifList();
844
+ }
845
+ }
846
+
847
+ function closeNotifPanel() {
848
+ if (_notifPanel) _notifPanel.classList.remove('open');
849
+ }
850
+
729
851
  // ── Workspace overlay (Notes + To-do) ────────────────────────────────
730
852
  var wsOverlay, wsActivePane = 'notes', wsNotesTimer, wsTodosData = [];
731
853
 
@@ -1091,6 +1213,9 @@
1091
1213
  });
1092
1214
  dockInner.appendChild(toolsBtn);
1093
1215
 
1216
+ var settingsDockBtn = makeDockItem('⚙', 'Settings', '/settings.html', page === 'settings', false);
1217
+ dockInner.appendChild(settingsDockBtn);
1218
+
1094
1219
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
1095
1220
 
1096
1221
  // HUD toggle
@@ -1213,6 +1338,28 @@
1213
1338
  })
1214
1339
  .catch(function () {});
1215
1340
 
1341
+ // HUD Drafts
1342
+ fetch('/api/search/recent?status=draft&limit=10', { credentials: 'include' })
1343
+ .then(function (r) { return r.ok ? r.json() : []; })
1344
+ .then(function (drafts) {
1345
+ var hudDrafts = document.getElementById('xfce-hud-drafts');
1346
+ if (!hudDrafts) return;
1347
+ if (!drafts.length) {
1348
+ hudDrafts.innerHTML = '<div class="xfce-hud-empty">No drafts</div>';
1349
+ return;
1350
+ }
1351
+ hudDrafts.innerHTML = drafts.map(function (d) {
1352
+ var href = '/collections/' + encodeURIComponent(d.collection) + '/entries/' + encodeURIComponent(d.slug);
1353
+ var ago = Math.floor((Date.now() - new Date(d.updated_at).getTime()) / 60000);
1354
+ var t = ago < 60 ? ago + 'm' : Math.floor(ago / 60) + 'h';
1355
+ return '<div class="xfce-hud-draft-row">'
1356
+ + '<a class="xfce-hud-draft-link" href="' + href + '">' + escHtml(d.title || d.slug) + '</a>'
1357
+ + '<span class="xfce-hud-draft-meta">' + escHtml(d.label) + ' · ' + t + '</span>'
1358
+ + '</div>';
1359
+ }).join('');
1360
+ })
1361
+ .catch(function () {});
1362
+
1216
1363
  // Site meta
1217
1364
  fetch('/api/meta/site~name', { credentials: 'include' })
1218
1365
  .then(function (r) { return r.ok ? r.json() : null; })
@@ -1232,12 +1379,98 @@
1232
1379
  if (sbUser) sbUser.textContent = d.user.username;
1233
1380
  })
1234
1381
  .catch(function () {});
1382
+
1383
+ // Prefetch recent entries for palette empty state
1384
+ fetch('/api/search/recent?limit=7', { credentials: 'include' })
1385
+ .then(function (r) { return r.ok ? r.json() : []; })
1386
+ .then(function (rows) { _palRecents = rows; })
1387
+ .catch(function () {});
1235
1388
  }
1236
1389
 
1237
1390
  function hudRow(label, value) {
1238
1391
  return '<div class="xfce-hud-row"><span>' + label + '</span><span>' + value + '</span></div>';
1239
1392
  }
1240
1393
 
1394
+ // ── Zen / Focus mode ─────────────────────────────────────────────────
1395
+ function toggleZen() {
1396
+ var html = document.documentElement;
1397
+ var on = html.dataset.zen !== '1';
1398
+ html.dataset.zen = on ? '1' : '';
1399
+ localStorage.setItem('orb_zen', on ? '1' : '');
1400
+ if (on) window.xfceToast('Focus mode on — ⌘⇧F to exit', 'info');
1401
+ }
1402
+
1403
+ // ── Shortcut cheatsheet ───────────────────────────────────────────────
1404
+ var _cheatEl = null;
1405
+
1406
+ function buildCheatsheet() {
1407
+ _cheatEl = el('div', 'xfce-cheat-overlay');
1408
+ _cheatEl.id = 'xfce-cheat';
1409
+ _cheatEl.innerHTML = [
1410
+ '<div class="xfce-cheat-panel">',
1411
+ '<div class="xfce-cheat-bar">',
1412
+ '<span class="xfce-cheat-title">⌨ Shortcuts</span>',
1413
+ '<button class="xfce-cheat-close" id="xfce-cheat-close">✕</button>',
1414
+ '</div>',
1415
+ '<div class="xfce-cheat-body">',
1416
+ '<div class="xfce-cheat-col">',
1417
+ '<div class="xfce-cheat-section">Navigation</div>',
1418
+ cheatRow('⌘K &nbsp;/&nbsp; /', 'Open command palette'),
1419
+ cheatRow('↑ ↓', 'Move selection in palette'),
1420
+ cheatRow('↵', 'Go to selected item'),
1421
+ cheatRow('Esc', 'Close overlay / panel'),
1422
+ cheatRow('g &nbsp;+&nbsp; d', 'Dashboard'),
1423
+ cheatRow('g &nbsp;+&nbsp; m', 'Media'),
1424
+ cheatRow('g &nbsp;+&nbsp; u', 'Users'),
1425
+ cheatRow('g &nbsp;+&nbsp; s', 'Settings'),
1426
+ cheatRow('g &nbsp;+&nbsp; b', 'Build'),
1427
+ cheatRow('g &nbsp;+&nbsp; i', 'Import'),
1428
+ cheatRow('g &nbsp;+&nbsp; h', 'Schema'),
1429
+ cheatRow('g &nbsp;+&nbsp; a', 'Account'),
1430
+ cheatRow('1 – 9', 'Jump to nth dock item'),
1431
+ '</div>',
1432
+ '<div class="xfce-cheat-col">',
1433
+ '<div class="xfce-cheat-section">Panels</div>',
1434
+ cheatRow('⌘⇧D', 'Toggle HUD'),
1435
+ cheatRow('⌘⇧F', 'Focus / Zen mode'),
1436
+ cheatRow('⌘⇧L', 'Switch to Glass mode'),
1437
+ cheatRow('?', 'This cheatsheet'),
1438
+ '<div class="xfce-cheat-section" style="margin-top:14px">Palette commands</div>',
1439
+ cheatRow('&gt; ls', 'List collections'),
1440
+ cheatRow('&gt; go &lt;page&gt;', 'Navigate to page'),
1441
+ cheatRow('&gt; new &lt;col&gt;', 'New entry in collection'),
1442
+ cheatRow('&gt; search &lt;q&gt;', 'Search entries'),
1443
+ cheatRow('&gt; build', 'Trigger site build'),
1444
+ cheatRow('&gt; export &lt;col&gt;', 'Export collection'),
1445
+ cheatRow('&gt; info', 'Show pod info'),
1446
+ cheatRow('&gt; help', 'Show command help'),
1447
+ '</div>',
1448
+ '</div>',
1449
+ '</div>',
1450
+ ].join('');
1451
+ document.body.appendChild(_cheatEl);
1452
+ document.getElementById('xfce-cheat-close').addEventListener('click', closeCheatsheet);
1453
+ _cheatEl.addEventListener('click', function (e) {
1454
+ if (e.target === _cheatEl) closeCheatsheet();
1455
+ });
1456
+ document.addEventListener('keydown', function (e) {
1457
+ if (e.key === 'Escape' && _cheatEl && _cheatEl.classList.contains('open')) closeCheatsheet();
1458
+ });
1459
+ }
1460
+
1461
+ function cheatRow(key, desc) {
1462
+ return '<div class="xfce-cheat-row"><kbd class="xfce-cheat-key">' + key + '</kbd><span class="xfce-cheat-desc">' + desc + '</span></div>';
1463
+ }
1464
+
1465
+ function toggleCheatsheet() {
1466
+ if (!_cheatEl) buildCheatsheet();
1467
+ _cheatEl.classList.toggle('open');
1468
+ }
1469
+
1470
+ function closeCheatsheet() {
1471
+ if (_cheatEl) _cheatEl.classList.remove('open');
1472
+ }
1473
+
1241
1474
  // ── Keyboard shortcuts ────────────────────────────────────────────────
1242
1475
  var _gPending = false, _gTimer = null;
1243
1476
  var G_MAP = { d: '/dashboard.html', m: '/media.html', s: '/settings.html',
@@ -1299,6 +1532,13 @@
1299
1532
  return;
1300
1533
  }
1301
1534
 
1535
+ // ⌘⇧F — zen / focus mode
1536
+ if (mod && e.shiftKey && (e.key === 'f' || e.key === 'F')) {
1537
+ e.preventDefault();
1538
+ toggleZen();
1539
+ return;
1540
+ }
1541
+
1302
1542
  // ⌘⇧L — switch back to glass mode
1303
1543
  if (mod && e.shiftKey && (e.key === 'l' || e.key === 'L')) {
1304
1544
  e.preventDefault();
@@ -1307,6 +1547,13 @@
1307
1547
  return;
1308
1548
  }
1309
1549
 
1550
+ // ? — shortcut cheatsheet
1551
+ if (!mod && !e.altKey && e.key === '?' && !isEditing(e.target)) {
1552
+ e.preventDefault();
1553
+ toggleCheatsheet();
1554
+ return;
1555
+ }
1556
+
1310
1557
  // 1–9 — jump to nth dock link (no modifier, not in input)
1311
1558
  if (!mod && !e.shiftKey && !e.altKey && !isEditing(e.target)) {
1312
1559
  var n = parseInt(e.key);
@@ -1359,6 +1606,9 @@
1359
1606
  bindKeys();
1360
1607
  initFocusMode();
1361
1608
  observeSavedFlash();
1609
+ if (localStorage.getItem('orb_zen') === '1') {
1610
+ document.documentElement.dataset.zen = '1';
1611
+ }
1362
1612
  }
1363
1613
 
1364
1614
  if (document.readyState === 'loading') {
@@ -5,17 +5,19 @@ export const searchRoutes = new Hono();
5
5
 
6
6
  // GET /api/search/recent — last N entries across all collections
7
7
  searchRoutes.get('/recent', (c) => {
8
- const limit = Math.min(parseInt(c.req.query('limit') ?? '10', 10), 50);
9
- const db = openPod(c.get('podPath'));
10
- const cols = db.getCollections();
8
+ const limit = Math.min(parseInt(c.req.query('limit') ?? '10', 10), 50);
9
+ const status = c.req.query('status') ?? null;
10
+ const db = openPod(c.get('podPath'));
11
+ const cols = db.getCollections();
11
12
  const colMap = Object.fromEntries(cols.map(c => [c.id, c.label]));
12
13
 
13
- const rows = db.db
14
- .prepare(`SELECT e.*, e.collection_id as collection FROM _entries e ORDER BY e.updated_at DESC LIMIT ?`)
15
- .all(limit);
14
+ const rows = status
15
+ ? db.db.prepare(`SELECT * FROM _entries WHERE status = ? ORDER BY updated_at DESC LIMIT ?`).all(status, limit)
16
+ : db.db.prepare(`SELECT * FROM _entries ORDER BY updated_at DESC LIMIT ?`).all(limit);
16
17
 
17
18
  const results = rows.map(r => {
18
- const data = JSON.parse(r.data);
19
+ let data = {};
20
+ try { data = JSON.parse(r.data); } catch {}
19
21
  return {
20
22
  collection: r.collection_id,
21
23
  label: colMap[r.collection_id] ?? r.collection_id,