@expensify/react-native-live-markdown 0.1.184 → 0.1.185

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.
@@ -8,6 +8,7 @@
8
8
  #include <memory>
9
9
 
10
10
  #include "MarkdownTextInputDecoratorShadowNode.h"
11
+ #include "RCTMarkdownUtils.h"
11
12
 
12
13
  using namespace facebook::react;
13
14
 
@@ -35,6 +36,10 @@ public:
35
36
  RootShadowNode::Unshared const &newRootShadowNode) noexcept override;
36
37
 
37
38
  private:
39
+ static RCTMarkdownUtils *
40
+ getMarkdownUtils(const MarkdownTextInputDecoratorShadowNode &decorator);
41
+ static RCTMarkdownUtils *getOrCreateMarkdownUtils(
42
+ const MarkdownTextInputDecoratorShadowNode &decorator);
38
43
  const std::shared_ptr<UIManager> uiManager_;
39
44
  };
40
45
 
@@ -3,11 +3,11 @@
3
3
  #include <React/RCTUtils.h>
4
4
  #include <react/renderer/core/ComponentDescriptor.h>
5
5
  #include <react/renderer/textlayoutmanager/RCTAttributedTextUtils.h>
6
+ #include <react/utils/ManagedObjectWrapper.h>
6
7
 
7
8
  #include "MarkdownCommitHook.h"
8
9
  #include "MarkdownShadowFamilyRegistry.h"
9
10
  #include "RCTMarkdownStyle.h"
10
- #include "RCTMarkdownUtils.h"
11
11
 
12
12
  using namespace facebook::react;
13
13
 
@@ -24,6 +24,27 @@ MarkdownCommitHook::~MarkdownCommitHook() noexcept {
24
24
  uiManager_->unregisterCommitHook(*this);
25
25
  }
26
26
 
27
+ RCTMarkdownUtils *MarkdownCommitHook::getMarkdownUtils(
28
+ const MarkdownTextInputDecoratorShadowNode &decorator) {
29
+ const auto decoratorState = std::static_pointer_cast<
30
+ const ConcreteState<MarkdownTextInputDecoratorState>>(
31
+ decorator.getState());
32
+ const auto memoizedUtils = (RCTMarkdownUtils *)unwrapManagedObject(
33
+ decoratorState->getData().markdownUtils);
34
+ return memoizedUtils;
35
+ }
36
+
37
+ RCTMarkdownUtils *MarkdownCommitHook::getOrCreateMarkdownUtils(
38
+ const MarkdownTextInputDecoratorShadowNode &decorator) {
39
+ const auto memoizedUtils = MarkdownCommitHook::getMarkdownUtils(decorator);
40
+
41
+ if (memoizedUtils != nullptr) {
42
+ return memoizedUtils;
43
+ } else {
44
+ return [[RCTMarkdownUtils alloc] init];
45
+ }
46
+ }
47
+
27
48
  RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
28
49
  ShadowTree const &, RootShadowNode::Shared const &,
