react-native-enriched-markdown 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -8
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +17 -2
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +6 -1
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/android/src/main/cpp/jni-adapter.cpp +28 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +132 -15
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +1 -16
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +67 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +241 -21
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +17 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +13 -18
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +23 -24
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +18 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +22 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +16 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +23 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +24 -10
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +8 -17
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +19 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +3 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +32 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +22 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +23 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +58 -56
- package/android/src/main/jni/CMakeLists.txt +1 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +0 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +2 -14
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +3 -0
- package/cpp/parser/MD4CParser.cpp +21 -8
- package/cpp/parser/MD4CParser.hpp +5 -1
- package/cpp/parser/MarkdownASTNode.hpp +2 -0
- package/ios/EnrichedMarkdownText.mm +356 -29
- package/ios/attachments/{ImageAttachment.h → EnrichedMarkdownImageAttachment.h} +1 -1
- package/ios/attachments/{ImageAttachment.m → EnrichedMarkdownImageAttachment.m} +4 -4
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/ios/parser/MarkdownASTNode.h +2 -0
- package/ios/parser/MarkdownParser.h +9 -0
- package/ios/parser/MarkdownParser.mm +31 -2
- package/ios/parser/MarkdownParserBridge.mm +13 -3
- package/ios/renderer/AttributedRenderer.h +2 -0
- package/ios/renderer/AttributedRenderer.m +52 -19
- package/ios/renderer/BlockquoteRenderer.m +7 -6
- package/ios/renderer/CodeBlockRenderer.m +9 -8
- package/ios/renderer/HeadingRenderer.m +31 -24
- package/ios/renderer/ImageRenderer.m +31 -10
- package/ios/renderer/ListItemRenderer.m +51 -39
- package/ios/renderer/ListRenderer.m +21 -18
- package/ios/renderer/ParagraphRenderer.m +27 -16
- package/ios/renderer/RenderContext.h +17 -0
- package/ios/renderer/RenderContext.m +66 -2
- package/ios/renderer/RendererFactory.m +6 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +46 -0
- package/ios/styles/StyleConfig.mm +351 -12
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/CodeBlockBackground.m +4 -9
- package/ios/utils/FontUtils.h +5 -0
- package/ios/utils/FontUtils.m +14 -0
- package/ios/utils/HTMLGenerator.m +21 -7
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.m +18 -5
- package/ios/utils/ParagraphStyleUtils.h +10 -2
- package/ios/utils/ParagraphStyleUtils.m +57 -2
- package/ios/utils/PasteboardUtils.h +1 -1
- package/ios/utils/PasteboardUtils.m +3 -3
- package/lib/module/EnrichedMarkdownText.js +33 -2
- package/lib/module/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +58 -14
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +85 -3
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +75 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownText.tsx +133 -5
- package/src/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/src/index.tsx +5 -2
- package/src/normalizeMarkdownStyle.ts +46 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +0 -9
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +0 -25
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
#import "EnrichedMarkdownText.h"
|
|
2
|
+
#import "AccessibilityInfo.h"
|
|
2
3
|
#import "AttributedRenderer.h"
|
|
3
4
|
#import "CodeBlockBackground.h"
|
|
4
5
|
#import "EditMenuUtils.h"
|
|
6
|
+
#import "EnrichedMarkdownImageAttachment.h"
|
|
5
7
|
#import "FontUtils.h"
|
|
6
|
-
#import "ImageAttachment.h"
|
|
7
8
|
#import "LastElementUtils.h"
|
|
8
9
|
#import "MarkdownASTNode.h"
|
|
10
|
+
#import "MarkdownAccessibilityElementBuilder.h"
|
|
9
11
|
#import "MarkdownExtractor.h"
|
|
10
12
|
#import "MarkdownParser.h"
|
|
13
|
+
#import "ParagraphStyleUtils.h"
|
|
11
14
|
#import "RenderContext.h"
|
|
12
15
|
#import "RuntimeKeys.h"
|
|
13
16
|
#import "StyleConfig.h"
|
|
14
17
|
#import "TextViewLayoutManager.h"
|
|
18
|
+
#import <React/RCTUtils.h>
|
|
15
19
|
#import <objc/runtime.h>
|
|
16
20
|
|
|
17
21
|
#import <ReactNativeEnrichedMarkdown/EnrichedMarkdownTextComponentDescriptor.h>
|
|
@@ -39,6 +43,7 @@ using namespace facebook::react;
|
|
|
39
43
|
MarkdownParser *_parser;
|
|
40
44
|
NSString *_cachedMarkdown;
|
|
41
45
|
StyleConfig *_config;
|
|
46
|
+
Md4cFlags *_md4cFlags;
|
|
42
47
|
|
|
43
48
|
// Background rendering support
|
|
44
49
|
dispatch_queue_t _renderQueue;
|
|
@@ -47,6 +52,22 @@ using namespace facebook::react;
|
|
|
47
52
|
|
|
48
53
|
EnrichedMarkdownTextShadowNode::ConcreteState::Shared _state;
|
|
49
54
|
int _heightUpdateCounter;
|
|
55
|
+
|
|
56
|
+
// Font scale tracking
|
|
57
|
+
CGFloat _currentFontScale;
|
|
58
|
+
BOOL _allowFontScaling;
|
|
59
|
+
CGFloat _maxFontSizeMultiplier;
|
|
60
|
+
|
|
61
|
+
// Last element marginBottom tracking
|
|
62
|
+
CGFloat _lastElementMarginBottom;
|
|
63
|
+
BOOL _allowTrailingMargin;
|
|
64
|
+
|
|
65
|
+
// iOS link preview control
|
|
66
|
+
BOOL _enableLinkPreview;
|
|
67
|
+
|
|
68
|
+
// Accessibility data for VoiceOver
|
|
69
|
+
AccessibilityInfo *_accessibilityInfo;
|
|
70
|
+
NSMutableArray<UIAccessibilityElement *> *_accessibilityElements;
|
|
50
71
|
}
|
|
51
72
|
|
|
52
73
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
@@ -61,37 +82,37 @@ using namespace facebook::react;
|
|
|
61
82
|
NSAttributedString *text = _textView.attributedText;
|
|
62
83
|
CGFloat defaultHeight = [UIFont systemFontOfSize:16.0].lineHeight;
|
|
63
84
|
|
|
64
|
-
if (
|
|
85
|
+
if (text.length == 0) {
|
|
65
86
|
return CGSizeMake(maxWidth, defaultHeight);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
89
|
+
// Use UITextView's layout manager for measurement to avoid
|
|
90
|
+
// boundingRectWithSize: height discrepancies with NSTextAttachment objects.
|
|
91
|
+
_textView.textContainer.size = CGSizeMake(maxWidth, CGFLOAT_MAX);
|
|
92
|
+
[_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
|
|
93
|
+
CGRect usedRect = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer];
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
CGFloat measuredWidth = ceil(usedRect.size.width);
|
|
96
|
+
CGFloat measuredHeight = usedRect.size.height;
|
|
76
97
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
// When text ends with \n (e.g. code block's bottom padding spacer),
|
|
99
|
+
// TextKit creates an "extra line fragment" after it that adds unwanted height.
|
|
100
|
+
CGRect extraFragment = _textView.layoutManager.extraLineFragmentRect;
|
|
101
|
+
if (!CGRectIsEmpty(extraFragment)) {
|
|
102
|
+
measuredHeight -= extraFragment.size.height;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
context:nil];
|
|
86
|
-
|
|
87
|
-
CGFloat measuredHeight = boundingRect.size.height;
|
|
88
|
-
|
|
89
|
-
// Compensate for iOS not measuring trailing newlines (code block bottom padding)
|
|
105
|
+
// Code block's bottom padding is a spacer \n with minimumLineHeight = codeBlockPadding.
|
|
106
|
+
// The layout manager may not size it accurately, so add the padding explicitly.
|
|
90
107
|
if (isLastElementCodeBlock(text)) {
|
|
91
108
|
measuredHeight += [_config codeBlockPadding];
|
|
92
109
|
}
|
|
93
110
|
|
|
94
|
-
|
|
111
|
+
if (_allowTrailingMargin && _lastElementMarginBottom > 0) {
|
|
112
|
+
measuredHeight += _lastElementMarginBottom;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return CGSizeMake(measuredWidth, ceil(measuredHeight));
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
- (void)updateState:(const facebook::react::State::Shared &)state
|
|
@@ -124,17 +145,61 @@ using namespace facebook::react;
|
|
|
124
145
|
|
|
125
146
|
self.backgroundColor = [UIColor clearColor];
|
|
126
147
|
_parser = [[MarkdownParser alloc] init];
|
|
148
|
+
_md4cFlags = [Md4cFlags defaultFlags];
|
|
127
149
|
|
|
128
150
|
// Serial queue for background rendering
|
|
129
151
|
_renderQueue = dispatch_queue_create("com.swmansion.enriched.markdown.render", DISPATCH_QUEUE_SERIAL);
|
|
130
152
|
_currentRenderId = 0;
|
|
131
153
|
|
|
154
|
+
// Initialize font scale from current content size category
|
|
155
|
+
_allowFontScaling = YES;
|
|
156
|
+
_maxFontSizeMultiplier = 0;
|
|
157
|
+
_allowTrailingMargin = NO;
|
|
158
|
+
_currentFontScale = RCTFontSizeMultiplier();
|
|
159
|
+
_enableLinkPreview = YES;
|
|
160
|
+
|
|
161
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
162
|
+
selector:@selector(contentSizeCategoryDidChange:)
|
|
163
|
+
name:UIContentSizeCategoryDidChangeNotification
|
|
164
|
+
object:nil];
|
|
165
|
+
|
|
132
166
|
[self setupTextView];
|
|
133
167
|
}
|
|
134
168
|
|
|
135
169
|
return self;
|
|
136
170
|
}
|
|
137
171
|
|
|
172
|
+
- (void)dealloc
|
|
173
|
+
{
|
|
174
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (CGFloat)effectiveFontScale
|
|
178
|
+
{
|
|
179
|
+
// If font scaling is disabled, always return 1.0 (no scaling)
|
|
180
|
+
return _allowFontScaling ? _currentFontScale : 1.0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
- (void)contentSizeCategoryDidChange:(NSNotification *)notification
|
|
184
|
+
{
|
|
185
|
+
if (!_allowFontScaling) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
CGFloat newFontScale = RCTFontSizeMultiplier();
|
|
190
|
+
if (_currentFontScale != newFontScale) {
|
|
191
|
+
_currentFontScale = newFontScale;
|
|
192
|
+
|
|
193
|
+
if (_config != nil) {
|
|
194
|
+
[_config setFontScaleMultiplier:[self effectiveFontScale]];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (_cachedMarkdown != nil && _cachedMarkdown.length > 0) {
|
|
198
|
+
[self renderMarkdownContent:_cachedMarkdown];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
138
203
|
- (void)setupTextView
|
|
139
204
|
{
|
|
140
205
|
_textView = [[UITextView alloc] init];
|
|
@@ -145,15 +210,19 @@ using namespace facebook::react;
|
|
|
145
210
|
_textView.editable = NO;
|
|
146
211
|
_textView.delegate = self;
|
|
147
212
|
_textView.scrollEnabled = NO;
|
|
213
|
+
_textView.showsVerticalScrollIndicator = NO;
|
|
214
|
+
_textView.showsHorizontalScrollIndicator = NO;
|
|
148
215
|
_textView.textContainerInset = UIEdgeInsetsZero;
|
|
149
216
|
_textView.textContainer.lineFragmentPadding = 0;
|
|
150
217
|
// Disable UITextView's default link styling - we handle it directly in attributed strings
|
|
151
218
|
_textView.linkTextAttributes = @{};
|
|
152
|
-
//
|
|
219
|
+
// selectable controls text selection and link previews
|
|
153
220
|
// Default to YES to match the prop default
|
|
154
221
|
_textView.selectable = YES;
|
|
155
222
|
// Hide initially to prevent flash before content is rendered
|
|
156
223
|
_textView.hidden = YES;
|
|
224
|
+
// Disable textView's built-in accessibility - we provide custom elements with proper traits
|
|
225
|
+
_textView.accessibilityElementsHidden = YES;
|
|
157
226
|
|
|
158
227
|
// Add tap gesture recognizer
|
|
159
228
|
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
|
|
@@ -218,14 +287,19 @@ using namespace facebook::react;
|
|
|
218
287
|
// Capture state needed for background rendering
|
|
219
288
|
StyleConfig *config = [_config copy];
|
|
220
289
|
MarkdownParser *parser = _parser;
|
|
290
|
+
Md4cFlags *md4cFlags = [_md4cFlags copy];
|
|
221
291
|
NSUInteger inputLength = markdownString.length;
|
|
222
292
|
NSDate *scheduleStart = [NSDate date];
|
|
223
293
|
|
|
294
|
+
// Capture font scaling settings
|
|
295
|
+
BOOL allowFontScaling = _allowFontScaling;
|
|
296
|
+
CGFloat maxFontSizeMultiplier = _maxFontSizeMultiplier;
|
|
297
|
+
|
|
224
298
|
// Dispatch heavy work to background queue
|
|
225
299
|
dispatch_async(_renderQueue, ^{
|
|
226
300
|
// 1. Parse Markdown → AST (C++ md4c parser)
|
|
227
301
|
NSDate *parseStart = [NSDate date];
|
|
228
|
-
MarkdownASTNode *ast = [parser parseMarkdown:markdownString];
|
|
302
|
+
MarkdownASTNode *ast = [parser parseMarkdown:markdownString flags:md4cFlags];
|
|
229
303
|
if (!ast) {
|
|
230
304
|
return;
|
|
231
305
|
}
|
|
@@ -235,9 +309,14 @@ using namespace facebook::react;
|
|
|
235
309
|
// 2. Render AST → NSAttributedString
|
|
236
310
|
NSDate *renderStart = [NSDate date];
|
|
237
311
|
AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:config];
|
|
312
|
+
[renderer setAllowTrailingMargin:self->_allowTrailingMargin];
|
|
238
313
|
RenderContext *context = [RenderContext new];
|
|
314
|
+
context.allowFontScaling = allowFontScaling;
|
|
315
|
+
context.maxFontSizeMultiplier = maxFontSizeMultiplier;
|
|
239
316
|
NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
|
|
240
317
|
|
|
318
|
+
self->_lastElementMarginBottom = [renderer getLastElementMarginBottom];
|
|
319
|
+
|
|
241
320
|
// Add link attributes
|
|
242
321
|
for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
|
|
243
322
|
NSValue *rangeValue = context.linkRanges[i];
|
|
@@ -245,6 +324,10 @@ using namespace facebook::react;
|
|
|
245
324
|
NSString *url = context.linkURLs[i];
|
|
246
325
|
[attributedText addAttribute:@"linkURL" value:url range:range];
|
|
247
326
|
}
|
|
327
|
+
|
|
328
|
+
// Capture accessibility info
|
|
329
|
+
AccessibilityInfo *accessibilityInfo = [AccessibilityInfo infoFromContext:context];
|
|
330
|
+
|
|
248
331
|
NSTimeInterval renderTime = [[NSDate date] timeIntervalSinceDate:renderStart] * 1000;
|
|
249
332
|
NSUInteger styledLength = attributedText.length;
|
|
250
333
|
|
|
@@ -255,6 +338,9 @@ using namespace facebook::react;
|
|
|
255
338
|
return;
|
|
256
339
|
}
|
|
257
340
|
|
|
341
|
+
// Store accessibility info
|
|
342
|
+
self->_accessibilityInfo = accessibilityInfo;
|
|
343
|
+
|
|
258
344
|
[self applyRenderedText:attributedText];
|
|
259
345
|
|
|
260
346
|
NSTimeInterval totalTime = [[NSDate date] timeIntervalSinceDate:scheduleStart] * 1000;
|
|
@@ -279,15 +365,20 @@ using namespace facebook::react;
|
|
|
279
365
|
_blockAsyncRender = YES;
|
|
280
366
|
_cachedMarkdown = [markdownString copy];
|
|
281
367
|
|
|
282
|
-
MarkdownASTNode *ast = [_parser parseMarkdown:markdownString];
|
|
368
|
+
MarkdownASTNode *ast = [_parser parseMarkdown:markdownString flags:_md4cFlags];
|
|
283
369
|
if (!ast) {
|
|
284
370
|
return;
|
|
285
371
|
}
|
|
286
372
|
|
|
287
373
|
AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:_config];
|
|
374
|
+
[renderer setAllowTrailingMargin:_allowTrailingMargin];
|
|
288
375
|
RenderContext *context = [RenderContext new];
|
|
376
|
+
context.allowFontScaling = _allowFontScaling;
|
|
377
|
+
context.maxFontSizeMultiplier = _maxFontSizeMultiplier;
|
|
289
378
|
NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
|
|
290
379
|
|
|
380
|
+
_lastElementMarginBottom = [renderer getLastElementMarginBottom];
|
|
381
|
+
|
|
291
382
|
for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
|
|
292
383
|
NSValue *rangeValue = context.linkRanges[i];
|
|
293
384
|
NSRange range = [rangeValue rangeValue];
|
|
@@ -295,6 +386,9 @@ using namespace facebook::react;
|
|
|
295
386
|
[attributedText addAttribute:@"linkURL" value:url range:range];
|
|
296
387
|
}
|
|
297
388
|
|
|
389
|
+
// Store accessibility info
|
|
390
|
+
_accessibilityInfo = [AccessibilityInfo infoFromContext:context];
|
|
391
|
+
|
|
298
392
|
_textView.attributedText = attributedText;
|
|
299
393
|
}
|
|
300
394
|
|
|
@@ -323,6 +417,9 @@ using namespace facebook::react;
|
|
|
323
417
|
// Request height recalculation from shadow node FIRST
|
|
324
418
|
[self requestHeightUpdate];
|
|
325
419
|
|
|
420
|
+
// Build accessibility elements after layout is complete
|
|
421
|
+
[self buildAccessibilityElements];
|
|
422
|
+
|
|
326
423
|
// Show text view on next run loop, after layout has settled
|
|
327
424
|
if (_textView.hidden) {
|
|
328
425
|
dispatch_async(dispatch_get_main_queue(), ^{ self->_textView.hidden = NO; });
|
|
@@ -338,6 +435,7 @@ using namespace facebook::react;
|
|
|
338
435
|
|
|
339
436
|
if (_config == nil) {
|
|
340
437
|
_config = [[StyleConfig alloc] init];
|
|
438
|
+
[_config setFontScaleMultiplier:[self effectiveFontScale]];
|
|
341
439
|
}
|
|
342
440
|
|
|
343
441
|
// Paragraph style
|
|
@@ -378,6 +476,11 @@ using namespace facebook::react;
|
|
|
378
476
|
stylePropChanged = YES;
|
|
379
477
|
}
|
|
380
478
|
|
|
479
|
+
if (newViewProps.markdownStyle.paragraph.marginTop != oldViewProps.markdownStyle.paragraph.marginTop) {
|
|
480
|
+
[_config setParagraphMarginTop:newViewProps.markdownStyle.paragraph.marginTop];
|
|
481
|
+
stylePropChanged = YES;
|
|
482
|
+
}
|
|
483
|
+
|
|
381
484
|
if (newViewProps.markdownStyle.paragraph.marginBottom != oldViewProps.markdownStyle.paragraph.marginBottom) {
|
|
382
485
|
[_config setParagraphMarginBottom:newViewProps.markdownStyle.paragraph.marginBottom];
|
|
383
486
|
stylePropChanged = YES;
|
|
@@ -388,6 +491,11 @@ using namespace facebook::react;
|
|
|
388
491
|
stylePropChanged = YES;
|
|
389
492
|
}
|
|
390
493
|
|
|
494
|
+
if (newViewProps.markdownStyle.paragraph.textAlign != oldViewProps.markdownStyle.paragraph.textAlign) {
|
|
495
|
+
[_config setParagraphTextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.paragraph.textAlign.c_str()))];
|
|
496
|
+
stylePropChanged = YES;
|
|
497
|
+
}
|
|
498
|
+
|
|
391
499
|
// H1 style
|
|
392
500
|
if (newViewProps.markdownStyle.h1.fontSize != oldViewProps.markdownStyle.h1.fontSize) {
|
|
393
501
|
[_config setH1FontSize:newViewProps.markdownStyle.h1.fontSize];
|
|
@@ -424,6 +532,11 @@ using namespace facebook::react;
|
|
|
424
532
|
stylePropChanged = YES;
|
|
425
533
|
}
|
|
426
534
|
|
|
535
|
+
if (newViewProps.markdownStyle.h1.marginTop != oldViewProps.markdownStyle.h1.marginTop) {
|
|
536
|
+
[_config setH1MarginTop:newViewProps.markdownStyle.h1.marginTop];
|
|
537
|
+
stylePropChanged = YES;
|
|
538
|
+
}
|
|
539
|
+
|
|
427
540
|
if (newViewProps.markdownStyle.h1.marginBottom != oldViewProps.markdownStyle.h1.marginBottom) {
|
|
428
541
|
[_config setH1MarginBottom:newViewProps.markdownStyle.h1.marginBottom];
|
|
429
542
|
stylePropChanged = YES;
|
|
@@ -434,6 +547,11 @@ using namespace facebook::react;
|
|
|
434
547
|
stylePropChanged = YES;
|
|
435
548
|
}
|
|
436
549
|
|
|
550
|
+
if (newViewProps.markdownStyle.h1.textAlign != oldViewProps.markdownStyle.h1.textAlign) {
|
|
551
|
+
[_config setH1TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h1.textAlign.c_str()))];
|
|
552
|
+
stylePropChanged = YES;
|
|
553
|
+
}
|
|
554
|
+
|
|
437
555
|
// H2 style
|
|
438
556
|
if (newViewProps.markdownStyle.h2.fontSize != oldViewProps.markdownStyle.h2.fontSize) {
|
|
439
557
|
[_config setH2FontSize:newViewProps.markdownStyle.h2.fontSize];
|
|
@@ -470,6 +588,11 @@ using namespace facebook::react;
|
|
|
470
588
|
stylePropChanged = YES;
|
|
471
589
|
}
|
|
472
590
|
|
|
591
|
+
if (newViewProps.markdownStyle.h2.marginTop != oldViewProps.markdownStyle.h2.marginTop) {
|
|
592
|
+
[_config setH2MarginTop:newViewProps.markdownStyle.h2.marginTop];
|
|
593
|
+
stylePropChanged = YES;
|
|
594
|
+
}
|
|
595
|
+
|
|
473
596
|
if (newViewProps.markdownStyle.h2.marginBottom != oldViewProps.markdownStyle.h2.marginBottom) {
|
|
474
597
|
[_config setH2MarginBottom:newViewProps.markdownStyle.h2.marginBottom];
|
|
475
598
|
stylePropChanged = YES;
|
|
@@ -480,6 +603,11 @@ using namespace facebook::react;
|
|
|
480
603
|
stylePropChanged = YES;
|
|
481
604
|
}
|
|
482
605
|
|
|
606
|
+
if (newViewProps.markdownStyle.h2.textAlign != oldViewProps.markdownStyle.h2.textAlign) {
|
|
607
|
+
[_config setH2TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h2.textAlign.c_str()))];
|
|
608
|
+
stylePropChanged = YES;
|
|
609
|
+
}
|
|
610
|
+
|
|
483
611
|
// H3 style
|
|
484
612
|
if (newViewProps.markdownStyle.h3.fontSize != oldViewProps.markdownStyle.h3.fontSize) {
|
|
485
613
|
[_config setH3FontSize:newViewProps.markdownStyle.h3.fontSize];
|
|
@@ -516,6 +644,11 @@ using namespace facebook::react;
|
|
|
516
644
|
stylePropChanged = YES;
|
|
517
645
|
}
|
|
518
646
|
|
|
647
|
+
if (newViewProps.markdownStyle.h3.marginTop != oldViewProps.markdownStyle.h3.marginTop) {
|
|
648
|
+
[_config setH3MarginTop:newViewProps.markdownStyle.h3.marginTop];
|
|
649
|
+
stylePropChanged = YES;
|
|
650
|
+
}
|
|
651
|
+
|
|
519
652
|
if (newViewProps.markdownStyle.h3.marginBottom != oldViewProps.markdownStyle.h3.marginBottom) {
|
|
520
653
|
[_config setH3MarginBottom:newViewProps.markdownStyle.h3.marginBottom];
|
|
521
654
|
stylePropChanged = YES;
|
|
@@ -526,6 +659,11 @@ using namespace facebook::react;
|
|
|
526
659
|
stylePropChanged = YES;
|
|
527
660
|
}
|
|
528
661
|
|
|
662
|
+
if (newViewProps.markdownStyle.h3.textAlign != oldViewProps.markdownStyle.h3.textAlign) {
|
|
663
|
+
[_config setH3TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h3.textAlign.c_str()))];
|
|
664
|
+
stylePropChanged = YES;
|
|
665
|
+
}
|
|
666
|
+
|
|
529
667
|
// H4 style
|
|
530
668
|
if (newViewProps.markdownStyle.h4.fontSize != oldViewProps.markdownStyle.h4.fontSize) {
|
|
531
669
|
[_config setH4FontSize:newViewProps.markdownStyle.h4.fontSize];
|
|
@@ -562,6 +700,11 @@ using namespace facebook::react;
|
|
|
562
700
|
stylePropChanged = YES;
|
|
563
701
|
}
|
|
564
702
|
|
|
703
|
+
if (newViewProps.markdownStyle.h4.marginTop != oldViewProps.markdownStyle.h4.marginTop) {
|
|
704
|
+
[_config setH4MarginTop:newViewProps.markdownStyle.h4.marginTop];
|
|
705
|
+
stylePropChanged = YES;
|
|
706
|
+
}
|
|
707
|
+
|
|
565
708
|
if (newViewProps.markdownStyle.h4.marginBottom != oldViewProps.markdownStyle.h4.marginBottom) {
|
|
566
709
|
[_config setH4MarginBottom:newViewProps.markdownStyle.h4.marginBottom];
|
|
567
710
|
stylePropChanged = YES;
|
|
@@ -572,6 +715,11 @@ using namespace facebook::react;
|
|
|
572
715
|
stylePropChanged = YES;
|
|
573
716
|
}
|
|
574
717
|
|
|
718
|
+
if (newViewProps.markdownStyle.h4.textAlign != oldViewProps.markdownStyle.h4.textAlign) {
|
|
719
|
+
[_config setH4TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h4.textAlign.c_str()))];
|
|
720
|
+
stylePropChanged = YES;
|
|
721
|
+
}
|
|
722
|
+
|
|
575
723
|
// H5 style
|
|
576
724
|
if (newViewProps.markdownStyle.h5.fontSize != oldViewProps.markdownStyle.h5.fontSize) {
|
|
577
725
|
[_config setH5FontSize:newViewProps.markdownStyle.h5.fontSize];
|
|
@@ -608,6 +756,11 @@ using namespace facebook::react;
|
|
|
608
756
|
stylePropChanged = YES;
|
|
609
757
|
}
|
|
610
758
|
|
|
759
|
+
if (newViewProps.markdownStyle.h5.marginTop != oldViewProps.markdownStyle.h5.marginTop) {
|
|
760
|
+
[_config setH5MarginTop:newViewProps.markdownStyle.h5.marginTop];
|
|
761
|
+
stylePropChanged = YES;
|
|
762
|
+
}
|
|
763
|
+
|
|
611
764
|
if (newViewProps.markdownStyle.h5.marginBottom != oldViewProps.markdownStyle.h5.marginBottom) {
|
|
612
765
|
[_config setH5MarginBottom:newViewProps.markdownStyle.h5.marginBottom];
|
|
613
766
|
stylePropChanged = YES;
|
|
@@ -618,6 +771,11 @@ using namespace facebook::react;
|
|
|
618
771
|
stylePropChanged = YES;
|
|
619
772
|
}
|
|
620
773
|
|
|
774
|
+
if (newViewProps.markdownStyle.h5.textAlign != oldViewProps.markdownStyle.h5.textAlign) {
|
|
775
|
+
[_config setH5TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h5.textAlign.c_str()))];
|
|
776
|
+
stylePropChanged = YES;
|
|
777
|
+
}
|
|
778
|
+
|
|
621
779
|
// H6 style
|
|
622
780
|
if (newViewProps.markdownStyle.h6.fontSize != oldViewProps.markdownStyle.h6.fontSize) {
|
|
623
781
|
[_config setH6FontSize:newViewProps.markdownStyle.h6.fontSize];
|
|
@@ -654,6 +812,11 @@ using namespace facebook::react;
|
|
|
654
812
|
stylePropChanged = YES;
|
|
655
813
|
}
|
|
656
814
|
|
|
815
|
+
if (newViewProps.markdownStyle.h6.marginTop != oldViewProps.markdownStyle.h6.marginTop) {
|
|
816
|
+
[_config setH6MarginTop:newViewProps.markdownStyle.h6.marginTop];
|
|
817
|
+
stylePropChanged = YES;
|
|
818
|
+
}
|
|
819
|
+
|
|
657
820
|
if (newViewProps.markdownStyle.h6.marginBottom != oldViewProps.markdownStyle.h6.marginBottom) {
|
|
658
821
|
[_config setH6MarginBottom:newViewProps.markdownStyle.h6.marginBottom];
|
|
659
822
|
stylePropChanged = YES;
|
|
@@ -664,6 +827,11 @@ using namespace facebook::react;
|
|
|
664
827
|
stylePropChanged = YES;
|
|
665
828
|
}
|
|
666
829
|
|
|
830
|
+
if (newViewProps.markdownStyle.h6.textAlign != oldViewProps.markdownStyle.h6.textAlign) {
|
|
831
|
+
[_config setH6TextAlign:textAlignmentFromString(@(newViewProps.markdownStyle.h6.textAlign.c_str()))];
|
|
832
|
+
stylePropChanged = YES;
|
|
833
|
+
}
|
|
834
|
+
|
|
667
835
|
// Blockquote style
|
|
668
836
|
if (newViewProps.markdownStyle.blockquote.fontSize != oldViewProps.markdownStyle.blockquote.fontSize) {
|
|
669
837
|
[_config setBlockquoteFontSize:newViewProps.markdownStyle.blockquote.fontSize];
|
|
@@ -690,6 +858,11 @@ using namespace facebook::react;
|
|
|
690
858
|
stylePropChanged = YES;
|
|
691
859
|
}
|
|
692
860
|
|
|
861
|
+
if (newViewProps.markdownStyle.blockquote.marginTop != oldViewProps.markdownStyle.blockquote.marginTop) {
|
|
862
|
+
[_config setBlockquoteMarginTop:newViewProps.markdownStyle.blockquote.marginTop];
|
|
863
|
+
stylePropChanged = YES;
|
|
864
|
+
}
|
|
865
|
+
|
|
693
866
|
if (newViewProps.markdownStyle.blockquote.marginBottom != oldViewProps.markdownStyle.blockquote.marginBottom) {
|
|
694
867
|
[_config setBlockquoteMarginBottom:newViewProps.markdownStyle.blockquote.marginBottom];
|
|
695
868
|
stylePropChanged = YES;
|
|
@@ -754,6 +927,18 @@ using namespace facebook::react;
|
|
|
754
927
|
stylePropChanged = YES;
|
|
755
928
|
}
|
|
756
929
|
|
|
930
|
+
if (newViewProps.markdownStyle.strikethrough.color != oldViewProps.markdownStyle.strikethrough.color) {
|
|
931
|
+
UIColor *strikethroughColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.strikethrough.color);
|
|
932
|
+
[_config setStrikethroughColor:strikethroughColor];
|
|
933
|
+
stylePropChanged = YES;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (newViewProps.markdownStyle.underline.color != oldViewProps.markdownStyle.underline.color) {
|
|
937
|
+
UIColor *underlineColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.underline.color);
|
|
938
|
+
[_config setUnderlineColor:underlineColor];
|
|
939
|
+
stylePropChanged = YES;
|
|
940
|
+
}
|
|
941
|
+
|
|
757
942
|
if (newViewProps.markdownStyle.code.color != oldViewProps.markdownStyle.code.color) {
|
|
758
943
|
if (newViewProps.markdownStyle.code.color) {
|
|
759
944
|
UIColor *codeColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.color);
|
|
@@ -794,6 +979,11 @@ using namespace facebook::react;
|
|
|
794
979
|
stylePropChanged = YES;
|
|
795
980
|
}
|
|
796
981
|
|
|
982
|
+
if (newViewProps.markdownStyle.image.marginTop != oldViewProps.markdownStyle.image.marginTop) {
|
|
983
|
+
[_config setImageMarginTop:newViewProps.markdownStyle.image.marginTop];
|
|
984
|
+
stylePropChanged = YES;
|
|
985
|
+
}
|
|
986
|
+
|
|
797
987
|
if (newViewProps.markdownStyle.image.marginBottom != oldViewProps.markdownStyle.image.marginBottom) {
|
|
798
988
|
[_config setImageMarginBottom:newViewProps.markdownStyle.image.marginBottom];
|
|
799
989
|
stylePropChanged = YES;
|
|
@@ -828,6 +1018,11 @@ using namespace facebook::react;
|
|
|
828
1018
|
stylePropChanged = YES;
|
|
829
1019
|
}
|
|
830
1020
|
|
|
1021
|
+
if (newViewProps.markdownStyle.list.marginTop != oldViewProps.markdownStyle.list.marginTop) {
|
|
1022
|
+
[_config setListStyleMarginTop:newViewProps.markdownStyle.list.marginTop];
|
|
1023
|
+
stylePropChanged = YES;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
831
1026
|
if (newViewProps.markdownStyle.list.marginBottom != oldViewProps.markdownStyle.list.marginBottom) {
|
|
832
1027
|
[_config setListStyleMarginBottom:newViewProps.markdownStyle.list.marginBottom];
|
|
833
1028
|
stylePropChanged = YES;
|
|
@@ -898,6 +1093,11 @@ using namespace facebook::react;
|
|
|
898
1093
|
stylePropChanged = YES;
|
|
899
1094
|
}
|
|
900
1095
|
|
|
1096
|
+
if (newViewProps.markdownStyle.codeBlock.marginTop != oldViewProps.markdownStyle.codeBlock.marginTop) {
|
|
1097
|
+
[_config setCodeBlockMarginTop:newViewProps.markdownStyle.codeBlock.marginTop];
|
|
1098
|
+
stylePropChanged = YES;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
901
1101
|
if (newViewProps.markdownStyle.codeBlock.marginBottom != oldViewProps.markdownStyle.codeBlock.marginBottom) {
|
|
902
1102
|
[_config setCodeBlockMarginBottom:newViewProps.markdownStyle.codeBlock.marginBottom];
|
|
903
1103
|
stylePropChanged = YES;
|
|
@@ -967,16 +1167,51 @@ using namespace facebook::react;
|
|
|
967
1167
|
}
|
|
968
1168
|
}
|
|
969
1169
|
|
|
970
|
-
// Control text selection and link previews via
|
|
971
|
-
// According to Apple docs,
|
|
1170
|
+
// Control text selection and link previews via selectable property
|
|
1171
|
+
// According to Apple docs, selectable controls whether text selection and link previews work
|
|
972
1172
|
// https://developer.apple.com/documentation/uikit/uitextview/isselectable
|
|
973
|
-
if (_textView.selectable != newViewProps.
|
|
974
|
-
_textView.selectable = newViewProps.
|
|
1173
|
+
if (_textView.selectable != newViewProps.selectable) {
|
|
1174
|
+
_textView.selectable = newViewProps.selectable;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) {
|
|
1178
|
+
_allowFontScaling = newViewProps.allowFontScaling;
|
|
1179
|
+
|
|
1180
|
+
if (_config != nil) {
|
|
1181
|
+
[_config setFontScaleMultiplier:[self effectiveFontScale]];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
stylePropChanged = YES;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (newViewProps.maxFontSizeMultiplier != oldViewProps.maxFontSizeMultiplier) {
|
|
1188
|
+
_maxFontSizeMultiplier = newViewProps.maxFontSizeMultiplier;
|
|
1189
|
+
|
|
1190
|
+
if (_config != nil) {
|
|
1191
|
+
[_config setMaxFontSizeMultiplier:_maxFontSizeMultiplier];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
stylePropChanged = YES;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Update allowTrailingMargin
|
|
1198
|
+
if (newViewProps.allowTrailingMargin != oldViewProps.allowTrailingMargin) {
|
|
1199
|
+
_allowTrailingMargin = newViewProps.allowTrailingMargin;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// Update md4cFlags
|
|
1203
|
+
BOOL md4cFlagsChanged = NO;
|
|
1204
|
+
if (newViewProps.md4cFlags.underline != oldViewProps.md4cFlags.underline) {
|
|
1205
|
+
_md4cFlags.underline = newViewProps.md4cFlags.underline;
|
|
1206
|
+
md4cFlagsChanged = YES;
|
|
975
1207
|
}
|
|
976
1208
|
|
|
977
1209
|
BOOL markdownChanged = oldViewProps.markdown != newViewProps.markdown;
|
|
1210
|
+
BOOL allowTrailingMarginChanged = newViewProps.allowTrailingMargin != oldViewProps.allowTrailingMargin;
|
|
978
1211
|
|
|
979
|
-
|
|
1212
|
+
_enableLinkPreview = newViewProps.enableLinkPreview;
|
|
1213
|
+
|
|
1214
|
+
if (markdownChanged || stylePropChanged || md4cFlagsChanged || allowTrailingMarginChanged) {
|
|
980
1215
|
NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()];
|
|
981
1216
|
[self renderMarkdownContent:markdownString];
|
|
982
1217
|
}
|
|
@@ -1061,6 +1296,36 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
|
|
|
1061
1296
|
}
|
|
1062
1297
|
}
|
|
1063
1298
|
|
|
1299
|
+
#pragma mark - UITextViewDelegate (Link Interaction)
|
|
1300
|
+
|
|
1301
|
+
- (BOOL)textView:(UITextView *)textView
|
|
1302
|
+
shouldInteractWithURL:(NSURL *)URL
|
|
1303
|
+
inRange:(NSRange)characterRange
|
|
1304
|
+
interaction:(UITextItemInteraction)interaction
|
|
1305
|
+
{
|
|
1306
|
+
// Only intercept long-press interactions
|
|
1307
|
+
if (interaction != UITextItemInteractionPresentActions) {
|
|
1308
|
+
return YES;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Safely extract the custom URL attribute
|
|
1312
|
+
NSString *urlString = [textView.attributedText attribute:@"linkURL"
|
|
1313
|
+
atIndex:characterRange.location
|
|
1314
|
+
effectiveRange:NULL];
|
|
1315
|
+
|
|
1316
|
+
// If link preview is enabled or no URL found, allow default system behavior
|
|
1317
|
+
if (!urlString || _enableLinkPreview) {
|
|
1318
|
+
return YES;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// System preview disabled — emit onLinkLongPress event to React Native
|
|
1322
|
+
auto eventEmitter = std::static_pointer_cast<EnrichedMarkdownTextEventEmitter const>(_eventEmitter);
|
|
1323
|
+
if (eventEmitter) {
|
|
1324
|
+
eventEmitter->onLinkLongPress({.url = std::string([urlString UTF8String])});
|
|
1325
|
+
}
|
|
1326
|
+
return NO;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1064
1329
|
#pragma mark - UITextViewDelegate (Edit Menu)
|
|
1065
1330
|
|
|
1066
1331
|
// Customizes the edit menu
|
|
@@ -1071,4 +1336,66 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
|
|
|
1071
1336
|
return buildEditMenuForSelection(_textView.attributedText, range, _cachedMarkdown, _config, suggestedActions);
|
|
1072
1337
|
}
|
|
1073
1338
|
|
|
1339
|
+
#pragma mark - Accessibility (VoiceOver Navigation)
|
|
1340
|
+
|
|
1341
|
+
- (void)buildAccessibilityElements
|
|
1342
|
+
{
|
|
1343
|
+
_accessibilityElements = [MarkdownAccessibilityElementBuilder buildElementsForTextView:_textView
|
|
1344
|
+
info:_accessibilityInfo
|
|
1345
|
+
container:self];
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
- (BOOL)isAccessibilityElement
|
|
1349
|
+
{
|
|
1350
|
+
return NO; // This is a container, not a single element
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
- (NSInteger)accessibilityElementCount
|
|
1354
|
+
{
|
|
1355
|
+
return _accessibilityElements.count;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
- (id)accessibilityElementAtIndex:(NSInteger)index
|
|
1359
|
+
{
|
|
1360
|
+
if (index < 0 || index >= (NSInteger)_accessibilityElements.count) {
|
|
1361
|
+
return nil;
|
|
1362
|
+
}
|
|
1363
|
+
return _accessibilityElements[index];
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
- (NSInteger)indexOfAccessibilityElement:(id)element
|
|
1367
|
+
{
|
|
1368
|
+
return [_accessibilityElements indexOfObject:element];
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
- (NSArray *)accessibilityElements
|
|
1372
|
+
{
|
|
1373
|
+
return _accessibilityElements;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
- (NSArray<UIAccessibilityCustomRotor *> *)accessibilityCustomRotors
|
|
1377
|
+
{
|
|
1378
|
+
NSMutableArray<UIAccessibilityCustomRotor *> *rotors = [NSMutableArray array];
|
|
1379
|
+
|
|
1380
|
+
NSArray<UIAccessibilityElement *> *headingElements =
|
|
1381
|
+
[MarkdownAccessibilityElementBuilder filterHeadingElements:_accessibilityElements];
|
|
1382
|
+
if (headingElements.count > 0) {
|
|
1383
|
+
[rotors addObject:[MarkdownAccessibilityElementBuilder createHeadingRotorWithElements:headingElements]];
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
NSArray<UIAccessibilityElement *> *linkElements =
|
|
1387
|
+
[MarkdownAccessibilityElementBuilder filterLinkElements:_accessibilityElements];
|
|
1388
|
+
if (linkElements.count > 0) {
|
|
1389
|
+
[rotors addObject:[MarkdownAccessibilityElementBuilder createLinkRotorWithElements:linkElements]];
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
NSArray<UIAccessibilityElement *> *imageElements =
|
|
1393
|
+
[MarkdownAccessibilityElementBuilder filterImageElements:_accessibilityElements];
|
|
1394
|
+
if (imageElements.count > 0) {
|
|
1395
|
+
[rotors addObject:[MarkdownAccessibilityElementBuilder createImageRotorWithElements:imageElements]];
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
return rotors;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1074
1401
|
@end
|
|
@@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
11
11
|
* Images are loaded asynchronously and scaled dynamically based on text container width.
|
|
12
12
|
* Supports inline and block images with custom height and border radius from config.
|
|
13
13
|
*/
|
|
14
|
-
@interface
|
|
14
|
+
@interface EnrichedMarkdownImageAttachment : NSTextAttachment
|
|
15
15
|
|
|
16
16
|
@property (nonatomic, readonly) NSString *imageURL;
|
|
17
17
|
@property (nonatomic, readonly) BOOL isInline;
|