@a83/orbiter-admin 0.3.34 → 0.3.36

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.34",
3
+ "version": "0.3.36",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2102,9 +2102,17 @@ a.xfce-sb-logo:hover { opacity: .8; }
2102
2102
  .xfce-pal-item:hover, .xfce-pal-item.pal-active {
2103
2103
  background: color-mix(in srgb, var(--accent) 12%, transparent);
2104
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); }
2107
- .xfce-pal-empty { padding: 20px 14px; color: var(--muted); font-size: 11px; font-family: var(--mono); }
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); }
2108
2116
 
2109
2117
  /* ── Toast host (above dock) ─────────────────────────────── */
2110
2118
  .xfce-toast-host {
package/public/xfce.js CHANGED
@@ -28,10 +28,11 @@
28
28
  { icon: '☑', label: 'To-do', pane: 'todos' },
29
29
  ];
30
30
 
31
- // palette items populated after /api/info loads
31
+ // palette items nav + tools pre-seeded; collections appended after /api/info
32
32
  var _palItems = NAV.concat(TOOLS).map(function (n) {
33
33
  return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1 } ? 'Tools' : 'Nav' };
34
34
  });
35
+ var _termCols = []; // collection metadata for palette commands
35
36
 
36
37
  // ── Helpers ───────────────────────────────────────────────────────────
37
38
  function el(tag, cls, html) {
@@ -215,6 +216,12 @@
215
216
  // ── Command Palette ───────────────────────────────────────────────────
216
217
  var palette, paletteInp, paletteResults, palActive = -1;
217
218
 
219
+ var NAV_DEST = {
220
+ dashboard: '/dashboard.html', media: '/media.html', settings: '/settings.html',
221
+ users: '/users.html', schema: '/schema.html', build: '/build.html',
222
+ import: '/import.html', account: '/account.html',
223
+ };
224
+
218
225
  function buildPalette() {
219
226
  palette = el('div', 'xfce-palette');
220
227
  palette.id = 'xfce-palette';
@@ -222,7 +229,7 @@
222
229
  '<div class="xfce-palette-inner">',
223
230
  '<div class="xfce-palette-bar">',
224
231
  '<span class="xfce-palette-cmd">⌘</span>',
225
- '<input id="xfce-palette-inp" class="xfce-palette-inp" placeholder="Go to page or collection…" autocomplete="off" spellcheck="false" />',
232
+ '<input id="xfce-palette-inp" class="xfce-palette-inp" placeholder="Go to page or collection… › type > for commands" autocomplete="off" spellcheck="false" />',
226
233
  '<span class="xfce-palette-hint">ESC to close</span>',
227
234
  '</div>',
228
235
  '<div id="xfce-palette-results" class="xfce-palette-results"></div>',
@@ -239,22 +246,29 @@
239
246
  });
240
247
 
241
248
  paletteInp.addEventListener('keydown', function (e) {
242
- var items = paletteResults.querySelectorAll('.xfce-pal-item');
243
- if (e.key === 'ArrowDown') {
249
+ var val = paletteInp.value.trim();
250
+ if (e.key === 'Enter') {
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
+ }
262
+ } else if (e.key === 'ArrowDown') {
244
263
  e.preventDefault();
264
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
245
265
  palActive = Math.min(palActive + 1, items.length - 1);
246
266
  updatePalActive(items);
247
267
  } else if (e.key === 'ArrowUp') {
248
268
  e.preventDefault();
269
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
249
270
  palActive = Math.max(palActive - 1, 0);
250
271
  updatePalActive(items);
251
- } else if (e.key === 'Enter') {
252
- var active = paletteResults.querySelector('.xfce-pal-item.pal-active');
253
- if (active) { location.href = active.dataset.href; closePalette(); }
254
- else {
255
- var first = paletteResults.querySelector('.xfce-pal-item');
256
- if (first) { location.href = first.dataset.href; closePalette(); }
257
- }
258
272
  } else if (e.key === 'Escape') {
259
273
  closePalette();
260
274
  }
@@ -273,7 +287,17 @@
273
287
  }
274
288
 
275
289
  function renderPalette(q) {
276
- q = (q || '').toLowerCase().trim();
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();
277
301
  var filtered = q
278
302
  ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
279
303
  : _palItems;
@@ -283,11 +307,11 @@
283
307
  return;
284
308
  }
285
309
 
286
- // Group items
287
310
  var groups = {};
288
311
  filtered.forEach(function (it) {
289
- if (!groups[it.group]) groups[it.group] = [];
290
- groups[it.group].push(it);
312
+ var g = it.group || 'Nav';
313
+ if (!groups[g]) groups[g] = [];
314
+ groups[g].push(it);
291
315
  });
292
316
 
293
317
  var html = '';
@@ -296,25 +320,218 @@
296
320
  groups[g].forEach(function (it) {
297
321
  html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
298
322
  + '<span class="xfce-pal-icon">' + it.icon + '</span>'
299
- + '<span class="xfce-pal-label">' + it.label + '</span>'
323
+ + '<span class="xfce-pal-label">' + escHtml(it.label) + '</span>'
324
+ + (it.meta ? '<span class="xfce-pal-hint-r">' + escHtml(it.meta) + '</span>' : '')
300
325
  + '</div>';
301
326
  });
302
327
  });
