react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.2
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/Props.h +48 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +14 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +30 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +4 -4
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +4 -4
- package/docs/API_REFERENCE.md +12 -4
- package/docs/COPY_OPTIONS.md +26 -0
- package/ios/EnrichedMarkdown.mm +22 -8
- package/ios/EnrichedMarkdownText.mm +4 -5
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +48 -0
- package/ios/utils/ENRMBlockMenu.h +57 -0
- package/ios/utils/EditMenuUtils.h +22 -5
- package/ios/utils/EditMenuUtils.m +36 -13
- package/ios/views/ENRMCodeBlockContainerView.h +9 -2
- package/ios/views/ENRMCodeBlockContainerView.m +12 -19
- package/ios/views/ENRMMathContainerView.h +4 -0
- package/ios/views/ENRMMathContainerView.m +4 -19
- package/ios/views/TableContainerView.h +4 -0
- package/ios/views/TableContainerView.m +6 -20
- package/lib/module/EnrichedMarkdownNativeComponent.ts +8 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +8 -0
- package/lib/module/native/EnrichedMarkdownText.js +7 -1
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +4 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +4 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +40 -9
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +8 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +8 -0
- package/src/native/EnrichedMarkdownText.tsx +13 -0
- package/src/types/MarkdownTextProps.ts +37 -10
|
@@ -2462,6 +2462,10 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2462
2462
|
std::string copyImageUrlLabel{};
|
|
2463
2463
|
std::string copyImageUrlsLabel{};
|
|
2464
2464
|
std::vector<std::string> copyImageUrlPluralTemplates{};
|
|
2465
|
+
bool copyEnabled{false};
|
|
2466
|
+
bool copyFormatMarkdown{false};
|
|
2467
|
+
bool systemItemsNone{false};
|
|
2468
|
+
bool codeBlockCopyButton{false};
|
|
2465
2469
|
|
|
2466
2470
|
|
|
2467
2471
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -2476,6 +2480,10 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2476
2480
|
result["copyImageUrlLabel"] = copyImageUrlLabel;
|
|
2477
2481
|
result["copyImageUrlsLabel"] = copyImageUrlsLabel;
|
|
2478
2482
|
result["copyImageUrlPluralTemplates"] = ::facebook::react::toDynamic(copyImageUrlPluralTemplates);
|
|
2483
|
+
result["copyEnabled"] = copyEnabled;
|
|
2484
|
+
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
2485
|
+
result["systemItemsNone"] = systemItemsNone;
|
|
2486
|
+
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
2479
2487
|
return result;
|
|
2480
2488
|
}
|
|
2481
2489
|
#endif
|
|
@@ -2512,6 +2520,22 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
2512
2520
|
if (tmp_copyImageUrlPluralTemplates != map.end()) {
|
|
2513
2521
|
fromRawValue(context, tmp_copyImageUrlPluralTemplates->second, result.copyImageUrlPluralTemplates);
|
|
2514
2522
|
}
|
|
2523
|
+
auto tmp_copyEnabled = map.find("copyEnabled");
|
|
2524
|
+
if (tmp_copyEnabled != map.end()) {
|
|
2525
|
+
fromRawValue(context, tmp_copyEnabled->second, result.copyEnabled);
|
|
2526
|
+
}
|
|
2527
|
+
auto tmp_copyFormatMarkdown = map.find("copyFormatMarkdown");
|
|
2528
|
+
if (tmp_copyFormatMarkdown != map.end()) {
|
|
2529
|
+
fromRawValue(context, tmp_copyFormatMarkdown->second, result.copyFormatMarkdown);
|
|
2530
|
+
}
|
|
2531
|
+
auto tmp_systemItemsNone = map.find("systemItemsNone");
|
|
2532
|
+
if (tmp_systemItemsNone != map.end()) {
|
|
2533
|
+
fromRawValue(context, tmp_systemItemsNone->second, result.systemItemsNone);
|
|
2534
|
+
}
|
|
2535
|
+
auto tmp_codeBlockCopyButton = map.find("codeBlockCopyButton");
|
|
2536
|
+
if (tmp_codeBlockCopyButton != map.end()) {
|
|
2537
|
+
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
2538
|
+
}
|
|
2515
2539
|
}
|
|
2516
2540
|
|
|
2517
2541
|
static inline std::string toString(const EnrichedMarkdownSelectionMenuConfigStruct &value) {
|
|
@@ -6415,6 +6439,10 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6415
6439
|
std::string copyImageUrlLabel{};
|
|
6416
6440
|
std::string copyImageUrlsLabel{};
|
|
6417
6441
|
std::vector<std::string> copyImageUrlPluralTemplates{};
|
|
6442
|
+
bool copyEnabled{false};
|
|
6443
|
+
bool copyFormatMarkdown{false};
|
|
6444
|
+
bool systemItemsNone{false};
|
|
6445
|
+
bool codeBlockCopyButton{false};
|
|
6418
6446
|
|
|
6419
6447
|
|
|
6420
6448
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -6429,6 +6457,10 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6429
6457
|
result["copyImageUrlLabel"] = copyImageUrlLabel;
|
|
6430
6458
|
result["copyImageUrlsLabel"] = copyImageUrlsLabel;
|
|
6431
6459
|
result["copyImageUrlPluralTemplates"] = ::facebook::react::toDynamic(copyImageUrlPluralTemplates);
|
|
6460
|
+
result["copyEnabled"] = copyEnabled;
|
|
6461
|
+
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
6462
|
+
result["systemItemsNone"] = systemItemsNone;
|
|
6463
|
+
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
6432
6464
|
return result;
|
|
6433
6465
|
}
|
|
6434
6466
|
#endif
|
|
@@ -6465,6 +6497,22 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
6465
6497
|
if (tmp_copyImageUrlPluralTemplates != map.end()) {
|
|
6466
6498
|
fromRawValue(context, tmp_copyImageUrlPluralTemplates->second, result.copyImageUrlPluralTemplates);
|
|
6467
6499
|
}
|
|
6500
|
+
auto tmp_copyEnabled = map.find("copyEnabled");
|
|
6501
|
+
if (tmp_copyEnabled != map.end()) {
|
|
6502
|
+
fromRawValue(context, tmp_copyEnabled->second, result.copyEnabled);
|
|
6503
|
+
}
|
|
6504
|
+
auto tmp_copyFormatMarkdown = map.find("copyFormatMarkdown");
|
|
6505
|
+
if (tmp_copyFormatMarkdown != map.end()) {
|
|
6506
|
+
fromRawValue(context, tmp_copyFormatMarkdown->second, result.copyFormatMarkdown);
|
|
6507
|
+
}
|
|
6508
|
+
auto tmp_systemItemsNone = map.find("systemItemsNone");
|
|
6509
|
+
if (tmp_systemItemsNone != map.end()) {
|
|
6510
|
+
fromRawValue(context, tmp_systemItemsNone->second, result.systemItemsNone);
|
|
6511
|
+
}
|
|
6512
|
+
auto tmp_codeBlockCopyButton = map.find("codeBlockCopyButton");
|
|
6513
|
+
if (tmp_codeBlockCopyButton != map.end()) {
|
|
6514
|
+
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
6515
|
+
}
|
|
6468
6516
|
}
|
|
6469
6517
|
|
|
6470
6518
|
static inline std::string toString(const EnrichedMarkdownTextSelectionMenuConfigStruct &value) {
|
|
@@ -30,6 +30,7 @@ import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
|
|
|
30
30
|
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
|
|
31
31
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
32
32
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
|
|
33
|
+
import com.swmansion.enriched.markdown.views.BlockMenuOptions
|
|
33
34
|
import com.swmansion.enriched.markdown.views.BlockSegmentView
|
|
34
35
|
import com.swmansion.enriched.markdown.views.CodeBlockContainerView
|
|
35
36
|
import com.swmansion.enriched.markdown.views.TableContainerView
|
|
@@ -309,24 +310,27 @@ class EnrichedMarkdown
|
|
|
309
310
|
segmentViews.filterIsInstance<EnrichedMarkdownInternalText>().forEach {
|
|
310
311
|
it.selectionMenuConfig = config
|
|
311
312
|
}
|
|
312
|
-
//
|
|
313
|
-
// (e.g. a language change without a remount) to avoid stale
|
|
313
|
+
// Block views cache the copy labels and menu options, so re-push them on
|
|
314
|
+
// update (e.g. a language change without a remount) to avoid stale values.
|
|
314
315
|
pushCopyLabelsToBlockSegments()
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
private fun pushCopyLabelsToBlockSegments() {
|
|
318
319
|
val copyLabel = selectionMenuConfig.copyLabel
|
|
319
320
|
val copyAsMarkdownLabel = selectionMenuConfig.copyAsMarkdownLabel
|
|
321
|
+
val menuOptions = BlockMenuOptions.from(selectionMenuConfig)
|
|
320
322
|
segmentViews.forEach { view ->
|
|
321
323
|
when {
|
|
322
324
|
view is TableContainerView -> {
|
|
323
325
|
view.copyLabel = copyLabel
|
|
324
326
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
327
|
+
view.menuOptions = menuOptions
|
|
325
328
|
}
|
|
326
329
|
|
|
327
330
|
view is CodeBlockContainerView -> {
|
|
328
331
|
view.copyLabel = copyLabel
|
|
329
332
|
view.copyAsMarkdownLabel = copyAsMarkdownLabel
|
|
333
|
+
view.menuOptions = menuOptions
|
|
330
334
|
}
|
|
331
335
|
|
|
332
336
|
isMathContainerView(view) -> {
|
|
@@ -335,6 +339,9 @@ class EnrichedMarkdown
|
|
|
335
339
|
view.javaClass
|
|
336
340
|
.getMethod("setCopyAsMarkdownLabel", String::class.java)
|
|
337
341
|
.invoke(view, copyAsMarkdownLabel)
|
|
342
|
+
view.javaClass
|
|
343
|
+
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
344
|
+
.invoke(view, menuOptions)
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
347
|
}
|
|
@@ -628,6 +635,7 @@ class EnrichedMarkdown
|
|
|
628
635
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
629
636
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
630
637
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
638
|
+
menuOptions = BlockMenuOptions.from(this@EnrichedMarkdown.selectionMenuConfig)
|
|
631
639
|
applyTableNode(segment.node)
|
|
632
640
|
}
|
|
633
641
|
|
|
@@ -638,6 +646,7 @@ class EnrichedMarkdown
|
|
|
638
646
|
copyLabel = this@EnrichedMarkdown.selectionMenuConfig.copyLabel
|
|
639
647
|
copyAsMarkdownLabel = this@EnrichedMarkdown.selectionMenuConfig.copyAsMarkdownLabel
|
|
640
648
|
contextMenuHidden = this@EnrichedMarkdown.contextMenuHidden
|
|
649
|
+
menuOptions = BlockMenuOptions.from(this@EnrichedMarkdown.selectionMenuConfig)
|
|
641
650
|
onCopyPress = { code, language ->
|
|
642
651
|
this@EnrichedMarkdown.onCopyPressCallback?.invoke(code, language)
|
|
643
652
|
}
|
|
@@ -667,6 +676,9 @@ class EnrichedMarkdown
|
|
|
667
676
|
resolvedClass
|
|
668
677
|
.getMethod("setCopyAsMarkdownLabel", String::class.java)
|
|
669
678
|
.invoke(view, selectionMenuConfig.copyAsMarkdownLabel)
|
|
679
|
+
resolvedClass
|
|
680
|
+
.getMethod("setMenuOptions", BlockMenuOptions::class.java)
|
|
681
|
+
.invoke(view, BlockMenuOptions.from(selectionMenuConfig))
|
|
670
682
|
setMathContextMenuHidden(view)
|
|
671
683
|
resolvedClass.getMethod("applyLatex", String::class.java).invoke(view, segment.latex)
|
|
672
684
|
view
|
|
@@ -128,6 +128,10 @@ fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
|
|
|
128
128
|
copyImageUrlLabel = value.getString("copyImageUrlLabel") ?: "",
|
|
129
129
|
copyImageUrlsLabel = value.getString("copyImageUrlsLabel") ?: "",
|
|
130
130
|
copyImageUrlPluralTemplates = parseStringList(value.getArray("copyImageUrlPluralTemplates")),
|
|
131
|
+
copyEnabled = value.getBooleanOrDefault("copyEnabled", true),
|
|
132
|
+
copyFormatMarkdown = value.getBooleanOrDefault("copyFormatMarkdown", false),
|
|
133
|
+
systemItemsNone = value.getBooleanOrDefault("systemItemsNone", false),
|
|
134
|
+
codeBlockCopyButton = value.getBooleanOrDefault("codeBlockCopyButton", true),
|
|
131
135
|
)
|
|
132
136
|
}
|
|
133
137
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt
CHANGED
|
@@ -32,6 +32,12 @@ data class SelectionMenuConfig(
|
|
|
32
32
|
// Plural templates indexed by image count (0..100). Empty when no pluralLabels
|
|
33
33
|
// are set; counts > 100 use copyImageUrlsLabel (the "other" form).
|
|
34
34
|
val copyImageUrlPluralTemplates: List<String> = emptyList(),
|
|
35
|
+
val copyEnabled: Boolean = true,
|
|
36
|
+
// Copy writes Markdown (plain text) instead of plain + HTML / raw block content.
|
|
37
|
+
val copyFormatMarkdown: Boolean = false,
|
|
38
|
+
// Drop every system-provided item (Select all, Share, text assist, PROCESS_TEXT…).
|
|
39
|
+
val systemItemsNone: Boolean = false,
|
|
40
|
+
val codeBlockCopyButton: Boolean = true,
|
|
35
41
|
)
|
|
36
42
|
|
|
37
43
|
/**
|
|
@@ -92,19 +98,26 @@ fun createSelectionActionModeCallback(
|
|
|
92
98
|
menu.removeGroup(MENU_ITEM_CUSTOM_GROUP)
|
|
93
99
|
|
|
94
100
|
val selectionMenuConfig = getSelectionMenuConfig()
|
|
101
|
+
val hasSelection = textView.selectionStart >= 0 && textView.selectionEnd > textView.selectionStart
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
if (selectionMenuConfig.systemItemsNone) {
|
|
104
|
+
// Editor re-adds Select all and the text-assist items before calling
|
|
105
|
+
// us on every prepare, so clear on each pass and put back only Copy.
|
|
106
|
+
// Its click is still intercepted in onActionItemClicked.
|
|
107
|
+
menu.clear()
|
|
108
|
+
if (selectionMenuConfig.copyEnabled && hasSelection) {
|
|
109
|
+
menu.add(Menu.NONE, android.R.id.copy, Menu.NONE, selectionMenuConfig.copyLabel)
|
|
110
|
+
}
|
|
111
|
+
} else if (!selectionMenuConfig.copyEnabled) {
|
|
112
|
+
menu.removeItem(android.R.id.copy)
|
|
113
|
+
} else if (selectionMenuConfig.copyLabel.isNotEmpty()) {
|
|
114
|
+
// The system Copy item is added by the platform; relabel it so it
|
|
115
|
+
// matches the rest of the localized menu (iOS rebuilds the menu so
|
|
116
|
+
// gets this for free; Android only intercepts the click).
|
|
100
117
|
menu.findItem(android.R.id.copy)?.title = selectionMenuConfig.copyLabel
|
|
101
118
|
}
|
|
102
119
|
|
|
103
|
-
if (
|
|
104
|
-
selectionMenuConfig.copyAsMarkdown &&
|
|
105
|
-
textView.selectionStart >= 0 &&
|
|
106
|
-
textView.selectionEnd > textView.selectionStart
|
|
107
|
-
) {
|
|
120
|
+
if (selectionMenuConfig.copyAsMarkdown && hasSelection) {
|
|
108
121
|
menu.add(
|
|
109
122
|
Menu.NONE,
|
|
110
123
|
MENU_ITEM_COPY_MARKDOWN,
|
|
@@ -113,7 +126,7 @@ fun createSelectionActionModeCallback(
|
|
|
113
126
|
)
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
if (
|
|
129
|
+
if (hasSelection) {
|
|
117
130
|
val customItems = getCustomItemTexts()
|
|
118
131
|
customItems.forEachIndexed { index, text ->
|
|
119
132
|
menu
|
|
@@ -144,7 +157,9 @@ fun createSelectionActionModeCallback(
|
|
|
144
157
|
|
|
145
158
|
when (itemId) {
|
|
146
159
|
android.R.id.copy -> {
|
|
147
|
-
textView.
|
|
160
|
+
if (!getSelectionMenuConfig().copyFormatMarkdown || !textView.copyMarkdownToClipboard()) {
|
|
161
|
+
textView.copyWithHTML()
|
|
162
|
+
}
|
|
148
163
|
mode?.finish()
|
|
149
164
|
return true
|
|
150
165
|
}
|
|
@@ -214,10 +229,12 @@ private fun TextView.copyWithHTML() {
|
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
|
|
217
|
-
|
|
218
|
-
|
|
232
|
+
/** Returns false when the selection has no Markdown to copy. */
|
|
233
|
+
private fun TextView.copyMarkdownToClipboard(): Boolean {
|
|
234
|
+
val markdown = MarkdownExtractor.getMarkdownForSelection(this) ?: return false
|
|
219
235
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
220
236
|
clipboard.setPrimaryClip(ClipData.newPlainText("Markdown", markdown))
|
|
237
|
+
return true
|
|
221
238
|
}
|
|
222
239
|
|
|
223
240
|
/** Returns remote image URLs (http/https only) from the current selection. */
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.views
|
|
2
|
+
|
|
3
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Selection-menu options that table, math and code block views need for their
|
|
7
|
+
* own long-press menus. Every default keeps the legacy behavior, so a block view
|
|
8
|
+
* that has not received options yet behaves exactly as before.
|
|
9
|
+
*/
|
|
10
|
+
data class BlockMenuOptions(
|
|
11
|
+
val copyHidden: Boolean = false,
|
|
12
|
+
// The Copy item writes the block's Markdown instead of its raw content.
|
|
13
|
+
val copyWritesMarkdown: Boolean = false,
|
|
14
|
+
val copyAsMarkdownHidden: Boolean = false,
|
|
15
|
+
// Hides the copy button in the code block header.
|
|
16
|
+
val copyButtonHidden: Boolean = false,
|
|
17
|
+
) {
|
|
18
|
+
val hasItems: Boolean get() = !copyHidden || !copyAsMarkdownHidden
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
fun from(config: SelectionMenuConfig) =
|
|
22
|
+
BlockMenuOptions(
|
|
23
|
+
copyHidden = !config.copyEnabled,
|
|
24
|
+
copyWritesMarkdown = config.copyFormatMarkdown,
|
|
25
|
+
copyAsMarkdownHidden = !config.copyAsMarkdown,
|
|
26
|
+
copyButtonHidden = !config.codeBlockCopyButton,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Adds the Copy / Copy as Markdown items shared by the table, math and code block menus. */
|
|
32
|
+
fun ContextMenuPopup.Builder.blockCopyItems(
|
|
33
|
+
options: BlockMenuOptions,
|
|
34
|
+
copyLabel: String,
|
|
35
|
+
copyAsMarkdownLabel: String,
|
|
36
|
+
copyRaw: () -> Unit,
|
|
37
|
+
copyMarkdown: () -> Unit,
|
|
38
|
+
) {
|
|
39
|
+
if (!options.copyHidden) {
|
|
40
|
+
item(ContextMenuPopup.Icon.COPY, copyLabel, if (options.copyWritesMarkdown) copyMarkdown else copyRaw)
|
|
41
|
+
}
|
|
42
|
+
if (!options.copyAsMarkdownHidden) {
|
|
43
|
+
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel, copyMarkdown)
|
|
44
|
+
}
|
|
45
|
+
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt
CHANGED
|
@@ -70,6 +70,15 @@ class CodeBlockContainerView(
|
|
|
70
70
|
// control, not a context menu, and stays.
|
|
71
71
|
var contextMenuHidden: Boolean = false
|
|
72
72
|
|
|
73
|
+
// Copy / Copy as Markdown visibility, Copy format and the header copy button.
|
|
74
|
+
// The header button always copies the raw code.
|
|
75
|
+
var menuOptions: BlockMenuOptions = BlockMenuOptions()
|
|
76
|
+
set(value) {
|
|
77
|
+
field = value
|
|
78
|
+
// Hiding the button leaves the header (and its height) untouched.
|
|
79
|
+
copyButton.visibility = if (value.copyButtonHidden) View.GONE else View.VISIBLE
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
var onCopyPress: ((code: String, language: String) -> Unit)? = null
|
|
74
83
|
|
|
75
84
|
private var code: String = ""
|
|
@@ -250,10 +259,9 @@ class CodeBlockContainerView(
|
|
|
250
259
|
// the event when there is one (a pending block has no menu yet, and a hidden
|
|
251
260
|
// one never does).
|
|
252
261
|
private fun showContextMenu(anchor: View): Boolean {
|
|
253
|
-
if (pending || contextMenuHidden) return false
|
|
262
|
+
if (pending || contextMenuHidden || !menuOptions.hasItems) return false
|
|
254
263
|
ContextMenuPopup.show(anchor, this) {
|
|
255
|
-
|
|
256
|
-
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel) { copyFencedMarkdown() }
|
|
264
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, ::copyCode, ::copyFencedMarkdown)
|
|
257
265
|
}
|
|
258
266
|
return true
|
|
259
267
|
}
|
|
@@ -64,6 +64,7 @@ class TableContainerView(
|
|
|
64
64
|
var copyLabel: String = ""
|
|
65
65
|
var copyAsMarkdownLabel: String = ""
|
|
66
66
|
var contextMenuHidden: Boolean = false
|
|
67
|
+
var menuOptions: BlockMenuOptions = BlockMenuOptions()
|
|
67
68
|
|
|
68
69
|
private val scrollView =
|
|
69
70
|
HorizontalScrollView(context).apply {
|
|
@@ -366,7 +367,7 @@ class TableContainerView(
|
|
|
366
367
|
if (contextMenuHidden) return
|
|
367
368
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
368
369
|
ContextMenuPopup.show(anchor, this) {
|
|
369
|
-
|
|
370
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, copyRaw = {
|
|
370
371
|
val plainText = rows.joinToString("\n") { row -> row.joinToString("\t") { it.plainText } }
|
|
371
372
|
if (plainText.isNotEmpty()) {
|
|
372
373
|
val displayMetrics = context.resources.displayMetrics
|
|
@@ -377,10 +378,9 @@ class TableContainerView(
|
|
|
377
378
|
val html = HTMLGenerator.generateTableHTML(tableRows, styleConfig, displayMetrics.scaledDensity, displayMetrics.density)
|
|
378
379
|
clipboard.setPrimaryClip(ClipData.newHtmlText("Table", plainText, html))
|
|
379
380
|
}
|
|
380
|
-
}
|
|
381
|
-
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel) {
|
|
381
|
+
}, copyMarkdown = {
|
|
382
382
|
if (tableMarkdown.isNotEmpty()) clipboard.setPrimaryClip(ClipData.newPlainText("Table", tableMarkdown))
|
|
383
|
-
}
|
|
383
|
+
})
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
|
|
@@ -42,6 +42,7 @@ class MathContainerView(
|
|
|
42
42
|
var copyLabel: String = ""
|
|
43
43
|
var copyAsMarkdownLabel: String = ""
|
|
44
44
|
var contextMenuHidden: Boolean = false
|
|
45
|
+
var menuOptions: BlockMenuOptions = BlockMenuOptions()
|
|
45
46
|
|
|
46
47
|
override val segmentMarginTop: Int get() = mathStyle.marginTop.toInt()
|
|
47
48
|
override val segmentMarginBottom: Int get() = mathStyle.marginBottom.toInt()
|
|
@@ -121,12 +122,11 @@ class MathContainerView(
|
|
|
121
122
|
if (contextMenuHidden) return
|
|
122
123
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
123
124
|
ContextMenuPopup.show(anchor, this) {
|
|
124
|
-
|
|
125
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, copyRaw = {
|
|
125
126
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", cachedLatex))
|
|
126
|
-
}
|
|
127
|
-
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel) {
|
|
127
|
+
}, copyMarkdown = {
|
|
128
128
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", "$$\n$cachedLatex\n$$"))
|
|
129
|
-
}
|
|
129
|
+
})
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -469,7 +469,7 @@ Controls built-in actions added to the native text selection menu. Custom app-pr
|
|
|
469
469
|
| -------------------- | ---------------------------------------------- | -------- |
|
|
470
470
|
| `SelectionMenuConfig` | `{}` (see shape below for per-field defaults) | iOS, Android, macOS |
|
|
471
471
|
|
|
472
|
-
Each item takes an object: `{ enabled }` toggles visibility
|
|
472
|
+
Each item takes an object: `{ enabled }` toggles visibility and `label` overrides the English default. Items and labels apply to the main text selection menu as well as the table, math and code block copy menus. `copy.enabled`, `copy.format` and `systemItems` are ignored on macOS.
|
|
473
473
|
|
|
474
474
|
> **Deprecation:** the previous boolean shape (`copyAsMarkdown: false`) is still accepted at runtime for backward compatibility but logs a one-time warning. It will be removed in 0.8 — migrate to `{ enabled: false }`.
|
|
475
475
|
|
|
@@ -477,9 +477,13 @@ Each item takes an object: `{ enabled }` toggles visibility (the system `copy` i
|
|
|
477
477
|
|
|
478
478
|
```ts
|
|
479
479
|
interface SelectionMenuConfig {
|
|
480
|
-
/**
|
|
481
|
-
|
|
482
|
-
|
|
480
|
+
/**
|
|
481
|
+
* "Copy" item. `format: 'markdown'` copies Markdown as plain text instead of
|
|
482
|
+
* rich text (text selections) or the raw content (blocks).
|
|
483
|
+
* @default { enabled: true, label: "Copy", format: "rich" }
|
|
484
|
+
*/
|
|
485
|
+
copy?: { enabled?: boolean; label?: string; format?: 'rich' | 'markdown' };
|
|
486
|
+
/** "Copy as Markdown" action, in the text and block menus. @default { enabled: true, label: "Copy as Markdown" } */
|
|
483
487
|
copyAsMarkdown?: { enabled?: boolean; label?: string };
|
|
484
488
|
/** "Copy Image URL" action, shown when the selection contains images. */
|
|
485
489
|
copyImageUrl?: {
|
|
@@ -489,6 +493,10 @@ interface SelectionMenuConfig {
|
|
|
489
493
|
/** Forms for multiple images, chosen with Intl.PluralRules. @default { other: "Copy {count} Image URLs" } */
|
|
490
494
|
pluralLabels?: SelectionMenuPluralLabels;
|
|
491
495
|
};
|
|
496
|
+
/** `'none'` drops every system item (Select All, Look Up, Share, Translate…). @default 'default' */
|
|
497
|
+
systemItems?: 'default' | 'none';
|
|
498
|
+
/** Copy button in the code block header. @default { enabled: true } */
|
|
499
|
+
codeBlockCopyButton?: { enabled?: boolean };
|
|
492
500
|
}
|
|
493
501
|
|
|
494
502
|
interface SelectionMenuPluralLabels {
|
package/docs/COPY_OPTIONS.md
CHANGED
|
@@ -44,6 +44,32 @@ Use `selectionMenuConfig` to hide built-in selection menu actions while keeping
|
|
|
44
44
|
/>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Copy as Markdown only, no system items
|
|
48
|
+
|
|
49
|
+
`copy` can also be hidden (`{ enabled: false }`) or switched to Markdown with
|
|
50
|
+
`format: 'markdown'`, and `systemItems: 'none'` drops everything the OS adds
|
|
51
|
+
(Select All, Look Up, Share, Translate, app text actions…):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<EnrichedMarkdownText
|
|
55
|
+
markdown={content}
|
|
56
|
+
selectionMenuConfig={{
|
|
57
|
+
copy: { format: 'markdown' }, // inline math → $…$, plain text only
|
|
58
|
+
copyAsMarkdown: { enabled: false },
|
|
59
|
+
systemItems: 'none',
|
|
60
|
+
codeBlockCopyButton: { enabled: false },
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The same `copy` and `copyAsMarkdown` settings drive the long-press menus on
|
|
66
|
+
table, math and code blocks: with `format: 'markdown'` their Copy item writes
|
|
67
|
+
the block's Markdown (`$$…$$`, a fenced code block, a Markdown table). On code
|
|
68
|
+
blocks, the header copy button and the VoiceOver copy action always copy the raw
|
|
69
|
+
code and fire `onCopyPress`; `codeBlockCopyButton: { enabled: false }` hides the
|
|
70
|
+
button, and `copy: { enabled: false }` also removes the VoiceOver action.
|
|
71
|
+
`copy.enabled`, `copy.format` and `systemItems` are ignored on macOS.
|
|
72
|
+
|
|
47
73
|
To turn the menu off entirely rather than item by item, use `contextMenuHidden`:
|
|
48
74
|
|
|
49
75
|
```tsx
|
package/ios/EnrichedMarkdown.mm
CHANGED
|
@@ -173,7 +173,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
173
173
|
_tableStreamingMode = ENRMTableStreamingModeProgressive;
|
|
174
174
|
_codeBlockStreamingMode = ENRMCodeBlockStreamingModeProgressive;
|
|
175
175
|
_contextMenuHidden = NO;
|
|
176
|
-
_selectionMenuConfig = (
|
|
176
|
+
_selectionMenuConfig = ENRMDefaultSelectionMenuConfig();
|
|
177
177
|
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
178
178
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
179
179
|
_resolvedLayoutDirection =
|
|
@@ -505,9 +505,19 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
508
|
+
- (ENRMBlockMenuOptions)blockMenuOptions
|
|
509
|
+
{
|
|
510
|
+
return (ENRMBlockMenuOptions){
|
|
511
|
+
.copyHidden = !_selectionMenuConfig.copyEnabled,
|
|
512
|
+
.copyWritesMarkdown = _selectionMenuConfig.copyFormatMarkdown,
|
|
513
|
+
.copyAsMarkdownHidden = !_selectionMenuConfig.copyAsMarkdown,
|
|
514
|
+
.copyButtonHidden = !_selectionMenuConfig.codeBlockCopyButton,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Table, math and code block views cache the copy labels and menu options at
|
|
519
|
+
// creation time, so re-push them on prop updates (e.g. a language change
|
|
520
|
+
// without a remount) to avoid stale values.
|
|
511
521
|
- (void)pushSelectionMenuLabelsToSegments
|
|
512
522
|
{
|
|
513
523
|
for (RCTUIView *segment in _segmentViews) {
|
|
@@ -516,6 +526,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
516
526
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
517
527
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
518
528
|
tableView.contextMenuHidden = _contextMenuHidden;
|
|
529
|
+
tableView.menuOptions = [self blockMenuOptions];
|
|
519
530
|
}
|
|
520
531
|
#if ENRICHED_MARKDOWN_MATH
|
|
521
532
|
else if ([segment isKindOfClass:[ENRMMathContainerView class]]) {
|
|
@@ -523,6 +534,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
523
534
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
524
535
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
525
536
|
mathView.contextMenuHidden = _contextMenuHidden;
|
|
537
|
+
mathView.menuOptions = [self blockMenuOptions];
|
|
526
538
|
}
|
|
527
539
|
#endif
|
|
528
540
|
else if ([segment isKindOfClass:[ENRMCodeBlockContainerView class]]) {
|
|
@@ -530,6 +542,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
530
542
|
codeBlockView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
531
543
|
codeBlockView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
532
544
|
codeBlockView.contextMenuHidden = _contextMenuHidden;
|
|
545
|
+
codeBlockView.menuOptions = [self blockMenuOptions];
|
|
533
546
|
}
|
|
534
547
|
}
|
|
535
548
|
}
|
|
@@ -787,7 +800,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
787
800
|
selectionEnd:selectionEnd];
|
|
788
801
|
});
|
|
789
802
|
return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, segmentMarkdown, strongSelf->_config,
|
|
790
|
-
@[ baseMenu ], customItems, strongSelf->_selectionMenuConfig);
|
|
803
|
+
@[ baseMenu ], customItems, strongSelf -> _selectionMenuConfig);
|
|
791
804
|
}];
|
|
792
805
|
#endif
|
|
793
806
|
|
|
@@ -807,6 +820,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
807
820
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
808
821
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
809
822
|
tableView.contextMenuHidden = _contextMenuHidden;
|
|
823
|
+
tableView.menuOptions = [self blockMenuOptions];
|
|
810
824
|
|
|
811
825
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
812
826
|
|
|
@@ -847,6 +861,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
847
861
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
848
862
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
849
863
|
mathView.contextMenuHidden = _contextMenuHidden;
|
|
864
|
+
mathView.menuOptions = [self blockMenuOptions];
|
|
850
865
|
[mathView applyLatex:mathSegment.latex];
|
|
851
866
|
return mathView;
|
|
852
867
|
}
|
|
@@ -858,6 +873,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
858
873
|
codeBlockView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
859
874
|
codeBlockView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
860
875
|
codeBlockView.contextMenuHidden = _contextMenuHidden;
|
|
876
|
+
codeBlockView.menuOptions = [self blockMenuOptions];
|
|
861
877
|
|
|
862
878
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
863
879
|
codeBlockView.onCopyPress = ^(NSString *code, NSString *language) {
|
|
@@ -1026,9 +1042,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
1026
1042
|
}
|
|
1027
1043
|
|
|
1028
1044
|
_selectionMenuLabels = ENRMParseSelectionMenuLabels(newViewProps.selectionMenuConfig);
|
|
1029
|
-
_selectionMenuConfig =
|
|
1030
|
-
ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig.copyAsMarkdown,
|
|
1031
|
-
newViewProps.selectionMenuConfig.copyImageUrl);
|
|
1045
|
+
_selectionMenuConfig = ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig);
|
|
1032
1046
|
[self pushSelectionMenuLabelsToSegments];
|
|
1033
1047
|
|
|
1034
1048
|
if (ENRMAccessibilityLabelsChanged(oldViewProps.accessibilityLabels, newViewProps.accessibilityLabels)) {
|
|
@@ -239,7 +239,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
239
239
|
_enableTaskListItemToggle = YES;
|
|
240
240
|
_forceHeightUpdateOnNextRender = NO;
|
|
241
241
|
_contextMenuHidden = NO;
|
|
242
|
-
_selectionMenuConfig = (
|
|
242
|
+
_selectionMenuConfig = ENRMDefaultSelectionMenuConfig();
|
|
243
243
|
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
244
244
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
245
245
|
_resolvedLayoutDirection =
|
|
@@ -301,7 +301,8 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
301
301
|
selectionEnd:selectionEnd];
|
|
302
302
|
});
|
|
303
303
|
return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, strongSelf->_cachedMarkdown,
|
|
304
|
-
strongSelf->_config, @[ baseMenu ], customItems,
|
|
304
|
+
strongSelf->_config, @[ baseMenu ], customItems,
|
|
305
|
+
strongSelf -> _selectionMenuConfig);
|
|
305
306
|
};
|
|
306
307
|
#endif
|
|
307
308
|
|
|
@@ -577,9 +578,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
577
578
|
}
|
|
578
579
|
|
|
579
580
|
_selectionMenuLabels = ENRMParseSelectionMenuLabels(newViewProps.selectionMenuConfig);
|
|
580
|
-
_selectionMenuConfig =
|
|
581
|
-
ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig.copyAsMarkdown,
|
|
582
|
-
newViewProps.selectionMenuConfig.copyImageUrl);
|
|
581
|
+
_selectionMenuConfig = ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig);
|
|
583
582
|
|
|
584
583
|
if (ENRMAccessibilityLabelsChanged(oldViewProps.accessibilityLabels, newViewProps.accessibilityLabels)) {
|
|
585
584
|
_accessibilityLabels = [[ENRMAccessibilityLabels alloc] init];
|