Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom parsing logic (pass worklet as parser prop) #439

Open
wants to merge 162 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
162 commits
Select commit Hold shift + click to select a range
6cbb4a9
WIP
tomekzaw Mar 23, 2024
bb5506d
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Mar 25, 2024
5d97778
make it work on android
tomekzaw Mar 25, 2024
45bbd66
remove parser from resources
tomekzaw Mar 25, 2024
12e2798
make it work on android fabric
tomekzaw Mar 26, 2024
258d9b2
plug expensimark
tomekzaw Mar 27, 2024
5a749ff
add useExpensiMarkParser
tomekzaw Mar 27, 2024
03de712
rename escape to escapeText and stringify error
tomekzaw Mar 27, 2024
0cdc5c3
workletize str.js
tomekzaw Mar 27, 2024
efe3ea6
Merge branch 'main' into @tomekzaw/worklets
tomekzaw May 24, 2024
368255a
Eliminate JSON conversion on iOS
tomekzaw May 25, 2024
f83febc
Bump versions
tomekzaw May 27, 2024
245037a
Update JS parser
tomekzaw May 27, 2024
bc7b202
Fix undefined depth
tomekzaw May 27, 2024
8f8d948
Update patches
tomekzaw May 27, 2024
1b19df4
Remove postinstall-postinstall
tomekzaw May 27, 2024
9d6ef85
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jun 17, 2024
e1ee2f5
Bump expensify-common
tomekzaw Jun 17, 2024
91e6ec2
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jun 25, 2024
e78dc39
Fix Android build
tomekzaw Jun 25, 2024
1f63094
Bump react-native-reanimated
tomekzaw Jun 25, 2024
1ad5f03
Downgrade react-native-reanimated
tomekzaw Jun 25, 2024
4888f58
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jun 27, 2024
28ca8b0
It works!
tomekzaw Jun 27, 2024
c1cad07
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jun 28, 2024
43b3f2e
Update Podfile.lock
tomekzaw Jun 28, 2024
1d78410
Fix URLs
tomekzaw Jul 1, 2024
f1c5462
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 1, 2024
b711a9e
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 2, 2024
9bd96ab
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 3, 2024
16cd0f3
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 3, 2024
9432d87
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 5, 2024
48d63c8
Regenerate patch
tomekzaw Jul 5, 2024
4bdbbce
Bump expensify-common to 2.0.32
tomekzaw Jul 5, 2024
23caaf8
Bump expensify-common to 2.0.34
tomekzaw Jul 5, 2024
a72e5cb
Bump expensify-common to 2.0.35
tomekzaw Jul 5, 2024
7d99cfc
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 9, 2024
e8eb357
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 15, 2024
7124567
Fix instant formatting on parser change on Paper iOS
tomekzaw Jul 15, 2024
4b6cd16
Fix instant formatting on parser change on Fabric iOS
tomekzaw Jul 15, 2024
e91dfd0
Update Podfile.lock
tomekzaw Jul 15, 2024
9350262
Fix import
tomekzaw Jul 15, 2024
581f4f8
Fix instant formatting on parser change on Paper Android
tomekzaw Jul 15, 2024
138289a
Fix instant formatting on parser change on Fabric Android
tomekzaw Jul 16, 2024
5679b2a
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 16, 2024
3e057de
Bump expensify-common to 2.0.44
tomekzaw Jul 16, 2024
f0f62fe
Bump expensify-common to 2.0.46
tomekzaw Jul 16, 2024
bb1d1de
Bump expensify-common to 2.0.48
tomekzaw Jul 16, 2024
8f03306
Add worklet check
tomekzaw Jul 17, 2024
4f9ab6a
Use custom parser on web
tomekzaw Jul 17, 2024
04b34e0
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 17, 2024
e02e2e6
Bump expensify-common to 2.0.49
tomekzaw Jul 17, 2024
afb1621
Rename `parseExpensiMarkToRanges` to `parseExpensiMark`
tomekzaw Jul 17, 2024
9806762
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 18, 2024
f9e147f
Bump expensify-common to 2.0.53
tomekzaw Jul 23, 2024
1446097
Bump react-native-reanimated to nightly
tomekzaw Jul 25, 2024
00ad148
Update README.md
tomekzaw Jul 25, 2024
e5323f3
Handle case when parser is undefined
tomekzaw Jul 25, 2024
97c9501
Handle case when parser is undefined on web
tomekzaw Jul 25, 2024
d14215f
Ignore linter error
tomekzaw Jul 25, 2024
8860469
Bump expensify-common to 2.0.54
tomekzaw Jul 25, 2024
f9e3590
Bump expensify-common to 2.0.55
tomekzaw Jul 26, 2024
3ce2c15
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Jul 26, 2024
72d3b4d
Update Podfile.lock
tomekzaw Jul 29, 2024
7cd62c1
Update CMakeLists.txt
tomekzaw Jul 29, 2024
e0c1d61
Correctly unregister Markdown worklet
tomekzaw Jul 31, 2024
605ca14
Cleanup worklet registration logic
tomekzaw Jul 31, 2024
16f4ac4
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Aug 1, 2024
33d6578
Update Podfile.lock
tomekzaw Aug 1, 2024
1e394b3
Add expensify-common as peerDependency
tomekzaw Aug 2, 2024
aeceb98
Fix TypeScript errors
tomekzaw Aug 2, 2024
93364b5
Update README.md
tomekzaw Aug 2, 2024
eb0b35c
Update parseExpensiMark.ts
tomekzaw Aug 2, 2024
b7dfec1
Update CMakeLists.txt
tomekzaw Aug 2, 2024
64c6b85
De-duplicate type `Range` and `MarkdownType`
tomekzaw Aug 2, 2024
cef26a8
Update README.md
tomekzaw Aug 2, 2024
bc16685
Update README.md
tomekzaw Aug 2, 2024
2b5c5a9
Remove parser diff check
tomekzaw Aug 2, 2024
aef7d7a
Update CMakeLists.txt
tomekzaw Aug 2, 2024
9ec6d3e
Remove parser leftovers
tomekzaw Aug 2, 2024
adb95de
Restore original App.tsx
tomekzaw Aug 2, 2024
396c09e
Restore original App.tsx
tomekzaw Aug 2, 2024
dd3a815
Bump react-native-reanimated
tomekzaw Aug 5, 2024
cc36c00
Bump expensify-common
tomekzaw Aug 5, 2024
221fa36
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Aug 13, 2024
eed5984
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Aug 30, 2024
41ef20e
Bump required dependencies
tomekzaw Aug 30, 2024
74baa10
Restore `markdownRanges`
tomekzaw Aug 30, 2024
4b3e504
Merge branch main into @tomekzaw/worklets
tomekzaw Aug 31, 2024
bd6ae31
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Aug 31, 2024
f5a5452
Fix dependencies
tomekzaw Aug 31, 2024
4dbd18a
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 2, 2024
c45fbca
Fix TS errors
tomekzaw Sep 2, 2024
fbdbcec
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 2, 2024
2342eef
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 5, 2024
589478c
Update README.md
tomekzaw Sep 5, 2024
a521066
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 9, 2024
a74ed4d
Update Podfile.lock
tomekzaw Sep 9, 2024
eb20acb
Fix registering commit hook on iOS
tomekzaw Sep 10, 2024
b744c05
Fix building on iOS Paper
tomekzaw Sep 10, 2024
5aeb9dc
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 13, 2024
a9d9685
Add comment
tomekzaw Sep 13, 2024
4e9b157
Patch html-entities
tomekzaw Sep 13, 2024
cc8889f
Fix Android build
tomekzaw Sep 13, 2024
781843a
Remove unsupported type error
tomekzaw Sep 13, 2024
14064d3
Fix height issue on Android
tomekzaw Sep 13, 2024
769e7a3
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 16, 2024
41af79a
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 18, 2024
99d9418
Add workaround for incorrect height on Android
tomekzaw Sep 19, 2024
688fc8d
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 19, 2024
9d49fb1
Exclude `libruntimeexecutor.so`
tomekzaw Sep 23, 2024
bc5c08a
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 23, 2024
aa46028
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Sep 30, 2024
ce9a681
Bump react-native and react-native-reanimated
tomekzaw Sep 30, 2024
f731a33
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 1, 2024
2ec375b
Fix crash on hot reload when modifying parser worklet
tomekzaw Oct 1, 2024
1534f46
Add mutex for parser worklets registry
tomekzaw Oct 1, 2024
bd8034d
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 1, 2024
ede3d9e
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 3, 2024
64d7728
Update patch
tomekzaw Oct 3, 2024
6c3280f
Remove newline
tomekzaw Oct 3, 2024
cf2c96d
Use global worklet directive
tomekzaw Oct 3, 2024
b71b5f0
Remove `parseMarkdown` adapter
tomekzaw Oct 3, 2024
cea8507
Revert some changes in LiveMarkdownModule.mm
tomekzaw Oct 3, 2024
0fd1dc2
Remove react-native-reanimated patch
tomekzaw Oct 4, 2024
3a5f413
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 4, 2024
796e162
Update Podfile.lock
tomekzaw Oct 4, 2024
5490735
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 4, 2024
3662ba0
Update Podfile.lock
tomekzaw Oct 4, 2024
2ff85a3
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 7, 2024
2729fe6
Add `synchronized` keyword to fix crash
tomekzaw Oct 7, 2024
75b914e
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 7, 2024
e28662c
Add missing dependency
tomekzaw Oct 7, 2024
4737f76
Fix crash in Expensify/App
tomekzaw Oct 8, 2024
2d28192
Bump expensify-common
tomekzaw Oct 8, 2024
a913bd0
Add `const` modifier
tomekzaw Oct 9, 2024
ccda7ac
Add error message
tomekzaw Oct 9, 2024
9612826
Use `self.bridge` instead of `[RCTBridge currentBridge]`
tomekzaw Oct 9, 2024
44a3fc6
Add missing parser dependency
tomekzaw Oct 10, 2024
29fef38
Improve ExpensiMark import
tomekzaw Oct 10, 2024
0d08556
Bump versions in README.md
tomekzaw Oct 10, 2024
c4df160
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 10, 2024
fb1173b
Rename `ios` to `apple`
tomekzaw Oct 10, 2024
51eecd5
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 10, 2024
c506d7b
Remove unused variable
tomekzaw Oct 11, 2024
090ec8c
Add warning when worklet parser returns incorrect schema
tomekzaw Oct 11, 2024
f96d37c
Return unformatted string on error
tomekzaw Oct 11, 2024
5e630d6
Add assert
tomekzaw Oct 11, 2024
8cd222e
Magically fix crash due to already unregistered parser worklet after …
tomekzaw Oct 11, 2024
c82c6fc
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 28, 2024
676f762
Bump react-native-reanimated
tomekzaw Oct 28, 2024
680c4b9
Bump expensify-common to 2.0.106
tomekzaw Oct 28, 2024
a13b307
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Oct 31, 2024
25a6bba
Update Podfile.lock
tomekzaw Oct 31, 2024
0d72eb0
Fix build iOS workflow
tomekzaw Oct 31, 2024
62bc84e
Remove `_installed` property
tomekzaw Oct 31, 2024
ff832bb
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Nov 5, 2024
320e85b
Explain why we don't need a mutex
tomekzaw Nov 5, 2024
7ae462f
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Nov 5, 2024
a5f9812
Update Podfile.lock
tomekzaw Nov 5, 2024
726ce79
Merge branch 'main' into @tomekzaw/worklets
tomekzaw Nov 6, 2024
d3db42d
Update Podfile.lock
tomekzaw Nov 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
**/node_modules/*
parser/react-native-live-markdown-parser.js

# any js file inside android and ios folders
**/android/**/*.js
Expand Down
13 changes: 0 additions & 13 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,9 @@ jobs:
- name: Install node_modules
run: yarn install --immutable

