style: enhance Telegram message formatting with visual hierarchy
Improved markdownToHtml converter for richer Telegram messages: - Heading hierarchy: h1 (🚀+separator), h2 (█), h3 (▸), h4 (●) - Multi-line blockquote merging - Indented bullet lists - Markdown table support (rendered as <pre>) - Horizontal rule rendering - Language class on fenced code blocks Co-Authored-By: zcode <noreply@zcode.dev>
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -55,6 +55,24 @@ recovers cleanly from stale processes, and has zero EADDRINUSE crashes.
|
||||
|
||||
## [2.0.0] - 2026-05-06
|
||||
|
||||
### 🎨 Improved
|
||||
|
||||
#### Telegram Message Formatting Overhaul
|
||||
|
||||
Enhanced the `markdownToHtml` converter in `src/bot/message-sender.js` to produce
|
||||
visually rich, well-structured Telegram messages:
|
||||
|
||||
- **Heading hierarchy** — h1 gets 🚀 + separator line, h2 gets █ block marker,
|
||||
h3 gets ▸ triangle, h4 gets ● dot — all bold, visually distinct
|
||||
- **Multi-line blockquotes** — consecutive `>` lines now merge into a single
|
||||
`<blockquote>` element instead of one per line
|
||||
- **Indented bullet lists** — ` • ` with leading spaces for better readability
|
||||
- **Table support** — Markdown tables (`| col | col |`) rendered as `<pre>` blocks
|
||||
- **Horizontal rules** — `---` and `***` render as ──── separator lines
|
||||
- **Code blocks** — fenced code blocks get `<pre><code>` with language class attribute
|
||||
- Cleaner vertical spacing (excessive blank lines collapsed)
|
||||
|
||||
|
||||
### 🎉 Major Release - Ruflo Integration Complete
|
||||
|
||||
Complete integration of Ruflo's multi-agent orchestration system with comprehensive documentation update.
|
||||
|
||||
@@ -31,6 +31,16 @@ const CURSOR = ' ▉';
|
||||
export function markdownToHtml(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// 0. Extract tables → protect, render as <pre>
|
||||
const tables = [];
|
||||
text = text.replace(/^(\|.+\|)\n(\|[-:\s|]+\|)\n((?:\|.+\|\n?)+)/gm, (match, header, sep, body) => {
|
||||
const idx = tables.length;
|
||||
const rows = [header, sep, ...body.trim().split('\n').filter(Boolean)];
|
||||
const escaped = rows.map(r => r.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')).join('\n');
|
||||
tables.push(`<pre>${escaped}</pre>`);
|
||||
return `\x00TB${idx}\x00`;
|
||||
});
|
||||
|
||||
// 1. Extract fenced code blocks → protect from escaping
|
||||
const codeBlocks = [];
|
||||
text = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
|
||||
@@ -62,19 +72,34 @@ export function markdownToHtml(text) {
|
||||
.replace(/>/g, '>');
|
||||
|
||||
// 4. Convert Markdown patterns → HTML tags
|
||||
// Headings: visual hierarchy via Unicode markers + bold
|
||||
text = text
|
||||
.replace(/\*\*([\s\S]+?)\*\*/g, '<b>$1</b>') // **bold** (multiline)
|
||||
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<i>$1</i>') // *italic* (not inside **)
|
||||
.replace(/\*\*([\s\S]+?)\*\*/g, '<b>$1</b>') // **bold**
|
||||
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<i>$1</i>') // *italic*
|
||||
.replace(/~~(.+?)~~/g, '<s>$1</s>') // ~~strike~~
|
||||
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>') // [link](url)
|
||||
.replace(/^####\s+(.+)$/gm, '<b>$1</b>') // h4
|
||||
.replace(/^###\s+(.+)$/gm, '<b>$1</b>') // h3
|
||||
.replace(/^##\s+(.+)$/gm, '<b>$1</b>') // h2
|
||||
.replace(/^#\s+(.+)$/gm, '<b>$1</b>') // h1
|
||||
.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>') // > quote
|
||||
.replace(/^[-*]\s+/gm, '• '); // - or * list → bullet
|
||||
// Headings with Unicode visual hierarchy
|
||||
.replace(/^#\s+(.+)$/gm, '\n\n<b>\u{1F680} $1</b>\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n') // h1 — rocket + line
|
||||
.replace(/^##\s+(.+)$/gm, '\n<b>\u2588 $1</b>') // h2 — block + bold (standout)
|
||||
.replace(/^###\s+(.+)$/gm, '\n<b>\u25B8 $1</b>') // h3 — triangle + bold
|
||||
.replace(/^####\s+(.+)$/gm, '\n<b>\u25CF $1</b>') // h4 — dot + bold
|
||||
// Multi-line blockquotes: merge consecutive > lines into one blockquote
|
||||
.replace(/(^>\s+(.+)$\n?)+/gm, (match) => {
|
||||
const content = match.trim().split('\n').map(l => l.replace(/^>\s+/, '')).join('\n');
|
||||
return `<blockquote>${content}</blockquote>`;
|
||||
})
|
||||
// Unordered lists (bullet with indent)
|
||||
.replace(/^[-*+]\s+/gm, ' \u2022 ')
|
||||
// Ordered lists
|
||||
.replace(/^\d+\.\s/gm, (m) => m)
|
||||
// Horizontal rules
|
||||
.replace(/^---+$/gm, '\u2500'.repeat(30))
|
||||
.replace(/^\*\*\*+$/gm, '\u2500'.repeat(30));
|
||||
|
||||
// 5. Restore protected code blocks and inline code
|
||||
// 5. Restore protected elements
|
||||
for (let i = 0; i < tables.length; i++) {
|
||||
text = text.replace(`\x00TB${i}\x00`, tables[i]);
|
||||
}
|
||||
for (let i = 0; i < codeBlocks.length; i++) {
|
||||
text = text.replace(`\x00CB${i}\x00`, codeBlocks[i]);
|
||||
}
|
||||
@@ -82,7 +107,10 @@ export function markdownToHtml(text) {
|
||||
text = text.replace(`\x00IC${i}\x00`, inlineCodes[i]);
|
||||
}
|
||||
|
||||
return text;
|
||||
// 6. Clean up excessive blank lines
|
||||
text = text.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user