react-native-enriched-markdown-scaffold 0.6.0-scaffold.8 → 0.7.4-scaffold.1
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/ReactNativeEnrichedMarkdown.podspec +62 -11
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/android/src/main/baseline-prof.txt +75 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +9 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/AccessibilityLabels.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +44 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +55 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +22 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputRemend.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +54 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +4 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +36 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +73 -3
- package/android/src/main/jni/CMakeLists.txt +15 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +24 -2
- package/app.plugin.js +2 -1
- package/cpp/parser/MD4CParser.cpp +1 -1
- package/cpp/wasm/build.sh +9 -9
- package/docs/ACCESSIBILITY.md +172 -0
- package/docs/API_REFERENCE.md +948 -0
- package/docs/COPY_OPTIONS.md +127 -0
- package/docs/ELEMENTS_STRUCTURE.md +112 -0
- package/docs/IMAGE_CACHING.md +25 -0
- package/docs/INPUT.md +233 -0
- package/docs/LATEX_MATH.md +136 -0
- package/docs/MACOS.md +19 -0
- package/docs/MARKDOWN_STREAMING.md +36 -0
- package/docs/MENTIONS.md +97 -0
- package/docs/RTL.md +82 -0
- package/docs/STYLES.md +519 -0
- package/docs/TEXT.md +110 -0
- package/docs/WEB.md +49 -0
- package/ios/EnrichedMarkdown.mm +82 -25
- package/ios/EnrichedMarkdownText.mm +37 -5
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +15 -0
- package/ios/input/ENRMFormattingStore.h +5 -0
- package/ios/input/ENRMFormattingStore.mm +34 -0
- package/ios/input/ENRMInputTextView.h +2 -0
- package/ios/input/ENRMInputTextView.mm +8 -17
- package/ios/input/ENRMMarkdownSerializer.mm +1 -1
- package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +71 -53
- package/ios/input/EnrichedMarkdownTextInput+Internal.h +26 -0
- package/ios/input/EnrichedMarkdownTextInput.h +1 -0
- package/ios/input/EnrichedMarkdownTextInput.mm +194 -9
- package/ios/input/styles/ENRMBoldStyleHandler.mm +3 -2
- package/ios/input/styles/ENRMItalicStyleHandler.mm +3 -2
- package/ios/renderer/AttributedRenderer.m +51 -6
- package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
- package/ios/styles/ENRMFontSlot.h +1 -1
- package/ios/utils/ENRMAccessibilityLabels.h +36 -0
- package/ios/utils/ENRMAccessibilityLabels.m +5 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -5
- package/ios/utils/EditMenuUtils.h +70 -0
- package/ios/utils/EditMenuUtils.m +14 -11
- package/ios/utils/LastElementUtils.h +39 -2
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +18 -16
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +64 -32
- package/ios/utils/ParagraphStyleUtils.m +21 -2
- package/ios/views/ENRMMathContainerView.h +8 -0
- package/ios/views/ENRMMathContainerView.m +7 -5
- package/ios/views/ENRMTableIOSGridView.h +40 -0
- package/ios/views/ENRMTableIOSGridView.m +216 -0
- package/ios/views/EnrichedMarkdownInternalText.h +2 -0
- package/ios/views/EnrichedMarkdownInternalText.m +2 -0
- package/ios/views/TableContainerView.h +8 -0
- package/ios/views/TableContainerView.m +72 -128
- package/lib/module/EnrichedMarkdownNativeComponent.ts +37 -0
- package/lib/module/EnrichedMarkdownTextInput.js +48 -1
- package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/lib/module/accessibilityLabelDefaults.js +64 -0
- package/lib/module/accessibilityLabelDefaults.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +72 -4
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMenuItem.js +38 -0
- package/lib/module/normalizeMenuItem.js.map +1 -0
- package/lib/module/types/AccessibilityLabels.js +2 -0
- package/lib/module/types/AccessibilityLabels.js.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +55 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +30 -0
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts +16 -0
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -2
- 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/normalizeMenuItem.d.ts +7 -0
- package/lib/typescript/src/normalizeMenuItem.d.ts.map +1 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts +114 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts.map +1 -0
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +78 -9
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/package.json +4 -2
- package/plugin/build/withAndroidMath.js +19 -0
- package/plugin/build/withIosMath.js +28 -0
- package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
- package/plugin/tsconfig.build.json +16 -0
- package/src/EnrichedMarkdownNativeComponent.ts +37 -0
- package/src/EnrichedMarkdownTextInput.tsx +117 -1
- package/src/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/src/accessibilityLabelDefaults.ts +83 -0
- package/src/index.tsx +11 -0
- package/src/native/EnrichedMarkdownText.tsx +120 -6
- package/src/normalizeMenuItem.ts +52 -0
- package/src/types/AccessibilityLabels.ts +118 -0
- package/src/types/MarkdownTextProps.ts +74 -9
- package/lib/module/plugin/withAndroidMath.js +0 -20
- package/lib/module/plugin/withAndroidMath.js.map +0 -1
- package/lib/module/plugin/withIosMath.js +0 -23
- package/lib/module/plugin/withIosMath.js.map +0 -1
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
- package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
- /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
|
@@ -32,6 +32,8 @@ import com.swmansion.enriched.markdown.input.events.OnRequestMarkdownResultEvent
|
|
|
32
32
|
import com.swmansion.enriched.markdown.input.events.OnStartMentionEvent
|
|
33
33
|
import com.swmansion.enriched.markdown.input.layout.InputMeasurementStore
|
|
34
34
|
import com.swmansion.enriched.markdown.input.model.StyleType
|
|
35
|
+
import com.swmansion.enriched.markdown.input.toolbar.FormatMenuConfig
|
|
36
|
+
import com.swmansion.enriched.markdown.input.toolbar.InputSelectionMenuConfig
|
|
35
37
|
import com.swmansion.enriched.markdown.utils.input.BorderPropsApplicator
|
|
36
38
|
import com.swmansion.enriched.markdown.utils.input.MarkdownStyleParser
|
|
37
39
|
|
|
@@ -266,6 +268,52 @@ class EnrichedMarkdownTextInputManager :
|
|
|
266
268
|
view.setContextMenuItems(items)
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
@ReactProp(name = "selectionMenuConfig")
|
|
272
|
+
override fun setSelectionMenuConfig(
|
|
273
|
+
view: EnrichedMarkdownTextInputView?,
|
|
274
|
+
value: ReadableMap?,
|
|
275
|
+
) {
|
|
276
|
+
if (view == null) return
|
|
277
|
+
view.contextMenu.selectionMenuConfig =
|
|
278
|
+
if (value == null) {
|
|
279
|
+
InputSelectionMenuConfig()
|
|
280
|
+
} else {
|
|
281
|
+
InputSelectionMenuConfig(
|
|
282
|
+
format = value.getBoolean("format"),
|
|
283
|
+
formatLabel = value.getString("formatLabel") ?: "",
|
|
284
|
+
copyAsMarkdown = value.getBoolean("copyAsMarkdown"),
|
|
285
|
+
copyAsMarkdownLabel = value.getString("copyAsMarkdownLabel") ?: "",
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@ReactProp(name = "formatMenuConfig")
|
|
291
|
+
override fun setFormatMenuConfig(
|
|
292
|
+
view: EnrichedMarkdownTextInputView?,
|
|
293
|
+
value: ReadableMap?,
|
|
294
|
+
) {
|
|
295
|
+
if (view == null) return
|
|
296
|
+
view.contextMenu.formatMenuConfig =
|
|
297
|
+
if (value == null) {
|
|
298
|
+
FormatMenuConfig()
|
|
299
|
+
} else {
|
|
300
|
+
FormatMenuConfig(
|
|
301
|
+
bold = value.getBoolean("bold"),
|
|
302
|
+
boldLabel = value.getString("boldLabel") ?: "",
|
|
303
|
+
italic = value.getBoolean("italic"),
|
|
304
|
+
italicLabel = value.getString("italicLabel") ?: "",
|
|
305
|
+
underline = value.getBoolean("underline"),
|
|
306
|
+
underlineLabel = value.getString("underlineLabel") ?: "",
|
|
307
|
+
strikethrough = value.getBoolean("strikethrough"),
|
|
308
|
+
strikethroughLabel = value.getString("strikethroughLabel") ?: "",
|
|
309
|
+
spoiler = value.getBoolean("spoiler"),
|
|
310
|
+
spoilerLabel = value.getString("spoilerLabel") ?: "",
|
|
311
|
+
link = value.getBoolean("link"),
|
|
312
|
+
linkLabel = value.getString("linkLabel") ?: "",
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
269
317
|
@ReactProp(name = "linkRegex")
|
|
270
318
|
override fun setLinkRegex(
|
|
271
319
|
view: EnrichedMarkdownTextInputView?,
|
|
@@ -445,6 +493,10 @@ class EnrichedMarkdownTextInputManager :
|
|
|
445
493
|
view?.removeMath(start)
|
|
446
494
|
}
|
|
447
495
|
|
|
496
|
+
override fun copyToClipboard(view: EnrichedMarkdownTextInputView?) {
|
|
497
|
+
view?.copyToClipboard()
|
|
498
|
+
}
|
|
499
|
+
|
|
448
500
|
override fun requestMarkdown(
|
|
449
501
|
view: EnrichedMarkdownTextInputView?,
|
|
450
502
|
requestId: Int,
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.input
|
|
2
2
|
|
|
3
|
+
import android.content.ClipData
|
|
4
|
+
import android.content.ClipboardManager
|
|
3
5
|
import android.content.Context
|
|
4
6
|
import android.graphics.BlendMode
|
|
5
7
|
import android.graphics.BlendModeColorFilter
|
|
@@ -44,6 +46,8 @@ import com.swmansion.enriched.markdown.input.toolbar.InputContextMenu
|
|
|
44
46
|
import com.swmansion.enriched.markdown.utils.input.AutoCapitalizeUtils
|
|
45
47
|
import kotlin.math.ceil
|
|
46
48
|
|
|
49
|
+
private fun Char.isLineBreak(): Boolean = this == '\n' || this == '\r' || this == '\u0085' || this == '\u2028' || this == '\u2029'
|
|
50
|
+
|
|
47
51
|
class EnrichedMarkdownTextInputView(
|
|
48
52
|
context: Context,
|
|
49
53
|
) : AppCompatEditText(context) {
|
|
@@ -71,6 +75,7 @@ class EnrichedMarkdownTextInputView(
|
|
|
71
75
|
var autoFocusRequested = false
|
|
72
76
|
var stateWrapper: StateWrapper? = null
|
|
73
77
|
val layoutManager = InputLayoutManager(this)
|
|
78
|
+
private var pendingAutoFocusKeyboard = false
|
|
74
79
|
|
|
75
80
|
private var typefaceDirty = false
|
|
76
81
|
private var fontFamilyValue: String? = null
|
|
@@ -176,6 +181,13 @@ class EnrichedMarkdownTextInputView(
|
|
|
176
181
|
refreshAllMathChips()
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
|
185
|
+
super.onWindowFocusChanged(hasWindowFocus)
|
|
186
|
+
// The autofocus keyboard request may run before the window has IME focus (e.g. while a modal is
|
|
187
|
+
// still presenting), where showSoftInput() is dropped.
|
|
188
|
+
showAutoFocusKeyboardIfPending()
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
override fun clearFocus() {
|
|
180
192
|
super.clearFocus()
|
|
181
193
|
inputMethodManager?.hideSoftInputFromWindow(windowToken, 0)
|
|
@@ -312,6 +324,12 @@ class EnrichedMarkdownTextInputView(
|
|
|
312
324
|
if (!isComponentReady || isDuringTransaction) return
|
|
313
325
|
|
|
314
326
|
if (!isTextChanging) {
|
|
327
|
+
// Links (e.g. mentions) are atomic: snap a partial selection to the whole link, and a caret
|
|
328
|
+
// inside a link to its end. Returning lets the recursive onSelectionChanged emit for the result.
|
|
329
|
+
formattingStore.selectionAdjustedForAtomicLinks(selStart, selEnd)?.let { (newStart, newEnd) ->
|
|
330
|
+
setSelection(newStart, newEnd)
|
|
331
|
+
return
|
|
332
|
+
}
|
|
315
333
|
if (didTextChangeRecently) {
|
|
316
334
|
didTextChangeRecently = false
|
|
317
335
|
} else {
|
|
@@ -345,8 +363,18 @@ class EnrichedMarkdownTextInputView(
|
|
|
345
363
|
val rangeStart = if (preEditSelectionStart != preEditSelectionEnd) preEditSelectionStart else editStart
|
|
346
364
|
val rangeEnd = rangeStart + insertedLength
|
|
347
365
|
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
// Skip applying pending styles when the insertion is only line breaks —
|
|
367
|
+
// a phantom range over a bare newline corrupts isStyleActive() at the boundary.
|
|
368
|
+
val currentText = text
|
|
369
|
+
val insertedHasGlyphContent =
|
|
370
|
+
currentText != null &&
|
|
371
|
+
rangeEnd <= currentText.length &&
|
|
372
|
+
(rangeStart until rangeEnd).any { !currentText[it].isLineBreak() }
|
|
373
|
+
|
|
374
|
+
if (insertedHasGlyphContent) {
|
|
375
|
+
for (style in pendingStyles) {
|
|
376
|
+
formattingStore.addRange(FormattingRange(style, rangeStart, rangeEnd))
|
|
377
|
+
}
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
for (style in pendingStyleRemovals) {
|
|
@@ -439,6 +467,16 @@ class EnrichedMarkdownTextInputView(
|
|
|
439
467
|
}
|
|
440
468
|
}
|
|
441
469
|
|
|
470
|
+
// Copies the input's plain text to the system clipboard without disturbing
|
|
471
|
+
// the current selection. Android's system clipboard round-trip is plain text
|
|
472
|
+
// only — inline styles are not preserved for external paste targets.
|
|
473
|
+
fun copyToClipboard() {
|
|
474
|
+
val content = text
|
|
475
|
+
if (content.isNullOrEmpty()) return
|
|
476
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
|
|
477
|
+
clipboard.setPrimaryClip(ClipData.newPlainText(null, content))
|
|
478
|
+
}
|
|
479
|
+
|
|
442
480
|
fun setLinkForSelection(url: String) {
|
|
443
481
|
val selStart = selectionStart
|
|
444
482
|
val selEnd = selectionEnd
|
|
@@ -954,11 +992,25 @@ class EnrichedMarkdownTextInputView(
|
|
|
954
992
|
setSelection(selectionStart.coerceAtLeast(0))
|
|
955
993
|
}
|
|
956
994
|
|
|
995
|
+
private fun showAutoFocusKeyboardIfPending() {
|
|
996
|
+
if (!pendingAutoFocusKeyboard || !hasWindowFocus()) return
|
|
997
|
+
pendingAutoFocusKeyboard = false
|
|
998
|
+
inputMethodManager?.showSoftInput(this, 0)
|
|
999
|
+
}
|
|
1000
|
+
|
|
957
1001
|
fun afterUpdateTransaction() {
|
|
958
1002
|
updateTypeface()
|
|
959
1003
|
if (autoFocusRequested) {
|
|
960
1004
|
autoFocusRequested = false
|
|
961
|
-
|
|
1005
|
+
pendingAutoFocusKeyboard = true
|
|
1006
|
+
post {
|
|
1007
|
+
// afterUpdateTransaction runs before onAttachedToWindow, where requestFocus()/showSoftInput()
|
|
1008
|
+
// are dropped and setTextIsSelectable(true) would reset the caret to 0. Defer to the next loop
|
|
1009
|
+
// so focus sticks and the caret lands at end (matching iOS).
|
|
1010
|
+
requestFocus()
|
|
1011
|
+
setSelection(text?.length ?: 0)
|
|
1012
|
+
showAutoFocusKeyboardIfPending()
|
|
1013
|
+
}
|
|
962
1014
|
}
|
|
963
1015
|
}
|
|
964
1016
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt
CHANGED
|
@@ -34,6 +34,25 @@ class FormattingStore {
|
|
|
34
34
|
end: Int,
|
|
35
35
|
): Boolean = ranges.any { it.type == type && it.start < end && it.end > start }
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Snaps a selection so it never partially overlaps an atomic link: a partial selection expands to
|
|
39
|
+
* the whole link, a caret inside a link moves to its end. Returns the adjusted (start, end) or null.
|
|
40
|
+
*/
|
|
41
|
+
fun selectionAdjustedForAtomicLinks(
|
|
42
|
+
start: Int,
|
|
43
|
+
end: Int,
|
|
44
|
+
): Pair<Int, Int>? {
|
|
45
|
+
if (start != end) {
|
|
46
|
+
var newStart = start
|
|
47
|
+
var newEnd = end
|
|
48
|
+
rangeOfType(StyleType.LINK, newStart)?.let { newStart = minOf(newStart, it.start) }
|
|
49
|
+
if (newEnd > 0) rangeOfType(StyleType.LINK, newEnd - 1)?.let { newEnd = maxOf(newEnd, it.end) }
|
|
50
|
+
return if (newStart != start || newEnd != end) Pair(newStart, newEnd) else null
|
|
51
|
+
}
|
|
52
|
+
val link = rangeOfType(StyleType.LINK, start)
|
|
53
|
+
return if (link != null && start > link.start && start < link.end) Pair(link.end, link.end) else null
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
fun addRange(newRange: FormattingRange) {
|
|
38
57
|
var mergedStart = newRange.start
|
|
39
58
|
var mergedEnd = newRange.end
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt
CHANGED
|
@@ -7,6 +7,7 @@ import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
|
7
7
|
import com.swmansion.enriched.markdown.parser.MarkdownASTNode.NodeType
|
|
8
8
|
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
9
9
|
import com.swmansion.enriched.markdown.parser.Parser
|
|
10
|
+
import com.swmansion.enriched.markdown.parser.isTopLevelBlock
|
|
10
11
|
import com.swmansion.enriched.markdown.utils.common.FeatureFlags
|
|
11
12
|
|
|
12
13
|
data class ParseResult(
|
|
@@ -18,6 +19,9 @@ data class ParseResult(
|
|
|
18
19
|
object InputParser {
|
|
19
20
|
private const val ATTACHMENT_CHAR = ''
|
|
20
21
|
|
|
22
|
+
// A source paragraph break: a newline followed by one or more blank lines.
|
|
23
|
+
private val PARAGRAPH_BREAK_RE = Regex("\\n[ \\t]*(?:\\n[ \\t]*)+")
|
|
24
|
+
|
|
21
25
|
fun parseToPlainTextAndRanges(markdown: String): ParseResult {
|
|
22
26
|
if (markdown.isEmpty()) {
|
|
23
27
|
return ParseResult("", emptyList())
|
|
@@ -36,7 +40,17 @@ object InputParser {
|
|
|
36
40
|
val ranges = mutableListOf<FormattingRange>()
|
|
37
41
|
val mathAtoms = mutableListOf<MathAtom>()
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
// md4c collapses any blank-line run into a single break, unlike iOS which keeps them.
|
|
44
|
+
// Re-read the real runs so each break replays its original number of newlines.
|
|
45
|
+
val blankRuns =
|
|
46
|
+
ArrayDeque(
|
|
47
|
+
PARAGRAPH_BREAK_RE
|
|
48
|
+
.findAll(completed.trim())
|
|
49
|
+
.map { match -> match.value.count { it == '\n' } }
|
|
50
|
+
.toList(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
walkNode(ast, plainText, ranges, mathAtoms, ArrayDeque(), blankRuns)
|
|
40
54
|
|
|
41
55
|
return ParseResult(plainText.toString(), ranges, mathAtoms)
|
|
42
56
|
}
|
|
@@ -47,6 +61,7 @@ object InputParser {
|
|
|
47
61
|
ranges: MutableList<FormattingRange>,
|
|
48
62
|
mathAtoms: MutableList<MathAtom>,
|
|
49
63
|
activeStyles: ArrayDeque<ActiveStyle>,
|
|
64
|
+
blankRuns: ArrayDeque<Int>,
|
|
50
65
|
) {
|
|
51
66
|
if (node.type == NodeType.LatexMathInline || node.type == NodeType.LatexMathDisplay) {
|
|
52
67
|
val latex = extractLatex(node)
|
|
@@ -75,8 +90,12 @@ object InputParser {
|
|
|
75
90
|
plainText.append("\n")
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
for (child in node.children) {
|
|
79
|
-
|
|
93
|
+
for ((index, child) in node.children.withIndex()) {
|
|
94
|
+
// Keep the source's blank lines between top-level blocks (md4c drops them, iOS keeps them).
|
|
95
|
+
if (index > 0 && child.type.isTopLevelBlock() && plainText.isNotEmpty()) {
|
|
96
|
+
plainText.append("\n".repeat(blankRuns.removeFirstOrNull() ?: 2))
|
|
97
|
+
}
|
|
98
|
+
walkNode(child, plainText, ranges, mathAtoms, activeStyles, blankRuns)
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
if (styleType != null) {
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt
CHANGED
|
@@ -9,6 +9,7 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
9
9
|
import com.facebook.react.uimanager.PixelUtil
|
|
10
10
|
import com.facebook.yoga.YogaMeasureMode
|
|
11
11
|
import com.facebook.yoga.YogaMeasureOutput
|
|
12
|
+
import com.swmansion.enriched.markdown.input.formatting.InputParser
|
|
12
13
|
import java.util.concurrent.ConcurrentHashMap
|
|
13
14
|
|
|
14
15
|
object InputMeasurementStore {
|
|
@@ -93,7 +94,12 @@ object InputMeasurementStore {
|
|
|
93
94
|
width: Float,
|
|
94
95
|
props: ReadableMap?,
|
|
95
96
|
): Long {
|
|
96
|
-
|
|
97
|
+
// Measure the rendered plain text, not the raw markdown. A mention link such as
|
|
98
|
+
// [Label](placeholder://x) hides its URL in the source, so measuring the markdown counts those
|
|
99
|
+
// invisible characters and over-estimates the height into extra lines, until the next text
|
|
100
|
+
// change forces a re-measure. Parsing first matches what the editor actually renders.
|
|
101
|
+
val markdown = props?.getString("defaultValue") ?: props?.getString("placeholder") ?: "I"
|
|
102
|
+
val text = InputParser.parseToPlainTextAndRanges(markdown).plainText
|
|
97
103
|
val fontSize = props?.getDouble("fontSize")?.toFloat() ?: 16f
|
|
98
104
|
val spSize = kotlin.math.ceil(PixelUtil.toPixelFromSP(fontSize).toDouble()).toFloat()
|
|
99
105
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt
CHANGED
|
@@ -14,12 +14,34 @@ import com.swmansion.enriched.markdown.input.formatting.MarkdownSerializer
|
|
|
14
14
|
import com.swmansion.enriched.markdown.input.model.FormattingRange
|
|
15
15
|
import com.swmansion.enriched.markdown.input.model.StyleType
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
data class InputSelectionMenuConfig(
|
|
18
|
+
val format: Boolean = true,
|
|
19
|
+
val formatLabel: String = "",
|
|
20
|
+
val copyAsMarkdown: Boolean = true,
|
|
21
|
+
val copyAsMarkdownLabel: String = "",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
data class FormatMenuConfig(
|
|
25
|
+
val bold: Boolean = true,
|
|
26
|
+
val boldLabel: String = "",
|
|
27
|
+
val italic: Boolean = true,
|
|
28
|
+
val italicLabel: String = "",
|
|
29
|
+
val underline: Boolean = true,
|
|
30
|
+
val underlineLabel: String = "",
|
|
31
|
+
val strikethrough: Boolean = true,
|
|
32
|
+
val strikethroughLabel: String = "",
|
|
33
|
+
val spoiler: Boolean = true,
|
|
34
|
+
val spoilerLabel: String = "",
|
|
35
|
+
val link: Boolean = true,
|
|
36
|
+
val linkLabel: String = "",
|
|
37
|
+
)
|
|
18
38
|
|
|
19
39
|
class InputContextMenu(
|
|
20
40
|
private val view: EnrichedMarkdownTextInputView,
|
|
21
41
|
) {
|
|
22
42
|
private var customItemTexts: List<String> = emptyList()
|
|
43
|
+
var selectionMenuConfig: InputSelectionMenuConfig = InputSelectionMenuConfig()
|
|
44
|
+
var formatMenuConfig: FormatMenuConfig = FormatMenuConfig()
|
|
23
45
|
|
|
24
46
|
fun setContextMenuItems(items: List<String>) {
|
|
25
47
|
customItemTexts = items
|
|
@@ -42,13 +64,20 @@ class InputContextMenu(
|
|
|
42
64
|
menu.removeGroup(FORMAT_MENU_GROUP_ID)
|
|
43
65
|
menu.removeGroup(CUSTOM_MENU_GROUP_ID)
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
67
|
+
if (selectionMenuConfig.format) {
|
|
68
|
+
val formatSubMenu =
|
|
69
|
+
menu.addSubMenu(FORMAT_MENU_GROUP_ID, MENU_FORMAT_ID, 100, selectionMenuConfig.formatLabel)
|
|
70
|
+
FORMAT_ITEMS.forEachIndexed { index, (styleType, labelOf) ->
|
|
71
|
+
if (isFormatItemVisible(styleType)) {
|
|
72
|
+
formatSubMenu.add(Menu.NONE, MENU_FORMAT_ITEM_BASE + index, index, labelOf(formatMenuConfig))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
if (view.selectionStart < view.selectionEnd) {
|
|
51
|
-
|
|
78
|
+
if (selectionMenuConfig.copyAsMarkdown) {
|
|
79
|
+
menu.add(FORMAT_MENU_GROUP_ID, MENU_COPY_MARKDOWN_ID, 101, selectionMenuConfig.copyAsMarkdownLabel)
|
|
80
|
+
}
|
|
52
81
|
|
|
53
82
|
customItemTexts.forEachIndexed { index, text ->
|
|
54
83
|
menu
|
|
@@ -74,7 +103,7 @@ class InputContextMenu(
|
|
|
74
103
|
|
|
75
104
|
val formatIndex = itemId - MENU_FORMAT_ITEM_BASE
|
|
76
105
|
if (formatIndex in FORMAT_ITEMS.indices) {
|
|
77
|
-
applyFormat(FORMAT_ITEMS[formatIndex].
|
|
106
|
+
applyFormat(FORMAT_ITEMS[formatIndex].first)
|
|
78
107
|
mode.finish()
|
|
79
108
|
return true
|
|
80
109
|
}
|
|
@@ -96,6 +125,16 @@ class InputContextMenu(
|
|
|
96
125
|
}
|
|
97
126
|
}
|
|
98
127
|
|
|
128
|
+
private fun isFormatItemVisible(styleType: StyleType): Boolean =
|
|
129
|
+
when (styleType) {
|
|
130
|
+
StyleType.BOLD -> formatMenuConfig.bold
|
|
131
|
+
StyleType.ITALIC -> formatMenuConfig.italic
|
|
132
|
+
StyleType.UNDERLINE -> formatMenuConfig.underline
|
|
133
|
+
StyleType.STRIKETHROUGH -> formatMenuConfig.strikethrough
|
|
134
|
+
StyleType.SPOILER -> formatMenuConfig.spoiler
|
|
135
|
+
StyleType.LINK -> formatMenuConfig.link
|
|
136
|
+
}
|
|
137
|
+
|
|
99
138
|
private fun applyFormat(styleType: StyleType) {
|
|
100
139
|
val start = view.selectionStart
|
|
101
140
|
val end = view.selectionEnd
|
|
@@ -168,14 +207,16 @@ class InputContextMenu(
|
|
|
168
207
|
private const val CUSTOM_MENU_GROUP_ID = 2000
|
|
169
208
|
private const val MENU_CUSTOM_BASE = 2001
|
|
170
209
|
|
|
171
|
-
|
|
210
|
+
// Order here defines submenu order and the index used by
|
|
211
|
+
// `MENU_FORMAT_ITEM_BASE + index` when handling clicks.
|
|
212
|
+
private val FORMAT_ITEMS: List<Pair<StyleType, (FormatMenuConfig) -> String>> =
|
|
172
213
|
listOf(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
214
|
+
StyleType.BOLD to { it.boldLabel },
|
|
215
|
+
StyleType.ITALIC to { it.italicLabel },
|
|
216
|
+
StyleType.UNDERLINE to { it.underlineLabel },
|
|
217
|
+
StyleType.STRIKETHROUGH to { it.strikethroughLabel },
|
|
218
|
+
StyleType.SPOILER to { it.spoilerLabel },
|
|
219
|
+
StyleType.LINK to { it.linkLabel },
|
|
179
220
|
)
|
|
180
221
|
}
|
|
181
222
|
}
|
|
@@ -41,3 +41,20 @@ data class MarkdownASTNode(
|
|
|
41
41
|
|
|
42
42
|
fun getAttribute(key: String): String? = attributes[key]
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
// A node type that md4c emits as a standalone block stacked vertically, as opposed to an inline span.
|
|
46
|
+
internal fun MarkdownASTNode.NodeType.isTopLevelBlock(): Boolean =
|
|
47
|
+
when (this) {
|
|
48
|
+
MarkdownASTNode.NodeType.Paragraph,
|
|
49
|
+
MarkdownASTNode.NodeType.Heading,
|
|
50
|
+
MarkdownASTNode.NodeType.Blockquote,
|
|
51
|
+
MarkdownASTNode.NodeType.UnorderedList,
|
|
52
|
+
MarkdownASTNode.NodeType.OrderedList,
|
|
53
|
+
MarkdownASTNode.NodeType.CodeBlock,
|
|
54
|
+
MarkdownASTNode.NodeType.ThematicBreak,
|
|
55
|
+
MarkdownASTNode.NodeType.Table,
|
|
56
|
+
MarkdownASTNode.NodeType.LatexMathDisplay,
|
|
57
|
+
-> true
|
|
58
|
+
|
|
59
|
+
else -> false
|
|
60
|
+
}
|
|
@@ -52,10 +52,10 @@ class SpanStyleCache(
|
|
|
52
52
|
val paragraphColor = style.paragraphStyle.color
|
|
53
53
|
return buildList {
|
|
54
54
|
style.strongStyle.color
|
|
55
|
-
?.takeIf { it != 0 }
|
|
55
|
+
?.takeIf { it != 0 && it != paragraphColor }
|
|
56
56
|
?.let { add(it) }
|
|
57
57
|
style.emphasisStyle.color
|
|
58
|
-
?.takeIf { it != 0 }
|
|
58
|
+
?.takeIf { it != 0 && it != paragraphColor }
|
|
59
59
|
?.let { add(it) }
|
|
60
60
|
style.linkStyle.color
|
|
61
61
|
.takeIf { it != 0 && it != paragraphColor }
|
|
@@ -68,10 +68,10 @@ class SpanStyleCache(
|
|
|
68
68
|
style
|
|
69
69
|
.codeStyle
|
|
70
70
|
.color
|
|
71
|
-
.takeIf { it != 0 }
|
|
71
|
+
.takeIf { it != 0 && it != paragraphColor }
|
|
72
72
|
?.let { add(it) }
|
|
73
73
|
style.taskListStyle.checkedTextColor
|
|
74
|
-
.takeIf { it != 0 }
|
|
74
|
+
.takeIf { it != 0 && it != paragraphColor }
|
|
75
75
|
?.let { add(it) }
|
|
76
76
|
}.toIntArray()
|
|
77
77
|
}
|
|
@@ -4,6 +4,7 @@ import android.view.View
|
|
|
4
4
|
import com.facebook.react.bridge.ReadableArray
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
6
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
+
import com.swmansion.enriched.markdown.accessibility.AccessibilityLabels
|
|
7
8
|
import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent
|
|
8
9
|
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
9
10
|
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
@@ -96,5 +97,34 @@ fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
|
|
|
96
97
|
return SelectionMenuConfig(
|
|
97
98
|
copyAsMarkdown = value.getBoolean("copyAsMarkdown"),
|
|
98
99
|
copyImageUrl = value.getBoolean("copyImageUrl"),
|
|
100
|
+
copyLabel = value.getString("copyLabel") ?: "",
|
|
101
|
+
copyAsMarkdownLabel = value.getString("copyAsMarkdownLabel") ?: "",
|
|
102
|
+
copyImageUrlLabel = value.getString("copyImageUrlLabel") ?: "",
|
|
103
|
+
copyImageUrlsLabel = value.getString("copyImageUrlsLabel") ?: "",
|
|
104
|
+
copyImageUrlPluralTemplates = parseStringList(value.getArray("copyImageUrlPluralTemplates")),
|
|
99
105
|
)
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
fun parseAccessibilityLabels(value: ReadableMap?): AccessibilityLabels {
|
|
109
|
+
if (value == null) return AccessibilityLabels()
|
|
110
|
+
val list = value.getMap("list")
|
|
111
|
+
val blockquote = value.getMap("blockquote")
|
|
112
|
+
val table = value.getMap("table")
|
|
113
|
+
val math = value.getMap("math")
|
|
114
|
+
val rotor = value.getMap("rotor")
|
|
115
|
+
return AccessibilityLabels(
|
|
116
|
+
bulletPoint = list?.getString("bulletPoint") ?: "",
|
|
117
|
+
nestedBulletPoint = list?.getString("nestedBulletPoint") ?: "",
|
|
118
|
+
orderedItem = list?.getString("orderedItem") ?: "",
|
|
119
|
+
nestedOrderedItem = list?.getString("nestedOrderedItem") ?: "",
|
|
120
|
+
blockquote = blockquote?.getString("quote") ?: "",
|
|
121
|
+
nestedBlockquote = blockquote?.getString("nestedQuote") ?: "",
|
|
122
|
+
tableRow = table?.getString("row") ?: "",
|
|
123
|
+
mathEquation = math?.getString("equation") ?: "",
|
|
124
|
+
rotorHeadings = rotor?.getString("headings") ?: "",
|
|
125
|
+
rotorLinks = rotor?.getString("links") ?: "",
|
|
126
|
+
rotorImages = rotor?.getString("images") ?: "",
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun parseStringList(value: ReadableArray?): List<String> = (0 until (value?.size() ?: 0)).mapNotNull { value?.getString(it) }
|
|
@@ -26,18 +26,40 @@ fun View.emitLinkLongPressEvent(url: String) {
|
|
|
26
26
|
/**
|
|
27
27
|
* Cancels the JS touch for an active link tap, preventing parent
|
|
28
28
|
* Pressable/TouchableOpacity from firing onPress for the same tap.
|
|
29
|
+
*
|
|
30
|
+
* Also requests that parent ViewGroups do not intercept subsequent touch
|
|
31
|
+
* events in this gesture. This is necessary for react-native-gesture-handler's
|
|
32
|
+
* Pressable, which intercepts at the native ViewGroup level and would otherwise
|
|
33
|
+
* steal the gesture before ACTION_UP reaches the TextView.
|
|
29
34
|
*/
|
|
30
35
|
fun TextView.cancelJSTouchForLinkTap(event: MotionEvent) {
|
|
31
36
|
val currentMovementMethod = movementMethod
|
|
32
37
|
if (currentMovementMethod is LinkLongPressMovementMethod && currentMovementMethod.isLinkTouchActive) {
|
|
38
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
33
39
|
NativeGestureUtil.notifyNativeGestureStarted(this, event)
|
|
34
40
|
}
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Re-allows parent ViewGroups to intercept touch events once the link
|
|
45
|
+
* touch is no longer active (slop exceeded, gesture ended, or cancelled).
|
|
46
|
+
* This restores normal scrolling behavior for parent ScrollView/RecyclerView.
|
|
47
|
+
*
|
|
48
|
+
* Must be called after [LinkLongPressMovementMethod.onTouchEvent] has
|
|
49
|
+
* processed the event, since that is where [isLinkTouchActive] is updated.
|
|
50
|
+
*/
|
|
51
|
+
fun TextView.reallowParentInterceptIfLinkReleased() {
|
|
52
|
+
val currentMovementMethod = movementMethod
|
|
53
|
+
if (currentMovementMethod is LinkLongPressMovementMethod && !currentMovementMethod.isLinkTouchActive) {
|
|
54
|
+
parent?.requestDisallowInterceptTouchEvent(false)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
/**
|
|
38
59
|
* Cancels the JS touch unconditionally, preventing parent
|
|
39
60
|
* Pressable/TouchableOpacity from firing onPress for the same gesture.
|
|
40
61
|
*/
|
|
41
62
|
fun View.cancelJSTouchForCheckboxTap(event: MotionEvent) {
|
|
63
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
42
64
|
NativeGestureUtil.notifyNativeGestureStarted(this, event)
|
|
43
65
|
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt
CHANGED
|
@@ -25,8 +25,30 @@ private const val MENU_ITEM_CUSTOM_GROUP = 2001
|
|
|
25
25
|
data class SelectionMenuConfig(
|
|
26
26
|
val copyAsMarkdown: Boolean = true,
|
|
27
27
|
val copyImageUrl: Boolean = true,
|
|
28
|
+
val copyLabel: String = "",
|
|
29
|
+
val copyAsMarkdownLabel: String = "",
|
|
30
|
+
val copyImageUrlLabel: String = "",
|
|
31
|
+
val copyImageUrlsLabel: String = "",
|
|
32
|
+
// Plural templates indexed by image count (0..100). Empty when no pluralLabels
|
|
33
|
+
// are set; counts > 100 use copyImageUrlsLabel (the "other" form).
|
|
34
|
+
val copyImageUrlPluralTemplates: List<String> = emptyList(),
|
|
28
35
|
)
|
|
29
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the "Copy Image URL(s)" menu title for the given image count. Uses the
|
|
39
|
+
* precomputed plural templates when present (counts > 100 use the "other" form),
|
|
40
|
+
* otherwise the singular/`{count}` templates. All labels are resolved JS-side.
|
|
41
|
+
*/
|
|
42
|
+
private fun SelectionMenuConfig.imageUrlsTitle(count: Int): String {
|
|
43
|
+
val template =
|
|
44
|
+
when {
|
|
45
|
+
copyImageUrlPluralTemplates.isNotEmpty() && count in 0..100 -> copyImageUrlPluralTemplates[count]
|
|
46
|
+
count == 1 -> copyImageUrlLabel
|
|
47
|
+
else -> copyImageUrlsLabel
|
|
48
|
+
}
|
|
49
|
+
return template.replace("{count}", count.toString())
|
|
50
|
+
}
|
|
51
|
+
|
|
30
52
|
/**
|
|
31
53
|
* Creates an ActionMode.Callback that adds custom copy options and
|
|
32
54
|
* overrides the default "Copy" action to include HTML for rich text support.
|
|
@@ -56,12 +78,24 @@ fun createSelectionActionModeCallback(
|
|
|
56
78
|
|
|
57
79
|
val selectionMenuConfig = getSelectionMenuConfig()
|
|
58
80
|
|
|
81
|
+
// The system Copy item is added by the platform; relabel it so it
|
|
82
|
+
// matches the rest of the localized menu (iOS rebuilds the menu so
|
|
83
|
+
// gets this for free; Android only intercepts the click).
|
|
84
|
+
if (selectionMenuConfig.copyLabel.isNotEmpty()) {
|
|
85
|
+
menu.findItem(android.R.id.copy)?.title = selectionMenuConfig.copyLabel
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
if (
|
|
60
89
|
selectionMenuConfig.copyAsMarkdown &&
|
|
61
90
|
textView.selectionStart >= 0 &&
|
|
62
91
|
textView.selectionEnd > textView.selectionStart
|
|
63
92
|
) {
|
|
64
|
-
menu.add(
|
|
93
|
+
menu.add(
|
|
94
|
+
Menu.NONE,
|
|
95
|
+
MENU_ITEM_COPY_MARKDOWN,
|
|
96
|
+
Menu.NONE,
|
|
97
|
+
selectionMenuConfig.copyAsMarkdownLabel,
|
|
98
|
+
)
|
|
65
99
|
}
|
|
66
100
|
|
|
67
101
|
if (textView.selectionStart >= 0 && textView.selectionEnd > textView.selectionStart) {
|
|
@@ -80,12 +114,7 @@ fun createSelectionActionModeCallback(
|
|
|
80
114
|
emptyList()
|
|
81
115
|
}
|
|
82
116
|
if (imageUrls.isNotEmpty()) {
|
|
83
|
-
val title =
|
|
84
|
-
if (imageUrls.size == 1) {
|
|
85
|
-
"Copy Image URL"
|
|
86
|
-
} else {
|
|
87
|
-
"Copy ${imageUrls.size} Image URLs"
|
|
88
|
-
}
|
|
117
|
+
val title = selectionMenuConfig.imageUrlsTitle(imageUrls.size)
|
|
89
118
|
menu.add(Menu.NONE, MENU_ITEM_COPY_IMAGE_URL, Menu.NONE, title)
|
|
90
119
|
}
|
|
91
120
|
|