@a83/orbiter-admin 0.3.33 → 0.3.35
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 +60 -36
- package/public/xfce.js +255 -99
- package/src/routes/terminal.js +42 -0
- package/src/server.js +2 -0
package/package.json
CHANGED
package/public/style.css
CHANGED
|
@@ -2009,8 +2009,13 @@ a.xfce-sb-logo:hover { opacity: .8; }
|
|
|
2009
2009
|
font-family: var(--mono); transition: color .15s;
|
|
2010
2010
|
}
|
|
2011
2011
|
.xfce-sb-logout:hover { color: var(--red); }
|
|
2012
|
-
.xfce-sb-palette-btn {
|
|
2013
|
-
|
|
2012
|
+
.xfce-sb-palette-btn {
|
|
2013
|
+
background: none; border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
|
|
2014
|
+
border-radius: 4px; cursor: pointer; padding: 1px 6px;
|
|
2015
|
+
font-size: 13px; color: var(--accent); line-height: 1.4;
|
|
2016
|
+
font-family: var(--mono); transition: background .15s;
|
|
2017
|
+
}
|
|
2018
|
+
.xfce-sb-palette-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
2014
2019
|
|
|
2015
2020
|
/* Draft badge on dock items */
|
|
2016
2021
|
.xfce-dock-badge {
|
|
@@ -2041,54 +2046,73 @@ a.xfce-sb-logo:hover { opacity: .8; }
|
|
|
2041
2046
|
opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto;
|
|
2042
2047
|
}
|
|
2043
2048
|
|
|
2044
|
-
/* ──
|
|
2045
|
-
.xfce-
|
|
2049
|
+
/* ── Orbiter Terminal ────────────────────────────────────── */
|
|
2050
|
+
.xfce-terminal {
|
|
2046
2051
|
display: none; position: fixed; inset: 0; z-index: 10000;
|
|
2047
|
-
background: rgba(0,0,0
|
|
2048
|
-
align-items: flex-
|
|
2049
|
-
padding-
|
|
2052
|
+
background: rgba(0,0,0,0);
|
|
2053
|
+
align-items: flex-end; justify-content: center;
|
|
2054
|
+
padding-bottom: calc(var(--dock-h, 76px) + 20px);
|
|
2055
|
+
transition: background .18s;
|
|
2056
|
+
}
|
|
2057
|
+
.xfce-terminal.open {
|
|
2058
|
+
display: flex;
|
|
2059
|
+
background: rgba(0,0,0,.28);
|
|
2050
2060
|
}
|
|
2051
|
-
.xfce-palette.open { display: flex; }
|
|
2052
2061
|
|
|
2053
|
-
.xfce-
|
|
2054
|
-
width: min(
|
|
2055
|
-
background:
|
|
2056
|
-
border: 1px solid
|
|
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);
|
|
2057
2067
|
border-radius: 12px;
|
|
2058
|
-
box-shadow: 0
|
|
2068
|
+
box-shadow: 0 -2px 48px rgba(0,0,0,.5);
|
|
2059
2069
|
overflow: hidden;
|
|
2070
|
+
display: flex; flex-direction: column;
|
|
2071
|
+
transform: translateY(14px); opacity: 0;
|
|
2072
|
+
transition: transform .2s cubic-bezier(.34,1.15,.64,1), opacity .15s;
|
|
2073
|
+
}
|
|
2074
|
+
.xfce-terminal.open .xfce-term-inner {
|
|
2075
|
+
transform: translateY(0); opacity: 1;
|
|
2060
2076
|
}
|
|
2061
2077
|
|
|
2062
|
-
.xfce-
|
|
2063
|
-
display: flex; align-items: center;
|
|
2064
|
-
padding:
|
|
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;
|
|
2065
2081
|
}
|
|
2066
|
-
.xfce-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2082
|
+
.xfce-term-title {
|
|
2083
|
+
font-family: var(--mono); font-size: 9px; letter-spacing: .12em;
|
|
2084
|
+
text-transform: uppercase; color: var(--accent, #7c6fcd);
|
|
2085
|
+
}
|
|
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;
|
|
2070
2090
|
}
|
|
2071
|
-
.xfce-palette-inp::placeholder { color: var(--muted); }
|
|
2072
|
-
.xfce-palette-hint { font-size: 8px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
|
|
2073
2091
|
|
|
2074
|
-
.xfce-
|
|
2075
|
-
|
|
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;
|
|
2076
2095
|
}
|
|
2077
|
-
.xfce-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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;
|
|
2081
2106
|
}
|
|
2082
|
-
.xfce-
|
|
2083
|
-
|
|
2084
|
-
|
|
2107
|
+
.xfce-term-prompt {
|
|
2108
|
+
font-family: var(--mono); font-size: 11px;
|
|
2109
|
+
color: var(--accent, #7c6fcd); white-space: nowrap; flex-shrink: 0;
|
|
2085
2110
|
}
|
|
2086
|
-
.xfce-
|
|
2087
|
-
background:
|
|
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);
|
|
2088
2115
|
}
|
|
2089
|
-
.xfce-pal-icon { width: 20px; text-align: center; font-size: 13px; color: var(--accent); flex-shrink: 0; }
|
|
2090
|
-
.xfce-pal-label { font-size: 12px; color: var(--heading); font-family: var(--mono); }
|
|
2091
|
-
.xfce-pal-empty { padding: 20px 14px; color: var(--muted); font-size: 11px; font-family: var(--mono); }
|
|
2092
2116
|
|
|
2093
2117
|
/* ── Toast host (above dock) ─────────────────────────────── */
|
|
2094
2118
|
.xfce-toast-host {
|
package/public/xfce.js
CHANGED
|
@@ -28,10 +28,8 @@
|
|
|
28
28
|
{ icon: '☑', label: 'To-do', pane: 'todos' },
|
|
29
29
|
];
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
var
|
|
33
|
-
return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1 } ? 'Tools' : 'Nav' };
|
|
34
|
-
});
|
|
31
|
+
// collections loaded by /api/info — used by terminal
|
|
32
|
+
var _termCols = [];
|
|
35
33
|
|
|
36
34
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
37
35
|
function el(tag, cls, html) {
|
|
@@ -96,7 +94,7 @@
|
|
|
96
94
|
// Palette trigger in status bar
|
|
97
95
|
document.getElementById('xfce-sb-palette-btn').addEventListener('click', function (e) {
|
|
98
96
|
e.stopPropagation();
|
|
99
|
-
|
|
97
|
+
openTerminal();
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
|
|
@@ -162,7 +160,7 @@
|
|
|
162
160
|
palBtn.addEventListener('click', function (e) {
|
|
163
161
|
e.stopPropagation();
|
|
164
162
|
toolsPopup.classList.remove('open');
|
|
165
|
-
|
|
163
|
+
openTerminal();
|
|
166
164
|
});
|
|
167
165
|
toolsPopup.appendChild(palBtn);
|
|
168
166
|
document.body.appendChild(toolsPopup);
|
|
@@ -212,120 +210,278 @@
|
|
|
212
210
|
if (colCreateEl) colCreateEl.classList.remove('visible');
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
// ──
|
|
216
|
-
var
|
|
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 = {
|
|
218
|
+
dashboard: '/dashboard.html', media: '/media.html', settings: '/settings.html',
|
|
219
|
+
users: '/users.html', schema: '/schema.html', build: '/build.html',
|
|
220
|
+
import: '/import.html', account: '/account.html',
|
|
221
|
+
};
|
|
217
222
|
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
'<div class="xfce-
|
|
223
|
-
'<div class="xfce-
|
|
224
|
-
'<span class="xfce-
|
|
225
|
-
'<
|
|
226
|
-
|
|
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" />',
|
|
227
236
|
'</div>',
|
|
228
|
-
'<div id="xfce-palette-results" class="xfce-palette-results"></div>',
|
|
229
237
|
'</div>',
|
|
230
238
|
].join('');
|
|
231
|
-
document.body.appendChild(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
document.body.appendChild(terminal);
|
|
240
|
+
|
|
241
|
+
termOutput = document.getElementById('xfce-term-output');
|
|
242
|
+
termInput = document.getElementById('xfce-term-input');
|
|
243
|
+
|
|
244
|
+
termPrint('Orbiter Admin · type \'help\' for available commands', 'muted');
|
|
245
|
+
termPrint('', '');
|
|
246
|
+
|
|
247
|
+
termInput.addEventListener('keydown', function (e) {
|
|
248
|
+
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);
|
|
247
257
|
} else if (e.key === 'ArrowUp') {
|
|
248
258
|
e.preventDefault();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
259
|
+
if (termHistIdx < termHistory.length - 1) { termHistIdx++; termInput.value = termHistory[termHistIdx]; }
|
|
260
|
+
} else if (e.key === 'ArrowDown') {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
if (termHistIdx > 0) { termHistIdx--; termInput.value = termHistory[termHistIdx]; }
|
|
263
|
+
else { termHistIdx = -1; termInput.value = ''; }
|
|
264
|
+
} else if (e.key === 'Tab') {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
termTabComplete();
|
|
258
267
|
} else if (e.key === 'Escape') {
|
|
259
|
-
|
|
268
|
+
closeTerminal();
|
|
260
269
|
}
|
|
261
270
|
});
|
|
262
271
|
|
|
263
|
-
|
|
264
|
-
if (e.target ===
|
|
272
|
+
terminal.addEventListener('click', function (e) {
|
|
273
|
+
if (e.target === terminal) closeTerminal();
|
|
265
274
|
});
|
|
266
275
|
}
|
|
267
276
|
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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;
|
|
273
283
|
}
|
|
274
284
|
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
function termClear() { termOutput.innerHTML = ''; }
|
|
286
|
+
|
|
287
|
+
function execCmd(raw) {
|
|
288
|
+
var parts = raw.trim().split(/\s+/);
|
|
289
|
+
var cmd = parts[0].toLowerCase();
|
|
290
|
+
var args = parts.slice(1);
|
|
291
|
+
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');
|
|
284
302
|
}
|
|
303
|
+
termPrint('', '');
|
|
304
|
+
}
|
|
285
305
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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'); });
|
|
321
|
+
}
|
|
292
322
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
323
|
+
function termInfo() {
|
|
324
|
+
fetch('/api/info', { credentials: 'include' })
|
|
325
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
326
|
+
.then(function (d) {
|
|
327
|
+
if (!d) { termPrint('error fetching info', 'err'); return; }
|
|
328
|
+
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');
|
|
301
335
|
});
|
|
302
|
-
|
|
303
|
-
paletteResults.innerHTML = html;
|
|
336
|
+
}
|
|
304
337
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
338
|
+
function termLs(args) {
|
|
339
|
+
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');
|
|
309
346
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
var colId = args[0];
|
|
350
|
+
fetch('/api/collections/' + encodeURIComponent(colId) + '/entries?status=published&limit=25', { credentials: 'include' })
|
|
351
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
352
|
+
.then(function (d) {
|
|
353
|
+
if (!d) { termPrint('collection not found: ' + colId, 'err'); return; }
|
|
354
|
+
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) {
|
|
358
|
+
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');
|
|
314
362
|
});
|
|
315
|
-
});
|
|
316
363
|
}
|
|
317
364
|
|
|
318
|
-
function
|
|
319
|
-
if (!
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
365
|
+
function termGo(args) {
|
|
366
|
+
if (!args.length) { termPrint('usage: go <page|collection>', 'err'); return; }
|
|
367
|
+
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
|
+
}
|
|
374
|
+
var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
|
|
375
|
+
if (col) {
|
|
376
|
+
termPrint('→ ' + col.label, 'ok');
|
|
377
|
+
closeTerminal();
|
|
378
|
+
var href = col.singleton
|
|
379
|
+
? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
|
|
380
|
+
: '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
|
|
381
|
+
setTimeout(function () { location.href = href; }, 180);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
termPrint('not found: ' + dest, 'err');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function termNew(args) {
|
|
388
|
+
if (!args.length) { termPrint('usage: new <collection>', 'err'); return; }
|
|
389
|
+
var dest = args[0].toLowerCase();
|
|
390
|
+
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);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function termSearch(term) {
|
|
398
|
+
if (!term) { termPrint('usage: search <term>', 'err'); return; }
|
|
399
|
+
termPrint('searching…', 'muted');
|
|
400
|
+
fetch('/api/search?q=' + encodeURIComponent(term), { credentials: 'include' })
|
|
401
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
402
|
+
.then(function (d) {
|
|
403
|
+
if (!d) { termPrint('search error', 'err'); return; }
|
|
404
|
+
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');
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function termBuild() {
|
|
416
|
+
termPrint('triggering build…', 'muted');
|
|
417
|
+
fetch('/api/build', { method: 'POST', credentials: 'include' })
|
|
418
|
+
.then(function (r) { return r.json(); })
|
|
419
|
+
.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');
|
|
422
|
+
})
|
|
423
|
+
.catch(function () { termPrint('build request failed', 'err'); });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function termExport(args) {
|
|
427
|
+
if (!args.length) { termPrint('usage: export <collection> [--md] [--drafts]', 'err'); return; }
|
|
428
|
+
var colId = args[0];
|
|
429
|
+
var format = args.indexOf('--md') !== -1 ? 'md' : 'json';
|
|
430
|
+
var drafts = args.indexOf('--drafts') !== -1 ? '1' : '0';
|
|
431
|
+
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');
|
|
434
|
+
fetch('/api/terminal/export?col=' + encodeURIComponent(colId) + '&format=' + format + '&drafts=' + drafts, { credentials: 'include' })
|
|
435
|
+
.then(function (r) {
|
|
436
|
+
if (!r.ok) return r.json().then(function (e) { throw new Error(e.error || r.status); });
|
|
437
|
+
return r.blob().then(function (blob) {
|
|
438
|
+
var a = document.createElement('a');
|
|
439
|
+
a.href = URL.createObjectURL(blob);
|
|
440
|
+
a.download = colId + '.' + format;
|
|
441
|
+
document.body.appendChild(a); a.click(); a.remove();
|
|
442
|
+
termPrint('✓ downloaded ' + colId + '.' + format, 'ok');
|
|
443
|
+
});
|
|
444
|
+
})
|
|
445
|
+
.catch(function (err) { termPrint('export failed: ' + err.message, 'err'); });
|
|
446
|
+
}
|
|
447
|
+
|
|
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
|
+
}
|
|
325
466
|
}
|
|
326
467
|
|
|
327
|
-
function
|
|
328
|
-
if (
|
|
468
|
+
function openTerminal() {
|
|
469
|
+
if (!terminal) buildTerminal();
|
|
470
|
+
terminal.style.display = 'flex';
|
|
471
|
+
requestAnimationFrame(function () {
|
|
472
|
+
requestAnimationFrame(function () {
|
|
473
|
+
terminal.classList.add('open');
|
|
474
|
+
setTimeout(function () { termInput.focus(); }, 40);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function closeTerminal() {
|
|
480
|
+
if (!terminal) return;
|
|
481
|
+
terminal.classList.remove('open');
|
|
482
|
+
setTimeout(function () {
|
|
483
|
+
if (!terminal.classList.contains('open')) terminal.style.display = 'none';
|
|
484
|
+
}, 200);
|
|
329
485
|
}
|
|
330
486
|
|
|
331
487
|
// ── Toast system ──────────────────────────────────────────────────────
|
|
@@ -812,8 +968,8 @@
|
|
|
812
968
|
|
|
813
969
|
colGroup.appendChild(item);
|
|
814
970
|
|
|
815
|
-
// Add to
|
|
816
|
-
|
|
971
|
+
// Add to terminal collection list
|
|
972
|
+
_termCols.push({ id: col.id, label: col.label, total: col.total || 0, drafts: col.drafts || 0, singleton: !!col.singleton });
|
|
817
973
|
});
|
|
818
974
|
}
|
|
819
975
|
|
|
@@ -871,11 +1027,11 @@
|
|
|
871
1027
|
document.addEventListener('keydown', function (e) {
|
|
872
1028
|
var mod = e.metaKey || e.ctrlKey;
|
|
873
1029
|
|
|
874
|
-
// ⌘K —
|
|
1030
|
+
// ⌘K — terminal
|
|
875
1031
|
if (mod && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
|
|
876
1032
|
e.preventDefault();
|
|
877
|
-
if (
|
|
878
|
-
else
|
|
1033
|
+
if (terminal && terminal.classList.contains('open')) closeTerminal();
|
|
1034
|
+
else openTerminal();
|
|
879
1035
|
return;
|
|
880
1036
|
}
|
|
881
1037
|
|
|
@@ -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
|
|