react-native-enriched-markdown-scaffold 1.0.2-scaffold.2 → 1.0.2-scaffold.3
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/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +24 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +21 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +27 -8
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +9 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +4 -2
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +3 -1
- package/docs/API_REFERENCE.md +48 -0
- package/docs/COPY_OPTIONS.md +14 -0
- package/ios/EnrichedMarkdown.mm +43 -2
- package/ios/EnrichedMarkdownText.mm +25 -3
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +12 -0
- package/ios/utils/ENRMBlockMenu.h +12 -3
- package/ios/utils/EditMenuUtils.h +10 -1
- package/ios/utils/EditMenuUtils.m +18 -4
- package/ios/views/ENRMCodeBlockContainerView.h +4 -0
- package/ios/views/ENRMCodeBlockContainerView.m +8 -1
- package/ios/views/ENRMMathContainerView.h +4 -0
- package/ios/views/ENRMMathContainerView.m +8 -1
- package/ios/views/TableContainerView.h +4 -0
- package/ios/views/TableContainerView.m +9 -3
- package/lib/module/EnrichedMarkdownNativeComponent.ts +16 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +16 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +23 -2
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +13 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +13 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +13 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +22 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +16 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +16 -0
- package/src/index.tsx +1 -0
- package/src/native/EnrichedMarkdownText.tsx +24 -1
- package/src/types/MarkdownTextProps.ts +13 -0
- package/src/types/events.ts +23 -0
package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp
CHANGED
|
@@ -52,6 +52,18 @@ payload.setProperty(runtime, "language", event.language);
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
void EnrichedMarkdownEventEmitter::onCopyRequest(OnCopyRequest event) const {
|
|
56
|
+
dispatchEvent("copyRequest", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
57
|
+
auto payload = jsi::Object(runtime);
|
|
58
|
+
payload.setProperty(runtime, "source", event.source);
|
|
59
|
+
payload.setProperty(runtime, "markdown", event.markdown);
|
|
60
|
+
payload.setProperty(runtime, "text", event.text);
|
|
61
|
+
payload.setProperty(runtime, "whole", event.whole);
|
|
62
|
+
return payload;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
55
67
|
void EnrichedMarkdownEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const {
|
|
56
68
|
dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
57
69
|
auto payload = jsi::Object(runtime);
|
|
@@ -379,6 +391,18 @@ payload.setProperty(runtime, "language", event.language);
|
|
|
379
391
|
}
|
|
380
392
|
|
|
381
393
|
|
|
394
|
+
void EnrichedMarkdownTextEventEmitter::onCopyRequest(OnCopyRequest event) const {
|
|
395
|
+
dispatchEvent("copyRequest", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
396
|
+
auto payload = jsi::Object(runtime);
|
|
397
|
+
payload.setProperty(runtime, "source", event.source);
|
|
398
|
+
payload.setProperty(runtime, "markdown", event.markdown);
|
|
399
|
+
payload.setProperty(runtime, "text", event.text);
|
|
400
|
+
payload.setProperty(runtime, "whole", event.whole);
|
|
401
|
+
return payload;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
382
406
|
void EnrichedMarkdownTextEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const {
|
|
383
407
|
dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
384
408
|
auto payload = jsi::Object(runtime);
|
package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h
CHANGED
|
@@ -36,6 +36,13 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
|
|
|
36
36
|
std::string language;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
struct OnCopyRequest {
|
|
40
|
+
std::string source;
|
|
41
|
+
std::string markdown;
|
|
42
|
+
std::string text;
|
|
43
|
+
bool whole;
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
struct OnContextMenuItemPress {
|
|
40
47
|
std::string itemText;
|
|
41
48
|
std::string selectedText;
|
|
@@ -50,6 +57,8 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
|
|
|
50
57
|
|
|
51
58
|
void onCopyPress(OnCopyPress value) const;
|
|
52
59
|
|
|
60
|
+
void onCopyRequest(OnCopyRequest value) const;
|
|
61
|
+
|
|
53
62
|
void onContextMenuItemPress(OnContextMenuItemPress value) const;
|
|
54
63
|
};
|
|
55
64
|
class EnrichedMarkdownTextInputEventEmitter : public ViewEventEmitter {
|
|
@@ -303,6 +312,13 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
|
|
|
303
312
|
std::string language;
|
|
304
313
|
};
|
|
305
314
|
|
|
315
|
+
struct OnCopyRequest {
|
|
316
|
+
std::string source;
|
|
317
|
+
std::string markdown;
|
|
318
|
+
std::string text;
|
|
319
|
+
bool whole;
|
|
320
|
+
};
|
|
321
|
+
|
|
306
322
|
struct OnContextMenuItemPress {
|
|
307
323
|
std::string itemText;
|
|
308
324
|
std::string selectedText;
|
|
@@ -317,6 +333,8 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
|
|
|
317
333
|
|
|
318
334
|
void onCopyPress(OnCopyPress value) const;
|
|
319
335
|
|
|
336
|
+
void onCopyRequest(OnCopyRequest value) const;
|
|
337
|
+
|
|
320
338
|
void onContextMenuItemPress(OnContextMenuItemPress value) const;
|
|
321
339
|
};
|
|
322
340
|
} // namespace facebook::react
|
|
@@ -2466,6 +2466,7 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2466
2466
|
bool copyFormatMarkdown{false};
|
|
2467
2467
|
bool systemItemsNone{false};
|
|
2468
2468
|
bool codeBlockCopyButton{false};
|
|
2469
|
+
bool interceptCopy{false};
|
|
2469
2470
|
|
|
2470
2471
|
|
|
2471
2472
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -2484,6 +2485,7 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2484
2485
|
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
2485
2486
|
result["systemItemsNone"] = systemItemsNone;
|
|
2486
2487
|
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
2488
|
+
result["interceptCopy"] = interceptCopy;
|
|
2487
2489
|
return result;
|
|
2488
2490
|
}
|
|
2489
2491
|
#endif
|
|
@@ -2536,6 +2538,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
2536
2538
|
if (tmp_codeBlockCopyButton != map.end()) {
|
|
2537
2539
|
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
2538
2540
|
}
|
|
2541
|
+
auto tmp_interceptCopy = map.find("interceptCopy");
|
|
2542
|
+
if (tmp_interceptCopy != map.end()) {
|
|
2543
|
+
fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
|
|
2544
|
+
}
|
|
2539
2545
|
}
|
|
2540
2546
|
|
|
2541
2547
|
static inline std::string toString(const EnrichedMarkdownSelectionMenuConfigStruct &value) {
|
|
@@ -6443,6 +6449,7 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6443
6449
|
bool copyFormatMarkdown{false};
|
|
6444
6450
|
bool systemItemsNone{false};
|
|
6445
6451
|
bool codeBlockCopyButton{false};
|
|
6452
|
+
bool interceptCopy{false};
|
|
6446
6453
|
|
|
6447
6454
|
|
|
6448
6455
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -6461,6 +6468,7 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6461
6468
|
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
6462
6469
|
result["systemItemsNone"] = systemItemsNone;
|
|
6463
6470
|
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
6471
|
+
result["interceptCopy"] = interceptCopy;
|
|
6464
6472
|
return result;
|
|
6465
6473
|
}
|
|
6466
6474
|
#endif
|
|
@@ -6513,6 +6521,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
6513
6521
|
if (tmp_codeBlockCopyButton != map.end()) {
|
|
6514
6522
|
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
6515
6523
|
}
|
|
6524
|
+
auto tmp_interceptCopy = map.find("interceptCopy");
|
|
6525
|
+
if (tmp_interceptCopy != map.end()) {
|
|
6526
|
+
fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
|
|
6527
|
+
}
|
|
6516
6528
|
}
|
|
6517
6529
|
|
|
6518
6530
|
static inline std::string toString(const EnrichedMarkdownTextSelectionMenuConfigStruct &value) {
|
|
@@ -106,6 +106,7 @@ class EnrichedMarkdown
|
|
|
106
106
|
private var onCopyPressCallback: ((String, String) -> Unit)? = null
|
|
107
107
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
108
108
|
var onContextMenuItemPressCallback: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
109
|
+
var onCopyRequestCallback: ((source: String, markdown: String, text: String, whole: Boolean) -> Unit)? = null
|
|
109
110
|
var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES
|
|
110
111
|
set(value) {
|
|
111
112
|
if (field == value) return
|
|
@@ -318,19 +319,18 @@ class EnrichedMarkdown
|
|
|
318
319
|
private fun pushCopyLabelsToBlockSegments() {
|
|
319
320
|
val copyLabel = selectionMenuConfig.copyLabel
|
|
320
321
|
val copyAsMarkdownLabel = selectionMenuConfig.copyAsMarkdownLabel
|
|
321
|
-
val menuOptions = BlockMenuOptions.from(selectionMenuConfig)
|
|
322
322
|
segmentViews.forEach { view ->
|
|
323
323
|
when {
|
|
324
324
|
view is TableContainerView -> {
|
|
325
325
|
view.copyLabel = copyLabel
|
|
326
326
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
327
|
-
view.menuOptions =
|
|
327
|
+
view.menuOptions = blockMenuOptions("table")
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
view is CodeBlockContainerView -> {
|
|
331
331
|
view.copyLabel = copyLabel
|
|
332
332
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
333
|
-
view.menuOptions =
|
|
333
|
+
view.menuOptions = blockMenuOptions("code")
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
isMathContainerView(view) -> {
|
|
@@ -341,7 +341,7 @@ class EnrichedMarkdown
|
|
|
341
341
|
.invoke(view, copyAsMarkdownLabel)
|
|
342
342
|
view.javaClass
|
|
343
343
|
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
344
|
-
.invoke(view,
|
|
344
|
+
.invoke(view, blockMenuOptions("math"))
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
}
|
|
@@ -368,6 +368,22 @@ class EnrichedMarkdown
|
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
private fun blockMenuOptions(source: String) =
|
|
372
|
+
BlockMenuOptions.from(selectionMenuConfig) { markdown, text ->
|
|
373
|
+
onCopyRequestCallback?.invoke(source, markdown, text, true)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Segments only see their own text, so a selection is the whole component
|
|
377
|
+
// only when it spans the sole segment; the markdown is then the original.
|
|
378
|
+
private fun forwardSegmentCopyRequest(
|
|
379
|
+
markdown: String,
|
|
380
|
+
text: String,
|
|
381
|
+
fullSelection: Boolean,
|
|
382
|
+
) {
|
|
383
|
+
val whole = fullSelection && segmentViews.size == 1 && currentMarkdown.isNotEmpty()
|
|
384
|
+
onCopyRequestCallback?.invoke("selection", if (whole) currentMarkdown else markdown, text, whole)
|
|
385
|
+
}
|
|
386
|
+
|
|
371
387
|
private fun forwardContextMenuItemPress(
|
|
372
388
|
itemText: String,
|
|
373
389
|
selectedText: String,
|
|
@@ -615,6 +631,7 @@ class EnrichedMarkdown
|
|
|
615
631
|
onTaskListItemPressCallback = { taskIndex, checked, itemText ->
|
|
616
632
|
this@EnrichedMarkdown.onTaskListItemPressCallback?.invoke(taskIndex, checked, itemText)
|
|
617
633
|
}
|
|
634
|
+
onCopyRequest = ::forwardSegmentCopyRequest
|
|
618
635
|
|
|
619
636
|
if (contextMenuItemTexts.isNotEmpty()) {
|
|
620
637
|
setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress)
|
|
@@ -635,7 +652,7 @@ class EnrichedMarkdown
|
|
|
635
652
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
636
653
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
637
654
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
638
|
-
menuOptions =
|
|
655
|
+
menuOptions = blockMenuOptions("table")
|
|
639
656
|
applyTableNode(segment.node)
|
|
640
657
|
}
|
|
641
658
|
|
|
@@ -646,7 +663,7 @@ class EnrichedMarkdown
|
|
|
646
663
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
647
664
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
648
665
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
649
|
-
menuOptions =
|
|
666
|
+
menuOptions = blockMenuOptions("code")
|
|
650
667
|
onCopyPress = { code, language ->
|
|
651
668
|
this@EnrichedMarkdown.onCopyPressCallback?.invoke(code, language)
|
|
652
669
|
}
|
|
@@ -678,7 +695,7 @@ class EnrichedMarkdown
|
|
|
678
695
|
.invoke(view, selectionMenuConfig.copyAsMarkdownLabel)
|
|
679
696
|
resolvedClass
|
|
680
697
|
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
681
|
-
.invoke(view,
|
|
698
|
+
.invoke(view, blockMenuOptions("math"))
|
|
682
699
|
setMathContextMenuHidden(view)
|
|
683
700
|
resolvedClass.getMethod("applyLatex", String::class.java).invoke(view, segment.latex)
|
|
684
701
|
view
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt
CHANGED
|
@@ -56,6 +56,7 @@ class EnrichedMarkdownInternalText
|
|
|
56
56
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
57
57
|
private var onContextMenuItemPress: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
58
58
|
var selectionMenuConfig: SelectionMenuConfig = SelectionMenuConfig()
|
|
59
|
+
var onCopyRequest: ((markdown: String, text: String, fullSelection: Boolean) -> Unit)? = null
|
|
59
60
|
private var activeActionMode: ActionMode? = null
|
|
60
61
|
var contextMenuHidden: Boolean = false
|
|
61
62
|
set(value) {
|
|
@@ -82,6 +83,9 @@ class EnrichedMarkdownInternalText
|
|
|
82
83
|
onCustomItemPress = { itemText, selectedText, start, end ->
|
|
83
84
|
onContextMenuItemPress?.invoke(itemText, selectedText, start, end)
|
|
84
85
|
},
|
|
86
|
+
onCopyRequest = { markdown, text, fullSelection ->
|
|
87
|
+
onCopyRequest?.invoke(markdown, text, fullSelection)
|
|
88
|
+
},
|
|
85
89
|
)
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -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
|
|
|
@@ -68,6 +68,7 @@ class EnrichedMarkdownText
|
|
|
68
68
|
|
|
69
69
|
private var contextMenuItemTexts: List<String> = emptyList()
|
|
70
70
|
var onContextMenuItemPressCallback: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
|
|
71
|
+
var onCopyRequestCallback: ((markdown: String, text: String, whole: Boolean) -> Unit)? = null
|
|
71
72
|
|
|
72
73
|
var markdownStyle: StyleConfig? = null
|
|
73
74
|
private set
|
|
@@ -113,6 +114,10 @@ class EnrichedMarkdownText
|
|
|
113
114
|
onCustomItemPress = { itemText, selectedText, start, end ->
|
|
114
115
|
onContextMenuItemPressCallback?.invoke(itemText, selectedText, start, end)
|
|
115
116
|
},
|
|
117
|
+
// A full selection already carries the original markdown.
|
|
118
|
+
onCopyRequest = { markdown, text, fullSelection ->
|
|
119
|
+
onCopyRequestCallback?.invoke(markdown, text, fullSelection && currentMarkdown.isNotEmpty())
|
|
120
|
+
},
|
|
116
121
|
)
|
|
117
122
|
}
|
|
118
123
|
|
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)
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
|
|
7
|
+
class CopyRequestEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewId: Int,
|
|
10
|
+
private val source: String,
|
|
11
|
+
private val markdown: String,
|
|
12
|
+
private val text: String,
|
|
13
|
+
private val whole: Boolean,
|
|
14
|
+
) : Event<CopyRequestEvent>(surfaceId, viewId) {
|
|
15
|
+
override fun getEventName(): String = EVENT_NAME
|
|
16
|
+
|
|
17
|
+
override fun getEventData(): WritableMap =
|
|
18
|
+
Arguments.createMap().apply {
|
|
19
|
+
putString("source", source)
|
|
20
|
+
putString("markdown", markdown)
|
|
21
|
+
putString("text", text)
|
|
22
|
+
putBoolean("whole", whole)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
companion object {
|
|
26
|
+
const val EVENT_NAME: String = "onCopyRequest"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -7,6 +7,7 @@ import com.facebook.react.uimanager.UIManagerHelper
|
|
|
7
7
|
import com.swmansion.enriched.markdown.accessibility.AccessibilityLabels
|
|
8
8
|
import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent
|
|
9
9
|
import com.swmansion.enriched.markdown.events.CopyPressEvent
|
|
10
|
+
import com.swmansion.enriched.markdown.events.CopyRequestEvent
|
|
10
11
|
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
11
12
|
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
12
13
|
import com.swmansion.enriched.markdown.events.TaskListItemPressEvent
|
|
@@ -22,6 +23,8 @@ fun markdownEventTypeConstants(): MutableMap<String, Any> {
|
|
|
22
23
|
mapOf("registrationName" to TaskListItemPressEvent.EVENT_NAME)
|
|
23
24
|
map[CopyPressEvent.EVENT_NAME] =
|
|
24
25
|
mapOf("registrationName" to CopyPressEvent.EVENT_NAME)
|
|
26
|
+
map[CopyRequestEvent.EVENT_NAME] =
|
|
27
|
+
mapOf("registrationName" to CopyRequestEvent.EVENT_NAME)
|
|
25
28
|
map[ContextMenuItemPressEvent.EVENT_NAME] =
|
|
26
29
|
mapOf("registrationName" to ContextMenuItemPressEvent.EVENT_NAME)
|
|
27
30
|
return map
|
|
@@ -74,6 +77,21 @@ fun emitCopyPress(
|
|
|
74
77
|
)
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
fun emitCopyRequest(
|
|
81
|
+
view: View,
|
|
82
|
+
source: String,
|
|
83
|
+
markdown: String,
|
|
84
|
+
text: String,
|
|
85
|
+
whole: Boolean,
|
|
86
|
+
) {
|
|
87
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
88
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
89
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
90
|
+
eventDispatcher?.dispatchEvent(
|
|
91
|
+
CopyRequestEvent(surfaceId, view.id, source, markdown, text, whole),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
fun emitContextMenuItemPress(
|
|
78
96
|
view: View,
|
|
79
97
|
itemText: String,
|
|
@@ -132,6 +150,7 @@ fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
|
|
|
132
150
|
copyFormatMarkdown = value.getBooleanOrDefault("copyFormatMarkdown", false),
|
|
133
151
|
systemItemsNone = value.getBooleanOrDefault("systemItemsNone", false),
|
|
134
152
|
codeBlockCopyButton = value.getBooleanOrDefault("codeBlockCopyButton", true),
|
|
153
|
+
interceptCopy = value.getBooleanOrDefault("interceptCopy", false),
|
|
135
154
|
)
|
|
136
155
|
}
|
|
137
156
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt
CHANGED
|
@@ -38,6 +38,8 @@ data class SelectionMenuConfig(
|
|
|
38
38
|
// Drop every system-provided item (Select all, Share, text assist, PROCESS_TEXT…).
|
|
39
39
|
val systemItemsNone: Boolean = false,
|
|
40
40
|
val codeBlockCopyButton: Boolean = true,
|
|
41
|
+
// Copy hands the selection to onCopyRequest instead of writing the clipboard.
|
|
42
|
+
val interceptCopy: Boolean = false,
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -69,6 +71,9 @@ fun createSelectionActionModeCallback(
|
|
|
69
71
|
onActionModeChanged: (mode: ActionMode?) -> Unit = {},
|
|
70
72
|
onCustomItemPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit =
|
|
71
73
|
{ _, _, _, _ -> },
|
|
74
|
+
// Receives the intercepted Copy when `interceptCopy` is set. `fullSelection`
|
|
75
|
+
// is whether the selection spans the whole text view.
|
|
76
|
+
onCopyRequest: (markdown: String, text: String, fullSelection: Boolean) -> Unit = { _, _, _ -> },
|
|
72
77
|
): ActionMode.Callback =
|
|
73
78
|
object : ActionMode.Callback {
|
|
74
79
|
override fun onCreateActionMode(
|
|
@@ -157,7 +162,10 @@ fun createSelectionActionModeCallback(
|
|
|
157
162
|
|
|
158
163
|
when (itemId) {
|
|
159
164
|
android.R.id.copy -> {
|
|
160
|
-
|
|
165
|
+
val config = getSelectionMenuConfig()
|
|
166
|
+
if (config.interceptCopy) {
|
|
167
|
+
textView.requestCopy(onCopyRequest)
|
|
168
|
+
} else if (!config.copyFormatMarkdown || !textView.copyMarkdownToClipboard()) {
|
|
161
169
|
textView.copyWithHTML()
|
|
162
170
|
}
|
|
163
171
|
mode?.finish()
|
|
@@ -229,6 +237,18 @@ private fun TextView.copyWithHTML() {
|
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
|
|
240
|
+
/** Hands the selection to [onCopyRequest] without touching the clipboard. */
|
|
241
|
+
private fun TextView.requestCopy(onCopyRequest: (markdown: String, text: String, fullSelection: Boolean) -> Unit) {
|
|
242
|
+
val start = selectionStart
|
|
243
|
+
val end = selectionEnd
|
|
244
|
+
if (start < 0 || end < 0 || start >= end) return
|
|
245
|
+
|
|
246
|
+
val selectedText = text.subSequence(start, end).toString()
|
|
247
|
+
val markdown = MarkdownExtractor.getMarkdownForSelection(this) ?: selectedText
|
|
248
|
+
// Same threshold MarkdownExtractor uses to return the original markdown.
|
|
249
|
+
onCopyRequest(markdown, selectedText, start == 0 && end >= text.length - 1)
|
|
250
|
+
}
|
|
251
|
+
|
|
232
252
|
/** Returns false when the selection has no Markdown to copy. */
|
|
233
253
|
private fun TextView.copyMarkdownToClipboard(): Boolean {
|
|
234
254
|
val markdown = MarkdownExtractor.getMarkdownForSelection(this) ?: return false
|
|
@@ -2,6 +2,9 @@ package com.swmansion.enriched.markdown.views
|
|
|
2
2
|
|
|
3
3
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
4
4
|
|
|
5
|
+
/** Receives an intercepted block Copy: the block's Markdown and its raw content. */
|
|
6
|
+
typealias BlockCopyRequest = (markdown: String, text: String) -> Unit
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Selection-menu options that table, math and code block views need for their
|
|
7
10
|
* own long-press menus. Every default keeps the legacy behavior, so a block view
|
|
@@ -14,17 +17,23 @@ data class BlockMenuOptions(
|
|
|
14
17
|
val copyAsMarkdownHidden: Boolean = false,
|
|
15
18
|
// Hides the copy button in the code block header.
|
|
16
19
|
val copyButtonHidden: Boolean = false,
|
|
20
|
+
// Set when `onCopy` is on: the Copy item hands the content here instead of
|
|
21
|
+
// writing the clipboard. The host binds it to the block's source.
|
|
22
|
+
val onCopyRequest: BlockCopyRequest? = null,
|
|
17
23
|
) {
|
|
18
24
|
val hasItems: Boolean get() = !copyHidden || !copyAsMarkdownHidden
|
|
19
25
|
|
|
20
26
|
companion object {
|
|
21
|
-
fun from(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
fun from(
|
|
28
|
+
config: SelectionMenuConfig,
|
|
29
|
+
onCopyRequest: BlockCopyRequest? = null,
|
|
30
|
+
) = BlockMenuOptions(
|
|
31
|
+
copyHidden = !config.copyEnabled,
|
|
32
|
+
copyWritesMarkdown = config.copyFormatMarkdown,
|
|
33
|
+
copyAsMarkdownHidden = !config.copyAsMarkdown,
|
|
34
|
+
copyButtonHidden = !config.codeBlockCopyButton,
|
|
35
|
+
onCopyRequest = onCopyRequest.takeIf { config.interceptCopy },
|
|
36
|
+
)
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
|
|
@@ -33,11 +42,21 @@ fun ContextMenuPopup.Builder.blockCopyItems(
|
|
|
33
42
|
options: BlockMenuOptions,
|
|
34
43
|
copyLabel: String,
|
|
35
44
|
copyAsMarkdownLabel: String,
|
|
45
|
+
// The block's Markdown and raw content, read only for an intercepted Copy.
|
|
46
|
+
markdown: () -> String,
|
|
47
|
+
rawText: () -> String,
|
|
36
48
|
copyRaw: () -> Unit,
|
|
37
49
|
copyMarkdown: () -> Unit,
|
|
38
50
|
) {
|
|
39
51
|
if (!options.copyHidden) {
|
|
40
|
-
|
|
52
|
+
val onCopyRequest = options.onCopyRequest
|
|
53
|
+
val onCopy =
|
|
54
|
+
when {
|
|
55
|
+
onCopyRequest != null -> { -> onCopyRequest(markdown(), rawText()) }
|
|
56
|
+
options.copyWritesMarkdown -> copyMarkdown
|
|
57
|
+
else -> copyRaw
|
|
58
|
+
}
|
|
59
|
+
item(ContextMenuPopup.Icon.COPY, copyLabel, onCopy)
|
|
41
60
|
}
|
|
42
61
|
if (!options.copyAsMarkdownHidden) {
|
|
43
62
|
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel, copyMarkdown)
|
package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt
CHANGED
|
@@ -261,7 +261,15 @@ class CodeBlockContainerView(
|
|
|
261
261
|
private fun showContextMenu(anchor: View): Boolean {
|
|
262
262
|
if (pending || contextMenuHidden || !menuOptions.hasItems) return false
|
|
263
263
|
ContextMenuPopup.show(anchor, this) {
|
|
264
|
-
blockCopyItems(
|
|
264
|
+
blockCopyItems(
|
|
265
|
+
menuOptions,
|
|
266
|
+
copyLabel,
|
|
267
|
+
copyAsMarkdownLabel,
|
|
268
|
+
markdown = { CodeBlockNode.fencedMarkdown(code, language, fenceChar) },
|
|
269
|
+
rawText = { code },
|
|
270
|
+
copyRaw = ::copyCode,
|
|
271
|
+
copyMarkdown = ::copyFencedMarkdown,
|
|
272
|
+
)
|
|
265
273
|
}
|
|
266
274
|
return true
|
|
267
275
|
}
|
|
@@ -367,8 +367,8 @@ class TableContainerView(
|
|
|
367
367
|
if (contextMenuHidden) return
|
|
368
368
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
369
369
|
ContextMenuPopup.show(anchor, this) {
|
|
370
|
-
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, copyRaw = {
|
|
371
|
-
val plainText =
|
|
370
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, markdown = { tableMarkdown }, rawText = ::buildPlainText, copyRaw = {
|
|
371
|
+
val plainText = buildPlainText()
|
|
372
372
|
if (plainText.isNotEmpty()) {
|
|
373
373
|
val displayMetrics = context.resources.displayMetrics
|
|
374
374
|
val tableRows =
|
|
@@ -384,6 +384,8 @@ class TableContainerView(
|
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
private fun buildPlainText(): String = rows.joinToString("\n") { row -> row.joinToString("\t") { it.plainText } }
|
|
388
|
+
|
|
387
389
|
private fun buildMarkdownFromRows(): String =
|
|
388
390
|
rows.joinToString("") { row ->
|
|
389
391
|
val line = "| ${row.joinToString(" | ") { it.markdownText }} |\n"
|
|
@@ -122,7 +122,9 @@ class MathContainerView(
|
|
|
122
122
|
if (contextMenuHidden) return
|
|
123
123
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
124
124
|
ContextMenuPopup.show(anchor, this) {
|
|
125
|
-
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel,
|
|
125
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, markdown = {
|
|
126
|
+
"$$\n$cachedLatex\n$$"
|
|
127
|
+
}, rawText = { cachedLatex }, copyRaw = {
|
|
126
128
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", cachedLatex))
|
|
127
129
|
}, copyMarkdown = {
|
|
128
130
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", "$$\n$cachedLatex\n$$"))
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -122,6 +122,54 @@ Only fires when `flavor="github"` — the copy button is part of the GitHub flav
|
|
|
122
122
|
/>
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### `onCopy`
|
|
126
|
+
|
|
127
|
+
Takes over the **Copy** item of the text selection menu and of the table, math and code block menus. When set, native does **not** write the clipboard: it calls `onCopy` with the copied content and your handler writes whatever it wants — for example with [`expo-clipboard`](https://docs.expo.dev/versions/latest/sdk/clipboard/). Use it to post-process what users copy (strip formatting, drop link targets…) without a native change.
|
|
128
|
+
|
|
129
|
+
| Type | Default Value | Platform |
|
|
130
|
+
| ----------------------------- | ------------- | -------- |
|
|
131
|
+
| `(event: CopyEvent) => void` | - | Both |
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
interface CopyEvent {
|
|
135
|
+
/** Which menu the Copy item belongs to. */
|
|
136
|
+
source: 'selection' | 'math' | 'table' | 'code';
|
|
137
|
+
/** What `copy.format: 'markdown'` would write. */
|
|
138
|
+
markdown: string;
|
|
139
|
+
/** Rendered text of the selection, or the block's raw content. */
|
|
140
|
+
text: string;
|
|
141
|
+
/** The selection spans the whole component (`markdown` is the source). Always true for blocks. */
|
|
142
|
+
whole: boolean;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| `source` | `markdown` | `text` |
|
|
147
|
+
| ----------- | -------------------------------------------------------------------------- | ----------------------------- |
|
|
148
|
+
| `selection` | Original source when `whole`, otherwise rebuilt from the rendered styles | Selected text as rendered |
|
|
149
|
+
| `math` | `$$…$$` | LaTeX |
|
|
150
|
+
| `table` | The table as Markdown | Tab-separated cells |
|
|
151
|
+
| `code` | Fenced code block | Raw code |
|
|
152
|
+
|
|
153
|
+
- `onCopy` overrides `selectionMenuConfig.copy.format`; `copy.enabled: false` still hides the item, so no event fires.
|
|
154
|
+
- **Copy as Markdown**, the code block header copy button and the VoiceOver copy action are unaffected. `onCopyPress` does not fire for an intercepted Copy.
|
|
155
|
+
- With `flavor="github"` the text is split into segments around blocks, so a selection is `whole` only when the markdown renders as a single text segment.
|
|
156
|
+
- A rebuilt `markdown` wraps each styled run separately (`**a *****b***** c**`) and does not escape literal `*` / `_`.
|
|
157
|
+
- Copying with a hardware keyboard (Cmd+C / Ctrl+C) does not go through the menu and is not intercepted.
|
|
158
|
+
- Ignored on macOS. On web the prop is not this API: it reaches the container `div` as the DOM `onCopy` clipboard event.
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import * as Clipboard from 'expo-clipboard';
|
|
164
|
+
|
|
165
|
+
<EnrichedMarkdownText
|
|
166
|
+
markdown={content}
|
|
167
|
+
onCopy={({ source, markdown }) => {
|
|
168
|
+
Clipboard.setStringAsync(source === 'selection' ? stripFormatting(markdown) : markdown);
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
125
173
|
### `enableLinkPreview`
|
|
126
174
|
|
|
127
175
|
Controls the native link preview on long press (iOS only). Automatically set to `false` when `onLinkLongPress` is provided.
|
package/docs/COPY_OPTIONS.md
CHANGED
|
@@ -70,6 +70,20 @@ code and fire `onCopyPress`; `codeBlockCopyButton: { enabled: false }` hides the
|
|
|
70
70
|
button, and `copy: { enabled: false }` also removes the VoiceOver action.
|
|
71
71
|
`copy.enabled`, `copy.format` and `systemItems` are ignored on macOS.
|
|
72
72
|
|
|
73
|
+
### Handling Copy in JS
|
|
74
|
+
|
|
75
|
+
To decide in JS what lands on the clipboard, pass `onCopy`. The Copy item
|
|
76
|
+
(text selection and block menus) then writes nothing itself and hands you the
|
|
77
|
+
content — `markdown`, `text`, `source` and `whole` — to write with your
|
|
78
|
+
clipboard library. See [`onCopy`](API_REFERENCE.md#oncopy).
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<EnrichedMarkdownText
|
|
82
|
+
markdown={content}
|
|
83
|
+
onCopy={({ markdown }) => Clipboard.setStringAsync(markdown)}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
73
87
|
To turn the menu off entirely rather than item by item, use `contextMenuHidden`:
|
|
74
88
|
|
|
75
89
|
```tsx
|