From a72e74fe9d91adde4d2aeecbd3e9605eafb53da5 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Fri, 4 Sep 2026 00:20:32 +0530 Subject: [PATCH 1/4] refactor: add-optional-max-length-to-parse-expensimark --- src/__tests__/parseExpensiMark.test.ts | 22 ++++++++++++++++++++++ src/parseExpensiMark.ts | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/__tests__/parseExpensiMark.test.ts b/src/__tests__/parseExpensiMark.test.ts index 1453fafb9..91f5ac380 100644 --- a/src/__tests__/parseExpensiMark.test.ts +++ b/src/__tests__/parseExpensiMark.test.ts @@ -37,6 +37,28 @@ test('no formatting', () => { expect('Hello, world!').toBeParsedAs([]); }); +describe('maximum parsable length', () => { + const createBoldMarkdown = (length: number) => `*${'a'.repeat(length - 2)}*`; + const getBoldRanges = (length: number): MarkdownRange[] => [ + {type: 'syntax', start: 0, length: 1}, + {type: 'bold', start: 1, length: length - 2}, + {type: 'syntax', start: length - 1, length: 1}, + ]; + + test('keeps the default 4000-character limit', () => { + expect(parseExpensiMark(createBoldMarkdown(4000))).toEqual(getBoldRanges(4000)); + expect(parseExpensiMark(createBoldMarkdown(4001))).toEqual([]); + }); + + test('uses the caller-provided maximum length', () => { + const markdown = createBoldMarkdown(5000); + + expect(parseExpensiMark(markdown)).toEqual([]); + expect(parseExpensiMark(markdown, 5000)).toEqual(getBoldRanges(5000)); + expect(parseExpensiMark(`${markdown}a`, 5000)).toEqual([]); + }); +}); + describe('parsing error', () => { expect(`> [exa\nmple.com](https://example.com)`).toBeParsedAs([ {type: 'blockquote', start: 0, length: 6}, diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index ebb462d51..41faedb27 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -238,8 +238,8 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] { return [text, ranges]; } -function parseExpensiMark(markdown: string): MarkdownRange[] { - if (markdown.length > MAX_PARSABLE_LENGTH) { +function parseExpensiMark(markdown: string, maxLength = MAX_PARSABLE_LENGTH): MarkdownRange[] { + if (markdown.length > maxLength) { return []; } const html = parseMarkdownToHTML(markdown); From d891f619658899764011ea091441b3f3ed3e8e77 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Fri, 4 Sep 2026 01:17:46 +0530 Subject: [PATCH 2/4] chore: update expensify-common to 2.0.201 --- package-lock.json | 10 +++++----- package.json | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b6ffee30..b161a5ed8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react-native": "^5.0.0", "eslint-plugin-tsdoc": "^0.2.17", - "expensify-common": "2.0.189", + "expensify-common": "2.0.201", "jest": "^29.6.3", "jest-environment-jsdom": "^29.7.0", "nodemon": "^3.1.3", @@ -66,7 +66,7 @@ "node": ">= 18.0.0" }, "peerDependencies": { - "expensify-common": ">=2.0.189", + "expensify-common": ">=2.0.201", "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" @@ -11403,9 +11403,9 @@ } }, "node_modules/expensify-common": { - "version": "2.0.189", - "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.189.tgz", - "integrity": "sha512-w7AJQDpRdeyCvWxOhGPeWTckmVKOH2RBN2/dwUkNCm5tszeS2ELissz3YoQt02Meon14CXEbC1SAqiUw7Kca8w==", + "version": "2.0.201", + "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.201.tgz", + "integrity": "sha512-8ziun4VC5bcQobhb+rmpCuaAiXBapPrxiAUyITt6lLaYBa1K2pfkcvZypgCmrj3eLselOvAVkf5Y/dVDcWs6Cg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 70456c27c..23fa52438 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react-native": "^5.0.0", "eslint-plugin-tsdoc": "^0.2.17", - "expensify-common": "2.0.189", + "expensify-common": "2.0.201", "jest": "^29.6.3", "jest-environment-jsdom": "^29.7.0", "nodemon": "^3.1.3", @@ -109,7 +109,7 @@ "typescript": "^5.8.3" }, "peerDependencies": { - "expensify-common": ">=2.0.189", + "expensify-common": ">=2.0.201", "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" From 1f041288f3861f088639abcf2dee79b02cc4b511 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Fri, 4 Sep 2026 11:16:24 +0530 Subject: [PATCH 3/4] docs: document parseExpensiMark max length --- src/parseExpensiMark.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 41faedb27..80b5c9df1 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -238,6 +238,13 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] { return [text, ranges]; } +/** + * Parses Markdown text into formatting ranges using ExpensiMark. + * + * @param markdown - The raw Markdown string to parse. + * @param maxLength - Maximum input length to parse; longer input returns `[]` without parsing. Defaults to 4000. + * @returns The list of formatting ranges, or `[]` if input exceeds `maxLength` or fails to parse. + */ function parseExpensiMark(markdown: string, maxLength = MAX_PARSABLE_LENGTH): MarkdownRange[] { if (markdown.length > maxLength) { return []; From 041f80448de395650d60d45f2661c20155f8d0c4 Mon Sep 17 00:00:00 2001 From: Uzaifm127 Date: Fri, 4 Sep 2026 11:25:34 +0530 Subject: [PATCH 4/4] docs: document ExpensiMark limit and dependency Explain the default and configurable input lengths and require expensify-common 2.0.201 or newer. --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfe7d3d32..e802a8d01 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ npm install @expensify/react-native-live-markdown react-native-worklets expensif npx expo install @expensify/react-native-live-markdown react-native-worklets expensify-common html-entities@2.5.3 ``` -React Native Live Markdown requires [react-native-worklets](https://github.com/software-mansion/react-native-reanimated/tree/main/packages/react-native-worklets) 0.7.0 or newer as well as [expensify-common](https://github.com/Expensify/expensify-common) 2.0.115 and [html-entities](https://github.com/mdevils/html-entities) 2.5.3 exactly if using the default built-in ExpensiMark parser. +React Native Live Markdown requires [react-native-worklets](https://github.com/software-mansion/react-native-reanimated/tree/main/packages/react-native-worklets) 0.7.0 or newer as well as [expensify-common](https://github.com/Expensify/expensify-common) 2.0.201 or newer and [html-entities](https://github.com/mdevils/html-entities) 2.5.3 exactly if using the default built-in ExpensiMark parser. > [!IMPORTANT] > Please follow the `react-native-worklets` [Getting Started](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/getting-started/#react-native-community-cli) guide to avoid issues. @@ -55,6 +55,16 @@ export default function App() { } ``` +The built-in `parseExpensiMark` parser accepts an optional maximum input length. It defaults to 4,000 characters and returns no formatting ranges when the input is longer than the configured limit. To use a different limit, wrap the parser in a worklet function: + +```ts +function parser(input: string) { + 'worklet'; + + return parseExpensiMark(input, 5000); +} +``` + ## Styling `MarkdownTextInput` can be styled using `style` prop just like regular `TextInput` component.