feat(telegram): render blockquotes as native <blockquote> tags (#14608) (#14626)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 4a967c51f5
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight
This commit is contained in:
大猫子
2026-02-12 21:11:57 +08:00
committed by GitHub
parent 540996f10f
commit 8dd60fc7d9
5 changed files with 42 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Telegram: render blockquotes as native `<blockquote>` 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.

View File

@@ -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":

View File

@@ -21,6 +21,7 @@ export type RenderOptions = {
};
const STYLE_ORDER: MarkdownStyle[] = [
"blockquote",
"code_block",
"code",
"bold",

View File

@@ -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("<blockquote>");
expect(res).toContain("Quote");
expect(res).toContain("</blockquote>");
});
it("renders blockquotes with inline formatting", () => {
const res = markdownToTelegramHtml("> **bold** quote");
expect(res).toContain("<blockquote>");
expect(res).toContain("<b>bold</b>");
expect(res).toContain("</blockquote>");
});
it("renders multiline blockquotes as a single Telegram blockquote", () => {
const res = markdownToTelegramHtml("> first\n> second");
expect(res).toBe("<blockquote>first\nsecond</blockquote>");
});
it("renders separated quoted paragraphs as distinct blockquotes", () => {
const res = markdownToTelegramHtml("> first\n\n> second");
expect(res).toContain("<blockquote>first");
expect(res).toContain("<blockquote>second</blockquote>");
expect(res.match(/<blockquote>/g)).toHaveLength(2);
});
it("renders fenced code blocks", () => {

View File

@@ -46,6 +46,7 @@ function renderTelegramHtml(ir: MarkdownIR): string {
code: { open: "<code>", close: "</code>" },
code_block: { open: "<pre><code>", close: "</code></pre>" },
spoiler: { open: "<tg-spoiler>", close: "</tg-spoiler>" },
blockquote: { open: "<blockquote>", close: "</blockquote>" },
},
escapeText: escapeHtml,
buildLink: buildTelegramLink,