react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +60 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +31 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +50 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +19 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +7 -5
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +6 -4
- package/docs/API_REFERENCE.md +60 -4
- package/docs/COPY_OPTIONS.md +40 -0
- package/ios/EnrichedMarkdown.mm +63 -8
- package/ios/EnrichedMarkdownText.mm +26 -5
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +60 -0
- package/ios/utils/ENRMBlockMenu.h +66 -0
- package/ios/utils/EditMenuUtils.h +32 -6
- package/ios/utils/EditMenuUtils.m +51 -14
- package/ios/views/ENRMCodeBlockContainerView.h +13 -2
- package/ios/views/ENRMCodeBlockContainerView.m +19 -19
- package/ios/views/ENRMMathContainerView.h +8 -0
- package/ios/views/ENRMMathContainerView.m +10 -18
- package/ios/views/TableContainerView.h +8 -0
- package/ios/views/TableContainerView.m +10 -18
- package/lib/module/EnrichedMarkdownNativeComponent.ts +24 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +24 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +29 -2
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +17 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +17 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +53 -10
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +22 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +24 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +24 -0
- package/src/index.tsx +1 -0
- package/src/native/EnrichedMarkdownText.tsx +37 -1
- package/src/types/MarkdownTextProps.ts +50 -10
- package/src/types/events.ts +23 -0
|
@@ -64,6 +64,7 @@ class TableContainerView(
|
|
|
64
64
|
var copyLabel: String = ""
|
|
65
65
|
var copyAsMarkdownLabel: String = ""
|
|
66
66
|
var contextMenuHidden: Boolean = false
|
|
67
|
+
var menuOptions: BlockMenuOptions = BlockMenuOptions()
|
|
67
68
|
|
|
68
69
|
private val scrollView =
|
|
69
70
|
HorizontalScrollView(context).apply {
|
|
@@ -366,8 +367,8 @@ class TableContainerView(
|
|
|
366
367
|
if (contextMenuHidden) return
|
|
367
368
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
368
369
|
ContextMenuPopup.show(anchor, this) {
|
|
369
|
-
|
|
370
|
-
val plainText =
|
|
370
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, markdown = { tableMarkdown }, rawText = ::buildPlainText, copyRaw = {
|
|
371
|
+
val plainText = buildPlainText()
|
|
371
372
|
if (plainText.isNotEmpty()) {
|
|
372
373
|
val displayMetrics = context.resources.displayMetrics
|
|
373
374
|
val tableRows =
|
|
@@ -377,13 +378,14 @@ class TableContainerView(
|
|
|
377
378
|
val html = HTMLGenerator.generateTableHTML(tableRows, styleConfig, displayMetrics.scaledDensity, displayMetrics.density)
|
|
378
379
|
clipboard.setPrimaryClip(ClipData.newHtmlText("Table", plainText, html))
|
|
379
380
|
}
|
|
380
|
-
}
|
|
381
|
-
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel) {
|
|
381
|
+
}, copyMarkdown = {
|
|
382
382
|
if (tableMarkdown.isNotEmpty()) clipboard.setPrimaryClip(ClipData.newPlainText("Table", tableMarkdown))
|
|
383
|
-
}
|
|
383
|
+
})
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
private fun buildPlainText(): String = rows.joinToString("\n") { row -> row.joinToString("\t") { it.plainText } }
|
|
388
|
+
|
|
387
389
|
private fun buildMarkdownFromRows(): String =
|
|
388
390
|
rows.joinToString("") { row ->
|
|
389
391
|
val line = "| ${row.joinToString(" | ") { it.markdownText }} |\n"
|
|
@@ -42,6 +42,7 @@ class MathContainerView(
|
|
|
42
42
|
var copyLabel: String = ""
|
|
43
43
|
var copyAsMarkdownLabel: String = ""
|
|
44
44
|
var contextMenuHidden: Boolean = false
|
|
45
|
+
var menuOptions: BlockMenuOptions = BlockMenuOptions()
|
|
45
46
|
|
|
46
47
|
override val segmentMarginTop: Int get() = mathStyle.marginTop.toInt()
|
|
47
48
|
override val segmentMarginBottom: Int get() = mathStyle.marginBottom.toInt()
|
|
@@ -121,12 +122,13 @@ class MathContainerView(
|
|
|
121
122
|
if (contextMenuHidden) return
|
|
122
123
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
123
124
|
ContextMenuPopup.show(anchor, this) {
|
|
124
|
-
|
|
125
|
+
blockCopyItems(menuOptions, copyLabel, copyAsMarkdownLabel, markdown = {
|
|
126
|
+
"$$\n$cachedLatex\n$$"
|
|
127
|
+
}, rawText = { cachedLatex }, copyRaw = {
|
|
125
128
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", cachedLatex))
|
|
126
|
-
}
|
|
127
|
-
item(ContextMenuPopup.Icon.DOCUMENT, copyAsMarkdownLabel) {
|
|
129
|
+
}, copyMarkdown = {
|
|
128
130
|
clipboard.setPrimaryClip(ClipData.newPlainText("Math", "$$\n$cachedLatex\n$$"))
|
|
129
|
-
}
|
|
131
|
+
})
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -122,6 +122,54 @@ Only fires when `flavor="github"` — the copy button is part of the GitHub flav
|
|
|
122
122
|
/>
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### `onCopy`
|
|
126
|
+
|
|
127
|
+
Takes over the **Copy** item of the text selection menu and of the table, math and code block menus. When set, native does **not** write the clipboard: it calls `onCopy` with the copied content and your handler writes whatever it wants — for example with [`expo-clipboard`](https://docs.expo.dev/versions/latest/sdk/clipboard/). Use it to post-process what users copy (strip formatting, drop link targets…) without a native change.
|
|
128
|
+
|
|
129
|
+
| Type | Default Value | Platform |
|
|
130
|
+
| ----------------------------- | ------------- | -------- |
|
|
131
|
+
| `(event: CopyEvent) => void` | - | Both |
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
interface CopyEvent {
|
|
135
|
+
/** Which menu the Copy item belongs to. */
|
|
136
|
+
source: 'selection' | 'math' | 'table' | 'code';
|
|
137
|
+
/** What `copy.format: 'markdown'` would write. */
|
|
138
|
+
markdown: string;
|
|
139
|
+
/** Rendered text of the selection, or the block's raw content. */
|
|
140
|
+
text: string;
|
|
141
|
+
/** The selection spans the whole component (`markdown` is the source). Always true for blocks. */
|
|
142
|
+
whole: boolean;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| `source` | `markdown` | `text` |
|
|
147
|
+
| ----------- | -------------------------------------------------------------------------- | ----------------------------- |
|
|
148
|
+
| `selection` | Original source when `whole`, otherwise rebuilt from the rendered styles | Selected text as rendered |
|
|
149
|
+
| `math` | `$$…$$` | LaTeX |
|
|
150
|
+
| `table` | The table as Markdown | Tab-separated cells |
|
|
151
|
+
| `code` | Fenced code block | Raw code |
|
|
152
|
+
|
|
153
|
+
- `onCopy` overrides `selectionMenuConfig.copy.format`; `copy.enabled: false` still hides the item, so no event fires.
|
|
154
|
+
- **Copy as Markdown**, the code block header copy button and the VoiceOver copy action are unaffected. `onCopyPress` does not fire for an intercepted Copy.
|
|
155
|
+
- With `flavor="github"` the text is split into segments around blocks, so a selection is `whole` only when the markdown renders as a single text segment.
|
|
156
|
+
- A rebuilt `markdown` wraps each styled run separately (`**a *****b***** c**`) and does not escape literal `*` / `_`.
|
|
157
|
+
- Copying with a hardware keyboard (Cmd+C / Ctrl+C) does not go through the menu and is not intercepted.
|
|
158
|
+
- Ignored on macOS. On web the prop is not this API: it reaches the container `div` as the DOM `onCopy` clipboard event.
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import * as Clipboard from 'expo-clipboard';
|
|
164
|
+
|
|
165
|
+
<EnrichedMarkdownText
|
|
166
|
+
markdown={content}
|
|
167
|
+
onCopy={({ source, markdown }) => {
|
|
168
|
+
Clipboard.setStringAsync(source === 'selection' ? stripFormatting(markdown) : markdown);
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
125
173
|
### `enableLinkPreview`
|
|
126
174
|
|
|
127
175
|
Controls the native link preview on long press (iOS only). Automatically set to `false` when `onLinkLongPress` is provided.
|
|
@@ -469,7 +517,7 @@ Controls built-in actions added to the native text selection menu. Custom app-pr
|
|
|
469
517
|
| -------------------- | ---------------------------------------------- | -------- |
|
|
470
518
|
| `SelectionMenuConfig` | `{}` (see shape below for per-field defaults) | iOS, Android, macOS |
|
|
471
519
|
|
|
472
|
-
Each item takes an object: `{ enabled }` toggles visibility
|
|
520
|
+
Each item takes an object: `{ enabled }` toggles visibility and `label` overrides the English default. Items and labels apply to the main text selection menu as well as the table, math and code block copy menus. `copy.enabled`, `copy.format` and `systemItems` are ignored on macOS.
|
|
473
521
|
|
|
474
522
|
> **Deprecation:** the previous boolean shape (`copyAsMarkdown: false`) is still accepted at runtime for backward compatibility but logs a one-time warning. It will be removed in 0.8 — migrate to `{ enabled: false }`.
|
|
475
523
|
|
|
@@ -477,9 +525,13 @@ Each item takes an object: `{ enabled }` toggles visibility (the system `copy` i
|
|
|
477
525
|
|
|
478
526
|
```ts
|
|
479
527
|
interface SelectionMenuConfig {
|
|
480
|
-
/**
|
|
481
|
-
|
|
482
|
-
|
|
528
|
+
/**
|
|
529
|
+
* "Copy" item. `format: 'markdown'` copies Markdown as plain text instead of
|
|
530
|
+
* rich text (text selections) or the raw content (blocks).
|
|
531
|
+
* @default { enabled: true, label: "Copy", format: "rich" }
|
|
532
|
+
*/
|
|
533
|
+
copy?: { enabled?: boolean; label?: string; format?: 'rich' | 'markdown' };
|
|
534
|
+
/** "Copy as Markdown" action, in the text and block menus. @default { enabled: true, label: "Copy as Markdown" } */
|
|
483
535
|
copyAsMarkdown?: { enabled?: boolean; label?: string };
|
|
484
536
|
/** "Copy Image URL" action, shown when the selection contains images. */
|
|
485
537
|
copyImageUrl?: {
|
|
@@ -489,6 +541,10 @@ interface SelectionMenuConfig {
|
|
|
489
541
|
/** Forms for multiple images, chosen with Intl.PluralRules. @default { other: "Copy {count} Image URLs" } */
|
|
490
542
|
pluralLabels?: SelectionMenuPluralLabels;
|
|
491
543
|
};
|
|
544
|
+
/** `'none'` drops every system item (Select All, Look Up, Share, Translate…). @default 'default' */
|
|
545
|
+
systemItems?: 'default' | 'none';
|
|
546
|
+
/** Copy button in the code block header. @default { enabled: true } */
|
|
547
|
+
codeBlockCopyButton?: { enabled?: boolean };
|
|
492
548
|
}
|
|
493
549
|
|
|
494
550
|
interface SelectionMenuPluralLabels {
|
package/docs/COPY_OPTIONS.md
CHANGED
|
@@ -44,6 +44,46 @@ Use `selectionMenuConfig` to hide built-in selection menu actions while keeping
|
|
|
44
44
|
/>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Copy as Markdown only, no system items
|
|
48
|
+
|
|
49
|
+
`copy` can also be hidden (`{ enabled: false }`) or switched to Markdown with
|
|
50
|
+
`format: 'markdown'`, and `systemItems: 'none'` drops everything the OS adds
|
|
51
|
+
(Select All, Look Up, Share, Translate, app text actions…):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<EnrichedMarkdownText
|
|
55
|
+
markdown={content}
|
|
56
|
+
selectionMenuConfig={{
|
|
57
|
+
copy: { format: 'markdown' }, // inline math → $…$, plain text only
|
|
58
|
+
copyAsMarkdown: { enabled: false },
|
|
59
|
+
systemItems: 'none',
|
|
60
|
+
codeBlockCopyButton: { enabled: false },
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The same `copy` and `copyAsMarkdown` settings drive the long-press menus on
|
|
66
|
+
table, math and code blocks: with `format: 'markdown'` their Copy item writes
|
|
67
|
+
the block's Markdown (`$$…$$`, a fenced code block, a Markdown table). On code
|
|
68
|
+
blocks, the header copy button and the VoiceOver copy action always copy the raw
|
|
69
|
+
code and fire `onCopyPress`; `codeBlockCopyButton: { enabled: false }` hides the
|
|
70
|
+
button, and `copy: { enabled: false }` also removes the VoiceOver action.
|
|
71
|
+
`copy.enabled`, `copy.format` and `systemItems` are ignored on macOS.
|
|
72
|
+
|
|
73
|
+
### Handling Copy in JS
|
|
74
|
+
|
|
75
|
+
To decide in JS what lands on the clipboard, pass `onCopy`. The Copy item
|
|
76
|
+
(text selection and block menus) then writes nothing itself and hands you the
|
|
77
|
+
content — `markdown`, `text`, `source` and `whole` — to write with your
|
|
78
|
+
clipboard library. See [`onCopy`](API_REFERENCE.md#oncopy).
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<EnrichedMarkdownText
|
|
82
|
+
markdown={content}
|
|
83
|
+
onCopy={({ markdown }) => Clipboard.setStringAsync(markdown)}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
47
87
|
To turn the menu off entirely rather than item by item, use `contextMenuHidden`:
|
|
48
88
|
|
|
49
89
|
```tsx
|
package/ios/EnrichedMarkdown.mm
CHANGED
|
@@ -75,6 +75,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
75
75
|
- (void)emitLinkLongPress:(NSString *)url;
|
|
76
76
|
- (void)emitTaskListItemPress:(NSInteger)index checked:(BOOL)checked text:(NSString *)text;
|
|
77
77
|
- (void)emitCopyPress:(NSString *)code language:(NSString *)language;
|
|
78
|
+
- (void)emitCopyRequest:(NSString *)source markdown:(NSString *)markdown text:(NSString *)text whole:(BOOL)whole;
|
|
78
79
|
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
79
80
|
selectedText:(NSString *)selectedText
|
|
80
81
|
selectionStart:(NSUInteger)selectionStart
|
|
@@ -173,7 +174,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
173
174
|
_tableStreamingMode = ENRMTableStreamingModeProgressive;
|
|
174
175
|
_codeBlockStreamingMode = ENRMCodeBlockStreamingModeProgressive;
|
|
175
176
|
_contextMenuHidden = NO;
|
|
176
|
-
_selectionMenuConfig = (
|
|
177
|
+
_selectionMenuConfig = ENRMDefaultSelectionMenuConfig();
|
|
177
178
|
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
178
179
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
179
180
|
_resolvedLayoutDirection =
|
|
@@ -505,9 +506,30 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
505
506
|
}
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
- (ENRMBlockMenuOptions)blockMenuOptions
|
|
510
|
+
{
|
|
511
|
+
return (ENRMBlockMenuOptions){
|
|
512
|
+
.copyHidden = !_selectionMenuConfig.copyEnabled,
|
|
513
|
+
.copyWritesMarkdown = _selectionMenuConfig.copyFormatMarkdown,
|
|
514
|
+
.copyAsMarkdownHidden = !_selectionMenuConfig.copyAsMarkdown,
|
|
515
|
+
.copyButtonHidden = !_selectionMenuConfig.codeBlockCopyButton,
|
|
516
|
+
.interceptCopy = _selectionMenuConfig.interceptCopy,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
- (ENRMBlockCopyRequestBlock)blockCopyRequestForSource:(NSString *)source
|
|
521
|
+
{
|
|
522
|
+
__weak EnrichedMarkdown *weakSelf = self;
|
|
523
|
+
return ^(NSString *markdown, NSString *text) {
|
|
524
|
+
EnrichedMarkdown *strongSelf = weakSelf;
|
|
525
|
+
if (strongSelf)
|
|
526
|
+
[strongSelf emitCopyRequest:source markdown:markdown text:text whole:YES];
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Table, math and code block views cache the copy labels and menu options at
|
|
531
|
+
// creation time, so re-push them on prop updates (e.g. a language change
|
|
532
|
+
// without a remount) to avoid stale values.
|
|
511
533
|
- (void)pushSelectionMenuLabelsToSegments
|
|
512
534
|
{
|
|
513
535
|
for (RCTUIView *segment in _segmentViews) {
|
|
@@ -516,6 +538,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
516
538
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
517
539
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
518
540
|
tableView.contextMenuHidden = _contextMenuHidden;
|
|
541
|
+
tableView.menuOptions = [self blockMenuOptions];
|
|
519
542
|
}
|
|
520
543
|
#if ENRICHED_MARKDOWN_MATH
|
|
521
544
|
else if ([segment isKindOfClass:[ENRMMathContainerView class]]) {
|
|
@@ -523,6 +546,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
523
546
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
524
547
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
525
548
|
mathView.contextMenuHidden = _contextMenuHidden;
|
|
549
|
+
mathView.menuOptions = [self blockMenuOptions];
|
|
526
550
|
}
|
|
527
551
|
#endif
|
|
528
552
|
else if ([segment isKindOfClass:[ENRMCodeBlockContainerView class]]) {
|
|
@@ -530,6 +554,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
530
554
|
codeBlockView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
531
555
|
codeBlockView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
532
556
|
codeBlockView.contextMenuHidden = _contextMenuHidden;
|
|
557
|
+
codeBlockView.menuOptions = [self blockMenuOptions];
|
|
533
558
|
}
|
|
534
559
|
}
|
|
535
560
|
}
|
|
@@ -807,6 +832,8 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
807
832
|
tableView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
808
833
|
tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
809
834
|
tableView.contextMenuHidden = _contextMenuHidden;
|
|
835
|
+
tableView.menuOptions = [self blockMenuOptions];
|
|
836
|
+
tableView.onCopyRequest = [self blockCopyRequestForSource:@"table"];
|
|
810
837
|
|
|
811
838
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
812
839
|
|
|
@@ -847,6 +874,8 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
847
874
|
mathView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
848
875
|
mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
849
876
|
mathView.contextMenuHidden = _contextMenuHidden;
|
|
877
|
+
mathView.menuOptions = [self blockMenuOptions];
|
|
878
|
+
mathView.onCopyRequest = [self blockCopyRequestForSource:@"math"];
|
|
850
879
|
[mathView applyLatex:mathSegment.latex];
|
|
851
880
|
return mathView;
|
|
852
881
|
}
|
|
@@ -858,6 +887,8 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
858
887
|
codeBlockView.copyLabel = _selectionMenuLabels.copyLabel;
|
|
859
888
|
codeBlockView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
|
|
860
889
|
codeBlockView.contextMenuHidden = _contextMenuHidden;
|
|
890
|
+
codeBlockView.menuOptions = [self blockMenuOptions];
|
|
891
|
+
codeBlockView.onCopyRequest = [self blockCopyRequestForSource:@"code"];
|
|
861
892
|
|
|
862
893
|
__weak EnrichedMarkdown *weakSelf = self;
|
|
863
894
|
codeBlockView.onCopyPress = ^(NSString *code, NSString *language) {
|
|
@@ -1026,9 +1057,7 @@ static char kENRMSegmentFadeAnimatorKey;
|
|
|
1026
1057
|
}
|
|
1027
1058
|
|
|
1028
1059
|
_selectionMenuLabels = ENRMParseSelectionMenuLabels(newViewProps.selectionMenuConfig);
|
|
1029
|
-
_selectionMenuConfig =
|
|
1030
|
-
ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig.copyAsMarkdown,
|
|
1031
|
-
newViewProps.selectionMenuConfig.copyImageUrl);
|
|
1060
|
+
_selectionMenuConfig = ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig);
|
|
1032
1061
|
[self pushSelectionMenuLabelsToSegments];
|
|
1033
1062
|
|
|
1034
1063
|
if (ENRMAccessibilityLabelsChanged(oldViewProps.accessibilityLabels, newViewProps.accessibilityLabels)) {
|
|
@@ -1274,6 +1303,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownCls(void)
|
|
|
1274
1303
|
{.code = std::string(code.UTF8String ?: ""), .language = std::string(language.UTF8String ?: "")});
|
|
1275
1304
|
}
|
|
1276
1305
|
|
|
1306
|
+
- (void)emitCopyRequest:(NSString *)source markdown:(NSString *)markdown text:(NSString *)text whole:(BOOL)whole
|
|
1307
|
+
{
|
|
1308
|
+
auto emitter = std::static_pointer_cast<EnrichedMarkdownEventEmitter const>(_eventEmitter);
|
|
1309
|
+
if (emitter)
|
|
1310
|
+
emitter->onCopyRequest({
|
|
1311
|
+
.source = std::string(source.UTF8String),
|
|
1312
|
+
.markdown = std::string(markdown.UTF8String ?: ""),
|
|
1313
|
+
.text = std::string(text.UTF8String ?: ""),
|
|
1314
|
+
.whole = (bool)whole,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1277
1318
|
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
1278
1319
|
selectedText:(NSString *)selectedText
|
|
1279
1320
|
selectionStart:(NSUInteger)selectionStart
|
|
@@ -1341,8 +1382,22 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownCls(void)
|
|
|
1341
1382
|
ENRMBuildContextMenuActions(_contextMenuItemTexts, _contextMenuItemIcons, textView, range, handler);
|
|
1342
1383
|
|
|
1343
1384
|
NSString *segmentMarkdown = extractMarkdownFromAttributedString(textView.attributedText, range);
|
|
1385
|
+
|
|
1386
|
+
// Segments only see their own text, so a selection is the whole component
|
|
1387
|
+
// only when it spans the sole segment; the markdown is then the original.
|
|
1388
|
+
ENRMCopyRequestHandler onCopyRequest = ^(NSString *markdown, NSString *text, BOOL fullSelection) {
|
|
1389
|
+
EnrichedMarkdown *strongSelf = weakSelf;
|
|
1390
|
+
if (!strongSelf)
|
|
1391
|
+
return;
|
|
1392
|
+
BOOL whole = fullSelection && strongSelf->_segmentViews.count == 1 && strongSelf->_cachedMarkdown.length > 0;
|
|
1393
|
+
[strongSelf emitCopyRequest:@"selection"
|
|
1394
|
+
markdown:whole ? strongSelf->_cachedMarkdown : markdown
|
|
1395
|
+
text:text
|
|
1396
|
+
whole:whole];
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1344
1399
|
return buildEditMenuForSelection(textView.attributedText, range, segmentMarkdown, _config, suggestedActions,
|
|
1345
|
-
customActions, _selectionMenuConfig);
|
|
1400
|
+
customActions, _selectionMenuConfig, onCopyRequest);
|
|
1346
1401
|
}
|
|
1347
1402
|
|
|
1348
1403
|
- (BOOL)textView:(UITextView *)textView
|
|
@@ -61,6 +61,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
61
61
|
selectedText:(NSString *)selectedText
|
|
62
62
|
selectionStart:(NSUInteger)selectionStart
|
|
63
63
|
selectionEnd:(NSUInteger)selectionEnd;
|
|
64
|
+
- (void)emitCopyRequest:(NSString *)source markdown:(NSString *)markdown text:(NSString *)text whole:(BOOL)whole;
|
|
64
65
|
@end
|
|
65
66
|
|
|
66
67
|
@implementation EnrichedMarkdownText {
|
|
@@ -239,7 +240,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
239
240
|
_enableTaskListItemToggle = YES;
|
|
240
241
|
_forceHeightUpdateOnNextRender = NO;
|
|
241
242
|
_contextMenuHidden = NO;
|
|
242
|
-
_selectionMenuConfig = (
|
|
243
|
+
_selectionMenuConfig = ENRMDefaultSelectionMenuConfig();
|
|
243
244
|
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
244
245
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
245
246
|
_resolvedLayoutDirection =
|
|
@@ -577,9 +578,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
|
577
578
|
}
|
|
578
579
|
|
|
579
580
|
_selectionMenuLabels = ENRMParseSelectionMenuLabels(newViewProps.selectionMenuConfig);
|
|
580
|
-
_selectionMenuConfig =
|
|
581
|
-
ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig.copyAsMarkdown,
|
|
582
|
-
newViewProps.selectionMenuConfig.copyImageUrl);
|
|
581
|
+
_selectionMenuConfig = ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig);
|
|
583
582
|
|
|
584
583
|
if (ENRMAccessibilityLabelsChanged(oldViewProps.accessibilityLabels, newViewProps.accessibilityLabels)) {
|
|
585
584
|
_accessibilityLabels = [[ENRMAccessibilityLabels alloc] init];
|
|
@@ -750,6 +749,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
|
|
|
750
749
|
emitter->onTaskListItemPress({.index = (int)index, .checked = checked, .text = std::string(text.UTF8String ?: "")});
|
|
751
750
|
}
|
|
752
751
|
|
|
752
|
+
- (void)emitCopyRequest:(NSString *)source markdown:(NSString *)markdown text:(NSString *)text whole:(BOOL)whole
|
|
753
|
+
{
|
|
754
|
+
auto emitter = std::static_pointer_cast<EnrichedMarkdownTextEventEmitter const>(_eventEmitter);
|
|
755
|
+
if (emitter)
|
|
756
|
+
emitter->onCopyRequest({
|
|
757
|
+
.source = std::string(source.UTF8String),
|
|
758
|
+
.markdown = std::string(markdown.UTF8String ?: ""),
|
|
759
|
+
.text = std::string(text.UTF8String ?: ""),
|
|
760
|
+
.whole = (bool)whole,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
753
764
|
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
754
765
|
selectedText:(NSString *)selectedText
|
|
755
766
|
selectionStart:(NSUInteger)selectionStart
|
|
@@ -832,8 +843,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
|
|
|
832
843
|
NSMutableArray<UIAction *> *customActions =
|
|
833
844
|
ENRMBuildContextMenuActions(_contextMenuItemTexts, _contextMenuItemIcons, textView, range, handler);
|
|
834
845
|
|
|
846
|
+
// A full selection already carries the cached (original) markdown.
|
|
847
|
+
ENRMCopyRequestHandler onCopyRequest = ^(NSString *markdown, NSString *text, BOOL fullSelection) {
|
|
848
|
+
EnrichedMarkdownText *strongSelf = weakSelf;
|
|
849
|
+
if (strongSelf)
|
|
850
|
+
[strongSelf emitCopyRequest:@"selection"
|
|
851
|
+
markdown:markdown
|
|
852
|
+
text:text
|
|
853
|
+
whole:fullSelection && strongSelf->_cachedMarkdown.length > 0];
|
|
854
|
+
};
|
|
855
|
+
|
|
835
856
|
return buildEditMenuForSelection(textView.attributedText, range, _cachedMarkdown, _config, suggestedActions,
|
|
836
|
-
customActions, _selectionMenuConfig);
|
|
857
|
+
customActions, _selectionMenuConfig, onCopyRequest);
|
|
837
858
|
}
|
|
838
859
|
#endif
|
|
839
860
|
|
|
@@ -52,6 +52,18 @@ payload.setProperty(runtime, "language", event.language);
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
void EnrichedMarkdownEventEmitter::onCopyRequest(OnCopyRequest event) const {
|
|
56
|
+
dispatchEvent("copyRequest", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
57
|
+
auto payload = jsi::Object(runtime);
|
|
58
|
+
payload.setProperty(runtime, "source", event.source);
|
|
59
|
+
payload.setProperty(runtime, "markdown", event.markdown);
|
|
60
|
+
payload.setProperty(runtime, "text", event.text);
|
|
61
|
+
payload.setProperty(runtime, "whole", event.whole);
|
|
62
|
+
return payload;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
55
67
|
void EnrichedMarkdownEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const {
|
|
56
68
|
dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
57
69
|
auto payload = jsi::Object(runtime);
|
|
@@ -379,6 +391,18 @@ payload.setProperty(runtime, "language", event.language);
|
|
|
379
391
|
}
|
|
380
392
|
|
|
381
393
|
|
|
394
|
+
void EnrichedMarkdownTextEventEmitter::onCopyRequest(OnCopyRequest event) const {
|
|
395
|
+
dispatchEvent("copyRequest", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
396
|
+
auto payload = jsi::Object(runtime);
|
|
397
|
+
payload.setProperty(runtime, "source", event.source);
|
|
398
|
+
payload.setProperty(runtime, "markdown", event.markdown);
|
|
399
|
+
payload.setProperty(runtime, "text", event.text);
|
|
400
|
+
payload.setProperty(runtime, "whole", event.whole);
|
|
401
|
+
return payload;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
382
406
|
void EnrichedMarkdownTextEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const {
|
|
383
407
|
dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) {
|
|
384
408
|
auto payload = jsi::Object(runtime);
|
|
@@ -36,6 +36,13 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
|
|
|
36
36
|
std::string language;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
struct OnCopyRequest {
|
|
40
|
+
std::string source;
|
|
41
|
+
std::string markdown;
|
|
42
|
+
std::string text;
|
|
43
|
+
bool whole;
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
struct OnContextMenuItemPress {
|
|
40
47
|
std::string itemText;
|
|
41
48
|
std::string selectedText;
|
|
@@ -50,6 +57,8 @@ class EnrichedMarkdownEventEmitter : public ViewEventEmitter {
|
|
|
50
57
|
|
|
51
58
|
void onCopyPress(OnCopyPress value) const;
|
|
52
59
|
|
|
60
|
+
void onCopyRequest(OnCopyRequest value) const;
|
|
61
|
+
|
|
53
62
|
void onContextMenuItemPress(OnContextMenuItemPress value) const;
|
|
54
63
|
};
|
|
55
64
|
class EnrichedMarkdownTextInputEventEmitter : public ViewEventEmitter {
|
|
@@ -303,6 +312,13 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
|
|
|
303
312
|
std::string language;
|
|
304
313
|
};
|
|
305
314
|
|
|
315
|
+
struct OnCopyRequest {
|
|
316
|
+
std::string source;
|
|
317
|
+
std::string markdown;
|
|
318
|
+
std::string text;
|
|
319
|
+
bool whole;
|
|
320
|
+
};
|
|
321
|
+
|
|
306
322
|
struct OnContextMenuItemPress {
|
|
307
323
|
std::string itemText;
|
|
308
324
|
std::string selectedText;
|
|
@@ -317,6 +333,8 @@ class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter {
|
|
|
317
333
|
|
|
318
334
|
void onCopyPress(OnCopyPress value) const;
|
|
319
335
|
|
|
336
|
+
void onCopyRequest(OnCopyRequest value) const;
|
|
337
|
+
|
|
320
338
|
void onContextMenuItemPress(OnContextMenuItemPress value) const;
|
|
321
339
|
};
|
|
322
340
|
} // namespace facebook::react
|
|
@@ -2462,6 +2462,11 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2462
2462
|
std::string copyImageUrlLabel{};
|
|
2463
2463
|
std::string copyImageUrlsLabel{};
|
|
2464
2464
|
std::vector<std::string> copyImageUrlPluralTemplates{};
|
|
2465
|
+
bool copyEnabled{false};
|
|
2466
|
+
bool copyFormatMarkdown{false};
|
|
2467
|
+
bool systemItemsNone{false};
|
|
2468
|
+
bool codeBlockCopyButton{false};
|
|
2469
|
+
bool interceptCopy{false};
|
|
2465
2470
|
|
|
2466
2471
|
|
|
2467
2472
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -2476,6 +2481,11 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
|
|
|
2476
2481
|
result["copyImageUrlLabel"] = copyImageUrlLabel;
|
|
2477
2482
|
result["copyImageUrlsLabel"] = copyImageUrlsLabel;
|
|
2478
2483
|
result["copyImageUrlPluralTemplates"] = ::facebook::react::toDynamic(copyImageUrlPluralTemplates);
|
|
2484
|
+
result["copyEnabled"] = copyEnabled;
|
|
2485
|
+
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
2486
|
+
result["systemItemsNone"] = systemItemsNone;
|
|
2487
|
+
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
2488
|
+
result["interceptCopy"] = interceptCopy;
|
|
2479
2489
|
return result;
|
|
2480
2490
|
}
|
|
2481
2491
|
#endif
|
|
@@ -2512,6 +2522,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
2512
2522
|
if (tmp_copyImageUrlPluralTemplates != map.end()) {
|
|
2513
2523
|
fromRawValue(context, tmp_copyImageUrlPluralTemplates->second, result.copyImageUrlPluralTemplates);
|
|
2514
2524
|
}
|
|
2525
|
+
auto tmp_copyEnabled = map.find("copyEnabled");
|
|
2526
|
+
if (tmp_copyEnabled != map.end()) {
|
|
2527
|
+
fromRawValue(context, tmp_copyEnabled->second, result.copyEnabled);
|
|
2528
|
+
}
|
|
2529
|
+
auto tmp_copyFormatMarkdown = map.find("copyFormatMarkdown");
|
|
2530
|
+
if (tmp_copyFormatMarkdown != map.end()) {
|
|
2531
|
+
fromRawValue(context, tmp_copyFormatMarkdown->second, result.copyFormatMarkdown);
|
|
2532
|
+
}
|
|
2533
|
+
auto tmp_systemItemsNone = map.find("systemItemsNone");
|
|
2534
|
+
if (tmp_systemItemsNone != map.end()) {
|
|
2535
|
+
fromRawValue(context, tmp_systemItemsNone->second, result.systemItemsNone);
|
|
2536
|
+
}
|
|
2537
|
+
auto tmp_codeBlockCopyButton = map.find("codeBlockCopyButton");
|
|
2538
|
+
if (tmp_codeBlockCopyButton != map.end()) {
|
|
2539
|
+
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
2540
|
+
}
|
|
2541
|
+
auto tmp_interceptCopy = map.find("interceptCopy");
|
|
2542
|
+
if (tmp_interceptCopy != map.end()) {
|
|
2543
|
+
fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
|
|
2544
|
+
}
|
|
2515
2545
|
}
|
|
2516
2546
|
|
|
2517
2547
|
static inline std::string toString(const EnrichedMarkdownSelectionMenuConfigStruct &value) {
|
|
@@ -6415,6 +6445,11 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6415
6445
|
std::string copyImageUrlLabel{};
|
|
6416
6446
|
std::string copyImageUrlsLabel{};
|
|
6417
6447
|
std::vector<std::string> copyImageUrlPluralTemplates{};
|
|
6448
|
+
bool copyEnabled{false};
|
|
6449
|
+
bool copyFormatMarkdown{false};
|
|
6450
|
+
bool systemItemsNone{false};
|
|
6451
|
+
bool codeBlockCopyButton{false};
|
|
6452
|
+
bool interceptCopy{false};
|
|
6418
6453
|
|
|
6419
6454
|
|
|
6420
6455
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -6429,6 +6464,11 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
|
|
|
6429
6464
|
result["copyImageUrlLabel"] = copyImageUrlLabel;
|
|
6430
6465
|
result["copyImageUrlsLabel"] = copyImageUrlsLabel;
|
|
6431
6466
|
result["copyImageUrlPluralTemplates"] = ::facebook::react::toDynamic(copyImageUrlPluralTemplates);
|
|
6467
|
+
result["copyEnabled"] = copyEnabled;
|
|
6468
|
+
result["copyFormatMarkdown"] = copyFormatMarkdown;
|
|
6469
|
+
result["systemItemsNone"] = systemItemsNone;
|
|
6470
|
+
result["codeBlockCopyButton"] = codeBlockCopyButton;
|
|
6471
|
+
result["interceptCopy"] = interceptCopy;
|
|
6432
6472
|
return result;
|
|
6433
6473
|
}
|
|
6434
6474
|
#endif
|
|
@@ -6465,6 +6505,26 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
6465
6505
|
if (tmp_copyImageUrlPluralTemplates != map.end()) {
|
|
6466
6506
|
fromRawValue(context, tmp_copyImageUrlPluralTemplates->second, result.copyImageUrlPluralTemplates);
|
|
6467
6507
|
}
|
|
6508
|
+
auto tmp_copyEnabled = map.find("copyEnabled");
|
|
6509
|
+
if (tmp_copyEnabled != map.end()) {
|
|
6510
|
+
fromRawValue(context, tmp_copyEnabled->second, result.copyEnabled);
|
|
6511
|
+
}
|
|
6512
|
+
auto tmp_copyFormatMarkdown = map.find("copyFormatMarkdown");
|
|
6513
|
+
if (tmp_copyFormatMarkdown != map.end()) {
|
|
6514
|
+
fromRawValue(context, tmp_copyFormatMarkdown->second, result.copyFormatMarkdown);
|
|
6515
|
+
}
|
|
6516
|
+
auto tmp_systemItemsNone = map.find("systemItemsNone");
|
|
6517
|
+
if (tmp_systemItemsNone != map.end()) {
|
|
6518
|
+
fromRawValue(context, tmp_systemItemsNone->second, result.systemItemsNone);
|
|
6519
|
+
}
|
|
6520
|
+
auto tmp_codeBlockCopyButton = map.find("codeBlockCopyButton");
|
|
6521
|
+
if (tmp_codeBlockCopyButton != map.end()) {
|
|
6522
|
+
fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
|
|
6523
|
+
}
|
|
6524
|
+
auto tmp_interceptCopy = map.find("interceptCopy");
|
|
6525
|
+
if (tmp_interceptCopy != map.end()) {
|
|
6526
|
+
fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
|
|
6527
|
+
}
|
|
6468
6528
|
}
|
|
6469
6529
|
|
|
6470
6530
|
static inline std::string toString(const EnrichedMarkdownTextSelectionMenuConfigStruct &value) {
|