mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(editor): choose safe markdown code fences
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import type { JSONContent } from '@tiptap/core'
|
||||
|
||||
type MarkdownCodeBlockRenderHelpers = {
|
||||
renderChildren: (nodes: JSONContent[]) => string
|
||||
}
|
||||
|
||||
function longestFenceRun(text: string, character: '`' | '~'): number {
|
||||
let longest = 0
|
||||
let current = 0
|
||||
for (const value of text) {
|
||||
if (value === character) {
|
||||
current += 1
|
||||
longest = Math.max(longest, current)
|
||||
} else {
|
||||
current = 0
|
||||
}
|
||||
}
|
||||
return longest
|
||||
}
|
||||
|
||||
function chooseFence(text: string): { character: '`' | '~'; length: number } {
|
||||
const backtickLength = Math.max(3, longestFenceRun(text, '`') + 1)
|
||||
const tildeLength = Math.max(3, longestFenceRun(text, '~') + 1)
|
||||
return tildeLength < backtickLength
|
||||
? { character: '~', length: tildeLength }
|
||||
: { character: '`', length: backtickLength }
|
||||
}
|
||||
|
||||
export function renderRichMarkdownCodeBlock(
|
||||
node: JSONContent,
|
||||
helpers: MarkdownCodeBlockRenderHelpers
|
||||
): string {
|
||||
const language = typeof node.attrs?.language === 'string' ? node.attrs.language : ''
|
||||
const body = helpers.renderChildren(node.content ?? [])
|
||||
const fence = chooseFence(body)
|
||||
const marker = fence.character.repeat(fence.length)
|
||||
return [`${marker}${language}`, body, marker].join('\n')
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import { RichMarkdownInlineMath } from './rich-markdown-inline-math'
|
||||
import { RichMarkdownCodeBlockLowlight } from './rich-markdown-lowlight'
|
||||
import { RichMarkdownTaskList } from './rich-markdown-task-list'
|
||||
import { createCachedLowlight } from './rich-markdown-lowlight-cache'
|
||||
import { renderRichMarkdownCodeBlock } from './rich-markdown-code-block-markdown'
|
||||
|
||||
const lowlight = createCachedLowlight(createLowlight(common))
|
||||
|
||||
@@ -83,6 +84,7 @@ export function createRichMarkdownExtensions({
|
||||
RichMarkdownParagraph,
|
||||
RichMarkdownCode,
|
||||
RichMarkdownCodeBlockLowlight.extend({
|
||||
renderMarkdown: renderRichMarkdownCodeBlock,
|
||||
addNodeView() {
|
||||
// Why: RichMarkdownCodeBlock never reads getPos, so it must not re-render
|
||||
// just because earlier edits shifted this block's document position.
|
||||
|
||||
@@ -60,4 +60,16 @@ describe('ordered list continuation serialization', () => {
|
||||
}
|
||||
expect(current).toBe('1. one\n ```ts\n const value = 1\n ```\n2. two')
|
||||
})
|
||||
|
||||
it('preserves meaningful indentation inside an ordered-item fence', () => {
|
||||
const source = '1. one\n\n ```\n indented\n ```\n2. two'
|
||||
expect(roundTrip(source)).toBe('1. one\n ```\n indented\n ```\n2. two')
|
||||
})
|
||||
|
||||
it('chooses a non-colliding fence for fence-shaped code content', () => {
|
||||
const source = '~~~\n```\n~~~'
|
||||
const canonical = roundTrip(source)
|
||||
expect(canonical).toBe(source)
|
||||
expect(roundTrip(canonical)).toBe(canonical)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user