@a83/orbiter-admin 0.3.39 → 0.3.40

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.39",
3
+ "version": "0.3.40",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2115,6 +2115,36 @@ a.xfce-sb-logo:hover { opacity: .8; }
2115
2115
  .xfce-pal-muted { color: var(--muted); opacity: .7; }
2116
2116
  .xfce-pal-cmd-hint { color: var(--accent); }
2117
2117
 
2118
+ /* search result with snippet */
2119
+ .xfce-pal-item--rich { align-items: flex-start; }
2120
+ .xfce-pal-item--rich .xfce-pal-icon { margin-top: 2px; }
2121
+ .xfce-pal-item-body { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
2122
+ .xfce-pal-snippet { font-size: 10px; color: var(--muted); font-family: var(--mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2123
+
2124
+ /* ── Status bar build indicator ──────────────────────────── */
2125
+ .xfce-sb-build { font-size: 9px; font-family: var(--mono); color: var(--muted); white-space: nowrap; }
2126
+
2127
+ /* ── Dock context menu ───────────────────────────────────── */
2128
+ .xfce-ctx-menu {
2129
+ display: none; position: fixed; z-index: 100000;
2130
+ background: var(--bg1); border: 1px solid var(--line);
2131
+ border-top: 2px solid var(--accent);
2132
+ border-radius: 8px; padding: 4px 0;
2133
+ box-shadow: 0 4px 24px rgba(0,0,0,.25);
2134
+ min-width: 160px;
2135
+ opacity: 0; transform: scale(.96); transform-origin: top left;
2136
+ transition: opacity .1s, transform .1s;
2137
+ }
2138
+ .xfce-ctx-menu.open { opacity: 1; transform: scale(1); }
2139
+ .xfce-ctx-item {
2140
+ display: flex; align-items: center; gap: 10px;
2141
+ width: 100%; background: none; border: none; cursor: pointer;
2142
+ padding: 7px 14px; font-size: 12px; font-family: var(--mono);
2143
+ color: var(--body); text-align: left; transition: background .1s;
2144
+ }
2145
+ .xfce-ctx-item:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--heading); }
2146
+ .xfce-ctx-icon { width: 16px; text-align: center; color: var(--accent); flex-shrink: 0; }
2147
+
2118
2148
  /* ── Toast host (above dock) ─────────────────────────────── */
2119
2149
  .xfce-toast-host {
2120
2150
  position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%);
package/public/xfce.js CHANGED
@@ -65,6 +65,8 @@
65
65
  '<div class="xfce-sb-right">',
66
66
  '<button id="xfce-sb-palette-btn" class="xfce-sb-palette-btn" title="Command palette (⌘K)">⌘</button>',
67
67
  '<span class="xfce-sb-div">·</span>',
68
+ '<span id="xfce-sb-build" class="xfce-sb-build" title="Last build"></span>',
69
+ '<span id="xfce-sb-build-sep" class="xfce-sb-div" style="display:none">·</span>',
68
70
  '<a id="xfce-sb-user" href="/account.html" class="xfce-sb-user-link"></a>',
69
71
  '<span class="xfce-sb-div">·</span>',
70
72
  '<button id="xfce-sb-logout" class="xfce-sb-logout" title="Log out">⏻</button>',
@@ -215,6 +217,7 @@
215
217
 
216
218
  // ── Command Palette ───────────────────────────────────────────────────
217
219
  var palette, paletteInp, paletteResults, palActive = -1;
220
+ var _cmdHistory = [], _cmdHistIdx = -1;
218
221
 
219
222
  var NAV_DEST = {
220
223
  dashboard: '/dashboard.html', media: '/media.html', settings: '/settings.html',
@@ -247,10 +250,18 @@
247
250
 
248
251
  paletteInp.addEventListener('keydown', function (e) {
249
252
  var val = paletteInp.value.trim();
253
+ var inCmdMode = val.startsWith('>');
254
+
250
255
  if (e.key === 'Enter') {
251
- if (val.startsWith('>')) {
256
+ if (inCmdMode) {
252
257
  e.preventDefault();
253
- execPaletteCmd(val.slice(1).trim());
258
+ var cmd = val.slice(1).trim();
259
+ if (cmd) {
260
+ if (!_cmdHistory.length || _cmdHistory[0] !== cmd) _cmdHistory.unshift(cmd);
261
+ if (_cmdHistory.length > 30) _cmdHistory.pop();
262
+ }
263
+ _cmdHistIdx = -1;
264
+ execPaletteCmd(cmd);
254
265
  } else {
255
266
  var active = paletteResults.querySelector('.xfce-pal-item.pal-active');
256
267
  if (active && active.dataset.href) { location.href = active.dataset.href; closePalette(); }
@@ -259,16 +270,28 @@
259
270
  if (first) { location.href = first.dataset.href; closePalette(); }
260
271
  }
261
272
  }
262
- } else if (e.key === 'ArrowDown') {
263
- e.preventDefault();
264
- var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
265
- palActive = Math.min(palActive + 1, items.length - 1);
266
- updatePalActive(items);
267
273
  } else if (e.key === 'ArrowUp') {
268
274
  e.preventDefault();
269
- var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
270
- palActive = Math.max(palActive - 1, 0);
271
- updatePalActive(items);
275
+ if (inCmdMode && _cmdHistory.length) {
276
+ _cmdHistIdx = Math.min(_cmdHistIdx + 1, _cmdHistory.length - 1);
277
+ paletteInp.value = '> ' + _cmdHistory[_cmdHistIdx];
278
+ renderPalette(paletteInp.value);
279
+ } else if (!inCmdMode) {
280
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
281
+ palActive = Math.max(palActive - 1, 0);
282
+ updatePalActive(items);
283
+ }
284
+ } else if (e.key === 'ArrowDown') {
285
+ e.preventDefault();
286
+ if (inCmdMode && _cmdHistory.length) {
287
+ _cmdHistIdx = Math.max(_cmdHistIdx - 1, -1);
288
+ paletteInp.value = _cmdHistIdx < 0 ? '> ' : '> ' + _cmdHistory[_cmdHistIdx];
289
+ renderPalette(paletteInp.value);
290
+ } else if (!inCmdMode) {
291
+ var items = paletteResults.querySelectorAll('.xfce-pal-item[data-href]');
292
+ palActive = Math.min(palActive + 1, items.length - 1);
293
+ updatePalActive(items);
294
+ }
272
295
  } else if (e.key === 'Escape') {
273
296
  closePalette();
274
297
  }
@@ -480,11 +503,15 @@
480
503
  var results = d.results || (Array.isArray(d) ? d : []);
481
504
  if (!results.length) { palPrint('no results for “' + escHtml(term) + '”', 'muted'); return; }
482
505
  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>'
506
+ var href = '/editor.html?collection=' + encodeURIComponent(r.collection || '') + '&slug=' + encodeURIComponent(r.slug || '');
507
+ var snippet = r.snippet ? '<div class=”xfce-pal-snippet”>' + escHtml(r.snippet) + '</div>' : '';
508
+ return '<div class=”xfce-pal-item xfce-pal-item--rich” data-href=”' + href + '”>'
509
+ + '<span class=”xfce-pal-icon”>⌕</span>'
510
+ + '<span class=”xfce-pal-item-body”>'
511
+ + '<span class=”xfce-pal-label”>' + escHtml(r.title || r.slug || '') + '</span>'
512
+ + snippet
513
+ + '</span>'
514
+ + '<span class=”xfce-pal-hint-r”>' + escHtml(r.label || r.collection || '') + '</span>'
488
515
  + '</div>';
489
516
  }).join('');
490
517
  palSetItems(html);
@@ -494,7 +521,7 @@
494
521
 
495
522
  function palBuild() {
496
523
  palPrint('triggering build…', 'muted');
497
- fetch('/api/build', { method: 'POST', credentials: 'include' })
524
+ fetch('/api/build/trigger', { method: 'POST', credentials: 'include' })
498
525
  .then(function (r) { return r.json(); })
499
526
  .then(function (d) {
500
527
  paletteResults.innerHTML = '';
@@ -1036,6 +1063,9 @@
1036
1063
  item.addEventListener('mouseenter', function () { showColCreate(createHref, item); });
1037
1064
  item.addEventListener('mouseleave', function () { colCreateTimer = setTimeout(hideColCreate, 120); });
1038
1065
 
1066
+ // Right-click context menu
1067
+ addDockCtxMenu(item, col);
1068
+
1039
1069
  colGroup.appendChild(item);
1040
1070
 
1041
1071
  // Add to palette fuzzy search and command list
@@ -1154,6 +1184,104 @@
1154
1184
  });
1155
1185
  }
1156
1186
 
1187
+ // ── Build status in status bar ────────────────────────────────────────
1188
+ function loadBuildStatus() {
1189
+ fetch('/api/build/status', { credentials: 'include' })
1190
+ .then(function (r) { return r.ok ? r.json() : null; })
1191
+ .then(function (d) {
1192
+ if (!d || !d.lastTriggered) return;
1193
+ var el2 = document.getElementById('xfce-sb-build');
1194
+ var sep = document.getElementById('xfce-sb-build-sep');
1195
+ if (!el2) return;
1196
+ var dt = new Date(d.lastTriggered.replace(' ', 'T'));
1197
+ var now = new Date();
1198
+ var diffM = Math.floor((now - dt) / 60000);
1199
+ var label = diffM < 1 ? 'built now'
1200
+ : diffM < 60 ? 'built ' + diffM + 'm ago'
1201
+ : diffM < 1440 ? 'built ' + Math.floor(diffM / 60) + 'h ago'
1202
+ : 'built ' + dt.toLocaleDateString([], { month: 'short', day: 'numeric' });
1203
+ el2.textContent = '◉ ' + label;
1204
+ el2.title = 'Last build: ' + d.lastTriggered;
1205
+ if (sep) sep.style.display = '';
1206
+ });
1207
+ }
1208
+
1209
+ // ── Dock right-click context menu ─────────────────────────────────────
1210
+ var _ctxMenu = null;
1211
+
1212
+ function buildCtxMenu() {
1213
+ _ctxMenu = document.createElement('div');
1214
+ _ctxMenu.className = 'xfce-ctx-menu';
1215
+ _ctxMenu.id = 'xfce-ctx-menu';
1216
+ document.body.appendChild(_ctxMenu);
1217
+ document.addEventListener('click', function (e) {
1218
+ if (_ctxMenu && !_ctxMenu.contains(e.target)) closeCtxMenu();
1219
+ }, true);
1220
+ document.addEventListener('keydown', function (e) {
1221
+ if (e.key === 'Escape') closeCtxMenu();
1222
+ });
1223
+ }
1224
+
1225
+ function openCtxMenu(x, y, items) {
1226
+ if (!_ctxMenu) buildCtxMenu();
1227
+ _ctxMenu.innerHTML = items.map(function (it) {
1228
+ return '<button class="xfce-ctx-item" data-href="' + (it.href || '') + '">'
1229
+ + '<span class="xfce-ctx-icon">' + it.icon + '</span>'
1230
+ + '<span>' + it.label + '</span>'
1231
+ + '</button>';
1232
+ }).join('');
1233
+ _ctxMenu.querySelectorAll('.xfce-ctx-item').forEach(function (btn) {
1234
+ btn.addEventListener('click', function () {
1235
+ var href = btn.dataset.href;
1236
+ closeCtxMenu();
1237
+ if (href) location.href = href;
1238
+ });
1239
+ });
1240
+ var vw = window.innerWidth, vh = window.innerHeight;
1241
+ _ctxMenu.style.display = 'block';
1242
+ var w = _ctxMenu.offsetWidth, h = _ctxMenu.offsetHeight;
1243
+ _ctxMenu.style.left = Math.min(x, vw - w - 8) + 'px';
1244
+ _ctxMenu.style.top = Math.min(y, vh - h - 8) + 'px';
1245
+ _ctxMenu.classList.add('open');
1246
+ }
1247
+
1248
+ function closeCtxMenu() {
1249
+ if (!_ctxMenu) return;
1250
+ _ctxMenu.classList.remove('open');
1251
+ setTimeout(function () { if (_ctxMenu) _ctxMenu.style.display = 'none'; }, 120);
1252
+ }
1253
+
1254
+ function addDockCtxMenu(item, col) {
1255
+ item.addEventListener('contextmenu', function (e) {
1256
+ e.preventDefault();
1257
+ var entriesHref = col.singleton
1258
+ ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
1259
+ : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
1260
+ var newHref = '/editor.html?collection=' + encodeURIComponent(col.id);
1261
+ openCtxMenu(e.clientX, e.clientY, [
1262
+ { icon: '◫', label: 'View entries', href: entriesHref },
1263
+ { icon: '+', label: 'New entry', href: newHref },
1264
+ { icon: '↓', label: 'Export JSON', href: '' },
1265
+ ]);
1266
+ // Wire export separately (needs fetch, not href)
1267
+ var exportBtn = _ctxMenu.querySelectorAll('.xfce-ctx-item')[2];
1268
+ if (exportBtn) {
1269
+ exportBtn.addEventListener('click', function (ev) {
1270
+ ev.stopImmediatePropagation();
1271
+ closeCtxMenu();
1272
+ fetch('/api/terminal/export?col=' + encodeURIComponent(col.id) + '&format=json&drafts=0', { credentials: 'include' })
1273
+ .then(function (r) { return r.blob(); })
1274
+ .then(function (blob) {
1275
+ var a = document.createElement('a');
1276
+ a.href = URL.createObjectURL(blob);
1277
+ a.download = col.id + '.json';
1278
+ document.body.appendChild(a); a.click(); a.remove();
1279
+ });
1280
+ }, { once: true });
1281
+ }
1282
+ });
1283
+ }
1284
+
1157
1285
  // ── Init ──────────────────────────────────────────────────────────────
1158
1286
  function init() {
1159
1287
  buildStatusBar();
@@ -1161,6 +1289,7 @@
1161
1289
  buildDock();
1162
1290
  buildToastHost();
1163
1291
  loadInfo();
1292
+ loadBuildStatus();
1164
1293
  bindKeys();
1165
1294
  initFocusMode();
1166
1295
  observeSavedFlash();
@@ -29,6 +29,16 @@ searchRoutes.get('/recent', (c) => {
29
29
  return c.json(results);
30
30
  });
31
31
 
32
+ function makeSnippet(body, q, maxLen) {
33
+ if (!body) return '';
34
+ const lower = body.toLowerCase();
35
+ const idx = lower.indexOf(q);
36
+ if (idx < 0) return body.slice(0, maxLen) + (body.length > maxLen ? '…' : '');
37
+ const start = Math.max(0, idx - 30);
38
+ const end = Math.min(body.length, idx + q.length + 60);
39
+ return (start > 0 ? '…' : '') + body.slice(start, end).trim() + (end < body.length ? '…' : '');
40
+ }
41
+
32
42
  // GET /api/search?q=
33
43
  searchRoutes.get('/', (c) => {
34
44
  const q = (c.req.query('q') ?? '').trim().toLowerCase();
@@ -42,8 +52,8 @@ searchRoutes.get('/', (c) => {
42
52
  const entries = db.getEntries(col.id);
43
53
  for (const entry of entries) {
44
54
  const title = (entry.data?.title ?? entry.slug ?? '').toLowerCase();
45
- const body = (entry.data?.body ?? '').toLowerCase();
46
- if (title.includes(q) || body.includes(q) || entry.slug.includes(q)) {
55
+ const body = entry.data?.body ?? '';
56
+ if (title.includes(q) || body.toLowerCase().includes(q) || entry.slug.includes(q)) {
47
57
  results.push({
48
58
  type: 'entry',
49
59
  collection: col.id,
@@ -51,6 +61,7 @@ searchRoutes.get('/', (c) => {
51
61
  slug: entry.slug,
52
62
  title: entry.data?.title ?? entry.slug,
53
63
  status: entry.status,
64
+ snippet: makeSnippet(body, q, 100),
54
65
  });
55
66
  if (results.length >= 20) break;
56
67
  }