@a83/orbiter-admin 0.3.21 → 0.3.22

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.21",
3
+ "version": "0.3.22",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -1995,6 +1995,103 @@ html[data-style="xfce"] .xfce-in-focus .xfce-focus-trigger::after {
1995
1995
  flex-shrink: 0;
1996
1996
  }
1997
1997
 
1998
+ /* Status bar links & logout */
1999
+ a.xfce-sb-logo { text-decoration: none; color: var(--accent); }
2000
+ a.xfce-sb-logo:hover { opacity: .8; }
2001
+ .xfce-sb-user-link { color: var(--mid); text-decoration: none; font-size: 9px; font-family: var(--mono); }
2002
+ .xfce-sb-user-link:hover { color: var(--text); }
2003
+ .xfce-sb-logout {
2004
+ background: none; border: none; cursor: pointer;
2005
+ font-size: 10px; color: var(--muted); padding: 0; line-height: 1;
2006
+ font-family: var(--mono); transition: color .15s;
2007
+ }
2008
+ .xfce-sb-logout:hover { color: var(--red); }
2009
+
2010
+ /* Draft badge on dock items */
2011
+ .xfce-dock-badge {
2012
+ position: absolute; top: 2px; right: 2px;
2013
+ background: var(--gold); color: #000;
2014
+ font-size: 7px; font-family: var(--mono); font-weight: 700;
2015
+ min-width: 12px; height: 12px; border-radius: 6px;
2016
+ display: flex; align-items: center; justify-content: center;
2017
+ padding: 0 3px; pointer-events: none; line-height: 1;
2018
+ }
2019
+ .xfce-dock-item { position: relative; }
2020
+
2021
+ /* Quick-create button */
2022
+ .xfce-dock-create .xfce-dock-icon {
2023
+ font-size: 16px !important; font-family: inherit !important;
2024
+ color: var(--accent); font-weight: 300;
2025
+ }
2026
+
2027
+ /* ── Command Palette ─────────────────────────────────────── */
2028
+ .xfce-palette {
2029
+ display: none; position: fixed; inset: 0; z-index: 10000;
2030
+ background: rgba(0,0,0,.55); backdrop-filter: blur(4px);
2031
+ align-items: flex-start; justify-content: center;
2032
+ padding-top: 12vh;
2033
+ }
2034
+ .xfce-palette.open { display: flex; }
2035
+
2036
+ .xfce-palette-inner {
2037
+ width: min(560px, 92vw);
2038
+ background: var(--glass-bg, color-mix(in srgb, var(--bg1) 90%, transparent));
2039
+ border: 1px solid var(--line);
2040
+ border-radius: 12px;
2041
+ box-shadow: 0 24px 60px rgba(0,0,0,.5), 0 0 0 1px color-mix(in srgb,var(--accent) 20%,transparent);
2042
+ overflow: hidden;
2043
+ }
2044
+
2045
+ .xfce-palette-bar {
2046
+ display: flex; align-items: center; gap: 8px;
2047
+ padding: 10px 14px; border-bottom: 1px solid var(--line);
2048
+ }
2049
+ .xfce-palette-cmd { color: var(--accent); font-size: 13px; flex-shrink: 0; }
2050
+ .xfce-palette-inp {
2051
+ flex: 1; background: none; border: none; outline: none;
2052
+ font-family: var(--mono); font-size: 13px; color: var(--heading);
2053
+ }
2054
+ .xfce-palette-inp::placeholder { color: var(--muted); }
2055
+ .xfce-palette-hint { font-size: 8px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
2056
+
2057
+ .xfce-palette-results {
2058
+ max-height: 360px; overflow-y: auto; padding: 6px 0;
2059
+ }
2060
+ .xfce-pal-group {
2061
+ font-size: 8px; font-family: var(--mono); color: var(--muted);
2062
+ letter-spacing: .08em; text-transform: uppercase;
2063
+ padding: 8px 14px 4px;
2064
+ }
2065
+ .xfce-pal-item {
2066
+ display: flex; align-items: center; gap: 10px;
2067
+ padding: 7px 14px; cursor: pointer; transition: background .1s;
2068
+ }
2069
+ .xfce-pal-item:hover, .xfce-pal-item.pal-active {
2070
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
2071
+ }
2072
+ .xfce-pal-icon { width: 20px; text-align: center; font-size: 13px; color: var(--accent); flex-shrink: 0; }
2073
+ .xfce-pal-label { font-size: 12px; color: var(--heading); font-family: var(--mono); }
2074
+ .xfce-pal-empty { padding: 20px 14px; color: var(--muted); font-size: 11px; font-family: var(--mono); }
2075
+
2076
+ /* ── Toast host (above dock) ─────────────────────────────── */
2077
+ .xfce-toast-host {
2078
+ position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%);
2079
+ z-index: 9990; display: flex; flex-direction: column; align-items: center; gap: 6px;
2080
+ pointer-events: none;
2081
+ }
2082
+ .xfce-toast {
2083
+ background: var(--bg2); border: 1px solid var(--line);
2084
+ border-radius: 8px; padding: 6px 14px;
2085
+ font-family: var(--mono); font-size: 10px; color: var(--text);
2086
+ box-shadow: 0 4px 16px rgba(0,0,0,.3);
2087
+ opacity: 0; transform: translateY(8px);
2088
+ transition: opacity .2s, transform .2s;
2089
+ white-space: nowrap;
2090
+ }
2091
+ .xfce-toast.show { opacity: 1; transform: translateY(0); }
2092
+ .xfce-toast.xfce-toast-success { border-color: var(--jade); color: var(--jade); }
2093
+ .xfce-toast.xfce-toast-error { border-color: var(--red); color: var(--red); }
2094
+
1998
2095
  /* ════════════════════════════════════════════════════════════
1999
2096
  XFCE — MOBILE (≤ 640px)
2000
2097
  ════════════════════════════════════════════════════════════ */
