react-native-enriched-markdown 0.1.0 → 0.1.1

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.
Files changed (211) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +479 -0
  3. package/ReactNativeEnrichedMarkdown.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +39 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +21 -0
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  10. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  11. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  12. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1164 -0
  13. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  15. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +2 -0
  19. package/android/src/main/baseline-prof.txt +65 -0
  20. package/android/src/main/cpp/jni-adapter.cpp +203 -0
  21. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +153 -0
  22. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +30 -0
  23. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +119 -0
  24. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +165 -0
  26. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
  27. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +29 -0
  28. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +48 -0
  29. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
  30. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +89 -0
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +105 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +35 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +15 -0
  34. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +26 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +54 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +52 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +15 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +28 -0
  39. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
  40. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +58 -0
  41. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +69 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +99 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +66 -0
  44. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +95 -0
  45. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +85 -0
  46. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +26 -0
  47. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +29 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +44 -0
  49. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
  51. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
  52. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
  53. package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
  55. package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +320 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +36 -0
  58. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +37 -0
  59. package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
  60. package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
  61. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
  62. package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
  63. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
  64. package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
  65. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +10 -0
  66. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +48 -0
  67. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +51 -0
  68. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
  69. package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
  70. package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +29 -0
  71. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +21 -0
  72. package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
  73. package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
  74. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +54 -0
  75. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +29 -0
  76. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
  77. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +180 -0
  78. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +75 -0
  79. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
  80. package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
  81. package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +809 -0
  82. package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +365 -0
  83. package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
  84. package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +181 -0
  85. package/android/src/main/jni/CMakeLists.txt +82 -0
  86. package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
  87. package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
  88. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
  89. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
  90. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
  91. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +33 -0
  92. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +49 -0
  93. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
  94. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +25 -0
  95. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +19 -0
  96. package/cpp/md4c/md4c.c +6492 -0
  97. package/cpp/md4c/md4c.h +402 -0
  98. package/cpp/parser/MD4CParser.cpp +314 -0
  99. package/cpp/parser/MD4CParser.hpp +23 -0
  100. package/cpp/parser/MarkdownASTNode.hpp +49 -0
  101. package/ios/EnrichedMarkdownText.h +18 -0
  102. package/ios/EnrichedMarkdownText.mm +1074 -0
  103. package/ios/attachments/ImageAttachment.h +23 -0
  104. package/ios/attachments/ImageAttachment.m +185 -0
  105. package/ios/attachments/ThematicBreakAttachment.h +15 -0
  106. package/ios/attachments/ThematicBreakAttachment.m +33 -0
  107. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
  108. package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
  109. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  110. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +25 -0
  111. package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +57 -0
  112. package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1164 -0
  113. package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
  114. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
  115. package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
  116. package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
  117. package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
  118. package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
  119. package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
  120. package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
  121. package/ios/internals/EnrichedMarkdownTextState.h +24 -0
  122. package/ios/parser/MarkdownASTNode.h +33 -0
  123. package/ios/parser/MarkdownASTNode.m +32 -0
  124. package/ios/parser/MarkdownParser.h +8 -0
  125. package/ios/parser/MarkdownParser.mm +13 -0
  126. package/ios/parser/MarkdownParserBridge.mm +110 -0
  127. package/ios/renderer/AttributedRenderer.h +9 -0
  128. package/ios/renderer/AttributedRenderer.m +119 -0
  129. package/ios/renderer/BlockquoteRenderer.h +7 -0
  130. package/ios/renderer/BlockquoteRenderer.m +159 -0
  131. package/ios/renderer/CodeBlockRenderer.h +10 -0
  132. package/ios/renderer/CodeBlockRenderer.m +89 -0
  133. package/ios/renderer/CodeRenderer.h +10 -0
  134. package/ios/renderer/CodeRenderer.m +60 -0
  135. package/ios/renderer/EmphasisRenderer.h +6 -0
  136. package/ios/renderer/EmphasisRenderer.m +96 -0
  137. package/ios/renderer/HeadingRenderer.h +7 -0
  138. package/ios/renderer/HeadingRenderer.m +98 -0
  139. package/ios/renderer/ImageRenderer.h +12 -0
  140. package/ios/renderer/ImageRenderer.m +62 -0
  141. package/ios/renderer/LinkRenderer.h +7 -0
  142. package/ios/renderer/LinkRenderer.m +69 -0
  143. package/ios/renderer/ListItemRenderer.h +16 -0
  144. package/ios/renderer/ListItemRenderer.m +91 -0
  145. package/ios/renderer/ListRenderer.h +13 -0
  146. package/ios/renderer/ListRenderer.m +67 -0
  147. package/ios/renderer/NodeRenderer.h +8 -0
  148. package/ios/renderer/ParagraphRenderer.h +7 -0
  149. package/ios/renderer/ParagraphRenderer.m +69 -0
  150. package/ios/renderer/RenderContext.h +88 -0
  151. package/ios/renderer/RenderContext.m +248 -0
  152. package/ios/renderer/RendererFactory.h +12 -0
  153. package/ios/renderer/RendererFactory.m +110 -0
  154. package/ios/renderer/StrongRenderer.h +6 -0
  155. package/ios/renderer/StrongRenderer.m +83 -0
  156. package/ios/renderer/TextRenderer.h +6 -0
  157. package/ios/renderer/TextRenderer.m +16 -0
  158. package/ios/renderer/ThematicBreakRenderer.h +5 -0
  159. package/ios/renderer/ThematicBreakRenderer.m +53 -0
  160. package/ios/styles/StyleConfig.h +228 -0
  161. package/ios/styles/StyleConfig.mm +1467 -0
  162. package/ios/utils/BlockquoteBorder.h +20 -0
  163. package/ios/utils/BlockquoteBorder.m +92 -0
  164. package/ios/utils/CodeBackground.h +19 -0
  165. package/ios/utils/CodeBackground.m +191 -0
  166. package/ios/utils/CodeBlockBackground.h +17 -0
  167. package/ios/utils/CodeBlockBackground.m +87 -0
  168. package/ios/utils/EditMenuUtils.h +22 -0
  169. package/ios/utils/EditMenuUtils.m +118 -0
  170. package/ios/utils/FontUtils.h +20 -0
  171. package/ios/utils/FontUtils.m +13 -0
  172. package/ios/utils/HTMLGenerator.h +20 -0
  173. package/ios/utils/HTMLGenerator.m +779 -0
  174. package/ios/utils/LastElementUtils.h +53 -0
  175. package/ios/utils/ListMarkerDrawer.h +15 -0
  176. package/ios/utils/ListMarkerDrawer.m +127 -0
  177. package/ios/utils/MarkdownExtractor.h +17 -0
  178. package/ios/utils/MarkdownExtractor.m +295 -0
  179. package/ios/utils/ParagraphStyleUtils.h +13 -0
  180. package/ios/utils/ParagraphStyleUtils.m +56 -0
  181. package/ios/utils/PasteboardUtils.h +36 -0
  182. package/ios/utils/PasteboardUtils.m +134 -0
  183. package/ios/utils/RTFExportUtils.h +24 -0
  184. package/ios/utils/RTFExportUtils.m +297 -0
  185. package/ios/utils/RuntimeKeys.h +38 -0
  186. package/ios/utils/RuntimeKeys.m +11 -0
  187. package/ios/utils/TextViewLayoutManager.h +14 -0
  188. package/ios/utils/TextViewLayoutManager.mm +113 -0
  189. package/lib/module/EnrichedMarkdownText.js +34 -0
  190. package/lib/module/EnrichedMarkdownText.js.map +1 -0
  191. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +130 -0
  192. package/lib/module/index.js +5 -0
  193. package/lib/module/index.js.map +1 -0
  194. package/lib/module/normalizeMarkdownStyle.js +340 -0
  195. package/lib/module/normalizeMarkdownStyle.js.map +1 -0
  196. package/lib/module/package.json +1 -0
  197. package/lib/typescript/package.json +1 -0
  198. package/lib/typescript/src/EnrichedMarkdownText.d.ts +101 -0
  199. package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
  200. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +111 -0
  201. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
  202. package/lib/typescript/src/index.d.ts +5 -0
  203. package/lib/typescript/src/index.d.ts.map +1 -0
  204. package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
  205. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
  206. package/package.json +186 -1
  207. package/react-native.config.js +13 -0
  208. package/src/EnrichedMarkdownText.tsx +152 -0
  209. package/src/EnrichedMarkdownTextNativeComponent.ts +130 -0
  210. package/src/index.tsx +7 -0
  211. package/src/normalizeMarkdownStyle.ts +377 -0
