@expensify/react-native-live-markdown 0.1.44 → 0.1.46

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 (60) hide show
  1. package/RNLiveMarkdown.podspec +13 -0
  2. package/android/build.gradle +12 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java +47 -0
  5. package/android/src/main/java/com/expensify/livemarkdown/CustomMountingManager.java +248 -0
  6. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownModule.java +27 -0
  7. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +30 -3
  8. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +7 -1
  9. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +3 -1
  10. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +2 -6
  11. package/android/src/main/new_arch/CMakeLists.txt +82 -0
  12. package/android/src/main/new_arch/MarkdownCommitHook.cpp +173 -0
  13. package/android/src/main/new_arch/MarkdownCommitHook.h +52 -0
  14. package/android/src/main/new_arch/NativeProxy.cpp +38 -0
  15. package/android/src/main/new_arch/NativeProxy.h +37 -0
  16. package/android/src/main/new_arch/OnLoad.cpp +8 -0
  17. package/android/src/main/new_arch/RNLiveMarkdownSpec.cpp +44 -0
  18. package/android/src/main/new_arch/RNLiveMarkdownSpec.h +37 -0
  19. package/android/src/newarch/NativeProxy.java +24 -0
  20. package/android/src/oldarch/NativeLiveMarkdownModuleSpec.java +37 -0
  21. package/android/src/oldarch/NativeProxy.java +9 -0
  22. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp +58 -0
  23. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h +35 -0
  24. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp +38 -0
  25. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h +48 -0
  26. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h +34 -0
  27. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h +17 -0
  28. package/ios/MarkdownCommitHook.h +42 -0
  29. package/ios/MarkdownCommitHook.mm +195 -0
  30. package/ios/MarkdownTextInputDecoratorComponentView.mm +24 -1
  31. package/ios/MarkdownTextInputDecoratorView.mm +5 -1
  32. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +1 -1
  33. package/ios/RCTBaseTextInputView+Markdown.m +2 -2
  34. package/ios/RCTLiveMarkdownModule.h +12 -0
  35. package/ios/RCTLiveMarkdownModule.mm +48 -0
  36. package/ios/RCTMarkdownUtils.h +1 -3
  37. package/ios/RCTMarkdownUtils.mm +129 -136
  38. package/ios/RCTTextInputComponentView+Markdown.mm +18 -4
  39. package/ios/RCTUITextView+Markdown.mm +1 -1
  40. package/lib/commonjs/MarkdownTextInput.js +4 -0
  41. package/lib/commonjs/MarkdownTextInput.js.map +1 -1
  42. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +3 -1
  43. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
  44. package/lib/commonjs/NativeLiveMarkdownModule.js +9 -0
  45. package/lib/commonjs/NativeLiveMarkdownModule.js.map +1 -0
  46. package/lib/module/MarkdownTextInput.js +4 -0
  47. package/lib/module/MarkdownTextInput.js.map +1 -1
  48. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -1
  49. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
  50. package/lib/module/NativeLiveMarkdownModule.js +3 -0
  51. package/lib/module/NativeLiveMarkdownModule.js.map +1 -0
  52. package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -1
  53. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  54. package/lib/typescript/src/NativeLiveMarkdownModule.d.ts +7 -0
  55. package/lib/typescript/src/NativeLiveMarkdownModule.d.ts.map +1 -0
  56. package/package.json +3 -2
  57. package/react-native.config.js +12 -0
  58. package/src/MarkdownTextInput.tsx +5 -0
  59. package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +3 -1
  60. package/src/NativeLiveMarkdownModule.ts +8 -0
