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.
This commit is contained in:
@@ -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
|
||||
<div class="qi-item" data-qi="code"></> Code Block</div>
|
||||
<div class="qi-item" data-qi="table">📊 Table</div>
|
||||
<div class="qi-item" data-qi="divider">─ Divider</div>
|
||||
<div style="height:1px;margin:3px 6px;background:var(--border)"></div>
|
||||
<div style="font-size:10px;font-weight:600;color:var(--text3);padding:4px 9px 2px;text-transform:uppercase;letter-spacing:0.4px">📐 Diagram</div>
|
||||
<div class="qi-item" data-qi="mermaid-flowchart">🔀 Flowchart</div>
|
||||
<div class="qi-item" data-qi="mermaid-sequence">🔁 Sequence</div>
|
||||
<div class="qi-item" data-qi="mermaid-class">🔂 Class Diagram</div>
|
||||
<div class="qi-item" data-qi="mermaid-state">🔃 State Diagram</div>
|
||||
<div class="qi-item" data-qi="mermaid-er">🔄 ER Diagram</div>
|
||||
<div class="qi-item" data-qi="mermaid-gantt">🔅 Gantt Chart</div>
|
||||
<div class="qi-item" data-qi="mermaid-pie">🔆 Pie Chart</div>
|
||||
</div>
|
||||
|
||||
<!-- Slash menu -->
|
||||
@@ -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('', 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) {
|
||||
|
||||
Reference in New Issue
Block a user