react-native-jet-markdown 0.2.0-beta.7

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 (157) hide show
  1. package/JetMarkdown.podspec +42 -0
  2. package/LICENSE +20 -0
  3. package/README.md +247 -0
  4. package/android/CMakeLists.txt +52 -0
  5. package/android/build.gradle +55 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/JniBindings.cpp +243 -0
  8. package/android/src/main/java/com/jetmarkdown/JetMarkdownNative.kt +114 -0
  9. package/android/src/main/java/com/jetmarkdown/JetMarkdownPackage.kt +22 -0
  10. package/android/src/main/java/com/jetmarkdown/JetMarkdownView.kt +235 -0
  11. package/android/src/main/java/com/jetmarkdown/JetMarkdownViewManager.kt +75 -0
  12. package/android/src/main/java/com/jetmarkdown/editor/EditorSpans.kt +217 -0
  13. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorManager.kt +224 -0
  14. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorView.kt +1736 -0
  15. package/android/src/main/java/com/jetmarkdown/measure/MarkdownMeasurer.kt +54 -0
  16. package/android/src/main/java/com/jetmarkdown/parser/AstDecoder.kt +59 -0
  17. package/android/src/main/java/com/jetmarkdown/parser/MdNode.kt +43 -0
  18. package/android/src/main/java/com/jetmarkdown/render/ContentCache.kt +53 -0
  19. package/android/src/main/java/com/jetmarkdown/render/RenderedContent.kt +318 -0
  20. package/android/src/main/java/com/jetmarkdown/render/SpannableRenderer.kt +658 -0
  21. package/android/src/main/java/com/jetmarkdown/render/spans/ChipSpan.kt +17 -0
  22. package/android/src/main/java/com/jetmarkdown/render/spans/InteractiveSpans.kt +11 -0
  23. package/android/src/main/java/com/jetmarkdown/render/spans/MarkdownLineHeightSpan.kt +59 -0
  24. package/android/src/main/java/com/jetmarkdown/render/spans/RunSpan.kt +40 -0
  25. package/android/src/main/java/com/jetmarkdown/style/Fonts.kt +34 -0
  26. package/android/src/main/java/com/jetmarkdown/style/LayoutStyleSpec.kt +71 -0
  27. package/android/src/main/java/com/jetmarkdown/style/PlatformColorResolver.kt +77 -0
  28. package/android/src/main/java/com/jetmarkdown/style/StyleConfig.kt +80 -0
  29. package/android/src/main/java/com/jetmarkdown/style/TextStyleSpec.kt +61 -0
  30. package/android/src/main/java/com/jetmarkdown/views/BlockStackView.kt +292 -0
  31. package/android/src/main/java/com/jetmarkdown/views/BlockTextView.kt +378 -0
  32. package/android/src/main/java/com/jetmarkdown/views/BoxDrawing.kt +50 -0
  33. package/android/src/main/java/com/jetmarkdown/views/MarkdownHost.kt +12 -0
  34. package/android/src/main/java/com/jetmarkdown/views/MarkdownImageView.kt +113 -0
  35. package/android/src/main/java/com/jetmarkdown/views/NestedHorizontalScrollView.kt +87 -0
  36. package/android/src/main/java/com/jetmarkdown/views/TableBlockView.kt +139 -0
  37. package/cpp/core/Ast.h +85 -0
  38. package/cpp/core/AstJson.cpp +129 -0
  39. package/cpp/core/AstJson.h +12 -0
  40. package/cpp/core/AstSerializer.cpp +40 -0
  41. package/cpp/core/AstSerializer.h +21 -0
  42. package/cpp/core/AstToMarkdown.cpp +549 -0
  43. package/cpp/core/AstToMarkdown.h +16 -0
  44. package/cpp/core/EditorRuns.cpp +640 -0
  45. package/cpp/core/EditorRuns.h +94 -0
  46. package/cpp/core/EditorText.cpp +15 -0
  47. package/cpp/core/EditorText.h +18 -0
  48. package/cpp/core/InlineExtensions.cpp +305 -0
  49. package/cpp/core/InlineExtensions.h +17 -0
  50. package/cpp/core/Parser.cpp +470 -0
  51. package/cpp/core/Parser.h +16 -0
  52. package/cpp/core/Preprocess.cpp +181 -0
  53. package/cpp/core/Preprocess.h +14 -0
  54. package/cpp/md4c/LICENSE +22 -0
  55. package/cpp/md4c/VERSION +1 -0
  56. package/cpp/md4c/md4c.c +6462 -0
  57. package/cpp/md4c/md4c.h +407 -0
  58. package/cpp/react/JetMarkdownEditorShadowNode.cpp +54 -0
  59. package/cpp/react/JetMarkdownEditorShadowNode.h +55 -0
  60. package/cpp/react/JetMarkdownEditorState.h +35 -0
  61. package/cpp/react/JetMarkdownMeasurer.cpp +32 -0
  62. package/cpp/react/JetMarkdownMeasurer.h +41 -0
  63. package/cpp/react/JetMarkdownShadowNode.cpp +100 -0
  64. package/cpp/react/JetMarkdownShadowNode.h +53 -0
  65. package/cpp/react/JetMarkdownState.h +52 -0
  66. package/cpp/react/override/react/renderer/components/JetMarkdownViewSpec/ComponentDescriptors.h +21 -0
  67. package/cpp/tests/parser_tests.cpp +662 -0
  68. package/cpp/tests/run_tests.sh +25 -0
  69. package/ios/JetMarkdownView.h +14 -0
  70. package/ios/JetMarkdownView.mm +468 -0
  71. package/ios/editor/JetMarkdownEditor.h +12 -0
  72. package/ios/editor/JetMarkdownEditor.mm +2002 -0
  73. package/ios/measure/JMDMarkdownMeasurer.h +22 -0
  74. package/ios/measure/JMDMarkdownMeasurer.mm +38 -0
  75. package/ios/render/JMDBlock.h +97 -0
  76. package/ios/render/JMDBlock.m +28 -0
  77. package/ios/render/JMDBlockRenderer.h +17 -0
  78. package/ios/render/JMDBlockRenderer.mm +852 -0
  79. package/ios/render/JMDContentCache.h +17 -0
  80. package/ios/render/JMDContentCache.mm +42 -0
  81. package/ios/render/JMDRenderedContent.h +35 -0
  82. package/ios/render/JMDRenderedContent.mm +274 -0
  83. package/ios/style/JMDFontScale.h +33 -0
  84. package/ios/style/JMDLayoutStyle.h +37 -0
  85. package/ios/style/JMDLayoutStyle.mm +68 -0
  86. package/ios/style/JMDStyleConfig.h +39 -0
  87. package/ios/style/JMDStyleConfig.mm +111 -0
  88. package/ios/style/JMDTextStyle.h +25 -0
  89. package/ios/style/JMDTextStyle.mm +68 -0
  90. package/ios/views/JMDBlockStackView.h +18 -0
  91. package/ios/views/JMDBlockStackView.m +97 -0
  92. package/ios/views/JMDBlockTextView.h +24 -0
  93. package/ios/views/JMDBlockTextView.m +340 -0
  94. package/ios/views/JMDBoxViews.h +33 -0
  95. package/ios/views/JMDBoxViews.m +292 -0
  96. package/ios/views/JMDImageView.h +19 -0
  97. package/ios/views/JMDImageView.m +68 -0
  98. package/ios/views/JMDMarkdownHost.h +16 -0
  99. package/ios/views/JMDTableView.h +16 -0
  100. package/ios/views/JMDTableView.m +172 -0
  101. package/lib/module/JetMarkdownEditor.js +85 -0
  102. package/lib/module/JetMarkdownEditor.js.map +1 -0
  103. package/lib/module/JetMarkdownEditor.native.js +244 -0
  104. package/lib/module/JetMarkdownEditor.native.js.map +1 -0
  105. package/lib/module/JetMarkdownEditorNativeComponent.ts +173 -0
  106. package/lib/module/JetMarkdownView.js +19 -0
  107. package/lib/module/JetMarkdownView.js.map +1 -0
  108. package/lib/module/JetMarkdownView.native.js +44 -0
  109. package/lib/module/JetMarkdownView.native.js.map +1 -0
  110. package/lib/module/JetMarkdownViewNativeComponent.ts +35 -0
  111. package/lib/module/defaultStyles.js +122 -0
  112. package/lib/module/defaultStyles.js.map +1 -0
  113. package/lib/module/index.js +9 -0
  114. package/lib/module/index.js.map +1 -0
  115. package/lib/module/package.json +1 -0
  116. package/lib/module/serializeStyles.js +259 -0
  117. package/lib/module/serializeStyles.js.map +1 -0
  118. package/lib/module/types.js +4 -0
  119. package/lib/module/types.js.map +1 -0
  120. package/lib/module/useJetMarkdownEditor.js +41 -0
  121. package/lib/module/useJetMarkdownEditor.js.map +1 -0
  122. package/lib/typescript/package.json +1 -0
  123. package/lib/typescript/src/JetMarkdownEditor.d.ts +6 -0
  124. package/lib/typescript/src/JetMarkdownEditor.d.ts.map +1 -0
  125. package/lib/typescript/src/JetMarkdownEditor.native.d.ts +6 -0
  126. package/lib/typescript/src/JetMarkdownEditor.native.d.ts.map +1 -0
  127. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts +105 -0
  128. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts.map +1 -0
  129. package/lib/typescript/src/JetMarkdownView.d.ts +3 -0
  130. package/lib/typescript/src/JetMarkdownView.d.ts.map +1 -0
  131. package/lib/typescript/src/JetMarkdownView.native.d.ts +3 -0
  132. package/lib/typescript/src/JetMarkdownView.native.d.ts.map +1 -0
  133. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts +28 -0
  134. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts.map +1 -0
  135. package/lib/typescript/src/defaultStyles.d.ts +20 -0
  136. package/lib/typescript/src/defaultStyles.d.ts.map +1 -0
  137. package/lib/typescript/src/index.d.ts +7 -0
  138. package/lib/typescript/src/index.d.ts.map +1 -0
  139. package/lib/typescript/src/serializeStyles.d.ts +34 -0
  140. package/lib/typescript/src/serializeStyles.d.ts.map +1 -0
  141. package/lib/typescript/src/types.d.ts +352 -0
  142. package/lib/typescript/src/types.d.ts.map +1 -0
  143. package/lib/typescript/src/useJetMarkdownEditor.d.ts +36 -0
  144. package/lib/typescript/src/useJetMarkdownEditor.d.ts.map +1 -0
  145. package/package.json +150 -0
  146. package/react-native.config.js +11 -0
  147. package/src/JetMarkdownEditor.native.tsx +301 -0
  148. package/src/JetMarkdownEditor.tsx +90 -0
  149. package/src/JetMarkdownEditorNativeComponent.ts +173 -0
  150. package/src/JetMarkdownView.native.tsx +67 -0
  151. package/src/JetMarkdownView.tsx +16 -0
  152. package/src/JetMarkdownViewNativeComponent.ts +35 -0
  153. package/src/defaultStyles.ts +102 -0
  154. package/src/index.tsx +34 -0
  155. package/src/serializeStyles.ts +369 -0
  156. package/src/types.ts +438 -0
  157. package/src/useJetMarkdownEditor.ts +50 -0