package/public/xfce.js CHANGED
@@ -28,6 +28,11 @@
28
28
  { icon: '☑', label: 'To-do', pane: 'todos' },
29
29
  ];
30
30
 
31
+ // palette items populated after /api/info loads
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' };
34
+ });
35
+
31
36
  // ── Helpers ───────────────────────────────────────────────────────────
32
37
  function el(tag, cls, html) {
33
38
  var e = document.createElement(tag);
@@ -36,31 +41,38 @@
36
41
  return e;
37
42
  }
38
43
 
44
+ function isEditing(target) {
45
+ var t = target.tagName;
46
+ return t === 'INPUT' || t === 'TEXTAREA' || t === 'SELECT' || target.isContentEditable;
47
+ }
48
+
39
49
  // ── Status Bar ────────────────────────────────────────────────────────
40
50
  function buildStatusBar() {
41
51
  var sb = el('div', 'xfce-sb');
42
52
  sb.innerHTML = [
43
53
  '<div class="xfce-sb-left">',
44
- '<span class="xfce-sb-logo">',
54
+ '<a class="xfce-sb-logo" href="/dashboard.html">',
45
55
  '<svg viewBox="0 0 20 20" width="12" height="12" fill="none" style="margin-right:5px;vertical-align:middle">',
46
56
  '<circle cx="10" cy="10" r="4.5" fill="currentColor" opacity=".9"/>',
47
57
  '<ellipse cx="10" cy="10" rx="9" ry="3.2" stroke="currentColor" stroke-width="1" opacity=".5" transform="rotate(-22 10 10)"/>',
48
58
  '</svg>ORBITER',
49
- '</span>',
59
+ '</a>',
50
60
  '<span class="xfce-sb-div">·</span>',
51
61
  '<span id="xfce-sb-site">—</span>',
52
62
  '</div>',
53
63
  '<div class="xfce-sb-center" id="xfce-sb-title"></div>',
54
64
  '<div class="xfce-sb-right">',
55
- '<span id="xfce-sb-user"></span>',
65
+ '<a id="xfce-sb-user" href="/account.html" class="xfce-sb-user-link"></a>',
66
+ '<span class="xfce-sb-div">·</span>',
67
+ '<button id="xfce-sb-logout" class="xfce-sb-logout" title="Log out">⏻</button>',
56
68
  '<span class="xfce-sb-div">·</span>',
57
69
  '<span id="xfce-sb-clock"></span>',
58
70
  '</div>',
59
71
  ].join('');
60
72
  document.body.insertBefore(sb, document.body.firstChild);
61
73
 
62
- // Page title from document.title (strip " — Orbiter")
63
- var title = document.title.replace(/\s*—\s*Orbiter.*$/, '').trim();
74
+ // Page title from document.title
75
+ var title = document.title.replace(/\s*—\s*Orbiter.*$/, '').trim();
64
76
  var titleEl = document.getElementById('xfce-sb-title');
65
77
  if (titleEl && title) titleEl.textContent = title;
66
78
 
@@ -72,6 +84,12 @@
72
84
  }
