@a83/orbiter-admin 0.3.35 → 0.3.37

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.35",
3
+ "version": "0.3.37",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2046,73 +2046,73 @@ a.xfce-sb-logo:hover { opacity: .8; }
2046
2046
  opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto;
2047
2047
  }
2048
2048
 
2049
- /* ── Orbiter Terminal ────────────────────────────────────── */
2050
- .xfce-terminal {
2049
+ /* ── Command Palette ─────────────────────────────────────── */
2050
+ .xfce-palette {
2051
2051
  display: none; position: fixed; inset: 0; z-index: 10000;
2052
2052
  background: rgba(0,0,0,0);
2053
2053
  align-items: flex-end; justify-content: center;
2054
2054
  padding-bottom: calc(var(--dock-h, 76px) + 20px);
2055
2055
  transition: background .18s;
2056
2056
  }
2057
- .xfce-terminal.open {
2057
+ .xfce-palette.open {
2058
2058
  display: flex;
2059
2059
  background: rgba(0,0,0,.28);
2060
2060
  }
2061
2061
 
2062
- .xfce-term-inner {
2063
- width: min(680px, 92vw); max-height: min(480px, 60vh);
2064
- background: #0a0a14;
2065
- border: 1px solid rgba(120,90,210,.25);
2066
- border-top: 2px solid var(--accent, #7c6fcd);
2062
+ .xfce-palette-inner {
2063
+ width: min(560px, 92vw);
2064
+ background: var(--glass-bg, color-mix(in srgb, var(--bg1) 90%, transparent));
2065
+ border: 1px solid var(--line);
2067
2066
  border-radius: 12px;
2068
- box-shadow: 0 -2px 48px rgba(0,0,0,.5);
2067
+ box-shadow: 0 -2px 32px rgba(0,0,0,.25), 0 0 0 1px color-mix(in srgb,var(--accent) 20%,transparent);
2069
2068
  overflow: hidden;
2070
- display: flex; flex-direction: column;
2071
- transform: translateY(14px); opacity: 0;
2069
+ transform: translateY(14px);
2070
+ opacity: 0;
2072
2071
  transition: transform .2s cubic-bezier(.34,1.15,.64,1), opacity .15s;
2073
2072
  }
2074
- .xfce-terminal.open .xfce-term-inner {
2075
- transform: translateY(0); opacity: 1;
2073
+ .xfce-palette.open .xfce-palette-inner {
2074
+ transform: translateY(0);
2075
+ opacity: 1;
2076
2076
  }
2077
2077
 
2078
- .xfce-term-bar {
2079
- display: flex; align-items: center; justify-content: space-between;
2080
- padding: 8px 14px; border-bottom: 1px solid rgba(255,255,255,.06); flex-shrink: 0;
2081
- }
2082
- .xfce-term-title {
2083
- font-family: var(--mono); font-size: 9px; letter-spacing: .12em;
2084
- text-transform: uppercase; color: var(--accent, #7c6fcd);
2078
+ .xfce-palette-bar {
2079
+ display: flex; align-items: center; gap: 8px;
2080
+ padding: 10px 14px; border-bottom: 1px solid var(--line);
2085
2081
  }
2086
- .xfce-term-esc {
2087
- font-family: var(--mono); font-size: 8px; color: rgba(150,150,200,.5);
2088
- background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.1);
2089
- border-radius: 3px; padding: 1px 5px;
2082
+ .xfce-palette-cmd { color: var(--accent); font-size: 13px; flex-shrink: 0; }
2083
+ .xfce-palette-inp {
2084
+ flex: 1; background: none; border: none; outline: none;
2085
+ font-family: var(--mono); font-size: 13px; color: var(--heading);
2090
2086
  }
2087
+ .xfce-palette-inp::placeholder { color: var(--muted); }
2088
+ .xfce-palette-hint { font-size: 8px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
2091
2089
 
2092
- .xfce-term-output {
2093
- flex: 1; overflow-y: auto; padding: 10px 0;
2094
- font-family: var(--mono); font-size: 11px; line-height: 1.7;
2090
+ .xfce-palette-results {
2091
+ max-height: 360px; overflow-y: auto; padding: 6px 0;
2095
2092
  }
2096
- .xfce-term-line { padding: 0 16px; color: rgba(200,200,230,.75); white-space: pre; }
2097
- .xfce-term-cmd { color: rgba(200,200,230,.9); }
2098
- .xfce-term-ok { color: #5dc97e; }
2099
- .xfce-term-err { color: #e06c6c; }
2100
- .xfce-term-dim { color: rgba(150,150,190,.6); }
2101
- .xfce-term-muted { color: rgba(120,120,170,.45); }
2102
-
2103
- .xfce-term-input-row {
2104
- display: flex; align-items: center; gap: 8px;
2105
- padding: 10px 16px; border-top: 1px solid rgba(255,255,255,.06); flex-shrink: 0;
2093
+ .xfce-pal-group {
2094
+ font-size: 8px; font-family: var(--mono); color: var(--muted);
2095
+ letter-spacing: .08em; text-transform: uppercase;
2096
+ padding: 8px 14px 4px;
2106
2097
  }
2107
- .xfce-term-prompt {
2108
- font-family: var(--mono); font-size: 11px;
2109
- color: var(--accent, #7c6fcd); white-space: nowrap; flex-shrink: 0;
2098
+ .xfce-pal-item {
2099
+ display: flex; align-items: center; gap: 10px;
2100
+ padding: 7px 14px; cursor: pointer; transition: background .1s;
2110
2101
  }
2111
- .xfce-term-input {
2112
- flex: 1; background: none; border: none; outline: none;
2113
- font-family: var(--mono); font-size: 11px; color: rgba(220,220,255,.9);
2114
- caret-color: var(--accent, #7c6fcd);
2102
+ .xfce-pal-item:hover, .xfce-pal-item.pal-active {
2103
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
2115
2104
  }
2105
+ .xfce-pal-icon { width: 20px; text-align: center; font-size: 13px; color: var(--accent); flex-shrink: 0; }
2106
+ .xfce-pal-label { font-size: 12px; color: var(--heading); font-family: var(--mono); flex: 1; }
2107
+ .xfce-pal-hint-r { font-size: 10px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
2108
+ .xfce-pal-empty { padding: 20px 14px; color: var(--muted); font-size: 11px; font-family: var(--mono); }
2109
+ .xfce-pal-output { padding: 8px 14px; font-size: 11px; font-family: var(--mono); color: var(--body); line-height: 1.6; }
2110
+ .xfce-pal-output code { color: var(--accent); }
2111
+ .xfce-pal-dim { color: var(--muted); }
2112
+ .xfce-pal-ok { color: var(--success, #5dc97e); }
2113
+ .xfce-pal-err { color: var(--danger, #e06c6c); }
2114
+ .xfce-pal-muted { color: var(--muted); opacity: .7; }
2115
+ .xfce-pal-cmd-hint { color: var(--accent); }
2116
2116
 
2117
2117
  /* ── Toast host (above dock) ─────────────────────────────── */
2118
2118
  .xfce-toast-host {
package/public/xfce.js CHANGED
@@ -28,8 +28,11 @@
28
28
  { icon: '☑', label: 'To-do', pane: 'todos' },
29
29
  ];
30
30
 
31
- // collections loaded by /api/info — used by terminal
32
- var _termCols = [];
31
+ // palette items — nav + tools pre-seeded; collections appended after /api/info
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
+ var _termCols = []; // collection metadata for palette commands
33
36
 
34
37
  // ── Helpers ───────────────────────────────────────────────────────────
35
38
  function el(tag, cls, html) {
@@ -94,7 +97,7 @@
94
97
  // Palette trigger in status bar
95
98
  document.getElementById('xfce-sb-palette-btn').addEventListener('click', function (e) {
96
99
  e.stopPropagation();
97
- openTerminal();
100
+ openPalette();
98
101
  });
99
102
  }
100
103
 
@@ -160,7 +163,7 @@
160
163
  palBtn.addEventListener('click', function (e) {
161
164
  e.stopPropagation();
162
165
  toolsPopup.classList.remove('open');
163
- openTerminal();
166
+ openPalette();
164
167
  });
165
168
  toolsPopup.appendChild(palBtn);
166
169
  document.body.appendChild(toolsPopup);
@@ -210,227 +213,305 @@
210
213
  if (colCreateEl) colCreateEl.classList.remove('visible');
211
214
  }
212
215
 
213
- // ── Orbiter Terminal ─────────────────────────────────────────────────
214
- var terminal, termOutput, termInput;
215
- var termHistory = [], termHistIdx = -1;
216
- var TERM_CMDS = ['help','clear','info','ls','go','new','search','build','export'];
217
- var NAV_DEST = {
216
+ // ── Command Palette ───────────────────────────────────────────────────
217
+ var palette, paletteInp, paletteResults, palActive = -1;
218
+
219
+ var NAV_DEST = {
218
220
  dashboard: '/dashboard.html', media: '/media.html', settings: '/settings.html',
219
221
  users: '/users.html', schema: '/schema.html', build: '/build.html',
220
222
  import: '/import.html', account: '/account.html',
221
223
  };
222
224
 
223
- function buildTerminal() {
224
- terminal = el('div', 'xfce-terminal');
225
- terminal.id = 'xfce-terminal';
226
- terminal.innerHTML = [
227
- '<div class="xfce-term-inner">',
228
- '<div class="xfce-term-bar">',
229
- '<span class="xfce-term-title">◈ orbiter terminal</span>',
230
- '<kbd class="xfce-term-esc">ESC</kbd>',
231
- '</div>',
232
- '<div id="xfce-term-output" class="xfce-term-output"></div>',
233
- '<div class="xfce-term-input-row">',
234
- '<span class="xfce-term-prompt">orbiter $</span>',
235
- '<input id="xfce-term-input" class="xfce-term-input" autocomplete="off" spellcheck="false" />',
225
+ function buildPalette() {
226
+ palette = el('div', 'xfce-palette');
227
+ palette.id = 'xfce-palette';
228
+ palette.innerHTML = [
229
+ '<div class="xfce-palette-inner">',
230
+ '<div class="xfce-palette-bar">',
231
+ '<span class="xfce-palette-cmd">⌘</span>',
232
+ '<input id="xfce-palette-inp" class="xfce-palette-inp" placeholder="Go to page or collection… › type > for commands" autocomplete="off" spellcheck="false" />',
233
+ '<span class="xfce-palette-hint">ESC to close</span>',
236
234
  '</div>',
235
+ '<div id="xfce-palette-results" class="xfce-palette-results"></div>',
237
236
  '</div>',
238
237
  ].join('');
239
- document.body.appendChild(terminal);
238
+ document.body.appendChild(palette);
240
239
 
241
- termOutput = document.getElementById('xfce-term-output');
242
- termInput = document.getElementById('xfce-term-input');
240
+ paletteInp = document.getElementById('xfce-palette-inp');
241
+ paletteResults = document.getElementById('xfce-palette-results');
243
242
 
244
- termPrint('Orbiter Admin · type \'help\' for available commands', 'muted');
245
- termPrint('', '');
243
+ paletteInp.addEventListener('input', function () {
244
+ palActive = -1;
245
+ renderPalette(paletteInp.value);
246
+ });
246
247
 
247
- termInput.addEventListener('keydown', function (e) {
248
+ paletteInp.addEventListener('keydown', function (e) {
249
+ var val = paletteInp.value.trim();
248
250
  if (e.key === 'Enter') {
249
- var line = termInput.value.trim();
250
- termInput.value = '';
251
- if (!line) return;
252
- termHistIdx = -1;
253
- if (!termHistory.length || termHistory[0] !== line) termHistory.unshift(line);
254
- if (termHistory.length > 50) termHistory.pop();
255
- termPrint('orbiter $ ' + line, 'cmd');
256
- execCmd(line);
257
- } else if (e.key === 'ArrowUp') {
258
- e.preventDefault();
259
- if (termHistIdx < termHistory.length - 1) { termHistIdx++; termInput.value = termHistory[termHistIdx]; }
251
+ if (val.startsWith('>')) {
252
+ e.preventDefault();
253
+ execPaletteCmd(val.slice(1).trim());
254
+ } else {
255
+ var active = paletteResults.querySelector('.xfce-pal-item.pal-active');
256
+ if (active && active.dataset.href) { location.href = active.dataset.href; closePalette(); }
257
+ else {
258
+ var first = paletteResults.querySelector('.xfce-pal-item[data-href]');
259
+ if (first) { location.href = first.dataset.href; closePalette(); }
260
+ }
261
+ }
260
262
  } else if (e.key === 'ArrowDown') {
261
263
  e.preventDefault();
262
- if (termHistIdx > 0) { termHistIdx--; termInput.value = termHistory[termHistIdx]; }
263
- else { termHistIdx = -1; termInput.value = ''; }
264
- } else if (e.key === 'Tab') {
264
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
265
+ palActive = Math.min(palActive + 1, items.length - 1);
266
+ updatePalActive(items);
267
+ } else if (e.key === 'ArrowUp') {
265
268
  e.preventDefault();
266
- termTabComplete();
269
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
270
+ palActive = Math.max(palActive - 1, 0);
271
+ updatePalActive(items);
267
272
  } else if (e.key === 'Escape') {
268
- closeTerminal();
273
+ closePalette();
269
274
  }
270
275
  });
271
276
 
272
- terminal.addEventListener('click', function (e) {
273
- if (e.target === terminal) closeTerminal();
277
+ palette.addEventListener('click', function (e) {
278
+ if (e.target === palette) closePalette();
279
+ });
280
+ }
281
+
282
+ function updatePalActive(items) {
283
+ items.forEach(function (it, i) {
284
+ it.classList.toggle('pal-active', i === palActive);
285
+ if (i === palActive) it.scrollIntoView({ block: 'nearest' });
286
+ });
287
+ }
288
+
289
+ function renderPalette(q) {
290
+ q = (q || '').trim();
291
+
292
+ if (q.startsWith('>')) {
293
+ var cmd = q.slice(1).trim();
294
+ paletteResults.innerHTML = cmd
295
+ ? '<div class="xfce-pal-output xfce-pal-cmd-hint">↵ run: ' + escHtml(cmd) + '</div>'
296
+ : '<div class="xfce-pal-output xfce-pal-dim">commands: ls &nbsp;· go &lt;page&gt; &nbsp;· new &lt;col&gt; &nbsp;· search &lt;term&gt; &nbsp;· build &nbsp;· export &lt;col&gt; &nbsp;· info</div>';
297
+ return;
298
+ }
299
+
300
+ q = q.toLowerCase();
301
+ var filtered = q
302
+ ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
303
+ : _palItems;
304
+
305
+ if (!filtered.length) {
306
+ paletteResults.innerHTML = '<div class="xfce-pal-empty">No results</div>';
307
+ return;
308
+ }
309
+
310
+ var groups = {};
311
+ filtered.forEach(function (it) {
312
+ var g = it.group || 'Nav';
313
+ if (!groups[g]) groups[g] = [];
314
+ groups[g].push(it);
315
+ });
316
+
317
+ var html = '';
318
+ Object.keys(groups).forEach(function (g) {
319
+ html += '<div class="xfce-pal-group">' + g + '</div>';
320
+ groups[g].forEach(function (it) {
321
+ html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
322
+ + '<span class="xfce-pal-icon">' + it.icon + '</span>'
323
+ + '<span class="xfce-pal-label">' + escHtml(it.label) + '</span>'
324
+ + (it.meta ? '<span class="xfce-pal-hint-r">' + escHtml(it.meta) + '</span>' : '')
325
+ + '</div>';
326
+ });
327
+ });
328
+ paletteResults.innerHTML = html;
329
+
330
+ paletteResults.querySelectorAll('.xfce-pal-item[data-href]').forEach(function (item) {
331
+ item.addEventListener('click', function () {
332
+ location.href = item.dataset.href;
333
+ closePalette();
334
+ });
335
+ item.addEventListener('mouseenter', function () {
336
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
337
+ palActive = Array.from(items).indexOf(item);
338
+ updatePalActive(items);
339
+ });
274
340
  });
275
341
  }
276
342
 
277
- function termPrint(text, cls) {
278
- var line = document.createElement('div');
279
- line.className = 'xfce-term-line' + (cls ? ' xfce-term-' + cls : '');
280
- line.textContent = text;
281
- termOutput.appendChild(line);
282
- termOutput.scrollTop = termOutput.scrollHeight;
343
+ function palPrint(html, cls) {
344
+ paletteResults.insertAdjacentHTML('beforeend',
345
+ '<div class="xfce-pal-output' + (cls ? ' xfce-pal-' + cls : '') + '">' + html + '</div>');
283
346
  }
284
347
 
285
- function termClear() { termOutput.innerHTML = ''; }
348
+ function palSetItems(html) {
349
+ paletteResults.innerHTML = html;
350
+ paletteResults.querySelectorAll('.xfce-pal-item[data-href]').forEach(function (item) {
351
+ item.addEventListener('click', function () { location.href = item.dataset.href; closePalette(); });
352
+ item.addEventListener('mouseenter', function () {
353
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
354
+ palActive = Array.from(items).indexOf(item);
355
+ updatePalActive(items);
356
+ });
357
+ });
358
+ }
286
359
 
287
- function execCmd(raw) {
360
+ function execPaletteCmd(raw) {
361
+ if (!raw) return;
288
362
  var parts = raw.trim().split(/\s+/);
289
363
  var cmd = parts[0].toLowerCase();
290
364
  var args = parts.slice(1);
365
+ paletteResults.innerHTML = '';
366
+
291
367
  switch (cmd) {
292
- case 'help': termHelp(); break;
293
- case 'clear': termClear(); break;
294
- case 'info': termInfo(); break;
295
- case 'ls': termLs(args); break;
296
- case 'go': termGo(args); break;
297
- case 'new': termNew(args); break;
298
- case 'search': termSearch(args.join(' ')); break;
299
- case 'build': termBuild(); break;
300
- case 'export': termExport(args); break;
301
- default: termPrint('command not found: ' + cmd + ' (try \'help\')', 'err');
368
+ case 'help': palHelp(); break;
369
+ case 'info': palInfo(); break;
370
+ case 'ls': palLs(args); break;
371
+ case 'go': palGo(args); break;
372
+ case 'new': palNew(args); break;
373
+ case 'search': palSearch(args.join(' ')); break;
374
+ case 'build': palBuild(); break;
375
+ case 'export': palExport(args); break;
376
+ default: palPrint('unknown: <b>' + escHtml(cmd) + '</b> &mdash; try <b>&gt; help</b>', 'err');
302
377
  }
303
- termPrint('', '');
304
378
  }
305
379
 
306
- function termHelp() {
307
- termPrint('', '');
308
- [
309
- ' go <page|collection> navigate to a page or collection',
310
- ' new <collection> open editor for a new entry',
311
- ' ls [collection] list collections or recent entries',
312
- ' search <term> full-text search across all content',
313
- ' info show pod and version info',
314
- ' build trigger deploy webhook',
315
- ' export <col> [flags] download entries flags: --md --drafts',
316
- ' clear clear terminal',
317
- ' help show this message',
318
- '',
319
- ' shortcuts: ↑↓ history · Tab completion · ⌘K toggle',
320
- ].forEach(function (l) { termPrint(l, 'dim'); });
380
+ function palHelp() {
381
+ palPrint([
382
+ '<code>go &lt;page|collection&gt;</code> &mdash; navigate',
383
+ '<code>new &lt;collection&gt;</code> &mdash; new entry',
384
+ '<code>ls [collection]</code> &mdash; list collections or entries',
385
+ '<code>search &lt;term&gt;</code> &mdash; full-text search',
386
+ '<code>info</code> &mdash; pod &amp; version info',
387
+ '<code>build</code> &mdash; trigger deploy',
388
+ '<code>export &lt;col&gt; [--md] [--drafts]</code> &mdash; download',
389
+ ].join('<br>'), 'dim');
321
390
  }
322
391
 
323
- function termInfo() {
392
+ function palInfo() {
324
393
  fetch('/api/info', { credentials: 'include' })
325
394
  .then(function (r) { return r.ok ? r.json() : null; })
326
395
  .then(function (d) {
327
- if (!d) { termPrint('error fetching info', 'err'); return; }
396
+ if (!d) { palPrint('error fetching info', 'err'); return; }
328
397
  var total = (d.collections || []).reduce(function (s, c) { return s + (c.total || 0); }, 0);
329
- termPrint('', '');
330
- termPrint(' pod ' + d.podPath.split('/').pop(), 'dim');
331
- termPrint(' format v' + d.formatVersion, 'dim');
332
- termPrint(' admin v' + d.adminVersion, 'dim');
333
- termPrint(' collections ' + (d.collections || []).length, 'dim');
334
- termPrint(' published ' + total + ' entries', 'dim');
398
+ palPrint([
399
+ 'Pod: <b>' + escHtml(d.podPath.split('/').pop()) + '</b>',
400
+ 'Format: v' + d.formatVersion + ' &nbsp;· Admin: v' + d.adminVersion,
401
+ 'Collections: ' + (d.collections || []).length + ' &nbsp;· Published: ' + total,
402
+ ].join('<br>'), 'dim');
335
403
  });
336
404
  }
337
405
 
338
- function termLs(args) {
406
+ function palLs(args) {
339
407
  if (!args.length) {
340
- if (!_termCols.length) { termPrint('no collections loaded yet', 'muted'); return; }
341
- termPrint('', '');
342
- _termCols.forEach(function (col) {
343
- var info = col.total + ' entr' + (col.total === 1 ? 'y' : 'ies');
344
- if (col.drafts > 0) info += ' ' + col.drafts + ' draft' + (col.drafts === 1 ? '' : 's');
345
- termPrint(' ' + col.id.padEnd(22) + info, 'dim');
346
- });
408
+ if (!_termCols.length) { palPrint('no collections loaded yet', 'muted'); return; }
409
+ var html = _termCols.map(function (col) {
410
+ var href = col.singleton
411
+ ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
412
+ : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
413
+ var abbr = col.label.substring(0, 2).toUpperCase();
414
+ var meta = col.total + (col.total === 1 ? ' entry' : ' entries') + (col.drafts > 0 ? ', ' + col.drafts + ' draft' + (col.drafts === 1 ? '' : 's') : '');
415
+ return '<div class="xfce-pal-item" data-href="' + href + '">'
416
+ + '<span class="xfce-pal-icon">' + abbr + '</span>'
417
+ + '<span class="xfce-pal-label">' + escHtml(col.label) + '</span>'
418
+ + '<span class="xfce-pal-hint-r">' + meta + '</span>'
419
+ + '</div>';
420
+ }).join('');
421
+ palSetItems(html);
347
422
  return;
348
423
  }
349
424
  var colId = args[0];
350
- fetch('/api/collections/' + encodeURIComponent(colId) + '/entries?status=published&limit=25', { credentials: 'include' })
425
+ palPrint('loading…', 'muted');
426
+ fetch('/api/collections/' + encodeURIComponent(colId) + '/entries?status=published&limit=20', { credentials: 'include' })
351
427
  .then(function (r) { return r.ok ? r.json() : null; })
352
428
  .then(function (d) {
353
- if (!d) { termPrint('collection not found: ' + colId, 'err'); return; }
429
+ paletteResults.innerHTML = '';
430
+ if (!d) { palPrint('collection not found: ' + escHtml(colId), 'err'); return; }
354
431
  var entries = d.entries || (Array.isArray(d) ? d : []);
355
- termPrint('', '');
356
- if (!entries.length) { termPrint(' (no published entries)', 'muted'); return; }
357
- entries.forEach(function (e) {
432
+ if (!entries.length) { palPrint('(no published entries)', 'muted'); return; }
433
+ var html = entries.map(function (e) {
434
+ var href = '/editor.html?collection=' + encodeURIComponent(colId) + '&id=' + encodeURIComponent(e.id || '');
358
435
  var date = (e.updated_at || e.created_at || '').substring(0, 10);
359
- termPrint(' ' + (e.slug || e.id || '').padEnd(36) + date, 'dim');
360
- });
361
- if (d.total > entries.length) termPrint(' ' + (d.total - entries.length) + ' more', 'muted');
436
+ return '<div class="xfce-pal-item" data-href="' + href + '">'
437
+ + '<span class="xfce-pal-icon">✎</span>'
438
+ + '<span class="xfce-pal-label">' + escHtml(e.slug || e.id || '') + '</span>'
439
+ + '<span class="xfce-pal-hint-r">' + date + '</span>'
440
+ + '</div>';
441
+ }).join('');
442
+ palSetItems(html);
443
+ if (d.total > entries.length) palPrint('… ' + (d.total - entries.length) + ' more', 'muted');
362
444
  });
363
445
  }
364
446
 
365
- function termGo(args) {
366
- if (!args.length) { termPrint('usage: go <page|collection>', 'err'); return; }
447
+ function palGo(args) {
448
+ if (!args.length) { palPrint('usage: go &lt;page|collection&gt;', 'err'); return; }
367
449
  var dest = args[0].toLowerCase();
368
- if (NAV_DEST[dest]) {
369
- termPrint('→ ' + dest, 'ok');
370
- closeTerminal();
371
- setTimeout(function () { location.href = NAV_DEST[dest]; }, 180);
372
- return;
373
- }
450
+ if (NAV_DEST[dest]) { closePalette(); setTimeout(function () { location.href = NAV_DEST[dest]; }, 120); return; }
374
451
  var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
375
452
  if (col) {
376
- termPrint('→ ' + col.label, 'ok');
377
- closeTerminal();
453
+ closePalette();
378
454
  var href = col.singleton
379
455
  ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
380
456
  : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
381
- setTimeout(function () { location.href = href; }, 180);
457
+ setTimeout(function () { location.href = href; }, 120);
382
458
  return;
383
459
  }
384
- termPrint('not found: ' + dest, 'err');
460
+ palPrint('not found: ' + escHtml(dest), 'err');
385
461
  }
386
462
 
387
- function termNew(args) {
388
- if (!args.length) { termPrint('usage: new <collection>', 'err'); return; }
463
+ function palNew(args) {
464
+ if (!args.length) { palPrint('usage: new &lt;collection&gt;', 'err'); return; }
389
465
  var dest = args[0].toLowerCase();
390
466
  var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
391
- if (!col) { termPrint('collection not found: ' + dest, 'err'); return; }
392
- termPrint('→ new entry in ' + col.label, 'ok');
393
- closeTerminal();
394
- setTimeout(function () { location.href = '/editor.html?collection=' + encodeURIComponent(col.id); }, 180);
467
+ if (!col) { palPrint('collection not found: ' + escHtml(dest), 'err'); return; }
468
+ closePalette();
469
+ setTimeout(function () { location.href = '/editor.html?collection=' + encodeURIComponent(col.id); }, 120);
395
470
  }
396
471
 
397
- function termSearch(term) {
398
- if (!term) { termPrint('usage: search <term>', 'err'); return; }
399
- termPrint('searching…', 'muted');
472
+ function palSearch(term) {
473
+ if (!term) { palPrint('usage: search &lt;term&gt;', 'err'); return; }
474
+ palPrint('searching…', 'muted');
400
475
  fetch('/api/search?q=' + encodeURIComponent(term), { credentials: 'include' })
401
476
  .then(function (r) { return r.ok ? r.json() : null; })
402
477
  .then(function (d) {
403
- if (!d) { termPrint('search error', 'err'); return; }
478
+ paletteResults.innerHTML = '';
479
+ if (!d) { palPrint('search error', 'err'); return; }
404
480
  var results = d.results || (Array.isArray(d) ? d : []);
405
- termPrint('', '');
406
- if (!results.length) { termPrint(' no results for "' + term + '"', 'muted'); return; }
407
- results.slice(0, 15).forEach(function (r) {
408
- termPrint(' › ' + (r.collection || '') + '/' + (r.slug || r.id || ''), 'ok');
409
- if (r.title) termPrint(' ' + r.title, 'dim');
410
- });
411
- if (results.length > 15) termPrint(' ' + (results.length - 15) + ' more', 'muted');
481
+ if (!results.length) { palPrint('no results for “' + escHtml(term) + '”', 'muted'); return; }
482
+ var html = results.slice(0, 15).map(function (r) {
483
+ var href = '/editor.html?collection=' + encodeURIComponent(r.collection || '') + '&id=' + encodeURIComponent(r.id || '');
484
+ return '<div class="xfce-pal-item" data-href="' + href + '">'
485
+ + '<span class="xfce-pal-icon">⌕</span>'
486
+ + '<span class="xfce-pal-label">' + escHtml(r.title || r.slug || r.id || '') + '</span>'
487
+ + '<span class="xfce-pal-hint-r">' + escHtml(r.collection || '') + '</span>'
488
+ + '</div>';
489
+ }).join('');
490
+ palSetItems(html);
491
+ if (results.length > 15) palPrint('… ' + (results.length - 15) + ' more', 'muted');
412
492
  });
413
493
  }
414
494
 
415
- function termBuild() {
416
- termPrint('triggering build…', 'muted');
495
+ function palBuild() {
496
+ palPrint('triggering build…', 'muted');
417
497
  fetch('/api/build', { method: 'POST', credentials: 'include' })
418
498
  .then(function (r) { return r.json(); })
419
499
  .then(function (d) {
420
- if (d.ok || d.message) termPrint('' + (d.message || 'build triggered'), 'ok');
421
- else termPrint('build: ' + (d.error || JSON.stringify(d)), 'err');
500
+ paletteResults.innerHTML = '';
501
+ if (d.ok || d.message) { palPrint(' ' + (d.message || 'build triggered'), 'ok'); setTimeout(closePalette, 900); }
502
+ else palPrint('build error: ' + escHtml(d.error || JSON.stringify(d)), 'err');
422
503
  })
423
- .catch(function () { termPrint('build request failed', 'err'); });
504
+ .catch(function () { paletteResults.innerHTML = ''; palPrint('build request failed', 'err'); });
424
505
  }
425
506
 
426
- function termExport(args) {
427
- if (!args.length) { termPrint('usage: export <collection> [--md] [--drafts]', 'err'); return; }
507
+ function palExport(args) {
508
+ if (!args.length) { palPrint('usage: export &lt;collection&gt; [--md] [--drafts]', 'err'); return; }
428
509
  var colId = args[0];
429
510
  var format = args.indexOf('--md') !== -1 ? 'md' : 'json';
430
511
  var drafts = args.indexOf('--drafts') !== -1 ? '1' : '0';
431
512
  var col = _termCols.find(function (c) { return c.id === colId; });
432
- if (!col) { termPrint('collection not found: ' + colId, 'err'); return; }
433
- termPrint('exporting ' + col.label + ' as ' + format + '…', 'muted');
513
+ if (!col) { palPrint('collection not found: ' + escHtml(colId), 'err'); return; }
514
+ palPrint('exporting…', 'muted');
434
515
  fetch('/api/terminal/export?col=' + encodeURIComponent(colId) + '&format=' + format + '&drafts=' + drafts, { credentials: 'include' })
435
516
  .then(function (r) {
436
517
  if (!r.ok) return r.json().then(function (e) { throw new Error(e.error || r.status); });
@@ -439,48 +520,37 @@
439
520
  a.href = URL.createObjectURL(blob);
440
521
  a.download = colId + '.' + format;
441
522
  document.body.appendChild(a); a.click(); a.remove();
442
- termPrint('✓ downloaded ' + colId + '.' + format, 'ok');
523
+ paletteResults.innerHTML = '';
524
+ palPrint('✓ downloaded ' + colId + '.' + format, 'ok');
525
+ setTimeout(closePalette, 900);
443
526
  });
444
527
  })
445
- .catch(function (err) { termPrint('export failed: ' + err.message, 'err'); });
528
+ .catch(function (err) { paletteResults.innerHTML = ''; palPrint('export failed: ' + escHtml(err.message), 'err'); });
446
529
  }
447
530
 
448
- function termTabComplete() {
449
- var val = termInput.value;
450
- var parts = val.split(/\s+/);
451
- if (parts.length === 1) {
452
- var p = parts[0].toLowerCase();
453
- var m = TERM_CMDS.filter(function (c) { return c.startsWith(p); });
454
- if (m.length === 1) termInput.value = m[0] + ' ';
455
- else if (m.length > 1) termPrint(m.join(' '), 'muted');
456
- } else if (parts.length === 2) {
457
- var cmd2 = parts[0].toLowerCase(), p2 = parts[1].toLowerCase();
458
- var pool = _termCols.map(function (c) { return c.id; });
459
- if (cmd2 === 'go') pool = pool.concat(Object.keys(NAV_DEST));
460
- if (['go','new','ls','export'].indexOf(cmd2) !== -1) {
461
- var m2 = pool.filter(function (c) { return c.startsWith(p2); });
462
- if (m2.length === 1) termInput.value = parts[0] + ' ' + m2[0];
463
- else if (m2.length > 1) termPrint(m2.join(' '), 'muted');
464
- }
465
- }
531
+ function escHtml(s) {
532
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
466
533
  }
467
534
 
468
- function openTerminal() {
469
- if (!terminal) buildTerminal();
470
- terminal.style.display = 'flex';
535
+ function openPalette() {
536
+ if (!palette) buildPalette();
537
+ palette.style.display = 'flex';
538
+ paletteInp.value = '';
539
+ palActive = -1;
540
+ renderPalette('');
471
541
  requestAnimationFrame(function () {
472
542
  requestAnimationFrame(function () {
473
- terminal.classList.add('open');
474
- setTimeout(function () { termInput.focus(); }, 40);
543
+ palette.classList.add('open');
544
+ setTimeout(function () { paletteInp.focus(); }, 40);
475
545
  });
476
546
  });
477
547
  }
478
548
 
479
- function closeTerminal() {
480
- if (!terminal) return;
481
- terminal.classList.remove('open');
549
+ function closePalette() {
550
+ if (!palette) return;
551
+ palette.classList.remove('open');
482
552
  setTimeout(function () {
483
- if (!terminal.classList.contains('open')) terminal.style.display = 'none';
553
+ if (!palette.classList.contains('open')) palette.style.display = 'none';
484
554
  }, 200);
485
555
  }
486
556
 
@@ -968,7 +1038,8 @@
968
1038
 
969
1039
  colGroup.appendChild(item);
970
1040
 
971
- // Add to terminal collection list
1041
+ // Add to palette fuzzy search and command list
1042
+ _palItems.push({ icon: abbr, label: col.label, href: href, group: 'Collections' });
972
1043
  _termCols.push({ id: col.id, label: col.label, total: col.total || 0, drafts: col.drafts || 0, singleton: !!col.singleton });
973
1044
  });
974
1045
  }
@@ -1024,14 +1095,21 @@
1024
1095
 
1025
1096
  // ── Keyboard shortcuts ────────────────────────────────────────────────
1026
1097
  function bindKeys() {
1098
+ // Capture-phase ⌘K: fires before admin-utils.js bubble-phase listener, stops it
1099
+ document.addEventListener('keydown', function (e) {
1100
+ if ((e.metaKey || e.ctrlKey) && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
1101
+ e.preventDefault();
1102
+ e.stopImmediatePropagation();
1103
+ if (palette && palette.classList.contains('open')) closePalette();
1104
+ else openPalette();
1105
+ }
1106
+ }, true);
1107
+
1027
1108
  document.addEventListener('keydown', function (e) {
1028
1109
  var mod = e.metaKey || e.ctrlKey;
1029
1110
 
1030
- // ⌘K terminal
1111
+ // ⌘K handled above in capture phase
1031
1112
  if (mod && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
1032
- e.preventDefault();
1033
- if (terminal && terminal.classList.contains('open')) closeTerminal();
1034
- else openTerminal();
1035
1113
  return;
1036
1114
  }
1037
1115