diff --git a/frontend/editor-poc.html b/frontend/editor-poc.html index 33d18e3..dd2093b 100644 --- a/frontend/editor-poc.html +++ b/frontend/editor-poc.html @@ -285,7 +285,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
- Type / for commands + Type / for commands | Alt+I Insert Ctrl+J AI panel Ctrl+S save ✨ AI processing... @@ -609,6 +609,32 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig }); })(); + // ---- Caret position to pixel (for monospace textarea) ---- + function caretToPixel(pos) { + if (pos == null) pos = getPos().s; + var v = val(); + var before = v.substring(0, pos); + var lineNum = before.split('\n').length - 1; + var colNum = pos - v.lastIndexOf('\n', pos - 1) - 1; + if (colNum < 0) colNum = 0; + var style = getComputedStyle(ta); + var fontSize = parseFloat(style.fontSize); + var lineH = parseFloat(style.lineHeight); + if (isNaN(lineH)) lineH = fontSize * 1.75; + var charW = fontSize * 0.615; + var wrapRect = editorWrap.getBoundingClientRect(); + var taRect = ta.getBoundingClientRect(); + // Position relative to editorWrap + var x = taRect.left - wrapRect.left + 2 + colNum * charW; + var y = taRect.top - wrapRect.top + (lineNum + 1) * lineH - ta.scrollTop; + // Clamp + if (x < 4) x = 4; + if (x > wrapRect.width - 310) x = wrapRect.width - 310; + if (y > wrapRect.height - 360) y = wrapRect.height - 360; + if (y < 20) y = 20; + return { x: x, y: y }; + } + // ---- Slash menu ---- function initSlashItems() { slashItems = Array.from(slashMenu.querySelectorAll('.slash-item')); } @@ -631,9 +657,11 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig slashMenu.classList.add('on'); slashVisible = true; slashIdx = -1; slashSearch.value = ''; slashSearch.focus(); filterSlash(''); - // Position - var wr = editorWrap.getBoundingClientRect(); - slashMenu.style.left = '40px'; slashMenu.style.top = '80px'; + // Position below current line at slash character + var pos = getPos().s - 1; // position of the / character + var pt = caretToPixel(pos); + slashMenu.style.left = pt.x + 'px'; + slashMenu.style.top = pt.y + 'px'; } function hideSlash() { slashMenu.classList.remove('on'); slashVisible = false; resetSlash(); } @@ -675,9 +703,9 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig function showBubble() { if (ta.selectionStart === ta.selectionEnd) { hideBubble(); return; } var tr = ta.getBoundingClientRect(); - var top = tr.top - 44; + var top = tr.bottom + 6; var left = tr.left + (tr.width / 2) - (bubble.offsetWidth / 2); - if (top < 56) top = tr.bottom + 6; + if (top + 36 > window.innerHeight) top = tr.top - 44; if (left < 6) left = 6; if (left + bubble.offsetWidth > window.innerWidth - 6) left = window.innerWidth - bubble.offsetWidth - 6; bubble.style.left = left + 'px'; bubble.style.top = top + 'px'; @@ -706,10 +734,19 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig } // ---- Quick Insert ---- + function showQI() { + qiMenu.classList.remove('on'); + // Position below current line, left-aligned + var pt = caretToPixel(null); + pt.x = Math.max(pt.x - 10, 4); + pt.y += 2; + qiMenu.style.left = pt.x + 'px'; + qiMenu.style.top = pt.y + 'px'; + qiMenu.classList.add('on'); + } function toggleQI() { - var r = document.getElementById('qi-btn').getBoundingClientRect(); - qiMenu.style.left = (r.right + 6) + 'px'; qiMenu.style.top = r.top + 'px'; - qiMenu.classList.toggle('on'); + if (qiMenu.classList.contains('on')) { qiMenu.classList.remove('on'); return; } + showQI(); } // ---- AI Panel ---- @@ -792,6 +829,8 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig if (e.ctrlKey && e.key === 's') { e.preventDefault(); forceSave(); } if (e.ctrlKey && e.key === 'b') { e.preventDefault(); wrapSelection('**'); } if (e.ctrlKey && e.key === 'i') { e.preventDefault(); wrapSelection('*'); } + // Quick Insert: Alt+I + if (e.altKey && e.key === 'i') { e.preventDefault(); showQI(); } }); ta.addEventListener('keyup', function(e) {