29
50
  RootShadowNode::Unshared const &newRootShadowNode) noexcept {
@@ -94,6 +115,8 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
94
115
  const auto fontSizeMultiplier =
95
116
  newRootShadowNode->getConcreteProps().layoutContext.fontSizeMultiplier;
96
117
 
118
+ RCTMarkdownUtils *usedUtils = nil;
119
+
97
120
  // We only want to update the shadow node when the attributed string is
98
121
  // stored by value If it's stored by pointer, the markdown formatting should
99
122
  // already by applied to it, since the only source of that pointer (besides
@@ -115,7 +138,7 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
115
138
  nodes.textInput->getTag())) {
116
139
  rootNode = rootNode->cloneTree(
117
140
  nodes.textInput->getFamily(),
118
- [&nodes, &textInputState, &stateData,
141
+ [&nodes, &textInputState, &stateData, &usedUtils,
119
142
  fontSizeMultiplier](const ShadowNode &node) {
120
143
  const auto &markdownProps = *std::static_pointer_cast<
121
144
  MarkdownTextInputDecoratorViewProps const>(
@@ -132,8 +155,9 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
132
155
  // this can possibly be optimized
133
156
  RCTMarkdownStyle *markdownStyle = [[RCTMarkdownStyle alloc]
134
157
  initWithStruct:markdownProps.markdownStyle];
135
- RCTMarkdownUtils *utils = [[RCTMarkdownUtils alloc] init];
136
- [utils setMarkdownStyle:markdownStyle];
158
+ usedUtils =
159
+ MarkdownCommitHook::getOrCreateMarkdownUtils(*nodes.decorator);
160
+ [usedUtils setMarkdownStyle:markdownStyle];
137
161
 
138
162
  // convert the attibuted string stored in state to
139
163
  // NSAttributedString
@@ -142,14 +166,15 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
142
166
  stateData.attributedStringBox);
143
167
 
144
168
  // Handles the first render, where the text stored in props is
145
- // different than the one stored in state. The one in state is empty,
146
- // while the one in props is passed from JS. If we don't update the
147
- // state here, we'll end up with a one-default-line-sized text input
169
+ // different than the one stored in state. The one in state is
170
+ // empty, while the one in props is passed from JS. If we don't
171
+ // update the state here, we'll end up with a one-default-line-sized
172
+ // text input
148
173
  if (textInputState.getRevision() == State::initialRevisionValue) {
149
- auto plainStringFromState =
150
- std::string([[nsAttributedString string] UTF8String]);
174
+ auto plainStringFromState =
175
+ std::string([[nsAttributedString string] UTF8String]);
151
176
 
152
- if (plainStringFromState != textInputProps.text) {
177
+ if (plainStringFromState != textInputProps.text) {
153
178
  // creates new AttributedString from props, adapted from
154
179
  // TextInputShadowNode (ios one, text inputs are
155
180
  // platform-specific)
@@ -164,14 +189,15 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
164
189
 
165
190
  // convert the newly created attributed string to
166
191
  // NSAttributedString
167
- nsAttributedString = RCTNSAttributedStringFromAttributedStringBox(
168
- AttributedStringBox{attributedString});
169
- }
192
+ nsAttributedString =
193
+ RCTNSAttributedStringFromAttributedStringBox(
194
+ AttributedStringBox{attributedString});
195
+ }
170
196
  }
171
197
 
172
198
  // apply markdown
173
- auto newString = [utils parseMarkdown:nsAttributedString
174
- withAttributes:defaultNSTextAttributes];
199
+ auto newString = [usedUtils parseMarkdown:nsAttributedString
200
+ withAttributes:defaultNSTextAttributes];
175
201
 
176
202
  // create a clone of the old TextInputState and update the
177
203
  // attributed string box to point to the string with markdown
@@ -187,10 +213,10 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
187
213
  });
188
214
  });
189
215
  } else if (stateData.attributedStringBox.getMode() ==
190
- AttributedStringBox::Mode::OpaquePointer) {
216
+ AttributedStringBox::Mode::OpaquePointer) {
191
217
  rootNode = rootNode->cloneTree(
192
218
  nodes.textInput->getFamily(),
193
- [&nodes, &textInputState, &stateData,
219
+ [&nodes, &textInputState, &stateData, &usedUtils,
194
220
  fontSizeMultiplier](const ShadowNode &node) {
195
221
  const auto &markdownProps = *std::static_pointer_cast<
196
222
  MarkdownTextInputDecoratorViewProps const>(
@@ -207,8 +233,9 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
207
233
  // this can possibly be optimized
208
234
  RCTMarkdownStyle *markdownStyle = [[RCTMarkdownStyle alloc]
209
235
  initWithStruct:markdownProps.markdownStyle];
210
- RCTMarkdownUtils *utils = [[RCTMarkdownUtils alloc] init];
211
- [utils setMarkdownStyle:markdownStyle];
236
+ usedUtils =
237
+ MarkdownCommitHook::getOrCreateMarkdownUtils(*nodes.decorator);
238
+ [usedUtils setMarkdownStyle:markdownStyle];
212
239
 
213
240
  // convert the attibuted string stored in state to
214
241
  // NSAttributedString
@@ -217,8 +244,8 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
217
244
  stateData.attributedStringBox);
218
245
 
219
246
  // apply markdown
220
- auto newString = [utils parseMarkdown:nsAttributedString
221
- withAttributes:defaultNSTextAttributes];
247
+ auto newString = [usedUtils parseMarkdown:nsAttributedString
248
+ withAttributes:defaultNSTextAttributes];
222
249
 
223
250
  // create a clone of the old TextInputState and update the
224
251
  // attributed string box to point to the string with markdown
@@ -234,6 +261,39 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
234
261
  });
235
262
  });
