-
Notifications
You must be signed in to change notification settings - Fork 66
Expand file tree
/
Copy pathZeroWidthSpaceUtils.mm
More file actions
164 lines (137 loc) · 7.32 KB
/
Copy pathZeroWidthSpaceUtils.mm
File metadata and controls
164 lines (137 loc) · 7.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#import "ZeroWidthSpaceUtils.h"
#import "ReactNativeRichTextEditorView.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
#import "UIView+React.h"
@implementation ZeroWidthSpaceUtils
+ (void)handleZeroWidthSpacesInEditor:(id)editor {
ReactNativeRichTextEditorView *typedEditor = (ReactNativeRichTextEditorView *)editor;
if(typedEditor == nullptr) { return; }
[self removeSpacesIfNeededInEditor:typedEditor];
[self addSpacesIfNeededInEditor:typedEditor];
}
+ (void)removeSpacesIfNeededInEditor:(ReactNativeRichTextEditorView *)editor {
NSMutableArray *indexesToBeRemoved = [[NSMutableArray alloc] init];
NSRange preRemoveSelection = editor->textView.selectedRange;
for(int i = 0; i < editor->textView.textStorage.string.length; i++) {
unichar character = [editor->textView.textStorage.string characterAtIndex:i];
if(character == 0x200B) {
NSRange characterRange = NSMakeRange(i, 1);
NSRange paragraphRange = [editor->textView.textStorage.string paragraphRangeForRange:characterRange];
// having paragraph longer than 1 character means someone most likely added something and we probably can remove the space
BOOL removeSpace = paragraphRange.length > 1;
// exception; 2 characters paragraph with zero width space + newline
// here, we still need zero width space to keep the empty list items
if(paragraphRange.length == 2 &&
paragraphRange.location == i &&
[[NSCharacterSet newlineCharacterSet] characterIsMember:[editor->textView.textStorage.string characterAtIndex:i+1]]
) {
removeSpace = NO;
}
if(removeSpace) {
[indexesToBeRemoved addObject:@(characterRange.location)];
continue;
}
UnorderedListStyle *ulStyle = editor->stylesDict[@([UnorderedListStyle getStyleType])];
OrderedListStyle *olStyle = editor->stylesDict[@([OrderedListStyle getStyleType])];
BlockQuoteStyle *bqStyle = editor->stylesDict[@([BlockQuoteStyle getStyleType])];
// zero width spaces with no lists/blockquote styles on them get removed
if(![ulStyle detectStyle:characterRange] && ![olStyle detectStyle:characterRange] && ![bqStyle detectStyle:characterRange]) {
[indexesToBeRemoved addObject:@(characterRange.location)];
}
}
}
// do the removing
NSInteger offset = 0;
NSInteger postRemoveOffset = 0;
for(NSNumber *index in indexesToBeRemoved) {
NSRange replaceRange = NSMakeRange([index integerValue] + offset, 1);
[TextInsertionUtils replaceText:@"" at:replaceRange additionalAttributes:nullptr editor:editor withSelection:NO];
offset -= 1;
if([index integerValue] < preRemoveSelection.location) {
postRemoveOffset -= 1;
}
}
// fix the selection if needed
if(editor->textView.focused) {
[editor->textView reactFocus];
editor->textView.selectedRange = NSMakeRange(preRemoveSelection.location + postRemoveOffset, preRemoveSelection.length);
}
}
+ (void)addSpacesIfNeededInEditor:(ReactNativeRichTextEditorView *)editor {
UnorderedListStyle *ulStyle = editor->stylesDict[@([UnorderedListStyle getStyleType])];
OrderedListStyle *olStyle = editor->stylesDict[@([OrderedListStyle getStyleType])];
BlockQuoteStyle *bqStyle = editor->stylesDict[@([BlockQuoteStyle getStyleType])];
NSMutableArray *indexesToBeInserted = [[NSMutableArray alloc] init];
NSRange preAddSelection = editor->textView.selectedRange;
for(int i = 0; i < editor->textView.textStorage.string.length; i++) {
unichar character = [editor->textView.textStorage.string characterAtIndex:i];
if([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) {
NSRange characterRange = NSMakeRange(i, 1);
NSRange paragraphRange = [editor->textView.textStorage.string paragraphRangeForRange:characterRange];
if(paragraphRange.length == 1) {
if([ulStyle detectStyle:characterRange] || [olStyle detectStyle:characterRange] || [bqStyle detectStyle:characterRange]) {
// we have an empty list or quote item with no space: add it!
[indexesToBeInserted addObject:@(paragraphRange.location)];
}
}
}
}
// do the replacing
NSInteger offset = 0;
NSInteger postAddOffset = 0;
for(NSNumber *index in indexesToBeInserted) {
NSRange replaceRange = NSMakeRange([index integerValue] + offset, 1);
[TextInsertionUtils replaceText:@"\u200B\n" at:replaceRange additionalAttributes:nullptr editor:editor withSelection:NO];
offset += 1;
if([index integerValue] < preAddSelection.location) {
postAddOffset += 1;
}
}
// additional check for last index of the input
NSRange lastRange = NSMakeRange(editor->textView.textStorage.string.length, 0);
NSRange lastParagraphRange = [editor->textView.textStorage.string paragraphRangeForRange:lastRange];
if(lastParagraphRange.length == 0 && ([ulStyle detectStyle:lastRange] || [olStyle detectStyle:lastRange] || [bqStyle detectStyle:lastRange])) {
[TextInsertionUtils insertText:@"\u200B" at:lastRange.location additionalAttributes:nullptr editor:editor withSelection:NO];
}
// fix the selection if needed
if(editor->textView.focused) {
[editor->textView reactFocus];
editor->textView.selectedRange = NSMakeRange(preAddSelection.location + postAddOffset, preAddSelection.length);
}
}
+ (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text editor:(id)editor {
if(range.length != 1 || ![text isEqualToString:@""]) { return NO; }
ReactNativeRichTextEditorView *typedEditor = (ReactNativeRichTextEditorView *)editor;
if(typedEditor == nullptr) { return NO; }
unichar character = [typedEditor->textView.textStorage.string characterAtIndex:range.location];
// zero-width space got backspaced
if(character == 0x200B) {
// in such case: remove the whole line without the endline if there is one
NSRange paragraphRange = [typedEditor->textView.textStorage.string paragraphRangeForRange:range];
NSRange removalRange = paragraphRange;
// if whole paragraph gets removed then 0 length for style removal
NSRange styleRemovalRange = NSMakeRange(paragraphRange.location, 0);
if([[NSCharacterSet newlineCharacterSet] characterIsMember:[typedEditor->textView.textStorage.string characterAtIndex:NSMaxRange(paragraphRange) - 1]]) {
// if endline is there, don't remove it
removalRange = NSMakeRange(paragraphRange.location, paragraphRange.length - 1);
// if endline is left then 1 length for style removal
styleRemovalRange = NSMakeRange(paragraphRange.location, 1);
}
[TextInsertionUtils replaceText:@"" at:removalRange additionalAttributes:nullptr editor:typedEditor withSelection:YES];
// and then remove associated styling
UnorderedListStyle *ulStyle = typedEditor->stylesDict[@([UnorderedListStyle getStyleType])];
OrderedListStyle *olStyle = typedEditor->stylesDict[@([OrderedListStyle getStyleType])];
BlockQuoteStyle *bqStyle = typedEditor->stylesDict[@([BlockQuoteStyle getStyleType])];
if([ulStyle detectStyle:styleRemovalRange]) {
[ulStyle removeAttributes:styleRemovalRange];
} else if([olStyle detectStyle:styleRemovalRange]) {
[olStyle removeAttributes:styleRemovalRange];
} else if([bqStyle detectStyle:styleRemovalRange]) {
[bqStyle removeAttributes:styleRemovalRange];
}
return YES;
}
return NO;
}
@end