mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(editor): preserve ordered list continuation columns
This commit is contained in:
@@ -38,6 +38,7 @@ import { createRichMarkdownHtmlSuperscriptLink } from './rich-markdown-html-supe
|
||||
import type { RichMarkdownHtmlSuperscriptLinkContext } from './rich-markdown-html-superscript-link-context'
|
||||
import { RichMarkdownOrderedList } from './rich-markdown-ordered-list'
|
||||
import { RichMarkdownCodeSpanPadding } from './rich-markdown-code-span-padding'
|
||||
import { RichMarkdownListItem } from './rich-markdown-list-item'
|
||||
import { RichMarkdownParagraph } from './rich-markdown-paragraph'
|
||||
import { RichMarkdownInlineMath } from './rich-markdown-inline-math'
|
||||
import { RichMarkdownCodeBlockLowlight } from './rich-markdown-lowlight'
|
||||
@@ -74,6 +75,7 @@ export function createRichMarkdownExtensions({
|
||||
link: false,
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
listItem: false,
|
||||
orderedList: false,
|
||||
paragraph: false
|
||||
}),
|
||||
@@ -215,6 +217,7 @@ export function createRichMarkdownExtensions({
|
||||
inline: true
|
||||
}),
|
||||
RichMarkdownOrderedList,
|
||||
RichMarkdownListItem,
|
||||
RichMarkdownTaskList,
|
||||
TaskItem.configure({
|
||||
nested: true
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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 continuation serialization', () => {
|
||||
it.each([
|
||||
['9. item\n continuation', '9. item\n continuation'],
|
||||
['10. item\n continuation', '10. item\n continuation'],
|
||||
['10. item\n continuation', '10. item\n continuation'],
|
||||
['100. item\n continuation', '100. item\n continuation']
|
||||
])('uses the marker width for %j', (source, expected) => {
|
||||
expect(roundTrip(source)).toBe(expected)
|
||||
})
|
||||
|
||||
it('keeps multi-digit continuation text intact across repeated saves', () => {
|
||||
const source = '10. An item mentioning the 12-step\n write-up are in the folder.'
|
||||
let current = source
|
||||
for (let cycle = 0; cycle < 3; cycle += 1) {
|
||||
current = roundTrip(current)
|
||||
}
|
||||
expect(current).toBe('10. An item mentioning the 12-step\n write-up are in the folder.')
|
||||
})
|
||||
|
||||
it('does not change bullet continuation formatting', () => {
|
||||
const source = '- item\n continuation'
|
||||
expect(roundTrip(source)).toBe('- item\ncontinuation')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { JSONContent, RenderContext } from '@tiptap/core'
|
||||
import { ListItem } from '@tiptap/extension-list'
|
||||
|
||||
const BASE_INDENT = 2
|
||||
|
||||
const baseRenderMarkdown = ListItem.config.renderMarkdown
|
||||
|
||||
function listStart(context: RenderContext): number {
|
||||
const start = context.meta?.parentAttrs?.start
|
||||
return typeof start === 'number' ? start : 1
|
||||
}
|
||||
|
||||
function markerWidth(context: RenderContext): number {
|
||||
if (context.parentType !== 'orderedList') {
|
||||
return 2
|
||||
}
|
||||
return `${listStart(context) + (context.index ?? 0)}. `.length
|
||||
}
|
||||
|
||||
function paragraphLineCount(node: JSONContent): number {
|
||||
const first = Array.isArray(node.content) ? node.content[0] : undefined
|
||||
if (first?.type !== 'paragraph') {
|
||||
return 1
|
||||
}
|
||||
const text = (first.content ?? [])
|
||||
.map((child) => (child.type === 'hardBreak' ? '\n' : (child.text ?? '')))
|
||||
.join('')
|
||||
return text.split('\n').length
|
||||
}
|
||||
|
||||
function indentParagraphContinuations(markdown: string, column: number): string {
|
||||
return markdown.replace(/\n(?!\n)[ \t]*/g, `\n${' '.repeat(column)}`)
|
||||
}
|
||||
|
||||
export const RichMarkdownListItem = ListItem.extend({
|
||||
renderMarkdown: (node, helpers, context) => {
|
||||
if (typeof baseRenderMarkdown !== 'function') {
|
||||
return ''
|
||||
}
|
||||
if (context.parentType !== 'orderedList') {
|
||||
return baseRenderMarkdown(node, helpers, context)
|
||||
}
|
||||
const width = markerWidth(context)
|
||||
const lines = baseRenderMarkdown(node, helpers, context).split('\n')
|
||||
const paragraphLines = paragraphLineCount(node)
|
||||
const paragraph = indentParagraphContinuations(lines.slice(0, paragraphLines).join('\n'), width)
|
||||
const blocks = lines
|
||||
.slice(paragraphLines)
|
||||
.map((line) =>
|
||||
line === '' ? line : `${' '.repeat(Math.max(0, width - BASE_INDENT))}${line}`
|
||||
)
|
||||
return [paragraph, ...blocks].join('\n')
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user