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.