react-native-enriched-markdown-scaffold 1.0.2-scaffold.2 → 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.
Files changed (55) hide show
  1. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  2. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
  3. package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +12 -0
  4. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +24 -7
  5. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +4 -0
  6. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +5 -0
  7. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +5 -0
  8. package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +4 -0
  9. package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
  10. package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +19 -0
  11. package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +21 -1
  12. package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +27 -8
  13. package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +9 -1
  14. package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +4 -2
  15. package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +3 -1
  16. package/docs/API_REFERENCE.md +48 -0
  17. package/docs/COPY_OPTIONS.md +14 -0
  18. package/ios/EnrichedMarkdown.mm +43 -2
  19. package/ios/EnrichedMarkdownText.mm +25 -3
  20. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +24 -0
  21. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +18 -0
  22. package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +12 -0
  23. package/ios/utils/ENRMBlockMenu.h +12 -3
  24. package/ios/utils/EditMenuUtils.h +10 -1
  25. package/ios/utils/EditMenuUtils.m +18 -4
  26. package/ios/views/ENRMCodeBlockContainerView.h +4 -0
  27. package/ios/views/ENRMCodeBlockContainerView.m +8 -1
  28. package/ios/views/ENRMMathContainerView.h +4 -0
  29. package/ios/views/ENRMMathContainerView.m +8 -1
  30. package/ios/views/TableContainerView.h +4 -0
  31. package/ios/views/TableContainerView.m +9 -3
  32. package/lib/module/EnrichedMarkdownNativeComponent.ts +16 -0
  33. package/lib/module/EnrichedMarkdownTextNativeComponent.ts +16 -0
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/native/EnrichedMarkdownText.js +23 -2
  36. package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
  37. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +13 -0
  38. package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
  39. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +13 -0
  40. package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +1 -1
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
  44. package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
  45. package/lib/typescript/src/types/MarkdownTextProps.d.ts +13 -1
  46. package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
  47. package/lib/typescript/src/types/events.d.ts +22 -0
  48. package/lib/typescript/src/types/events.d.ts.map +1 -1
  49. package/package.json +1 -1
  50. package/src/EnrichedMarkdownNativeComponent.ts +16 -0
  51. package/src/EnrichedMarkdownTextNativeComponent.ts +16 -0
  52. package/src/index.tsx +1 -0
  53. package/src/native/EnrichedMarkdownText.tsx +24 -1
  54. package/src/types/MarkdownTextProps.ts +13 -0
  55. package/src/types/events.ts +23 -0
@@ -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
@@ -512,6 +513,17 @@ static char kENRMSegmentFadeAnimatorKey;
512
513
  .copyWritesMarkdown = _selectionMenuConfig.copyFormatMarkdown,
513
514
  .copyAsMarkdownHidden = !_selectionMenuConfig.copyAsMarkdown,
514
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];
515
527
  };
516
528
  }
517
529
 
@@ -800,7 +812,7 @@ static char kENRMSegmentFadeAnimatorKey;
800
812
  selectionEnd:selectionEnd];
801
813
  });
802
814
  return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, segmentMarkdown, strongSelf->_config,
803
- @[ baseMenu ], customItems, strongSelf -> _selectionMenuConfig);
815
+ @[ baseMenu ], customItems, strongSelf->_selectionMenuConfig);
804
816
  }];
805
817
  #endif
806
818
 
@@ -821,6 +833,7 @@ static char kENRMSegmentFadeAnimatorKey;
821
833
  tableView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
822
834
  tableView.contextMenuHidden = _contextMenuHidden;
823
835
  tableView.menuOptions = [self blockMenuOptions];
836
+ tableView.onCopyRequest = [self blockCopyRequestForSource:@"table"];
824
837
 
825
838
  __weak EnrichedMarkdown *weakSelf = self;
826
839
 
@@ -862,6 +875,7 @@ static char kENRMSegmentFadeAnimatorKey;
862
875
  mathView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
863
876
  mathView.contextMenuHidden = _contextMenuHidden;
864
877
  mathView.menuOptions = [self blockMenuOptions];
878
+ mathView.onCopyRequest = [self blockCopyRequestForSource:@"math"];
865
879
  [mathView applyLatex:mathSegment.latex];
866
880
  return mathView;
867
881
  }
