From b216f2b90d3c017ab58471039ad72fe5636318fa Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 6 May 2026 13:01:45 +0000 Subject: [PATCH] style: enhance Telegram message formatting with visual hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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
)
- Horizontal rule rendering
- Language class on fenced code blocks

Co-Authored-By: zcode 
---
 CHANGELOG.md              | 18 ++++++++++++++
 src/bot/message-sender.js | 52 ++++++++++++++++++++++++++++++---------
 2 files changed, 58 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ecd73e1..bcfcd1bf 100644
--- a/CHANGELOG.md
+++ b/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
+  `
` element instead of one per line +- **Indented bullet lists** — ` • ` with leading spaces for better readability +- **Table support** — Markdown tables (`| col | col |`) rendered as `
` blocks
+- **Horizontal rules** — `---` and `***` render as ──── separator lines
+- **Code blocks** — fenced code blocks get `
` 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.
diff --git a/src/bot/message-sender.js b/src/bot/message-sender.js
index 53cbccd6..af147c84 100644
--- a/src/bot/message-sender.js
+++ b/src/bot/message-sender.js
@@ -31,6 +31,16 @@ const CURSOR = ' ▉';
 export function markdownToHtml(text) {
   if (!text) return '';
 
+  // 0. Extract tables → protect, render as 
+  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, '>')).join('\n');
+    tables.push(`
${escaped}
`); + 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, '$1') // **bold** (multiline) - .replace(/(?$1') // *italic* (not inside **) - .replace(/~~(.+?)~~/g, '$1') // ~~strike~~ - .replace(/\[(.+?)\]\((.+?)\)/g, '$1') // [link](url) - .replace(/^####\s+(.+)$/gm, '$1') // h4 - .replace(/^###\s+(.+)$/gm, '$1') // h3 - .replace(/^##\s+(.+)$/gm, '$1') // h2 - .replace(/^#\s+(.+)$/gm, '$1') // h1 - .replace(/^>\s+(.+)$/gm, '
$1
') // > quote - .replace(/^[-*]\s+/gm, '• '); // - or * list → bullet + .replace(/\*\*([\s\S]+?)\*\*/g, '$1') // **bold** + .replace(/(?$1') // *italic* + .replace(/~~(.+?)~~/g, '$1') // ~~strike~~ + .replace(/\[(.+?)\]\((.+?)\)/g, '$1') // [link](url) + // Headings with Unicode visual hierarchy + .replace(/^#\s+(.+)$/gm, '\n\n\u{1F680} $1\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\u2588 $1') // h2 — block + bold (standout) + .replace(/^###\s+(.+)$/gm, '\n\u25B8 $1') // h3 — triangle + bold + .replace(/^####\s+(.+)$/gm, '\n\u25CF $1') // 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 `
${content}
`; + }) + // 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(); } /**