@expensify/react-native-live-markdown 0.1.295 → 0.1.297

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -188,16 +188,17 @@ Currently, `react-native-live-markdown` supports only [ExpensiMark](https://gith
188
188
 
189
189
  `react-native-live-markdown` supports only latest React Native minor releases with the New Architecture enabled.
190
190
 
191
- | @expensify/react-native-live-markdown | 0.73 | 0.74 | 0.75 | 0.76 | 0.77 | 0.78 | 0.79 |
192
- | :-----------------------------------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
193
- | 0.1.260+ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
194
- | 0.1.256 – 0.1.259 | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
195
- | 0.1.248 – 0.1.255 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
196
- | 0.1.235 – 0.1.247 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
197
- | 0.1.141 – 0.1.234 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
198
- | 0.1.129 – 0.1.140 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
199
- | 0.1.122 – 0.1.128 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
200
- | 0.1.15 – 0.1.121 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
191
+ | @expensify/react-native-live-markdown | 0.73 | 0.74 | 0.75 | 0.76 | 0.77 | 0.78 | 0.79 | 0.80 |
192
+ | :-----------------------------------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
193
+ | 0.1.297+ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
194
+ | 0.1.260 – 0.1.296 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ |
195
+ | 0.1.256 – 0.1.259 | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
196
+ | 0.1.248 – 0.1.255 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
197
+ | 0.1.235 – 0.1.247 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
198
+ | 0.1.141 – 0.1.234 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
199
+ | 0.1.129 – 0.1.140 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
200
+ | 0.1.122 – 0.1.128 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
201
+ | 0.1.15 – 0.1.121 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
201
202
 
202
203
  ## License
203
204
 
@@ -128,8 +128,8 @@ android {
128
128
  ]
129
129
 
130
130
  // TextLayoutManager
131
- if (REACT_NATIVE_MINOR_VERSION <= 76 ) {
132
- java.srcDirs += "src/reactNativeVersionPatch/CustomMountingManager/76"
131
+ if (REACT_NATIVE_MINOR_VERSION <= 79 ) {
132
+ java.srcDirs += "src/reactNativeVersionPatch/CustomMountingManager/79"
133
133
  } else {
134
134
  java.srcDirs += "src/reactNativeVersionPatch/CustomMountingManager/latest"
135
135
  }
@@ -51,6 +51,7 @@ target_compile_options(
51
51
  -frtti
52
52
  -Wall
53
53
  -std=c++20
54
+ -DREACT_NATIVE_MINOR_VERSION=${ReactAndroid_VERSION_MINOR}
54
55
  )
55
56
 
56
57
  target_include_directories(
@@ -3,7 +3,9 @@
3
3
  #include <fbjni/fbjni.h>
4
4
  #include <react/fabric/JFabricUIManager.h>
5
5
  #include <react/jni/ReadableNativeMap.h>
6
+ #if REACT_NATIVE_MINOR_VERSION < 80
6
7
  #include <react/jni/SafeReleaseJniRef.h>
8
+ #endif // REACT_NATIVE_MINOR_VERSION < 80
7
9
  #include <react/renderer/components/view/conversions.h>
8
10
  #include <react/renderer/core/ComponentDescriptor.h>
9
11
  #include <yoga/Yoga.h>
@@ -3,7 +3,6 @@ package com.expensify.livemarkdown;
3
3
  import static com.facebook.react.views.text.TextAttributeProps.UNSET;
4
4
 
5
5
  import android.content.Context;
6
- import android.content.res.AssetManager;
7
6
  import android.text.BoringLayout;
8
7
  import android.text.Layout;
9
8
  import android.text.Spannable;
@@ -12,6 +11,7 @@ import android.text.TextPaint;
12
11
 
13
12
  import androidx.annotation.NonNull;
14
13
  import androidx.annotation.Nullable;
14
+ import androidx.core.util.Preconditions;
15
15
 
16
16
  import com.facebook.react.bridge.ReactContext;
17
17
  import com.facebook.react.bridge.ReadableMap;
@@ -21,6 +21,7 @@ import com.facebook.react.uimanager.PixelUtil;
21
21
  import com.facebook.react.uimanager.ViewManagerRegistry;
22
22
  import com.facebook.react.views.text.TextAttributeProps;
23
23
  import com.facebook.react.views.text.TextLayoutManager;
24
+ import com.facebook.react.views.text.internal.span.ReactTextPaintHolderSpan;
24
25
  import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan;
25
26
  import com.facebook.yoga.YogaMeasureMode;
26
27
  import com.facebook.yoga.YogaMeasureOutput;
@@ -30,7 +31,13 @@ import java.lang.reflect.Method;
30
31
 
31
32
  public class CustomMountingManager extends MountingManager {
32
33
  private static final boolean DEFAULT_INCLUDE_FONT_PADDING = true;
33
- private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
34
+ private static final ThreadLocal<TextPaint> sTextPaintInstance =
35
+ new ThreadLocal<TextPaint>() {
36
+ @Override
37
+ protected TextPaint initialValue() {
38
+ return new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
39
+ }
40
+ };
34
41
 
35
42
  private MarkdownUtils markdownUtils;
36
43
 
@@ -77,24 +84,48 @@ public class CustomMountingManager extends MountingManager {
77
84
  TextAttributeProps.getHyphenationFrequency(
78
85
  paragraphAttributes.getString(TextLayoutManager.PA_KEY_HYPHENATION_FREQUENCY));
79
86
 
80
- // StaticLayout returns wrong metrics for the last line if it's empty, add something to the
81
- // last line so it's measured correctly
82
- if (text.toString().endsWith("\n")) {
83
- SpannableStringBuilder sb = new SpannableStringBuilder(text);
84
- sb.append("I");
87
+ try {
88
+ Class<TextLayoutManager> textLayoutManagerClass = TextLayoutManager.class;
85
89
 
86
- text = sb;
87
- }
90
+ Method getTextAlignmentAttrMethod = textLayoutManagerClass.getDeclaredMethod("getTextAlignmentAttr", MapBuffer.class);
91
+ getTextAlignmentAttrMethod.setAccessible(true);
88
92
 
89
- Layout.Alignment alignment = TextLayoutManager.getTextAlignment(attributedString, text);
93
+ String textAlignmentAttr = (String)getTextAlignmentAttrMethod.invoke(null, attributedString);
90
94
 
91
- markdownUtils.applyMarkdownFormatting((SpannableStringBuilder)text);
95
+ Method getTextAlignmentMethod = textLayoutManagerClass.getDeclaredMethod("getTextAlignment", MapBuffer.class, Spannable.class, String.class);
96
+ getTextAlignmentMethod.setAccessible(true);
92
97
 
93
- BoringLayout.Metrics boring = BoringLayout.isBoring(text, sTextPaintInstance);
98
+ Layout.Alignment alignment = (Layout.Alignment)getTextAlignmentMethod.invoke(
99
+ null,
100
+ attributedString,
101
+ text,
102
+ textAlignmentAttr
103
+ );
94
104
 
95
- Class<TextLayoutManager> mapBufferClass = TextLayoutManager.class;
96
- try {
97
- Method createLayoutMethod = mapBufferClass.getDeclaredMethod("createLayout", Spannable.class, BoringLayout.Metrics.class, float.class, YogaMeasureMode.class, boolean.class, int.class, int.class, Layout.Alignment.class);
105
+ Method getTextJustificationModeMethod = textLayoutManagerClass.getDeclaredMethod("getTextJustificationMode", String.class);
106
+ getTextJustificationModeMethod.setAccessible(true);
107
+
108
+ Integer justificationMode = (Integer) getTextJustificationModeMethod.invoke(null, textAlignmentAttr);
109
+
110
+
111
+ markdownUtils.applyMarkdownFormatting((SpannableStringBuilder)text);
112
+
113
+ TextPaint paint;
114
+ if (attributedString.contains(TextLayoutManager.AS_KEY_CACHE_ID)) {
115
+ paint = text.getSpans(0, 0, ReactTextPaintHolderSpan.class)[0].getTextPaint();
116
+ } else {
117
+ TextAttributeProps baseTextAttributes =
118
+ TextAttributeProps.fromMapBuffer(attributedString.getMapBuffer(TextLayoutManager.AS_KEY_BASE_ATTRIBUTES));
119
+ paint = Preconditions.checkNotNull(sTextPaintInstance.get());
120
+
121
+ Method updateTextPaintMethod = textLayoutManagerClass.getDeclaredMethod("updateTextPaint", TextPaint.class, TextAttributeProps.class, Context.class);
122
+ updateTextPaintMethod.setAccessible(true);
123
+ updateTextPaintMethod.invoke(null, paint, baseTextAttributes, context);
124
+ }
125
+
126
+ BoringLayout.Metrics boring = BoringLayout.isBoring(text, paint);
127
+
128
+ Method createLayoutMethod = textLayoutManagerClass.getDeclaredMethod("createLayout", Spannable.class, BoringLayout.Metrics.class, float.class, YogaMeasureMode.class, boolean.class, int.class, int.class, Layout.Alignment.class, int.class, TextPaint.class);
98
129
  createLayoutMethod.setAccessible(true);
99
130
 
100
131
  Layout layout = (Layout)createLayoutMethod.invoke(
@@ -106,7 +137,9 @@ public class CustomMountingManager extends MountingManager {
106
137
  includeFontPadding,
107
138
  textBreakStrategy,
108
139
  hyphenationFrequency,
109
- alignment);
140
+ alignment,
141
+ justificationMode,
142
+ paint);
110
143
 
111
144
  int maximumNumberOfLines =
112
145
  paragraphAttributes.contains(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
@@ -0,0 +1,47 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext;
4
+ import com.facebook.react.bridge.ReadableMap;
5
+ import com.facebook.react.fabric.FabricUIManager;
6
+ import com.facebook.react.fabric.mounting.MountingManager;
7
+ import com.facebook.react.uimanager.ViewManagerRegistry;
8
+ import com.facebook.react.uimanager.events.BatchEventDispatchedListener;
9
+
10
+ import java.lang.reflect.Field;
11
+
12
+ public class CustomFabricUIManager {
13
+
14
+ public static FabricUIManager create(FabricUIManager source, ReadableMap markdownProps, int parserId) {
15
+ Class<? extends FabricUIManager> uiManagerClass = source.getClass();
16
+
17
+ try {
18
+ Field mountingManagerField = uiManagerClass.getDeclaredField("mMountingManager");
19
+ mountingManagerField.setAccessible(true);
20
+
21
+ MountingManager sourceMountingManager = readPrivateField(source, "mMountingManager");
22
+ ReactApplicationContext reactContext = readPrivateField(source, "mReactApplicationContext");
23
+ ViewManagerRegistry viewManagerRegistry = readPrivateField(source, "mViewManagerRegistry");
24
+ BatchEventDispatchedListener batchEventDispatchedListener = readPrivateField(source, "mBatchEventDispatchedListener");
25
+ MountingManager.MountItemExecutor mountItemExecutor = readPrivateField(source, "mMountItemExecutor");
26
+
27
+ FabricUIManager customFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, batchEventDispatchedListener);
28
+
29
+ mountingManagerField.set(customFabricUIManager, new CustomMountingManager(sourceMountingManager, viewManagerRegistry, mountItemExecutor, reactContext, markdownProps, parserId));
30
+
31
+ return customFabricUIManager;
32
+ } catch (NoSuchFieldException | IllegalAccessException e) {
33
+ throw new RuntimeException("[LiveMarkdown] Cannot read data from FabricUIManager");
34
+ }
35
+ }
36
+
37
+ @SuppressWarnings("unchecked")
38
+ private static <T> T readPrivateField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
39
+ Class<?> clazz = obj.getClass();
40
+
41
+ Field field = clazz.getDeclaredField(name);
42
+ field.setAccessible(true);
43
+ T value = (T) field.get(obj);
44
+
45
+ return value;
46
+ }
47
+ }
@@ -8,6 +8,7 @@ import android.text.Layout;
8
8
  import android.text.Spannable;
9
9
  import android.text.SpannableStringBuilder;
10
10
  import android.text.TextPaint;
11
+ import android.text.TextUtils;
11
12
 
12
13
  import androidx.annotation.NonNull;
13
14
  import androidx.annotation.Nullable;
@@ -15,8 +16,10 @@ import androidx.core.util.Preconditions;
15
16
 
16
17
  import com.facebook.react.bridge.ReactContext;
17
18
  import com.facebook.react.bridge.ReadableMap;
19
+ import com.facebook.react.common.ReactConstants;
18
20
  import com.facebook.react.common.mapbuffer.MapBuffer;
19
21
  import com.facebook.react.fabric.mounting.MountingManager;
22
+ import com.facebook.react.fabric.mounting.SurfaceMountingManager;
20
23
  import com.facebook.react.uimanager.PixelUtil;
21
24
  import com.facebook.react.uimanager.ViewManagerRegistry;
22
25
  import com.facebook.react.views.text.TextAttributeProps;
@@ -39,20 +42,28 @@ public class CustomMountingManager extends MountingManager {
39
42
  }
40
43
  };
41
44
 
45
+ private MountingManager sourceMountingManager;
42
46
  private MarkdownUtils markdownUtils;
43
47
 
44
48
  public CustomMountingManager(
49
+ @NonNull MountingManager sourceMountingManager,
45
50
  @NonNull ViewManagerRegistry viewManagerRegistry,
46
51
  @NonNull MountItemExecutor mountItemExecutor,
47
52
  @NonNull Context context,
48
53
  @NonNull ReadableMap decoratorProps,
49
54
  int parserId) {
50
55
  super(viewManagerRegistry, mountItemExecutor);
56
+ this.sourceMountingManager = sourceMountingManager;
51
57
  this.markdownUtils = new MarkdownUtils((ReactContext) context);
52
58
  this.markdownUtils.setMarkdownStyle(new MarkdownStyle(decoratorProps, context));
53
59
  this.markdownUtils.setParserId(parserId);
54
60
  }
55
61
 
62
+ @Override
63
+ public SurfaceMountingManager getSurfaceManagerEnforced(int surfaceId, String context) {
64
+ return sourceMountingManager.getSurfaceManagerEnforced(surfaceId, context);
65
+ }
66
+
56
67
  @Override
57
68
  public long measureMapBuffer(
58
69
  @NonNull ReactContext context,
@@ -125,9 +136,20 @@ public class CustomMountingManager extends MountingManager {
125
136
 
126
137
  BoringLayout.Metrics boring = BoringLayout.isBoring(text, paint);
127
138
 
128
- Method createLayoutMethod = textLayoutManagerClass.getDeclaredMethod("createLayout", Spannable.class, BoringLayout.Metrics.class, float.class, YogaMeasureMode.class, boolean.class, int.class, int.class, Layout.Alignment.class, int.class, TextPaint.class);
139
+ Method createLayoutMethod = textLayoutManagerClass.getDeclaredMethod("createLayout", Spannable.class, BoringLayout.Metrics.class, float.class, YogaMeasureMode.class, boolean.class, int.class, int.class, Layout.Alignment.class, int.class, TextUtils.TruncateAt.class, int.class, TextPaint.class);
129
140
  createLayoutMethod.setAccessible(true);
130
141
 
142
+ int maximumNumberOfLines =
143
+ paragraphAttributes.contains(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
144
+ ? paragraphAttributes.getInt(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
145
+ : ReactConstants.UNSET;
146
+ @Nullable
147
+ TextUtils.TruncateAt ellipsizeMode =
148
+ paragraphAttributes.contains(TextLayoutManager.PA_KEY_ELLIPSIZE_MODE)
149
+ ? TextAttributeProps.getEllipsizeMode(
150
+ paragraphAttributes.getString(TextLayoutManager.PA_KEY_ELLIPSIZE_MODE))
151
+ : null;
152
+
131
153
  Layout layout = (Layout)createLayoutMethod.invoke(
132
154
  null,
133
155
  text,
@@ -139,13 +161,10 @@ public class CustomMountingManager extends MountingManager {
139
161
  hyphenationFrequency,
140
162
  alignment,
141
163
  justificationMode,
164
+ ellipsizeMode,
165
+ maximumNumberOfLines,
142
166
  paint);
143
167
 
144
- int maximumNumberOfLines =
145
- paragraphAttributes.contains(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
146
- ? paragraphAttributes.getInt(TextLayoutManager.PA_KEY_MAX_NUMBER_OF_LINES)
147
- : UNSET;
148
-
149
168
  int calculatedLineCount =
150
169
  maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
151
170
  ? layout.getLineCount()
@@ -3,13 +3,13 @@
3
3
  @implementation MarkdownRange
4
4
 
5
5
  - (instancetype)initWithType:(NSString *)type range:(NSRange)range depth:(NSUInteger)depth {
6
- self = [super init];
7
- if (self) {
8
- _type = type;
9
- _range = range;
10
- _depth = depth;
11
- }
12
- return self;
6
+ self = [super init];
7
+ if (self) {
8
+ _type = type;
9
+ _range = range;
10
+ _depth = depth;
11
+ }
12
+ return self;
13
13
  }
14
14
 
15
15
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expensify/react-native-live-markdown",
3
- "version": "0.1.295",
3
+ "version": "0.1.297",
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",
@@ -64,13 +64,13 @@
64
64
  "@commitlint/config-conventional": "^17.0.2",
65
65
  "@evilmartians/lefthook": "^1.5.0",
66
66
  "@lwc/eslint-plugin-lwc": "^1.7.2",
67
- "@react-native-community/cli": "18.0.0",
68
- "@react-native-community/cli-platform-android": "18.0.0",
69
- "@react-native-community/cli-platform-ios": "18.0.0",
70
- "@react-native/eslint-config": "0.79.2",
67
+ "@react-native-community/cli": "19.0.0",
68
+ "@react-native-community/cli-platform-android": "19.0.0",
69
+ "@react-native-community/cli-platform-ios": "19.0.0",
70
+ "@react-native/eslint-config": "0.80.1",
71
71
  "@release-it/conventional-changelog": "^5.0.0",
72
72
  "@types/jest": "^29.5.14",
73
- "@types/react": "^19.0.0",
73
+ "@types/react": "^19.1.0",
74
74
  "@typescript-eslint/eslint-plugin": "^6.15.0",
75
75
  "@typescript-eslint/parser": "^6.15.0",
76
76
  "del-cli": "^5.0.0",
@@ -93,10 +93,11 @@
93
93
  "nodemon": "^3.1.3",
94
94
  "patch-package": "^8.0.0",
95
95
  "prettier": "2.8.8",
96
- "react": "^19.0.0",
97
- "react-native": "0.79.2",
96
+ "react": "19.1.0",
97
+ "react-dom": "19.1.0",
98
+ "react-native": "0.80.1",
98
99
  "react-native-builder-bob": "^0.30.2",
99
- "react-native-reanimated": "3.17.3",
100
+ "react-native-reanimated": "3.18.0",
100
101
  "react-native-web": "^0.20.0",
101
102
  "release-it": "^15.0.0",
102
103
  "turbo": "^1.10.7",