react-native-enriched-markdown 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +80 -8
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +17 -2
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +6 -1
  4. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
  5. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
  6. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +28 -3
  7. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +225 -1
  8. package/android/src/main/cpp/jni-adapter.cpp +28 -11
  9. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +132 -15
  10. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +1 -16
  11. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +67 -13
  12. package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +241 -21
  13. package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
  14. package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
  15. package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +2 -0
  16. package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +17 -3
  17. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +13 -18
  18. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +23 -24
  19. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +1 -0
  20. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +2 -1
  21. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +2 -1
  22. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +18 -2
  23. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +22 -6
  24. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +1 -0
  25. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +3 -2
  26. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +2 -1
  27. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +16 -9
  28. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +5 -1
  29. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +23 -9
  30. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +24 -10
  31. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +1 -0
  32. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
  33. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +2 -1
  34. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +1 -0
  35. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +1 -0
  36. package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
  37. package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +1 -0
  38. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +8 -17
  39. package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +19 -5
  40. package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +1 -1
  41. package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
  42. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +1 -0
  43. package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +3 -0
  44. package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +3 -0
  45. package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +5 -1
  46. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +3 -1
  47. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +3 -0
  48. package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +5 -1
  49. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
  50. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +32 -1
  51. package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +22 -5
  52. package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
  53. package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
  54. package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +23 -5
  55. package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
  56. package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +10 -0
  57. package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +58 -56
  58. package/android/src/main/jni/CMakeLists.txt +1 -13
  59. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +0 -13
  60. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +2 -14
  61. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +3 -0
  62. package/cpp/parser/MD4CParser.cpp +21 -8
  63. package/cpp/parser/MD4CParser.hpp +5 -1
  64. package/cpp/parser/MarkdownASTNode.hpp +2 -0
  65. package/ios/EnrichedMarkdownText.mm +356 -29
  66. package/ios/attachments/{ImageAttachment.h → EnrichedMarkdownImageAttachment.h} +1 -1
  67. package/ios/attachments/{ImageAttachment.m → EnrichedMarkdownImageAttachment.m} +4 -4
  68. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
  69. package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
  70. package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +28 -3
  71. package/ios/generated/EnrichedMarkdownTextSpec/Props.h +225 -1
  72. package/ios/parser/MarkdownASTNode.h +2 -0
  73. package/ios/parser/MarkdownParser.h +9 -0
  74. package/ios/parser/MarkdownParser.mm +31 -2
  75. package/ios/parser/MarkdownParserBridge.mm +13 -3
  76. package/ios/renderer/AttributedRenderer.h +2 -0
  77. package/ios/renderer/AttributedRenderer.m +52 -19
  78. package/ios/renderer/BlockquoteRenderer.m +7 -6
  79. package/ios/renderer/CodeBlockRenderer.m +9 -8
  80. package/ios/renderer/HeadingRenderer.m +31 -24
  81. package/ios/renderer/ImageRenderer.m +31 -10
  82. package/ios/renderer/ListItemRenderer.m +51 -39
  83. package/ios/renderer/ListRenderer.m +21 -18
  84. package/ios/renderer/ParagraphRenderer.m +27 -16
  85. package/ios/renderer/RenderContext.h +17 -0
  86. package/ios/renderer/RenderContext.m +66 -2
  87. package/ios/renderer/RendererFactory.m +6 -0
  88. package/ios/renderer/StrikethroughRenderer.h +6 -0
  89. package/ios/renderer/StrikethroughRenderer.m +40 -0
  90. package/ios/renderer/UnderlineRenderer.h +6 -0
  91. package/ios/renderer/UnderlineRenderer.m +39 -0
  92. package/ios/styles/StyleConfig.h +46 -0
  93. package/ios/styles/StyleConfig.mm +351 -12
  94. package/ios/utils/AccessibilityInfo.h +35 -0
  95. package/ios/utils/AccessibilityInfo.m +24 -0
  96. package/ios/utils/CodeBlockBackground.m +4 -9
  97. package/ios/utils/FontUtils.h +5 -0
  98. package/ios/utils/FontUtils.m +14 -0
  99. package/ios/utils/HTMLGenerator.m +21 -7
  100. package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
  101. package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
  102. package/ios/utils/MarkdownExtractor.m +18 -5
  103. package/ios/utils/ParagraphStyleUtils.h +10 -2
  104. package/ios/utils/ParagraphStyleUtils.m +57 -2
  105. package/ios/utils/PasteboardUtils.h +1 -1
  106. package/ios/utils/PasteboardUtils.m +3 -3
  107. package/lib/module/EnrichedMarkdownText.js +33 -2
  108. package/lib/module/EnrichedMarkdownText.js.map +1 -1
  109. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +83 -3
  110. package/lib/module/index.js +0 -1
  111. package/lib/module/index.js.map +1 -1
  112. package/lib/module/normalizeMarkdownStyle.js +58 -14
  113. package/lib/module/normalizeMarkdownStyle.js.map +1 -1
  114. package/lib/typescript/src/EnrichedMarkdownText.d.ts +85 -3
  115. package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -1
  116. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +75 -1
  117. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  118. package/lib/typescript/src/index.d.ts +2 -3
  119. package/lib/typescript/src/index.d.ts.map +1 -1
  120. package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/EnrichedMarkdownText.tsx +133 -5
  123. package/src/EnrichedMarkdownTextNativeComponent.ts +83 -3
  124. package/src/index.tsx +5 -2
  125. package/src/normalizeMarkdownStyle.ts +46 -0
  126. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +0 -9
  127. package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +0 -25
