@a83/orbiter-admin 0.3.34 → 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 +44 -36
- package/public/xfce.js +249 -102
- package/src/routes/terminal.js +42 -0
- package/src/server.js +2 -0
package/package.json
CHANGED
package/public/style.css
CHANGED
|
@@ -2046,65 +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
|
-
/* ──
|
|
2050
|
-
.xfce-
|
|
2049
|
+
/* ── Orbiter Terminal ────────────────────────────────────── */
|
|
2050
|
+
.xfce-terminal {
|
|
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-
|
|
2057
|
+
.xfce-terminal.open {
|
|
2058
2058
|
display: flex;
|
|
2059
2059
|
background: rgba(0,0,0,.28);
|
|
2060
2060
|
}
|
|
2061
2061
|
|
|
2062
|
-
.xfce-
|
|
2063
|
-
width: min(
|
|
2064
|
-
background:
|
|
2065
|
-
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);
|
|
2066
2067
|
border-radius: 12px;
|
|
2067
|
-
box-shadow: 0 -2px
|
|
2068
|
+
box-shadow: 0 -2px 48px rgba(0,0,0,.5);
|
|
2068
2069
|
overflow: hidden;
|
|
2069
|
-
|
|
2070
|
-
opacity: 0;
|
|
2070
|
+
display: flex; flex-direction: column;
|
|
2071
|
+
transform: translateY(14px); opacity: 0;
|
|
2071
2072
|
transition: transform .2s cubic-bezier(.34,1.15,.64,1), opacity .15s;
|
|
2072
2073
|
}
|
|
2073
|
-
.xfce-
|
|
2074
|
-
transform: translateY(0);
|
|
2075
|
-
opacity: 1;
|
|
2074
|
+
.xfce-terminal.open .xfce-term-inner {
|
|
2075
|
+
transform: translateY(0); opacity: 1;
|
|
2076
2076
|
}
|
|
2077
2077
|
|
|
2078
|
-
.xfce-
|
|
2079
|
-
display: flex; align-items: center;
|
|
2080
|
-
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;
|
|
2081
2081
|
}
|
|
2082
|
-
.xfce-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
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;
|
|
2086
2090
|
}
|
|
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; }
|
|
2089
2091
|
|
|
2090
|
-
.xfce-
|
|
2091
|
-
|
|
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;
|
|
2092
2095
|
}
|
|
2093
|
-
.xfce-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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;
|
|
2097
2106
|
}
|
|
2098
|
-
.xfce-
|
|
2099
|
-
|
|
2100
|
-
|
|
2107
|
+
.xfce-term-prompt {
|
|
2108
|
+
font-family: var(--mono); font-size: 11px;
|
|
2109
|
+
color: var(--accent, #7c6fcd); white-space: nowrap; flex-shrink: 0;
|
|
2101
2110
|
}
|
|
2102
|
-
.xfce-
|
|
2103
|
-
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);
|
|
2104
2115
|
}
|
|
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); }
|
|
2108
2116
|
|
|
2109
2117
|
/* ── Toast host (above dock) ─────────────────────────────── */
|
|
2110
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,128 +210,277 @@
|
|
|
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
|
-
|
|
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
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function openTerminal() {
|
|
469
|
+
if (!terminal) buildTerminal();
|
|
470
|
+
terminal.style.display = 'flex';
|
|
324
471
|
requestAnimationFrame(function () {
|
|
325
472
|
requestAnimationFrame(function () {
|
|
326
|
-
|
|
327
|
-
setTimeout(function () {
|
|
473
|
+
terminal.classList.add('open');
|
|
474
|
+
setTimeout(function () { termInput.focus(); }, 40);
|
|
328
475
|
});
|
|
329
476
|
});
|
|
330
477
|
}
|
|
331
478
|
|
|
332
|
-
function
|
|
333
|
-
if (!
|
|
334
|
-
|
|
479
|
+
function closeTerminal() {
|
|
480
|
+
if (!terminal) return;
|
|
481
|
+
terminal.classList.remove('open');
|
|
335
482
|
setTimeout(function () {
|
|
336
|
-
if (!
|
|
483
|
+
if (!terminal.classList.contains('open')) terminal.style.display = 'none';
|
|
337
484
|
}, 200);
|
|
338
485
|
}
|
|
339
486
|
|
|
@@ -821,8 +968,8 @@
|
|
|
821
968
|
|
|
822
969
|
colGroup.appendChild(item);
|
|
823
970
|
|
|
824
|
-
// Add to
|
|
825
|
-
|
|
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 });
|
|
826
973
|
});
|
|
827
974
|
}
|
|
828
975
|
|
|
@@ -880,11 +1027,11 @@
|
|
|
880
1027
|
document.addEventListener('keydown', function (e) {
|
|
881
1028
|
var mod = e.metaKey || e.ctrlKey;
|
|
882
1029
|
|
|
883
|
-
// ⌘K —
|
|
1030
|
+
// ⌘K — terminal
|
|
884
1031
|
if (mod && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
|
|
885
1032
|
e.preventDefault();
|
|
886
|
-
if (
|
|
887
|
-
else
|
|
1033
|
+
if (terminal && terminal.classList.contains('open')) closeTerminal();
|
|
1034
|
+
else openTerminal();
|
|
888
1035
|
return;
|
|
889
1036
|
}
|
|
890
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
|
|