react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.10
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/build.gradle +2 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +4 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +4 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +176 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +112 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +46 -8
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +43 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +25 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +7 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +34 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +88 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +53 -14
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +232 -0
- 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 +29 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +30 -7
- package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +25 -9
- package/docs/API_REFERENCE.md +183 -4
- package/docs/COPY_OPTIONS.md +40 -0
- package/docs/INPUT.md +3 -1
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/docs/WEB.md +1 -0
- package/ios/EnrichedMarkdown.mm +321 -9
- package/ios/EnrichedMarkdownText.mm +203 -6
- package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
- package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +8 -2
- package/ios/math/ENRMRaTeXBridge.swift +23 -5
- package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +20 -0
- package/ios/styles/StyleConfig.mm +84 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMBlockMenu.h +66 -0
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +213 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +120 -0
- package/ios/utils/ENRMSelectionChange.m +282 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.h +35 -6
- package/ios/utils/EditMenuUtils.m +171 -15
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +47 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +16 -2
- package/ios/views/ENRMCodeBlockContainerView.m +22 -19
- package/ios/views/ENRMMathContainerView.h +11 -0
- package/ios/views/ENRMMathContainerView.m +17 -20
- package/ios/views/TableContainerView.h +14 -0
- package/ios/views/TableContainerView.m +34 -18
- package/lib/module/EnrichedMarkdownNativeComponent.ts +138 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +14 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +169 -11
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +17 -4
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +17 -4
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +123 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +123 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +50 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +12 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +144 -11
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +66 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +138 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +22 -1
- package/src/native/EnrichedMarkdownText.tsx +220 -9
- package/src/normalizeMarkdownStyle.ts +15 -0
- package/src/normalizeMarkdownStyle.web.ts +13 -0
- package/src/types/MarkdownStyle.ts +51 -5
- package/src/types/MarkdownStyleInternal.ts +12 -0
- package/src/types/MarkdownTextProps.ts +151 -11
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +69 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#import "ENRMSelectionChange.h"
|
|
2
|
+
#import "ENRMLinkChip.h"
|
|
3
|
+
|
|
4
|
+
NSString *const ENRMSelectionActiveKey = @"active";
|
|
5
|
+
NSString *const ENRMSelectionSourceKey = @"source";
|
|
6
|
+
NSString *const ENRMSelectionTextKey = @"text";
|
|
7
|
+
NSString *const ENRMSelectionMarkdownKey = @"markdown";
|
|
8
|
+
NSString *const ENRMSelectionWholeKey = @"whole";
|
|
9
|
+
NSString *const ENRMSelectionRectKey = @"rect";
|
|
10
|
+
NSString *const ENRMSelectionStartRectKey = @"startRect";
|
|
11
|
+
NSString *const ENRMSelectionEndRectKey = @"endRect";
|
|
12
|
+
NSString *const ENRMSelectionAdjustingKey = @"adjusting";
|
|
13
|
+
NSString *const ENRMSelectionFullKey = @"full";
|
|
14
|
+
|
|
15
|
+
static NSValue *ENRMRectValue(CGRect rect)
|
|
16
|
+
{
|
|
17
|
+
#if TARGET_OS_OSX
|
|
18
|
+
return [NSValue valueWithRect:NSRectFromCGRect(rect)];
|
|
19
|
+
#else
|
|
20
|
+
return [NSValue valueWithCGRect:rect];
|
|
21
|
+
#endif
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
NSDictionary *ENRMInactiveSelectionPayload(void)
|
|
25
|
+
{
|
|
26
|
+
static NSDictionary *payload;
|
|
27
|
+
static dispatch_once_t onceToken;
|
|
28
|
+
dispatch_once(&onceToken, ^{
|
|
29
|
+
NSValue *zero = ENRMRectValue(CGRectZero);
|
|
30
|
+
payload = @{
|
|
31
|
+
ENRMSelectionActiveKey : @NO,
|
|
32
|
+
ENRMSelectionSourceKey : @"selection",
|
|
33
|
+
ENRMSelectionTextKey : @"",
|
|
34
|
+
ENRMSelectionMarkdownKey : @"",
|
|
35
|
+
ENRMSelectionWholeKey : @NO,
|
|
36
|
+
ENRMSelectionRectKey : zero,
|
|
37
|
+
ENRMSelectionStartRectKey : zero,
|
|
38
|
+
ENRMSelectionEndRectKey : zero,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
return payload;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
NSDictionary *ENRMBlockSelectionPayload(NSString *source, NSString *markdown, NSString *text, CGRect frame)
|
|
45
|
+
{
|
|
46
|
+
NSValue *rect = ENRMRectValue(frame);
|
|
47
|
+
return @{
|
|
48
|
+
ENRMSelectionActiveKey : @YES,
|
|
49
|
+
ENRMSelectionSourceKey : source,
|
|
50
|
+
ENRMSelectionTextKey : text ?: @"",
|
|
51
|
+
ENRMSelectionMarkdownKey : markdown ?: @"",
|
|
52
|
+
ENRMSelectionWholeKey : @YES,
|
|
53
|
+
ENRMSelectionRectKey : rect,
|
|
54
|
+
ENRMSelectionStartRectKey : rect,
|
|
55
|
+
ENRMSelectionEndRectKey : rect,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#if !TARGET_OS_OSX
|
|
60
|
+
NSDictionary *ENRMTextSelectionInTextView(UITextView *textView, UIView *targetView)
|
|
61
|
+
{
|
|
62
|
+
NSRange range = textView.selectedRange;
|
|
63
|
+
UITextRange *textRange = textView.selectedTextRange;
|
|
64
|
+
if (range.location == NSNotFound || range.length == 0 || textRange == nil || textRange.isEmpty) {
|
|
65
|
+
return nil;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
NSAttributedString *attributedText = textView.attributedText;
|
|
69
|
+
if (NSMaxRange(range) > attributedText.length) {
|
|
70
|
+
return nil;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
CGRect unionRect = CGRectNull;
|
|
74
|
+
CGRect startRect = CGRectNull;
|
|
75
|
+
CGRect endRect = CGRectNull;
|
|
76
|
+
CGRect firstRect = CGRectNull;
|
|
77
|
+
CGRect lastRect = CGRectNull;
|
|
78
|
+
for (UITextSelectionRect *selectionRect in [textView selectionRectsForRange:textRange]) {
|
|
79
|
+
CGRect rect = selectionRect.rect;
|
|
80
|
+
if (CGRectIsNull(rect) || CGRectIsInfinite(rect) || CGRectIsEmpty(rect)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
unionRect = CGRectUnion(unionRect, rect);
|
|
84
|
+
if (CGRectIsNull(firstRect)) {
|
|
85
|
+
firstRect = rect;
|
|
86
|
+
}
|
|
87
|
+
lastRect = rect;
|
|
88
|
+
if (selectionRect.containsStart && CGRectIsNull(startRect)) {
|
|
89
|
+
startRect = rect;
|
|
90
|
+
}
|
|
91
|
+
if (selectionRect.containsEnd) {
|
|
92
|
+
endRect = rect;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Not laid out yet: report once layout gives the range a position.
|
|
96
|
+
if (CGRectIsNull(unionRect)) {
|
|
97
|
+
return nil;
|
|
98
|
+
}
|
|
99
|
+
if (CGRectIsNull(startRect)) {
|
|
100
|
+
startRect = firstRect;
|
|
101
|
+
}
|
|
102
|
+
if (CGRectIsNull(endRect)) {
|
|
103
|
+
endRect = lastRect;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return @{
|
|
107
|
+
ENRMSelectionTextKey : ENRMPlainTextByRemovingLinkSpacers(attributedText, range),
|
|
108
|
+
ENRMSelectionFullKey : @(range.location == 0 && range.length >= attributedText.length - 1),
|
|
109
|
+
ENRMSelectionRectKey : ENRMRectValue([textView convertRect:unionRect toView:targetView]),
|
|
110
|
+
ENRMSelectionStartRectKey : ENRMRectValue([textView convertRect:startRect toView:targetView]),
|
|
111
|
+
ENRMSelectionEndRectKey : ENRMRectValue([textView convertRect:endRect toView:targetView]),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
#endif
|
|
115
|
+
|
|
116
|
+
@implementation ENRMSelectionChangeReporter {
|
|
117
|
+
NSDictionary * (^_snapshot)(void);
|
|
118
|
+
void (^_emit)(NSDictionary *);
|
|
119
|
+
NSDictionary *_lastReported;
|
|
120
|
+
BOOL _pending;
|
|
121
|
+
// Bumped on cancel so a scheduled block that already left the queue is a no-op.
|
|
122
|
+
NSUInteger _generation;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
- (instancetype)initWithSnapshot:(NSDictionary * (^)(void))snapshot emit:(void (^)(NSDictionary *))emit
|
|
126
|
+
{
|
|
127
|
+
if (self = [super init]) {
|
|
128
|
+
_snapshot = [snapshot copy];
|
|
129
|
+
_emit = [emit copy];
|
|
130
|
+
_lastReported = ENRMInactiveSelectionPayload();
|
|
131
|
+
}
|
|
132
|
+
return self;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)setEnabled:(BOOL)enabled
|
|
136
|
+
{
|
|
137
|
+
if (_enabled == enabled) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
_enabled = enabled;
|
|
141
|
+
[self cancel];
|
|
142
|
+
_lastReported = ENRMInactiveSelectionPayload();
|
|
143
|
+
if (enabled) {
|
|
144
|
+
[self schedule];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
- (void)schedule
|
|
149
|
+
{
|
|
150
|
+
if (!_enabled || _pending) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
_pending = YES;
|
|
154
|
+
NSUInteger generation = _generation;
|
|
155
|
+
__weak ENRMSelectionChangeReporter *weakSelf = self;
|
|
156
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
157
|
+
ENRMSelectionChangeReporter *strongSelf = weakSelf;
|
|
158
|
+
if (!strongSelf || !strongSelf->_pending || strongSelf->_generation != generation) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
strongSelf->_pending = NO;
|
|
162
|
+
[strongSelf report];
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
- (void)selectionChanged:(BOOL)hasSelection
|
|
167
|
+
{
|
|
168
|
+
if (hasSelection) {
|
|
169
|
+
[self schedule];
|
|
170
|
+
} else {
|
|
171
|
+
[self flushNow];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
- (void)flushNow
|
|
176
|
+
{
|
|
177
|
+
[self cancel];
|
|
178
|
+
[self report];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
- (void)forgetLastReported
|
|
182
|
+
{
|
|
183
|
+
_lastReported = ENRMInactiveSelectionPayload();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
- (void)cancel
|
|
187
|
+
{
|
|
188
|
+
_pending = NO;
|
|
189
|
+
_generation++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
- (void)report
|
|
193
|
+
{
|
|
194
|
+
if (!_enabled) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
NSDictionary *current = _snapshot();
|
|
198
|
+
if ([current isEqualToDictionary:_lastReported]) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
_lastReported = current;
|
|
202
|
+
_emit(current);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@end
|
|
206
|
+
|
|
207
|
+
#if !TARGET_OS_OSX
|
|
208
|
+
static void ENRMCollectGestures(UIView *view, NSMutableArray<UIGestureRecognizer *> *into)
|
|
209
|
+
{
|
|
210
|
+
for (UIGestureRecognizer *recognizer in view.gestureRecognizers) {
|
|
211
|
+
[into addObject:recognizer];
|
|
212
|
+
}
|
|
213
|
+
for (UIView *subview in view.subviews) {
|
|
214
|
+
ENRMCollectGestures(subview, into);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@implementation ENRMSelectionGestureTracker {
|
|
219
|
+
void (^_change)(void);
|
|
220
|
+
NSHashTable<UIGestureRecognizer *> *_gestures;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
- (instancetype)initWithChange:(void (^)(void))change
|
|
224
|
+
{
|
|
225
|
+
if (self = [super init]) {
|
|
226
|
+
_change = [change copy];
|
|
227
|
+
_gestures = [NSHashTable weakObjectsHashTable];
|
|
228
|
+
}
|
|
229
|
+
return self;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
- (void)dealloc
|
|
233
|
+
{
|
|
234
|
+
// Recognizers don't retain their targets.
|
|
235
|
+
for (UIGestureRecognizer *recognizer in _gestures) {
|
|
236
|
+
[recognizer removeTarget:self action:@selector(gestureChanged:)];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
- (void)trackTextView:(UITextView *)textView
|
|
241
|
+
{
|
|
242
|
+
NSMutableArray<UIGestureRecognizer *> *all = [NSMutableArray array];
|
|
243
|
+
ENRMCollectGestures(textView, all);
|
|
244
|
+
for (UIGestureRecognizer *recognizer in all) {
|
|
245
|
+
if ([_gestures containsObject:recognizer]) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (recognizer == textView.panGestureRecognizer || recognizer == textView.pinchGestureRecognizer) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
[_gestures addObject:recognizer];
|
|
255
|
+
[recognizer addTarget:self action:@selector(gestureChanged:)];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
- (void)reset
|
|
260
|
+
{
|
|
261
|
+
_adjusting = NO;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
- (void)gestureChanged:(UIGestureRecognizer *)recognizer
|
|
265
|
+
{
|
|
266
|
+
BOOL adjusting = NO;
|
|
267
|
+
for (UIGestureRecognizer *tracked in _gestures) {
|
|
268
|
+
UIGestureRecognizerState state = tracked.state;
|
|
269
|
+
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged) {
|
|
270
|
+
adjusting = YES;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (adjusting == _adjusting) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
_adjusting = adjusting;
|
|
278
|
+
_change();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@end
|
|
282
|
+
#endif
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "ContextMenuUtils.h"
|
|
2
|
+
#import "ENRMLinkChip.h"
|
|
2
3
|
#import "ENRMMenuAction.h"
|
|
3
4
|
#import "EditMenuUtils.h"
|
|
4
5
|
#import "PasteboardUtils.h"
|
|
@@ -19,7 +20,9 @@ NSMenu *_Nullable buildEditMenuForSelection(NSAttributedString *attributedText,
|
|
|
19
20
|
return menu;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// Link spacers only reserve layout space; keep them out of every copied flavor.
|
|
24
|
+
NSAttributedString *selectedText =
|
|
25
|
+
ENRMAttributedStringByRemovingLinkSpacers([attributedText attributedSubstringFromRange:range]);
|
|
23
26
|
NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
|
|
24
27
|
NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
|
|
25
28
|
|
|
@@ -9,6 +9,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
9
9
|
typedef struct {
|
|
10
10
|
BOOL copyAsMarkdown;
|
|
11
11
|
BOOL copyImageURL;
|
|
12
|
+
BOOL copyEnabled;
|
|
13
|
+
// Copy writes Markdown as plain text instead of rich text.
|
|
14
|
+
BOOL copyFormatMarkdown;
|
|
15
|
+
// Drop every system-provided item from the text selection menu.
|
|
16
|
+
BOOL systemItemsNone;
|
|
17
|
+
BOOL codeBlockCopyButton;
|
|
18
|
+
// Copy hands the selection to the owner (JS `onCopy`) instead of writing the
|
|
19
|
+
// pasteboard.
|
|
20
|
+
BOOL interceptCopy;
|
|
12
21
|
// The owner must keep these strings alive for the duration of the call (the
|
|
13
22
|
// view holds them in strong ivars).
|
|
14
23
|
__unsafe_unretained NSString *_Nullable copyLabel;
|
|
@@ -18,6 +27,10 @@ typedef struct {
|
|
|
18
27
|
__unsafe_unretained NSArray<NSString *> *_Nullable copyImageUrlPluralTemplates;
|
|
19
28
|
} ENRMSelectionMenuConfig;
|
|
20
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
|
+
|
|
21
34
|
/// Resolves the "Copy Image URL(s)" menu title for a given image count.
|
|
22
35
|
/// Uses the precomputed plural templates (indexed by count, 0..100) when present;
|
|
23
36
|
/// counts > 100 use the "other" form (copyImageUrlsLabel). Otherwise the
|
|
@@ -36,6 +49,13 @@ static inline NSString *ENRMResolveImageURLsTitle(ENRMSelectionMenuConfig config
|
|
|
36
49
|
return [titleTemplate stringByReplacingOccurrencesOfString:@"{count}" withString:[@(count) stringValue]];
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
/// Defaults matching the JS-side defaults, used until the first props update.
|
|
53
|
+
static inline ENRMSelectionMenuConfig ENRMDefaultSelectionMenuConfig(void)
|
|
54
|
+
{
|
|
55
|
+
return (ENRMSelectionMenuConfig){
|
|
56
|
+
.copyAsMarkdown = YES, .copyImageURL = YES, .copyEnabled = YES, .codeBlockCopyButton = YES};
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
#ifdef __cplusplus
|
|
40
60
|
|
|
41
61
|
/// Strong owners for the label strings in ENRMSelectionMenuConfig.
|
|
@@ -63,13 +83,18 @@ template <typename T> static inline ENRMSelectionMenuLabels ENRMParseSelectionMe
|
|
|
63
83
|
};
|
|
64
84
|
}
|
|
65
85
|
|
|
66
|
-
/// Builds an ENRMSelectionMenuConfig from pre-parsed labels and
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
/// Builds an ENRMSelectionMenuConfig from pre-parsed labels and the codegen struct's flags.
|
|
87
|
+
template <typename T>
|
|
88
|
+
static inline ENRMSelectionMenuConfig ENRMBuildSelectionMenuConfig(const ENRMSelectionMenuLabels &labels, const T &src)
|
|
69
89
|
{
|
|
70
90
|
return (ENRMSelectionMenuConfig){
|
|
71
|
-
.copyAsMarkdown = copyAsMarkdown,
|
|
72
|
-
.copyImageURL = copyImageUrl,
|
|
91
|
+
.copyAsMarkdown = src.copyAsMarkdown,
|
|
92
|
+
.copyImageURL = src.copyImageUrl,
|
|
93
|
+
.copyEnabled = src.copyEnabled,
|
|
94
|
+
.copyFormatMarkdown = src.copyFormatMarkdown,
|
|
95
|
+
.systemItemsNone = src.systemItemsNone,
|
|
96
|
+
.codeBlockCopyButton = src.codeBlockCopyButton,
|
|
97
|
+
.interceptCopy = src.interceptCopy,
|
|
73
98
|
.copyLabel = labels.copyLabel,
|
|
74
99
|
.copyAsMarkdownLabel = labels.copyAsMarkdownLabel,
|
|
75
100
|
.copyImageUrlLabel = labels.copyImageUrlLabel,
|
|
@@ -90,7 +115,11 @@ extern "C" {
|
|
|
90
115
|
UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
|
|
91
116
|
StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions,
|
|
92
117
|
NSArray<UIAction *> *_Nullable customActions,
|
|
93
|
-
ENRMSelectionMenuConfig selectionMenuConfig
|
|
118
|
+
ENRMSelectionMenuConfig selectionMenuConfig,
|
|
119
|
+
ENRMCopyRequestHandler _Nullable onCopyRequest) API_AVAILABLE(ios(16.0));
|
|
120
|
+
|
|
121
|
+
void ENRMSelectRangeAndPresentEditMenu(UITextView *textView, NSRange range);
|
|
122
|
+
void ENRMDismissSelectionEditMenuIfMoved(UITextView *textView);
|
|
94
123
|
#else
|
|
95
124
|
NSMenu *_Nullable buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range,
|
|
96
125
|
NSString *_Nullable cachedMarkdown, StyleConfig *styleConfig,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "EditMenuUtils.h"
|
|
2
|
+
#import "ENRMLinkChip.h"
|
|
2
3
|
#import "PasteboardUtils.h"
|
|
3
4
|
#import "StyleConfig.h"
|
|
4
5
|
#include <TargetConditionals.h>
|
|
@@ -10,14 +11,34 @@ static NSString *const kActionIdentifierCopy = @"com.swmansion.enriched.markdown
|
|
|
10
11
|
static NSString *const kActionIdentifierCopyMarkdown = @"com.swmansion.enriched.markdown.copyMarkdown";
|
|
11
12
|
static NSString *const kActionIdentifierCopyImageURL = @"com.swmansion.enriched.markdown.copyImageURL";
|
|
12
13
|
|
|
13
|
-
static UIAction *createCopyAction(NSAttributedString *selectedText, NSString *markdown,
|
|
14
|
-
|
|
14
|
+
static UIAction *_Nullable createCopyAction(NSAttributedString *selectedText, NSString *markdown, BOOL fullSelection,
|
|
15
|
+
StyleConfig *styleConfig, ENRMSelectionMenuConfig selectionMenuConfig,
|
|
16
|
+
ENRMCopyRequestHandler _Nullable onCopyRequest)
|
|
15
17
|
{
|
|
16
|
-
|
|
18
|
+
if (!selectionMenuConfig.copyEnabled)
|
|
19
|
+
return nil;
|
|
20
|
+
|
|
21
|
+
if (selectionMenuConfig.interceptCopy && onCopyRequest) {
|
|
22
|
+
NSString *text = selectedText.string;
|
|
23
|
+
NSString *copied = markdown.length > 0 ? markdown : text;
|
|
24
|
+
return [UIAction actionWithTitle:selectionMenuConfig.copyLabel
|
|
25
|
+
image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
|
|
26
|
+
identifier:kActionIdentifierCopy
|
|
27
|
+
handler:^(__kindof UIAction *action) { onCopyRequest(copied, text, fullSelection); }];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
BOOL writesMarkdown = selectionMenuConfig.copyFormatMarkdown;
|
|
31
|
+
return [UIAction actionWithTitle:selectionMenuConfig.copyLabel
|
|
17
32
|
image:[RCTUIImage systemImageNamed:@"doc.on.doc"]
|
|
18
33
|
identifier:kActionIdentifierCopy
|
|
19
34
|
handler:^(__kindof UIAction *action) {
|
|
20
|
-
|
|
35
|
+
if (!writesMarkdown) {
|
|
36
|
+
copyAttributedStringToPasteboard(selectedText, markdown, styleConfig);
|
|
37
|
+
} else {
|
|
38
|
+
// Plain text only: with HTML/RTF alongside, rich-paste targets
|
|
39
|
+
// would take those and drop the Markdown.
|
|
40
|
+
copyStringToPasteboard(markdown.length > 0 ? markdown : selectedText.string);
|
|
41
|
+
}
|
|
21
42
|
}];
|
|
22
43
|
}
|
|
23
44
|
|
|
@@ -48,10 +69,11 @@ static UIAction *_Nullable createCopyImageURLAction(NSArray<NSString *> *imageUR
|
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
/// Rebuilds the standard-edit menu with the system Copy swapped for the enhanced
|
|
51
|
-
/// copy action
|
|
52
|
-
///
|
|
53
|
-
/// long-press menu of a
|
|
54
|
-
|
|
72
|
+
/// copy action, or dropped when `copyAction` is nil (Copy disabled). All other
|
|
73
|
+
/// system items are kept — dropping them would remove Select All, which on
|
|
74
|
+
/// iOS 16+ is the only way to start a selection from the long-press menu of a
|
|
75
|
+
/// non-editable text view.
|
|
76
|
+
static UIMenu *createEnhancedStandardEditMenu(UIMenu *originalMenu, UIAction *_Nullable copyAction)
|
|
55
77
|
{
|
|
56
78
|
NSMutableArray<UIMenuElement *> *children = [NSMutableArray arrayWithCapacity:originalMenu.children.count];
|
|
57
79
|
BOOL replacedCopy = NO;
|
|
@@ -59,14 +81,16 @@ static UIMenu *createEnhancedStandardEditMenu(UIMenu *originalMenu, UIAction *co
|
|
|
59
81
|
for (UIMenuElement *element in originalMenu.children) {
|
|
60
82
|
if (!replacedCopy && [element isKindOfClass:[UICommand class]] &&
|
|
61
83
|
((UICommand *)element).action == @selector(copy:)) {
|
|
62
|
-
|
|
84
|
+
if (copyAction) {
|
|
85
|
+
[children addObject:copyAction];
|
|
86
|
+
}
|
|
63
87
|
replacedCopy = YES;
|
|
64
88
|
continue;
|
|
65
89
|
}
|
|
66
90
|
[children addObject:element];
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
if (!replacedCopy) {
|
|
93
|
+
if (!replacedCopy && copyAction) {
|
|
70
94
|
[children insertObject:copyAction atIndex:0];
|
|
71
95
|
}
|
|
72
96
|
|
|
@@ -95,13 +119,19 @@ static void insertOptionalAction(NSMutableArray<UIMenuElement *> *array, UIActio
|
|
|
95
119
|
UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange range, NSString *_Nullable cachedMarkdown,
|
|
96
120
|
StyleConfig *styleConfig, NSArray<UIMenuElement *> *suggestedActions,
|
|
97
121
|
NSArray<UIAction *> *_Nullable customActions,
|
|
98
|
-
ENRMSelectionMenuConfig selectionMenuConfig
|
|
122
|
+
ENRMSelectionMenuConfig selectionMenuConfig,
|
|
123
|
+
ENRMCopyRequestHandler _Nullable onCopyRequest) API_AVAILABLE(ios(16.0))
|
|
99
124
|
{
|
|
100
|
-
|
|
125
|
+
// Link spacers only reserve layout space; keep them out of every copied flavor.
|
|
126
|
+
NSAttributedString *selectedText =
|
|
127
|
+
ENRMAttributedStringByRemovingLinkSpacers([attributedText attributedSubstringFromRange:range]);
|
|
101
128
|
NSString *markdown = markdownForRange(attributedText, range, cachedMarkdown);
|
|
102
129
|
NSArray<NSString *> *imageURLs = imageURLsInRange(attributedText, range);
|
|
130
|
+
// Same threshold markdownForRange uses to return the cached markdown.
|
|
131
|
+
BOOL fullSelection = range.location == 0 && range.length >= attributedText.length - 1;
|
|
103
132
|
|
|
104
|
-
UIAction *copyAction =
|
|
133
|
+
UIAction *copyAction =
|
|
134
|
+
createCopyAction(selectedText, markdown, fullSelection, styleConfig, selectionMenuConfig, onCopyRequest);
|
|
105
135
|
UIAction *copyMarkdownAction = selectionMenuConfig.copyAsMarkdown
|
|
106
136
|
? createCopyMarkdownAction(markdown, selectionMenuConfig.copyAsMarkdownLabel)
|
|
107
137
|
: nil;
|
|
@@ -109,6 +139,16 @@ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange ra
|
|
|
109
139
|
selectionMenuConfig.copyImageURL ? createCopyImageURLAction(imageURLs, selectionMenuConfig) : nil;
|
|
110
140
|
|
|
111
141
|
NSMutableArray<UIMenuElement *> *result = [NSMutableArray array];
|
|
142
|
+
|
|
143
|
+
if (selectionMenuConfig.systemItemsNone) {
|
|
144
|
+
// Skip suggestedActions entirely. An empty result still yields an (empty)
|
|
145
|
+
// menu: returning nil would make UIKit present its default menu.
|
|
146
|
+
addOptionalAction(result, copyAction);
|
|
147
|
+
addOptionalAction(result, copyMarkdownAction);
|
|
148
|
+
addOptionalAction(result, copyImageURLAction);
|
|
149
|
+
return [UIMenu menuWithChildren:[(customActions ?: @[]) arrayByAddingObjectsFromArray:result]];
|
|
150
|
+
}
|
|
151
|
+
|
|
112
152
|
BOOL foundStandardEditMenu = NO;
|
|
113
153
|
|
|
114
154
|
for (UIMenuElement *element in suggestedActions) {
|
|
@@ -128,8 +168,8 @@ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange ra
|
|
|
128
168
|
}
|
|
129
169
|
|
|
130
170
|
if (!foundStandardEditMenu) {
|
|
131
|
-
|
|
132
|
-
insertOptionalAction(result,
|
|
171
|
+
insertOptionalAction(result, copyMarkdownAction, 0);
|
|
172
|
+
insertOptionalAction(result, copyAction, 0);
|
|
133
173
|
addOptionalAction(result, copyImageURLAction);
|
|
134
174
|
}
|
|
135
175
|
|
|
@@ -140,4 +180,120 @@ UIMenu *buildEditMenuForSelection(NSAttributedString *attributedText, NSRange ra
|
|
|
140
180
|
return [UIMenu menuWithChildren:result];
|
|
141
181
|
}
|
|
142
182
|
|
|
183
|
+
#import <objc/runtime.h>
|
|
184
|
+
|
|
185
|
+
static const void *kSelectionEditMenuPresenterKey = &kSelectionEditMenuPresenterKey;
|
|
186
|
+
|
|
187
|
+
API_AVAILABLE(ios(16.0))
|
|
188
|
+
@interface ENRMSelectionEditMenuPresenter : NSObject <UIEditMenuInteractionDelegate>
|
|
189
|
+
@property (nonatomic, weak) UITextView *textView;
|
|
190
|
+
@property (nonatomic, strong) UIEditMenuInteraction *interaction;
|
|
191
|
+
@property (nonatomic, assign) NSRange range;
|
|
192
|
+
@end
|
|
193
|
+
|
|
194
|
+
@implementation ENRMSelectionEditMenuPresenter
|
|
195
|
+
|
|
196
|
+
- (UIMenu *)editMenuInteraction:(UIEditMenuInteraction *)interaction
|
|
197
|
+
menuForConfiguration:(UIEditMenuConfiguration *)configuration
|
|
198
|
+
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions
|
|
199
|
+
{
|
|
200
|
+
UITextView *textView = self.textView;
|
|
201
|
+
id<UITextViewDelegate> delegate = textView.delegate;
|
|
202
|
+
if (!textView || ![delegate respondsToSelector:@selector(textView:editMenuForTextInRange:suggestedActions:)]) {
|
|
203
|
+
return nil;
|
|
204
|
+
}
|
|
205
|
+
return [delegate textView:textView editMenuForTextInRange:textView.selectedRange suggestedActions:suggestedActions];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
- (CGRect)editMenuInteraction:(UIEditMenuInteraction *)interaction
|
|
209
|
+
targetRectForConfiguration:(UIEditMenuConfiguration *)configuration
|
|
210
|
+
{
|
|
211
|
+
UITextView *textView = self.textView;
|
|
212
|
+
UITextRange *textRange = textView.selectedTextRange;
|
|
213
|
+
if (!textView || !textRange) {
|
|
214
|
+
return CGRectNull;
|
|
215
|
+
}
|
|
216
|
+
return [textView firstRectForRange:textRange];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@end
|
|
220
|
+
|
|
221
|
+
static BOOL selectionEditMenuTextViewHasActiveGesture(UITextView *textView)
|
|
222
|
+
{
|
|
223
|
+
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
|
|
224
|
+
if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged) {
|
|
225
|
+
return YES;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return NO;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static void presentSelectionEditMenu(UITextView *textView)
|
|
232
|
+
{
|
|
233
|
+
UITextRange *textRange = textView.selectedTextRange;
|
|
234
|
+
if (!textRange || textRange.isEmpty) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
CGRect rect = [textView firstRectForRange:textRange];
|
|
238
|
+
if (CGRectIsNull(rect) || CGRectIsEmpty(rect)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (@available(iOS 16.0, *)) {
|
|
242
|
+
ENRMSelectionEditMenuPresenter *presenter = objc_getAssociatedObject(textView, kSelectionEditMenuPresenterKey);
|
|
243
|
+
if (!presenter) {
|
|
244
|
+
presenter = [[ENRMSelectionEditMenuPresenter alloc] init];
|
|
245
|
+
presenter.textView = textView;
|
|
246
|
+
presenter.interaction = [[UIEditMenuInteraction alloc] initWithDelegate:presenter];
|
|
247
|
+
[textView addInteraction:presenter.interaction];
|
|
248
|
+
objc_setAssociatedObject(textView, kSelectionEditMenuPresenterKey, presenter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
249
|
+
}
|
|
250
|
+
presenter.range = textView.selectedRange;
|
|
251
|
+
UIEditMenuConfiguration *configuration =
|
|
252
|
+
[UIEditMenuConfiguration configurationWithIdentifier:nil
|
|
253
|
+
sourcePoint:CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect))];
|
|
254
|
+
[presenter.interaction presentEditMenuWithConfiguration:configuration];
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
#pragma clang diagnostic push
|
|
258
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
259
|
+
[[UIMenuController sharedMenuController] showMenuFromView:textView rect:rect];
|
|
260
|
+
#pragma clang diagnostic pop
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static void presentSelectionEditMenuWhenTouchEnds(UITextView *textView, NSRange range, NSUInteger attemptsLeft)
|
|
264
|
+
{
|
|
265
|
+
if (!NSEqualRanges(textView.selectedRange, range)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (attemptsLeft > 0 && selectionEditMenuTextViewHasActiveGesture(textView)) {
|
|
269
|
+
__weak UITextView *weakTextView = textView;
|
|
270
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(50 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
|
|
271
|
+
UITextView *strongTextView = weakTextView;
|
|
272
|
+
if (strongTextView) {
|
|
273
|
+
presentSelectionEditMenuWhenTouchEnds(strongTextView, range, attemptsLeft - 1);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
presentSelectionEditMenu(textView);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
void ENRMSelectRangeAndPresentEditMenu(UITextView *textView, NSRange range)
|
|
282
|
+
{
|
|
283
|
+
[textView becomeFirstResponder];
|
|
284
|
+
textView.selectedRange = range;
|
|
285
|
+
presentSelectionEditMenuWhenTouchEnds(textView, range, 200);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
void ENRMDismissSelectionEditMenuIfMoved(UITextView *textView)
|
|
289
|
+
{
|
|
290
|
+
if (@available(iOS 16.0, *)) {
|
|
291
|
+
ENRMSelectionEditMenuPresenter *presenter = objc_getAssociatedObject(textView, kSelectionEditMenuPresenterKey);
|
|
292
|
+
if (presenter && !NSEqualRanges(textView.selectedRange, presenter.range)) {
|
|
293
|
+
presenter.range = NSMakeRange(NSNotFound, 0);
|
|
294
|
+
[presenter.interaction dismissMenu];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
143
299
|
#endif
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "CodeBackground.h"
|
|
4
4
|
#import "ENRMFeatureFlags.h"
|
|
5
5
|
#import "ENRMImageAttachment.h"
|
|
6
|
+
#import "ENRMLinkChip.h"
|
|
6
7
|
#import "HighlightRenderer.h"
|
|
7
8
|
#if ENRICHED_MARKDOWN_MATH
|
|
8
9
|
#import "ENRMMathInlineAttachment.h"
|
|
@@ -460,7 +461,7 @@ static void generateInlineHTML(NSMutableString *html, NSAttributedString *attrib
|
|
|
460
461
|
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange attrRange, BOOL *stop) {
|
|
461
462
|
NSString *text = [string substringWithRange:attrRange];
|
|
462
463
|
|
|
463
|
-
if ([text isEqualToString:@"\n"])
|
|
464
|
+
if ([text isEqualToString:@"\n"] || attrs[ENRMLinkSpacerAttributeName])
|
|
464
465
|
return;
|
|
465
466
|
|
|
466
467
|
if ([text containsString:kObjectReplacementChar]) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "AccessibilityInfo.h"
|
|
3
3
|
#import "BlockquoteBorder.h"
|
|
4
4
|
#import "ENRMAccessibilityLabels.h"
|
|
5
|
+
#import "ENRMLinkChip.h"
|
|
5
6
|
#include <TargetConditionals.h>
|
|
6
7
|
|
|
7
8
|
typedef NS_ENUM(NSInteger, ElementType) { ElementTypeText, ElementTypeLink, ElementTypeImage };
|
|
@@ -113,7 +114,8 @@ static const CGFloat kFocusRectPadding = 2.0;
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
BOOL isImg = item[@"altText"] != nil;
|
|
116
|
-
|
|
117
|
+
// Link chips pad their text with spacer characters; keep them out of the spoken label.
|
|
118
|
+
NSString *label = isImg ? item[@"altText"] : ENRMPlainTextByRemovingLinkSpacers(textView.attributedText, itemRange);
|
|
117
119
|
[elements addObject:[self createElementForRange:itemRange
|
|
118
120
|
type:isImg ? ElementTypeImage : ElementTypeLink
|
|
119
121
|
text:label
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "BlockquoteBorder.h"
|
|
3
3
|
#import "ENRMFeatureFlags.h"
|
|
4
4
|
#import "ENRMImageAttachment.h"
|
|
5
|
+
#import "ENRMLinkChip.h"
|
|
5
6
|
#import "ENRMUIKit.h"
|
|
6
7
|
#import "HighlightRenderer.h"
|
|
7
8
|
#include <TargetConditionals.h>
|
|
@@ -152,7 +153,7 @@ NSString *_Nullable extractMarkdownFromAttributedString(NSAttributedString *attr
|
|
|
152
153
|
options:0
|
|
153
154
|
usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange attrRange, BOOL *stop) {
|
|
154
155
|
NSString *text = [[attributedText attributedSubstringFromRange:attrRange] string];
|
|
155
|
-
if (text.length == 0)
|
|
156
|
+
if (text.length == 0 || attrs[ENRMLinkSpacerAttributeName])
|
|
156
157
|
return;
|
|
157
158
|
|
|
158
159
|
// Images and Thematic Breaks
|