@alexnodeland/claude-telegram 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/html.ts +63 -2
package/package.json
CHANGED
package/src/html.ts
CHANGED
|
@@ -35,9 +35,9 @@ export function markdownToTelegramHtml(md: string): string {
|
|
|
35
35
|
const PLACEHOLDER_PREFIX = "\u2060CBLK";
|
|
36
36
|
const PLACEHOLDER_SUFFIX = "CBLK\u2060";
|
|
37
37
|
|
|
38
|
-
// 1. Extract fenced code blocks into placeholders
|
|
38
|
+
// 1. Extract fenced code blocks and tables into placeholders
|
|
39
39
|
const codeBlocks: string[] = [];
|
|
40
|
-
|
|
40
|
+
let withPlaceholders = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang: string, code: string) => {
|
|
41
41
|
const escaped = escapeHtml(code.replace(/\n$/, ""));
|
|
42
42
|
const html = lang
|
|
43
43
|
? `<pre><code class="language-${escapeHtml(lang)}">${escaped}</code></pre>`
|
|
@@ -46,6 +46,13 @@ export function markdownToTelegramHtml(md: string): string {
|
|
|
46
46
|
return `${PLACEHOLDER_PREFIX}${codeBlocks.length - 1}${PLACEHOLDER_SUFFIX}`;
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
// 1b. Extract Markdown tables into <pre> placeholders
|
|
50
|
+
withPlaceholders = withPlaceholders.replace(/(?:^|\n)(\|.+\|(?:\r?\n\|.+\|)*)/g, (_match, tableBlock: string) => {
|
|
51
|
+
const html = convertMarkdownTable(tableBlock);
|
|
52
|
+
codeBlocks.push(html);
|
|
53
|
+
return `\n${PLACEHOLDER_PREFIX}${codeBlocks.length - 1}${PLACEHOLDER_SUFFIX}`;
|
|
54
|
+
});
|
|
55
|
+
|
|
49
56
|
// 2. Process non-code-block text
|
|
50
57
|
const placeholderRe = new RegExp(`(${PLACEHOLDER_PREFIX}\\d+${PLACEHOLDER_SUFFIX})`, "g");
|
|
51
58
|
const matchRe = new RegExp(`^${PLACEHOLDER_PREFIX}(\\d+)${PLACEHOLDER_SUFFIX}$`);
|
|
@@ -91,10 +98,64 @@ function convertInlineFormatting(text: string): string {
|
|
|
91
98
|
// Links: [text](url)
|
|
92
99
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
93
100
|
|
|
101
|
+
// Numbered lists: leading "1. " → keep number with period
|
|
102
|
+
s = s.replace(/^[\t ]*(\d+)\.\s+/gm, "$1. ");
|
|
103
|
+
|
|
94
104
|
// Bullet lists: leading "- " or "* " → "• "
|
|
95
105
|
s = s.replace(/^[\t ]*[-*]\s+/gm, "• ");
|
|
96
106
|
|
|
107
|
+
// Blockquotes: leading "> " → <blockquote>
|
|
108
|
+
// Collect consecutive quoted lines into a single blockquote
|
|
109
|
+
s = s.replace(/(?:^> (.+)$(?:\n|$))+/gm, (match) => {
|
|
110
|
+
const inner = match
|
|
111
|
+
.split("\n")
|
|
112
|
+
.map((line) => line.replace(/^> /, ""))
|
|
113
|
+
.filter((line) => line !== "")
|
|
114
|
+
.join("\n");
|
|
115
|
+
return `<blockquote>${inner}</blockquote>\n`;
|
|
116
|
+
});
|
|
117
|
+
|
|
97
118
|
return s;
|
|
98
119
|
})
|
|
99
120
|
.join("");
|
|
100
121
|
}
|
|
122
|
+
|
|
123
|
+
/** Convert a Markdown table to an aligned monospace <pre> block. */
|
|
124
|
+
function convertMarkdownTable(tableText: string): string {
|
|
125
|
+
const rows = tableText.split(/\r?\n/).filter((r) => r.includes("|"));
|
|
126
|
+
|
|
127
|
+
// Parse cells from each row
|
|
128
|
+
const parsed = rows.map((row) =>
|
|
129
|
+
row
|
|
130
|
+
.replace(/^\|/, "")
|
|
131
|
+
.replace(/\|$/, "")
|
|
132
|
+
.split("|")
|
|
133
|
+
.map((c) => c.trim()),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Filter out separator rows (--- or :---: etc.)
|
|
137
|
+
const dataRows = parsed.filter((cells) => !cells.every((c) => /^[:\-\s]+$/.test(c)));
|
|
138
|
+
if (dataRows.length === 0) return `<pre>${escapeHtml(tableText)}</pre>`;
|
|
139
|
+
|
|
140
|
+
// Calculate max width per column
|
|
141
|
+
const colCount = Math.max(...dataRows.map((r) => r.length));
|
|
142
|
+
const widths: number[] = Array.from({ length: colCount }, () => 0);
|
|
143
|
+
for (const row of dataRows) {
|
|
144
|
+
for (let i = 0; i < colCount; i++) {
|
|
145
|
+
widths[i] = Math.max(widths[i] ?? 0, (row[i] ?? "").length);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Build aligned rows
|
|
150
|
+
const lines = dataRows.map((row, rowIdx) => {
|
|
151
|
+
const padded = widths.map((w, i) => (row[i] ?? "").padEnd(w)).join(" ");
|
|
152
|
+
// Add a separator line after the header
|
|
153
|
+
if (rowIdx === 0 && dataRows.length > 1) {
|
|
154
|
+
const sep = widths.map((w) => "─".repeat(w)).join("──");
|
|
155
|
+
return `${padded}\n${sep}`;
|
|
156
|
+
}
|
|
157
|
+
return padded;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return `<pre>${escapeHtml(lines.join("\n"))}</pre>`;
|
|
161
|
+
}
|