@expensify/react-native-live-markdown 0.1.202 → 0.1.204
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/apple/MarkdownCommitHook.mm +2 -2
- package/apple/MarkdownFormatter.h +18 -0
- package/apple/MarkdownFormatter.mm +158 -0
- package/apple/MarkdownLayoutManager.h +1 -0
- package/apple/MarkdownParser.h +2 -1
- package/apple/MarkdownParser.mm +3 -1
- package/apple/RCTBackedTextFieldDelegateAdapter+Markdown.mm +1 -1
- package/apple/RCTBaseTextInputView+Markdown.mm +2 -2
- package/apple/RCTMarkdownUtils.h +2 -3
- package/apple/RCTMarkdownUtils.mm +23 -156
- package/apple/RCTTextInputComponentView+Markdown.mm +2 -2
- package/apple/RCTUITextView+Markdown.mm +1 -1
- package/package.json +1 -1
|
@@ -198,7 +198,7 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
|
|
|
198
198
|
|
|
199
199
|
// apply markdown
|
|
200
200
|
auto newString = [usedUtils parseMarkdown:nsAttributedString
|
|
201
|
-
|
|
201
|
+
withDefaultTextAttributes:defaultNSTextAttributes];
|
|
202
202
|
|
|
203
203
|
// create a clone of the old TextInputState and update the
|
|
204
204
|
// attributed string box to point to the string with markdown
|
|
@@ -247,7 +247,7 @@ RootShadowNode::Unshared MarkdownCommitHook::shadowTreeWillCommit(
|
|
|
247
247
|
|
|
248
248
|
// apply markdown
|
|
249
249
|
auto newString = [usedUtils parseMarkdown:nsAttributedString
|
|
250
|
-
|
|
250
|
+
withDefaultTextAttributes:defaultNSTextAttributes];
|
|
251
251
|
|
|
252
252
|
// create a clone of the old TextInputState and update the
|
|
253
253
|
// attributed string box to point to the string with markdown
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <RNLiveMarkdown/MarkdownRange.h>
|
|
3
|
+
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";
|
|
8
|
+
|
|
9
|
+
@interface MarkdownFormatter : NSObject
|
|
10
|
+
|
|
11
|
+
- (nonnull NSAttributedString *)format:(nonnull NSString *)text
|
|
12
|
+
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
|
13
|
+
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
|
|
14
|
+
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle;
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_END
|
|
17
|
+
|
|
18
|
+
@end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#import "MarkdownFormatter.h"
|
|
2
|
+
#import <React/RCTFont.h>
|
|
3
|
+
|
|
4
|
+
@implementation MarkdownFormatter
|
|
5
|
+
|
|
6
|
+
- (nonnull NSAttributedString *)format:(nonnull NSString *)text
|
|
7
|
+
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
|
8
|
+
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
|
|
9
|
+
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
|
|
10
|
+
{
|
|
11
|
+
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:defaultTextAttributes];
|
|
12
|
+
|
|
13
|
+
[attributedString beginEditing];
|
|
14
|
+
|
|
15
|
+
// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
|
|
16
|
+
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
|
|
17
|
+
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
|
|
18
|
+
[attributedString addAttribute:NSUnderlineStyleAttributeName
|
|
19
|
+
value:[NSNumber numberWithInteger:NSUnderlineStyleNone]
|
|
20
|
+
range:NSMakeRange(0, attributedString.length)];
|
|
21
|
+
|
|
22
|
+
for (MarkdownRange *markdownRange in markdownRanges) {
|
|
23
|
+
[self applyRangeToAttributedString:attributedString
|
|
24
|
+
type:std::string([markdownRange.type UTF8String])
|
|
25
|
+
range:markdownRange.range
|
|
26
|
+
depth:markdownRange.depth
|
|
27
|
+
markdownStyle:markdownStyle];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
RCTApplyBaselineOffset(attributedString);
|
|
31
|
+
|
|
32
|
+
[attributedString endEditing];
|
|
33
|
+
|
|
34
|
+
return attributedString;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString
|
|
38
|
+
type:(const std::string)type
|
|
39
|
+
range:(const NSRange)range
|
|
40
|
+
depth:(const int)depth
|
|
41
|
+
markdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle {
|
|
42
|
+
if (type == "bold" || type == "italic" || type == "code" || type == "pre" || type == "h1" || type == "emoji") {
|
|
43
|
+
UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:range.location effectiveRange:NULL];
|
|
44
|
+
if (type == "bold") {
|
|
45
|
+
font = [RCTFont updateFont:font withWeight:@"bold"];
|
|
46
|
+
} else if (type == "italic") {
|
|
47
|
+
font = [RCTFont updateFont:font withStyle:@"italic"];
|
|
48
|
+
} else if (type == "code") {
|
|
49
|
+
font = [RCTFont updateFont:font withFamily:markdownStyle.codeFontFamily
|
|
50
|
+
size:[NSNumber numberWithFloat:markdownStyle.codeFontSize]
|
|
51
|
+
weight:nil
|
|
52
|
+
style:nil
|
|
53
|
+
variant:nil
|
|
54
|
+
scaleMultiplier:0];
|
|
55
|
+
} else if (type == "pre") {
|
|
56
|
+
font = [RCTFont updateFont:font withFamily:markdownStyle.preFontFamily
|
|
57
|
+
size:[NSNumber numberWithFloat:markdownStyle.preFontSize]
|
|
58
|
+
weight:nil
|
|
59
|
+
style:nil
|
|
60
|
+
variant:nil
|
|
61
|
+
scaleMultiplier:0];
|
|
62
|
+
} else if (type == "h1") {
|
|
63
|
+
font = [RCTFont updateFont:font withFamily:nil
|
|
64
|
+
size:[NSNumber numberWithFloat:markdownStyle.h1FontSize]
|
|
65
|
+
weight:@"bold"
|
|
66
|
+
style:nil
|
|
67
|
+
variant:nil
|
|
68
|
+
scaleMultiplier:0];
|
|
69
|
+
} else if (type == "emoji") {
|
|
70
|
+
font = [RCTFont updateFont:font withFamily:nil
|
|
71
|
+
size:[NSNumber numberWithFloat:markdownStyle.emojiFontSize]
|
|
72
|
+
weight:nil
|
|
73
|
+
style:nil
|
|
74
|
+
variant:nil
|
|
75
|
+
scaleMultiplier:0];
|
|
76
|
+
}
|
|
77
|
+
[attributedString addAttribute:NSFontAttributeName value:font range:range];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (type == "syntax") {
|
|
81
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.syntaxColor range:range];
|
|
82
|
+
} else if (type == "strikethrough") {
|
|
83
|
+
[attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
84
|
+
} else if (type == "code") {
|
|
85
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.codeColor range:range];
|
|
86
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.codeBackgroundColor range:range];
|
|
87
|
+
} else if (type == "mention-here") {
|
|
88
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionHereColor range:range];
|
|
89
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionHereBackgroundColor range:range];
|
|
90
|
+
} else if (type == "mention-user") {
|
|
91
|
+
// TODO: change mention color when it mentions current user
|
|
92
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionUserColor range:range];
|
|
93
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionUserBackgroundColor range:range];
|
|
94
|
+
} else if (type == "mention-report") {
|
|
95
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.mentionReportColor range:range];
|
|
96
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.mentionReportBackgroundColor range:range];
|
|
97
|
+
} else if (type == "link") {
|
|
98
|
+
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
99
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.linkColor range:range];
|
|
100
|
+
} else if (type == "blockquote") {
|
|
101
|
+
CGFloat indent = (markdownStyle.blockquoteMarginLeft + markdownStyle.blockquoteBorderWidth + markdownStyle.blockquotePaddingLeft) * depth;
|
|
102
|
+
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
|
103
|
+
paragraphStyle.firstLineHeadIndent = indent;
|
|
104
|
+
paragraphStyle.headIndent = indent;
|
|
105
|
+
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
|
106
|
+
[attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range];
|
|
107
|
+
} else if (type == "pre") {
|
|
108
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:markdownStyle.preColor range:range];
|
|
109
|
+
NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range;
|
|
110
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:markdownStyle.preBackgroundColor range:rangeForBackground];
|
|
111
|
+
// TODO: pass background color and ranges to layout manager
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
|
|
116
|
+
{
|
|
117
|
+
__block CGFloat maximumLineHeight = 0;
|
|
118
|
+
|
|
119
|
+
[attributedText enumerateAttribute:NSParagraphStyleAttributeName
|
|
120
|
+
inRange:NSMakeRange(0, attributedText.length)
|
|
121
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
122
|
+
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
|
|
123
|
+
if (!paragraphStyle) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
|
|
128
|
+
}];
|
|
129
|
+
|
|
130
|
+
if (maximumLineHeight == 0) {
|
|
131
|
+
// `lineHeight` was not specified, nothing to do.
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
__block CGFloat maximumFontLineHeight = 0;
|
|
136
|
+
|
|
137
|
+
[attributedText enumerateAttribute:NSFontAttributeName
|
|
138
|
+
inRange:NSMakeRange(0, attributedText.length)
|
|
139
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
140
|
+
usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
|
141
|
+
if (!font) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
|
|
146
|
+
}];
|
|
147
|
+
|
|
148
|
+
if (maximumLineHeight < maximumFontLineHeight) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
CGFloat baseLineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0;
|
|
153
|
+
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
|
154
|
+
value:@(baseLineOffset)
|
|
155
|
+
range:NSMakeRange(0, attributedText.length)];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@end
|
package/apple/MarkdownParser.h
CHANGED
|
@@ -5,7 +5,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
5
5
|
|
|
6
6
|
@interface MarkdownParser : NSObject
|
|
7
7
|
|
|
8
|
-
- (NSArray<MarkdownRange *> *)parse:(NSString *)text
|
|
8
|
+
- (NSArray<MarkdownRange *> *)parse:(nonnull NSString *)text
|
|
9
|
+
withParserId:(nonnull NSNumber *)parserId;
|
|
9
10
|
|
|
10
11
|
NS_ASSUME_NONNULL_END
|
|
11
12
|
|
package/apple/MarkdownParser.mm
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
NSArray<MarkdownRange *> *_prevMarkdownRanges;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
- (NSArray<MarkdownRange *> *)parse:(NSString *)text
|
|
12
|
+
- (NSArray<MarkdownRange *> *)parse:(nonnull NSString *)text
|
|
13
|
+
withParserId:(nonnull NSNumber *)parserId
|
|
14
|
+
{
|
|
13
15
|
@synchronized (self) {
|
|
14
16
|
if ([text isEqualToString:_prevText] && [parserId isEqualToNumber:_prevParserId]) {
|
|
15
17
|
return _prevMarkdownRanges;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
if (markdownUtils != nil) {
|
|
20
20
|
RCTUITextField *backedTextInputView = [self valueForKey:@"_backedTextInputView"];
|
|
21
21
|
UITextRange *range = backedTextInputView.selectedTextRange;
|
|
22
|
-
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText
|
|
22
|
+
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText withDefaultTextAttributes:backedTextInputView.defaultTextAttributes];
|
|
23
23
|
[backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
18
18
|
if (markdownUtils != nil) {
|
|
19
|
-
attributedText = [markdownUtils parseMarkdown:attributedText
|
|
19
|
+
attributedText = [markdownUtils parseMarkdown:attributedText withDefaultTextAttributes:self.backedTextInputView.defaultTextAttributes];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Call the original method
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
if (markdownUtils != nil) {
|
|
47
47
|
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
|
|
48
48
|
NSAttributedString *oldAttributedText = backedTextInputView.attributedText;
|
|
49
|
-
NSAttributedString *newAttributedText = [markdownUtils parseMarkdown:oldAttributedText
|
|
49
|
+
NSAttributedString *newAttributedText = [markdownUtils parseMarkdown:oldAttributedText withDefaultTextAttributes:backedTextInputView.defaultTextAttributes];
|
|
50
50
|
UITextRange *range = backedTextInputView.selectedTextRange;
|
|
51
51
|
|
|
52
52
|
// update attributed text without emitting onSelectionChange event
|
package/apple/RCTMarkdownUtils.h
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
-
const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";
|
|
7
|
-
|
|
8
6
|
@interface RCTMarkdownUtils : NSObject
|
|
9
7
|
|
|
10
8
|
@property (nonatomic) RCTMarkdownStyle *markdownStyle;
|
|
11
9
|
@property (nonatomic) NSNumber *parserId;
|
|
12
10
|
|
|
13
|
-
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
|
|
11
|
+
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
|
|
12
|
+
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes;
|
|
14
13
|
|
|
15
14
|
@end
|
|
16
15
|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
2
|
-
#import <RNLiveMarkdown/MarkdownGlobal.h>
|
|
3
2
|
#import <RNLiveMarkdown/MarkdownParser.h>
|
|
4
|
-
#import
|
|
5
|
-
#import <React/RCTAssert.h>
|
|
6
|
-
#import <React/RCTFont.h>
|
|
7
|
-
#include <jsi/jsi.h>
|
|
3
|
+
#import <RNLiveMarkdown/MarkdownFormatter.h>
|
|
8
4
|
|
|
9
5
|
@implementation RCTMarkdownUtils {
|
|
10
6
|
MarkdownParser *_markdownParser;
|
|
7
|
+
MarkdownFormatter *_markdownFormatter;
|
|
11
8
|
NSString *_prevInputString;
|
|
12
9
|
NSAttributedString *_prevAttributedString;
|
|
13
|
-
NSDictionary<NSAttributedStringKey, id> *
|
|
10
|
+
NSDictionary<NSAttributedStringKey, id> *_prevDefaultTextAttributes;
|
|
14
11
|
__weak RCTMarkdownStyle *_prevMarkdownStyle;
|
|
15
12
|
__weak NSNumber *_prevParserId;
|
|
16
13
|
}
|
|
@@ -19,169 +16,39 @@
|
|
|
19
16
|
{
|
|
20
17
|
if (self = [super init]) {
|
|
21
18
|
_markdownParser = [MarkdownParser new];
|
|
19
|
+
_markdownFormatter = [MarkdownFormatter new];
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
return self;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
|
|
25
|
+
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
|
|
26
|
+
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
|
|
28
27
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
NSString *inputString = [input string];
|
|
35
|
-
if ([inputString isEqualToString:_prevInputString] && [attributes isEqualToDictionary:_prevTextAttributes] && [_markdownStyle isEqual:_prevMarkdownStyle] && [_parserId isEqualToNumber:_prevParserId]) {
|
|
36
|
-
return _prevAttributedString;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
NSArray<MarkdownRange *> *markdownRanges = [_markdownParser parse:inputString withParserId:_parserId];
|
|
40
|
-
|
|
41
|
-
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes];
|
|
42
|
-
[attributedString beginEditing];
|
|
43
|
-
|
|
44
|
-
// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
|
|
45
|
-
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
|
|
46
|
-
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
|
|
47
|
-
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];
|
|
48
|
-
|
|
49
|
-
for (MarkdownRange *markdownRange in markdownRanges) {
|
|
50
|
-
[self applyRangeToAttributedString:attributedString
|
|
51
|
-
type:std::string([markdownRange.type UTF8String])
|
|
52
|
-
range:markdownRange.range
|
|
53
|
-
depth:markdownRange.depth];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
RCTApplyBaselineOffset(attributedString);
|
|
57
|
-
|
|
58
|
-
[attributedString endEditing];
|
|
59
|
-
|
|
60
|
-
_prevInputString = inputString;
|
|
61
|
-
_prevAttributedString = attributedString;
|
|
62
|
-
_prevTextAttributes = attributes;
|
|
63
|
-
_prevMarkdownStyle = _markdownStyle;
|
|
64
|
-
_prevParserId = _parserId;
|
|
65
|
-
|
|
66
|
-
return attributedString;
|
|
28
|
+
@synchronized (self) {
|
|
29
|
+
if (input == nil) {
|
|
30
|
+
return nil;
|
|
67
31
|
}
|
|
68
|
-
}
|
|
69
32
|
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
if (type == "bold") {
|
|
74
|
-
font = [RCTFont updateFont:font withWeight:@"bold"];
|
|
75
|
-
} else if (type == "italic") {
|
|
76
|
-
font = [RCTFont updateFont:font withStyle:@"italic"];
|
|
77
|
-
} else if (type == "code") {
|
|
78
|
-
font = [RCTFont updateFont:font withFamily:_markdownStyle.codeFontFamily
|
|
79
|
-
size:[NSNumber numberWithFloat:_markdownStyle.codeFontSize]
|
|
80
|
-
weight:nil
|
|
81
|
-
style:nil
|
|
82
|
-
variant:nil
|
|
83
|
-
scaleMultiplier:0];
|
|
84
|
-
} else if (type == "pre") {
|
|
85
|
-
font = [RCTFont updateFont:font withFamily:_markdownStyle.preFontFamily
|
|
86
|
-
size:[NSNumber numberWithFloat:_markdownStyle.preFontSize]
|
|
87
|
-
weight:nil
|
|
88
|
-
style:nil
|
|
89
|
-
variant:nil
|
|
90
|
-
scaleMultiplier:0];
|
|
91
|
-
} else if (type == "h1") {
|
|
92
|
-
font = [RCTFont updateFont:font withFamily:nil
|
|
93
|
-
size:[NSNumber numberWithFloat:_markdownStyle.h1FontSize]
|
|
94
|
-
weight:@"bold"
|
|
95
|
-
style:nil
|
|
96
|
-
variant:nil
|
|
97
|
-
scaleMultiplier:0];
|
|
98
|
-
} else if (type == "emoji") {
|
|
99
|
-
font = [RCTFont updateFont:font withFamily:nil
|
|
100
|
-
size:[NSNumber numberWithFloat:_markdownStyle.emojiFontSize]
|
|
101
|
-
weight:nil
|
|
102
|
-
style:nil
|
|
103
|
-
variant:nil
|
|
104
|
-
scaleMultiplier:0];
|
|
105
|
-
}
|
|
106
|
-
[attributedString addAttribute:NSFontAttributeName value:font range:range];
|
|
33
|
+
NSString *inputString = [input string];
|
|
34
|
+
if ([inputString isEqualToString:_prevInputString] && [defaultTextAttributes isEqualToDictionary:_prevDefaultTextAttributes] && [_markdownStyle isEqual:_prevMarkdownStyle] && [_parserId isEqualToNumber:_prevParserId]) {
|
|
35
|
+
return _prevAttributedString;
|
|
107
36
|
}
|
|
108
37
|
|
|
109
|
-
|
|
110
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.syntaxColor range:range];
|
|
111
|
-
} else if (type == "strikethrough") {
|
|
112
|
-
[attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
113
|
-
} else if (type == "code") {
|
|
114
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.codeColor range:range];
|
|
115
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.codeBackgroundColor range:range];
|
|
116
|
-
} else if (type == "mention-here") {
|
|
117
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionHereColor range:range];
|
|
118
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionHereBackgroundColor range:range];
|
|
119
|
-
} else if (type == "mention-user") {
|
|
120
|
-
// TODO: change mention color when it mentions current user
|
|
121
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionUserColor range:range];
|
|
122
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionUserBackgroundColor range:range];
|
|
123
|
-
} else if (type == "mention-report") {
|
|
124
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionReportColor range:range];
|
|
125
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionReportBackgroundColor range:range];
|
|
126
|
-
} else if (type == "link") {
|
|
127
|
-
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
128
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.linkColor range:range];
|
|
129
|
-
} else if (type == "blockquote") {
|
|
130
|
-
CGFloat indent = (_markdownStyle.blockquoteMarginLeft + _markdownStyle.blockquoteBorderWidth + _markdownStyle.blockquotePaddingLeft) * depth;
|
|
131
|
-
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
|
132
|
-
paragraphStyle.firstLineHeadIndent = indent;
|
|
133
|
-
paragraphStyle.headIndent = indent;
|
|
134
|
-
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
|
135
|
-
[attributedString addAttribute:RCTLiveMarkdownBlockquoteDepthAttributeName value:@(depth) range:range];
|
|
136
|
-
} else if (type == "pre") {
|
|
137
|
-
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range];
|
|
138
|
-
NSRange rangeForBackground = [[attributedString string] characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range;
|
|
139
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.preBackgroundColor range:rangeForBackground];
|
|
140
|
-
// TODO: pass background color and ranges to layout manager
|
|
141
|
-
}
|
|
142
|
-
}
|
|
38
|
+
NSArray<MarkdownRange *> *markdownRanges = [_markdownParser parse:inputString withParserId:_parserId];
|
|
143
39
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
40
|
+
NSAttributedString *attributedString = [_markdownFormatter format:inputString
|
|
41
|
+
withDefaultTextAttributes:defaultTextAttributes
|
|
42
|
+
withMarkdownRanges:markdownRanges
|
|
43
|
+
withMarkdownStyle:_markdownStyle];
|
|
44
|
+
_prevInputString = inputString;
|
|
45
|
+
_prevAttributedString = attributedString;
|
|
46
|
+
_prevDefaultTextAttributes = defaultTextAttributes;
|
|
47
|
+
_prevMarkdownStyle = _markdownStyle;
|
|
48
|
+
_prevParserId = _parserId;
|
|
155
49
|
|
|
156
|
-
|
|
157
|
-
}];
|
|
158
|
-
|
|
159
|
-
if (maximumLineHeight == 0) {
|
|
160
|
-
// `lineHeight` was not specified, nothing to do.
|
|
161
|
-
return;
|
|
50
|
+
return attributedString;
|
|
162
51
|
}
|
|
163
|
-
|
|
164
|
-
__block CGFloat maximumFontLineHeight = 0;
|
|
165
|
-
|
|
166
|
-
[attributedText enumerateAttribute:NSFontAttributeName
|
|
167
|
-
inRange:NSMakeRange(0, attributedText.length)
|
|
168
|
-
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
169
|
-
usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
|
170
|
-
if (!font) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
|
|
175
|
-
}];
|
|
176
|
-
|
|
177
|
-
if (maximumLineHeight < maximumFontLineHeight) {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
CGFloat baseLineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0;
|
|
182
|
-
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
|
183
|
-
value:@(baseLineOffset)
|
|
184
|
-
range:NSMakeRange(0, attributedText.length)];
|
|
185
52
|
}
|
|
186
53
|
|
|
187
54
|
@end
|
|
@@ -18,7 +18,7 @@ using namespace expensify::livemarkdown;
|
|
|
18
18
|
if (markdownUtils != nil) {
|
|
19
19
|
// force Markdown formatting on first render because `_setAttributedText` is called before `setMarkdownUtils`
|
|
20
20
|
RCTUITextField *backedTextInputView = [self getBackedTextInputView];
|
|
21
|
-
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText
|
|
21
|
+
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText withDefaultTextAttributes:backedTextInputView.defaultTextAttributes];
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ using namespace expensify::livemarkdown;
|
|
|
36
36
|
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
37
37
|
RCTUITextField *backedTextInputView = [self getBackedTextInputView];
|
|
38
38
|
if (markdownUtils != nil && backedTextInputView != nil) {
|
|
39
|
-
attributedString = [markdownUtils parseMarkdown:attributedString
|
|
39
|
+
attributedString = [markdownUtils parseMarkdown:attributedString withDefaultTextAttributes:backedTextInputView.defaultTextAttributes];
|
|
40
40
|
} else {
|
|
41
41
|
// If markdownUtils is undefined, the text input hasn't been mounted yet. It will
|
|
42
42
|
// update its state with the unformatted attributed string, we want to prevent displaying
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
18
18
|
if (markdownUtils != nil) {
|
|
19
19
|
UITextRange *range = self.selectedTextRange;
|
|
20
|
-
super.attributedText = [markdownUtils parseMarkdown:self.attributedText
|
|
20
|
+
super.attributedText = [markdownUtils parseMarkdown:self.attributedText withDefaultTextAttributes:self.defaultTextAttributes];
|
|
21
21
|
[super setSelectedTextRange:range]; // prevents cursor from jumping at the end when typing in the middle of the text
|
|
22
22
|
self.typingAttributes = self.defaultTextAttributes; // removes indent in new line when typing after blockquote
|
|
23
23
|
}
|
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.204",
|
|
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",
|