@@ -0,0 +1,1074 @@
1
+ #import "EnrichedMarkdownText.h"
2
+ #import "AttributedRenderer.h"
3
+ #import "CodeBlockBackground.h"
4
+ #import "EditMenuUtils.h"
5
+ #import "FontUtils.h"
6
+ #import "ImageAttachment.h"
7
+ #import "LastElementUtils.h"
8
+ #import "MarkdownASTNode.h"
9
+ #import "MarkdownExtractor.h"
10
+ #import "MarkdownParser.h"
11
+ #import "RenderContext.h"
12
+ #import "RuntimeKeys.h"
13
+ #import "StyleConfig.h"
14
+ #import "TextViewLayoutManager.h"
15
+ #import <objc/runtime.h>
16
+
17
+ #import <ReactNativeEnrichedMarkdown/EnrichedMarkdownTextComponentDescriptor.h>
18
+ #import <ReactNativeEnrichedMarkdown/EventEmitters.h>
19
+ #import <ReactNativeEnrichedMarkdown/Props.h>
20
+ #import <ReactNativeEnrichedMarkdown/RCTComponentViewHelpers.h>
21
+
22
+ #import "RCTFabricComponentsPlugins.h"
23
+ #import <React/RCTConversions.h>
24
+ #import <React/RCTFont.h>
25
+ #import <react/utils/ManagedObjectWrapper.h>
26
+
27
+ using namespace facebook::react;
28
+
29
+ @interface EnrichedMarkdownText () <RCTEnrichedMarkdownTextViewProtocol, UITextViewDelegate>
30
+ - (void)setupTextView;
31
+ - (void)renderMarkdownContent:(NSString *)markdownString;
32
+ - (void)applyRenderedText:(NSMutableAttributedString *)attributedText;
33
+ - (void)textTapped:(UITapGestureRecognizer *)recognizer;
34
+ - (void)setupLayoutManager;
35
+ @end
36
+
37
+ @implementation EnrichedMarkdownText {
38
+ UITextView *_textView;
39
+ MarkdownParser *_parser;
40
+ NSString *_cachedMarkdown;
41
+ StyleConfig *_config;
42
+
43
+ // Background rendering support
44
+ dispatch_queue_t _renderQueue;
45
+ NSUInteger _currentRenderId;
46
+ BOOL _blockAsyncRender;
47
+
48
+ EnrichedMarkdownTextShadowNode::ConcreteState::Shared _state;
49
+ int _heightUpdateCounter;
50
+ }
51
+
52
+ + (ComponentDescriptorProvider)componentDescriptorProvider
53
+ {
54
+ return concreteComponentDescriptorProvider<EnrichedMarkdownTextComponentDescriptor>();
55
+ }
56
+
57
+ #pragma mark - Measuring and State
58
+
59
+ - (CGSize)measureSize:(CGFloat)maxWidth
60
+ {
61
+ NSAttributedString *text = _textView.attributedText;
62
+ CGFloat defaultHeight = [UIFont systemFontOfSize:16.0].lineHeight;
63
+
64
+ if (!text || text.length == 0) {
65
+ return CGSizeMake(maxWidth, defaultHeight);
66
+ }
67
+
68
+ // Find last content character (exclude trailing newlines from measurement)
69
+ NSRange lastContent = [text.string rangeOfCharacterFromSet:[[NSCharacterSet newlineCharacterSet] invertedSet]
70
+ options:NSBackwardsSearch];
71
+ if (lastContent.location == NSNotFound) {
72
+ return CGSizeMake(maxWidth, defaultHeight);
73
+ }
74
+
75
+ NSAttributedString *contentToMeasure = [text attributedSubstringFromRange:NSMakeRange(0, NSMaxRange(lastContent))];
76
+
77
+ // Use NSStringDrawingUsesDeviceMetrics for tighter bounds (especially for images)
78
+ NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
79
+ if (isLastElementImage(text)) {
80
+ options |= NSStringDrawingUsesDeviceMetrics;
81
+ }
82
+
83
+ CGRect boundingRect = [contentToMeasure boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
84
+ options:options
85
+ context:nil];
86
+
87
+ CGFloat measuredHeight = boundingRect.size.height;
88
+
89
+ // Compensate for iOS not measuring trailing newlines (code block bottom padding)
90
+ if (isLastElementCodeBlock(text)) {
91
+ measuredHeight += [_config codeBlockPadding];
92
+ }
93
+
94
+ return CGSizeMake(maxWidth, ceil(measuredHeight));
95
+ }
96
+
97
+ - (void)updateState:(const facebook::react::State::Shared &)state
98
+ oldState:(const facebook::react::State::Shared &)oldState
99
+ {
100
+ _state = std::static_pointer_cast<const EnrichedMarkdownTextShadowNode::ConcreteState>(state);
101
+
102
+ // Trigger initial height calculation when state is first set
103
+ if (oldState == nullptr) {
104
+ [self requestHeightUpdate];
105
+ }
106
+ }
107
+
108
+ - (void)requestHeightUpdate
109
+ {
110
+ if (_state == nullptr) {
111
+ return;
112
+ }
113
+
114
+ _heightUpdateCounter++;
115
+ auto selfRef = wrapManagedObjectWeakly(self);
116
+ _state->updateState(EnrichedMarkdownTextState(_heightUpdateCounter, selfRef));
117
+ }
118
+
119
+ - (instancetype)initWithFrame:(CGRect)frame
120
+ {
121
+ if (self = [super initWithFrame:frame]) {
122
+ static const auto defaultProps = std::make_shared<const EnrichedMarkdownTextProps>();
123
+ _props = defaultProps;
124
+
125
+ self.backgroundColor = [UIColor clearColor];
126
+ _parser = [[MarkdownParser alloc] init];
127
+
128
+ // Serial queue for background rendering
129
+ _renderQueue = dispatch_queue_create("com.swmansion.enriched.markdown.render", DISPATCH_QUEUE_SERIAL);
130
+ _currentRenderId = 0;
131
+
132
+ [self setupTextView];
133
+ }
134
+
135
+ return self;
136
+ }
137
+
138
+ - (void)setupTextView
139
+ {
140
+ _textView = [[UITextView alloc] init];
141
+ _textView.text = @"";
142
+ _textView.font = [UIFont systemFontOfSize:16.0];
143
+ _textView.backgroundColor = [UIColor clearColor];
144
+ _textView.textColor = [UIColor blackColor];
145
+ _textView.editable = NO;
146
+ _textView.delegate = self;
147
+ _textView.scrollEnabled = NO;
148
+ _textView.textContainerInset = UIEdgeInsetsZero;
149
+ _textView.textContainer.lineFragmentPadding = 0;
150
+ // Disable UITextView's default link styling - we handle it directly in attributed strings
151
+ _textView.linkTextAttributes = @{};
152
+ // isSelectable controls text selection and link previews
153
+ // Default to YES to match the prop default
154
+ _textView.selectable = YES;
155
+ // Hide initially to prevent flash before content is rendered
156
+ _textView.hidden = YES;
157
+
158
+ // Add tap gesture recognizer
159
+ UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
160
+ action:@selector(textTapped:)];
161
+ [_textView addGestureRecognizer:tapRecognizer];
162
+
163
+ // Use RCTViewComponentView's contentView for automatic sizing
164
+ self.contentView = _textView;
165
+ }
166
+
167
+ - (void)didAddSubview:(UIView *)subview
168
+ {
169
+ [super didAddSubview:subview];
170
+
171
+ // Set up layout manager when text view is added
172
+ if (subview == _textView) {
173
+ [self setupLayoutManager];
174
+ }
175
+ }
176
+
177
+ - (void)willRemoveSubview:(UIView *)subview
178
+ {
179
+ // Clean up layout manager when text view is removed
180
+ if (subview == _textView && _textView.layoutManager != nil) {
181
+ NSLayoutManager *layoutManager = _textView.layoutManager;
182
+ if ([object_getClass(layoutManager) isEqual:[TextViewLayoutManager class]]) {
183
+ [layoutManager setValue:nil forKey:@"config"];
184
+ object_setClass(layoutManager, [NSLayoutManager class]);
185
+ }
186
+ }
187
+ [super willRemoveSubview:subview];
188
+ }
189
+
190
+ - (void)setupLayoutManager
191
+ {
192
+ // Set up custom layout manager for rich text custom drawing (code, blockquotes, etc.)
193
+ // This single manager can handle multiple element types
194
+ NSLayoutManager *layoutManager = _textView.layoutManager;
195
+ if (layoutManager != nil) {
196
+ layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue (like react-native-live-markdown)
197
+ object_setClass(layoutManager, [TextViewLayoutManager class]);
198
+
199
+ // Set config on layout manager (like react-native-live-markdown sets markdownUtils)
200
+ if (_config != nil) {
201
+ [layoutManager setValue:_config forKey:@"config"];
202
+ }
203
+ }
204
+ }
205
+
206
+ - (void)renderMarkdownContent:(NSString *)markdownString
207
+ {
208
+ // Skip async render for mock views (they use renderMarkdownSynchronously)
209
+ if (_blockAsyncRender) {
210
+ return;
211
+ }
212
+
213
+ _cachedMarkdown = [markdownString copy];
214
+
215
+ // Increment render ID to invalidate any in-flight renders
216
+ NSUInteger renderId = ++_currentRenderId;
217
+
218
+ // Capture state needed for background rendering
219
+ StyleConfig *config = [_config copy];
220
+ MarkdownParser *parser = _parser;
221
+ NSUInteger inputLength = markdownString.length;
222
+ NSDate *scheduleStart = [NSDate date];
223
+
224
+ // Dispatch heavy work to background queue
225
+ dispatch_async(_renderQueue, ^{
226
+ // 1. Parse Markdown → AST (C++ md4c parser)
227
+ NSDate *parseStart = [NSDate date];
228
+ MarkdownASTNode *ast = [parser parseMarkdown:markdownString];
229
+ if (!ast) {
230
+ return;
231
+ }
232
+ NSTimeInterval parseTime = [[NSDate date] timeIntervalSinceDate:parseStart] * 1000;
233
+ NSUInteger nodeCount = ast.children.count;
234
+
235
+ // 2. Render AST → NSAttributedString
236
+ NSDate *renderStart = [NSDate date];
237
+ AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:config];
238
+ RenderContext *context = [RenderContext new];
239
+ NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
240
+
241
+ // Add link attributes
242
+ for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
243
+ NSValue *rangeValue = context.linkRanges[i];
244
+ NSRange range = [rangeValue rangeValue];
245
+ NSString *url = context.linkURLs[i];
246
+ [attributedText addAttribute:@"linkURL" value:url range:range];
247
+ }
248
+ NSTimeInterval renderTime = [[NSDate date] timeIntervalSinceDate:renderStart] * 1000;
249
+ NSUInteger styledLength = attributedText.length;
250
+
251
+ // Apply result on main thread
252
+ dispatch_async(dispatch_get_main_queue(), ^{
253
+ // Check if this render is still current
254
+ if (renderId != self->_currentRenderId) {
255
+ return;
256
+ }
257
+
258
+ [self applyRenderedText:attributedText];
259
+
260
+ NSTimeInterval totalTime = [[NSDate date] timeIntervalSinceDate:scheduleStart] * 1000;
261
+ NSLog(@"┌──────────────────────────────────────────────");
262
+ NSLog(@"│ 📝 Input: %lu chars of Markdown", (unsigned long)inputLength);
263
+ NSLog(@"│ ⚡ md4c (C++ native): %.0fms → %lu AST nodes", parseTime, (unsigned long)nodeCount);
264
+ NSLog(@"│ 🎨 NSAttributedString render: %.0fms → %lu styled chars", renderTime, (unsigned long)styledLength);
265
+ NSLog(@"│ ✅ Total time to display: %.0fms", totalTime);
266
+ NSLog(@"└──────────────────────────────────────────────");
267
+ });
268
+ });
269
+ }
270
+
271
+ // Synchronous rendering for mock view measurement (no UI updates needed)
272
+ - (void)renderMarkdownSynchronously:(NSString *)markdownString
273
+ {
274
+ if (!markdownString || markdownString.length == 0) {
275
+ return;
276
+ }
277
+
278
+ // Block any async renders triggered by updateProps
279
+ _blockAsyncRender = YES;
280
+ _cachedMarkdown = [markdownString copy];
281
+
282
+ MarkdownASTNode *ast = [_parser parseMarkdown:markdownString];
283
+ if (!ast) {
284
+ return;
285
+ }
286
+
287
+ AttributedRenderer *renderer = [[AttributedRenderer alloc] initWithConfig:_config];
288
+ RenderContext *context = [RenderContext new];
289
+ NSMutableAttributedString *attributedText = [renderer renderRoot:ast context:context];
290
+
291
+ for (NSUInteger i = 0; i < context.linkRanges.count; i++) {
292
+ NSValue *rangeValue = context.linkRanges[i];
293
+ NSRange range = [rangeValue rangeValue];
294
+ NSString *url = context.linkURLs[i];
295
+ [attributedText addAttribute:@"linkURL" value:url range:range];
296
+ }
297
+
298
+ _textView.attributedText = attributedText;
299
+ }
300
+
301
+ - (void)applyRenderedText:(NSMutableAttributedString *)attributedText
302
+ {
303
+ // Set config on the layout manager
304
+ NSLayoutManager *layoutManager = _textView.layoutManager;
305
+ if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
306
+ [layoutManager setValue:_config forKey:@"config"];
307
+ }
308
+
309
+ // Store text view on text container so attachments can access it
310
+ objc_setAssociatedObject(_textView.textContainer, kTextViewKey, _textView, OBJC_ASSOCIATION_ASSIGN);
311
+
312
+ _textView.attributedText = attributedText;
313
+
314
+ // Ensure layout is updated
315
+ [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
316
+ [_textView.layoutManager invalidateLayoutForCharacterRange:NSMakeRange(0, attributedText.length)
317
+ actualCharacterRange:NULL];
318
+
319
+ [_textView setNeedsLayout];
320
+ [_textView setNeedsDisplay];
321
+ [self setNeedsLayout];
322
+
323
+ // Request height recalculation from shadow node FIRST
324
+ [self requestHeightUpdate];
325
+
326
+ // Show text view on next run loop, after layout has settled
327
+ if (_textView.hidden) {
328
+ dispatch_async(dispatch_get_main_queue(), ^{ self->_textView.hidden = NO; });
329
+ }
330
+ }
331
+
332
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
333
+ {
334
+ const auto &oldViewProps = *std::static_pointer_cast<EnrichedMarkdownTextProps const>(_props);
335
+ const auto &newViewProps = *std::static_pointer_cast<EnrichedMarkdownTextProps const>(props);
336
+
337
+ BOOL stylePropChanged = NO;
338
+
339
+ if (_config == nil) {
340
+ _config = [[StyleConfig alloc] init];
341
+ }
342
+
343
+ // Paragraph style
344
+ if (newViewProps.markdownStyle.paragraph.fontSize != oldViewProps.markdownStyle.paragraph.fontSize) {
345
+ [_config setParagraphFontSize:newViewProps.markdownStyle.paragraph.fontSize];
346
+ stylePropChanged = YES;
347
+ }
348
+
349
+ if (newViewProps.markdownStyle.paragraph.fontFamily != oldViewProps.markdownStyle.paragraph.fontFamily) {
350
+ if (!newViewProps.markdownStyle.paragraph.fontFamily.empty()) {
351
+ NSString *fontFamily =
352
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.paragraph.fontFamily.c_str()];
353
+ [_config setParagraphFontFamily:fontFamily];
354
+ } else {
355
+ [_config setParagraphFontFamily:nullptr];
356
+ }
357
+ stylePropChanged = YES;
358
+ }
359
+
360
+ if (newViewProps.markdownStyle.paragraph.fontWeight != oldViewProps.markdownStyle.paragraph.fontWeight) {
361
+ if (!newViewProps.markdownStyle.paragraph.fontWeight.empty()) {
362
+ NSString *fontWeight =
363
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.paragraph.fontWeight.c_str()];
364
+ [_config setParagraphFontWeight:fontWeight];
365
+ } else {
366
+ [_config setParagraphFontWeight:nullptr];
367
+ }
368
+ stylePropChanged = YES;
369
+ }
370
+
371
+ if (newViewProps.markdownStyle.paragraph.color != oldViewProps.markdownStyle.paragraph.color) {
372
+ if (newViewProps.markdownStyle.paragraph.color) {
373
+ UIColor *paragraphColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.paragraph.color);
374
+ [_config setParagraphColor:paragraphColor];
375
+ } else {
376
+ [_config setParagraphColor:nullptr];
377
+ }
378
+ stylePropChanged = YES;
379
+ }
380
+
381
+ if (newViewProps.markdownStyle.paragraph.marginBottom != oldViewProps.markdownStyle.paragraph.marginBottom) {
382
+ [_config setParagraphMarginBottom:newViewProps.markdownStyle.paragraph.marginBottom];
383
+ stylePropChanged = YES;
384
+ }
385
+
386
+ if (newViewProps.markdownStyle.paragraph.lineHeight != oldViewProps.markdownStyle.paragraph.lineHeight) {
387
+ [_config setParagraphLineHeight:newViewProps.markdownStyle.paragraph.lineHeight];
388
+ stylePropChanged = YES;
389
+ }
390
+
391
+ // H1 style
392
+ if (newViewProps.markdownStyle.h1.fontSize != oldViewProps.markdownStyle.h1.fontSize) {
393
+ [_config setH1FontSize:newViewProps.markdownStyle.h1.fontSize];
394
+ stylePropChanged = YES;
395
+ }
396
+
397
+ if (newViewProps.markdownStyle.h1.fontFamily != oldViewProps.markdownStyle.h1.fontFamily) {
398
+ if (!newViewProps.markdownStyle.h1.fontFamily.empty()) {
399
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h1.fontFamily.c_str()];
400
+ [_config setH1FontFamily:fontFamily];
401
+ } else {
402
+ [_config setH1FontFamily:nullptr];
403
+ }
404
+ stylePropChanged = YES;
405
+ }
406
+
407
+ if (newViewProps.markdownStyle.h1.fontWeight != oldViewProps.markdownStyle.h1.fontWeight) {
408
+ if (!newViewProps.markdownStyle.h1.fontWeight.empty()) {
409
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h1.fontWeight.c_str()];
410
+ [_config setH1FontWeight:fontWeight];
411
+ } else {
412
+ [_config setH1FontWeight:nullptr];
413
+ }
414
+ stylePropChanged = YES;
415
+ }
416
+
417
+ if (newViewProps.markdownStyle.h1.color != oldViewProps.markdownStyle.h1.color) {
418
+ if (newViewProps.markdownStyle.h1.color) {
419
+ UIColor *h1Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h1.color);
420
+ [_config setH1Color:h1Color];
421
+ } else {
422
+ [_config setH1Color:nullptr];
423
+ }
424
+ stylePropChanged = YES;
425
+ }
426
+
427
+ if (newViewProps.markdownStyle.h1.marginBottom != oldViewProps.markdownStyle.h1.marginBottom) {
428
+ [_config setH1MarginBottom:newViewProps.markdownStyle.h1.marginBottom];
429
+ stylePropChanged = YES;
430
+ }
431
+
432
+ if (newViewProps.markdownStyle.h1.lineHeight != oldViewProps.markdownStyle.h1.lineHeight) {
433
+ [_config setH1LineHeight:newViewProps.markdownStyle.h1.lineHeight];
434
+ stylePropChanged = YES;
435
+ }
436
+
437
+ // H2 style
438
+ if (newViewProps.markdownStyle.h2.fontSize != oldViewProps.markdownStyle.h2.fontSize) {
439
+ [_config setH2FontSize:newViewProps.markdownStyle.h2.fontSize];
440
+ stylePropChanged = YES;
441
+ }
442
+
443
+ if (newViewProps.markdownStyle.h2.fontFamily != oldViewProps.markdownStyle.h2.fontFamily) {
444
+ if (!newViewProps.markdownStyle.h2.fontFamily.empty()) {
445
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h2.fontFamily.c_str()];
446
+ [_config setH2FontFamily:fontFamily];
447
+ } else {
448
+ [_config setH2FontFamily:nullptr];
449
+ }
450
+ stylePropChanged = YES;
451
+ }
452
+
453
+ if (newViewProps.markdownStyle.h2.fontWeight != oldViewProps.markdownStyle.h2.fontWeight) {
454
+ if (!newViewProps.markdownStyle.h2.fontWeight.empty()) {
455
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h2.fontWeight.c_str()];
456
+ [_config setH2FontWeight:fontWeight];
457
+ } else {
458
+ [_config setH2FontWeight:nullptr];
459
+ }
460
+ stylePropChanged = YES;
461
+ }
462
+
463
+ if (newViewProps.markdownStyle.h2.color != oldViewProps.markdownStyle.h2.color) {
464
+ if (newViewProps.markdownStyle.h2.color) {
465
+ UIColor *h2Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h2.color);
466
+ [_config setH2Color:h2Color];
467
+ } else {
468
+ [_config setH2Color:nullptr];
469
+ }
470
+ stylePropChanged = YES;
471
+ }
472
+
473
+ if (newViewProps.markdownStyle.h2.marginBottom != oldViewProps.markdownStyle.h2.marginBottom) {
474
+ [_config setH2MarginBottom:newViewProps.markdownStyle.h2.marginBottom];
475
+ stylePropChanged = YES;
476
+ }
477
+
478
+ if (newViewProps.markdownStyle.h2.lineHeight != oldViewProps.markdownStyle.h2.lineHeight) {
479
+ [_config setH2LineHeight:newViewProps.markdownStyle.h2.lineHeight];
480
+ stylePropChanged = YES;
481
+ }
482
+
483
+ // H3 style
484
+ if (newViewProps.markdownStyle.h3.fontSize != oldViewProps.markdownStyle.h3.fontSize) {
485
+ [_config setH3FontSize:newViewProps.markdownStyle.h3.fontSize];
486
+ stylePropChanged = YES;
487
+ }
488
+
489
+ if (newViewProps.markdownStyle.h3.fontFamily != oldViewProps.markdownStyle.h3.fontFamily) {
490
+ if (!newViewProps.markdownStyle.h3.fontFamily.empty()) {
491
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h3.fontFamily.c_str()];
492
+ [_config setH3FontFamily:fontFamily];
493
+ } else {
494
+ [_config setH3FontFamily:nullptr];
495
+ }
496
+ stylePropChanged = YES;
497
+ }
498
+
499
+ if (newViewProps.markdownStyle.h3.fontWeight != oldViewProps.markdownStyle.h3.fontWeight) {
500
+ if (!newViewProps.markdownStyle.h3.fontWeight.empty()) {
501
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h3.fontWeight.c_str()];
502
+ [_config setH3FontWeight:fontWeight];
503
+ } else {
504
+ [_config setH3FontWeight:nullptr];
505
+ }
506
+ stylePropChanged = YES;
507
+ }
508
+
509
+ if (newViewProps.markdownStyle.h3.color != oldViewProps.markdownStyle.h3.color) {
510
+ if (newViewProps.markdownStyle.h3.color) {
511
+ UIColor *h3Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h3.color);
512
+ [_config setH3Color:h3Color];
513
+ } else {
514
+ [_config setH3Color:nullptr];
515
+ }
516
+ stylePropChanged = YES;
517
+ }
518
+
519
+ if (newViewProps.markdownStyle.h3.marginBottom != oldViewProps.markdownStyle.h3.marginBottom) {
520
+ [_config setH3MarginBottom:newViewProps.markdownStyle.h3.marginBottom];
521
+ stylePropChanged = YES;
522
+ }
523
+
524
+ if (newViewProps.markdownStyle.h3.lineHeight != oldViewProps.markdownStyle.h3.lineHeight) {
525
+ [_config setH3LineHeight:newViewProps.markdownStyle.h3.lineHeight];
526
+ stylePropChanged = YES;
527
+ }
528
+
529
+ // H4 style
530
+ if (newViewProps.markdownStyle.h4.fontSize != oldViewProps.markdownStyle.h4.fontSize) {
531
+ [_config setH4FontSize:newViewProps.markdownStyle.h4.fontSize];
532
+ stylePropChanged = YES;
533
+ }
534
+
535
+ if (newViewProps.markdownStyle.h4.fontFamily != oldViewProps.markdownStyle.h4.fontFamily) {
536
+ if (!newViewProps.markdownStyle.h4.fontFamily.empty()) {
537
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h4.fontFamily.c_str()];
538
+ [_config setH4FontFamily:fontFamily];
539
+ } else {
540
+ [_config setH4FontFamily:nullptr];
541
+ }
542
+ stylePropChanged = YES;
543
+ }
544
+
545
+ if (newViewProps.markdownStyle.h4.fontWeight != oldViewProps.markdownStyle.h4.fontWeight) {
546
+ if (!newViewProps.markdownStyle.h4.fontWeight.empty()) {
547
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h4.fontWeight.c_str()];
548
+ [_config setH4FontWeight:fontWeight];
549
+ } else {
550
+ [_config setH4FontWeight:nullptr];
551
+ }
552
+ stylePropChanged = YES;
553
+ }
554
+
555
+ if (newViewProps.markdownStyle.h4.color != oldViewProps.markdownStyle.h4.color) {
556
+ if (newViewProps.markdownStyle.h4.color) {
557
+ UIColor *h4Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h4.color);
558
+ [_config setH4Color:h4Color];
559
+ } else {
560
+ [_config setH4Color:nullptr];
561
+ }
562
+ stylePropChanged = YES;
563
+ }
564
+
565
+ if (newViewProps.markdownStyle.h4.marginBottom != oldViewProps.markdownStyle.h4.marginBottom) {
566
+ [_config setH4MarginBottom:newViewProps.markdownStyle.h4.marginBottom];
567
+ stylePropChanged = YES;
568
+ }
569
+
570
+ if (newViewProps.markdownStyle.h4.lineHeight != oldViewProps.markdownStyle.h4.lineHeight) {
571
+ [_config setH4LineHeight:newViewProps.markdownStyle.h4.lineHeight];
572
+ stylePropChanged = YES;
573
+ }
574
+
575
+ // H5 style
576
+ if (newViewProps.markdownStyle.h5.fontSize != oldViewProps.markdownStyle.h5.fontSize) {
577
+ [_config setH5FontSize:newViewProps.markdownStyle.h5.fontSize];
578
+ stylePropChanged = YES;
579
+ }
580
+
581
+ if (newViewProps.markdownStyle.h5.fontFamily != oldViewProps.markdownStyle.h5.fontFamily) {
582
+ if (!newViewProps.markdownStyle.h5.fontFamily.empty()) {
583
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h5.fontFamily.c_str()];
584
+ [_config setH5FontFamily:fontFamily];
585
+ } else {
586
+ [_config setH5FontFamily:nullptr];
587
+ }
588
+ stylePropChanged = YES;
589
+ }
590
+
591
+ if (newViewProps.markdownStyle.h5.fontWeight != oldViewProps.markdownStyle.h5.fontWeight) {
592
+ if (!newViewProps.markdownStyle.h5.fontWeight.empty()) {
593
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h5.fontWeight.c_str()];
594
+ [_config setH5FontWeight:fontWeight];
595
+ } else {
596
+ [_config setH5FontWeight:nullptr];
597
+ }
598
+ stylePropChanged = YES;
599
+ }
600
+
601
+ if (newViewProps.markdownStyle.h5.color != oldViewProps.markdownStyle.h5.color) {
602
+ if (newViewProps.markdownStyle.h5.color) {
603
+ UIColor *h5Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h5.color);
604
+ [_config setH5Color:h5Color];
605
+ } else {
606
+ [_config setH5Color:nullptr];
607
+ }
608
+ stylePropChanged = YES;
609
+ }
610
+
611
+ if (newViewProps.markdownStyle.h5.marginBottom != oldViewProps.markdownStyle.h5.marginBottom) {
612
+ [_config setH5MarginBottom:newViewProps.markdownStyle.h5.marginBottom];
613
+ stylePropChanged = YES;
614
+ }
615
+
616
+ if (newViewProps.markdownStyle.h5.lineHeight != oldViewProps.markdownStyle.h5.lineHeight) {
617
+ [_config setH5LineHeight:newViewProps.markdownStyle.h5.lineHeight];
618
+ stylePropChanged = YES;
619
+ }
620
+
621
+ // H6 style
622
+ if (newViewProps.markdownStyle.h6.fontSize != oldViewProps.markdownStyle.h6.fontSize) {
623
+ [_config setH6FontSize:newViewProps.markdownStyle.h6.fontSize];
624
+ stylePropChanged = YES;
625
+ }
626
+
627
+ if (newViewProps.markdownStyle.h6.fontFamily != oldViewProps.markdownStyle.h6.fontFamily) {
628
+ if (!newViewProps.markdownStyle.h6.fontFamily.empty()) {
629
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h6.fontFamily.c_str()];
630
+ [_config setH6FontFamily:fontFamily];
631
+ } else {
632
+ [_config setH6FontFamily:nullptr];
633
+ }
634
+ stylePropChanged = YES;
635
+ }
636
+
637
+ if (newViewProps.markdownStyle.h6.fontWeight != oldViewProps.markdownStyle.h6.fontWeight) {
638
+ if (!newViewProps.markdownStyle.h6.fontWeight.empty()) {
639
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.h6.fontWeight.c_str()];
640
+ [_config setH6FontWeight:fontWeight];
641
+ } else {
642
+ [_config setH6FontWeight:nullptr];
643
+ }
644
+ stylePropChanged = YES;
645
+ }
646
+
647
+ if (newViewProps.markdownStyle.h6.color != oldViewProps.markdownStyle.h6.color) {
648
+ if (newViewProps.markdownStyle.h6.color) {
649
+ UIColor *h6Color = RCTUIColorFromSharedColor(newViewProps.markdownStyle.h6.color);
650
+ [_config setH6Color:h6Color];
651
+ } else {
652
+ [_config setH6Color:nullptr];
653
+ }
654
+ stylePropChanged = YES;
655
+ }
656
+
657
+ if (newViewProps.markdownStyle.h6.marginBottom != oldViewProps.markdownStyle.h6.marginBottom) {
658
+ [_config setH6MarginBottom:newViewProps.markdownStyle.h6.marginBottom];
659
+ stylePropChanged = YES;
660
+ }
661
+
662
+ if (newViewProps.markdownStyle.h6.lineHeight != oldViewProps.markdownStyle.h6.lineHeight) {
663
+ [_config setH6LineHeight:newViewProps.markdownStyle.h6.lineHeight];
664
+ stylePropChanged = YES;
665
+ }
666
+
667
+ // Blockquote style
668
+ if (newViewProps.markdownStyle.blockquote.fontSize != oldViewProps.markdownStyle.blockquote.fontSize) {
669
+ [_config setBlockquoteFontSize:newViewProps.markdownStyle.blockquote.fontSize];
670
+ stylePropChanged = YES;
671
+ }
672
+
673
+ if (newViewProps.markdownStyle.blockquote.fontFamily != oldViewProps.markdownStyle.blockquote.fontFamily) {
674
+ NSString *fontFamily =
675
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.blockquote.fontFamily.c_str()];
676
+ [_config setBlockquoteFontFamily:fontFamily];
677
+ stylePropChanged = YES;
678
+ }
679
+
680
+ if (newViewProps.markdownStyle.blockquote.fontWeight != oldViewProps.markdownStyle.blockquote.fontWeight) {
681
+ NSString *fontWeight =
682
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.blockquote.fontWeight.c_str()];
683
+ [_config setBlockquoteFontWeight:fontWeight];
684
+ stylePropChanged = YES;
685
+ }
686
+
687
+ if (newViewProps.markdownStyle.blockquote.color != oldViewProps.markdownStyle.blockquote.color) {
688
+ UIColor *blockquoteColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.color);
689
+ [_config setBlockquoteColor:blockquoteColor];
690
+ stylePropChanged = YES;
691
+ }
692
+
693
+ if (newViewProps.markdownStyle.blockquote.marginBottom != oldViewProps.markdownStyle.blockquote.marginBottom) {
694
+ [_config setBlockquoteMarginBottom:newViewProps.markdownStyle.blockquote.marginBottom];
695
+ stylePropChanged = YES;
696
+ }
697
+
698
+ if (newViewProps.markdownStyle.blockquote.lineHeight != oldViewProps.markdownStyle.blockquote.lineHeight) {
699
+ [_config setBlockquoteLineHeight:newViewProps.markdownStyle.blockquote.lineHeight];
700
+ stylePropChanged = YES;
701
+ }
702
+
703
+ if (newViewProps.markdownStyle.blockquote.borderColor != oldViewProps.markdownStyle.blockquote.borderColor) {
704
+ UIColor *blockquoteBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.borderColor);
705
+ [_config setBlockquoteBorderColor:blockquoteBorderColor];
706
+ stylePropChanged = YES;
707
+ }
708
+
709
+ if (newViewProps.markdownStyle.blockquote.borderWidth != oldViewProps.markdownStyle.blockquote.borderWidth) {
710
+ [_config setBlockquoteBorderWidth:newViewProps.markdownStyle.blockquote.borderWidth];
711
+ stylePropChanged = YES;
712
+ }
713
+
714
+ if (newViewProps.markdownStyle.blockquote.gapWidth != oldViewProps.markdownStyle.blockquote.gapWidth) {
715
+ [_config setBlockquoteGapWidth:newViewProps.markdownStyle.blockquote.gapWidth];
716
+ stylePropChanged = YES;
717
+ }
718
+
719
+ if (newViewProps.markdownStyle.blockquote.backgroundColor != oldViewProps.markdownStyle.blockquote.backgroundColor) {
720
+ UIColor *blockquoteBackgroundColor =
721
+ RCTUIColorFromSharedColor(newViewProps.markdownStyle.blockquote.backgroundColor);
722
+ [_config setBlockquoteBackgroundColor:blockquoteBackgroundColor];
723
+ stylePropChanged = YES;
724
+ }
725
+
726
+ if (newViewProps.markdownStyle.link.color != oldViewProps.markdownStyle.link.color) {
727
+ UIColor *linkColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.link.color);
728
+ [_config setLinkColor:linkColor];
729
+ stylePropChanged = YES;
730
+ }
731
+
732
+ if (newViewProps.markdownStyle.link.underline != oldViewProps.markdownStyle.link.underline) {
733
+ [_config setLinkUnderline:newViewProps.markdownStyle.link.underline];
734
+ stylePropChanged = YES;
735
+ }
736
+
737
+ if (newViewProps.markdownStyle.strong.color != oldViewProps.markdownStyle.strong.color) {
738
+ if (newViewProps.markdownStyle.strong.color) {
739
+ UIColor *strongColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.strong.color);
740
+ [_config setStrongColor:strongColor];
741
+ } else {
742
+ [_config setStrongColor:nullptr];
743
+ }
744
+ stylePropChanged = YES;
745
+ }
746
+
747
+ if (newViewProps.markdownStyle.em.color != oldViewProps.markdownStyle.em.color) {
748
+ if (newViewProps.markdownStyle.em.color) {
749
+ UIColor *emphasisColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.em.color);
750
+ [_config setEmphasisColor:emphasisColor];
751
+ } else {
752
+ [_config setEmphasisColor:nullptr];
753
+ }
754
+ stylePropChanged = YES;
755
+ }
756
+
757
+ if (newViewProps.markdownStyle.code.color != oldViewProps.markdownStyle.code.color) {
758
+ if (newViewProps.markdownStyle.code.color) {
759
+ UIColor *codeColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.color);
760
+ [_config setCodeColor:codeColor];
761
+ } else {
762
+ [_config setCodeColor:nullptr];
763
+ }
764
+ stylePropChanged = YES;
765
+ }
766
+
767
+ if (newViewProps.markdownStyle.code.backgroundColor != oldViewProps.markdownStyle.code.backgroundColor) {
768
+ if (newViewProps.markdownStyle.code.backgroundColor) {
769
+ UIColor *codeBackgroundColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.backgroundColor);
770
+ [_config setCodeBackgroundColor:codeBackgroundColor];
771
+ } else {
772
+ [_config setCodeBackgroundColor:nullptr];
773
+ }
774
+ stylePropChanged = YES;
775
+ }
776
+
777
+ if (newViewProps.markdownStyle.code.borderColor != oldViewProps.markdownStyle.code.borderColor) {
778
+ if (newViewProps.markdownStyle.code.borderColor) {
779
+ UIColor *codeBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.code.borderColor);
780
+ [_config setCodeBorderColor:codeBorderColor];
781
+ } else {
782
+ [_config setCodeBorderColor:nullptr];
783
+ }
784
+ stylePropChanged = YES;
785
+ }
786
+
787
+ if (newViewProps.markdownStyle.image.height != oldViewProps.markdownStyle.image.height) {
788
+ [_config setImageHeight:newViewProps.markdownStyle.image.height];
789
+ stylePropChanged = YES;
790
+ }
791
+
792
+ if (newViewProps.markdownStyle.image.borderRadius != oldViewProps.markdownStyle.image.borderRadius) {
793
+ [_config setImageBorderRadius:newViewProps.markdownStyle.image.borderRadius];
794
+ stylePropChanged = YES;
795
+ }
796
+
797
+ if (newViewProps.markdownStyle.image.marginBottom != oldViewProps.markdownStyle.image.marginBottom) {
798
+ [_config setImageMarginBottom:newViewProps.markdownStyle.image.marginBottom];
799
+ stylePropChanged = YES;
800
+ }
801
+
802
+ if (newViewProps.markdownStyle.inlineImage.size != oldViewProps.markdownStyle.inlineImage.size) {
803
+ [_config setInlineImageSize:newViewProps.markdownStyle.inlineImage.size];
804
+ stylePropChanged = YES;
805
+ }
806
+
807
+ // List style
808
+ if (newViewProps.markdownStyle.list.fontSize != oldViewProps.markdownStyle.list.fontSize) {
809
+ [_config setListStyleFontSize:newViewProps.markdownStyle.list.fontSize];
810
+ stylePropChanged = YES;
811
+ }
812
+
813
+ if (newViewProps.markdownStyle.list.fontFamily != oldViewProps.markdownStyle.list.fontFamily) {
814
+ NSString *fontFamily = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.fontFamily.c_str()];
815
+ [_config setListStyleFontFamily:fontFamily];
816
+ stylePropChanged = YES;
817
+ }
818
+
819
+ if (newViewProps.markdownStyle.list.fontWeight != oldViewProps.markdownStyle.list.fontWeight) {
820
+ NSString *fontWeight = [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.fontWeight.c_str()];
821
+ [_config setListStyleFontWeight:fontWeight];
822
+ stylePropChanged = YES;
823
+ }
824
+
825
+ if (newViewProps.markdownStyle.list.color != oldViewProps.markdownStyle.list.color) {
826
+ UIColor *listColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.color);
827
+ [_config setListStyleColor:listColor];
828
+ stylePropChanged = YES;
829
+ }
830
+
831
+ if (newViewProps.markdownStyle.list.marginBottom != oldViewProps.markdownStyle.list.marginBottom) {
832
+ [_config setListStyleMarginBottom:newViewProps.markdownStyle.list.marginBottom];
833
+ stylePropChanged = YES;
834
+ }
835
+
836
+ if (newViewProps.markdownStyle.list.lineHeight != oldViewProps.markdownStyle.list.lineHeight) {
837
+ [_config setListStyleLineHeight:newViewProps.markdownStyle.list.lineHeight];
838
+ stylePropChanged = YES;
839
+ }
840
+
841
+ if (newViewProps.markdownStyle.list.bulletColor != oldViewProps.markdownStyle.list.bulletColor) {
842
+ UIColor *bulletColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.bulletColor);
843
+ [_config setListStyleBulletColor:bulletColor];
844
+ stylePropChanged = YES;
845
+ }
846
+
847
+ if (newViewProps.markdownStyle.list.bulletSize != oldViewProps.markdownStyle.list.bulletSize) {
848
+ [_config setListStyleBulletSize:newViewProps.markdownStyle.list.bulletSize];
849
+ stylePropChanged = YES;
850
+ }
851
+
852
+ if (newViewProps.markdownStyle.list.markerColor != oldViewProps.markdownStyle.list.markerColor) {
853
+ UIColor *markerColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.list.markerColor);
854
+ [_config setListStyleMarkerColor:markerColor];
855
+ stylePropChanged = YES;
856
+ }
857
+
858
+ if (newViewProps.markdownStyle.list.markerFontWeight != oldViewProps.markdownStyle.list.markerFontWeight) {
859
+ NSString *markerFontWeight =
860
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.list.markerFontWeight.c_str()];
861
+ [_config setListStyleMarkerFontWeight:markerFontWeight];
862
+ stylePropChanged = YES;
863
+ }
864
+
865
+ if (newViewProps.markdownStyle.list.gapWidth != oldViewProps.markdownStyle.list.gapWidth) {
866
+ [_config setListStyleGapWidth:newViewProps.markdownStyle.list.gapWidth];
867
+ stylePropChanged = YES;
868
+ }
869
+
870
+ if (newViewProps.markdownStyle.list.marginLeft != oldViewProps.markdownStyle.list.marginLeft) {
871
+ [_config setListStyleMarginLeft:newViewProps.markdownStyle.list.marginLeft];
872
+ stylePropChanged = YES;
873
+ }
874
+
875
+ // Code block style
876
+ if (newViewProps.markdownStyle.codeBlock.fontSize != oldViewProps.markdownStyle.codeBlock.fontSize) {
877
+ [_config setCodeBlockFontSize:newViewProps.markdownStyle.codeBlock.fontSize];
878
+ stylePropChanged = YES;
879
+ }
880
+
881
+ if (newViewProps.markdownStyle.codeBlock.fontFamily != oldViewProps.markdownStyle.codeBlock.fontFamily) {
882
+ NSString *fontFamily =
883
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.codeBlock.fontFamily.c_str()];
884
+ [_config setCodeBlockFontFamily:fontFamily];
885
+ stylePropChanged = YES;
886
+ }
887
+
888
+ if (newViewProps.markdownStyle.codeBlock.fontWeight != oldViewProps.markdownStyle.codeBlock.fontWeight) {
889
+ NSString *fontWeight =
890
+ [[NSString alloc] initWithUTF8String:newViewProps.markdownStyle.codeBlock.fontWeight.c_str()];
891
+ [_config setCodeBlockFontWeight:fontWeight];
892
+ stylePropChanged = YES;
893
+ }
894
+
895
+ if (newViewProps.markdownStyle.codeBlock.color != oldViewProps.markdownStyle.codeBlock.color) {
896
+ UIColor *codeBlockColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.color);
897
+ [_config setCodeBlockColor:codeBlockColor];
898
+ stylePropChanged = YES;
899
+ }
900
+
901
+ if (newViewProps.markdownStyle.codeBlock.marginBottom != oldViewProps.markdownStyle.codeBlock.marginBottom) {
902
+ [_config setCodeBlockMarginBottom:newViewProps.markdownStyle.codeBlock.marginBottom];
903
+ stylePropChanged = YES;
904
+ }
905
+
906
+ if (newViewProps.markdownStyle.codeBlock.lineHeight != oldViewProps.markdownStyle.codeBlock.lineHeight) {
907
+ [_config setCodeBlockLineHeight:newViewProps.markdownStyle.codeBlock.lineHeight];
908
+ stylePropChanged = YES;
909
+ }
910
+
911
+ if (newViewProps.markdownStyle.codeBlock.backgroundColor != oldViewProps.markdownStyle.codeBlock.backgroundColor) {
912
+ UIColor *codeBlockBackgroundColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.backgroundColor);
913
+ [_config setCodeBlockBackgroundColor:codeBlockBackgroundColor];
914
+ stylePropChanged = YES;
915
+ }
916
+
917
+ if (newViewProps.markdownStyle.codeBlock.borderColor != oldViewProps.markdownStyle.codeBlock.borderColor) {
918
+ UIColor *codeBlockBorderColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.codeBlock.borderColor);
919
+ [_config setCodeBlockBorderColor:codeBlockBorderColor];
920
+ stylePropChanged = YES;
921
+ }
922
+
923
+ if (newViewProps.markdownStyle.codeBlock.borderRadius != oldViewProps.markdownStyle.codeBlock.borderRadius) {
924
+ [_config setCodeBlockBorderRadius:newViewProps.markdownStyle.codeBlock.borderRadius];
925
+ stylePropChanged = YES;
926
+ }
927
+
928
+ if (newViewProps.markdownStyle.codeBlock.borderWidth != oldViewProps.markdownStyle.codeBlock.borderWidth) {
929
+ [_config setCodeBlockBorderWidth:newViewProps.markdownStyle.codeBlock.borderWidth];
930
+ stylePropChanged = YES;
931
+ }
932
+
933
+ if (newViewProps.markdownStyle.codeBlock.padding != oldViewProps.markdownStyle.codeBlock.padding) {
934
+ [_config setCodeBlockPadding:newViewProps.markdownStyle.codeBlock.padding];
935
+ stylePropChanged = YES;
936
+ }
937
+
938
+ // Thematic break style
939
+ if (newViewProps.markdownStyle.thematicBreak.color != oldViewProps.markdownStyle.thematicBreak.color) {
940
+ UIColor *thematicBreakColor = RCTUIColorFromSharedColor(newViewProps.markdownStyle.thematicBreak.color);
941
+ [_config setThematicBreakColor:thematicBreakColor];
942
+ stylePropChanged = YES;
943
+ }
944
+
945
+ if (newViewProps.markdownStyle.thematicBreak.height != oldViewProps.markdownStyle.thematicBreak.height) {
946
+ [_config setThematicBreakHeight:newViewProps.markdownStyle.thematicBreak.height];
947
+ stylePropChanged = YES;
948
+ }
949
+
950
+ if (newViewProps.markdownStyle.thematicBreak.marginTop != oldViewProps.markdownStyle.thematicBreak.marginTop) {
951
+ [_config setThematicBreakMarginTop:newViewProps.markdownStyle.thematicBreak.marginTop];
952
+ stylePropChanged = YES;
953
+ }
954
+
955
+ if (newViewProps.markdownStyle.thematicBreak.marginBottom != oldViewProps.markdownStyle.thematicBreak.marginBottom) {
956
+ [_config setThematicBreakMarginBottom:newViewProps.markdownStyle.thematicBreak.marginBottom];
957
+ stylePropChanged = YES;
958
+ }
959
+
960
+ // Update config reference on layout manager if it's not already set
961
+ NSLayoutManager *layoutManager = _textView.layoutManager;
962
+ if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
963
+ StyleConfig *currentConfig = [layoutManager valueForKey:@"config"];
964
+ if (currentConfig != _config) {
965
+ // Only update reference if it's different (first time setup)
966
+ [layoutManager setValue:_config forKey:@"config"];
967
+ }
968
+ }
969
+
970
+ // Control text selection and link previews via isSelectable property
971
+ // According to Apple docs, isSelectable controls whether text selection and link previews work
972
+ // https://developer.apple.com/documentation/uikit/uitextview/isselectable
973
+ if (_textView.selectable != newViewProps.isSelectable) {
974
+ _textView.selectable = newViewProps.isSelectable;
975
+ }
976
+
977
+ BOOL markdownChanged = oldViewProps.markdown != newViewProps.markdown;
978
+
979
+ if (markdownChanged || stylePropChanged) {
980
+ NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()];
981
+ [self renderMarkdownContent:markdownString];
982
+ }
983
+
984
+ [super updateProps:props oldProps:oldProps];
985
+ }
986
+
987
+ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
988
+ {
989
+ return EnrichedMarkdownText.class;
990
+ }
991
+
992
+ - (void)textTapped:(UITapGestureRecognizer *)recognizer
993
+ {
994
+ /*
995
+ * HOW LINK TAPPING WORKS:
996
+ *
997
+ * 1. SETUP PHASE (During Rendering):
998
+ * - Each link gets a custom @"linkURL" attribute attached to its text range
999
+ * - The URL is stored as the attribute's value
1000
+ * - This creates an "invisible map" of where links are in the text
1001
+ *
1002
+ * Example:
1003
+ * Text: "Check out this [link to React Native](https://reactnative.dev)"
1004
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1005
+ * | | | | | | | | | | |
1006
+ * 0 5 10 15 20 25 30 35 40 45 50
1007
+ *
1008
+ * Attributes:
1009
+ * - Characters 15-29: @"linkURL" = "https://reactnative.dev"
1010
+ * - Characters 0-14: no special attributes
1011
+ * - Characters 30-50: no special attributes
1012
+ *
1013
+ * 2. TOUCH DETECTION PHASE (When User Taps):
1014
+ * - UITapGestureRecognizer detects the tap
1015
+ * - We get the tap coordinates relative to the text view
1016
+ * - We adjust for text container insets to get precise text coordinates
1017
+ */
1018
+
1019
+ UITextView *textView = (UITextView *)recognizer.view;
1020
+
1021
+ // Location of the tap in text-container coordinates
1022
+ NSLayoutManager *layoutManager = textView.layoutManager;
1023
+ CGPoint location = [recognizer locationInView:textView];
1024
+ location.x -= textView.textContainerInset.left;
1025
+ location.y -= textView.textContainerInset.top;
1026
+
1027
+ /*
1028
+ * 3. CHARACTER INDEX LOOKUP:
1029
+ * - NSLayoutManager converts the tap coordinates to a character index
1030
+ * - This tells us exactly which character in the text was tapped
1031
+ * - Uses UIKit's built-in text layout system (very accurate)
1032
+ */
1033
+ NSUInteger characterIndex;
1034
+ characterIndex = [layoutManager characterIndexForPoint:location
1035
+ inTextContainer:textView.textContainer
1036
+ fractionOfDistanceBetweenInsertionPoints:NULL];
1037
+
1038
+ /*
1039
+ * 4. LINK DETECTION:
1040
+ * - We check if there's a @"linkURL" attribute at the tapped character
1041
+ * - If found, we get the URL value and the effective range
1042
+ * - If it's a link, we emit the onLinkPress event to React Native
1043
+ *
1044
+ * COMPLETE FLOW:
1045
+ * 1. User taps → UITapGestureRecognizer fires
1046
+ * 2. Get coordinates → Convert to text container coordinates
1047
+ * 3. Find character → NSLayoutManager.characterIndexForPoint
1048
+ * 4. Check attributes → Look for @"linkURL" at that character
1049
+ * 5. If link found → Emit onLinkPress event with URL
1050
+ * 6. React Native → Receives event and shows alert
1051
+ */
1052
+ if (characterIndex < textView.textStorage.length) {
1053
+ NSRange range;
1054
+ NSString *url = [textView.attributedText attribute:@"linkURL" atIndex:characterIndex effectiveRange:&range];
1055
+
1056
+ if (url) {
1057
+ // Emit onLinkPress event to React Native
1058
+ const auto &eventEmitter = *std::static_pointer_cast<EnrichedMarkdownTextEventEmitter const>(_eventEmitter);
1059
+ eventEmitter.onLinkPress({.url = std::string([url UTF8String])});
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ #pragma mark - UITextViewDelegate (Edit Menu)
1065
+
1066
+ // Customizes the edit menu
1067
+ - (UIMenu *)textView:(UITextView *)textView
1068
+ editMenuForTextInRange:(NSRange)range
1069
+ suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0))
1070
+ {
1071
+ return buildEditMenuForSelection(_textView.attributedText, range, _cachedMarkdown, _config, suggestedActions);
1072
+ }
1073
+
1074
+ @end