feat(feishu): render post rich text as markdown (openclaw#12755)

* feat(feishu): parse post rich text as markdown

* chore: rerun ci

* Feishu: resolve post parser rebase conflicts and gate fixes

---------

Co-authored-by: Wilson Liu <wilson.liu@example.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
WilsonLiu95
2026-02-28 13:33:20 +08:00
committed by GitHub
parent 49cf2bceb6
commit 8818464f5f
6 changed files with 368 additions and 83 deletions

View File

@@ -0,0 +1,103 @@
import { describe, expect, it } from "vitest";
import { parsePostContent } from "./post.js";
describe("parsePostContent", () => {
it("renders title and styled text as markdown", () => {
const content = JSON.stringify({
title: "Daily *Plan*",
content: [
[
{ tag: "text", text: "Bold", style: { bold: true } },
{ tag: "text", text: " " },
{ tag: "text", text: "Italic", style: { italic: true } },
{ tag: "text", text: " " },
{ tag: "text", text: "Underline", style: { underline: true } },
{ tag: "text", text: " " },
{ tag: "text", text: "Strike", style: { strikethrough: true } },
{ tag: "text", text: " " },
{ tag: "text", text: "Code", style: { code: true, bold: true } },
],
],
});
const result = parsePostContent(content);
expect(result.textContent).toBe(
"Daily \\*Plan\\*\n\n**Bold** *Italic* <u>Underline</u> ~~Strike~~ `Code`",
);
expect(result.imageKeys).toEqual([]);
expect(result.mentionedOpenIds).toEqual([]);
});
it("renders links and mentions", () => {
const content = JSON.stringify({
title: "",
content: [
[
{ tag: "a", text: "Docs [v2]", href: "https://example.com/guide(a)" },
{ tag: "text", text: " " },
{ tag: "at", user_name: "alice_bob" },
{ tag: "text", text: " " },
{ tag: "at", open_id: "ou_123" },
{ tag: "text", text: " " },
{ tag: "a", href: "https://example.com/no-text" },
],
],
});
const result = parsePostContent(content);
expect(result.textContent).toBe(
"[Docs \\[v2\\]](https://example.com/guide(a)) @alice\\_bob @ou\\_123 [https://example.com/no\\-text](https://example.com/no-text)",
);
expect(result.mentionedOpenIds).toEqual(["ou_123"]);
});
it("inserts image placeholders and collects image keys", () => {
const content = JSON.stringify({
title: "",
content: [
[
{ tag: "text", text: "Before " },
{ tag: "img", image_key: "img_1" },
{ tag: "text", text: " after" },
],
[{ tag: "img", image_key: "img_2" }],
],
});
const result = parsePostContent(content);
expect(result.textContent).toBe("Before ![image] after\n![image]");
expect(result.imageKeys).toEqual(["img_1", "img_2"]);
expect(result.mentionedOpenIds).toEqual([]);
});
it("supports locale wrappers", () => {
const wrappedByPost = JSON.stringify({
post: {
zh_cn: {
title: "标题",
content: [[{ tag: "text", text: "内容A" }]],
},
},
});
const wrappedByLocale = JSON.stringify({
zh_cn: {
title: "标题",
content: [[{ tag: "text", text: "内容B" }]],
},
});
expect(parsePostContent(wrappedByPost)).toEqual({
textContent: "标题\n\n内容A",
imageKeys: [],
mentionedOpenIds: [],
});
expect(parsePostContent(wrappedByLocale)).toEqual({
textContent: "标题\n\n内容B",
imageKeys: [],
mentionedOpenIds: [],
});
});
});