73
85
  tick();
74
86
  setInterval(tick, 15000);
87
+
88
+ // Logout
89
+ document.getElementById('xfce-sb-logout').addEventListener('click', function () {
90
+ fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
91
+ .finally(function () { location.href = '/login.html'; });
92
+ });
75
93
  }
76
94
 
77
95
  // ── HUD Meta Panel ────────────────────────────────────────────────────
@@ -100,7 +118,6 @@
100
118
  metaPanel.classList.remove('open');
101
119
  });
102
120
 
103
- // Nav links inside HUD (all items including tools)
104
121
  var navWrap = document.getElementById('xfce-hud-nav');
105
122
  if (navWrap) {
106
123
  NAV.concat(TOOLS).forEach(function (n) {
@@ -148,6 +165,152 @@
148
165
  toolsPopup.classList.toggle('open');
149
166
  }
150
167
 
168
+ // ── Command Palette ───────────────────────────────────────────────────
169
+ var palette, paletteInp, paletteResults, palActive = -1;
170
+
171
+ function buildPalette() {
172
+ palette = el('div', 'xfce-palette');
173
+ palette.id = 'xfce-palette';
174
+ palette.innerHTML = [
175
+ '<div class="xfce-palette-inner">',
176
+ '<div class="xfce-palette-bar">',
177
+ '<span class="xfce-palette-cmd">⌘</span>',
178
+ '<input id="xfce-palette-inp" class="xfce-palette-inp" placeholder="Go to page or collection…" autocomplete="off" spellcheck="false" />',
179
+ '<span class="xfce-palette-hint">ESC to close</span>',
180
+ '</div>',
181
+ '<div id="xfce-palette-results" class="xfce-palette-results"></div>',
182
+ '</div>',
183
+ ].join('');
184
+ document.body.appendChild(palette);
185
+
186
+ paletteInp = document.getElementById('xfce-palette-inp');
187
+ paletteResults = document.getElementById('xfce-palette-results');
188
+
189
+ paletteInp.addEventListener('input', function () {
190
+ palActive = -1;
191
+ renderPalette(paletteInp.value);
192
+ });
193
+
194
+ paletteInp.addEventListener('keydown', function (e) {
195
+ var items = paletteResults.querySelectorAll('.xfce-pal-item');
196
+ if (e.key === 'ArrowDown') {
197
+ e.preventDefault();
198
+ palActive = Math.min(palActive + 1, items.length - 1);
199
+ updatePalActive(items);
200
+ } else if (e.key === 'ArrowUp') {
201
+ e.preventDefault();
202
+ palActive = Math.max(palActive - 1, 0);
203
+ updatePalActive(items);
204
+ } else if (e.key === 'Enter') {
205
+ var active = paletteResults.querySelector('.xfce-pal-item.pal-active');
206
+ if (active) { location.href = active.dataset.href; closePalette(); }
207
+ else {
208
+ var first = paletteResults.querySelector('.xfce-pal-item');
209
+ if (first) { location.href = first.dataset.href; closePalette(); }
210
+ }
211
+ } else if (e.key === 'Escape') {
212
+ closePalette();
213
+ }
214
+ });
215
+
216
+ palette.addEventListener('click', function (e) {
217
+ if (e.target === palette) closePalette();
218
+ });
219
+ }
220
+
221
+ function updatePalActive(items) {
222
+ items.forEach(function (it, i) {
223
+ it.classList.toggle('pal-active', i === palActive);
224
+ if (i === palActive) it.scrollIntoView({ block: 'nearest' });
225
+ });
226
+ }
227
+
228
+ function renderPalette(q) {
229
+ q = (q || '').toLowerCase().trim();
230
+ var filtered = q
231
+ ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
232
+ : _palItems;
233
+
234
+ if (!filtered.length) {
235
+ paletteResults.innerHTML = '<div class="xfce-pal-empty">No results</div>';
236
+ return;
237
+ }
238
+
239
+ // Group items
240
+ var groups = {};
241
+ filtered.forEach(function (it) {
242
+ if (!groups[it.group]) groups[it.group] = [];
243
+ groups[it.group].push(it);
244
+ });
245
+
246
+ var html = '';
247
+ Object.keys(groups).forEach(function (g) {
248
+ html += '<div class="xfce-pal-group">' + g + '</div>';
249
+ groups[g].forEach(function (it) {
250
+ html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
251
+ + '<span class="xfce-pal-icon">' + it.icon + '</span>'
252
+ + '<span class="xfce-pal-label">' + it.label + '</span>'
253
+ + '</div>';
254
+ });
255
+ });
256
+ paletteResults.innerHTML = html;
257
+
258
+ paletteResults.querySelectorAll('.xfce-pal-item').forEach(function (item) {
259
+ item.addEventListener('click', function () {
260
+ location.href = item.dataset.href;
261
+ closePalette();
262
+ });
263
+ item.addEventListener('mouseenter', function () {
264
+ var items = paletteResults.querySelectorAll('.xfce-pal-item');
265
+ palActive = Array.from(items).indexOf(item);
266
+ updatePalActive(items);
267
+ });
268
+ });
269
+ }
270
+
271
+ function openPalette() {
272
+ if (!palette) buildPalette();
273
+ palette.classList.add('open');
274
+ paletteInp.value = '';
275
+ palActive = -1;
276
+ renderPalette('');
277
+ setTimeout(function () { paletteInp.focus(); }, 30);
278
+ }
279
+
280
+ function closePalette() {
281
+ if (palette) palette.classList.remove('open');
282
+ }
283
+
284
+ // ── Toast system ──────────────────────────────────────────────────────
285
+ function buildToastHost() {
286
+ var host = el('div', 'xfce-toast-host');
287
+ host.id = 'xfce-toast-host';
288
+ document.body.appendChild(host);
289
+ }
290
+
291
+ window.xfceToast = function (msg, type) {
292
+ var host = document.getElementById('xfce-toast-host');
293
+ if (!host) return;
294
+ var t = el('div', 'xfce-toast' + (type ? ' xfce-toast-' + type : ''));
295
+ t.textContent = msg;
296
+ host.appendChild(t);
297
+ requestAnimationFrame(function () { t.classList.add('show'); });
298
+ setTimeout(function () {
299
+ t.classList.remove('show');
300
+ setTimeout(function () { t.remove(); }, 300);
301
+ }, 2500);
302
+ };
303
+
304
+ function observeSavedFlash() {
305
+ var flash = document.getElementById('saved-flash');
306
+ if (!flash) return;
307
+ new MutationObserver(function () {
308
+ if (flash.style.display !== 'none' && flash.textContent.trim()) {
309
+ window.xfceToast(flash.textContent.trim(), 'success');
310
+ }
311
+ }).observe(flash, { attributes: true, attributeFilter: ['style'] });
312
+ }
313
+
151
314
  // ── Workspace overlay (Notes + To-do) ────────────────────────────────
152
315
  var wsOverlay, wsActivePane = 'notes', wsNotesTimer, wsTodosData = [];
153
316
 
@@ -186,12 +349,10 @@
186
349
  ].join('');
187
350
  document.body.appendChild(wsOverlay);
188
351
 
189
- // Tabs
190
352
  wsOverlay.querySelectorAll('.xfce-ws-tab').forEach(function (btn) {
191
353
  btn.addEventListener('click', function () { switchWsPane(btn.dataset.pane); });
192
354
  });
193
355
 
194
- // Close
195
356
  document.getElementById('xfce-ws-close').addEventListener('click', closeWorkspace);
196
357
  document.addEventListener('keydown', function (e) {
197
358
  if (e.key === 'Escape' && wsOverlay.classList.contains('open')) closeWorkspace();
@@ -202,7 +363,6 @@
202
363
  closeWorkspace();
203
364
  });
204
365
 
205
- // Notes auto-save
206
366
  var notesEl = document.getElementById('xfce-ws-notes');
207
367
  var ind = document.getElementById('xfce-ws-ind');
208
368
  notesEl.addEventListener('input', function () {
@@ -221,9 +381,8 @@
221
381
  }, 1200);
222
382
  });
