react-native-enriched-markdown 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -8
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +17 -2
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +6 -1
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/android/src/main/cpp/jni-adapter.cpp +28 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +132 -15
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +1 -16
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +67 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +241 -21
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +17 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +13 -18
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +23 -24
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +18 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +22 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +16 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +23 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +24 -10
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +8 -17
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +19 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +3 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +32 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +22 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +23 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +58 -56
- package/android/src/main/jni/CMakeLists.txt +1 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +0 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +2 -14
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +3 -0
- package/cpp/parser/MD4CParser.cpp +21 -8
- package/cpp/parser/MD4CParser.hpp +5 -1
- package/cpp/parser/MarkdownASTNode.hpp +2 -0
- package/ios/EnrichedMarkdownText.mm +356 -29
- package/ios/attachments/{ImageAttachment.h → EnrichedMarkdownImageAttachment.h} +1 -1
- package/ios/attachments/{ImageAttachment.m → EnrichedMarkdownImageAttachment.m} +4 -4
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/ios/parser/MarkdownASTNode.h +2 -0
- package/ios/parser/MarkdownParser.h +9 -0
- package/ios/parser/MarkdownParser.mm +31 -2
- package/ios/parser/MarkdownParserBridge.mm +13 -3
- package/ios/renderer/AttributedRenderer.h +2 -0
- package/ios/renderer/AttributedRenderer.m +52 -19
- package/ios/renderer/BlockquoteRenderer.m +7 -6
- package/ios/renderer/CodeBlockRenderer.m +9 -8
- package/ios/renderer/HeadingRenderer.m +31 -24
- package/ios/renderer/ImageRenderer.m +31 -10
- package/ios/renderer/ListItemRenderer.m +51 -39
- package/ios/renderer/ListRenderer.m +21 -18
- package/ios/renderer/ParagraphRenderer.m +27 -16
- package/ios/renderer/RenderContext.h +17 -0
- package/ios/renderer/RenderContext.m +66 -2
- package/ios/renderer/RendererFactory.m +6 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +46 -0
- package/ios/styles/StyleConfig.mm +351 -12
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/CodeBlockBackground.m +4 -9
- package/ios/utils/FontUtils.h +5 -0
- package/ios/utils/FontUtils.m +14 -0
- package/ios/utils/HTMLGenerator.m +21 -7
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.m +18 -5
- package/ios/utils/ParagraphStyleUtils.h +10 -2
- package/ios/utils/ParagraphStyleUtils.m +57 -2
- package/ios/utils/PasteboardUtils.h +1 -1
- package/ios/utils/PasteboardUtils.m +3 -3
- package/lib/module/EnrichedMarkdownText.js +33 -2
- package/lib/module/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +58 -14
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +85 -3
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +75 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownText.tsx +133 -5
- package/src/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/src/index.tsx +5 -2
- package/src/normalizeMarkdownStyle.ts +46 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +0 -9
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +0 -25
package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt
ADDED
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h
CHANGED
|
@@ -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,
|
|
285
|
-
|
|
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;
|