mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(editor): render CJK-adjacent emphasis in Markdown preview
This commit is contained in:
@@ -289,6 +289,7 @@
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-cjk-friendly": "^2.3.1",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
|
||||
Generated
+81
@@ -472,6 +472,9 @@ importers:
|
||||
remark-breaks:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
remark-cjk-friendly:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(unified@11.0.5)
|
||||
remark-frontmatter:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(supports-color@7.2.0)
|
||||
@@ -5724,6 +5727,15 @@ packages:
|
||||
mdast-util-to-hast@13.2.1:
|
||||
resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
|
||||
|
||||
mdast-util-to-markdown-cjk-friendly@1.0.0:
|
||||
resolution: {integrity: sha512-BoaAm8mlJ+LAYz0Qs532Y3ciTuQYgBUPZcSFbvC/ZKmEMAKgulw84YvQK1gI34t/vL2euSfuaWlqczkTBgamkw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@types/mdast': '*'
|
||||
peerDependenciesMeta:
|
||||
'@types/mdast':
|
||||
optional: true
|
||||
|
||||
mdast-util-to-markdown@2.1.2:
|
||||
resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
|
||||
|
||||
@@ -5751,6 +5763,25 @@ packages:
|
||||
micromark-core-commonmark@2.0.3:
|
||||
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
|
||||
|
||||
micromark-extension-cjk-friendly-util@3.0.1:
|
||||
resolution: {integrity: sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
micromark-util-types: '*'
|
||||
peerDependenciesMeta:
|
||||
micromark-util-types:
|
||||
optional: true
|
||||
|
||||
micromark-extension-cjk-friendly@2.0.1:
|
||||
resolution: {integrity: sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
micromark: ^4.0.0
|
||||
micromark-util-types: ^2.0.0
|
||||
peerDependenciesMeta:
|
||||
micromark-util-types:
|
||||
optional: true
|
||||
|
||||
micromark-extension-frontmatter@2.0.0:
|
||||
resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==}
|
||||
|
||||
@@ -6534,6 +6565,16 @@ packages:
|
||||
remark-breaks@4.0.0:
|
||||
resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
|
||||
|
||||
remark-cjk-friendly@2.3.1:
|
||||
resolution: {integrity: sha512-f+pKZRxCRwNEGFBKNRAZAqU91GIK1SAo3ZyFHWRUgC9zcxRR0BXKd6YwqgSsxtW0rNpUDtONj7H5nje2WL3fcA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@types/mdast': ^4.0.0
|
||||
unified: ^11.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/mdast':
|
||||
optional: true
|
||||
|
||||
remark-frontmatter@5.0.0:
|
||||
resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
|
||||
|
||||
@@ -12632,6 +12673,16 @@ snapshots:
|
||||
unist-util-visit: 5.1.0
|
||||
vfile: 6.0.3
|
||||
|
||||
mdast-util-to-markdown-cjk-friendly@1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2):
|
||||
dependencies:
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2)
|
||||
micromark-util-symbol: 2.0.1
|
||||
optionalDependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
transitivePeerDependencies:
|
||||
- micromark-util-types
|
||||
|
||||
mdast-util-to-markdown@2.1.2:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -12700,6 +12751,25 @@ snapshots:
|
||||
micromark-util-symbol: 2.0.1
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-cjk-friendly-util@3.0.1(micromark-util-types@2.0.2):
|
||||
dependencies:
|
||||
get-east-asian-width: 1.5.0
|
||||
micromark-util-character: 2.1.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
optionalDependencies:
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-cjk-friendly@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0)):
|
||||
dependencies:
|
||||
devlop: 1.1.0
|
||||
micromark: 4.0.2(supports-color@7.2.0)
|
||||
micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2)
|
||||
micromark-util-chunked: 2.0.1
|
||||
micromark-util-resolve-all: 2.0.1
|
||||
micromark-util-symbol: 2.0.1
|
||||
optionalDependencies:
|
||||
micromark-util-types: 2.0.2
|
||||
|
||||
micromark-extension-frontmatter@2.0.0:
|
||||
dependencies:
|
||||
fault: 2.0.1
|
||||
@@ -13768,6 +13838,17 @@ snapshots:
|
||||
mdast-util-newline-to-break: 2.0.0
|
||||
unified: 11.0.5
|
||||
|
||||
remark-cjk-friendly@2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))(unified@11.0.5):
|
||||
dependencies:
|
||||
mdast-util-to-markdown-cjk-friendly: 1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2)
|
||||
micromark-extension-cjk-friendly: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2(supports-color@7.2.0))
|
||||
unified: 11.0.5
|
||||
optionalDependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
transitivePeerDependencies:
|
||||
- micromark
|
||||
- micromark-util-types
|
||||
|
||||
remark-frontmatter@5.0.0(supports-color@7.2.0):
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import remarkCjkFriendly from 'remark-cjk-friendly/parseOnly'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkParse from 'remark-parse'
|
||||
import { unified } from 'unified'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { MarkdownPreviewBody } from './MarkdownPreviewBody'
|
||||
|
||||
const cjkEmphasisExamples = [
|
||||
{
|
||||
name: 'Korean text after a quoted phrase',
|
||||
markdown: '**"이런"**것은 강조됩니다.',
|
||||
expectedHtml: '<strong>"이런"</strong>것은 강조됩니다.'
|
||||
},
|
||||
{
|
||||
name: 'Korean text after parentheses',
|
||||
markdown: '**(강조)**입니다.',
|
||||
expectedHtml: '<strong>(강조)</strong>입니다.'
|
||||
},
|
||||
{
|
||||
name: 'Korean text on both delimiter boundaries',
|
||||
markdown: '문장은**"여기"**에서 이어집니다.',
|
||||
expectedHtml: '문장은<strong>"여기"</strong>에서 이어집니다.'
|
||||
},
|
||||
{
|
||||
name: 'Japanese text after corner brackets',
|
||||
markdown: '**「強調」**です。',
|
||||
expectedHtml: '<strong>「強調」</strong>です。'
|
||||
},
|
||||
{
|
||||
name: 'Chinese text after quotation marks',
|
||||
markdown: '**“强调”**文本',
|
||||
expectedHtml: '<strong>“强调”</strong>文本'
|
||||
}
|
||||
]
|
||||
|
||||
const unchangedMarkdownExamples = [
|
||||
'# Heading',
|
||||
'**bold** text and *italic* text',
|
||||
'foo_bar_baz',
|
||||
'`**literal**`',
|
||||
'[link](https://example.com)',
|
||||
'- [x] completed task',
|
||||
'| left | right |\n| --- | --- |\n| a | b |',
|
||||
'~~deleted~~ text',
|
||||
'~~"삭제"~~문장'
|
||||
]
|
||||
|
||||
function parseMarkdown(markdown: string) {
|
||||
return unified().use(remarkParse).use(remarkGfm).parse(markdown)
|
||||
}
|
||||
|
||||
function parseCjkFriendlyMarkdown(markdown: string) {
|
||||
return unified().use(remarkParse).use(remarkGfm).use(remarkCjkFriendly).parse(markdown)
|
||||
}
|
||||
|
||||
describe('MarkdownPreviewBody', () => {
|
||||
it.each(cjkEmphasisExamples)('renders emphasis for $name', ({ markdown, expectedHtml }) => {
|
||||
const html = renderToStaticMarkup(<MarkdownPreviewBody content={markdown} components={{}} />)
|
||||
|
||||
expect(html).toContain(expectedHtml)
|
||||
})
|
||||
|
||||
it.each(unchangedMarkdownExamples)('keeps existing parsing for %s', (markdown) => {
|
||||
expect(parseCjkFriendlyMarkdown(markdown)).toEqual(parseMarkdown(markdown))
|
||||
})
|
||||
})
|
||||
@@ -7,6 +7,7 @@ import rehypeRaw from 'rehype-raw'
|
||||
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
import remarkCjkFriendly from 'remark-cjk-friendly/parseOnly'
|
||||
import remarkFrontmatter from 'remark-frontmatter'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
@@ -55,6 +56,7 @@ const markdownPreviewSanitizeSchema = {
|
||||
type MarkdownPluginList = NonNullable<ReactMarkdownOptions['remarkPlugins']>
|
||||
const MARKDOWN_REMARK_PLUGINS: MarkdownPluginList = [
|
||||
remarkGfm,
|
||||
remarkCjkFriendly,
|
||||
remarkBreaks,
|
||||
remarkFrontmatter,
|
||||
remarkMath,
|
||||
|
||||
@@ -98,6 +98,15 @@ describe('markdown table of contents', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('extracts emphasized headings next to Korean text', () => {
|
||||
const toc = buildMarkdownTableOfContents('# **"이런"**것은 강조됩니다')
|
||||
|
||||
expect(toc[0]).toMatchObject({
|
||||
id: '이런것은-강조됩니다',
|
||||
title: '"이런"것은 강조됩니다'
|
||||
})
|
||||
})
|
||||
|
||||
it('uses GitHub-compatible duplicate slugs', () => {
|
||||
const toc = buildMarkdownTableOfContents('# Repeat\n# Repeat')
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import remarkCjkFriendly from 'remark-cjk-friendly/parseOnly'
|
||||
import remarkFrontmatter from 'remark-frontmatter'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkParse from 'remark-parse'
|
||||
@@ -112,7 +113,7 @@ function appendTocItem(stack: MarkdownTocItem[], item: MarkdownTocItem): void {
|
||||
}
|
||||
|
||||
type MarkdownAstNode = {
|
||||
alt?: string
|
||||
alt?: string | null
|
||||
children?: MarkdownAstNode[]
|
||||
depth?: number
|
||||
type?: string
|
||||
@@ -142,8 +143,9 @@ export function buildMarkdownTableOfContents(markdown: string): MarkdownTocItem[
|
||||
const tree = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkCjkFriendly)
|
||||
.use(remarkFrontmatter, ['yaml', 'toml'])
|
||||
.parse(markdown) as MarkdownAstNode
|
||||
.parse(markdown)
|
||||
|
||||
function visit(node: MarkdownAstNode): void {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user