diff --git a/.eslintignore b/.eslintignore index f868bfeb..45ef798e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,7 +3,7 @@ parser/react-native-live-markdown-parser.js # any js file inside android and ios folders **/android/**/*.js -**/ios/**/*.js +**/apple/**/*.js # Output of the build process & scripts lib/**/* diff --git a/.github/OSBotify-private-key.asc.gpg b/.github/OSBotify-private-key.asc.gpg index c19d5c97..03f06222 100644 Binary files a/.github/OSBotify-private-key.asc.gpg and b/.github/OSBotify-private-key.asc.gpg differ diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 41158c7b..2b820cc8 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -4,7 +4,7 @@ on: paths: - .github/workflows/build-ios.yml - RNLiveMarkdown.podspec - - ios/** + - apple/** - cpp/** - example/package.json - example/ios/** @@ -17,7 +17,7 @@ on: paths: - .github/workflows/build-ios.yml - RNLiveMarkdown.podspec - - ios/** + - apple/** - cpp/** - example/package.json - example/ios/** diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 20fa2316..43124188 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: - name: Set up git for OSBotify run: | - git config --global user.signingkey 367811D53E34168C + git config --global user.signingkey AEE1036472A782AB git config --global commit.gpgsign true git config --global user.name OSBotify git config --global user.email infra+osbotify@expensify.com diff --git a/.gitignore b/.gitignore index d9ceaf0b..9ede3eca 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,6 @@ lib/ # react-native-live-markdown android/src/main/assets/react-native-live-markdown-parser.js .build_complete + +# GitHub GPG Keys +.github/OSBotify-private-key.asc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e718b19b..7611f1f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -149,3 +149,16 @@ When you're sending a pull request: - Review the documentation to make sure it looks good. - Follow the pull request template when opening a pull request. - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. + +### Testing with Expensify/App (or other projects) +It's possible to locally develop this repo such with live-reload in another React Native project. These instructions are for Expensify/App, but they can be adapted to other repos as well. + +1. Clone this repo +2. Run `yarn install` +3. Run `yarn build:watch` +4. In Expensify/App, run `npm install`. + - _Note:_ There is a patch for the `link` dev dependency in this repo. If you want these steps to work reliably, you'll likely need to copy that patch over. +5. In Expensify/App, run `npx link publish --watch ~/react-native-live-markdown --litmus .build_complete` +6. In E/App, run the app with `npm run web`/`npm run ios`/etc... + +The end result should be that you can make a change directly in this repo, and your changes will live-reload in E/App. diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index f2601e76..c70a0608 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -18,7 +18,7 @@ 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 = "ios/**/*.{h,m,mm}" + s.source_files = "apple/**/*.{h,m,mm}" s.resources = "parser/react-native-live-markdown-parser.js" @@ -37,8 +37,10 @@ Pod::Spec.new do |s| ]) end - s.subspec "common" do |ss| - ss.source_files = "cpp/**/*.{cpp,h}" - ss.header_dir = "RNLiveMarkdown" + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' + s.subspec "newarch" do |ss| + ss.source_files = "cpp/**/*.{cpp,h}" + ss.header_dir = "RNLiveMarkdown" + end end end diff --git a/WebExample/__tests__/styles.spec.ts b/WebExample/__tests__/styles.spec.ts index 665957f1..f077423d 100644 --- a/WebExample/__tests__/styles.spec.ts +++ b/WebExample/__tests__/styles.spec.ts @@ -52,7 +52,7 @@ test.describe('markdown content styling', () => { test('blockquote', async ({page, browserName}) => { const blockquoteStyle = - 'border-color: gray; border-width: 6px; margin-left: 6px; padding-left: 6px; border-left-style: solid; display: inline-block; max-width: 100%; box-sizing: border-box;'; + 'border-color: gray; border-width: 6px; margin-left: 6px; padding-left: 6px; border-left-style: solid; display: inline-block; max-width: 100%; box-sizing: border-box; overflow-wrap: anywhere;'; // Firefox border properties are serialized slightly differently const browserStyle = browserName === 'firefox' ? blockquoteStyle.replace('border-left-style: solid', 'border-left: 6px solid gray') : blockquoteStyle; diff --git a/android/build.gradle b/android/build.gradle index 5eb2578e..5aff2c79 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -191,3 +191,7 @@ task copyJS(type: Copy) { tasks.preBuild { dependsOn copyJS } + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << "-Xlint:all" << "-Werror" +} diff --git a/android/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index 8d3a3cc0..b314c4ab 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -18,18 +18,17 @@ find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) find_package(hermes-engine REQUIRED CONFIG) +target_link_libraries( + ${CMAKE_PROJECT_NAME} + fbjni::fbjni + hermes-engine::libhermes + ReactAndroid::jsi +) + if (ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) - target_link_libraries(${CMAKE_PROJECT_NAME} - fbjni::fbjni - ReactAndroid::jsi - ReactAndroid::reactnative - hermes-engine::libhermes) + target_link_libraries(${CMAKE_PROJECT_NAME} ReactAndroid::reactnative) elseif (ReactAndroid_VERSION_MINOR GREATER_EQUAL 75) - target_link_libraries(${CMAKE_PROJECT_NAME} - fbjni::fbjni - ReactAndroid::jsi - ReactAndroid::reactnativejni - hermes-engine::libhermes) + target_link_libraries(${CMAKE_PROJECT_NAME} ReactAndroid::reactnativejni) else () message(FATAL_ERROR "react-native-live-markdown requires react-native 0.75 or newer.") endif () diff --git a/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java b/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java index c55b6c5c..efabbe87 100644 --- a/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java +++ b/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java @@ -35,6 +35,7 @@ public static FabricUIManager create(FabricUIManager source, ReadableMap markdow } } + @SuppressWarnings("unchecked") private static T readPrivateField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException { Class clazz = obj.getClass(); diff --git a/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java b/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java index 9f55fc87..ecea5222 100644 --- a/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +++ b/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java @@ -17,8 +17,10 @@ import java.util.Map; public class LiveMarkdownPackage extends TurboReactPackage { + @NonNull @Override - public List createViewManagers(ReactApplicationContext reactContext) { + @SuppressWarnings("rawtypes") + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { List viewManagers = new ArrayList<>(); viewManagers.add(new MarkdownTextInputDecoratorViewManager()); return viewManagers; @@ -27,6 +29,7 @@ public List createViewManagers(ReactApplicationContext reactContext @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { return new ReactModuleInfoProvider() { + @NonNull @Override public Map getReactModuleInfos() { return Map.of(LiveMarkdownModule.NAME, new ReactModuleInfo( diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java b/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java index b71f80eb..e51aa4b4 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +++ b/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; +import com.expensify.livemarkdown.spans.*; import com.facebook.react.views.text.internal.span.CustomLineHeightSpan; import com.facebook.soloader.SoLoader; @@ -164,8 +165,6 @@ private void applyRange(SpannableStringBuilder ssb, String type, int start, int depth); setSpan(ssb, span, start, end); break; - default: - throw new IllegalStateException("Unsupported type: " + type); } } diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBackgroundColorSpan.java similarity index 86% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBackgroundColorSpan.java index 4a4ce1d1..deb7dea1 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBackgroundColorSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.BackgroundColorSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBlockquoteSpan.java similarity index 97% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBlockquoteSpan.java index 058c1564..5614c194 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBlockquoteSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.graphics.Canvas; import android.graphics.Paint; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBoldSpan.java similarity index 83% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBoldSpan.java index cd2b07fc..581d813f 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownBoldSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.graphics.Typeface; import android.text.style.StyleSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownEmojiSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownEmojiSpan.java similarity index 83% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownEmojiSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownEmojiSpan.java index 80f71e35..7655bc70 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownEmojiSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownEmojiSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.AbsoluteSizeSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontFamilySpan.java similarity index 74% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontFamilySpan.java index b9e71dd0..0f796d9d 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontFamilySpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.content.res.AssetManager; import android.graphics.Paint; @@ -8,8 +8,7 @@ import androidx.annotation.NonNull; -import com.facebook.react.common.assets.ReactFontManager.TypefaceStyle; -import com.facebook.react.views.text.ReactFontManager; +import com.facebook.react.common.assets.ReactFontManager; public class MarkdownFontFamilySpan extends MetricAffectingSpan implements MarkdownSpan { @@ -32,12 +31,7 @@ public void updateDrawState(TextPaint tp) { } private void apply(@NonNull TextPaint textPaint) { - int style = TypefaceStyle.NORMAL; - if (textPaint.getTypeface() != null) { - style = textPaint.getTypeface().getStyle(); - } else { - style = TypefaceStyle.NORMAL; - } + int style = textPaint.getTypeface() != null ? textPaint.getTypeface().getStyle() : ReactFontManager.TypefaceStyle.NORMAL; Typeface typeface = ReactFontManager.getInstance().getTypeface(mFontFamily, style, mAssetManager); textPaint.setTypeface(typeface); textPaint.setFlags(textPaint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontSizeSpan.java similarity index 87% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontSizeSpan.java index 25d4dadc..e044b92a 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownFontSizeSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.AbsoluteSizeSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownForegroundColorSpan.java similarity index 86% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownForegroundColorSpan.java index 35228222..3f3a5e82 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownForegroundColorSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.ForegroundColorSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownItalicSpan.java similarity index 83% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownItalicSpan.java index 7e1ce058..2cb9c016 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownItalicSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.graphics.Typeface; import android.text.style.StyleSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java similarity index 92% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java index ca660f37..3f4c33fc 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownLineHeightSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.graphics.Paint; import android.text.style.LineHeightSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownSpan.java similarity index 84% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownSpan.java index d6cea3e5..32710b7e 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; /* * Enables us to distinguish between spans that were added by Live Markdown and spans that were diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownStrikethroughSpan.java similarity index 76% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownStrikethroughSpan.java index 63a07a38..e7833fb8 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownStrikethroughSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.StrikethroughSpan; diff --git a/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownUnderlineSpan.java similarity index 75% rename from android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java rename to android/src/main/java/com/expensify/livemarkdown/spans/MarkdownUnderlineSpan.java index 08fcbe4f..c3e66183 100644 --- a/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java +++ b/android/src/main/java/com/expensify/livemarkdown/spans/MarkdownUnderlineSpan.java @@ -1,4 +1,4 @@ -package com.expensify.livemarkdown; +package com.expensify.livemarkdown.spans; import android.text.style.UnderlineSpan; diff --git a/android/src/main/new_arch/CMakeLists.txt b/android/src/main/new_arch/CMakeLists.txt index 53d5cc44..2b506f3f 100644 --- a/android/src/main/new_arch/CMakeLists.txt +++ b/android/src/main/new_arch/CMakeLists.txt @@ -18,84 +18,76 @@ file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_COMPONENTS_ set(RN_DIR ${LIB_ANDROID_DIR}/../example/node_modules/react-native) add_library( - ${LIB_TARGET_NAME} - SHARED - ${LIB_MODULE_SRCS} - ${LIB_CUSTOM_SRCS} - ${LIB_CODEGEN_SRCS} + ${LIB_TARGET_NAME} + SHARED + ${LIB_MODULE_SRCS} + ${LIB_CUSTOM_SRCS} + ${LIB_CODEGEN_SRCS} ) target_include_directories( - ${LIB_TARGET_NAME} - PUBLIC - . - ${LIB_ANDROID_GENERATED_JNI_DIR} - ${LIB_ANDROID_GENERATED_COMPONENTS_DIR} - ${LIB_CPP_DIR} + ${LIB_TARGET_NAME} + PUBLIC + . + ${LIB_ANDROID_GENERATED_JNI_DIR} + ${LIB_ANDROID_GENERATED_COMPONENTS_DIR} + ${LIB_CPP_DIR} ) +find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) +target_link_libraries( + ${LIB_TARGET_NAME} + fbjni::fbjni + ReactAndroid::jsi +) + if (ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) target_link_libraries( - ${LIB_TARGET_NAME} - ReactAndroid::reactnative - ReactAndroid::jsi - fbjni::fbjni + ${LIB_TARGET_NAME} + ReactAndroid::reactnative ) elseif (ReactAndroid_VERSION_MINOR GREATER_EQUAL 75) target_link_libraries( - ${LIB_TARGET_NAME} - ReactAndroid::rrc_text - ReactAndroid::rrc_textinput - ReactAndroid::react_render_textlayoutmanager - ReactAndroid::react_render_imagemanager - ReactAndroid::reactnativejni - ReactAndroid::mapbufferjni - fabricjni - fbjni - folly_runtime - glog - jsi - react_codegen_rncore - react_debug - react_nativemodule_core - react_render_core - react_render_debug - react_render_graphics - react_render_mapbuffer - ReactAndroid::react_render_uimanager - ReactAndroid::react_render_scheduler - react_utils - runtimeexecutor - rrc_view - turbomodulejsijni - yoga - android - log - mapbufferjni - reactnativejni - react_render_consistency - react_performance_timeline - react_render_observers_events - react_featureflags + ${LIB_TARGET_NAME} + ReactAndroid::fabricjni + ReactAndroid::folly_runtime + ReactAndroid::glog + ReactAndroid::react_debug + ReactAndroid::react_nativemodule_core + ReactAndroid::react_performance_timeline + ReactAndroid::react_render_consistency + ReactAndroid::react_render_core + ReactAndroid::react_render_debug + ReactAndroid::react_render_graphics + ReactAndroid::react_render_imagemanager + ReactAndroid::react_render_mapbuffer + ReactAndroid::react_render_observers_events + ReactAndroid::react_render_textlayoutmanager + ReactAndroid::reactnativejni + ReactAndroid::rrc_text + ReactAndroid::rrc_textinput + ReactAndroid::rrc_view + ReactAndroid::runtimeexecutor + ReactAndroid::yoga ) else () message(FATAL_ERROR "react-native-live-markdown requires react-native 0.75 or newer.") endif () target_compile_options( - ${LIB_TARGET_NAME} - PRIVATE - -DLOG_TAG=\"ReactNative\" - -fexceptions - -frtti - -Wall - -std=c++20 + ${LIB_TARGET_NAME} + PRIVATE + -DLOG_TAG=\"ReactNative\" + -fexceptions + -frtti + -Wall + -std=c++20 ) target_include_directories( - ${CMAKE_PROJECT_NAME} - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java b/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java index c6d6b4cb..ec679599 100644 --- a/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java +++ b/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java @@ -13,7 +13,7 @@ public abstract class MarkdownTextInputDecoratorViewManagerSpec private final ViewManagerDelegate mDelegate; public MarkdownTextInputDecoratorViewManagerSpec() { - mDelegate = new MarkdownTextInputDecoratorViewManagerDelegate(this); + mDelegate = new MarkdownTextInputDecoratorViewManagerDelegate<>(this); } @Nullable diff --git a/ios/MarkdownCommitHook.h b/apple/MarkdownCommitHook.h similarity index 100% rename from ios/MarkdownCommitHook.h rename to apple/MarkdownCommitHook.h diff --git a/ios/MarkdownCommitHook.mm b/apple/MarkdownCommitHook.mm similarity index 100% rename from ios/MarkdownCommitHook.mm rename to apple/MarkdownCommitHook.mm diff --git a/ios/MarkdownLayoutManager.h b/apple/MarkdownLayoutManager.h similarity index 100% rename from ios/MarkdownLayoutManager.h rename to apple/MarkdownLayoutManager.h diff --git a/ios/MarkdownLayoutManager.mm b/apple/MarkdownLayoutManager.mm similarity index 100% rename from ios/MarkdownLayoutManager.mm rename to apple/MarkdownLayoutManager.mm diff --git a/ios/MarkdownTextInputDecoratorComponentView.h b/apple/MarkdownTextInputDecoratorComponentView.h similarity index 100% rename from ios/MarkdownTextInputDecoratorComponentView.h rename to apple/MarkdownTextInputDecoratorComponentView.h diff --git a/ios/MarkdownTextInputDecoratorComponentView.mm b/apple/MarkdownTextInputDecoratorComponentView.mm similarity index 100% rename from ios/MarkdownTextInputDecoratorComponentView.mm rename to apple/MarkdownTextInputDecoratorComponentView.mm diff --git a/ios/MarkdownTextInputDecoratorView.h b/apple/MarkdownTextInputDecoratorView.h similarity index 100% rename from ios/MarkdownTextInputDecoratorView.h rename to apple/MarkdownTextInputDecoratorView.h diff --git a/ios/MarkdownTextInputDecoratorView.mm b/apple/MarkdownTextInputDecoratorView.mm similarity index 100% rename from ios/MarkdownTextInputDecoratorView.mm rename to apple/MarkdownTextInputDecoratorView.mm diff --git a/ios/MarkdownTextInputDecoratorViewManager.h b/apple/MarkdownTextInputDecoratorViewManager.h similarity index 100% rename from ios/MarkdownTextInputDecoratorViewManager.h rename to apple/MarkdownTextInputDecoratorViewManager.h diff --git a/ios/MarkdownTextInputDecoratorViewManager.mm b/apple/MarkdownTextInputDecoratorViewManager.mm similarity index 100% rename from ios/MarkdownTextInputDecoratorViewManager.mm rename to apple/MarkdownTextInputDecoratorViewManager.mm diff --git a/apple/RCTBackedTextFieldDelegateAdapter+Markdown.h b/apple/RCTBackedTextFieldDelegateAdapter+Markdown.h new file mode 100644 index 00000000..f8ddc1d2 --- /dev/null +++ b/apple/RCTBackedTextFieldDelegateAdapter+Markdown.h @@ -0,0 +1,14 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTBackedTextFieldDelegateAdapter (Markdown) + +@property(nonatomic, nullable, getter=getMarkdownUtils) RCTMarkdownUtils *markdownUtils; + +- (void)markdown_textFieldDidChange; + +@end + +NS_ASSUME_NONNULL_END diff --git a/apple/RCTBackedTextFieldDelegateAdapter+Markdown.mm b/apple/RCTBackedTextFieldDelegateAdapter+Markdown.mm new file mode 100644 index 00000000..11c3baf8 --- /dev/null +++ b/apple/RCTBackedTextFieldDelegateAdapter+Markdown.mm @@ -0,0 +1,43 @@ +#import +#import +#import +#import + +@implementation RCTBackedTextFieldDelegateAdapter (Markdown) + +- (void)setMarkdownUtils:(RCTMarkdownUtils *)markdownUtils { + objc_setAssociatedObject(self, @selector(getMarkdownUtils), markdownUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (RCTMarkdownUtils *)getMarkdownUtils { + return objc_getAssociatedObject(self, @selector(getMarkdownUtils)); +} + +- (void)markdown_textFieldDidChange +{ + RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils]; + if (markdownUtils != nil) { + RCTUITextField *backedTextInputView = [self valueForKey:@"_backedTextInputView"]; + UITextRange *range = backedTextInputView.selectedTextRange; + backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText withAttributes:backedTextInputView.defaultTextAttributes]; + [backedTextInputView setSelectedTextRange:range notifyDelegate:YES]; + } + + // Call the original method + [self markdown_textFieldDidChange]; +} + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = [self class]; + SEL originalSelector = @selector(textFieldDidChange); + SEL swizzledSelector = @selector(markdown_textFieldDidChange); + Method originalMethod = class_getInstanceMethod(cls, originalSelector); + Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); + method_exchangeImplementations(originalMethod, swizzledMethod); + }); +} + +@end diff --git a/ios/RCTBaseTextInputView+Markdown.h b/apple/RCTBaseTextInputView+Markdown.h similarity index 100% rename from ios/RCTBaseTextInputView+Markdown.h rename to apple/RCTBaseTextInputView+Markdown.h diff --git a/ios/RCTBaseTextInputView+Markdown.mm b/apple/RCTBaseTextInputView+Markdown.mm similarity index 100% rename from ios/RCTBaseTextInputView+Markdown.mm rename to apple/RCTBaseTextInputView+Markdown.mm diff --git a/ios/RCTLiveMarkdownModule.h b/apple/RCTLiveMarkdownModule.h similarity index 100% rename from ios/RCTLiveMarkdownModule.h rename to apple/RCTLiveMarkdownModule.h diff --git a/ios/RCTLiveMarkdownModule.mm b/apple/RCTLiveMarkdownModule.mm similarity index 100% rename from ios/RCTLiveMarkdownModule.mm rename to apple/RCTLiveMarkdownModule.mm diff --git a/ios/RCTMarkdownStyle.h b/apple/RCTMarkdownStyle.h similarity index 100% rename from ios/RCTMarkdownStyle.h rename to apple/RCTMarkdownStyle.h diff --git a/ios/RCTMarkdownStyle.mm b/apple/RCTMarkdownStyle.mm similarity index 100% rename from ios/RCTMarkdownStyle.mm rename to apple/RCTMarkdownStyle.mm diff --git a/ios/RCTMarkdownUtils.h b/apple/RCTMarkdownUtils.h similarity index 100% rename from ios/RCTMarkdownUtils.h rename to apple/RCTMarkdownUtils.h diff --git a/apple/RCTMarkdownUtils.mm b/apple/RCTMarkdownUtils.mm new file mode 100644 index 00000000..4e678c65 --- /dev/null +++ b/apple/RCTMarkdownUtils.mm @@ -0,0 +1,214 @@ +#import +#import "react_native_assert.h" +#import +#import + +#include +#include + +using namespace facebook; + +@implementation RCTMarkdownUtils { + NSString *_prevInputString; + NSAttributedString *_prevAttributedString; + NSDictionary *_prevTextAttributes; + __weak RCTMarkdownStyle *_prevMarkdownStyle; +} + +- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withAttributes:(nullable NSDictionary *)attributes +{ + auto inputString = [input string]; + if ([inputString isEqualToString:_prevInputString] && [attributes isEqualToDictionary:_prevTextAttributes] && [_markdownStyle isEqual:_prevMarkdownStyle]) { + return _prevAttributedString; + } + + auto attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes]; + [self parseMarkdown:attributedString]; + + _prevInputString = inputString; + _prevAttributedString = attributedString; + _prevTextAttributes = attributes; + _prevMarkdownStyle = _markdownStyle; + + return attributedString; +} + +- (void)parseMarkdown:(nullable NSMutableAttributedString *)attributedString +{ + @synchronized (self) { + if (attributedString == nil) { + return; + } + + static std::shared_ptr runtime; + static std::mutex runtimeMutex; + auto lock = std::lock_guard(runtimeMutex); + + if (runtime == nullptr) { + NSString *path = [[NSBundle mainBundle] pathForResource:@"react-native-live-markdown-parser" ofType:@"js"]; + assert(path != nil && "[react-native-live-markdown] Markdown parser bundle not found"); + NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + assert(content != nil && "[react-native-live-markdown] Markdown parser bundle is empty"); + runtime = facebook::hermes::makeHermesRuntime(); + auto codeBuffer = std::make_shared([content UTF8String]); + runtime->evaluateJavaScript(codeBuffer, "evaluateJavaScript"); + } + + jsi::Runtime &rt = *runtime; + auto inputString = [attributedString string]; + auto text = jsi::String::createFromUtf8(rt, [inputString UTF8String]); + + auto func = rt.global().getPropertyAsFunction(rt, "parseExpensiMarkToRanges"); + auto output = func.call(rt, text); + if (output.isUndefined()) { + return; + } + const auto &ranges = output.asObject(rt).asArray(rt); + + [attributedString beginEditing]; + + // If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string. + // It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting. + // This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem. + [attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)]; + + _blockquoteRangesAndLevels = [NSMutableArray new]; + + for (size_t i = 0, n = ranges.size(rt); i < n; ++i) { + const auto &item = ranges.getValueAtIndex(rt, i).asObject(rt); + const auto &type = item.getProperty(rt, "type").asString(rt).utf8(rt); + const auto &start = static_cast(item.getProperty(rt, "start").asNumber()); + const auto &length = static_cast(item.getProperty(rt, "length").asNumber()); + const auto &depth = item.hasProperty(rt, "depth") ? static_cast(item.getProperty(rt, "depth").asNumber()) : 1; + + [self applyRangeToAttributedString:attributedString type:type start:start length:length depth:depth]; + } + + RCTApplyBaselineOffset(attributedString); + + [attributedString endEditing]; + } +} + +- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString type:(const std::string)type start:(const int)start length:(const int)length depth:(const int)depth { + if (length == 0 || start + length > attributedString.length) { + return; + } + + NSRange range = NSMakeRange(start, length); + + if (type == "bold" || type == "italic" || type == "code" || type == "pre" || type == "h1" || type == "emoji") { + UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:start effectiveRange:NULL]; + if (type == "bold") { + font = [RCTFont updateFont:font withWeight:@"bold"]; + } else if (type == "italic") { + font = [RCTFont updateFont:font withStyle:@"italic"]; + } else if (type == "code") { + font = [RCTFont updateFont:font withFamily:_markdownStyle.codeFontFamily + size:[NSNumber numberWithFloat:_markdownStyle.codeFontSize] + weight:nil + style:nil + variant:nil + scaleMultiplier:0]; + } else if (type == "pre") { + font = [RCTFont updateFont:font withFamily:_markdownStyle.preFontFamily + size:[NSNumber numberWithFloat:_markdownStyle.preFontSize] + weight:nil + style:nil + variant:nil + scaleMultiplier:0]; + } else if (type == "h1") { + font = [RCTFont updateFont:font withFamily:nil + size:[NSNumber numberWithFloat:_markdownStyle.h1FontSize] + weight:@"bold" + style:nil + variant:nil + scaleMultiplier:0]; + } else if (type == "emoji") { + font = [[font copy] fontWithSize:_markdownStyle.emojiFontSize]; + } + [attributedString addAttribute:NSFontAttributeName value:font range:range]; + } + + if (type == "syntax") { + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.syntaxColor range:range]; + } else if (type == "strikethrough") { + [attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range]; + } else if (type == "code") { + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.codeColor range:range]; + [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.codeBackgroundColor range:range]; + } else if (type == "mention-here") { + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionHereColor range:range]; + [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionHereBackgroundColor range:range]; + } else if (type == "mention-user") { + // TODO: change mention color when it mentions current user + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionUserColor range:range]; + [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionUserBackgroundColor range:range]; + } else if (type == "mention-report") { + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionReportColor range:range]; + [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionReportBackgroundColor range:range]; + } else if (type == "link") { + [attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range]; + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.linkColor range:range]; + } else if (type == "blockquote") { + CGFloat indent = (_markdownStyle.blockquoteMarginLeft + _markdownStyle.blockquoteBorderWidth + _markdownStyle.blockquotePaddingLeft) * depth; + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + paragraphStyle.firstLineHeadIndent = indent; + paragraphStyle.headIndent = indent; + [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; + [_blockquoteRangesAndLevels addObject:@{ + @"range": [NSValue valueWithRange:range], + @"depth": @(depth) + }]; + } else if (type == "pre") { + [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range]; + NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range; + [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.preBackgroundColor range:rangeForBackground]; + // TODO: pass background color and ranges to layout manager + } +} + +static void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText) +{ + __block CGFloat maximumLineHeight = 0; + + [attributedText enumerateAttribute:NSParagraphStyleAttributeName + inRange:NSMakeRange(0, attributedText.length) + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) { + if (!paragraphStyle) { + return; + } + + maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight); + }]; + + if (maximumLineHeight == 0) { + // `lineHeight` was not specified, nothing to do. + return; + } + + __block CGFloat maximumFontLineHeight = 0; + + [attributedText enumerateAttribute:NSFontAttributeName + inRange:NSMakeRange(0, attributedText.length) + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { + if (!font) { + return; + } + + maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight); + }]; + + if (maximumLineHeight < maximumFontLineHeight) { + return; + } + + CGFloat baseLineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0; + [attributedText addAttribute:NSBaselineOffsetAttributeName + value:@(baseLineOffset) + range:NSMakeRange(0, attributedText.length)]; +} + +@end diff --git a/ios/RCTTextInputComponentView+Markdown.h b/apple/RCTTextInputComponentView+Markdown.h similarity index 100% rename from ios/RCTTextInputComponentView+Markdown.h rename to apple/RCTTextInputComponentView+Markdown.h diff --git a/ios/RCTTextInputComponentView+Markdown.mm b/apple/RCTTextInputComponentView+Markdown.mm similarity index 100% rename from ios/RCTTextInputComponentView+Markdown.mm rename to apple/RCTTextInputComponentView+Markdown.mm diff --git a/ios/RCTUITextView+Markdown.h b/apple/RCTUITextView+Markdown.h similarity index 100% rename from ios/RCTUITextView+Markdown.h rename to apple/RCTUITextView+Markdown.h diff --git a/ios/RCTUITextView+Markdown.mm b/apple/RCTUITextView+Markdown.mm similarity index 100% rename from ios/RCTUITextView+Markdown.mm rename to apple/RCTUITextView+Markdown.mm diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp index de564b32..897a3f59 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp @@ -1,5 +1,3 @@ -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) - #include "MarkdownShadowFamilyRegistry.h" namespace expensify { @@ -60,5 +58,3 @@ bool MarkdownShadowFamilyRegistry::shouldForceUpdate(facebook::react::Tag tag) { } // namespace livemarkdown } // namespace expensify - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h index 4e9431b5..d61ea773 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h @@ -1,5 +1,4 @@ #pragma once -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) #include @@ -37,5 +36,3 @@ class MarkdownShadowFamilyRegistry { } // namespace livemarkdown } // namespace expensify - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp index ae56126d..941d30b7 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp @@ -1,8 +1,3 @@ -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) - -#include - -#include "MarkdownShadowFamilyRegistry.h" #include "MarkdownTextInputDecoratorShadowNode.h" using namespace expensify::livemarkdown; @@ -36,5 +31,3 @@ MarkdownTextInputDecoratorShadowNode::updateFragmentState( } // namespace react } // namespace facebook - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h index 3392d557..6f2423e5 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h @@ -1,9 +1,8 @@ #pragma once -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) #include "MarkdownShadowFamilyRegistry.h" #include "MarkdownTextInputDecoratorState.h" -#include +#include "OwningShadowNodeFragment.h" #include #include #include @@ -13,20 +12,6 @@ using namespace expensify::livemarkdown; namespace facebook { namespace react { -struct OwningShadowNodeFragment { - Props::Shared props; - ShadowNode::SharedListOfShared children; - State::Shared state; - - operator ShadowNodeFragment() const { - return ShadowNodeFragment { - .props = props, - .children = children, - .state = state - }; - } -}; - JSI_EXPORT extern const char MarkdownTextInputDecoratorViewComponentName[]; class JSI_EXPORT MarkdownTextInputDecoratorShadowNode final @@ -59,5 +44,3 @@ class JSI_EXPORT MarkdownTextInputDecoratorShadowNode final } // namespace react } // namespace facebook - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h index 169990d7..7590b996 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h @@ -1,5 +1,4 @@ #pragma once -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) #include @@ -33,5 +32,3 @@ class JSI_EXPORT MarkdownTextInputDecoratorState final { } // namespace react } // namespace facebook - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h index 8eaa1b01..c145e45c 100644 --- a/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h @@ -1,5 +1,4 @@ #pragma once -#if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID) #include "MarkdownTextInputDecoratorShadowNode.h" #include @@ -16,5 +15,3 @@ class MarkdownTextInputDecoratorViewComponentDescriptor final } // namespace react } // namespace facebook - -#endif diff --git a/cpp/react/renderer/components/RNLiveMarkdownSpec/OwningShadowNodeFragment.h b/cpp/react/renderer/components/RNLiveMarkdownSpec/OwningShadowNodeFragment.h new file mode 100644 index 00000000..601187c2 --- /dev/null +++ b/cpp/react/renderer/components/RNLiveMarkdownSpec/OwningShadowNodeFragment.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +using namespace facebook::react; + +namespace expensify { +namespace livemarkdown { + +struct OwningShadowNodeFragment { + Props::Shared props; + ShadowNode::SharedListOfShared children; + State::Shared state; + + operator ShadowNodeFragment() const { + return ShadowNodeFragment { + .props = props, + .children = children, + .state = state + }; + } +}; + +} // namespace livemarkdown +} // namespace expensify diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cc793edc..14c774cb 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1497,7 +1497,7 @@ PODS: - React-logger (= 0.75.2) - React-perflogger (= 0.75.2) - React-utils (= 0.75.2) - - RNLiveMarkdown (0.1.137): + - RNLiveMarkdown (0.1.169): - DoubleConversion - glog - hermes-engine @@ -1517,9 +1517,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.137) + - RNLiveMarkdown/newarch (= 0.1.169) - Yoga - - RNLiveMarkdown/common (0.1.137): + - RNLiveMarkdown/newarch (0.1.169): - DoubleConversion - glog - hermes-engine @@ -1805,7 +1805,7 @@ SPEC CHECKSUMS: React-utils: 81a715d9c0a2a49047e77a86f3a2247408540deb ReactCodegen: 60973d382704c793c605b9be0fc7f31cb279442f ReactCommon: 6ef348087d250257c44c0204461c03f036650e9b - RNLiveMarkdown: b2d706acf1bbd968b8dab0be0dc69f130a14db6d + RNLiveMarkdown: 00ab78496be2ae15a15a83f14ba732c01624f02c SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae diff --git a/example/src/App.tsx b/example/src/App.tsx index 360103d3..d3d63426 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,118 +1,56 @@ import * as React from 'react'; -import {Button, Platform, StyleSheet, Text, View} from 'react-native'; +import {Button, StyleSheet, Text, View} from 'react-native'; import {MarkdownTextInput} from '@expensify/react-native-live-markdown'; import type {TextInput} from 'react-native'; import * as TEST_CONST from './testConstants'; - -function isWeb() { - return Platform.OS === 'web'; -} - -function getPlatform() { - if (isWeb()) { - return 'web'; - } - // @ts-expect-error it works - return Platform.constants.systemName || Platform.constants.Brand; -} - -function getPlatformVersion() { - return Platform.Version; -} - -function getBundle() { - return __DEV__ ? 'dev' : 'production'; -} - -function getRuntime() { - if ('HermesInternal' in global) { - const version = - // @ts-expect-error this is fine - global.HermesInternal?.getRuntimeProperties?.()['OSS Release Version']; - return `Hermes (${version})`; - } - if ('_v8runtime' in global) { - // @ts-expect-error this is fine - const version = global._v8runtime().version; - return `V8 (${version})`; - } - return 'JSC'; -} - -function getArchitecture() { - return 'nativeFabricUIManager' in global ? 'Fabric' : 'Paper'; -} - -function getReactNativeVersion() { - const {major, minor, patch} = Platform.constants.reactNativeVersion; - return `${major}.${minor}.${patch}`; -} - -function getRandomColor() { - return `#${Math.floor(Math.random() * 16777215) - .toString(16) - .padStart(6, '0')}`; -} +import {PlatformInfo} from './PlatformInfo'; export default function App() { const [value, setValue] = React.useState(TEST_CONST.EXAMPLE_CONTENT); - const [markdownStyle, setMarkdownStyle] = React.useState({}); + const [textColorState, setTextColorState] = React.useState(false); + const [linkColorState, setLinkColorState] = React.useState(false); + const [textFontSizeState, setTextFontSizeState] = React.useState(false); + const [emojiFontSizeState, setEmojiFontSizeState] = React.useState(false); const [selection, setSelection] = React.useState({start: 0, end: 0}); + const style = React.useMemo(() => { + return { + color: textColorState ? 'gray' : 'black', + fontSize: textFontSizeState ? 15 : 20, + }; + }, [textColorState, textFontSizeState]); + + const markdownStyle = React.useMemo(() => { + return { + emoji: { + fontSize: emojiFontSizeState ? 15 : 20, + }, + link: { + color: linkColorState ? 'red' : 'blue', + }, + }; + }, [emojiFontSizeState, linkColorState]); + // TODO: use MarkdownTextInput ref instead of TextInput ref const ref = React.useRef(null); return ( - - - Platform: {getPlatform()} {getPlatformVersion()} - - Bundle: {getBundle()} - {!isWeb() && ( - <> - Architecture: {getArchitecture()} - RN version: {getReactNativeVersion()} - RN runtime: {getRuntime()} - - )} - - {/* MarkdownTextInput singleline - */} - MarkdownTextInput multiline + setSelection(e.nativeEvent.selection)} selection={selection} id={TEST_CONST.INPUT_ID} + maxLength={30000} /> - {/* TextInput singleline - */} - {/* TextInput multiline - */} {JSON.stringify(value)}