236
263
  }
264
+
265
+ // if usedUtils is not nil, then we did some work on the TextInput
266
+ // ShadowNode, we may need to update the decorator as well
267
+ if (usedUtils != nil) {
268
+ const auto ancestors =
269
+ nodes.decorator->getFamily().getAncestors(*rootNode);
270
+ const auto parentInfo = ancestors.back();
271
+ const auto decoratorNode =
272
+ std::static_pointer_cast<const MarkdownTextInputDecoratorShadowNode>(
273
+ parentInfo.first.get().getChildren().at(parentInfo.second));
274
+
275
+ // if usedUtils is defferent from the one kept in the decorator state, it
276
+ // needs to be updated to ensure memoization works correctly
277
+ if (usedUtils != MarkdownCommitHook::getMarkdownUtils(*decoratorNode)) {
278
+ const auto oldDecoratorState = *std::static_pointer_cast<
279
+ const ConcreteState<MarkdownTextInputDecoratorState>>(
280
+ decoratorNode->getState());
281
+ const auto newDecoratorState =
282
+ std::make_shared<const MarkdownTextInputDecoratorState>(
283
+ oldDecoratorState.getData().decoratorFamily,
284
+ wrapManagedObject(usedUtils));
285
+ const auto newDecoratorNode = decoratorNode->clone(
286
+ {.state = std::make_shared<
287
+ const ConcreteState<MarkdownTextInputDecoratorState>>(
288
+ newDecoratorState, oldDecoratorState)});
289
+
290
+ // since we did clone the path to the text input, parent node is mutable
291
+ // at this point - no need to clone the entire path
292
+ const auto mutableParent =
293
+ const_cast<ShadowNode *>(&parentInfo.first.get());
294
+ mutableParent->replaceChild(*decoratorNode, newDecoratorNode);
295
+ }
296
+ }
237
297
  }
238
298
 
239
299
  return std::static_pointer_cast<RootShadowNode>(rootNode);
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <memory>
3
4
  #include <react/renderer/core/ShadowNodeFamily.h>
4
5
 
5
6
  namespace facebook {
@@ -9,18 +10,25 @@ class JSI_EXPORT MarkdownTextInputDecoratorState final {
9
10
  public:
10
11
  using Shared = std::shared_ptr<const MarkdownTextInputDecoratorState>;
11
12
 
12
- MarkdownTextInputDecoratorState() : decoratorFamily(nullptr){};
13
+ MarkdownTextInputDecoratorState()
14
+ : decoratorFamily(nullptr), markdownUtils(nullptr){};
13
15
  MarkdownTextInputDecoratorState(
14
16
  const ShadowNodeFamily::Shared textInputFamily_)
15
- : decoratorFamily(textInputFamily_){};
17
+ : decoratorFamily(textInputFamily_), markdownUtils(nullptr){};
18
+ MarkdownTextInputDecoratorState(
19
+ const ShadowNodeFamily::Shared textInputFamily_,
20
+ const std::shared_ptr<void> markdownUtils_)
21
+ : decoratorFamily(textInputFamily_), markdownUtils(markdownUtils_){};
16
22
 
17
23
  #ifdef ANDROID
18
24
  MarkdownTextInputDecoratorState(
19
25
  MarkdownTextInputDecoratorState const &previousState, folly::dynamic data)
20
- : decoratorFamily(previousState.decoratorFamily){};
26
+ : decoratorFamily(previousState.decoratorFamily),
27
+ markdownUtils(nullptr){};
21
28
  #endif
22
29
 
23
30
  const ShadowNodeFamily::Shared decoratorFamily;
31
+ const std::shared_ptr<void> markdownUtils;
24
32
 
25
33
  #ifdef ANDROID
26
34
  folly::dynamic getDynamic() const {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expensify/react-native-live-markdown",
3
- "version": "0.1.184",
3
+ "version": "0.1.185",
4
4
  "description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",