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 (
,
+ // 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();