PageSourceSearch

https://jayreddinjr-jr-odysseus-hf.hf.space/static/js/editor/shortcuts-popover.js

js jayreddinjr-jr-odysseus-hf.hf.space collected 2026-10-04 17:01:16 UTC 6,039 bytes, 149 lines download raw bytes

1/**
2 * Shortcuts-cheatsheet popover — floating frosted-glass list of every
3 * editor keyboard shortcut, anchored above the topbar keyboard icon
4 * (drops below if there's no room above). Drag the header to move;
5 * Esc or click outside dismisses; position is persisted in
6 * localStorage so re-opening restores where the user left it.
7 *
8 * Public API: `toggleShortcuts(show?)` — true/false to force a state,
9 * undefined to toggle.
10 *
11 * @returns {{ toggleShortcuts: (show?: boolean) => void }}
12 */
13import { shortcutsPopupHTML } from './build/popups.js';
14
15export function createShortcutsPopover() {
16  let pop = null;
17  let outside = null;
18
19  function ensurePopover() {
20    if (pop) return pop;
21    const el = document.createElement('div');
22    el.id = 'ge-shortcuts-popover';
23    el.style.cssText = [
24      'position:fixed', 'z-index:10000', 'display:none',
25      // Frosted-glass background: semi-transparent + heavy blur of
26      // what's behind. Layered with an inner translucent veil so
27      // light themes also read clearly without losing the see-through
28      // feel.
29      'background:color-mix(in srgb, var(--panel, #1a1a1a) 55%, transparent)',
30      'backdrop-filter:blur(18px) saturate(150%)',
31      '-webkit-backdrop-filter:blur(18px) saturate(150%)',
32      'color:var(--fg,#eee)',
33      'border:1px solid color-mix(in srgb, var(--fg, #eee) 18%, transparent)',
34      'border-radius:12px',
35      'box-shadow:0 14px 36px rgba(0,0,0,0.5), inset 0 1px 0 color-mix(in srgb, var(--fg, #fff) 8%, transparent)',
36      'padding:12px 14px', 'min-width:540px', 'max-width:min(720px,92vw)',
37      'font-size:12px', 'line-height:1.5',
38    ].join(';');
39    el.innerHTML = shortcutsPopupHTML();
40    document.body.appendChild(el);
41    el.querySelector('#ge-shortcuts-close').addEventListener('click', () => toggleShortcuts(false));
42
43    // Drag by the header handle. Position survives across opens
44    // (localStorage).
45    const handle = el.querySelector('#ge-shortcuts-handle');
46    if (handle) {
47      let drag = null;
48      handle.addEventListener('pointerdown', (e) => {
49        if (e.target.closest('#ge-shortcuts-close')) return;
50        const r = el.getBoundingClientRect();
51        drag = { dx: e.clientX - r.left, dy: e.clientY - r.top, w: r.width, h: r.height };
52        handle.setPointerCapture(e.pointerId);
53        handle.style.cursor = 'grabbing';
54        // Mark as user-positioned so subsequent toggles don't re-anchor.
55        el.dataset.userPositioned = '1';
56        e.preventDefault();
57      });
58      handle.addEventListener('pointermove', (e) => {
59        if (!drag) return;
60        let left = e.clientX - drag.dx;
61        let top  = e.clientY - drag.dy;
62        const m = 4;
63        left = Math.max(m, Math.min(left, window.innerWidth  - drag.w - m));
64        top  = Math.max(m, Math.min(top,  window.innerHeight - drag.h - m));
65        el.style.left = left + 'px';
66        el.style.top  = top + 'px';
67      });
68      const endDrag = () => {
69        if (!drag) return;
70        drag = null;
71        handle.style.cursor = 'grab';
72        try {
73          localStorage.setItem('ge-shortcuts-pos', JSON.stringify({
74            left: el.style.left, top: el.style.top,
75          }));
76        } catch {}
77      };
78      handle.addEventListener('pointerup', endDrag);
79      handle.addEventListener('pointercancel', endDrag);
80    }
81    pop = el;
82    return pop;
83  }
84
85  function positionPopover(el, anchor) {
86    // Place ABOVE the anchor, horizontally centred but clamped to
87    // viewport. Falls back to BELOW if there's no room above.
88    el.style.display = 'block';   // need a layout pass for accurate size
89    const ar = anchor.getBoundingClientRect();
90    const pr = el.getBoundingClientRect();
91    const margin = 8;
92    let left = ar.left + (ar.width / 2) - (pr.width / 2);
93    let top = ar.top - pr.height - margin;
94    if (top < margin) top = ar.bottom + margin;
95    left = Math.max(margin, Math.min(left, window.innerWidth - pr.width - margin));
96    top  = Math.max(margin, Math.min(top, window.innerHeight - pr.height - margin));
97    el.style.left = left + 'px';
98    el.style.top  = top + 'px';
99  }
100
101  function toggleShortcuts(show) {
102    const el = ensurePopover();
103    const open = show === undefined ? el.style.display === 'none' : show;
104    if (open) {
105      // Restore the user's last-dragged position if any; otherwise
106      // anchor above the button.
107      let saved = null;
108      try { saved = JSON.parse(localStorage.getItem('ge-shortcuts-pos') || 'null'); } catch {}
109      if (saved && saved.left && saved.top) {
110        el.style.display = 'block';
111        el.style.left = saved.left;
112        el.style.top  = saved.top;
113        // Re-clamp in case the viewport changed since the user dragged.
114        requestAnimationFrame(() => {
115          const r = el.getBoundingClientRect();
116          const m = 4;
117          if (r.right > window.innerWidth)  el.style.left = (window.innerWidth - r.width - m) + 'px';
118          if (r.bottom > window.innerHeight) el.style.top = (window.innerHeight - r.height - m) + 'px';
119          if (r.left < 0) el.style.left = m + 'px';
120          if (r.top  < 0) el.style.top  = m + 'px';
121        });
122      } else {
123        const anchor = document.getElementById('ge-shortcuts-btn');
124        if (anchor) positionPopover(el, anchor);
125        else el.style.display = 'block';
126      }
127      // Defer outside-click so the click that opened us doesn't close us.
128      outside = (e) => {
129        if (el.contains(e.target)) return;
130        if (e.target.closest('#ge-shortcuts-btn')) return;
131        toggleShortcuts(false);
132      };
133      setTimeout(() => document.addEventListener('mousedown', outside, true), 0);
134    } else {
135      el.style.display = 'none';
136      if (outside) {
137        document.removeEventListener('mousedown', outside, true);
138        outside = null;
139      }
140    }
141  }
142
143  /** True when the popover is currently visible. */
144  function isOpen() {
145    return !!(pop && pop.style.display && pop.style.display !== 'none');
146  }
147
148  return { toggleShortcuts, isOpen };
149}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.