@expensify/react-native-live-markdown 0.1.293 → 0.1.295
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/new_arch/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp +37 -1
- package/android/src/main/new_arch/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h +4 -25
- package/apple/BlockquoteTextLayoutFragment.h +15 -0
- package/apple/BlockquoteTextLayoutFragment.mm +51 -0
- package/apple/MarkdownTextInputDecoratorComponentView.mm +24 -13
- package/apple/MarkdownTextInputDecoratorShadowNode.h +3 -16
- package/apple/MarkdownTextInputDecoratorShadowNode.mm +39 -0
- package/apple/MarkdownTextLayoutManagerDelegate.h +14 -0
- package/apple/MarkdownTextLayoutManagerDelegate.mm +22 -0
- package/package.json +1 -1
|
@@ -14,13 +14,49 @@ namespace react {
|
|
|
14
14
|
extern const char MarkdownTextInputDecoratorViewComponentName[] =
|
|
15
15
|
"MarkdownTextInputDecoratorView";
|
|
16
16
|
|
|
17
|
+
MarkdownTextInputDecoratorShadowNode::MarkdownTextInputDecoratorShadowNode(
|
|
18
|
+
ShadowNodeFragment const &fragment,
|
|
19
|
+
ShadowNodeFamily::Shared const &family,
|
|
20
|
+
ShadowNodeTraits traits)
|
|
21
|
+
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
22
|
+
initialize();
|
|
23
|
+
createCustomContextContainer();
|
|
24
|
+
makeChildNodeMutable();
|
|
25
|
+
|
|
26
|
+
if (fragment.children) {
|
|
27
|
+
overwriteTextLayoutManager();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
MarkdownTextInputDecoratorShadowNode::MarkdownTextInputDecoratorShadowNode(
|
|
32
|
+
ShadowNode const &sourceShadowNode,
|
|
33
|
+
ShadowNodeFragment const &fragment)
|
|
34
|
+
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
35
|
+
initialize();
|
|
36
|
+
|
|
37
|
+
const auto &sourceDecorator = static_cast<const MarkdownTextInputDecoratorShadowNode &>(sourceShadowNode);
|
|
38
|
+
|
|
39
|
+
customContextContainer_ = sourceDecorator.customContextContainer_;
|
|
40
|
+
previousMarkdownStyle_ = sourceDecorator.previousMarkdownStyle_;
|
|
41
|
+
previousParserId_ = sourceDecorator.previousParserId_;
|
|
42
|
+
|
|
43
|
+
updateCustomContextContainerIfNeeded();
|
|
44
|
+
makeChildNodeMutable();
|
|
45
|
+
|
|
46
|
+
if (fragment.children) {
|
|
47
|
+
overwriteTextLayoutManager();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
void MarkdownTextInputDecoratorShadowNode::initialize() {
|
|
18
52
|
// Setting display: contents style results in ForceFlattenView trait being set
|
|
19
53
|
// on the shadow node. This trait causes the node not to have a host view. By
|
|
20
54
|
// removing the trait, it's possible to force RN to create a host view, layout
|
|
21
55
|
// of which can then be customized.
|
|
22
56
|
ShadowNode::traits_.unset(ShadowNodeTraits::ForceFlattenView);
|
|
57
|
+
}
|
|
23
58
|
|
|
59
|
+
void MarkdownTextInputDecoratorShadowNode::makeChildNodeMutable() {
|
|
24
60
|
// When the decorator is cloned and has a child node, the child node should be
|
|
25
61
|
// cloned as well to ensure it is mutable.
|
|
26
62
|
const auto &children = getChildren();
|
|
@@ -28,7 +64,7 @@ void MarkdownTextInputDecoratorShadowNode::initialize() {
|
|
|
28
64
|
react_native_assert(
|
|
29
65
|
children.size() == 1 &&
|
|
30
66
|
"MarkdownTextInputDecoratorView received more than one child");
|
|
31
|
-
|
|
67
|
+
|
|
32
68
|
const auto clonedChild = children[0]->clone({});
|
|
33
69
|
replaceChild(*children[0], clonedChild);
|
|
34
70
|
}
|
|
@@ -21,33 +21,10 @@ class JSI_EXPORT MarkdownTextInputDecoratorShadowNode final
|
|
|
21
21
|
public:
|
|
22
22
|
MarkdownTextInputDecoratorShadowNode(ShadowNodeFragment const &fragment,
|
|
23
23
|
ShadowNodeFamily::Shared const &family,
|
|
24
|
-
ShadowNodeTraits traits)
|
|
25
|
-
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
26
|
-
initialize();
|
|
27
|
-
createCustomContextContainer();
|
|
28
|
-
|
|
29
|
-
if (fragment.children) {
|
|
30
|
-
overwriteTextLayoutManager();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
24
|
+
ShadowNodeTraits traits);
|
|
33
25
|
|
|
34
26
|
MarkdownTextInputDecoratorShadowNode(ShadowNode const &sourceShadowNode,
|
|
35
|
-
ShadowNodeFragment const &fragment)
|
|
36
|
-
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
37
|
-
initialize();
|
|
38
|
-
|
|
39
|
-
const auto &sourceDecorator = static_cast<const MarkdownTextInputDecoratorShadowNode &>(sourceShadowNode);
|
|
40
|
-
|
|
41
|
-
customContextContainer_ = sourceDecorator.customContextContainer_;
|
|
42
|
-
previousMarkdownStyle_ = sourceDecorator.previousMarkdownStyle_;
|
|
43
|
-
previousParserId_ = sourceDecorator.previousParserId_;
|
|
44
|
-
|
|
45
|
-
updateCustomContextContainerIfNeeded();
|
|
46
|
-
|
|
47
|
-
if (fragment.children) {
|
|
48
|
-
overwriteTextLayoutManager();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
27
|
+
ShadowNodeFragment const &fragment);
|
|
51
28
|
|
|
52
29
|
void appendChild(const ShadowNode::Shared &child) override;
|
|
53
30
|
|
|
@@ -69,6 +46,8 @@ private:
|
|
|
69
46
|
void createCustomContextContainer();
|
|
70
47
|
|
|
71
48
|
void updateCustomContextContainerIfNeeded();
|
|
49
|
+
|
|
50
|
+
void makeChildNodeMutable();
|
|
72
51
|
};
|
|
73
52
|
|
|
74
53
|
} // namespace react
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
API_AVAILABLE(ios(15.0))
|
|
7
|
+
@interface BlockquoteTextLayoutFragment : NSTextLayoutFragment
|
|
8
|
+
|
|
9
|
+
@property (nonnull, atomic) RCTMarkdownUtils *markdownUtils;
|
|
10
|
+
|
|
11
|
+
@property NSUInteger depth;
|
|
12
|
+
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/BlockquoteTextLayoutFragment.h>
|
|
2
|
+
|
|
3
|
+
@implementation BlockquoteTextLayoutFragment
|
|
4
|
+
|
|
5
|
+
- (CGRect)boundingRect {
|
|
6
|
+
CGRect fragmentTextBounds = CGRectNull;
|
|
7
|
+
for (NSTextLineFragment *lineFragment in self.textLineFragments) {
|
|
8
|
+
if (lineFragment.characterRange.length == 0) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
CGRect lineFragmentBounds = lineFragment.typographicBounds;
|
|
12
|
+
if (CGRectIsNull(fragmentTextBounds)) {
|
|
13
|
+
fragmentTextBounds = lineFragmentBounds;
|
|
14
|
+
} else {
|
|
15
|
+
fragmentTextBounds = CGRectUnion(fragmentTextBounds, lineFragmentBounds);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
CGFloat marginLeft = _markdownUtils.markdownStyle.blockquoteMarginLeft;
|
|
20
|
+
CGFloat borderWidth = _markdownUtils.markdownStyle.blockquoteBorderWidth;
|
|
21
|
+
CGFloat paddingLeft = _markdownUtils.markdownStyle.blockquotePaddingLeft;
|
|
22
|
+
CGFloat shift = marginLeft + borderWidth + paddingLeft;
|
|
23
|
+
|
|
24
|
+
fragmentTextBounds.origin.x -= (paddingLeft + borderWidth) + shift * (_depth - 1);
|
|
25
|
+
fragmentTextBounds.size.width = borderWidth + shift * (_depth - 1);
|
|
26
|
+
|
|
27
|
+
return fragmentTextBounds;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (void)drawAtPoint:(CGPoint)point inContext:(CGContextRef)ctx {
|
|
31
|
+
CGFloat marginLeft = _markdownUtils.markdownStyle.blockquoteMarginLeft;
|
|
32
|
+
CGFloat borderWidth = _markdownUtils.markdownStyle.blockquoteBorderWidth;
|
|
33
|
+
CGFloat paddingLeft = _markdownUtils.markdownStyle.blockquotePaddingLeft;
|
|
34
|
+
CGFloat shift = marginLeft + borderWidth + paddingLeft;
|
|
35
|
+
|
|
36
|
+
[_markdownUtils.markdownStyle.blockquoteBorderColor setFill];
|
|
37
|
+
|
|
38
|
+
CGRect boundingRect = self.boundingRect;
|
|
39
|
+
for (NSUInteger i = 0; i < _depth; ++i) {
|
|
40
|
+
CGRect ribbonRect = CGRectMake(boundingRect.origin.x + i * shift, boundingRect.origin.y, borderWidth, boundingRect.size.height);
|
|
41
|
+
UIRectFill(ribbonRect);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[super drawAtPoint:point inContext:ctx];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
- (CGRect)renderingSurfaceBounds {
|
|
48
|
+
return CGRectUnion(self.boundingRect, [super renderingSurfaceBounds]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@end
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#import <RNLiveMarkdown/MarkdownBackedTextInputDelegate.h>
|
|
9
9
|
#import <RNLiveMarkdown/MarkdownLayoutManager.h>
|
|
10
|
+
#import <RNLiveMarkdown/MarkdownTextLayoutManagerDelegate.h>
|
|
10
11
|
#import <RNLiveMarkdown/MarkdownTextFieldObserver.h>
|
|
11
12
|
#import <RNLiveMarkdown/MarkdownTextViewObserver.h>
|
|
12
13
|
#import <RNLiveMarkdown/MarkdownTextInputDecoratorComponentView.h>
|
|
@@ -22,6 +23,7 @@ using namespace facebook::react;
|
|
|
22
23
|
RCTMarkdownUtils *_markdownUtils;
|
|
23
24
|
RCTMarkdownStyle *_markdownStyle;
|
|
24
25
|
NSNumber *_parserId;
|
|
26
|
+
MarkdownTextLayoutManagerDelegate *_markdownTextLayoutManagerDelegate;
|
|
25
27
|
MarkdownBackedTextInputDelegate *_markdownBackedTextInputDelegate;
|
|
26
28
|
MarkdownTextStorageDelegate *_markdownTextStorageDelegate;
|
|
27
29
|
MarkdownTextViewObserver *_markdownTextViewObserver;
|
|
@@ -111,18 +113,25 @@ using namespace facebook::react;
|
|
|
111
113
|
// format initial value
|
|
112
114
|
[_textView.textStorage setAttributedString:_textView.attributedText];
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
if (@available(iOS 16.0, *)) {
|
|
117
|
+
_markdownTextLayoutManagerDelegate = [[MarkdownTextLayoutManagerDelegate alloc] init];
|
|
118
|
+
_markdownTextLayoutManagerDelegate.textStorage = _textView.textStorage;
|
|
119
|
+
_markdownTextLayoutManagerDelegate.markdownUtils = _markdownUtils;
|
|
120
|
+
_textView.textLayoutManager.delegate = _markdownTextLayoutManagerDelegate;
|
|
121
|
+
} else {
|
|
122
|
+
NSLayoutManager *layoutManager = _textView.layoutManager; // switching to TextKit 1 compatibility mode
|
|
123
|
+
|
|
124
|
+
// Correct content height in TextKit 1 compatibility mode. (See https://github.com/Expensify/App/issues/41567)
|
|
125
|
+
// Consider removing this fix if it is no longer needed after migrating to TextKit 2.
|
|
126
|
+
CGSize contentSize = _textView.contentSize;
|
|
127
|
+
CGRect textBounds = [layoutManager usedRectForTextContainer:_textView.textContainer];
|
|
128
|
+
contentSize.height = textBounds.size.height + _textView.textContainerInset.top + _textView.textContainerInset.bottom;
|
|
129
|
+
[_textView setContentSize:contentSize];
|
|
130
|
+
|
|
131
|
+
layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue
|
|
132
|
+
object_setClass(layoutManager, [MarkdownLayoutManager class]);
|
|
133
|
+
[layoutManager setValue:_markdownUtils forKey:@"markdownUtils"];
|
|
134
|
+
}
|
|
126
135
|
|
|
127
136
|
// register delegate for fixing cursor position after blockquote
|
|
128
137
|
_markdownBackedTextInputDelegate = [[MarkdownBackedTextInputDelegate alloc] initWithTextView:_textView];
|
|
@@ -137,7 +146,9 @@ using namespace facebook::react;
|
|
|
137
146
|
_observersAdded = false;
|
|
138
147
|
|
|
139
148
|
if (_textView != nil) {
|
|
140
|
-
if (
|
|
149
|
+
if (@available(iOS 16.0, *)) {
|
|
150
|
+
_textView.textLayoutManager.delegate = nil;
|
|
151
|
+
} else if (_textView.layoutManager != nil && [object_getClass(_textView.layoutManager) isEqual:[MarkdownLayoutManager class]]) {
|
|
141
152
|
[_textView.layoutManager setValue:nil forKey:@"markdownUtils"];
|
|
142
153
|
object_setClass(_textView.layoutManager, [NSLayoutManager class]);
|
|
143
154
|
}
|
|
@@ -21,24 +21,10 @@ class JSI_EXPORT MarkdownTextInputDecoratorShadowNode final
|
|
|
21
21
|
public:
|
|
22
22
|
MarkdownTextInputDecoratorShadowNode(ShadowNodeFragment const &fragment,
|
|
23
23
|
ShadowNodeFamily::Shared const &family,
|
|
24
|
-
ShadowNodeTraits traits)
|
|
25
|
-
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
26
|
-
initialize();
|
|
27
|
-
|
|
28
|
-
if (fragment.children) {
|
|
29
|
-
overwriteMeasureCallbackConnector();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
24
|
+
ShadowNodeTraits traits);
|
|
32
25
|
|
|
33
26
|
MarkdownTextInputDecoratorShadowNode(ShadowNode const &sourceShadowNode,
|
|
34
|
-
ShadowNodeFragment const &fragment)
|
|
35
|
-
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
36
|
-
initialize();
|
|
37
|
-
|
|
38
|
-
if (fragment.children) {
|
|
39
|
-
overwriteMeasureCallbackConnector();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
27
|
+
ShadowNodeFragment const &fragment);
|
|
42
28
|
|
|
43
29
|
void appendChild(const ShadowNode::Shared &child) override;
|
|
44
30
|
void replaceChild(const ShadowNode &oldChild,
|
|
@@ -52,6 +38,7 @@ public:
|
|
|
52
38
|
private:
|
|
53
39
|
void initialize();
|
|
54
40
|
void overwriteMeasureCallbackConnector();
|
|
41
|
+
void makeChildNodeMutable();
|
|
55
42
|
void applyMarkdownFormattingToTextInputState(std::shared_ptr<TextInputShadowNode> node,
|
|
56
43
|
const LayoutContext &layoutContext) const;
|
|
57
44
|
static YGSize yogaNodeMeasureCallbackConnector(YGNodeConstRef yogaNode,
|
|
@@ -15,6 +15,31 @@ namespace react {
|
|
|
15
15
|
extern const char MarkdownTextInputDecoratorViewComponentName[] =
|
|
16
16
|
"MarkdownTextInputDecoratorView";
|
|
17
17
|
|
|
18
|
+
MarkdownTextInputDecoratorShadowNode::MarkdownTextInputDecoratorShadowNode(
|
|
19
|
+
ShadowNodeFragment const &fragment,
|
|
20
|
+
ShadowNodeFamily::Shared const &family,
|
|
21
|
+
ShadowNodeTraits traits)
|
|
22
|
+
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
23
|
+
initialize();
|
|
24
|
+
makeChildNodeMutable();
|
|
25
|
+
|
|
26
|
+
if (fragment.children) {
|
|
27
|
+
overwriteMeasureCallbackConnector();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
MarkdownTextInputDecoratorShadowNode::MarkdownTextInputDecoratorShadowNode(
|
|
32
|
+
ShadowNode const &sourceShadowNode,
|
|
33
|
+
ShadowNodeFragment const &fragment)
|
|
34
|
+
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
35
|
+
initialize();
|
|
36
|
+
makeChildNodeMutable();
|
|
37
|
+
|
|
38
|
+
if (fragment.children) {
|
|
39
|
+
overwriteMeasureCallbackConnector();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
void MarkdownTextInputDecoratorShadowNode::initialize() {
|
|
19
44
|
// Setting display: contents style results in ForceFlattenView trait being set
|
|
20
45
|
// on the shadow node. This trait causes the node not to have a host view. By
|
|
@@ -23,6 +48,20 @@ void MarkdownTextInputDecoratorShadowNode::initialize() {
|
|
|
23
48
|
ShadowNode::traits_.unset(ShadowNodeTraits::ForceFlattenView);
|
|
24
49
|
}
|
|
25
50
|
|
|
51
|
+
void MarkdownTextInputDecoratorShadowNode::makeChildNodeMutable() {
|
|
52
|
+
// When the decorator is cloned and has a child node, the child node should be
|
|
53
|
+
// cloned as well to ensure it is mutable.
|
|
54
|
+
const auto &children = getChildren();
|
|
55
|
+
if (!children.empty()) {
|
|
56
|
+
react_native_assert(
|
|
57
|
+
children.size() == 1 &&
|
|
58
|
+
"MarkdownTextInputDecoratorView received more than one child");
|
|
59
|
+
|
|
60
|
+
const auto clonedChild = children[0]->clone({});
|
|
61
|
+
replaceChild(*children[0], clonedChild);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
void MarkdownTextInputDecoratorShadowNode::overwriteMeasureCallbackConnector() {
|
|
27
66
|
const auto &children = getChildren();
|
|
28
67
|
if (children.empty()) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@interface MarkdownTextLayoutManagerDelegate : NSObject <NSTextLayoutManagerDelegate>
|
|
7
|
+
|
|
8
|
+
@property (nonnull, atomic) NSTextStorage *textStorage;
|
|
9
|
+
|
|
10
|
+
@property (nonnull, atomic) RCTMarkdownUtils *markdownUtils;
|
|
11
|
+
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/MarkdownTextLayoutManagerDelegate.h>
|
|
2
|
+
#import <RNLiveMarkdown/BlockquoteTextLayoutFragment.h>
|
|
3
|
+
#import <RNLiveMarkdown/MarkdownFormatter.h>
|
|
4
|
+
|
|
5
|
+
@implementation MarkdownTextLayoutManagerDelegate
|
|
6
|
+
|
|
7
|
+
- (NSTextLayoutFragment *)textLayoutManager:(NSTextLayoutManager *)textLayoutManager textLayoutFragmentForLocation:(id <NSTextLocation>)location inTextElement:(NSTextElement *)textElement
|
|
8
|
+
API_AVAILABLE(ios(15.0)){
|
|
9
|
+
NSInteger index = [textLayoutManager offsetFromLocation:textLayoutManager.documentRange.location toLocation:location];
|
|
10
|
+
if (index < self.textStorage.length) {
|
|
11
|
+
NSNumber *depth = [self.textStorage attribute:RCTLiveMarkdownBlockquoteDepthAttributeName atIndex:index effectiveRange:nil];
|
|
12
|
+
if (depth != nil) {
|
|
13
|
+
BlockquoteTextLayoutFragment *textLayoutFragment = [[BlockquoteTextLayoutFragment alloc] initWithTextElement:textElement range:textElement.elementRange];
|
|
14
|
+
textLayoutFragment.markdownUtils = _markdownUtils;
|
|
15
|
+
textLayoutFragment.depth = [depth unsignedIntValue];
|
|
16
|
+
return textLayoutFragment;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [[NSTextLayoutFragment alloc] initWithTextElement:textElement range:textElement.elementRange];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@end
|
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.295",
|
|
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",
|