@expensify/react-native-live-markdown 0.1.197 → 0.1.199
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/android/src/main/java/com/expensify/livemarkdown/MarkdownFormatter.java +25 -9
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownParser.java +46 -33
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +9 -3
- package/apple/MarkdownLayoutManager.mm +19 -26
- package/apple/RCTMarkdownUtils.h +2 -1
- package/apple/RCTMarkdownUtils.mm +1 -6
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
|
|
8
8
|
|
|
9
9
|
import com.expensify.livemarkdown.spans.*;
|
|
10
10
|
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan;
|
|
11
|
+
import com.facebook.systrace.Systrace;
|
|
11
12
|
|
|
12
13
|
import java.util.List;
|
|
13
14
|
import java.util.Objects;
|
|
@@ -20,22 +21,37 @@ public class MarkdownFormatter {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
public void format(SpannableStringBuilder ssb, List<MarkdownRange> markdownRanges, @NonNull MarkdownStyle markdownStyle) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
try {
|
|
25
|
+
Systrace.beginSection(0, "format");
|
|
26
|
+
Objects.requireNonNull(markdownStyle, "mMarkdownStyle is null");
|
|
27
|
+
removeSpans(ssb);
|
|
28
|
+
applyRanges(ssb, markdownRanges, markdownStyle);
|
|
29
|
+
} finally {
|
|
30
|
+
Systrace.endSection(0);
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
private void removeSpans(SpannableStringBuilder ssb) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
ssb.
|
|
35
|
+
try {
|
|
36
|
+
Systrace.beginSection(0, "removeSpans");
|
|
37
|
+
// We shouldn't use `removeSpans()` because it also removes SpellcheckSpan, SuggestionSpan etc.
|
|
38
|
+
MarkdownSpan[] spans = ssb.getSpans(0, ssb.length(), MarkdownSpan.class);
|
|
39
|
+
for (MarkdownSpan span : spans) {
|
|
40
|
+
ssb.removeSpan(span);
|
|
41
|
+
}
|
|
42
|
+
} finally {
|
|
43
|
+
Systrace.endSection(0);
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
private void applyRanges(SpannableStringBuilder ssb, List<MarkdownRange> markdownRanges, @NonNull MarkdownStyle markdownStyle) {
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
try {
|
|
49
|
+
Systrace.beginSection(0, "applyRanges");
|
|
50
|
+
for (MarkdownRange markdownRange : markdownRanges) {
|
|
51
|
+
applyRange(ssb, markdownRange, markdownStyle);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
Systrace.endSection(0);
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
|
|
@@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
|
|
5
5
|
import com.facebook.react.bridge.ReactContext;
|
|
6
6
|
import com.facebook.react.util.RNLog;
|
|
7
7
|
import com.facebook.soloader.SoLoader;
|
|
8
|
+
import com.facebook.systrace.Systrace;
|
|
8
9
|
|
|
9
10
|
import org.json.JSONArray;
|
|
10
11
|
import org.json.JSONException;
|
|
@@ -31,46 +32,58 @@ public class MarkdownParser {
|
|
|
31
32
|
private native String nativeParse(String text, int parserId);
|
|
32
33
|
|
|
33
34
|
public synchronized List<MarkdownRange> parse(String text, int parserId) {
|
|
34
|
-
if (text.equals(mPrevText) && parserId == mPrevParserId) {
|
|
35
|
-
return mPrevMarkdownRanges;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
String json;
|
|
39
35
|
try {
|
|
40
|
-
|
|
41
|
-
} catch (Exception e) {
|
|
42
|
-
// Skip formatting, runGuarded will show the error in LogBox
|
|
43
|
-
mPrevText = text;
|
|
44
|
-
mPrevParserId = parserId;
|
|
45
|
-
mPrevMarkdownRanges = Collections.emptyList();
|
|
46
|
-
return mPrevMarkdownRanges;
|
|
47
|
-
}
|
|
36
|
+
Systrace.beginSection(0, "parse");
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
if (text.equals(mPrevText) && parserId == mPrevParserId) {
|
|
39
|
+
return mPrevMarkdownRanges;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
String json;
|
|
43
|
+
try {
|
|
44
|
+
Systrace.beginSection(0, "nativeParse");
|
|
45
|
+
json = nativeParse(text, parserId);
|
|
46
|
+
} catch (Exception e) {
|
|
47
|
+
// Skip formatting, runGuarded will show the error in LogBox
|
|
48
|
+
mPrevText = text;
|
|
49
|
+
mPrevParserId = parserId;
|
|
50
|
+
mPrevMarkdownRanges = Collections.emptyList();
|
|
51
|
+
return mPrevMarkdownRanges;
|
|
52
|
+
} finally {
|
|
53
|
+
Systrace.endSection(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
List<MarkdownRange> markdownRanges = new LinkedList<>();
|
|
57
|
+
try {
|
|
58
|
+
Systrace.beginSection(0, "markdownRanges");
|
|
59
|
+
JSONArray ranges = new JSONArray(json);
|
|
60
|
+
for (int i = 0; i < ranges.length(); i++) {
|
|
61
|
+
JSONObject range = ranges.getJSONObject(i);
|
|
62
|
+
String type = range.getString("type");
|
|
63
|
+
int start = range.getInt("start");
|
|
64
|
+
int length = range.getInt("length");
|
|
65
|
+
int depth = range.optInt("depth", 1);
|
|
66
|
+
if (length == 0 || start + length > text.length()) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
markdownRanges.add(new MarkdownRange(type, start, length, depth));
|
|
60
70
|
}
|
|
61
|
-
|
|
71
|
+
} catch (JSONException e) {
|
|
72
|
+
RNLog.w(mReactContext, "[react-native-live-markdown] Incorrect schema of worklet parser output: " + e.getMessage());
|
|
73
|
+
mPrevText = text;
|
|
74
|
+
mPrevParserId = parserId;
|
|
75
|
+
mPrevMarkdownRanges = Collections.emptyList();
|
|
76
|
+
return mPrevMarkdownRanges;
|
|
77
|
+
} finally {
|
|
78
|
+
Systrace.endSection(0);
|
|
62
79
|
}
|
|
63
|
-
|
|
64
|
-
RNLog.w(mReactContext, "[react-native-live-markdown] Incorrect schema of worklet parser output: " + e.getMessage());
|
|
80
|
+
|
|
65
81
|
mPrevText = text;
|
|
66
82
|
mPrevParserId = parserId;
|
|
67
|
-
mPrevMarkdownRanges =
|
|
83
|
+
mPrevMarkdownRanges = markdownRanges;
|
|
68
84
|
return mPrevMarkdownRanges;
|
|
85
|
+
} finally {
|
|
86
|
+
Systrace.endSection(0);
|
|
69
87
|
}
|
|
70
|
-
|
|
71
|
-
mPrevText = text;
|
|
72
|
-
mPrevParserId = parserId;
|
|
73
|
-
mPrevMarkdownRanges = markdownRanges;
|
|
74
|
-
return mPrevMarkdownRanges;
|
|
75
88
|
}
|
|
76
89
|
}
|
|
@@ -5,6 +5,7 @@ import android.text.SpannableStringBuilder;
|
|
|
5
5
|
import androidx.annotation.NonNull;
|
|
6
6
|
|
|
7
7
|
import com.facebook.react.bridge.ReactContext;
|
|
8
|
+
import com.facebook.systrace.Systrace;
|
|
8
9
|
|
|
9
10
|
import java.util.List;
|
|
10
11
|
|
|
@@ -29,8 +30,13 @@ public class MarkdownUtils {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
public void applyMarkdownFormatting(SpannableStringBuilder ssb) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
try {
|
|
34
|
+
Systrace.beginSection(0, "applyMarkdownFormatting");
|
|
35
|
+
String text = ssb.toString();
|
|
36
|
+
List<MarkdownRange> markdownRanges = mMarkdownParser.parse(text, mParserId);
|
|
37
|
+
mMarkdownFormatter.format(ssb, markdownRanges, mMarkdownStyle);
|
|
38
|
+
} finally {
|
|
39
|
+
Systrace.endSection(0);
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -5,34 +5,27 @@
|
|
|
5
5
|
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
|
|
6
6
|
[super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
|
|
7
7
|
|
|
8
|
+
NSTextStorage *textStorage = self.textStorage;
|
|
9
|
+
|
|
8
10
|
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
NSNumber *depth = [textStorage attribute:RCTLiveMarkdownBlockquoteDepthAttributeName atIndex:glyphRange.location effectiveRange:nil];
|
|
12
|
+
if (depth == nil) {
|
|
13
|
+
return; // not a blockquote
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
CGFloat paddingLeft = origin.x;
|
|
25
|
-
CGFloat paddingTop = origin.y;
|
|
26
|
-
CGFloat y = paddingTop + rect.origin.y;
|
|
27
|
-
CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
|
|
28
|
-
CGFloat height = rect.size.height;
|
|
29
|
-
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
|
|
30
|
-
for (int level = 0; level < currentDepth; level++) {
|
|
31
|
-
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
|
|
32
|
-
CGRect lineRect = CGRectMake(x, y, width, height);
|
|
33
|
-
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
|
|
34
|
-
UIRectFill(lineRect);
|
|
35
|
-
}
|
|
17
|
+
CGFloat paddingLeft = origin.x;
|
|
18
|
+
CGFloat paddingTop = origin.y;
|
|
19
|
+
CGFloat y = paddingTop + rect.origin.y;
|
|
20
|
+
CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
|
|
21
|
+
CGFloat height = rect.size.height;
|
|
22
|
+
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
|
|
23
|
+
|
|
24
|
+
for (NSUInteger level = 0; level < [depth unsignedIntValue]; level++) {
|
|
25
|
+
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
|
|
26
|
+
CGRect lineRect = CGRectMake(x, y, width, height);
|
|
27
|
+
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
|
|
28
|
+
UIRectFill(lineRect);
|
|
36
29
|
}
|
|
37
30
|
}];
|
|
38
31
|
}
|
package/apple/RCTMarkdownUtils.h
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
+
const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";
|
|
7
|
+
|
|
6
8
|
@interface RCTMarkdownUtils : NSObject
|
|
7
9
|
|
|
8
10
|
@property (nonatomic) RCTMarkdownStyle *markdownStyle;
|
|
9
11
|
@property (nonatomic) NSNumber *parserId;
|
|
10
|
-
@property (nonatomic) NSMutableArray<NSDictionary *> *blockquoteRangesAndLevels;
|
|
11
12
|
|
|
12
13
|
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withAttributes:(nullable NSDictionary<NSAttributedStringKey, id>*)attributes;
|
|
13
14
|
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
|
|
47
47
|
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];
|
|
48
48
|
|
|
49
|
-
_blockquoteRangesAndLevels = [NSMutableArray new];
|
|
50
|
-
|
|
51
49
|
for (MarkdownRange *markdownRange in markdownRanges) {
|
|
52
50
|
[self applyRangeToAttributedString:attributedString
|
|
53
51
|
type:std::string([markdownRange.type UTF8String])
|
|
@@ -134,10 +132,7 @@
|
|
|
134
132
|
paragraphStyle.firstLineHeadIndent = indent;
|
|
135
133
|
paragraphStyle.headIndent = indent;
|
|
136
134
|
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
|
137
|
-
[
|
|
138
|
-
@"range": [NSValue valueWithRange:range],
|
|
139
|
-
@"depth": @(depth)
|
|
140
|
-
}];
|
|
135
|
+
[attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range];
|
|
141
136
|
} else if (type == "pre") {
|
|
142
137
|
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range];
|
|
143
138
|
NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range;
|
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.199",
|
|
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",
|