- name: Verify there's no parser diff
working-directory: parser
run: |
yarn build
if ! git diff --name-only --exit-code; then
# shellcheck disable=SC2016
echo 'Error: Parser diff detected! Please run `cd parser && yarn build` and commit the changes.'
exit 1
fi
- name: Typecheck library
run: yarn tsc --project tsconfig.json --noEmit

- name: Typecheck parser
run: yarn tsc --project parser/tsconfig.json --noEmit

- name: Typecheck example app
run: yarn tsc --project example/tsconfig.json --noEmit

Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ android/keystores/debug.keystore
lib/

# react-native-live-markdown
android/src/main/assets/react-native-live-markdown-parser.js
.build_complete

# GitHub GPG Keys
Expand Down
9 changes: 9 additions & 0 deletions .yarn/patches/html-entities-npm-2.5.2-0b6113e376.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
diff --git a/lib/index.js b/lib/index.js
index 3a44c851c4895f74db30360befb509d232055c56..2f7809ba105f32aa3d9620c1be3f14c93e589185 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,2 +1,3 @@
+"worklet";
"use strict";var __assign=this&&this.__assign||function(){__assign=Object.assign||function(t){for(var s,i=1,n=arguments.length;i<n;i++){s=arguments[i];for(var p in s)if(Object.prototype.hasOwnProperty.call(s,p))t[p]=s[p]}return t};return __assign.apply(this,arguments)};Object.defineProperty(exports,"__esModule",{value:true});var named_references_1=require("./named-references");var numeric_unicode_map_1=require("./numeric-unicode-map");var surrogate_pairs_1=require("./surrogate-pairs");var allNamedReferences=__assign(__assign({},named_references_1.namedReferences),{all:named_references_1.namedReferences.html5});function replaceUsingRegExp(macroText,macroRegExp,macroReplacer){macroRegExp.lastIndex=0;var replaceMatch=macroRegExp.exec(macroText);var replaceResult;if(replaceMatch){replaceResult="";var replaceLastIndex=0;do{if(replaceLastIndex!==replaceMatch.index){replaceResult+=macroText.substring(replaceLastIndex,replaceMatch.index)}var replaceInput=replaceMatch[0];replaceResult+=macroReplacer(replaceInput);replaceLastIndex=replaceMatch.index+replaceInput.length}while(replaceMatch=macroRegExp.exec(macroText));if(replaceLastIndex!==macroText.length){replaceResult+=macroText.substring(replaceLastIndex)}}else{replaceResult=macroText}return replaceResult}var encodeRegExps={specialChars:/[<>'"&]/g,nonAscii:/[<>'"&\u0080-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g,nonAsciiPrintable:/[<>'"&\x01-\x08\x11-\x15\x17-\x1F\x7f-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g,nonAsciiPrintableOnly:/[\x01-\x08\x11-\x15\x17-\x1F\x7f-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g,extensive:/[\x01-\x0c\x0e-\x1f\x21-\x2c\x2e-\x2f\x3a-\x40\x5b-\x60\x7b-\x7d\x7f-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g};var defaultEncodeOptions={mode:"specialChars",level:"all",numeric:"decimal"};function encode(text,_a){var _b=_a===void 0?defaultEncodeOptions:_a,_c=_b.mode,mode=_c===void 0?"specialChars":_c,_d=_b.numeric,numeric=_d===void 0?"decimal":_d,_e=_b.level,level=_e===void 0?"all":_e;if(!text){return""}var encodeRegExp=encodeRegExps[mode];var references=allNamedReferences[level].characters;var isHex=numeric==="hexadecimal";return replaceUsingRegExp(text,encodeRegExp,(function(input){var result=references[input];if(!result){var code=input.length>1?surrogate_pairs_1.getCodePoint(input,0):input.charCodeAt(0);result=(isHex?"&#x"+code.toString(16):"&#"+code)+";"}return result}))}exports.encode=encode;var defaultDecodeOptions={scope:"body",level:"all"};var strict=/&(?:#\d+|#[xX][\da-fA-F]+|[0-9a-zA-Z]+);/g;var attribute=/&(?:#\d+|#[xX][\da-fA-F]+|[0-9a-zA-Z]+)[;=]?/g;var baseDecodeRegExps={xml:{strict:strict,attribute:attribute,body:named_references_1.bodyRegExps.xml},html4:{strict:strict,attribute:attribute,body:named_references_1.bodyRegExps.html4},html5:{strict:strict,attribute:attribute,body:named_references_1.bodyRegExps.html5}};var decodeRegExps=__assign(__assign({},baseDecodeRegExps),{all:baseDecodeRegExps.html5});var fromCharCode=String.fromCharCode;var outOfBoundsChar=fromCharCode(65533);var defaultDecodeEntityOptions={level:"all"};function getDecodedEntity(entity,references,isAttribute,isStrict){var decodeResult=entity;var decodeEntityLastChar=entity[entity.length-1];if(isAttribute&&decodeEntityLastChar==="="){decodeResult=entity}else if(isStrict&&decodeEntityLastChar!==";"){decodeResult=entity}else{var decodeResultByReference=references[entity];if(decodeResultByReference){decodeResult=decodeResultByReference}else if(entity[0]==="&"&&entity[1]==="#"){var decodeSecondChar=entity[2];var decodeCode=decodeSecondChar=="x"||decodeSecondChar=="X"?parseInt(entity.substr(3),16):parseInt(entity.substr(2));decodeResult=decodeCode>=1114111?outOfBoundsChar:decodeCode>65535?surrogate_pairs_1.fromCodePoint(decodeCode):fromCharCode(numeric_unicode_map_1.numericUnicodeMap[decodeCode]||decodeCode)}}return decodeResult}function decodeEntity(entity,_a){var _b=(_a===void 0?defaultDecodeEntityOptions:_a).level,level=_b===void 0?"all":_b;if(!entity){return""}return getDecodedEntity(entity,allNamedReferences[level].entities,false,false)}exports.decodeEntity=decodeEntity;function decode(text,_a){var _b=_a===void 0?defaultDecodeOptions:_a,_c=_b.level,level=_c===void 0?"all":_c,_d=_b.scope,scope=_d===void 0?level==="xml"?"strict":"body":_d;if(!text){return""}var decodeRegExp=decodeRegExps[level][scope];var references=allNamedReferences[level].entities;var isAttribute=scope==="attribute";var isStrict=scope==="strict";return replaceUsingRegExp(text,decodeRegExp,(function(entity){return getDecodedEntity(entity,references,isAttribute,isStrict)}))}exports.decode=decode;
//# sourceMappingURL=./index.js.map
\ No newline at end of file
63 changes: 55 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- ⚛️ Drop-in replacement for `<TextInput>` component
- ⌨️ Live synchronous formatting on every keystroke
- ⚡ Fully native experience (selection, spellcheck, autocomplete)
- 🔧 Customizable logic
- 🎨 Customizable styles
- 🌐 Universal support (Android, iOS, web)
- 🏗️ Supports New Architecture
Expand All @@ -14,15 +15,17 @@
First, install the library from npm with the package manager of your choice:

```sh
yarn add @expensify/react-native-live-markdown
npm install @expensify/react-native-live-markdown --save
npx expo install @expensify/react-native-live-markdown
yarn add @expensify/react-native-live-markdown react-native-reanimated expensify-common
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
npm install @expensify/react-native-live-markdown react-native-reanimated expensify-common --save
npx expo install @expensify/react-native-live-markdown react-native-reanimated expensify-common
```

React Native Live Markdown requires [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated) 3.16.0 or newer and [expensify-common](https://github.com/Expensify/expensify-common) 2.0.98 or newer.

Then, install the iOS dependencies with CocoaPods:

```sh
cd ios && pod install
cd ios && bundler install && bundler exec pod install
```

The library includes native code so you will need to re-build the native app.
Expand All @@ -33,7 +36,7 @@ The library includes native code so you will need to re-build the native app.
## Usage

```tsx
import {MarkdownTextInput} from '@expensify/react-native-live-markdown';
import {MarkdownTextInput, parseExpensiMark} from '@expensify/react-native-live-markdown';
import React from 'react';

export default function App() {
Expand All @@ -43,6 +46,7 @@ export default function App() {
<MarkdownTextInput
value={text}
onChangeText={setText}
parser={parseExpensiMark}
/>
);
}
Expand Down Expand Up @@ -118,6 +122,48 @@ The style object can be passed to multiple `MarkdownTextInput` components using
> [!TIP]
> We recommend to store the style object outside of a component body or memoize the style object with `React.useMemo`.

## Parsing logic

`MarkdownTextInput` behavior can be customized via `parser` property. Parser is a function that accepts a plaintext string and returns an array of `MarkdownRange` objects:

```ts
interface MarkdownRange {
type: MarkdownType;
start: number;
length: number;
depth?: number;
}
```

Currently, only the following types are supported:

```ts
type MarkdownType = 'bold' | 'italic' | 'strikethrough' | 'emoji' | 'mention-here' | 'mention-user' | 'mention-report' | 'link' | 'code' | 'pre' | 'blockquote' | 'h1' | 'syntax';
```

Parser needs to be marked as a [worklet](https://docs.swmansion.com/react-native-reanimated/docs/guides/worklets/) because it's executed on the UI thread as the user types.

Here's a sample function that parses all substrings located between two asterisks as bold text:

```ts
function parser(input: string) {
'worklet';

const ranges = [];
const regexp = /\*(.*?)\*/g;
let match;
while ((match = regexp.exec(input)) !== null) {
ranges.push({start: match.index, length: 1, type: 'syntax'});
ranges.push({start: match.index + 1, length: match[1]!.length, type: 'bold'});
ranges.push({start: match.index + 1 + match[1]!.length, length: 1, type: 'syntax'});
}
return ranges;
}
```

> [!TIP]
> We recommend to store the parser function outside of a component body or memoize the parser function with `React.useMemo`.

## Markdown flavors support

Currently, `react-native-live-markdown` supports only [ExpensiMark](https://github.com/Expensify/expensify-common/blob/main/lib/ExpensiMark.ts) flavor. We are working on CommonMark support as well as possibility to use other Markdown parsers.
Expand All @@ -126,9 +172,10 @@ Currently, `react-native-live-markdown` supports only [ExpensiMark](https://gith

`MarkdownTextInput` inherits all props of React Native's `TextInput` component as well as introduces the following properties:

| Prop | Type | Default | Note |
| --------------- | --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `markdownStyle` | `MarkdownStyle` | `undefined` | Adds custom styling to Markdown text. The provided value is merged with default style object. See [Styling](https://github.com/expensify/react-native-live-markdown/blob/main/README.md#styling) for more information. |
| Prop | Type | Default | Note |
| --------------- | ------------------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parser` | `(value: string) => MarkdownRange[]` | `undefined` | A function that parses the current value and returns an array of ranges. |
| `markdownStyle` | `MarkdownStyle` | `undefined` | Adds custom styling to Markdown text. The provided value is merged with default style object. See [Styling](https://github.com/expensify/react-native-live-markdown/blob/main/README.md#styling) for more information. |

## Compatibility

Expand Down
6 changes: 2 additions & 4 deletions RNLiveMarkdown.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ Pod::Spec.new do |s|
s.platforms = { :ios => "11.0", :visionos => "1.0" }
s.source = { :git => "https://github.com/expensify/react-native-live-markdown.git", :tag => "#{s.version}" }

s.source_files = "apple/**/*.{h,m,mm}"
s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}"

s.resources = "parser/react-native-live-markdown-parser.js"

s.dependency "hermes-engine"
s.dependency "RNReanimated/worklets"

s.xcconfig = {
"OTHER_CFLAGS" => "$(inherited) -DREACT_NATIVE_MINOR_VERSION=#{react_native_minor_version}"
Expand Down
13 changes: 4 additions & 9 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ android {
"**/libjsi.so",
"**/libreactnativejni.so",
"**/libreactnative.so",
"**/libreact_nativemodule_core.so",
"**/libruntimeexecutor.so",
"**/libworklets.so",
"**/libreact_render*.so",
"**/librrc_root.so",
]
Expand All @@ -171,6 +174,7 @@ repositories {
dependencies {
implementation "com.facebook.react:react-android" // version substituted by RNGP
implementation "com.facebook.react:hermes-android" // version substituted by RNGP
implementation project(":react-native-reanimated")
}

if (isNewArchitectureEnabled()) {
Expand All @@ -180,12 +184,3 @@ if (isNewArchitectureEnabled()) {
codegenJavaPackageName = "com.expensify.livemarkdown"
}
}

task copyJS(type: Copy) {
from '../parser/react-native-live-markdown-parser.js'
into 'src/main/assets'
}

tasks.preBuild {
dependsOn copyJS
}
20 changes: 14 additions & 6 deletions android/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,35 @@ add_compile_options(-fvisibility=hidden -fexceptions -frtti)

string(APPEND CMAKE_CXX_FLAGS " -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}")

file(GLOB livemarkdown_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
set(CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cpp")

add_library(${CMAKE_PROJECT_NAME} SHARED ${livemarkdown_SRC})
file(GLOB ANDROID_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB CPP_SRC CONFIGURE_DEPENDS "${CPP_DIR}/*.cpp")

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_library(${CMAKE_PROJECT_NAME} SHARED ${ANDROID_SRC} ${CPP_SRC})

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR})

find_package(fbjni REQUIRED CONFIG)
find_package(ReactAndroid REQUIRED CONFIG)
find_package(hermes-engine REQUIRED CONFIG)
find_package(react-native-reanimated REQUIRED CONFIG)

target_link_libraries(
${CMAKE_PROJECT_NAME}
fbjni::fbjni
hermes-engine::libhermes
ReactAndroid::jsi
react-native-reanimated::worklets
)

if (ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
target_link_libraries(${CMAKE_PROJECT_NAME} ReactAndroid::reactnative)
elseif (ReactAndroid_VERSION_MINOR GREATER_EQUAL 75)
target_link_libraries(${CMAKE_PROJECT_NAME} ReactAndroid::reactnativejni)
target_link_libraries(
${CMAKE_PROJECT_NAME}
ReactAndroid::react_nativemodule_core
ReactAndroid::reactnativejni
ReactAndroid::runtimeexecutor
)
else ()
message(FATAL_ERROR "react-native-live-markdown requires react-native 0.75 or newer.")
endif ()
37 changes: 13 additions & 24 deletions android/src/main/cpp/MarkdownUtils.cpp
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
#include "MarkdownUtils.h"
#include "MarkdownGlobal.h"

#include <fbjni/fbjni.h>
#include <hermes/hermes.h>

using namespace facebook;

namespace expensify {
namespace livemarkdown {
std::shared_ptr<jsi::Runtime> MarkdownUtils::runtime_;

void MarkdownUtils::nativeInitializeRuntime(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> code) {
assert(runtime_ == nullptr && "Markdown runtime is already initialized");
runtime_ = facebook::hermes::makeHermesRuntime();
auto codeBuffer = std::make_shared<const jsi::StringBuffer>(code->toStdString());
runtime_->evaluateJavaScript(codeBuffer, "nativeInitializeRuntime");
}

jni::local_ref<jni::JString> MarkdownUtils::nativeParseMarkdown(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> input) {
jsi::Runtime &rt = *runtime_;
auto func = rt.global().getPropertyAsFunction(rt, "parseExpensiMarkToRanges");
auto arg = input->toStdString();
jsi::Value result;
try {
result = func.call(rt, arg);
} catch (jsi::JSError e) {
result = jsi::Array(rt, 0);
}
auto json = rt.global().getPropertyAsObject(rt, "JSON").getPropertyAsFunction(rt, "stringify").call(rt, result).asString(rt).utf8(rt);
jni::alias_ref<jni::JString> input,
int parserId) {
// This method is synchronized (see MarkdownUtils.java) so we don't need a mutex here.
const auto markdownRuntime = expensify::livemarkdown::getMarkdownRuntime();
tomekzaw marked this conversation as resolved.
Show resolved Hide resolved
jsi::Runtime &rt = markdownRuntime->getJSIRuntime();

const auto markdownWorklet = expensify::livemarkdown::getMarkdownWorklet(parserId);

const auto text = jsi::String::createFromUtf8(rt, input->toStdString());
const auto result = markdownRuntime->runGuarded(markdownWorklet, text);

const auto json = rt.global().getPropertyAsObject(rt, "JSON").getPropertyAsFunction(rt, "stringify").call(rt, result).asString(rt).utf8(rt);
return jni::make_jstring(json);
}

void MarkdownUtils::registerNatives() {
registerHybrid({
makeNativeMethod("nativeInitializeRuntime", MarkdownUtils::nativeInitializeRuntime),
makeNativeMethod("nativeParseMarkdown", MarkdownUtils::nativeParseMarkdown)});
}

Expand Down
9 changes: 2 additions & 7 deletions android/src/main/cpp/MarkdownUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,14 @@ namespace livemarkdown {
static constexpr auto kJavaDescriptor =
"Lcom/expensify/livemarkdown/MarkdownUtils;";

static void nativeInitializeRuntime(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> code);

static jni::local_ref<jni::JString> nativeParseMarkdown(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> input);
jni::alias_ref<jni::JString> input,
int parserId);

static void registerNatives();

private:
static std::shared_ptr<jsi::Runtime> runtime_;

friend HybridBase;
};

Expand Down
6 changes: 6 additions & 0 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#include <fbjni/fbjni.h>

#include "MarkdownUtils.h"
#include "RuntimeDecorator.h"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { expensify::livemarkdown::MarkdownUtils::registerNatives(); });
}

extern "C" JNIEXPORT void JNICALL Java_com_expensify_livemarkdown_LiveMarkdownModule_injectJSIBindings(JNIEnv *env, jobject thiz, jlong jsiRuntime) {
jsi::Runtime &rt = *reinterpret_cast<jsi::Runtime*>(jsiRuntime);
expensify::livemarkdown::injectJSIBindings(rt);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

public class CustomFabricUIManager {

public static FabricUIManager create(FabricUIManager source, ReadableMap markdownProps) {
public static FabricUIManager create(FabricUIManager source, ReadableMap markdownProps, int parserId) {
Class<? extends FabricUIManager> uiManagerClass = source.getClass();

try {
Expand All @@ -27,7 +27,7 @@ public static FabricUIManager create(FabricUIManager source, ReadableMap markdow

FabricUIManager customFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, batchEventDispatchedListener);

mountingManagerField.set(customFabricUIManager, new CustomMountingManager(viewManagerRegistry, mountItemExecutor, reactContext, markdownProps));
mountingManagerField.set(customFabricUIManager, new CustomMountingManager(viewManagerRegistry, mountItemExecutor, reactContext, markdownProps, parserId));

return customFabricUIManager;
} catch (NoSuchFieldException | IllegalAccessException e) {
Expand Down
Loading
Loading