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 <[email protected]>
This commit is contained in:
@@ -674,6 +674,7 @@ class GuideEditor {
|
|||||||
}, header);
|
}, header);
|
||||||
|
|
||||||
if (kind === 'text') {
|
if (kind === 'text') {
|
||||||
|
card.dataset.level = block.level || 'info';
|
||||||
const position = makeSelect(block.position, [
|
const position = makeSelect(block.position, [
|
||||||
{ value: 'before-title', label: 'Before title' },
|
{ value: 'before-title', label: 'Before title' },
|
||||||
{ value: 'after-title', label: 'After title' },
|
{ value: 'after-title', label: 'After title' },
|
||||||
@@ -696,7 +697,7 @@ class GuideEditor {
|
|||||||
body.dataset.blockField = 'body';
|
body.dataset.blockField = 'body';
|
||||||
body.value = (block.descriptionHtml || '').replace(/<[^>]+>/g, '');
|
body.value = (block.descriptionHtml || '').replace(/<[^>]+>/g, '');
|
||||||
position.addEventListener('change', () => { block.position = position.value; save(); });
|
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(); });
|
title.addEventListener('input', () => { block.title = title.value; save(); });
|
||||||
body.addEventListener('input', () => { block.descriptionHtml = `<p>${escapeHtml(body.value)}</p>`; save(); });
|
body.addEventListener('input', () => { block.descriptionHtml = `<p>${escapeHtml(body.value)}</p>`; save(); });
|
||||||
card.append(
|
card.append(
|
||||||
|
|||||||
@@ -514,6 +514,12 @@ kbd {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: var(--panel-2);
|
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,
|
.block-card,
|
||||||
.annotation-editor-inner {
|
.annotation-editor-inner {
|
||||||
@@ -523,12 +529,17 @@ kbd {
|
|||||||
}
|
}
|
||||||
.block-card {
|
.block-card {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
border-left: 4px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: var(--panel-solid);
|
background: var(--panel-solid);
|
||||||
}
|
}
|
||||||
.block-card[draggable="true"] { cursor: grab; }
|
.block-card[draggable="true"] { cursor: grab; }
|
||||||
.block-card[draggable="true"]:active { cursor: grabbing; }
|
.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 {
|
.annotation-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
+12
-2
@@ -18,6 +18,15 @@ const DEFAULT_TEMPLATE = {
|
|||||||
imageWidthTwips: 9000, // ~15.9cm inside A4 margins
|
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
|
const EMU_PER_PX = 9525; // 96 dpi
|
||||||
|
|
||||||
function p(children, props = '') {
|
function p(children, props = '') {
|
||||||
@@ -117,9 +126,10 @@ function exportDocx(ast, outDir, template = {}) {
|
|||||||
function emitTextBlocks(step, position) {
|
function emitTextBlocks(step, position) {
|
||||||
for (const tb of stepBlocks(step).filter((b) => b.kind === 'text' && b.position === 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 label = `${LEVEL_LABEL[tb.level] || 'Note'}${tb.title ? `: ${tb.title}` : ''}`;
|
||||||
|
const style = LEVEL_STYLE[tb.level] || LEVEL_STYLE.info;
|
||||||
body.push(p(
|
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 }) : ''),
|
||||||
'<w:shd w:val="clear" w:fill="F9FAFB"/>'
|
`<w:shd w:val="clear" w:fill="${style.fill}"/><w:pBdr><w:left w:val="single" w:sz="24" w:space="4" w:color="${style.color}"/></w:pBdr>`
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -78,10 +78,14 @@ const BASE_CSS = `
|
|||||||
pre.code { background: #f3f4f6; padding: 12px; border-radius: 6px; overflow-x: auto; }
|
pre.code { background: #f3f4f6; padding: 12px; border-radius: 6px; overflow-x: auto; }
|
||||||
table { border-collapse: collapse; margin: .6em 0; }
|
table { border-collapse: collapse; margin: .6em 0; }
|
||||||
th, td { border: 1px solid #d1d5db; padding: 4px 10px; text-align: left; }
|
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 { border-color: #f59e0b; background: #fffbeb; }
|
||||||
|
.block-warn strong { color: #b45309; }
|
||||||
.block-error { border-color: #ef4444; background: #fef2f2; }
|
.block-error { border-color: #ef4444; background: #fef2f2; }
|
||||||
|
.block-error strong { color: #b91c1c; }
|
||||||
.block-success { border-color: #10b981; background: #ecfdf5; }
|
.block-success { border-color: #10b981; background: #ecfdf5; }
|
||||||
|
.block-success strong { color: #047857; }
|
||||||
.skipped { opacity: .55; }
|
.skipped { opacity: .55; }
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body { background: #111827; color: #e5e7eb; }
|
body { background: #111827; color: #e5e7eb; }
|
||||||
|
|||||||
+13
-2
@@ -11,6 +11,15 @@ const LIST_INDENT = 14;
|
|||||||
const QUOTE_INDENT = 10;
|
const QUOTE_INDENT = 10;
|
||||||
const HEADING_BUMP = { h1: 2.5, h2: 2, h3: 1.5, h4: 1 };
|
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) {
|
function fontForRun(run) {
|
||||||
if (run.code) return 'F3';
|
if (run.code) return 'F3';
|
||||||
if (run.bold && run.italic) return 'F5';
|
if (run.bold && run.italic) return 'F5';
|
||||||
@@ -258,9 +267,11 @@ function exportPdf(ast, outDir, template = {}) {
|
|||||||
? layoutDescription(pdf, tb.descriptionHtml, usableW - 18, 9.5)
|
? layoutDescription(pdf, tb.descriptionHtml, usableW - 18, 9.5)
|
||||||
: { items: [], height: 0 };
|
: { items: [], height: 0 };
|
||||||
const blockH = 16 + bodyH;
|
const blockH = 16 + bodyH;
|
||||||
|
const style = LEVEL_STYLE[tb.level] || LEVEL_STYLE.info;
|
||||||
ensure(blockH + 4);
|
ensure(blockH + 4);
|
||||||
pdf.rect(M, y, 3, blockH, { fill: tpl.accentColor });
|
pdf.rect(M, y, usableW, blockH, { fill: style.tint });
|
||||||
pdf.text(label, M + 10, y + 2, { size: 9.5, font: 'F2' });
|
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;
|
let by = y + 16;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.kind === 'hr') {
|
if (item.kind === 'hr') {
|
||||||
|
|||||||
Reference in New Issue
Block a user