@@ -0,0 +1,292 @@
1
+ #import "JMDBoxViews.h"
2
+
3
+ #import "../render/JMDRenderedContent.h"
4
+ #import "JMDBlockStackView.h"
5
+ #import "JMDBlockTextView.h"
6
+
7
+ @implementation JMDNestedScrollView {
8
+ __weak UIControl *_trackingControl;
9
+ }
10
+
11
+ - (instancetype)initWithFrame:(CGRect)frame {
12
+ if (self = [super initWithFrame:frame]) {
13
+ self.delegate = self;
14
+ }
15
+ return self;
16
+ }
17
+
18
+ // As the hit view this scroller consumes the raw touch stream, which starves
19
+ // UIControl-based wrappers: react-native-gesture-handler's button fires
20
+ // presses from UIControl events, and a control refuses to track touches
21
+ // hit-tested to another view. Synthesize the control events instead — press
22
+ // down on touch start, press on a clean touch up, cancel when the pan takes
23
+ // the touches — so a tap on a code block or table still presses the
24
+ // wrapping card while drags scroll without pressing.
25
+ - (nullable UIControl *)jmdAncestorControl {
26
+ UIView *view = self.superview;
27
+ while (view != nil) {
28
+ if ([view isKindOfClass:[UIControl class]]) {
29
+ return (UIControl *)view;
30
+ }
31
+ view = view.superview;
32
+ }
33
+ return nil;
34
+ }
35
+
36
+ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
37
+ [super touchesBegan:touches withEvent:event];
38
+ _trackingControl = [self jmdAncestorControl];
39
+ [_trackingControl sendActionsForControlEvents:UIControlEventTouchDown];
40
+ }
41
+
42
+ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
43
+ [super touchesEnded:touches withEvent:event];
44
+ [_trackingControl sendActionsForControlEvents:UIControlEventTouchUpInside];
45
+ _trackingControl = nil;
46
+ }
47
+
48
+ - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
49
+ [super touchesCancelled:touches withEvent:event];
50
+ [_trackingControl sendActionsForControlEvents:UIControlEventTouchCancel];
51
+ _trackingControl = nil;
52
+ }
53
+
54
+ // This scroller is not a React view, so nothing cancels the JS responder
55
+ // (an RN Pressable wrapping the markdown view) when a drag starts here —
56
+ // React only cancels for its own scroll views. Kill the surface touch
57
+ // handler's active touches the way RCTTouchHandler itself cancels: an
58
+ // enabled toggle.
59
+ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
60
+ UIView *view = self.superview;
61
+ while (view != nil) {
62
+ for (UIGestureRecognizer *recognizer in view.gestureRecognizers) {
63
+ if ([NSStringFromClass(recognizer.class) containsString:@"TouchHandler"]) {
64
+ recognizer.enabled = NO;
65
+ recognizer.enabled = YES;
66
+ return;
67
+ }
68
+ }
69
+ view = view.superview;
70
+ }
71
+ }
72
+
73
+ @end
74
+
75
+ // Shared background + border painting on a host view's layer tree.
76
+ static void JMDApplyBox(UIView *view, JMDLayoutStyle *style) {
77
+ view.backgroundColor = style.backgroundColor ?: UIColor.clearColor;
78
+ view.layer.cornerRadius = style.borderRadius;
79
+ view.layer.cornerCurve =
80
+ style.continuousCorners ? kCACornerCurveContinuous : kCACornerCurveCircular;
81
+ view.layer.masksToBounds = style.borderRadius > 0;
82
+ }
83
+
84
+ @interface JMDBorderView : UIView
85
+ @property (nonatomic, strong, nullable) JMDLayoutStyle *boxStyle;
86
+ @end
87
+
88
+ @implementation JMDBorderView
89
+
90
+ - (instancetype)initWithFrame:(CGRect)frame {
91
+ if (self = [super initWithFrame:frame]) {
92
+ self.backgroundColor = UIColor.clearColor;
93
+ self.userInteractionEnabled = NO;
94
+ self.contentMode = UIViewContentModeRedraw;
95
+ }
96
+ return self;
97
+ }
98
+
99
+ - (void)drawRect:(CGRect)rect {
100
+ JMDLayoutStyle *style = self.boxStyle;
101
+ if (style == nil) {
102
+ return;
103
+ }
104
+ CGContextRef context = UIGraphicsGetCurrentContext();
105
+ const CGSize size = self.bounds.size;
106
+
107
+ if (style.borderLeftWidth > 0 && style.borderLeftColor != nil) {
108
+ CGContextSetFillColorWithColor(context, style.borderLeftColor.CGColor);
109
+ if (style.borderRadius > 0) {
110
+ UIBezierPath *path = [UIBezierPath
111
+ bezierPathWithRoundedRect:CGRectMake(0, 0, style.borderLeftWidth, size.height)
112
+ cornerRadius:style.borderRadius / 2];
113
+ [path fill];
114
+ } else {
115
+ CGContextFillRect(context, CGRectMake(0, 0, style.borderLeftWidth, size.height));
116
+ }
117
+ }
118
+ if (style.borderRightWidth > 0 && style.borderRightColor != nil) {
119
+ CGContextSetFillColorWithColor(context, style.borderRightColor.CGColor);
120
+ CGContextFillRect(
121
+ context,
122
+ CGRectMake(size.width - style.borderRightWidth, 0, style.borderRightWidth, size.height));
123
+ }
124
+ if (style.borderTopWidth > 0 && style.borderTopColor != nil) {
125
+ CGContextSetFillColorWithColor(context, style.borderTopColor.CGColor);
126
+ CGContextFillRect(context, CGRectMake(0, 0, size.width, style.borderTopWidth));
127
+ }
128
+ if (style.borderBottomWidth > 0 && style.borderBottomColor != nil) {
129
+ CGContextSetFillColorWithColor(context, style.borderBottomColor.CGColor);
130
+ CGContextFillRect(
131
+ context,
132
+ CGRectMake(0, size.height - style.borderBottomWidth, size.width, style.borderBottomWidth));
133
+ }
134
+ }
135
+
136
+ @end
137
+
138
+ @implementation JMDQuoteView {
139
+ JMDMeasuredBlock *_measured;
140
+ JMDBlockStackView *_stack;
141
+ JMDBorderView *_borders;
142
+ }
143
+
144
+ - (void)bind:(JMDMeasuredBlock *)measured
145
+ gap:(CGFloat)gap
146
+ host:(nullable id<JMDMarkdownHost>)host {
147
+ _measured = measured;
148
+ if (_stack == nil) {
149
+ _stack = [[JMDBlockStackView alloc] initWithFrame:CGRectZero];
150
+ _borders = [[JMDBorderView alloc] initWithFrame:CGRectZero];
151
+ [self addSubview:_stack];
152
+ [self addSubview:_borders];
153
+ }
154
+ JMDApplyBox(self, measured.block.layoutStyle);
155
+ _borders.boxStyle = measured.block.layoutStyle;
156
+ _stack.host = host;
157
+ [_stack setBlocks:measured.children gap:gap];
158
+ [self setNeedsLayout];
159
+ }
160
+
161
+
162
+ // Never the hit view itself: markdown touches belong to the host component
163
+ // view; only nested scrollers (code blocks, tables) claim touches.
164
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
165
+ UIView *hit = [super hitTest:point withEvent:event];
166
+ return hit == self ? nil : hit;
167
+ }
168
+
169
+ - (void)layoutSubviews {
170
+ [super layoutSubviews];
171
+ JMDLayoutStyle *style = _measured.block.layoutStyle;
172
+ const CGFloat left = style.borderLeftWidth + style.paddingLeft;
173
+ const CGFloat top = style.borderTopWidth + style.paddingTop;
174
+ _stack.frame = CGRectMake(
175
+ left,
176
+ top,
177
+ self.bounds.size.width - left - style.borderRightWidth - style.paddingRight,
178
+ self.bounds.size.height - top - style.borderBottomWidth - style.paddingBottom);
179
+ _borders.frame = self.bounds;
180
+ [_borders setNeedsDisplay];
181
+ }
182
+
183
+ @end
184
+
185
+ @implementation JMDCodeBlockView {
186
+ JMDMeasuredBlock *_measured;
187
+ UIScrollView *_scroller;
188
+ JMDBlockTextView *_text;
189
+ }
190
+
191
+ - (void)bind:(JMDMeasuredBlock *)measured {
192
+ _measured = measured;
193
+ if (_scroller == nil) {
194
+ _scroller = [[JMDNestedScrollView alloc] initWithFrame:CGRectZero];
195
+ _scroller.showsHorizontalScrollIndicator = NO;
196
+ _scroller.showsVerticalScrollIndicator = NO;
197
+ _scroller.alwaysBounceHorizontal = YES;
198
+ _scroller.alwaysBounceVertical = NO;
199
+ _scroller.delaysContentTouches = NO;
200
+ _scroller.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
201
+ _text = [[JMDBlockTextView alloc] initWithFrame:CGRectZero];
202
+ [_scroller addSubview:_text];
203
+ [self addSubview:_scroller];
204
+ }
205
+ JMDApplyBox(self, measured.block.layoutStyle);
206
+ _text.attributedText = measured.block.attributedText;
207
+ [self setNeedsLayout];
208
+ }
209
+
210
+
211
+ // Never the hit view itself: markdown touches belong to the host component
212
+ // view; only nested scrollers (code blocks, tables) claim touches.
213
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
214
+ UIView *hit = [super hitTest:point withEvent:event];
215
+ return hit == self ? nil : hit;
216
+ }
217
+
218
+ - (void)layoutSubviews {
219
+ [super layoutSubviews];
220
+ JMDLayoutStyle *style = _measured.block.layoutStyle;
221
+ const CGFloat innerWidth =
222
+ self.bounds.size.width - style.paddingLeft - style.paddingRight;
223
+ _scroller.frame = CGRectMake(
224
+ style.paddingLeft, style.paddingTop, innerWidth, _measured.textHeight);
225
+ _text.frame = CGRectMake(0, 0, _measured.contentWidth, _measured.textHeight);
226
+ _scroller.contentSize = CGSizeMake(_measured.contentWidth, _measured.textHeight);
227
+ }
228
+
229
+ @end
230
+
231
+ @implementation JMDListBlockView {
232
+ JMDMeasuredBlock *_measured;
233
+ CGFloat _gap;
234
+ }
235
+
236
+ - (void)bind:(JMDMeasuredBlock *)measured
237
+ gap:(CGFloat)gap
238
+ host:(nullable id<JMDMarkdownHost>)host {
239
+ _measured = measured;
240
+ _gap = gap;
241
+ for (UIView *subview in [self.subviews copy]) {
242
+ [subview removeFromSuperview];
243
+ }
244
+ JMDBlock *block = measured.block;
245
+ for (NSUInteger i = 0; i < block.rows.count; i++) {
246
+ JMDBlockTextView *marker = [[JMDBlockTextView alloc] initWithFrame:CGRectZero];
247
+ marker.attributedText = block.rows[i].marker;
248
+ [self addSubview:marker];
249
+
250
+ JMDBlockStackView *content = [[JMDBlockStackView alloc] initWithFrame:CGRectZero];
251
+ content.host = host;
252
+ [content setBlocks:measured.rowContents[i] gap:gap];
253
+ [self addSubview:content];
254
+ }
255
+ [self setNeedsLayout];
256
+ }
257
+
258
+
259
+ // Never the hit view itself: markdown touches belong to the host component
260
+ // view; only nested scrollers (code blocks, tables) claim touches.
261
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
262
+ UIView *hit = [super hitTest:point withEvent:event];
263
+ return hit == self ? nil : hit;
264
+ }
265
+
266
+ - (void)layoutSubviews {
267
+ [super layoutSubviews];
268
+ JMDBlock *block = _measured.block;
269
+ const CGFloat markerX = block.listMarginLeft + block.markerMarginLeft;
270
+ const CGFloat contentX = markerX + block.markerWidth;
271
+ const CGFloat contentWidth = _measured.contentWidth;
272
+
273
+ CGFloat y = 0;
274
+ for (NSUInteger i = 0; i < block.rows.count; i++) {
275
+ const CGFloat markerHeight = _measured.markerHeights[i].doubleValue;
276
+ const CGFloat contentHeight =
277
+ [JMDRenderedContent stackHeight:_measured.rowContents[i] gap:_gap];
278
+ const CGFloat rowHeight = MAX(markerHeight, contentHeight);
279
+
280
+ UIView *markerView = self.subviews[i * 2];
281
+ UIView *contentView = self.subviews[i * 2 + 1];
282
+ markerView.frame = CGRectMake(markerX, y, block.markerWidth, markerHeight);
283
+ contentView.frame = CGRectMake(contentX, y, contentWidth, contentHeight);
284
+
285
+ y += rowHeight;
286
+ if (i + 1 < block.rows.count) {
287
+ y += _gap / 2;
288
+ }
289
+ }
290
+ }
291
+
292
+ @end
@@ -0,0 +1,19 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #import "../render/JMDBlock.h"
4
+ #import "JMDMarkdownHost.h"
5
+
6
+ NS_ASSUME_NONNULL_BEGIN
7
+
8
+ /// One markdown image: rounded-corner aspect-fit bitmap, background while
9
+ /// loading. Requests are URL-owned; this view only listens.
10
+ @interface JMDImageView : UIView
11
+
12
+ @property (nonatomic, weak, nullable) id<JMDMarkdownHost> host;
13
+ @property (nonatomic, readonly, nullable) NSString *imageUrl;
14
+
15
+ - (void)bind:(JMDBlock *)block;
16
+
17
+ @end
18
+
19
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
1
+ #import "JMDImageView.h"
2
+
3
+ #import <SDWebImage/SDWebImage.h>
4
+
5
+ @implementation JMDImageView {
6
+ JMDBlock *_block;
7
+ SDAnimatedImageView *_imageView;
8
+ }
9
+
10
+ - (instancetype)initWithFrame:(CGRect)frame {
11
+ if (self = [super initWithFrame:frame]) {
12
+ // Animated formats (GIF/APNG) play with lazy per-frame decoding and a
13
+ // dynamic frame-buffer cap, so long GIFs in image-heavy feeds do not
14
+ // balloon memory.
15
+ _imageView = [[SDAnimatedImageView alloc] initWithFrame:CGRectZero];
16
+ _imageView.contentMode = UIViewContentModeScaleAspectFit;
17
+ _imageView.clearBufferWhenStopped = YES;
18
+ [self addSubview:_imageView];
19
+ }
20
+ return self;
21
+ }
22
+
23
+ // Hit-test transparent: the host component view owns all touch handling.
24
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
25
+ return nil;
26
+ }
27
+
28
+ - (nullable NSString *)imageUrl {
29
+ return _block.imageUrl;
30
+ }
31
+
32
+ - (void)bind:(JMDBlock *)block {
33
+ _block = block;
34
+ self.backgroundColor = block.imageBackground ?: UIColor.clearColor;
35
+ self.layer.cornerRadius = block.imageBorderRadius;
36
+ self.layer.masksToBounds = block.imageBorderRadius > 0;
37
+
38
+ NSString *url = block.imageUrl ?: @"";
39
+ if (url.length == 0) {
40
+ [_imageView sd_cancelCurrentImageLoad];
41
+ _imageView.image = nil;
42
+ return;
43
+ }
44
+
45
+ __weak JMDImageView *weakSelf = self;
46
+ // sd_setImage cancels this view's previous request (recycling) and shares
47
+ // in-flight downloads by URL; completion runs synchronously on memory
48
+ // cache hits, so a fresh view still resizes un-presized images.
49
+ [_imageView sd_setImageWithURL:[NSURL URLWithString:url]
50
+ placeholderImage:nil
51
+ options:SDWebImageRetryFailed | SDWebImageScaleDownLargeImages
52
+ completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
53
+ NSURL *imageURL) {
54
+ JMDImageView *strongSelf = weakSelf;
55
+ if (strongSelf == nil || image == nil ||
56
+ ![strongSelf->_block.imageUrl isEqualToString:url]) {
57
+ return;
58
+ }
59
+ [strongSelf.host imageIntrinsicSize:image.size forUrl:url];
60
+ }];
61
+ }
62
+
63
+ - (void)layoutSubviews {
64
+ [super layoutSubviews];
65
+ _imageView.frame = self.bounds;
66
+ }
67
+
68
+ @end
@@ -0,0 +1,16 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// Callbacks from block views up to the host component view.
6
+ @protocol JMDMarkdownHost <NSObject>
7
+ - (void)imageIntrinsicSize:(CGSize)size forUrl:(NSString *)url;
8
+ - (BOOL)isSpoilerRevealed:(NSInteger)spoilerId;
9
+ - (void)toggleSpoiler:(NSInteger)spoilerId;
10
+ - (void)linkPressed:(NSString *)url;
11
+ - (void)linkLongPressed:(NSString *)url;
12
+ /// `frame` is the pressed image's frame in screen coordinates (points).
13
+ - (void)imagePressed:(NSString *)url frame:(CGRect)frame;
14
+ @end
15
+
16
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,16 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #import "../render/JMDBlock.h"
4
+ #import "JMDMarkdownHost.h"
5
+
6
+ NS_ASSUME_NONNULL_BEGIN
7
+
8
+ /// Table: paints the table box, hosts a cell grid in a horizontal scroller
9
+ /// so wide tables keep readable column widths.
10
+ @interface JMDTableView : UIView
11
+
12
+ - (void)bind:(JMDMeasuredBlock *)measured host:(nullable id<JMDMarkdownHost>)host;
13
+
14
+ @end
15
+
16
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,172 @@
1
+ #import "JMDTableView.h"
2
+
3
+ #import "JMDBlockTextView.h"
4
+ #import "JMDBoxViews.h"
5
+
6
+ /// The unclipped grid inside the scroller: row boxes + cell text.
7
+ @interface JMDTableGridView : UIView
8
+ - (void)bind:(JMDMeasuredBlock *)measured host:(nullable id<JMDMarkdownHost>)host;
9
+ @end
10
+
11
+ @implementation JMDTableGridView {
12
+ JMDMeasuredBlock *_measured;
13
+ }
14
+
15
+ - (instancetype)initWithFrame:(CGRect)frame {
16
+ if (self = [super initWithFrame:frame]) {
17
+ self.backgroundColor = UIColor.clearColor;
18
+ self.contentMode = UIViewContentModeRedraw;
19
+ }
20
+ return self;
21
+ }
22
+
23
+ - (void)bind:(JMDMeasuredBlock *)measured host:(nullable id<JMDMarkdownHost>)host {
24
+ _measured = measured;
25
+ for (UIView *subview in [self.subviews copy]) {
26
+ [subview removeFromSuperview];
27
+ }
28
+ JMDBlock *block = measured.block;
29
+ for (JMDTableRow *row in block.tableRows) {
30
+ for (NSAttributedString *cell in row.cells) {
31
+ JMDBlockTextView *view = [[JMDBlockTextView alloc] initWithFrame:CGRectZero];
32
+ view.host = host;
33
+ view.attributedText = cell;
34
+ [self addSubview:view];
35
+ }
36
+ }
37
+ [self setNeedsDisplay];
38
+ [self setNeedsLayout];
39
+ }
40
+
41
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
42
+ // Touches inside the grid belong to the horizontal scroller.
43
+ UIView *hit = [super hitTest:point withEvent:event];
44
+ return hit == self ? nil : hit;
45
+ }
46
+
47
+ - (void)drawRect:(CGRect)rect {
48
+ JMDBlock *block = _measured.block;
49
+ CGContextRef context = UIGraphicsGetCurrentContext();
50
+ const CGFloat width = _measured.contentWidth;
51
+
52
+ CGFloat y = 0;
53
+ NSUInteger rowIndex = 0;
54
+ for (NSNumber *rowHeight in _measured.rowHeights) {
55
+ const BOOL isHeader =
56
+ rowIndex < block.tableRows.count && block.tableRows[rowIndex].isHeader;
57
+ JMDLayoutStyle *rowStyle = isHeader ? block.headerRowStyle : block.bodyRowStyle;
58
+ rowIndex++;
59
+ if (rowStyle.backgroundColor != nil) {
60
+ CGContextSetFillColorWithColor(context, rowStyle.backgroundColor.CGColor);
61
+ CGContextFillRect(context, CGRectMake(0, y, width, rowHeight.doubleValue));
62
+ }
63
+ if (rowStyle.borderBottomWidth > 0 && rowStyle.borderBottomColor != nil) {
64
+ CGContextSetFillColorWithColor(context, rowStyle.borderBottomColor.CGColor);
65
+ CGContextFillRect(
66
+ context,
67
+ CGRectMake(
68
+ 0,
69
+ y + rowHeight.doubleValue - rowStyle.borderBottomWidth,
70
+ width,
71
+ rowStyle.borderBottomWidth));
72
+ }
73
+ if (rowStyle.borderTopWidth > 0 && rowStyle.borderTopColor != nil) {
74
+ CGContextSetFillColorWithColor(context, rowStyle.borderTopColor.CGColor);
75
+ CGContextFillRect(context, CGRectMake(0, y, width, rowStyle.borderTopWidth));
76
+ }
77
+ y += rowHeight.doubleValue;
78
+ }
79
+ }
80
+
81
+ - (void)layoutSubviews {
82
+ [super layoutSubviews];
83
+ JMDBlock *block = _measured.block;
84
+
85
+ NSUInteger index = 0;
86
+ CGFloat y = 0;
87
+ for (NSUInteger rowIndex = 0; rowIndex < block.tableRows.count; rowIndex++) {
88
+ JMDTableRow *row = block.tableRows[rowIndex];
89
+ JMDLayoutStyle *rowStyle = row.isHeader ? block.headerRowStyle : block.bodyRowStyle;
90
+ const UIEdgeInsets pad = row.isHeader ? block.headerCellPadding : block.cellPadding;
91
+ const CGFloat cellPadH = pad.left + pad.right;
92
+ CGFloat x = 0;
93
+ for (NSUInteger column = 0; column < row.cells.count; column++) {
94
+ if (index >= self.subviews.count) {
95
+ break;
96
+ }
97
+ const CGFloat columnWidth = _measured.columnWidths[column].doubleValue;
98
+ const CGFloat cellHeight = _measured.rowHeights[rowIndex].doubleValue -
99
+ pad.top - pad.bottom -
100
+ rowStyle.borderTopWidth - rowStyle.borderBottomWidth;
101
+ self.subviews[index].frame = CGRectMake(
102
+ x + pad.left,
103
+ y + pad.top + rowStyle.borderTopWidth,
104
+ MAX(columnWidth - cellPadH, 1),
105
+ MAX(cellHeight, 0));
106
+ index++;
107
+ x += columnWidth;
108
+ }
109
+ y += _measured.rowHeights[rowIndex].doubleValue;
110
+ }
111
+ }
112
+
113
+ @end
114
+
115
+ @implementation JMDTableView {
116
+ JMDMeasuredBlock *_measured;
117
+ UIScrollView *_scroller;
118
+ JMDTableGridView *_grid;
119
+ }
120
+
121
+ - (void)bind:(JMDMeasuredBlock *)measured host:(nullable id<JMDMarkdownHost>)host {
122
+ _measured = measured;
123
+ if (_scroller == nil) {
124
+ _scroller = [[JMDNestedScrollView alloc] initWithFrame:CGRectZero];
125
+ _scroller.showsHorizontalScrollIndicator = NO;
126
+ _scroller.showsVerticalScrollIndicator = NO;
127
+ _scroller.alwaysBounceVertical = NO;
128
+ _scroller.delaysContentTouches = NO;
129
+ _scroller.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
130
+ _grid = [[JMDTableGridView alloc] initWithFrame:CGRectZero];
131
+ [_scroller addSubview:_grid];
132
+ [self addSubview:_scroller];
133
+ }
134
+
135
+ JMDLayoutStyle *style = measured.block.layoutStyle;
136
+ self.backgroundColor = style.backgroundColor ?: UIColor.clearColor;
137
+ self.layer.cornerRadius = style.borderRadius;
138
+ self.layer.cornerCurve =
139
+ style.continuousCorners ? kCACornerCurveContinuous : kCACornerCurveCircular;
140
+ self.layer.masksToBounds = style.borderRadius > 0;
141
+
142
+ [_grid bind:measured host:host];
143
+ [self setNeedsLayout];
144
+ }
145
+
146
+
147
+ // Never the hit view itself: markdown touches belong to the host component
148
+ // view; only nested scrollers (code blocks, tables) claim touches.
149
+ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
150
+ UIView *hit = [super hitTest:point withEvent:event];
151
+ return hit == self ? nil : hit;
152
+ }
153
+
154
+ - (void)layoutSubviews {
155
+ [super layoutSubviews];
156
+ JMDLayoutStyle *style = _measured.block.layoutStyle;
157
+ CGFloat gridHeight = 0;
158
+ for (NSNumber *rowHeight in _measured.rowHeights) {
159
+ gridHeight += rowHeight.doubleValue;
160
+ }
161
+ const CGFloat left = style.borderLeftWidth + style.paddingLeft;
162
+ const CGFloat top = style.borderTopWidth + style.paddingTop;
163
+ _scroller.frame = CGRectMake(
164
+ left,
165
+ top,
166
+ self.bounds.size.width - left - style.borderRightWidth - style.paddingRight,
167
+ gridHeight);
168
+ _grid.frame = CGRectMake(0, 0, _measured.contentWidth, gridHeight);
169
+ _scroller.contentSize = CGSizeMake(_measured.contentWidth, gridHeight);
170
+ }
171
+
172
+ @end
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+
3
+ import { useImperativeHandle, useRef } from "react";
4
+ import { TextInput } from "react-native";
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ // Non-native (web) fallback: a plain multiline text input over raw markdown.
7
+ export function JetMarkdownEditor({
8
+ allowFontScaling,
9
+ autoCapitalize,
10
+ autoCorrect,
11
+ autoFocus,
12
+ cursorColor,
13
+ defaultValue,
14
+ editable,
15
+ multiline = true,
16
+ onBlur,
17
+ onChangeMarkdown,
18
+ onChangeText,
19
+ onFocus,
20
+ placeholder,
21
+ placeholderTextColor,
22
+ scrollEnabled,
23
+ selectionColor,
24
+ style,
25
+ ref
26
+ }) {
27
+ const inputRef = useRef(null);
28
+ const textRef = useRef(defaultValue ?? "");
29
+ useImperativeHandle(ref, () => ({
30
+ blur: () => inputRef.current?.blur(),
31
+ focus: () => inputRef.current?.focus(),
32
+ getMarkdown: () => Promise.resolve(textRef.current),
33
+ insertLink: () => undefined,
34
+ insertMarkdown: () => undefined,
35
+ insertMention: () => undefined,
36
+ removeLink: () => undefined,
37
+ setSelection: () => {
38
+ // Not supported in the web fallback.
39
+ },
40
+ setValue: markdown => {
41
+ textRef.current = markdown;
42
+ inputRef.current?.setNativeProps({
43
+ text: markdown
44
+ });
45
+ },
46
+ // Formatting toggles are not supported in the web fallback (raw
47
+ // markdown).
48
+ toggleBlockQuote: () => undefined,
49
+ toggleBold: () => undefined,
50
+ toggleCode: () => undefined,
51
+ toggleCodeBlock: () => undefined,
52
+ toggleHeading: () => undefined,
53
+ toggleItalic: () => undefined,
54
+ toggleOrderedList: () => undefined,
55
+ toggleSpoiler: () => undefined,
56
+ toggleStrikethrough: () => undefined,
57
+ toggleSubscript: () => undefined,
58
+ toggleSuperscript: () => undefined,
59
+ toggleUnorderedList: () => undefined
60
+ }), []);
61
+ return /*#__PURE__*/_jsx(TextInput, {
62
+ allowFontScaling: allowFontScaling,
63
+ autoCapitalize: autoCapitalize,
64
+ autoCorrect: autoCorrect,
65
+ autoFocus: autoFocus,
66
+ cursorColor: cursorColor,
67
+ defaultValue: defaultValue,
68
+ editable: editable,
69
+ multiline: multiline,
70
+ onBlur: onBlur,
71
+ onChangeText: text => {
72
+ textRef.current = text;
73
+ onChangeText?.(text);
74
+ onChangeMarkdown?.(text);
75
+ },
76
+ onFocus: onFocus,
77
+ placeholder: placeholder,
78
+ placeholderTextColor: placeholderTextColor,
79
+ ref: inputRef,
80
+ scrollEnabled: scrollEnabled,
81
+ selectionColor: selectionColor,
82
+ style: style
83
+ });
84
+ }
85
+ //# sourceMappingURL=JetMarkdownEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useImperativeHandle","useRef","TextInput","jsx","_jsx","JetMarkdownEditor","allowFontScaling","autoCapitalize","autoCorrect","autoFocus","cursorColor","defaultValue","editable","multiline","onBlur","onChangeMarkdown","onChangeText","onFocus","placeholder","placeholderTextColor","scrollEnabled","selectionColor","style","ref","inputRef","textRef","blur","current","focus","getMarkdown","Promise","resolve","insertLink","undefined","insertMarkdown","insertMention","removeLink","setSelection","setValue","markdown","setNativeProps","text","toggleBlockQuote","toggleBold","toggleCode","toggleCodeBlock","toggleHeading","toggleItalic","toggleOrderedList","toggleSpoiler","toggleStrikethrough","toggleSubscript","toggleSuperscript","toggleUnorderedList"],"sourceRoot":"../../src","sources":["JetMarkdownEditor.tsx"],"mappings":"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","ignoreList":[]}