mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(editor): preserve zero-start ordered lists
This commit is contained in:
@@ -17,6 +17,17 @@ function markerWidth(context: RenderContext): number {
|
||||
return `${listStart(context) + (context.index ?? 0)}. `.length
|
||||
}
|
||||
|
||||
function renderZeroStartMarker(rendered: string, context: RenderContext): string {
|
||||
if (context.parentType !== 'orderedList' || listStart(context) !== 0) {
|
||||
return rendered
|
||||
}
|
||||
const index = context.index ?? 0
|
||||
const baseMarker = `${index + 1}. `
|
||||
return rendered.startsWith(baseMarker)
|
||||
? `${index}. ${rendered.slice(baseMarker.length)}`
|
||||
: rendered
|
||||
}
|
||||
|
||||
function paragraphLineCount(node: JSONContent): number {
|
||||
const first = Array.isArray(node.content) ? node.content[0] : undefined
|
||||
if (first?.type !== 'paragraph') {
|
||||
@@ -41,7 +52,9 @@ export const RichMarkdownListItem = ListItem.extend({
|
||||
return baseRenderMarkdown(node, helpers, context)
|
||||
}
|
||||
const width = markerWidth(context)
|
||||
const lines = baseRenderMarkdown(node, helpers, context).split('\n')
|
||||
const lines = renderZeroStartMarker(baseRenderMarkdown(node, helpers, context), context).split(
|
||||
'\n'
|
||||
)
|
||||
const paragraphLines = paragraphLineCount(node)
|
||||
const paragraph = indentParagraphContinuations(lines.slice(0, paragraphLines).join('\n'), width)
|
||||
const blocks = lines
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { encodeRawMarkdownHtmlForRichEditor } from './raw-markdown-html'
|
||||
import { createRichMarkdownExtensions } from './rich-markdown-extensions'
|
||||
import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport'
|
||||
function roundTrip(source: string): string {
|
||||
const codec = createRichMarkdownEditorCodec()
|
||||
const editor = new Editor({
|
||||
element: null,
|
||||
extensions: createRichMarkdownExtensions({ codec }),
|
||||
content: encodeRawMarkdownHtmlForRichEditor(source, codec),
|
||||
contentType: 'markdown'
|
||||
})
|
||||
try {
|
||||
return editor.getMarkdown().trimEnd()
|
||||
} finally {
|
||||
editor.destroy()
|
||||
}
|
||||
}
|
||||
describe('ordered list start values', () => {
|
||||
it('preserves a zero-start ordered list', () => {
|
||||
const source = '0. zero\n1. one'
|
||||
expect(roundTrip(source)).toBe(source)
|
||||
})
|
||||
it('keeps a zero-start list stable across repeated saves', () => {
|
||||
let current = '0. zero\n1. one'
|
||||
for (let cycle = 0; cycle < 3; cycle += 1) {
|
||||
current = roundTrip(current)
|
||||
}
|
||||
expect(current).toBe('0. zero\n1. one')
|
||||
})
|
||||
it('does not change ordinary ordered lists', () => {
|
||||
expect(roundTrip('1. one\n2. two')).toBe('1. one\n2. two')
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,30 @@
|
||||
import type { MarkdownTokenizer } from '@tiptap/core'
|
||||
import type { MarkdownParseResult, MarkdownToken, MarkdownTokenizer } from '@tiptap/core'
|
||||
import { OrderedList, ORDERED_LIST_MARKER_PATTERN } from '@tiptap/extension-list'
|
||||
|
||||
const orderedListStart = new RegExp(`^\\s*(?:${ORDERED_LIST_MARKER_PATTERN})[.)]\\s`)
|
||||
const zeroOrderedListStart = /^\s*0[.)]\s/
|
||||
|
||||
const baseTokenizer = OrderedList.config.markdownTokenizer as MarkdownTokenizer
|
||||
const baseParseMarkdown = OrderedList.config.parseMarkdown
|
||||
|
||||
function withZeroStart(parsed: MarkdownParseResult, token: MarkdownToken): MarkdownParseResult {
|
||||
if (
|
||||
token.start !== 0 ||
|
||||
!parsed ||
|
||||
Array.isArray(parsed) ||
|
||||
!('type' in parsed) ||
|
||||
parsed.type !== 'orderedList'
|
||||
) {
|
||||
return parsed
|
||||
}
|
||||
return {
|
||||
...parsed,
|
||||
attrs: {
|
||||
...parsed.attrs,
|
||||
start: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const RichMarkdownOrderedList = OrderedList.extend({
|
||||
markdownTokenizer: {
|
||||
@@ -13,7 +34,18 @@ export const RichMarkdownOrderedList = OrderedList.extend({
|
||||
if (!orderedListStart.test(src)) {
|
||||
return undefined
|
||||
}
|
||||
return baseTokenizer.tokenize(src, tokens, lexer)
|
||||
const token = baseTokenizer.tokenize(src, tokens, lexer)
|
||||
if (zeroOrderedListStart.test(src) && token && typeof token === 'object') {
|
||||
token.start = 0
|
||||
}
|
||||
return token
|
||||
}
|
||||
},
|
||||
parseMarkdown: (token, helpers) => {
|
||||
const parsed = baseParseMarkdown?.(token, helpers)
|
||||
if (!parsed) {
|
||||
return []
|
||||
}
|
||||
return withZeroStart(parsed, token)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user