@fendent/react-native-enriched 0.5.2-fork.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 +343 -0
- package/ReactNativeEnriched.podspec +31 -0
- package/android/build.gradle +106 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +197 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +72 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/EventEmitters.cpp +434 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/EventEmitters.h +391 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/Props.cpp +173 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/Props.h +833 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/ShadowNodes.h +23 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/ReactNativeEnrichedSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/lint.gradle +70 -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/ReactNativeEnrichedPackage.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/AsyncDrawable.kt +126 -0
- package/android/src/main/java/com/swmansion/enriched/common/CheckboxDrawable.kt +81 -0
- package/android/src/main/java/com/swmansion/enriched/common/EnrichedConstants.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/common/EnrichedStyle.kt +57 -0
- package/android/src/main/java/com/swmansion/enriched/common/ForceRedrawSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/common/GumboNormalizer.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/common/MentionStyle.kt +7 -0
- package/android/src/main/java/com/swmansion/enriched/common/ResourceManager.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java +956 -0
- package/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt +79 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedBlockQuoteSpan.kt +53 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedBoldSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCheckboxListSpan.kt +92 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedCodeBlockSpan.kt +81 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH1Span.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH2Span.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH3Span.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH4Span.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH5Span.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedH6Span.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedImageSpan.kt +184 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedInlineCodeSpan.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedItalicSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedLinkSpan.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedMentionSpan.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedOrderedListSpan.kt +79 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedStrikeThroughSpan.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedUnderlineSpan.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedUnorderedListSpan.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedBlockSpan.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedHeadingSpan.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedInlineSpan.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedParagraphSpan.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedSpan.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/common/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputConnectionWrapper.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt +83 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +1120 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewLayoutManager.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt +478 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/MeasurementStore.kt +225 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/MentionHandler.kt +55 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnChangeHtmlEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnChangeSelectionEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnChangeStateEvent.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnChangeTextEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnContextMenuItemPressEvent.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnInputBlurEvent.kt +25 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnInputFocusEvent.kt +25 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnInputKeyPressEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnLinkDetectedEvent.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnMentionDetectedEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnMentionEvent.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnPasteImagesEvent.kt +47 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnRequestHtmlResultEvent.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/events/OnSubmitEditingEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputBlockQuoteSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputBoldSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCheckboxListSpan.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputCodeBlockSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH1Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH2Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH3Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH4Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH5Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputH6Span.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputImageSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputInlineCodeSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputItalicSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputLinkSpan.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputMentionSpan.kt +18 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputOrderedListSpan.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputStrikeThroughSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputUnderlineSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedInputUnorderedListSpan.kt +14 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedLineHeightSpan.kt +44 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/EnrichedSpans.kt +241 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/spans/interfaces/EnrichedInputSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/styles/HtmlStyle.kt +372 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt +164 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt +263 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt +434 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt +394 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedEditableFactory.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt +320 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpanState.kt +310 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt +106 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannableStringBuilder.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/RichContentReceiver.kt +127 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt +106 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedSpanWatcher.kt +107 -0
- package/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedTextWatcher.kt +74 -0
- package/android/src/main/new_arch/CMakeLists.txt +62 -0
- package/android/src/main/new_arch/GumboNormalizerJni.cpp +14 -0
- package/android/src/main/new_arch/ReactNativeEnrichedSpec.cpp +11 -0
- package/android/src/main/new_arch/ReactNativeEnrichedSpec.h +15 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputComponentDescriptor.h +35 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputMeasurementManager.cpp +53 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputMeasurementManager.h +25 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputShadowNode.cpp +35 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputShadowNode.h +53 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputState.cpp +9 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/EnrichedTextInputState.h +24 -0
- package/android/src/main/new_arch/react/renderer/components/ReactNativeEnrichedSpec/conversions.h +27 -0
- package/android/src/main/res/drawable/broken_image.xml +10 -0
- package/cpp/CMakeLists.txt +50 -0
- package/cpp/GumboParser/GumboParser.h +34043 -0
- package/cpp/README.md +59 -0
- package/cpp/parser/GumboNormalizer.c +915 -0
- package/cpp/parser/GumboParser.cpp +16 -0
- package/cpp/parser/GumboParser.hpp +23 -0
- package/cpp/tests/GumboParserTest.cpp +457 -0
- package/ios/EnrichedTextInputView.h +53 -0
- package/ios/EnrichedTextInputView.mm +2360 -0
- package/ios/EnrichedTextInputViewManager.mm +13 -0
- package/ios/attributesManager/AttributesManager.h +17 -0
- package/ios/attributesManager/AttributesManager.mm +195 -0
- package/ios/config/InputConfig.h +104 -0
- package/ios/config/InputConfig.mm +664 -0
- package/ios/extensions/ColorExtension.h +7 -0
- package/ios/extensions/ColorExtension.mm +38 -0
- package/ios/extensions/FontExtension.h +11 -0
- package/ios/extensions/FontExtension.mm +72 -0
- package/ios/extensions/ImageExtension.h +34 -0
- package/ios/extensions/ImageExtension.mm +165 -0
- package/ios/extensions/LayoutManagerExtension.h +6 -0
- package/ios/extensions/LayoutManagerExtension.mm +443 -0
- package/ios/extensions/StringExtension.h +15 -0
- package/ios/extensions/StringExtension.mm +69 -0
- package/ios/generated/ReactNativeEnrichedSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/ReactNativeEnrichedSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/ReactNativeEnrichedSpec/EventEmitters.cpp +434 -0
- package/ios/generated/ReactNativeEnrichedSpec/EventEmitters.h +391 -0
- package/ios/generated/ReactNativeEnrichedSpec/Props.cpp +173 -0
- package/ios/generated/ReactNativeEnrichedSpec/Props.h +833 -0
- package/ios/generated/ReactNativeEnrichedSpec/RCTComponentViewHelpers.h +582 -0
- package/ios/generated/ReactNativeEnrichedSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/ReactNativeEnrichedSpec/ShadowNodes.h +23 -0
- package/ios/generated/ReactNativeEnrichedSpec/States.cpp +16 -0
- package/ios/generated/ReactNativeEnrichedSpec/States.h +20 -0
- package/ios/inputParser/InputParser.h +11 -0
- package/ios/inputParser/InputParser.mm +1463 -0
- package/ios/inputTextView/InputTextView.h +6 -0
- package/ios/inputTextView/InputTextView.mm +285 -0
- package/ios/interfaces/AttributeEntry.h +9 -0
- package/ios/interfaces/AttributeEntry.mm +4 -0
- package/ios/interfaces/BaseStyleProtocol.h +17 -0
- package/ios/interfaces/ImageAttachment.h +11 -0
- package/ios/interfaces/ImageAttachment.mm +107 -0
- package/ios/interfaces/ImageData.h +10 -0
- package/ios/interfaces/ImageData.mm +4 -0
- package/ios/interfaces/LinkData.h +11 -0
- package/ios/interfaces/LinkData.mm +29 -0
- package/ios/interfaces/LinkRegexConfig.h +19 -0
- package/ios/interfaces/LinkRegexConfig.mm +37 -0
- package/ios/interfaces/MediaAttachment.h +23 -0
- package/ios/interfaces/MediaAttachment.mm +31 -0
- package/ios/interfaces/MentionParams.h +8 -0
- package/ios/interfaces/MentionParams.mm +4 -0
- package/ios/interfaces/MentionStyleProps.h +13 -0
- package/ios/interfaces/MentionStyleProps.mm +63 -0
- package/ios/interfaces/StyleBase.h +36 -0
- package/ios/interfaces/StyleBase.mm +256 -0
- package/ios/interfaces/StyleHeaders.h +102 -0
- package/ios/interfaces/StylePair.h +9 -0
- package/ios/interfaces/StylePair.mm +4 -0
- package/ios/interfaces/StyleTypeEnum.h +26 -0
- package/ios/interfaces/TextDecorationLineEnum.h +6 -0
- package/ios/interfaces/TextDecorationLineEnum.mm +4 -0
- package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.h +44 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +103 -0
- package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
- package/ios/internals/EnrichedTextInputViewState.h +22 -0
- package/ios/styles/BlockQuoteStyle.mm +55 -0
- package/ios/styles/BoldStyle.mm +37 -0
- package/ios/styles/CheckboxListStyle.mm +153 -0
- package/ios/styles/CodeBlockStyle.mm +49 -0
- package/ios/styles/H1Style.mm +20 -0
- package/ios/styles/H2Style.mm +20 -0
- package/ios/styles/H3Style.mm +20 -0
- package/ios/styles/H4Style.mm +20 -0
- package/ios/styles/H5Style.mm +20 -0
- package/ios/styles/H6Style.mm +20 -0
- package/ios/styles/HeadingStyleBase.mm +65 -0
- package/ios/styles/ImageStyle.mm +146 -0
- package/ios/styles/InlineCodeStyle.mm +65 -0
- package/ios/styles/ItalicStyle.mm +37 -0
- package/ios/styles/LinkStyle.mm +532 -0
- package/ios/styles/MentionStyle.mm +538 -0
- package/ios/styles/OrderedListStyle.mm +86 -0
- package/ios/styles/StrikethroughStyle.mm +25 -0
- package/ios/styles/UnderlineStyle.mm +24 -0
- package/ios/styles/UnorderedListStyle.mm +86 -0
- package/ios/utils/CheckboxHitTestUtils.h +10 -0
- package/ios/utils/CheckboxHitTestUtils.mm +122 -0
- package/ios/utils/DotReplacementUtils.h +10 -0
- package/ios/utils/DotReplacementUtils.mm +68 -0
- package/ios/utils/KeyboardUtils.h +7 -0
- package/ios/utils/KeyboardUtils.mm +31 -0
- package/ios/utils/OccurenceUtils.h +44 -0
- package/ios/utils/OccurenceUtils.mm +179 -0
- package/ios/utils/ParagraphAttributesUtils.h +15 -0
- package/ios/utils/ParagraphAttributesUtils.mm +257 -0
- package/ios/utils/RangeUtils.h +12 -0
- package/ios/utils/RangeUtils.mm +183 -0
- package/ios/utils/TextBlockTapGestureRecognizer.h +17 -0
- package/ios/utils/TextBlockTapGestureRecognizer.mm +56 -0
- package/ios/utils/TextInsertionUtils.h +17 -0
- package/ios/utils/TextInsertionUtils.mm +64 -0
- package/ios/utils/WordsUtils.h +7 -0
- package/ios/utils/WordsUtils.mm +98 -0
- package/ios/utils/ZeroWidthSpaceUtils.h +9 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +270 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/EnrichedTextInput.js +304 -0
- package/lib/module/native/EnrichedTextInput.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/spec/EnrichedTextInputNativeComponent.ts +517 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/EnrichedTextInputDefaultProps.js +12 -0
- package/lib/module/utils/EnrichedTextInputDefaultProps.js.map +1 -0
- package/lib/module/utils/normalizeHtmlStyle.js +155 -0
- package/lib/module/utils/normalizeHtmlStyle.js.map +1 -0
- package/lib/module/utils/nullthrows.js +9 -0
- package/lib/module/utils/nullthrows.js.map +1 -0
- package/lib/module/utils/regexParser.js +46 -0
- package/lib/module/utils/regexParser.js.map +1 -0
- package/lib/typescript/package.json +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/native/EnrichedTextInput.d.ts +3 -0
- package/lib/typescript/src/native/EnrichedTextInput.d.ts.map +1 -0
- package/lib/typescript/src/spec/EnrichedTextInputNativeComponent.d.ts +397 -0
- package/lib/typescript/src/spec/EnrichedTextInputNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +447 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/EnrichedTextInputDefaultProps.d.ts +10 -0
- package/lib/typescript/src/utils/EnrichedTextInputDefaultProps.d.ts.map +1 -0
- package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts +4 -0
- package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts.map +1 -0
- package/lib/typescript/src/utils/nullthrows.d.ts +2 -0
- package/lib/typescript/src/utils/nullthrows.d.ts.map +1 -0
- package/lib/typescript/src/utils/regexParser.d.ts +3 -0
- package/lib/typescript/src/utils/regexParser.d.ts.map +1 -0
- package/package.json +226 -0
- package/react-native.config.js +13 -0
- package/src/index.tsx +20 -0
- package/src/native/EnrichedTextInput.tsx +370 -0
- package/src/spec/EnrichedTextInputNativeComponent.ts +517 -0
- package/src/types.ts +499 -0
- package/src/utils/EnrichedTextInputDefaultProps.ts +9 -0
- package/src/utils/normalizeHtmlStyle.ts +199 -0
- package/src/utils/nullthrows.ts +7 -0
- package/src/utils/regexParser.ts +56 -0
|
@@ -0,0 +1,2360 @@
|
|
|
1
|
+
#import "EnrichedTextInputView.h"
|
|
2
|
+
#import "CoreText/CoreText.h"
|
|
3
|
+
#import "DotReplacementUtils.h"
|
|
4
|
+
#import "ImageAttachment.h"
|
|
5
|
+
#import "KeyboardUtils.h"
|
|
6
|
+
#import "LayoutManagerExtension.h"
|
|
7
|
+
#import "ParagraphAttributesUtils.h"
|
|
8
|
+
#import "RCTFabricComponentsPlugins.h"
|
|
9
|
+
#import "StringExtension.h"
|
|
10
|
+
#import "StyleHeaders.h"
|
|
11
|
+
#import "TextBlockTapGestureRecognizer.h"
|
|
12
|
+
#import "UIView+React.h"
|
|
13
|
+
#import "WordsUtils.h"
|
|
14
|
+
#import "ZeroWidthSpaceUtils.h"
|
|
15
|
+
#import <React/RCTConversions.h>
|
|
16
|
+
#import <ReactNativeEnriched/EnrichedTextInputViewComponentDescriptor.h>
|
|
17
|
+
#import <ReactNativeEnriched/EventEmitters.h>
|
|
18
|
+
#import <ReactNativeEnriched/Props.h>
|
|
19
|
+
#import <ReactNativeEnriched/RCTComponentViewHelpers.h>
|
|
20
|
+
#import <folly/dynamic.h>
|
|
21
|
+
#import <react/utils/ManagedObjectWrapper.h>
|
|
22
|
+
|
|
23
|
+
#define GET_STYLE_STATE(TYPE_ENUM) \
|
|
24
|
+
{ \
|
|
25
|
+
.isActive = [self isStyleActive:TYPE_ENUM], \
|
|
26
|
+
.isBlocking = [self isStyle:TYPE_ENUM activeInMap:blockingStyles], \
|
|
27
|
+
.isConflicting = [self isStyle:TYPE_ENUM activeInMap:conflictingStyles] \
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
using namespace facebook::react;
|
|
31
|
+
|
|
32
|
+
@interface EnrichedTextInputView () <
|
|
33
|
+
RCTEnrichedTextInputViewViewProtocol, UITextViewDelegate,
|
|
34
|
+
UIGestureRecognizerDelegate, NSTextStorageDelegate, NSObject>
|
|
35
|
+
|
|
36
|
+
@end
|
|
37
|
+
|
|
38
|
+
@implementation EnrichedTextInputView {
|
|
39
|
+
EnrichedTextInputViewShadowNode::ConcreteState::Shared _state;
|
|
40
|
+
int _componentViewHeightUpdateCounter;
|
|
41
|
+
NSMutableSet<NSNumber *> *_activeStyles;
|
|
42
|
+
NSMutableSet<NSNumber *> *_blockedStyles;
|
|
43
|
+
LinkData *_recentlyActiveLinkData;
|
|
44
|
+
NSRange _recentlyActiveLinkRange;
|
|
45
|
+
NSString *_recentInputString;
|
|
46
|
+
MentionParams *_recentlyActiveMentionParams;
|
|
47
|
+
NSRange _recentlyActiveMentionRange;
|
|
48
|
+
NSString *_recentlyEmittedHtml;
|
|
49
|
+
BOOL _emitHtml;
|
|
50
|
+
UILabel *_placeholderLabel;
|
|
51
|
+
UIColor *_placeholderColor;
|
|
52
|
+
BOOL _emitFocusBlur;
|
|
53
|
+
BOOL _emitTextChange;
|
|
54
|
+
NSMutableDictionary<NSValue *, UIImageView *> *_attachmentViews;
|
|
55
|
+
NSArray<NSDictionary *> *_contextMenuItems;
|
|
56
|
+
NSString *_submitBehavior;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: - Component utils
|
|
60
|
+
|
|
61
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
62
|
+
return concreteComponentDescriptorProvider<
|
|
63
|
+
EnrichedTextInputViewComponentDescriptor>();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
67
|
+
return EnrichedTextInputView.class;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
+ (BOOL)shouldBeRecycled {
|
|
71
|
+
return NO;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// MARK: - Init
|
|
75
|
+
|
|
76
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
77
|
+
if (self = [super initWithFrame:frame]) {
|
|
78
|
+
static const auto defaultProps =
|
|
79
|
+
std::make_shared<const EnrichedTextInputViewProps>();
|
|
80
|
+
_props = defaultProps;
|
|
81
|
+
[self setDefaults];
|
|
82
|
+
[self setupTextView];
|
|
83
|
+
[self setupPlaceholderLabel];
|
|
84
|
+
self.contentView = textView;
|
|
85
|
+
}
|
|
86
|
+
return self;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
- (void)setDefaults {
|
|
90
|
+
_componentViewHeightUpdateCounter = 0;
|
|
91
|
+
_activeStyles = [[NSMutableSet alloc] init];
|
|
92
|
+
_blockedStyles = [[NSMutableSet alloc] init];
|
|
93
|
+
_recentlyActiveLinkRange = NSMakeRange(0, 0);
|
|
94
|
+
_recentlyActiveMentionRange = NSMakeRange(0, 0);
|
|
95
|
+
_recentInputString = @"";
|
|
96
|
+
_recentlyEmittedHtml = @"<html>\n<p></p>\n</html>";
|
|
97
|
+
_emitHtml = NO;
|
|
98
|
+
blockEmitting = NO;
|
|
99
|
+
_emitFocusBlur = YES;
|
|
100
|
+
_emitTextChange = NO;
|
|
101
|
+
dotReplacementRange = nullptr;
|
|
102
|
+
|
|
103
|
+
defaultTypingAttributes =
|
|
104
|
+
[[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
|
|
105
|
+
|
|
106
|
+
stylesDict = @{
|
|
107
|
+
@([BoldStyle getType]) : [[BoldStyle alloc] initWithInput:self],
|
|
108
|
+
@([ItalicStyle getType]) : [[ItalicStyle alloc] initWithInput:self],
|
|
109
|
+
@([UnderlineStyle getType]) : [[UnderlineStyle alloc] initWithInput:self],
|
|
110
|
+
@([StrikethroughStyle getType]) :
|
|
111
|
+
[[StrikethroughStyle alloc] initWithInput:self],
|
|
112
|
+
@([InlineCodeStyle getType]) : [[InlineCodeStyle alloc] initWithInput:self],
|
|
113
|
+
@([LinkStyle getType]) : [[LinkStyle alloc] initWithInput:self],
|
|
114
|
+
@([MentionStyle getType]) : [[MentionStyle alloc] initWithInput:self],
|
|
115
|
+
@([H1Style getType]) : [[H1Style alloc] initWithInput:self],
|
|
116
|
+
@([H2Style getType]) : [[H2Style alloc] initWithInput:self],
|
|
117
|
+
@([H3Style getType]) : [[H3Style alloc] initWithInput:self],
|
|
118
|
+
@([H4Style getType]) : [[H4Style alloc] initWithInput:self],
|
|
119
|
+
@([H5Style getType]) : [[H5Style alloc] initWithInput:self],
|
|
120
|
+
@([H6Style getType]) : [[H6Style alloc] initWithInput:self],
|
|
121
|
+
@([UnorderedListStyle getType]) :
|
|
122
|
+
[[UnorderedListStyle alloc] initWithInput:self],
|
|
123
|
+
@([OrderedListStyle getType]) :
|
|
124
|
+
[[OrderedListStyle alloc] initWithInput:self],
|
|
125
|
+
@([CheckboxListStyle getType]) :
|
|
126
|
+
[[CheckboxListStyle alloc] initWithInput:self],
|
|
127
|
+
@([BlockQuoteStyle getType]) : [[BlockQuoteStyle alloc] initWithInput:self],
|
|
128
|
+
@([CodeBlockStyle getType]) : [[CodeBlockStyle alloc] initWithInput:self],
|
|
129
|
+
@([ImageStyle getType]) : [[ImageStyle alloc] initWithInput:self]
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
conflictingStyles = [@{
|
|
133
|
+
@([BoldStyle getType]) : @[],
|
|
134
|
+
@([ItalicStyle getType]) : @[],
|
|
135
|
+
@([UnderlineStyle getType]) : @[],
|
|
136
|
+
@([StrikethroughStyle getType]) : @[],
|
|
137
|
+
@([InlineCodeStyle getType]) :
|
|
138
|
+
@[ @([LinkStyle getType]), @([MentionStyle getType]) ],
|
|
139
|
+
@([LinkStyle getType]) : @[
|
|
140
|
+
@([InlineCodeStyle getType]), @([LinkStyle getType]),
|
|
141
|
+
@([MentionStyle getType])
|
|
142
|
+
],
|
|
143
|
+
@([MentionStyle getType]) :
|
|
144
|
+
@[ @([InlineCodeStyle getType]), @([LinkStyle getType]) ],
|
|
145
|
+
@([H1Style getType]) : @[
|
|
146
|
+
@([H2Style getType]), @([H3Style getType]), @([H4Style getType]),
|
|
147
|
+
@([H5Style getType]), @([H6Style getType]),
|
|
148
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
149
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
150
|
+
@([CheckboxListStyle getType])
|
|
151
|
+
],
|
|
152
|
+
@([H2Style getType]) : @[
|
|
153
|
+
@([H1Style getType]), @([H3Style getType]), @([H4Style getType]),
|
|
154
|
+
@([H5Style getType]), @([H6Style getType]),
|
|
155
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
156
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
157
|
+
@([CheckboxListStyle getType])
|
|
158
|
+
],
|
|
159
|
+
@([H3Style getType]) : @[
|
|
160
|
+
@([H1Style getType]), @([H2Style getType]), @([H4Style getType]),
|
|
161
|
+
@([H5Style getType]), @([H6Style getType]),
|
|
162
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
163
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
164
|
+
@([CheckboxListStyle getType])
|
|
165
|
+
],
|
|
166
|
+
@([H4Style getType]) : @[
|
|
167
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
168
|
+
@([H5Style getType]), @([H6Style getType]),
|
|
169
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
170
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
171
|
+
@([CheckboxListStyle getType])
|
|
172
|
+
],
|
|
173
|
+
@([H5Style getType]) : @[
|
|
174
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
175
|
+
@([H4Style getType]), @([H6Style getType]),
|
|
176
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
177
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
178
|
+
@([CheckboxListStyle getType])
|
|
179
|
+
],
|
|
180
|
+
@([H6Style getType]) : @[
|
|
181
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
182
|
+
@([H4Style getType]), @([H5Style getType]),
|
|
183
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
184
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
|
|
185
|
+
@([CheckboxListStyle getType])
|
|
186
|
+
],
|
|
187
|
+
@([UnorderedListStyle getType]) : @[
|
|
188
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
189
|
+
@([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
|
|
190
|
+
@([OrderedListStyle getType]), @([BlockQuoteStyle getType]),
|
|
191
|
+
@([CodeBlockStyle getType]), @([CheckboxListStyle getType])
|
|
192
|
+
],
|
|
193
|
+
@([OrderedListStyle getType]) : @[
|
|
194
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
195
|
+
@([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
|
|
196
|
+
@([UnorderedListStyle getType]), @([BlockQuoteStyle getType]),
|
|
197
|
+
@([CodeBlockStyle getType]), @([CheckboxListStyle getType])
|
|
198
|
+
],
|
|
199
|
+
@([CheckboxListStyle getType]) : @[
|
|
200
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
201
|
+
@([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
|
|
202
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
203
|
+
@([BlockQuoteStyle getType]), @([CodeBlockStyle getType])
|
|
204
|
+
],
|
|
205
|
+
@([BlockQuoteStyle getType]) : @[
|
|
206
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
207
|
+
@([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
|
|
208
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
209
|
+
@([CodeBlockStyle getType]), @([CheckboxListStyle getType])
|
|
210
|
+
],
|
|
211
|
+
@([CodeBlockStyle getType]) : @[
|
|
212
|
+
@([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
|
|
213
|
+
@([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
|
|
214
|
+
@([BoldStyle getType]), @([UnderlineStyle getType]),
|
|
215
|
+
@([ItalicStyle getType]), @([StrikethroughStyle getType]),
|
|
216
|
+
@([UnorderedListStyle getType]), @([OrderedListStyle getType]),
|
|
217
|
+
@([BlockQuoteStyle getType]), @([InlineCodeStyle getType]),
|
|
218
|
+
@([MentionStyle getType]), @([LinkStyle getType]),
|
|
219
|
+
@([CheckboxListStyle getType])
|
|
220
|
+
],
|
|
221
|
+
@([ImageStyle getType]) :
|
|
222
|
+
@[ @([LinkStyle getType]), @([MentionStyle getType]) ]
|
|
223
|
+
} mutableCopy];
|
|
224
|
+
|
|
225
|
+
blockingStyles = [@{
|
|
226
|
+
@([BoldStyle getType]) : @[ @([CodeBlockStyle getType]) ],
|
|
227
|
+
@([ItalicStyle getType]) : @[ @([CodeBlockStyle getType]) ],
|
|
228
|
+
@([UnderlineStyle getType]) : @[ @([CodeBlockStyle getType]) ],
|
|
229
|
+
@([StrikethroughStyle getType]) : @[ @([CodeBlockStyle getType]) ],
|
|
230
|
+
@([InlineCodeStyle getType]) :
|
|
231
|
+
@[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ],
|
|
232
|
+
@([LinkStyle getType]) :
|
|
233
|
+
@[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ],
|
|
234
|
+
@([MentionStyle getType]) :
|
|
235
|
+
@[ @([CodeBlockStyle getType]), @([ImageStyle getType]) ],
|
|
236
|
+
@([H1Style getType]) : @[],
|
|
237
|
+
@([H2Style getType]) : @[],
|
|
238
|
+
@([H3Style getType]) : @[],
|
|
239
|
+
@([H4Style getType]) : @[],
|
|
240
|
+
@([H5Style getType]) : @[],
|
|
241
|
+
@([H6Style getType]) : @[],
|
|
242
|
+
@([UnorderedListStyle getType]) : @[],
|
|
243
|
+
@([OrderedListStyle getType]) : @[],
|
|
244
|
+
@([CheckboxListStyle getType]) : @[],
|
|
245
|
+
@([BlockQuoteStyle getType]) : @[],
|
|
246
|
+
@([CodeBlockStyle getType]) : @[],
|
|
247
|
+
@([ImageStyle getType]) : @[ @([InlineCodeStyle getType]) ]
|
|
248
|
+
} mutableCopy];
|
|
249
|
+
|
|
250
|
+
parser = [[InputParser alloc] initWithInput:self];
|
|
251
|
+
_attachmentViews = [[NSMutableDictionary alloc] init];
|
|
252
|
+
attributesManager = [[AttributesManager alloc] initWithInput:self];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
- (void)setupTextView {
|
|
256
|
+
textView = [[InputTextView alloc] init];
|
|
257
|
+
textView.backgroundColor = UIColor.clearColor;
|
|
258
|
+
textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
|
259
|
+
textView.textContainer.lineFragmentPadding = 0;
|
|
260
|
+
textView.delegate = self;
|
|
261
|
+
textView.input = self;
|
|
262
|
+
textView.layoutManager.input = self;
|
|
263
|
+
textView.textStorage.delegate = self;
|
|
264
|
+
|
|
265
|
+
textView.adjustsFontForContentSizeCategory = YES;
|
|
266
|
+
[textView addGestureRecognizer:[[TextBlockTapGestureRecognizer alloc]
|
|
267
|
+
initWithInput:self
|
|
268
|
+
action:@selector(onTextBlockTap:)]];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
- (void)setupPlaceholderLabel {
|
|
272
|
+
_placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
|
273
|
+
_placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
274
|
+
[textView addSubview:_placeholderLabel];
|
|
275
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
276
|
+
[_placeholderLabel.leadingAnchor
|
|
277
|
+
constraintEqualToAnchor:textView.leadingAnchor],
|
|
278
|
+
[_placeholderLabel.widthAnchor
|
|
279
|
+
constraintEqualToAnchor:textView.widthAnchor],
|
|
280
|
+
[_placeholderLabel.topAnchor constraintEqualToAnchor:textView.topAnchor],
|
|
281
|
+
[_placeholderLabel.bottomAnchor
|
|
282
|
+
constraintEqualToAnchor:textView.bottomAnchor]
|
|
283
|
+
]];
|
|
284
|
+
_placeholderLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
285
|
+
_placeholderLabel.text = @"";
|
|
286
|
+
_placeholderLabel.hidden = YES;
|
|
287
|
+
_placeholderLabel.adjustsFontForContentSizeCategory = YES;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// MARK: - Props
|
|
291
|
+
|
|
292
|
+
- (void)updateProps:(Props::Shared const &)props
|
|
293
|
+
oldProps:(Props::Shared const &)oldProps {
|
|
294
|
+
const auto &oldViewProps =
|
|
295
|
+
*std::static_pointer_cast<EnrichedTextInputViewProps const>(_props);
|
|
296
|
+
const auto &newViewProps =
|
|
297
|
+
*std::static_pointer_cast<EnrichedTextInputViewProps const>(props);
|
|
298
|
+
BOOL isFirstMount = NO;
|
|
299
|
+
BOOL stylePropChanged = NO;
|
|
300
|
+
|
|
301
|
+
// initial config
|
|
302
|
+
if (config == nullptr) {
|
|
303
|
+
isFirstMount = YES;
|
|
304
|
+
config = [[InputConfig alloc] init];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// any style prop changes:
|
|
308
|
+
// firstly we create the new config for the changes
|
|
309
|
+
|
|
310
|
+
InputConfig *newConfig = [config copy];
|
|
311
|
+
|
|
312
|
+
if (newViewProps.color != oldViewProps.color) {
|
|
313
|
+
if (isColorMeaningful(newViewProps.color)) {
|
|
314
|
+
UIColor *uiColor = RCTUIColorFromSharedColor(newViewProps.color);
|
|
315
|
+
[newConfig setPrimaryColor:uiColor];
|
|
316
|
+
} else {
|
|
317
|
+
[newConfig setPrimaryColor:nullptr];
|
|
318
|
+
}
|
|
319
|
+
stylePropChanged = YES;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (newViewProps.fontSize != oldViewProps.fontSize) {
|
|
323
|
+
if (newViewProps.fontSize) {
|
|
324
|
+
NSNumber *fontSize = @(newViewProps.fontSize);
|
|
325
|
+
[newConfig setPrimaryFontSize:fontSize];
|
|
326
|
+
} else {
|
|
327
|
+
[newConfig setPrimaryFontSize:nullptr];
|
|
328
|
+
}
|
|
329
|
+
stylePropChanged = YES;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (newViewProps.lineHeight != oldViewProps.lineHeight) {
|
|
333
|
+
[newConfig setPrimaryLineHeight:newViewProps.lineHeight];
|
|
334
|
+
stylePropChanged = YES;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (newViewProps.fontWeight != oldViewProps.fontWeight) {
|
|
338
|
+
if (!newViewProps.fontWeight.empty()) {
|
|
339
|
+
[newConfig
|
|
340
|
+
setPrimaryFontWeight:[NSString
|
|
341
|
+
fromCppString:newViewProps.fontWeight]];
|
|
342
|
+
} else {
|
|
343
|
+
[newConfig setPrimaryFontWeight:nullptr];
|
|
344
|
+
}
|
|
345
|
+
stylePropChanged = YES;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (newViewProps.fontFamily != oldViewProps.fontFamily) {
|
|
349
|
+
if (!newViewProps.fontFamily.empty()) {
|
|
350
|
+
[newConfig
|
|
351
|
+
setPrimaryFontFamily:[NSString
|
|
352
|
+
fromCppString:newViewProps.fontFamily]];
|
|
353
|
+
} else {
|
|
354
|
+
[newConfig setPrimaryFontFamily:nullptr];
|
|
355
|
+
}
|
|
356
|
+
stylePropChanged = YES;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// rich text style
|
|
360
|
+
|
|
361
|
+
if (newViewProps.htmlStyle.h1.fontSize !=
|
|
362
|
+
oldViewProps.htmlStyle.h1.fontSize) {
|
|
363
|
+
[newConfig setH1FontSize:newViewProps.htmlStyle.h1.fontSize];
|
|
364
|
+
stylePropChanged = YES;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (newViewProps.htmlStyle.h1.bold != oldViewProps.htmlStyle.h1.bold) {
|
|
368
|
+
[newConfig setH1Bold:newViewProps.htmlStyle.h1.bold];
|
|
369
|
+
|
|
370
|
+
// Update style blocks and conflicts for bold
|
|
371
|
+
if (newViewProps.htmlStyle.h1.bold) {
|
|
372
|
+
[self addStyleBlock:H1 to:Bold];
|
|
373
|
+
[self addStyleConflict:Bold to:H1];
|
|
374
|
+
} else {
|
|
375
|
+
[self removeStyleBlock:H1 from:Bold];
|
|
376
|
+
[self removeStyleConflict:Bold from:H1];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
stylePropChanged = YES;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (newViewProps.htmlStyle.h2.fontSize !=
|
|
383
|
+
oldViewProps.htmlStyle.h2.fontSize) {
|
|
384
|
+
[newConfig setH2FontSize:newViewProps.htmlStyle.h2.fontSize];
|
|
385
|
+
stylePropChanged = YES;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (newViewProps.htmlStyle.h2.bold != oldViewProps.htmlStyle.h2.bold) {
|
|
389
|
+
[newConfig setH2Bold:newViewProps.htmlStyle.h2.bold];
|
|
390
|
+
|
|
391
|
+
// Update style blocks and conflicts for bold
|
|
392
|
+
if (newViewProps.htmlStyle.h2.bold) {
|
|
393
|
+
[self addStyleBlock:H2 to:Bold];
|
|
394
|
+
[self addStyleConflict:Bold to:H2];
|
|
395
|
+
} else {
|
|
396
|
+
[self removeStyleBlock:H2 from:Bold];
|
|
397
|
+
[self removeStyleConflict:Bold from:H2];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
stylePropChanged = YES;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (newViewProps.htmlStyle.h3.fontSize !=
|
|
404
|
+
oldViewProps.htmlStyle.h3.fontSize) {
|
|
405
|
+
[newConfig setH3FontSize:newViewProps.htmlStyle.h3.fontSize];
|
|
406
|
+
stylePropChanged = YES;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (newViewProps.htmlStyle.h3.bold != oldViewProps.htmlStyle.h3.bold) {
|
|
410
|
+
[newConfig setH3Bold:newViewProps.htmlStyle.h3.bold];
|
|
411
|
+
|
|
412
|
+
// Update style blocks and conflicts for bold
|
|
413
|
+
if (newViewProps.htmlStyle.h3.bold) {
|
|
414
|
+
[self addStyleBlock:H3 to:Bold];
|
|
415
|
+
[self addStyleConflict:Bold to:H3];
|
|
416
|
+
} else {
|
|
417
|
+
[self removeStyleBlock:H3 from:Bold];
|
|
418
|
+
[self removeStyleConflict:Bold from:H3];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
stylePropChanged = YES;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (newViewProps.htmlStyle.h4.fontSize !=
|
|
425
|
+
oldViewProps.htmlStyle.h4.fontSize) {
|
|
426
|
+
[newConfig setH4FontSize:newViewProps.htmlStyle.h4.fontSize];
|
|
427
|
+
stylePropChanged = YES;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (newViewProps.htmlStyle.h4.bold != oldViewProps.htmlStyle.h4.bold) {
|
|
431
|
+
[newConfig setH4Bold:newViewProps.htmlStyle.h4.bold];
|
|
432
|
+
|
|
433
|
+
// Update style blocks and conflicts for bold
|
|
434
|
+
if (newViewProps.htmlStyle.h4.bold) {
|
|
435
|
+
[self addStyleBlock:H4 to:Bold];
|
|
436
|
+
[self addStyleConflict:Bold to:H4];
|
|
437
|
+
} else {
|
|
438
|
+
[self removeStyleBlock:H4 from:Bold];
|
|
439
|
+
[self removeStyleConflict:Bold from:H4];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
stylePropChanged = YES;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (newViewProps.htmlStyle.h5.fontSize !=
|
|
446
|
+
oldViewProps.htmlStyle.h5.fontSize) {
|
|
447
|
+
[newConfig setH5FontSize:newViewProps.htmlStyle.h5.fontSize];
|
|
448
|
+
stylePropChanged = YES;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (newViewProps.htmlStyle.h5.bold != oldViewProps.htmlStyle.h5.bold) {
|
|
452
|
+
[newConfig setH5Bold:newViewProps.htmlStyle.h5.bold];
|
|
453
|
+
|
|
454
|
+
// Update style blocks and conflicts for bold
|
|
455
|
+
if (newViewProps.htmlStyle.h5.bold) {
|
|
456
|
+
[self addStyleBlock:H5 to:Bold];
|
|
457
|
+
[self addStyleConflict:Bold to:H5];
|
|
458
|
+
} else {
|
|
459
|
+
[self removeStyleBlock:H5 from:Bold];
|
|
460
|
+
[self removeStyleConflict:Bold from:H5];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
stylePropChanged = YES;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (newViewProps.htmlStyle.h6.fontSize !=
|
|
467
|
+
oldViewProps.htmlStyle.h6.fontSize) {
|
|
468
|
+
[newConfig setH6FontSize:newViewProps.htmlStyle.h6.fontSize];
|
|
469
|
+
stylePropChanged = YES;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (newViewProps.htmlStyle.h6.bold != oldViewProps.htmlStyle.h6.bold) {
|
|
473
|
+
[newConfig setH6Bold:newViewProps.htmlStyle.h6.bold];
|
|
474
|
+
|
|
475
|
+
// Update style blocks and conflicts for bold
|
|
476
|
+
if (newViewProps.htmlStyle.h6.bold) {
|
|
477
|
+
[self addStyleBlock:H6 to:Bold];
|
|
478
|
+
[self addStyleConflict:Bold to:H6];
|
|
479
|
+
} else {
|
|
480
|
+
[self removeStyleBlock:H6 from:Bold];
|
|
481
|
+
[self removeStyleConflict:Bold from:H6];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
stylePropChanged = YES;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (newViewProps.htmlStyle.blockquote.borderColor !=
|
|
488
|
+
oldViewProps.htmlStyle.blockquote.borderColor) {
|
|
489
|
+
if (isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) {
|
|
490
|
+
[newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor(
|
|
491
|
+
newViewProps.htmlStyle.blockquote
|
|
492
|
+
.borderColor)];
|
|
493
|
+
stylePropChanged = YES;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (newViewProps.htmlStyle.blockquote.borderWidth !=
|
|
498
|
+
oldViewProps.htmlStyle.blockquote.borderWidth) {
|
|
499
|
+
[newConfig
|
|
500
|
+
setBlockquoteBorderWidth:newViewProps.htmlStyle.blockquote.borderWidth];
|
|
501
|
+
stylePropChanged = YES;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (newViewProps.htmlStyle.blockquote.gapWidth !=
|
|
505
|
+
oldViewProps.htmlStyle.blockquote.gapWidth) {
|
|
506
|
+
[newConfig
|
|
507
|
+
setBlockquoteGapWidth:newViewProps.htmlStyle.blockquote.gapWidth];
|
|
508
|
+
stylePropChanged = YES;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// since this prop defaults to undefined on JS side, we need to force set the
|
|
512
|
+
// value on first mount
|
|
513
|
+
if (newViewProps.htmlStyle.blockquote.color !=
|
|
514
|
+
oldViewProps.htmlStyle.blockquote.color ||
|
|
515
|
+
isFirstMount) {
|
|
516
|
+
if (isColorMeaningful(newViewProps.htmlStyle.blockquote.color)) {
|
|
517
|
+
[newConfig
|
|
518
|
+
setBlockquoteColor:RCTUIColorFromSharedColor(
|
|
519
|
+
newViewProps.htmlStyle.blockquote.color)];
|
|
520
|
+
} else {
|
|
521
|
+
[newConfig setBlockquoteColor:[newConfig primaryColor]];
|
|
522
|
+
}
|
|
523
|
+
stylePropChanged = YES;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (newViewProps.htmlStyle.code.color != oldViewProps.htmlStyle.code.color) {
|
|
527
|
+
if (isColorMeaningful(newViewProps.htmlStyle.code.color)) {
|
|
528
|
+
[newConfig setInlineCodeFgColor:RCTUIColorFromSharedColor(
|
|
529
|
+
newViewProps.htmlStyle.code.color)];
|
|
530
|
+
stylePropChanged = YES;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (newViewProps.htmlStyle.code.backgroundColor !=
|
|
535
|
+
oldViewProps.htmlStyle.code.backgroundColor) {
|
|
536
|
+
if (isColorMeaningful(newViewProps.htmlStyle.code.backgroundColor)) {
|
|
537
|
+
[newConfig setInlineCodeBgColor:RCTUIColorFromSharedColor(
|
|
538
|
+
newViewProps.htmlStyle.code
|
|
539
|
+
.backgroundColor)];
|
|
540
|
+
stylePropChanged = YES;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (newViewProps.htmlStyle.ol.gapWidth !=
|
|
545
|
+
oldViewProps.htmlStyle.ol.gapWidth) {
|
|
546
|
+
[newConfig setOrderedListGapWidth:newViewProps.htmlStyle.ol.gapWidth];
|
|
547
|
+
stylePropChanged = YES;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (newViewProps.htmlStyle.ol.marginLeft !=
|
|
551
|
+
oldViewProps.htmlStyle.ol.marginLeft) {
|
|
552
|
+
[newConfig setOrderedListMarginLeft:newViewProps.htmlStyle.ol.marginLeft];
|
|
553
|
+
stylePropChanged = YES;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// since this prop defaults to undefined on JS side, we need to force set the
|
|
557
|
+
// value on first mount
|
|
558
|
+
if (newViewProps.htmlStyle.ol.markerFontWeight !=
|
|
559
|
+
oldViewProps.htmlStyle.ol.markerFontWeight ||
|
|
560
|
+
isFirstMount) {
|
|
561
|
+
if (!newViewProps.htmlStyle.ol.markerFontWeight.empty()) {
|
|
562
|
+
[newConfig
|
|
563
|
+
setOrderedListMarkerFontWeight:
|
|
564
|
+
[NSString
|
|
565
|
+
fromCppString:newViewProps.htmlStyle.ol.markerFontWeight]];
|
|
566
|
+
} else {
|
|
567
|
+
[newConfig setOrderedListMarkerFontWeight:[newConfig primaryFontWeight]];
|
|
568
|
+
}
|
|
569
|
+
stylePropChanged = YES;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// since this prop defaults to undefined on JS side, we need to force set the
|
|
573
|
+
// value on first mount
|
|
574
|
+
if (newViewProps.htmlStyle.ol.markerColor !=
|
|
575
|
+
oldViewProps.htmlStyle.ol.markerColor ||
|
|
576
|
+
isFirstMount) {
|
|
577
|
+
if (isColorMeaningful(newViewProps.htmlStyle.ol.markerColor)) {
|
|
578
|
+
[newConfig
|
|
579
|
+
setOrderedListMarkerColor:RCTUIColorFromSharedColor(
|
|
580
|
+
newViewProps.htmlStyle.ol.markerColor)];
|
|
581
|
+
} else {
|
|
582
|
+
[newConfig setOrderedListMarkerColor:[newConfig primaryColor]];
|
|
583
|
+
}
|
|
584
|
+
stylePropChanged = YES;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (newViewProps.htmlStyle.ul.bulletColor !=
|
|
588
|
+
oldViewProps.htmlStyle.ul.bulletColor) {
|
|
589
|
+
if (isColorMeaningful(newViewProps.htmlStyle.ul.bulletColor)) {
|
|
590
|
+
[newConfig setUnorderedListBulletColor:RCTUIColorFromSharedColor(
|
|
591
|
+
newViewProps.htmlStyle.ul
|
|
592
|
+
.bulletColor)];
|
|
593
|
+
stylePropChanged = YES;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (newViewProps.htmlStyle.ul.bulletSize !=
|
|
598
|
+
oldViewProps.htmlStyle.ul.bulletSize) {
|
|
599
|
+
[newConfig setUnorderedListBulletSize:newViewProps.htmlStyle.ul.bulletSize];
|
|
600
|
+
stylePropChanged = YES;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (newViewProps.htmlStyle.ul.gapWidth !=
|
|
604
|
+
oldViewProps.htmlStyle.ul.gapWidth) {
|
|
605
|
+
[newConfig setUnorderedListGapWidth:newViewProps.htmlStyle.ul.gapWidth];
|
|
606
|
+
stylePropChanged = YES;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (newViewProps.htmlStyle.ul.marginLeft !=
|
|
610
|
+
oldViewProps.htmlStyle.ul.marginLeft) {
|
|
611
|
+
[newConfig setUnorderedListMarginLeft:newViewProps.htmlStyle.ul.marginLeft];
|
|
612
|
+
stylePropChanged = YES;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (newViewProps.htmlStyle.a.color != oldViewProps.htmlStyle.a.color) {
|
|
616
|
+
if (isColorMeaningful(newViewProps.htmlStyle.a.color)) {
|
|
617
|
+
[newConfig setLinkColor:RCTUIColorFromSharedColor(
|
|
618
|
+
newViewProps.htmlStyle.a.color)];
|
|
619
|
+
stylePropChanged = YES;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (newViewProps.htmlStyle.codeblock.color !=
|
|
624
|
+
oldViewProps.htmlStyle.codeblock.color) {
|
|
625
|
+
if (isColorMeaningful(newViewProps.htmlStyle.codeblock.color)) {
|
|
626
|
+
[newConfig
|
|
627
|
+
setCodeBlockFgColor:RCTUIColorFromSharedColor(
|
|
628
|
+
newViewProps.htmlStyle.codeblock.color)];
|
|
629
|
+
stylePropChanged = YES;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (newViewProps.htmlStyle.codeblock.backgroundColor !=
|
|
634
|
+
oldViewProps.htmlStyle.codeblock.backgroundColor) {
|
|
635
|
+
if (isColorMeaningful(newViewProps.htmlStyle.codeblock.backgroundColor)) {
|
|
636
|
+
[newConfig setCodeBlockBgColor:RCTUIColorFromSharedColor(
|
|
637
|
+
newViewProps.htmlStyle.codeblock
|
|
638
|
+
.backgroundColor)];
|
|
639
|
+
stylePropChanged = YES;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (newViewProps.htmlStyle.codeblock.borderRadius !=
|
|
644
|
+
oldViewProps.htmlStyle.codeblock.borderRadius) {
|
|
645
|
+
[newConfig
|
|
646
|
+
setCodeBlockBorderRadius:newViewProps.htmlStyle.codeblock.borderRadius];
|
|
647
|
+
stylePropChanged = YES;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (newViewProps.htmlStyle.ulCheckbox.boxSize !=
|
|
651
|
+
oldViewProps.htmlStyle.ulCheckbox.boxSize) {
|
|
652
|
+
[newConfig
|
|
653
|
+
setCheckboxListBoxSize:newViewProps.htmlStyle.ulCheckbox.boxSize];
|
|
654
|
+
stylePropChanged = YES;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (newViewProps.htmlStyle.ulCheckbox.gapWidth !=
|
|
658
|
+
oldViewProps.htmlStyle.ulCheckbox.gapWidth) {
|
|
659
|
+
[newConfig
|
|
660
|
+
setCheckboxListGapWidth:newViewProps.htmlStyle.ulCheckbox.gapWidth];
|
|
661
|
+
stylePropChanged = YES;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (newViewProps.htmlStyle.ulCheckbox.marginLeft !=
|
|
665
|
+
oldViewProps.htmlStyle.ulCheckbox.marginLeft) {
|
|
666
|
+
[newConfig
|
|
667
|
+
setCheckboxListMarginLeft:newViewProps.htmlStyle.ulCheckbox.marginLeft];
|
|
668
|
+
stylePropChanged = YES;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (newViewProps.htmlStyle.ulCheckbox.boxColor !=
|
|
672
|
+
oldViewProps.htmlStyle.ulCheckbox.boxColor) {
|
|
673
|
+
if (isColorMeaningful(newViewProps.htmlStyle.ulCheckbox.boxColor)) {
|
|
674
|
+
[newConfig setCheckboxListBoxColor:RCTUIColorFromSharedColor(
|
|
675
|
+
newViewProps.htmlStyle.ulCheckbox
|
|
676
|
+
.boxColor)];
|
|
677
|
+
stylePropChanged = YES;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (newViewProps.htmlStyle.a.textDecorationLine !=
|
|
682
|
+
oldViewProps.htmlStyle.a.textDecorationLine) {
|
|
683
|
+
NSString *objcString =
|
|
684
|
+
[NSString fromCppString:newViewProps.htmlStyle.a.textDecorationLine];
|
|
685
|
+
if ([objcString isEqualToString:DecorationUnderline]) {
|
|
686
|
+
[newConfig setLinkDecorationLine:DecorationUnderline];
|
|
687
|
+
} else {
|
|
688
|
+
// both DecorationNone and a different, wrong value gets a DecorationNone
|
|
689
|
+
// here
|
|
690
|
+
[newConfig setLinkDecorationLine:DecorationNone];
|
|
691
|
+
}
|
|
692
|
+
stylePropChanged = YES;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (newViewProps.scrollEnabled != oldViewProps.scrollEnabled ||
|
|
696
|
+
textView.scrollEnabled != newViewProps.scrollEnabled) {
|
|
697
|
+
[textView setScrollEnabled:newViewProps.scrollEnabled];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
folly::dynamic oldMentionStyle = oldViewProps.htmlStyle.mention;
|
|
701
|
+
folly::dynamic newMentionStyle = newViewProps.htmlStyle.mention;
|
|
702
|
+
if (oldMentionStyle != newMentionStyle) {
|
|
703
|
+
bool newSingleProps = NO;
|
|
704
|
+
|
|
705
|
+
for (const auto &obj : newMentionStyle.items()) {
|
|
706
|
+
if (obj.second.isInt() || obj.second.isString()) {
|
|
707
|
+
// we are in just a single MentionStyleProps object
|
|
708
|
+
newSingleProps = YES;
|
|
709
|
+
break;
|
|
710
|
+
} else if (obj.second.isObject()) {
|
|
711
|
+
// we are in map of indicators to MentionStyleProps
|
|
712
|
+
newSingleProps = NO;
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (newSingleProps) {
|
|
718
|
+
[newConfig setMentionStyleProps:
|
|
719
|
+
[MentionStyleProps
|
|
720
|
+
getSinglePropsFromFollyDynamic:newMentionStyle]];
|
|
721
|
+
} else {
|
|
722
|
+
[newConfig setMentionStyleProps:
|
|
723
|
+
[MentionStyleProps
|
|
724
|
+
getComplexPropsFromFollyDynamic:newMentionStyle]];
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
stylePropChanged = YES;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (stylePropChanged) {
|
|
731
|
+
// all the text needs to be rebuilt
|
|
732
|
+
// we get the current html using old config, then switch to new config and
|
|
733
|
+
// replace text using the html this way, the newest config attributes are
|
|
734
|
+
// being used!
|
|
735
|
+
|
|
736
|
+
// the html needs to be generated using the old config
|
|
737
|
+
NSString *currentHtml = [parser
|
|
738
|
+
parseToHtmlFromRange:NSMakeRange(0,
|
|
739
|
+
textView.textStorage.string.length)];
|
|
740
|
+
// we want to preserve the selection between props changes
|
|
741
|
+
NSRange prevSelectedRange = textView.selectedRange;
|
|
742
|
+
|
|
743
|
+
// now set the new config
|
|
744
|
+
config = newConfig;
|
|
745
|
+
|
|
746
|
+
// fill the typing attributes with style props
|
|
747
|
+
defaultTypingAttributes[NSForegroundColorAttributeName] =
|
|
748
|
+
[config primaryColor];
|
|
749
|
+
defaultTypingAttributes[NSFontAttributeName] = [config primaryFont];
|
|
750
|
+
defaultTypingAttributes[NSUnderlineColorAttributeName] =
|
|
751
|
+
[config primaryColor];
|
|
752
|
+
defaultTypingAttributes[NSStrikethroughColorAttributeName] =
|
|
753
|
+
[config primaryColor];
|
|
754
|
+
NSMutableParagraphStyle *defaultPStyle =
|
|
755
|
+
[[NSMutableParagraphStyle alloc] init];
|
|
756
|
+
defaultPStyle.minimumLineHeight = [config scaledPrimaryLineHeight];
|
|
757
|
+
defaultTypingAttributes[NSParagraphStyleAttributeName] = defaultPStyle;
|
|
758
|
+
|
|
759
|
+
// no emitting during styles reload
|
|
760
|
+
blockEmitting = YES;
|
|
761
|
+
|
|
762
|
+
// make sure everything is sound in the html
|
|
763
|
+
NSString *initiallyProcessedHtml =
|
|
764
|
+
[parser initiallyProcessHtml:currentHtml];
|
|
765
|
+
if (initiallyProcessedHtml != nullptr) {
|
|
766
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
blockEmitting = NO;
|
|
770
|
+
|
|
771
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
772
|
+
textView.selectedRange = prevSelectedRange;
|
|
773
|
+
|
|
774
|
+
// make sure the newest lineHeight is applied
|
|
775
|
+
[self refreshLineHeight];
|
|
776
|
+
// update the placeholder as well
|
|
777
|
+
[self refreshPlaceholderLabelStyles];
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// editable
|
|
781
|
+
if (newViewProps.editable != textView.editable) {
|
|
782
|
+
textView.editable = newViewProps.editable;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// useHtmlNormalizer
|
|
786
|
+
if (newViewProps.useHtmlNormalizer != oldViewProps.useHtmlNormalizer) {
|
|
787
|
+
useHtmlNormalizer = newViewProps.useHtmlNormalizer;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// default value - must be set before placeholder to make sure it correctly
|
|
791
|
+
// shows on first mount
|
|
792
|
+
if (newViewProps.defaultValue != oldViewProps.defaultValue) {
|
|
793
|
+
NSString *newDefaultValue =
|
|
794
|
+
[NSString fromCppString:newViewProps.defaultValue];
|
|
795
|
+
|
|
796
|
+
NSString *initiallyProcessedHtml =
|
|
797
|
+
[parser initiallyProcessHtml:newDefaultValue];
|
|
798
|
+
if (initiallyProcessedHtml == nullptr) {
|
|
799
|
+
// just plain text
|
|
800
|
+
textView.text = newDefaultValue;
|
|
801
|
+
} else {
|
|
802
|
+
// we've got some seemingly proper html
|
|
803
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
804
|
+
}
|
|
805
|
+
textView.selectedRange = NSRange(textView.textStorage.string.length, 0);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// placeholderTextColor
|
|
809
|
+
if (newViewProps.placeholderTextColor != oldViewProps.placeholderTextColor) {
|
|
810
|
+
// some real color
|
|
811
|
+
if (isColorMeaningful(newViewProps.placeholderTextColor)) {
|
|
812
|
+
_placeholderColor =
|
|
813
|
+
RCTUIColorFromSharedColor(newViewProps.placeholderTextColor);
|
|
814
|
+
} else {
|
|
815
|
+
_placeholderColor = nullptr;
|
|
816
|
+
}
|
|
817
|
+
[self refreshPlaceholderLabelStyles];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// placeholder
|
|
821
|
+
if (newViewProps.placeholder != oldViewProps.placeholder) {
|
|
822
|
+
_placeholderLabel.text = [NSString fromCppString:newViewProps.placeholder];
|
|
823
|
+
[self refreshPlaceholderLabelStyles];
|
|
824
|
+
// additionally show placeholder on first mount if it should be there
|
|
825
|
+
if (isFirstMount && textView.text.length == 0) {
|
|
826
|
+
[self setPlaceholderLabelShown:YES];
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// mention indicators
|
|
831
|
+
auto mismatchPair = std::mismatch(newViewProps.mentionIndicators.begin(),
|
|
832
|
+
newViewProps.mentionIndicators.end(),
|
|
833
|
+
oldViewProps.mentionIndicators.begin(),
|
|
834
|
+
oldViewProps.mentionIndicators.end());
|
|
835
|
+
if (mismatchPair.first != newViewProps.mentionIndicators.end() ||
|
|
836
|
+
mismatchPair.second != oldViewProps.mentionIndicators.end()) {
|
|
837
|
+
NSMutableSet<NSNumber *> *newIndicators = [[NSMutableSet alloc] init];
|
|
838
|
+
for (const std::string &item : newViewProps.mentionIndicators) {
|
|
839
|
+
if (item.length() == 1) {
|
|
840
|
+
[newIndicators addObject:@(item[0])];
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
[config setMentionIndicators:newIndicators];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// linkRegex
|
|
847
|
+
LinkRegexConfig *oldRegexConfig =
|
|
848
|
+
[[LinkRegexConfig alloc] initWithLinkRegexProp:oldViewProps.linkRegex];
|
|
849
|
+
LinkRegexConfig *newRegexConfig =
|
|
850
|
+
[[LinkRegexConfig alloc] initWithLinkRegexProp:newViewProps.linkRegex];
|
|
851
|
+
if (![newRegexConfig isEqualToConfig:oldRegexConfig]) {
|
|
852
|
+
[config setLinkRegexConfig:newRegexConfig];
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// selection color sets both selection and cursor on iOS (just as in RN)
|
|
856
|
+
if (newViewProps.selectionColor != oldViewProps.selectionColor) {
|
|
857
|
+
if (isColorMeaningful(newViewProps.selectionColor)) {
|
|
858
|
+
textView.tintColor =
|
|
859
|
+
RCTUIColorFromSharedColor(newViewProps.selectionColor);
|
|
860
|
+
} else {
|
|
861
|
+
textView.tintColor = nullptr;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (newViewProps.returnKeyType != oldViewProps.returnKeyType) {
|
|
866
|
+
NSString *str = [NSString fromCppString:newViewProps.returnKeyType];
|
|
867
|
+
|
|
868
|
+
textView.returnKeyType =
|
|
869
|
+
[KeyboardUtils getUIReturnKeyTypeFromReturnKeyType:str];
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (newViewProps.submitBehavior != oldViewProps.submitBehavior) {
|
|
873
|
+
_submitBehavior = [NSString fromCppString:newViewProps.submitBehavior];
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// autoCapitalize
|
|
877
|
+
if (newViewProps.autoCapitalize != oldViewProps.autoCapitalize) {
|
|
878
|
+
NSString *str = [NSString fromCppString:newViewProps.autoCapitalize];
|
|
879
|
+
if ([str isEqualToString:@"none"]) {
|
|
880
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
881
|
+
} else if ([str isEqualToString:@"sentences"]) {
|
|
882
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeSentences;
|
|
883
|
+
} else if ([str isEqualToString:@"words"]) {
|
|
884
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
|
885
|
+
} else if ([str isEqualToString:@"characters"]) {
|
|
886
|
+
textView.autocapitalizationType =
|
|
887
|
+
UITextAutocapitalizationTypeAllCharacters;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// textView needs to be refocused on autocapitalization type change and we
|
|
891
|
+
// don't want to emit these events
|
|
892
|
+
if ([textView isFirstResponder]) {
|
|
893
|
+
_emitFocusBlur = NO;
|
|
894
|
+
[textView reactBlur];
|
|
895
|
+
[textView reactFocus];
|
|
896
|
+
_emitFocusBlur = YES;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// isOnChangeHtmlSet
|
|
901
|
+
_emitHtml = newViewProps.isOnChangeHtmlSet;
|
|
902
|
+
|
|
903
|
+
// isOnChangeTextSet
|
|
904
|
+
_emitTextChange = newViewProps.isOnChangeTextSet;
|
|
905
|
+
|
|
906
|
+
// contextMenuItems
|
|
907
|
+
bool contextMenuChanged = newViewProps.contextMenuItems.size() !=
|
|
908
|
+
oldViewProps.contextMenuItems.size();
|
|
909
|
+
if (!contextMenuChanged) {
|
|
910
|
+
for (size_t i = 0; i < newViewProps.contextMenuItems.size(); i++) {
|
|
911
|
+
if (newViewProps.contextMenuItems[i].text !=
|
|
912
|
+
oldViewProps.contextMenuItems[i].text) {
|
|
913
|
+
contextMenuChanged = true;
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (contextMenuChanged) {
|
|
919
|
+
NSMutableArray<NSString *> *items = [NSMutableArray new];
|
|
920
|
+
for (const auto &item : newViewProps.contextMenuItems) {
|
|
921
|
+
[items addObject:[NSString fromCppString:item.text]];
|
|
922
|
+
}
|
|
923
|
+
_contextMenuItems = [items copy];
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
[super updateProps:props oldProps:oldProps];
|
|
927
|
+
// run the changes callback
|
|
928
|
+
[self anyTextMayHaveBeenModified];
|
|
929
|
+
|
|
930
|
+
// autofocus - needs to be done at the very end
|
|
931
|
+
if (isFirstMount && newViewProps.autoFocus) {
|
|
932
|
+
[textView reactFocus];
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
- (void)setPlaceholderLabelShown:(BOOL)shown {
|
|
937
|
+
if (shown) {
|
|
938
|
+
[self refreshPlaceholderLabelStyles];
|
|
939
|
+
_placeholderLabel.hidden = NO;
|
|
940
|
+
} else {
|
|
941
|
+
_placeholderLabel.hidden = YES;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
- (void)refreshPlaceholderLabelStyles {
|
|
946
|
+
NSMutableDictionary *newAttrs = [defaultTypingAttributes mutableCopy];
|
|
947
|
+
if (_placeholderColor != nullptr) {
|
|
948
|
+
newAttrs[NSForegroundColorAttributeName] = _placeholderColor;
|
|
949
|
+
}
|
|
950
|
+
NSAttributedString *newAttrStr =
|
|
951
|
+
[[NSAttributedString alloc] initWithString:_placeholderLabel.text
|
|
952
|
+
attributes:newAttrs];
|
|
953
|
+
_placeholderLabel.attributedText = newAttrStr;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
- (void)refreshLineHeight {
|
|
957
|
+
[textView.textStorage
|
|
958
|
+
enumerateAttribute:NSParagraphStyleAttributeName
|
|
959
|
+
inRange:NSMakeRange(0, textView.textStorage.string.length)
|
|
960
|
+
options:0
|
|
961
|
+
usingBlock:^(id _Nullable value, NSRange range,
|
|
962
|
+
BOOL *_Nonnull stop) {
|
|
963
|
+
NSMutableParagraphStyle *pStyle =
|
|
964
|
+
[(NSParagraphStyle *)value mutableCopy];
|
|
965
|
+
if (pStyle == nil)
|
|
966
|
+
return;
|
|
967
|
+
pStyle.minimumLineHeight = [config scaledPrimaryLineHeight];
|
|
968
|
+
[textView.textStorage addAttribute:NSParagraphStyleAttributeName
|
|
969
|
+
value:pStyle
|
|
970
|
+
range:range];
|
|
971
|
+
}];
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// MARK: - Measuring and states
|
|
975
|
+
|
|
976
|
+
- (CGSize)measureSize:(CGFloat)maxWidth {
|
|
977
|
+
// copy the the whole attributed string
|
|
978
|
+
NSMutableAttributedString *currentStr = [[NSMutableAttributedString alloc]
|
|
979
|
+
initWithAttributedString:textView.textStorage];
|
|
980
|
+
|
|
981
|
+
// edge case: empty input should still be of a height of a single line, so we
|
|
982
|
+
// add a mock "I" character
|
|
983
|
+
if ([currentStr length] == 0) {
|
|
984
|
+
[currentStr
|
|
985
|
+
appendAttributedString:[[NSAttributedString alloc]
|
|
986
|
+
initWithString:@"I"
|
|
987
|
+
attributes:textView.typingAttributes]];
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// edge case: input with only a zero width space should still be of a height
|
|
991
|
+
// of a single line, so we add a mock "I" character
|
|
992
|
+
if ([currentStr length] == 1 &&
|
|
993
|
+
[[currentStr.string substringWithRange:NSMakeRange(0, 1)]
|
|
994
|
+
isEqualToString:@"\u200B"]) {
|
|
995
|
+
[currentStr
|
|
996
|
+
appendAttributedString:[[NSAttributedString alloc]
|
|
997
|
+
initWithString:@"I"
|
|
998
|
+
attributes:textView.typingAttributes]];
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// edge case: trailing newlines aren't counted towards height calculations, so
|
|
1002
|
+
// we add a mock "I" character
|
|
1003
|
+
if (currentStr.length > 0) {
|
|
1004
|
+
unichar lastChar =
|
|
1005
|
+
[currentStr.string characterAtIndex:currentStr.length - 1];
|
|
1006
|
+
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
|
|
1007
|
+
[currentStr
|
|
1008
|
+
appendAttributedString:[[NSAttributedString alloc]
|
|
1009
|
+
initWithString:@"I"
|
|
1010
|
+
attributes:defaultTypingAttributes]];
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
CGRect boundingBox =
|
|
1015
|
+
[currentStr boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
|
|
1016
|
+
options:NSStringDrawingUsesLineFragmentOrigin |
|
|
1017
|
+
NSStringDrawingUsesFontLeading
|
|
1018
|
+
context:nullptr];
|
|
1019
|
+
|
|
1020
|
+
return CGSizeMake(maxWidth, ceil(boundingBox.size.height));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// make sure the newest state is kept in _state property
|
|
1024
|
+
- (void)updateState:(State::Shared const &)state
|
|
1025
|
+
oldState:(State::Shared const &)oldState {
|
|
1026
|
+
_state = std::static_pointer_cast<
|
|
1027
|
+
const EnrichedTextInputViewShadowNode::ConcreteState>(state);
|
|
1028
|
+
|
|
1029
|
+
// first render with all the needed stuff already defined (state and
|
|
1030
|
+
// componentView) so we need to run a single height calculation for any
|
|
1031
|
+
// initial values
|
|
1032
|
+
if (oldState == nullptr) {
|
|
1033
|
+
[self tryUpdatingHeight];
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
- (void)tryUpdatingHeight {
|
|
1038
|
+
if (_state == nullptr) {
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
_componentViewHeightUpdateCounter++;
|
|
1042
|
+
auto selfRef = wrapManagedObjectWeakly(self);
|
|
1043
|
+
_state->updateState(
|
|
1044
|
+
EnrichedTextInputViewState(_componentViewHeightUpdateCounter, selfRef));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// MARK: - Active styles
|
|
1048
|
+
|
|
1049
|
+
- (void)tryUpdatingActiveStyles {
|
|
1050
|
+
// style updates are emitted only if something differs from the previously
|
|
1051
|
+
// active styles
|
|
1052
|
+
BOOL updateNeeded = NO;
|
|
1053
|
+
|
|
1054
|
+
// active styles are kept in a separate set until we're sure they can be
|
|
1055
|
+
// emitted
|
|
1056
|
+
NSMutableSet *newActiveStyles = [_activeStyles mutableCopy];
|
|
1057
|
+
|
|
1058
|
+
// currently blocked styles are subject to change (e.g. bold being blocked by
|
|
1059
|
+
// headings might change in reaction to prop change) so they also are kept
|
|
1060
|
+
// separately
|
|
1061
|
+
NSMutableSet *newBlockedStyles = [_blockedStyles mutableCopy];
|
|
1062
|
+
|
|
1063
|
+
// data for onLinkDetected event
|
|
1064
|
+
LinkData *detectedLinkData;
|
|
1065
|
+
NSRange detectedLinkRange = NSMakeRange(0, 0);
|
|
1066
|
+
|
|
1067
|
+
// data for onMentionDetected event
|
|
1068
|
+
MentionParams *detectedMentionParams = nullptr;
|
|
1069
|
+
NSRange detectedMentionRange = NSMakeRange(0, 0);
|
|
1070
|
+
|
|
1071
|
+
for (NSNumber *type in stylesDict) {
|
|
1072
|
+
StyleBase *style = stylesDict[type];
|
|
1073
|
+
|
|
1074
|
+
BOOL wasActive = [newActiveStyles containsObject:type];
|
|
1075
|
+
BOOL isActive = [style detect:textView.selectedRange];
|
|
1076
|
+
|
|
1077
|
+
BOOL wasBlocked = [newBlockedStyles containsObject:type];
|
|
1078
|
+
BOOL isBlocked = [self isStyle:(StyleType)[type integerValue]
|
|
1079
|
+
activeInMap:blockingStyles];
|
|
1080
|
+
|
|
1081
|
+
if (wasActive != isActive) {
|
|
1082
|
+
updateNeeded = YES;
|
|
1083
|
+
if (isActive) {
|
|
1084
|
+
[newActiveStyles addObject:type];
|
|
1085
|
+
} else {
|
|
1086
|
+
[newActiveStyles removeObject:type];
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// blocked state change for a style also needs an update
|
|
1091
|
+
if (wasBlocked != isBlocked) {
|
|
1092
|
+
updateNeeded = YES;
|
|
1093
|
+
if (isBlocked) {
|
|
1094
|
+
[newBlockedStyles addObject:type];
|
|
1095
|
+
} else {
|
|
1096
|
+
[newBlockedStyles removeObject:type];
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// onLinkDetected event
|
|
1101
|
+
if (isActive && [type intValue] == [LinkStyle getType]) {
|
|
1102
|
+
// get the link data
|
|
1103
|
+
LinkData *candidateLinkData;
|
|
1104
|
+
NSRange candidateLinkRange = NSMakeRange(0, 0);
|
|
1105
|
+
LinkStyle *linkStyleClass =
|
|
1106
|
+
(LinkStyle *)stylesDict[@([LinkStyle getType])];
|
|
1107
|
+
if (linkStyleClass != nullptr) {
|
|
1108
|
+
candidateLinkData =
|
|
1109
|
+
[linkStyleClass getLinkDataAt:textView.selectedRange.location];
|
|
1110
|
+
candidateLinkRange =
|
|
1111
|
+
[linkStyleClass getFullLinkRangeAt:textView.selectedRange.location];
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (wasActive == NO) {
|
|
1115
|
+
// we changed selection from non-link to a link
|
|
1116
|
+
detectedLinkData = candidateLinkData;
|
|
1117
|
+
detectedLinkRange = candidateLinkRange;
|
|
1118
|
+
} else if (![_recentlyActiveLinkData
|
|
1119
|
+
isEqualToLinkData:candidateLinkData] ||
|
|
1120
|
+
!NSEqualRanges(_recentlyActiveLinkRange, candidateLinkRange)) {
|
|
1121
|
+
// we changed selection from one link to the other or modified
|
|
1122
|
+
// current link's text
|
|
1123
|
+
detectedLinkData = candidateLinkData;
|
|
1124
|
+
detectedLinkRange = candidateLinkRange;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// onMentionDetected event
|
|
1129
|
+
if (isActive && [type intValue] == [MentionStyle getType]) {
|
|
1130
|
+
// get mention data
|
|
1131
|
+
MentionParams *candidateMentionParams;
|
|
1132
|
+
NSRange candidateMentionRange = NSMakeRange(0, 0);
|
|
1133
|
+
MentionStyle *mentionStyleClass =
|
|
1134
|
+
(MentionStyle *)stylesDict[@([MentionStyle getType])];
|
|
1135
|
+
if (mentionStyleClass != nullptr) {
|
|
1136
|
+
candidateMentionParams = [mentionStyleClass
|
|
1137
|
+
getMentionParamsAt:textView.selectedRange.location];
|
|
1138
|
+
candidateMentionRange = [mentionStyleClass
|
|
1139
|
+
getFullMentionRangeAt:textView.selectedRange.location];
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (wasActive == NO) {
|
|
1143
|
+
// selection was changed from a non-mention to a mention
|
|
1144
|
+
detectedMentionParams = candidateMentionParams;
|
|
1145
|
+
detectedMentionRange = candidateMentionRange;
|
|
1146
|
+
} else if (![_recentlyActiveMentionParams.text
|
|
1147
|
+
isEqualToString:candidateMentionParams.text] ||
|
|
1148
|
+
![_recentlyActiveMentionParams.attributes
|
|
1149
|
+
isEqualToString:candidateMentionParams.attributes] ||
|
|
1150
|
+
!NSEqualRanges(_recentlyActiveMentionRange,
|
|
1151
|
+
candidateMentionRange)) {
|
|
1152
|
+
// selection changed from one mention to another
|
|
1153
|
+
detectedMentionParams = candidateMentionParams;
|
|
1154
|
+
detectedMentionRange = candidateMentionRange;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (updateNeeded) {
|
|
1160
|
+
auto emitter = [self getEventEmitter];
|
|
1161
|
+
if (emitter != nullptr) {
|
|
1162
|
+
// update activeStyles and blockedStyles only if emitter is available
|
|
1163
|
+
_activeStyles = newActiveStyles;
|
|
1164
|
+
_blockedStyles = newBlockedStyles;
|
|
1165
|
+
|
|
1166
|
+
emitter->onChangeState(
|
|
1167
|
+
{.bold = GET_STYLE_STATE([BoldStyle getType]),
|
|
1168
|
+
.italic = GET_STYLE_STATE([ItalicStyle getType]),
|
|
1169
|
+
.underline = GET_STYLE_STATE([UnderlineStyle getType]),
|
|
1170
|
+
.strikeThrough = GET_STYLE_STATE([StrikethroughStyle getType]),
|
|
1171
|
+
.inlineCode = GET_STYLE_STATE([InlineCodeStyle getType]),
|
|
1172
|
+
.link = GET_STYLE_STATE([LinkStyle getType]),
|
|
1173
|
+
.mention = GET_STYLE_STATE([MentionStyle getType]),
|
|
1174
|
+
.h1 = GET_STYLE_STATE([H1Style getType]),
|
|
1175
|
+
.h2 = GET_STYLE_STATE([H2Style getType]),
|
|
1176
|
+
.h3 = GET_STYLE_STATE([H3Style getType]),
|
|
1177
|
+
.h4 = GET_STYLE_STATE([H4Style getType]),
|
|
1178
|
+
.h5 = GET_STYLE_STATE([H5Style getType]),
|
|
1179
|
+
.h6 = GET_STYLE_STATE([H6Style getType]),
|
|
1180
|
+
.unorderedList = GET_STYLE_STATE([UnorderedListStyle getType]),
|
|
1181
|
+
.orderedList = GET_STYLE_STATE([OrderedListStyle getType]),
|
|
1182
|
+
.blockQuote = GET_STYLE_STATE([BlockQuoteStyle getType]),
|
|
1183
|
+
.codeBlock = GET_STYLE_STATE([CodeBlockStyle getType]),
|
|
1184
|
+
.image = GET_STYLE_STATE([ImageStyle getType]),
|
|
1185
|
+
.checkboxList = GET_STYLE_STATE([CheckboxListStyle getType])});
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
if (detectedLinkData != nullptr) {
|
|
1190
|
+
// emit onLinkeDetected event
|
|
1191
|
+
[self emitOnLinkDetectedEvent:detectedLinkData range:detectedLinkRange];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (detectedMentionParams != nullptr) {
|
|
1195
|
+
// emit onMentionDetected event
|
|
1196
|
+
[self emitOnMentionDetectedEvent:detectedMentionParams.text
|
|
1197
|
+
indicator:detectedMentionParams.indicator
|
|
1198
|
+
attributes:detectedMentionParams.attributes];
|
|
1199
|
+
|
|
1200
|
+
_recentlyActiveMentionParams = detectedMentionParams;
|
|
1201
|
+
_recentlyActiveMentionRange = detectedMentionRange;
|
|
1202
|
+
}
|
|
1203
|
+
// emit onChangeHtml event if needed
|
|
1204
|
+
[self tryEmittingOnChangeHtmlEvent];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
- (bool)isStyleActive:(StyleType)type {
|
|
1208
|
+
return [_activeStyles containsObject:@(type)];
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
- (bool)isStyle:(StyleType)type activeInMap:(NSDictionary *)styleMap {
|
|
1212
|
+
NSArray *relatedStyles = styleMap[@(type)];
|
|
1213
|
+
|
|
1214
|
+
if (!relatedStyles) {
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
for (NSNumber *style in relatedStyles) {
|
|
1219
|
+
if ([_activeStyles containsObject:style]) {
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
- (bool)textInputShouldReturn {
|
|
1228
|
+
return [_submitBehavior isEqualToString:@"blurAndSubmit"];
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
- (bool)textInputShouldSubmitOnReturn {
|
|
1232
|
+
return [_submitBehavior isEqualToString:@"blurAndSubmit"] ||
|
|
1233
|
+
[_submitBehavior isEqualToString:@"submit"];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
- (void)addStyleBlock:(StyleType)blocking to:(StyleType)blocked {
|
|
1237
|
+
NSMutableArray *blocksArr = [blockingStyles[@(blocked)] mutableCopy];
|
|
1238
|
+
if (![blocksArr containsObject:@(blocking)]) {
|
|
1239
|
+
[blocksArr addObject:@(blocking)];
|
|
1240
|
+
blockingStyles[@(blocked)] = blocksArr;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
- (void)removeStyleBlock:(StyleType)blocking from:(StyleType)blocked {
|
|
1245
|
+
NSMutableArray *blocksArr = [blockingStyles[@(blocked)] mutableCopy];
|
|
1246
|
+
if ([blocksArr containsObject:@(blocking)]) {
|
|
1247
|
+
[blocksArr removeObject:@(blocking)];
|
|
1248
|
+
blockingStyles[@(blocked)] = blocksArr;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
- (void)addStyleConflict:(StyleType)conflicting to:(StyleType)conflicted {
|
|
1253
|
+
NSMutableArray *conflictsArr = [conflictingStyles[@(conflicted)] mutableCopy];
|
|
1254
|
+
if (![conflictsArr containsObject:@(conflicting)]) {
|
|
1255
|
+
[conflictsArr addObject:@(conflicting)];
|
|
1256
|
+
conflictingStyles[@(conflicted)] = conflictsArr;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
- (void)removeStyleConflict:(StyleType)conflicting from:(StyleType)conflicted {
|
|
1261
|
+
NSMutableArray *conflictsArr = [conflictingStyles[@(conflicted)] mutableCopy];
|
|
1262
|
+
if ([conflictsArr containsObject:@(conflicting)]) {
|
|
1263
|
+
[conflictsArr removeObject:@(conflicting)];
|
|
1264
|
+
conflictingStyles[@(conflicted)] = conflictsArr;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// MARK: - Native commands and events
|
|
1269
|
+
|
|
1270
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
1271
|
+
if ([commandName isEqualToString:@"focus"]) {
|
|
1272
|
+
[self focus];
|
|
1273
|
+
} else if ([commandName isEqualToString:@"blur"]) {
|
|
1274
|
+
[self blur];
|
|
1275
|
+
} else if ([commandName isEqualToString:@"setValue"]) {
|
|
1276
|
+
NSString *value = (NSString *)args[0];
|
|
1277
|
+
[self setValue:value];
|
|
1278
|
+
} else if ([commandName isEqualToString:@"toggleBold"]) {
|
|
1279
|
+
[self toggleRegularStyle:[BoldStyle getType]];
|
|
1280
|
+
} else if ([commandName isEqualToString:@"toggleItalic"]) {
|
|
1281
|
+
[self toggleRegularStyle:[ItalicStyle getType]];
|
|
1282
|
+
} else if ([commandName isEqualToString:@"toggleUnderline"]) {
|
|
1283
|
+
[self toggleRegularStyle:[UnderlineStyle getType]];
|
|
1284
|
+
} else if ([commandName isEqualToString:@"toggleStrikeThrough"]) {
|
|
1285
|
+
[self toggleRegularStyle:[StrikethroughStyle getType]];
|
|
1286
|
+
} else if ([commandName isEqualToString:@"toggleInlineCode"]) {
|
|
1287
|
+
[self toggleRegularStyle:[InlineCodeStyle getType]];
|
|
1288
|
+
} else if ([commandName isEqualToString:@"addLink"]) {
|
|
1289
|
+
NSInteger start = [((NSNumber *)args[0]) integerValue];
|
|
1290
|
+
NSInteger end = [((NSNumber *)args[1]) integerValue];
|
|
1291
|
+
NSString *text = (NSString *)args[2];
|
|
1292
|
+
NSString *url = (NSString *)args[3];
|
|
1293
|
+
[self addLinkAt:start end:end text:text url:url];
|
|
1294
|
+
} else if ([commandName isEqualToString:@"removeLink"]) {
|
|
1295
|
+
NSInteger start = [((NSNumber *)args[0]) integerValue];
|
|
1296
|
+
NSInteger end = [((NSNumber *)args[1]) integerValue];
|
|
1297
|
+
[self removeLinkAt:start end:end];
|
|
1298
|
+
} else if ([commandName isEqualToString:@"addMention"]) {
|
|
1299
|
+
NSString *indicator = (NSString *)args[0];
|
|
1300
|
+
NSString *text = (NSString *)args[1];
|
|
1301
|
+
NSString *attributes = (NSString *)args[2];
|
|
1302
|
+
[self addMention:indicator text:text attributes:attributes];
|
|
1303
|
+
} else if ([commandName isEqualToString:@"startMention"]) {
|
|
1304
|
+
NSString *indicator = (NSString *)args[0];
|
|
1305
|
+
[self startMentionWithIndicator:indicator];
|
|
1306
|
+
} else if ([commandName isEqualToString:@"toggleH1"]) {
|
|
1307
|
+
[self toggleRegularStyle:[H1Style getType]];
|
|
1308
|
+
} else if ([commandName isEqualToString:@"toggleH2"]) {
|
|
1309
|
+
[self toggleRegularStyle:[H2Style getType]];
|
|
1310
|
+
} else if ([commandName isEqualToString:@"toggleH3"]) {
|
|
1311
|
+
[self toggleRegularStyle:[H3Style getType]];
|
|
1312
|
+
} else if ([commandName isEqualToString:@"toggleH4"]) {
|
|
1313
|
+
[self toggleRegularStyle:[H4Style getType]];
|
|
1314
|
+
} else if ([commandName isEqualToString:@"toggleH5"]) {
|
|
1315
|
+
[self toggleRegularStyle:[H5Style getType]];
|
|
1316
|
+
} else if ([commandName isEqualToString:@"toggleH6"]) {
|
|
1317
|
+
[self toggleRegularStyle:[H6Style getType]];
|
|
1318
|
+
} else if ([commandName isEqualToString:@"toggleUnorderedList"]) {
|
|
1319
|
+
[self toggleRegularStyle:[UnorderedListStyle getType]];
|
|
1320
|
+
} else if ([commandName isEqualToString:@"toggleOrderedList"]) {
|
|
1321
|
+
[self toggleRegularStyle:[OrderedListStyle getType]];
|
|
1322
|
+
} else if ([commandName isEqualToString:@"toggleCheckboxList"]) {
|
|
1323
|
+
BOOL checked = [args[0] boolValue];
|
|
1324
|
+
[self toggleCheckboxList:checked];
|
|
1325
|
+
} else if ([commandName isEqualToString:@"toggleBlockQuote"]) {
|
|
1326
|
+
[self toggleRegularStyle:[BlockQuoteStyle getType]];
|
|
1327
|
+
} else if ([commandName isEqualToString:@"toggleCodeBlock"]) {
|
|
1328
|
+
[self toggleRegularStyle:[CodeBlockStyle getType]];
|
|
1329
|
+
} else if ([commandName isEqualToString:@"addImage"]) {
|
|
1330
|
+
NSString *uri = (NSString *)args[0];
|
|
1331
|
+
CGFloat imgWidth = [(NSNumber *)args[1] floatValue];
|
|
1332
|
+
CGFloat imgHeight = [(NSNumber *)args[2] floatValue];
|
|
1333
|
+
|
|
1334
|
+
[self addImage:uri width:imgWidth height:imgHeight];
|
|
1335
|
+
} else if ([commandName isEqualToString:@"insertText"]) {
|
|
1336
|
+
NSString *text = (NSString *)args[0];
|
|
1337
|
+
NSInteger start = [((NSNumber *)args[1]) integerValue];
|
|
1338
|
+
NSInteger end = [((NSNumber *)args[2]) integerValue];
|
|
1339
|
+
[self insertText:text start:start end:end];
|
|
1340
|
+
} else if ([commandName isEqualToString:@"setSelection"]) {
|
|
1341
|
+
NSInteger start = [((NSNumber *)args[0]) integerValue];
|
|
1342
|
+
NSInteger end = [((NSNumber *)args[1]) integerValue];
|
|
1343
|
+
[self setCustomSelection:start end:end];
|
|
1344
|
+
} else if ([commandName isEqualToString:@"requestHTML"]) {
|
|
1345
|
+
NSInteger requestId = [((NSNumber *)args[0]) integerValue];
|
|
1346
|
+
[self requestHTML:requestId];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
- (std::shared_ptr<EnrichedTextInputViewEventEmitter>)getEventEmitter {
|
|
1351
|
+
if (_eventEmitter != nullptr && !blockEmitting) {
|
|
1352
|
+
auto emitter =
|
|
1353
|
+
static_cast<const EnrichedTextInputViewEventEmitter &>(*_eventEmitter);
|
|
1354
|
+
return std::make_shared<EnrichedTextInputViewEventEmitter>(emitter);
|
|
1355
|
+
} else {
|
|
1356
|
+
return nullptr;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
- (void)blur {
|
|
1361
|
+
[textView reactBlur];
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
- (void)focus {
|
|
1365
|
+
[textView reactFocus];
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
- (void)setValue:(NSString *)value {
|
|
1369
|
+
NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value];
|
|
1370
|
+
if (initiallyProcessedHtml == nullptr) {
|
|
1371
|
+
// just plain text
|
|
1372
|
+
textView.text = value;
|
|
1373
|
+
} else {
|
|
1374
|
+
// we've got some seemingly proper html
|
|
1375
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// set selectedRange and check for changes
|
|
1379
|
+
textView.selectedRange = NSRange(textView.textStorage.string.length, 0);
|
|
1380
|
+
[self anyTextMayHaveBeenModified];
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
- (void)insertText:(NSString *)text start:(NSInteger)start end:(NSInteger)end {
|
|
1384
|
+
NSString *currentText = textView.textStorage.string;
|
|
1385
|
+
|
|
1386
|
+
NSRange replaceRange;
|
|
1387
|
+
if (start < 0) {
|
|
1388
|
+
// Use current selection
|
|
1389
|
+
replaceRange = textView.selectedRange;
|
|
1390
|
+
} else {
|
|
1391
|
+
NSUInteger actualStart = [self getActualIndex:start text:currentText];
|
|
1392
|
+
NSUInteger actualEnd = [self getActualIndex:end text:currentText];
|
|
1393
|
+
replaceRange = NSMakeRange(actualStart, actualEnd - actualStart);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Replace the range with the new text, preserving surrounding attributes
|
|
1397
|
+
[textView.textStorage replaceCharactersInRange:replaceRange withString:text];
|
|
1398
|
+
|
|
1399
|
+
// Move cursor to end of inserted text
|
|
1400
|
+
textView.selectedRange = NSMakeRange(replaceRange.location + text.length, 0);
|
|
1401
|
+
|
|
1402
|
+
[self anyTextMayHaveBeenModified];
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
- (void)setCustomSelection:(NSInteger)visibleStart end:(NSInteger)visibleEnd {
|
|
1406
|
+
NSString *text = textView.textStorage.string;
|
|
1407
|
+
|
|
1408
|
+
NSUInteger actualStart = [self getActualIndex:visibleStart text:text];
|
|
1409
|
+
NSUInteger actualEnd = [self getActualIndex:visibleEnd text:text];
|
|
1410
|
+
|
|
1411
|
+
textView.selectedRange = NSMakeRange(actualStart, actualEnd - actualStart);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Helper: Walks through the string skipping ZWSPs to find the Nth visible
|
|
1415
|
+
// character
|
|
1416
|
+
- (NSUInteger)getActualIndex:(NSInteger)visibleIndex text:(NSString *)text {
|
|
1417
|
+
NSUInteger currentVisibleCount = 0;
|
|
1418
|
+
NSUInteger actualIndex = 0;
|
|
1419
|
+
|
|
1420
|
+
while (actualIndex < text.length) {
|
|
1421
|
+
if (currentVisibleCount == visibleIndex) {
|
|
1422
|
+
return actualIndex;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// If the current char is not a hidden space, it counts towards our visible
|
|
1426
|
+
// index.
|
|
1427
|
+
if ([text characterAtIndex:actualIndex] != 0x200B) {
|
|
1428
|
+
currentVisibleCount++;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
actualIndex++;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return actualIndex;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
- (void)emitOnSubmitEdittingEvent {
|
|
1438
|
+
auto emitter = [self getEventEmitter];
|
|
1439
|
+
if (emitter != nullptr) {
|
|
1440
|
+
NSString *stringToBeEmitted = [[textView.textStorage.string
|
|
1441
|
+
stringByReplacingOccurrencesOfString:@"\u200B"
|
|
1442
|
+
withString:@""] copy];
|
|
1443
|
+
|
|
1444
|
+
emitter->onSubmitEditing({
|
|
1445
|
+
.text = [stringToBeEmitted toCppString],
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
- (void)emitOnLinkDetectedEvent:(LinkData *)linkData range:(NSRange)range {
|
|
1451
|
+
auto emitter = [self getEventEmitter];
|
|
1452
|
+
if (emitter != nullptr) {
|
|
1453
|
+
// update recently active link info
|
|
1454
|
+
_recentlyActiveLinkData = linkData;
|
|
1455
|
+
_recentlyActiveLinkRange = range;
|
|
1456
|
+
|
|
1457
|
+
emitter->onLinkDetected({
|
|
1458
|
+
.text = [linkData.text toCppString],
|
|
1459
|
+
.url = [linkData.url toCppString],
|
|
1460
|
+
.start = static_cast<int>(range.location),
|
|
1461
|
+
.end = static_cast<int>(range.location + range.length),
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
- (void)emitOnPasteImagesEvent:(NSArray<NSDictionary *> *)images {
|
|
1467
|
+
auto emitter = [self getEventEmitter];
|
|
1468
|
+
if (emitter != nullptr) {
|
|
1469
|
+
std::vector<EnrichedTextInputViewEventEmitter::OnPasteImagesImages>
|
|
1470
|
+
imagesVector;
|
|
1471
|
+
imagesVector.reserve(images.count);
|
|
1472
|
+
|
|
1473
|
+
for (NSDictionary *img in images) {
|
|
1474
|
+
NSString *uri = img[@"uri"];
|
|
1475
|
+
NSString *type = img[@"type"];
|
|
1476
|
+
double width = [img[@"width"] doubleValue];
|
|
1477
|
+
double height = [img[@"height"] doubleValue];
|
|
1478
|
+
|
|
1479
|
+
EnrichedTextInputViewEventEmitter::OnPasteImagesImages imageStruct = {
|
|
1480
|
+
.uri = [uri toCppString],
|
|
1481
|
+
.type = [type toCppString],
|
|
1482
|
+
.width = width,
|
|
1483
|
+
.height = height};
|
|
1484
|
+
|
|
1485
|
+
imagesVector.push_back(imageStruct);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
emitter->onPasteImages({.images = imagesVector});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
- (void)emitOnMentionDetectedEvent:(NSString *)text
|
|
1493
|
+
indicator:(NSString *)indicator
|
|
1494
|
+
attributes:(NSString *)attributes {
|
|
1495
|
+
auto emitter = [self getEventEmitter];
|
|
1496
|
+
if (emitter != nullptr) {
|
|
1497
|
+
emitter->onMentionDetected({.text = [text toCppString],
|
|
1498
|
+
.indicator = [indicator toCppString],
|
|
1499
|
+
.payload = [attributes toCppString]});
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
- (void)emitOnMentionEvent:(NSString *)indicator text:(NSString *)text {
|
|
1504
|
+
auto emitter = [self getEventEmitter];
|
|
1505
|
+
if (emitter != nullptr) {
|
|
1506
|
+
if (text != nullptr) {
|
|
1507
|
+
folly::dynamic fdStr = [text toCppString];
|
|
1508
|
+
emitter->onMention({.indicator = [indicator toCppString], .text = fdStr});
|
|
1509
|
+
} else {
|
|
1510
|
+
folly::dynamic nul = nullptr;
|
|
1511
|
+
emitter->onMention({.indicator = [indicator toCppString], .text = nul});
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
- (void)tryEmittingOnChangeHtmlEvent {
|
|
1517
|
+
if (!_emitHtml || textView.markedTextRange != nullptr) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
auto emitter = [self getEventEmitter];
|
|
1521
|
+
if (emitter != nullptr) {
|
|
1522
|
+
NSString *htmlOutput = [parser
|
|
1523
|
+
parseToHtmlFromRange:NSMakeRange(0,
|
|
1524
|
+
textView.textStorage.string.length)];
|
|
1525
|
+
// make sure html really changed
|
|
1526
|
+
if (![htmlOutput isEqualToString:_recentlyEmittedHtml]) {
|
|
1527
|
+
_recentlyEmittedHtml = htmlOutput;
|
|
1528
|
+
emitter->onChangeHtml({.value = [htmlOutput toCppString]});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
- (void)requestHTML:(NSInteger)requestId {
|
|
1534
|
+
auto emitter = [self getEventEmitter];
|
|
1535
|
+
if (emitter != nullptr) {
|
|
1536
|
+
@try {
|
|
1537
|
+
NSString *htmlOutput = [parser
|
|
1538
|
+
parseToHtmlFromRange:NSMakeRange(0,
|
|
1539
|
+
textView.textStorage.string.length)];
|
|
1540
|
+
emitter->onRequestHtmlResult({.requestId = static_cast<int>(requestId),
|
|
1541
|
+
.html = [htmlOutput toCppString]});
|
|
1542
|
+
} @catch (NSException *exception) {
|
|
1543
|
+
emitter->onRequestHtmlResult({.requestId = static_cast<int>(requestId),
|
|
1544
|
+
.html = folly::dynamic(nullptr)});
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
- (void)emitOnKeyPressEvent:(NSString *)key {
|
|
1550
|
+
auto emitter = [self getEventEmitter];
|
|
1551
|
+
if (emitter != nullptr) {
|
|
1552
|
+
emitter->onInputKeyPress({.key = [key toCppString]});
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// MARK: - Styles manipulation
|
|
1557
|
+
|
|
1558
|
+
- (void)toggleRegularStyle:(StyleType)type {
|
|
1559
|
+
StyleBase *style = stylesDict[@(type)];
|
|
1560
|
+
NSRange range = textView.selectedRange;
|
|
1561
|
+
if ([style isParagraph]) {
|
|
1562
|
+
range = [textView.textStorage.string paragraphRangeForRange:range];
|
|
1563
|
+
}
|
|
1564
|
+
if ([self handleStyleBlocksAndConflicts:type range:range]) {
|
|
1565
|
+
[style toggle:range];
|
|
1566
|
+
[self anyTextMayHaveBeenModified];
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
- (void)toggleCheckboxList:(BOOL)checked {
|
|
1571
|
+
CheckboxListStyle *style =
|
|
1572
|
+
(CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
|
|
1573
|
+
if (style == nullptr) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
NSRange range = [textView.textStorage.string
|
|
1577
|
+
paragraphRangeForRange:textView.selectedRange];
|
|
1578
|
+
if ([self handleStyleBlocksAndConflicts:[CheckboxListStyle getType]
|
|
1579
|
+
range:range]) {
|
|
1580
|
+
[style toggleWithChecked:checked range:range];
|
|
1581
|
+
[self anyTextMayHaveBeenModified];
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
- (void)addLinkAt:(NSInteger)start
|
|
1586
|
+
end:(NSInteger)end
|
|
1587
|
+
text:(NSString *)text
|
|
1588
|
+
url:(NSString *)url {
|
|
1589
|
+
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getType])];
|
|
1590
|
+
if (linkStyleClass == nullptr) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// translate the output start-end notation to range
|
|
1595
|
+
NSRange linkRange = NSMakeRange(start, end - start);
|
|
1596
|
+
if ([self handleStyleBlocksAndConflicts:[LinkStyle getType]
|
|
1597
|
+
range:linkRange]) {
|
|
1598
|
+
LinkData *linkData = [[LinkData alloc] init];
|
|
1599
|
+
linkData.text = text;
|
|
1600
|
+
linkData.url = url;
|
|
1601
|
+
linkData.isManual = YES;
|
|
1602
|
+
[linkStyleClass addLink:linkData range:linkRange withSelection:YES];
|
|
1603
|
+
[self anyTextMayHaveBeenModified];
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
- (void)removeLinkAt:(NSInteger)start end:(NSInteger)end {
|
|
1608
|
+
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getType])];
|
|
1609
|
+
if (linkStyleClass == nullptr) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
NSInteger textLength = (NSInteger)textView.textStorage.length;
|
|
1614
|
+
if (start < 0) {
|
|
1615
|
+
start = 0;
|
|
1616
|
+
}
|
|
1617
|
+
if (end > textLength) {
|
|
1618
|
+
end = textLength;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
NSInteger rangeStart = MIN(start, end);
|
|
1622
|
+
NSInteger rangeLength = MAX(start, end) - rangeStart;
|
|
1623
|
+
NSRange linkRange = NSMakeRange(rangeStart, rangeLength);
|
|
1624
|
+
|
|
1625
|
+
[linkStyleClass remove:linkRange withDirtyRange:YES];
|
|
1626
|
+
[self anyTextMayHaveBeenModified];
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
- (void)addMention:(NSString *)indicator
|
|
1630
|
+
text:(NSString *)text
|
|
1631
|
+
attributes:(NSString *)attributes {
|
|
1632
|
+
MentionStyle *mentionStyleClass =
|
|
1633
|
+
(MentionStyle *)stylesDict[@([MentionStyle getType])];
|
|
1634
|
+
if (mentionStyleClass == nullptr) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
if ([mentionStyleClass getActiveMentionRange] == nullptr) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if ([self handleStyleBlocksAndConflicts:[MentionStyle getType]
|
|
1642
|
+
range:[[mentionStyleClass
|
|
1643
|
+
getActiveMentionRange]
|
|
1644
|
+
rangeValue]]) {
|
|
1645
|
+
[mentionStyleClass addMention:indicator text:text attributes:attributes];
|
|
1646
|
+
[self anyTextMayHaveBeenModified];
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
- (void)addImage:(NSString *)uri width:(float)width height:(float)height {
|
|
1651
|
+
ImageStyle *imageStyleClass =
|
|
1652
|
+
(ImageStyle *)stylesDict[@([ImageStyle getType])];
|
|
1653
|
+
if (imageStyleClass == nullptr) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if ([self handleStyleBlocksAndConflicts:[ImageStyle getType]
|
|
1658
|
+
range:textView.selectedRange]) {
|
|
1659
|
+
[imageStyleClass addImage:uri width:width height:height];
|
|
1660
|
+
[self anyTextMayHaveBeenModified];
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
- (void)startMentionWithIndicator:(NSString *)indicator {
|
|
1665
|
+
MentionStyle *mentionStyleClass =
|
|
1666
|
+
(MentionStyle *)stylesDict[@([MentionStyle getType])];
|
|
1667
|
+
if (mentionStyleClass == nullptr) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
if ([self handleStyleBlocksAndConflicts:[MentionStyle getType]
|
|
1672
|
+
range:textView.selectedRange]) {
|
|
1673
|
+
[mentionStyleClass startMentionWithIndicator:indicator];
|
|
1674
|
+
[self anyTextMayHaveBeenModified];
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// returns false when style shouldn't be applied and true when it can be
|
|
1679
|
+
- (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
|
|
1680
|
+
// handle blocking styles: if any is present we do not apply the toggled style
|
|
1681
|
+
NSArray<NSNumber *> *blocking =
|
|
1682
|
+
[self getPresentStyleTypesFrom:blockingStyles[@(type)] range:range];
|
|
1683
|
+
if (blocking.count != 0) {
|
|
1684
|
+
return NO;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// handle conflicting styles: remove styles within the range
|
|
1688
|
+
NSArray<NSNumber *> *conflicting =
|
|
1689
|
+
[self getPresentStyleTypesFrom:conflictingStyles[@(type)] range:range];
|
|
1690
|
+
if (conflicting.count != 0) {
|
|
1691
|
+
for (NSNumber *type in conflicting) {
|
|
1692
|
+
StyleBase *style = stylesDict[type];
|
|
1693
|
+
|
|
1694
|
+
if ([style isParagraph]) {
|
|
1695
|
+
// for paragraph styles we can just call remove since it will pick up
|
|
1696
|
+
// proper paragraph range
|
|
1697
|
+
[style remove:range withDirtyRange:YES];
|
|
1698
|
+
} else {
|
|
1699
|
+
// for inline styles we have to differentiate betweeen normal and typing
|
|
1700
|
+
// attributes removal
|
|
1701
|
+
range.length >= 1 ? [style remove:range withDirtyRange:YES]
|
|
1702
|
+
: [style removeTyping];
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return YES;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
- (NSArray<NSNumber *> *)getPresentStyleTypesFrom:(NSArray<NSNumber *> *)types
|
|
1710
|
+
range:(NSRange)range {
|
|
1711
|
+
NSMutableArray<NSNumber *> *resultArray =
|
|
1712
|
+
[[NSMutableArray<NSNumber *> alloc] init];
|
|
1713
|
+
for (NSNumber *type in types) {
|
|
1714
|
+
StyleBase *style = stylesDict[type];
|
|
1715
|
+
|
|
1716
|
+
if (range.length >= 1) {
|
|
1717
|
+
if ([style any:range]) {
|
|
1718
|
+
[resultArray addObject:type];
|
|
1719
|
+
}
|
|
1720
|
+
} else {
|
|
1721
|
+
if ([style detect:range]) {
|
|
1722
|
+
[resultArray addObject:type];
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return resultArray;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
- (void)manageSelectionBasedChanges {
|
|
1730
|
+
NSString *currentString = [textView.textStorage.string copy];
|
|
1731
|
+
|
|
1732
|
+
MentionStyle *mentionStyleClass =
|
|
1733
|
+
(MentionStyle *)stylesDict[@([MentionStyle getType])];
|
|
1734
|
+
if (mentionStyleClass != nullptr) {
|
|
1735
|
+
// mention editing runs if only a selection was done (no text change)
|
|
1736
|
+
// otherwise we would double-emit with a second call in the
|
|
1737
|
+
// anyTextMayHaveBeenModified method
|
|
1738
|
+
if ([_recentInputString isEqualToString:currentString]) {
|
|
1739
|
+
[mentionStyleClass manageMentionEditing];
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// attributes manager handles proper typingAttributes at all times to properly
|
|
1744
|
+
// extend meta-attributes
|
|
1745
|
+
BOOL onlySelectionChanged =
|
|
1746
|
+
textView.selectedRange.length == 0 &&
|
|
1747
|
+
[_recentInputString isEqualToString:currentString];
|
|
1748
|
+
// removedTypingAttributes aren't normally removed during the regular flow
|
|
1749
|
+
// and we do remove them only here - so when we are sure that selection
|
|
1750
|
+
// changed. We want to remember which attributes were removed as long as we
|
|
1751
|
+
// stay at the same position. This prevents a removed attribute from being
|
|
1752
|
+
// re-applied from the preceding character right after we toggled it off.
|
|
1753
|
+
[attributesManager clearRemovedTypingAttributes];
|
|
1754
|
+
[attributesManager
|
|
1755
|
+
manageTypingAttributesWithOnlySelection:onlySelectionChanged];
|
|
1756
|
+
|
|
1757
|
+
// always update active styles
|
|
1758
|
+
[self tryUpdatingActiveStyles];
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
- (void)handleWordModificationBasedChanges:(NSString *)word
|
|
1762
|
+
inRange:(NSRange)range {
|
|
1763
|
+
// manual links refreshing and automatic links detection handling
|
|
1764
|
+
LinkStyle *linkStyle = [stylesDict objectForKey:@([LinkStyle getType])];
|
|
1765
|
+
|
|
1766
|
+
if (linkStyle != nullptr) {
|
|
1767
|
+
// manual links need to be handled first because they can block automatic
|
|
1768
|
+
// links after being refreshed
|
|
1769
|
+
[linkStyle handleManualLinks:word inRange:range];
|
|
1770
|
+
[linkStyle handleAutomaticLinks:word inRange:range];
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
- (void)anyTextMayHaveBeenModified {
|
|
1775
|
+
// we don't do no text changes when working with iOS marked text
|
|
1776
|
+
if (textView.markedTextRange != nullptr) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// zero width space adding or removal
|
|
1781
|
+
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1782
|
+
|
|
1783
|
+
// emptying input typing attributes management
|
|
1784
|
+
if (textView.textStorage.string.length == 0 &&
|
|
1785
|
+
_recentInputString.length > 0) {
|
|
1786
|
+
// reset typing attribtues
|
|
1787
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// mentions management: removal and editing
|
|
1791
|
+
MentionStyle *mentionStyleClass =
|
|
1792
|
+
(MentionStyle *)stylesDict[@([MentionStyle getType])];
|
|
1793
|
+
if (mentionStyleClass != nullptr) {
|
|
1794
|
+
[mentionStyleClass handleExistingMentions];
|
|
1795
|
+
[mentionStyleClass manageMentionEditing];
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// placholder management
|
|
1799
|
+
if (!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
|
|
1800
|
+
[self setPlaceholderLabelShown:NO];
|
|
1801
|
+
} else if (textView.textStorage.string.length == 0 &&
|
|
1802
|
+
_placeholderLabel.hidden) {
|
|
1803
|
+
[self setPlaceholderLabelShown:YES];
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// modified words handling
|
|
1807
|
+
NSArray *currentDirtyRanges = [attributesManager getDirtyRanges];
|
|
1808
|
+
if (currentDirtyRanges.count > 0) {
|
|
1809
|
+
NSMutableArray *modifiedWords = [[NSMutableArray alloc] init];
|
|
1810
|
+
|
|
1811
|
+
for (NSValue *dirtyRangeValue in currentDirtyRanges) {
|
|
1812
|
+
NSRange dirtyRange = [dirtyRangeValue rangeValue];
|
|
1813
|
+
NSArray *words =
|
|
1814
|
+
[WordsUtils getAffectedWordsFromText:textView.textStorage.string
|
|
1815
|
+
modificationRange:dirtyRange];
|
|
1816
|
+
if (words != nullptr) {
|
|
1817
|
+
[modifiedWords addObjectsFromArray:words];
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
for (NSDictionary *wordDict in modifiedWords) {
|
|
1822
|
+
NSString *wordText = (NSString *)[wordDict objectForKey:@"word"];
|
|
1823
|
+
NSValue *wordRange = (NSValue *)[wordDict objectForKey:@"range"];
|
|
1824
|
+
|
|
1825
|
+
if (wordText == nullptr || wordRange == nullptr) {
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
[self handleWordModificationBasedChanges:wordText
|
|
1830
|
+
inRange:[wordRange rangeValue]];
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
if (![textView.textStorage.string isEqualToString:_recentInputString]) {
|
|
1835
|
+
// emit onChangeText event
|
|
1836
|
+
auto emitter = [self getEventEmitter];
|
|
1837
|
+
if (emitter != nullptr && _emitTextChange) {
|
|
1838
|
+
// set the recent input string only if the emitter is defined
|
|
1839
|
+
_recentInputString = [textView.textStorage.string copy];
|
|
1840
|
+
|
|
1841
|
+
// emit string without zero width spaces
|
|
1842
|
+
NSString *stringToBeEmitted = [[textView.textStorage.string
|
|
1843
|
+
stringByReplacingOccurrencesOfString:@"\u200B"
|
|
1844
|
+
withString:@""] copy];
|
|
1845
|
+
|
|
1846
|
+
emitter->onChangeText({.value = [stringToBeEmitted toCppString]});
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
// all the visible (not meta) attributes handling in the ranges that could
|
|
1850
|
+
// have changed
|
|
1851
|
+
[attributesManager handleDirtyRangesStyling];
|
|
1852
|
+
// update height on each character change
|
|
1853
|
+
[self tryUpdatingHeight];
|
|
1854
|
+
// update active styles as well
|
|
1855
|
+
[self tryUpdatingActiveStyles];
|
|
1856
|
+
[self layoutAttachments];
|
|
1857
|
+
// update drawing - schedule debounced relayout
|
|
1858
|
+
[self scheduleRelayoutIfNeeded];
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// Debounced relayout helper - coalesces multiple requests into one per runloop
|
|
1862
|
+
// tick
|
|
1863
|
+
- (void)scheduleRelayoutIfNeeded {
|
|
1864
|
+
// Cancel any previously scheduled invocation to debounce
|
|
1865
|
+
[NSObject cancelPreviousPerformRequestsWithTarget:self
|
|
1866
|
+
selector:@selector(_performRelayout)
|
|
1867
|
+
object:nil];
|
|
1868
|
+
// Schedule on next runloop cycle
|
|
1869
|
+
[self performSelector:@selector(_performRelayout)
|
|
1870
|
+
withObject:nil
|
|
1871
|
+
afterDelay:0];
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
- (void)_performRelayout {
|
|
1875
|
+
if (!textView) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1880
|
+
NSRange wholeRange =
|
|
1881
|
+
NSMakeRange(0, self->textView.textStorage.string.length);
|
|
1882
|
+
NSRange actualRange = NSMakeRange(0, 0);
|
|
1883
|
+
[self->textView.layoutManager
|
|
1884
|
+
invalidateLayoutForCharacterRange:wholeRange
|
|
1885
|
+
actualCharacterRange:&actualRange];
|
|
1886
|
+
[self->textView.layoutManager ensureLayoutForCharacterRange:actualRange];
|
|
1887
|
+
[self->textView.layoutManager
|
|
1888
|
+
invalidateDisplayForCharacterRange:wholeRange];
|
|
1889
|
+
|
|
1890
|
+
// We have to explicitly set contentSize
|
|
1891
|
+
// That way textView knows if content overflows and if should be scrollable
|
|
1892
|
+
// We recall measureSize here because value returned from previous
|
|
1893
|
+
// measureSize may not be up-to date at that point
|
|
1894
|
+
CGSize measuredSize = [self measureSize:self->textView.frame.size.width];
|
|
1895
|
+
self->textView.contentSize = measuredSize;
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
- (void)didMoveToWindow {
|
|
1900
|
+
[super didMoveToWindow];
|
|
1901
|
+
// used to run all lifecycle callbacks
|
|
1902
|
+
[self anyTextMayHaveBeenModified];
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// MARK: - Delegate methods
|
|
1906
|
+
|
|
1907
|
+
- (void)textViewDidBeginEditing:(UITextView *)textView {
|
|
1908
|
+
auto emitter = [self getEventEmitter];
|
|
1909
|
+
if (emitter != nullptr) {
|
|
1910
|
+
// send onFocus event if allowed
|
|
1911
|
+
if (_emitFocusBlur) {
|
|
1912
|
+
emitter->onInputFocus({});
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
NSString *textAtSelection =
|
|
1916
|
+
[[[NSMutableString alloc] initWithString:textView.textStorage.string]
|
|
1917
|
+
substringWithRange:textView.selectedRange];
|
|
1918
|
+
emitter->onChangeSelection(
|
|
1919
|
+
{.start = static_cast<int>(textView.selectedRange.location),
|
|
1920
|
+
.end = static_cast<int>(textView.selectedRange.location +
|
|
1921
|
+
textView.selectedRange.length),
|
|
1922
|
+
.text = [textAtSelection toCppString]});
|
|
1923
|
+
}
|
|
1924
|
+
// manage selection changes since textViewDidChangeSelection sometimes doesn't
|
|
1925
|
+
// run on focus
|
|
1926
|
+
[self manageSelectionBasedChanges];
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
- (void)textViewDidEndEditing:(UITextView *)textView {
|
|
1930
|
+
auto emitter = [self getEventEmitter];
|
|
1931
|
+
if (emitter != nullptr && _emitFocusBlur) {
|
|
1932
|
+
// send onBlur event
|
|
1933
|
+
emitter->onInputBlur({});
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
- (UIMenu *)textView:(UITextView *)tv
|
|
1938
|
+
editMenuForTextInRange:(NSRange)range
|
|
1939
|
+
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions
|
|
1940
|
+
API_AVAILABLE(ios(16.0)) {
|
|
1941
|
+
if (_contextMenuItems == nil || _contextMenuItems.count == 0) {
|
|
1942
|
+
return [UIMenu menuWithChildren:suggestedActions];
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
NSMutableArray<UIMenuElement *> *customActions = [NSMutableArray new];
|
|
1946
|
+
|
|
1947
|
+
for (NSString *title in _contextMenuItems) {
|
|
1948
|
+
__weak EnrichedTextInputView *weakSelf = self;
|
|
1949
|
+
|
|
1950
|
+
UIAction *action =
|
|
1951
|
+
[UIAction actionWithTitle:title
|
|
1952
|
+
image:nil
|
|
1953
|
+
identifier:nil
|
|
1954
|
+
handler:^(__kindof UIAction *_Nonnull action) {
|
|
1955
|
+
[weakSelf emitOnContextMenuItemPressEvent:title];
|
|
1956
|
+
}];
|
|
1957
|
+
[customActions addObject:action];
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
[customActions addObjectsFromArray:suggestedActions];
|
|
1961
|
+
return [UIMenu menuWithChildren:customActions];
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
- (void)emitOnContextMenuItemPressEvent:(NSString *)itemText {
|
|
1965
|
+
auto emitter = [self getEventEmitter];
|
|
1966
|
+
if (emitter != nullptr) {
|
|
1967
|
+
NSRange selectedRange = textView.selectedRange;
|
|
1968
|
+
NSString *selectedText = @"";
|
|
1969
|
+
if (selectedRange.length > 0) {
|
|
1970
|
+
selectedText =
|
|
1971
|
+
[textView.textStorage.string substringWithRange:selectedRange];
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
emitter->onContextMenuItemPress(
|
|
1975
|
+
{.itemText = [itemText toCppString],
|
|
1976
|
+
.selectedText = [selectedText toCppString],
|
|
1977
|
+
.selectionStart = static_cast<int>(selectedRange.location),
|
|
1978
|
+
.selectionEnd =
|
|
1979
|
+
static_cast<int>(selectedRange.location + selectedRange.length),
|
|
1980
|
+
.styleState = {
|
|
1981
|
+
.bold = GET_STYLE_STATE([BoldStyle getType]),
|
|
1982
|
+
.italic = GET_STYLE_STATE([ItalicStyle getType]),
|
|
1983
|
+
.underline = GET_STYLE_STATE([UnderlineStyle getType]),
|
|
1984
|
+
.strikeThrough = GET_STYLE_STATE([StrikethroughStyle getType]),
|
|
1985
|
+
.inlineCode = GET_STYLE_STATE([InlineCodeStyle getType]),
|
|
1986
|
+
.h1 = GET_STYLE_STATE([H1Style getType]),
|
|
1987
|
+
.h2 = GET_STYLE_STATE([H2Style getType]),
|
|
1988
|
+
.h3 = GET_STYLE_STATE([H3Style getType]),
|
|
1989
|
+
.h4 = GET_STYLE_STATE([H4Style getType]),
|
|
1990
|
+
.h5 = GET_STYLE_STATE([H5Style getType]),
|
|
1991
|
+
.h6 = GET_STYLE_STATE([H6Style getType]),
|
|
1992
|
+
.codeBlock = GET_STYLE_STATE([CodeBlockStyle getType]),
|
|
1993
|
+
.blockQuote = GET_STYLE_STATE([BlockQuoteStyle getType]),
|
|
1994
|
+
.orderedList = GET_STYLE_STATE([OrderedListStyle getType]),
|
|
1995
|
+
.unorderedList = GET_STYLE_STATE([UnorderedListStyle getType]),
|
|
1996
|
+
.link = GET_STYLE_STATE([LinkStyle getType]),
|
|
1997
|
+
.image = GET_STYLE_STATE([ImageStyle getType]),
|
|
1998
|
+
.mention = GET_STYLE_STATE([MentionStyle getType]),
|
|
1999
|
+
.checkboxList = GET_STYLE_STATE([CheckboxListStyle getType])}});
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
- (void)handleKeyPressInRange:(NSString *)text range:(NSRange)range {
|
|
2004
|
+
NSString *key = nil;
|
|
2005
|
+
|
|
2006
|
+
if (text.length == 0 && range.length > 0) {
|
|
2007
|
+
key = @"Backspace";
|
|
2008
|
+
} else if ([text isEqualToString:@"\n"]) {
|
|
2009
|
+
key = @"Enter";
|
|
2010
|
+
} else if ([text isEqualToString:@"\t"]) {
|
|
2011
|
+
key = @"Tab";
|
|
2012
|
+
} else if (text.length == 1) {
|
|
2013
|
+
key = text;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (key != nil) {
|
|
2017
|
+
[self emitOnKeyPressEvent:key];
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
- (bool)textView:(UITextView *)textView
|
|
2022
|
+
shouldChangeTextInRange:(NSRange)range
|
|
2023
|
+
replacementText:(NSString *)text {
|
|
2024
|
+
// Check if the user pressed "Enter"
|
|
2025
|
+
if ([text isEqualToString:@"\n"]) {
|
|
2026
|
+
const bool shouldSubmit = [self textInputShouldSubmitOnReturn];
|
|
2027
|
+
const bool shouldReturn = [self textInputShouldReturn];
|
|
2028
|
+
|
|
2029
|
+
if (shouldSubmit) {
|
|
2030
|
+
[self emitOnSubmitEdittingEvent];
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
if (shouldReturn) {
|
|
2034
|
+
[textView endEditing:NO];
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
if (shouldSubmit || shouldReturn) {
|
|
2038
|
+
return NO;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
[self handleKeyPressInRange:text range:range];
|
|
2043
|
+
|
|
2044
|
+
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getType])];
|
|
2045
|
+
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getType])];
|
|
2046
|
+
CheckboxListStyle *cbLStyle =
|
|
2047
|
+
(CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
|
|
2048
|
+
H1Style *h1Style = stylesDict[@([H1Style getType])];
|
|
2049
|
+
H2Style *h2Style = stylesDict[@([H2Style getType])];
|
|
2050
|
+
H3Style *h3Style = stylesDict[@([H3Style getType])];
|
|
2051
|
+
H4Style *h4Style = stylesDict[@([H4Style getType])];
|
|
2052
|
+
H5Style *h5Style = stylesDict[@([H5Style getType])];
|
|
2053
|
+
H6Style *h6Style = stylesDict[@([H6Style getType])];
|
|
2054
|
+
|
|
2055
|
+
// some of the changes these checks do could interfere with later checks and
|
|
2056
|
+
// cause a crash so here we rely on short circuiting evaluation of the logical
|
|
2057
|
+
// expression. Either way it's not possible to have two of them come off at
|
|
2058
|
+
// the same time
|
|
2059
|
+
if (
|
|
2060
|
+
// ZWS backspace handling for paragraph styles
|
|
2061
|
+
[ZeroWidthSpaceUtils handleBackspaceInRange:range
|
|
2062
|
+
replacementText:text
|
|
2063
|
+
input:self] ||
|
|
2064
|
+
[uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
2065
|
+
[oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
2066
|
+
[cbLStyle handleNewlinesInRange:range replacementText:text] ||
|
|
2067
|
+
[h1Style handleNewlinesInRange:range replacementText:text] ||
|
|
2068
|
+
[h2Style handleNewlinesInRange:range replacementText:text] ||
|
|
2069
|
+
[h3Style handleNewlinesInRange:range replacementText:text] ||
|
|
2070
|
+
[h4Style handleNewlinesInRange:range replacementText:text] ||
|
|
2071
|
+
[h5Style handleNewlinesInRange:range replacementText:text] ||
|
|
2072
|
+
[h6Style handleNewlinesInRange:range replacementText:text] ||
|
|
2073
|
+
[ParagraphAttributesUtils handleBackspaceInRange:range
|
|
2074
|
+
replacementText:text
|
|
2075
|
+
input:self] ||
|
|
2076
|
+
[ParagraphAttributesUtils handleResetTypingAttributesOnBackspace:range
|
|
2077
|
+
replacementText:text
|
|
2078
|
+
input:self]
|
|
2079
|
+
|
|
2080
|
+
// CRITICAL: This callback HAS TO be always evaluated last.
|
|
2081
|
+
//
|
|
2082
|
+
// This function is the "Generic Fallback": if no specific style
|
|
2083
|
+
// claims the backspace action to change its state, only then do we
|
|
2084
|
+
// proceed to physically delete the newline and merge paragraphs.
|
|
2085
|
+
||
|
|
2086
|
+
[ParagraphAttributesUtils handleParagraphStylesMergeOnBackspace:range
|
|
2087
|
+
replacementText:text
|
|
2088
|
+
input:self]) {
|
|
2089
|
+
[self anyTextMayHaveBeenModified];
|
|
2090
|
+
return NO;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
return YES;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
|
2097
|
+
// emit the event
|
|
2098
|
+
NSString *textAtSelection =
|
|
2099
|
+
[[[NSMutableString alloc] initWithString:textView.textStorage.string]
|
|
2100
|
+
substringWithRange:textView.selectedRange];
|
|
2101
|
+
|
|
2102
|
+
auto emitter = [self getEventEmitter];
|
|
2103
|
+
if (emitter != nullptr) {
|
|
2104
|
+
// iOS range works differently because it specifies location and length
|
|
2105
|
+
// here, start is the location, but end is the first index BEHIND the end.
|
|
2106
|
+
// So a 0 length range will have equal start and end
|
|
2107
|
+
emitter->onChangeSelection(
|
|
2108
|
+
{.start = static_cast<int>(textView.selectedRange.location),
|
|
2109
|
+
.end = static_cast<int>(textView.selectedRange.location +
|
|
2110
|
+
textView.selectedRange.length),
|
|
2111
|
+
.text = [textAtSelection toCppString]});
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// manage selection changes
|
|
2115
|
+
[self manageSelectionBasedChanges];
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// this function isn't called always when some text changes (for example setting
|
|
2119
|
+
// link or starting mention with indicator doesn't fire it) so all the logic is
|
|
2120
|
+
// in anyTextMayHaveBeenModified
|
|
2121
|
+
- (void)textViewDidChange:(UITextView *)textView {
|
|
2122
|
+
[self anyTextMayHaveBeenModified];
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* Handles iOS Dynamic Type changes (User changing font size in System
|
|
2127
|
+
* Settings).
|
|
2128
|
+
*
|
|
2129
|
+
* Unlike Android, iOS Views do not automatically rescale existing
|
|
2130
|
+
* NSAttributedStrings when the system font size changes. The text attributes
|
|
2131
|
+
* are static once drawn.
|
|
2132
|
+
*
|
|
2133
|
+
* This method detects the change and performs a "Hard Refresh" of the content.
|
|
2134
|
+
*/
|
|
2135
|
+
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
|
2136
|
+
[super traitCollectionDidChange:previousTraitCollection];
|
|
2137
|
+
|
|
2138
|
+
if (previousTraitCollection.preferredContentSizeCategory !=
|
|
2139
|
+
self.traitCollection.preferredContentSizeCategory) {
|
|
2140
|
+
[config invalidateFonts];
|
|
2141
|
+
|
|
2142
|
+
NSMutableDictionary *newTypingAttrs = [defaultTypingAttributes mutableCopy];
|
|
2143
|
+
newTypingAttrs[NSFontAttributeName] = [config primaryFont];
|
|
2144
|
+
|
|
2145
|
+
defaultTypingAttributes = newTypingAttrs;
|
|
2146
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
2147
|
+
|
|
2148
|
+
[self refreshPlaceholderLabelStyles];
|
|
2149
|
+
|
|
2150
|
+
NSRange prevSelectedRange = textView.selectedRange;
|
|
2151
|
+
|
|
2152
|
+
NSString *currentHtml = [parser
|
|
2153
|
+
parseToHtmlFromRange:NSMakeRange(0,
|
|
2154
|
+
textView.textStorage.string.length)];
|
|
2155
|
+
NSString *initiallyProcessedHtml =
|
|
2156
|
+
[parser initiallyProcessHtml:currentHtml];
|
|
2157
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
2158
|
+
|
|
2159
|
+
textView.selectedRange = prevSelectedRange;
|
|
2160
|
+
[self anyTextMayHaveBeenModified];
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
- (void)onTextBlockTap:(TextBlockTapGestureRecognizer *)gr {
|
|
2165
|
+
if (gr.state != UIGestureRecognizerStateEnded)
|
|
2166
|
+
return;
|
|
2167
|
+
if (![self->textView isFirstResponder]) {
|
|
2168
|
+
[self->textView becomeFirstResponder];
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
switch (gr.tapKind) {
|
|
2172
|
+
|
|
2173
|
+
case TextBlockTapKindCheckbox: {
|
|
2174
|
+
CheckboxListStyle *checkboxStyle =
|
|
2175
|
+
(CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
|
|
2176
|
+
|
|
2177
|
+
if (checkboxStyle) {
|
|
2178
|
+
NSUInteger charIndex = (NSUInteger)gr.characterIndex;
|
|
2179
|
+
[checkboxStyle toggleCheckedAt:charIndex];
|
|
2180
|
+
[self anyTextMayHaveBeenModified];
|
|
2181
|
+
|
|
2182
|
+
NSString *fullText = textView.textStorage.string;
|
|
2183
|
+
NSRange paragraphRange =
|
|
2184
|
+
[fullText paragraphRangeForRange:NSMakeRange(charIndex, 0)];
|
|
2185
|
+
NSUInteger endOfLineIndex = NSMaxRange(paragraphRange);
|
|
2186
|
+
|
|
2187
|
+
// If the paragraph ends with a newline, step back by 1 so the cursor
|
|
2188
|
+
// stays on the current line instead of jumping to the next one.
|
|
2189
|
+
if (endOfLineIndex > 0 && endOfLineIndex <= fullText.length) {
|
|
2190
|
+
unichar lastChar = [fullText characterAtIndex:endOfLineIndex - 1];
|
|
2191
|
+
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
|
|
2192
|
+
endOfLineIndex--;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// Move the cursor to the end of the currently tapped checkbox line.
|
|
2197
|
+
// Without this, the cursor may remain at its previous position,
|
|
2198
|
+
// potentially inside a different checkbox line.
|
|
2199
|
+
textView.selectedRange = NSMakeRange(endOfLineIndex, 0);
|
|
2200
|
+
}
|
|
2201
|
+
break;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
default:
|
|
2205
|
+
break;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
- (void)textStorage:(NSTextStorage *)textStorage
|
|
2210
|
+
didProcessEditing:(NSTextStorageEditActions)editedMask
|
|
2211
|
+
range:(NSRange)editedRange
|
|
2212
|
+
changeInLength:(NSInteger)delta {
|
|
2213
|
+
// iOS replacing quick double space with ". " attributes fix.
|
|
2214
|
+
[DotReplacementUtils handleDotReplacement:self
|
|
2215
|
+
textStorage:textStorage
|
|
2216
|
+
editedMask:editedMask
|
|
2217
|
+
editedRange:editedRange
|
|
2218
|
+
delta:delta];
|
|
2219
|
+
|
|
2220
|
+
// Needed dirty ranges adjustments happen on every character edition.
|
|
2221
|
+
if ((editedMask & NSTextStorageEditedCharacters) != 0) {
|
|
2222
|
+
// Always try shifting dirty ranges (happens only with delta != 0).
|
|
2223
|
+
[attributesManager shiftDirtyRangesWithEditedRange:editedRange
|
|
2224
|
+
changeInLength:delta];
|
|
2225
|
+
|
|
2226
|
+
// Add dirty ranges. We also add zero-length ranges because they are useful
|
|
2227
|
+
// for word modification-based changes.
|
|
2228
|
+
[attributesManager addDirtyRange:editedRange];
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
// MARK: - Media attachments delegate
|
|
2233
|
+
|
|
2234
|
+
- (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment {
|
|
2235
|
+
NSTextStorage *storage = textView.textStorage;
|
|
2236
|
+
NSRange fullRange = NSMakeRange(0, storage.length);
|
|
2237
|
+
|
|
2238
|
+
__block NSRange foundRange = NSMakeRange(NSNotFound, 0);
|
|
2239
|
+
|
|
2240
|
+
[storage enumerateAttribute:NSAttachmentAttributeName
|
|
2241
|
+
inRange:fullRange
|
|
2242
|
+
options:0
|
|
2243
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
2244
|
+
if (value == attachment) {
|
|
2245
|
+
foundRange = range;
|
|
2246
|
+
*stop = YES;
|
|
2247
|
+
}
|
|
2248
|
+
}];
|
|
2249
|
+
|
|
2250
|
+
if (foundRange.location == NSNotFound) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
[storage edited:NSTextStorageEditedAttributes
|
|
2255
|
+
range:foundRange
|
|
2256
|
+
changeInLength:0];
|
|
2257
|
+
|
|
2258
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2259
|
+
[self layoutAttachments];
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// MARK: - Image/GIF Overlay Management
|
|
2264
|
+
|
|
2265
|
+
- (void)layoutAttachments {
|
|
2266
|
+
NSTextStorage *storage = textView.textStorage;
|
|
2267
|
+
NSMutableDictionary<NSValue *, UIImageView *> *activeAttachmentViews =
|
|
2268
|
+
[NSMutableDictionary dictionary];
|
|
2269
|
+
|
|
2270
|
+
// Iterate over the entire text to find ImageAttachments
|
|
2271
|
+
[storage enumerateAttribute:NSAttachmentAttributeName
|
|
2272
|
+
inRange:NSMakeRange(0, storage.length)
|
|
2273
|
+
options:0
|
|
2274
|
+
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
2275
|
+
if ([value isKindOfClass:[ImageAttachment class]]) {
|
|
2276
|
+
ImageAttachment *attachment = (ImageAttachment *)value;
|
|
2277
|
+
|
|
2278
|
+
CGRect rect = [self frameForAttachment:attachment
|
|
2279
|
+
atRange:range];
|
|
2280
|
+
|
|
2281
|
+
// Get or Create the UIImageView for this specific
|
|
2282
|
+
// attachment key
|
|
2283
|
+
NSValue *key =
|
|
2284
|
+
[NSValue valueWithNonretainedObject:attachment];
|
|
2285
|
+
UIImageView *imgView = _attachmentViews[key];
|
|
2286
|
+
|
|
2287
|
+
if (!imgView) {
|
|
2288
|
+
// It doesn't exist yet, create it
|
|
2289
|
+
imgView = [[UIImageView alloc] initWithFrame:rect];
|
|
2290
|
+
imgView.contentMode = UIViewContentModeScaleAspectFit;
|
|
2291
|
+
imgView.tintColor = [UIColor labelColor];
|
|
2292
|
+
|
|
2293
|
+
// Add it directly to the TextView
|
|
2294
|
+
[textView addSubview:imgView];
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// Update position (in case text moved/scrolled)
|
|
2298
|
+
if (!CGRectEqualToRect(imgView.frame, rect)) {
|
|
2299
|
+
imgView.frame = rect;
|
|
2300
|
+
}
|
|
2301
|
+
UIImage *targetImage =
|
|
2302
|
+
attachment.storedAnimatedImage ?: attachment.image;
|
|
2303
|
+
|
|
2304
|
+
// Only set if different to avoid resetting the animation
|
|
2305
|
+
// loop
|
|
2306
|
+
if (imgView.image != targetImage) {
|
|
2307
|
+
imgView.image = targetImage;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// Ensure it is visible on top
|
|
2311
|
+
imgView.hidden = NO;
|
|
2312
|
+
[textView bringSubviewToFront:imgView];
|
|
2313
|
+
|
|
2314
|
+
activeAttachmentViews[key] = imgView;
|
|
2315
|
+
// Remove from the old map so we know it has been claimed
|
|
2316
|
+
[_attachmentViews removeObjectForKey:key];
|
|
2317
|
+
}
|
|
2318
|
+
}];
|
|
2319
|
+
|
|
2320
|
+
// Everything remaining in _attachmentViews is dead or off-screen
|
|
2321
|
+
for (UIImageView *danglingView in _attachmentViews.allValues) {
|
|
2322
|
+
[danglingView removeFromSuperview];
|
|
2323
|
+
}
|
|
2324
|
+
_attachmentViews = activeAttachmentViews;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
- (CGRect)frameForAttachment:(ImageAttachment *)attachment
|
|
2328
|
+
atRange:(NSRange)range {
|
|
2329
|
+
NSLayoutManager *layoutManager = textView.layoutManager;
|
|
2330
|
+
NSTextContainer *textContainer = textView.textContainer;
|
|
2331
|
+
NSTextStorage *storage = textView.textStorage;
|
|
2332
|
+
|
|
2333
|
+
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range
|
|
2334
|
+
actualCharacterRange:NULL];
|
|
2335
|
+
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:glyphRange
|
|
2336
|
+
inTextContainer:textContainer];
|
|
2337
|
+
|
|
2338
|
+
CGRect lineRect =
|
|
2339
|
+
[layoutManager lineFragmentRectForGlyphAtIndex:glyphRange.location
|
|
2340
|
+
effectiveRange:NULL];
|
|
2341
|
+
CGSize attachmentSize = attachment.bounds.size;
|
|
2342
|
+
|
|
2343
|
+
UIFont *font = [storage attribute:NSFontAttributeName
|
|
2344
|
+
atIndex:range.location
|
|
2345
|
+
effectiveRange:NULL];
|
|
2346
|
+
if (!font) {
|
|
2347
|
+
font = [config primaryFont];
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// Calculate (Baseline Alignment)
|
|
2351
|
+
CGFloat targetY =
|
|
2352
|
+
CGRectGetMaxY(lineRect) + font.descender - attachmentSize.height;
|
|
2353
|
+
CGRect rect =
|
|
2354
|
+
CGRectMake(glyphRect.origin.x + textView.textContainerInset.left,
|
|
2355
|
+
targetY + textView.textContainerInset.top,
|
|
2356
|
+
attachmentSize.width, attachmentSize.height);
|
|
2357
|
+
|
|
2358
|
+
return CGRectIntegral(rect);
|
|
2359
|
+
}
|
|
2360
|
+
@end
|