@a83/orbiter-admin 0.3.3 → 0.3.5

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.3",
3
+ "version": "0.3.5",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
@@ -1201,9 +1201,13 @@
1201
1201
  const b=getFocusedBlock(); if (!b) return;
1202
1202
  // Skip block-level Enter/Backspace handling when focus is inside a table cell
1203
1203
  if (b.dataset.type==='table') return;
1204
- if (e.key==='Enter'&&!e.shiftKey) {
1204
+ if (e.key==='Enter') {
1205
1205
  e.preventDefault();
1206
- const type=b.dataset.type; const newType=(type==='ul'||type==='ol')?type:'p';
1206
+ const type=b.dataset.type;
1207
+ if (type==='pre' && !e.shiftKey) { document.execCommand('insertText',false,'\n'); syncToHidden(); scheduleAutosave(); return; }
1208
+ if (type==='pre' && e.shiftKey) { const next=createBlock('p',''); b.after(next); focusStart(next); syncToHidden(); scheduleAutosave(); updatePreview(); return; }
1209
+ if (e.shiftKey) return;
1210
+ const newType=(type==='ul'||type==='ol')?type:'p';
1207
1211
  const next=createBlock(newType,''); b.after(next); focusStart(next);
1208
1212
  syncToHidden(); scheduleAutosave(); updatePreview(); return;
1209
1213
  }
@@ -1333,12 +1337,12 @@
1333
1337
  {label:'Heading 2', icon:'H2', hint:'##', type:'h2'},
1334
1338
  {label:'Heading 3', icon:'H3', hint:'###', type:'h3'},
1335
1339
  {label:'Quote', icon:'❝', hint:'>', type:'blockquote'},
1336
- {label:'Code block', icon:'{}', hint:'```', type:'pre'},
1340
+ {label:'Code block', icon:'{}', hint:'code', type:'pre'},
1337
1341
  {label:'Callout', icon:'ℹ', hint:'note', type:'callout'},
1338
1342
  {label:'Table', icon:'▦', hint:'tbl', type:'table'},
1339
1343
  {label:'List', icon:'·', hint:'-', type:'ul'},
1340
1344
  {label:'Numbered', icon:'1.', hint:'1.', type:'ol'},
1341
- {label:'Divider', icon:'—', hint:'---', type:'hr'},
1345
+ {label:'Divider', icon:'—', hint:'hr', type:'hr'},
1342
1346
  {label:'Image', icon:'⊡', hint:'img', type:'image'},
1343
1347
  {label:'Video', icon:'▶', hint:'vid', type:'video'},
1344
1348
  ];
@@ -311,37 +311,20 @@
311
311
  });
312
312
 
313
313
  // Bulk actions
314
- document.getElementById('bulk-publish').addEventListener('click', async () => {
315
- await Promise.all([...selected].map(slug =>
316
- fetch(`/api/collections/${colId}/entries/${slug}/status`, {
317
- method: 'PATCH', credentials: 'include',
318
- headers: { 'Content-Type': 'application/json' },
319
- body: JSON.stringify({ status: 'published' }),
320
- })
321
- ));
322
- loadEntries();
323
- });
324
-
325
- document.getElementById('bulk-draft').addEventListener('click', async () => {
326
- await Promise.all([...selected].map(slug =>
327
- fetch(`/api/collections/${colId}/entries/${slug}/status`, {
328
- method: 'PATCH', credentials: 'include',
329
- headers: { 'Content-Type': 'application/json' },
330
- body: JSON.stringify({ status: 'draft' }),
331
- })
332
- ));
333
- loadEntries();
334
- });
335
-
336
- document.getElementById('bulk-delete').addEventListener('click', async () => {
337
- if (!confirm(`Delete ${selected.size} entr${selected.size !== 1 ? 'ies' : 'y'}?`)) return;
338
- await Promise.all([...selected].map(slug =>
339
- fetch(`/api/collections/${colId}/entries/${slug}`, {
340
- method: 'DELETE', credentials: 'include',
341
- })
342
- ));
314
+ async function bulkAction(action) {
315
+ const slugs = [...selected];
316
+ if (!slugs.length) return;
317
+ if (action === 'delete' && !confirm(`Delete ${slugs.length} entr${slugs.length !== 1 ? 'ies' : 'y'}?`)) return;
318
+ await fetch(`/api/collections/${colId}/entries/bulk`, {
319
+ method: 'POST', credentials: 'include',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: JSON.stringify({ action, slugs }),
322
+ });
343
323
  loadEntries();
344
- });
324
+ }
325
+ document.getElementById('bulk-publish').addEventListener('click', () => bulkAction('publish'));
326
+ document.getElementById('bulk-draft').addEventListener('click', () => bulkAction('draft'));
327
+ document.getElementById('bulk-delete').addEventListener('click', () => bulkAction('delete'));
345
328
 
346
329
  // New entry modal
347
330
  const overlay = document.getElementById('modal-overlay');
package/public/sidebar.js CHANGED
@@ -96,7 +96,8 @@
96
96
  var parts = [];
97
97
  if (info.adminVersion) parts.push('Orbiter v' + info.adminVersion);
98
98
  if (info.formatVersion) parts.push('pod v' + info.formatVersion);
