fix: slash menu at caret position, bubble below, Alt+I Quick Insert
Slash menu now positioned below current line at the / character using monospace caret-to-pixel calc. Bubble menu now below textarea (was above). Quick Insert menu now opens below cursor line via Alt+I shortcut and click button. Status bar shows Alt+I hint.
This commit is contained in:
@@ -285,7 +285,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
||||
<!-- Status bar -->
|
||||
<div class="stat">
|
||||
<div class="stat-left">
|
||||
<span>Type <strong>/</strong> for commands</span>
|
||||
<span>Type <strong>/</strong> for commands | <strong>Alt+I</strong> Insert</span>
|
||||
<span>Ctrl+J AI panel</span>
|
||||
<span>Ctrl+S save</span>
|
||||
<span class="stat-ai" id="stat-ai">✨ AI processing...</span>
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user