@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,263 @@
|
|
|
1
|
+
package com.swmansion.enriched.textinput.styles
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.Spannable
|
|
5
|
+
import android.text.SpannableStringBuilder
|
|
6
|
+
import android.text.Spanned
|
|
7
|
+
import com.swmansion.enriched.common.EnrichedConstants
|
|
8
|
+
import com.swmansion.enriched.textinput.EnrichedTextInputView
|
|
9
|
+
import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan
|
|
10
|
+
import com.swmansion.enriched.textinput.spans.EnrichedInputOrderedListSpan
|
|
11
|
+
import com.swmansion.enriched.textinput.spans.EnrichedInputUnorderedListSpan
|
|
12
|
+
import com.swmansion.enriched.textinput.spans.EnrichedSpans
|
|
13
|
+
import com.swmansion.enriched.textinput.utils.getParagraphBounds
|
|
14
|
+
import com.swmansion.enriched.textinput.utils.getSafeSpanBoundaries
|
|
15
|
+
import com.swmansion.enriched.textinput.utils.removeZWS
|
|
16
|
+
|
|
17
|
+
class ListStyles(
|
|
18
|
+
private val view: EnrichedTextInputView,
|
|
19
|
+
) {
|
|
20
|
+
private fun <T> getPreviousParagraphSpan(
|
|
21
|
+
spannable: Spannable,
|
|
22
|
+
s: Int,
|
|
23
|
+
type: Class<T>,
|
|
24
|
+
): T? {
|
|
25
|
+
if (s <= 0) return null
|
|
26
|
+
|
|
27
|
+
val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1)
|
|
28
|
+
val spans = spannable.getSpans(previousParagraphStart, previousParagraphEnd, type)
|
|
29
|
+
|
|
30
|
+
if (spans.isNotEmpty()) {
|
|
31
|
+
return spans.last()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private fun <T> isPreviousParagraphList(
|
|
38
|
+
spannable: Spannable,
|
|
39
|
+
s: Int,
|
|
40
|
+
type: Class<T>,
|
|
41
|
+
): Boolean {
|
|
42
|
+
val previousSpan = getPreviousParagraphSpan(spannable, s, type)
|
|
43
|
+
|
|
44
|
+
return previousSpan != null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun getOrderedListIndex(
|
|
48
|
+
spannable: Spannable,
|
|
49
|
+
s: Int,
|
|
50
|
+
): Int {
|
|
51
|
+
val span = getPreviousParagraphSpan(spannable, s, EnrichedInputOrderedListSpan::class.java)
|
|
52
|
+
val index = span?.getListIndex() ?: 0
|
|
53
|
+
return index + 1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private fun setSpan(
|
|
57
|
+
spannable: Spannable,
|
|
58
|
+
name: String,
|
|
59
|
+
start: Int,
|
|
60
|
+
end: Int,
|
|
61
|
+
isChecked: Boolean? = false,
|
|
62
|
+
) {
|
|
63
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
|
|
64
|
+
|
|
65
|
+
when (name) {
|
|
66
|
+
EnrichedSpans.UNORDERED_LIST -> {
|
|
67
|
+
val span = EnrichedInputUnorderedListSpan(view.htmlStyle)
|
|
68
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
EnrichedSpans.ORDERED_LIST -> {
|
|
72
|
+
val index = getOrderedListIndex(spannable, safeStart)
|
|
73
|
+
val span = EnrichedInputOrderedListSpan(index, view.htmlStyle)
|
|
74
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
EnrichedSpans.CHECKBOX_LIST -> {
|
|
78
|
+
val span = EnrichedInputCheckboxListSpan(isChecked ?: false, view.htmlStyle)
|
|
79
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
80
|
+
|
|
81
|
+
// Invalidate layout to update checkbox drawing in case checkbox is bigger than line height
|
|
82
|
+
view.layoutManager.invalidateLayout()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private fun <T> removeSpansForRange(
|
|
88
|
+
spannable: Spannable,
|
|
89
|
+
start: Int,
|
|
90
|
+
end: Int,
|
|
91
|
+
clazz: Class<T>,
|
|
92
|
+
): Boolean {
|
|
93
|
+
val ssb = spannable as SpannableStringBuilder
|
|
94
|
+
val spans = ssb.getSpans(start, end, clazz)
|
|
95
|
+
if (spans.isEmpty()) return false
|
|
96
|
+
|
|
97
|
+
for (span in spans) {
|
|
98
|
+
ssb.removeSpan(span)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
ssb.removeZWS(start, end)
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun updateOrderedListIndexes(
|
|
106
|
+
text: Spannable,
|
|
107
|
+
position: Int,
|
|
108
|
+
) {
|
|
109
|
+
val spans = text.getSpans(position + 1, text.length, EnrichedInputOrderedListSpan::class.java)
|
|
110
|
+
val sortedSpans = spans.sortedBy { text.getSpanStart(it) }
|
|
111
|
+
for (span in sortedSpans) {
|
|
112
|
+
val spanStart = text.getSpanStart(span)
|
|
113
|
+
val index = getOrderedListIndex(text, spanStart)
|
|
114
|
+
span.setListIndex(index)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private fun toggleStyle(
|
|
119
|
+
name: String,
|
|
120
|
+
checkboxState: Boolean?,
|
|
121
|
+
) {
|
|
122
|
+
if (view.selection == null) return
|
|
123
|
+
val config = EnrichedSpans.listSpans[name] ?: return
|
|
124
|
+
val spannable = view.text as SpannableStringBuilder
|
|
125
|
+
val (start, end) = view.selection.getParagraphSelection()
|
|
126
|
+
val styleStart = view.spanState?.getStart(name)
|
|
127
|
+
|
|
128
|
+
if (styleStart != null) {
|
|
129
|
+
view.spanState.setStart(name, null)
|
|
130
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
131
|
+
view.selection.validateStyles()
|
|
132
|
+
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (start == end) {
|
|
137
|
+
spannable.insert(start, EnrichedConstants.ZWS_STRING)
|
|
138
|
+
view.spanState?.setStart(name, start + 1)
|
|
139
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
140
|
+
setSpan(spannable, name, start, end + 1, checkboxState)
|
|
141
|
+
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
var currentStart = start
|
|
146
|
+
val paragraphs = spannable.substring(start, end).split("\n")
|
|
147
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
148
|
+
|
|
149
|
+
for (paragraph in paragraphs) {
|
|
150
|
+
spannable.insert(currentStart, EnrichedConstants.ZWS_STRING)
|
|
151
|
+
val currentEnd = currentStart + paragraph.length + 1
|
|
152
|
+
setSpan(spannable, name, currentStart, currentEnd, checkboxState)
|
|
153
|
+
|
|
154
|
+
currentStart = currentEnd + 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
view.spanState?.setStart(name, currentStart)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fun toggleStyle(name: String) {
|
|
161
|
+
toggleStyle(name, false)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fun toggleCheckboxListStyle(checked: Boolean) {
|
|
165
|
+
toggleStyle(EnrichedSpans.CHECKBOX_LIST, checked)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun handleAfterTextChanged(
|
|
169
|
+
s: Editable,
|
|
170
|
+
name: String,
|
|
171
|
+
endCursorPosition: Int,
|
|
172
|
+
previousTextLength: Int,
|
|
173
|
+
) {
|
|
174
|
+
val config = EnrichedSpans.listSpans[name] ?: return
|
|
175
|
+
val cursorPosition = endCursorPosition.coerceAtMost(s.length)
|
|
176
|
+
val (start, end) = s.getParagraphBounds(cursorPosition)
|
|
177
|
+
|
|
178
|
+
val isBackspace = previousTextLength > s.length
|
|
179
|
+
val isNewLine = cursorPosition > 0 && s[cursorPosition - 1] == '\n'
|
|
180
|
+
val isShortcut = config.shortcut?.let { s.substring(start, end).startsWith(it) } ?: false
|
|
181
|
+
val spans = s.getSpans(start, end, config.clazz)
|
|
182
|
+
|
|
183
|
+
// Remove spans if cursor is at the start of the paragraph and spans exist
|
|
184
|
+
if (isBackspace && start == cursorPosition && spans.isNotEmpty()) {
|
|
185
|
+
removeSpansForRange(s, start, end, config.clazz)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!isBackspace && isShortcut) {
|
|
190
|
+
s.replace(start, cursorPosition, EnrichedConstants.ZWS_STRING)
|
|
191
|
+
setSpan(s, name, start, start + 1)
|
|
192
|
+
// Inform that new span has been added
|
|
193
|
+
view.selection?.validateStyles()
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!isBackspace && isNewLine && isPreviousParagraphList(s, start, config.clazz)) {
|
|
198
|
+
// Check if the span from the previous line "leaked" into this one
|
|
199
|
+
if (spans.isNotEmpty()) {
|
|
200
|
+
val existingSpan = spans[0]
|
|
201
|
+
val spanStart = s.getSpanStart(existingSpan)
|
|
202
|
+
|
|
203
|
+
// If the span started before the current paragraph (belongs to the previous item)
|
|
204
|
+
// update it to end at the newline (start - 1)
|
|
205
|
+
if (spanStart < start) {
|
|
206
|
+
val spanFlags = s.getSpanFlags(existingSpan)
|
|
207
|
+
s.setSpan(existingSpan, spanStart, start - 1, spanFlags)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
s.insert(cursorPosition, EnrichedConstants.ZWS_STRING)
|
|
212
|
+
setSpan(s, name, start, end + 1)
|
|
213
|
+
// Inform that new span has been added
|
|
214
|
+
view.selection?.validateStyles()
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (name === EnrichedSpans.CHECKBOX_LIST) {
|
|
219
|
+
if (spans.isNotEmpty()) {
|
|
220
|
+
val previousSpan = spans[0] as EnrichedInputCheckboxListSpan
|
|
221
|
+
val isChecked = previousSpan.isChecked
|
|
222
|
+
|
|
223
|
+
for (span in spans) {
|
|
224
|
+
s.removeSpan(span)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setSpan(s, EnrichedSpans.CHECKBOX_LIST, start, end, isChecked)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (spans.isNotEmpty()) {
|
|
234
|
+
for (span in spans) {
|
|
235
|
+
s.removeSpan(span)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
setSpan(s, name, start, end)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fun afterTextChanged(
|
|
243
|
+
s: Editable,
|
|
244
|
+
endCursorPosition: Int,
|
|
245
|
+
previousTextLength: Int,
|
|
246
|
+
) {
|
|
247
|
+
handleAfterTextChanged(s, EnrichedSpans.ORDERED_LIST, endCursorPosition, previousTextLength)
|
|
248
|
+
handleAfterTextChanged(s, EnrichedSpans.UNORDERED_LIST, endCursorPosition, previousTextLength)
|
|
249
|
+
handleAfterTextChanged(s, EnrichedSpans.CHECKBOX_LIST, endCursorPosition, previousTextLength)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fun getStyleRange(): Pair<Int, Int> = view.selection?.getParagraphSelection() ?: Pair(0, 0)
|
|
253
|
+
|
|
254
|
+
fun removeStyle(
|
|
255
|
+
name: String,
|
|
256
|
+
start: Int,
|
|
257
|
+
end: Int,
|
|
258
|
+
): Boolean {
|
|
259
|
+
val config = EnrichedSpans.listSpans[name] ?: return false
|
|
260
|
+
val spannable = view.text as Spannable
|
|
261
|
+
return removeSpansForRange(spannable, start, end, config.clazz)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
package com.swmansion.enriched.textinput.styles
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.Spannable
|
|
5
|
+
import android.text.SpannableStringBuilder
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.swmansion.enriched.common.EnrichedConstants
|
|
8
|
+
import com.swmansion.enriched.textinput.EnrichedTextInputView
|
|
9
|
+
import com.swmansion.enriched.textinput.spans.EnrichedSpans
|
|
10
|
+
import com.swmansion.enriched.textinput.spans.interfaces.EnrichedInputSpan
|
|
11
|
+
import com.swmansion.enriched.textinput.utils.getParagraphBounds
|
|
12
|
+
import com.swmansion.enriched.textinput.utils.getSafeSpanBoundaries
|
|
13
|
+
import com.swmansion.enriched.textinput.utils.removeZWS
|
|
14
|
+
|
|
15
|
+
class ParagraphStyles(
|
|
16
|
+
private val view: EnrichedTextInputView,
|
|
17
|
+
) {
|
|
18
|
+
private fun <T> getPreviousParagraphSpan(
|
|
19
|
+
spannable: Spannable,
|
|
20
|
+
paragraphStart: Int,
|
|
21
|
+
type: Class<T>,
|
|
22
|
+
): T? {
|
|
23
|
+
if (paragraphStart <= 0) return null
|
|
24
|
+
|
|
25
|
+
val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(paragraphStart - 1)
|
|
26
|
+
val spans = spannable.getSpans(previousParagraphStart, previousParagraphEnd, type)
|
|
27
|
+
|
|
28
|
+
// A paragraph implies a single cohesive style. having multiple spans of the
|
|
29
|
+
// same type (e.g., two codeblock spans) in one paragraph is an invalid state in current library logic
|
|
30
|
+
if (spans.size > 1) {
|
|
31
|
+
Log.w("ParagraphStyles", "getPreviousParagraphSpan(): Found more than one span in the paragraph!")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (spans.isNotEmpty()) {
|
|
35
|
+
return spans.first()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private fun <T> getNextParagraphSpan(
|
|
42
|
+
spannable: Spannable,
|
|
43
|
+
paragraphEnd: Int,
|
|
44
|
+
type: Class<T>,
|
|
45
|
+
): T? {
|
|
46
|
+
if (paragraphEnd >= spannable.length - 1) return null
|
|
47
|
+
|
|
48
|
+
val (nextParagraphStart, nextParagraphEnd) = spannable.getParagraphBounds(paragraphEnd + 1)
|
|
49
|
+
|
|
50
|
+
val spans = spannable.getSpans(nextParagraphStart, nextParagraphEnd, type)
|
|
51
|
+
|
|
52
|
+
// A paragraph implies a single cohesive style. having multiple spans of the
|
|
53
|
+
// same type (e.g., two codeblock spans) in one paragraph is an invalid state in current library logic
|
|
54
|
+
if (spans.size > 1) {
|
|
55
|
+
Log.w("ParagraphStyles", "getNextParagraphSpan(): Found more than one span in the paragraph!")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (spans.isNotEmpty()) {
|
|
59
|
+
return spans.first()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Applies a continuous span to the specified range.
|
|
67
|
+
* If the new range touches existing continuous spans, they are coalesced into a single span
|
|
68
|
+
*/
|
|
69
|
+
private fun <T> setContinuousSpan(
|
|
70
|
+
spannable: Spannable,
|
|
71
|
+
start: Int,
|
|
72
|
+
end: Int,
|
|
73
|
+
type: Class<T>,
|
|
74
|
+
) {
|
|
75
|
+
val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
|
|
76
|
+
val previousSpan = getPreviousParagraphSpan(spannable, start, type)
|
|
77
|
+
val nextSpan = getNextParagraphSpan(spannable, end, type)
|
|
78
|
+
var newStart = start
|
|
79
|
+
var newEnd = end
|
|
80
|
+
|
|
81
|
+
if (previousSpan != null) {
|
|
82
|
+
newStart = spannable.getSpanStart(previousSpan)
|
|
83
|
+
spannable.removeSpan(previousSpan)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (nextSpan != null && start != end) {
|
|
87
|
+
newEnd = spannable.getSpanEnd(nextSpan)
|
|
88
|
+
spannable.removeSpan(nextSpan)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(newStart, newEnd)
|
|
92
|
+
spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private fun <T> setSpan(
|
|
96
|
+
spannable: Spannable,
|
|
97
|
+
type: Class<T>,
|
|
98
|
+
start: Int,
|
|
99
|
+
end: Int,
|
|
100
|
+
) {
|
|
101
|
+
if (EnrichedSpans.isTypeContinuous(type)) {
|
|
102
|
+
setContinuousSpan(spannable, start, end, type)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
|
|
107
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
|
|
108
|
+
spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Removes spans of the given type in the specified range.
|
|
112
|
+
// If the removed span intersects with the range, it will be split and the remaining part will be re-applied after the removal
|
|
113
|
+
// Returns true if any spans were removed, false otherwise
|
|
114
|
+
private fun <T> removeSpansForRange(
|
|
115
|
+
spannable: Spannable,
|
|
116
|
+
start: Int,
|
|
117
|
+
end: Int,
|
|
118
|
+
clazz: Class<T>,
|
|
119
|
+
): Boolean {
|
|
120
|
+
val ssb = spannable as SpannableStringBuilder
|
|
121
|
+
val spans = ssb.getSpans(start, end, clazz)
|
|
122
|
+
if (spans.isEmpty()) return false
|
|
123
|
+
|
|
124
|
+
for (span in spans) {
|
|
125
|
+
val spanStart = ssb.getSpanStart(span)
|
|
126
|
+
val spanEnd = ssb.getSpanEnd(span)
|
|
127
|
+
ssb.removeSpan(span)
|
|
128
|
+
|
|
129
|
+
if (spanStart < start) {
|
|
130
|
+
setSpan(ssb, clazz, spanStart, start - 1)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (spanEnd > end) {
|
|
134
|
+
setSpan(ssb, clazz, end + 1, spanEnd)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
ssb.removeZWS(start, end)
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private fun <T> setAndMergeSpans(
|
|
143
|
+
spannable: Spannable,
|
|
144
|
+
type: Class<T>,
|
|
145
|
+
start: Int,
|
|
146
|
+
end: Int,
|
|
147
|
+
) {
|
|
148
|
+
val spans = spannable.getSpans(start, end, type)
|
|
149
|
+
|
|
150
|
+
// No spans setup for current selection, means we just need to assign new span
|
|
151
|
+
if (spans.isEmpty()) {
|
|
152
|
+
setSpan(spannable, type, start, end)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var setSpanOnFinish = false
|
|
157
|
+
|
|
158
|
+
// Some spans are present, we have to remove spans and (optionally) apply new spans
|
|
159
|
+
for (span in spans) {
|
|
160
|
+
val spanStart = spannable.getSpanStart(span)
|
|
161
|
+
val spanEnd = spannable.getSpanEnd(span)
|
|
162
|
+
var finalStart: Int? = null
|
|
163
|
+
var finalEnd: Int? = null
|
|
164
|
+
|
|
165
|
+
spannable.removeSpan(span)
|
|
166
|
+
|
|
167
|
+
if (start == spanStart && end == spanEnd) {
|
|
168
|
+
setSpanOnFinish = false
|
|
169
|
+
} else if (start > spanStart && end < spanEnd) {
|
|
170
|
+
setSpan(spannable, type, spanStart, start)
|
|
171
|
+
setSpan(spannable, type, end, spanEnd)
|
|
172
|
+
} else if (start == spanStart && end < spanEnd) {
|
|
173
|
+
finalStart = end
|
|
174
|
+
finalEnd = spanEnd
|
|
175
|
+
} else if (start > spanStart && end == spanEnd) {
|
|
176
|
+
finalStart = spanStart
|
|
177
|
+
finalEnd = start
|
|
178
|
+
} else if (start > spanStart) {
|
|
179
|
+
finalStart = spanStart
|
|
180
|
+
finalEnd = end
|
|
181
|
+
} else if (start < spanStart && end < spanEnd) {
|
|
182
|
+
finalStart = start
|
|
183
|
+
finalEnd = spanEnd
|
|
184
|
+
} else {
|
|
185
|
+
setSpanOnFinish = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!setSpanOnFinish && finalStart != null && finalEnd != null) {
|
|
189
|
+
setSpan(spannable, type, finalStart, finalEnd)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (setSpanOnFinish) {
|
|
194
|
+
setSpan(spannable, type, start, end)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private fun <T> isSpanEnabledInNextLine(
|
|
199
|
+
spannable: Spannable,
|
|
200
|
+
index: Int,
|
|
201
|
+
type: Class<T>,
|
|
202
|
+
): Boolean {
|
|
203
|
+
val selection = view.selection ?: return false
|
|
204
|
+
if (index + 1 >= spannable.length) return false
|
|
205
|
+
val (start, end) = selection.getParagraphSelection()
|
|
206
|
+
|
|
207
|
+
val spans = spannable.getSpans(start, end, type)
|
|
208
|
+
return spans.isNotEmpty()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private fun <T> mergeAdjacentStyleSpans(
|
|
212
|
+
s: Editable,
|
|
213
|
+
endCursorPosition: Int,
|
|
214
|
+
type: Class<T>,
|
|
215
|
+
) {
|
|
216
|
+
val (start, end) = s.getParagraphBounds(endCursorPosition)
|
|
217
|
+
val currParagraphSpans = s.getSpans(start, end, type)
|
|
218
|
+
|
|
219
|
+
if (currParagraphSpans.isEmpty()) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
val currSpan = currParagraphSpans[0]
|
|
224
|
+
val nextSpan = getNextParagraphSpan(s, end, type) ?: return
|
|
225
|
+
|
|
226
|
+
val newStart = s.getSpanStart(currSpan)
|
|
227
|
+
val newEnd = s.getSpanEnd(nextSpan)
|
|
228
|
+
|
|
229
|
+
s.removeSpan(nextSpan)
|
|
230
|
+
s.removeSpan(currSpan)
|
|
231
|
+
|
|
232
|
+
val (safeStart, safeEnd) = s.getSafeSpanBoundaries(newStart, newEnd)
|
|
233
|
+
val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
|
|
234
|
+
|
|
235
|
+
s.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private fun handleConflictsDuringNewlineDeletion(
|
|
239
|
+
s: Editable,
|
|
240
|
+
style: String,
|
|
241
|
+
paragraphStart: Int,
|
|
242
|
+
paragraphEnd: Int,
|
|
243
|
+
): Boolean {
|
|
244
|
+
val spanState = view.spanState ?: return false
|
|
245
|
+
val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return false
|
|
246
|
+
var isConflicting = false
|
|
247
|
+
val stylesToCheck = mergingConfig.blockingStyles + mergingConfig.conflictingStyles
|
|
248
|
+
|
|
249
|
+
for (styleToCheck in stylesToCheck) {
|
|
250
|
+
val conflictingType = EnrichedSpans.allSpans[styleToCheck]?.clazz ?: continue
|
|
251
|
+
|
|
252
|
+
val spans = s.getSpans(paragraphStart, paragraphEnd, conflictingType)
|
|
253
|
+
if (spans.isEmpty()) {
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
isConflicting = true
|
|
257
|
+
|
|
258
|
+
val isParagraphStyle = EnrichedSpans.paragraphSpans[styleToCheck] != null
|
|
259
|
+
if (!isParagraphStyle) {
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (span in spans) {
|
|
264
|
+
extendStyleOnWholeParagraph(s, span as EnrichedInputSpan, conflictingType, paragraphEnd)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (isConflicting) {
|
|
269
|
+
val styleStart = spanState.getStart(style) ?: return false
|
|
270
|
+
spanState.setStart(style, null)
|
|
271
|
+
removeStyle(style, styleStart, paragraphEnd)
|
|
272
|
+
return true
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private fun deleteConflictingAndBlockingStyles(
|
|
279
|
+
s: Editable,
|
|
280
|
+
style: String,
|
|
281
|
+
paragraphStart: Int,
|
|
282
|
+
paragraphEnd: Int,
|
|
283
|
+
) {
|
|
284
|
+
val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return
|
|
285
|
+
val stylesToCheck = mergingConfig.blockingStyles + mergingConfig.conflictingStyles
|
|
286
|
+
|
|
287
|
+
for (styleToCheck in stylesToCheck) {
|
|
288
|
+
val conflictingType = EnrichedSpans.allSpans[styleToCheck]?.clazz ?: continue
|
|
289
|
+
|
|
290
|
+
val spans = s.getSpans(paragraphStart, paragraphEnd, conflictingType)
|
|
291
|
+
for (span in spans) {
|
|
292
|
+
s.removeSpan(span)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private fun <T> extendStyleOnWholeParagraph(
|
|
298
|
+
s: Editable,
|
|
299
|
+
span: EnrichedInputSpan,
|
|
300
|
+
type: Class<T>,
|
|
301
|
+
paragraphEnd: Int,
|
|
302
|
+
) {
|
|
303
|
+
val currStyleStart = s.getSpanStart(span)
|
|
304
|
+
s.removeSpan(span)
|
|
305
|
+
val (safeStart, safeEnd) = s.getSafeSpanBoundaries(currStyleStart, paragraphEnd)
|
|
306
|
+
setSpan(s, type, safeStart, safeEnd)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fun afterTextChanged(
|
|
310
|
+
s: Editable,
|
|
311
|
+
endPosition: Int,
|
|
312
|
+
previousTextLength: Int,
|
|
313
|
+
) {
|
|
314
|
+
var endCursorPosition = endPosition
|
|
315
|
+
val isBackspace = s.length < previousTextLength
|
|
316
|
+
val isNewLine = endCursorPosition == 0 || (endCursorPosition > 0 && s[endCursorPosition - 1] == '\n')
|
|
317
|
+
val spanState = view.spanState ?: return
|
|
318
|
+
|
|
319
|
+
for ((style, config) in EnrichedSpans.paragraphSpans) {
|
|
320
|
+
val styleStart = spanState.getStart(style)
|
|
321
|
+
|
|
322
|
+
if (styleStart == null) {
|
|
323
|
+
if (isBackspace) {
|
|
324
|
+
val (start, end) = s.getParagraphBounds(endCursorPosition)
|
|
325
|
+
val spans = s.getSpans(start, end, config.clazz)
|
|
326
|
+
|
|
327
|
+
for (span in spans) {
|
|
328
|
+
// handle conflicts when entering paragraph with some paragraph style applied
|
|
329
|
+
deleteConflictingAndBlockingStyles(s, style, start, end)
|
|
330
|
+
extendStyleOnWholeParagraph(s, span as EnrichedInputSpan, config.clazz, end)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (config.isContinuous) {
|
|
335
|
+
mergeAdjacentStyleSpans(s, endCursorPosition, config.clazz)
|
|
336
|
+
}
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (isNewLine) {
|
|
341
|
+
if (!config.isContinuous) {
|
|
342
|
+
spanState.setStart(style, null)
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// If removing text at the beginning of the line, we want to remove the span for the whole paragraph
|
|
347
|
+
if (isBackspace) {
|
|
348
|
+
val currentParagraphBounds = s.getParagraphBounds(endCursorPosition)
|
|
349
|
+
removeSpansForRange(s, currentParagraphBounds.first, currentParagraphBounds.second, config.clazz)
|
|
350
|
+
spanState.setStart(style, null)
|
|
351
|
+
continue
|
|
352
|
+
} else {
|
|
353
|
+
s.insert(endCursorPosition, EnrichedConstants.ZWS_STRING)
|
|
354
|
+
endCursorPosition += 1
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
var (start, end) = s.getParagraphBounds(styleStart, endCursorPosition)
|
|
359
|
+
|
|
360
|
+
// handle conflicts when deleting newline from paragraph style (going back to previous line)
|
|
361
|
+
if (isBackspace && styleStart != start) {
|
|
362
|
+
val isConflicting = handleConflictsDuringNewlineDeletion(s, style, start, end)
|
|
363
|
+
if (isConflicting) {
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
val isNotEndLineSpan = isSpanEnabledInNextLine(s, end, config.clazz)
|
|
369
|
+
val spans = s.getSpans(start, end, config.clazz)
|
|
370
|
+
|
|
371
|
+
for (span in spans) {
|
|
372
|
+
if (isNotEndLineSpan) {
|
|
373
|
+
start = s.getSpanStart(span).coerceAtMost(start)
|
|
374
|
+
end = s.getSpanEnd(span).coerceAtLeast(end)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
s.removeSpan(span)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
setSpan(s, config.clazz, start, end)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fun toggleStyle(name: String) {
|
|
385
|
+
if (view.selection == null) return
|
|
386
|
+
val spannable = view.text as SpannableStringBuilder
|
|
387
|
+
val (start, end) = view.selection.getParagraphSelection()
|
|
388
|
+
val config = EnrichedSpans.paragraphSpans[name] ?: return
|
|
389
|
+
val type = config.clazz
|
|
390
|
+
|
|
391
|
+
val styleStart = view.spanState?.getStart(name)
|
|
392
|
+
|
|
393
|
+
if (styleStart != null) {
|
|
394
|
+
view.spanState.setStart(name, null)
|
|
395
|
+
removeSpansForRange(spannable, start, end, type)
|
|
396
|
+
view.selection.validateStyles()
|
|
397
|
+
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (start == end) {
|
|
402
|
+
spannable.insert(start, EnrichedConstants.ZWS_STRING)
|
|
403
|
+
setAndMergeSpans(spannable, type, start, end + 1)
|
|
404
|
+
view.selection.validateStyles()
|
|
405
|
+
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
var currentStart = start
|
|
410
|
+
var currentEnd = currentStart
|
|
411
|
+
val paragraphs = spannable.substring(start, end).split("\n")
|
|
412
|
+
|
|
413
|
+
for (paragraph in paragraphs) {
|
|
414
|
+
spannable.insert(currentStart, EnrichedConstants.ZWS_STRING)
|
|
415
|
+
currentEnd = currentStart + paragraph.length + 1
|
|
416
|
+
currentStart = currentEnd + 1
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
setAndMergeSpans(spannable, type, start, currentEnd)
|
|
420
|
+
view.selection.validateStyles()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fun getStyleRange(): Pair<Int, Int> = view.selection?.getParagraphSelection() ?: Pair(0, 0)
|
|
424
|
+
|
|
425
|
+
fun removeStyle(
|
|
426
|
+
name: String,
|
|
427
|
+
start: Int,
|
|
428
|
+
end: Int,
|
|
429
|
+
): Boolean {
|
|
430
|
+
val config = EnrichedSpans.paragraphSpans[name] ?: return false
|
|
431
|
+
val spannable = view.text as Spannable
|
|
432
|
+
return removeSpansForRange(spannable, start, end, config.clazz)
|
|
433
|
+
}
|
|
434
|
+
}
|