feat(telegram): convert markdown tables to bullet points (#1495)

Tables render poorly in Telegram (pipes stripped, whitespace collapses).
This adds a 'tableMode' option to markdownToIR that converts tables to
nested bullet points, which render cleanly on mobile.

- Add tableMode: 'flat' | 'bullets' to MarkdownParseOptions
- Track table state during token rendering
- Render tables as bullet points with first column as row labels
- Apply bold styling to row labels for visual hierarchy
- Enable tableMode: 'bullets' for Telegram formatter

Closes #TBD
This commit is contained in:
George Zhang
2026-01-24 02:00:51 +08:00
committed by GitHub
parent bfbeea0f20
commit a1413a011e
3 changed files with 276 additions and 11 deletions

View File

@@ -0,0 +1,91 @@
import { describe, expect, it } from "vitest";
import { markdownToIR } from "./ir.js";
describe("markdownToIR tableMode bullets", () => {
it("converts simple table to bullets", () => {
const md = `
| Name | Value |
|------|-------|
| A | 1 |
| B | 2 |
`.trim();
const ir = markdownToIR(md, { tableMode: "bullets" });
// Should contain bullet points with header:value format
expect(ir.text).toContain("• Value: 1");
expect(ir.text).toContain("• Value: 2");
// Should use first column as labels
expect(ir.text).toContain("A");
expect(ir.text).toContain("B");
});
it("handles table with multiple columns", () => {
const md = `
| Feature | SQLite | Postgres |
|---------|--------|----------|
| Speed | Fast | Medium |
| Scale | Small | Large |
`.trim();
const ir = markdownToIR(md, { tableMode: "bullets" });
// First column becomes row label
expect(ir.text).toContain("Speed");
expect(ir.text).toContain("Scale");
// Other columns become bullet points
expect(ir.text).toContain("• SQLite: Fast");
expect(ir.text).toContain("• Postgres: Medium");
expect(ir.text).toContain("• SQLite: Small");
expect(ir.text).toContain("• Postgres: Large");
});
it("preserves flat mode as default", () => {
const md = `
| A | B |
|---|---|
| 1 | 2 |
`.trim();
const ir = markdownToIR(md); // default is flat
// Flat mode uses tabs
expect(ir.text).toContain("A");
expect(ir.text).toContain("B");
expect(ir.text).toContain("1");
expect(ir.text).toContain("2");
// Should not have bullet formatting
expect(ir.text).not.toContain("•");
});
it("handles empty cells gracefully", () => {
const md = `
| Name | Value |
|------|-------|
| A | |
| B | 2 |
`.trim();
const ir = markdownToIR(md, { tableMode: "bullets" });
// Should handle empty cell without crashing
expect(ir.text).toContain("B");
expect(ir.text).toContain("• Value: 2");
});
it("bolds row labels in bullets mode", () => {
const md = `
| Name | Value |
|------|-------|
| Row1 | Data1 |
`.trim();
const ir = markdownToIR(md, { tableMode: "bullets" });
// Should have bold style for row label
const hasRowLabelBold = ir.styles.some(
(s) => s.style === "bold" && ir.text.slice(s.start, s.end) === "Row1"
);
expect(hasRowLabelBold).toBe(true);
});
});