@@ -874,6 +888,7 @@ static char kENRMSegmentFadeAnimatorKey;
874
888
  codeBlockView.copyAsMarkdownLabel = _selectionMenuLabels.copyAsMarkdownLabel;
875
889
  codeBlockView.contextMenuHidden = _contextMenuHidden;
876
890
  codeBlockView.menuOptions = [self blockMenuOptions];
891
+ codeBlockView.onCopyRequest = [self blockCopyRequestForSource:@"code"];
877
892
 
878
893
  __weak EnrichedMarkdown *weakSelf = self;
879
894
  codeBlockView.onCopyPress = ^(NSString *code, NSString *language) {
@@ -1288,6 +1303,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownCls(void)
1288
1303
  {.code = std::string(code.UTF8String ?: ""), .language = std::string(language.UTF8String ?: "")});
1289
1304
  }
1290
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
+
1291
1318
  - (void)emitContextMenuItemPress:(NSString *)itemText
1292
1319
  selectedText:(NSString *)selectedText
1293
1320
  selectionStart:(NSUInteger)selectionStart
@@ -1355,8 +1382,22 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownCls(void)
1355
1382
  ENRMBuildContextMenuActions(_contextMenuItemTexts, _contextMenuItemIcons, textView, range, handler);
1356
1383
 
1357
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
+
1358
1399
  return buildEditMenuForSelection(textView.attributedText, range, segmentMarkdown, _config, suggestedActions,
1359
- customActions, _selectionMenuConfig);
1400
+ customActions, _selectionMenuConfig, onCopyRequest);
1360
1401
  }
1361
1402
 
1362
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 {
@@ -301,8 +302,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
301
302
  selectionEnd:selectionEnd];
302
303
  });
303
304
  return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, strongSelf->_cachedMarkdown,
304
- strongSelf->_config, @[ baseMenu ], customItems,
305
- strongSelf -> _selectionMenuConfig);
305
+ strongSelf->_config, @[ baseMenu ], customItems, strongSelf->_selectionMenuConfig);
306
306
  };
307
307
  #endif
308
308
 
@@ -749,6 +749,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
749
749
  emitter->onTaskListItemPress({.index = (int)index, .checked = checked, .text = std::string(text.UTF8String ?: "")});
750
750
  }
751
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
+
752
764
  - (void)emitContextMenuItemPress:(NSString *)itemText
753
765
  selectedText:(NSString *)selectedText
754
766
  selectionStart:(NSUInteger)selectionStart
@@ -831,8 +843,18 @@ Class<RCTComponentViewProtocol> EnrichedMarkdownTextCls(void)
831
843
  NSMutableArray<UIAction *> *customActions =
832
844
  ENRMBuildContextMenuActions(_contextMenuItemTexts, _contextMenuItemIcons, textView, range, handler);
833
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
+
834
856
  return buildEditMenuForSelection(textView.attributedText, range, _cachedMarkdown, _config, suggestedActions,
835
- customActions, _selectionMenuConfig);
857
+ customActions, _selectionMenuConfig, onCopyRequest);
836
858
  }
837
859
  #endif
838
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
@@ -2466,6 +2466,7 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
2466
2466
  bool copyFormatMarkdown{false};
2467
2467
  bool systemItemsNone{false};
2468
2468
  bool codeBlockCopyButton{false};
2469
+ bool interceptCopy{false};
2469
2470
 
2470
2471
 
2471
2472
  #ifdef RN_SERIALIZABLE_STATE
@@ -2484,6 +2485,7 @@ struct EnrichedMarkdownSelectionMenuConfigStruct {
2484
2485
  result["copyFormatMarkdown"] = copyFormatMarkdown;
2485
2486
  result["systemItemsNone"] = systemItemsNone;
2486
2487
  result["codeBlockCopyButton"] = codeBlockCopyButton;
2488
+ result["interceptCopy"] = interceptCopy;
2487
2489
  return result;
2488
2490
  }
2489
2491
  #endif
@@ -2536,6 +2538,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
2536
2538
  if (tmp_codeBlockCopyButton != map.end()) {
2537
2539
  fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
2538
2540
  }
2541
+ auto tmp_interceptCopy = map.find("interceptCopy");
2542
+ if (tmp_interceptCopy != map.end()) {
2543
+ fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
2544
+ }
2539
2545
  }
