@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 +1 -1
- package/public/style.css +30 -0
- package/public/xfce.js +145 -16
- package/src/routes/search.js +13 -2
package/package.json
CHANGED
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 (
|
|
256
|
+
if (inCmdMode) {
|
|
252
257
|
e.preventDefault();
|
|
253
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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 || '') + '&
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
+ '<span class
|
|
487
|
-
+ '<span class
|
|
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();
|
package/src/routes/search.js
CHANGED
|
@@ -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 =
|
|
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
|
}
|