diff --git a/app/renderer/editor.js b/app/renderer/editor.js index 36e1c52..814bad2 100644 --- a/app/renderer/editor.js +++ b/app/renderer/editor.js @@ -329,10 +329,32 @@ class GuideEditor { } toolbarBtn(action, label, block = null) { - return el('button', { + const btn = el('button', { type: 'button', onClick: () => this.formatDescription(action, block), }, label); + btn.dataset.action = action; + if (block) btn.dataset.block = block; + return btn; + } + + /** Reflect the current selection's formatting state on the toolbar buttons. */ + updateToolbarState() { + if (!this.dom.richToolbar) return; + for (const btn of this.dom.richToolbar.children) { + const { action, block } = btn.dataset; + let active = false; + try { + if (action === 'formatBlock') { + active = document.queryCommandValue('formatBlock').toLowerCase() === (block || 'blockquote'); + } else if (action === 'bold' || action === 'italic' || action === 'insertUnorderedList' || action === 'insertOrderedList') { + active = document.queryCommandState(action); + } + } catch { + active = false; + } + btn.classList.toggle('active', active); + } } bindShellEvents() { @@ -422,7 +444,14 @@ class GuideEditor { this.dom.addTableBlockBtn.addEventListener('click', () => this.addBlock('table')); this.dom.descEditor.addEventListener('focus', () => { + // Make Enter start a new paragraph (

) rather than a plain

, + // so line breaks survive sanitization and show up in exports. + document.execCommand('defaultParagraphSeparator', false, 'p'); if (this.currentStep) this.pushCanvasHistory('description'); + this.updateToolbarState(); + }); + this.dom.descEditor.addEventListener('blur', () => { + for (const btn of this.dom.richToolbar.children) btn.classList.remove('active'); }); this.dom.descEditor.addEventListener('input', () => { if (!this.currentStep) return; @@ -430,6 +459,12 @@ class GuideEditor { this.pendingSave = true; this.saveStepDebounced(); this.emitMeta(); + this.updateToolbarState(); + }); + this.dom.descEditor.addEventListener('keyup', () => this.updateToolbarState()); + this.dom.descEditor.addEventListener('mouseup', () => this.updateToolbarState()); + document.addEventListener('selectionchange', () => { + if (document.activeElement === this.dom.descEditor) this.updateToolbarState(); }); this.dom.descEditor.addEventListener('paste', (e) => { @@ -1821,9 +1856,12 @@ class GuideEditor { case 'insertOrderedList': document.execCommand('insertOrderedList'); break; - case 'formatBlock': - document.execCommand('formatBlock', false, block || 'blockquote'); + case 'formatBlock': { + const want = block || 'blockquote'; + const current = document.queryCommandValue('formatBlock').toLowerCase(); + document.execCommand('formatBlock', false, current === want ? 'p' : want); break; + } case 'createLink': { const selectedText = window.getSelection().toString(); const text = selectedText || window.prompt('Link text'); @@ -1844,6 +1882,7 @@ class GuideEditor { this.pendingSave = true; this.saveStepDebounced(); } + this.updateToolbarState(); } onDocumentKeyDown(e) { diff --git a/app/renderer/style.css b/app/renderer/style.css index 9f1c37e..137b1a0 100644 --- a/app/renderer/style.css +++ b/app/renderer/style.css @@ -487,6 +487,11 @@ kbd { margin-bottom: 8px; } .rich-toolbar button { padding: 4px 8px; font-size: 12px; } +.rich-toolbar button.active { + background: var(--accent); + border-color: var(--accent); + color: var(--accent-fg); +} .rich-editor { min-height: 110px; max-height: 220px; diff --git a/exporters/htmlmd.js b/exporters/htmlmd.js index fdc224c..067fd59 100644 --- a/exporters/htmlmd.js +++ b/exporters/htmlmd.js @@ -42,6 +42,8 @@ function htmlToMarkdown(html) { .replace(//gi, '\n---\n') .replace(/

/gi, '\n') .replace(/<\/p>/gi, '\n') + .replace(/

/gi, '\n') + .replace(/<\/div>/gi, '\n') .replace(/<[^>]+>/g, ''); return decodeEntities(out).replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();