react-native-enriched-markdown-scaffold 0.6.0-scaffold.8 → 0.7.4-scaffold.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReactNativeEnrichedMarkdown.podspec +62 -11
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +1 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +1 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/android/src/main/baseline-prof.txt +75 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +9 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/AccessibilityLabels.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +44 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +89 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +22 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputRemend.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +54 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +4 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +36 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +73 -3
- package/android/src/main/jni/CMakeLists.txt +15 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +24 -2
- package/app.plugin.js +2 -1
- package/cpp/parser/MD4CParser.cpp +1 -1
- package/cpp/wasm/build.sh +9 -9
- package/docs/ACCESSIBILITY.md +172 -0
- package/docs/API_REFERENCE.md +948 -0
- package/docs/COPY_OPTIONS.md +127 -0
- package/docs/ELEMENTS_STRUCTURE.md +112 -0
- package/docs/IMAGE_CACHING.md +25 -0
- package/docs/INPUT.md +233 -0
- package/docs/LATEX_MATH.md +136 -0
- package/docs/MACOS.md +19 -0
- package/docs/MARKDOWN_STREAMING.md +36 -0
- package/docs/MENTIONS.md +97 -0
- package/docs/RTL.md +82 -0
- package/docs/STYLES.md +519 -0
- package/docs/TEXT.md +110 -0
- package/docs/WEB.md +49 -0
- package/ios/EnrichedMarkdown.mm +82 -25
- package/ios/EnrichedMarkdownText.mm +37 -5
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +744 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +15 -0
- package/ios/input/ENRMFormattingStore.h +5 -0
- package/ios/input/ENRMFormattingStore.mm +34 -0
- package/ios/input/ENRMInputTextView.h +2 -0
- package/ios/input/ENRMInputTextView.mm +8 -17
- package/ios/input/ENRMMarkdownSerializer.mm +1 -1
- package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +71 -53
- package/ios/input/EnrichedMarkdownTextInput+Internal.h +26 -0
- package/ios/input/EnrichedMarkdownTextInput.h +1 -0
- package/ios/input/EnrichedMarkdownTextInput.mm +230 -29
- package/ios/input/styles/ENRMBoldStyleHandler.mm +3 -2
- package/ios/input/styles/ENRMItalicStyleHandler.mm +3 -2
- package/ios/renderer/AttributedRenderer.m +51 -6
- package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
- package/ios/styles/ENRMFontSlot.h +1 -1
- package/ios/utils/ENRMAccessibilityLabels.h +36 -0
- package/ios/utils/ENRMAccessibilityLabels.m +5 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -5
- package/ios/utils/EditMenuUtils.h +70 -0
- package/ios/utils/EditMenuUtils.m +14 -11
- package/ios/utils/LastElementUtils.h +39 -2
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +18 -16
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +64 -32
- package/ios/utils/ParagraphStyleUtils.m +21 -2
- package/ios/views/ENRMMathContainerView.h +8 -0
- package/ios/views/ENRMMathContainerView.m +7 -5
- package/ios/views/ENRMTableIOSGridView.h +40 -0
- package/ios/views/ENRMTableIOSGridView.m +216 -0
- package/ios/views/EnrichedMarkdownInternalText.h +2 -0
- package/ios/views/EnrichedMarkdownInternalText.m +2 -0
- package/ios/views/TableContainerView.h +8 -0
- package/ios/views/TableContainerView.m +72 -128
- package/lib/module/EnrichedMarkdownNativeComponent.ts +37 -0
- package/lib/module/EnrichedMarkdownTextInput.js +48 -1
- package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/lib/module/accessibilityLabelDefaults.js +64 -0
- package/lib/module/accessibilityLabelDefaults.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +72 -4
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMenuItem.js +38 -0
- package/lib/module/normalizeMenuItem.js.map +1 -0
- package/lib/module/types/AccessibilityLabels.js +2 -0
- package/lib/module/types/AccessibilityLabels.js.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +55 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +30 -0
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +34 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts +16 -0
- package/lib/typescript/src/accessibilityLabelDefaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMenuItem.d.ts +7 -0
- package/lib/typescript/src/normalizeMenuItem.d.ts.map +1 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts +114 -0
- package/lib/typescript/src/types/AccessibilityLabels.d.ts.map +1 -0
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +78 -9
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/package.json +4 -2
- package/plugin/build/withAndroidMath.js +19 -0
- package/plugin/build/withIosMath.js +28 -0
- package/plugin/build/withReactNativeEnrichedMarkdown.js +11 -0
- package/plugin/tsconfig.build.json +16 -0
- package/src/EnrichedMarkdownNativeComponent.ts +37 -0
- package/src/EnrichedMarkdownTextInput.tsx +117 -1
- package/src/EnrichedMarkdownTextInputNativeComponent.ts +35 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +37 -0
- package/src/accessibilityLabelDefaults.ts +83 -0
- package/src/index.tsx +11 -0
- package/src/native/EnrichedMarkdownText.tsx +120 -6
- package/src/normalizeMenuItem.ts +52 -0
- package/src/types/AccessibilityLabels.ts +118 -0
- package/src/types/MarkdownTextProps.ts +74 -9
- package/lib/module/plugin/withAndroidMath.js +0 -20
- package/lib/module/plugin/withAndroidMath.js.map +0 -1
- package/lib/module/plugin/withIosMath.js +0 -23
- package/lib/module/plugin/withIosMath.js.map +0 -1
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js +0 -16
- package/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map +0 -1
- package/lib/typescript/src/plugin/withAndroidMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withAndroidMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withIosMath.d.ts +0 -5
- package/lib/typescript/src/plugin/withIosMath.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts +0 -6
- package/lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map +0 -1
- /package/{src/plugin → plugin/src}/withAndroidMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withIosMath.ts +0 -0
- /package/{src/plugin → plugin/src}/withReactNativeEnrichedMarkdown.ts +0 -0
|
@@ -20,9 +20,12 @@
|
|
|
20
20
|
#import "ENRMStyleMergingConfig.h"
|
|
21
21
|
#import "ENRMUIKit.h"
|
|
22
22
|
#import "ENRMWordsUtils.h"
|
|
23
|
+
#import "EnrichedMarkdownTextInput+Internal.h"
|
|
23
24
|
#import "InputStylePropsUtils.h"
|
|
24
25
|
#import "ParagraphStyleUtils.h"
|
|
26
|
+
#import "PasteboardUtils.h"
|
|
25
27
|
#import "SelectionColorUtils.h"
|
|
28
|
+
#import <QuartzCore/CABase.h>
|
|
26
29
|
#import <React/RCTI18nUtil.h>
|
|
27
30
|
#if TARGET_OS_OSX
|
|
28
31
|
#import <React/RCTBackedTextInputDelegate.h>
|
|
@@ -65,6 +68,7 @@ using namespace facebook::react;
|
|
|
65
68
|
BOOL _isApplyingFormatting;
|
|
66
69
|
BOOL _isTextChanging;
|
|
67
70
|
BOOL _emitMarkdown;
|
|
71
|
+
CFTimeInterval _lastTextChangeTime;
|
|
68
72
|
|
|
69
73
|
ENRMPlaceholderLabel *_placeholderLabel;
|
|
70
74
|
|
|
@@ -102,6 +106,20 @@ using namespace facebook::react;
|
|
|
102
106
|
BOOL _suppressMathChipClearOnce;
|
|
103
107
|
/// Character index of the currently selected math chip, or NSNotFound.
|
|
104
108
|
NSUInteger _activeMathChipIndex;
|
|
109
|
+
|
|
110
|
+
ENRMInputSelectionMenuConfig _inputSelectionMenuConfig;
|
|
111
|
+
ENRMFormatMenuConfig _formatMenuConfig;
|
|
112
|
+
|
|
113
|
+
// Strong owners for the strings the two config structs above reference via
|
|
114
|
+
// `__unsafe_unretained` pointers.
|
|
115
|
+
NSString *_inputSelectionMenuFormatLabel;
|
|
116
|
+
NSString *_inputSelectionMenuCopyAsMarkdownLabel;
|
|
117
|
+
NSString *_formatMenuBoldLabel;
|
|
118
|
+
NSString *_formatMenuItalicLabel;
|
|
119
|
+
NSString *_formatMenuUnderlineLabel;
|
|
120
|
+
NSString *_formatMenuStrikethroughLabel;
|
|
121
|
+
NSString *_formatMenuSpoilerLabel;
|
|
122
|
+
NSString *_formatMenuLinkLabel;
|
|
105
123
|
}
|
|
106
124
|
|
|
107
125
|
#pragma mark - Fabric lifecycle
|
|
@@ -139,6 +157,9 @@ using namespace facebook::react;
|
|
|
139
157
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
140
158
|
_resolvedLayoutDirection =
|
|
141
159
|
[[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
|
|
160
|
+
_inputSelectionMenuConfig = (ENRMInputSelectionMenuConfig){.format = YES, .copyAsMarkdown = YES};
|
|
161
|
+
_formatMenuConfig = (ENRMFormatMenuConfig){
|
|
162
|
+
.bold = YES, .italic = YES, .underline = YES, .strikethrough = YES, .spoiler = YES, .link = YES};
|
|
142
163
|
|
|
143
164
|
_suppressMathChipClearOnce = NO;
|
|
144
165
|
_activeMathChipIndex = NSNotFound;
|
|
@@ -232,6 +253,20 @@ using namespace facebook::react;
|
|
|
232
253
|
|
|
233
254
|
#if ENRICHED_MARKDOWN_MATH && !TARGET_OS_OSX
|
|
234
255
|
|
|
256
|
+
// Glyph rect of the chip at `index`, in _textView coordinates (the same space
|
|
257
|
+
// `locationInView:_textView` reports). Single source of truth for both the tap
|
|
258
|
+
// hit-test and the rect reported to JS.
|
|
259
|
+
- (CGRect)mathChipRectInTextViewAtIndex:(NSUInteger)index layoutManager:(NSLayoutManager *)lm
|
|
260
|
+
{
|
|
261
|
+
if (lm == nil)
|
|
262
|
+
return CGRectNull;
|
|
263
|
+
NSRange glyphRange = [lm glyphRangeForCharacterRange:NSMakeRange(index, 1) actualCharacterRange:NULL];
|
|
264
|
+
CGRect rect = [lm boundingRectForGlyphRange:glyphRange inTextContainer:_textView.textContainer];
|
|
265
|
+
rect.origin.x += _textView.textContainerInset.left;
|
|
266
|
+
rect.origin.y += _textView.textContainerInset.top;
|
|
267
|
+
return rect;
|
|
268
|
+
}
|
|
269
|
+
|
|
235
270
|
- (void)handleTapForMathChip:(UITapGestureRecognizer *)gesture
|
|
236
271
|
{
|
|
237
272
|
if (gesture.state != UIGestureRecognizerStateRecognized)
|
|
@@ -244,23 +279,28 @@ using namespace facebook::react;
|
|
|
244
279
|
if (idx < 0)
|
|
245
280
|
return;
|
|
246
281
|
NSTextStorage *storage = _textView.textStorage;
|
|
247
|
-
|
|
282
|
+
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
283
|
+
// The closest position can land just past the chip, so probe both `idx` and `idx-1`.
|
|
284
|
+
// `closestPositionToPoint:` has no distance bound — it clamps a tap anywhere in the
|
|
285
|
+
// text view (blank space past the text, textContainerInset padding) onto the nearest
|
|
286
|
+
// character. Requiring the touch to fall inside the chip's own glyph rect is what
|
|
287
|
+
// keeps a tap beside a chip from reading as a tap on it.
|
|
248
288
|
ENRMMathInlineAttachment *att = nil;
|
|
249
289
|
NSUInteger chipIdx = NSNotFound;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
290
|
+
for (NSInteger probe = idx; probe >= idx - 1; probe--) {
|
|
291
|
+
if (probe < 0 || (NSUInteger)probe >= storage.length)
|
|
292
|
+
continue;
|
|
293
|
+
id v = [storage attribute:NSAttachmentAttributeName atIndex:(NSUInteger)probe effectiveRange:NULL];
|
|
294
|
+
if (![v isKindOfClass:[ENRMMathInlineAttachment class]])
|
|
295
|
+
continue;
|
|
296
|
+
// An empty rect means the layout could not give us the chip's extent; accept the
|
|
297
|
+
// candidate then, rather than making the chip untappable.
|
|
298
|
+
CGRect rect = [self mathChipRectInTextViewAtIndex:(NSUInteger)probe layoutManager:lm];
|
|
299
|
+
if (!CGRectIsEmpty(rect) && !CGRectContainsPoint(rect, loc))
|
|
300
|
+
continue;
|
|
301
|
+
att = v;
|
|
302
|
+
chipIdx = (NSUInteger)probe;
|
|
303
|
+
break;
|
|
264
304
|
}
|
|
265
305
|
if (!att)
|
|
266
306
|
return;
|
|
@@ -269,7 +309,6 @@ using namespace facebook::react;
|
|
|
269
309
|
[self clearMathChipSelectionsExcept:chipRange];
|
|
270
310
|
att.isSelected = YES;
|
|
271
311
|
[att invalidateRender];
|
|
272
|
-
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
273
312
|
if (lm) {
|
|
274
313
|
[lm invalidateLayoutForCharacterRange:chipRange actualCharacterRange:NULL];
|
|
275
314
|
[lm ensureLayoutForCharacterRange:chipRange];
|
|
@@ -285,10 +324,8 @@ using namespace facebook::react;
|
|
|
285
324
|
_suppressMathChipClearOnce = YES;
|
|
286
325
|
_textView.selectedRange = NSMakeRange(caretIndex, 0);
|
|
287
326
|
|
|
288
|
-
|
|
289
|
-
CGRect glyphRect = [
|
|
290
|
-
glyphRect.origin.x += _textView.textContainerInset.left;
|
|
291
|
-
glyphRect.origin.y += _textView.textContainerInset.top;
|
|
327
|
+
// Recomputed rather than reused from the hit-test: the layout was invalidated above.
|
|
328
|
+
CGRect glyphRect = [self mathChipRectInTextViewAtIndex:chipIdx layoutManager:lm];
|
|
292
329
|
CGRect rectInSelf = [self.contentView convertRect:glyphRect fromView:_textView];
|
|
293
330
|
|
|
294
331
|
[self emitOnMathPressWithLatex:att.latex start:chipIdx end:chipIdx + 1 displayMode:att.displayMode rect:rectInSelf];
|
|
@@ -463,6 +500,44 @@ using namespace facebook::react;
|
|
|
463
500
|
_contextMenuItemIcons = ENRMContextMenuIconsFromItems(newViewProps.contextMenuItems);
|
|
464
501
|
}
|
|
465
502
|
|
|
503
|
+
// Coalesce against `initWithUTF8String:` returning nil on malformed UTF-8 —
|
|
504
|
+
// UIMenu / UIAction / NSMenuItem titles are _Nonnull and would crash.
|
|
505
|
+
_inputSelectionMenuFormatLabel =
|
|
506
|
+
[[NSString alloc] initWithUTF8String:newViewProps.selectionMenuConfig.formatLabel.c_str()] ?: @"";
|
|
507
|
+
_inputSelectionMenuCopyAsMarkdownLabel =
|
|
508
|
+
[[NSString alloc] initWithUTF8String:newViewProps.selectionMenuConfig.copyAsMarkdownLabel.c_str()] ?: @"";
|
|
509
|
+
_inputSelectionMenuConfig = (ENRMInputSelectionMenuConfig){
|
|
510
|
+
.format = newViewProps.selectionMenuConfig.format,
|
|
511
|
+
.formatLabel = _inputSelectionMenuFormatLabel,
|
|
512
|
+
.copyAsMarkdown = newViewProps.selectionMenuConfig.copyAsMarkdown,
|
|
513
|
+
.copyAsMarkdownLabel = _inputSelectionMenuCopyAsMarkdownLabel,
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
_formatMenuBoldLabel = [[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.boldLabel.c_str()] ?: @"";
|
|
517
|
+
_formatMenuItalicLabel =
|
|
518
|
+
[[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.italicLabel.c_str()] ?: @"";
|
|
519
|
+
_formatMenuUnderlineLabel =
|
|
520
|
+
[[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.underlineLabel.c_str()] ?: @"";
|
|
521
|
+
_formatMenuStrikethroughLabel =
|
|
522
|
+
[[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.strikethroughLabel.c_str()] ?: @"";
|
|
523
|
+
_formatMenuSpoilerLabel =
|
|
524
|
+
[[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.spoilerLabel.c_str()] ?: @"";
|
|
525
|
+
_formatMenuLinkLabel = [[NSString alloc] initWithUTF8String:newViewProps.formatMenuConfig.linkLabel.c_str()] ?: @"";
|
|
526
|
+
_formatMenuConfig = (ENRMFormatMenuConfig){
|
|
527
|
+
.bold = newViewProps.formatMenuConfig.bold,
|
|
528
|
+
.boldLabel = _formatMenuBoldLabel,
|
|
529
|
+
.italic = newViewProps.formatMenuConfig.italic,
|
|
530
|
+
.italicLabel = _formatMenuItalicLabel,
|
|
531
|
+
.underline = newViewProps.formatMenuConfig.underline,
|
|
532
|
+
.underlineLabel = _formatMenuUnderlineLabel,
|
|
533
|
+
.strikethrough = newViewProps.formatMenuConfig.strikethrough,
|
|
534
|
+
.strikethroughLabel = _formatMenuStrikethroughLabel,
|
|
535
|
+
.spoiler = newViewProps.formatMenuConfig.spoiler,
|
|
536
|
+
.spoilerLabel = _formatMenuSpoilerLabel,
|
|
537
|
+
.link = newViewProps.formatMenuConfig.link,
|
|
538
|
+
.linkLabel = _formatMenuLinkLabel,
|
|
539
|
+
};
|
|
540
|
+
|
|
466
541
|
if (newViewProps.mentionIndicators != oldViewProps.mentionIndicators) {
|
|
467
542
|
NSMutableArray<NSString *> *indicators = [NSMutableArray array];
|
|
468
543
|
for (const auto &indicator : newViewProps.mentionIndicators) {
|
|
@@ -964,6 +1039,7 @@ using namespace facebook::react;
|
|
|
964
1039
|
}
|
|
965
1040
|
|
|
966
1041
|
[self applyFormatting];
|
|
1042
|
+
[self syncTypingAttributesWithPendingStyles];
|
|
967
1043
|
[self emitFormattingChanged];
|
|
968
1044
|
}
|
|
969
1045
|
|
|
@@ -1221,6 +1297,21 @@ using namespace facebook::react;
|
|
|
1221
1297
|
return [ENRMMarkdownSerializer serializeAttributedString:selectedAttr ranges:clippedRanges];
|
|
1222
1298
|
}
|
|
1223
1299
|
|
|
1300
|
+
- (void)copyToClipboard
|
|
1301
|
+
{
|
|
1302
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1303
|
+
if (plainText.length == 0) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
NSString *markdown = [ENRMMarkdownSerializer serializePlainText:plainText ranges:[self allRangesIncludingTransient]];
|
|
1307
|
+
NSMutableDictionary *items = [NSMutableDictionary dictionary];
|
|
1308
|
+
items[kUTIPlainText] = plainText;
|
|
1309
|
+
if (markdown.length > 0) {
|
|
1310
|
+
items[kENRMMarkdownPasteboardType] = markdown;
|
|
1311
|
+
}
|
|
1312
|
+
copyItemsToPasteboard(items);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1224
1315
|
- (void)requestMarkdown:(NSInteger)requestId
|
|
1225
1316
|
{
|
|
1226
1317
|
auto emitter = [self getEventEmitter];
|
|
@@ -1293,6 +1384,57 @@ using namespace facebook::react;
|
|
|
1293
1384
|
return inRange;
|
|
1294
1385
|
}
|
|
1295
1386
|
|
|
1387
|
+
- (void)syncTypingAttributesWithPendingStyles
|
|
1388
|
+
{
|
|
1389
|
+
UIFontDescriptorSymbolicTraits traits = 0;
|
|
1390
|
+
if ([_pendingStyles containsObject:@(ENRMInputStyleTypeStrong)]) {
|
|
1391
|
+
traits |= UIFontDescriptorTraitBold;
|
|
1392
|
+
}
|
|
1393
|
+
if ([_pendingStyles containsObject:@(ENRMInputStyleTypeEmphasis)]) {
|
|
1394
|
+
traits |= UIFontDescriptorTraitItalic;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
NSMutableDictionary *attrs = [_textView.typingAttributes mutableCopy];
|
|
1398
|
+
attrs[NSFontAttributeName] = [_formatterStyle fontForTraits:traits];
|
|
1399
|
+
_textView.typingAttributes = attrs;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
- (void)resetPendingStylesForSelectionChange
|
|
1403
|
+
{
|
|
1404
|
+
// Skip system-driven selection adjustments (e.g., predictive text) that fire
|
|
1405
|
+
// immediately after a text edit.
|
|
1406
|
+
static const CFTimeInterval kPostEditGracePeriod = 0.1;
|
|
1407
|
+
BOOL isPostEditAdjustment =
|
|
1408
|
+
(_lastTextChangeTime > 0 && (CACurrentMediaTime() - _lastTextChangeTime) < kPostEditGracePeriod);
|
|
1409
|
+
if (isPostEditAdjustment) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
[_pendingStyles removeAllObjects];
|
|
1413
|
+
[_pendingStyleRemovals removeAllObjects];
|
|
1414
|
+
[self rebuildPendingStylesFromContext];
|
|
1415
|
+
[self syncTypingAttributesWithPendingStyles];
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
- (void)rebuildPendingStylesFromContext
|
|
1419
|
+
{
|
|
1420
|
+
NSUInteger cursor = _textView.selectedRange.location;
|
|
1421
|
+
if (_textView.selectedRange.length > 0 || cursor == 0) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
static const ENRMInputStyleType inlineStyles[] = {
|
|
1426
|
+
ENRMInputStyleTypeStrong, ENRMInputStyleTypeEmphasis, ENRMInputStyleTypeUnderline,
|
|
1427
|
+
ENRMInputStyleTypeStrikethrough, ENRMInputStyleTypeSpoiler,
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
for (NSUInteger i = 0; i < sizeof(inlineStyles) / sizeof(inlineStyles[0]); i++) {
|
|
1431
|
+
ENRMInputStyleType type = inlineStyles[i];
|
|
1432
|
+
if ([_formattingStore isStyleAdjacentBefore:type position:cursor]) {
|
|
1433
|
+
[_pendingStyles addObject:@(type)];
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1296
1438
|
#pragma mark - Event emitters
|
|
1297
1439
|
|
|
1298
1440
|
- (void)emitFormattingChanged
|
|
@@ -1531,6 +1673,16 @@ using namespace facebook::react;
|
|
|
1531
1673
|
return _contextMenuItemIcons ?: @[];
|
|
1532
1674
|
}
|
|
1533
1675
|
|
|
1676
|
+
- (ENRMInputSelectionMenuConfig)inputSelectionMenuConfig
|
|
1677
|
+
{
|
|
1678
|
+
return _inputSelectionMenuConfig;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
- (ENRMFormatMenuConfig)formatMenuConfig
|
|
1682
|
+
{
|
|
1683
|
+
return _formatMenuConfig;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1534
1686
|
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
1535
1687
|
{
|
|
1536
1688
|
auto eventEmitter = [self getEventEmitter];
|
|
@@ -1674,10 +1826,27 @@ using namespace facebook::react;
|
|
|
1674
1826
|
if (insertedLength > 0) {
|
|
1675
1827
|
NSRange insertedRange = NSMakeRange(editLocation, insertedLength);
|
|
1676
1828
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1829
|
+
// Skip applying pending styles when the insertion is only line breaks —
|
|
1830
|
+
// a phantom range over a bare newline corrupts isStyleActive() at the boundary.
|
|
1831
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1832
|
+
NSUInteger insertedEnd = NSMaxRange(insertedRange);
|
|
1833
|
+
BOOL insertedHasGlyphContent = NO;
|
|
1834
|
+
if (insertedEnd <= plainText.length) {
|
|
1835
|
+
NSCharacterSet *newlines = [NSCharacterSet newlineCharacterSet];
|
|
1836
|
+
for (NSUInteger i = insertedRange.location; i < insertedEnd; i++) {
|
|
1837
|
+
if (![newlines characterIsMember:[plainText characterAtIndex:i]]) {
|
|
1838
|
+
insertedHasGlyphContent = YES;
|
|
1839
|
+
break;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (insertedHasGlyphContent) {
|
|
1845
|
+
for (NSNumber *styleNum in _pendingStyles) {
|
|
1846
|
+
ENRMFormattingRange *newRange = [ENRMFormattingRange rangeWithType:(ENRMInputStyleType)styleNum.integerValue
|
|
1847
|
+
range:insertedRange];
|
|
1848
|
+
[_formattingStore addRange:newRange];
|
|
1849
|
+
}
|
|
1681
1850
|
}
|
|
1682
1851
|
|
|
1683
1852
|
// adjustForEditAtLocation may have expanded an existing range to cover
|
|
@@ -1781,6 +1950,7 @@ using namespace facebook::react;
|
|
|
1781
1950
|
}
|
|
1782
1951
|
[self handleTextChanged];
|
|
1783
1952
|
_isTextChanging = NO;
|
|
1953
|
+
_lastTextChangeTime = CACurrentMediaTime();
|
|
1784
1954
|
_lastSelectedRange = textView.selectedRange;
|
|
1785
1955
|
}
|
|
1786
1956
|
|
|
@@ -1798,6 +1968,15 @@ using namespace facebook::react;
|
|
|
1798
1968
|
- (void)textViewDidChangeSelection:(UITextView *)textView
|
|
1799
1969
|
{
|
|
1800
1970
|
NSRange newSelection = textView.selectedRange;
|
|
1971
|
+
// Links (e.g. mentions) are atomic: snap a partial selection to the whole link, and a caret inside
|
|
1972
|
+
// a link to its end. Returning hands the adjusted selection to the recursive change (no double emit).
|
|
1973
|
+
if (!_isApplyingFormatting && !_isTextChanging && !ENRMHasMarkedText(_textView)) {
|
|
1974
|
+
NSRange adjusted = [_formattingStore selectionAdjustedForAtomicLinks:newSelection];
|
|
1975
|
+
if (!NSEqualRanges(adjusted, newSelection)) {
|
|
1976
|
+
_textView.selectedRange = adjusted;
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1801
1980
|
NSRange previousSelection = _lastSelectedRange;
|
|
1802
1981
|
_lastSelectedRange = newSelection;
|
|
1803
1982
|
|
|
@@ -1805,6 +1984,10 @@ using namespace facebook::react;
|
|
|
1805
1984
|
return;
|
|
1806
1985
|
}
|
|
1807
1986
|
|
|
1987
|
+
if (ENRMHasMarkedText(_textView)) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1808
1991
|
#if ENRICHED_MARKDOWN_MATH
|
|
1809
1992
|
if (_suppressMathChipClearOnce) {
|
|
1810
1993
|
// Caret update was triggered by handleTapForMathChip — keep that chip
|
|
@@ -1820,8 +2003,7 @@ using namespace facebook::react;
|
|
|
1820
2003
|
newSelection.location != previousSelection.location || newSelection.length != previousSelection.length;
|
|
1821
2004
|
|
|
1822
2005
|
if (selectionMoved) {
|
|
1823
|
-
[
|
|
1824
|
-
[_pendingStyleRemovals removeAllObjects];
|
|
2006
|
+
[self resetPendingStylesForSelectionChange];
|
|
1825
2007
|
}
|
|
1826
2008
|
|
|
1827
2009
|
[self manageSelectionBasedChanges];
|
|
@@ -1928,19 +2110,38 @@ using namespace facebook::react;
|
|
|
1928
2110
|
}
|
|
1929
2111
|
[self handleTextChanged];
|
|
1930
2112
|
_isTextChanging = NO;
|
|
2113
|
+
_lastTextChangeTime = CACurrentMediaTime();
|
|
1931
2114
|
_lastSelectedRange = _textView.selectedRange;
|
|
1932
2115
|
}
|
|
1933
2116
|
|
|
1934
2117
|
- (void)textInputDidChangeSelection
|
|
1935
2118
|
{
|
|
1936
|
-
|
|
2119
|
+
NSRange newSelection = _textView.selectedRange;
|
|
2120
|
+
// Atomic link snapping — same logic as textViewDidChangeSelection: (iOS).
|
|
2121
|
+
if (!_isApplyingFormatting && !_isTextChanging && !ENRMHasMarkedText(_textView)) {
|
|
2122
|
+
NSRange adjusted = [_formattingStore selectionAdjustedForAtomicLinks:newSelection];
|
|
2123
|
+
if (!NSEqualRanges(adjusted, newSelection)) {
|
|
2124
|
+
_textView.selectedRange = adjusted;
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
NSRange previousSelection = _lastSelectedRange;
|
|
2129
|
+
_lastSelectedRange = newSelection;
|
|
1937
2130
|
|
|
1938
2131
|
if (_isApplyingFormatting || _isTextChanging) {
|
|
1939
2132
|
return;
|
|
1940
2133
|
}
|
|
1941
2134
|
|
|
1942
|
-
|
|
1943
|
-
|
|
2135
|
+
if (ENRMHasMarkedText(_textView)) {
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
BOOL selectionMoved =
|
|
2140
|
+
newSelection.location != previousSelection.location || newSelection.length != previousSelection.length;
|
|
2141
|
+
|
|
2142
|
+
if (selectionMoved) {
|
|
2143
|
+
[self resetPendingStylesForSelectionChange];
|
|
2144
|
+
}
|
|
1944
2145
|
|
|
1945
2146
|
[self emitOnChangeSelection];
|
|
1946
2147
|
[self updateActiveMention];
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
formattingRange:(ENRMFormattingRange *)formattingRange
|
|
23
23
|
style:(ENRMInputFormatterStyle *)style
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (style.boldColor) {
|
|
26
|
+
[storage addAttribute:NSForegroundColorAttributeName value:style.boldColor range:range];
|
|
27
|
+
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
@end
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
formattingRange:(ENRMFormattingRange *)formattingRange
|
|
23
23
|
style:(ENRMInputFormatterStyle *)style
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (style.italicColor) {
|
|
26
|
+
[storage addAttribute:NSForegroundColorAttributeName value:style.italicColor range:range];
|
|
27
|
+
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
@end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "LastElementUtils.h"
|
|
4
4
|
#import "MarkdownASTNode.h"
|
|
5
5
|
#import "NodeRenderer.h"
|
|
6
|
+
#import "ParagraphStyleUtils.h"
|
|
6
7
|
#import "RenderContext.h"
|
|
7
8
|
#import "RendererFactory.h"
|
|
8
9
|
#import "StyleConfig.h"
|
|
@@ -58,7 +59,32 @@
|
|
|
58
59
|
return output;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Trims trailing newlines after the last rendered block, with special handling for code blocks.
|
|
64
|
+
*
|
|
65
|
+
* For non-code-block tail elements: deletes all trailing newlines after the last non-newline
|
|
66
|
+
* character and zeroes the last element's paragraph spacing so it doesn't render a margin past
|
|
67
|
+
* the content.
|
|
68
|
+
*
|
|
69
|
+
* For code blocks (`isLastBlockACodeBlock` == YES): keeps the bottom padding spacer in place
|
|
70
|
+
* AND preserves a single trailing newline immediately after it (typically the
|
|
71
|
+
* codeBlockMarginBottom spacer that CodeBlockRenderer already appended; if none exists, one is
|
|
72
|
+
* synthesized here). That extra newline matters because of an iOS layout quirk:
|
|
73
|
+
*
|
|
74
|
+
* - iOS treats the trailing `\n` of the storage as a paragraph terminator and reports the
|
|
75
|
+
* line fragment for the paragraph after it via `extraLineFragmentRect`.
|
|
76
|
+
* - `boundingRectForGlyphRange:` excludes that extra line, and the graphics-context clip
|
|
77
|
+
* passed to `drawBackgroundForGlyphRange:` is tiled to the laid-out content area (rounded
|
|
78
|
+
* up to 128pt boundaries) which also excludes it.
|
|
79
|
+
* - If the bottom padding spacer were the trailing `\n`, its 16/19pt line fragment would
|
|
80
|
+
* fall into that excluded zone — the background rect would have to be manually extended
|
|
81
|
+
* past the clip and the bottom rounded corner would get sliced off at the tile boundary
|
|
82
|
+
* (the original visual bug in #354).
|
|
83
|
+
*
|
|
84
|
+
* By keeping a 1pt-tall tail newline beyond the bottom padding spacer, iOS lays the spacer
|
|
85
|
+
* out as a normal interior line fragment included in usedRect/boundingRect and inside the
|
|
86
|
+
* drawing tiles, so the corner renders intact and no rect extension is needed.
|
|
87
|
+
*/
|
|
62
88
|
- (void)removeTrailingSpacing:(NSMutableAttributedString *)output
|
|
63
89
|
{
|
|
64
90
|
if (output.length == 0)
|
|
@@ -83,17 +109,36 @@
|
|
|
83
109
|
|
|
84
110
|
// 2. Trim trailing characters
|
|
85
111
|
NSUInteger logicalEnd = NSMaxRange(lastContent);
|
|
86
|
-
BOOL isCodeBlock =
|
|
112
|
+
BOOL isCodeBlock = isLastBlockACodeBlock(output);
|
|
113
|
+
NSRange codeRange = NSMakeRange(0, 0);
|
|
87
114
|
if (isCodeBlock) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
[output attribute:CodeBlockAttributeName
|
|
116
|
+
atIndex:lastContent.location
|
|
117
|
+
longestEffectiveRange:&codeRange
|
|
118
|
+
inRange:NSMakeRange(0, output.length)];
|
|
119
|
+
NSUInteger codeEnd = NSMaxRange(codeRange);
|
|
120
|
+
if (codeEnd >= output.length) {
|
|
121
|
+
[output appendAttributedString:kNewlineAttributedString];
|
|
122
|
+
}
|
|
123
|
+
logicalEnd = codeEnd + 1;
|
|
91
124
|
}
|
|
92
125
|
|
|
93
126
|
if (logicalEnd < output.length) {
|
|
94
127
|
[output deleteCharactersInRange:NSMakeRange(logicalEnd, output.length - logicalEnd)];
|
|
95
128
|
}
|
|
96
129
|
|
|
130
|
+
if (isCodeBlock && NSMaxRange(codeRange) < output.length) {
|
|
131
|
+
NSUInteger tailIdx = NSMaxRange(codeRange);
|
|
132
|
+
[output removeAttribute:CodeBlockAttributeName range:NSMakeRange(tailIdx, 1)];
|
|
133
|
+
NSParagraphStyle *style = [output attribute:NSParagraphStyleAttributeName atIndex:tailIdx effectiveRange:NULL];
|
|
134
|
+
NSMutableParagraphStyle *mutableStyle = style ? [style mutableCopy] : [[NSMutableParagraphStyle alloc] init];
|
|
135
|
+
mutableStyle.paragraphSpacing = 0;
|
|
136
|
+
mutableStyle.paragraphSpacingBefore = 0;
|
|
137
|
+
mutableStyle.minimumLineHeight = 1;
|
|
138
|
+
mutableStyle.maximumLineHeight = 1;
|
|
139
|
+
[output addAttribute:NSParagraphStyleAttributeName value:mutableStyle range:NSMakeRange(tailIdx, 1)];
|
|
140
|
+
}
|
|
141
|
+
|
|
97
142
|
// 3. Zero out internal spacing for the last element (if not a code block)
|
|
98
143
|
if (!isCodeBlock) {
|
|
99
144
|
NSRange styleRange;
|
|
@@ -150,4 +195,4 @@
|
|
|
150
195
|
}
|
|
151
196
|
}
|
|
152
197
|
|
|
153
|
-
@end
|
|
198
|
+
@end
|
|
@@ -32,7 +32,11 @@
|
|
|
32
32
|
[attachment renderForMacOS];
|
|
33
33
|
#endif
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
NSMutableAttributedString *attachmentString =
|
|
36
|
+
[[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy];
|
|
37
|
+
if (currentFont) {
|
|
38
|
+
[attachmentString addAttribute:NSFontAttributeName value:currentFont range:NSMakeRange(0, attachmentString.length)];
|
|
39
|
+
}
|
|
36
40
|
[output appendAttributedString:attachmentString];
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
@interface ENRMAccessibilityLabels : NSObject
|
|
6
|
+
|
|
7
|
+
@property (nonatomic, copy) NSString *bulletPoint;
|
|
8
|
+
@property (nonatomic, copy) NSString *nestedBulletPoint;
|
|
9
|
+
@property (nonatomic, copy) NSString *orderedItem;
|
|
10
|
+
@property (nonatomic, copy) NSString *nestedOrderedItem;
|
|
11
|
+
@property (nonatomic, copy) NSString *blockquote;
|
|
12
|
+
@property (nonatomic, copy) NSString *nestedBlockquote;
|
|
13
|
+
@property (nonatomic, copy) NSString *tableRow;
|
|
14
|
+
@property (nonatomic, copy) NSString *mathEquation;
|
|
15
|
+
@property (nonatomic, copy) NSString *rotorHeadings;
|
|
16
|
+
@property (nonatomic, copy) NSString *rotorLinks;
|
|
17
|
+
@property (nonatomic, copy) NSString *rotorImages;
|
|
18
|
+
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
NS_ASSUME_NONNULL_END
|
|
22
|
+
|
|
23
|
+
#ifdef __cplusplus
|
|
24
|
+
template <typename T> static bool ENRMAccessibilityLabelsChanged(const T &oldLabels, const T &newLabels)
|
|
25
|
+
{
|
|
26
|
+
return oldLabels.list.bulletPoint != newLabels.list.bulletPoint ||
|
|
27
|
+
oldLabels.list.nestedBulletPoint != newLabels.list.nestedBulletPoint ||
|
|
28
|
+
oldLabels.list.orderedItem != newLabels.list.orderedItem ||
|
|
29
|
+
oldLabels.list.nestedOrderedItem != newLabels.list.nestedOrderedItem ||
|
|
30
|
+
oldLabels.blockquote.quote != newLabels.blockquote.quote ||
|
|
31
|
+
oldLabels.blockquote.nestedQuote != newLabels.blockquote.nestedQuote ||
|
|
32
|
+
oldLabels.table.row != newLabels.table.row || oldLabels.math.equation != newLabels.math.equation ||
|
|
33
|
+
oldLabels.rotor.headings != newLabels.rotor.headings || oldLabels.rotor.links != newLabels.rotor.links ||
|
|
34
|
+
oldLabels.rotor.images != newLabels.rotor.images;
|
|
35
|
+
}
|
|
36
|
+
#endif
|
|
@@ -25,8 +25,9 @@ NSMenu *_Nullable buildEditMenuForSelection(NSAttributedString *attributedText,
|
|
|
25
25
|
|
|
26
26
|
// Replace the system Copy item with our enhanced version (copies RTF/HTML/Markdown).
|
|
27
27
|
// This mirrors the iOS behaviour where we replace the standard-edit Copy action.
|
|
28
|
+
NSString *copyTitle = selectionMenuConfig.copyLabel;
|
|
28
29
|
NSMenuItem *enhancedCopy =
|
|
29
|
-
ENRMCreateMenuItem(
|
|
30
|
+
ENRMCreateMenuItem(copyTitle, ^{ copyAttributedStringToPasteboard(selectedText, markdown, styleConfig); });
|
|
30
31
|
NSInteger systemCopyIndex = [menu indexOfItemWithTarget:nil andAction:@selector(copy:)];
|
|
31
32
|
if (systemCopyIndex != NSNotFound) {
|
|
32
33
|
[menu removeItemAtIndex:systemCopyIndex];
|
|
@@ -39,13 +40,11 @@ NSMenu *_Nullable buildEditMenuForSelection(NSAttributedString *attributedText,
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (selectionMenuConfig.copyAsMarkdown && markdown.length > 0) {
|
|
42
|
-
[menu addItem:ENRMCreateMenuItem(
|
|
43
|
+
[menu addItem:ENRMCreateMenuItem(selectionMenuConfig.copyAsMarkdownLabel, ^{ copyStringToPasteboard(markdown); })];
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
if (selectionMenuConfig.copyImageURL && imageURLs.count > 0) {
|
|
46
|
-
NSString *title = (imageURLs.count
|
|
47
|
-
? @"Copy Image URL"
|
|
48
|
-
: [NSString stringWithFormat:@"Copy %lu Image URLs", (unsigned long)imageURLs.count];
|
|
47
|
+
NSString *title = ENRMResolveImageURLsTitle(selectionMenuConfig, imageURLs.count);
|
|
49
48
|
[menu addItem:ENRMCreateMenuItem(title, ^{
|
|
50
49
|
NSString *urlsToCopy = [imageURLs componentsJoinedByString:@"\n"];
|
|
51
50
|
copyStringToPasteboard(urlsToCopy);
|