@a83/orbiter-admin 0.3.40 → 0.3.41

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.40",
3
+ "version": "0.3.41",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2028,23 +2028,47 @@ a.xfce-sb-logo:hover { opacity: .8; }
2028
2028
  }
2029
2029
  .xfce-dock-item { position: relative; }
2030
2030
 
2031
- /* Hover + badge above collection dock items */
2032
- .xfce-col-create {
2031
+ /* Hover preview card above collection dock items */
2032
+ .xfce-col-preview {
2033
2033
  position: fixed; z-index: 99990;
2034
- width: 22px; height: 22px; border-radius: 50%;
2035
- background: var(--accent);
2036
- color: color-mix(in srgb, var(--bg1) 15%, #000);
2037
- font-size: 17px; line-height: 22px; font-weight: 300;
2038
- text-align: center; text-decoration: none;
2039
2034
  transform: translateX(-50%) translateY(6px);
2040
2035
  opacity: 0; pointer-events: none;
2041
- transition: opacity .13s, transform .13s cubic-bezier(.34,1.5,.64,1);
2042
- box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent);
2043
- user-select: none;
2036
+ transition: opacity .15s, transform .15s cubic-bezier(.34,1.4,.64,1);
2037
+ background: var(--bg1); border: 1px solid var(--line);
2038
+ border-top: 2px solid var(--accent);
2039
+ border-radius: 10px; min-width: 180px; max-width: 240px;
2040
+ box-shadow: 0 4px 24px rgba(0,0,0,.25);
2041
+ overflow: hidden; font-family: var(--mono);
2044
2042
  }
2045
- .xfce-col-create.visible {
2043
+ .xfce-col-preview.visible {
2046
2044
  opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto;
2047
2045
  }
2046
+ html[data-dock-pos="left"] .xfce-col-preview {
2047
+ transform: translateX(0) translateY(-50%);
2048
+ }
2049
+ html[data-dock-pos="left"] .xfce-col-preview.visible {
2050
+ transform: translateX(6px) translateY(-50%);
2051
+ }
2052
+ .xfce-preview-head { padding: 7px 12px; border-bottom: 1px solid var(--line); }
2053
+ .xfce-preview-head a { font-size: 10px; font-weight: 600; color: var(--accent); text-decoration: none; text-transform: uppercase; letter-spacing: .06em; }
2054
+ .xfce-preview-head a:hover { text-decoration: underline; }
2055
+ .xfce-preview-entries { padding: 4px 0; }
2056
+ .xfce-preview-row {
2057
+ display: flex; align-items: center; justify-content: space-between;
2058
+ padding: 4px 12px; gap: 8px; text-decoration: none;
2059
+ transition: background .1s;
2060
+ }
2061
+ .xfce-preview-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
2062
+ .xfce-preview-slug { font-size: 10px; color: var(--body); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2063
+ .xfce-preview-date { font-size: 9px; color: var(--muted); flex-shrink: 0; }
2064
+ .xfce-preview-empty { padding: 6px 12px; font-size: 10px; color: var(--muted); }
2065
+ .xfce-preview-loading { padding: 10px 12px; font-size: 10px; color: var(--muted); }
2066
+ .xfce-preview-new {
2067
+ display: block; padding: 6px 12px; font-size: 10px;
2068
+ color: var(--accent); text-decoration: none; border-top: 1px solid var(--line);
2069
+ transition: background .1s;
2070
+ }
2071
+ .xfce-preview-new:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
2048
2072
 
2049
2073
  /* ── Command Palette ─────────────────────────────────────── */
2050
2074
  .xfce-palette {
@@ -2123,6 +2147,38 @@ a.xfce-sb-logo:hover { opacity: .8; }
2123
2147
 
2124
2148
  /* ── Status bar build indicator ──────────────────────────── */
2125
2149
  .xfce-sb-build { font-size: 9px; font-family: var(--mono); color: var(--muted); white-space: nowrap; }
2150
+ .xfce-sb-g-ind { font-size: 10px; font-family: var(--mono); color: var(--accent); font-weight: 600; letter-spacing: .05em; animation: xfce-blink .7s step-end infinite; }
2151
+ @keyframes xfce-blink { 0%,100% { opacity:1; } 50% { opacity:.3; } }
2152
+
2153
+ /* ── Left dock mode ───────────────────────────────────────── */
2154
+ html[data-style="xfce"][data-dock-pos="left"] .main {
2155
+ padding-bottom: 20px !important;
2156
+ padding-left: 100px !important;
2157
+ }
2158
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-dock {
2159
+ bottom: auto; left: 16px; top: 50%;
2160
+ transform: translateY(-50%);
2161
+ padding: 10px 6px;
2162
+ align-items: center;
2163
+ }
2164
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-dock-inner {
2165
+ flex-direction: column; align-items: center; gap: 4px;
2166
+ }
2167
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-dock-group {
2168
+ flex-direction: column; align-items: center; gap: 4px;
2169
+ }
2170
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-dock-sep {
2171
+ width: 28px; height: 1px; margin: 2px 0;
2172
+ }
2173
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-dock-item {
2174
+ transform-origin: left center;
2175
+ }
2176
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-toast-host {
2177
+ bottom: 20px; left: 110px; transform: none;
2178
+ }
2179
+ html[data-style="xfce"][data-dock-pos="left"] .xfce-palette {
2180
+ padding-bottom: 20px; padding-left: 100px; align-items: center;
2181
+ }
2126
2182
 
2127
2183
  /* ── Dock context menu ───────────────────────────────────── */
2128
2184
  .xfce-ctx-menu {
package/public/xfce.js CHANGED
@@ -63,6 +63,7 @@
63
63
  '</div>',
64
64
  '<div class="xfce-sb-center" id="xfce-sb-title"></div>',
65
65
  '<div class="xfce-sb-right">',
66
+ '<span id="xfce-sb-g-ind" class="xfce-sb-g-ind" style="display:none" title="g — type destination key">g_</span>',
66
67
  '<button id="xfce-sb-palette-btn" class="xfce-sb-palette-btn" title="Command palette (⌘K)">⌘</button>',
67
68
  '<span class="xfce-sb-div">·</span>',
68
69
  '<span id="xfce-sb-build" class="xfce-sb-build" title="Last build"></span>',
@@ -168,6 +169,30 @@
168
169
  openPalette();
169
170
  });
170
171
  toolsPopup.appendChild(palBtn);
172
+
173
+ var dockSep2 = document.createElement('div');
174
+ dockSep2.className = 'xfce-tools-sep';
175
+ toolsPopup.appendChild(dockSep2);
176
+
177
+ var dockPosBtn = el('button', 'xfce-tools-item');
178
+ dockPosBtn.id = 'xfce-dock-pos-btn';
179
+ function updateDockPosLabel() {
180
+ var pos = document.documentElement.dataset.dockPos || 'bottom';
181
+ dockPosBtn.innerHTML = '<span class="xfce-tools-icon">' + (pos === 'left' ? '⬌' : '⬍') + '</span>'
182
+ + '<span>Dock: ' + (pos === 'left' ? 'left' : 'bottom') + '</span>';
183
+ }
184
+ updateDockPosLabel();
185
+ dockPosBtn.addEventListener('click', function (e) {
186
+ e.stopPropagation();
187
+ var cur = document.documentElement.dataset.dockPos || 'bottom';
188
+ var next = cur === 'bottom' ? 'left' : 'bottom';
189
+ localStorage.setItem('orb_dock_pos', next);
190
+ document.documentElement.dataset.dockPos = next;
191
+ updateDockPosLabel();
192
+ toolsPopup.classList.remove('open');
193
+ _previewCache = {};
194
+ });
195
+ toolsPopup.appendChild(dockPosBtn);
171
196
  document.body.appendChild(toolsPopup);
172
197
  document.addEventListener('click', function () {
173
198
  toolsPopup.classList.remove('open');
@@ -187,32 +212,86 @@
187
212
  toolsPopup.classList.toggle('open');
188
213
  }
189
214
 
190
- // ── Hover + badge above collection items ─────────────────────────────
191
- var colCreateEl, colCreateTimer;
215
+ // ── Hover preview card above collection items ────────────────────────
216
+ var _previewEl = null, _previewTimer = null, _previewCache = {};
192
217
 
193
- function buildColCreate() {
194
- colCreateEl = el('a', 'xfce-col-create');
195
- colCreateEl.id = 'xfce-col-create';
196
- colCreateEl.textContent = '+';
197
- colCreateEl.addEventListener('mouseenter', function () { clearTimeout(colCreateTimer); });
198
- colCreateEl.addEventListener('mouseleave', function () { colCreateTimer = setTimeout(hideColCreate, 120); });
199
- document.body.appendChild(colCreateEl);
218
+ function buildPreview() {
219
+ _previewEl = el('div', 'xfce-col-preview');
220
+ _previewEl.id = 'xfce-col-preview';
221
+ _previewEl.addEventListener('mouseenter', function () { clearTimeout(_previewTimer); });
222
+ _previewEl.addEventListener('mouseleave', function () { _previewTimer = setTimeout(hideColPreview, 150); });
223
+ document.body.appendChild(_previewEl);
200
224
  }
201
225
 
202
- function showColCreate(href, itemEl) {
203
- if (!colCreateEl) buildColCreate();
204
- clearTimeout(colCreateTimer);
205
- var dock = document.getElementById('xfce-dock');
206
- var dockTop = dock ? dock.getBoundingClientRect().top : 0;
207
- var itemRect = itemEl.getBoundingClientRect();
208
- colCreateEl.href = href;
209
- colCreateEl.style.left = Math.round(itemRect.left + itemRect.width / 2) + 'px';
210
- colCreateEl.style.top = Math.round(dockTop - 34) + 'px';
211
- colCreateEl.classList.add('visible');
226
+ function showColPreview(col, itemEl) {
227
+ if (!_previewEl) buildPreview();
228
+ clearTimeout(_previewTimer);
229
+ _previewTimer = setTimeout(function () { _renderPreview(col, itemEl); }, 280);
212
230
  }
213
231
 
214
- function hideColCreate() {
215
- if (colCreateEl) colCreateEl.classList.remove('visible');
232
+ function _renderPreview(col, itemEl) {
233
+ var dock = document.getElementById('xfce-dock');
234
+ var isLeft = document.documentElement.dataset.dockPos === 'left';
235
+ var dockR = dock ? dock.getBoundingClientRect() : { top: 0, right: 0 };
236
+ var itemR = itemEl.getBoundingClientRect();
237
+
238
+ function place() {
239
+ if (isLeft) {
240
+ _previewEl.style.left = Math.round(dockR.right + 10) + 'px';
241
+ _previewEl.style.top = Math.round(itemR.top + itemR.height / 2 - _previewEl.offsetHeight / 2) + 'px';
242
+ _previewEl.style.bottom = 'auto';
243
+ } else {
244
+ var cx = Math.round(itemR.left + itemR.width / 2);
245
+ _previewEl.style.left = cx + 'px';
246
+ _previewEl.style.bottom = Math.round(window.innerHeight - dockR.top + 10) + 'px';
247
+ _previewEl.style.top = 'auto';
248
+ }
249
+ }
250
+
251
+ var newHref = '/editor.html?collection=' + encodeURIComponent(col.id);
252
+ var entriesHref = col.singleton
253
+ ? newHref + '&singleton=1'
254
+ : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
255
+
256
+ function render(entries) {
257
+ var rows = entries.length
258
+ ? entries.slice(0, 3).map(function (e) {
259
+ var slug = e.slug || e.id || '';
260
+ var date = (e.updated_at || e.created_at || '').substring(5, 10);
261
+ var href = '/editor.html?collection=' + encodeURIComponent(col.id) + '&slug=' + encodeURIComponent(slug);
262
+ return '<a class="xfce-preview-row" href="' + href + '">'
263
+ + '<span class="xfce-preview-slug">' + escHtml(slug) + '</span>'
264
+ + '<span class="xfce-preview-date">' + date + '</span>'
265
+ + '</a>';
266
+ }).join('')
267
+ : '<div class="xfce-preview-empty">no entries yet</div>';
268
+ _previewEl.innerHTML =
269
+ '<div class="xfce-preview-head"><a href="' + entriesHref + '">' + escHtml(col.label) + '</a></div>'
270
+ + '<div class="xfce-preview-entries">' + rows + '</div>'
271
+ + '<a class="xfce-preview-new" href="' + newHref + '">+ new entry</a>';
272
+ _previewEl.classList.add('visible');
273
+ place();
274
+ }
275
+
276
+ if (_previewCache[col.id]) {
277
+ render(_previewCache[col.id]);
278
+ return;
279
+ }
280
+ _previewEl.innerHTML = '<div class="xfce-preview-loading">…</div>';
281
+ _previewEl.classList.add('visible');
282
+ place();
283
+ fetch('/api/collections/' + encodeURIComponent(col.id) + '/entries?limit=3', { credentials: 'include' })
284
+ .then(function (r) { return r.ok ? r.json() : null; })
285
+ .then(function (d) {
286
+ var entries = d ? (d.entries || (Array.isArray(d) ? d : [])) : [];
287
+ _previewCache[col.id] = entries;
288
+ if (_previewEl.classList.contains('visible')) render(entries);
289
+ });
290
+ }
291
+
292
+ function hideColPreview() {
293
+ clearTimeout(_previewTimer);
294
+ if (_previewEl) _previewEl.classList.remove('visible');
216
295
  }
217
296
 
218
297
  // ── Command Palette ───────────────────────────────────────────────────
@@ -1000,19 +1079,25 @@
1000
1079
  if (focusedEl) exitFocusMode();
1001
1080
  }, true);
1002
1081
 
1082
+ // Apply saved dock position
1083
+ var _savedDockPos = localStorage.getItem('orb_dock_pos') || 'bottom';
1084
+ document.documentElement.dataset.dockPos = _savedDockPos;
1085
+
1003
1086
  // ── Magnification ─────────────────────────────────────────────────
1004
- function applyMag(cx) {
1005
- var items = dockInner.querySelectorAll('.xfce-dock-item');
1087
+ function applyMag(cx, cy) {
1088
+ var isLeft = document.documentElement.dataset.dockPos === 'left';
1089
+ var items = dockInner.querySelectorAll('.xfce-dock-item');
1006
1090
  items.forEach(function (item) {
1007
1091
  var r = item.getBoundingClientRect();
1008
- var mid = r.left + r.width / 2;
1009
- var d = Math.abs(cx - mid);
1092
+ var mid = isLeft ? (r.top + r.height / 2) : (r.left + r.width / 2);
1093
+ var pos = isLeft ? cy : cx;
1094
+ var d = Math.abs(pos - mid);
1010
1095
  var s = d < 80 ? 1 + (1 - d / 80) * 0.50 : 1;
1011
1096
  item.style.setProperty('--ds', s.toFixed(3));
1012
1097
  });
1013
1098
  }
1014
1099
 
1015
- dock.addEventListener('mousemove', function (e) { applyMag(e.clientX); });
1100
+ dock.addEventListener('mousemove', function (e) { applyMag(e.clientX, e.clientY); });
1016
1101
  dock.addEventListener('mouseleave', function () {
1017
1102
  dockInner.querySelectorAll('.xfce-dock-item').forEach(function (item) {
1018
1103
  item.style.setProperty('--ds', '1');
@@ -1056,12 +1141,9 @@
1056
1141
  item.appendChild(badge);
1057
1142
  }
1058
1143
 
1059
- // Hover shows + badge above this item
1060
- var createHref = col.singleton
1061
- ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
1062
- : '/editor.html?collection=' + encodeURIComponent(col.id);
1063
- item.addEventListener('mouseenter', function () { showColCreate(createHref, item); });
1064
- item.addEventListener('mouseleave', function () { colCreateTimer = setTimeout(hideColCreate, 120); });
1144
+ // Hover shows preview card with recent entries
1145
+ item.addEventListener('mouseenter', function () { showColPreview(col, item); });
1146
+ item.addEventListener('mouseleave', function () { _previewTimer = setTimeout(hideColPreview, 150); });
1065
1147
 
1066
1148
  // Right-click context menu
1067
1149
  addDockCtxMenu(item, col);
@@ -1124,6 +1206,17 @@
1124
1206
  }
1125
1207
 
1126
1208
  // ── Keyboard shortcuts ────────────────────────────────────────────────
1209
+ var _gPending = false, _gTimer = null;
1210
+ var G_MAP = { d: '/dashboard.html', m: '/media.html', s: '/settings.html',
1211
+ u: '/users.html', b: '/build.html', i: '/import.html',
1212
+ h: '/schema.html', a: '/account.html' };
1213
+
1214
+ function setGMode(on) {
1215
+ _gPending = on;
1216
+ var ind = document.getElementById('xfce-sb-g-ind');
1217
+ if (ind) ind.style.display = on ? '' : 'none';
1218
+ }
1219
+
1127
1220
  function bindKeys() {
1128
1221
  // Capture-phase ⌘K: fires before admin-utils.js bubble-phase listener, stops it
1129
1222
  document.addEventListener('keydown', function (e) {
@@ -1146,11 +1239,26 @@
1146
1239
  // / — open palette (when not typing in an input; Shift+7 on DE keyboard also produces '/')
1147
1240
  if (!mod && !e.altKey && e.key === '/' && !isEditing(e.target)) {
1148
1241
  e.preventDefault();
1242
+ setGMode(false); clearTimeout(_gTimer);
1149
1243
  if (palette && palette.classList.contains('open')) closePalette();
1150
1244
  else openPalette();
1151
1245
  return;
1152
1246
  }
1153
1247
 
1248
+ // g — vim-style navigation prefix (g d = dashboard, g m = media, …)
1249
+ if (!mod && !e.shiftKey && !e.altKey && e.key === 'g' && !isEditing(e.target)) {
1250
+ e.preventDefault();
1251
+ setGMode(true);
1252
+ clearTimeout(_gTimer);
1253
+ _gTimer = setTimeout(function () { setGMode(false); }, 1500);
1254
+ return;
1255
+ }
1256
+ if (_gPending && !isEditing(e.target)) {
1257
+ clearTimeout(_gTimer); setGMode(false);
1258
+ if (G_MAP[e.key]) { e.preventDefault(); location.href = G_MAP[e.key]; }
1259
+ return;
1260
+ }
1261
+
1154
1262
  // ⌘⇧D — toggle HUD
1155
1263
  if (mod && e.shiftKey && (e.key === 'd' || e.key === 'D')) {
1156
1264
  e.preventDefault();