99
- podVersionEl.textContent = parts.join(' · ');
99
+ var label = parts.join(' · ');
100
+ podVersionEl.innerHTML = '<a href="https://github.com/aeon022/orbiter/releases" target="_blank" rel="noopener noreferrer">' + label + '</a>';
100
101
  }
101
102
  })
102
103
  .catch(function () {});
package/public/style.css CHANGED
@@ -906,7 +906,9 @@ a:hover { color: var(--heading); }
906
906
  .pod-name { color: var(--text); font-size: 12px; font-weight: 500; margin-bottom: 3px; display: flex; align-items: center; gap: 5px; }
907
907
  .pod-name::before { content: "◆"; font-size: 6px; color: var(--gold); }
908
908
  .pod-info { font-size: 10px; color: var(--muted); margin-bottom: 2px; }
909
- .pod-version { font-size: 10px; color: var(--muted); opacity: 0.6; margin-bottom: 4px; }
909
+ .pod-version { font-size: 10px; color: var(--muted); opacity: 0.75; margin-bottom: 4px; }
910
+ .pod-version a { color: inherit; text-decoration: none; }
911
+ .pod-version a:hover { color: var(--text); }
910
912
  .pod-status { display: flex; align-items: center; margin-top: 8px; }
911
913
  .pod-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--jade); margin-right: 5px; flex-shrink: 0; animation: pulse 2.5s ease-in-out infinite; }
912
914
  @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
@@ -24,7 +24,7 @@ authRoutes.post('/login', async (c) => {
24
24
 
25
25
  setCookie(c, 'orb_sess', token, {
26
26
  httpOnly: true,
27
- sameSite: 'Lax',
27
+ sameSite: 'Strict',
28
28
  path: '/',
29
29
  maxAge: 30 * 24 * 60 * 60,
30
30
  });
@@ -11,6 +11,27 @@ function fireWebhook(podPath) {
11
11
  if (url) fetch(url, { method: 'POST' }).catch(() => {});
12
12
  }
13
13
 
14
+ // POST /api/collections/:id/entries/bulk — bulk publish or delete
15
+ entryRoutes.post('/:collectionId/entries/bulk', async (c) => {
16
+ const { collectionId } = c.req.param();
17
+ const { action, slugs } = await c.req.json();
18
+ if (!Array.isArray(slugs) || !slugs.length) return c.json({ error: 'slugs required' }, 400);
19
+ if (!['publish', 'draft', 'delete'].includes(action)) return c.json({ error: 'Invalid action' }, 400);
20
+ const db = openPod(c.get('podPath'));
21
+ if (action === 'delete') {
22
+ slugs.forEach(slug => db.deleteEntry(collectionId, slug));
23
+ } else {
24
+ const status = action === 'publish' ? 'published' : 'draft';
25
+ slugs.forEach(slug => {
26
+ const entry = db.getEntry(collectionId, slug);
27
+ if (entry) db.updateEntry(collectionId, slug, { slug, data: entry.data, status });
28
+ });
29
+ }
30
+ db.close();
31
+ if (action === 'publish') fireWebhook(c.get('podPath'));
32
+ return c.json({ ok: true, count: slugs.length });
33
+ });
34
+
14
35
  // PATCH /api/collections/:id/entries/reorder — set sort_order by slug array
15
36
  entryRoutes.patch('/:collectionId/entries/reorder', async (c) => {
16
37
  const { collectionId } = c.req.param();
package/src/server.js CHANGED
@@ -8,6 +8,8 @@ import { dirname, join } from 'node:path';
8
8
  // Ensure CWD is the package root so serveStatic finds ./public
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
10
  process.chdir(join(__dirname, '..'));
11
+ import { readFileSync } from 'node:fs';
12
+ import { openPod } from '@a83/orbiter-core';
11
13
  import { authRoutes } from './routes/auth.js';
12
14
  import { collectionRoutes } from './routes/collections.js';
13
15
  import { entryRoutes } from './routes/entries.js';
@@ -22,6 +24,10 @@ import { infoRoutes } from './routes/info.js';
22
24
  import { importRoutes } from './routes/import.js';
23
25
  import { requireAuth } from './middleware/auth.js';
24
26
 
27
+ const { version: adminVersion } = JSON.parse(
28
+ readFileSync(join(__dirname, '../package.json'), 'utf8')
29
+ );
30
+
25
31
  const POD_PATH = process.env.ORBITER_POD;
26
32
  if (!POD_PATH) {
27
33
  console.error('Error: ORBITER_POD environment variable is required.');
@@ -68,7 +74,11 @@ export function createApp(podPath) {
68
74
 
69
75
  app.route('/api', api);
70
76
 
71
- app.get('/health', (c) => c.json({ ok: true, pod: podPath }));
77
+ app.get('/health', (c) => {
78
+ let podOk = false;
79
+ try { const db = openPod(podPath); db.close(); podOk = true; } catch {}
80
+ return c.json({ ok: podOk, version: adminVersion, pod: podPath, uptime: Math.floor(process.uptime()) });
81
+ });
72
82
 
73
83
  // Redirect root to login
74
84
  app.get('/', (c) => c.redirect('/login.html'));