From 76a5b23e967974f8169030e02ceb6fc77b168267 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sat, 6 Jun 2026 00:02:16 -0400 Subject: [PATCH] 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. --- frontend/editor-poc.html | 161 +++++++++++++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 16 deletions(-) diff --git a/frontend/editor-poc.html b/frontend/editor-poc.html index 7738e02..b09d885 100644 --- a/frontend/editor-poc.html +++ b/frontend/editor-poc.html @@ -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
🔆 Pie Chart
+ +
+
Table dimensions
+
+
+
+ + +
+
+ + +
+
@@ -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 = '
Langage
'; + LANGS.forEach(function(l, i) { + html += '
' + l + '
'; + }); + 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);