From 218c77dafbbc798a4fd63d3aaccc9ccb7da3f8ee Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Fri, 5 Jun 2026 23:33:37 -0400 Subject: [PATCH] feat: auto-height, sync scroll, Mermaid QI, table prompt, bubble follow Textarea overflow:hidden + auto-height via scrollHeight to eliminate double scrollbar. Sync scroll between editor and preview panes (bidirectional percentage). Alt+I menu now includes 7 Mermaid diagram templates (flowchart, sequence, class, state, ER, gantt, pie). Table now prompts for cols/rows via prompt(). Bubble menu follows cursor via document selectionchange event. --- frontend/editor-poc.html | 79 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/frontend/editor-poc.html b/frontend/editor-poc.html index c87d328..271415b 100644 --- a/frontend/editor-poc.html +++ b/frontend/editor-poc.html @@ -52,7 +52,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig /* Textarea pane */ .editor-wrap { flex: 1; overflow-y: auto; position: relative; padding: 24px 16px 160px 48px; } -.textarea { width: 100%; min-height: 100%; resize: none; background: transparent; border: none; outline: none; color: var(--text); font-family: var(--mono); font-size: 13.5px; line-height: 1.75; tab-size: 2; } +.textarea { width: 100%; height: auto; min-height: 100%; resize: none; overflow: hidden; background: transparent; border: none; outline: none; color: var(--text); font-family: var(--mono); font-size: 13.5px; line-height: 1.75; tab-size: 2; } .textarea::placeholder { color: var(--text3); } /* Quick insert + */ @@ -130,7 +130,7 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig .bubble-more-item:hover { background: var(--bg-hover); color: var(--text); } /* Quick Insert menu */ -.qi-menu { position: absolute; z-index: 190; width: 170px; background: var(--bg4); border: 1px solid var(--border2); border-radius: var(--R); box-shadow: var(--shadow); display: none; padding: 3px; } +.qi-menu { position: absolute; z-index: 190; width: 200px; max-height: 380px; overflow-y: auto; background: var(--bg4); border: 1px solid var(--border2); border-radius: var(--R); box-shadow: var(--shadow); display: none; padding: 3px; } .qi-menu.on { display: block; animation: fadeIn 100ms; } .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); } @@ -201,6 +201,15 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig
</> Code Block
📊 Table
─ Divider
+
+
📐 Diagram
+
🔀 Flowchart
+
🔁 Sequence
+
🔂 Class Diagram
+
🔃 State Diagram
+
🔄 ER Diagram
+
🔅 Gantt Chart
+
🔆 Pie Chart
@@ -583,6 +592,23 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig if (previewVisible) renderPreview(); } + // ---- Sync scroll between editor and preview ---- + var _syncScrolling = false; + editorWrap.addEventListener('scroll', function() { + if (!previewVisible || _syncScrolling) return; + _syncScrolling = true; + var pct = editorWrap.scrollTop / (editorWrap.scrollHeight - editorWrap.clientHeight); + previewPane.scrollTop = pct * (previewPane.scrollHeight - previewPane.clientHeight); + setTimeout(function() { _syncScrolling = false; }, 50); + }); + previewPane.addEventListener('scroll', function() { + if (_syncScrolling) return; + _syncScrolling = true; + var pct = previewPane.scrollTop / (previewPane.scrollHeight - previewPane.clientHeight); + editorWrap.scrollTop = pct * (editorWrap.scrollHeight - editorWrap.clientHeight); + setTimeout(function() { _syncScrolling = false; }, 50); + }); + // ---- Resize handle ---- (function() { var dragging = false; @@ -650,7 +676,6 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig divider: function() { insertAtCursor('\n---\n'); }, image: function() { insertAtCursor('![](url)', 2); }, link: function() { insertAtCursor('[text](url)', 1); }, - table: function() { insertAtCursor('\n| Col 1 | Col 2 |\n|-------|-------|\n| | |\n', 2); }, }; function showSlash() { @@ -693,6 +718,37 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig function selectSlashAction(action) { hideSlash(); + // Handle QI-specific actions + if (action && action.indexOf('mermaid-') === 0) { + var type = action.replace('mermaid-', ''); + var templates = { + flowchart: 'flowchart TD\n A[Start] --> B[Process] --> C[End]', + sequence: 'sequenceDiagram\n Alice->>Bob: Hello\n Bob-->>Alice: Hi!', + class: 'classDiagram\n class Animal {\n +name: string\n +eat()\n }\n class Dog {\n +bark()\n }\n Animal <|-- Dog', + state: 'stateDiagram-v2\n [*] --> Idle\n Idle --> Processing\n Processing --> Done\n Done --> [*]', + er: 'erDiagram\n CUSTOMER ||--o{ ORDER : places\n ORDER ||--|{ LINE_ITEM : contains', + gantt: 'gantt\n title Project Plan\n dateFormat YYYY-MM-DD\n section Phase 1\n Task A :a1, 2026-01-01, 30d\n Task B :after a1, 20d', + pie: 'pie title Distribution\n "Category A" : 45\n "Category B" : 30\n "Category C" : 25' + }; + var tpl = templates[type] || 'flowchart TD\n A --> B'; + insertAtCursor('\n```mermaid\n' + tpl + '\n```\n', 12); + ta.focus(); return; + } + // Table: prompt for dimensions + if (action === 'table') { + var cols = parseInt(prompt('Number of columns?', '3')); + var rows = parseInt(prompt('Number of rows?', '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; + } 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); @@ -847,8 +903,8 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig 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(); } + qiItems = Array.from(qiMenu.querySelectorAll('.qi-item')); // refresh each time + if (e.key === 'Escape') { e.preventDefault(); qiMenu.classList.remove('on'); qiIdx = -1; 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') { @@ -872,8 +928,19 @@ body { font-family: var(--sans); background: var(--bg); color: var(--text); heig } }); - ta.addEventListener('input', function() { updateStats(); markDirty(); }); + ta.addEventListener('input', function() { + updateStats(); markDirty(); + // Auto-height: grow textarea to fit content (no double scrollbar) + ta.style.height = 'auto'; + ta.style.height = ta.scrollHeight + 'px'; + }); ta.addEventListener('mouseup', function() { setTimeout(showBubble, 50); }); + // Bubble follows cursor on any selection change (keyboard too) + document.addEventListener('selectionchange', function() { + if (document.activeElement !== ta) return; + clearTimeout(window._bubbleTimer); + window._bubbleTimer = setTimeout(showBubble, 80); + }); slashSearch.addEventListener('input', function() { filterSlash(slashSearch.value); }); slashMenu.addEventListener('click', function(e) {