源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
// react-markdown's default transform only keeps http(s)/irc(s)/mailto/xmpp
|
||||
// URLs, which would strip the inline `data:image/...` sources the markdown
|
||||
// serializer produces for attachments. Allow exactly those data images and
|
||||
// defer everything else to the default sanitization. Raw HTML embedded in
|
||||
// message text stays inert because react-markdown ignores html nodes.
|
||||
//
|
||||
// The allow-list is raster subtypes only, not a bare `data:image/` prefix:
|
||||
// an image block's mimeType comes from unvalidated message content (tool
|
||||
// output, a collaborator's message), so a `data:image/svg+xml` source could
|
||||
// otherwise reach the text/html clipboard flavor verbatim — SVG is an XML
|
||||
// document that can carry <script> and event handlers, unlike a raster
|
||||
// image.
|
||||
const SAFE_DATA_IMAGE_URL_PATTERN =
|
||||
/^data:image\/(?:png|jpe?g|gif|webp|bmp|avif|x-icon|vnd\.microsoft\.icon);/i;
|
||||
|
||||
function conversationCopyUrlTransform(url: string): string {
|
||||
return SAFE_DATA_IMAGE_URL_PATTERN.test(url) ? url : defaultUrlTransform(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the clipboard markdown transcript to an HTML fragment for the
|
||||
* `text/html` clipboard flavor. Rich-paste targets (Word, Feishu/Lark docs,
|
||||
* Notion, mail clients) consume this flavor, so inline data-URI images paste
|
||||
* as real images instead of a base64 wall of text; `text/plain` keeps the
|
||||
* markdown source for plain editors.
|
||||
*/
|
||||
export function renderAgentConversationCopyHtml(markdown: string): string {
|
||||
return renderToStaticMarkup(
|
||||
createElement(
|
||||
ReactMarkdown,
|
||||
{
|
||||
remarkPlugins: [remarkGfm],
|
||||
urlTransform: conversationCopyUrlTransform
|
||||
},
|
||||
markdown
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user