303
328
  paletteResults.innerHTML = html;
304
329
 
305
- paletteResults.querySelectorAll('.xfce-pal-item').forEach(function (item) {
330
+ paletteResults.querySelectorAll('.xfce-pal-item[data-href]').forEach(function (item) {
306
331
  item.addEventListener('click', function () {
307
332
  location.href = item.dataset.href;
308
333
  closePalette();
309
334
  });
310
335
  item.addEventListener('mouseenter', function () {
311
- var items = paletteResults.querySelectorAll('.xfce-pal-item');
336
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
337
+ palActive = Array.from(items).indexOf(item);
338
+ updatePalActive(items);
339
+ });
340
+ });
341
+ }
342
+
343
+ function palPrint(html, cls) {
344
+ paletteResults.insertAdjacentHTML('beforeend',
345
+ '<div class="xfce-pal-output' + (cls ? ' xfce-pal-' + cls : '') + '">' + html + '</div>');
346
+ }
347
+
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]');
312
354
  palActive = Array.from(items).indexOf(item);
313
355
  updatePalActive(items);
314
356
  });
315
357
  });
316
358
  }
317
359
 
360
+ function execPaletteCmd(raw) {
361
+ if (!raw) return;
362
+ var parts = raw.trim().split(/\s+/);
363
+ var cmd = parts[0].toLowerCase();
364
+ var args = parts.slice(1);
365
+ paletteResults.innerHTML = '';
366
+
367
+ switch (cmd) {
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');
377
+ }
378
+ }
379
+
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');
390
+ }
391
+
392
+ function palInfo() {
393
+ fetch('/api/info', { credentials: 'include' })
394
+ .then(function (r) { return r.ok ? r.json() : null; })
395
+ .then(function (d) {
396
+ if (!d) { palPrint('error fetching info', 'err'); return; }
397
+ var total = (d.collections || []).reduce(function (s, c) { return s + (c.total || 0); }, 0);
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');
403
+ });
404
+ }
405
+
406
+ function palLs(args) {
407
+ if (!args.length) {
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);
422
+ return;
423
+ }
424
+ var colId = args[0];
425
+ palPrint('loading…', 'muted');
426
+ fetch('/api/collections/' + encodeURIComponent(colId) + '/entries?status=published&limit=20', { credentials: 'include' })
427
+ .then(function (r) { return r.ok ? r.json() : null; })
428
+ .then(function (d) {
429
+ paletteResults.innerHTML = '';
430
+ if (!d) { palPrint('collection not found: ' + escHtml(colId), 'err'); return; }
431
+ var entries = d.entries || (Array.isArray(d) ? d : []);
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 || '');
435
+ var date = (e.updated_at || e.created_at || '').substring(0, 10);
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');
444
+ });
445
+ }
446
+
447
+ function palGo(args) {
448
+ if (!args.length) { palPrint('usage: go &lt;page|collection&gt;', 'err'); return; }
449
+ var dest = args[0].toLowerCase();
450
+ if (NAV_DEST[dest]) { closePalette(); setTimeout(function () { location.href = NAV_DEST[dest]; }, 120); return; }
451
+ var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
452
+ if (col) {
453
+ closePalette();
454
+ var href = col.singleton
455
+ ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
456
+ : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
457
+ setTimeout(function () { location.href = href; }, 120);
458
+ return;
459
+ }
460
+ palPrint('not found: ' + escHtml(dest), 'err');
461
+ }
462
+
463
+ function palNew(args) {
464
+ if (!args.length) { palPrint('usage: new &lt;collection&gt;', 'err'); return; }
465
+ var dest = args[0].toLowerCase();
466
+ var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
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);
470
+ }
471
+
472
+ function palSearch(term) {
473
+ if (!term) { palPrint('usage: search &lt;term&gt;', 'err'); return; }
474
+ palPrint('searching…', 'muted');
475
+ fetch('/api/search?q=' + encodeURIComponent(term), { credentials: 'include' })
476
+ .then(function (r) { return r.ok ? r.json() : null; })
477
+ .then(function (d) {
478
+ paletteResults.innerHTML = '';
479
+ if (!d) { palPrint('search error', 'err'); return; }
480
+ var results = d.results || (Array.isArray(d) ? d : []);
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');
492
+ });
493
+ }
494
+
495
+ function palBuild() {
496
+ palPrint('triggering build…', 'muted');
497
+ fetch('/api/build', { method: 'POST', credentials: 'include' })
498
+ .then(function (r) { return r.json(); })
499
+ .then(function (d) {
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');
503
+ })
504
+ .catch(function () { paletteResults.innerHTML = ''; palPrint('build request failed', 'err'); });
505
+ }
506
+
507
+ function palExport(args) {
508
+ if (!args.length) { palPrint('usage: export &lt;collection&gt; [--md] [--drafts]', 'err'); return; }
509
+ var colId = args[0];
510
+ var format = args.indexOf('--md') !== -1 ? 'md' : 'json';
511
+ var drafts = args.indexOf('--drafts') !== -1 ? '1' : '0';
512
+ var col = _termCols.find(function (c) { return c.id === colId; });
513
+ if (!col) { palPrint('collection not found: ' + escHtml(colId), 'err'); return; }
514
+ palPrint('exporting…', 'muted');
515
+ fetch('/api/terminal/export?col=' + encodeURIComponent(colId) + '&format=' + format + '&drafts=' + drafts, { credentials: 'include' })
516
+ .then(function (r) {
517
+ if (!r.ok) return r.json().then(function (e) { throw new Error(e.error || r.status); });
518
+ return r.blob().then(function (blob) {
519
+ var a = document.createElement('a');
520
+ a.href = URL.createObjectURL(blob);
521
+ a.download = colId + '.' + format;
522
+ document.body.appendChild(a); a.click(); a.remove();
523
+ paletteResults.innerHTML = '';
524
+ palPrint('✓ downloaded ' + colId + '.' + format, 'ok');
525
+ setTimeout(closePalette, 900);
526
+ });
527
+ })
528
+ .catch(function (err) { paletteResults.innerHTML = ''; palPrint('export failed: ' + escHtml(err.message), 'err'); });
529
+ }
530
+
531
+ function escHtml(s) {
532
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
533
+ }
534
+
318
535
  function openPalette() {
319
536
  if (!palette) buildPalette();
320
537
  palette.style.display = 'flex';
@@ -821,8 +1038,9 @@
821
1038
 
822
1039
  colGroup.appendChild(item);
823
1040
 
824
- // Add to palette
1041
+ // Add to palette fuzzy search and command list
825
1042
  _palItems.push({ icon: abbr, label: col.label, href: href, group: 'Collections' });
1043
+ _termCols.push({ id: col.id, label: col.label, total: col.total || 0, drafts: col.drafts || 0, singleton: !!col.singleton });
826
1044
  });
