feat: inline table form + code language picker (no more prompt dialogs)
CI / lint (push) Successful in 15s
CI / security (push) Successful in 9s
CI / test (push) Successful in 25s
CI / build (push) Successful in 3s

Table: inline form with cols/rows inputs, positioned at cursor. Enter to insert, Esc to cancel. Code block: language picker with 41 languages (bash,js,ts,py,java,rust,go,c,cpp,html,css,json,yaml,sql,dockerfile...), navigable via arrows+Enter. All positioned via caretToPixel like other menus. Click outside closes.
This commit is contained in:
2026-06-06 00:02:16 -04:00
parent f42e37489b
commit 76a5b23e96
+145 -16
View File
@@ -135,6 +135,26 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
.qi-item { display: flex; align-items: center; gap: 7px; padding: 6px 9px; font-size: 12px; color: var(--text2); cursor: pointer; border-radius: var(--r); }
.qi-item:hover { background: var(--bg-hover); color: var(--text); }
/* Inline form (table dims, code lang) */
.inline-form { position: absolute; z-index: 195; background: var(--bg4); border: 1px solid var(--border2); border-radius: var(--R); box-shadow: var(--shadow); display: none; padding: 8px 10px; min-width: 200px; }
.inline-form.on { display: block; animation: fadeIn 100ms; }
.inline-form-label { font-size: 10px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 6px; }
.inline-form-row { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
.inline-form-row label { font-size: 11px; color: var(--text2); min-width: 55px; }
.inline-form-row input { flex: 1; padding: 4px 7px; border-radius: var(--r); border: 1px solid var(--border); background: var(--bg3); color: var(--text); font-size: 12px; outline: none; font-family: var(--mono); width: 60px; }
.inline-form-row input:focus { border-color: var(--accent); }
.inline-form-actions { display: flex; gap: 4px; justify-content: flex-end; margin-top: 4px; }
.inline-form-actions button { padding: 4px 12px; border-radius: var(--r); border: 1px solid var(--border); background: var(--bg3); color: var(--text); cursor: pointer; font-size: 11px; font-family: var(--sans); }
.inline-form-actions button.ok { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.inline-form-actions button:hover { background: var(--bg-hover); }
.inline-form-actions button.ok:hover { background: #5a8fc5; }
/* Lang picker (in QI menu style) */
.lang-picker { position: absolute; z-index: 195; width: 200px; max-height: 320px; overflow-y: auto; background: var(--bg4); border: 1px solid var(--border2); border-radius: var(--R); box-shadow: var(--shadow); display: none; padding: 3px; }
.lang-picker.on { display: block; animation: fadeIn 100ms; }
.lang-item { display: flex; align-items: center; gap: 7px; padding: 6px 9px; font-size: 12px; color: var(--text2); cursor: pointer; border-radius: var(--r); font-family: var(--mono); }
.lang-item:hover, .lang-item.sel { background: var(--bg-hover); color: var(--text); }
/* Drag & Drop */
.drop-overlay { position: fixed; inset: 0; z-index: 500; display: none; align-items: center; justify-content: center; background: rgba(15,17,23,0.85); border: 3px dashed var(--accent); pointer-events: none; }
.drop-overlay.on { display: flex; }
@@ -212,6 +232,20 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
<div class="qi-item" data-qi="mermaid-pie">&#128262; Pie Chart</div>
</div>
<!-- Inline form (table dims) -->
<div class="inline-form" id="inline-form">
<div class="inline-form-label" id="inline-form-label">Table dimensions</div>
<div class="inline-form-row"><label>Colonnes</label><input type="number" id="if-cols" value="3" min="1" max="12"></div>
<div class="inline-form-row"><label>Lignes</label><input type="number" id="if-rows" value="3" min="1" max="50"></div>
<div class="inline-form-actions">
<button class="ok" id="if-ok">Insérer</button>
<button id="if-cancel">Annuler</button>
</div>
</div>
<!-- Language picker (code block) -->
<div class="lang-picker" id="lang-picker"></div>
<!-- Slash menu -->
<div class="slash" id="slash-menu">
<div class="slash-search"><input type="text" id="slash-search" placeholder="Filter..." autocomplete="off"></div>
@@ -674,7 +708,6 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
numbered: function() { replaceLine('1. '); },
checklist: function() { replaceLine('- [ ] '); },
quote: function() { replaceLine('> '); },
code: function() { insertAtCursor('\n```\n\n```\n', 5); },
divider: function() { insertAtCursor('\n---\n'); },
image: function() { insertAtCursor('![](url)', 2); },
link: function() { insertAtCursor('[text](url)', 1); },
@@ -736,21 +769,10 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
insertAtCursor('\n```mermaid\n' + tpl + '\n```\n', 12);
ta.focus(); return;
}
// Table: prompt for dimensions
if (action === 'table') {
var cols = parseInt(prompt('Nombre de colonnes ?', '3'));
var rows = parseInt(prompt('Nombre de lignes ?', '3'));
if (!cols || !rows || cols < 1 || rows < 1) { ta.focus(); return; }
var hdr = '|'; var sep = '|';
for (var c = 1; c <= cols; c++) { hdr += ' Col ' + c + ' |'; sep += '-------|'; }
var body = '';
for (var r = 1; r <= rows; r++) {
body += '\n|';
for (var cc = 1; cc <= cols; cc++) body += ' |';
}
insertAtCursor('\n' + hdr + '\n' + sep + body + '\n', 2);
ta.focus(); return;
}
// Table: inline form (not prompt)
if (action === 'table') { showTableForm(); return; }
// Code block: language picker (not bare ```)
if (action === 'code') { showLangPicker(); return; }
if (action && action.indexOf('ai-') === 0) { execAIAction(action); ta.focus(); return; }
var fn = SLASH_INSERT[action];
if (fn) fn(); else if (action) showToast('Action: ' + action);
@@ -796,6 +818,111 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
hideBubble(); ta.focus();
}
// ---- Table form (inline, no prompt) ----
var inlineForm = document.getElementById('inline-form');
var ifCols = document.getElementById('if-cols');
var ifRows = document.getElementById('if-rows');
function showTableForm() {
hideAllMenus();
ifCols.value = '3'; ifRows.value = '3';
var pt = caretToPixel(null);
var wr = editorWrap.getBoundingClientRect();
inlineForm.style.left = (wr.left + pt.x) + 'px';
inlineForm.style.top = (wr.top + pt.y + 8) + 'px';
inlineForm.classList.add('on');
ifCols.focus(); ifCols.select();
}
function insertTableFromForm() {
var cols = parseInt(ifCols.value) || 3;
var rows = parseInt(ifRows.value) || 3;
if (cols < 1) cols = 1; if (rows < 1) rows = 1;
var hdr = '|'; var sep = '|';
for (var c = 1; c <= cols; c++) { hdr += ' Col ' + c + ' |'; sep += '-------|'; }
var body = '';
for (var r = 1; r <= rows; r++) {
body += '\n|';
for (var cc = 1; cc <= cols; cc++) body += ' |';
}
insertAtCursor('\n' + hdr + '\n' + sep + body + '\n', 2);
inlineForm.classList.remove('on');
ta.focus();
}
document.getElementById('if-ok').addEventListener('click', insertTableFromForm);
document.getElementById('if-cancel').addEventListener('click', function() { inlineForm.classList.remove('on'); ta.focus(); });
ifCols.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); insertTableFromForm(); }
if (e.key === 'Escape') { e.preventDefault(); inlineForm.classList.remove('on'); ta.focus(); }
});
ifRows.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); insertTableFromForm(); }
if (e.key === 'Escape') { e.preventDefault(); inlineForm.classList.remove('on'); ta.focus(); }
});
// ---- Language picker (code block) ----
var langPicker = document.getElementById('lang-picker');
var LANGS = ['bash','javascript','typescript','python','java','rust','go','c','cpp','csharp','html','css','scss','json','yaml','toml','xml','sql','dockerfile','makefile','markdown','php','ruby','swift','kotlin','lua','r','perl','scala','elixir','haskell','clojure','erlang','ocaml','zig','nim','groovy','dart','graphql','protobuf','text'];
var langIdx = -1;
function showLangPicker() {
hideAllMenus();
langIdx = -1;
var html = '<div style="font-size:10px;font-weight:600;color:var(--text3);padding:4px 9px 2px;text-transform:uppercase">Langage</div>';
LANGS.forEach(function(l, i) {
html += '<div class="lang-item" data-idx="' + i + '">' + l + '</div>';
});
langPicker.innerHTML = html;
var pt = caretToPixel(null);
var wr = editorWrap.getBoundingClientRect();
langPicker.style.left = (wr.left + pt.x) + 'px';
langPicker.style.top = (wr.top + pt.y + 8) + 'px';
langPicker.classList.add('on');
// Click handler
langPicker.querySelectorAll('.lang-item').forEach(function(item) {
item.addEventListener('click', function() {
var idx = parseInt(this.getAttribute('data-idx'));
insertCodeBlock(LANGS[idx]);
});
});
}
function insertCodeBlock(lang) {
langPicker.classList.remove('on');
insertAtCursor('\n```' + lang + '\n\n```\n', 4 + lang.length);
ta.focus();
}
function hideAllMenus() {
inlineForm.classList.remove('on');
langPicker.classList.remove('on');
qiMenu.classList.remove('on');
qiIdx = -1; qiItems = [];
}
// Lang picker keyboard nav (document-level)
var _origKeydownQI = null; // will store reference — actually we handle in the existing document keydown
// Add lang picker nav to the existing document keydown handler
var _langItems = [];
document.addEventListener('keydown', function(e) {
if (!langPicker.classList.contains('on')) return;
_langItems = Array.from(langPicker.querySelectorAll('.lang-item'));
if (e.key === 'Escape') { e.preventDefault(); langPicker.classList.remove('on'); ta.focus(); }
else if (e.key === 'ArrowDown') { e.preventDefault(); langIdx = (langIdx + 1) % _langItems.length; highlightLang(); }
else if (e.key === 'ArrowUp') { e.preventDefault(); langIdx = (langIdx - 1 + _langItems.length) % _langItems.length; highlightLang(); }
else if (e.key === 'Enter') {
e.preventDefault();
if (langIdx >= 0 && langIdx < _langItems.length) {
var idx = parseInt(_langItems[langIdx].getAttribute('data-idx'));
insertCodeBlock(LANGS[idx]);
}
}
});
function highlightLang() {
_langItems.forEach(function(item, i) { item.classList.toggle('sel', i === langIdx); });
}
// ---- Quick Insert ----
function showQI() {
qiMenu.classList.remove('on');
@@ -980,6 +1107,8 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
if (slashVisible && !slashMenu.contains(e.target) && e.target !== ta) hideSlash();
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'); qiIdx = -1; qiItems = []; }
if (inlineForm.classList.contains('on') && !inlineForm.contains(e.target)) { inlineForm.classList.remove('on'); ta.focus(); }
if (langPicker.classList.contains('on') && !langPicker.contains(e.target)) { langPicker.classList.remove('on'); ta.focus(); }
});
btnAI.addEventListener('click', toggleAIPanel);