From 9030bc5d58b6ba351645f34e45fd08635e9ab62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= <39538890+Skalakid@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:50:52 -0800 Subject: [PATCH] Fix custom parsing on the web (#585) --- src/parseExpensiMark.ts | 40 +------------------------- src/rangeUtils.ts | 56 ++++++++++++++++++++++++++++++++++++ src/web/utils/parserUtils.ts | 19 +++--------- 3 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 src/rangeUtils.ts diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 7657efd9..7073d349 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -6,6 +6,7 @@ import {unescapeText} from 'expensify-common/dist/utils'; import {decode} from 'html-entities'; import type {WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; import type {MarkdownType, MarkdownRange} from './commonTypes'; +import {groupRanges, sortRanges} from './rangeUtils'; function isWeb() { return Platform.OS === 'web'; @@ -233,45 +234,6 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] { return [text, ranges]; } -// getTagPriority returns a priority for a tag, higher priority means the tag should be processed first -function getTagPriority(tag: string) { - switch (tag) { - case 'blockquote': - return 2; - case 'h1': - return 1; - default: - return 0; - } -} - -function sortRanges(ranges: MarkdownRange[]) { - // sort ranges by start position, then by length, then by tag hierarchy - return ranges.sort((a, b) => a.start - b.start || b.length - a.length || getTagPriority(b.type) - getTagPriority(a.type) || 0); -} - -function groupRanges(ranges: MarkdownRange[]) { - const lastVisibleRangeIndex: {[key in MarkdownType]?: number} = {}; - - return ranges.reduce((acc, range) => { - const start = range.start; - const end = range.start + range.length; - - const rangeWithSameStyleIndex = lastVisibleRangeIndex[range.type]; - const sameStyleRange = rangeWithSameStyleIndex !== undefined ? acc[rangeWithSameStyleIndex] : undefined; - - if (sameStyleRange && sameStyleRange.start <= start && sameStyleRange.start + sameStyleRange.length >= end && range.length > 1) { - // increment depth of overlapping range - sameStyleRange.depth = (sameStyleRange.depth || 1) + 1; - } else { - lastVisibleRangeIndex[range.type] = acc.length; - acc.push(range); - } - - return acc; - }, [] as MarkdownRange[]); -} - function parseExpensiMark(markdown: string): MarkdownRange[] { if (markdown.length > MAX_PARSABLE_LENGTH) { return []; diff --git a/src/rangeUtils.ts b/src/rangeUtils.ts new file mode 100644 index 00000000..dcfb913f --- /dev/null +++ b/src/rangeUtils.ts @@ -0,0 +1,56 @@ +import type {MarkdownRange, MarkdownType} from './commonTypes'; + +// getTagPriority returns a priority for a tag, higher priority means the tag should be processed first +function getTagPriority(tag: string) { + switch (tag) { + case 'blockquote': + return 2; + case 'h1': + return 1; + default: + return 0; + } +} + +function sortRanges(ranges: MarkdownRange[]) { + // sort ranges by start position, then by length, then by tag hierarchy + return ranges.sort((a, b) => a.start - b.start || b.length - a.length || getTagPriority(b.type) - getTagPriority(a.type) || 0); +} + +function groupRanges(ranges: MarkdownRange[]) { + const lastVisibleRangeIndex: {[key in MarkdownType]?: number} = {}; + + return ranges.reduce((acc, range) => { + const start = range.start; + const end = range.start + range.length; + + const rangeWithSameStyleIndex = lastVisibleRangeIndex[range.type]; + const sameStyleRange = rangeWithSameStyleIndex !== undefined ? acc[rangeWithSameStyleIndex] : undefined; + + if (sameStyleRange && sameStyleRange.start <= start && sameStyleRange.start + sameStyleRange.length >= end && range.length > 1) { + // increment depth of overlapping range + sameStyleRange.depth = (sameStyleRange.depth || 1) + 1; + } else { + lastVisibleRangeIndex[range.type] = acc.length; + acc.push(range); + } + + return acc; + }, [] as MarkdownRange[]); +} + +function ungroupRanges(ranges: MarkdownRange[]): MarkdownRange[] { + const ungroupedRanges: MarkdownRange[] = []; + ranges.forEach((range) => { + if (!range.depth) { + ungroupedRanges.push(range); + } + const {depth, ...rangeWithoutDepth} = range; + Array.from({length: depth!}).forEach(() => { + ungroupedRanges.push(rangeWithoutDepth); + }); + }); + return ungroupedRanges; +} + +export {sortRanges, groupRanges, ungroupRanges}; diff --git a/src/web/utils/parserUtils.ts b/src/web/utils/parserUtils.ts index 419ed780..694b6601 100644 --- a/src/web/utils/parserUtils.ts +++ b/src/web/utils/parserUtils.ts @@ -6,6 +6,7 @@ import {getCurrentCursorPosition, moveCursorToEnd, setCursorPosition} from './cu import {addStyleToBlock, extendBlockStructure, getFirstBlockMarkdownRange, isBlockMarkdownType} from './blockUtils'; import type {InlineImagesInputProps, MarkdownRange} from '../../commonTypes'; import {getAnimationCurrentTimes, updateAnimationsTime} from './animationUtils'; +import {sortRanges, ungroupRanges} from '../../rangeUtils'; type Paragraph = { text: string; @@ -14,20 +15,6 @@ type Paragraph = { markdownRanges: MarkdownRange[]; }; -function ungroupRanges(ranges: MarkdownRange[]): MarkdownRange[] { - const ungroupedRanges: MarkdownRange[] = []; - ranges.forEach((range) => { - if (!range.depth) { - ungroupedRanges.push(range); - } - const {depth, ...rangeWithoutDepth} = range; - Array.from({length: depth!}).forEach(() => { - ungroupedRanges.push(rangeWithoutDepth); - }); - }); - return ungroupedRanges; -} - function splitTextIntoLines(text: string): Paragraph[] { let lineStartIndex = 0; const lines: Paragraph[] = text.split('\n').map((line) => { @@ -167,7 +154,9 @@ function parseRangesToHTMLNodes( return {dom: rootElement, tree: rootNode}; } - const markdownRanges = ungroupRanges(ranges); + // Sort all ranges by start position, length, and by tag hierarchy so the styles and text are applied in correct order + const sortedRanges = sortRanges(ranges); + const markdownRanges = ungroupRanges(sortedRanges); lines = mergeLinesWithMultilineTags(lines, markdownRanges); let lastRangeEndIndex = 0;