@expensify/react-native-live-markdown 0.1.296 → 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 +11 -10
- package/android/build.gradle +2 -2
- package/android/src/main/new_arch/CMakeLists.txt +1 -0
- package/android/src/main/new_arch/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp +2 -0
- package/android/src/reactNativeVersionPatch/CustomMountingManager/{76 → 79}/com/expensify/livemarkdown/CustomMountingManager.java +49 -16
- package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/com/expensify/livemarkdown/CustomFabricUIManager.java +47 -0
- package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/com/expensify/livemarkdown/CustomMountingManager.java +25 -6
- package/package.json +10 -9
- /package/android/src/{main/java → reactNativeVersionPatch/CustomMountingManager/79}/com/expensify/livemarkdown/CustomFabricUIManager.java +0 -0
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.
|
|
194
|
-
| 0.1.
|
|
195
|
-
| 0.1.
|
|
196
|
-
| 0.1.
|
|
197
|
-
| 0.1.
|
|
198
|
-
| 0.1.
|
|
199
|
-
| 0.1.
|
|
200
|
-
| 0.1.
|
|
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
|
|
package/android/build.gradle
CHANGED
|
@@ -128,8 +128,8 @@ android {
|
|
|
128
128
|
]
|
|
129
129
|
|
|
130
130
|
// TextLayoutManager
|
|
131
|
-
if (REACT_NATIVE_MINOR_VERSION <=
|
|
132
|
-
java.srcDirs += "src/reactNativeVersionPatch/CustomMountingManager/
|
|
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
|
}
|
|
@@ -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 =
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
87
|
-
|
|
90
|
+
Method getTextAlignmentAttrMethod = textLayoutManagerClass.getDeclaredMethod("getTextAlignmentAttr", MapBuffer.class);
|
|
91
|
+
getTextAlignmentAttrMethod.setAccessible(true);
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
String textAlignmentAttr = (String)getTextAlignmentAttrMethod.invoke(null, attributedString);
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
Method getTextAlignmentMethod = textLayoutManagerClass.getDeclaredMethod("getTextAlignment", MapBuffer.class, Spannable.class, String.class);
|
|
96
|
+
getTextAlignmentMethod.setAccessible(true);
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
Layout.Alignment alignment = (Layout.Alignment)getTextAlignmentMethod.invoke(
|
|
99
|
+
null,
|
|
100
|
+
attributedString,
|
|
101
|
+
text,
|
|
102
|
+
textAlignmentAttr
|
|
103
|
+
);
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expensify/react-native-live-markdown",
|
|
3
|
-
"version": "0.1.
|
|
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": "
|
|
68
|
-
"@react-native-community/cli-platform-android": "
|
|
69
|
-
"@react-native-community/cli-platform-ios": "
|
|
70
|
-
"@react-native/eslint-config": "0.
|
|
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.
|
|
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": "
|
|
97
|
-
"react-
|
|
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.
|
|
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",
|