react-native-enriched-markdown-scaffold 0.7.4-scaffold.10 → 0.7.4-scaffold.12
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/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 +6 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +2 -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/EventEmitters.cpp +10 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +7 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +15 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +39 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +8 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +8 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +18 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +128 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnPasteEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt +8 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +19 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +2 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +2 -0
- package/docs/API_REFERENCE.md +64 -0
- package/docs/COPY_OPTIONS.md +13 -0
- package/docs/INPUT.md +39 -0
- package/docs/WEB.md +1 -0
- package/ios/EnrichedMarkdown.mm +14 -0
- package/ios/EnrichedMarkdownText.mm +10 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +10 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +7 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +15 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +3 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +29 -0
- package/ios/input/ENRMInputTextView.mm +11 -1
- package/ios/input/EnrichedMarkdownTextInput.h +5 -0
- package/ios/input/EnrichedMarkdownTextInput.mm +33 -0
- package/ios/views/ENRMMathContainerView.h +4 -0
- package/ios/views/ENRMMathContainerView.m +11 -0
- package/ios/views/TableContainerView.h +4 -0
- package/ios/views/TableContainerView.m +7 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +4 -0
- package/lib/module/EnrichedMarkdownTextInput.js +15 -0
- package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +22 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +2 -0
- 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/EnrichedMarkdownTextInput.d.ts +24 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +16 -0
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.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/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +16 -0
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +15 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +4 -0
- package/src/EnrichedMarkdownTextInput.tsx +37 -0
- package/src/EnrichedMarkdownTextInputNativeComponent.ts +22 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +4 -0
- package/src/index.tsx +1 -0
- package/src/native/EnrichedMarkdownText.tsx +2 -0
- package/src/types/MarkdownTextProps.ts +16 -0
- package/src/types/events.ts +16 -0
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt
CHANGED
|
@@ -57,6 +57,10 @@ fun createSelectionActionModeCallback(
|
|
|
57
57
|
textView: TextView,
|
|
58
58
|
getCustomItemTexts: () -> List<String> = { emptyList() },
|
|
59
59
|
getSelectionMenuConfig: () -> SelectionMenuConfig = { SelectionMenuConfig() },
|
|
60
|
+
isContextMenuHidden: () -> Boolean = { false },
|
|
61
|
+
// Lets the owning view hold the live ActionMode so it can invalidate() the
|
|
62
|
+
// toolbar when a prop changes while the menu is already on screen.
|
|
63
|
+
onActionModeChanged: (mode: ActionMode?) -> Unit = {},
|
|
60
64
|
onCustomItemPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit =
|
|
61
65
|
{ _, _, _, _ -> },
|
|
62
66
|
): ActionMode.Callback =
|
|
@@ -64,7 +68,10 @@ fun createSelectionActionModeCallback(
|
|
|
64
68
|
override fun onCreateActionMode(
|
|
65
69
|
mode: ActionMode?,
|
|
66
70
|
menu: Menu?,
|
|
67
|
-
): Boolean
|
|
71
|
+
): Boolean {
|
|
72
|
+
onActionModeChanged(mode)
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
68
75
|
|
|
69
76
|
override fun onPrepareActionMode(
|
|
70
77
|
mode: ActionMode?,
|
|
@@ -72,6 +79,14 @@ fun createSelectionActionModeCallback(
|
|
|
72
79
|
): Boolean {
|
|
73
80
|
if (menu == null) return false
|
|
74
81
|
|
|
82
|
+
// Keep the ActionMode alive (selection handles and highlighting stay
|
|
83
|
+
// usable, which is what TalkBack relies on) but ship an empty menu so
|
|
84
|
+
// the floating toolbar has nothing to show.
|
|
85
|
+
if (isContextMenuHidden()) {
|
|
86
|
+
menu.clear()
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
menu.removeItem(MENU_ITEM_COPY_MARKDOWN)
|
|
76
91
|
menu.removeItem(MENU_ITEM_COPY_IMAGE_URL)
|
|
77
92
|
menu.removeGroup(MENU_ITEM_CUSTOM_GROUP)
|
|
@@ -161,7 +176,9 @@ fun createSelectionActionModeCallback(
|
|
|
161
176
|
return false
|
|
162
177
|
}
|
|
163
178
|
|
|
164
|
-
override fun onDestroyActionMode(mode: ActionMode?) {
|
|
179
|
+
override fun onDestroyActionMode(mode: ActionMode?) {
|
|
180
|
+
onActionModeChanged(null)
|
|
181
|
+
}
|
|
165
182
|
}
|
|
166
183
|
|
|
167
184
|
/** Copies selection as both plain text and HTML with inline styles. */
|
|
@@ -63,6 +63,7 @@ class TableContainerView(
|
|
|
63
63
|
|
|
64
64
|
var copyLabel: String = ""
|
|
65
65
|
var copyAsMarkdownLabel: String = ""
|
|
66
|
+
var contextMenuHidden: Boolean = false
|
|
66
67
|
|
|
67
68
|
private val scrollView =
|
|
68
69
|
HorizontalScrollView(context).apply {
|
|
@@ -337,6 +338,7 @@ class TableContainerView(
|
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
private fun showContextMenu(anchor: View) {
|
|
341
|
+
if (contextMenuHidden) return
|
|
340
342
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
341
343
|
ContextMenuPopup.show(anchor, this) {
|
|
342
344
|
item(ContextMenuPopup.Icon.COPY, copyLabel) {
|
|
@@ -41,6 +41,7 @@ class MathContainerView(
|
|
|
41
41
|
// Set reflectively by EnrichedMarkdown (math is an optional module).
|
|
42
42
|
var copyLabel: String = ""
|
|
43
43
|
var copyAsMarkdownLabel: String = ""
|
|
44
|
+
var contextMenuHidden: Boolean = false
|
|
44
45
|
|
|
45
46
|
override val segmentMarginTop: Int get() = mathStyle.marginTop.toInt()
|
|
46
47
|
override val segmentMarginBottom: Int get() = mathStyle.marginBottom.toInt()
|
|
@@ -117,6 +118,7 @@ class MathContainerView(
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
private fun showContextMenu(anchor: View) {
|
|
121
|
+
if (contextMenuHidden) return
|
|
120
122
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
121
123
|
ContextMenuPopup.show(anchor, this) {
|
|
122
124
|
item(ContextMenuPopup.Icon.COPY, copyLabel) {
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -105,6 +105,36 @@ Whether text can be selected.
|
|
|
105
105
|
| --------- | ------------- | -------- |
|
|
106
106
|
| `boolean` | `true` | Both |
|
|
107
107
|
|
|
108
|
+
### `contextMenuHidden`
|
|
109
|
+
|
|
110
|
+
Hides the text selection context menu. Text stays selectable — only the menu is
|
|
111
|
+
suppressed. This is deliberately different from `selectable={false}`, which
|
|
112
|
+
disables selection altogether and, on Android, also removes the text from the
|
|
113
|
+
TalkBack focus order.
|
|
114
|
+
|
|
115
|
+
| Type | Default Value | Platform |
|
|
116
|
+
| --------- | ------------- | ----------- |
|
|
117
|
+
| `boolean` | `false` | Both, macOS |
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Text can still be selected, but long-press shows no menu
|
|
121
|
+
<EnrichedMarkdownText markdown={content} contextMenuHidden />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Notes:
|
|
125
|
+
|
|
126
|
+
- It hides the **whole** menu. The custom items from `contextMenuItems` and the
|
|
127
|
+
built-in items from `selectionMenuConfig` share that same menu, so they are
|
|
128
|
+
hidden too. To drop individual items instead, use `selectionMenuConfig`.
|
|
129
|
+
- It also covers the separate copy menus on table and math blocks (which appear
|
|
130
|
+
with `flavor="github"`), not just the text selection menu.
|
|
131
|
+
- It does **not** suppress the system "Save to Camera Roll / Copy" sheet that
|
|
132
|
+
UIKit shows when long-pressing an image — that sheet is owned by UIKit.
|
|
133
|
+
- On macOS the menu opens on click-with-selection rather than long-press; the
|
|
134
|
+
prop suppresses it the same way.
|
|
135
|
+
- Applies to `EnrichedMarkdownText` only — it does not affect
|
|
136
|
+
`EnrichedMarkdownTextInput`.
|
|
137
|
+
|
|
108
138
|
### `selectionColor`
|
|
109
139
|
|
|
110
140
|
Color of the text selection highlight. On iOS, this also affects the caret and selection handle colors (they share a single tint). On macOS, only the selection background is affected. On Android, use `selectionHandleColor` to override the handle color independently.
|
|
@@ -532,6 +562,18 @@ Auto-capitalization behavior.
|
|
|
532
562
|
| -------- | -------------- | -------- |
|
|
533
563
|
| `string` | `'sentences'` | Both |
|
|
534
564
|
|
|
565
|
+
### `interceptPaste`
|
|
566
|
+
|
|
567
|
+
Reports pastes through [`onPaste`](#onpaste) instead of inserting them. Only takes effect when `onPaste` is also set — otherwise native would swallow every paste with nobody left to resolve it, so the library keeps the prop off.
|
|
568
|
+
|
|
569
|
+
See [Intercepting paste](./INPUT.md#intercepting-paste).
|
|
570
|
+
|
|
571
|
+
| Type | Default Value | Platform |
|
|
572
|
+
| --------- | ------------- | ------------- |
|
|
573
|
+
| `boolean` | `false` | iOS, Android |
|
|
574
|
+
|
|
575
|
+
macOS ignores this prop and always pastes inline.
|
|
576
|
+
|
|
535
577
|
### `multiline`
|
|
536
578
|
|
|
537
579
|
Whether the input supports multiple lines.
|
|
@@ -710,6 +752,18 @@ Fires when the active mention flow ends.
|
|
|
710
752
|
| ---- | ------------- | -------- |
|
|
711
753
|
| `(event: { indicator: string }) => void` | - | Both |
|
|
712
754
|
|
|
755
|
+
### `onPaste`
|
|
756
|
+
|
|
757
|
+
Fires instead of the insert when [`interceptPaste`](#interceptpaste) is on. Native reads the clipboard and leaves the buffer untouched, so the handler owns the paste from that point: it must call [`resolvePaste`](#resolvepastetext-string-markdown-string) exactly once, on every path including failures, or the paste is silently dropped.
|
|
758
|
+
|
|
759
|
+
| Type | Default Value | Platform |
|
|
760
|
+
| ---- | ------------- | -------- |
|
|
761
|
+
| `(event: { text: string; markdown: string }) => void` | - | iOS, Android |
|
|
762
|
+
|
|
763
|
+
`markdown` is non-empty only when the copy source was an `EnrichedMarkdownTextInput`, which the system clipboard can only carry on iOS; Android always reports `''`.
|
|
764
|
+
|
|
765
|
+
See [Intercepting paste](./INPUT.md#intercepting-paste).
|
|
766
|
+
|
|
713
767
|
### `writingDirection`
|
|
714
768
|
|
|
715
769
|
Paragraph writing direction in the input. iOS only — Android's `EditText` already resolves direction per paragraph via `TEXT_DIRECTION_FIRST_STRONG` and is unaffected by this prop.
|
|
@@ -942,6 +996,16 @@ Copies the input's full content to the system clipboard, matching the result of
|
|
|
942
996
|
|
|
943
997
|
On iOS and macOS the clipboard receives both plain text and a private Markdown pasteboard type, so pasting back into an `EnrichedMarkdownTextInput` restores the formatting; external apps receive plain text only. On Android the clipboard receives plain text only — inline styles are not preserved for any paste target.
|
|
944
998
|
|
|
999
|
+
### `resolvePaste(text: string, markdown?: string)`
|
|
1000
|
+
|
|
1001
|
+
Completes a paste reported through [`onPaste`](#onpaste). Call it exactly once per event:
|
|
1002
|
+
|
|
1003
|
+
- `resolvePaste(text, markdown)` — inserts, restoring the copied formatting when `markdown` is non-empty.
|
|
1004
|
+
- `resolvePaste(text)` — inserts `text` verbatim.
|
|
1005
|
+
- `resolvePaste('', '')` — drops the clipboard content, replacing the current selection. With an empty selection this is a no-op that still emits `onChangeText` with unchanged content.
|
|
1006
|
+
|
|
1007
|
+
Anything that replaces a selection replaces it here too, so a paste over selected text behaves the same on every branch.
|
|
1008
|
+
|
|
945
1009
|
### `startMention(indicator: string)`
|
|
946
1010
|
|
|
947
1011
|
Programmatically triggers a mention flow by inserting the indicator character at the current cursor position. The indicator must be listed in the `mentionIndicators` prop. Useful for toolbar buttons.
|
package/docs/COPY_OPTIONS.md
CHANGED
|
@@ -44,6 +44,19 @@ Use `selectionMenuConfig` to hide built-in selection menu actions while keeping
|
|
|
44
44
|
/>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
To turn the menu off entirely rather than item by item, use `contextMenuHidden`:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// No selection menu at all — text is still selectable
|
|
51
|
+
<EnrichedMarkdownText markdown={content} contextMenuHidden />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The two are complementary: `selectionMenuConfig` prunes individual built-in
|
|
55
|
+
items and leaves the menu in place, while `contextMenuHidden` suppresses the
|
|
56
|
+
menu as a whole — including `contextMenuItems` and the copy menus on table and
|
|
57
|
+
math blocks. Note that `contextMenuHidden` applies to `EnrichedMarkdownText`
|
|
58
|
+
only; it does not affect `EnrichedMarkdownTextInput`.
|
|
59
|
+
|
|
47
60
|
`EnrichedMarkdownTextInput` supports the same `{ enabled, label }` shape. In addition to `copyAsMarkdown`, the input's `selectionMenuConfig` exposes the built-in **Format** submenu, and `formatMenuConfig` controls the items inside it:
|
|
48
61
|
|
|
49
62
|
```tsx
|
package/docs/INPUT.md
CHANGED
|
@@ -205,6 +205,45 @@ The input's content can be copied to the system clipboard from a ref, without re
|
|
|
205
205
|
|
|
206
206
|
On iOS and macOS the clipboard receives both plain text and a private Markdown pasteboard type, so pasting back into an `EnrichedMarkdownTextInput` restores the formatting; external apps receive plain text only. On Android the clipboard receives plain text only — inline styles are not preserved for any paste target.
|
|
207
207
|
|
|
208
|
+
## Intercepting paste
|
|
209
|
+
|
|
210
|
+
By default a paste goes straight into the buffer. Set [`interceptPaste`](API_REFERENCE.md#interceptpaste) together with [`onPaste`](API_REFERENCE.md#onpaste) to decide what a paste means before any text lands — turning a long paste into an attachment chip, stripping it, or inserting it unchanged.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
const inputRef = useRef<EnrichedMarkdownTextInputInstance>(null);
|
|
214
|
+
|
|
215
|
+
<EnrichedMarkdownTextInput
|
|
216
|
+
ref={inputRef}
|
|
217
|
+
interceptPaste
|
|
218
|
+
onPaste={({ text, markdown }) => {
|
|
219
|
+
if (text.length > 2000) {
|
|
220
|
+
attachAsFile(text);
|
|
221
|
+
inputRef.current?.resolvePaste('', ''); // drops it, and replaces the selection
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
inputRef.current?.resolvePaste(text, markdown);
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Native reads the clipboard and **does not touch the buffer**. That makes the handler the only thing that can complete the paste, so the contract is strict:
|
|
230
|
+
|
|
231
|
+
- Call [`resolvePaste`](API_REFERENCE.md#resolvepastetext-string-markdown-string) **exactly once per event, on every path including failures**. A handler that throws before resolving leaves the paste dropped — the user sees nothing happen.
|
|
232
|
+
- Pass `markdown` back through when inserting. It is the copied formatting; dropping it turns a copy-then-paste inside your app into flat text.
|
|
233
|
+
- `interceptPaste` is ignored unless `onPaste` is set, so switching the prop on alone cannot make the input swallow pastes.
|
|
234
|
+
|
|
235
|
+
Because nothing is written until you resolve, an async decision is safe: the selection is still there, and the user sees an unchanged input rather than text that vanished.
|
|
236
|
+
|
|
237
|
+
### Platform behavior
|
|
238
|
+
|
|
239
|
+
| | Behavior |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| **iOS** | Context menu and hardware ⌘V. `markdown` is set when the copy source was an `EnrichedMarkdownTextInput`. |
|
|
242
|
+
| **Android** | Context menu, hardware Ctrl+V and `KEYCODE_PASTE`. `markdown` is always `''` — the Android clipboard carries plain text only. |
|
|
243
|
+
| **macOS** | `interceptPaste` is ignored, `onPaste` never fires, and pastes insert inline as usual. |
|
|
244
|
+
|
|
245
|
+
Only clipboard content is intercepted. Drag-and-drop, autofill and IME content insertion bypass `onPaste` on both platforms and insert normally — on Android they are filtered out explicitly, so an autofill never arrives looking like a paste.
|
|
246
|
+
|
|
208
247
|
## Style Detection
|
|
209
248
|
|
|
210
249
|
All of the above styles can be detected with the use of [onChangeState](API_REFERENCE.md#onchangestate) callback payload.
|
package/docs/WEB.md
CHANGED
|
@@ -41,6 +41,7 @@ The web implementation also exports `WebMarkdownTextProps` which extends `Enrich
|
|
|
41
41
|
| `streamingConfig` | Native-only streaming table configuration. Not yet implemented on web. |
|
|
42
42
|
| `contextMenuItems` | Not supported — browsers don't allow extending the native context menu. |
|
|
43
43
|
| `selectionMenuConfig` | Not supported — native-only built-in selection menu actions. |
|
|
44
|
+
| `contextMenuHidden` | Not supported — browsers own the context menu on selected text. |
|
|
44
45
|
| `selectionHandleColor` | Android-only — desktop browsers don't render selection handles. |
|
|
45
46
|
|
|
46
47
|
## Not supported on web
|
package/ios/EnrichedMarkdown.mm
CHANGED
|
@@ -98,6 +98,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
98
98
|
BOOL _allowTrailingMargin;
|
|
99
99
|
BOOL _selectable;
|
|
100
100
|
BOOL _enableLinkPreview;
|
|
101
|
+
BOOL _contextMenuHidden;
|
|
101
102
|
BOOL _streamingAnimation;
|
|
102
103
|
ENRMTableStreamingMode _tableStreamingMode;
|
|
103
104
|
|
|
@@ -179,6 +180,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
179
180
|
|
|
180
181
|
_selectable = YES;
|
|
181
182
|
_enableLinkPreview = YES;
|
|
183
|
+
_contextMenuHidden = NO;
|
|
182
184
|
_selectionMenuConfig = (ENRMSelectionMenuConfig){.copyAsMarkdown = YES, .copyImageURL = YES};
|
|
183
185
|
_resolvedLayoutDirection =
|
|
184
186
|
[[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
|
|
@@ -450,12 +452,14 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
450
452
|
TableContainerView *tableView = (TableContainerView *)segment;
|
|
451
453
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
452
454
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
455
|
+
tableView.contextMenuHidden = _contextMenuHidden;
|
|
453
456
|
}
|
|
454
457
|
#if ENRICHED_MARKDOWN_MATH
|
|
455
458
|
else if ([segment isKindOfClass:[ENRMMathContainerView class]]) {
|
|
456
459
|
ENRMMathContainerView *mathView = (ENRMMathContainerView *)segment;
|
|
457
460
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
458
461
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
462
|
+
mathView.contextMenuHidden = _contextMenuHidden;
|
|
459
463
|
}
|
|
460
464
|
#endif
|
|
461
465
|
}
|
|
@@ -647,6 +651,9 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
647
651
|
if (!strongSelf) {
|
|
648
652
|
return baseMenu;
|
|
649
653
|
}
|
|
654
|
+
if (strongSelf->_contextMenuHidden) {
|
|
655
|
+
return nil;
|
|
656
|
+
}
|
|
650
657
|
NSString *segmentMarkdown = extractMarkdownFromAttributedString(textView.textStorage, textView.selectedRange);
|
|
651
658
|
NSArray<NSMenuItem *> *customItems = ENRMBuildContextMenuItems(
|
|
652
659
|
strongSelf->_contextMenuItemTexts, strongSelf->_contextMenuItemIcons, textView,
|
|
@@ -676,6 +683,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
676
683
|
tableView.accessibilityLabels = _accessibilityLabels;
|
|
677
684
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
678
685
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
686
|
+
tableView.contextMenuHidden = _contextMenuHidden;
|
|
679
687
|
|
|
680
688
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
681
689
|
|
|
@@ -715,6 +723,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
715
723
|
mathView.accessibilityLabels = _accessibilityLabels;
|
|
716
724
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
717
725
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
726
|
+
mathView.contextMenuHidden = _contextMenuHidden;
|
|
718
727
|
[mathView applyLatex:mathSegment.latex];
|
|
719
728
|
return mathView;
|
|
720
729
|
}
|
|
@@ -838,6 +847,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
838
847
|
}
|
|
839
848
|
|
|
840
849
|
_enableLinkPreview = newViewProps.enableLinkPreview;
|
|
850
|
+
_contextMenuHidden = newViewProps.contextMenuHidden;
|
|
841
851
|
|
|
842
852
|
if (newViewProps.streamingAnimation != oldViewProps.streamingAnimation) {
|
|
843
853
|
_streamingAnimation = newViewProps.streamingAnimation;
|
|
@@ -1132,6 +1142,10 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownCls(void)
|
|
|
1132
1142
|
editMenuForTextInRange:(NSRange)range
|
|
1133
1143
|
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0))
|
|
1134
1144
|
{
|
|
1145
|
+
if (_contextMenuHidden) {
|
|
1146
|
+
return nil;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1135
1149
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
1136
1150
|
ENRMContextMenuPressHandler handler =
|
|
1137
1151
|
^(NSString *itemText, NSString *selectedText, NSUInteger selectionStart, NSUInteger selectionEnd) {
|
|
@@ -80,6 +80,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
80
80
|
CGFloat _lastElementMarginBottom;
|
|
81
81
|
BOOL _allowTrailingMargin;
|
|
82
82
|
BOOL _enableLinkPreview;
|
|
83
|
+
BOOL _contextMenuHidden;
|
|
83
84
|
BOOL _streamingAnimation;
|
|
84
85
|
BOOL _forceHeightUpdateOnNextRender;
|
|
85
86
|
|
|
@@ -231,6 +232,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
231
232
|
[[ENRMAsyncRenderCoordinator alloc] initWithQueueLabel:"com.swmansion.enriched.markdown.render"];
|
|
232
233
|
|
|
233
234
|
_enableLinkPreview = YES;
|
|
235
|
+
_contextMenuHidden = NO;
|
|
234
236
|
_selectionMenuConfig = (ENRMSelectionMenuConfig){.copyAsMarkdown = YES, .copyImageURL = YES};
|
|
235
237
|
_resolvedLayoutDirection =
|
|
236
238
|
[[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
|
|
@@ -280,6 +282,9 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
280
282
|
if (!strongSelf) {
|
|
281
283
|
return baseMenu;
|
|
282
284
|
}
|
|
285
|
+
if (strongSelf->_contextMenuHidden) {
|
|
286
|
+
return nil;
|
|
287
|
+
}
|
|
283
288
|
NSArray<NSMenuItem *> *customItems = ENRMBuildContextMenuItems(
|
|
284
289
|
strongSelf->_contextMenuItemTexts, strongSelf->_contextMenuItemIcons, textView,
|
|
285
290
|
^(NSString *itemText, NSString *selectedText, NSUInteger selectionStart, NSUInteger selectionEnd) {
|
|
@@ -566,6 +571,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
566
571
|
}
|
|
567
572
|
|
|
568
573
|
_enableLinkPreview = newViewProps.enableLinkPreview;
|
|
574
|
+
_contextMenuHidden = newViewProps.contextMenuHidden;
|
|
569
575
|
|
|
570
576
|
if (ENRMContextMenuItemsChanged(oldViewProps.contextMenuItems, newViewProps.contextMenuItems)) {
|
|
571
577
|
_contextMenuItemTexts = ENRMContextMenuTextsFromItems(newViewProps.contextMenuItems);
|
|
@@ -790,6 +796,10 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
|
|
|
790
796
|
editMenuForTextInRange:(NSRange)range
|
|
791
797
|
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0))
|
|
792
798
|
{
|
|
799
|
+
if (_contextMenuHidden) {
|
|
800
|
+
return nil;
|
|
801
|
+
}
|
|
802
|
+
|
|
793
803
|
__weak EnrichedMarkdownText *weakSelf = self;
|
|
794
804
|
ENRMContextMenuPressHandler handler =
|
|
795
805
|
^(NSString *itemText, NSString *selectedText, NSUInteger selectionStart, NSUInteger selectionEnd) {
|
|
@@ -247,6 +247,16 @@ payload.setProperty(runtime, "height", event.height);
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
void EnrichedMarkdownTextInputEventEmitter::onPaste(OnPaste event) const {
|
|
251
|
+
dispatchEvent("paste", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
252
|
+
auto payload = jsi::Object(runtime);
|
|
253
|
+
payload.setProperty(runtime, "text", event.text);
|
|
254
|
+
payload.setProperty(runtime, "markdown", event.markdown);
|
|
255
|
+
return payload;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
250
260
|
void EnrichedMarkdownTextInputEventEmitter::onStartMention(OnStartMention event) const {
|
|
251
261
|
dispatchEvent("startMention", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
252
262
|
auto payload = jsi::Object(runtime);
|
|
@@ -182,6 +182,11 @@ class EnrichedMarkdownTextInputEventEmitter : public ViewEventEmitter {
|
|
|
182
182
|
double height;
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
+
struct OnPaste {
|
|
186
|
+
std::string text;
|
|
187
|
+
std::string markdown;
|
|
188
|
+
};
|
|
189
|
+
|
|
185
190
|
struct OnStartMention {
|
|
186
191
|
std::string indicator;
|
|
187
192
|
};
|
|
@@ -218,6 +223,8 @@ class EnrichedMarkdownTextInputEventEmitter : public ViewEventEmitter {
|
|
|
218
223
|
|
|
219
224
|
void onMathPress(OnMathPress value) const;
|
|
220
225
|
|
|
226
|
+
void onPaste(OnPaste value) const;
|
|
227
|
+
|
|
221
228
|
void onStartMention(OnStartMention value) const;
|
|
222
229
|
|
|
223
230
|
void onChangeMention(OnChangeMention value) const;
|
|
@@ -32,6 +32,7 @@ EnrichedMarkdownProps::EnrichedMarkdownProps(
|
|
|
32
32
|
streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})),
|
|
33
33
|
streamingConfig(convertRawProp(context, rawProps, "streamingConfig", sourceProps.streamingConfig, {})),
|
|
34
34
|
spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})),
|
|
35
|
+
contextMenuHidden(convertRawProp(context, rawProps, "contextMenuHidden", sourceProps.contextMenuHidden, {false})),
|
|
35
36
|
contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})),
|
|
36
37
|
selectionMenuConfig(convertRawProp(context, rawProps, "selectionMenuConfig", sourceProps.selectionMenuConfig, {})),
|
|
37
38
|
accessibilityLabels(convertRawProp(context, rawProps, "accessibilityLabels", sourceProps.accessibilityLabels, {})),
|
|
@@ -107,6 +108,10 @@ folly::dynamic EnrichedMarkdownProps::getDiffProps(
|
|
|
107
108
|
result["spoilerOverlay"] = spoilerOverlay;
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
if (contextMenuHidden != oldProps->contextMenuHidden) {
|
|
112
|
+
result["contextMenuHidden"] = contextMenuHidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
if (contextMenuItems != oldProps->contextMenuItems) {
|
|
111
116
|
result["contextMenuItems"] = toDynamic(contextMenuItems);
|
|
112
117
|
}
|
|
@@ -146,6 +151,7 @@ EnrichedMarkdownTextInputProps::EnrichedMarkdownTextInputProps(
|
|
|
146
151
|
scrollEnabled(convertRawProp(context, rawProps, "scrollEnabled", sourceProps.scrollEnabled, {false})),
|
|
147
152
|
blockSyntax(convertRawProp(context, rawProps, "blockSyntax", sourceProps.blockSyntax, {false})),
|
|
148
153
|
autoCapitalize(convertRawProp(context, rawProps, "autoCapitalize", sourceProps.autoCapitalize, {})),
|
|
154
|
+
interceptPaste(convertRawProp(context, rawProps, "interceptPaste", sourceProps.interceptPaste, {false})),
|
|
149
155
|
multiline(convertRawProp(context, rawProps, "multiline", sourceProps.multiline, {false})),
|
|
150
156
|
cursorColor(convertRawProp(context, rawProps, "cursorColor", sourceProps.cursorColor, {})),
|
|
151
157
|
selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
|
|
@@ -211,6 +217,10 @@ folly::dynamic EnrichedMarkdownTextInputProps::getDiffProps(
|
|
|
211
217
|
result["autoCapitalize"] = autoCapitalize;
|
|
212
218
|
}
|
|
213
219
|
|
|
220
|
+
if (interceptPaste != oldProps->interceptPaste) {
|
|
221
|
+
result["interceptPaste"] = interceptPaste;
|
|
222
|
+
}
|
|
223
|
+
|
|
214
224
|
if (multiline != oldProps->multiline) {
|
|
215
225
|
result["multiline"] = multiline;
|
|
216
226
|
}
|
|
@@ -295,6 +305,7 @@ EnrichedMarkdownTextProps::EnrichedMarkdownTextProps(
|
|
|
295
305
|
streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})),
|
|
296
306
|
streamingConfig(convertRawProp(context, rawProps, "streamingConfig", sourceProps.streamingConfig, {})),
|
|
297
307
|
spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})),
|
|
308
|
+
contextMenuHidden(convertRawProp(context, rawProps, "contextMenuHidden", sourceProps.contextMenuHidden, {false})),
|
|
298
309
|
contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})),
|
|
299
310
|
selectionMenuConfig(convertRawProp(context, rawProps, "selectionMenuConfig", sourceProps.selectionMenuConfig, {})),
|
|
300
311
|
accessibilityLabels(convertRawProp(context, rawProps, "accessibilityLabels", sourceProps.accessibilityLabels, {})),
|
|
@@ -370,6 +381,10 @@ folly::dynamic EnrichedMarkdownTextProps::getDiffProps(
|
|
|
370
381
|
result["spoilerOverlay"] = spoilerOverlay;
|
|
371
382
|
}
|
|
372
383
|
|
|
384
|
+
if (contextMenuHidden != oldProps->contextMenuHidden) {
|
|
385
|
+
result["contextMenuHidden"] = contextMenuHidden;
|
|
386
|
+
}
|
|
387
|
+
|
|
373
388
|
if (contextMenuItems != oldProps->contextMenuItems) {
|
|
374
389
|
result["contextMenuItems"] = toDynamic(contextMenuItems);
|
|
375
390
|
}
|
|
@@ -2579,6 +2579,7 @@ class EnrichedMarkdownProps final : public ViewProps {
|
|
|
2579
2579
|
bool streamingAnimation{false};
|
|
2580
2580
|
EnrichedMarkdownStreamingConfigStruct streamingConfig{};
|
|
2581
2581
|
std::string spoilerOverlay{std::string{"particles"}};
|
|
2582
|
+
bool contextMenuHidden{false};
|
|
2582
2583
|
std::vector<EnrichedMarkdownContextMenuItemsStruct> contextMenuItems{};
|
|
2583
2584
|
EnrichedMarkdownSelectionMenuConfigStruct selectionMenuConfig{};
|
|
2584
2585
|
EnrichedMarkdownAccessibilityLabelsStruct accessibilityLabels{};
|
|
@@ -3361,6 +3362,7 @@ class EnrichedMarkdownTextInputProps final : public ViewProps {
|
|
|
3361
3362
|
bool scrollEnabled{false};
|
|
3362
3363
|
bool blockSyntax{false};
|
|
3363
3364
|
std::string autoCapitalize{};
|
|
3365
|
+
bool interceptPaste{false};
|
|
3364
3366
|
bool multiline{false};
|
|
3365
3367
|
SharedColor cursorColor{};
|
|
3366
3368
|
SharedColor selectionColor{};
|
|
@@ -5948,6 +5950,7 @@ class EnrichedMarkdownTextProps final : public ViewProps {
|
|
|
5948
5950
|
bool streamingAnimation{false};
|
|
5949
5951
|
EnrichedMarkdownTextStreamingConfigStruct streamingConfig{};
|
|
5950
5952
|
std::string spoilerOverlay{std::string{"particles"}};
|
|
5953
|
+
bool contextMenuHidden{false};
|
|
5951
5954
|
std::vector<EnrichedMarkdownTextContextMenuItemsStruct> contextMenuItems{};
|
|
5952
5955
|
EnrichedMarkdownTextSelectionMenuConfigStruct selectionMenuConfig{};
|
|
5953
5956
|
EnrichedMarkdownTextAccessibilityLabelsStruct accessibilityLabels{};
|
|
@@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
36
36
|
- (void)updateMath:(NSInteger)start latex:(NSString *)latex displayMode:(BOOL)displayMode;
|
|
37
37
|
- (void)removeMath:(NSInteger)start;
|
|
38
38
|
- (void)copyToClipboard;
|
|
39
|
+
- (void)resolvePaste:(NSString *)text markdown:(NSString *)markdown;
|
|
39
40
|
- (void)requestMarkdown:(NSInteger)requestId;
|
|
40
41
|
- (void)requestCaretRect:(NSInteger)requestId;
|
|
41
42
|
@end
|
|
@@ -399,6 +400,34 @@ if ([commandName isEqualToString:@"copyToClipboard"]) {
|
|
|
399
400
|
return;
|
|
400
401
|
}
|
|
401
402
|
|
|
403
|
+
if ([commandName isEqualToString:@"resolvePaste"]) {
|
|
404
|
+
#if RCT_DEBUG
|
|
405
|
+
if ([args count] != 2) {
|
|
406
|
+
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownTextInput", commandName, (int)[args count], 2);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
#endif
|
|
410
|
+
|
|
411
|
+
NSObject *arg0 = args[0];
|
|
412
|
+
#if RCT_DEBUG
|
|
413
|
+
if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSString class], @"string", @"EnrichedMarkdownTextInput", commandName, @"1st")) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
#endif
|
|
417
|
+
NSString * text = (NSString *)arg0;
|
|
418
|
+
|
|
419
|
+
NSObject *arg1 = args[1];
|
|
420
|
+
#if RCT_DEBUG
|
|
421
|
+
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSString class], @"string", @"EnrichedMarkdownTextInput", commandName, @"2nd")) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
#endif
|
|
425
|
+
NSString * markdown = (NSString *)arg1;
|
|
426
|
+
|
|
427
|
+
[componentView resolvePaste:text markdown:markdown];
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
402
431
|
if ([commandName isEqualToString:@"requestMarkdown"]) {
|
|
403
432
|
#if RCT_DEBUG
|
|
404
433
|
if ([args count] != 1) {
|
|
@@ -46,12 +46,22 @@ NSString *const kENRMMarkdownPasteboardType = @"com.swmansion.enriched-markdown.
|
|
|
46
46
|
markdown = [[NSString alloc] initWithData:markdownValue encoding:NSUTF8StringEncoding];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
NSString *plainText = pasteboard.string;
|
|
50
|
+
|
|
51
|
+
// Both flavors are read before anything is decided, then nothing is written:
|
|
52
|
+
// JS owns the paste from here and must call resolvePaste. Mutating the buffer
|
|
53
|
+
// first would mean a handler that dies mid-flight leaves the selection gone
|
|
54
|
+
// with no text to show for it — a no-op paste is the visible failure.
|
|
55
|
+
if (self.markdownTextInput.interceptPaste && (plainText.length > 0 || markdown.length > 0)) {
|
|
56
|
+
[self.markdownTextInput emitOnPasteWithText:(plainText ?: @"") markdown:(markdown ?: @"")];
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
if (markdown.length > 0 && self.markdownTextInput != nil) {
|
|
50
61
|
[self.markdownTextInput pasteMarkdown:markdown];
|
|
51
62
|
return;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
NSString *plainText = pasteboard.string;
|
|
55
65
|
if (plainText.length > 0) {
|
|
56
66
|
[self replaceRange:self.selectedTextRange withText:plainText];
|
|
57
67
|
}
|
|
@@ -8,12 +8,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
8
8
|
|
|
9
9
|
@interface EnrichedMarkdownTextInput : RCTViewComponentView
|
|
10
10
|
@property (nonatomic, assign) BOOL blockEmitting;
|
|
11
|
+
@property (nonatomic, assign) BOOL interceptPaste;
|
|
11
12
|
- (CGSize)measureSize:(CGFloat)maxWidth;
|
|
12
13
|
- (nullable NSString *)markdownForSelectedRange;
|
|
13
14
|
- (void)copyToClipboard;
|
|
14
15
|
- (void)pasteMarkdown:(NSString *)markdown;
|
|
15
16
|
- (void)replaceSelectedTextWith:(NSString *)text formattingRanges:(NSArray *)ranges;
|
|
16
17
|
- (void)scheduleRelayoutIfNeeded;
|
|
18
|
+
// Paste interception. Declared here rather than in +Internal.h because
|
|
19
|
+
// ENRMInputTextView.mm only imports that header on macOS.
|
|
20
|
+
- (void)emitOnPasteWithText:(NSString *)text markdown:(NSString *)markdown;
|
|
21
|
+
- (void)resolvePaste:(NSString *)text markdown:(NSString *)markdown;
|
|
17
22
|
@end
|
|
18
23
|
|
|
19
24
|
NS_ASSUME_NONNULL_END
|
|
@@ -359,6 +359,35 @@ using namespace facebook::react;
|
|
|
359
359
|
|
|
360
360
|
#endif
|
|
361
361
|
|
|
362
|
+
#pragma mark - Paste
|
|
363
|
+
|
|
364
|
+
- (void)emitOnPasteWithText:(NSString *)text markdown:(NSString *)markdown
|
|
365
|
+
{
|
|
366
|
+
auto emitter = [self getEventEmitter];
|
|
367
|
+
if (emitter == nullptr)
|
|
368
|
+
return;
|
|
369
|
+
emitter->onPaste({
|
|
370
|
+
.text = std::string([text UTF8String] ?: ""),
|
|
371
|
+
.markdown = std::string([markdown UTF8String] ?: ""),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// The single exit for an intercepted paste. One command rather than two makes
|
|
376
|
+
/// "never swallow a paste" structural: the branch that turns the clipboard into
|
|
377
|
+
/// something else still has to come through here, with empty strings.
|
|
378
|
+
- (void)resolvePaste:(NSString *)text markdown:(NSString *)markdown
|
|
379
|
+
{
|
|
380
|
+
// Round-trips the formatting when the copy came from an input of ours;
|
|
381
|
+
// without this a copy-then-paste inside the app would land as flat text.
|
|
382
|
+
if (markdown.length > 0) {
|
|
383
|
+
[self pasteMarkdown:markdown];
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// Empty text with an empty selection is a zero-length edit: it still emits
|
|
387
|
+
// onChangeText with unchanged content, which the caller reads as a no-op.
|
|
388
|
+
[self replaceSelectedTextWith:text formattingRanges:@[]];
|
|
389
|
+
}
|
|
390
|
+
|
|
362
391
|
#pragma mark - State
|
|
363
392
|
|
|
364
393
|
- (void)updateState:(const facebook::react::State::Shared &)state
|
|
@@ -456,6 +485,10 @@ using namespace facebook::react;
|
|
|
456
485
|
}
|
|
457
486
|
}
|
|
458
487
|
|
|
488
|
+
if (newViewProps.interceptPaste != oldViewProps.interceptPaste) {
|
|
489
|
+
_interceptPaste = newViewProps.interceptPaste;
|
|
490
|
+
}
|
|
491
|
+
|
|
459
492
|
if (newViewProps.multiline != oldViewProps.multiline) {
|
|
460
493
|
_textView.textContainer.maximumNumberOfLines = newViewProps.multiline ? 0 : 1;
|
|
461
494
|
_textView.textContainer.lineBreakMode =
|
|
@@ -23,6 +23,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
23
23
|
@property (nonatomic, copy, nullable, getter=menuCopyLabel) NSString *copyLabel;
|
|
24
24
|
@property (nonatomic, copy, nullable, getter=menuCopyAsMarkdownLabel) NSString *copyAsMarkdownLabel;
|
|
25
25
|
|
|
26
|
+
/// Suppresses the long-press copy menu on this block. Mirrors the
|
|
27
|
+
/// `contextMenuHidden` prop; selection and link handling are unaffected.
|
|
28
|
+
@property (nonatomic, assign) BOOL contextMenuHidden;
|
|
29
|
+
|
|
26
30
|
@end
|
|
27
31
|
|
|
28
32
|
NS_ASSUME_NONNULL_END
|
|
@@ -117,6 +117,11 @@
|
|
|
117
117
|
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|
|
118
118
|
configurationForMenuAtLocation:(CGPoint)location
|
|
119
119
|
{
|
|
120
|
+
// Returning nil is the UIKit-sanctioned way to suppress a context menu.
|
|
121
|
+
if (self.contextMenuHidden) {
|
|
122
|
+
return nil;
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
return [UIContextMenuConfiguration
|
|
121
126
|
configurationWithIdentifier:nil
|
|
122
127
|
previewProvider:nil
|
|
@@ -141,6 +146,12 @@
|
|
|
141
146
|
#if TARGET_OS_OSX
|
|
142
147
|
- (NSMenu *)menuForEvent:(NSEvent *)event
|
|
143
148
|
{
|
|
149
|
+
// This override never falls through to [super menuForEvent:], so nil fully
|
|
150
|
+
// suppresses the menu rather than restoring the AppKit default.
|
|
151
|
+
if (self.contextMenuHidden) {
|
|
152
|
+
return nil;
|
|
153
|
+
}
|
|
154
|
+
|
|
144
155
|
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
|
|
145
156
|
[menu addItem:ENRMCreateMenuItem(self.copyLabel, ^{ [self copyLatexToPasteboard]; })];
|
|
146
157
|
[menu addItem:ENRMCreateMenuItem(self.copyAsMarkdownLabel, ^{ [self copyMarkdownToPasteboard]; })];
|