diff --git a/packages/desktop/src/renderer/components/Markdown/ShadowView.tsx b/packages/desktop/src/renderer/components/Markdown/ShadowView.tsx index dd01015c8a..fdb3de6694 100644 --- a/packages/desktop/src/renderer/components/Markdown/ShadowView.tsx +++ b/packages/desktop/src/renderer/components/Markdown/ShadowView.tsx @@ -189,6 +189,22 @@ export const createInitStyle = ( font-family: var(--font-mono); font-weight: var(--font-mono-weight); } + .markdown-shadow-body kbd { + display: inline-block; + padding: 2px 6px; + font-size: 0.8em; + line-height: 1.2; + font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace); + font-weight: 500; + color: var(--text-primary); + background-color: var(--bg-2); + border: 1px solid var(--bg-3); + border-bottom-width: 2px; + border-radius: 5px; + box-shadow: inset 0 -1px 0 var(--bg-3); + vertical-align: middle; + white-space: nowrap; + } blockquote { border-left: 3px solid var(--bg-3); padding-left: 12px; diff --git a/packages/desktop/src/renderer/components/Markdown/index.tsx b/packages/desktop/src/renderer/components/Markdown/index.tsx index a8394994e2..5484493f30 100644 --- a/packages/desktop/src/renderer/components/Markdown/index.tsx +++ b/packages/desktop/src/renderer/components/Markdown/index.tsx @@ -23,7 +23,7 @@ import LocalImageView from '@renderer/components/media/LocalImageView'; import CodeBlock from './CodeBlock'; import LocalFileLink from './LocalFileLink'; import ShadowView from './ShadowView'; -import { MARKDOWN_REMARK_PLUGINS, MarkdownTable, MarkdownTd } from './markdownComponents'; +import { DEFAULT_REHYPE_PLUGINS, MARKDOWN_REMARK_PLUGINS, MarkdownTable, MarkdownTd } from './markdownComponents'; import { resolveLocalFileLinkPath, resolveLocalFileLinkReference } from './markdownUtils'; import type { LocalFileLinkReference } from './markdownUtils'; @@ -125,7 +125,7 @@ const MarkdownView: React.FC = React.memo( [codeStyle, hiddenCodeCopyButton, handleLinkClick, onLocalFileLink] ); - const rehypePlugins = useMemo(() => (allowHtml ? [rehypeRaw, rehypeKatex] : [rehypeKatex]), [allowHtml]); + const rehypePlugins = useMemo(() => (allowHtml ? [rehypeRaw, rehypeKatex] : DEFAULT_REHYPE_PLUGINS), [allowHtml]); return (
diff --git a/packages/desktop/src/renderer/components/Markdown/markdownComponents.tsx b/packages/desktop/src/renderer/components/Markdown/markdownComponents.tsx index 3eadd2618c..0e7c063ce6 100644 --- a/packages/desktop/src/renderer/components/Markdown/markdownComponents.tsx +++ b/packages/desktop/src/renderer/components/Markdown/markdownComponents.tsx @@ -5,9 +5,10 @@ */ import React from 'react'; +import type { Options as ReactMarkdownOptions } from 'react-markdown'; import rehypeKatex from 'rehype-katex'; import rehypeRaw from 'rehype-raw'; -import rehypeSanitize from 'rehype-sanitize'; +import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import remarkBreaks from 'remark-breaks'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; @@ -34,7 +35,34 @@ export const MARKDOWN_REMARK_PLUGINS = [remarkGfm, remarkMath, remarkBreaks]; * Sanitizing before KaTeX (not after) is what lets raw HTML be shown safely while * math keeps working — this is the behaviour that must not regress. */ -export const SANITIZED_HTML_REHYPE_PLUGINS = [rehypeRaw, rehypeSanitize, rehypeKatex]; +export const SANITIZED_HTML_REHYPE_PLUGINS: ReactMarkdownOptions['rehypePlugins'] = [ + rehypeRaw, + [ + rehypeSanitize, + { + ...defaultSchema, + protocols: { + ...defaultSchema.protocols, + src: [...(defaultSchema.protocols?.src || []), 'data', 'file'], + href: [ + ...(defaultSchema.protocols?.href || []), + 'file', + // Allow Windows drive letter paths (e.g. C:/path) parsed as protocols + ...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), + ], + }, + }, + ], + rehypeKatex, +]; + +/** + * Default rehype pipeline for chat and standard markdown surfaces. + * Uses rehype-raw with sanitized HTML tags (rehypeSanitize) followed by KaTeX. + * This allows benign tags like `` while stripping dangerous + * markup (`