Skip to content

Commit

Permalink
chore: resolve merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
BartoszGrajdek committed Dec 9, 2024
2 parents bb46ce0 + c35696d commit cddf8a0
Show file tree
Hide file tree
Showing 75 changed files with 2,019 additions and 2,227 deletions.
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: npm ci

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

- name: Typecheck parser
run: npm run typecheck -- --project parser/tsconfig.json

- name: Typecheck example app
run: npm run typecheck -- --project example/tsconfig.json

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

# react-native-live-markdown
android/src/main/assets/react-native-live-markdown-parser.js
.build_complete
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
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.3 or newer and [expensify-common](https://github.com/Expensify/expensify-common) 2.0.108 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 ()
35 changes: 35 additions & 0 deletions android/src/main/cpp/MarkdownParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "MarkdownParser.h"
#include "MarkdownGlobal.h"

#include <fbjni/fbjni.h>

using namespace facebook;

namespace expensify {
namespace livemarkdown {
jni::local_ref<jni::JString> MarkdownParser::nativeParse(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> text,
const int parserId) {
static std::mutex workletRuntimeMutex; // this needs to be global since the worklet runtime is also global
const auto lock = std::lock_guard<std::mutex>(workletRuntimeMutex);

const auto markdownRuntime = expensify::livemarkdown::getMarkdownRuntime();
jsi::Runtime &rt = markdownRuntime->getJSIRuntime();

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

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

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

void MarkdownParser::registerNatives() {
registerHybrid({
makeNativeMethod("nativeParse", MarkdownParser::nativeParse)});
}

} // namespace livemarkdown
} // namespace expensify
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,20 @@ using namespace facebook;
namespace expensify {
namespace livemarkdown {

class MarkdownUtils : public jni::HybridClass<MarkdownUtils>,
class MarkdownParser : public jni::HybridClass<MarkdownParser>,
public jsi::HostObject {
public:
static constexpr auto kJavaDescriptor =
"Lcom/expensify/livemarkdown/MarkdownUtils;";
"Lcom/expensify/livemarkdown/MarkdownParser;";

static void nativeInitializeRuntime(
static jni::local_ref<jni::JString> nativeParse(
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> text,
const int parserId);

static void registerNatives();

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

friend HybridBase;
};

Expand Down
44 changes: 0 additions & 44 deletions android/src/main/cpp/MarkdownUtils.cpp

This file was deleted.

10 changes: 8 additions & 2 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 "MarkdownParser.h"
#include "RuntimeDecorator.h"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { expensify::livemarkdown::MarkdownUtils::registerNatives(); });
vm, [] { expensify::livemarkdown::MarkdownParser::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);
}
Loading

0 comments on commit cddf8a0

Please sign in to comment.