@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.
Files changed (80) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +139 -0
  3. package/RNLiveMarkdown.podspec +44 -0
  4. package/android/build.gradle +143 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/cpp/CMakeLists.txt +23 -0
  9. package/android/src/main/cpp/MarkdownUtils.cpp +44 -0
  10. package/android/src/main/cpp/MarkdownUtils.h +41 -0
  11. package/android/src/main/cpp/OnLoad.cpp +8 -0
  12. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +24 -0
  13. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java +11 -0
  14. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java +47 -0
  15. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java +10 -0
  16. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +39 -0
  17. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +9 -0
  18. package/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java +11 -0
  19. package/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java +10 -0
  20. package/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java +18 -0
  21. package/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java +7 -0
  22. package/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java +5 -0
  23. package/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java +186 -0
  24. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorView.java +85 -0
  25. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorViewManager.java +31 -0
  26. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java +38 -0
  27. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java +5 -0
  28. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +176 -0
  29. package/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java +24 -0
  30. package/android/src/oldarch/MarkdownTextInputDecoratorViewManagerSpec.java +10 -0
  31. package/ios/MarkdownLayoutManager.h +12 -0
  32. package/ios/MarkdownLayoutManager.mm +35 -0
  33. package/ios/MarkdownTextInputDecoratorComponentView.h +12 -0
  34. package/ios/MarkdownTextInputDecoratorComponentView.mm +55 -0
  35. package/ios/MarkdownTextInputDecoratorView.h +12 -0
  36. package/ios/MarkdownTextInputDecoratorView.mm +106 -0
  37. package/ios/MarkdownTextInputDecoratorViewManager.h +8 -0
  38. package/ios/MarkdownTextInputDecoratorViewManager.mm +23 -0
  39. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.h +14 -0
  40. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +43 -0
  41. package/ios/RCTBaseTextInputView+Markdown.h +16 -0
  42. package/ios/RCTBaseTextInputView+Markdown.m +66 -0
  43. package/ios/RCTMarkdownStyle.h +35 -0
  44. package/ios/RCTMarkdownStyle.mm +81 -0
  45. package/ios/RCTMarkdownUtils.h +18 -0
  46. package/ios/RCTMarkdownUtils.mm +134 -0
  47. package/ios/RCTTextInputComponentView+Markdown.h +21 -0
  48. package/ios/RCTTextInputComponentView+Markdown.mm +51 -0
  49. package/ios/RCTUITextView+Markdown.h +15 -0
  50. package/ios/RCTUITextView+Markdown.mm +42 -0
  51. package/lib/commonjs/MarkdownTextInput.js +103 -0
  52. package/lib/commonjs/MarkdownTextInput.js.map +1 -0
  53. package/lib/commonjs/MarkdownTextInput.web.js +21 -0
  54. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
  55. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +10 -0
  56. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
  57. package/lib/commonjs/index.js +14 -0
  58. package/lib/commonjs/index.js.map +1 -0
  59. package/lib/module/MarkdownTextInput.js +96 -0
  60. package/lib/module/MarkdownTextInput.js.map +1 -0
  61. package/lib/module/MarkdownTextInput.web.js +15 -0
  62. package/lib/module/MarkdownTextInput.web.js.map +1 -0
  63. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -0
  64. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
  65. package/lib/module/index.js +2 -0
  66. package/lib/module/index.js.map +1 -0
  67. package/lib/typescript/src/MarkdownTextInput.d.ts +15 -0
  68. package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -0
  69. package/lib/typescript/src/MarkdownTextInput.web.d.ts +9 -0
  70. package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -0
  71. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts +46 -0
  72. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  73. package/lib/typescript/src/index.d.ts +3 -0
  74. package/lib/typescript/src/index.d.ts.map +1 -0
  75. package/package.json +159 -0
  76. package/parser/react-native-live-markdown-parser.js +34 -0
  77. package/src/MarkdownTextInput.tsx +129 -0
  78. package/src/MarkdownTextInput.web.tsx +24 -0
  79. package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +48 -0
  80. 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,5 @@
1
+ LiveMarkdown_kotlinVersion=1.7.0
2
+ LiveMarkdown_minSdkVersion=21
3
+ LiveMarkdown_targetSdkVersion=31
4
+ LiveMarkdown_compileSdkVersion=31
5
+ LiveMarkdown_ndkversion=21.4.7075529
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.expensify.livemarkdown">
3
+ </manifest>
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -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,8 @@
1
+ #include <fbjni/fbjni.h>
2
+
3
+ #include "MarkdownUtils.h"
4
+
5
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
6
+ return facebook::jni::initialize(
7
+ vm, [] { expensify::livemarkdown::MarkdownUtils::registerNatives(); });
8
+ }
@@ -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,10 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.graphics.Typeface;
4
+ import android.text.style.StyleSpan;
5
+
6
+ public class MarkdownBoldSpan extends StyleSpan implements MarkdownSpan {
7
+ public MarkdownBoldSpan() {
8
+ super(Typeface.BOLD);
9
+ }
10
+ }
@@ -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,9 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.text.style.AbsoluteSizeSpan;
4
+
5
+ public class MarkdownFontSizeSpan extends AbsoluteSizeSpan implements MarkdownSpan {
6
+ public MarkdownFontSizeSpan(float fontSize) {
7
+ super((int) fontSize, true);
8
+ }
9
+ }
@@ -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,10 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.graphics.Typeface;
4
+ import android.text.style.StyleSpan;
5
+
6
+ public class MarkdownItalicSpan extends StyleSpan implements MarkdownSpan {
7
+ public MarkdownItalicSpan() {
8
+ super(Typeface.ITALIC);
9
+ }
10
+ }
@@ -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 {}