2540
2546
 
2541
2547
  static inline std::string toString(const EnrichedMarkdownSelectionMenuConfigStruct &value) {
@@ -6443,6 +6449,7 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
6443
6449
  bool copyFormatMarkdown{false};
6444
6450
  bool systemItemsNone{false};
6445
6451
  bool codeBlockCopyButton{false};
6452
+ bool interceptCopy{false};
6446
6453
 
6447
6454
 
6448
6455
  #ifdef RN_SERIALIZABLE_STATE
@@ -6461,6 +6468,7 @@ struct EnrichedMarkdownTextSelectionMenuConfigStruct {
6461
6468
  result["copyFormatMarkdown"] = copyFormatMarkdown;
6462
6469
  result["systemItemsNone"] = systemItemsNone;
6463
6470
  result["codeBlockCopyButton"] = codeBlockCopyButton;
6471
+ result["interceptCopy"] = interceptCopy;
6464
6472
  return result;
6465
6473
  }
6466
6474
  #endif
@@ -6513,6 +6521,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
6513
6521
  if (tmp_codeBlockCopyButton != map.end()) {
6514
6522
  fromRawValue(context, tmp_codeBlockCopyButton->second, result.codeBlockCopyButton);
6515
6523
  }
6524
+ auto tmp_interceptCopy = map.find("interceptCopy");
6525
+ if (tmp_interceptCopy != map.end()) {
6526
+ fromRawValue(context, tmp_interceptCopy->second, result.interceptCopy);
6527
+ }
6516
6528
  }
6517
6529
 
