react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.10
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/build.gradle +2 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +4 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +4 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +176 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +112 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +46 -8
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -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 +43 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +25 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +7 -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/InlineMathStyle.kt +16 -1
- 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/styles/MathStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +34 -2
- 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/HeadlessActionMode.kt +88 -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 +53 -14
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +232 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +29 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +30 -7
- package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +25 -9
- package/docs/API_REFERENCE.md +183 -4
- package/docs/COPY_OPTIONS.md +40 -0
- package/docs/INPUT.md +3 -1
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/docs/WEB.md +1 -0
- package/ios/EnrichedMarkdown.mm +321 -9
- package/ios/EnrichedMarkdownText.mm +203 -6
- package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
- package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +8 -2
- package/ios/math/ENRMRaTeXBridge.swift +23 -5
- package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +20 -0
- package/ios/styles/StyleConfig.mm +84 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMBlockMenu.h +66 -0
- 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 +213 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +120 -0
- package/ios/utils/ENRMSelectionChange.m +282 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.h +35 -6
- package/ios/utils/EditMenuUtils.m +171 -15
- 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 +47 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +16 -2
- package/ios/views/ENRMCodeBlockContainerView.m +22 -19
- package/ios/views/ENRMMathContainerView.h +11 -0
- package/ios/views/ENRMMathContainerView.m +17 -20
- package/ios/views/TableContainerView.h +14 -0
- package/ios/views/TableContainerView.m +34 -18
- package/lib/module/EnrichedMarkdownNativeComponent.ts +138 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +14 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +169 -11
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +17 -4
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +17 -4
- 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 +123 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +123 -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 +50 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +12 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +144 -11
- 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 +66 -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 +138 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +22 -1
- package/src/native/EnrichedMarkdownText.tsx +220 -9
- package/src/normalizeMarkdownStyle.ts +15 -0
- package/src/normalizeMarkdownStyle.web.ts +13 -0
- package/src/types/MarkdownStyle.ts +51 -5
- package/src/types/MarkdownStyleInternal.ts +12 -0
- package/src/types/MarkdownTextProps.ts +151 -11
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +69 -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,20 @@ 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
|
|
41
|
+
import com.swmansion.enriched.markdown.views.BlockMenuOptions
|
|
33
42
|
import com.swmansion.enriched.markdown.views.BlockSegmentView
|
|
34
43
|
import com.swmansion.enriched.markdown.views.CodeBlockContainerView
|
|
35
44
|
import com.swmansion.enriched.markdown.views.TableContainerView
|
|
@@ -98,6 +107,23 @@ class EnrichedMarkdown
|
|
|
98
107
|
private var selectionMenuConfig = SelectionMenuConfig()
|
|
99
108
|
private var accessibilityLabels = AccessibilityLabels()
|
|
100
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) }
|
|
101
127
|
|
|
102
128
|
private var onLinkPressCallback: ((String) -> Unit)? = null
|
|
103
129
|
private var onLinkLongPressCallback: ((String) -> Unit)? = null
|
|
@@ -105,6 +131,7 @@ class EnrichedMarkdown
|
|
|
105
131
|
private var onCopyPressCallback: ((String, String) -> Unit)? = null
|
|
106
132
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
107
133
|
var onContextMenuItemPressCallback: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
134
|
+
var onCopyRequestCallback: ((source: String, markdown: String, text: String, whole: Boolean) -> Unit)? = null
|
|
108
135
|
var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES
|
|
109
136
|
set(value) {
|
|
110
137
|
if (field == value) return
|
|
@@ -309,8 +336,8 @@ class EnrichedMarkdown
|
|
|
309
336
|
segmentViews.filterIsInstance<EnrichedMarkdownInternalText>().forEach {
|
|
310
337
|
it.selectionMenuConfig = config
|
|
311
338
|
}
|
|
312
|
-
//
|
|
313
|
-
// (e.g. a language change without a remount) to avoid stale
|
|
339
|
+
// Block views cache the copy labels and menu options, so re-push them on
|
|
340
|
+
// update (e.g. a language change without a remount) to avoid stale values.
|
|
314
341
|
pushCopyLabelsToBlockSegments()
|
|
315
342
|
}
|
|
316
343
|
|
|
@@ -322,11 +349,13 @@ class EnrichedMarkdown
|
|
|
322
349
|
view is TableContainerView -> {
|
|
323
350
|
view.copyLabel = copyLabel
|
|
324
351
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
352
|
+
view.menuOptions = blockMenuOptions("table")
|
|
325
353
|
}
|
|
326
354
|
|
|
327
355
|
view is CodeBlockContainerView -> {
|
|
328
356
|
view.copyLabel = copyLabel
|
|
329
357
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
358
|
+
view.menuOptions = blockMenuOptions("code")
|
|
330
359
|
}
|
|
331
360
|
|
|
332
361
|
isMathContainerView(view) -> {
|
|
@@ -335,6 +364,9 @@ class EnrichedMarkdown
|
|
|
335
364
|
view.javaClass
|
|
336
365
|
.getMethod("setCopyAsMarkdownLabel", String::class.java)
|
|
337
366
|
.invoke(view, copyAsMarkdownLabel)
|
|
367
|
+
view.javaClass
|
|
368
|
+
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
369
|
+
.invoke(view, blockMenuOptions("math"))
|
|
338
370
|
}
|
|
339
371
|
}
|
|
340
372
|
}
|
|
@@ -361,6 +393,22 @@ class EnrichedMarkdown
|
|
|
361
393
|
}
|
|
362
394
|
}
|
|
363
395
|
|
|
396
|
+
private fun blockMenuOptions(source: String) =
|
|
397
|
+
BlockMenuOptions.from(selectionMenuConfig) { markdown, text ->
|
|
398
|
+
onCopyRequestCallback?.invoke(source, markdown, text, true)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Segments only see their own text, so a selection is the whole component
|
|
402
|
+
// only when it spans the sole segment; the markdown is then the original.
|
|
403
|
+
private fun forwardSegmentCopyRequest(
|
|
404
|
+
markdown: String,
|
|
405
|
+
text: String,
|
|
406
|
+
fullSelection: Boolean,
|
|
407
|
+
) {
|
|
408
|
+
val whole = fullSelection && segmentViews.size == 1 && currentMarkdown.isNotEmpty()
|
|
409
|
+
onCopyRequestCallback?.invoke("selection", if (whole) currentMarkdown else markdown, text, whole)
|
|
410
|
+
}
|
|
411
|
+
|
|
364
412
|
private fun forwardContextMenuItemPress(
|
|
365
413
|
itemText: String,
|
|
366
414
|
selectedText: String,
|
|
@@ -370,6 +418,78 @@ class EnrichedMarkdown
|
|
|
370
418
|
onContextMenuItemPressCallback?.invoke(itemText, selectedText, selectionStart, selectionEnd)
|
|
371
419
|
}
|
|
372
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
|
+
|
|
373
493
|
private fun recreateStyleConfig() {
|
|
374
494
|
markdownStyleMap?.let {
|
|
375
495
|
markdownStyle =
|
|
@@ -470,6 +590,9 @@ class EnrichedMarkdown
|
|
|
470
590
|
segmentSignatures.clear()
|
|
471
591
|
segmentSignatures.addAll(result.signatures)
|
|
472
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
|
+
|
|
473
596
|
// A just-closed block has unchanged content, so the reconciler reuses it
|
|
474
597
|
// without an update; sync pending here to trigger its deferred highlight.
|
|
475
598
|
segmentViews.forEachIndexed { index, view ->
|
|
@@ -608,6 +731,8 @@ class EnrichedMarkdown
|
|
|
608
731
|
onTaskListItemPressCallback = { taskIndex, checked, itemText ->
|
|
609
732
|
this@EnrichedMarkdown.onTaskListItemPressCallback?.invoke(taskIndex, checked, itemText)
|
|
610
733
|
}
|
|
734
|
+
onCopyRequest = ::forwardSegmentCopyRequest
|
|
735
|
+
onSelectionChangedCallback = ::onSegmentSelectionChanged
|
|
611
736
|
|
|
612
737
|
if (contextMenuItemTexts.isNotEmpty()) {
|
|
613
738
|
setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress)
|
|
@@ -628,6 +753,8 @@ class EnrichedMarkdown
|
|
|
628
753
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
629
754
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
630
755
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
756
|
+
menuOptions = blockMenuOptions("table")
|
|
757
|
+
onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "table", markdown, text) }
|
|
631
758
|
applyTableNode(segment.node)
|
|
632
759
|
}
|
|
633
760
|
|
|
@@ -638,6 +765,8 @@ class EnrichedMarkdown
|
|
|
638
765
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
639
766
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
640
767
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
768
|
+
menuOptions = blockMenuOptions("code")
|
|
769
|
+
onMenuSuppressed = { markdown, text -> onBlockMenuSuppressed(this, "code", markdown, text) }
|
|
641
770
|
onCopyPress = { code, language ->
|
|
642
771
|
this@EnrichedMarkdown.onCopyPressCallback?.invoke(code, language)
|
|
643
772
|
}
|
|
@@ -667,7 +796,16 @@ class EnrichedMarkdown
|
|
|
667
796
|
resolvedClass
|
|
668
797
|
.getMethod("setCopyAsMarkdownLabel", String::class.java)
|
|
669
798
|
.invoke(view, selectionMenuConfig.copyAsMarkdownLabel)
|
|
799
|
+
resolvedClass
|
|
800
|
+
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
801
|
+
.invoke(view, blockMenuOptions("math"))
|
|
670
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) }
|
|
671
809
|
resolvedClass.getMethod("applyLatex", String::class.java).invoke(view, segment.latex)
|
|
672
810
|
view
|
|
673
811
|
} catch (e: Exception) {
|
|
@@ -753,6 +891,41 @@ class EnrichedMarkdown
|
|
|
753
891
|
currentY += segment?.segmentMarginBottom ?: 0
|
|
754
892
|
}
|
|
755
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)
|
|
756
929
|
}
|
|
757
930
|
|
|
758
931
|
private fun computeSegmentsTotalHeight(): Int {
|
|
@@ -770,6 +943,7 @@ class EnrichedMarkdown
|
|
|
770
943
|
}
|
|
771
944
|
|
|
772
945
|
fun cleanup() {
|
|
946
|
+
selectionReporter.cancel()
|
|
773
947
|
executor.shutdownNow()
|
|
774
948
|
}
|
|
775
949
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt
CHANGED
|
@@ -13,12 +13,15 @@ import com.swmansion.enriched.markdown.spoiler.SpoilerCapable
|
|
|
13
13
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
|
|
14
14
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlayDrawer
|
|
15
15
|
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
|
|
16
|
+
import com.swmansion.enriched.markdown.utils.text.view.HeadlessActionMode
|
|
16
17
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
|
|
17
18
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
18
19
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
|
|
19
20
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
|
|
20
21
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
|
|
22
|
+
import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
|
|
21
23
|
import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
|
|
24
|
+
import com.swmansion.enriched.markdown.utils.text.view.isRomSelectionPopupShower
|
|
22
25
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
23
26
|
import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
|
|
24
27
|
import com.swmansion.enriched.markdown.views.BlockSegmentView
|
|
@@ -56,6 +59,11 @@ class EnrichedMarkdownInternalText
|
|
|
56
59
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
57
60
|
private var onContextMenuItemPress: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
58
61
|
var selectionMenuConfig: SelectionMenuConfig = SelectionMenuConfig()
|
|
62
|
+
var onCopyRequest: ((markdown: String, text: String, fullSelection: Boolean) -> Unit)? = null
|
|
63
|
+
|
|
64
|
+
// Read from onSelectionChanged, which TextView already calls from its own
|
|
65
|
+
// constructor — so it must stay nullable.
|
|
66
|
+
var onSelectionChangedCallback: ((view: EnrichedMarkdownInternalText, hasSelection: Boolean) -> Unit)? = null
|
|
59
67
|
private var activeActionMode: ActionMode? = null
|
|
60
68
|
var contextMenuHidden: Boolean = false
|
|
61
69
|
set(value) {
|
|
@@ -82,6 +90,9 @@ class EnrichedMarkdownInternalText
|
|
|
82
90
|
onCustomItemPress = { itemText, selectedText, start, end ->
|
|
83
91
|
onContextMenuItemPress?.invoke(itemText, selectedText, start, end)
|
|
84
92
|
},
|
|
93
|
+
onCopyRequest = { markdown, text, fullSelection ->
|
|
94
|
+
onCopyRequest?.invoke(markdown, text, fullSelection)
|
|
95
|
+
},
|
|
85
96
|
)
|
|
86
97
|
}
|
|
87
98
|
|
|
@@ -111,6 +122,40 @@ class EnrichedMarkdownInternalText
|
|
|
111
122
|
applySelectableState(selectable)
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
override fun onSelectionChanged(
|
|
126
|
+
selStart: Int,
|
|
127
|
+
selEnd: Int,
|
|
128
|
+
) {
|
|
129
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
130
|
+
onSelectionChangedCallback?.invoke(this, selEnd > selStart)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// See HeadlessActionMode: with the menu hidden, no toolbar window is created.
|
|
134
|
+
override fun startActionMode(
|
|
135
|
+
callback: ActionMode.Callback?,
|
|
136
|
+
type: Int,
|
|
137
|
+
): ActionMode? {
|
|
138
|
+
if (contextMenuHidden && type == ActionMode.TYPE_FLOATING && callback != null) {
|
|
139
|
+
return HeadlessActionMode.start(this, callback)
|
|
140
|
+
}
|
|
141
|
+
return super.startActionMode(callback, type)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// See isRomSelectionPopupShower: MIUI shows its own selection popup this way.
|
|
145
|
+
override fun postDelayed(
|
|
146
|
+
action: Runnable?,
|
|
147
|
+
delayMillis: Long,
|
|
148
|
+
): Boolean {
|
|
149
|
+
if (contextMenuHidden && isRomSelectionPopupShower(action)) return false
|
|
150
|
+
return super.postDelayed(action, delayMillis)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Clears the selection and ends its ActionMode. */
|
|
154
|
+
fun clearSelection() {
|
|
155
|
+
clearTextSelection()
|
|
156
|
+
activeActionMode?.finish()
|
|
157
|
+
}
|
|
158
|
+
|
|
114
159
|
fun setContextMenuItems(
|
|
115
160
|
items: List<String>,
|
|
116
161
|
onPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit,
|
|
@@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.CodeBlockStreamingMode
|
|
|
18
18
|
import com.swmansion.enriched.markdown.utils.common.TableStreamingMode
|
|
19
19
|
import com.swmansion.enriched.markdown.utils.common.emitContextMenuItemPress
|
|
20
20
|
import com.swmansion.enriched.markdown.utils.common.emitCopyPress
|
|
21
|
+
import com.swmansion.enriched.markdown.utils.common.emitCopyRequest
|
|
21
22
|
import com.swmansion.enriched.markdown.utils.common.emitLinkLongPress
|
|
22
23
|
import com.swmansion.enriched.markdown.utils.common.emitLinkPress
|
|
23
24
|
import com.swmansion.enriched.markdown.utils.common.emitTaskListItemPress
|
|
@@ -65,6 +66,10 @@ class EnrichedMarkdownManager :
|
|
|
65
66
|
emitCopyPress(view, code, language)
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
view.onCopyRequestCallback = { source, markdown, text, whole ->
|
|
70
|
+
emitCopyRequest(view, source, markdown, text, whole)
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
return view
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -172,6 +177,28 @@ class EnrichedMarkdownManager :
|
|
|
172
177
|
view?.setContextMenuHidden(contextMenuHidden)
|
|
173
178
|
}
|
|
174
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
|
+
|
|
175
202
|
@ReactProp(name = "enableLinkPreview", defaultBoolean = true)
|
|
176
203
|
override fun setEnableLinkPreview(
|
|
177
204
|
view: EnrichedMarkdown?,
|
|
@@ -180,6 +207,14 @@ class EnrichedMarkdownManager :
|
|
|
180
207
|
// No-op on Android — only used on iOS
|
|
181
208
|
}
|
|
182
209
|
|
|
210
|
+
@ReactProp(name = "linkLongPressSelects", defaultBoolean = false)
|
|
211
|
+
override fun setLinkLongPressSelects(
|
|
212
|
+
view: EnrichedMarkdown?,
|
|
213
|
+
linkLongPressSelects: Boolean,
|
|
214
|
+
) {
|
|
215
|
+
// No-op on Android — only used on iOS
|
|
216
|
+
}
|
|
217
|
+
|
|
183
218
|
@ReactProp(name = "enableTaskListItemToggle", defaultBoolean = true)
|
|
184
219
|
override fun setEnableTaskListItemToggle(
|
|
185
220
|
view: EnrichedMarkdown?,
|
|
@@ -24,17 +24,27 @@ 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.HeadlessActionMode
|
|
32
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
|
|
33
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
29
34
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
|
|
35
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionChangeReporter
|
|
30
36
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
37
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
31
38
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
|
|
32
39
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
|
|
33
40
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
|
|
34
41
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
|
|
42
|
+
import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
|
|
35
43
|
import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
|
|
44
|
+
import com.swmansion.enriched.markdown.utils.text.view.currentTextSelection
|
|
36
45
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent
|
|
37
46
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent
|
|
47
|
+
import com.swmansion.enriched.markdown.utils.text.view.isRomSelectionPopupShower
|
|
38
48
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
39
49
|
import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
|
|
40
50
|
import java.util.concurrent.Executors
|
|
@@ -68,6 +78,7 @@ class EnrichedMarkdownText
|
|
|
68
78
|
|
|
69
79
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
70
80
|
var onContextMenuItemPressCallback: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
81
|
+
var onCopyRequestCallback: ((markdown: String, text: String, whole: Boolean) -> Unit)? = null
|
|
71
82
|
|
|
72
83
|
var markdownStyle: StyleConfig? = null
|
|
73
84
|
private set
|
|
@@ -101,8 +112,17 @@ class EnrichedMarkdownText
|
|
|
101
112
|
private var contextMenuHidden = false
|
|
102
113
|
private var activeActionMode: ActionMode? = null
|
|
103
114
|
|
|
115
|
+
private var linkLayoutEnabled = false
|
|
116
|
+
private var lastLinkLayout: List<LinkLayoutFragment>? = null
|
|
117
|
+
|
|
118
|
+
// Nullable and assigned in init: TextView calls onSelectionChanged from its
|
|
119
|
+
// own constructor, before this class's fields exist.
|
|
120
|
+
private var selectionReporter: SelectionChangeReporter? = null
|
|
121
|
+
|
|
104
122
|
init {
|
|
105
123
|
setupAsMarkdownTextView()
|
|
124
|
+
selectionReporter =
|
|
125
|
+
SelectionChangeReporter(this, ::selectionSnapshot) { snapshot -> emitSelectionChange(this, snapshot) }
|
|
106
126
|
customSelectionActionModeCallback =
|
|
107
127
|
createSelectionActionModeCallback(
|
|
108
128
|
this,
|
|
@@ -113,6 +133,10 @@ class EnrichedMarkdownText
|
|
|
113
133
|
onCustomItemPress = { itemText, selectedText, start, end ->
|
|
114
134
|
onContextMenuItemPressCallback?.invoke(itemText, selectedText, start, end)
|
|
115
135
|
},
|
|
136
|
+
// A full selection already carries the original markdown.
|
|
137
|
+
onCopyRequest = { markdown, text, fullSelection ->
|
|
138
|
+
onCopyRequestCallback?.invoke(markdown, text, fullSelection && currentMarkdown.isNotEmpty())
|
|
139
|
+
},
|
|
116
140
|
)
|
|
117
141
|
}
|
|
118
142
|
|
|
@@ -305,6 +329,93 @@ class EnrichedMarkdownText
|
|
|
305
329
|
}
|
|
306
330
|
|
|
307
331
|
applySelectionColors(selectionColor, selectionHandleColor)
|
|
332
|
+
emitLinkLayoutIfNeeded()
|
|
333
|
+
// New text drops the selection; report it if setText didn't already.
|
|
334
|
+
selectionReporter?.schedule()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
fun setEmitSelectionChange(enabled: Boolean) {
|
|
338
|
+
selectionReporter?.enabled = enabled
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// See HeadlessActionMode: with the menu hidden, no toolbar window is created.
|
|
342
|
+
override fun startActionMode(
|
|
343
|
+
callback: ActionMode.Callback?,
|
|
344
|
+
type: Int,
|
|
345
|
+
): ActionMode? {
|
|
346
|
+
if (contextMenuHidden && type == ActionMode.TYPE_FLOATING && callback != null) {
|
|
347
|
+
return HeadlessActionMode.start(this, callback)
|
|
348
|
+
}
|
|
349
|
+
return super.startActionMode(callback, type)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// See isRomSelectionPopupShower: MIUI shows its own selection popup this way.
|
|
353
|
+
override fun postDelayed(
|
|
354
|
+
action: Runnable?,
|
|
355
|
+
delayMillis: Long,
|
|
356
|
+
): Boolean {
|
|
357
|
+
if (contextMenuHidden && isRomSelectionPopupShower(action)) return false
|
|
358
|
+
return super.postDelayed(action, delayMillis)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Clears the selection (ending its ActionMode) and reports `active: false`. */
|
|
362
|
+
fun clearSelection() {
|
|
363
|
+
clearTextSelection()
|
|
364
|
+
activeActionMode?.finish()
|
|
365
|
+
selectionReporter?.flushNow()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
override fun onSelectionChanged(
|
|
369
|
+
selStart: Int,
|
|
370
|
+
selEnd: Int,
|
|
371
|
+
) {
|
|
372
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
373
|
+
selectionReporter?.onSelectionChanged(selEnd > selStart)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private fun selectionSnapshot(): SelectionSnapshot {
|
|
377
|
+
val selection = currentTextSelection() ?: return SelectionSnapshot.INACTIVE
|
|
378
|
+
return SelectionSnapshot(
|
|
379
|
+
active = true,
|
|
380
|
+
source = "selection",
|
|
381
|
+
text = selection.text,
|
|
382
|
+
markdown = selection.markdown,
|
|
383
|
+
// Same rule as onCopyRequest: a full selection already carries the original markdown.
|
|
384
|
+
whole = selection.fullSelection && currentMarkdown.isNotEmpty(),
|
|
385
|
+
rect = selection.rect,
|
|
386
|
+
startRect = selection.startRect,
|
|
387
|
+
endRect = selection.endRect,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
fun setEmitLinkLayout(enabled: Boolean) {
|
|
392
|
+
if (linkLayoutEnabled == enabled) return
|
|
393
|
+
linkLayoutEnabled = enabled
|
|
394
|
+
lastLinkLayout = null
|
|
395
|
+
emitLinkLayoutIfNeeded()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Reports link rects to JS (for `renderLinkDecoration`) after every render and layout;
|
|
400
|
+
* deduplicated so an unchanged layout — the common case while streaming — sends nothing.
|
|
401
|
+
*/
|
|
402
|
+
private fun emitLinkLayoutIfNeeded() {
|
|
403
|
+
if (!linkLayoutEnabled || layout == null || width <= 0) return
|
|
404
|
+
val (fragments, _) = LinkLayoutCollector.collect(this)
|
|
405
|
+
if (fragments == lastLinkLayout) return
|
|
406
|
+
lastLinkLayout = fragments
|
|
407
|
+
emitLinkLayout(this, fragments)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
override fun onLayout(
|
|
411
|
+
changed: Boolean,
|
|
412
|
+
left: Int,
|
|
413
|
+
top: Int,
|
|
414
|
+
right: Int,
|
|
415
|
+
bottom: Int,
|
|
416
|
+
) {
|
|
417
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
418
|
+
emitLinkLayoutIfNeeded()
|
|
308
419
|
}
|
|
309
420
|
|
|
310
421
|
// Trailing bottom margin included in the shadow-node measurement — must be
|
|
@@ -382,6 +493,7 @@ class EnrichedMarkdownText
|
|
|
382
493
|
}
|
|
383
494
|
|
|
384
495
|
override fun onDetachedFromWindow() {
|
|
496
|
+
selectionReporter?.cancel()
|
|
385
497
|
stopSpoilerAnimations()
|
|
386
498
|
super.onDetachedFromWindow()
|
|
387
499
|
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt
CHANGED
|
@@ -15,6 +15,7 @@ import com.facebook.react.viewmanagers.EnrichedMarkdownTextManagerInterface
|
|
|
15
15
|
import com.facebook.yoga.YogaMeasureMode
|
|
16
16
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
|
|
17
17
|
import com.swmansion.enriched.markdown.utils.common.emitContextMenuItemPress
|
|
18
|
+
import com.swmansion.enriched.markdown.utils.common.emitCopyRequest
|
|
18
19
|
import com.swmansion.enriched.markdown.utils.common.emitLinkLongPress
|
|
19
20
|
import com.swmansion.enriched.markdown.utils.common.emitLinkPress
|
|
20
21
|
import com.swmansion.enriched.markdown.utils.common.emitTaskListItemPress
|
|
@@ -42,6 +43,9 @@ class EnrichedMarkdownTextManager :
|
|
|
42
43
|
view.onContextMenuItemPressCallback = { itemText, selectedText, selectionStart, selectionEnd ->
|
|
43
44
|
emitContextMenuItemPress(view, itemText, selectedText, selectionStart, selectionEnd)
|
|
44
45
|
}
|
|
46
|
+
view.onCopyRequestCallback = { markdown, text, whole ->
|
|
47
|
+
emitCopyRequest(view, "selection", markdown, text, whole)
|
|
48
|
+
}
|
|
45
49
|
|
|
46
50
|
view.setOnLinkPressCallback { url ->
|
|
47
51
|
emitLinkPress(view, url)
|
|
@@ -173,6 +177,28 @@ class EnrichedMarkdownTextManager :
|
|
|
173
177
|
view?.setContextMenuHidden(contextMenuHidden)
|
|
174
178
|
}
|
|
175
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
|
+
|
|
176
202
|
@ReactProp(name = "enableLinkPreview", defaultBoolean = true)
|
|
177
203
|
override fun setEnableLinkPreview(
|
|
178
204
|
view: EnrichedMarkdownText?,
|
|
@@ -181,6 +207,14 @@ class EnrichedMarkdownTextManager :
|
|
|
181
207
|
// No-op on Android — only used on iOS
|
|
182
208
|
}
|
|
183
209
|
|
|
210
|
+
@ReactProp(name = "linkLongPressSelects", defaultBoolean = false)
|
|
211
|
+
override fun setLinkLongPressSelects(
|
|
212
|
+
view: EnrichedMarkdownText?,
|
|
213
|
+
linkLongPressSelects: Boolean,
|
|
214
|
+
) {
|
|
215
|
+
// No-op on Android — only used on iOS
|
|
216
|
+
}
|
|
217
|
+
|
|
184
218
|
@ReactProp(name = "enableTaskListItemToggle", defaultBoolean = true)
|
|
185
219
|
override fun setEnableTaskListItemToggle(
|
|
186
220
|
view: EnrichedMarkdownText?,
|