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
|
## [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
|
### 🎉 Major Release - Ruflo Integration Complete
|
||||||
|
|
||||||
Complete integration of Ruflo's multi-agent orchestration system with comprehensive documentation update.
|
Complete integration of Ruflo's multi-agent orchestration system with comprehensive documentation update.
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ const CURSOR = ' ▉';
|
|||||||
export function markdownToHtml(text) {
|
export function markdownToHtml(text) {
|
||||||
if (!text) return '';
|
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
|
// 1. Extract fenced code blocks → protect from escaping
|
||||||
const codeBlocks = [];
|
const codeBlocks = [];
|
||||||
text = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
|
text = text.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
|
||||||
@@ -62,19 +72,34 @@ export function markdownToHtml(text) {
|
|||||||
.replace(/>/g, '>');
|
.replace(/>/g, '>');
|
||||||
|
|
||||||
// 4. Convert Markdown patterns → HTML tags
|
// 4. Convert Markdown patterns → HTML tags
|
||||||
|
// Headings: visual hierarchy via Unicode markers + bold
|
||||||
text = text
|
text = text
|
||||||
.replace(/\*\*([\s\S]+?)\*\*/g, '<b>$1</b>') // **bold** (multiline)
|
.replace(/\*\*([\s\S]+?)\*\*/g, '<b>$1</b>') // **bold**
|
||||||
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<i>$1</i>') // *italic* (not inside **)
|
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<i>$1</i>') // *italic*
|
||||||
.replace(/~~(.+?)~~/g, '<s>$1</s>') // ~~strike~~
|
.replace(/~~(.+?)~~/g, '<s>$1</s>') // ~~strike~~
|
||||||
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>') // [link](url)
|
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>') // [link](url)
|
||||||
.replace(/^####\s+(.+)$/gm, '<b>$1</b>') // h4
|
// Headings with Unicode visual hierarchy
|
||||||
.replace(/^###\s+(.+)$/gm, '<b>$1</b>') // h3
|
.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, '<b>$1</b>') // h2
|
.replace(/^##\s+(.+)$/gm, '\n<b>\u2588 $1</b>') // h2 — block + bold (standout)
|
||||||
.replace(/^#\s+(.+)$/gm, '<b>$1</b>') // h1
|
.replace(/^###\s+(.+)$/gm, '\n<b>\u25B8 $1</b>') // h3 — triangle + bold
|
||||||
.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>') // > quote
|
.replace(/^####\s+(.+)$/gm, '\n<b>\u25CF $1</b>') // h4 — dot + bold
|
||||||
.replace(/^[-*]\s+/gm, '• '); // - or * list → bullet
|
// 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++) {
|
for (let i = 0; i < codeBlocks.length; i++) {
|
||||||
text = text.replace(`\x00CB${i}\x00`, codeBlocks[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]);
|
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