diff --git a/src/renderer/src/components/editor/raw-markdown-html.ts b/src/renderer/src/components/editor/raw-markdown-html.ts index cde5311e80b..d07eb182e63 100644 --- a/src/renderer/src/components/editor/raw-markdown-html.ts +++ b/src/renderer/src/components/editor/raw-markdown-html.ts @@ -7,7 +7,10 @@ import type { RichMarkdownSourceKind, RichMarkdownSourceTransport } from './rich-markdown-source-transport' -import { isReservedRichMarkdownTransportBody } from './rich-markdown-source-transport' +import { + isReservedRichMarkdownTransportBody, + skipInlineTransportStartScan +} from './rich-markdown-source-transport' import { matchHtmlSuperscriptLinkSource } from './rich-markdown-html-superscript-link-source' const INLINE_HTML_PATTERN = /^|^<\/?[A-Za-z][\w.:-]*(?:\s[^<>]*?)?\/?>/ @@ -273,7 +276,7 @@ function createRawSourceNode({ markdownTokenizer: { name, level: inline ? 'inline' : 'block', - start: transport.startFor(kind), + start: inline ? skipInlineTransportStartScan : transport.startFor(kind), tokenize(src) { const matched = transport.match(src, kind) if (!matched) { diff --git a/src/renderer/src/components/editor/rich-markdown-doc-link.ts b/src/renderer/src/components/editor/rich-markdown-doc-link.ts index e2245ec472a..1ae68d44bfe 100644 --- a/src/renderer/src/components/editor/rich-markdown-doc-link.ts +++ b/src/renderer/src/components/editor/rich-markdown-doc-link.ts @@ -11,6 +11,7 @@ import { } from './markdown-doc-links' import { isReservedRichMarkdownTransportBody, + skipInlineTransportStartScan, type RichMarkdownSourceTransport } from './rich-markdown-source-transport' import { renderRichMarkdownDocLinkHtml } from './rich-markdown-doc-link-dom' @@ -126,7 +127,7 @@ export function createMarkdownDocLink(transport: RichMarkdownSourceTransport) { markdownTokenizer: { name: 'markdownDocLink', level: 'inline', - start: transport.startFor('document-link'), + start: skipInlineTransportStartScan, tokenize(src: string) { const matched = transport.match(src, 'document-link') if (!matched) { diff --git a/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts b/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts index da3f919fd3b..a60a6e9c777 100644 --- a/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts +++ b/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts @@ -1,5 +1,8 @@ import { Node } from '@tiptap/core' -import type { RichMarkdownSourceTransport } from './rich-markdown-source-transport' +import { + skipInlineTransportStartScan, + type RichMarkdownSourceTransport +} from './rich-markdown-source-transport' import { HTML_SUPERSCRIPT_LINK_SOURCE_LIMIT, parseHtmlSuperscriptLinkSource, @@ -41,7 +44,7 @@ export function createRichMarkdownHtmlSuperscriptLink( markdownTokenizer: { name: 'richMarkdownHtmlSuperscriptLink', level: 'inline', - start: transport.startFor('html-superscript-link'), + start: skipInlineTransportStartScan, tokenize(source) { const matched = transport.match(source, 'html-superscript-link') if (!matched) { diff --git a/src/renderer/src/components/editor/rich-markdown-inline-source-tokenization.test.ts b/src/renderer/src/components/editor/rich-markdown-inline-source-tokenization.test.ts new file mode 100644 index 00000000000..bc86bf4011a --- /dev/null +++ b/src/renderer/src/components/editor/rich-markdown-inline-source-tokenization.test.ts @@ -0,0 +1,22 @@ +// @vitest-environment happy-dom + +import { describe, expect, it } from 'vitest' +import { getRichMarkdownRoundTripOutput } from './markdown-round-trip' + +describe('inline source tokens at text boundaries', () => { + for (const prefix of ['plain ', '中文', '(', '*emphasis* ']) { + it.each(['Key', '[[note|Label]]', '[12]'])( + `recognizes %j after ${JSON.stringify(prefix)}`, + (fragment) => { + const source = `${prefix}${fragment} suffix` + expect(getRichMarkdownRoundTripOutput(source)).toBe(source) + } + ) + } + + it('preserves a large paragraph with dense malformed links and embedded source tokens', () => { + const prose = 'Text [[]] with a blank [[alias|]] and ordinary prose. '.repeat(1000) + const source = `${prose}Key [[note|Label]] ${prose.trim()}` + expect(getRichMarkdownRoundTripOutput(source)).toBe(source) + }) +}) diff --git a/src/renderer/src/components/editor/rich-markdown-source-transport.ts b/src/renderer/src/components/editor/rich-markdown-source-transport.ts index 94369d6e667..138ffb1ad91 100644 --- a/src/renderer/src/components/editor/rich-markdown-source-transport.ts +++ b/src/renderer/src/components/editor/rich-markdown-source-transport.ts @@ -15,6 +15,11 @@ const TRANSPORT_BODY_PATTERN = /^ORCA_RICH_MD:[a-f0-9]{32}:(?:literal|inline-html|block-html|document-link|html-superscript-link):/ const LEGACY_PREFIXES = ['ORCA_RAW_HTML_INLINE:', 'ORCA_RAW_HTML_BLOCK:', 'ORCA_DOC_LINK:'] as const +export function skipInlineTransportStartScan(): number { + // Marked already stops text at `[`, so inline envelopes need no suffix scan. + return -1 +} + export type RichMarkdownSourceTransport = { readonly key: string readonly authoredPrefix: string