From 7b34f62526d989bcc0ad7398dffd18cda0c3c12c Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 7 Jan 2025 05:29:00 +0700 Subject: [PATCH 1/6] fix: return empty array instead of throwing error if text !== markdown (#593) --- src/__tests__/parseExpensiMark.test.ts | 4 ++++ src/parseExpensiMark.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/__tests__/parseExpensiMark.test.ts b/src/__tests__/parseExpensiMark.test.ts index b49f46fa3..1d6a0436c 100644 --- a/src/__tests__/parseExpensiMark.test.ts +++ b/src/__tests__/parseExpensiMark.test.ts @@ -31,6 +31,10 @@ test('no formatting', () => { expect('Hello, world!').toBeParsedAs([]); }); +describe('parsing error', () => { + expect(`> [exa\nmple.com](https://example.com)`).toBeParsedAs([]); +}); + test('bold', () => { expect('Hello, *world*!').toBeParsedAs([ {type: 'syntax', start: 7, length: 1}, diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index cbf69845b..7657efd94 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -281,11 +281,12 @@ function parseExpensiMark(markdown: string): MarkdownRange[] { const tree = parseTokensToTree(tokens); const [text, ranges] = parseTreeToTextAndRanges(tree); if (text !== markdown) { - throw new Error( + console.error( `[react-native-live-markdown] Parsing error: the processed text does not match the original Markdown input. This may be caused by incorrect parsing functions or invalid input Markdown.\nProcessed input: '${JSON.stringify( text, )}'\nOriginal input: '${JSON.stringify(markdown)}'`, ); + return []; } const sortedRanges = sortRanges(ranges); const groupedRanges = groupRanges(sortedRanges); From 3f51bda020df9935dd6ff567477069f6b597cf73 Mon Sep 17 00:00:00 2001 From: "os-botify[bot]" <140437396+os-botify[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:30:03 +0000 Subject: [PATCH 2/6] Update package-lock.json version to 0.1.212 --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dee2f10c4..02cbca171 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@expensify/react-native-live-markdown", - "version": "0.1.211", + "version": "0.1.212", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@expensify/react-native-live-markdown", - "version": "0.1.211", + "version": "0.1.212", "hasInstallScript": true, "license": "MIT", "workspaces": [ From 24460a73fecab1fd8692be957ac7ed38a12870f2 Mon Sep 17 00:00:00 2001 From: "os-botify[bot]" <140437396+os-botify[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:30:04 +0000 Subject: [PATCH 3/6] Update package.json version to 0.1.212 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e554fc2f..14c421d48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expensify/react-native-live-markdown", - "version": "0.1.211", + "version": "0.1.212", "description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.", "main": "lib/commonjs/index", "module": "lib/module/index", 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 4/6] 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 7657efd94..7073d349a 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 000000000..dcfb913f4 --- /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 419ed780e..694b6601c 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; From 113c773cca31c9945cb63a07f1e7a6f8756040ad Mon Sep 17 00:00:00 2001 From: "os-botify[bot]" <140437396+os-botify[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 08:51:48 +0000 Subject: [PATCH 5/6] Update package-lock.json version to 0.1.213 --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02cbca171..5eb44974c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@expensify/react-native-live-markdown", - "version": "0.1.212", + "version": "0.1.213", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@expensify/react-native-live-markdown", - "version": "0.1.212", + "version": "0.1.213", "hasInstallScript": true, "license": "MIT", "workspaces": [ From e9c2893f7505f9db6fd45a708df00872dfc8a20d Mon Sep 17 00:00:00 2001 From: "os-botify[bot]" <140437396+os-botify[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 08:51:49 +0000 Subject: [PATCH 6/6] Update package.json version to 0.1.213 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14c421d48..6801fdafa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expensify/react-native-live-markdown", - "version": "0.1.212", + "version": "0.1.213", "description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.", "main": "lib/commonjs/index", "module": "lib/module/index",