@a83/orbiter-admin 0.3.43 → 0.3.44

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.43",
3
+ "version": "0.3.44",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2196,6 +2196,74 @@ html[data-style="xfce"][data-dock-pos="left"] .xfce-ws-overlay {
2196
2196
  bottom: auto; left: 110px; top: 50%; transform: translateY(-50%);
2197
2197
  }
2198
2198
 
2199
+ /* ── Bell / Notification Center ─────────────────────────── */
2200
+ .xfce-sb-bell {
2201
+ background: none; border: none; cursor: pointer; padding: 0 2px;
2202
+ color: var(--muted); font-size: 12px; line-height: 1;
2203
+ position: relative; display: flex; align-items: center; gap: 2px;
2204
+ transition: color .15s;
2205
+ }
2206
+ .xfce-sb-bell:hover { color: var(--accent); }
2207
+ .xfce-sb-bell-badge {
2208
+ position: absolute; top: -3px; right: -4px;
2209
+ background: var(--accent); color: var(--bg1);
2210
+ font-size: 8px; font-family: var(--mono); font-weight: 700;
2211
+ border-radius: 6px; padding: 0 3px; min-width: 12px; text-align: center;
2212
+ line-height: 13px; pointer-events: none;
2213
+ }
2214
+ .xfce-notif-panel {
2215
+ position: fixed; top: calc(var(--sb-h) + 6px); right: 12px;
2216
+ width: 300px; max-height: 340px;
2217
+ background: var(--bg1); border: 1px solid var(--line);
2218
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,.45);
2219
+ z-index: 100000; display: none; flex-direction: column; overflow: hidden;
2220
+ font-family: var(--mono); font-size: 11px;
2221
+ }
2222
+ .xfce-notif-panel.open { display: flex; }
2223
+ .xfce-notif-bar {
2224
+ display: flex; align-items: center; justify-content: space-between;
2225
+ padding: 8px 12px; border-bottom: 1px solid var(--line);
2226
+ background: var(--bg2); flex-shrink: 0;
2227
+ }
2228
+ .xfce-notif-title { color: var(--accent); font-weight: 700; font-size: 10px; letter-spacing: .06em; }
2229
+ .xfce-notif-clear {
2230
+ background: none; border: 1px solid var(--line); border-radius: 4px;
2231
+ color: var(--muted); font-size: 9px; font-family: var(--mono);
2232
+ padding: 1px 7px; cursor: pointer; transition: color .15s, border-color .15s;
2233
+ }
2234
+ .xfce-notif-clear:hover { color: var(--accent); border-color: var(--accent); }
2235
+ .xfce-notif-list {
2236
+ overflow-y: auto; flex: 1; padding: 4px 0;
2237
+ }
2238
+ .xfce-notif-list::-webkit-scrollbar { width: 4px; }
2239
+ .xfce-notif-list::-webkit-scrollbar-thumb { background: var(--line); border-radius: 2px; }
2240
+ .xfce-notif-item {
2241
+ display: flex; align-items: flex-start; justify-content: space-between;
2242
+ gap: 8px; padding: 6px 12px;
2243
+ border-bottom: 1px solid rgba(255,255,255,.04);
2244
+ }
2245
+ .xfce-notif-item:last-child { border-bottom: none; }
2246
+ .xfce-notif-msg { flex: 1; color: var(--text); font-size: 10px; line-height: 1.4; }
2247
+ .xfce-notif-time { color: var(--muted); font-size: 9px; white-space: nowrap; flex-shrink: 0; margin-top: 1px; }
2248
+ .xfce-notif-ok .xfce-notif-msg { color: var(--jade); }
2249
+ .xfce-notif-err .xfce-notif-msg { color: var(--red); }
2250
+ .xfce-notif-empty { padding: 20px 12px; color: var(--muted); font-size: 10px; text-align: center; }
2251
+
2252
+ /* ── HUD Drafts section ──────────────────────────────────── */
2253
+ .xfce-hud-draft-row {
2254
+ display: flex; align-items: baseline; justify-content: space-between;
2255
+ gap: 8px; padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,.04);
2256
+ }
2257
+ .xfce-hud-draft-row:last-child { border-bottom: none; }
2258
+ .xfce-hud-draft-link {
2259
+ color: var(--accent); text-decoration: none; font-size: 10px;
2260
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1;
2261
+ max-width: 160px;
2262
+ }
2263
+ .xfce-hud-draft-link:hover { text-decoration: underline; }
2264
+ .xfce-hud-draft-meta { color: var(--muted); font-size: 9px; white-space: nowrap; flex-shrink: 0; }
2265
+ .xfce-hud-empty { color: var(--muted); font-size: 10px; padding: 4px 0; }
2266
+
2199
2267
  /* ── Toast host (above dock) ─────────────────────────────── */
