@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/html.ts +63 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexnodeland/claude-telegram",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Claude Code Channel plugin + standalone orchestrator bridging Telegram to Claude Code sessions",
5
5
  "module": "src/index.ts",
6
6
  "main": "src/index.ts",
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
- const withPlaceholders = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang: string, code: string) => {
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(/(?:^&gt; (.+)$(?:\n|$))+/gm, (match) => {
110
+ const inner = match
111
+ .split("\n")
112
+ .map((line) => line.replace(/^&gt; /, ""))
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
+ }