diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c7b80f9cc..eff2631d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Telegram: render blockquotes as native `
` tags instead of stripping them. (#14608) - Version alignment: bump manifests and package versions to `2026.2.10`; keep `appcast.xml` unchanged until the next macOS release cut. - CLI: add `openclaw logs --local-time` to display log timestamps in local timezone. (#13818) Thanks @xialonglee. - Config: avoid redacting `maxTokens`-like fields during config snapshot redaction, preventing round-trip validation failures in `/config`. (#14006) Thanks @constansino. diff --git a/src/markdown/ir.ts b/src/markdown/ir.ts index 2fd3a5a0c6..37c15c198a 100644 --- a/src/markdown/ir.ts +++ b/src/markdown/ir.ts @@ -24,7 +24,14 @@ type MarkdownToken = { attrGet?: (name: string) => string | null; }; -export type MarkdownStyle = "bold" | "italic" | "strikethrough" | "code" | "code_block" | "spoiler"; +export type MarkdownStyle = + | "bold" + | "italic" + | "strikethrough" + | "code" + | "code_block" + | "spoiler" + | "blockquote"; export type MarkdownStyleSpan = { start: number; @@ -578,8 +585,10 @@ function renderTokens(tokens: MarkdownToken[], state: RenderState): void { if (state.blockquotePrefix) { state.text += state.blockquotePrefix; } + openStyle(state, "blockquote"); break; case "blockquote_close": + closeStyle(state, "blockquote"); state.text += "\n"; break; case "bullet_list_open": diff --git a/src/markdown/render.ts b/src/markdown/render.ts index fb55ee8477..ee44ac9740 100644 --- a/src/markdown/render.ts +++ b/src/markdown/render.ts @@ -21,6 +21,7 @@ export type RenderOptions = { }; const STYLE_ORDER: MarkdownStyle[] = [ + "blockquote", "code_block", "code", "bold", diff --git a/src/telegram/format.test.ts b/src/telegram/format.test.ts index 67aaba3d34..48e9534375 100644 --- a/src/telegram/format.test.ts +++ b/src/telegram/format.test.ts @@ -37,9 +37,35 @@ describe("markdownToTelegramHtml", () => { expect(res).toBe("2. two\n3. three"); }); - it("flattens headings and blockquotes", () => { - const res = markdownToTelegramHtml("# Title\n\n> Quote"); - expect(res).toBe("Title\n\nQuote"); + it("flattens headings", () => { + const res = markdownToTelegramHtml("# Title"); + expect(res).toBe("Title"); + }); + + it("renders blockquotes as native Telegram blockquote tags", () => { + const res = markdownToTelegramHtml("> Quote"); + expect(res).toContain(""); + expect(res).toContain("Quote"); + expect(res).toContain(""); + }); + + it("renders blockquotes with inline formatting", () => { + const res = markdownToTelegramHtml("> **bold** quote"); + expect(res).toContain(""); + expect(res).toContain("bold"); + expect(res).toContain(""); + }); + + it("renders multiline blockquotes as a single Telegram blockquote", () => { + const res = markdownToTelegramHtml("> first\n> second"); + expect(res).toBe("first\nsecond"); + }); + + it("renders separated quoted paragraphs as distinct blockquotes", () => { + const res = markdownToTelegramHtml("> first\n\n> second"); + expect(res).toContain("first"); + expect(res).toContain("second"); + expect(res.match(//g)).toHaveLength(2); }); it("renders fenced code blocks", () => { diff --git a/src/telegram/format.ts b/src/telegram/format.ts index 3d50b9902c..eb457edff0 100644 --- a/src/telegram/format.ts +++ b/src/telegram/format.ts @@ -46,6 +46,7 @@ function renderTelegramHtml(ir: MarkdownIR): string { code: { open: "", close: "" }, code_block: { open: "" }, spoiler: { open: "", close: "", close: " " }, + blockquote: { open: "", close: "" }, }, escapeText: escapeHtml, buildLink: buildTelegramLink,