feat: inline table form + code language picker (no more prompt dialogs)
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:
+145
-16
@@ -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">🔆 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('', 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);
|
||||
|
||||
Reference in New Issue
Block a user