2200
2268
  .xfce-toast-host {
2201
2269
  position: fixed; bottom: 90px; left: 50%; transform: translateX(-50%);
package/public/xfce.js CHANGED
@@ -64,6 +64,8 @@
64
64
  '<div class="xfce-sb-center" id="xfce-sb-title"></div>',
65
65
  '<div class="xfce-sb-right">',
66
66
  '<span id="xfce-sb-g-ind" class="xfce-sb-g-ind" style="display:none" title="g mode: d=dashboard m=media s=settings u=users b=build i=import h=schema a=account">g ›</span>',
67
+ '<button id="xfce-sb-bell" class="xfce-sb-bell" title="Notifications"><span id="xfce-sb-bell-icon">○</span><span id="xfce-sb-bell-badge" class="xfce-sb-bell-badge" style="display:none"></span></button>',
68
+ '<span class="xfce-sb-div">·</span>',
67
69
  '<button id="xfce-sb-palette-btn" class="xfce-sb-palette-btn" title="Command palette (⌘K)">⌘</button>',
68
70
  '<span class="xfce-sb-div">·</span>',
69
71
  '<span id="xfce-sb-build" class="xfce-sb-build" title="Last build"></span>',
@@ -102,6 +104,12 @@
102
104
  e.stopPropagation();
103
105
  openPalette();
104
106
  });
107
+
108
+ // Bell / notification center
109
+ document.getElementById('xfce-sb-bell').addEventListener('click', function (e) {
110
+ e.stopPropagation();
111
+ toggleNotifPanel();
112
+ });
105
113
  }
106
114
 
107
115
  // ── HUD Meta Panel ────────────────────────────────────────────────────
@@ -120,6 +128,8 @@
120
128
  '<div id="xfce-hud-pod" class="xfce-hud-rows"></div>',
121
129
  '<div class="xfce-hud-section-label" style="margin-top:16px">Collections</div>',
122
130
  '<div id="xfce-hud-cols" class="xfce-hud-rows"></div>',
131
+ '<div class="xfce-hud-section-label" style="margin-top:16px">Drafts</div>',
132
+ '<div id="xfce-hud-drafts" class="xfce-hud-rows xfce-hud-drafts"></div>',
123
133
  '<div class="xfce-hud-section-label" style="margin-top:16px">Navigation</div>',
124
134
  '<div class="xfce-hud-nav-links" id="xfce-hud-nav"></div>',
125
135
  '</div>',
@@ -714,6 +724,7 @@
714
724
  t.classList.remove('show');
715
725
  setTimeout(function () { t.remove(); }, 300);
716
726
  }, 2500);
727
+ pushNotif(msg, type);
717
728
  };
718
729
 
719
730
  function observeSavedFlash() {
@@ -726,6 +737,77 @@
726
737
  }).observe(flash, { attributes: true, attributeFilter: ['style'] });
727
738
  }
728
739
 
740
+ // ── Notification Center ───────────────────────────────────────────────
741
+ var _notifications = [], _notifUnread = 0, _notifPanel = null;
742
+
743
+ function pushNotif(msg, type) {
744
+ _notifications.unshift({ msg: msg, type: type || 'info', time: Date.now() });
745
+ if (_notifications.length > 40) _notifications.pop();
746
+ _notifUnread++;
747
+ var badge = document.getElementById('xfce-sb-bell-badge');
748
+ var icon = document.getElementById('xfce-sb-bell-icon');
749
+ if (badge) { badge.textContent = _notifUnread > 9 ? '9+' : _notifUnread; badge.style.display = ''; }
750
+ if (icon) { icon.textContent = '●'; }
751
+ if (_notifPanel && _notifPanel.classList.contains('open')) renderNotifList();
752
+ }
753
+
754
+ function buildNotifPanel() {
755
+ _notifPanel = el('div', 'xfce-notif-panel');
756
+ _notifPanel.id = 'xfce-notif-panel';
757
+ _notifPanel.innerHTML = '<div class="xfce-notif-bar">'
758
+ + '<span class="xfce-notif-title">Notifications</span>'
759
+ + '<button id="xfce-notif-clear" class="xfce-notif-clear">Clear all</button>'
760
+ + '</div>'
761
+ + '<div id="xfce-notif-list" class="xfce-notif-list"></div>';
762
+ document.body.appendChild(_notifPanel);
763
+ document.getElementById('xfce-notif-clear').addEventListener('click', function () {
764
+ _notifications = []; _notifUnread = 0;
765
+ renderNotifList();
766
+ var badge = document.getElementById('xfce-sb-bell-badge');
767
+ var icon = document.getElementById('xfce-sb-bell-icon');
768
+ if (badge) badge.style.display = 'none';
769
+ if (icon) icon.textContent = '○';
770
+ });
771
+ document.addEventListener('click', function (e) {
772
+ if (_notifPanel && _notifPanel.classList.contains('open')
773
+ && !_notifPanel.contains(e.target)
774
+ && e.target.id !== 'xfce-sb-bell') closeNotifPanel();
775
+ });
776
+ }
777
+
778
+ function renderNotifList() {
779
+ var list = document.getElementById('xfce-notif-list');
780
+ if (!list) return;
781
+ if (!_notifications.length) {
782
+ list.innerHTML = '<div class="xfce-notif-empty">No notifications yet</div>';
783
+ return;
784
+ }
785
+ list.innerHTML = _notifications.map(function (n) {
786
+ var ago = Math.floor((Date.now() - n.time) / 1000);
787
+ var t = ago < 60 ? ago + 's' : ago < 3600 ? Math.floor(ago/60) + 'm' : Math.floor(ago/3600) + 'h';
788
+ var cls = n.type === 'success' ? 'ok' : n.type === 'error' ? 'err' : 'info';
789
+ return '<div class="xfce-notif-item xfce-notif-' + cls + '">'
790
+ + '<span class="xfce-notif-msg">' + escHtml(n.msg) + '</span>'
791
+ + '<span class="xfce-notif-time">' + t + '</span>'
792
+ + '</div>';
793
+ }).join('');
794
+ }
795
+
796
+ function toggleNotifPanel() {
797
+ if (!_notifPanel) buildNotifPanel();
798
+ var isOpen = _notifPanel.classList.toggle('open');
799
+ if (isOpen) {
800
+ _notifUnread = 0;
801
+ var badge = document.getElementById('xfce-sb-bell-badge');
802
+ if (badge) badge.style.display = 'none';
803
+ renderNotifList();
804
+ }
805
+ }
806
+
807
+ function closeNotifPanel() {
808
+ if (_notifPanel) _notifPanel.classList.remove('open');
809
+ }
810
+
729
811
  // ── Workspace overlay (Notes + To-do) ────────────────────────────────