@@ -0,0 +1,173 @@
1
+ #include <react/fabric/Binding.h>
2
+ #include <react/jni/ReadableNativeMap.h>
3
+ #include <react/renderer/core/ComponentDescriptor.h>
4
+ #include <react/renderer/scheduler/Scheduler.h>
5
+
6
+ #include "MarkdownCommitHook.h"
7
+ #include "react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h"
8
+
9
+ using namespace facebook;
10
+ using namespace react;
11
+
12
+ namespace livemarkdown {
13
+
14
+ MarkdownCommitHook::MarkdownCommitHook(
15
+ jni::global_ref<facebook::react::JFabricUIManager::javaobject>
16
+ fabricUIManager)
17
+ : fabricUIManager_(fabricUIManager),
18
+ uiManager_(
19
+ fabricUIManager->getBinding()->getScheduler()->getUIManager()) {
20
+ uiManager_->registerCommitHook(*this);
21
+ }
22
+
23
+ MarkdownCommitHook::~MarkdownCommitHook() noexcept {
24
+ uiManager_->unregisterCommitHook(*this);
25
+ }
26
+
27
+ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
28
+ ShadowTree const &, RootShadowNode::Shared const &,
29
+ RootShadowNode::Unshared const &newRootShadowNode) noexcept {
30
+ auto rootNode = newRootShadowNode->ShadowNode::clone(ShadowNodeFragment{});
31
+
32
+ // A preface to why we do the weird thing below:
33
+ // On the new architecture there are two ways of measuring text on Android: by
34
+ // passing a cache key, or by passing a measured text with attributes by a map
35
+ // buffer. We could implement both, but that would increase the complexity of
36
+ // the code and duplication between here and RN core, so we implement
37
+ // measurement for map buffers since it's the independent one, and force RN to
38
+ // use this path every time. It shouldn't have a negative performance impact,
39
+ // since there is a cpp cache layer anyway.
40
+ //
41
+ // AndroidTextInputShadowNode is closed pretty tightly, but there's a one
42
+ // place where we can insert ourselves (well, there are two but we really
43
+ // shouldn't mess with vtable). The path to measurement looks like this:
44
+ // AndroidTextInputShadowNode::measureContent ->
45
+ // TextLayoutManager::measureAndroidComponentMapBuffer
46
+ // -> (jni) -> FabricUIManager::measureMapBuffer ->
47
+ // MountingManager::measureMapBuffer
48
+ // We cannot modify the shadow node directly, but we can replace its
49
+ // TextLayoutManager. Literally every method it has is linked statically so we
50
+ // cannot override anything, but we can replace its ContextContainer where a
51
+ // jni reference to the FabricUIManager is stored. Now `measureMapBuffer` is
52
+ // private in the ui manager, but the only thing it does is calling
53
+ // `measureMapBuffer` on MountingManager where it's public. At this point the
54
+ // path forward is clear: we make a custom MountingManager that will perform
55
+ // our measurement, then create a custom FabricUIManager which will direct
56
+ // measurement to our MountingManager. Then we only need to create the
57
+ // ContextContainer with our FabricUIManager, create the TextLayoutManager
58
+ // with the newly created ContextContainer and replace the pointer to
59
+ // TextLayoutManager inside the AndroidTextInputShadowNode.
60
+
61
+ // In order to properly apply markdown formatting to the text input, we need
62
+ // to update the TextInputShadowNode's state to reset the cache key and update
63
+ // its TextLayoutManager reference, but we only have access to the
64
+ // ShadowNodeFamilies of the decorator components. We also know that a
65
+ // markdown decorator is always preceded with the TextInput to decorate, so we
66
+ // need to take the sibling.
67
+ std::vector<MarkdownTextInputDecoratorPair> nodesToUpdate;
68
+ MarkdownShadowFamilyRegistry::runForEveryFamily([&rootNode, &nodesToUpdate](
69
+ ShadowNodeFamily::Shared
70
+ family) {
71
+ // get the path from the root to the node from the decorator family
72
+ const auto ancestors = family->getAncestors(*rootNode);
73
+
74
+ if (!ancestors.empty()) {
75
+ auto &parentNode = ancestors.back().first.get();
76
+ auto index = ancestors.back().second;
77
+
78
+ // this is node represented by one of the registered families and since we
79
+ // only register markdown decorator shadow families, static casting should
80
+ // be safe here
81
+ const auto &decoratorNode =
82
+ std::static_pointer_cast<const MarkdownTextInputDecoratorShadowNode>(
83
+ parentNode.getChildren().at(index));
84
+ // text input always precedes the decorator component
85
+ const auto &previousSibling = parentNode.getChildren().at(index - 1);
86
+
87
+ if (const auto &textInputNode =
88
+ std::dynamic_pointer_cast<const AndroidTextInputShadowNode>(
89
+ previousSibling)) {
90
+ // store the pair of text input and decorator to update in the next step
91
+ // we need both, decorator to get markdown style and text input to
92
+ // update it
93
+ nodesToUpdate.push_back({
94
+ textInputNode,
95
+ decoratorNode,
96
+ });
97
+ }
98
+ }
99
+ });
100
+
101
+ for (const auto &nodes : nodesToUpdate) {
102
+ const auto &textInputState =
103
+ *std::static_pointer_cast<const ConcreteState<AndroidTextInputState>>(
104
+ nodes.textInput->getState());
105
+ const auto &stateData = textInputState.getData();
106
+
107
+ rootNode = rootNode->cloneTree(
108
+ nodes.textInput->getFamily(),
109
+ [this, &stateData, &textInputState, &nodes](ShadowNode const &node) {
110
+ auto newStateData =
111
+ std::make_shared<AndroidTextInputState>(stateData);
112
+ // force measurement of a map buffer
113
+ newStateData->cachedAttributedStringId = 0;
114
+
115
+ // clone the text input with the new state
116
+ auto newNode = node.clone({
117
+ .state =
118
+ std::make_shared<const ConcreteState<AndroidTextInputState>>(
119
+ newStateData, textInputState),
120
+ });
121
+
122
+ const auto currentDecoratorProps =
123
+ nodes.decorator->getProps()->rawProps["markdownStyle"];
124
+
125
+ // if it's the first time we encounter this particular input or the
126
+ // markdown styles have changed (in which case we need to reset the
127
+ // cpp cache, to which we don't have a direct access), create a new
128
+ // instance of TextLayoutManager that will be performing measurement
129
+ // for this particular input
130
+ if (!textLayoutManagers_.contains(nodes.textInput->getTag()) ||
131
+ previousDecoratorProps_[nodes.textInput->getTag()] !=
132
+ currentDecoratorProps) {
133
+ static auto customUIManagerClass = jni::findClassStatic(
134
+ "com/expensify/livemarkdown/CustomFabricUIManager");
135
+ static auto createCustomUIManager =
136
+ customUIManagerClass
137
+ ->getStaticMethod<JFabricUIManager::javaobject(
138
+ JFabricUIManager::javaobject, ReadableMap::javaobject)>(
139
+ "create");
140
+
141
+ auto const decoratorPropsRNM =
142
+ ReadableNativeMap::newObjectCxxArgs(currentDecoratorProps);
143
+ auto const decoratorPropsRM =
144
+ jni::make_local(reinterpret_cast<ReadableMap::javaobject>(
145
+ decoratorPropsRNM.get()));
146
+
147
+ const auto customUIManager = jni::make_global(createCustomUIManager(
148
+ customUIManagerClass, fabricUIManager_.get(),
149
+ decoratorPropsRM.get()));
150
+ const ContextContainer::Shared contextContainer =
151
+ std::make_shared<ContextContainer const>();
152
+ contextContainer->insert("FabricUIManager", customUIManager);
153
+ textLayoutManagers_[nodes.textInput->getTag()] =
154
+ std::make_shared<TextLayoutManager>(contextContainer);
155
+ previousDecoratorProps_[nodes.textInput->getTag()] =
156
+ currentDecoratorProps;
157
+ }
158
+
159
+ // we need to replace the TextLayoutManager every time to make sure
160
+ // the correct measurement code is run
161
+ auto newTextInputShadowNode =
162
+ std::static_pointer_cast<AndroidTextInputShadowNode>(newNode);
163
+ newTextInputShadowNode->setTextLayoutManager(
164
+ textLayoutManagers_[nodes.textInput->getTag()]);
165
+
166
+ return newNode;
167
+ });
168
+ }
169
+
170
+ return std::static_pointer_cast<RootShadowNode>(rootNode);
171
+ }
172
+
173
+ } // namespace livemarkdown
@@ -0,0 +1,52 @@
1
+ #pragma once
2
+
3
+ #include <fbjni/fbjni.h>
4
+ #include <react/fabric/JFabricUIManager.h>
5
+
6
+ #include <react/renderer/components/androidtextinput/AndroidTextInputShadowNode.h>
7
+ #include <react/renderer/uimanager/UIManager.h>
8
+ #include <react/renderer/uimanager/UIManagerCommitHook.h>
9
+
10
+ #include <memory>
11
+ #include <unordered_map>
12
+
13
+ #include "react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h"
14
+
15
+ using namespace facebook;
16
+ using namespace react;
17
+
18
+ namespace livemarkdown {
19
+
20
+ struct MarkdownTextInputDecoratorPair {
21
+ const std::shared_ptr<const AndroidTextInputShadowNode> textInput;
22
+ const std::shared_ptr<const MarkdownTextInputDecoratorShadowNode> decorator;
23
+ };
24
+
25
+ class MarkdownCommitHook : public UIManagerCommitHook {
26
+ public:
27
+ MarkdownCommitHook(
28
+ jni::global_ref<facebook::react::JFabricUIManager::javaobject>
29
+ fabricUIManager);
30
+
31
+ ~MarkdownCommitHook() noexcept override;
32
+
33
+ void commitHookWasRegistered(UIManager const &) noexcept override {}
34
+
35
+ void commitHookWasUnregistered(UIManager const &) noexcept override {}
36
+
37
+ RootShadowNode::Unshared shadowTreeWillCommit(
38
+ ShadowTree const &shadowTree,
39
+ RootShadowNode::Shared const &oldRootShadowNode,
40
+ RootShadowNode::Unshared const &newRootShadowNode) noexcept override;
41
+
42
+ private:
43
+ const jni::global_ref<facebook::react::JFabricUIManager::javaobject>
44
+ fabricUIManager_;
45
+ const std::shared_ptr<UIManager> uiManager_;
46
+ std::unordered_map<facebook::react::Tag, SharedTextLayoutManager>
47
+ textLayoutManagers_;
48
+ std::unordered_map<facebook::react::Tag, folly::dynamic>
49
+ previousDecoratorProps_;
50
+ };
51
+
52
+ } // namespace livemarkdown
@@ -0,0 +1,38 @@
1
+ #include <fbjni/fbjni.h>
2
+ #include <react/fabric/Binding.h>
3
+ #include <react/renderer/scheduler/Scheduler.h>
4
+
5
+ #include <string>
6
+
7
+ #include "NativeProxy.h"
8
+
9
+ namespace livemarkdown {
10
+
11
+ using namespace facebook;
12
+ using namespace react;
13
+
14
+ NativeProxy::NativeProxy(jni::alias_ref<NativeProxy::javaobject> jThis)
15
+ : javaPart_(jni::make_global(jThis)) {}
16
+
17
+ NativeProxy::~NativeProxy() {}
18
+
19
+ void NativeProxy::registerNatives() {
20
+ registerHybrid(
21
+ {makeNativeMethod("initHybrid", NativeProxy::initHybrid),
22
+ makeNativeMethod("createCommitHook", NativeProxy::createCommitHook)});
23
+ }
24
+
25
+ void NativeProxy::createCommitHook(
26
+ jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
27
+ fabricUIManager) {
28
+ const auto &globalUIManager = jni::make_global(fabricUIManager);
29
+
30
+ this->commitHook_ = std::make_shared<MarkdownCommitHook>(globalUIManager);
31
+ }
32
+
33
+ jni::local_ref<NativeProxy::jhybriddata>
34
+ NativeProxy::initHybrid(jni::alias_ref<jhybridobject> jThis) {
35
+ return makeCxxInstance(jThis);
36
+ }
37
+
38
+ } // namespace livemarkdown
@@ -0,0 +1,37 @@
1
+ #pragma once
2
+
3
+ #include <fbjni/fbjni.h>
4
+ #include <react/fabric/JFabricUIManager.h>
5
+
6
+ #include <string>
7
+
8
+ #include "MarkdownCommitHook.h"
9
+
10
+ namespace livemarkdown {
11
+
12
+ using namespace facebook;
13
+ using namespace facebook::jni;
14
+
15
+ class NativeProxy : public jni::HybridClass<NativeProxy> {
16
+ public:
17
+ static auto constexpr kJavaDescriptor =
18
+ "Lcom/expensify/livemarkdown/NativeProxy;";
19
+ static jni::local_ref<jhybriddata>
20
+ initHybrid(jni::alias_ref<jhybridobject> jThis);
21
+ static void registerNatives();
22
+
23
+ ~NativeProxy();
24
+
25
+ private:
26
+ friend HybridBase;
27
+ jni::global_ref<NativeProxy::javaobject> javaPart_;
28
+ std::shared_ptr<MarkdownCommitHook> commitHook_;
29
+
30
+ explicit NativeProxy(jni::alias_ref<NativeProxy::javaobject> jThis);
31
+
32
+ void
33
+ createCommitHook(jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
34
+ fabricUIManager);
35
+ };
36
+
37
+ } // namespace livemarkdown
@@ -0,0 +1,8 @@
1
+ #include <fbjni/fbjni.h>
2
+
3
+ #include "NativeProxy.h"
4
+
5
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
6
+ return facebook::jni::initialize(
7
+ vm, [] { livemarkdown::NativeProxy::registerNatives(); });
8
+ }
@@ -0,0 +1,44 @@
1
+
2
+ /**
3
+ * This code was generated by
4
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
5
+ *
6
+ * Do not edit this file as changes may cause incorrect behavior and will be
7
+ * lost once the code is regenerated.
8
+ *
9
+ * @generated by codegen project: GenerateModuleJniCpp.js
10
+ */
11
+
12
+ #include "RNLiveMarkdownSpec.h"
13
+
14
+ namespace facebook {
15
+ namespace react {
16
+
17
+ static facebook::jsi::Value
18
+ __hostFunction_NativeLiveMarkdownModuleSpecJSI_install(
19
+ facebook::jsi::Runtime &rt, TurboModule &turboModule,
20
+ const facebook::jsi::Value *args, size_t count) {
21
+ static jmethodID cachedMethodId = nullptr;
22
+ return static_cast<JavaTurboModule &>(turboModule)
23
+ .invokeJavaMethod(rt, BooleanKind, "install", "()Z", args, count,
24
+ cachedMethodId);
25
+ }
26
+
27
+ NativeLiveMarkdownModuleSpecJSI::NativeLiveMarkdownModuleSpecJSI(
28
+ const JavaTurboModule::InitParams &params)
29
+ : JavaTurboModule(params) {
30
+ methodMap_["install"] =
31
+ MethodMetadata{0, __hostFunction_NativeLiveMarkdownModuleSpecJSI_install};
32
+ }
33
+
34
+ std::shared_ptr<TurboModule>
35
+ RNLiveMarkdownSpec_ModuleProvider(const std::string &moduleName,
36
+ const JavaTurboModule::InitParams &params) {
37
+ if (moduleName == "LiveMarkdownModule") {
38
+ return std::make_shared<NativeLiveMarkdownModuleSpecJSI>(params);
39
+ }
40
+ return nullptr;
41
+ }
42
+
43
+ } // namespace react
44
+ } // namespace facebook
@@ -0,0 +1,37 @@
1
+
2
+ /**
3
+ * This code was generated by
4
+ * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
5
+ *
6
+ * Do not edit this file as changes may cause incorrect behavior and will be
7
+ * lost once the code is regenerated.
8
+ *
9
+ * @generated by codegen project: GenerateModuleJniH.js
10
+ */
11
+
12
+ #pragma once
13
+
14
+ #include <ReactCommon/JavaTurboModule.h>
15
+ #include <ReactCommon/TurboModule.h>
16
+ #include <jsi/jsi.h>
17
+
18
+ #include <react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h>
19
+
20
+ namespace facebook {
21
+ namespace react {
22
+
23
+ /**
24
+ * JNI C++ class for module 'NativeLiveMarkdownModule'
25
+ */
26
+ class JSI_EXPORT NativeLiveMarkdownModuleSpecJSI : public JavaTurboModule {
27
+ public:
28
+ NativeLiveMarkdownModuleSpecJSI(const JavaTurboModule::InitParams &params);
29
+ };
30
+
31
+ JSI_EXPORT
32
+ std::shared_ptr<TurboModule>
33
+ RNLiveMarkdownSpec_ModuleProvider(const std::string &moduleName,
34
+ const JavaTurboModule::InitParams &params);
35
+
36
+ } // namespace react
37
+ } // namespace facebook
@@ -0,0 +1,24 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import com.facebook.jni.HybridData;
4
+ import com.facebook.proguard.annotations.DoNotStrip;
5
+ import com.facebook.react.fabric.FabricUIManager;
6
+ import com.facebook.soloader.SoLoader;
7
+
8
+ public class NativeProxy {
9
+ static {
10
+ SoLoader.loadLibrary("react_codegen_RNLiveMarkdownSpec");
11
+ }
12
+
13
+ @DoNotStrip
14
+ @SuppressWarnings("unused")
15
+ private final HybridData mHybridData;
16
+
17
+ public NativeProxy() {
18
+ mHybridData = initHybrid();
19
+ }
20
+
21
+ private native HybridData initHybrid();
22
+
23
+ public native void createCommitHook(FabricUIManager fabricUIManager);
24
+ }
@@ -0,0 +1,37 @@
1
+
2
+ /**
3
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
+ *
5
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
6
+ * once the code is regenerated.
7
+ *
8
+ * @generated by codegen project: GenerateModuleJavaSpec.js
9
+ *
10
+ * @nolint
11
+ */
12
+
13
+ package com.expensify.livemarkdown;
14
+
15
+ import com.facebook.proguard.annotations.DoNotStrip;
16
+ import com.facebook.react.bridge.ReactApplicationContext;
17
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
18
+ import com.facebook.react.bridge.ReactMethod;
19
+ import com.facebook.react.turbomodule.core.interfaces.TurboModule;
20
+ import javax.annotation.Nonnull;
21
+
22
+ public abstract class NativeLiveMarkdownModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
23
+ public static final String NAME = "LiveMarkdownModule";
24
+
25
+ public NativeLiveMarkdownModuleSpec(ReactApplicationContext reactContext) {
26
+ super(reactContext);
27
+ }
28
+
29
+ @Override
30
+ public @Nonnull String getName() {
31
+ return NAME;
32
+ }
33
+
34
+ @ReactMethod(isBlockingSynchronousMethod = true)
35
+ @DoNotStrip
36
+ public abstract boolean install();
37
+ }
@@ -0,0 +1,9 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import com.facebook.react.bridge.UIManager;
4
+
5
+ public class NativeProxy {
6
+ public void createCommitHook(UIManager uiManager) {
7
+ // no-op on the old arch
8
+ }
9
+ }
@@ -0,0 +1,58 @@
1
+ #if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID)
2
+
3
+ #include "MarkdownShadowFamilyRegistry.h"
4
+
5
+ std::set<facebook::react::ShadowNodeFamily::Shared>
6
+ MarkdownShadowFamilyRegistry::familiesToUpdate_;
7
+ std::set<facebook::react::Tag> MarkdownShadowFamilyRegistry::forcedUpdates_;
8
+ std::mutex MarkdownShadowFamilyRegistry::mutex_;
9
+
10
+ void MarkdownShadowFamilyRegistry::registerFamilyForUpdates(
11
+ facebook::react::ShadowNodeFamily::Shared family) {
12
+ auto lock =
13
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
14
+ MarkdownShadowFamilyRegistry::familiesToUpdate_.insert(family);
15
+ }
16
+
17
+ void MarkdownShadowFamilyRegistry::unregisterFamilyForUpdates(
18
+ facebook::react::ShadowNodeFamily::Shared family) {
19
+ auto lock =
20
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
21
+ MarkdownShadowFamilyRegistry::familiesToUpdate_.erase(family);
22
+ }
23
+
24
+ void MarkdownShadowFamilyRegistry::reset() {
25
+ auto lock =
26
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
27
+ MarkdownShadowFamilyRegistry::familiesToUpdate_.clear();
28
+ MarkdownShadowFamilyRegistry::forcedUpdates_.clear();
29
+ }
30
+
31
+ void MarkdownShadowFamilyRegistry::runForEveryFamily(
32
+ std::function<void(facebook::react::ShadowNodeFamily::Shared)> fun) {
33
+ auto lock =
34
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
35
+ for (auto &family : MarkdownShadowFamilyRegistry::familiesToUpdate_) {
36
+ fun(family);
37
+ }
38
+ }
39
+
40
+ void MarkdownShadowFamilyRegistry::forceNextStateUpdate(
41
+ facebook::react::Tag tag) {
42
+ auto lock =
43
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
44
+ forcedUpdates_.insert(tag);
45
+ }
46
+
47
+ bool MarkdownShadowFamilyRegistry::shouldForceUpdate(facebook::react::Tag tag) {
48
+ auto lock =
49
+ std::unique_lock<std::mutex>(MarkdownShadowFamilyRegistry::mutex_);
50
+ bool force = forcedUpdates_.contains(tag);
51
+ if (force) {
52
+ forcedUpdates_.erase(tag);
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+
58
+ #endif
@@ -0,0 +1,35 @@
1
+ #pragma once
2
+ #if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID)
3
+
4
+ #include <react/renderer/core/ShadowNodeFamily.h>
5
+
6
+ #include <mutex>
7
+ #include <set>
8
+
9
+ // A registry to store pointers to the ShadowNodeFamilies of markdown
10
+ // decorators. The only place we can _legally_ access the family of shadow node
11
+ // is in the constructor and we need it inside commit hook. To achieve it, we
12
+ // use this simple registry where families are registered when nodes are created
13
+ // and cleaned up when native view is removed from window or when a turbomodule
14
+ // is deallocated.
15
+
16
+ class MarkdownShadowFamilyRegistry {
17
+ public:
18
+ static void
19
+ registerFamilyForUpdates(facebook::react::ShadowNodeFamily::Shared family);
20
+ static void
21
+ unregisterFamilyForUpdates(facebook::react::ShadowNodeFamily::Shared family);
22
+ static void reset();
23
+ static void runForEveryFamily(
24
+ std::function<void(facebook::react::ShadowNodeFamily::Shared family)>
25
+ fun);
26
+ static void forceNextStateUpdate(facebook::react::Tag tag);
27
+ static bool shouldForceUpdate(facebook::react::Tag tag);
28
+
29
+ private:
30
+ static std::set<facebook::react::ShadowNodeFamily::Shared> familiesToUpdate_;
31
+ static std::set<facebook::react::Tag> forcedUpdates_;
32
+ static std::mutex mutex_;
33
+ };
34
+
35
+ #endif
@@ -0,0 +1,38 @@
1
+ #if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID)
2
+
3
+ #include <react/renderer/core/LayoutContext.h>
4
+
5
+ #include "MarkdownShadowFamilyRegistry.h"
6
+ #include "MarkdownTextInputDecoratorShadowNode.h"
7
+
8
+ namespace facebook {
9
+ namespace react {
10
+
11
+ extern const char MarkdownTextInputDecoratorViewComponentName[] =
12
+ "MarkdownTextInputDecoratorView";
13
+
14
+ const ShadowNodeFragment::Value
15
+ MarkdownTextInputDecoratorShadowNode::updateFragmentState(
16
+ ShadowNodeFragment const &fragment,
17
+ ShadowNodeFamily::Shared const &family) {
18
+ const auto newStateData =
19
+ std::make_shared<MarkdownTextInputDecoratorState>(family);
20
+
21
+ MarkdownShadowFamilyRegistry::registerFamilyForUpdates(family);
22
+
23
+ // we pass the pointer to the ShadowNodeFamily in the initial state, so it's
24
+ // propagated on every clone we need it to clear the reference in the registry
25
+ // when the view is removed from window it cannot be done in the destructor,
26
+ // as multiple shadow nodes for the same family may be created
27
+ return ShadowNodeFragment::Value({
28
+ .props = fragment.props,
29
+ .children = fragment.children,
30
+ .state =
31
+ std::make_shared<const ConcreteState>(newStateData, *fragment.state),
32
+ });
33
+ }
34
+
35
+ } // namespace react
36
+ } // namespace facebook
37
+
38
+ #endif
@@ -0,0 +1,48 @@
1
+ #pragma once
2
+ #if defined(RCT_NEW_ARCH_ENABLED) || defined(ANDROID)
3
+
4
+ #include "MarkdownShadowFamilyRegistry.h"
5
+ #include "MarkdownTextInputDecoratorState.h"
6
+ #include <jsi/jsi.h>
7
+ #include <react/renderer/components/RNLiveMarkdownSpec/EventEmitters.h>
8
+ #include <react/renderer/components/RNLiveMarkdownSpec/Props.h>
9
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
10
+
11
+ namespace facebook {
12
+ namespace react {
13
+
14
+ JSI_EXPORT extern const char MarkdownTextInputDecoratorViewComponentName[];
15
+
16
+ class JSI_EXPORT MarkdownTextInputDecoratorShadowNode final
17
+ : public ConcreteViewShadowNode<MarkdownTextInputDecoratorViewComponentName,
18
+ MarkdownTextInputDecoratorViewProps,
19
+ MarkdownTextInputDecoratorViewEventEmitter,
20
+ MarkdownTextInputDecoratorState> {
21
+ public:
22
+ MarkdownTextInputDecoratorShadowNode(ShadowNodeFragment const &fragment,
23
+ ShadowNodeFamily::Shared const &family,
24
+ ShadowNodeTraits traits)
25
+ : ConcreteViewShadowNode(static_cast<ShadowNodeFragment>(
26
+ updateFragmentState(fragment, family)),
27
+ family, traits) {}
28
+
29
+ MarkdownTextInputDecoratorShadowNode(ShadowNode const &sourceShadowNode,
30
+ ShadowNodeFragment const &fragment)
31
+ : ConcreteViewShadowNode(sourceShadowNode, fragment) {
32
+ // if the props changed, we need to update the shadow node state to reflect
33
+ // potential style changes
34
+ if (fragment.props != ShadowNodeFragment::propsPlaceholder()) {
35
+ MarkdownShadowFamilyRegistry::forceNextStateUpdate(this->getTag());
36
+ }
37
+ }
38
+
39
+ private:
40
+ static const ShadowNodeFragment::Value
41
+ updateFragmentState(ShadowNodeFragment const &fragment,
42
+ ShadowNodeFamily::Shared const &family);
43
+ };
44
+
45
+ } // namespace react
46
+ } // namespace facebook
47
+
48
+ #endif