827
1045
  }
828
1046
 
@@ -0,0 +1,42 @@
1
+ import { Hono } from 'hono';
2
+ import { openPod } from '@a83/orbiter-core';
3
+
4
+ export const terminalRoutes = new Hono();
5
+
6
+ // GET /api/terminal/export?col=<id>&format=json|md&drafts=0|1
7
+ terminalRoutes.get('/export', (c) => {
8
+ const podPath = c.get('podPath');
9
+ const colId = c.req.query('col');
10
+ const format = c.req.query('format') || 'json';
11
+ const inclDrafts = c.req.query('drafts') === '1';
12
+
13
+ if (!colId) return c.json({ error: 'col is required' }, 400);
14
+
15
+ const db = openPod(podPath);
16
+ const col = db.getCollections().find(col => col.id === colId);
17
+ if (!col) { db.close(); return c.json({ error: 'Collection not found' }, 404); }
18
+
19
+ const entries = inclDrafts
20
+ ? db.getEntries(colId)
21
+ : db.getEntries(colId, { status: 'published' });
22
+ db.close();
23
+
24
+ if (format === 'md') {
25
+ const md = entries.map(e =>
26
+ `---\nslug: ${JSON.stringify(e.slug)}\nstatus: ${JSON.stringify(e.status)}\n---\n\n${e.content || ''}`
27
+ ).join('\n\n---\n\n');
28
+ return new Response(md, {
29
+ headers: {
30
+ 'Content-Type': 'text/markdown; charset=utf-8',
31
+ 'Content-Disposition': `attachment; filename="${colId}.md"`,
32
+ },
33
+ });
34
+ }
35
+
36
+ return new Response(JSON.stringify({ collection: colId, entries }, null, 2), {
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Disposition': `attachment; filename="${colId}.json"`,
40
+ },
41
+ });
42
+ });
package/src/server.js CHANGED
@@ -24,6 +24,7 @@ import { infoRoutes } from './routes/info.js';
24
24
  import { importRoutes } from './routes/import.js';
25
25
  import { commentRoutes } from './routes/comments.js';
26
26
  import { lockRoutes } from './routes/locks.js';
27
+ import { terminalRoutes } from './routes/terminal.js';
27
28
  import { requireAuth } from './middleware/auth.js';
28
29
  import { csrfMiddleware } from './middleware/csrf.js';
29
30
 
@@ -78,6 +79,7 @@ export function createApp(podPath) {
78
79
  api.route('/collections', commentRoutes);
79
80
  api.route('/', commentRoutes);
80
81
  api.route('/locks', lockRoutes);
82
+ api.route('/terminal', terminalRoutes);
81
83
 
82
84
  app.route('/api', api);
83
85