@@ -0,0 +1,121 @@
1
+ package com.swmansion.enriched.markdown.utils
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.text.Selection
6
+ import android.text.Spannable
7
+ import android.text.method.LinkMovementMethod
8
+ import android.view.MotionEvent
9
+ import android.view.ViewConfiguration
10
+ import android.widget.TextView
11
+ import com.swmansion.enriched.markdown.spans.LinkSpan
12
+ import kotlin.math.abs
13
+
14
+ /**
15
+ * Custom MovementMethod that handles both link clicks and long presses.
16
+ * Extends LinkMovementMethod to maintain click functionality while adding long press support.
17
+ */
18
+ class LinkLongPressMovementMethod : LinkMovementMethod() {
19
+ private val handler = Handler(Looper.getMainLooper())
20
+ private var longPressRunnable: Runnable? = null
21
+
22
+ private var startX = 0f
23
+ private var startY = 0f
24
+
25
+ override fun onTouchEvent(
26
+ widget: TextView,
27
+ buffer: Spannable,
28
+ event: MotionEvent,
29
+ ): Boolean {
30
+ when (event.action) {
31
+ MotionEvent.ACTION_DOWN -> {
32
+ startX = event.x
33
+ startY = event.y
34
+
35
+ // Identify if a LinkSpan exists at the touch coordinates
36
+ findLinkSpan(widget, buffer, event)?.let { span ->
37
+ scheduleLongPress(widget, span)
38
+ }
39
+ }
40
+
41
+ MotionEvent.ACTION_MOVE -> {
42
+ val config = ViewConfiguration.get(widget.context)
43
+ // Cancel if the finger moves beyond the standard system touch slop
44
+ if (abs(event.x - startX) > config.scaledTouchSlop ||
45
+ abs(event.y - startY) > config.scaledTouchSlop
46
+ ) {
47
+ cancelLongPress()
48
+ }
49
+ }
50
+
51
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
52
+ cancelLongPress()
53
+ // Clear text selection to prevent the "stuck" highlight look
54
+ if (widget.hasSelection()) {
55
+ Selection.removeSelection(buffer)
56
+ }
57
+ }
58
+ }
59
+
60
+ // Let the parent LinkMovementMethod handle the standard click logic
61
+ val result = super.onTouchEvent(widget, buffer, event)
62
+
63
+ // LinkMovementMethod sets a Selection highlight around the link on ACTION_DOWN,
64
+ // which causes a visible selection color on the link text while pressed.
65
+ // We remove that selection immediately so the user never sees it.
66
+ if (event.action == MotionEvent.ACTION_DOWN) {
67
+ Selection.removeSelection(buffer)
68
+ }
69
+
70
+ return result
71
+ }
72
+
73
+ private fun scheduleLongPress(
74
+ widget: TextView,
75
+ span: LinkSpan,
76
+ ) {
77
+ cancelLongPress()
78
+
79
+ longPressRunnable =
80
+ Runnable {
81
+ if (widget.hasSelection()) {
82
+ Selection.removeSelection(widget.text as Spannable)
83
+ }
84
+ // Execute the long click logic on the span
85
+ if (span.onLongClick(widget)) {
86
+ // If consumed, cancel the system's own long-press logic (like context menus)
87
+ widget.cancelLongPress()
88
+ }
89
+ longPressRunnable = null
90
+ }.also {
91
+ handler.postDelayed(it, ViewConfiguration.getLongPressTimeout().toLong())
92
+ }
93
+ }
94
+
95
+ private fun cancelLongPress() {
96
+ longPressRunnable?.let(handler::removeCallbacks)
97
+ longPressRunnable = null
98
+ }
99
+
100
+ private fun findLinkSpan(
101
+ widget: TextView,
102
+ buffer: Spannable,
103
+ event: MotionEvent,
104
+ ): LinkSpan? {
105
+ // Adjust coordinates for padding and scroll
106
+ val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX
107
+ val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY
108
+
109
+ val layout = widget.layout ?: return null
110
+ val line = layout.getLineForVertical(y)
111
+ val offset = layout.getOffsetForHorizontal(line, x.toFloat())
112
+
113
+ // Ensure the touch is within the character bounds
114
+ return buffer.getSpans(offset, offset, LinkSpan::class.java).firstOrNull()
115
+ }
116
+
117
+ companion object {
118
+ @JvmStatic
119
+ fun createInstance(): LinkLongPressMovementMethod = LinkLongPressMovementMethod()
120
+ }
121
+ }
@@ -1,6 +1,7 @@
1
1
  package com.swmansion.enriched.markdown.utils