730
812
  var wsOverlay, wsActivePane = 'notes', wsNotesTimer, wsTodosData = [];
731
813
 
@@ -1213,6 +1295,28 @@
1213
1295
  })
1214
1296
  .catch(function () {});
1215
1297
 
1298
+ // HUD Drafts
1299
+ fetch('/api/search/recent?status=draft&limit=10', { credentials: 'include' })
1300
+ .then(function (r) { return r.ok ? r.json() : []; })
1301
+ .then(function (drafts) {
1302
+ var hudDrafts = document.getElementById('xfce-hud-drafts');
1303
+ if (!hudDrafts) return;
1304
+ if (!drafts.length) {
1305
+ hudDrafts.innerHTML = '<div class="xfce-hud-empty">No drafts</div>';
1306
+ return;
1307
+ }
1308
+ hudDrafts.innerHTML = drafts.map(function (d) {
1309
+ var href = '/collections/' + encodeURIComponent(d.collection) + '/entries/' + encodeURIComponent(d.slug);
1310
+ var ago = Math.floor((Date.now() - new Date(d.updated_at).getTime()) / 60000);
1311
+ var t = ago < 60 ? ago + 'm' : Math.floor(ago / 60) + 'h';
1312
+ return '<div class="xfce-hud-draft-row">'
1313
+ + '<a class="xfce-hud-draft-link" href="' + href + '">' + escHtml(d.title || d.slug) + '</a>'
1314
+ + '<span class="xfce-hud-draft-meta">' + escHtml(d.label) + ' · ' + t + '</span>'
1315
+ + '</div>';
1316
+ }).join('');
1317
+ })
1318
+ .catch(function () {});
1319
+
1216
1320
  // Site meta
1217
1321
  fetch('/api/meta/site~name', { credentials: 'include' })
1218
1322
  .then(function (r) { return r.ok ? r.json() : null; })
@@ -5,17 +5,19 @@ export const searchRoutes = new Hono();
5
5
 
6
6
  // GET /api/search/recent — last N entries across all collections
7
7
  searchRoutes.get('/recent', (c) => {
8
- const limit = Math.min(parseInt(c.req.query('limit') ?? '10', 10), 50);
9
- const db = openPod(c.get('podPath'));
10
- const cols = db.getCollections();
8
+ const limit = Math.min(parseInt(c.req.query('limit') ?? '10', 10), 50);
9
+ const status = c.req.query('status') ?? null;
10
+ const db = openPod(c.get('podPath'));
11
+ const cols = db.getCollections();
11
12
  const colMap = Object.fromEntries(cols.map(c => [c.id, c.label]));
12
13
 
13
- const rows = db.db
14
- .prepare(`SELECT e.*, e.collection_id as collection FROM _entries e ORDER BY e.updated_at DESC LIMIT ?`)
15
- .all(limit);
14
+ const rows = status
15
+ ? db.db.prepare(`SELECT * FROM _entries WHERE status = ? ORDER BY updated_at DESC LIMIT ?`).all(status, limit)
16
+ : db.db.prepare(`SELECT * FROM _entries ORDER BY updated_at DESC LIMIT ?`).all(limit);
16
17
 
17
18
  const results = rows.map(r => {
18
- const data = JSON.parse(r.data);
19
+ let data = {};
20
+ try { data = JSON.parse(r.data); } catch {}
19
21
  return {
20
22
  collection: r.collection_id,
21
23
  label: colMap[r.collection_id] ?? r.collection_id,