fix: slash/QI keyboard nav + bubble position at selection
Slash nav: moved from textarea keydown to slashSearch input keydown (textarea loses focus when slash menu opens). QI nav: document-level up/down/enter/esc with highlight. Bubble: positioned at selection start via caretToPixel instead of textarea bottom center.
This commit is contained in:
+47
-16
@@ -702,10 +702,15 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
// ---- Bubble menu ----
|
// ---- Bubble menu ----
|
||||||
function showBubble() {
|
function showBubble() {
|
||||||
if (ta.selectionStart === ta.selectionEnd) { hideBubble(); return; }
|
if (ta.selectionStart === ta.selectionEnd) { hideBubble(); return; }
|
||||||
var tr = ta.getBoundingClientRect();
|
// Position below the selection start, using caret pixel calc
|
||||||
var top = tr.bottom + 6;
|
var pt = caretToPixel(ta.selectionStart);
|
||||||
var left = tr.left + (tr.width / 2) - (bubble.offsetWidth / 2);
|
// Convert from editorWrap-relative to viewport-relative
|
||||||
if (top + 36 > window.innerHeight) top = tr.top - 44;
|
var wr = editorWrap.getBoundingClientRect();
|
||||||
|
var top = wr.top + pt.y + 4;
|
||||||
|
var left = wr.left + pt.x - 10;
|
||||||
|
// Fallback if caret calc gives odd values
|
||||||
|
if (top < 50) { var tr = ta.getBoundingClientRect(); top = tr.bottom + 6; left = tr.left + (tr.width / 2) - (bubble.offsetWidth / 2); }
|
||||||
|
if (top + 36 > window.innerHeight) top = top - 60;
|
||||||
if (left < 6) left = 6;
|
if (left < 6) left = 6;
|
||||||
if (left + bubble.offsetWidth > window.innerWidth - 6) left = window.innerWidth - bubble.offsetWidth - 6;
|
if (left + bubble.offsetWidth > window.innerWidth - 6) left = window.innerWidth - bubble.offsetWidth - 6;
|
||||||
bubble.style.left = left + 'px'; bubble.style.top = top + 'px';
|
bubble.style.left = left + 'px'; bubble.style.top = top + 'px';
|
||||||
@@ -736,6 +741,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
// ---- Quick Insert ----
|
// ---- Quick Insert ----
|
||||||
function showQI() {
|
function showQI() {
|
||||||
qiMenu.classList.remove('on');
|
qiMenu.classList.remove('on');
|
||||||
|
qiIdx = -1; qiItems = [];
|
||||||
// Position below current line, left-aligned
|
// Position below current line, left-aligned
|
||||||
var pt = caretToPixel(null);
|
var pt = caretToPixel(null);
|
||||||
pt.x = Math.max(pt.x - 10, 4);
|
pt.x = Math.max(pt.x - 10, 4);
|
||||||
@@ -809,22 +815,12 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
|
|
||||||
// ---- Event handlers ----
|
// ---- Event handlers ----
|
||||||
ta.addEventListener('keydown', function(e) {
|
ta.addEventListener('keydown', function(e) {
|
||||||
// Slash menu
|
// Slash menu trigger: type / at start of line
|
||||||
if (e.key === '/') {
|
if (e.key === '/') {
|
||||||
var l = getLine(); var p = getPos();
|
var l = getLine(); var p = getPos();
|
||||||
var before = l.text.substring(0, p.s - l.start);
|
var before = l.text.substring(0, p.s - l.start);
|
||||||
if (before === '' || /^\s*$/.test(before)) setTimeout(showSlash, 20);
|
if (before === '' || /^\s*$/.test(before)) setTimeout(showSlash, 20);
|
||||||
}
|
}
|
||||||
if (slashVisible) {
|
|
||||||
if (e.key === 'Escape') { e.preventDefault(); hideSlash(); }
|
|
||||||
else if (e.key === 'ArrowDown') { e.preventDefault(); navSlash(1); }
|
|
||||||
else if (e.key === 'ArrowUp') { e.preventDefault(); navSlash(-1); }
|
|
||||||
else if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
var vis = getVisibleSlash();
|
|
||||||
if (slashIdx >= 0 && slashIdx < vis.length) selectSlashAction(vis[slashIdx].getAttribute('data-action'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e.ctrlKey && e.key === 'j') { e.preventDefault(); toggleAIPanel(); }
|
if (e.ctrlKey && e.key === 'j') { e.preventDefault(); toggleAIPanel(); }
|
||||||
if (e.ctrlKey && e.key === 's') { e.preventDefault(); forceSave(); }
|
if (e.ctrlKey && e.key === 's') { e.preventDefault(); forceSave(); }
|
||||||
if (e.ctrlKey && e.key === 'b') { e.preventDefault(); wrapSelection('**'); }
|
if (e.ctrlKey && e.key === 'b') { e.preventDefault(); wrapSelection('**'); }
|
||||||
@@ -833,6 +829,41 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
if (e.altKey && e.key === 'i') { e.preventDefault(); showQI(); }
|
if (e.altKey && e.key === 'i') { e.preventDefault(); showQI(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Slash menu keyboard nav (bound to search input which has focus)
|
||||||
|
slashSearch.addEventListener('keydown', function(e) {
|
||||||
|
if (!slashVisible) return;
|
||||||
|
if (e.key === 'Escape') { e.preventDefault(); hideSlash(); ta.focus(); }
|
||||||
|
else if (e.key === 'ArrowDown') { e.preventDefault(); navSlash(1); }
|
||||||
|
else if (e.key === 'ArrowUp') { e.preventDefault(); navSlash(-1); }
|
||||||
|
else if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
var vis = getVisibleSlash();
|
||||||
|
if (slashIdx >= 0 && slashIdx < vis.length) selectSlashAction(vis[slashIdx].getAttribute('data-action'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick Insert keyboard nav (document-level since no input to focus)
|
||||||
|
var qiIdx = -1;
|
||||||
|
var qiItems = [];
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (!qiMenu.classList.contains('on')) return;
|
||||||
|
if (!qiItems.length) qiItems = Array.from(qiMenu.querySelectorAll('.qi-item'));
|
||||||
|
if (e.key === 'Escape') { e.preventDefault(); qiMenu.classList.remove('on'); ta.focus(); }
|
||||||
|
else if (e.key === 'ArrowDown') { e.preventDefault(); qiIdx = (qiIdx + 1) % qiItems.length; highlightQI(); }
|
||||||
|
else if (e.key === 'ArrowUp') { e.preventDefault(); qiIdx = (qiIdx - 1 + qiItems.length) % qiItems.length; highlightQI(); }
|
||||||
|
else if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (qiIdx >= 0 && qiIdx < qiItems.length) {
|
||||||
|
var action = qiItems[qiIdx].getAttribute('data-qi');
|
||||||
|
qiMenu.classList.remove('on');
|
||||||
|
selectSlashAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function highlightQI() {
|
||||||
|
qiItems.forEach(function(item, i) { item.style.background = i === qiIdx ? 'var(--bg-hover)' : ''; item.style.color = i === qiIdx ? 'var(--text)' : ''; });
|
||||||
|
}
|
||||||
|
|
||||||
ta.addEventListener('keyup', function(e) {
|
ta.addEventListener('keyup', function(e) {
|
||||||
if (e.key === '/' && slashVisible) {
|
if (e.key === '/' && slashVisible) {
|
||||||
var l = getLine(); var p = getPos();
|
var l = getLine(); var p = getPos();
|
||||||
@@ -879,7 +910,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
|
|||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
if (slashVisible && !slashMenu.contains(e.target) && e.target !== ta) hideSlash();
|
if (slashVisible && !slashMenu.contains(e.target) && e.target !== ta) hideSlash();
|
||||||
if (bubble.classList.contains('on') && !bubble.contains(e.target)) hideBubble();
|
if (bubble.classList.contains('on') && !bubble.contains(e.target)) hideBubble();
|
||||||
if (qiMenu.classList.contains('on') && !qiMenu.contains(e.target) && e.target.id !== 'qi-btn') qiMenu.classList.remove('on');
|
if (qiMenu.classList.contains('on') && !qiMenu.contains(e.target) && e.target.id !== 'qi-btn') { qiMenu.classList.remove('on'); qiIdx = -1; qiItems = []; }
|
||||||
});
|
});
|
||||||
|
|
||||||
btnAI.addEventListener('click', toggleAIPanel);
|
btnAI.addEventListener('click', toggleAIPanel);
|
||||||
|
|||||||
Reference in New Issue
Block a user