react-native-enriched 0.0.0 → 0.1.1
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/LICENSE +20 -0
- package/README.md +875 -0
- package/ReactNativeEnriched.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
- package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
- package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
- package/android/src/main/new_arch/CMakeLists.txt +56 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
- package/ios/EnrichedTextInputView.h +33 -0
- package/ios/EnrichedTextInputView.mm +1190 -0
- package/ios/EnrichedTextInputViewManager.mm +13 -0
- package/ios/config/InputConfig.h +67 -0
- package/ios/config/InputConfig.mm +382 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/ios/inputParser/InputParser.h +11 -0
- package/ios/inputParser/InputParser.mm +659 -0
- package/ios/inputTextView/InputTextView.h +6 -0
- package/ios/inputTextView/InputTextView.mm +115 -0
- package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
- package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
- package/ios/internals/EnrichedTextInputViewState.h +20 -0
- package/ios/styles/BlockQuoteStyle.mm +248 -0
- package/ios/styles/BoldStyle.mm +122 -0
- package/ios/styles/H1Style.mm +10 -0
- package/ios/styles/H2Style.mm +10 -0
- package/ios/styles/H3Style.mm +10 -0
- package/ios/styles/HeadingStyleBase.mm +144 -0
- package/ios/styles/InlineCodeStyle.mm +163 -0
- package/ios/styles/ItalicStyle.mm +110 -0
- package/ios/styles/LinkStyle.mm +463 -0
- package/ios/styles/MentionStyle.mm +476 -0
- package/ios/styles/OrderedListStyle.mm +225 -0
- package/ios/styles/StrikethroughStyle.mm +80 -0
- package/ios/styles/UnderlineStyle.mm +112 -0
- package/ios/styles/UnorderedListStyle.mm +225 -0
- package/ios/utils/BaseStyleProtocol.h +16 -0
- package/ios/utils/ColorExtension.h +6 -0
- package/ios/utils/ColorExtension.mm +27 -0
- package/ios/utils/FontExtension.h +13 -0
- package/ios/utils/FontExtension.mm +91 -0
- package/ios/utils/LayoutManagerExtension.h +6 -0
- package/ios/utils/LayoutManagerExtension.mm +171 -0
- package/ios/utils/LinkData.h +9 -0
- package/ios/utils/LinkData.mm +4 -0
- package/ios/utils/MentionParams.h +9 -0
- package/ios/utils/MentionParams.mm +4 -0
- package/ios/utils/MentionStyleProps.h +13 -0
- package/ios/utils/MentionStyleProps.mm +56 -0
- package/ios/utils/OccurenceUtils.h +37 -0
- package/ios/utils/OccurenceUtils.mm +124 -0
- package/ios/utils/ParagraphsUtils.h +7 -0
- package/ios/utils/ParagraphsUtils.mm +54 -0
- package/ios/utils/StringExtension.h +15 -0
- package/ios/utils/StringExtension.mm +57 -0
- package/ios/utils/StyleHeaders.h +74 -0
- package/ios/utils/StylePair.h +9 -0
- package/ios/utils/StylePair.mm +4 -0
- package/ios/utils/StyleTypeEnum.h +22 -0
- package/ios/utils/TextDecorationLineEnum.h +6 -0
- package/ios/utils/TextDecorationLineEnum.mm +4 -0
- package/ios/utils/TextInsertionUtils.h +6 -0
- package/ios/utils/TextInsertionUtils.mm +48 -0
- package/ios/utils/WordsUtils.h +6 -0
- package/ios/utils/WordsUtils.mm +88 -0
- package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
- package/lib/module/EnrichedTextInput.js +191 -0
- package/lib/module/EnrichedTextInput.js.map +1 -0
- package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeHtmlStyle.js +141 -0
- package/lib/module/normalizeHtmlStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
- package/package.json +172 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedTextInput.tsx +358 -0
- package/src/EnrichedTextInputNativeComponent.ts +235 -0
- package/src/index.tsx +9 -0
- package/src/normalizeHtmlStyle.ts +188 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#import "InputTextView.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
#import "StringExtension.h"
|
|
4
|
+
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
|
5
|
+
#import "TextInsertionUtils.h"
|
|
6
|
+
|
|
7
|
+
@implementation InputTextView
|
|
8
|
+
|
|
9
|
+
- (void)copy:(id)sender {
|
|
10
|
+
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)_input;
|
|
11
|
+
if(typedInput == nullptr) { return; }
|
|
12
|
+
|
|
13
|
+
// remove zero width spaces before copying the text
|
|
14
|
+
NSString *plainText = [typedInput->textView.textStorage.string substringWithRange:typedInput->textView.selectedRange];
|
|
15
|
+
NSString *fixedPlainText = [plainText stringByReplacingOccurrencesOfString:@"\u200B" withString:@""];
|
|
16
|
+
|
|
17
|
+
NSString *escapedHtml = [NSString stringByEscapingHtml:[typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange]];
|
|
18
|
+
|
|
19
|
+
NSMutableAttributedString *attrStr = [[typedInput->textView.textStorage attributedSubstringFromRange:typedInput->textView.selectedRange] mutableCopy];
|
|
20
|
+
NSRange fullAttrStrRange = NSMakeRange(0, attrStr.length);
|
|
21
|
+
[attrStr.mutableString replaceOccurrencesOfString:@"\u200B" withString:@"" options:0 range:fullAttrStrRange];
|
|
22
|
+
|
|
23
|
+
NSData *rtfData = [attrStr dataFromRange:NSMakeRange(0, attrStr.length)
|
|
24
|
+
documentAttributes:@{NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType}
|
|
25
|
+
error:nullptr
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
|
29
|
+
[pasteboard setItems:@[@{
|
|
30
|
+
UTTypeUTF8PlainText.identifier : fixedPlainText,
|
|
31
|
+
UTTypeHTML.identifier : escapedHtml,
|
|
32
|
+
UTTypeRTF.identifier : rtfData
|
|
33
|
+
}]];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)paste:(id)sender {
|
|
37
|
+
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)_input;
|
|
38
|
+
if(typedInput == nullptr) { return; }
|
|
39
|
+
|
|
40
|
+
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
|
41
|
+
NSArray<NSString *> *pasteboardTypes = pasteboard.pasteboardTypes;
|
|
42
|
+
NSRange currentRange = typedInput->textView.selectedRange;
|
|
43
|
+
|
|
44
|
+
if([pasteboardTypes containsObject:UTTypeHTML.identifier]) {
|
|
45
|
+
// we try processing the html contents
|
|
46
|
+
|
|
47
|
+
NSString *htmlString;
|
|
48
|
+
id htmlValue = [pasteboard valueForPasteboardType:UTTypeHTML.identifier];
|
|
49
|
+
|
|
50
|
+
if([htmlValue isKindOfClass:[NSData class]]) {
|
|
51
|
+
htmlString = [[NSString alloc]initWithData:htmlValue encoding:NSUTF8StringEncoding];
|
|
52
|
+
} else if([htmlValue isKindOfClass:[NSString class]]) {
|
|
53
|
+
htmlString = htmlValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// unescape the html
|
|
57
|
+
htmlString = [NSString stringByUnescapingHtml:htmlString];
|
|
58
|
+
// validate it
|
|
59
|
+
NSString *initiallyProcessedHtml = [typedInput->parser initiallyProcessHtml:htmlString];
|
|
60
|
+
|
|
61
|
+
if(initiallyProcessedHtml != nullptr) {
|
|
62
|
+
// valid html, let's apply it
|
|
63
|
+
currentRange.length > 0
|
|
64
|
+
? [typedInput->parser replaceFromHtml:initiallyProcessedHtml range:currentRange]
|
|
65
|
+
: [typedInput->parser insertFromHtml:initiallyProcessedHtml location:currentRange.location];
|
|
66
|
+
} else {
|
|
67
|
+
// fall back to plain text, otherwise do nothing
|
|
68
|
+
[self tryHandlingPlainTextItemsIn:pasteboard range:currentRange input:typedInput];
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
[self tryHandlingPlainTextItemsIn:pasteboard range:currentRange input:typedInput];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[typedInput anyTextMayHaveBeenModified];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (void)tryHandlingPlainTextItemsIn:(UIPasteboard *)pasteboard range:(NSRange)range input:(EnrichedTextInputView *)input {
|
|
78
|
+
NSArray *existingTypes = pasteboard.pasteboardTypes;
|
|
79
|
+
NSArray *handledTypes = @[UTTypeUTF8PlainText.identifier, UTTypePlainText.identifier];
|
|
80
|
+
NSString *plainText;
|
|
81
|
+
|
|
82
|
+
for(NSString *type in handledTypes) {
|
|
83
|
+
if(![existingTypes containsObject:type]) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
id value = [pasteboard valueForPasteboardType:type];
|
|
88
|
+
|
|
89
|
+
if([value isKindOfClass:[NSData class]]) {
|
|
90
|
+
plainText = [[NSString alloc]initWithData:value encoding:NSUTF8StringEncoding];
|
|
91
|
+
} else if([value isKindOfClass:[NSString class]]) {
|
|
92
|
+
plainText = (NSString *)value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if(!plainText) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
range.length > 0
|
|
101
|
+
? [TextInsertionUtils replaceText:plainText at:range additionalAttributes:nullptr input:input withSelection:YES]
|
|
102
|
+
: [TextInsertionUtils insertText:plainText at:range.location additionalAttributes:nullptr input:input withSelection:YES];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)cut:(id)sender {
|
|
106
|
+
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)_input;
|
|
107
|
+
if(typedInput == nullptr) { return; }
|
|
108
|
+
|
|
109
|
+
[self copy:sender];
|
|
110
|
+
[TextInsertionUtils replaceText:@"" at:typedInput->textView.selectedRange additionalAttributes:nullptr input:typedInput withSelection:YES];
|
|
111
|
+
|
|
112
|
+
[typedInput anyTextMayHaveBeenModified];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <react/debug/react_native_assert.h>
|
|
3
|
+
#include <ReactNativeEnriched/Props.h>
|
|
4
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
5
|
+
#include <ReactNativeEnriched/EnrichedTextInputViewShadowNode.h>
|
|
6
|
+
|
|
7
|
+
namespace facebook::react {
|
|
8
|
+
class EnrichedTextInputViewComponentDescriptor final : public ConcreteComponentDescriptor<EnrichedTextInputViewShadowNode> {
|
|
9
|
+
public:
|
|
10
|
+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
11
|
+
void adopt(ShadowNode &shadowNode) const override {
|
|
12
|
+
react_native_assert(dynamic_cast<EnrichedTextInputViewShadowNode *>(&shadowNode));
|
|
13
|
+
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <ReactNativeEnriched/EventEmitters.h>
|
|
3
|
+
#include <ReactNativeEnriched/Props.h>
|
|
4
|
+
#include <ReactNativeEnriched/EnrichedTextInputViewState.h>
|
|
5
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
6
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
7
|
+
#include <jsi/jsi.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
JSI_EXPORT extern const char EnrichedTextInputViewComponentName[];
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* `ShadowNode` for <EnrichedTextInputView> component.
|
|
15
|
+
*/
|
|
16
|
+
class EnrichedTextInputViewShadowNode : public ConcreteViewShadowNode<
|
|
17
|
+
EnrichedTextInputViewComponentName,
|
|
18
|
+
EnrichedTextInputViewProps,
|
|
19
|
+
EnrichedTextInputViewEventEmitter,
|
|
20
|
+
EnrichedTextInputViewState> {
|
|
21
|
+
public:
|
|
22
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
23
|
+
EnrichedTextInputViewShadowNode(const ShadowNodeFragment& fragment, const ShadowNodeFamily::Shared& family, ShadowNodeTraits traits);
|
|
24
|
+
EnrichedTextInputViewShadowNode(const ShadowNode& sourceShadowNode, const ShadowNodeFragment& fragment);
|
|
25
|
+
void dirtyLayoutIfNeeded();
|
|
26
|
+
Size measureContent(const LayoutContext& layoutContext, const LayoutConstraints& layoutConstraints) const override;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
static ShadowNodeTraits BaseTraits() {
|
|
30
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
31
|
+
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
|
|
32
|
+
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
|
|
33
|
+
return traits;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private:
|
|
37
|
+
int localForceHeightRecalculationCounter_;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "EnrichedTextInputViewShadowNode.h"
|
|
2
|
+
#import <EnrichedTextInputView.h>
|
|
3
|
+
#import <react/utils/ManagedObjectWrapper.h>
|
|
4
|
+
#import <yoga/Yoga.h>
|
|
5
|
+
#import <React/RCTShadowView+Layout.h>
|
|
6
|
+
#import "CoreText/CoreText.h"
|
|
7
|
+
|
|
8
|
+
namespace facebook::react {
|
|
9
|
+
|
|
10
|
+
extern const char EnrichedTextInputViewComponentName[] = "EnrichedTextInputView";
|
|
11
|
+
|
|
12
|
+
EnrichedTextInputViewShadowNode::EnrichedTextInputViewShadowNode(
|
|
13
|
+
const ShadowNodeFragment& fragment,
|
|
14
|
+
const ShadowNodeFamily::Shared& family,
|
|
15
|
+
ShadowNodeTraits traits
|
|
16
|
+
): ConcreteViewShadowNode(fragment, family, traits) {
|
|
17
|
+
localForceHeightRecalculationCounter_ = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
EnrichedTextInputViewShadowNode::EnrichedTextInputViewShadowNode(
|
|
21
|
+
const ShadowNode& sourceShadowNode,
|
|
22
|
+
const ShadowNodeFragment& fragment
|
|
23
|
+
): ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
24
|
+
dirtyLayoutIfNeeded();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void EnrichedTextInputViewShadowNode::dirtyLayoutIfNeeded() {
|
|
28
|
+
const auto state = this->getStateData();
|
|
29
|
+
const int receivedCounter = state.getForceHeightRecalculationCounter();
|
|
30
|
+
|
|
31
|
+
if(receivedCounter > localForceHeightRecalculationCounter_) {
|
|
32
|
+
localForceHeightRecalculationCounter_ = receivedCounter;
|
|
33
|
+
YGNodeMarkDirty(&yogaNode_);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Size EnrichedTextInputViewShadowNode::measureContent(const LayoutContext& layoutContext, const LayoutConstraints& layoutConstraints) const {
|
|
38
|
+
const auto state = this->getStateData();
|
|
39
|
+
const auto componentRef = state.getComponentViewRef();
|
|
40
|
+
RCTInternalGenericWeakWrapper *weakWrapper = (RCTInternalGenericWeakWrapper *)unwrapManagedObject(componentRef);
|
|
41
|
+
|
|
42
|
+
if(weakWrapper != nullptr) {
|
|
43
|
+
id componentObject = weakWrapper.object;
|
|
44
|
+
EnrichedTextInputView *typedComponentObject = (EnrichedTextInputView *) componentObject;
|
|
45
|
+
|
|
46
|
+
if(typedComponentObject != nullptr) {
|
|
47
|
+
__block CGSize estimatedSize;
|
|
48
|
+
|
|
49
|
+
// synchronously dispatch to main thread if needed
|
|
50
|
+
if([NSThread isMainThread]) {
|
|
51
|
+
estimatedSize = [typedComponentObject measureSize:layoutConstraints.maximumSize.width];
|
|
52
|
+
} else {
|
|
53
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
54
|
+
estimatedSize = [typedComponentObject measureSize:layoutConstraints.maximumSize.width];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {estimatedSize.width, estimatedSize.height};
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// on the very first call there is no componentView that we can query for the component height
|
|
62
|
+
// thus, a little heuristic: just put a height that is exactly height of letter "I" with default apple font and size from props
|
|
63
|
+
// in a lot of cases it will be the desired height
|
|
64
|
+
// in others, the jump on the second call will at least be smaller
|
|
65
|
+
const auto props = this->getProps();
|
|
66
|
+
const auto &typedProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(props);
|
|
67
|
+
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"I" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:typedProps.fontSize]}];
|
|
68
|
+
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
|
|
69
|
+
const CGSize &suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(
|
|
70
|
+
framesetter,
|
|
71
|
+
CFRangeMake(0, 1),
|
|
72
|
+
nullptr,
|
|
73
|
+
CGSizeMake(layoutConstraints.maximumSize.width, DBL_MAX),
|
|
74
|
+
nullptr
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {suggestedSize.width, suggestedSize.height};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Size();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#include "EnrichedTextInputViewState.h"
|
|
2
|
+
|
|
3
|
+
namespace facebook::react {
|
|
4
|
+
int EnrichedTextInputViewState::getForceHeightRecalculationCounter() const {
|
|
5
|
+
return forceHeightRecalculationCounter_;
|
|
6
|
+
}
|
|
7
|
+
std::shared_ptr<void> EnrichedTextInputViewState::getComponentViewRef() const {
|
|
8
|
+
return componentViewRef_;
|
|
9
|
+
}
|
|
10
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <memory>
|
|
3
|
+
|
|
4
|
+
namespace facebook::react {
|
|
5
|
+
|
|
6
|
+
class EnrichedTextInputViewState {
|
|
7
|
+
public:
|
|
8
|
+
EnrichedTextInputViewState(): forceHeightRecalculationCounter_(0), componentViewRef_(nullptr) {}
|
|
9
|
+
EnrichedTextInputViewState(int counter, std::shared_ptr<void> ref) {
|
|
10
|
+
forceHeightRecalculationCounter_ = counter;
|
|
11
|
+
componentViewRef_ = ref;
|
|
12
|
+
}
|
|
13
|
+
int getForceHeightRecalculationCounter() const;
|
|
14
|
+
std::shared_ptr<void> getComponentViewRef() const;
|
|
15
|
+
private:
|
|
16
|
+
int forceHeightRecalculationCounter_{};
|
|
17
|
+
std::shared_ptr<void> componentViewRef_{};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
#import "OccurenceUtils.h"
|
|
4
|
+
#import "ParagraphsUtils.h"
|
|
5
|
+
#import "TextInsertionUtils.h"
|
|
6
|
+
#import "ColorExtension.h"
|
|
7
|
+
|
|
8
|
+
@implementation BlockQuoteStyle {
|
|
9
|
+
EnrichedTextInputView *_input;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
+ (StyleType)getStyleType { return BlockQuote; }
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithInput:(id)input {
|
|
15
|
+
self = [super init];
|
|
16
|
+
_input = (EnrichedTextInputView *)input;
|
|
17
|
+
return self;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
- (CGFloat)getHeadIndent {
|
|
21
|
+
// rectangle width + gap
|
|
22
|
+
return [_input->config blockquoteBorderWidth] + [_input->config blockquoteGapWidth];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// the range will already be the full paragraph/s range
|
|
26
|
+
- (void)applyStyle:(NSRange)range {
|
|
27
|
+
BOOL isStylePresent = [self detectStyle:range];
|
|
28
|
+
if(range.length >= 1) {
|
|
29
|
+
isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
|
|
30
|
+
} else {
|
|
31
|
+
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (void)addAttributes:(NSRange)range {
|
|
36
|
+
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
|
|
37
|
+
// if we fill empty lines with zero width spaces, we need to offset later ranges
|
|
38
|
+
NSInteger offset = 0;
|
|
39
|
+
NSRange preModificationRange = _input->textView.selectedRange;
|
|
40
|
+
|
|
41
|
+
// to not emit any space filling selection/text changes
|
|
42
|
+
_input->blockEmitting = YES;
|
|
43
|
+
|
|
44
|
+
for(NSValue *value in paragraphs) {
|
|
45
|
+
NSRange pRange = NSMakeRange([value rangeValue].location + offset, [value rangeValue].length);
|
|
46
|
+
|
|
47
|
+
// length 0 with first line, length 1 and newline with some empty lines in the middle
|
|
48
|
+
if(pRange.length == 0 ||
|
|
49
|
+
(pRange.length == 1 &&
|
|
50
|
+
[[NSCharacterSet newlineCharacterSet] characterIsMember: [_input->textView.textStorage.string characterAtIndex:pRange.location]])
|
|
51
|
+
) {
|
|
52
|
+
[TextInsertionUtils insertText:@"\u200B" at:pRange.location additionalAttributes:nullptr input:_input withSelection:NO];
|
|
53
|
+
pRange = NSMakeRange(pRange.location, pRange.length + 1);
|
|
54
|
+
offset += 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
|
|
58
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
59
|
+
NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
|
|
60
|
+
pStyle.headIndent = [self getHeadIndent];
|
|
61
|
+
pStyle.firstLineHeadIndent = [self getHeadIndent];
|
|
62
|
+
[_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// back to emitting
|
|
68
|
+
_input->blockEmitting = NO;
|
|
69
|
+
|
|
70
|
+
if(preModificationRange.length == 0) {
|
|
71
|
+
// fix selection if only one line was possibly made a list and filled with a space
|
|
72
|
+
_input->textView.selectedRange = preModificationRange;
|
|
73
|
+
} else {
|
|
74
|
+
// in other cases, fix the selection with newly made offsets
|
|
75
|
+
_input->textView.selectedRange = NSMakeRange(preModificationRange.location, preModificationRange.length + offset);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// also add typing attributes
|
|
79
|
+
NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
80
|
+
NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
|
|
81
|
+
pStyle.headIndent = [self getHeadIndent];
|
|
82
|
+
pStyle.firstLineHeadIndent = [self getHeadIndent];
|
|
83
|
+
typingAttrs[NSParagraphStyleAttributeName] = pStyle;
|
|
84
|
+
_input->textView.typingAttributes = typingAttrs;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// does pretty much the same as addAttributes
|
|
88
|
+
- (void)addTypingAttributes {
|
|
89
|
+
[self addAttributes:_input->textView.selectedRange];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)removeAttributes:(NSRange)range {
|
|
93
|
+
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
|
|
94
|
+
|
|
95
|
+
for(NSValue *value in paragraphs) {
|
|
96
|
+
NSRange pRange = [value rangeValue];
|
|
97
|
+
[_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
|
|
98
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
99
|
+
NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
|
|
100
|
+
pStyle.headIndent = 0;
|
|
101
|
+
pStyle.firstLineHeadIndent = 0;
|
|
102
|
+
[_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// also remove typing attributes
|
|
108
|
+
NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
109
|
+
NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
|
|
110
|
+
pStyle.headIndent = 0;
|
|
111
|
+
pStyle.firstLineHeadIndent = 0;
|
|
112
|
+
typingAttrs[NSParagraphStyleAttributeName] = pStyle;
|
|
113
|
+
_input->textView.typingAttributes = typingAttrs;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// needed for the sake of style conflicts, needs to do exactly the same as removeAttribtues
|
|
117
|
+
- (void)removeTypingAttributes {
|
|
118
|
+
[self removeAttributes:_input->textView.selectedRange];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// removing first quote line by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
122
|
+
// so we try guessing that a point should be deleted here
|
|
123
|
+
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
124
|
+
if([self detectStyle:_input->textView.selectedRange] &&
|
|
125
|
+
NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
|
|
126
|
+
[text isEqualToString:@""]
|
|
127
|
+
) {
|
|
128
|
+
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
129
|
+
[self removeAttributes:paragraphRange];
|
|
130
|
+
return YES;
|
|
131
|
+
}
|
|
132
|
+
return NO;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
|
|
136
|
+
NSParagraphStyle *pStyle = (NSParagraphStyle *)value;
|
|
137
|
+
return pStyle != nullptr && pStyle.headIndent == [self getHeadIndent] && pStyle.firstLineHeadIndent == [self getHeadIndent] && pStyle.textLists.count == 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (BOOL)detectStyle:(NSRange)range {
|
|
141
|
+
if(range.length >= 1) {
|
|
142
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
143
|
+
withCondition: ^BOOL(id _Nullable value, NSRange range) {
|
|
144
|
+
return [self styleCondition:value :range];
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
} else {
|
|
148
|
+
NSInteger searchLocation = range.location;
|
|
149
|
+
if(searchLocation == _input->textView.textStorage.length) {
|
|
150
|
+
NSParagraphStyle *pStyle = _input->textView.typingAttributes[NSParagraphStyleAttributeName];
|
|
151
|
+
return [self styleCondition:pStyle :NSMakeRange(0, 0)];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
155
|
+
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
156
|
+
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
157
|
+
attribute:NSParagraphStyleAttributeName
|
|
158
|
+
atIndex:searchLocation
|
|
159
|
+
longestEffectiveRange: ¶graphRange
|
|
160
|
+
inRange:inputRange
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
- (BOOL)anyOccurence:(NSRange)range {
|
|
168
|
+
return [OccurenceUtils any:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
169
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
170
|
+
return [self styleCondition:value :range];
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
- (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
|
|
176
|
+
return [OccurenceUtils all:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
177
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
178
|
+
return [self styleCondition:value :range];
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// gets ranges that aren't link, mention or inline code
|
|
184
|
+
- (NSArray *)getProperColorRangesIn:(NSRange)range {
|
|
185
|
+
LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])];
|
|
186
|
+
MentionStyle *mentionStyle = _input->stylesDict[@([MentionStyle getStyleType])];
|
|
187
|
+
InlineCodeStyle *codeStyle = _input->stylesDict[@([InlineCodeStyle getStyleType])];
|
|
188
|
+
|
|
189
|
+
NSMutableArray *newRanges = [[NSMutableArray alloc] init];
|
|
190
|
+
int lastRangeLocation = range.location;
|
|
191
|
+
|
|
192
|
+
for(int i = range.location; i < range.location + range.length; i++) {
|
|
193
|
+
NSRange currentRange = NSMakeRange(i, 1);
|
|
194
|
+
if([linkStyle detectStyle:currentRange] || [mentionStyle detectStyle:currentRange] || [codeStyle detectStyle:currentRange]) {
|
|
195
|
+
if(i - lastRangeLocation > 0) {
|
|
196
|
+
[newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]];
|
|
197
|
+
}
|
|
198
|
+
lastRangeLocation = i+1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if(lastRangeLocation < range.location + range.length) {
|
|
202
|
+
[newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return newRanges;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// general checkup correcting blockquote color
|
|
209
|
+
// since links, mentions and inline code affects coloring, the checkup gets done only outside of them
|
|
210
|
+
- (void)manageBlockquoteColor {
|
|
211
|
+
if([[_input->config blockquoteColor] isEqualToColor:[_input->config primaryColor]]) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
NSRange wholeRange = NSMakeRange(0, _input->textView.textStorage.string.length);
|
|
216
|
+
|
|
217
|
+
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange];
|
|
218
|
+
for(NSValue *pValue in paragraphs) {
|
|
219
|
+
NSRange paragraphRange = [pValue rangeValue];
|
|
220
|
+
NSArray *properRanges = [self getProperColorRangesIn:paragraphRange];
|
|
221
|
+
|
|
222
|
+
for(NSValue *value in properRanges) {
|
|
223
|
+
NSRange currRange = [value rangeValue];
|
|
224
|
+
BOOL selfDetected = [self detectStyle:currRange];
|
|
225
|
+
|
|
226
|
+
[_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:currRange options:0
|
|
227
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
228
|
+
UIColor *newColor = nullptr;
|
|
229
|
+
BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config blockquoteColor]];
|
|
230
|
+
|
|
231
|
+
if(colorApplied && !selfDetected) {
|
|
232
|
+
newColor = [_input->config primaryColor];
|
|
233
|
+
} else if(!colorApplied && selfDetected) {
|
|
234
|
+
newColor = [_input->config blockquoteColor];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if(newColor != nullptr) {
|
|
238
|
+
[_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:currRange];
|
|
239
|
+
[_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:newColor range:currRange];
|
|
240
|
+
[_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:newColor range:currRange];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
#import "FontExtension.h"
|
|
4
|
+
#import "OccurenceUtils.h"
|
|
5
|
+
|
|
6
|
+
@implementation BoldStyle {
|
|
7
|
+
EnrichedTextInputView *_input;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
+ (StyleType)getStyleType { return Bold; }
|
|
11
|
+
|
|
12
|
+
- (instancetype)initWithInput:(id)input {
|
|
13
|
+
self = [super init];
|
|
14
|
+
_input = (EnrichedTextInputView *)input;
|
|
15
|
+
return self;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
- (void)applyStyle:(NSRange)range {
|
|
19
|
+
BOOL isStylePresent = [self detectStyle:range];
|
|
20
|
+
if(range.length >= 1) {
|
|
21
|
+
isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
|
|
22
|
+
} else {
|
|
23
|
+
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
- (void)addAttributes:(NSRange)range {
|
|
28
|
+
[_input->textView.textStorage beginEditing];
|
|
29
|
+
[_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0
|
|
30
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
31
|
+
UIFont *font = (UIFont *)value;
|
|
32
|
+
if(font != nullptr) {
|
|
33
|
+
UIFont *newFont = [font setBold];
|
|
34
|
+
[_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
[_input->textView.textStorage endEditing];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
- (void)addTypingAttributes {
|
|
42
|
+
UIFont *currentFontAttr = (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
|
|
43
|
+
if(currentFontAttr != nullptr) {
|
|
44
|
+
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
45
|
+
newTypingAttrs[NSFontAttributeName] = [currentFontAttr setBold];
|
|
46
|
+
_input->textView.typingAttributes = newTypingAttrs;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
- (void)removeAttributes:(NSRange)range {
|
|
51
|
+
[_input->textView.textStorage beginEditing];
|
|
52
|
+
[_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0
|
|
53
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
54
|
+
UIFont *font = (UIFont *)value;
|
|
55
|
+
if(font != nullptr) {
|
|
56
|
+
UIFont *newFont = [font removeBold];
|
|
57
|
+
[_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
[_input->textView.textStorage endEditing];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
- (void)removeTypingAttributes {
|
|
65
|
+
UIFont *currentFontAttr = (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
|
|
66
|
+
if(currentFontAttr != nullptr) {
|
|
67
|
+
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
68
|
+
newTypingAttrs[NSFontAttributeName] = [currentFontAttr removeBold];
|
|
69
|
+
_input->textView.typingAttributes = newTypingAttrs;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
- (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type {
|
|
74
|
+
if(type == H1) {
|
|
75
|
+
if(![_input->config h1Bold]) { return NO; }
|
|
76
|
+
} else if(type == H2) {
|
|
77
|
+
if(![_input->config h2Bold]) { return NO; }
|
|
78
|
+
} else if(type == H3) {
|
|
79
|
+
if(![_input->config h3Bold]) { return NO; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
id<BaseStyleProtocol> headingStyle = _input->stylesDict[@(type)];
|
|
83
|
+
return range.length > 0
|
|
84
|
+
? [headingStyle anyOccurence:range]
|
|
85
|
+
: [headingStyle detectStyle:range];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
|
|
89
|
+
UIFont *font = (UIFont *)value;
|
|
90
|
+
return font != nullptr && [font isBold] && ![self boldHeadingConflictsInRange:range type:H1] && ![self boldHeadingConflictsInRange:range type:H2] && ![self boldHeadingConflictsInRange:range type:H3];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (BOOL)detectStyle:(NSRange)range {
|
|
94
|
+
if(range.length >= 1) {
|
|
95
|
+
return [OccurenceUtils detect:NSFontAttributeName withInput:_input inRange:range
|
|
96
|
+
withCondition: ^BOOL(id _Nullable value, NSRange range) {
|
|
97
|
+
return [self styleCondition:value :range];
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
} else {
|
|
101
|
+
UIFont *currentFontAttr = (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
|
|
102
|
+
return [self styleCondition:currentFontAttr :range];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (BOOL)anyOccurence:(NSRange)range {
|
|
107
|
+
return [OccurenceUtils any:NSFontAttributeName withInput:_input inRange:range
|
|
108
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
109
|
+
return [self styleCondition:value :range];
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
- (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
|
|
115
|
+
return [OccurenceUtils all:NSFontAttributeName withInput:_input inRange:range
|
|
116
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
117
|
+
return [self styleCondition:value :range];
|
|
118
|
+
}
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
|
|
4
|
+
@implementation H1Style
|
|
5
|
+
+ (StyleType)getStyleType { return H1; }
|
|
6
|
+
- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h1FontSize]; }
|
|
7
|
+
- (BOOL)isHeadingBold {
|
|
8
|
+
return [((EnrichedTextInputView *)input)->config h1Bold];
|
|
9
|
+
}
|
|
10
|
+
@end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
|
|
4
|
+
@implementation H2Style
|
|
5
|
+
+ (StyleType)getStyleType { return H2; }
|
|
6
|
+
- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h2FontSize]; }
|
|
7
|
+
- (BOOL)isHeadingBold {
|
|
8
|
+
return [((EnrichedTextInputView *)input)->config h2Bold];
|
|
9
|
+
}
|
|
10
|
+
@end
|