react-native-enriched-markdown-scaffold 1.0.2-scaffold.3 → 1.0.2-scaffold.5
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/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +145 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +22 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
- package/docs/API_REFERENCE.md +111 -0
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/ios/EnrichedMarkdown.mm +221 -1
- package/ios/EnrichedMarkdownText.mm +142 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +5 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +16 -0
- package/ios/styles/StyleConfig.mm +60 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +194 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +96 -0
- package/ios/utils/ENRMSelectionChange.m +204 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.m +4 -1
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +37 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +3 -0
- package/ios/views/ENRMCodeBlockContainerView.m +3 -0
- package/ios/views/ENRMMathContainerView.h +3 -0
- package/ios/views/ENRMMathContainerView.m +3 -0
- package/ios/views/TableContainerView.h +6 -0
- package/ios/views/TableContainerView.m +24 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +101 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +13 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +138 -9
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +13 -2
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +13 -2
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +76 -2
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +36 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +101 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/src/index.tsx +5 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +21 -1
- package/src/native/EnrichedMarkdownText.tsx +181 -8
- package/src/normalizeMarkdownStyle.ts +13 -0
- package/src/normalizeMarkdownStyle.web.ts +11 -0
- package/src/types/MarkdownStyle.ts +29 -5
- package/src/types/MarkdownStyleInternal.ts +10 -0
- package/src/types/MarkdownTextProps.ts +85 -1
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +38 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
|
@@ -25,11 +25,19 @@ import com.swmansion.enriched.markdown.utils.common.RenderedSegment
|
|
|
25
25
|
import com.swmansion.enriched.markdown.utils.common.SegmentReconciler
|
|
26
26
|
import com.swmansion.enriched.markdown.utils.common.StreamingMarkdownFilter
|
|
27
27
|
import com.swmansion.enriched.markdown.utils.common.TableStreamingMode
|
|
28
|
+
import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
|
|
29
|
+
import com.swmansion.enriched.markdown.utils.common.emitSelectionChange
|
|
28
30
|
import com.swmansion.enriched.markdown.utils.common.isReducedMotionEnabled
|
|
29
31
|
import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
|
|
30
32
|
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
|
|
33
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
|
|
34
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
35
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionChangeReporter
|
|
31
36
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
37
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionRect
|
|
38
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
32
39
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
|
|
40
|
+
import com.swmansion.enriched.markdown.utils.text.view.currentTextSelection
|
|
33
41
|
import com.swmansion.enriched.markdown.views.BlockMenuOptions
|
|
34
42
|
import com.swmansion.enriched.markdown.views.BlockSegmentView
|
|
35
43
|
import com.swmansion.enriched.markdown.views.CodeBlockContainerView
|
|
@@ -99,6 +107,23 @@ class EnrichedMarkdown
|
|
|
99
107
|
private var selectionMenuConfig = SelectionMenuConfig()
|
|
100
108
|
private var accessibilityLabels = AccessibilityLabels()
|
|
101
109
|
private var textBreakStrategy: String = BreakStrategyUtils.DEFAULT_STRATEGY
|
|
110
|
+
private var linkLayoutEnabled = false
|
|
111
|
+
private var lastLinkLayout: List<LinkLayoutFragment>? = null
|
|
112
|
+
|
|
113
|
+
// At most one segment holds a selection; a new one clears the previous.
|
|
114
|
+
private var selectionOwner: EnrichedMarkdownInternalText? = null
|
|
115
|
+
|
|
116
|
+
// A block long-pressed while contextMenuHidden is on, reported as the selection.
|
|
117
|
+
private class ActiveBlock(
|
|
118
|
+
val view: View,
|
|
119
|
+
val source: String,
|
|
120
|
+
val markdown: String,
|
|
121
|
+
val text: String,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
private var activeBlock: ActiveBlock? = null
|
|
125
|
+
private val selectionReporter =
|
|
126
|
+
SelectionChangeReporter(this, ::selectionSnapshot) { snapshot -> emitSelectionChange(this, snapshot) }
|
|
102
127
|
|
|
103
128
|
private var onLinkPressCallback: ((String) -> Unit)? = null
|
|
104
129
|
private var onLinkLongPressCallback: ((String) -> Unit)? = null
|
|
@@ -393,6 +418,78 @@ class EnrichedMarkdown
|
|
|
393
418
|
onContextMenuItemPressCallback?.invoke(itemText, selectedText, selectionStart, selectionEnd)
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
fun setEmitSelectionChange(enabled: Boolean) {
|
|
422
|
+
selectionReporter.enabled = enabled
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Clears the selection of every segment (and any active block) and reports `active: false`. */
|
|
426
|
+
fun clearSelection() {
|
|
427
|
+
val owner = selectionOwner
|
|
428
|
+
selectionOwner = null
|
|
429
|
+
activeBlock = null
|
|
430
|
+
owner?.clearSelection()
|
|
431
|
+
selectionReporter.flushNow()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private fun onSegmentSelectionChanged(
|
|
435
|
+
view: EnrichedMarkdownInternalText,
|
|
436
|
+
hasSelection: Boolean,
|
|
437
|
+
) {
|
|
438
|
+
if (hasSelection) {
|
|
439
|
+
// Take ownership first so the previous owner's own "cleared" callback is ignored.
|
|
440
|
+
val previous = selectionOwner
|
|
441
|
+
selectionOwner = view
|
|
442
|
+
activeBlock = null
|
|
443
|
+
if (previous !== view) previous?.clearSelection()
|
|
444
|
+
selectionReporter.schedule()
|
|
445
|
+
} else if (selectionOwner === view) {
|
|
446
|
+
selectionReporter.onSelectionChanged(false)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private fun onBlockMenuSuppressed(
|
|
451
|
+
view: View,
|
|
452
|
+
source: String,
|
|
453
|
+
markdown: String,
|
|
454
|
+
text: String,
|
|
455
|
+
) {
|
|
456
|
+
if (!selectionReporter.enabled) return
|
|
457
|
+
val previous = selectionOwner
|
|
458
|
+
selectionOwner = null
|
|
459
|
+
previous?.clearSelection()
|
|
460
|
+
activeBlock = ActiveBlock(view, source, markdown, text)
|
|
461
|
+
selectionReporter.forgetLastReported()
|
|
462
|
+
selectionReporter.flushNow()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private fun selectionSnapshot(): SelectionSnapshot {
|
|
466
|
+
activeBlock?.let { block ->
|
|
467
|
+
if (block.view.parent !== this) return SelectionSnapshot.INACTIVE
|
|
468
|
+
val rect =
|
|
469
|
+
SelectionRect.fromPx(
|
|
470
|
+
block.view.left.toFloat(),
|
|
471
|
+
block.view.top.toFloat(),
|
|
472
|
+
block.view.right.toFloat(),
|
|
473
|
+
block.view.bottom.toFloat(),
|
|
474
|
+
)
|
|
475
|
+
return SelectionSnapshot.block(block.source, block.markdown, block.text, rect)
|
|
476
|
+
}
|
|
477
|
+
val owner = selectionOwner?.takeIf { it.parent === this } ?: return SelectionSnapshot.INACTIVE
|
|
478
|
+
val selection = owner.currentTextSelection(owner.left, owner.top) ?: return SelectionSnapshot.INACTIVE
|
|
479
|
+
// Same rule as forwardSegmentCopyRequest.
|
|
480
|
+
val whole = selection.fullSelection && segmentViews.size == 1 && currentMarkdown.isNotEmpty()
|
|
481
|
+
return SelectionSnapshot(
|
|
482
|
+
active = true,
|
|
483
|
+
source = "selection",
|
|
484
|
+
text = selection.text,
|
|
485
|
+
markdown = if (whole) currentMarkdown else selection.markdown,
|
|
486
|
+
whole = whole,
|
|
487
|
+
rect = selection.rect,
|
|
488
|
+
startRect = selection.startRect,
|
|
489
|
+
endRect = selection.endRect,
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
|
|
396
493
|
private fun recreateStyleConfig() {
|
|
397
494
|
markdownStyleMap?.let {
|
|
398
495
|
markdownStyle =
|
|
@@ -493,6 +590,9 @@ class EnrichedMarkdown
|
|
|
493
590
|
segmentSignatures.clear()
|
|
494
591
|
segmentSignatures.addAll(result.signatures)
|
|
495
592
|
|
|
593
|
+
if (selectionOwner?.let { owner -> segmentViews.none { it === owner } } == true) selectionOwner = null
|
|
594
|
+
if (activeBlock?.let { block -> segmentViews.none { it === block.view } } == true) activeBlock = null
|
|
595
|
+
|
|
496
596
|
// A just-closed block has unchanged content, so the reconciler reuses it
|
|
497
597
|
// without an update; sync pending here to trigger its deferred highlight.
|
|
498
598
|
segmentViews.forEachIndexed { index, view ->
|
|
@@ -632,6 +732,7 @@ class EnrichedMarkdown
|
|
|
632
732
|
this@EnrichedMarkdown.onTaskListItemPressCallback?.invoke(taskIndex, checked, itemText)
|
|
633
733
|
}
|
|
634
734
|
onCopyRequest = ::forwardSegmentCopyRequest
|
|
735
|
+
onSelectionChangedCallback = ::onSegmentSelectionChanged
|
|
635
736
|
|
|
636
737
|
if (contextMenuItemTexts.isNotEmpty()) {
|
|
637
738
|
setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress)
|
|
@@ -653,6 +754,7 @@ class EnrichedMarkdown
|
|
|
653
754
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
654
755
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
655
756
|
menuOptions = blockMenuOptions("table")
|
|
757
|
+
onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "table", markdown, text) }
|
|
656
758
|
applyTableNode(segment.node)
|
|
657
759
|
}
|
|
658
760
|
|
|
@@ -664,6 +766,7 @@ class EnrichedMarkdown
|
|
|
664
766
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
665
767
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
666
768
|
menuOptions = blockMenuOptions("code")
|
|
769
|
+
onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "code", markdown, text) }
|
|
667
770
|
onCopyPress = { code, language ->
|
|
668
771
|
this@EnrichedMarkdown.onCopyPressCallback?.invoke(code, language)
|
|
669
772
|
}
|
|
@@ -697,6 +800,12 @@ class EnrichedMarkdown
|
|
|
697
800
|
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
698
801
|
.invoke(view, blockMenuOptions("math"))
|
|
699
802
|
setMathContextMenuHidden(view)
|
|
803
|
+
val onMenuSuppressed: (String, String) -> Unit = { markdown, text ->
|
|
804
|
+
onBlockMenuSuppressed(view, "math", markdown, text)
|
|
805
|
+
}
|
|
806
|
+
runCatching {
|
|
807
|
+
resolvedClass.getMethod("setOnMenuSuppressed", Function2::class.java).invoke(view, onMenuSuppressed)
|
|
808
|
+
}.onFailure { Log.e(TAG, "Failed to set onMenuSuppressed on math view", it) }
|
|
700
809
|
resolvedClass.getMethod("applyLatex", String::class.java).invoke(view, segment.latex)
|
|
701
810
|
view
|
|
702
811
|
} catch (e: Exception) {
|
|
@@ -782,6 +891,41 @@ class EnrichedMarkdown
|
|
|
782
891
|
currentY += segment?.segmentMarginBottom ?: 0
|
|
783
892
|
}
|
|
784
893
|
}
|
|
894
|
+
|
|
895
|
+
emitLinkLayoutIfNeeded()
|
|
896
|
+
// Segments may have moved or been replaced; re-read the selection (deduplicated).
|
|
897
|
+
if (selectionOwner != null || activeBlock != null) selectionReporter.schedule() else selectionReporter.flushNow()
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
fun setEmitLinkLayout(enabled: Boolean) {
|
|
901
|
+
if (linkLayoutEnabled == enabled) return
|
|
902
|
+
linkLayoutEnabled = enabled
|
|
903
|
+
lastLinkLayout = null
|
|
904
|
+
emitLinkLayoutIfNeeded()
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Reports link rects of every text segment (offset by the segment's position) to JS for
|
|
909
|
+
* `renderLinkDecoration`. Tables are skipped: their cells scroll horizontally, so rects would
|
|
910
|
+
* go stale. Runs after each segment layout pass; deduplicated so an unchanged layout sends nothing.
|
|
911
|
+
*/
|
|
912
|
+
private fun emitLinkLayoutIfNeeded() {
|
|
913
|
+
if (!linkLayoutEnabled || width <= 0) return
|
|
914
|
+
val fragments = mutableListOf<LinkLayoutFragment>()
|
|
915
|
+
var linkCount = 0
|
|
916
|
+
segmentViews.forEach { view ->
|
|
917
|
+
if (view is TableContainerView) {
|
|
918
|
+
linkCount += view.linkCount
|
|
919
|
+
return@forEach
|
|
920
|
+
}
|
|
921
|
+
if (view !is EnrichedMarkdownInternalText) return@forEach
|
|
922
|
+
val (segmentFragments, segmentLinks) = LinkLayoutCollector.collect(view, view.left, view.top, linkCount)
|
|
923
|
+
fragments += segmentFragments
|
|
924
|
+
linkCount += segmentLinks
|
|
925
|
+
}
|
|
926
|
+
if (fragments == lastLinkLayout) return
|
|
927
|
+
lastLinkLayout = fragments
|
|
928
|
+
emitLinkLayout(this, fragments)
|
|
785
929
|
}
|
|
786
930
|
|
|
787
931
|
private fun computeSegmentsTotalHeight(): Int {
|
|
@@ -799,6 +943,7 @@ class EnrichedMarkdown
|
|
|
799
943
|
}
|
|
800
944
|
|
|
801
945
|
fun cleanup() {
|
|
946
|
+
selectionReporter.cancel()
|
|
802
947
|
executor.shutdownNow()
|
|
803
948
|
}
|
|
804
949
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt
CHANGED
|
@@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
|
18
18
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
|
|
19
19
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
|
|
20
20
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
|
|
21
|
+
import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
|
|
21
22
|
import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
|
|
22
23
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
23
24
|
import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
|
|
@@ -57,6 +58,10 @@ class EnrichedMarkdownInternalText
|
|
|
57
58
|
private var onContextMenuItemPress: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
58
59
|
var selectionMenuConfig: SelectionMenuConfig = SelectionMenuConfig()
|
|
59
60
|
var onCopyRequest: ((markdown: String, text: String, fullSelection: Boolean) -> Unit)? = null
|
|
61
|
+
|
|
62
|
+
// Read from onSelectionChanged, which TextView already calls from its own
|
|
63
|
+
// constructor — so it must stay nullable.
|
|
64
|
+
var onSelectionChangedCallback: ((view: EnrichedMarkdownInternalText, hasSelection: Boolean) -> Unit)? = null
|
|
60
65
|
private var activeActionMode: ActionMode? = null
|
|
61
66
|
var contextMenuHidden: Boolean = false
|
|
62
67
|
set(value) {
|
|
@@ -115,6 +120,20 @@ class EnrichedMarkdownInternalText
|
|
|
115
120
|
applySelectableState(selectable)
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
override fun onSelectionChanged(
|
|
124
|
+
selStart: Int,
|
|
125
|
+
selEnd: Int,
|
|
126
|
+
) {
|
|
127
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
128
|
+
onSelectionChangedCallback?.invoke(this, selEnd > selStart)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Clears the selection and ends its ActionMode. */
|
|
132
|
+
fun clearSelection() {
|
|
133
|
+
clearTextSelection()
|
|
134
|
+
activeActionMode?.finish()
|
|
135
|
+
}
|
|
136
|
+
|
|
118
137
|
fun setContextMenuItems(
|
|
119
138
|
items: List<String>,
|
|
120
139
|
onPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit,
|
|
@@ -177,6 +177,28 @@ class EnrichedMarkdownManager :
|
|
|
177
177
|
view?.setContextMenuHidden(contextMenuHidden)
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
@ReactProp(name = "emitLinkLayout", defaultBoolean = false)
|
|
181
|
+
override fun setEmitLinkLayout(
|
|
182
|
+
view: EnrichedMarkdown?,
|
|
183
|
+
emitLinkLayout: Boolean,
|
|
184
|
+
) {
|
|
185
|
+
view?.setEmitLinkLayout(emitLinkLayout)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@ReactProp(name = "emitSelectionChange", defaultBoolean = false)
|
|
189
|
+
override fun setEmitSelectionChange(
|
|
190
|
+
view: EnrichedMarkdown?,
|
|
191
|
+
emitSelectionChange: Boolean,
|
|
192
|
+
) {
|
|
193
|
+
view?.setEmitSelectionChange(emitSelectionChange)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Commands
|
|
197
|
+
|
|
198
|
+
override fun clearSelection(view: EnrichedMarkdown?) {
|
|
199
|
+
view?.clearSelection()
|
|
200
|
+
}
|
|
201
|
+
|
|
180
202
|
@ReactProp(name = "enableLinkPreview", defaultBoolean = true)
|
|
181
203
|
override fun setEnableLinkPreview(
|
|
182
204
|
view: EnrichedMarkdown?,
|
|
@@ -24,15 +24,23 @@ import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
|
|
|
24
24
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlayDrawer
|
|
25
25
|
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
26
26
|
import com.swmansion.enriched.markdown.utils.common.BreakStrategyUtils
|
|
27
|
+
import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
|
|
28
|
+
import com.swmansion.enriched.markdown.utils.common.emitSelectionChange
|
|
27
29
|
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
|
|
28
30
|
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
|
|
31
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
|
|
32
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
29
33
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
|
|
34
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionChangeReporter
|
|
30
35
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
36
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
31
37
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
|
|
32
38
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
|
|
33
39
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
|
|
34
40
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
|
|
41
|
+
import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
|
|
35
42
|
import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
|
|
43
|
+
import com.swmansion.enriched.markdown.utils.text.view.currentTextSelection
|
|
36
44
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent
|
|
37
45
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent
|
|
38
46
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
@@ -102,8 +110,17 @@ class EnrichedMarkdownText
|
|
|
102
110
|
private var contextMenuHidden = false
|
|
103
111
|
private var activeActionMode: ActionMode? = null
|
|
104
112
|
|
|
113
|
+
private var linkLayoutEnabled = false
|
|
114
|
+
private var lastLinkLayout: List<LinkLayoutFragment>? = null
|
|
115
|
+
|
|
116
|
+
// Nullable and assigned in init: TextView calls onSelectionChanged from its
|
|
117
|
+
// own constructor, before this class's fields exist.
|
|
118
|
+
private var selectionReporter: SelectionChangeReporter? = null
|
|
119
|
+
|
|
105
120
|
init {
|
|
106
121
|
setupAsMarkdownTextView()
|
|
122
|
+
selectionReporter =
|
|
123
|
+
SelectionChangeReporter(this, ::selectionSnapshot) { snapshot -> emitSelectionChange(this, snapshot) }
|
|
107
124
|
customSelectionActionModeCallback =
|
|
108
125
|
createSelectionActionModeCallback(
|
|
109
126
|
this,
|
|
@@ -310,6 +327,73 @@ class EnrichedMarkdownText
|
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
applySelectionColors(selectionColor, selectionHandleColor)
|
|
330
|
+
emitLinkLayoutIfNeeded()
|
|
331
|
+
// New text drops the selection; report it if setText didn't already.
|
|
332
|
+
selectionReporter?.schedule()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fun setEmitSelectionChange(enabled: Boolean) {
|
|
336
|
+
selectionReporter?.enabled = enabled
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Clears the selection (ending its ActionMode) and reports `active: false`. */
|
|
340
|
+
fun clearSelection() {
|
|
341
|
+
clearTextSelection()
|
|
342
|
+
activeActionMode?.finish()
|
|
343
|
+
selectionReporter?.flushNow()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
override fun onSelectionChanged(
|
|
347
|
+
selStart: Int,
|
|
348
|
+
selEnd: Int,
|
|
349
|
+
) {
|
|
350
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
351
|
+
selectionReporter?.onSelectionChanged(selEnd > selStart)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private fun selectionSnapshot(): SelectionSnapshot {
|
|
355
|
+
val selection = currentTextSelection() ?: return SelectionSnapshot.INACTIVE
|
|
356
|
+
return SelectionSnapshot(
|
|
357
|
+
active = true,
|
|
358
|
+
source = "selection",
|
|
359
|
+
text = selection.text,
|
|
360
|
+
markdown = selection.markdown,
|
|
361
|
+
// Same rule as onCopyRequest: a full selection already carries the original markdown.
|
|
362
|
+
whole = selection.fullSelection && currentMarkdown.isNotEmpty(),
|
|
363
|
+
rect = selection.rect,
|
|
364
|
+
startRect = selection.startRect,
|
|
365
|
+
endRect = selection.endRect,
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
fun setEmitLinkLayout(enabled: Boolean) {
|
|
370
|
+
if (linkLayoutEnabled == enabled) return
|
|
371
|
+
linkLayoutEnabled = enabled
|
|
372
|
+
lastLinkLayout = null
|
|
373
|
+
emitLinkLayoutIfNeeded()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Reports link rects to JS (for `renderLinkDecoration`) after every render and layout;
|
|
378
|
+
* deduplicated so an unchanged layout — the common case while streaming — sends nothing.
|
|
379
|
+
*/
|
|
380
|
+
private fun emitLinkLayoutIfNeeded() {
|
|
381
|
+
if (!linkLayoutEnabled || layout == null || width <= 0) return
|
|
382
|
+
val (fragments, _) = LinkLayoutCollector.collect(this)
|
|
383
|
+
if (fragments == lastLinkLayout) return
|
|
384
|
+
lastLinkLayout = fragments
|
|
385
|
+
emitLinkLayout(this, fragments)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
override fun onLayout(
|
|
389
|
+
changed: Boolean,
|
|
390
|
+
left: Int,
|
|
391
|
+
top: Int,
|
|
392
|
+
right: Int,
|
|
393
|
+
bottom: Int,
|
|
394
|
+
) {
|
|
395
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
396
|
+
emitLinkLayoutIfNeeded()
|
|
313
397
|
}
|
|
314
398
|
|
|
315
399
|
// Trailing bottom margin included in the shadow-node measurement — must be
|
|
@@ -387,6 +471,7 @@ class EnrichedMarkdownText
|
|
|
387
471
|
}
|
|
388
472
|
|
|
389
473
|
override fun onDetachedFromWindow() {
|
|
474
|
+
selectionReporter?.cancel()
|
|
390
475
|
stopSpoilerAnimations()
|
|
391
476
|
super.onDetachedFromWindow()
|
|
392
477
|
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt
CHANGED
|
@@ -177,6 +177,28 @@ class EnrichedMarkdownTextManager :
|
|
|
177
177
|
view?.setContextMenuHidden(contextMenuHidden)
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
@ReactProp(name = "emitLinkLayout", defaultBoolean = false)
|
|
181
|
+
override fun setEmitLinkLayout(
|
|
182
|
+
view: EnrichedMarkdownText?,
|
|
183
|
+
emitLinkLayout: Boolean,
|
|
184
|
+
) {
|
|
185
|
+
view?.setEmitLinkLayout(emitLinkLayout)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@ReactProp(name = "emitSelectionChange", defaultBoolean = false)
|
|
189
|
+
override fun setEmitSelectionChange(
|
|
190
|
+
view: EnrichedMarkdownText?,
|
|
191
|
+
emitSelectionChange: Boolean,
|
|
192
|
+
) {
|
|
193
|
+
view?.setEmitSelectionChange(emitSelectionChange)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Commands
|
|
197
|
+
|
|
198
|
+
override fun clearSelection(view: EnrichedMarkdownText?) {
|
|
199
|
+
view?.clearSelection()
|
|
200
|
+
}
|
|
201
|
+
|
|
180
202
|
@ReactProp(name = "enableLinkPreview", defaultBoolean = true)
|
|
181
203
|
override fun setEnableLinkPreview(
|
|
182
204
|
view: EnrichedMarkdownText?,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
7
|
+
|
|
8
|
+
class LinkLayoutEvent(
|
|
9
|
+
surfaceId: Int,
|
|
10
|
+
viewId: Int,
|
|
11
|
+
private val fragments: List<LinkLayoutFragment>,
|
|
12
|
+
) : Event<LinkLayoutEvent>(surfaceId, viewId) {
|
|
13
|
+
override fun getEventName(): String = EVENT_NAME
|
|
14
|
+
|
|
15
|
+
override fun getEventData(): WritableMap {
|
|
16
|
+
val array = Arguments.createArray()
|
|
17
|
+
fragments.forEach { fragment ->
|
|
18
|
+
array.pushMap(
|
|
19
|
+
Arguments.createMap().apply {
|
|
20
|
+
putInt("linkIndex", fragment.linkIndex)
|
|
21
|
+
putString("url", fragment.url)
|
|
22
|
+
putString("text", fragment.text)
|
|
23
|
+
putDouble("x", fragment.x.toDouble())
|
|
24
|
+
putDouble("y", fragment.y.toDouble())
|
|
25
|
+
putDouble("width", fragment.width.toDouble())
|
|
26
|
+
putDouble("height", fragment.height.toDouble())
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
return Arguments.createMap().apply { putArray("fragments", array) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
companion object {
|
|
34
|
+
const val EVENT_NAME: String = "onLinkLayout"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionRect
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
8
|
+
|
|
9
|
+
class SelectionChangeEvent(
|
|
10
|
+
surfaceId: Int,
|
|
11
|
+
viewId: Int,
|
|
12
|
+
private val snapshot: SelectionSnapshot,
|
|
13
|
+
) : Event<SelectionChangeEvent>(surfaceId, viewId) {
|
|
14
|
+
override fun getEventName(): String = EVENT_NAME
|
|
15
|
+
|
|
16
|
+
// Every state is distinct (active/inactive flips must all arrive), so never merge.
|
|
17
|
+
override fun canCoalesce(): Boolean = false
|
|
18
|
+
|
|
19
|
+
override fun getEventData(): WritableMap =
|
|
20
|
+
Arguments.createMap().apply {
|
|
21
|
+
putBoolean("active", snapshot.active)
|
|
22
|
+
putString("source", snapshot.source)
|
|
23
|
+
putString("text", snapshot.text)
|
|
24
|
+
putString("markdown", snapshot.markdown)
|
|
25
|
+
putBoolean("whole", snapshot.whole)
|
|
26
|
+
putMap("rect", snapshot.rect.toWritableMap())
|
|
27
|
+
putMap("startRect", snapshot.startRect.toWritableMap())
|
|
28
|
+
putMap("endRect", snapshot.endRect.toWritableMap())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private fun SelectionRect.toWritableMap(): WritableMap =
|
|
32
|
+
Arguments.createMap().apply {
|
|
33
|
+
putDouble("x", x.toDouble())
|
|
34
|
+
putDouble("y", y.toDouble())
|
|
35
|
+
putDouble("width", width.toDouble())
|
|
36
|
+
putDouble("height", height.toDouble())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
companion object {
|
|
40
|
+
const val EVENT_NAME: String = "onSelectionChange"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -2,6 +2,9 @@ package com.swmansion.enriched.markdown.renderer
|
|
|
2
2
|
|
|
3
3
|
import android.text.SpannableStringBuilder
|
|
4
4
|
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.LinkChipBackgroundSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacerSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacers
|
|
5
8
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
6
9
|
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
10
|
|
|
@@ -16,14 +19,41 @@ class LinkRenderer(
|
|
|
16
19
|
factory: RendererFactory,
|
|
17
20
|
) {
|
|
18
21
|
val url = node.getAttribute("url") ?: return
|
|
22
|
+
// Chip ("box") properties never fall back to the base link style.
|
|
23
|
+
val chip = factory.styleCache.linkChipForUrl(url)
|
|
24
|
+
val padding = chip.paddingHorizontal
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
val start = builder.length
|
|
27
|
+
// The leading spacer goes in before the children so ranges they register stay valid.
|
|
28
|
+
if (padding > 0f) appendSpacer(builder, padding)
|
|
29
|
+
val contentStart = builder.length
|
|
30
|
+
|
|
31
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
32
|
+
|
|
33
|
+
if (builder.length == contentStart) {
|
|
34
|
+
if (contentStart > start) builder.delete(start, contentStart)
|
|
35
|
+
return
|
|
27
36
|
}
|
|
37
|
+
if (padding > 0f) appendSpacer(builder, padding)
|
|
38
|
+
val end = builder.length
|
|
39
|
+
|
|
40
|
+
val blockStyle = factory.blockStyleContext.requireBlockStyle()
|
|
41
|
+
builder.setSpan(
|
|
42
|
+
LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context),
|
|
43
|
+
start,
|
|
44
|
+
end,
|
|
45
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
46
|
+
)
|
|
47
|
+
// One instance per link: also identifies the link for link-layout reporting.
|
|
48
|
+
builder.setSpan(LinkChipBackgroundSpan(chip), start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private fun appendSpacer(
|
|
52
|
+
builder: SpannableStringBuilder,
|
|
53
|
+
width: Float,
|
|
54
|
+
) {
|
|
55
|
+
val position = builder.length
|
|
56
|
+
builder.append(LinkSpacers.CHAR)
|
|
57
|
+
builder.setSpan(LinkSpacerSpan(width), position, position + 1, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
|
|
28
58
|
}
|
|
29
59
|
}
|
|
@@ -4,6 +4,7 @@ import android.content.res.AssetManager
|
|
|
4
4
|
import android.graphics.Typeface
|
|
5
5
|
import com.facebook.react.common.ReactConstants
|
|
6
6
|
import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
|
|
7
|
+
import com.swmansion.enriched.markdown.styles.LinkChipStyle
|
|
7
8
|
import com.swmansion.enriched.markdown.styles.LinkVariantEntry
|
|
8
9
|
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
9
10
|
|
|
@@ -26,7 +27,7 @@ class SpanStyleCache(
|
|
|
26
27
|
val linkFontFamily: String = style.linkStyle.fontFamily
|
|
27
28
|
val linkColor: Int = style.linkStyle.color
|
|
28
29
|
val linkUnderline: Boolean = style.linkStyle.underline
|
|
29
|
-
val
|
|
30
|
+
val linkChip: LinkChipStyle = style.linkStyle.chip
|
|
30
31
|
val linkVariants: List<LinkVariantEntry> = style.linkVariants
|
|
31
32
|
|
|
32
33
|
private val compiledVariantPatterns: List<Pair<Regex, LinkVariantEntry>> =
|
|
@@ -100,6 +101,9 @@ class SpanStyleCache(
|
|
|
100
101
|
return compiledVariantPatterns.firstOrNull { (regex, _) -> regex.containsMatchIn(url) }?.second
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
/** Chip style for [url]: the matching variant's own, else the base link's (never merged). */
|
|
105
|
+
fun linkChipForUrl(url: String): LinkChipStyle = resolvedVariantForUrl(url)?.chip ?: linkChip
|
|
106
|
+
|
|
103
107
|
/**
|
|
104
108
|
* Cached typeface for font family + style (BOLD, ITALIC, BOLD_ITALIC).
|
|
105
109
|
*
|