@expensify/react-native-live-markdown 0.1.3
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/LICENSE +20 -0
- package/README.md +139 -0
- package/RNLiveMarkdown.podspec +44 -0
- package/android/build.gradle +143 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/cpp/CMakeLists.txt +23 -0
- package/android/src/main/cpp/MarkdownUtils.cpp +44 -0
- package/android/src/main/cpp/MarkdownUtils.h +41 -0
- package/android/src/main/cpp/OnLoad.cpp +8 -0
- package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +24 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java +11 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java +47 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java +10 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +39 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +9 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java +11 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java +10 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java +18 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java +7 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java +5 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java +186 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorView.java +85 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorViewManager.java +31 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java +38 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java +5 -0
- package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +176 -0
- package/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java +24 -0
- package/android/src/oldarch/MarkdownTextInputDecoratorViewManagerSpec.java +10 -0
- package/ios/MarkdownLayoutManager.h +12 -0
- package/ios/MarkdownLayoutManager.mm +35 -0
- package/ios/MarkdownTextInputDecoratorComponentView.h +12 -0
- package/ios/MarkdownTextInputDecoratorComponentView.mm +55 -0
- package/ios/MarkdownTextInputDecoratorView.h +12 -0
- package/ios/MarkdownTextInputDecoratorView.mm +106 -0
- package/ios/MarkdownTextInputDecoratorViewManager.h +8 -0
- package/ios/MarkdownTextInputDecoratorViewManager.mm +23 -0
- package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.h +14 -0
- package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +43 -0
- package/ios/RCTBaseTextInputView+Markdown.h +16 -0
- package/ios/RCTBaseTextInputView+Markdown.m +66 -0
- package/ios/RCTMarkdownStyle.h +35 -0
- package/ios/RCTMarkdownStyle.mm +81 -0
- package/ios/RCTMarkdownUtils.h +18 -0
- package/ios/RCTMarkdownUtils.mm +134 -0
- package/ios/RCTTextInputComponentView+Markdown.h +21 -0
- package/ios/RCTTextInputComponentView+Markdown.mm +51 -0
- package/ios/RCTUITextView+Markdown.h +15 -0
- package/ios/RCTUITextView+Markdown.mm +42 -0
- package/lib/commonjs/MarkdownTextInput.js +103 -0
- package/lib/commonjs/MarkdownTextInput.js.map +1 -0
- package/lib/commonjs/MarkdownTextInput.web.js +21 -0
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
- package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +10 -0
- package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/MarkdownTextInput.js +96 -0
- package/lib/module/MarkdownTextInput.js.map +1 -0
- package/lib/module/MarkdownTextInput.web.js +15 -0
- package/lib/module/MarkdownTextInput.web.js.map +1 -0
- package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -0
- package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/MarkdownTextInput.d.ts +15 -0
- package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -0
- package/lib/typescript/src/MarkdownTextInput.web.d.ts +9 -0
- package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -0
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts +46 -0
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +159 -0
- package/parser/react-native-live-markdown-parser.js +34 -0
- package/src/MarkdownTextInput.tsx +129 -0
- package/src/MarkdownTextInput.web.tsx +24 -0
- package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +48 -0
- package/src/index.tsx +2 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#import <React/RCTUITextField.h>
|
|
2
|
+
#import "react_native_assert.h"
|
|
3
|
+
|
|
4
|
+
#import <RNLiveMarkdown/MarkdownLayoutManager.h>
|
|
5
|
+
#import <RNLiveMarkdown/MarkdownTextInputDecoratorView.h>
|
|
6
|
+
#import <RNLiveMarkdown/RCTBackedTextFieldDelegateAdapter+Markdown.h>
|
|
7
|
+
#import <RNLiveMarkdown/RCTUITextView+Markdown.h>
|
|
8
|
+
|
|
9
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
10
|
+
#import <RNLiveMarkdown/RCTTextInputComponentView+Markdown.h>
|
|
11
|
+
#else
|
|
12
|
+
#import <RNLiveMarkdown/RCTBaseTextInputView+Markdown.h>
|
|
13
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
14
|
+
|
|
15
|
+
#import <objc/runtime.h>
|
|
16
|
+
|
|
17
|
+
@implementation MarkdownTextInputDecoratorView {
|
|
18
|
+
RCTMarkdownUtils *_markdownUtils;
|
|
19
|
+
RCTMarkdownStyle *_markdownStyle;
|
|
20
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
21
|
+
__weak RCTTextInputComponentView *_textInput;
|
|
22
|
+
#else
|
|
23
|
+
__weak RCTBaseTextInputView *_textInput;
|
|
24
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
25
|
+
__weak RCTBackedTextFieldDelegateAdapter *_adapter;
|
|
26
|
+
__weak RCTUITextView *_textView;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
- (void)didMoveToWindow {
|
|
30
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
31
|
+
if (self.superview.superview == nil) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
#else
|
|
35
|
+
if (self.superview == nil) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
39
|
+
|
|
40
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
41
|
+
NSArray *viewsArray = self.superview.superview.subviews;
|
|
42
|
+
NSUInteger currentIndex = [viewsArray indexOfObject:self.superview];
|
|
43
|
+
#else
|
|
44
|
+
NSArray *viewsArray = self.superview.subviews;
|
|
45
|
+
NSUInteger currentIndex = [viewsArray indexOfObject:self];
|
|
46
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
47
|
+
|
|
48
|
+
react_native_assert(currentIndex != 0 && currentIndex != NSNotFound && "Error while finding current component.");
|
|
49
|
+
UIView *view = [viewsArray objectAtIndex:currentIndex - 1];
|
|
50
|
+
|
|
51
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
52
|
+
react_native_assert([view isKindOfClass:[RCTTextInputComponentView class]] && "Previous sibling component is not an instance of RCTTextInputComponentView.");
|
|
53
|
+
_textInput = (RCTTextInputComponentView *)view;
|
|
54
|
+
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = [_textInput valueForKey:@"_backedTextInputView"];
|
|
55
|
+
#else
|
|
56
|
+
react_native_assert([view isKindOfClass:[RCTBaseTextInputView class]] && "Previous sibling component is not an instance of RCTBaseTextInputView.");
|
|
57
|
+
_textInput = (RCTBaseTextInputView *)view;
|
|
58
|
+
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = _textInput.backedTextInputView;
|
|
59
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
60
|
+
|
|
61
|
+
_markdownUtils = [[RCTMarkdownUtils alloc] initWithBackedTextInputView:backedTextInputView];
|
|
62
|
+
react_native_assert(_markdownStyle != nil);
|
|
63
|
+
[_markdownUtils setMarkdownStyle:_markdownStyle];
|
|
64
|
+
|
|
65
|
+
[_textInput setMarkdownUtils:_markdownUtils];
|
|
66
|
+
if ([backedTextInputView isKindOfClass:[RCTUITextField class]]) {
|
|
67
|
+
RCTUITextField *textField = (RCTUITextField *)backedTextInputView;
|
|
68
|
+
_adapter = [textField valueForKey:@"textInputDelegateAdapter"];
|
|
69
|
+
[_adapter setMarkdownUtils:_markdownUtils];
|
|
70
|
+
} else if ([backedTextInputView isKindOfClass:[RCTUITextView class]]) {
|
|
71
|
+
_textView = (RCTUITextView *)backedTextInputView;
|
|
72
|
+
[_textView setMarkdownUtils:_markdownUtils];
|
|
73
|
+
NSLayoutManager *layoutManager = _textView.layoutManager; // switching to TextKit 1 compatibility mode
|
|
74
|
+
layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue
|
|
75
|
+
object_setClass(layoutManager, [MarkdownLayoutManager class]);
|
|
76
|
+
[layoutManager setValue:_markdownUtils forKey:@"markdownUtils"];
|
|
77
|
+
} else {
|
|
78
|
+
react_native_assert(false && "Cannot enable Markdown for this type of TextInput.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (void)willMoveToWindow:(UIWindow *)newWindow
|
|
83
|
+
{
|
|
84
|
+
if (_textInput != nil) {
|
|
85
|
+
[_textInput setMarkdownUtils:nil];
|
|
86
|
+
}
|
|
87
|
+
if (_adapter != nil) {
|
|
88
|
+
[_adapter setMarkdownUtils:nil];
|
|
89
|
+
}
|
|
90
|
+
if (_textView != nil) {
|
|
91
|
+
[_textView setMarkdownUtils:nil];
|
|
92
|
+
if (_textView.layoutManager != nil && [object_getClass(_textView.layoutManager) isEqual:[MarkdownLayoutManager class]]) {
|
|
93
|
+
[_textView.layoutManager setValue:nil forKey:@"markdownUtils"];
|
|
94
|
+
object_setClass(_textView.layoutManager, [NSLayoutManager class]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)setMarkdownStyle:(RCTMarkdownStyle *)markdownStyle
|
|
100
|
+
{
|
|
101
|
+
_markdownStyle = markdownStyle;
|
|
102
|
+
[_markdownUtils setMarkdownStyle:markdownStyle];
|
|
103
|
+
[_textInput textInputDidChange]; // trigger attributed text update
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/MarkdownTextInputDecoratorViewManager.h>
|
|
2
|
+
#import <RNLiveMarkdown/MarkdownTextInputDecoratorView.h>
|
|
3
|
+
|
|
4
|
+
@implementation MarkdownTextInputDecoratorViewManager
|
|
5
|
+
|
|
6
|
+
RCT_EXPORT_MODULE(MarkdownTextInputDecoratorView)
|
|
7
|
+
|
|
8
|
+
- (UIView *)view
|
|
9
|
+
{
|
|
10
|
+
return [[MarkdownTextInputDecoratorView alloc] init];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
RCT_CUSTOM_VIEW_PROPERTY(markdownStyle, NSDictionary, MarkdownTextInputDecoratorView)
|
|
14
|
+
{
|
|
15
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
16
|
+
// implemented in MarkdownTextInputDecoratorView updateProps:
|
|
17
|
+
#else
|
|
18
|
+
RCTMarkdownStyle *markdownStyle = [[RCTMarkdownStyle alloc] initWithDictionary:json];
|
|
19
|
+
[view setMarkdownStyle:markdownStyle];
|
|
20
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
|
2
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@interface RCTBackedTextFieldDelegateAdapter (Markdown)
|
|
7
|
+
|
|
8
|
+
@property(nonatomic, nullable, getter=getMarkdownUtils) RCTMarkdownUtils *markdownUtils;
|
|
9
|
+
|
|
10
|
+
- (void)markdown_textFieldDidChange;
|
|
11
|
+
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTBackedTextFieldDelegateAdapter+Markdown.h>
|
|
2
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
3
|
+
#import <React/RCTUITextField.h>
|
|
4
|
+
#import <objc/message.h>
|
|
5
|
+
|
|
6
|
+
@implementation RCTBackedTextFieldDelegateAdapter (Markdown)
|
|
7
|
+
|
|
8
|
+
- (void)setMarkdownUtils:(RCTMarkdownUtils *)markdownUtils {
|
|
9
|
+
objc_setAssociatedObject(self, @selector(getMarkdownUtils), markdownUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
- (RCTMarkdownUtils *)getMarkdownUtils {
|
|
13
|
+
return objc_getAssociatedObject(self, @selector(getMarkdownUtils));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (void)markdown_textFieldDidChange
|
|
17
|
+
{
|
|
18
|
+
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
19
|
+
if (markdownUtils != nil) {
|
|
20
|
+
RCTUITextField *backedTextInputView = [self valueForKey:@"_backedTextInputView"];
|
|
21
|
+
UITextRange *range = backedTextInputView.selectedTextRange;
|
|
22
|
+
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText];
|
|
23
|
+
[backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Call the original method
|
|
27
|
+
[self markdown_textFieldDidChange];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
+ (void)load
|
|
31
|
+
{
|
|
32
|
+
static dispatch_once_t onceToken;
|
|
33
|
+
dispatch_once(&onceToken, ^{
|
|
34
|
+
Class cls = [self class];
|
|
35
|
+
SEL originalSelector = @selector(textFieldDidChange);
|
|
36
|
+
SEL swizzledSelector = @selector(markdown_textFieldDidChange);
|
|
37
|
+
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
|
|
38
|
+
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
|
|
39
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import <React/RCTBaseTextInputView.h>
|
|
2
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@interface RCTBaseTextInputView (Markdown)
|
|
7
|
+
|
|
8
|
+
@property(nonatomic, nullable, getter=getMarkdownUtils) RCTMarkdownUtils *markdownUtils;
|
|
9
|
+
|
|
10
|
+
- (void)markdown_setAttributedText:(NSAttributedString *)attributedText;
|
|
11
|
+
|
|
12
|
+
- (void)markdown_updateLocalData;
|
|
13
|
+
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTBaseTextInputView+Markdown.h>
|
|
2
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
3
|
+
#import <objc/message.h>
|
|
4
|
+
|
|
5
|
+
@implementation RCTBaseTextInputView (Markdown)
|
|
6
|
+
|
|
7
|
+
- (void)setMarkdownUtils:(RCTMarkdownUtils *)markdownUtils {
|
|
8
|
+
objc_setAssociatedObject(self, @selector(getMarkdownUtils), markdownUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
- (RCTMarkdownUtils *)getMarkdownUtils {
|
|
12
|
+
return objc_getAssociatedObject(self, @selector(getMarkdownUtils));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
- (void)markdown_setAttributedText:(NSAttributedString *)attributedText
|
|
16
|
+
{
|
|
17
|
+
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
18
|
+
if (markdownUtils != nil) {
|
|
19
|
+
attributedText = [markdownUtils parseMarkdown:attributedText];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Call the original method
|
|
23
|
+
[self markdown_setAttributedText:attributedText];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)markdown_updateLocalData
|
|
27
|
+
{
|
|
28
|
+
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
29
|
+
if (markdownUtils != nil) {
|
|
30
|
+
UITextRange *range = self.backedTextInputView.selectedTextRange;
|
|
31
|
+
NSAttributedString *attributedText = [markdownUtils parseMarkdown:self.backedTextInputView.attributedText];
|
|
32
|
+
[self.backedTextInputView setAttributedText:attributedText];
|
|
33
|
+
[self.backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Call the original method
|
|
37
|
+
[self markdown_updateLocalData];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
+ (void)load
|
|
41
|
+
{
|
|
42
|
+
static dispatch_once_t onceToken;
|
|
43
|
+
dispatch_once(&onceToken, ^{
|
|
44
|
+
Class cls = [self class];
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
// swizzle setAttributedText
|
|
48
|
+
SEL originalSelector = @selector(setAttributedText:);
|
|
49
|
+
SEL swizzledSelector = @selector(markdown_setAttributedText:);
|
|
50
|
+
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
|
|
51
|
+
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
|
|
52
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
// swizzle updateLocalData
|
|
57
|
+
SEL originalSelector = @selector(updateLocalData);
|
|
58
|
+
SEL swizzledSelector = @selector(markdown_updateLocalData);
|
|
59
|
+
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
|
|
60
|
+
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
|
|
61
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
2
|
+
#import <react/renderer/components/RNLiveMarkdownSpec/Props.h>
|
|
3
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
@interface RCTMarkdownStyle : NSObject
|
|
8
|
+
|
|
9
|
+
@property (nonatomic) UIColor *syntaxColor;
|
|
10
|
+
@property (nonatomic) UIColor *linkColor;
|
|
11
|
+
@property (nonatomic) CGFloat h1FontSize;
|
|
12
|
+
@property (nonatomic) UIColor *blockquoteBorderColor;
|
|
13
|
+
@property (nonatomic) CGFloat blockquoteBorderWidth;
|
|
14
|
+
@property (nonatomic) CGFloat blockquoteMarginLeft;
|
|
15
|
+
@property (nonatomic) CGFloat blockquotePaddingLeft;
|
|
16
|
+
@property (nonatomic) NSString *codeFontFamily;
|
|
17
|
+
@property (nonatomic) UIColor *codeColor;
|
|
18
|
+
@property (nonatomic) UIColor *codeBackgroundColor;
|
|
19
|
+
@property (nonatomic) NSString *preFontFamily;
|
|
20
|
+
@property (nonatomic) UIColor *preColor;
|
|
21
|
+
@property (nonatomic) UIColor *preBackgroundColor;
|
|
22
|
+
@property (nonatomic) UIColor *mentionHereColor;
|
|
23
|
+
@property (nonatomic) UIColor *mentionHereBackgroundColor;
|
|
24
|
+
@property (nonatomic) UIColor *mentionUserColor;
|
|
25
|
+
@property (nonatomic) UIColor *mentionUserBackgroundColor;
|
|
26
|
+
|
|
27
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
28
|
+
- (instancetype)initWithStruct:(const facebook::react::MarkdownTextInputDecoratorViewMarkdownStyleStruct &)style;
|
|
29
|
+
#else
|
|
30
|
+
- (instancetype)initWithDictionary:(NSDictionary *)json;
|
|
31
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
32
|
+
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
|
|
2
|
+
|
|
3
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
4
|
+
#import <React/RCTConversions.h>
|
|
5
|
+
#else
|
|
6
|
+
#import <React/RCTConvert.h>
|
|
7
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
8
|
+
|
|
9
|
+
@implementation RCTMarkdownStyle
|
|
10
|
+
|
|
11
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithStruct:(const facebook::react::MarkdownTextInputDecoratorViewMarkdownStyleStruct &)style
|
|
14
|
+
{
|
|
15
|
+
if (self = [super init]) {
|
|
16
|
+
_syntaxColor = RCTUIColorFromSharedColor(style.syntax.color);
|
|
17
|
+
|
|
18
|
+
_linkColor = RCTUIColorFromSharedColor(style.link.color);
|
|
19
|
+
|
|
20
|
+
_h1FontSize = style.h1.fontSize;
|
|
21
|
+
|
|
22
|
+
_blockquoteBorderColor = RCTUIColorFromSharedColor(style.blockquote.borderColor);
|
|
23
|
+
_blockquoteBorderWidth = style.blockquote.borderWidth;
|
|
24
|
+
_blockquoteMarginLeft = style.blockquote.marginLeft;
|
|
25
|
+
_blockquotePaddingLeft = style.blockquote.paddingLeft;
|
|
26
|
+
|
|
27
|
+
_codeFontFamily = RCTNSStringFromString(style.code.fontFamily);
|
|
28
|
+
_codeColor = RCTUIColorFromSharedColor(style.code.color);
|
|
29
|
+
_codeBackgroundColor = RCTUIColorFromSharedColor(style.code.backgroundColor);
|
|
30
|
+
|
|
31
|
+
_preFontFamily = RCTNSStringFromString(style.pre.fontFamily);
|
|
32
|
+
_preColor = RCTUIColorFromSharedColor(style.pre.color);
|
|
33
|
+
_preBackgroundColor = RCTUIColorFromSharedColor(style.pre.backgroundColor);
|
|
34
|
+
|
|
35
|
+
_mentionHereColor = RCTUIColorFromSharedColor(style.mentionHere.color);
|
|
36
|
+
_mentionHereBackgroundColor = RCTUIColorFromSharedColor(style.mentionHere.backgroundColor);
|
|
37
|
+
|
|
38
|
+
_mentionUserColor = RCTUIColorFromSharedColor(style.mentionUser.color);
|
|
39
|
+
_mentionUserBackgroundColor = RCTUIColorFromSharedColor(style.mentionUser.backgroundColor);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return self;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#else
|
|
46
|
+
|
|
47
|
+
- (instancetype)initWithDictionary:(NSDictionary *)json
|
|
48
|
+
{
|
|
49
|
+
if (self = [super init]) {
|
|
50
|
+
_syntaxColor = [RCTConvert UIColor:json[@"syntax"][@"color"]];
|
|
51
|
+
|
|
52
|
+
_linkColor = [RCTConvert UIColor:json[@"link"][@"color"]];
|
|
53
|
+
|
|
54
|
+
_h1FontSize = [RCTConvert CGFloat:json[@"h1"][@"fontSize"]];
|
|
55
|
+
|
|
56
|
+
_blockquoteBorderColor = [RCTConvert UIColor:json[@"blockquote"][@"borderColor"]];
|
|
57
|
+
_blockquoteBorderWidth = [RCTConvert CGFloat:json[@"blockquote"][@"borderWidth"]];
|
|
58
|
+
_blockquoteMarginLeft = [RCTConvert CGFloat:json[@"blockquote"][@"marginLeft"]];
|
|
59
|
+
_blockquotePaddingLeft = [RCTConvert CGFloat:json[@"blockquote"][@"paddingLeft"]];
|
|
60
|
+
|
|
61
|
+
_codeFontFamily = [RCTConvert NSString:json[@"code"][@"fontFamily"]];
|
|
62
|
+
_codeColor = [RCTConvert UIColor:json[@"code"][@"color"]];
|
|
63
|
+
_codeBackgroundColor = [RCTConvert UIColor:json[@"code"][@"backgroundColor"]];
|
|
64
|
+
|
|
65
|
+
_preFontFamily = [RCTConvert NSString:json[@"pre"][@"fontFamily"]];
|
|
66
|
+
_preColor = [RCTConvert UIColor:json[@"pre"][@"color"]];
|
|
67
|
+
_preBackgroundColor = [RCTConvert UIColor:json[@"pre"][@"backgroundColor"]];
|
|
68
|
+
|
|
69
|
+
_mentionHereColor = [RCTConvert UIColor:json[@"mentionHere"][@"color"]];
|
|
70
|
+
_mentionHereBackgroundColor = [RCTConvert UIColor:json[@"mentionHere"][@"backgroundColor"]];
|
|
71
|
+
|
|
72
|
+
_mentionUserColor = [RCTConvert UIColor:json[@"mentionUser"][@"color"]];
|
|
73
|
+
_mentionUserBackgroundColor = [RCTConvert UIColor:json[@"mentionUser"][@"backgroundColor"]];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return self;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
80
|
+
|
|
81
|
+
@end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#import <React/RCTBackedTextInputViewProtocol.h>
|
|
2
|
+
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@interface RCTMarkdownUtils : NSObject
|
|
7
|
+
|
|
8
|
+
@property (nonatomic) RCTMarkdownStyle *markdownStyle;
|
|
9
|
+
@property (nonatomic) NSMutableArray<NSValue *> *blockquoteRanges;
|
|
10
|
+
@property (weak, nonatomic) UIView<RCTBackedTextInputViewProtocol> *backedTextInputView;
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithBackedTextInputView:(UIView<RCTBackedTextInputViewProtocol> *)backedTextInputView;
|
|
13
|
+
|
|
14
|
+
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input;
|
|
15
|
+
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
2
|
+
#import "react_native_assert.h"
|
|
3
|
+
#import <React/RCTAssert.h>
|
|
4
|
+
#import <React/RCTFont.h>
|
|
5
|
+
#import <JavaScriptCore/JavaScriptCore.h>
|
|
6
|
+
|
|
7
|
+
@implementation RCTMarkdownUtils {
|
|
8
|
+
NSString *_prevInputString;
|
|
9
|
+
NSAttributedString *_prevAttributedString;
|
|
10
|
+
NSDictionary<NSAttributedStringKey, id> *_prevTextAttributes;
|
|
11
|
+
__weak RCTMarkdownStyle *_prevMarkdownStyle;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithBackedTextInputView:(UIView<RCTBackedTextInputViewProtocol> *)backedTextInputView
|
|
15
|
+
{
|
|
16
|
+
if (self = [super init]) {
|
|
17
|
+
_backedTextInputView = backedTextInputView;
|
|
18
|
+
}
|
|
19
|
+
return self;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input
|
|
23
|
+
{
|
|
24
|
+
RCTAssertMainQueue();
|
|
25
|
+
|
|
26
|
+
if (input == nil) {
|
|
27
|
+
return nil;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
NSString *inputString = [input string];
|
|
31
|
+
if ([inputString isEqualToString:_prevInputString] && [_backedTextInputView.defaultTextAttributes isEqualToDictionary:_prevTextAttributes] && [_markdownStyle isEqual:_prevMarkdownStyle]) {
|
|
32
|
+
return _prevAttributedString;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static JSContext *ctx = nil;
|
|
36
|
+
static JSValue *function = nil;
|
|
37
|
+
if (ctx == nil) {
|
|
38
|
+
NSString *path = [[NSBundle mainBundle] pathForResource:@"react-native-live-markdown-parser" ofType:@"js"];
|
|
39
|
+
assert(path != nil && "[react-native-live-markdown] Markdown parser bundle not found");
|
|
40
|
+
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
|
41
|
+
assert(content != nil && "[react-native-live-markdown] Markdown parser bundle is empty");
|
|
42
|
+
ctx = [[JSContext alloc] init];
|
|
43
|
+
[ctx evaluateScript:content];
|
|
44
|
+
function = ctx[@"parseExpensiMarkToRanges"];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
JSValue *result = [function callWithArguments:@[inputString]];
|
|
48
|
+
NSArray *ranges = [result toArray];
|
|
49
|
+
|
|
50
|
+
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:_backedTextInputView.defaultTextAttributes];
|
|
51
|
+
[attributedString beginEditing];
|
|
52
|
+
|
|
53
|
+
// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
|
|
54
|
+
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
|
|
55
|
+
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
|
|
56
|
+
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];
|
|
57
|
+
|
|
58
|
+
_blockquoteRanges = [NSMutableArray new];
|
|
59
|
+
|
|
60
|
+
[ranges enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
61
|
+
NSArray *item = obj;
|
|
62
|
+
NSString *type = item[0];
|
|
63
|
+
NSUInteger location = [item[1] unsignedIntegerValue];
|
|
64
|
+
NSUInteger length = [item[2] unsignedIntegerValue];
|
|
65
|
+
NSRange range = NSMakeRange(location, length);
|
|
66
|
+
|
|
67
|
+
if ([type isEqualToString:@"bold"] || [type isEqualToString:@"italic"] || [type isEqualToString:@"code"] || [type isEqualToString:@"pre"] || [type isEqualToString:@"h1"]) {
|
|
68
|
+
UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:location effectiveRange:NULL];
|
|
69
|
+
if ([type isEqualToString:@"bold"]) {
|
|
70
|
+
font = [RCTFont updateFont:font withWeight:@"bold"];
|
|
71
|
+
} else if ([type isEqualToString:@"italic"]) {
|
|
72
|
+
font = [RCTFont updateFont:font withStyle:@"italic"];
|
|
73
|
+
} else if ([type isEqualToString:@"code"]) {
|
|
74
|
+
font = [RCTFont updateFont:font withFamily:_markdownStyle.codeFontFamily];
|
|
75
|
+
} else if ([type isEqualToString:@"pre"]) {
|
|
76
|
+
font = [RCTFont updateFont:font withFamily:_markdownStyle.preFontFamily];
|
|
77
|
+
} else if ([type isEqualToString:@"h1"]) {
|
|
78
|
+
font = [RCTFont updateFont:font withFamily:nil
|
|
79
|
+
size:[NSNumber numberWithFloat:_markdownStyle.h1FontSize]
|
|
80
|
+
weight:@"bold"
|
|
81
|
+
style:nil
|
|
82
|
+
variant:nil
|
|
83
|
+
scaleMultiplier:0];
|
|
84
|
+
}
|
|
85
|
+
[attributedString addAttribute:NSFontAttributeName value:font range:range];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if ([type isEqualToString:@"syntax"]) {
|
|
89
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.syntaxColor range:range];
|
|
90
|
+
} else if ([type isEqualToString:@"strikethrough"]) {
|
|
91
|
+
[attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
92
|
+
} else if ([type isEqualToString:@"code"]) {
|
|
93
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.codeColor range:range];
|
|
94
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.codeBackgroundColor range:range];
|
|
95
|
+
} else if ([type isEqualToString:@"mention-here"]) {
|
|
96
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionHereColor range:range];
|
|
97
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionHereBackgroundColor range:range];
|
|
98
|
+
} else if ([type isEqualToString:@"mention-user"]) {
|
|
99
|
+
// TODO: change mention color when it mentions current user
|
|
100
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionUserColor range:range];
|
|
101
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionUserBackgroundColor range:range];
|
|
102
|
+
} else if ([type isEqualToString:@"link"]) {
|
|
103
|
+
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range];
|
|
104
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.linkColor range:range];
|
|
105
|
+
} else if ([type isEqualToString:@"blockquote"]) {
|
|
106
|
+
CGFloat indent = _markdownStyle.blockquoteMarginLeft + _markdownStyle.blockquoteBorderWidth + _markdownStyle.blockquotePaddingLeft;
|
|
107
|
+
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
|
108
|
+
paragraphStyle.firstLineHeadIndent = indent;
|
|
109
|
+
paragraphStyle.headIndent = indent;
|
|
110
|
+
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
|
111
|
+
[_blockquoteRanges addObject:[NSValue valueWithRange:range]];
|
|
112
|
+
} else if ([type isEqualToString:@"pre"]) {
|
|
113
|
+
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range];
|
|
114
|
+
NSRange rangeForBackground = [inputString characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range;
|
|
115
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.preBackgroundColor range:rangeForBackground];
|
|
116
|
+
// TODO: pass background color and ranges to layout manager
|
|
117
|
+
} else if ([type isEqualToString:@"h1"]) {
|
|
118
|
+
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
|
119
|
+
NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); // we also need to include prepending "# "
|
|
120
|
+
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace];
|
|
121
|
+
}
|
|
122
|
+
}];
|
|
123
|
+
|
|
124
|
+
[attributedString endEditing];
|
|
125
|
+
|
|
126
|
+
_prevInputString = inputString;
|
|
127
|
+
_prevAttributedString = attributedString;
|
|
128
|
+
_prevTextAttributes = _backedTextInputView.defaultTextAttributes;
|
|
129
|
+
_prevMarkdownStyle = _markdownStyle;
|
|
130
|
+
|
|
131
|
+
return attributedString;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// This guard prevent this file to be compiled in the old architecture.
|
|
2
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
3
|
+
|
|
4
|
+
#import <React/RCTTextInputComponentView.h>
|
|
5
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
6
|
+
|
|
7
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
8
|
+
|
|
9
|
+
@interface RCTTextInputComponentView (Markdown)
|
|
10
|
+
|
|
11
|
+
@property(nonatomic, nullable, getter=getMarkdownUtils) RCTMarkdownUtils *markdownUtils;
|
|
12
|
+
|
|
13
|
+
- (void)markdown__setAttributedString:(NSAttributedString *)attributedString;
|
|
14
|
+
|
|
15
|
+
- (void)textInputDidChange;
|
|
16
|
+
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
NS_ASSUME_NONNULL_END
|
|
20
|
+
|
|
21
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// This guard prevent this file to be compiled in the old architecture.
|
|
2
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
3
|
+
|
|
4
|
+
#import <RNLiveMarkdown/RCTTextInputComponentView+Markdown.h>
|
|
5
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
6
|
+
#import <React/RCTUITextField.h>
|
|
7
|
+
#import <objc/message.h>
|
|
8
|
+
|
|
9
|
+
@implementation RCTTextInputComponentView (Markdown)
|
|
10
|
+
|
|
11
|
+
- (void)setMarkdownUtils:(RCTMarkdownUtils *)markdownUtils {
|
|
12
|
+
objc_setAssociatedObject(self, @selector(getMarkdownUtils), markdownUtils, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
13
|
+
|
|
14
|
+
if (markdownUtils != nil) {
|
|
15
|
+
// force Markdown formatting on first render because `_setAttributedText` is called before `setMarkdownUtils`
|
|
16
|
+
RCTUITextField *backedTextInputView = [self valueForKey:@"_backedTextInputView"];
|
|
17
|
+
backedTextInputView.attributedText = [markdownUtils parseMarkdown:backedTextInputView.attributedText];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
- (RCTMarkdownUtils *)getMarkdownUtils {
|
|
22
|
+
return objc_getAssociatedObject(self, @selector(getMarkdownUtils));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (void)markdown__setAttributedString:(NSAttributedString *)attributedString
|
|
26
|
+
{
|
|
27
|
+
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
|
|
28
|
+
if (markdownUtils != nil) {
|
|
29
|
+
attributedString = [markdownUtils parseMarkdown:attributedString];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Call the original method
|
|
33
|
+
[self markdown__setAttributedString:attributedString];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
+ (void)load
|
|
37
|
+
{
|
|
38
|
+
static dispatch_once_t onceToken;
|
|
39
|
+
dispatch_once(&onceToken, ^{
|
|
40
|
+
Class cls = [self class];
|
|
41
|
+
SEL originalSelector = @selector(_setAttributedString:);
|
|
42
|
+
SEL swizzledSelector = @selector(markdown__setAttributedString:);
|
|
43
|
+
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
|
|
44
|
+
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
|
|
45
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@end
|
|
50
|
+
|
|
51
|
+
#endif /* RCT_NEW_ARCH_ENABLED */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <React/RCTUITextView.h>
|
|
3
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
@interface RCTUITextView (Markdown)
|
|
8
|
+
|
|
9
|
+
@property(nonatomic, nullable, getter=getMarkdownUtils) RCTMarkdownUtils *markdownUtils;
|
|
10
|
+
|
|
11
|
+
- (void)markdown_textDidChange;
|
|
12
|
+
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
NS_ASSUME_NONNULL_END
|