6518
6530
  static inline std::string toString(const EnrichedMarkdownTextSelectionMenuConfigStruct &value) {
@@ -18,20 +18,29 @@ typedef struct {
18
18
  BOOL copyAsMarkdownHidden;
19
19
  // `selectionMenuConfig.codeBlockCopyButton.enabled == false`.
20
20
  BOOL copyButtonHidden;
21
+ // JS `onCopy` is set: the Copy item hands the block's content to the view's
22
+ // `onCopyRequest` instead of writing the pasteboard.
23
+ BOOL interceptCopy;
21
24
  } ENRMBlockMenuOptions;
22
25
 
26
+ /// Receives an intercepted block Copy: the block's Markdown and its raw content.
27
+ typedef void (^ENRMBlockCopyRequestBlock)(NSString *markdown, NSString *text);
28
+
23
29
  #if !TARGET_OS_OSX
24
30
  /// Builds the long-press menu shared by the table, math and code block views.
25
31
  /// Returns nil when no item is left — nil is how UIKit suppresses a context
26
- /// menu, whereas an empty menu would still lift the view.
32
+ /// menu, whereas an empty menu would still lift the view. `requestCopy` replaces
33
+ /// the Copy item's handler when `options.interceptCopy` is set.
27
34
  static inline UIContextMenuConfiguration *_Nullable ENRMBlockContextMenuConfiguration(
28
35
  ENRMBlockMenuOptions options, NSString *_Nullable copyLabel, NSString *_Nullable copyAsMarkdownLabel,
29
- void (^copyRaw)(void), void (^copyMarkdown)(void))
36
+ void (^copyRaw)(void), void (^copyMarkdown)(void), void (^_Nullable requestCopy)(void))
30
37
  {
31
38
  if (options.copyHidden && options.copyAsMarkdownHidden) {
32
39
  return nil;
33
40
  }
34
- void (^copyHandler)(void) = options.copyWritesMarkdown ? copyMarkdown : copyRaw;
41
+ void (^copyHandler)(void) = (options.interceptCopy && requestCopy) ? requestCopy
42
+ : options.copyWritesMarkdown ? copyMarkdown
43
+ : copyRaw;
35
44
  return [UIContextMenuConfiguration
36
45
  configurationWithIdentifier:nil
37
46
  previewProvider:nil
@@ -15,6 +15,9 @@ typedef struct {
15
15
  // Drop every system-provided item from the text selection menu.
16
16
  BOOL systemItemsNone;
17
17
  BOOL codeBlockCopyButton;
18
+ // Copy hands the selection to the owner (JS `onCopy`) instead of writing the
19
+ // pasteboard.
20
+ BOOL interceptCopy;
18
21
  // The owner must keep these strings alive for the duration of the call (the
19
22
  // view holds them in strong ivars).
20
23
  __unsafe_unretained NSString *_Nullable copyLabel;
@@ -24,6 +27,10 @@ typedef struct {
24
27
  __unsafe_unretained NSArray<NSString *> *_Nullable copyImageUrlPluralTemplates;
25
28
  } ENRMSelectionMenuConfig;
26
29
 
30
+ /// Receives an intercepted Copy. `fullSelection` is whether the range spans the
31
+ /// whole attributed text, in which case `markdown` is the cached markdown.
32
+ typedef void (^ENRMCopyRequestHandler)(NSString *markdown, NSString *text, BOOL fullSelection);
33
+
27
34
  /// Resolves the "Copy Image URL(s)" menu title for a given image count.
28
35
  /// Uses the precomputed plural templates (indexed by count, 0..100) when present;
29
36
  /// counts > 100 use the "other" form (copyImageUrlsLabel). Otherwise the
@@ -87,6 +94,7 @@ static inline ENRMSelectionMenuConfig ENRMBuildSelectionMenuConfig(const ENRMSel
87
94
  .copyFormatMarkdown = src.copyFormatMarkdown,
88
95
  .systemItemsNone = src.systemItemsNone,
89
96
  .codeBlockCopyButton = src.codeBlockCopyButton,
97
+ .interceptCopy = src.interceptCopy,
90
98
  .copyLabel = labels.copyLabel,
91
99
  .copyAsMarkdownLabel = labels.copyAsMarkdownLabel,
92
100
  .copyImageUrlLabel = labels.copyImageUrlLabel,
@@ -107,7 +115,8 @@ extern "C" {
107
115
  UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
108
116
  StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions,
109
117
  NSArray<UIAction *> *_Nullable customActions,
110
- ENRMSelectionMenuConfig selectionMenuConfig) API_AVAILABLE(ios(16.0));
118
+ ENRMSelectionMenuConfig selectionMenuConfig,
119
+ ENRMCopyRequestHandler _Nullable onCopyRequest) API_AVAILABLE(ios(16.0));
111
120
  #else
112
121
  NSMenu *_Nullable buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range,
113
122
  NSString *_Nullable cachedMarkdown, StyleConfig *styleConfig,
@@ -10,12 +10,22 @@ static NSString *const kActionIdentifierCopy = @"com.swmansion.enriched.markdown
10
10
  static NSString *const kActionIdentifierCopyMarkdown = @"com.swmansion.enriched.markdown.copyMarkdown";
11
11
  static NSString *const kActionIdentifierCopyImageURL = @"com.swmansion.enriched.markdown.copyImageURL";
12
12
 
13
- static UIAction *_Nullable createCopyAction(NSAttributedString *selectedText, NSString *markdown,
14
- StyleConfig *styleConfig, ENRMSelectionMenuConfig selectionMenuConfig)
13
+ static UIAction *_Nullable createCopyAction(NSAttributedString *selectedText, NSString *markdown, BOOL fullSelection,
14
+ StyleConfig *styleConfig, ENRMSelectionMenuConfig selectionMenuConfig,
15
+ ENRMCopyRequestHandler _Nullable onCopyRequest)
15
16
  {
16
17
  if (!selectionMenuConfig.copyEnabled)
17
18
  return nil;
18
19
 
20
+ if (selectionMenuConfig.interceptCopy && onCopyRequest) {
21
+ NSString *text = selectedText.string;
22
+ NSString *copied = markdown.length > 0 ? markdown : text;
23
+ return [UIAction actionWithTitle:selectionMenuConfig.copyLabel
24
+ image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
25
+ identifier:kActionIdentifierCopy
26
+ handler:^(__kindof UIAction *action) { onCopyRequest(copied, text, fullSelection); }];
27
+ }
28
+
19
29
  BOOL writesMarkdown = selectionMenuConfig.copyFormatMarkdown;
20
30
  return [UIAction actionWithTitle:selectionMenuConfig.copyLabel
21
31
  image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
@@ -108,13 +118,17 @@ static void insertOptionalAction(NSMutableArray<UIMenuElement *> *array, UIActio
108
118
  UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
109
119
  StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions,
110
120
  NSArray<UIAction *> *_Nullable customActions,
111
- ENRMSelectionMenuConfig selectionMenuConfig) API_AVAILABLE(ios(16.0))
121
+ ENRMSelectionMenuConfig selectionMenuConfig,
122
+ ENRMCopyRequestHandler _Nullable onCopyRequest) API_AVAILABLE(ios(16.0))
112
123
  {
113
124
  NSAttributedString *selectedText = [attributedText attributedSubstringFromRange:range];
114
125
  NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
115
126
  NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
127
+ // Same threshold markdownForRange uses to return the cached markdown.
128
+ BOOL fullSelection = range.location == 0 && range.length >= attributedText.length - 1;
116
129
 
117
- UIAction *copyAction = createCopyAction(selectedText, markdown, styleConfig, selectionMenuConfig);
130
+ UIAction *copyAction =
131
+ createCopyAction(selectedText, markdown, fullSelection, styleConfig, selectionMenuConfig, onCopyRequest);
118
132
  UIAction *copyMarkdownAction = selectionMenuConfig.copyAsMarkdown
119
133
  ? createCopyMarkdownAction(markdown, selectionMenuConfig.copyAsMarkdownLabel)
120
134
  : nil;
@@ -42,6 +42,10 @@ typedef void (^ENRMCodeBlockCopyBlock)(NSString *code, NSString *language);
42
42
  /// the header button and VoiceOver action always copy the raw code.
43
43
  @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
44
44
 
45
+ /// Set by the host; receives the Copy item's content when
46
+ /// `menuOptions.interceptCopy` is set.
47
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
48
+
45
49
  // Fired when the code is copied (header button, context-menu Copy, or the
46
50
  // VoiceOver copy action); set by the host to bridge up to the JS onCopyPress
47
51
  // event. Not fired for "Copy as Markdown", nor for the context-menu Copy when
@@ -500,7 +500,14 @@ static BOOL ENRMColorIsDark(RCTUIColor *color)
500
500
  __weak __typeof(self) weakSelf = self;
501
501
  return ENRMBlockContextMenuConfiguration(
502
502
  self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyCodeToPasteboard]; },
503
- ^{ [weakSelf copyMarkdownToPasteboard]; });
503
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
504
+ }
505
+
506
+ - (void)requestCopy
507
+ {
508
+ if (self.onCopyRequest) {
509
+ self.onCopyRequest([self fencedMarkdown], _cachedCode ?: @"");
510
+ }
504
511
  }
505
512
  #endif
506
513
 
@@ -39,6 +39,10 @@ NS_ASSUME_NONNULL_BEGIN
39
39
  /// Copy / Copy as Markdown visibility and Copy format from `selectionMenuConfig`.
40
40
  @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
41
41
 
42
+ /// Set by the host; receives the Copy item's content when
43
+ /// `menuOptions.interceptCopy` is set.
44
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
45
+
42
46
  @end
43
47
 
44
48
  NS_ASSUME_NONNULL_END
@@ -133,7 +133,14 @@
133
133
  __weak __typeof(self) weakSelf = self;
134
134
  return ENRMBlockContextMenuConfiguration(
135
135
  self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyLatexToPasteboard]; },
136
- ^{ [weakSelf copyMarkdownToPasteboard]; });
136
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
137
+ }
138
+
139
+ - (void)requestCopy
140
+ {
141
+ if (self.onCopyRequest) {
142
+ self.onCopyRequest([NSString stringWithFormat:@"$$\n%@\n$$", _cachedLatex ?: @""], _cachedLatex ?: @"");
143
+ }
137
144
  }
