新增: - BrandCropModal 品牌图裁剪弹窗 + cropImage 工具 - Markdown 行内短代码(inlineShortcodes + remark 插件) - MdDetails 折叠块、MdVideo 视频块组件 - directiveBlocks/inlineShortcodes 测试 其它: - 聊天后端(handler/service chat)调整 - Markdown 渲染链路、编辑器、全局样式与品牌相关面板更新
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import type { Root as MdastRoot } from "mdast";
|
||
import type { Text } from "mdast";
|
||
import { visit, SKIP } from "unist-util-visit";
|
||
import { splitInlineShortcodes } from "@/lib/inlineShortcodes";
|
||
|
||
const QUICK_RE = /==|\[(?:kbd|color|bg)[=\]]/i;
|
||
|
||
type MdastParent = { children: Array<Record<string, unknown>>; type: string };
|
||
|
||
/** 行内短代码 → mdast 节点(mdast-util-to-hast 支持 data.hName / data.hProperties) */
|
||
function segToNodes(
|
||
seg: Exclude<ReturnType<typeof splitInlineShortcodes>, null>[number]
|
||
): Record<string, unknown>[] {
|
||
if (seg.type === "text") return [{ type: "text", value: seg.value }];
|
||
if (seg.type === "mark" || seg.type === "kbd") {
|
||
return [
|
||
{
|
||
type: "emphasis",
|
||
data: { hName: seg.type },
|
||
children: [{ type: "text", value: seg.value }],
|
||
},
|
||
];
|
||
}
|
||
const style = [
|
||
seg.fg ? `color:${seg.fg}` : null,
|
||
seg.bg ? `background-color:${seg.bg}` : null,
|
||
]
|
||
.filter(Boolean)
|
||
.join(";");
|
||
return [
|
||
{
|
||
type: "emphasis",
|
||
data: { hName: "span", hProperties: { style } },
|
||
children: [{ type: "text", value: seg.value }],
|
||
},
|
||
];
|
||
}
|
||
|
||
/**
|
||
* remark 插件:把文本节点中的 ==高亮== / [kbd] / [color] / [bg] 展开为
|
||
* mark/kbd/span 节点。code/inlineCode 是字面节点没有子文本,天然免疫;
|
||
* 未命中短代码的文本原样保留。
|
||
*/
|
||
export function remarkInlineShortcodes() {
|
||
return (tree: MdastRoot) => {
|
||
visit(tree, "text", (node: Text, index, parent) => {
|
||
const p = parent as unknown as MdastParent | null;
|
||
if (!p || index === undefined) return;
|
||
const value = node.value;
|
||
if (!QUICK_RE.test(value)) return;
|
||
const segs = splitInlineShortcodes(value);
|
||
if (!segs) return;
|
||
const nodes = segs.flatMap(segToNodes);
|
||
p.children.splice(index, 1, ...nodes);
|
||
return [SKIP, index + nodes.length];
|
||
});
|
||
};
|
||
}
|