Skip to content

Commit 33bd036

Browse files
authored
feat: add selectionMenuConfig prop to control built-in selection menu items (#285)
1 parent 7d8f347 commit 33bd036

23 files changed

Lines changed: 222 additions & 19 deletions

android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.swmansion.enriched.markdown.utils.common.StreamingMarkdownFilter
2222
import com.swmansion.enriched.markdown.utils.common.TableStreamingMode
2323
import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
2424
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
25+
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
2526
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
2627
import com.swmansion.enriched.markdown.views.BlockSegmentView
2728
import com.swmansion.enriched.markdown.views.TableContainerView
@@ -78,6 +79,7 @@ class EnrichedMarkdown
7879
private var selectable: Boolean = true
7980
private var selectionColor: Int? = null
8081
private var selectionHandleColor: Int? = null
82+
private var selectionMenuConfig = SelectionMenuConfig()
8183

8284
private var onLinkPressCallback: ((String) -> Unit)? = null
8385
private var onLinkLongPressCallback: ((String) -> Unit)? = null
@@ -207,6 +209,14 @@ class EnrichedMarkdown
207209
}
208210
}
209211

212+
fun setSelectionMenuConfig(config: SelectionMenuConfig) {
213+
if (selectionMenuConfig == config) return
214+
selectionMenuConfig = config
215+
segmentViews.filterIsInstance<EnrichedMarkdownInternalText>().forEach {
216+
it.selectionMenuConfig = config
217+
}
218+
}
219+
210220
private fun forwardContextMenuItemPress(
211221
itemText: String,
212222
selectedText: String,
@@ -405,6 +415,7 @@ class EnrichedMarkdown
405415
private fun createTextView(segment: RenderedSegment.Text) =
406416
EnrichedMarkdownInternalText(context).apply {
407417
spoilerOverlay = this@EnrichedMarkdown.spoilerOverlay
418+
selectionMenuConfig = this@EnrichedMarkdown.selectionMenuConfig
408419
setIsSelectable(selectable)
409420
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && segment.needsJustify) {
410421
justificationMode = android.text.Layout.JUSTIFICATION_MODE_INTER_WORD

android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
1212
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlayDrawer
1313
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
1414
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
15+
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
1516
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
1617
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
1718
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
@@ -45,13 +46,15 @@ class EnrichedMarkdownInternalText
4546
var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES
4647
private var contextMenuItemTexts: List<String> = emptyList()
4748
private var onContextMenuItemPress: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null
49+
var selectionMenuConfig: SelectionMenuConfig = SelectionMenuConfig()
4850

4951
init {
5052
setupAsMarkdownTextView()
5153
customSelectionActionModeCallback =
5254
createSelectionActionModeCallback(
5355
this,
5456
getCustomItemTexts = { contextMenuItemTexts },
57+
getSelectionMenuConfig = { selectionMenuConfig },
5558
onCustomItemPress = { itemText, selectedText, start, end ->
5659
onContextMenuItemPress?.invoke(itemText, selectedText, start, end)
5760
},

android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.swmansion.enriched.markdown.utils.common.emitTaskListItemPress
2020
import com.swmansion.enriched.markdown.utils.common.markdownEventTypeConstants
2121
import com.swmansion.enriched.markdown.utils.common.parseContextMenuItems
2222
import com.swmansion.enriched.markdown.utils.common.parseMd4cFlags
23+
import com.swmansion.enriched.markdown.utils.common.parseSelectionMenuConfig
2324
import com.swmansion.enriched.markdown.utils.text.interaction.TaskListToggleUtils
2425

2526
@ReactModule(name = EnrichedMarkdownManager.NAME)
@@ -186,6 +187,15 @@ class EnrichedMarkdownManager :
186187
view.setContextMenuItems(parseContextMenuItems(value))
187188
}
188189

190+
@ReactProp(name = "selectionMenuConfig")
191+
override fun setSelectionMenuConfig(
192+
view: EnrichedMarkdown?,
193+
value: ReadableMap?,
194+
) {
195+
if (view == null) return
196+
view.setSelectionMenuConfig(parseSelectionMenuConfig(value))
197+
}
198+
189199
override fun setPadding(
190200
view: EnrichedMarkdown,
191201
left: Int,

android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.swmansion.enriched.markdown.styles.StyleConfig
2222
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
2323
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
2424
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
25+
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
2526
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
2627
import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors
2728
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap
@@ -84,13 +85,15 @@ class EnrichedMarkdownText
8485

8586
private var selectionColor: Int? = null
8687
private var selectionHandleColor: Int? = null
88+
private var selectionMenuConfig = SelectionMenuConfig()
8789

8890
init {
8991
setupAsMarkdownTextView()
9092
customSelectionActionModeCallback =
9193
createSelectionActionModeCallback(
9294
this,
9395
getCustomItemTexts = { contextMenuItemTexts },
96+
getSelectionMenuConfig = { selectionMenuConfig },
9497
onCustomItemPress = { itemText, selectedText, start, end ->
9598
onContextMenuItemPressCallback?.invoke(itemText, selectedText, start, end)
9699
},
@@ -260,6 +263,11 @@ class EnrichedMarkdownText
260263
contextMenuItemTexts = items
261264
}
262265

266+
fun setSelectionMenuConfig(config: SelectionMenuConfig) {
267+
if (selectionMenuConfig == config) return
268+
selectionMenuConfig = config
269+
}
270+
263271
fun setIsSelectable(selectable: Boolean) {
264272
applySelectableState(selectable)
265273
}

android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.swmansion.enriched.markdown.utils.common.emitTaskListItemPress
1919
import com.swmansion.enriched.markdown.utils.common.markdownEventTypeConstants
2020
import com.swmansion.enriched.markdown.utils.common.parseContextMenuItems
2121
import com.swmansion.enriched.markdown.utils.common.parseMd4cFlags
22+
import com.swmansion.enriched.markdown.utils.common.parseSelectionMenuConfig
2223
import com.swmansion.enriched.markdown.utils.text.interaction.TaskListTapUtils
2324
import com.swmansion.enriched.markdown.utils.text.interaction.TaskListToggleUtils
2425

@@ -187,6 +188,15 @@ class EnrichedMarkdownTextManager :
187188
view.setContextMenuItems(parseContextMenuItems(value))
188189
}
189190

191+
@ReactProp(name = "selectionMenuConfig")
192+
override fun setSelectionMenuConfig(
193+
view: EnrichedMarkdownText?,
194+
value: ReadableMap?,
195+
) {
196+
if (view == null) return
197+
view.setSelectionMenuConfig(parseSelectionMenuConfig(value))
198+
}
199+
190200
override fun setPadding(
191201
view: EnrichedMarkdownText,
192202
left: Int,

android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.swmansion.enriched.markdown.events.LinkLongPressEvent
99
import com.swmansion.enriched.markdown.events.LinkPressEvent
1010
import com.swmansion.enriched.markdown.events.TaskListItemPressEvent
1111
import com.swmansion.enriched.markdown.parser.Md4cFlags
12+
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
1213

1314
fun markdownEventTypeConstants(): MutableMap<String, Any> {
1415
val map = mutableMapOf<String, Any>()
@@ -86,3 +87,11 @@ fun parseMd4cFlags(flags: ReadableMap?): Md4cFlags =
8687

8788
fun parseContextMenuItems(value: ReadableArray?): List<String> =
8889
(0 until (value?.size() ?: 0)).mapNotNull { value?.getMap(it)?.getString("text") }
90+
91+
fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
92+
if (value == null) return SelectionMenuConfig()
93+
return SelectionMenuConfig(
94+
copyAsMarkdown = value.getBoolean("copyAsMarkdown"),
95+
copyImageUrl = value.getBoolean("copyImageUrl"),
96+
)
97+
}

android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ private const val MENU_ITEM_COPY_IMAGE_URL = 1001
2222
private const val MENU_ITEM_CUSTOM_BASE = 2000
2323
private const val MENU_ITEM_CUSTOM_GROUP = 2001
2424

25+
data class SelectionMenuConfig(
26+
val copyAsMarkdown: Boolean = true,
27+
val copyImageUrl: Boolean = true,
28+
)
29+
2530
/**
2631
* Creates an ActionMode.Callback that adds custom copy options and
2732
* overrides the default "Copy" action to include HTML for rich text support.
2833
*/
2934
fun createSelectionActionModeCallback(
3035
textView: TextView,
3136
getCustomItemTexts: () -> List<String> = { emptyList() },
32-
onCustomItemPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit = { _, _, _, _ -> },
37+
getSelectionMenuConfig: () -> SelectionMenuConfig = { SelectionMenuConfig() },
38+
onCustomItemPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit =
39+
{ _, _, _, _ -> },
3340
): ActionMode.Callback =
3441
object : ActionMode.Callback {
3542
override fun onCreateActionMode(
@@ -47,9 +54,17 @@ fun createSelectionActionModeCallback(
4754
menu.removeItem(MENU_ITEM_COPY_IMAGE_URL)
4855
menu.removeGroup(MENU_ITEM_CUSTOM_GROUP)
4956

50-
if (textView.selectionStart >= 0 && textView.selectionEnd > textView.selectionStart) {
57+
val selectionMenuConfig = getSelectionMenuConfig()
58+
59+
if (
60+
selectionMenuConfig.copyAsMarkdown &&
61+
textView.selectionStart >= 0 &&
62+
textView.selectionEnd > textView.selectionStart
63+
) {
5164
menu.add(Menu.NONE, MENU_ITEM_COPY_MARKDOWN, Menu.NONE, "Copy as Markdown")
65+
}
5266

67+
if (textView.selectionStart >= 0 && textView.selectionEnd > textView.selectionStart) {
5368
val customItems = getCustomItemTexts()
5469
customItems.forEachIndexed { index, text ->
5570
menu
@@ -58,7 +73,12 @@ fun createSelectionActionModeCallback(
5873
}
5974
}
6075

61-
val imageUrls = textView.getImageUrlsInSelection()
76+
val imageUrls =
77+
if (selectionMenuConfig.copyImageUrl) {
78+
textView.getImageUrlsInSelection()
79+
} else {
80+
emptyList()
81+
}
6282
if (imageUrls.isNotEmpty()) {
6383
val title =
6484
if (imageUrls.size == 1) {

apps/example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,7 +2117,7 @@ EXTERNAL SOURCES:
21172117

21182118
SPEC CHECKSUMS:
21192119
FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da
2120-
hermes-engine: b0f9c82a51be8e938eb979ada628323e1e093f1d
2120+
hermes-engine: 40811a005e96e04818cff405ec04a5b4c4411c1c
21212121
iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba
21222122
RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7
21232123
RCTRequired: bb77b070f75f53398ce43c0aaaa58337cebe2bf6
@@ -2127,7 +2127,7 @@ SPEC CHECKSUMS:
21272127
React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b
21282128
React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b
21292129
React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f
2130-
React-Core-prebuilt: 3dc04e91547fc0f260f4b84c12da0f672b813862
2130+
React-Core-prebuilt: 7965d06a81dcc544164f8e98b26d35ae2a4eb36e
21312131
React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42
21322132
React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe
21332133
React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc
@@ -2189,7 +2189,7 @@ SPEC CHECKSUMS:
21892189
ReactAppDependencyProvider: e96e93b493d8d86eeaee3e590ba0be53f6abe46f
21902190
ReactCodegen: 797de5178718324c6eba3327b07f9a423fbd5787
21912191
ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e
2192-
ReactNativeDependencies: 44f7326a697de7f6c8629036b1a4689f0e64c684
2192+
ReactNativeDependencies: 0811b43c669e637a9f3c485fdb106f187fa88398
21932193
ReactNativeEnrichedMarkdown: e6fea750b1ca4cbd853c1c78de9bd1ccb638ab77
21942194
Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2
21952195

apps/example/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export default function App() {
104104
contextMenuItems={contextMenuItems}
105105
selectionColor={Platform.OS === 'ios' ? '#5A52FA' : '#DCDDFE'}
106106
selectionHandleColor="#5A52FA"
107+
selectionMenuConfig={{
108+
copyAsMarkdown: true,
109+
copyImageUrl: true,
110+
}}
107111
/>
108112
</ScrollView>
109113
)}

apps/macos-example/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ export default function App() {
8484
markdownStyle={markdownStyle}
8585
contextMenuItems={contextMenuItems}
8686
selectionColor="#DCDDFE"
87+
selectionMenuConfig={{
88+
copyAsMarkdown: true,
89+
copyImageUrl: true,
90+
}}
8791
/>
8892
</ScrollView>
8993
{lastLink != null && (

0 commit comments

Comments
 (0)