mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-07 21:08:34 +03:00
fix(webui): render adjacent CJK emphasis
This commit is contained in:
@@ -237,11 +237,48 @@ function remarkSafeHtmlSubset() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recover a common model-output edge case that CommonMark leaves as literal
|
||||||
|
// text: `**结论。**如果`, with no separator after the closing delimiter.
|
||||||
|
const CJK_AFTER_STRONG =
|
||||||
|
/(?<!\\)\*\*([^*\r\n]+?)(?<!\\)\*\*(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}])/gu;
|
||||||
|
|
||||||
|
function normalizeCjkStrongBoundaries(node: MarkdownAstNode): void {
|
||||||
|
if (!node.children) return;
|
||||||
|
node.children = node.children.flatMap((child) => {
|
||||||
|
if (child.type !== "text" || !child.value?.includes("**")) {
|
||||||
|
normalizeCjkStrongBoundaries(child);
|
||||||
|
return [child];
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacement: MarkdownAstNode[] = [];
|
||||||
|
let cursor = 0;
|
||||||
|
for (const match of child.value.matchAll(CJK_AFTER_STRONG)) {
|
||||||
|
const start = match.index;
|
||||||
|
if (start > cursor) replacement.push(safeText(child.value.slice(cursor, start)));
|
||||||
|
replacement.push({
|
||||||
|
type: "strong",
|
||||||
|
children: [safeText(match[1])],
|
||||||
|
});
|
||||||
|
cursor = start + match[0].length;
|
||||||
|
}
|
||||||
|
if (cursor === 0) return [child];
|
||||||
|
if (cursor < child.value.length) replacement.push(safeText(child.value.slice(cursor)));
|
||||||
|
return replacement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function remarkCjkStrongBoundaries() {
|
||||||
|
return (tree: MarkdownAstNode) => {
|
||||||
|
normalizeCjkStrongBoundaries(tree);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const remarkPlugins: NonNullable<StreamdownProps["remarkPlugins"]> = [
|
const remarkPlugins: NonNullable<StreamdownProps["remarkPlugins"]> = [
|
||||||
remarkBreaks,
|
remarkBreaks,
|
||||||
remarkGfm,
|
remarkGfm,
|
||||||
[remarkMath, { singleDollarTextMath: false }],
|
[remarkMath, { singleDollarTextMath: false }],
|
||||||
remarkTexMath,
|
remarkTexMath,
|
||||||
|
remarkCjkStrongBoundaries,
|
||||||
remarkSafeHtmlSubset,
|
remarkSafeHtmlSubset,
|
||||||
];
|
];
|
||||||
const rehypePlugins: NonNullable<StreamdownProps["rehypePlugins"]> = [rehypeKatex];
|
const rehypePlugins: NonNullable<StreamdownProps["rehypePlugins"]> = [rehypeKatex];
|
||||||
|
|||||||
@@ -523,6 +523,21 @@ describe("MarkdownTextRenderer", () => {
|
|||||||
expect(screen.getByRole("link", { name: "links" })).not.toHaveAttribute("node");
|
expect(screen.getByRole("link", { name: "links" })).not.toHaveAttribute("node");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders bold CJK text when more CJK text follows immediately", () => {
|
||||||
|
render(
|
||||||
|
<MarkdownTextRenderer streaming>
|
||||||
|
{
|
||||||
|
"**结论:目前看风险可控,没有发现常驻或可疑安装。**如果你之后不想再用,我可以帮你彻底卸载。"
|
||||||
|
}
|
||||||
|
</MarkdownTextRenderer>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText("结论:目前看风险可控,没有发现常驻或可疑安装。").tagName,
|
||||||
|
).toBe("STRONG");
|
||||||
|
expect(screen.getByText(/如果你之后不想再用/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("adds line numbers to multiline fenced code without changing inline code", () => {
|
it("adds line numbers to multiline fenced code without changing inline code", () => {
|
||||||
render(
|
render(
|
||||||
<MarkdownTextRenderer highlightCode={false}>
|
<MarkdownTextRenderer highlightCode={false}>
|
||||||
|
|||||||
Reference in New Issue
Block a user