223
383
 
224
- // Todo add
225
384
  function addTodo() {
226
- var inp = document.getElementById('xfce-ws-todo-inp');
385
+ var inp = document.getElementById('xfce-ws-todo-inp');
227
386
  var text = inp.value.trim();
228
387
  if (!text) return;
229
388
  wsTodosData.push({ text: text, done: false });
@@ -236,13 +395,11 @@
236
395
  if (e.key === 'Enter') addTodo();
237
396
  });
238
397
 
239
- // Clear done
240
398
  document.getElementById('xfce-ws-todo-clear').addEventListener('click', function () {
241
399
  wsTodosData = wsTodosData.filter(function (t) { return !t.done; });
242
400
  renderTodos(); saveTodos();
243
401
  });
244
402
 
245
- // Export .md
246
403
  document.getElementById('xfce-ws-export').addEventListener('click', function () {
247
404
  var date = new Date().toISOString().slice(0, 10);
248
405
  var text, filename;
@@ -306,7 +463,6 @@
306
463
  }
307
464
 
308
465
  function loadWsData() {
309
- // On dashboard, read directly from the page's own elements if available
310
466
  var dashNotes = document.getElementById('notes-area');
311
467
  if (dashNotes) {
312
468
  var ta = document.getElementById('xfce-ws-notes');
@@ -347,13 +503,12 @@
347
503
 
348
504
  function closeWorkspace() {
349
505
  if (wsOverlay) wsOverlay.classList.remove('open');
350
- // Sync indicator on dock buttons
351
506
  document.querySelectorAll('[data-wspane]').forEach(function (btn) {
352
507
  btn.classList.remove('active');
353
508
  });
354
509
  }
355
510
 
356
- // ── Focus mode (spotlight on recently edited) ─────────────────────────
511
+ // ── Focus mode ────────────────────────────────────────────────────────
357
512
  var focusedEl = null;
358
513
  var focusOrigStyle = null;
359
514
 
@@ -368,11 +523,9 @@
368
523
 
369
524
  function enterFocusMode(target) {
370
525
  if (focusedEl) return;
371
-
372
526
  var dim = document.getElementById('xfce-focus-dim') || buildFocusDim();
373
527
  var rect = target.getBoundingClientRect();
374
528
 
375
- // Capture current computed inline style so we can fully restore it
376
529
  focusOrigStyle = {
377
530
  position: target.style.position || '',
378
531
  left: target.style.left || '',
@@ -391,7 +544,6 @@
391
544
  var maxH = Math.round(window.innerHeight * 0.86);
392
545
  var tl = Math.round((window.innerWidth - tw) / 2);
393
546
 
394
- // Fix position and set target width — let height be auto so it fits content
395
547
  target.style.transition = 'none';
396
548
  target.style.position = 'fixed';
397
549
  target.style.left = rect.left + 'px';
@@ -403,11 +555,9 @@
403
555
  target.style.overflow = 'auto';
404
556
  target.classList.add('xfce-in-focus');
405
557
 
406
- // Reflow so the browser computes auto height at the new width
407
558
  var actualH = Math.min(target.scrollHeight, maxH);
408
559
  var tt = Math.round((window.innerHeight - actualH) / 2);
409
560
 
410
- // Animate only position — height stays auto (content-driven)
411
561
  target.style.transition = [
412
562
  'left .32s cubic-bezier(.34,1.15,.64,1)',
413
563
  'top .32s cubic-bezier(.34,1.15,.64,1)',
@@ -430,7 +580,6 @@
430
580
  if (dim) dim.classList.remove('active');
431
581
  document.removeEventListener('keydown', onFocusKey);
432
582
 
433
- // Fade out, restore, fade back in
434
583
  target.style.transition = 'opacity .18s';
435
584
  target.style.opacity = '0';
436
585
 
@@ -493,7 +642,6 @@
493
642
  });
494
643
  dockInner.appendChild(navGroup);
495
644
 
496
- // Separator
497
645
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
498
646
 
499
647
  // Collections group (populated by /api/info)
@@ -501,10 +649,9 @@
501
649
  colGroup.id = 'xfce-dock-cols';
502
650
  dockInner.appendChild(colGroup);
503
651
 
504
- // Separator
505
652
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
506
653
 
507
- // Workspace group: Notes + To-do (open overlay)
654
+ // Workspace group
508
655
  var wsGroup = el('div', 'xfce-dock-group');
509
656
  WORKSPACE.forEach(function (w) {
510
657
  var btn = makeDockItem(w.icon, w.label, null, false, true);
@@ -517,10 +664,9 @@
517
664
  });
518
665
  dockInner.appendChild(wsGroup);
519
666
 
520
- // Separator
521
667
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
522
668
 
523
- // Tools popup button (Schema, Build, Import)
669
+ // Tools popup button
524
670
  var toolsActive = TOOLS.some(function (t) { return t.key === page; });
525
671
  var toolsBtn = makeDockItem('⚒', 'Tools', null, toolsActive, true);
526
672
  toolsBtn.id = 'xfce-tools-btn';
@@ -530,16 +676,15 @@
530
676
  });
531
677
  dockInner.appendChild(toolsBtn);
532
678
 
533
- // Separator
534
679
  dockInner.appendChild(el('div', 'xfce-dock-sep'));
535
680
 
536
- // HUD toggle button
681
+ // HUD toggle
537
682
  var hudBtn = makeDockItem('▣', 'HUD', null, false, true);
538
683
  hudBtn.addEventListener('click', toggleHUD);
539
684
  dockInner.appendChild(hudBtn);
540
685
 
541
686
  // Scheme toggle
542
- var schemeBtn = document.getElementById('scheme-toggle');
687
+ var schemeBtn = document.getElementById('scheme-toggle');
543
688
  var schemeClone = makeDockItem('◐', 'Scheme', null, false, true);
544
689
  schemeClone.addEventListener('click', function () {
545
690
  if (schemeBtn) schemeBtn.click();
@@ -551,8 +696,6 @@
551
696
 
552
697
  document.body.appendChild(dock);
553
698
 
554
- // Any click inside the dock exits focus mode (capture phase runs
555
- // before stopPropagation on individual buttons can block it)
556
699
  dock.addEventListener('click', function () {
557
700
  if (focusedEl) exitFocusMode();
558
701
  }, true);
@@ -584,7 +727,7 @@
584
727
  .then(function (info) {
585
728
  if (!info) return;
586
729
 
587
- // Site name
730
+ // Site name in status bar
588
731
  var siteEl = document.getElementById('xfce-sb-site');
589
732
  if (siteEl) siteEl.textContent = info.siteName || info.podPath.split('/').pop().replace('.pod', '');
590
733
 
@@ -592,7 +735,7 @@
592
735
  var colGroup = document.getElementById('xfce-dock-cols');
593
736
  if (colGroup) {
594
737
  var topLevel = (info.collections || []).filter(function (c) { return !c.parent; });
595
- topLevel.forEach(function (col) {
738
+ topLevel.forEach(function (col, idx) {
596
739
  var isSingleton = !!col.singleton;
597
740
  var href = isSingleton
598
741
  ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
@@ -600,12 +743,32 @@
600
743
  var isActive = isSingleton
601
744
  ? page === 'editor' && activeCol === col.id
602
745
  : page === 'entries' && activeCol === col.id;
603
- // Abbreviation icon (first char of label)
604
746
  var abbr = col.label.substring(0, 2);
605
747
  var item = makeDockItem(abbr, col.label, href, isActive, false);
606
748
  item.querySelector('.xfce-dock-icon').style.cssText = 'font-size:9px;font-family:var(--mono);letter-spacing:-.02em;line-height:1;';
749
+ item.dataset.dockIdx = idx + 1;
750
+
751
+ // Draft badge
752
+ if (col.drafts > 0) {
753
+ var badge = el('span', 'xfce-dock-badge');
754
+ badge.textContent = col.drafts;
755
+ badge.title = col.drafts + ' draft' + (col.drafts === 1 ? '' : 's');
756
+ item.appendChild(badge);
757
+ }
758
+
607
759
  colGroup.appendChild(item);
760
+
761
+ // Add to palette
762
+ _palItems.push({ icon: abbr, label: col.label, href: href, group: 'Collections' });
608
763
  });
764
+
765
+ // Quick-create: + button when viewing a collection's entries
766
+ if (page === 'entries' && activeCol) {
767
+ var createHref = '/editor.html?collection=' + encodeURIComponent(activeCol);
768
+ var createBtn = makeDockItem('+', 'New entry', createHref, false, false);
769
+ createBtn.classList.add('xfce-dock-create');
770
+ colGroup.appendChild(createBtn);
771
+ }
609
772
  }
610
773
 
611
774
  // HUD pod section
@@ -613,11 +776,11 @@
613
776
  if (hudPod) {
614
777
  var total = (info.collections || []).reduce(function (s, c) { return s + (c.total || 0); }, 0);
615
778
  hudPod.innerHTML = [
616
- hudRow('File', info.podPath.split('/').pop()),
617
- hudRow('Format', 'v' + info.formatVersion),
618
- hudRow('Admin', 'v' + info.adminVersion),
779
+ hudRow('File', info.podPath.split('/').pop()),
780
+ hudRow('Format', 'v' + info.formatVersion),
781
+ hudRow('Admin', 'v' + info.adminVersion),
619
782
  hudRow('Collections', info.collections.length),
620
- hudRow('Entries', total),
783
+ hudRow('Published', total),
621
784
  ].join('');
622
785
  }
623
786
 
@@ -625,7 +788,8 @@
625
788
  var hudCols = document.getElementById('xfce-hud-cols');
626
789
  if (hudCols) {
627
790
  hudCols.innerHTML = (info.collections || []).map(function (col) {
628
- return hudRow(col.label, col.total + ' entr' + (col.total === 1 ? 'y' : 'ies'));
791
+ var draftTxt = col.drafts > 0 ? ', ' + col.drafts + ' draft' + (col.drafts === 1 ? '' : 's') : '';
792
+ return hudRow(col.label, col.total + ' published' + draftTxt);
629
793
  }).join('');
630
794
  }
631
795
  })
@@ -641,18 +805,15 @@
641
805
  })
642
806
  .catch(function () {});
643
807
 
644
- // User from topbar (wait for other scripts to populate it)
645
- setTimeout(function () {
646
- var topbarUser = document.getElementById('topbar-user');
647
- var sbUser = document.getElementById('xfce-sb-user');
648
- if (sbUser && topbarUser) {
649
- var observer = new MutationObserver(function () {
650
- sbUser.textContent = topbarUser.textContent;
651
- });
652
- observer.observe(topbarUser, { childList: true, characterData: true, subtree: true });
653
- sbUser.textContent = topbarUser.textContent;
654
- }
655
- }, 300);
808
+ // Current user
809
+ fetch('/api/auth/me', { credentials: 'include' })
810
+ .then(function (r) { return r.ok ? r.json() : null; })
811
+ .then(function (d) {
812
+ if (!d || !d.user) return;
813
+ var sbUser = document.getElementById('xfce-sb-user');
814
+ if (sbUser) sbUser.textContent = d.user.username;
815
+ })
816
+ .catch(function () {});
656
817
  }
657
818
 
658
819
  function hudRow(label, value) {
@@ -663,18 +824,44 @@
663
824
  function bindKeys() {
664
825
  document.addEventListener('keydown', function (e) {
665
826
  var mod = e.metaKey || e.ctrlKey;
666
- if (!mod || !e.shiftKey) return;
667
827
 
668
- if (e.key === 'd' || e.key === 'D') {
828
+ // ⌘K command palette
829
+ if (mod && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
830
+ e.preventDefault();
831
+ if (palette && palette.classList.contains('open')) closePalette();
832
+ else openPalette();
833
+ return;
834
+ }
835
+
836
+ // ⌘⇧D — toggle HUD
837
+ if (mod && e.shiftKey && (e.key === 'd' || e.key === 'D')) {
669
838
  e.preventDefault();
670
839
  toggleHUD();
840
+ return;
671
841
  }
672
842
 
673
- // ⌘⇧L — cycle back to glass
674
- if (e.key === 'l' || e.key === 'L') {
843
+ // ⌘⇧L — switch back to glass mode
844
+ if (mod && e.shiftKey && (e.key === 'l' || e.key === 'L')) {
675
845
  e.preventDefault();
676
846
  localStorage.setItem('orb_style', 'glass');
677
847
  location.reload();
848
+ return;
849
+ }
850
+
851
+ // 1–9 — jump to nth dock link (no modifier, not in input)
852
+ if (!mod && !e.shiftKey && !e.altKey && !isEditing(e.target)) {
853
+ var n = parseInt(e.key);
854
+ if (n >= 1 && n <= 9) {
855
+ var links = dockInner
856
+ ? Array.from(dockInner.querySelectorAll('a.xfce-dock-item')).filter(function (a) {
857
+ return !a.classList.contains('xfce-dock-create');
858
+ })
859
+ : [];
860
+ if (links[n - 1]) {
861
+ e.preventDefault();
862
+ location.href = links[n - 1].href;
863
+ }
864
+ }
678
865
  }
679
866
  });
680
867
  }
@@ -684,9 +871,11 @@
684
871
  buildStatusBar();
685
872
  buildMetaPanel();
686
873
  buildDock();
874
+ buildToastHost();
687
875
  loadInfo();
688
876
  bindKeys();
689
877
  initFocusMode();
878
+ observeSavedFlash();
690
879
  }
691
880
 
692
881
  if (document.readyState === 'loading') {
@@ -16,10 +16,11 @@ infoRoutes.get('/', (c) => {
16
16
  const podPath = c.get('podPath');
17
17
  const db = openPod(podPath);
18
18
  const cols = db.getCollections().map(col => ({
19
- id: col.id,
20
- label: col.label,
21
- total: db.getEntries(col.id).length,
22
- parent: db.getMeta(`collection.${col.id}.parent`) ?? null,
19
+ id: col.id,
20
+ label: col.label,
21
+ total: db.getEntries(col.id, { status: 'published' }).length,
22
+ drafts: db.getEntries(col.id, { status: 'draft' }).length,
23
+ parent: db.getMeta(`collection.${col.id}.parent`) ?? null,
23
24
  singleton: !!col.singleton,
24
25
  }));
25
26
  const version = db.getMeta('format_version') ?? '1';