diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 1295efff31..317dd87938 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -163,6 +163,8 @@ pnpm applies local patches to the following packages at install time, so shipped - `app-builder-lib@26.17.0` — [`patches/app-builder-lib@26.17.0.patch`](patches/app-builder-lib@26.17.0.patch) - `exceljs@4.4.0` — [`patches/exceljs@4.4.0.patch`](patches/exceljs@4.4.0.patch) - `lexical@0.49.0` — [`patches/lexical@0.49.0.patch`](patches/lexical@0.49.0.patch) +- `mdast-util-gfm-autolink-literal@2.0.1` — [`patches/mdast-util-gfm-autolink-literal@2.0.1.patch`](patches/mdast-util-gfm-autolink-literal@2.0.1.patch) +- `micromark-extension-gfm-autolink-literal@2.1.0` — [`patches/micromark-extension-gfm-autolink-literal@2.1.0.patch`](patches/micromark-extension-gfm-autolink-literal@2.1.0.patch) - `node-pty@1.2.0-beta.15` — [`patches/node-pty@1.2.0-beta.15.patch`](patches/node-pty@1.2.0-beta.15.patch) ## Chrome DevTools frontend diff --git a/apps/desktop/scripts/runtime-patch-policy.ts b/apps/desktop/scripts/runtime-patch-policy.ts index d285f9bab8..a56dc95e45 100644 --- a/apps/desktop/scripts/runtime-patch-policy.ts +++ b/apps/desktop/scripts/runtime-patch-policy.ts @@ -17,6 +17,8 @@ export const DESKTOP_RUNTIME_PATCHES: Readonly const before = readFileSync(join(repository, 'pnpm-lock.yaml')) const plan = prepareRuntimePatches(project, repository) expect(plan.patches.map(entry => entry.spec)).toEqual(['@earendil-works/pi-ai@1.3.0', 'node-pty@1.2.0-beta.15']) - expect(plan.workspaceOnly).toHaveLength(8) + expect(plan.workspaceOnly).toHaveLength(10) expect(plan.workspaceOnly).toContain('@botiverse/oar') expect(plan.workspaceOnly).toContain('lexical') const settings = yaml.load(readFileSync(join(project, 'pnpm-workspace.yaml'), 'utf8')) as { overrides: Record } diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml index cad46c62fc..3bf488202a 100644 --- a/packages/client/ui-primitives/README.i18n.yaml +++ b/packages/client/ui-primitives/README.i18n.yaml @@ -24,8 +24,8 @@ en: b3744373b4b2c1fb zh: 2dbde8ee0bfa5f28 /deepseek-ai-dsh-client-ui-primitives/use-this-package/rendering-agent-output: - en: 2daec758b1f35563 - zh: 32684be1f8b1b4c3 + en: 32d4557c4429e00d + zh: 293a6fef712f7502 /deepseek-ai-dsh-client-ui-primitives/use-this-package/localizing-copy: en: a943603b74080ef5 zh: 43f7a0188aff271b diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md index 95db022946..d15a9c8776 100644 --- a/packages/client/ui-primitives/README.md +++ b/packages/client/ui-primitives/README.md @@ -110,7 +110,7 @@ The pure Markdown helper `formatQuoteText` normalizes CRLF and CR line endings, The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and `openFile` navigation callbacks. Nested providers replace the enclosing capabilities, and callback changes reach already-rendered links without rebuilding Markdown. Its `openFile` makes local Markdown links clickable after settlement. Absolute and workspace-relative paths support percent escapes and `#L24` / `#L24-L30` fragments; ranges open at their first line. A destination with a backslash and no URL scheme (a drive letter is not one) is a Windows path when it has no `/` or has a backslash before a letter or digit. Such a path keeps every backslash, including one before punctuation, and still decodes character references, so `D:\repo\.github` is not read as an escaped `.`. Literal `?` and `#` in filenames must be percent-encoded. The tooltip uses the decoded path and supplies the accessible name when the label is empty. The callback receives the decoded path and optional line, while the renderer preserves the label and displays a file icon. Without a callback, local links remain text. URL schemes, queries, unsupported fragments, and malformed destinations never reach the file opener. -`MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. Strikethrough requires `~~`; a single `~`, as in `10~20`, stays literal. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. `omitHtml` renders raw HTML as nothing instead of literal text, for documents written for HTML renderers. When the owner passes a `pathImages` vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. Rewritten images accept HTTP(S), data, blob, and the Desktop `dsh-app://app/api/file`, `dsh-app://app/api/plugin-market/detail-file`, and `dsh-app://app/api/plugin-manager/detail-file` routes; authored Desktop URLs remain inert. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty; `fileImages` additionally supplies a localized failure prefix. Changing the image source permits a fresh load. Each mounted image appends a `load` query parameter to a resolved Host `api/file` URL, because browsers reuse a same-URL image within one document despite `no-store`; a newly rendered image of an overwritten file therefore loads its current bytes, and every remount requests the file again; the Host file route redirects that request to a cacheable URL for the current file version. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `UserTextRun` renders uninterpreted text and `ReferenceChip` renders business-resolved labels. An explicit `onActivate` makes a chip a keyboard-accessible button; repeated pointer clicks and an existing text selection do not activate it, while keyboard activation remains available. Labels inherit their host’s wrapping policy. Recognition and JSON transforms belong to [ui-conversation](../ui-conversation/README.md#sent-user-text). +`MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. Strikethrough requires `~~`; a single `~`, as in `10~20`, stays literal. A bare URL ends before non-ASCII punctuation or symbols, such as `。` or `,`, so CJK prose that follows it stays outside the link. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. `omitHtml` renders raw HTML as nothing instead of literal text, for documents written for HTML renderers. When the owner passes a `pathImages` vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. Rewritten images accept HTTP(S), data, blob, and the Desktop `dsh-app://app/api/file`, `dsh-app://app/api/plugin-market/detail-file`, and `dsh-app://app/api/plugin-manager/detail-file` routes; authored Desktop URLs remain inert. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty; `fileImages` additionally supplies a localized failure prefix. Changing the image source permits a fresh load. Each mounted image appends a `load` query parameter to a resolved Host `api/file` URL, because browsers reuse a same-URL image within one document despite `no-store`; a newly rendered image of an overwritten file therefore loads its current bytes, and every remount requests the file again; the Host file route redirects that request to a cacheable URL for the current file version. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `UserTextRun` renders uninterpreted text and `ReferenceChip` renders business-resolved labels. An explicit `onActivate` makes a chip a keyboard-accessible button; repeated pointer clicks and an existing text selection do not activate it, while keyboard activation remains available. Labels inherit their host’s wrapping policy. Recognition and JSON transforms belong to [ui-conversation](../ui-conversation/README.md#sent-user-text). `MarkdownText` defaults to `variant="body"`. Use `variant="compact"` for secondary content: its 13px text and 20px line height follow the content-size setting, all heading levels use the same size with weight 600, and paragraphs and lists use tighter spacing. Text, links, and code keep the tertiary color; dotted underlines distinguish links. Code headers scroll with their blocks. Tables and math stay enabled at the surrounding text size and scroll horizontally within the available width. Both variants share the parser and streaming cache. `textInteractionSource` marks its existing root as a source for text selection interactions. diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md index 910185907b..b10aa0135f 100644 --- a/packages/client/ui-primitives/README.zh.md +++ b/packages/client/ui-primitives/README.zh.md @@ -110,7 +110,7 @@ kind: "package-library" 最近的 `MarkdownDelegateProvider` 提供可选的 `openExternalLink` 和 `openFile` 导航回调。嵌套 Provider 替换外层能力,回调变化无需重新构建 Markdown 即可到达已渲染链接。其 `openFile` 使本地 Markdown 链接在落定后可点击。绝对路径和工作区相对路径支持百分号转义以及 `#L24` / `#L24-L30` 片段;范围定位到起始行。含反斜杠且无 URL 协议(盘符不算 URL 协议)的目标,若不含 `/`,或在字母或数字前有反斜杠,即为 Windows 路径。此类路径保留所有反斜杠(包括标点前的反斜杠),仍会解码字符引用,因此 `D:\repo\.github` 不会被读作转义的 `.`。文件名中的字面 `?` 和 `#` 必须百分号编码。悬停提示使用解码后的路径,并在标签为空时提供可访问名称。回调接收解码后的路径和可选行号,渲染器保留标签并显示文件图标。不传回调时,本地链接仍为文本。URL 协议、查询串、不支持的片段及格式错误的目标不会传给文件打开器。 -`MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。删除线需要 `~~`;单个 `~`(如 `10~20`)保持字面文本。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。`omitHtml` 不渲染原始 HTML,而不是将其显示为原样文本,用于为 HTML 渲染器编写的文档。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。重写后的图片支持 HTTP(S)、data、blob 及桌面端 `dsh-app://app/api/file`、`dsh-app://app/api/plugin-market/detail-file` 与 `dsh-app://app/api/plugin-manager/detail-file` 路由;正文直接书写的桌面端 URL 仍保持惰性。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径;`fileImages` 还提供本地化的失败提示前缀。图片源变化后可重新加载。每个挂载的图片会为解析得到的 Host `api/file` 地址追加 `load` 查询参数,因为浏览器在同一文档内会无视 `no-store` 复用相同 URL 的图片;因此覆盖后的文件在新渲染的图片中会加载当前内容,每次重新挂载都会重新请求该文件;Host 文件路由会把该请求重定向到当前文件版本的可缓存地址。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`UserTextRun` 渲染不做解析的文本,`ReferenceChip` 渲染业务已解析的标签。显式传入 `onActivate` 后,胶囊成为可由键盘操作的按钮;重复指针点击和已有文本选区不触发激活,键盘激活仍然可用。标签继承所属容器的换行规则。识别和 JSON 变换由 [ui-conversation](../ui-conversation/README.zh.md#sent-user-text) 负责。 +`MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。删除线需要 `~~`;单个 `~`(如 `10~20`)保持字面文本。裸 URL 在非 ASCII 标点或符号(如 `。`、`,`)前结束,紧随其后的中文正文不会并入链接。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。`omitHtml` 不渲染原始 HTML,而不是将其显示为原样文本,用于为 HTML 渲染器编写的文档。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。重写后的图片支持 HTTP(S)、data、blob 及桌面端 `dsh-app://app/api/file`、`dsh-app://app/api/plugin-market/detail-file` 与 `dsh-app://app/api/plugin-manager/detail-file` 路由;正文直接书写的桌面端 URL 仍保持惰性。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径;`fileImages` 还提供本地化的失败提示前缀。图片源变化后可重新加载。每个挂载的图片会为解析得到的 Host `api/file` 地址追加 `load` 查询参数,因为浏览器在同一文档内会无视 `no-store` 复用相同 URL 的图片;因此覆盖后的文件在新渲染的图片中会加载当前内容,每次重新挂载都会重新请求该文件;Host 文件路由会把该请求重定向到当前文件版本的可缓存地址。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`UserTextRun` 渲染不做解析的文本,`ReferenceChip` 渲染业务已解析的标签。显式传入 `onActivate` 后,胶囊成为可由键盘操作的按钮;重复指针点击和已有文本选区不触发激活,键盘激活仍然可用。标签继承所属容器的换行规则。识别和 JSON 变换由 [ui-conversation](../ui-conversation/README.zh.md#sent-user-text) 负责。 `MarkdownText` 默认为 `variant="body"`。次级内容使用 `variant="compact"`:其 13px 字号与 20px 行高跟随内容字号设置,各级标题保持同一字号并使用 600 字重,段落与列表采用更紧凑的间距。正文、链接和代码均保持 tertiary 颜色,以点状下划线区分链接。代码标题栏随代码块滚动。表格和公式仍然启用,使用周围文字的字号,并在可用宽度内横向滚动。两个变体共享解析器与流式缓存。 `textInteractionSource` 将已有根元素标记为文本选区交互的来源。 diff --git a/packages/client/ui-primitives/tests/markdown-autolink-literals.client.spec.ts b/packages/client/ui-primitives/tests/markdown-autolink-literals.client.spec.ts new file mode 100644 index 0000000000..eba95b1434 --- /dev/null +++ b/packages/client/ui-primitives/tests/markdown-autolink-literals.client.spec.ts @@ -0,0 +1,48 @@ +import type { Paragraph, Root } from 'mdast' +import { describe, expect, it } from 'vitest' +import { parseGfm, parseGfmWithMath } from '../src/markdown/parse.ts' + +/** Each phrasing child as a link destination, its literal text, or its type. */ +function phrasing(root: Root): unknown[] { + return (root.children[0] as Paragraph).children.map(node => + node.type === 'link' ? { link: node.url } : node.type === 'text' ? node.value : node.type) +} + +describe.each([parseGfm, parseGfmWithMath])('autolink literals in %o', (parse) => { + it('end before non-ASCII punctuation', () => { + expect(phrasing(parse('工作台还在 http://127.0.0.1:8790/。建议先彩排;如果超过 18 分钟'))).toEqual( + ['工作台还在 ', { link: 'http://127.0.0.1:8790/' }, '。建议先彩排;如果超过 18 分钟']) + expect(phrasing(parse('见 https://example.com,然后'))).toEqual(['见 ', { link: 'https://example.com' }, ',然后']) + expect(phrasing(parse('(https://example.com/a?b=1)'))).toEqual(['(', { link: 'https://example.com/a?b=1' }, ')']) + expect(phrasing(parse('见 www.example.com/a.。'))).toEqual(['见 ', { link: 'http://www.example.com/a' }, '.。']) + expect(phrasing(parse('https://a.com/x、https://b.com/y。'))).toEqual( + [{ link: 'https://a.com/x' }, '、', { link: 'https://b.com/y' }, '。']) + expect(phrasing(parse('(www.example.com/a)后'))).toEqual(['(', { link: 'http://www.example.com/a' }, ')后']) + expect(phrasing(parse('😀参见(https://example.com/a_(b)),再看'))).toEqual( + ['😀参见(', { link: 'https://example.com/a_(b)' }, '),再看']) + const qq = 'https://qm.qq.com/cgi-bin/qm/qr?k=Abc%2BDef%2Fghi%3D%3D&authKey=tok%252Fvalue%253D&noverify=0' + expect(phrasing(parse(`加入群聊:${qq},欢迎。`))).toEqual(['加入群聊:', { link: qq }, ',欢迎。']) + // After an unbalanced `[`, the literal comes from the text transform instead of the tokenizer. + expect(phrasing(parse('[x https://example.com/docs/Kit?s=K。后'))).toEqual( + ['[x ', { link: 'https://example.com/docs/Kit?s=K' }, '。后']) + // Unencoded non-ASCII punctuation inside a URL also ends it. + expect(phrasing(parse('https://zh.wikipedia.org/wiki/中文(消歧义)'))).toEqual( + [{ link: 'https://zh.wikipedia.org/wiki/中文' }, '(消歧义)']) + }) + + it('leave following inline Markdown to the inline grammar', () => { + expect(phrasing(parse('https://example.com/a,**下一句**'))).toEqual([{ link: 'https://example.com/a' }, ',', 'strong']) + expect((parse('预览在 **https://example.com/a**(PR #1)').children[0] as Paragraph).children).toMatchObject([ + { value: '预览在 ' }, + { type: 'strong', children: [{ type: 'link', url: 'https://example.com/a' }] }, + { value: '(PR #1)' }, + ]) + }) + + it('keep letters, ASCII punctuation, and authored links', () => { + expect(phrasing(parse('https://zh.wikipedia.org/wiki/中文_(消歧义)?q=a,b ok'))).toEqual( + [{ link: 'https://zh.wikipedia.org/wiki/中文_(消歧义)?q=a,b' }, ' ok']) + expect(phrasing(parse(''))).toEqual([{ link: 'https://example.com/。a' }]) + expect(phrasing(parse('[link](https://example.com/。a)'))).toEqual([{ link: 'https://example.com/。a' }]) + }) +}) diff --git a/patches/mdast-util-gfm-autolink-literal@2.0.1.patch b/patches/mdast-util-gfm-autolink-literal@2.0.1.patch new file mode 100644 index 0000000000..6d1cd94dda --- /dev/null +++ b/patches/mdast-util-gfm-autolink-literal@2.0.1.patch @@ -0,0 +1,14 @@ +diff --git a/lib/index.js b/lib/index.js +index c5ca771c24dd914e342f791716a822431ee32b3a..11265b3fe41bb41985f149e2bd8f2fc2b6f85e2e 100644 +--- a/lib/index.js ++++ b/lib/index.js +@@ -131,7 +131,8 @@ function transformGfmAutolinkLiterals(tree) { + findAndReplace( + tree, + [ +- [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl], ++ // Non-ASCII punctuation and symbols, such as `。` or `,`, end the path in CJK prose. ++ [/(https?:\/\/|www(?=\.))([-.\w]+)((?:[!-~]|(?![\p{P}\p{S}])[^ \t\r\n])*)/giu, findUrl], + [/(?<=^|\s|\p{P}|\p{S})([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/gu, findEmail] + ], + {ignore: ['link', 'linkReference']} diff --git a/patches/micromark-extension-gfm-autolink-literal@2.1.0.patch b/patches/micromark-extension-gfm-autolink-literal@2.1.0.patch new file mode 100644 index 0000000000..be54305c35 --- /dev/null +++ b/patches/micromark-extension-gfm-autolink-literal@2.1.0.patch @@ -0,0 +1,82 @@ +diff --git a/dev/lib/syntax.js b/dev/lib/syntax.js +index 904e27200ae4980183beda525d33feef70c2e7f7..f05dff0e41b478e856b8419a306dfba163e9a9af 100644 +--- a/dev/lib/syntax.js ++++ b/dev/lib/syntax.js +@@ -662,7 +662,8 @@ function tokenizePath(effects, ok) { + if ( + code === codes.eof || + markdownLineEndingOrSpace(code) || +- unicodeWhitespace(code) ++ unicodeWhitespace(code) || ++ nonAsciiPunctuation(code) + ) { + return ok(code) + } +@@ -762,7 +763,8 @@ function tokenizeTrail(effects, ok, nok) { + // So is whitespace. + code === codes.eof || + markdownLineEndingOrSpace(code) || +- unicodeWhitespace(code) ++ unicodeWhitespace(code) || ++ nonAsciiPunctuation(code) + ) { + return ok(code) + } +@@ -932,6 +934,17 @@ function previousEmail(code) { + return !(code === codes.slash || gfmAtext(code)) + } + ++/** ++ * Non-ASCII punctuation or symbol, such as `。` or `,`, which ends a literal ++ * in CJK prose. ++ * ++ * @param {Code} code ++ * @returns {boolean} ++ */ ++function nonAsciiPunctuation(code) { ++ return code !== codes.eof && code > 127 && unicodePunctuation(code) ++} ++ + /** + * @param {Code} code + * @returns {boolean} +diff --git a/lib/syntax.js b/lib/syntax.js +index dffbaf15146b34d3b3f634286629ccb148866f4c..00c336f17c389929d58dab156a72d8961a45caf8 100644 +--- a/lib/syntax.js ++++ b/lib/syntax.js +@@ -584,7 +584,7 @@ function tokenizePath(effects, ok) { + if (code === 33 || code === 34 || code === 38 || code === 39 || code === 41 || code === 42 || code === 44 || code === 46 || code === 58 || code === 59 || code === 60 || code === 63 || code === 93 || code === 95 || code === 126) { + return effects.check(trail, ok, pathAtPunctuation)(code); + } +- if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) { ++ if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || nonAsciiPunctuation(code)) { + return ok(code); + } + effects.consume(code); +@@ -665,7 +665,7 @@ function tokenizeTrail(effects, ok, nok) { + // `<` is an end. + code === 60 || + // So is whitespace. +- code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) { ++ code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || nonAsciiPunctuation(code)) { + return ok(code); + } + return nok(code); +@@ -816,6 +816,17 @@ function previousEmail(code) { + return !(code === 47 || gfmAtext(code)); + } + ++/** ++ * Non-ASCII punctuation or symbol, such as `。` or `,`, which ends a literal ++ * in CJK prose. ++ * ++ * @param {Code} code ++ * @returns {boolean} ++ */ ++function nonAsciiPunctuation(code) { ++ return code !== null && code > 127 && unicodePunctuation(code); ++} ++ + /** + * @param {Code} code + * @returns {boolean} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 281ea5d111..96fad1e899 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,8 @@ patchedDependencies: app-builder-lib@26.17.0: b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348 exceljs@4.4.0: c1bf8ac8f319dab382f08e3a669b715e691d46c703ac5a4163eb383fbedd1d48 lexical@0.49.0: 54c4413db208cf234cb68c6a837f2523d3b319aa17911f1da385af08362e1356 + mdast-util-gfm-autolink-literal@2.0.1: 52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46 + micromark-extension-gfm-autolink-literal@2.1.0: d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2 node-pty@1.2.0-beta.15: 9d30053266d0f39d3debe894c675867f6782da6bcadbc94a693b3cfb9eff8ed0 importers: @@ -31118,7 +31120,7 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-autolink-literal@2.0.1: + mdast-util-gfm-autolink-literal@2.0.1(patch_hash=52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46): dependencies: '@types/mdast': 4.0.4 ccount: 2.0.1 @@ -31166,7 +31168,7 @@ snapshots: mdast-util-gfm@3.1.0(supports-color@9.4.0): dependencies: mdast-util-from-markdown: 2.0.3(supports-color@9.4.0) - mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-autolink-literal: 2.0.1(patch_hash=52891f84fbc268311cc9229fc2447d0a5557010eb3faace21c5e72a086031e46) mdast-util-gfm-footnote: 2.1.0(supports-color@9.4.0) mdast-util-gfm-strikethrough: 2.0.0(supports-color@9.4.0) mdast-util-gfm-table: 2.0.0(supports-color@9.4.0) @@ -31270,7 +31272,7 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-autolink-literal@2.1.0: + micromark-extension-gfm-autolink-literal@2.1.0(patch_hash=d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2): dependencies: micromark-util-character: 2.1.1 micromark-util-sanitize-uri: 2.0.1 @@ -31319,7 +31321,7 @@ snapshots: micromark-extension-gfm@3.0.0: dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-autolink-literal: 2.1.0(patch_hash=d9ea8de22e086b2711ec954c5b76eaf93e7b8503854de02ca557632fa7ccb4d2) micromark-extension-gfm-footnote: 2.1.0 micromark-extension-gfm-strikethrough: 2.1.0 micromark-extension-gfm-table: 2.1.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e9f3c19f44..59b31eb46b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -117,4 +117,6 @@ patchedDependencies: app-builder-lib@26.17.0: patches/app-builder-lib@26.17.0.patch exceljs@4.4.0: patches/exceljs@4.4.0.patch lexical@0.49.0: patches/lexical@0.49.0.patch + mdast-util-gfm-autolink-literal@2.0.1: patches/mdast-util-gfm-autolink-literal@2.0.1.patch + micromark-extension-gfm-autolink-literal@2.1.0: patches/micromark-extension-gfm-autolink-literal@2.1.0.patch node-pty@1.2.0-beta.15: patches/node-pty@1.2.0-beta.15.patch