2
2
 
3
3
  import android.text.Spannable
4
+ import android.text.style.UnderlineSpan
4
5
  import android.widget.TextView
5
6
  import com.swmansion.enriched.markdown.EnrichedMarkdownText
6
7
  import com.swmansion.enriched.markdown.spans.BlockquoteSpan
@@ -11,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.HeadingSpan
11
12
  import com.swmansion.enriched.markdown.spans.ImageSpan
12
13
  import com.swmansion.enriched.markdown.spans.LinkSpan
13
14
  import com.swmansion.enriched.markdown.spans.OrderedListSpan
15
+ import com.swmansion.enriched.markdown.spans.StrikethroughSpan
14
16
  import com.swmansion.enriched.markdown.spans.StrongSpan
15
17
  import com.swmansion.enriched.markdown.spans.ThematicBreakSpan
16
18
  import com.swmansion.enriched.markdown.spans.UnorderedListSpan
@@ -283,6 +285,8 @@ object MarkdownExtractor {
283
285
  val hasStrong = spannable.getSpans(start, end, StrongSpan::class.java).isNotEmpty()
284
286
  val hasEmphasis = spannable.getSpans(start, end, EmphasisSpan::class.java).isNotEmpty()
285
287
  val hasCode = spannable.getSpans(start, end, CodeSpan::class.java).isNotEmpty()
288
+ val hasStrikethrough = spannable.getSpans(start, end, StrikethroughSpan::class.java).isNotEmpty()
289
+ val hasUnderline = spannable.getSpans(start, end, UnderlineSpan::class.java).isNotEmpty()
286
290
  val linkSpans = spannable.getSpans(start, end, LinkSpan::class.java)
287
291
 
288
292
  var result = text
@@ -291,6 +295,12 @@ object MarkdownExtractor {
291
295
  if (hasCode && linkSpans.isEmpty()) {
292
296
  result = "`$result`"
293
297
  }
298
+ if (hasStrikethrough) {
299
+ result = "~~$result~~"
300
+ }
301
+ if (hasUnderline && linkSpans.isEmpty()) {
302
+ result = "<u>$result</u>"
303
+ }
294
304
  if (hasEmphasis) {
295
305
  result = "*$result*"
296
306
  }
@@ -2,10 +2,10 @@ package com.swmansion.enriched.markdown.utils
2
2
 
3
3
  import android.content.Context
4
4
  import android.graphics.Typeface
5
- import android.os.Build
6
5
  import android.text.SpannableString
7
6
  import android.text.SpannableStringBuilder
8
7
  import android.text.TextPaint
8
+ import com.facebook.react.bridge.ReadableMap
9
9
  import com.facebook.react.common.ReactConstants
10
10
  import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
11
11
  import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
@@ -13,8 +13,6 @@ import com.swmansion.enriched.markdown.parser.MarkdownASTNode
13
13
  import com.swmansion.enriched.markdown.renderer.BlockStyle
14
14
  import com.swmansion.enriched.markdown.spans.LineHeightSpan
15
15
  import com.swmansion.enriched.markdown.spans.MarginBottomSpan
16
- import com.swmansion.enriched.markdown.styles.ParagraphStyle
17
- import com.swmansion.enriched.markdown.styles.StyleConfig
18
16
  import android.text.style.LineHeightSpan as AndroidLineHeightSpan
19
17
 
20
18
  // ============================================================================
@@ -95,37 +93,6 @@ fun MarkdownASTNode.containsBlockImage(): Boolean {
95
93
  return firstChild != null && children.size == 1 && firstChild.type == MarkdownASTNode.NodeType.Image
96
94
  }
97
95
 
98
- /**
99
- * Determines the appropriate marginBottom for a paragraph.
100
- * If paragraph contains only a single block-level element (e.g., image), uses that element's marginBottom.
101
- * Otherwise, uses paragraph's marginBottom.
102
- */
103
- fun getMarginBottomForParagraph(
104
- paragraph: MarkdownASTNode,
105
- paragraphStyle: ParagraphStyle,
106
- style: StyleConfig,
107
- ): Float {
108
- // If paragraph contains only a single block-level element, use that element's marginBottom
109
- // Otherwise, use paragraph's marginBottom
110
- if (paragraph.children.size == 1) {
111
- // Paragraph has exactly one child
112
- when (paragraph.children.first().type) {
113
- MarkdownASTNode.NodeType.Image -> {
114
- // Image: use image's marginBottom
115
- return style.imageStyle.marginBottom
116
- }
117
-
118
- // Future: Add other block elements here as they're implemented
119
- else -> {
120
- // Not a block element we handle specially
121
- }
122
- }
123
- }
124
-
125
- // Default: use paragraph's marginBottom
126
- return paragraphStyle.marginBottom
127
- }
128
-
129
96
  // ============================================================================
130
97
  // SpannableStringBuilder Extensions
131
98
  // ============================================================================
@@ -144,38 +111,73 @@ fun SpannableStringBuilder.isInlineImage(): Boolean {
144
111
  // Span Creation Utilities
145
112
  // ============================================================================
146
113
 
147
- /**
148
- * Creates a LineHeightSpan appropriate for the current API level.
149
- *
150
- * @param lineHeight The desired line height in pixels
151
- */
152
- fun createLineHeightSpan(lineHeight: Float): AndroidLineHeightSpan =
153
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
154
- AndroidLineHeightSpan.Standard(lineHeight.toInt())
155
- } else {
156
- LineHeightSpan(lineHeight)
157
- }
114
+ fun createLineHeightSpan(lineHeight: Float): AndroidLineHeightSpan = LineHeightSpan(lineHeight)
115
+
116
+ fun applyMarginTop(
117
+ builder: SpannableStringBuilder,
118
+ insertionPoint: Int,
119
+ marginTop: Float,
120
+ ) {
121
+ if (marginTop <= 0) return
122
+
123
+ // Insert a newline character to act as a vertical spacer
124
+ builder.insert(insertionPoint, "\n")
125
+
126
+ // Apply MarginBottomSpan to the spacer character to create the gap before the content
127
+ builder.setSpan(
128
+ MarginBottomSpan(marginTop),
129
+ insertionPoint,
130
+ insertionPoint + 1,
131
+ SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
132
+ )
133
+ }
158
134
 
159
135
  /**
160
136
  * Applies marginBottom spacing to a block element.
161
- * Appends a newline and applies MarginBottomSpan if marginBottom > 0.
137
+ * Appends a newline spacer and applies MarginBottomSpan to ONLY the spacer character.
138
+ *
139
+ * The span covers only the trailing newline to avoid overlapping with LineHeightSpan
140
+ * on content lines, which would cause incorrect spacing due to conflicting font metric
141
+ * modifications in chooseHeight().
162
142
  *
163
143
  * @param builder The SpannableStringBuilder to modify
164
- * @param start The start position of the block content (before appending newline)
165
144
  * @param marginBottom The spacing value to apply after the block
166
145
  */
167
146
  fun applyMarginBottom(
168
147
  builder: SpannableStringBuilder,
169
- start: Int,
170
148
  marginBottom: Float,
171
149
  ) {
150
+ val spacerStart = builder.length
172
151
  builder.append("\n")
173
- if (marginBottom > 0) {
174
- builder.setSpan(
175
- MarginBottomSpan(marginBottom),
176
- start,
177
- builder.length, // Includes the newline we just appended
178
- SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
179
- )
180
- }
152
+ // Always create a MarginBottomSpan, even when marginBottom = 0.
153
+ // This ensures removeTrailingMargin can correctly identify the LAST element's
154
+ // margin value. Without a span on the last element, it would pick up a previous
155
+ // element's span (e.g. blockquote with marginBottom: 16) and use that wrong value.
156
+ builder.setSpan(
157
+ MarginBottomSpan(marginBottom),
158
+ spacerStart,
159
+ builder.length,
160
+ SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
161
+ )
181
162
  }
163
+
164
+ // ============================================================================
165
+ // ReadableMap Extensions
166
+ // ============================================================================
167
+
168
+ fun ReadableMap?.getBooleanOrDefault(
169
+ key: String,
170
+ default: Boolean,
171
+ ): Boolean = if (this?.hasKey(key) == true) getBoolean(key) else default
172
+
173
+ fun ReadableMap?.getFloatOrDefault(
174
+ key: String,
175
+ default: Float,
176
+ ): Float = if (this?.hasKey(key) == true) getDouble(key).toFloat() else default
177
+
178
+ fun ReadableMap?.getStringOrDefault(
179
+ key: String,
180
+ default: String,
181
+ ): String = if (this?.hasKey(key) == true) getString(key) ?: default else default
182
+
183
+ fun ReadableMap?.getMapOrNull(key: String): ReadableMap? = if (this?.hasKey(key) == true) getMap(key) else null
@@ -67,16 +67,4 @@ target_include_directories(
67
67
  ${CMAKE_CURRENT_SOURCE_DIR}
68
68
  )
69
69
 
70
- if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
71
- target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
72
- else()
73
- target_compile_options(
74
- ${LIB_TARGET_NAME}
75
- PRIVATE
76
- -DLOG_TAG=\"ReactNative\"
77
- -fexceptions
78
- -frtti
79
- -std=c++20
80
- -Wall
81
- )
82
- endif()
70
+ target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
@@ -12,19 +12,6 @@ void MarkdownTextShadowNode::setMeasurementsManager(
12
12
  measurementsManager_ = measurementsManager;
13
13
  }
14
14
 
15
- // Mark layout as dirty after state has been updated.
16
- // Once layout is marked as dirty, `measureContent` will be called in order to
17
- // recalculate layout.
18
- void MarkdownTextShadowNode::dirtyLayoutIfNeeded() {
19
- const auto state = this->getStateData();
20
- const auto counter = state.getForceHeightRecalculationCounter();
21
-
22
- if (forceHeightRecalculationCounter_ != counter) {
23
- forceHeightRecalculationCounter_ = counter;
24
- dirtyLayout();
25
- }
26
- }
27
-
28
15
  Size MarkdownTextShadowNode::measureContent(const LayoutContext &layoutContext,
29
16
  const LayoutConstraints &layoutConstraints) const {
30
17
  return measurementsManager_->measure(getSurfaceId(), getTag(), getConcreteProps(), layoutConstraints);
@@ -1,7 +1,6 @@
1
1
  #pragma once
2
2
 
3
3
  #include "MarkdownTextMeasurementManager.h"
4
- #include "MarkdownTextState.h"
5
4
 
6
5
  #include <react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h>
7
6
  #include <react/renderer/components/EnrichedMarkdownTextSpec/Props.h>
@@ -14,19 +13,11 @@ JSI_EXPORT extern const char MarkdownTextComponentName[];
14
13
  /*
15
14
  * `ShadowNode` for <EnrichedMarkdownText> component.
16
15
  */
17
- class MarkdownTextShadowNode final
18
- : public ConcreteViewShadowNode<MarkdownTextComponentName, EnrichedMarkdownTextProps,
19
- EnrichedMarkdownTextEventEmitter, MarkdownTextState> {
16
+ class MarkdownTextShadowNode final : public ConcreteViewShadowNode<MarkdownTextComponentName, EnrichedMarkdownTextProps,
17
+ EnrichedMarkdownTextEventEmitter> {
20
18
  public:
21
19
  using ConcreteViewShadowNode::ConcreteViewShadowNode;
22
20
 
23
- // This constructor is called when we "update" shadow node, e.g. after
24
- // updating shadow node's state
25
- MarkdownTextShadowNode(ShadowNode const &sourceShadowNode, ShadowNodeFragment const &fragment)
26
- : ConcreteViewShadowNode(sourceShadowNode, fragment) {
27
- dirtyLayoutIfNeeded();
28
- }
29
-
30
21
  static ShadowNodeTraits BaseTraits() {
31
22
  auto traits = ConcreteViewShadowNode::BaseTraits();
32
23
  traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
@@ -37,12 +28,9 @@ public:
37
28
  // Associates a shared `MarkdownTextMeasurementManager` with the node.
38
29
  void setMeasurementsManager(const std::shared_ptr<MarkdownTextMeasurementManager> &measurementsManager);
39
30
 
40
- void dirtyLayoutIfNeeded();
41
-
42
31
  Size measureContent(const LayoutContext &layoutContext, const LayoutConstraints &layoutConstraints) const override;
43
32
 
44
33
  private:
45
- int forceHeightRecalculationCounter_;
46
34
  std::shared_ptr<MarkdownTextMeasurementManager> measurementsManager_;
47
35
  };
48
36
 
@@ -12,6 +12,9 @@ inline folly::dynamic toDynamic(const EnrichedMarkdownTextProps &props) {
12
12
  folly::dynamic serializedProps = folly::dynamic::object();
13
13
  serializedProps["markdown"] = props.markdown;
14
14
  serializedProps["markdownStyle"] = toDynamic(props.markdownStyle);
15
+ serializedProps["md4cFlags"] = toDynamic(props.md4cFlags);
16
+ serializedProps["allowTrailingMargin"] = props.allowTrailingMargin;
17
+
15
18
  return serializedProps;
16
19
  }
17
20
  #endif
@@ -194,11 +194,21 @@ public:
194
194
  break;
195
195
  }
196
196
 
197
+ case MD_SPAN_U: {
198
+ impl->pushNode(std::make_shared<MarkdownASTNode>(NodeType::Underline));
199
+ break;
200
+ }
201
+
197
202
  case MD_SPAN_CODE: {
198
203
  impl->pushNode(std::make_shared<MarkdownASTNode>(NodeType::Code));
199
204
  break;
200
205
  }
201
206
 
207
+ case MD_SPAN_DEL: {
208
+ impl->pushNode(std::make_shared<MarkdownASTNode>(NodeType::Strikethrough));
209
+ break;
210
+ }
211
+
202
212
  case MD_SPAN_IMG: {
203
213
  auto node = std::make_shared<MarkdownASTNode>(NodeType::Image);
204
214
  if (detail) {
@@ -261,7 +271,7 @@ MD4CParser::MD4CParser() : impl_(std::make_unique<Impl>()) {}
261
271
 
262
272
  MD4CParser::~MD4CParser() = default;
263
273
 
264
- std::shared_ptr<MarkdownASTNode> MD4CParser::parse(const std::string &markdown) {
274
+ std::shared_ptr<MarkdownASTNode> MD4CParser::parse(const std::string &markdown, const Md4cFlags &md4cFlags) {
265
275
  if (markdown.empty()) {
266
276
  return std::make_shared<MarkdownASTNode>(NodeType::Document);
267
277
  }
@@ -279,15 +289,18 @@ std::shared_ptr<MarkdownASTNode> MD4CParser::parse(const std::string &markdown)
279
289
  impl_->reset(estimatedDepth);
280
290
  impl_->inputText = markdown.c_str();
281
291
 
292
+ // MD_FLAG_NOHTML: Disable HTML parsing
293
+ // MD_FLAG_STRIKETHROUGH: Enable ~~strikethrough~~ syntax
294
+ // MD_FLAG_UNDERLINE: When enabled, __ creates underline; when disabled, __ creates emphasis
295
+ unsigned flags = MD_FLAG_NOHTML | MD_FLAG_STRIKETHROUGH;
296
+ if (md4cFlags.underline) {
297
+ flags |= MD_FLAG_UNDERLINE;
298
+ }
299
+
282
300
  // Configure MD4C parser with callbacks
283
301
  MD_PARSER parser = {
284
- 0, // abi_version
285
- MD_FLAG_NOHTML, // flags - disable HTML for security
286
- &Impl::enterBlock,
287
- &Impl::leaveBlock,
288
- &Impl::enterSpan,
289
- &Impl::leaveSpan,
290
- &Impl::text,
302
+ 0, // abi_version
303
+ flags, &Impl::enterBlock, &Impl::leaveBlock, &Impl::enterSpan, &Impl::leaveSpan, &Impl::text,
291
304
  nullptr, // debug_log
292
305
  nullptr // syntax
293
306
  };
@@ -6,13 +6,17 @@
6
6
 
7
7
  namespace Markdown {
8
8
 
9
+ struct Md4cFlags {
10
+ bool underline = false;
11
+ };
12
+
9
13
  class MD4CParser {
10
14
  public:
11
15
  MD4CParser();
12
16
  ~MD4CParser();
13
17
 
14
18
  // Parse markdown string and return AST root node
15
- std::shared_ptr<MarkdownASTNode> parse(const std::string& markdown);
19
+ std::shared_ptr<MarkdownASTNode> parse(const std::string& markdown, const Md4cFlags& flags = Md4cFlags{});
16
20
 
17
21
  private:
18
22
  class Impl;
@@ -16,6 +16,8 @@ enum class NodeType {
16
16
  LineBreak,
17
17
  Strong,
18
18
  Emphasis,
19
+ Strikethrough,
20
+ Underline,
19
21
  Code,
20
22
  Image,
21
23
  Blockquote,