@expensify/react-native-live-markdown 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +139 -0
- package/RNLiveMarkdown.podspec +44 -0
- package/android/build.gradle +143 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/cpp/CMakeLists.txt +23 -0
- package/android/src/main/cpp/MarkdownUtils.cpp +44 -0
- package/android/src/main/cpp/MarkdownUtils.h +41 -0
- package/android/src/main/cpp/OnLoad.cpp +8 -0
- package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +24 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java +11 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java +47 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java +10 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +39 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +9 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java +11 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java +10 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java +18 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java +7 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java +5 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java +186 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorView.java +85 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorViewManager.java +31 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java +38 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java +5 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +176 -0
- package/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java +24 -0
- package/android/src/oldarch/MarkdownTextInputDecoratorViewManagerSpec.java +10 -0
- package/ios/MarkdownLayoutManager.h +12 -0
- package/ios/MarkdownLayoutManager.mm +35 -0
- package/ios/MarkdownTextInputDecoratorComponentView.h +12 -0
- package/ios/MarkdownTextInputDecoratorComponentView.mm +55 -0
- package/ios/MarkdownTextInputDecoratorView.h +12 -0
- package/ios/MarkdownTextInputDecoratorView.mm +106 -0
- package/ios/MarkdownTextInputDecoratorViewManager.h +8 -0
- package/ios/MarkdownTextInputDecoratorViewManager.mm +23 -0
- package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.h +14 -0
- package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +43 -0
- package/ios/RCTBaseTextInputView+Markdown.h +16 -0
- package/ios/RCTBaseTextInputView+Markdown.m +66 -0
- package/ios/RCTMarkdownStyle.h +35 -0
- package/ios/RCTMarkdownStyle.mm +81 -0
- package/ios/RCTMarkdownUtils.h +18 -0
- package/ios/RCTMarkdownUtils.mm +134 -0
- package/ios/RCTTextInputComponentView+Markdown.h +21 -0
- package/ios/RCTTextInputComponentView+Markdown.mm +51 -0
- package/ios/RCTUITextView+Markdown.h +15 -0
- package/ios/RCTUITextView+Markdown.mm +42 -0
- package/lib/commonjs/MarkdownTextInput.js +103 -0
- package/lib/commonjs/MarkdownTextInput.js.map +1 -0
- package/lib/commonjs/MarkdownTextInput.web.js +21 -0
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
- package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +10 -0
- package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/MarkdownTextInput.js +96 -0
- package/lib/module/MarkdownTextInput.js.map +1 -0
- package/lib/module/MarkdownTextInput.web.js +15 -0
- package/lib/module/MarkdownTextInput.web.js.map +1 -0
- package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -0
- package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/MarkdownTextInput.d.ts +15 -0
- package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -0
- package/lib/typescript/src/MarkdownTextInput.web.d.ts +9 -0
- package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -0
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts +46 -0
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +159 -0
- package/parser/react-native-live-markdown-parser.js +34 -0
- package/src/MarkdownTextInput.tsx +129 -0
- package/src/MarkdownTextInput.web.tsx +24 -0
- package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +48 -0
- package/src/index.tsx +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Expensify, Inc.
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<img src="./assets/hero-animation.gif" alt="@expensify/react-native-live-markdown" />
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
- ⚛️ Drop-in replacement for `<TextInput>` component
|
|
6
|
+
- ⌨️ Live synchronous formatting on every keystroke
|
|
7
|
+
- ⚡ Fully native experience (selection, spellcheck, autocomplete)
|
|
8
|
+
- 🎨 Customizable styles
|
|
9
|
+
- 🌐 Universal support (Android, iOS, web)
|
|
10
|
+
- 🏗️ Supports New Architecture
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
First, install the library from npm with the package manager of your choice:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
yarn add @expensify/react-native-live-markdown
|
|
18
|
+
npm install @expensify/react-native-live-markdown --save
|
|
19
|
+
npx expo install @expensify/react-native-live-markdown
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then, install the iOS dependencies with CocoaPods:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
cd ios && pod install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The library includes native code so you will need to re-build the native app.
|
|
29
|
+
|
|
30
|
+
> [!NOTE]
|
|
31
|
+
> The library does not support Expo Go, you will need to setup Expo Dev Client (see [here](https://docs.expo.dev/workflow/prebuild/)).
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import {MarkdownTextInput} from '@expensify/react-native-live-markdown';
|
|
37
|
+
import React from 'react';
|
|
38
|
+
|
|
39
|
+
export default function App() {
|
|
40
|
+
const [text, setText] = React.useState('Hello, *world*!');
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<MarkdownTextInput
|
|
44
|
+
value={text}
|
|
45
|
+
onChangeText={setText}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Styling
|
|
52
|
+
|
|
53
|
+
`MarkdownTextInput` can be styled using `style` prop just like regular `TextInput` component.
|
|
54
|
+
|
|
55
|
+
It is also possible to customize the styling of the formatted contents of `MarkdownTextInput` component. The style object supports all color representations from React Native including `PlatformColor` and `DynamicColorIOS` according to the [color reference](https://reactnative.dev/docs/colors). Currently, a limited set of styles is customizable but this is subject to change in the future.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import type {MarkdownStyle} from '@expensify/react-native-live-markdown';
|
|
59
|
+
|
|
60
|
+
const markdownStyle: MarkdownStyle = {
|
|
61
|
+
syntax: {
|
|
62
|
+
color: 'gray',
|
|
63
|
+
},
|
|
64
|
+
link: {
|
|
65
|
+
color: 'blue',
|
|
66
|
+
},
|
|
67
|
+
h1: {
|
|
68
|
+
fontSize: 25,
|
|
69
|
+
},
|
|
70
|
+
blockquote: {
|
|
71
|
+
borderColor: 'gray',
|
|
72
|
+
borderWidth: 6,
|
|
73
|
+
marginLeft: 6,
|
|
74
|
+
paddingLeft: 6,
|
|
75
|
+
},
|
|
76
|
+
code: {
|
|
77
|
+
fontFamily: 'monospace',
|
|
78
|
+
color: 'black',
|
|
79
|
+
backgroundColor: 'lightgray',
|
|
80
|
+
},
|
|
81
|
+
pre: {
|
|
82
|
+
fontFamily: 'monospace',
|
|
83
|
+
color: 'black',
|
|
84
|
+
backgroundColor: 'lightgray',
|
|
85
|
+
},
|
|
86
|
+
mentionHere: {
|
|
87
|
+
color: 'green',
|
|
88
|
+
backgroundColor: 'lime',
|
|
89
|
+
},
|
|
90
|
+
mentionUser: {
|
|
91
|
+
color: 'blue',
|
|
92
|
+
backgroundColor: 'cyan',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The style object can be passed to multiple `MarkdownTextInput` components using `markdownStyle` prop:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<MarkdownTextInput
|
|
101
|
+
value={text}
|
|
102
|
+
onChangeText={setText}
|
|
103
|
+
style={styles.input}
|
|
104
|
+
markdownStyle={markdownStyle}
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> [!TIP]
|
|
109
|
+
> We recommend to store the style object outside of a component body or memoize the style object with `React.useMemo`.
|
|
110
|
+
|
|
111
|
+
## Markdown flavors support
|
|
112
|
+
|
|
113
|
+
Currently, `react-native-live-markdown` supports only [ExpensiMark](https://github.com/Expensify/expensify-common/blob/main/lib/ExpensiMark.js) flavor. We are working on CommonMark support as well as possibility to use other Markdown parsers.
|
|
114
|
+
|
|
115
|
+
## API reference
|
|
116
|
+
|
|
117
|
+
`MarkdownTextInput` inherits all props of React Native's `TextInput` component as well as introduces the following properties:
|
|
118
|
+
|
|
119
|
+
| Prop | Type | Default | Note |
|
|
120
|
+
| --------------- | --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `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. |
|
|
122
|
+
|
|
123
|
+
## Compatibility
|
|
124
|
+
|
|
125
|
+
`react-native-live-markdown` requires React Native 0.71 or newer.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
<p align="center">
|
|
134
|
+
<picture>
|
|
135
|
+
<source media="(prefers-color-scheme: light)" srcset="./assets/signature-light.png" />
|
|
136
|
+
<source media="(prefers-color-scheme: dark)" srcset="./assets/signature-dark.png" />
|
|
137
|
+
<img alt="Brought to you by Software Mansion + Expensify" src="./assets/signature-light.png" width="600" />
|
|
138
|
+
</picture>
|
|
139
|
+
</p>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
|
5
|
+
|
|
6
|
+
Pod::Spec.new do |s|
|
|
7
|
+
s.name = "RNLiveMarkdown"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => "11.0" }
|
|
15
|
+
s.source = { :git => "https://github.com/expensify/react-native-live-markdown.git", :tag => "#{s.version}" }
|
|
16
|
+
|
|
17
|
+
s.source_files = "ios/**/*.{h,m,mm}"
|
|
18
|
+
|
|
19
|
+
s.resources = "parser/react-native-live-markdown-parser.js"
|
|
20
|
+
|
|
21
|
+
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
22
|
+
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
|
23
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
24
|
+
install_modules_dependencies(s)
|
|
25
|
+
else
|
|
26
|
+
s.dependency "React-Core"
|
|
27
|
+
|
|
28
|
+
# Don't install the dependencies when we run `pod install` in the old architecture.
|
|
29
|
+
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
30
|
+
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
31
|
+
s.pod_target_xcconfig = {
|
|
32
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
33
|
+
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
34
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
35
|
+
}
|
|
36
|
+
s.dependency "React-RCTFabric"
|
|
37
|
+
s.dependency "React-Codegen"
|
|
38
|
+
s.dependency "RCT-Folly"
|
|
39
|
+
s.dependency "RCTRequired"
|
|
40
|
+
s.dependency "RCTTypeSafety"
|
|
41
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def isNewArchitectureEnabled() {
|
|
13
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply plugin: "com.android.library"
|
|
17
|
+
|
|
18
|
+
if (isNewArchitectureEnabled()) {
|
|
19
|
+
apply plugin: "com.facebook.react"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def getExtOrDefault(name) {
|
|
23
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["LiveMarkdown_" + name]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def getExtOrIntegerDefault(name) {
|
|
27
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["LiveMarkdown_" + name]).toInteger()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def supportsNamespace() {
|
|
31
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
32
|
+
def major = parsed[0].toInteger()
|
|
33
|
+
def minor = parsed[1].toInteger()
|
|
34
|
+
|
|
35
|
+
// Namespace support was added in 7.3.0
|
|
36
|
+
return (major == 7 && minor >= 3) || major >= 8
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
android {
|
|
40
|
+
if (supportsNamespace()) {
|
|
41
|
+
namespace "com.expensify.livemarkdown"
|
|
42
|
+
|
|
43
|
+
sourceSets {
|
|
44
|
+
main {
|
|
45
|
+
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
51
|
+
|
|
52
|
+
defaultConfig {
|
|
53
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
54
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
55
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
56
|
+
externalNativeBuild {
|
|
57
|
+
cmake {
|
|
58
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_TOOLCHAIN=clang"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
buildFeatures {
|
|
64
|
+
buildConfig true
|
|
65
|
+
prefab true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
buildTypes {
|
|
69
|
+
release {
|
|
70
|
+
minifyEnabled false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lintOptions {
|
|
75
|
+
disable "GradleCompatible"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
compileOptions {
|
|
79
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
80
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
sourceSets {
|
|
84
|
+
main {
|
|
85
|
+
if (isNewArchitectureEnabled()) {
|
|
86
|
+
java.srcDirs += [
|
|
87
|
+
"src/newarch",
|
|
88
|
+
// This is needed to build Kotlin project with NewArch enabled
|
|
89
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
90
|
+
]
|
|
91
|
+
} else {
|
|
92
|
+
java.srcDirs += ["src/oldarch"]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
externalNativeBuild {
|
|
98
|
+
cmake {
|
|
99
|
+
path file('src/main/cpp/CMakeLists.txt')
|
|
100
|
+
version '3.22.1'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
packagingOptions {
|
|
105
|
+
doNotStrip '**/**/*.so'
|
|
106
|
+
excludes = [
|
|
107
|
+
"META-INF",
|
|
108
|
+
"META-INF/**",
|
|
109
|
+
"**/libc++_shared.so",
|
|
110
|
+
"**/libfbjni.so",
|
|
111
|
+
"**/libjsi.so",
|
|
112
|
+
"**/libreactnativejni.so",
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
repositories {
|
|
118
|
+
mavenCentral()
|
|
119
|
+
google()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
dependencies {
|
|
124
|
+
implementation "com.facebook.react:react-android" // version substituted by RNGP
|
|
125
|
+
implementation "com.facebook.react:hermes-android" // version substituted by RNGP
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (isNewArchitectureEnabled()) {
|
|
129
|
+
react {
|
|
130
|
+
jsRootDir = file("../src/")
|
|
131
|
+
libraryName = "LiveMarkdown"
|
|
132
|
+
codegenJavaPackageName = "com.expensify.livemarkdown"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
task copyJS(type: Copy) {
|
|
137
|
+
from '../parser/react-native-live-markdown-parser.js'
|
|
138
|
+
into 'src/main/assets'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
tasks.preBuild {
|
|
142
|
+
dependsOn copyJS
|
|
143
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
project("livemarkdown")
|
|
2
|
+
|
|
3
|
+
cmake_minimum_required(VERSION 3.13)
|
|
4
|
+
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE on)
|
|
6
|
+
|
|
7
|
+
add_compile_options(-fvisibility=hidden -fexceptions -frtti)
|
|
8
|
+
|
|
9
|
+
file(GLOB livemarkdown_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
|
10
|
+
|
|
11
|
+
add_library(${CMAKE_PROJECT_NAME} SHARED ${livemarkdown_SRC})
|
|
12
|
+
|
|
13
|
+
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
|
14
|
+
|
|
15
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
16
|
+
find_package(ReactAndroid REQUIRED CONFIG)
|
|
17
|
+
find_package(hermes-engine REQUIRED CONFIG)
|
|
18
|
+
|
|
19
|
+
target_link_libraries(${CMAKE_PROJECT_NAME}
|
|
20
|
+
fbjni::fbjni
|
|
21
|
+
ReactAndroid::jsi
|
|
22
|
+
ReactAndroid::reactnativejni
|
|
23
|
+
hermes-engine::libhermes)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#include "MarkdownUtils.h"
|
|
2
|
+
|
|
3
|
+
#include <fbjni/fbjni.h>
|
|
4
|
+
#include <hermes/hermes.h>
|
|
5
|
+
|
|
6
|
+
using namespace facebook;
|
|
7
|
+
|
|
8
|
+
namespace expensify {
|
|
9
|
+
namespace livemarkdown {
|
|
10
|
+
std::shared_ptr<jsi::Runtime> MarkdownUtils::runtime_;
|
|
11
|
+
|
|
12
|
+
void MarkdownUtils::nativeInitializeRuntime(
|
|
13
|
+
jni::alias_ref<jhybridobject> jThis,
|
|
14
|
+
jni::alias_ref<jni::JString> code) {
|
|
15
|
+
assert(runtime_ == nullptr && "Markdown runtime is already initialized");
|
|
16
|
+
runtime_ = facebook::hermes::makeHermesRuntime();
|
|
17
|
+
auto codeBuffer = std::make_shared<const jsi::StringBuffer>(code->toStdString());
|
|
18
|
+
runtime_->evaluateJavaScript(codeBuffer, "nativeInitializeRuntime");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
jni::local_ref<jni::JString> MarkdownUtils::nativeParseMarkdown(
|
|
22
|
+
jni::alias_ref<jhybridobject> jThis,
|
|
23
|
+
jni::alias_ref<jni::JString> input) {
|
|
24
|
+
jsi::Runtime &rt = *runtime_;
|
|
25
|
+
auto func = rt.global().getPropertyAsFunction(rt, "parseExpensiMarkToRanges");
|
|
26
|
+
auto arg = input->toStdString();
|
|
27
|
+
jsi::Value result;
|
|
28
|
+
try {
|
|
29
|
+
result = func.call(rt, arg);
|
|
30
|
+
} catch (jsi::JSError e) {
|
|
31
|
+
result = jsi::Array(rt, 0);
|
|
32
|
+
}
|
|
33
|
+
auto json = rt.global().getPropertyAsObject(rt, "JSON").getPropertyAsFunction(rt, "stringify").call(rt, result).asString(rt).utf8(rt);
|
|
34
|
+
return jni::make_jstring(json);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void MarkdownUtils::registerNatives() {
|
|
38
|
+
registerHybrid({
|
|
39
|
+
makeNativeMethod("nativeInitializeRuntime", MarkdownUtils::nativeInitializeRuntime),
|
|
40
|
+
makeNativeMethod("nativeParseMarkdown", MarkdownUtils::nativeParseMarkdown)});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
} // namespace livemarkdown
|
|
44
|
+
} // namespace expensify
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <fbjni/fbjni.h>
|
|
11
|
+
#include <jsi/jsi.h>
|
|
12
|
+
|
|
13
|
+
using namespace facebook;
|
|
14
|
+
|
|
15
|
+
namespace expensify {
|
|
16
|
+
namespace livemarkdown {
|
|
17
|
+
|
|
18
|
+
class MarkdownUtils : public jni::HybridClass<MarkdownUtils>,
|
|
19
|
+
public jsi::HostObject {
|
|
20
|
+
public:
|
|
21
|
+
static constexpr auto kJavaDescriptor =
|
|
22
|
+
"Lcom/expensify/livemarkdown/MarkdownUtils;";
|
|
23
|
+
|
|
24
|
+
static void nativeInitializeRuntime(
|
|
25
|
+
jni::alias_ref<jhybridobject> jThis,
|
|
26
|
+
jni::alias_ref<jni::JString> code);
|
|
27
|
+
|
|
28
|
+
static jni::local_ref<jni::JString> nativeParseMarkdown(
|
|
29
|
+
jni::alias_ref<jhybridobject> jThis,
|
|
30
|
+
jni::alias_ref<jni::JString> input);
|
|
31
|
+
|
|
32
|
+
static void registerNatives();
|
|
33
|
+
|
|
34
|
+
private:
|
|
35
|
+
static std::shared_ptr<jsi::Runtime> runtime_;
|
|
36
|
+
|
|
37
|
+
friend HybridBase;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
} // namespace livemarkdown
|
|
41
|
+
} // namespace expensify
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage;
|
|
4
|
+
import com.facebook.react.bridge.NativeModule;
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
7
|
+
|
|
8
|
+
import java.util.ArrayList;
|
|
9
|
+
import java.util.Collections;
|
|
10
|
+
import java.util.List;
|
|
11
|
+
|
|
12
|
+
public class LiveMarkdownPackage implements ReactPackage {
|
|
13
|
+
@Override
|
|
14
|
+
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
15
|
+
List<ViewManager> viewManagers = new ArrayList<>();
|
|
16
|
+
viewManagers.add(new MarkdownTextInputDecoratorViewManager());
|
|
17
|
+
return viewManagers;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
22
|
+
return Collections.emptyList();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import android.text.style.BackgroundColorSpan;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.ColorInt;
|
|
6
|
+
|
|
7
|
+
public class MarkdownBackgroundColorSpan extends BackgroundColorSpan implements MarkdownSpan {
|
|
8
|
+
public MarkdownBackgroundColorSpan(@ColorInt int color) {
|
|
9
|
+
super(color);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas;
|
|
4
|
+
import android.graphics.Paint;
|
|
5
|
+
import android.text.Layout;
|
|
6
|
+
import android.text.style.LeadingMarginSpan;
|
|
7
|
+
|
|
8
|
+
import androidx.annotation.ColorInt;
|
|
9
|
+
|
|
10
|
+
import com.facebook.react.uimanager.PixelUtil;
|
|
11
|
+
|
|
12
|
+
public class MarkdownBlockquoteSpan implements MarkdownSpan, LeadingMarginSpan {
|
|
13
|
+
@ColorInt
|
|
14
|
+
private final int borderColor;
|
|
15
|
+
private final float borderWidth;
|
|
16
|
+
private final float marginLeft;
|
|
17
|
+
private final float paddingLeft;
|
|
18
|
+
|
|
19
|
+
public MarkdownBlockquoteSpan(@ColorInt int borderColor, float borderWidth, float marginLeft, float paddingLeft) {
|
|
20
|
+
this.borderColor = borderColor;
|
|
21
|
+
this.borderWidth = PixelUtil.toPixelFromDIP(borderWidth);
|
|
22
|
+
this.marginLeft = PixelUtil.toPixelFromDIP(marginLeft);
|
|
23
|
+
this.paddingLeft = PixelUtil.toPixelFromDIP(paddingLeft);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Override
|
|
27
|
+
public int getLeadingMargin(boolean first) {
|
|
28
|
+
return (int) (marginLeft + borderWidth + paddingLeft);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Override
|
|
32
|
+
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
|
|
33
|
+
CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
34
|
+
Paint.Style originalStyle = p.getStyle();
|
|
35
|
+
int originalColor = p.getColor();
|
|
36
|
+
|
|
37
|
+
p.setStyle(Paint.Style.FILL);
|
|
38
|
+
p.setColor(borderColor);
|
|
39
|
+
|
|
40
|
+
float left = x + dir * marginLeft;
|
|
41
|
+
float right = x + dir * (marginLeft + borderWidth);
|
|
42
|
+
c.drawRect(left, top, right, bottom, p);
|
|
43
|
+
|
|
44
|
+
p.setStyle(originalStyle);
|
|
45
|
+
p.setColor(originalColor);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import android.content.res.AssetManager;
|
|
4
|
+
import android.graphics.Paint;
|
|
5
|
+
import android.graphics.Typeface;
|
|
6
|
+
import android.text.TextPaint;
|
|
7
|
+
import android.text.style.MetricAffectingSpan;
|
|
8
|
+
|
|
9
|
+
import androidx.annotation.NonNull;
|
|
10
|
+
|
|
11
|
+
import com.facebook.react.views.text.ReactFontManager;
|
|
12
|
+
|
|
13
|
+
public class MarkdownFontFamilySpan extends MetricAffectingSpan implements MarkdownSpan {
|
|
14
|
+
|
|
15
|
+
private final @NonNull String mFontFamily;
|
|
16
|
+
private final @NonNull AssetManager mAssetManager;
|
|
17
|
+
|
|
18
|
+
public MarkdownFontFamilySpan(@NonNull String fontFamily, @NonNull AssetManager assetManager) {
|
|
19
|
+
mFontFamily = fontFamily;
|
|
20
|
+
mAssetManager = assetManager;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Override
|
|
24
|
+
public void updateMeasureState(@NonNull TextPaint textPaint) {
|
|
25
|
+
apply(textPaint);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
public void updateDrawState(TextPaint tp) {
|
|
30
|
+
apply(tp);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private void apply(@NonNull TextPaint textPaint) {
|
|
34
|
+
int style = textPaint.getTypeface().getStyle();
|
|
35
|
+
Typeface typeface = ReactFontManager.getInstance().getTypeface(mFontFamily, style, mAssetManager);
|
|
36
|
+
textPaint.setTypeface(typeface);
|
|
37
|
+
textPaint.setFlags(textPaint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import android.text.style.ForegroundColorSpan;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.ColorInt;
|
|
6
|
+
|
|
7
|
+
public class MarkdownForegroundColorSpan extends ForegroundColorSpan implements MarkdownSpan {
|
|
8
|
+
public MarkdownForegroundColorSpan(@ColorInt int color) {
|
|
9
|
+
super(color);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Paint;
|
|
4
|
+
import android.text.style.LineHeightSpan;
|
|
5
|
+
|
|
6
|
+
public class MarkdownLineHeightSpan implements MarkdownSpan, LineHeightSpan {
|
|
7
|
+
private final float mLineHeight;
|
|
8
|
+
|
|
9
|
+
public MarkdownLineHeightSpan(float lineHeight) {
|
|
10
|
+
mLineHeight = lineHeight;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Override
|
|
14
|
+
public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int lineHeight, Paint.FontMetricsInt fm) {
|
|
15
|
+
fm.top -= mLineHeight / 4;
|
|
16
|
+
fm.ascent -= mLineHeight / 4;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package com.expensify.livemarkdown;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Enables us to distinguish between spans that were added by Live Markdown and spans that were
|
|
5
|
+
* added by something else. All spans that Live Markdown adds should implement this interface.
|
|
6
|
+
*/
|
|
7
|
+
public interface MarkdownSpan {}
|