feat: 品牌裁剪弹窗/Markdown 行内短代码与详情视频块等

新增:
- BrandCropModal 品牌图裁剪弹窗 + cropImage 工具
- Markdown 行内短代码(inlineShortcodes + remark 插件)
- MdDetails 折叠块、MdVideo 视频块组件
- directiveBlocks/inlineShortcodes 测试

其它:
- 聊天后端(handler/service chat)调整
- Markdown 渲染链路、编辑器、全局样式与品牌相关面板更新
This commit is contained in:
2026-10-02 01:40:06 +08:00
parent 3e98052c16
commit 99c0f0319d
23 changed files with 2012 additions and 119 deletions

View File

@@ -0,0 +1,117 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
DETAILS_DEFAULT_TITLE,
isDetailsOpen,
parseDetailsOpen,
parseVideoInput,
parseVideoLine,
splitContentSegments,
} from "./directiveBlocks.ts";
describe("parseDetailsOpen / isDetailsOpen", () => {
it("开标记整行匹配,标题缺省「点击展开」", () => {
assert.equal(isDetailsOpen("[details]"), true);
assert.equal(parseDetailsOpen("[details]"), DETAILS_DEFAULT_TITLE);
assert.equal(parseDetailsOpen("[details 点击展开看结论]"), "点击展开看结论");
assert.equal(isDetailsOpen("[details 标题] 后缀"), false);
assert.equal(isDetailsOpen("[details]"), true);
});
});
describe("parseVideoInput", () => {
it("BV 号与 B站链接", () => {
assert.deepEqual(parseVideoInput("BV1GJ411x7h7"), {
provider: "bilibili",
id: "BV1GJ411x7h7",
});
assert.deepEqual(parseVideoInput("https://www.bilibili.com/video/BV1GJ411x7h7/?p=1"), {
provider: "bilibili",
id: "BV1GJ411x7h7",
});
});
it("YouTube ID 与链接", () => {
assert.deepEqual(parseVideoInput("dQw4w9WgXcQ"), {
provider: "youtube",
id: "dQw4w9WgXcQ",
});
assert.deepEqual(parseVideoInput("https://youtu.be/dQw4w9WgXcQ"), {
provider: "youtube",
id: "dQw4w9WgXcQ",
});
assert.deepEqual(parseVideoInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ"), {
provider: "youtube",
id: "dQw4w9WgXcQ",
});
});
it("非法输入返回 null", () => {
assert.equal(parseVideoInput(""), null);
assert.equal(parseVideoInput("hello world"), null);
assert.equal(parseVideoInput("https://evil.com/<script>"), null);
});
});
describe("parseVideoLine", () => {
it("整行短代码匹配且大小写不敏感", () => {
assert.deepEqual(parseVideoLine("[bilibili]BV1GJ411x7h7[/bilibili]"), {
provider: "bilibili",
id: "BV1GJ411x7h7",
});
assert.deepEqual(parseVideoLine("[BILIBILI]https://www.bilibili.com/video/BV1GJ411x7h7[/BILIBILI]"), {
provider: "bilibili",
id: "BV1GJ411x7h7",
});
assert.deepEqual(parseVideoLine("[youtube]dQw4w9WgXcQ[/youtube]"), {
provider: "youtube",
id: "dQw4w9WgXcQ",
});
});
it("标签与平台不一致或内容非法时返回 null", () => {
assert.equal(parseVideoLine("[bilibili]dQw4w9WgXcQ[/bilibili]"), null);
assert.equal(parseVideoLine("[youtube]BV1GJ411x7h7[/youtube]"), null);
assert.equal(parseVideoLine("[bilibili]随便[/bilibili]"), null);
});
});
describe("splitContentSegments: details/video", () => {
it("[details] 块拆分,正文保留 markdown", () => {
const segs = splitContentSegments(
"前文\n\n[details 结论]\n**加粗** 内容\n[/details]\n\n后文"
);
assert.deepEqual(
segs.map((s) => s.type),
["md", "details", "md"]
);
const d = segs[1];
assert.equal(d.type === "details" ? d.title : "", "结论");
assert.equal(d.type === "details" ? d.body : "", "**加粗** 内容");
});
it("details 嵌套开标签:外层按 md,内层与闭合配对(与 timeline 语义一致)", () => {
const segs = splitContentSegments("[details A]\n[details B]\n[/details]");
assert.deepEqual(segs.map((s) => s.type), ["md", "details"]);
});
it("未闭合的 details 整段按 md", () => {
const segs = splitContentSegments("正文\n\n[details A]\n没有闭合标记");
assert.deepEqual(segs.map((s) => s.type), ["md"]);
});
it("代码围栏内伪语法不拆", () => {
const segs = splitContentSegments("```\n[details 假的]\n[/details]\n```");
assert.deepEqual(segs.map((s) => s.type), ["md"]);
});
it("视频整行拆为 video 段;行内出现不拆", () => {
const segs = splitContentSegments(
"看这个:\n\n[bilibili]BV1GJ411x7h7[/bilibili]\n\n文中 [youtube]dQw4w9WgXcQ[/youtube] 不拆"
);
assert.deepEqual(
segs.map((s) => s.type),
["md", "video", "md"]
);
const v = segs[1];
assert.equal(v.type === "video" ? v.provider : "", "bilibili");
assert.equal(v.type === "video" ? v.id : "", "BV1GJ411x7h7");
});
});