





















@@ -87,6 +87,16 @@ const INLINE_DATA_IMAGE_RE = /^data:image\/[a-z0-9.+-]+;base64,/i;
8787const markdownCache = new Map<string, string>();
8888const TAIL_LINK_BLUR_CLASS = "chat-link-tail-blur";
898990+export type MarkdownCodeBlockChrome = "copy" | "none";
91+92+export type MarkdownRenderOptions = {
93+codeBlockChrome?: MarkdownCodeBlockChrome;
94+};
95+96+type MarkdownRenderEnv = {
97+codeBlockChrome: MarkdownCodeBlockChrome;
98+};
99+90100// CJK character ranges for URL boundary detection (RFC 3986: CJK is not valid in raw URLs).
91101// CJK Unified Ideographs, CJK Symbols/Punctuation, Fullwidth Forms, Hiragana, Katakana,
92102// Hangul Syllables, and CJK Compatibility Ideographs.
@@ -115,6 +125,16 @@ function setCachedMarkdown(key: string, value: string) {
115125}
116126}
117127128+function normalizeMarkdownRenderOptions(options: MarkdownRenderOptions = {}): MarkdownRenderEnv {
129+return {
130+codeBlockChrome: options.codeBlockChrome ?? "copy",
131+};
132+}
133+134+function shouldRenderCodeBlockCopy(env: unknown): boolean {
135+return (env as Partial<MarkdownRenderEnv> | undefined)?.codeBlockChrome !== "none";
136+}
137+118138function installHooks() {
119139if (hooksInstalled) {
120140return;
@@ -521,7 +541,7 @@ md.renderer.rules.image = (tokens, idx) => {
521541};
522542523543// Override fenced code blocks with copy button + JSON collapse
524-md.renderer.rules.fence = (tokens, idx) => {
544+md.renderer.rules.fence = (tokens, idx, _options, env) => {
525545const token = tokens[idx];
526546// token.info contains the full fence info string (e.g., "json title=foo");
527547// extract only the first whitespace-separated token as the language.
@@ -530,6 +550,9 @@ md.renderer.rules.fence = (tokens, idx) => {
530550const highlighted = highlightCode(text, lang);
531551const classAttr = codeClassAttribute(lang, highlighted);
532552const codeBlock = `<pre><code${classAttr}>${highlighted}</code></pre>`;
553+if (!shouldRenderCodeBlockCopy(env)) {
554+return codeBlock;
555+}
533556const langLabel = lang ? `<span class="code-block-lang">${escapeHtml(lang)}</span>` : "";
534557const attrSafe = escapeHtml(text);
535558const copyBtn = `<button type="button" class="code-block-copy" data-code="${attrSafe}" aria-label="${escapeHtml(t("common.copyCode"))}"><span class="code-block-copy__idle">${escapeHtml(t("common.copy"))}</span><span class="code-block-copy__done">${escapeHtml(t("common.copied"))}</span></button>`;
@@ -552,12 +575,15 @@ md.renderer.rules.fence = (tokens, idx) => {
552575};
553576554577// Override indented code blocks (code_block) with the same treatment as fence
555-md.renderer.rules.code_block = (tokens, idx) => {
578+md.renderer.rules.code_block = (tokens, idx, _options, env) => {
556579const token = tokens[idx];
557580const text = token.content;
558581const highlighted = highlightCode(text, "");
559582const classAttr = codeClassAttribute("", highlighted);
560583const codeBlock = `<pre><code${classAttr}>${highlighted}</code></pre>`;
584+if (!shouldRenderCodeBlockCopy(env)) {
585+return codeBlock;
586+}
561587const attrSafe = escapeHtml(text);
562588const copyBtn = `<button type="button" class="code-block-copy" data-code="${attrSafe}" aria-label="${escapeHtml(t("common.copyCode"))}"><span class="code-block-copy__idle">${escapeHtml(t("common.copy"))}</span><span class="code-block-copy__done">${escapeHtml(t("common.copied"))}</span></button>`;
563589const header = `<div class="code-block-header">${copyBtn}</div>`;
@@ -576,13 +602,17 @@ md.renderer.rules.code_block = (tokens, idx) => {
576602return `<div class="code-block-wrapper">${header}${codeBlock}</div>`;
577603};
578604579-export function toSanitizedMarkdownHtml(markdown: string): string {
605+export function toSanitizedMarkdownHtml(
606+markdown: string,
607+options: MarkdownRenderOptions = {},
608+): string {
609+const renderOptions = normalizeMarkdownRenderOptions(options);
580610const input = stripUnsupportedCitationControlMarkers(markdown).trim();
581611if (!input) {
582612return "";
583613}
584614installHooks();
585-const cacheKey = `${i18n.getLocale()}\0${input}`;
615+const cacheKey = `${i18n.getLocale()}\0${renderOptions.codeBlockChrome}\0${input}`;
586616if (input.length <= MARKDOWN_CACHE_MAX_CHARS) {
587617const cached = getCachedMarkdown(cacheKey);
588618if (cached !== null) {
@@ -606,7 +636,7 @@ export function toSanitizedMarkdownHtml(markdown: string): string {
606636}
607637let rendered: string;
608638try {
609-rendered = md.render(`${truncated.text}${suffix}`);
639+rendered = md.render(`${truncated.text}${suffix}`, renderOptions);
610640} catch (err) {
611641// Fall back to escaped plain text when md.render() throws (#36213).
612642console.warn("[markdown] md.render failed, falling back to plain text:", err);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。