From fee394f6b77a60c1ab6c95fade19fba8b70b4b73 Mon Sep 17 00:00:00 2001 From: Twest2 Date: Sat, 13 Jun 2026 23:02:03 -0500 Subject: [PATCH] Add quote indicator and distinct callout styling for Note/Tip/Warning/Important - Description editor: blockquotes get a left accent line and muted color so it's clear when typing in "quote mode" (matches the existing active Quote toolbar button highlight). - Editor block cards: each text block's left border is colored by its level (info/success/warn/error) so Note/Tip/Warning/Important are distinguishable at a glance. - PDF/HTML/DOCX exports: callouts now get a level-specific accent color and tinted background/shading (blue/green/amber/red), instead of all looking identical except for the label text. Co-Authored-By: Claude Sonnet 4.6 --- app/renderer/editor.js | 3 ++- app/renderer/style.css | 11 +++++++++++ exporters/docx.js | 14 ++++++++++++-- exporters/html.js | 6 +++++- exporters/pdf.js | 15 +++++++++++++-- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/app/renderer/editor.js b/app/renderer/editor.js index 2d6a51b..af20fd7 100644 --- a/app/renderer/editor.js +++ b/app/renderer/editor.js @@ -674,6 +674,7 @@ class GuideEditor { }, header); if (kind === 'text') { + card.dataset.level = block.level || 'info'; const position = makeSelect(block.position, [ { value: 'before-title', label: 'Before title' }, { value: 'after-title', label: 'After title' }, @@ -696,7 +697,7 @@ class GuideEditor { body.dataset.blockField = 'body'; body.value = (block.descriptionHtml || '').replace(/<[^>]+>/g, ''); position.addEventListener('change', () => { block.position = position.value; save(); }); - level.addEventListener('change', () => { block.level = level.value; save(); }); + level.addEventListener('change', () => { block.level = level.value; card.dataset.level = level.value; save(); }); title.addEventListener('input', () => { block.title = title.value; save(); }); body.addEventListener('input', () => { block.descriptionHtml = `

${escapeHtml(body.value)}

`; save(); }); card.append( diff --git a/app/renderer/style.css b/app/renderer/style.css index 137b1a0..06f6675 100644 --- a/app/renderer/style.css +++ b/app/renderer/style.css @@ -514,6 +514,12 @@ kbd { border-radius: 10px; background: var(--panel-2); } +.rich-editor blockquote { + margin: 6px 0; + padding: 2px 0 2px 12px; + border-left: 3px solid var(--accent); + color: var(--muted); +} .block-card, .annotation-editor-inner { @@ -523,12 +529,17 @@ kbd { } .block-card { border: 1px solid var(--border); + border-left: 4px solid var(--border); border-radius: 12px; padding: 10px; background: var(--panel-solid); } .block-card[draggable="true"] { cursor: grab; } .block-card[draggable="true"]:active { cursor: grabbing; } +.block-card[data-level="info"] { border-left-color: #3b82f6; } +.block-card[data-level="success"] { border-left-color: #10b981; } +.block-card[data-level="warn"] { border-left-color: #f59e0b; } +.block-card[data-level="error"] { border-left-color: #ef4444; } .annotation-list { display: flex; flex-direction: column; diff --git a/exporters/docx.js b/exporters/docx.js index 5ca8972..ef761ce 100644 --- a/exporters/docx.js +++ b/exporters/docx.js @@ -18,6 +18,15 @@ const DEFAULT_TEMPLATE = { imageWidthTwips: 9000, // ~15.9cm inside A4 margins }; +// Callout styling per text-block level, matching the colors used in the +// HTML/PDF exports so a "Tip" looks distinct from a "Warning" at a glance. +const LEVEL_STYLE = { + info: { fill: 'EFF6FF', color: '1D4ED8' }, // blue — Note + success: { fill: 'ECFDF5', color: '047857' }, // green — Tip + warn: { fill: 'FFFBEB', color: 'B45309' }, // amber — Warning + error: { fill: 'FEF2F2', color: 'B91C1C' }, // red — Important +}; + const EMU_PER_PX = 9525; // 96 dpi function p(children, props = '') { @@ -117,9 +126,10 @@ function exportDocx(ast, outDir, template = {}) { function emitTextBlocks(step, position) { for (const tb of stepBlocks(step).filter((b) => b.kind === 'text' && b.position === position)) { const label = `${LEVEL_LABEL[tb.level] || 'Note'}${tb.title ? `: ${tb.title}` : ''}`; + const style = LEVEL_STYLE[tb.level] || LEVEL_STYLE.info; body.push(p( - run(label, { bold: true, size: 20 }) + (tb.descriptionText ? run('\n' + tb.descriptionText, { size: 20 }) : ''), - '' + run(label, { bold: true, size: 20, color: style.color }) + (tb.descriptionText ? run('\n' + tb.descriptionText, { size: 20 }) : ''), + `` )); } } diff --git a/exporters/html.js b/exporters/html.js index 8b458c6..4318f4f 100644 --- a/exporters/html.js +++ b/exporters/html.js @@ -78,10 +78,14 @@ const BASE_CSS = ` pre.code { background: #f3f4f6; padding: 12px; border-radius: 6px; overflow-x: auto; } table { border-collapse: collapse; margin: .6em 0; } th, td { border: 1px solid #d1d5db; padding: 4px 10px; text-align: left; } - .block { border-left: 4px solid #9ca3af; background: #f9fafb; padding: 8px 12px; margin: .6em 0; border-radius: 0 6px 6px 0; } + .block { border-left: 4px solid #3b82f6; background: #eff6ff; padding: 8px 12px; margin: .6em 0; border-radius: 0 6px 6px 0; } + .block strong { color: #1d4ed8; } .block-warn { border-color: #f59e0b; background: #fffbeb; } + .block-warn strong { color: #b45309; } .block-error { border-color: #ef4444; background: #fef2f2; } + .block-error strong { color: #b91c1c; } .block-success { border-color: #10b981; background: #ecfdf5; } + .block-success strong { color: #047857; } .skipped { opacity: .55; } @media (prefers-color-scheme: dark) { body { background: #111827; color: #e5e7eb; } diff --git a/exporters/pdf.js b/exporters/pdf.js index bf3d5e7..1f5eedb 100644 --- a/exporters/pdf.js +++ b/exporters/pdf.js @@ -11,6 +11,15 @@ const LIST_INDENT = 14; const QUOTE_INDENT = 10; const HEADING_BUMP = { h1: 2.5, h2: 2, h3: 1.5, h4: 1 }; +// Callout styling per text-block level, matching the colors used in the +// HTML/editor UI so a "Tip" looks distinct from a "Warning" at a glance. +const LEVEL_STYLE = { + info: { accent: [59, 130, 246], tint: [239, 246, 255] }, // blue — Note + success: { accent: [16, 185, 129], tint: [236, 253, 245] }, // green — Tip + warn: { accent: [245, 158, 11], tint: [255, 251, 235] }, // amber — Warning + error: { accent: [239, 68, 68], tint: [254, 242, 242] }, // red — Important +}; + function fontForRun(run) { if (run.code) return 'F3'; if (run.bold && run.italic) return 'F5'; @@ -258,9 +267,11 @@ function exportPdf(ast, outDir, template = {}) { ? layoutDescription(pdf, tb.descriptionHtml, usableW - 18, 9.5) : { items: [], height: 0 }; const blockH = 16 + bodyH; + const style = LEVEL_STYLE[tb.level] || LEVEL_STYLE.info; ensure(blockH + 4); - pdf.rect(M, y, 3, blockH, { fill: tpl.accentColor }); - pdf.text(label, M + 10, y + 2, { size: 9.5, font: 'F2' }); + pdf.rect(M, y, usableW, blockH, { fill: style.tint }); + pdf.rect(M, y, 3, blockH, { fill: style.accent }); + pdf.text(label, M + 10, y + 2, { size: 9.5, font: 'F2', color: style.accent }); let by = y + 16; for (const item of items) { if (item.kind === 'hr') {