138
145
  #endif
139
146
 
@@ -60,6 +60,10 @@ typedef void (^TableLinkPressBlock)(NSString *url);
60
60
  /// Copy / Copy as Markdown visibility and Copy format from `selectionMenuConfig`.
61
61
  @property (nonatomic, assign) ENRMBlockMenuOptions menuOptions;
62
62
 
63
+ /// Set by the host; receives the Copy item's content when
64
+ /// `menuOptions.interceptCopy` is set.
65
+ @property (nonatomic, copy, nullable) ENRMBlockCopyRequestBlock onCopyRequest;
66
+
63
67
  @property (nonatomic, readonly) NSUInteger rowCount;
64
68
 
65
69
  - (void)animateNewRowsFromPreviousCount:(NSUInteger)previousRowCount duration:(NSTimeInterval)duration;
@@ -264,8 +264,7 @@ static void ENRMTableComputeLayout(NSArray<NSArray<TableCellData *> *> *rows, NS
264
264
  // a single drawRect: pass (no subview / layer compositing issues).
265
265
  ENRMTableGridView *gridView = [[ENRMTableGridView alloc] initWithFrame:CGRectZero];
266
266
  __weak TableContainerView *weakSelf = self;
267
- gridView.menuProvider = ^NSMenu *
268
- {
267
+ gridView.menuProvider = ^NSMenu * {
269
268
  TableContainerView *strongSelf = weakSelf;
270
269
  if (!strongSelf)
271
270
  return nil;
@@ -471,7 +470,14 @@ static void ENRMTableComputeLayout(NSArray<NSArray<TableCellData *> *> *rows, NS
471
470
  __weak __typeof(self) weakSelf = self;
472
471
  return ENRMBlockContextMenuConfiguration(
473
472
  self.menuOptions, self.copyLabel, self.copyAsMarkdownLabel, ^{ [weakSelf copyTableToPasteboard]; },
474
- ^{ [weakSelf copyMarkdownToPasteboard]; });
473
+ ^{ [weakSelf copyMarkdownToPasteboard]; }, ^{ [weakSelf requestCopy]; });
474
+ }
475
+
476
+ - (void)requestCopy
477
+ {
478
+ if (self.onCopyRequest) {
479
+ self.onCopyRequest(_cachedMarkdown ?: @"", [self buildPlainTextFromRows] ?: @"");
480
+ }
475
481
  }
476
482
  #endif // !TARGET_OS_OSX
477
483
 
@@ -256,6 +256,14 @@ export interface CopyPressEvent {
256
256
  language: string;
257
257
  }
258
258
 
259
+ export interface CopyRequestEvent {
260
+ // 'selection' | 'math' | 'table' | 'code'
261
+ source: string;
262
+ markdown: string;
263
+ text: string;
264
+ whole: boolean;
265
+ }
266
+
259
267
  export interface ContextMenuItemConfig {
260
268
  text: string;
261
269
  icon?: string;
@@ -284,6 +292,8 @@ export interface SelectionMenuConfig {
284
292
  systemItemsNone: boolean;
285
293
  // false hides the copy button in the code block header.
286
294
  codeBlockCopyButton: boolean;
295
+ // true: the Copy item emits onCopyRequest instead of writing the clipboard.
296
+ interceptCopy: boolean;
287
297
  }
288
298
 
289
299
  export interface OnContextMenuItemPressEvent {
@@ -410,6 +420,12 @@ export interface NativeProps extends ViewProps {
410
420
  * action. Receives the copied code and its language.
411
421
  */
412
422
  onCopyPress?: CodegenTypes.BubblingEventHandler<CopyPressEvent>;
423
+ /**
424
+ * Fired instead of writing the clipboard when the Copy item of the text
425
+ * selection menu or a table / math / code block menu is pressed while
426
+ * `selectionMenuConfig.interceptCopy` is set.
427
+ */
428
+ onCopyRequest?: CodegenTypes.BubblingEventHandler<CopyRequestEvent>;
413
429
  /**
414
430
  * Controls whether the system link preview is shown on long press (iOS only).
415
431
  *
@@ -257,6 +257,14 @@ export interface CopyPressEvent {
257
257
  language: string;
258
258
  }
259
259
 
260
+ export interface CopyRequestEvent {
261
+ // 'selection' | 'math' | 'table' | 'code'
262
+ source: string;
263
+ markdown: string;
264
+ text: string;
265
+ whole: boolean;
266
+ }
267
+
260
268
  export interface ContextMenuItemConfig {
261
269
  text: string;
262
270
  icon?: string;
@@ -285,6 +293,8 @@ export interface SelectionMenuConfig {
285
293
  systemItemsNone: boolean;
286
294
  // false hides the copy button in the code block header.
287
295
  codeBlockCopyButton: boolean;
296
+ // true: the Copy item emits onCopyRequest instead of writing the clipboard.
297
+ interceptCopy: boolean;
288
298
  }
289
299
 
290
300
  export interface OnContextMenuItemPressEvent {
@@ -411,6 +421,12 @@ export interface NativeProps extends ViewProps {
411
421
  * action. Receives the copied code and its language.
412
422
  */
413
423
  onCopyPress?: CodegenTypes.BubblingEventHandler<CopyPressEvent>;
424
+ /**
425
+ * Fired instead of writing the clipboard when the Copy item of the text
426
+ * selection menu or a table / math / code block menu is pressed while
427
+ * `selectionMenuConfig.interceptCopy` is set.
428
+ */
429
+ onCopyRequest?: CodegenTypes.BubblingEventHandler<CopyRequestEvent>;
414
430
  /**
415
431
  * Controls whether the system link preview is shown on long press (iOS only).
416
432
  *
@@ -1 +1 @@
1
- {"version":3,"names":["default","EnrichedMarkdownText","DEFAULT_ACCESSIBILITY_LABELS","resolveAccessibilityLabels","EnrichedMarkdownTextInput"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,oBAAoB,QAAQ,kCAA+B;AAqB/E,SACEC,4BAA4B,EAC5BC,0BAA0B,QACrB,iCAA8B;AAErC,SAASC,yBAAyB,QAAQ,gCAA6B","ignoreList":[]}
1
+ {"version":3,"names":["default","EnrichedMarkdownText","DEFAULT_ACCESSIBILITY_LABELS","resolveAccessibilityLabels","EnrichedMarkdownTextInput"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,oBAAoB,QAAQ,kCAA+B;AAsB/E,SACEC,4BAA4B,EAC5BC,0BAA0B,QACrB,iCAA8B;AAErC,SAASC,yBAAyB,QAAQ,gCAA6B","ignoreList":[]}
@@ -73,6 +73,7 @@ export const EnrichedMarkdownText = ({
73
73
  onTaskListItemPress,
74
74
  enableTaskListItemToggle = true,
75
75
  onCopyPress,
76
+ onCopy,
76
77
  enableLinkPreview,
77
78
  selectable = true,
78
79
  md4cFlags = defaultMd4cFlags,
@@ -183,6 +184,24 @@ export const EnrichedMarkdownText = ({
183
184
  language
184
185
  });
185
186
  }, [onCopyPress]);
187
+ const handleCopyRequest = useCallback(
188
+ // Codegen types `source` as a plain string; native only sends the four
189
+ // values of CopyEvent['source'].
190
+ e => {
191
+ const {
192
+ source,
193
+ markdown: copied,
194
+ text,
195
+ whole
196
+ } = e.nativeEvent;
197
+ onCopy?.({
198
+ source: source,
199
+ markdown: copied,
200
+ text,
201
+ whole
202
+ });
203
+ }, [onCopy]);
204
+ const interceptCopy = onCopy != null;
186
205
  const tableMode = streamingConfig?.tableMode ?? 'progressive';
187
206
  const codeBlockMode = streamingConfig?.codeBlockMode ?? 'progressive';
188
207
  const normalizedStreamingConfig = useMemo(() => ({
@@ -210,9 +229,10 @@ export const EnrichedMarkdownText = ({
210
229
  copyEnabled: copy.enabled,
211
230
  copyFormatMarkdown: copyFormat === 'markdown',
212
231
  systemItemsNone: config?.systemItems === 'none',
213
- codeBlockCopyButton: codeBlockCopyButton.enabled
232
+ codeBlockCopyButton: codeBlockCopyButton.enabled,
233
+ interceptCopy
214
234
  };
215
- }, [selectionMenuConfig]);
235
+ }, [selectionMenuConfig, interceptCopy]);
216
236
  const resolvedAccessibilityLabels = useMemo(() => resolveAccessibilityLabels(accessibilityLabels), [accessibilityLabels]);
217
237
  const sharedProps = {
218
238
  markdown,
@@ -222,6 +242,7 @@ export const EnrichedMarkdownText = ({
222
242
  onTaskListItemPress: handleTaskListItemPress,
223
243
  enableTaskListItemToggle,
224
244
  onCopyPress: handleCopyPress,
245
+ onCopyRequest: handleCopyRequest,
225
246
  enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true),
226
247
  selectable,
227
248
  md4cFlags: normalizedMd4cFlags,