import { matchCodeFenceStart } from '@admin/utils/editor/match-code-fence-start' import { matchHtmlMarkSyntax } from '@admin/utils/editor/match-html-mark-syntax' import { matchMarkdownBlockPrefix } from '@admin/utils/editor/match-markdown-block-prefix' import { matchMarkdownMarkSyntax } from '@admin/utils/editor/match-markdown-mark-syntax' interface MarkdownPlainTextOffsetMap { markdownToPlainOffsets: number[] plainToMarkdownOffsets: number[] } export function createMarkdownPlainTextOffsetMap(markdown: string): MarkdownPlainTextOffsetMap { const markdownToPlainOffsets = new Array(markdown.length + 1).fill(0) const plainToMarkdownOffsets = [0] let index = 0 let plainOffset = 0 const skipUntil = (nextIndex: number) => { const safeNextIndex = Math.max(index, Math.min(nextIndex, markdown.length)) while (index < safeNextIndex) { markdownToPlainOffsets[index] = plainOffset index++ markdownToPlainOffsets[index] = plainOffset } } const consumeVisibleUntil = (nextIndex: number) => { const safeNextIndex = Math.max(index, Math.min(nextIndex, markdown.length)) while (index < safeNextIndex) { consumeVisibleCharacter() } } const consumeVisibleCharacter = () => { const character = markdown[index] if (character === '\r') { skipUntil(index + 1) return } if (character === '\n') { const runStart = index let runEnd = index + 1 while (runEnd < markdown.length && markdown[runEnd] === '\n') { runEnd++ } markdownToPlainOffsets[runStart] = plainOffset plainOffset++ plainToMarkdownOffsets[plainOffset] = runEnd for (let offset = runStart + 1; offset <= runEnd; offset++) { markdownToPlainOffsets[offset] = plainOffset } index = runEnd return } markdownToPlainOffsets[index] = plainOffset plainOffset++ index++ plainToMarkdownOffsets[plainOffset] = index markdownToPlainOffsets[index] = plainOffset } while (index < markdown.length) { const imageMatch = /^!\[([^\]]*)\]\([^)]+\)/.exec(markdown.slice(index)) if (imageMatch) { const textStart = index + 2 const textEnd = textStart + imageMatch[1].length skipUntil(textStart) consumeVisibleUntil(textEnd) skipUntil(index + imageMatch[0].length) continue } const linkMatch = /^\[([^\]]+)\]\([^)]+\)/.exec(markdown.slice(index)) if (linkMatch) { const textStart = index + 1 const textEnd = textStart + linkMatch[1].length skipUntil(textStart) consumeVisibleUntil(textEnd) skipUntil(index + linkMatch[0].length) continue } const codeFenceEnd = matchCodeFenceStart(markdown, index) if (codeFenceEnd !== null) { skipUntil(codeFenceEnd) continue } const blockPrefixEnd = matchMarkdownBlockPrefix(markdown, index) if (blockPrefixEnd !== null) { skipUntil(blockPrefixEnd) continue } const markSyntaxEnd = matchMarkdownMarkSyntax(markdown, index) if (markSyntaxEnd !== null) { skipUntil(markSyntaxEnd) continue } const htmlMarkSyntaxEnd = matchHtmlMarkSyntax(markdown, index) if (htmlMarkSyntaxEnd !== null) { skipUntil(htmlMarkSyntaxEnd) continue } if (markdown[index] === '\\' && index + 1 < markdown.length) { skipUntil(index + 1) consumeVisibleCharacter() continue } consumeVisibleCharacter() } markdownToPlainOffsets[markdown.length] = plainOffset return { markdownToPlainOffsets, plainToMarkdownOffsets } }