react-native-enriched-markdown-scaffold 0.7.4-scaffold.8 → 1.0.2-scaffold.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/README.md +54 -8
- package/ReactNativeEnrichedMarkdown.podspec +82 -73
- package/android/build.gradle +191 -1
- package/android/consumer-rules.pro +1 -1
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerDelegate.java +27 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextInputManagerInterface.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +9 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +40 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +839 -23
- package/android/src/main/baseline-prof.txt +1 -1
- package/android/src/main/cpp/jni-adapter.cpp +74 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +175 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +50 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +48 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +10 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +82 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyPressEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt +62 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +911 -460
- package/android/src/main/java/com/swmansion/enriched/markdown/input/MarkdownClipboard.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/BlockEditCoordinator.kt +196 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/ClipboardCoordinator.kt +73 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditContext.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditPipeline.kt +248 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/EditSession.kt +52 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +101 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/LinkCoordinator.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MarkdownTextWatcher.kt +3 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/MentionCoordinator.kt +118 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnChangeStateEvent.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnContextMenuItemPressEvent.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnInputKeyPressEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnPasteEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockStore.kt +254 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/BlockSyntaxSentinel.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/FormattingStore.kt +80 -87
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputFormatter.kt +109 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/InputParser.kt +180 -27
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/MarkdownSerializer.kt +167 -38
- package/android/src/main/java/com/swmansion/enriched/markdown/input/formatting/RangeEditAdjustment.kt +149 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt +66 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputMeasurementStore.kt +7 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockRange.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/BlockType.kt +48 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/FormattingRange.kt +9 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/InputFormatterStyle.kt +39 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathAtom.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MathChipStyle.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/input/model/MutableRangeBounds.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputBulletSpan.kt +82 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputHeadingSpan.kt +58 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputListItemSpacingSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/spans/InputOrderedListMarkerSpan.kt +63 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/BlockHandler.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/HeadingBlockHandler.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/OrderedListBlockHandler.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/styles/UnorderedListBlockHandler.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/toolbar/InputContextMenu.kt +8 -26
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +9 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +100 -12
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +16 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +74 -18
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +43 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +9 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SoftBreakRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +30 -27
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +174 -24
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +27 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +13 -16
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +182 -46
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListItemSpacingSpan.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ListMarkerAnchorSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +7 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +4 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +13 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TableStyle.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockHighlighter.kt +90 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockNode.kt +57 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/FeatureFlags.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownSegment.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +33 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/ReadableMapExtensions.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/RenderedSegment.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/SegmentSignature.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/StreamingMarkdownFilter.kt +83 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/serialization/MarkdownASTSerializer.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/MarkdownStyleParser.kt +74 -44
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +20 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageCache.kt +26 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/ImageDownloader.kt +22 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/LocalImageLoader.kt +161 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/extensions/TextPaintExtensions.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/CheckboxTouchHelper.kt +7 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/interaction/TaskListTapUtils.kt +41 -21
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/BlockSpacing.kt +15 -8
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +58 -27
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +19 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +388 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/ContextMenuPopup.kt +9 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CopyGlyph.kt +101 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +32 -5
- package/android/src/main/jni/CMakeLists.txt +53 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.cpp +10 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownContainerShadowNode.h +11 -2
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +10 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +12 -2
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +9 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +24 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +16 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/input/math/MathChipControllerImpl.kt +21 -4
- package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +7 -2
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathChipSpan.kt +1 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +2 -0
- package/cpp/highlight/CodeBlockHighlighter.cpp +284 -0
- package/cpp/highlight/CodeBlockHighlighter.hpp +66 -0
- package/cpp/highlight/CodeBlockLanguages.cpp +138 -0
- package/cpp/highlight/CodeBlockLanguages.hpp +25 -0
- package/cpp/highlight/HighlightGrammars.hpp +28 -0
- package/cpp/highlight/code_highlight_podspec.rb +211 -0
- package/cpp/highlight/gen-registry.mjs +155 -0
- package/cpp/highlight/grammar-versions.json +159 -0
- package/cpp/highlight/vendor/generated/generated_queries.h +1665 -0
- package/cpp/highlight/vendor/generated/generated_registry.cpp +59 -0
- package/cpp/parser/MD4CParser.cpp +173 -61
- package/cpp/parser/MD4CParser.hpp +1 -0
- package/cpp/parser/MarkdownASTNode.hpp +2 -1
- package/cpp/wasm/ASTSerializer.cpp +2 -0
- package/cpp/wasm/build.sh +1 -0
- package/cpp/wasm/md4c_wasm.cpp +3 -1
- package/docs/ACCESSIBILITY.md +14 -2
- package/docs/API_REFERENCE.md +231 -8
- package/docs/BREAKING_CHANGES.md +129 -0
- package/docs/CODE_HIGHLIGHT.md +133 -0
- package/docs/COPY_OPTIONS.md +21 -3
- package/docs/ELEMENTS_STRUCTURE.md +52 -4
- package/docs/IMAGE_CACHING.md +30 -0
- package/docs/INPUT.md +146 -5
- package/docs/LATEX_MATH.md +87 -45
- package/docs/MACOS.md +1 -1
- package/docs/MARKDOWN_STREAMING.md +27 -1
- package/docs/NATIVE_ASSETS.md +127 -0
- package/docs/RTL.md +1 -1
- package/docs/STYLES.md +46 -6
- package/docs/TESTING.md +112 -0
- package/docs/TEXT.md +16 -6
- package/docs/WEB.md +3 -1
- package/ios/EnrichedMarkdown.h +7 -0
- package/ios/EnrichedMarkdown.mm +262 -67
- package/ios/EnrichedMarkdownText.h +7 -0
- package/ios/EnrichedMarkdownText.mm +97 -68
- package/ios/attachments/ENRMImageAttachment.h +9 -0
- package/ios/attachments/ENRMImageAttachment.m +143 -40
- package/ios/attachments/ENRMMathInlineAttachment.h +2 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +48 -81
- package/ios/attachments/ENRMMathInlineAttachmentShared.h +2 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +75 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +63 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +40 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +839 -23
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +131 -0
- package/ios/input/ENRMAutoLinkDetector.h +2 -1
- package/ios/input/ENRMAutoLinkDetector.mm +79 -41
- package/ios/input/ENRMBlockEditCoordinator.h +71 -0
- package/ios/input/ENRMBlockEditCoordinator.mm +195 -0
- package/ios/input/ENRMBlockRange.h +28 -0
- package/ios/input/ENRMBlockRange.mm +30 -0
- package/ios/input/ENRMBlockStore.h +56 -0
- package/ios/input/ENRMBlockStore.mm +275 -0
- package/ios/input/ENRMBlockSyntaxSentinel.h +25 -0
- package/ios/input/ENRMBlockSyntaxSentinel.mm +80 -0
- package/ios/input/ENRMClipboardCoordinator.h +45 -0
- package/ios/input/ENRMClipboardCoordinator.mm +120 -0
- package/ios/input/ENRMDetectorPipeline.h +2 -1
- package/ios/input/ENRMEditPipeline.h +76 -0
- package/ios/input/ENRMEditPipeline.mm +228 -0
- package/ios/input/ENRMEditSession.h +36 -0
- package/ios/input/ENRMEditSession.mm +63 -0
- package/ios/input/ENRMFormattingStore.h +17 -0
- package/ios/input/ENRMFormattingStore.mm +95 -105
- package/ios/input/ENRMInputBlockType.h +91 -0
- package/ios/input/ENRMInputBlockType.mm +5 -0
- package/ios/input/ENRMInputDecorationDrawer.h +18 -0
- package/ios/input/ENRMInputEventEmitter.h +56 -0
- package/ios/input/ENRMInputEventEmitter.mm +308 -0
- package/ios/input/ENRMInputFormatter.h +29 -69
- package/ios/input/ENRMInputFormatter.mm +216 -124
- package/ios/input/ENRMInputFormatterStyle.h +99 -0
- package/ios/input/ENRMInputFormatterStyle.mm +188 -0
- package/ios/input/ENRMInputLayoutManager.h +9 -0
- package/ios/input/ENRMInputLayoutManager.mm +30 -0
- package/ios/input/ENRMInputListMarkerDrawer.h +32 -0
- package/ios/input/ENRMInputListMarkerDrawer.mm +426 -0
- package/ios/input/ENRMInputParser.h +10 -6
- package/ios/input/ENRMInputParser.mm +527 -171
- package/ios/input/ENRMInputRemend.mm +2 -2
- package/ios/input/ENRMInputStyleStateBuilder.h +40 -0
- package/ios/input/ENRMInputStyleStateBuilder.mm +60 -0
- package/ios/input/ENRMInputTextView.mm +103 -8
- package/ios/input/ENRMInputTypingAttributesController.h +42 -0
- package/ios/input/ENRMInputTypingAttributesController.mm +203 -0
- package/ios/input/ENRMLinkCoordinator.h +41 -0
- package/ios/input/ENRMLinkCoordinator.mm +89 -0
- package/ios/input/ENRMMarkdownSerializer.h +29 -5
- package/ios/input/ENRMMarkdownSerializer.mm +170 -48
- package/ios/input/ENRMMathAtom.h +26 -0
- package/ios/input/ENRMMathAtom.mm +12 -0
- package/ios/input/ENRMMentionCoordinator.h +45 -0
- package/ios/input/ENRMMentionCoordinator.mm +167 -0
- package/ios/input/ENRMRangeEditAdjustment.h +38 -0
- package/ios/input/ENRMRangeEditAdjustment.mm +91 -0
- package/ios/input/ENRMRangeStoreUtils.h +39 -0
- package/ios/input/EnrichedMarkdownTextInput+ContextMenu.mm +6 -6
- package/ios/input/EnrichedMarkdownTextInput+Internal.h +11 -3
- package/ios/input/EnrichedMarkdownTextInput.h +7 -1
- package/ios/input/EnrichedMarkdownTextInput.mm +1104 -1024
- package/ios/input/InputStylePropsUtils.h +109 -68
- package/ios/input/internals/EnrichedMarkdownTextInputShadowNode.mm +2 -1
- package/ios/input/styles/ENRMBlockHandler.h +46 -0
- package/ios/input/styles/ENRMHeadingBlockHandler.h +15 -0
- package/ios/input/styles/ENRMHeadingBlockHandler.mm +38 -0
- package/ios/input/styles/ENRMOrderedListBlockHandler.h +14 -0
- package/ios/input/styles/ENRMOrderedListBlockHandler.mm +48 -0
- package/ios/input/styles/ENRMUnorderedListBlockHandler.h +15 -0
- package/ios/input/styles/ENRMUnorderedListBlockHandler.mm +51 -0
- package/ios/internals/ENRMViewFreeMeasurement.h +331 -0
- package/ios/internals/EnrichedMarkdownShadowNode.h +1 -3
- package/ios/internals/EnrichedMarkdownShadowNode.mm +16 -20
- package/ios/internals/EnrichedMarkdownTextShadowNode.h +1 -3
- package/ios/internals/EnrichedMarkdownTextShadowNode.mm +15 -20
- package/ios/internals/MeasurementCache.h +33 -7
- package/ios/internals/ShadowMeasurementUtils.h +63 -58
- package/ios/math/ENRMMathChipController.h +74 -0
- package/ios/math/ENRMMathChipController.mm +476 -0
- package/ios/math/ENRMRaTeXBridge.swift +4 -1
- package/ios/parser/ENRMMarkdownParser.h +1 -8
- package/ios/parser/ENRMMarkdownParser.mm +2 -11
- package/ios/parser/MarkdownASTNode.h +2 -1
- package/ios/parser/MarkdownParserBridge.mm +4 -0
- package/ios/renderer/AttributedRenderer.m +20 -2
- package/ios/renderer/BaseRenderer.h +17 -2
- package/ios/renderer/BaseRenderer.m +30 -0
- package/ios/renderer/BlockquoteRenderer.h +1 -1
- package/ios/renderer/BlockquoteRenderer.m +144 -35
- package/ios/renderer/CodeBlockRenderer.h +1 -1
- package/ios/renderer/CodeBlockRenderer.m +27 -23
- package/ios/renderer/CodeRenderer.h +1 -1
- package/ios/renderer/CodeRenderer.m +3 -1
- package/ios/renderer/ENRMImageRenderer.h +1 -1
- package/ios/renderer/ENRMImageRenderer.m +5 -1
- package/ios/renderer/ENRMMathInlineRenderer.h +1 -1
- package/ios/renderer/ENRMMathInlineRenderer.m +5 -1
- package/ios/renderer/ENRMSpoilerRenderer.h +1 -1
- package/ios/renderer/ENRMSpoilerRenderer.m +3 -1
- package/ios/renderer/EmphasisRenderer.h +1 -1
- package/ios/renderer/EmphasisRenderer.m +3 -1
- package/ios/renderer/HeadingRenderer.h +1 -1
- package/ios/renderer/HeadingRenderer.m +13 -6
- package/ios/renderer/HighlightRenderer.h +1 -1
- package/ios/renderer/HighlightRenderer.m +3 -1
- package/ios/renderer/LinkRenderer.h +1 -1
- package/ios/renderer/LinkRenderer.m +3 -1
- package/ios/renderer/ListItemRenderer.h +13 -1
- package/ios/renderer/ListItemRenderer.m +102 -44
- package/ios/renderer/ListRenderer.h +2 -2
- package/ios/renderer/ListRenderer.m +63 -25
- package/ios/renderer/ParagraphRenderer.h +1 -1
- package/ios/renderer/ParagraphRenderer.m +16 -8
- package/ios/renderer/RenderContext.h +10 -0
- package/ios/renderer/RenderContext.m +44 -0
- package/ios/renderer/RendererFactory.m +14 -0
- package/ios/renderer/StrikethroughRenderer.h +1 -1
- package/ios/renderer/StrikethroughRenderer.m +3 -1
- package/ios/renderer/StrongRenderer.h +1 -1
- package/ios/renderer/StrongRenderer.m +3 -1
- package/ios/renderer/SubscriptRenderer.h +1 -1
- package/ios/renderer/SubscriptRenderer.m +3 -1
- package/ios/renderer/SuperscriptRenderer.h +1 -1
- package/ios/renderer/SuperscriptRenderer.m +3 -1
- package/ios/renderer/TextRenderer.h +2 -2
- package/ios/renderer/TextRenderer.m +3 -1
- package/ios/renderer/ThematicBreakRenderer.h +1 -1
- package/ios/renderer/ThematicBreakRenderer.m +3 -1
- package/ios/renderer/UnderlineRenderer.h +1 -1
- package/ios/renderer/UnderlineRenderer.m +3 -1
- package/ios/styles/StyleConfig.h +22 -0
- package/ios/styles/StyleConfig.mm +132 -0
- package/ios/utils/BlockquoteBorder.h +6 -0
- package/ios/utils/BlockquoteBorder.m +249 -52
- package/ios/utils/CodeBlockBackground.m +16 -2
- package/ios/utils/ENRMAtomicSize.h +65 -0
- package/ios/utils/ENRMCodeBlockContent.h +37 -0
- package/ios/utils/ENRMCodeBlockContent.mm +79 -0
- package/ios/utils/ENRMCodeBlockHighlighter.h +37 -0
- package/ios/utils/ENRMCodeBlockHighlighter.mm +51 -0
- package/ios/utils/ENRMFeatureFlags.h +7 -0
- package/ios/utils/ENRMImageDownloader.h +11 -1
- package/ios/utils/ENRMImageDownloader.m +60 -16
- package/ios/utils/ENRMLocalImageLoader.h +31 -0
- package/ios/utils/ENRMLocalImageLoader.m +68 -0
- package/ios/utils/ENRMMathFallback.h +26 -0
- package/ios/utils/ENRMMathFallback.m +18 -0
- package/ios/utils/ENRMTextViewSetup.h +35 -17
- package/ios/utils/EditMenuUtils.m +22 -1
- package/ios/utils/ImageRequestHeaderUtils.h +35 -0
- package/ios/utils/LastElementUtils.h +1 -0
- package/ios/utils/ListMarkerDrawer.m +29 -42
- package/ios/utils/MarkdownASTSerializer.m +4 -0
- package/ios/utils/ParagraphStyleUtils.h +5 -0
- package/ios/utils/ParagraphStyleUtils.m +77 -17
- package/ios/utils/RenderedMarkdownSegment.h +13 -1
- package/ios/utils/RenderedMarkdownSegment.m +20 -0
- package/ios/utils/SegmentRenderer.m +17 -4
- package/ios/utils/StreamingMarkdownFilter.h +11 -1
- package/ios/utils/StreamingMarkdownFilter.m +98 -2
- package/ios/utils/StylePropsUtils.h +63 -0
- package/ios/utils/TaskListTapUtils.m +24 -0
- package/ios/views/ENRMCodeBlockContainerView.h +46 -0
- package/ios/views/ENRMCodeBlockContainerView.m +566 -0
- package/ios/views/ENRMMathContainerView.h +12 -0
- package/ios/views/ENRMMathContainerView.m +71 -1
- package/ios/views/TableContainerView.h +18 -0
- package/ios/views/TableContainerView.m +259 -159
- package/jest.d.ts +1 -0
- package/jest.js +5 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +65 -0
- package/lib/module/EnrichedMarkdownTextInput.js +86 -5
- package/lib/module/EnrichedMarkdownTextInput.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextInputNativeComponent.ts +89 -5
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +66 -0
- package/lib/module/headingDefaults.js +34 -0
- package/lib/module/headingDefaults.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +191 -0
- package/lib/module/jest/index.js.map +1 -0
- package/lib/module/native/EnrichedMarkdownText.js +30 -4
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +81 -19
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +56 -18
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/normalizeMarkdownTextInputStyle.js +62 -15
- package/lib/module/normalizeMarkdownTextInputStyle.js.map +1 -1
- package/lib/module/normalizeMenuItem.js +1 -1
- package/lib/module/utils/textInputState.js +26 -0
- package/lib/module/utils/textInputState.js.map +1 -0
- package/lib/module/web/EnrichedMarkdownText.js +9 -5
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/parseMarkdown.js +4 -3
- package/lib/module/web/parseMarkdown.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +6 -0
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/renderers/ListRenderers.js +7 -2
- package/lib/module/web/renderers/ListRenderers.js.map +1 -1
- package/lib/module/web/renderers/index.js +7 -4
- package/lib/module/web/renderers/index.js.map +1 -1
- package/lib/module/web/styles.js +83 -6
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +1 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/module/web/wasm/md4c.js +0 -0
- package/lib/module/web/wasm/md4c.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +62 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts +95 -9
- package/lib/typescript/src/EnrichedMarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts +80 -5
- package/lib/typescript/src/EnrichedMarkdownTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +62 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/headingDefaults.d.ts +9 -0
- package/lib/typescript/src/headingDefaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +6 -0
- package/lib/typescript/src/jest/index.d.ts.map +1 -0
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +3 -3
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts +19 -5
- package/lib/typescript/src/normalizeMarkdownTextInputStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +72 -0
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +27 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +65 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +19 -0
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +19 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/utils/textInputState.d.ts +24 -0
- package/lib/typescript/src/utils/textInputState.d.ts.map +1 -0
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/parseMarkdown.d.ts +1 -1
- package/lib/typescript/src/web/parseMarkdown.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/ListRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/index.d.ts +2 -1
- package/lib/typescript/src/web/renderers/index.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts +2 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +6 -2
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +36 -9
- package/postinstall.mjs +131 -0
- package/ratex-version.json +21 -0
- package/src/EnrichedMarkdownNativeComponent.ts +65 -0
- package/src/EnrichedMarkdownTextInput.tsx +162 -8
- package/src/EnrichedMarkdownTextInputNativeComponent.ts +89 -5
- package/src/EnrichedMarkdownTextNativeComponent.ts +66 -0
- package/src/headingDefaults.ts +21 -0
- package/src/index.tsx +9 -0
- package/src/index.web.tsx +1 -0
- package/src/jest/index.tsx +215 -0
- package/src/native/EnrichedMarkdownText.tsx +40 -2
- package/src/normalizeMarkdownStyle.ts +102 -16
- package/src/normalizeMarkdownStyle.web.ts +63 -15
- package/src/normalizeMarkdownTextInputStyle.ts +110 -35
- package/src/normalizeMenuItem.ts +1 -1
- package/src/types/MarkdownStyle.ts +74 -0
- package/src/types/MarkdownStyleInternal.ts +39 -0
- package/src/types/MarkdownTextProps.ts +65 -0
- package/src/types/MarkdownTextProps.web.ts +19 -0
- package/src/types/events.ts +21 -0
- package/src/utils/textInputState.ts +35 -0
- package/src/web/EnrichedMarkdownText.tsx +16 -2
- package/src/web/parseMarkdown.ts +6 -2
- package/src/web/renderers/InlineRenderers.tsx +5 -0
- package/src/web/renderers/ListRenderers.tsx +14 -2
- package/src/web/renderers/index.tsx +6 -2
- package/src/web/styles.ts +89 -5
- package/src/web/types.ts +6 -1
- package/src/web/utils.ts +1 -0
- package/src/web/wasm/md4c.js +0 -0
- package/vendor-grammars.mjs +511 -0
- package/vendor-ratex.mjs +173 -0
- package/app.plugin.js +0 -2
- package/plugin/build/withAndroidMath.js +0 -19
- package/plugin/build/withIosMath.js +0 -28
- package/plugin/build/withReactNativeEnrichedMarkdown.js +0 -11
- package/plugin/src/withAndroidMath.ts +0 -24
- package/plugin/src/withIosMath.ts +0 -35
- package/plugin/src/withReactNativeEnrichedMarkdown.ts +0 -17
- package/plugin/tsconfig.build.json +0 -16
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
#import "EnrichedMarkdownTextInput.h"
|
|
2
2
|
#import "ContextMenuUtils.h"
|
|
3
3
|
#import "ENRMAutoLinkDetector.h"
|
|
4
|
+
#import "ENRMBlockEditCoordinator.h"
|
|
5
|
+
#import "ENRMBlockHandler.h"
|
|
6
|
+
#import "ENRMBlockStore.h"
|
|
7
|
+
#import "ENRMClipboardCoordinator.h"
|
|
4
8
|
#import "ENRMDetectorPipeline.h"
|
|
5
|
-
#import "
|
|
9
|
+
#import "ENRMEditPipeline.h"
|
|
10
|
+
#import "ENRMEditSession.h"
|
|
6
11
|
#import "ENRMFormattingRange.h"
|
|
7
12
|
#import "ENRMFormattingStore.h"
|
|
13
|
+
#import "ENRMInputEventEmitter.h"
|
|
8
14
|
#import "ENRMInputFormatter.h"
|
|
9
15
|
#import "ENRMInputLayoutManager.h"
|
|
10
16
|
#import "ENRMInputLinkPrompt.h"
|
|
11
|
-
#import "
|
|
17
|
+
#import "ENRMInputListMarkerDrawer.h"
|
|
12
18
|
#import "ENRMInputParser.h"
|
|
13
19
|
#import "ENRMInputTextView.h"
|
|
20
|
+
#import "ENRMInputTypingAttributesController.h"
|
|
21
|
+
#import "ENRMLinkCoordinator.h"
|
|
14
22
|
#import "ENRMLinkRegexConfig.h"
|
|
15
|
-
#import "
|
|
16
|
-
#import "
|
|
17
|
-
#if ENRICHED_MARKDOWN_MATH
|
|
18
|
-
#import "ENRMMathInlineAttachment.h"
|
|
19
|
-
#endif
|
|
23
|
+
#import "ENRMMathChipController.h"
|
|
24
|
+
#import "ENRMMentionCoordinator.h"
|
|
20
25
|
#import "ENRMStyleHandler.h"
|
|
21
26
|
#import "ENRMStyleMergingConfig.h"
|
|
22
27
|
#import "ENRMUIKit.h"
|
|
23
|
-
#import "ENRMWordsUtils.h"
|
|
24
28
|
#import "EnrichedMarkdownTextInput+Internal.h"
|
|
25
29
|
#import "InputStylePropsUtils.h"
|
|
26
30
|
#import "ParagraphStyleUtils.h"
|
|
@@ -45,17 +49,25 @@
|
|
|
45
49
|
using namespace facebook::react;
|
|
46
50
|
|
|
47
51
|
#if !TARGET_OS_OSX
|
|
52
|
+
// UIGestureRecognizerDelegate is for the math chip tap: it has to run alongside
|
|
53
|
+
// UITextView's own selection gestures, never instead of them.
|
|
48
54
|
@interface EnrichedMarkdownTextInput () <RCTEnrichedMarkdownTextInputViewProtocol, UITextViewDelegate,
|
|
49
|
-
|
|
55
|
+
ENRMEditPipelineHost, ENRMInputEventEmitterDataSource,
|
|
56
|
+
ENRMInputTypingAttributesDataSource, UIGestureRecognizerDelegate>
|
|
50
57
|
#else
|
|
51
|
-
@interface EnrichedMarkdownTextInput () <RCTEnrichedMarkdownTextInputViewProtocol, RCTBackedTextInputDelegate
|
|
58
|
+
@interface EnrichedMarkdownTextInput () <RCTEnrichedMarkdownTextInputViewProtocol, RCTBackedTextInputDelegate,
|
|
59
|
+
ENRMEditPipelineHost, ENRMInputEventEmitterDataSource,
|
|
60
|
+
ENRMInputTypingAttributesDataSource>
|
|
52
61
|
#endif
|
|
53
62
|
- (void)setupTextView;
|
|
54
63
|
- (void)applyFormatting;
|
|
64
|
+
- (void)applyFormattingScopedToEditAtLocation:(NSUInteger)editLocation insertedLength:(NSUInteger)insertedLength;
|
|
55
65
|
- (void)toggleInlineStyle:(ENRMInputStyleType)styleType;
|
|
56
66
|
- (void)resetBaseTypingAttributes;
|
|
57
67
|
@end
|
|
58
68
|
|
|
69
|
+
static const NSTimeInterval kENRMAtomicSnapPollInterval = 0.1;
|
|
70
|
+
|
|
59
71
|
@implementation EnrichedMarkdownTextInput {
|
|
60
72
|
ENRMPlatformTextView *_textView;
|
|
61
73
|
ENRMInputLayoutManager *_layoutManager;
|
|
@@ -64,24 +76,30 @@ using namespace facebook::react;
|
|
|
64
76
|
ENRMInputFormatter *_formatter;
|
|
65
77
|
ENRMInputFormatterStyle *_formatterStyle;
|
|
66
78
|
ENRMFormattingStore *_formattingStore;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
BOOL
|
|
72
|
-
|
|
79
|
+
ENRMBlockStore *_blockStore;
|
|
80
|
+
ENRMInputTypingAttributesController *_typingController;
|
|
81
|
+
ENRMInputEventEmitter *_inputEventEmitter;
|
|
82
|
+
ENRMEditSession *_editSession;
|
|
83
|
+
BOOL _blockSyntax;
|
|
84
|
+
NSString *_lastImportedMarkdown;
|
|
73
85
|
|
|
74
86
|
ENRMPlaceholderLabel *_placeholderLabel;
|
|
75
87
|
|
|
76
88
|
NSUInteger _lastTextLength;
|
|
77
89
|
NSRange _lastSelectedRange;
|
|
78
90
|
NSRange _preEditSelectedRange;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
BOOL _atomicSnapScheduled;
|
|
92
|
+
|
|
93
|
+
// Block type/level of the line being edited, captured before a text change so
|
|
94
|
+
// a Return that continues a list (or an autocorrect/paste that replaces the
|
|
95
|
+
// line) can restore the right block on the resulting line(s).
|
|
96
|
+
ENRMInputBlockType _preEditBlockType;
|
|
97
|
+
NSInteger _preEditBlockLevel;
|
|
98
|
+
BOOL _preEditParagraphWasEmpty;
|
|
99
|
+
// Whether the replaced range contained a line break, captured before the edit
|
|
100
|
+
// applies (the deleted characters are gone afterwards). Drives the full-vs-
|
|
101
|
+
// scoped reformat decision in handleTextChanged.
|
|
102
|
+
BOOL _preEditReplacedNewline;
|
|
85
103
|
|
|
86
104
|
#if TARGET_OS_OSX
|
|
87
105
|
NSScrollView *_scrollView;
|
|
@@ -89,25 +107,24 @@ using namespace facebook::react;
|
|
|
89
107
|
|
|
90
108
|
NSArray<NSString *> *_contextMenuItemTexts;
|
|
91
109
|
NSArray<NSString *> *_contextMenuItemIcons;
|
|
92
|
-
NSArray<NSString *> *_mentionIndicators;
|
|
93
|
-
NSString *_activeMentionIndicator;
|
|
94
|
-
NSRange _activeMentionRange;
|
|
95
|
-
NSString *_activeMentionText;
|
|
96
|
-
|
|
97
110
|
ENRMAutoLinkDetector *_autoLinkDetector;
|
|
98
111
|
ENRMDetectorPipeline *_detectorPipeline;
|
|
112
|
+
ENRMEditPipeline *_editPipeline;
|
|
113
|
+
ENRMBlockEditCoordinator *_blockCoordinator;
|
|
114
|
+
ENRMMentionCoordinator *_mentionCoordinator;
|
|
115
|
+
ENRMLinkCoordinator *_linkCoordinator;
|
|
116
|
+
ENRMClipboardCoordinator *_clipboardCoordinator;
|
|
117
|
+
ENRMMathChipController *_mathChipController;
|
|
118
|
+
|
|
119
|
+
// Buffer position of the math chip currently drawn selected, or NSNotFound.
|
|
120
|
+
NSUInteger _activeMathChipPosition;
|
|
121
|
+
// A caret move made by the chip tap itself must not clear the highlight the tap
|
|
122
|
+
// just set; consumed by the next selection change.
|
|
123
|
+
BOOL _suppressMathChipClearOnce;
|
|
99
124
|
|
|
100
125
|
ENRMWritingDirectionMode _writingDirectionMode;
|
|
101
126
|
NSWritingDirection _resolvedLayoutDirection;
|
|
102
127
|
|
|
103
|
-
/// One-shot guard: when YES, the next textViewDidChangeSelection skips
|
|
104
|
-
/// clearing math chip selections. Set by handleTapForMathChip right before
|
|
105
|
-
/// it moves the caret to the chip, so the caret update doesn't immediately
|
|
106
|
-
/// wipe the chip we just selected.
|
|
107
|
-
BOOL _suppressMathChipClearOnce;
|
|
108
|
-
/// Character index of the currently selected math chip, or NSNotFound.
|
|
109
|
-
NSUInteger _activeMathChipIndex;
|
|
110
|
-
|
|
111
128
|
ENRMInputSelectionMenuConfig _inputSelectionMenuConfig;
|
|
112
129
|
ENRMFormatMenuConfig _formatMenuConfig;
|
|
113
130
|
|
|
@@ -123,6 +140,8 @@ using namespace facebook::react;
|
|
|
123
140
|
NSString *_formatMenuLinkLabel;
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
@synthesize editSession = _editSession;
|
|
144
|
+
|
|
126
145
|
#pragma mark - Fabric lifecycle
|
|
127
146
|
|
|
128
147
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
@@ -142,18 +161,16 @@ using namespace facebook::react;
|
|
|
142
161
|
_props = defaultProps;
|
|
143
162
|
|
|
144
163
|
self.backgroundColor = [RCTUIColor clearColor];
|
|
145
|
-
|
|
164
|
+
_blockSyntax = YES;
|
|
165
|
+
_lastImportedMarkdown = nil;
|
|
146
166
|
_heightUpdateCounter = 0;
|
|
147
167
|
_formatter = [[ENRMInputFormatter alloc] init];
|
|
148
168
|
_formatterStyle = [[ENRMInputFormatterStyle alloc] init];
|
|
149
169
|
_formattingStore = [[ENRMFormattingStore alloc] init];
|
|
150
|
-
|
|
151
|
-
_pendingStyleRemovals = [NSMutableSet set];
|
|
170
|
+
_blockStore = [[ENRMBlockStore alloc] init];
|
|
152
171
|
_lastTextLength = 0;
|
|
153
172
|
_lastSelectedRange = NSMakeRange(0, 0);
|
|
154
|
-
|
|
155
|
-
_activeMentionRange = NSMakeRange(NSNotFound, 0);
|
|
156
|
-
_activeMentionText = @"";
|
|
173
|
+
_mentionCoordinator = [[ENRMMentionCoordinator alloc] initWithFormattingStore:_formattingStore];
|
|
157
174
|
|
|
158
175
|
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
159
176
|
_resolvedLayoutDirection =
|
|
@@ -162,12 +179,32 @@ using namespace facebook::react;
|
|
|
162
179
|
_formatMenuConfig = (ENRMFormatMenuConfig){
|
|
163
180
|
.bold = YES, .italic = YES, .underline = YES, .strikethrough = YES, .spoiler = YES, .link = YES};
|
|
164
181
|
|
|
182
|
+
_activeMathChipPosition = NSNotFound;
|
|
165
183
|
_suppressMathChipClearOnce = NO;
|
|
166
|
-
_activeMathChipIndex = NSNotFound;
|
|
167
184
|
|
|
168
185
|
[self setupTextView];
|
|
186
|
+
_mathChipController = [[ENRMMathChipController alloc] initWithTextView:_textView style:_formatterStyle];
|
|
187
|
+
_editSession = [[ENRMEditSession alloc] initWithTextView:_textView];
|
|
188
|
+
_typingController = [[ENRMInputTypingAttributesController alloc] initWithTextView:_textView
|
|
189
|
+
formatterStyle:_formatterStyle
|
|
190
|
+
dataSource:self
|
|
191
|
+
editSession:_editSession];
|
|
192
|
+
_inputEventEmitter = [[ENRMInputEventEmitter alloc] initWithDataSource:self];
|
|
169
193
|
|
|
170
194
|
[self setupDetectorPipeline];
|
|
195
|
+
|
|
196
|
+
_editPipeline = [[ENRMEditPipeline alloc] initWithFormattingStore:_formattingStore
|
|
197
|
+
blockStore:_blockStore
|
|
198
|
+
formatter:_formatter
|
|
199
|
+
detectorPipeline:_detectorPipeline
|
|
200
|
+
host:self];
|
|
201
|
+
_blockCoordinator = [[ENRMBlockEditCoordinator alloc] initWithBlockStore:_blockStore];
|
|
202
|
+
_linkCoordinator = [[ENRMLinkCoordinator alloc] initWithFormattingStore:_formattingStore
|
|
203
|
+
autoLinkDetector:_autoLinkDetector];
|
|
204
|
+
_clipboardCoordinator = [[ENRMClipboardCoordinator alloc] initWithFormattingStore:_formattingStore
|
|
205
|
+
blockStore:_blockStore
|
|
206
|
+
transientRangeProvider:_detectorPipeline
|
|
207
|
+
formatter:_formatter];
|
|
171
208
|
}
|
|
172
209
|
return self;
|
|
173
210
|
}
|
|
@@ -180,7 +217,11 @@ using namespace facebook::react;
|
|
|
180
217
|
|
|
181
218
|
__weak EnrichedMarkdownTextInput *weakSelf = self;
|
|
182
219
|
_autoLinkDetector.onLinkDetected = ^(NSString *text, NSString *url, NSRange range) {
|
|
183
|
-
|
|
220
|
+
EnrichedMarkdownTextInput *strongSelf = weakSelf;
|
|
221
|
+
if (strongSelf == nil) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
[strongSelf->_inputEventEmitter emitOnLinkDetectedWithText:text url:url range:range];
|
|
184
225
|
};
|
|
185
226
|
|
|
186
227
|
_detectorPipeline = [[ENRMDetectorPipeline alloc] init];
|
|
@@ -201,6 +242,8 @@ using namespace facebook::react;
|
|
|
201
242
|
ENRMInputTextView *inputTextView = [[ENRMInputTextView alloc] initWithFrame:CGRectZero textContainer:textContainer];
|
|
202
243
|
#else
|
|
203
244
|
ENRMInputTextView *inputTextView = [[ENRMInputTextView alloc] initWithFrame:CGRectZero];
|
|
245
|
+
_layoutManager = [[ENRMInputLayoutManager alloc] init];
|
|
246
|
+
[inputTextView.textContainer replaceLayoutManager:_layoutManager];
|
|
204
247
|
#endif
|
|
205
248
|
inputTextView.markdownTextInput = self;
|
|
206
249
|
_textView = inputTextView;
|
|
@@ -237,124 +280,89 @@ using namespace facebook::react;
|
|
|
237
280
|
_placeholderLabel = ENRMCreatePlaceholderLabel(_textView, _formatterStyle.baseFont);
|
|
238
281
|
#if !TARGET_OS_OSX
|
|
239
282
|
_placeholderLabel.adjustsFontForContentSizeCategory = YES;
|
|
283
|
+
_placeholderLabel.accessibilityElementsHidden = YES;
|
|
284
|
+
_placeholderLabel.isAccessibilityElement = NO;
|
|
240
285
|
#endif
|
|
241
286
|
|
|
242
287
|
[self resetBaseTypingAttributes];
|
|
243
288
|
|
|
244
|
-
#if
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
289
|
+
#if !TARGET_OS_OSX
|
|
290
|
+
if (ENRMMathChipController.isAvailable) {
|
|
291
|
+
UITapGestureRecognizer *mathTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
|
292
|
+
action:@selector(handleMathChipTap:)];
|
|
293
|
+
// The text view's own tap gestures must still run: swallowing the touch is
|
|
294
|
+
// what made the first tap on a chip fail to focus the field and raise the
|
|
295
|
+
// keyboard. This recognizer only adds behaviour, it never replaces any.
|
|
296
|
+
mathTap.cancelsTouchesInView = NO;
|
|
297
|
+
mathTap.delaysTouchesBegan = NO;
|
|
298
|
+
mathTap.delaysTouchesEnded = NO;
|
|
299
|
+
mathTap.delegate = self;
|
|
300
|
+
[_textView addGestureRecognizer:mathTap];
|
|
301
|
+
}
|
|
252
302
|
#endif
|
|
253
303
|
}
|
|
254
304
|
|
|
255
|
-
#if
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// `locationInView:_textView` reports). Single source of truth for both the tap
|
|
259
|
-
// hit-test and the rect reported to JS.
|
|
260
|
-
- (CGRect)mathChipRectInTextViewAtIndex:(NSUInteger)index layoutManager:(NSLayoutManager *)lm
|
|
305
|
+
#if !TARGET_OS_OSX
|
|
306
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
307
|
+
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
261
308
|
{
|
|
262
|
-
|
|
263
|
-
return CGRectNull;
|
|
264
|
-
NSRange glyphRange = [lm glyphRangeForCharacterRange:NSMakeRange(index, 1) actualCharacterRange:NULL];
|
|
265
|
-
CGRect rect = [lm boundingRectForGlyphRange:glyphRange inTextContainer:_textView.textContainer];
|
|
266
|
-
rect.origin.x += _textView.textContainerInset.left;
|
|
267
|
-
rect.origin.y += _textView.textContainerInset.top;
|
|
268
|
-
return rect;
|
|
309
|
+
return YES;
|
|
269
310
|
}
|
|
311
|
+
#endif
|
|
312
|
+
|
|
313
|
+
#if !TARGET_OS_OSX
|
|
314
|
+
#pragma mark - Accessibility
|
|
270
315
|
|
|
271
|
-
|
|
316
|
+
// The custom TextKit 1 stack leaves the inner UITextView invisible to VoiceOver, so
|
|
317
|
+
// the host vends the content itself as one readable element.
|
|
318
|
+
- (BOOL)isAccessibilityElement
|
|
272
319
|
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
CGPoint loc = [gesture locationInView:_textView];
|
|
276
|
-
UITextPosition *pos = [_textView closestPositionToPoint:loc];
|
|
277
|
-
if (!pos)
|
|
278
|
-
return;
|
|
279
|
-
NSInteger idx = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:pos];
|
|
280
|
-
if (idx < 0)
|
|
281
|
-
return;
|
|
282
|
-
NSTextStorage *storage = _textView.textStorage;
|
|
283
|
-
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
284
|
-
// The closest position can land just past the chip, so probe both `idx` and `idx-1`.
|
|
285
|
-
// `closestPositionToPoint:` has no distance bound — it clamps a tap anywhere in the
|
|
286
|
-
// text view (blank space past the text, textContainerInset padding) onto the nearest
|
|
287
|
-
// character. Requiring the touch to fall inside the chip's own glyph rect is what
|
|
288
|
-
// keeps a tap beside a chip from reading as a tap on it.
|
|
289
|
-
ENRMMathInlineAttachment *att = nil;
|
|
290
|
-
NSUInteger chipIdx = NSNotFound;
|
|
291
|
-
for (NSInteger probe = idx; probe >= idx - 1; probe--) {
|
|
292
|
-
if (probe < 0 || (NSUInteger)probe >= storage.length)
|
|
293
|
-
continue;
|
|
294
|
-
id v = [storage attribute:NSAttachmentAttributeName atIndex:(NSUInteger)probe effectiveRange:NULL];
|
|
295
|
-
if (![v isKindOfClass:[ENRMMathInlineAttachment class]])
|
|
296
|
-
continue;
|
|
297
|
-
// An empty rect means the layout could not give us the chip's extent; accept the
|
|
298
|
-
// candidate then, rather than making the chip untappable.
|
|
299
|
-
CGRect rect = [self mathChipRectInTextViewAtIndex:(NSUInteger)probe layoutManager:lm];
|
|
300
|
-
if (!CGRectIsEmpty(rect) && !CGRectContainsPoint(rect, loc))
|
|
301
|
-
continue;
|
|
302
|
-
att = v;
|
|
303
|
-
chipIdx = (NSUInteger)probe;
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
if (!att)
|
|
307
|
-
return;
|
|
320
|
+
return YES;
|
|
321
|
+
}
|
|
308
322
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
[lm invalidateLayoutForCharacterRange:chipRange actualCharacterRange:NULL];
|
|
315
|
-
[lm ensureLayoutForCharacterRange:chipRange];
|
|
323
|
+
- (NSString *)accessibilityValue
|
|
324
|
+
{
|
|
325
|
+
NSString *text = ENRMGetPlainText(_textView);
|
|
326
|
+
if (text.length > 0) {
|
|
327
|
+
return text;
|
|
316
328
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
329
|
+
if (_placeholderLabel.text.length > 0) {
|
|
330
|
+
return _placeholderLabel.text;
|
|
331
|
+
}
|
|
332
|
+
return [super accessibilityValue];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
- (BOOL)accessibilityActivate
|
|
336
|
+
{
|
|
337
|
+
ENRMFocusTextView(_textView);
|
|
338
|
+
[super accessibilityActivate];
|
|
339
|
+
return YES;
|
|
340
|
+
}
|
|
341
|
+
#endif
|
|
327
342
|
|
|
328
|
-
|
|
329
|
-
CGRect glyphRect = [self mathChipRectInTextViewAtIndex:chipIdx layoutManager:lm];
|
|
330
|
-
CGRect rectInSelf = [self.contentView convertRect:glyphRect fromView:_textView];
|
|
343
|
+
#pragma mark - Paste
|
|
331
344
|
|
|
332
|
-
|
|
345
|
+
- (void)emitOnPasteWithText:(NSString *)text markdown:(NSString *)markdown
|
|
346
|
+
{
|
|
347
|
+
[_inputEventEmitter emitOnPasteWithText:text markdown:markdown];
|
|
333
348
|
}
|
|
334
349
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
rect:(CGRect)rect
|
|
350
|
+
/// The single exit for an intercepted paste. One command rather than two makes
|
|
351
|
+
/// "never swallow a paste" structural: the branch that turns the clipboard into
|
|
352
|
+
/// something else still has to come through here, with empty strings.
|
|
353
|
+
- (void)resolvePaste:(NSString *)text markdown:(NSString *)markdown
|
|
340
354
|
{
|
|
341
|
-
|
|
342
|
-
|
|
355
|
+
// Round-trips the formatting when the copy came from an input of ours;
|
|
356
|
+
// without this a copy-then-paste inside the app would land as flat text.
|
|
357
|
+
if (markdown.length > 0) {
|
|
358
|
+
[self pasteMarkdown:markdown];
|
|
343
359
|
return;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
.displayMode = displayMode ? true : false,
|
|
349
|
-
.x = rect.origin.x,
|
|
350
|
-
.y = rect.origin.y,
|
|
351
|
-
.width = rect.size.width,
|
|
352
|
-
.height = rect.size.height,
|
|
353
|
-
});
|
|
360
|
+
}
|
|
361
|
+
// Empty text with an empty selection is a zero-length edit: it still emits
|
|
362
|
+
// onChangeText with unchanged content, which the caller reads as a no-op.
|
|
363
|
+
[self replaceSelectedTextWith:text formattingRanges:@[]];
|
|
354
364
|
}
|
|
355
365
|
|
|
356
|
-
#endif
|
|
357
|
-
|
|
358
366
|
#pragma mark - State
|
|
359
367
|
|
|
360
368
|
- (void)updateState:(const facebook::react::State::Shared &)state
|
|
@@ -452,6 +460,10 @@ using namespace facebook::react;
|
|
|
452
460
|
}
|
|
453
461
|
}
|
|
454
462
|
|
|
463
|
+
if (newViewProps.interceptPaste != oldViewProps.interceptPaste) {
|
|
464
|
+
_interceptPaste = newViewProps.interceptPaste;
|
|
465
|
+
}
|
|
466
|
+
|
|
455
467
|
if (newViewProps.multiline != oldViewProps.multiline) {
|
|
456
468
|
_textView.textContainer.maximumNumberOfLines = newViewProps.multiline ? 0 : 1;
|
|
457
469
|
_textView.textContainer.lineBreakMode =
|
|
@@ -479,7 +491,7 @@ using namespace facebook::react;
|
|
|
479
491
|
ENRMApplySelectionColor(_textView, newViewProps.selectionColor);
|
|
480
492
|
}
|
|
481
493
|
|
|
482
|
-
|
|
494
|
+
_inputEventEmitter.emitMarkdown = newViewProps.isOnChangeMarkdownSet;
|
|
483
495
|
|
|
484
496
|
{
|
|
485
497
|
auto configFromProp = [](const auto &prop) {
|
|
@@ -547,11 +559,8 @@ using namespace facebook::react;
|
|
|
547
559
|
[indicators addObject:value];
|
|
548
560
|
}
|
|
549
561
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
[self clearActiveMention:_activeMentionIndicator];
|
|
553
|
-
}
|
|
554
|
-
[self updateActiveMention];
|
|
562
|
+
[self dispatchMentionEvents:[_mentionCoordinator setIndicators:indicators]];
|
|
563
|
+
[self dispatchMentionUpdate];
|
|
555
564
|
}
|
|
556
565
|
|
|
557
566
|
BOOL styleChanged = applyInputStyleProps(_formatterStyle, newViewProps, oldViewProps);
|
|
@@ -563,11 +572,20 @@ using namespace facebook::react;
|
|
|
563
572
|
writingDirectionChanged = YES;
|
|
564
573
|
}
|
|
565
574
|
|
|
575
|
+
// Must land before the defaultValue import below, so the first parse already sees
|
|
576
|
+
// the right flag. Assigned unconditionally, not just on change: the generated C++
|
|
577
|
+
// default is `false` while the JS default is `true`, so `blockSyntax={false}` on the
|
|
578
|
+
// first render compares equal to the old props and a change-guard would drop it.
|
|
579
|
+
BOOL blockSyntaxChanged = (_blockSyntax != (BOOL)newViewProps.blockSyntax);
|
|
580
|
+
_blockSyntax = newViewProps.blockSyntax;
|
|
581
|
+
|
|
566
582
|
if (newViewProps.defaultValue != oldViewProps.defaultValue) {
|
|
567
583
|
if (!newViewProps.defaultValue.empty() && oldViewProps.defaultValue.empty()) {
|
|
568
584
|
NSString *markdown = [NSString stringWithUTF8String:newViewProps.defaultValue.c_str()];
|
|
569
585
|
[self importMarkdown:markdown];
|
|
570
586
|
}
|
|
587
|
+
} else if (blockSyntaxChanged && _lastImportedMarkdown != nil) {
|
|
588
|
+
[self importMarkdown:_lastImportedMarkdown];
|
|
571
589
|
}
|
|
572
590
|
|
|
573
591
|
if (styleChanged) {
|
|
@@ -579,9 +597,11 @@ using namespace facebook::react;
|
|
|
579
597
|
[self applyFormatting];
|
|
580
598
|
}
|
|
581
599
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
600
|
+
// Chips cache the style they were built with, so a style pass has to re-stamp
|
|
601
|
+
// them. Not gated on a style diff: every field is a flat scalar on
|
|
602
|
+
// `_formatterStyle`, so there is no cheap "did the math style change" test,
|
|
603
|
+
// and `refreshAll` already no-ops when the buffer holds no chip.
|
|
604
|
+
[_mathChipController refreshAll];
|
|
585
605
|
|
|
586
606
|
[self requestHeightUpdate];
|
|
587
607
|
} else if (writingDirectionChanged && _textView.textStorage.length > 0) {
|
|
@@ -605,20 +625,23 @@ using namespace facebook::react;
|
|
|
605
625
|
return;
|
|
606
626
|
}
|
|
607
627
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
628
|
+
NSUInteger textLength = _textView.textStorage.length;
|
|
629
|
+
if (textLength == 0) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
NSRange wholeRange = NSMakeRange(0, textLength);
|
|
634
|
+
[_textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:NULL];
|
|
635
|
+
[_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
|
|
636
|
+
[_textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
|
|
637
|
+
|
|
638
|
+
CGSize measuredSize = [self measureSize:_textView.frame.size.width];
|
|
639
|
+
CGSize currentSize = _textView.contentSize;
|
|
640
|
+
BOOL sizeChanged =
|
|
641
|
+
fabs(currentSize.width - measuredSize.width) > 0.5 || fabs(currentSize.height - measuredSize.height) > 0.5;
|
|
642
|
+
if (sizeChanged) {
|
|
643
|
+
ENRMSetContentSize(_textView, measuredSize);
|
|
644
|
+
}
|
|
622
645
|
}
|
|
623
646
|
|
|
624
647
|
#pragma mark - Window attachment
|
|
@@ -672,14 +695,6 @@ using namespace facebook::react;
|
|
|
672
695
|
[_textView mouseDown:event];
|
|
673
696
|
}
|
|
674
697
|
|
|
675
|
-
#else // iOS
|
|
676
|
-
|
|
677
|
-
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
678
|
-
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
679
|
-
{
|
|
680
|
-
return YES;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
698
|
#endif
|
|
684
699
|
|
|
685
700
|
#if !TARGET_OS_OSX
|
|
@@ -700,181 +715,178 @@ using namespace facebook::react;
|
|
|
700
715
|
}
|
|
701
716
|
#endif
|
|
702
717
|
|
|
718
|
+
#pragma mark - ENRMEditPipelineHost
|
|
719
|
+
|
|
720
|
+
/// Backs `onChangeText`. A math chip stays as its raw `kENRMObjectReplacement`
|
|
721
|
+
/// character here — do not strip or expand it. `onChangeSelection`,
|
|
722
|
+
/// `onCaretRectChange` and the three math commands all speak in buffer offsets
|
|
723
|
+
/// where one formula is exactly one character, so removing it would put every
|
|
724
|
+
/// offset JS holds out by one per chip, and expanding it to `$latex$` would be
|
|
725
|
+
/// worse still. Use `onChangeMarkdown` when you want `$...$`.
|
|
726
|
+
- (NSString *)plainText
|
|
727
|
+
{
|
|
728
|
+
return ENRMGetPlainText(_textView);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
- (NSTextStorage *)textStorage
|
|
732
|
+
{
|
|
733
|
+
return _textView.textStorage;
|
|
734
|
+
}
|
|
735
|
+
|
|
703
736
|
#pragma mark - Placeholder
|
|
704
737
|
|
|
705
738
|
- (void)updatePlaceholderVisibility
|
|
706
739
|
{
|
|
707
|
-
|
|
740
|
+
// Hide the placeholder once there's any content OR any block has been started
|
|
741
|
+
// (e.g. an empty bullet/heading line with only a zero-length anchor): the block
|
|
742
|
+
// marker or indent would otherwise overlap the placeholder text.
|
|
743
|
+
BOOL hasText = ENRMGetPlainText(_textView).length > 0;
|
|
744
|
+
BOOL hasBlock = _blockStore.allRanges.count > 0;
|
|
745
|
+
_placeholderLabel.hidden = hasText || hasBlock;
|
|
708
746
|
}
|
|
709
747
|
|
|
710
748
|
#pragma mark - Markdown import
|
|
711
749
|
|
|
712
750
|
- (void)importMarkdown:(NSString *)markdown
|
|
713
751
|
{
|
|
752
|
+
_lastImportedMarkdown = [markdown copy];
|
|
714
753
|
ENRMInputParser *parser = [[ENRMInputParser alloc] init];
|
|
715
|
-
ENRMParseResult *parsed = [parser parseToPlainTextAndRanges:markdown];
|
|
754
|
+
ENRMParseResult *parsed = [parser parseToPlainTextAndRanges:markdown blockSyntax:_blockSyntax];
|
|
716
755
|
|
|
717
|
-
|
|
756
|
+
[_editSession enterPhase:ENRMEditPhaseImporting];
|
|
718
757
|
|
|
719
|
-
|
|
758
|
+
[_editSession enterPhase:ENRMEditPhaseFormatting];
|
|
720
759
|
ENRMSetPlainText(_textView, parsed.plainText);
|
|
760
|
+
[_editSession enterPhase:ENRMEditPhaseImporting];
|
|
721
761
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
unichar c = [storage.string characterAtIndex:atom.position];
|
|
730
|
-
if (c != (unichar)0xFFFC)
|
|
731
|
-
continue;
|
|
732
|
-
ENRMMathInlineAttachment *att = [self createMathAttachmentWithLatex:atom.latex displayMode:atom.displayMode];
|
|
733
|
-
NSAttributedString *replacement = [NSAttributedString attributedStringWithAttachment:att];
|
|
734
|
-
[storage replaceCharactersInRange:NSMakeRange(atom.position, 1) withAttributedString:replacement];
|
|
735
|
-
if (atom.displayMode && _formatterStyle.mathBlockAlignment != NSTextAlignmentNatural) {
|
|
736
|
-
NSMutableParagraphStyle *ps = [[NSMutableParagraphStyle alloc] init];
|
|
737
|
-
ps.alignment = _formatterStyle.mathBlockAlignment;
|
|
738
|
-
[storage addAttribute:NSParagraphStyleAttributeName value:ps range:NSMakeRange(atom.position, 1)];
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
[storage endEditing];
|
|
742
|
-
}
|
|
743
|
-
#endif
|
|
744
|
-
|
|
745
|
-
_isApplyingFormatting = NO;
|
|
762
|
+
// Stamped inside the Importing phase, before the stores are read back: the
|
|
763
|
+
// parser already reserved one placeholder character per formula, so this only
|
|
764
|
+
// hangs an attachment on characters that are part of `parsed.plainText`. No
|
|
765
|
+
// deferral is needed the way Android needs one — `applyInputStyleProps` runs at
|
|
766
|
+
// the top of `updateProps`, so `_formatterStyle` is current before a
|
|
767
|
+
// `defaultValue` import ever reaches here.
|
|
768
|
+
[_mathChipController applyAtoms:parsed.mathAtoms offset:0];
|
|
746
769
|
|
|
747
770
|
[_formattingStore setRanges:parsed.formattingRanges];
|
|
771
|
+
[_blockStore setRanges:parsed.blockRanges];
|
|
748
772
|
_lastTextLength = parsed.plainText.length;
|
|
749
773
|
_lastSelectedRange = _textView.selectedRange;
|
|
774
|
+
|
|
775
|
+
[_editSession exitPhase];
|
|
750
776
|
[self applyFormatting];
|
|
751
777
|
[self updatePlaceholderVisibility];
|
|
752
778
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
#if ENRICHED_MARKDOWN_MATH && !TARGET_OS_OSX
|
|
757
|
-
|
|
758
|
-
- (void)applyMathStyleToAttachment:(ENRMMathInlineAttachment *)att
|
|
759
|
-
{
|
|
760
|
-
if (att.displayMode) {
|
|
761
|
-
att.fontSize = _formatterStyle.mathBlockFontSize > 0 ? _formatterStyle.mathBlockFontSize : 20.0;
|
|
762
|
-
att.mathTextColor = _formatterStyle.mathBlockColor ?: _formatterStyle.baseTextColor;
|
|
763
|
-
att.containerBackgroundColor = _formatterStyle.mathBlockBackgroundColor;
|
|
764
|
-
att.containerBorderColor = _formatterStyle.mathBlockBorderColor;
|
|
765
|
-
att.containerBorderWidth = _formatterStyle.mathBlockBorderWidth;
|
|
766
|
-
att.containerBorderRadius = _formatterStyle.mathBlockBorderRadius;
|
|
767
|
-
att.containerPaddingHorizontal = _formatterStyle.mathBlockPaddingHorizontal;
|
|
768
|
-
att.containerPaddingVertical = _formatterStyle.mathBlockPaddingVertical;
|
|
769
|
-
att.blockMarginTop = _formatterStyle.mathBlockMarginTop;
|
|
770
|
-
att.blockMarginBottom = _formatterStyle.mathBlockMarginBottom;
|
|
771
|
-
att.blockAlignment = _formatterStyle.mathBlockAlignment;
|
|
772
|
-
att.selectedBackgroundColor = _formatterStyle.mathBlockSelectedBackgroundColor;
|
|
773
|
-
att.selectedBorderColor = _formatterStyle.mathBlockSelectedBorderColor;
|
|
774
|
-
att.bold = ENRMMathFontWeightIsBold(_formatterStyle.mathBlockFontWeight);
|
|
775
|
-
} else {
|
|
776
|
-
att.fontSize = _formatterStyle.mathFontSize > 0 ? _formatterStyle.mathFontSize : 16.0;
|
|
777
|
-
att.mathTextColor = _formatterStyle.mathColor ?: _formatterStyle.baseTextColor;
|
|
778
|
-
att.containerBackgroundColor = _formatterStyle.mathBackgroundColor;
|
|
779
|
-
att.containerBorderColor = _formatterStyle.mathBorderColor;
|
|
780
|
-
att.containerBorderWidth = _formatterStyle.mathBorderWidth;
|
|
781
|
-
att.containerBorderRadius = _formatterStyle.mathBorderRadius;
|
|
782
|
-
att.containerPaddingHorizontal = _formatterStyle.mathPaddingHorizontal;
|
|
783
|
-
att.containerPaddingVertical = _formatterStyle.mathPaddingVertical;
|
|
784
|
-
att.blockMarginTop = 0;
|
|
785
|
-
att.blockMarginBottom = 0;
|
|
786
|
-
att.blockAlignment = NSTextAlignmentNatural;
|
|
787
|
-
att.selectedBackgroundColor = _formatterStyle.mathSelectedBackgroundColor;
|
|
788
|
-
att.selectedBorderColor = _formatterStyle.mathSelectedBorderColor;
|
|
789
|
-
att.bold = ENRMMathFontWeightIsBold(_formatterStyle.mathFontWeight);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
- (ENRMMathInlineAttachment *)createMathAttachmentWithLatex:(NSString *)latex displayMode:(BOOL)displayMode
|
|
794
|
-
{
|
|
795
|
-
ENRMMathInlineAttachment *att = [[ENRMMathInlineAttachment alloc] init];
|
|
796
|
-
att.latex = latex ?: @"";
|
|
797
|
-
att.displayMode = displayMode;
|
|
798
|
-
[self applyMathStyleToAttachment:att];
|
|
799
|
-
return att;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
- (void)refreshAllMathChips
|
|
803
|
-
{
|
|
804
|
-
NSTextStorage *storage = _textView.textStorage;
|
|
805
|
-
if (storage.length == 0)
|
|
806
|
-
return;
|
|
807
|
-
__weak EnrichedMarkdownTextInput *weakSelf = self;
|
|
808
|
-
[storage beginEditing];
|
|
809
|
-
[storage enumerateAttribute:NSAttachmentAttributeName
|
|
810
|
-
inRange:NSMakeRange(0, storage.length)
|
|
811
|
-
options:0
|
|
812
|
-
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
813
|
-
EnrichedMarkdownTextInput *strongSelf = weakSelf;
|
|
814
|
-
if (!strongSelf)
|
|
815
|
-
return;
|
|
816
|
-
if ([value isKindOfClass:[ENRMMathInlineAttachment class]]) {
|
|
817
|
-
ENRMMathInlineAttachment *att = value;
|
|
818
|
-
[strongSelf applyMathStyleToAttachment:att];
|
|
819
|
-
[att invalidateRender];
|
|
820
|
-
}
|
|
821
|
-
}];
|
|
822
|
-
[storage endEditing];
|
|
823
|
-
|
|
824
|
-
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
825
|
-
if (lm) {
|
|
826
|
-
NSRange fullRange = NSMakeRange(0, storage.length);
|
|
827
|
-
[lm invalidateLayoutForCharacterRange:fullRange actualCharacterRange:NULL];
|
|
828
|
-
[lm ensureLayoutForCharacterRange:fullRange];
|
|
779
|
+
if (parsed.plainText.length == 0) {
|
|
780
|
+
[self resetBaseTypingAttributes];
|
|
829
781
|
}
|
|
830
|
-
ENRMSetNeedsDisplay(_textView);
|
|
831
782
|
}
|
|
832
783
|
|
|
833
|
-
#endif
|
|
834
|
-
|
|
835
784
|
- (void)replaceTextInRange:(NSRange)selection
|
|
836
785
|
withText:(NSString *)text
|
|
837
786
|
formattingRanges:(NSArray<ENRMFormattingRange *> *)ranges
|
|
787
|
+
blockRanges:(NSArray<ENRMBlockRange *> *)blockRanges
|
|
838
788
|
{
|
|
839
789
|
NSUInteger editLocation = selection.location;
|
|
840
790
|
|
|
841
|
-
|
|
791
|
+
[_editSession enterPhase:ENRMEditPhaseFormatting];
|
|
842
792
|
ENRMReplaceTextInRange(_textView, text, selection);
|
|
843
|
-
|
|
793
|
+
[_editSession exitPhase];
|
|
844
794
|
|
|
845
|
-
[
|
|
795
|
+
NSString *plainText = [self adjustStoresForEditAtLocation:editLocation
|
|
796
|
+
deletedLength:selection.length
|
|
797
|
+
insertedLength:text.length];
|
|
846
798
|
|
|
847
799
|
for (ENRMFormattingRange *range in ranges) {
|
|
848
800
|
NSRange shifted = NSMakeRange(range.range.location + editLocation, range.range.length);
|
|
849
801
|
[_formattingStore addRange:[ENRMFormattingRange rangeWithType:range.type range:shifted url:range.url]];
|
|
850
802
|
}
|
|
851
803
|
|
|
852
|
-
|
|
804
|
+
for (ENRMBlockRange *block in blockRanges) {
|
|
805
|
+
NSRange shifted = NSMakeRange(block.range.location + editLocation, block.range.length);
|
|
806
|
+
[_blockStore setBlockType:block.type level:block.level forParagraphRange:shifted inText:plainText];
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
_lastTextLength = plainText.length;
|
|
853
810
|
_lastSelectedRange = _textView.selectedRange;
|
|
854
811
|
|
|
855
812
|
[self applyFormatting];
|
|
856
813
|
|
|
857
|
-
[_detectorPipeline processTextChange:
|
|
858
|
-
modificationRange:NSMakeRange(editLocation, text.length)];
|
|
814
|
+
[_detectorPipeline processTextChange:plainText modificationRange:NSMakeRange(editLocation, text.length)];
|
|
859
815
|
|
|
860
816
|
[self updatePlaceholderVisibility];
|
|
861
|
-
[
|
|
862
|
-
[
|
|
863
|
-
[
|
|
817
|
+
[_inputEventEmitter emitOnChangeText];
|
|
818
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
819
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
864
820
|
[self requestHeightUpdate];
|
|
865
821
|
[self scheduleRelayoutIfNeeded];
|
|
866
822
|
}
|
|
867
823
|
|
|
824
|
+
- (void)replaceTextInRange:(NSRange)selection
|
|
825
|
+
withText:(NSString *)text
|
|
826
|
+
formattingRanges:(NSArray<ENRMFormattingRange *> *)ranges
|
|
827
|
+
{
|
|
828
|
+
[self replaceTextInRange:selection withText:text formattingRanges:ranges blockRanges:@[]];
|
|
829
|
+
}
|
|
830
|
+
|
|
868
831
|
- (void)replaceSelectedTextWith:(NSString *)text formattingRanges:(NSArray<ENRMFormattingRange *> *)ranges
|
|
869
832
|
{
|
|
870
833
|
[self replaceTextInRange:_textView.selectedRange withText:text formattingRanges:ranges];
|
|
871
834
|
}
|
|
872
835
|
|
|
836
|
+
/// Insertion is literal: the string's characters land at the cursor exactly as
|
|
837
|
+
/// given. Markdown parsing consumes leading and trailing newlines as block
|
|
838
|
+
/// structure, so they are split off before the parse and re-attached verbatim;
|
|
839
|
+
/// without this, inserting "\n- item\n" mid-paragraph would merge the list line
|
|
840
|
+
/// with the surrounding text. Callers decide separation by including (or
|
|
841
|
+
/// omitting) newlines in the string.
|
|
873
842
|
- (void)pasteMarkdown:(NSString *)markdown
|
|
874
843
|
{
|
|
844
|
+
NSUInteger lead = 0;
|
|
845
|
+
while (lead < markdown.length && [markdown characterAtIndex:lead] == '\n') {
|
|
846
|
+
lead++;
|
|
847
|
+
}
|
|
848
|
+
NSUInteger trail = 0;
|
|
849
|
+
while (lead + trail < markdown.length && [markdown characterAtIndex:markdown.length - 1 - trail] == '\n') {
|
|
850
|
+
trail++;
|
|
851
|
+
}
|
|
852
|
+
NSString *core = [markdown substringWithRange:NSMakeRange(lead, markdown.length - lead - trail)];
|
|
853
|
+
|
|
875
854
|
ENRMInputParser *parser = [[ENRMInputParser alloc] init];
|
|
876
|
-
ENRMParseResult *parsed = [parser parseToPlainTextAndRanges:
|
|
877
|
-
|
|
855
|
+
ENRMParseResult *parsed = [parser parseToPlainTextAndRanges:core blockSyntax:_blockSyntax];
|
|
856
|
+
|
|
857
|
+
if (lead == 0 && trail == 0) {
|
|
858
|
+
NSUInteger insertLocation = _textView.selectedRange.location;
|
|
859
|
+
[self replaceTextInRange:_textView.selectedRange
|
|
860
|
+
withText:parsed.plainText
|
|
861
|
+
formattingRanges:parsed.formattingRanges
|
|
862
|
+
blockRanges:parsed.blockRanges];
|
|
863
|
+
// Positions are relative to the parsed fragment, so shift them onto the spot
|
|
864
|
+
// it landed. Pasted markdown carries formulas like any other source.
|
|
865
|
+
[_mathChipController applyAtoms:parsed.mathAtoms offset:insertLocation];
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
NSString *prefix = [@"" stringByPaddingToLength:lead withString:@"\n" startingAtIndex:0];
|
|
870
|
+
NSString *suffix = [@"" stringByPaddingToLength:trail withString:@"\n" startingAtIndex:0];
|
|
871
|
+
NSString *plainText = [NSString stringWithFormat:@"%@%@%@", prefix, parsed.plainText, suffix];
|
|
872
|
+
|
|
873
|
+
NSMutableArray<ENRMFormattingRange *> *formattingRanges =
|
|
874
|
+
[NSMutableArray arrayWithCapacity:parsed.formattingRanges.count];
|
|
875
|
+
for (ENRMFormattingRange *range in parsed.formattingRanges) {
|
|
876
|
+
NSRange shifted = NSMakeRange(range.range.location + lead, range.range.length);
|
|
877
|
+
[formattingRanges addObject:[ENRMFormattingRange rangeWithType:range.type range:shifted url:range.url]];
|
|
878
|
+
}
|
|
879
|
+
NSMutableArray<ENRMBlockRange *> *blockRanges = [NSMutableArray arrayWithCapacity:parsed.blockRanges.count];
|
|
880
|
+
for (ENRMBlockRange *block in parsed.blockRanges) {
|
|
881
|
+
NSRange shifted = NSMakeRange(block.range.location + lead, block.range.length);
|
|
882
|
+
[blockRanges addObject:[ENRMBlockRange rangeWithType:block.type range:shifted level:block.level]];
|
|
883
|
+
}
|
|
884
|
+
NSUInteger insertLocation = _textView.selectedRange.location;
|
|
885
|
+
[self replaceTextInRange:_textView.selectedRange
|
|
886
|
+
withText:plainText
|
|
887
|
+
formattingRanges:formattingRanges
|
|
888
|
+
blockRanges:blockRanges];
|
|
889
|
+
[_mathChipController applyAtoms:parsed.mathAtoms offset:insertLocation + lead];
|
|
878
890
|
}
|
|
879
891
|
|
|
880
892
|
#pragma mark - Formatting
|
|
@@ -889,17 +901,44 @@ using namespace facebook::react;
|
|
|
889
901
|
|
|
890
902
|
- (void)applyFormatting
|
|
891
903
|
{
|
|
892
|
-
|
|
904
|
+
[self applyFormattingScopedToRange:NSMakeRange(0, ENRMGetPlainText(_textView).length)];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/// Per-keystroke variant: re-applies inline and block attributes only on the
|
|
908
|
+
/// line(s) touched by the edit, mirroring Android's applyFormattingScopedToEdit.
|
|
909
|
+
- (void)applyFormattingScopedToEditAtLocation:(NSUInteger)editLocation insertedLength:(NSUInteger)insertedLength
|
|
910
|
+
{
|
|
911
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
912
|
+
NSUInteger textLength = plainText.length;
|
|
913
|
+
if (textLength == 0) {
|
|
914
|
+
[self applyFormatting];
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
NSUInteger rawStart = MIN(editLocation, textLength);
|
|
919
|
+
NSUInteger rawEnd = MIN(editLocation + insertedLength, textLength);
|
|
920
|
+
rawEnd = MAX(rawEnd, rawStart);
|
|
921
|
+
NSRange scope = [plainText lineRangeForRange:NSMakeRange(rawStart, rawEnd - rawStart)];
|
|
922
|
+
[self applyFormattingScopedToRange:scope];
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
- (void)applyFormattingScopedToRange:(NSRange)scope
|
|
926
|
+
{
|
|
927
|
+
if (_editSession.shouldSuppressFormatting) {
|
|
893
928
|
return;
|
|
894
929
|
}
|
|
895
|
-
if (
|
|
930
|
+
if (_editSession.isComposing) {
|
|
896
931
|
return;
|
|
897
932
|
}
|
|
898
|
-
|
|
933
|
+
[_editSession enterPhase:ENRMEditPhaseFormatting];
|
|
899
934
|
|
|
900
935
|
NSRange savedSelection = _textView.selectedRange;
|
|
901
936
|
|
|
902
|
-
[_formatter applyFormattingRanges:_formattingStore.allRanges
|
|
937
|
+
[_formatter applyFormattingRanges:_formattingStore.allRanges
|
|
938
|
+
toTextView:_textView
|
|
939
|
+
style:_formatterStyle
|
|
940
|
+
scopedToRange:scope];
|
|
941
|
+
[_formatter applyBlockRanges:_blockStore.allRanges toTextView:_textView style:_formatterStyle scopedToRange:scope];
|
|
903
942
|
[_detectorPipeline refreshAllStyling];
|
|
904
943
|
[self applyWritingDirection];
|
|
905
944
|
|
|
@@ -908,7 +947,7 @@ using namespace facebook::react;
|
|
|
908
947
|
_textView.selectedRange = savedSelection;
|
|
909
948
|
}
|
|
910
949
|
|
|
911
|
-
|
|
950
|
+
[_editSession exitPhase];
|
|
912
951
|
}
|
|
913
952
|
|
|
914
953
|
- (void)applyWritingDirection
|
|
@@ -938,9 +977,9 @@ using namespace facebook::react;
|
|
|
938
977
|
{
|
|
939
978
|
[self importMarkdown:markdown];
|
|
940
979
|
_lastSelectedRange = _textView.selectedRange;
|
|
941
|
-
[
|
|
942
|
-
[
|
|
943
|
-
[
|
|
980
|
+
[_inputEventEmitter emitOnChangeText];
|
|
981
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
982
|
+
[_inputEventEmitter emitOnChangeState];
|
|
944
983
|
[self requestHeightUpdate];
|
|
945
984
|
}
|
|
946
985
|
|
|
@@ -950,8 +989,8 @@ using namespace facebook::react;
|
|
|
950
989
|
NSInteger clampedStart = MIN(MAX(start, 0), textLen);
|
|
951
990
|
NSInteger clampedEnd = MIN(MAX(end, clampedStart), textLen);
|
|
952
991
|
_textView.selectedRange = NSMakeRange((NSUInteger)clampedStart, (NSUInteger)(clampedEnd - clampedStart));
|
|
953
|
-
[
|
|
954
|
-
[
|
|
992
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
993
|
+
[_inputEventEmitter emitOnChangeState];
|
|
955
994
|
}
|
|
956
995
|
|
|
957
996
|
- (void)toggleBold
|
|
@@ -988,813 +1027,817 @@ using namespace facebook::react;
|
|
|
988
1027
|
ENRMStyleMergingConfig *mergingConfig = handler.mergingConfig;
|
|
989
1028
|
|
|
990
1029
|
NSRange selection = _textView.selectedRange;
|
|
991
|
-
NSUInteger cursor = selection.location;
|
|
992
|
-
NSNumber *key = @(styleType);
|
|
993
|
-
|
|
994
|
-
// Check blocking rules: if any blocking style is active, refuse to toggle on.
|
|
995
|
-
if (mergingConfig.blockingStyles.count > 0) {
|
|
996
|
-
BOOL isCurrentlyActive = [_formattingStore isStyleActive:styleType atPosition:cursor];
|
|
997
|
-
if (!isCurrentlyActive) {
|
|
998
|
-
for (NSNumber *blockerNum in mergingConfig.blockingStyles) {
|
|
999
|
-
if ([_formattingStore isStyleActive:(ENRMInputStyleType)blockerNum.integerValue atPosition:cursor]) {
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
1030
|
|
|
1006
|
-
if (
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
while (pos < selEnd) {
|
|
1011
|
-
ENRMFormattingRange *match = [_formattingStore rangeOfType:styleType containingPosition:pos];
|
|
1012
|
-
if (match == nil) {
|
|
1013
|
-
fullyStyled = NO;
|
|
1014
|
-
break;
|
|
1015
|
-
}
|
|
1016
|
-
pos = NSMaxRange(match.range);
|
|
1017
|
-
}
|
|
1018
|
-
if (fullyStyled) {
|
|
1019
|
-
[_formattingStore removeType:styleType inRange:selection];
|
|
1020
|
-
} else {
|
|
1021
|
-
// Remove conflicting styles from the range before applying.
|
|
1022
|
-
for (NSNumber *conflictNum in mergingConfig.conflictingStyles) {
|
|
1023
|
-
[_formattingStore removeType:(ENRMInputStyleType)conflictNum.integerValue inRange:selection];
|
|
1024
|
-
}
|
|
1025
|
-
ENRMFormattingRange *newRange = [ENRMFormattingRange rangeWithType:styleType range:selection];
|
|
1026
|
-
[_formattingStore addRange:newRange];
|
|
1027
|
-
}
|
|
1028
|
-
[_pendingStyles removeObject:key];
|
|
1029
|
-
[_pendingStyleRemovals removeObject:key];
|
|
1030
|
-
} else {
|
|
1031
|
-
BOOL isInsideRange = [_formattingStore isStyleActive:styleType atPosition:cursor];
|
|
1032
|
-
|
|
1033
|
-
if ([_pendingStyleRemovals containsObject:key]) {
|
|
1034
|
-
[_pendingStyleRemovals removeObject:key];
|
|
1035
|
-
} else if ([_pendingStyles containsObject:key]) {
|
|
1036
|
-
[_pendingStyles removeObject:key];
|
|
1037
|
-
} else if (isInsideRange) {
|
|
1038
|
-
[_pendingStyleRemovals addObject:key];
|
|
1039
|
-
} else {
|
|
1040
|
-
[_pendingStyles addObject:key];
|
|
1041
|
-
}
|
|
1031
|
+
if ([_formattingStore isToggleBlocked:styleType
|
|
1032
|
+
atPosition:selection.location
|
|
1033
|
+
blockingStyles:mergingConfig.blockingStyles]) {
|
|
1034
|
+
return;
|
|
1042
1035
|
}
|
|
1043
1036
|
|
|
1037
|
+
BOOL wasActive = [_formattingStore toggleStyle:styleType
|
|
1038
|
+
inRange:selection
|
|
1039
|
+
conflictingStyles:mergingConfig.conflictingStyles];
|
|
1040
|
+
|
|
1041
|
+
[_typingController togglePendingStyle:styleType wasActive:wasActive hasSelection:(selection.length > 0)];
|
|
1042
|
+
|
|
1044
1043
|
[self applyFormatting];
|
|
1045
|
-
[
|
|
1046
|
-
[
|
|
1044
|
+
[_typingController syncWithPendingStyles];
|
|
1045
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1047
1046
|
}
|
|
1048
1047
|
|
|
1049
|
-
- (void)
|
|
1048
|
+
- (void)toggleHeading:(NSInteger)level
|
|
1050
1049
|
{
|
|
1051
|
-
|
|
1052
|
-
|
|
1050
|
+
if (level < 1 || level > 6) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
[self toggleBlockType:ENRMBlockTypeForHeadingLevel(level) level:level];
|
|
1054
|
+
}
|
|
1053
1055
|
|
|
1054
|
-
|
|
1056
|
+
- (void)toggleBlockType:(ENRMInputBlockType)type level:(NSInteger)level
|
|
1057
|
+
{
|
|
1058
|
+
NSString *text = ENRMGetPlainText(_textView);
|
|
1059
|
+
BOOL alreadyActive = [_blockCoordinator toggleBlockType:type
|
|
1060
|
+
level:level
|
|
1061
|
+
selectionRange:_textView.selectedRange
|
|
1062
|
+
inText:text];
|
|
1055
1063
|
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1064
|
+
if (alreadyActive) {
|
|
1065
|
+
// Strip the stored block paragraph style directly: an empty item's line has no
|
|
1066
|
+
// stamped block-marker attribute (zero-length ranges are never stamped), so the
|
|
1067
|
+
// applyFormatting reset below — which is keyed off that marker — cannot reach it.
|
|
1068
|
+
NSRange paragraphRange = [text paragraphRangeForRange:_textView.selectedRange];
|
|
1069
|
+
NSTextStorage *storage = _textView.textStorage;
|
|
1070
|
+
NSRange clamped = NSIntersectionRange(paragraphRange, NSMakeRange(0, storage.length));
|
|
1071
|
+
if (clamped.length > 0) {
|
|
1072
|
+
[storage beginEditing];
|
|
1073
|
+
[storage removeAttribute:NSParagraphStyleAttributeName range:clamped];
|
|
1074
|
+
[storage endEditing];
|
|
1075
|
+
}
|
|
1065
1076
|
}
|
|
1066
1077
|
|
|
1067
1078
|
[self applyFormatting];
|
|
1068
|
-
[
|
|
1079
|
+
[_typingController syncWithCursorBlock];
|
|
1080
|
+
[self updatePlaceholderVisibility];
|
|
1081
|
+
if (alreadyActive) {
|
|
1082
|
+
[_typingController clearListParagraphStyle];
|
|
1083
|
+
}
|
|
1084
|
+
[self updateEmptyBulletMarker];
|
|
1085
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1069
1086
|
}
|
|
1070
1087
|
|
|
1071
|
-
- (void)
|
|
1088
|
+
- (void)toggleUnorderedList
|
|
1072
1089
|
{
|
|
1073
|
-
|
|
1074
|
-
NSRange linkRange = NSMakeRange(0, displayText.length);
|
|
1075
|
-
ENRMFormattingRange *range = [ENRMFormattingRange rangeWithType:ENRMInputStyleTypeLink
|
|
1076
|
-
range:linkRange
|
|
1077
|
-
url:[self sanitizeLinkURL:url]];
|
|
1078
|
-
[self replaceSelectedTextWith:displayText formattingRanges:@[ range ]];
|
|
1090
|
+
[self toggleBlockType:ENRMInputBlockTypeUnorderedListItem level:0];
|
|
1079
1091
|
}
|
|
1080
1092
|
|
|
1081
|
-
- (
|
|
1093
|
+
- (void)toggleOrderedList
|
|
1082
1094
|
{
|
|
1083
|
-
|
|
1084
|
-
return [result stringByReplacingOccurrencesOfString:@")" withString:@"%29"];
|
|
1095
|
+
[self toggleBlockType:ENRMInputBlockTypeOrderedListItem level:0];
|
|
1085
1096
|
}
|
|
1086
1097
|
|
|
1087
|
-
- (void)
|
|
1098
|
+
- (void)indentList
|
|
1088
1099
|
{
|
|
1089
|
-
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
[self replaceSelectedTextWith:indicator formattingRanges:@[]];
|
|
1094
|
-
[self updateActiveMention];
|
|
1100
|
+
[self changeListDepthBy:1];
|
|
1095
1101
|
}
|
|
1096
1102
|
|
|
1097
|
-
- (void)
|
|
1103
|
+
- (void)outdentList
|
|
1098
1104
|
{
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
}
|
|
1105
|
+
[self changeListDepthBy:-1];
|
|
1106
|
+
}
|
|
1102
1107
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1108
|
+
- (void)changeListDepthBy:(NSInteger)delta
|
|
1109
|
+
{
|
|
1110
|
+
NSString *text = ENRMGetPlainText(_textView);
|
|
1111
|
+
ENRMDepthChangeResult result = [_blockCoordinator changeDepthBy:delta
|
|
1112
|
+
cursorPosition:_textView.selectedRange.location
|
|
1113
|
+
selectionRange:_textView.selectedRange
|
|
1114
|
+
inText:text];
|
|
1115
|
+
if (result == ENRMDepthChangeResultNoOp) {
|
|
1106
1116
|
return;
|
|
1107
1117
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
range:NSMakeRange(0, displayText.length)
|
|
1114
|
-
url:[self sanitizeLinkURL:url]];
|
|
1115
|
-
NSString *indicator = _activeMentionIndicator;
|
|
1116
|
-
NSRange mentionRange = _activeMentionRange;
|
|
1118
|
+
[self applyFormatting];
|
|
1119
|
+
[_typingController syncWithCursorBlock];
|
|
1120
|
+
[self updateEmptyBulletMarker];
|
|
1121
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1122
|
+
}
|
|
1117
1123
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
_textView.selectedRange
|
|
1121
|
-
[self emitOnChangeSelection];
|
|
1124
|
+
- (nullable ENRMBlockRange *)listBlockForCursorParagraph
|
|
1125
|
+
{
|
|
1126
|
+
return [self listBlockForParagraphAtPosition:_textView.selectedRange.location];
|
|
1122
1127
|
}
|
|
1123
1128
|
|
|
1124
|
-
- (
|
|
1129
|
+
- (nullable ENRMBlockRange *)listBlockForParagraphAtPosition:(NSUInteger)position
|
|
1125
1130
|
{
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
if (activeLink == nil) {
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
+
return [_blockCoordinator listBlockAtPosition:position inText:_textView.textStorage.string];
|
|
1132
|
+
}
|
|
1131
1133
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
[
|
|
1134
|
+
- (nullable ENRMBlockRange *)blockForParagraphAtPosition:(NSUInteger)position
|
|
1135
|
+
{
|
|
1136
|
+
return [_blockCoordinator blockAtPosition:position inText:_textView.textStorage.string];
|
|
1135
1137
|
}
|
|
1136
1138
|
|
|
1137
|
-
|
|
1139
|
+
- (BOOL)listStateOfType:(ENRMInputBlockType)type forCursorParagraphDepth:(nullable NSInteger *)outDepth
|
|
1140
|
+
{
|
|
1141
|
+
return [_blockCoordinator listStateOfType:type
|
|
1142
|
+
atPosition:_textView.selectedRange.location
|
|
1143
|
+
inText:_textView.textStorage.string
|
|
1144
|
+
depth:outDepth];
|
|
1145
|
+
}
|
|
1138
1146
|
|
|
1139
|
-
|
|
1147
|
+
/// Records the block kind/level of the line being edited before the change, so a
|
|
1148
|
+
/// Return that continues a list can recover the previous item's depth and an
|
|
1149
|
+
/// in-line replacement (autocorrect/paste) that wipes the line can heal it.
|
|
1150
|
+
- (void)capturePreEditBlockForRange:(NSRange)range
|
|
1140
1151
|
{
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
NSRange selection = _textView.selectedRange;
|
|
1146
|
-
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1152
|
+
ENRMBlockRange *block = [self blockForParagraphAtPosition:range.location];
|
|
1153
|
+
_preEditBlockType = block != nil ? block.type : ENRMInputBlockTypeParagraph;
|
|
1154
|
+
_preEditBlockLevel = block != nil ? block.level : 0;
|
|
1155
|
+
}
|
|
1147
1156
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
[prefix appendString:@"\n"];
|
|
1156
|
-
if (!atLineEnd)
|
|
1157
|
-
[suffix appendString:@"\n"];
|
|
1157
|
+
/// Whether `range` of the current (pre-edit) text contains a line break. Scans
|
|
1158
|
+
/// only the replaced run, mirroring Android's editTouchedNewline.
|
|
1159
|
+
- (BOOL)replacedRangeTouchesNewline:(NSRange)range
|
|
1160
|
+
{
|
|
1161
|
+
NSString *text = _textView.textStorage.string;
|
|
1162
|
+
if (range.length == 0 || range.location >= text.length) {
|
|
1163
|
+
return NO;
|
|
1158
1164
|
}
|
|
1165
|
+
NSRange clamped = NSMakeRange(range.location, MIN(range.length, text.length - range.location));
|
|
1166
|
+
return [text rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:0 range:clamped].location !=
|
|
1167
|
+
NSNotFound;
|
|
1168
|
+
}
|
|
1159
1169
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1170
|
+
/// Whether the caret's paragraph had no glyph content at the start of the edit
|
|
1171
|
+
/// (an empty line). Used to decide whether Return continues or exits the list.
|
|
1172
|
+
- (BOOL)preEditParagraphWasEmpty:(NSRange)range
|
|
1173
|
+
{
|
|
1174
|
+
NSString *text = _textView.textStorage.string;
|
|
1175
|
+
if (text.length == 0) {
|
|
1176
|
+
return YES;
|
|
1164
1177
|
}
|
|
1165
|
-
|
|
1166
|
-
if (
|
|
1167
|
-
|
|
1178
|
+
NSRange paragraph = [text paragraphRangeForRange:range];
|
|
1179
|
+
if (paragraph.length == 0) {
|
|
1180
|
+
return YES;
|
|
1168
1181
|
}
|
|
1182
|
+
return [[text substringWithRange:paragraph] isEqualToString:@"\n"];
|
|
1183
|
+
}
|
|
1169
1184
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1185
|
+
/// Intercepts Tab (indent) and Backspace at a bullet item's start (outdent, then
|
|
1186
|
+
/// un-list at depth 0). Returns YES when handled, suppressing the default edit.
|
|
1187
|
+
/// Keyed off the caret's own paragraph (NSMaxRange(range)), not range.location —
|
|
1188
|
+
/// a backspace at a line start targets the previous line's newline, and using the
|
|
1189
|
+
/// caret's line lets the next Backspace after un-listing fall through to a merge.
|
|
1190
|
+
- (BOOL)handleListKeyForReplacementRange:(NSRange)range replacementText:(NSString *)text
|
|
1191
|
+
{
|
|
1192
|
+
NSUInteger caret = NSMaxRange(range);
|
|
1193
|
+
NSInteger depth;
|
|
1194
|
+
if (![self listStateForParagraphAtPosition:caret depth:&depth]) {
|
|
1195
|
+
return NO;
|
|
1175
1196
|
}
|
|
1176
1197
|
|
|
1177
|
-
|
|
1178
|
-
[
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
[_formattingStore adjustForEditAtLocation:selection.location
|
|
1183
|
-
deletedLength:selection.length
|
|
1184
|
-
insertedLength:insertedLength];
|
|
1198
|
+
// Tab indents the current item.
|
|
1199
|
+
if ([text isEqualToString:@"\t"]) {
|
|
1200
|
+
[self indentList];
|
|
1201
|
+
return YES;
|
|
1202
|
+
}
|
|
1185
1203
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1204
|
+
// Backspace at the very start of the caret's own item: outdent, or remove the
|
|
1205
|
+
// marker entirely once at depth 0 (de-indenting the line to a plain paragraph).
|
|
1206
|
+
if (text.length == 0 && range.length == 1) {
|
|
1207
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1208
|
+
NSRange paragraphRange = [plainText paragraphRangeForRange:NSMakeRange(caret, 0)];
|
|
1209
|
+
BOOL atItemStart = caret == paragraphRange.location;
|
|
1210
|
+
if (atItemStart) {
|
|
1211
|
+
if (depth > 0) {
|
|
1212
|
+
[self outdentList];
|
|
1213
|
+
} else {
|
|
1214
|
+
ENRMBlockRange *listBlock = [self listBlockForParagraphAtPosition:caret];
|
|
1215
|
+
[self toggleBlockType:listBlock != nil ? listBlock.type : ENRMInputBlockTypeUnorderedListItem level:0];
|
|
1216
|
+
}
|
|
1217
|
+
return YES;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1190
1220
|
|
|
1191
|
-
|
|
1192
|
-
[self updatePlaceholderVisibility];
|
|
1193
|
-
[self emitOnChangeText];
|
|
1194
|
-
[self emitOnChangeSelection];
|
|
1195
|
-
[self emitFormattingChanged];
|
|
1196
|
-
[self requestHeightUpdate];
|
|
1197
|
-
[self scheduleRelayoutIfNeeded];
|
|
1198
|
-
#endif
|
|
1221
|
+
return NO;
|
|
1199
1222
|
}
|
|
1200
1223
|
|
|
1201
|
-
|
|
1224
|
+
/// Backspace at the document start (caret at 0) never fires the text-change
|
|
1225
|
+
/// delegate — nothing precedes the caret — so the first line's bullet has to be
|
|
1226
|
+
/// outdented/removed here. Returns YES when handled.
|
|
1227
|
+
- (BOOL)handleBackspaceAtDocumentStart
|
|
1202
1228
|
{
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
return;
|
|
1207
|
-
id value = [storage attribute:NSAttachmentAttributeName atIndex:(NSUInteger)start effectiveRange:NULL];
|
|
1208
|
-
if (![value isKindOfClass:[ENRMMathInlineAttachment class]])
|
|
1209
|
-
return;
|
|
1210
|
-
|
|
1211
|
-
ENRMMathInlineAttachment *att = value;
|
|
1212
|
-
BOOL modeChanged = (att.displayMode != displayMode);
|
|
1213
|
-
att.latex = latex ?: @"";
|
|
1214
|
-
att.displayMode = displayMode;
|
|
1215
|
-
[self applyMathStyleToAttachment:att];
|
|
1216
|
-
[att invalidateRender];
|
|
1217
|
-
|
|
1218
|
-
NSRange chipRange = NSMakeRange((NSUInteger)start, 1);
|
|
1219
|
-
if (modeChanged) {
|
|
1220
|
-
[storage beginEditing];
|
|
1221
|
-
if (displayMode && att.blockAlignment != NSTextAlignmentNatural) {
|
|
1222
|
-
NSMutableParagraphStyle *ps = [[NSMutableParagraphStyle alloc] init];
|
|
1223
|
-
ps.alignment = att.blockAlignment;
|
|
1224
|
-
[storage addAttribute:NSParagraphStyleAttributeName value:ps range:chipRange];
|
|
1225
|
-
} else {
|
|
1226
|
-
[storage removeAttribute:NSParagraphStyleAttributeName range:chipRange];
|
|
1227
|
-
}
|
|
1228
|
-
[storage endEditing];
|
|
1229
|
+
NSRange selection = _textView.selectedRange;
|
|
1230
|
+
if (selection.location != 0 || selection.length != 0) {
|
|
1231
|
+
return NO;
|
|
1229
1232
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
[lm invalidateLayoutForCharacterRange:chipRange actualCharacterRange:NULL];
|
|
1234
|
-
[lm ensureLayoutForCharacterRange:chipRange];
|
|
1233
|
+
ENRMBlockRange *listBlock = [self listBlockForCursorParagraph];
|
|
1234
|
+
if (listBlock == nil) {
|
|
1235
|
+
return NO;
|
|
1235
1236
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
[self
|
|
1237
|
+
if (listBlock.level > 0) {
|
|
1238
|
+
[self outdentList];
|
|
1239
|
+
} else {
|
|
1240
|
+
[self toggleBlockType:listBlock.type level:0];
|
|
1240
1241
|
}
|
|
1241
|
-
|
|
1242
|
-
[self scheduleRelayoutIfNeeded];
|
|
1243
|
-
#endif
|
|
1242
|
+
return YES;
|
|
1244
1243
|
}
|
|
1245
1244
|
|
|
1246
|
-
- (
|
|
1245
|
+
- (BOOL)listStateForParagraphAtPosition:(NSUInteger)position depth:(NSInteger *)outDepth
|
|
1247
1246
|
{
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
[self replaceTextInRange:NSMakeRange((NSUInteger)start, 1) withText:@"" formattingRanges:@[]];
|
|
1257
|
-
#endif
|
|
1247
|
+
ENRMBlockRange *block = [_blockCoordinator listBlockAtPosition:position inText:_textView.textStorage.string];
|
|
1248
|
+
if (block != nil) {
|
|
1249
|
+
if (outDepth) {
|
|
1250
|
+
*outDepth = block.level;
|
|
1251
|
+
}
|
|
1252
|
+
return YES;
|
|
1253
|
+
}
|
|
1254
|
+
return NO;
|
|
1258
1255
|
}
|
|
1259
1256
|
|
|
1260
|
-
-
|
|
1257
|
+
/// Drives the layout manager's empty-line bullet: an empty bullet line has no
|
|
1258
|
+
/// character to anchor the marker to, so the manager is told its location/depth
|
|
1259
|
+
/// explicitly; cleared when the caret isn't on an empty bullet line. Runs on
|
|
1260
|
+
/// every selection-change fire, so it early-returns when there is nothing to
|
|
1261
|
+
/// draw or clear and only touches storage when the paragraph style differs,
|
|
1262
|
+
/// avoiding layout churn that jitters the edit menu.
|
|
1263
|
+
- (void)updateEmptyBulletMarker
|
|
1261
1264
|
{
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
+
NSString *text = ENRMGetPlainText(_textView);
|
|
1266
|
+
NSRange selection = _textView.selectedRange;
|
|
1267
|
+
BOOL show = NO;
|
|
1268
|
+
NSUInteger location = 0;
|
|
1269
|
+
NSInteger depth = 0;
|
|
1265
1270
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1271
|
+
BOOL ordered = NO;
|
|
1272
|
+
NSInteger ordinal = 1;
|
|
1273
|
+
ENRMBlockRange *cursorListBlock = selection.length == 0 ? [self listBlockForCursorParagraph] : nil;
|
|
1269
1274
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
return nil;
|
|
1275
|
+
ENRMInputListMarkerDrawer *listDrawer = _layoutManager.listMarkerDrawer;
|
|
1276
|
+
BOOL wasShown = listDrawer.emptyBulletDepth >= 0;
|
|
1277
|
+
if (cursorListBlock == nil && !wasShown) {
|
|
1278
|
+
return;
|
|
1275
1279
|
}
|
|
1276
1280
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1281
|
+
if (cursorListBlock != nil) {
|
|
1282
|
+
NSInteger cursorDepth = cursorListBlock.level;
|
|
1283
|
+
ordered = cursorListBlock.type == ENRMInputBlockTypeOrderedListItem;
|
|
1284
|
+
ordinal = cursorListBlock.ordinal;
|
|
1285
|
+
NSRange paragraphRange = text.length == 0 ? NSMakeRange(0, 0) : [text paragraphRangeForRange:selection];
|
|
1286
|
+
NSString *paragraphText = text.length == 0 ? @"" : [text substringWithRange:paragraphRange];
|
|
1287
|
+
BOOL empty = paragraphText.length == 0 || [paragraphText isEqualToString:@"\n"];
|
|
1288
|
+
if (empty) {
|
|
1289
|
+
show = YES;
|
|
1290
|
+
location = paragraphRange.location;
|
|
1291
|
+
depth = cursorDepth;
|
|
1292
|
+
|
|
1293
|
+
// A mid-document empty bullet line has no paragraph style and lays out
|
|
1294
|
+
// flush left; stamp the list style so caret and marker indent immediately.
|
|
1295
|
+
// (A trailing empty line uses the extra line fragment instead.)
|
|
1296
|
+
if (paragraphRange.length > 0) {
|
|
1297
|
+
CGFloat indent = (depth + 1) * kENRMListIndentPerDepth;
|
|
1298
|
+
NSTextStorage *storage = _textView.textStorage;
|
|
1299
|
+
NSParagraphStyle *existing = [storage attribute:NSParagraphStyleAttributeName
|
|
1300
|
+
atIndex:paragraphRange.location
|
|
1301
|
+
effectiveRange:NULL];
|
|
1302
|
+
if (existing == nil || existing.firstLineHeadIndent != indent || existing.headIndent != indent ||
|
|
1303
|
+
existing.paragraphSpacingBefore != _formatterStyle.listItemSpacing) {
|
|
1304
|
+
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
|
|
1305
|
+
paragraph.firstLineHeadIndent = indent;
|
|
1306
|
+
paragraph.headIndent = indent;
|
|
1307
|
+
paragraph.paragraphSpacingBefore = _formatterStyle.listItemSpacing;
|
|
1308
|
+
[storage beginEditing];
|
|
1309
|
+
[storage addAttribute:NSParagraphStyleAttributeName value:paragraph range:paragraphRange];
|
|
1310
|
+
[storage endEditing];
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1280
1314
|
}
|
|
1281
|
-
NSAttributedString *selectedAttr = [full attributedSubstringFromRange:selection];
|
|
1282
|
-
NSUInteger selEnd = NSMaxRange(selection);
|
|
1283
1315
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
NSUInteger rangeEnd = NSMaxRange(range.range);
|
|
1316
|
+
if (!show && !wasShown) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1288
1319
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1320
|
+
listDrawer.emptyBulletDepth = show ? depth : -1;
|
|
1321
|
+
listDrawer.emptyBulletOrdered = ordered;
|
|
1322
|
+
listDrawer.emptyBulletOrdinal = ordinal;
|
|
1323
|
+
listDrawer.emptyBulletLocation = location;
|
|
1324
|
+
listDrawer.emptyBulletFont = _formatterStyle.baseFont;
|
|
1325
|
+
listDrawer.emptyBulletColor = _formatterStyle.baseTextColor;
|
|
1326
|
+
listDrawer.emptyBulletRTL = [self emptyListLineIsRTL];
|
|
1327
|
+
listDrawer.listItemSpacing = _formatterStyle.listItemSpacing;
|
|
1292
1328
|
|
|
1293
|
-
|
|
1294
|
-
NSUInteger clippedEnd = MIN(rangeEnd, selEnd);
|
|
1295
|
-
NSRange shifted = NSMakeRange(clippedStart - selection.location, clippedEnd - clippedStart);
|
|
1329
|
+
BOOL isTrailing = show && text.length > 0 && location >= text.length;
|
|
1296
1330
|
|
|
1297
|
-
|
|
1331
|
+
// Ensure the extra line fragment is laid out so we can read
|
|
1332
|
+
// extraLineFragmentTextContainer / extraLineFragmentUsedRect. The
|
|
1333
|
+
// empty-editor case (length 0) has no glyphs at all; the trailing-line case
|
|
1334
|
+
// (location >= length) has glyphs on preceding lines but the extra line
|
|
1335
|
+
// fragment lives beyond them and isn't covered by ensureLayoutForCharRange:.
|
|
1336
|
+
if (show && (text.length == 0 || location >= text.length)) {
|
|
1337
|
+
[_layoutManager ensureLayoutForTextContainer:_textView.textContainer];
|
|
1298
1338
|
}
|
|
1299
1339
|
|
|
1300
|
-
|
|
1340
|
+
#if !TARGET_OS_OSX
|
|
1341
|
+
if (isTrailing && _layoutManager.extraLineFragmentTextContainer != nil) {
|
|
1342
|
+
[listDrawer showTrailingBulletInTextView:_textView
|
|
1343
|
+
textContainer:_layoutManager.extraLineFragmentTextContainer
|
|
1344
|
+
usedRect:_layoutManager.extraLineFragmentUsedRect];
|
|
1345
|
+
} else {
|
|
1346
|
+
[listDrawer hideTrailingBullet];
|
|
1347
|
+
}
|
|
1348
|
+
#endif
|
|
1349
|
+
|
|
1350
|
+
ENRMSetNeedsDisplay(_textView);
|
|
1351
|
+
|
|
1352
|
+
// The empty-editor bullet would otherwise overlap the placeholder.
|
|
1353
|
+
[self updatePlaceholderVisibility];
|
|
1301
1354
|
}
|
|
1302
1355
|
|
|
1303
|
-
|
|
1356
|
+
/// Writing direction the empty list line resolves to, for mirroring its marker.
|
|
1357
|
+
/// An empty line has no strong character, so Auto and FirstStrong fall back the
|
|
1358
|
+
/// same way the formatter's direction pass would.
|
|
1359
|
+
- (BOOL)emptyListLineIsRTL
|
|
1304
1360
|
{
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1361
|
+
switch (_writingDirectionMode) {
|
|
1362
|
+
case ENRMWritingDirectionModeLTR:
|
|
1363
|
+
return NO;
|
|
1364
|
+
case ENRMWritingDirectionModeRTL:
|
|
1365
|
+
return YES;
|
|
1366
|
+
case ENRMWritingDirectionModeFirstStrong:
|
|
1367
|
+
return _resolvedLayoutDirection == NSWritingDirectionRightToLeft;
|
|
1368
|
+
case ENRMWritingDirectionModeAuto:
|
|
1369
|
+
default:
|
|
1370
|
+
return ENRMParagraphIsRTL(nil);
|
|
1314
1371
|
}
|
|
1315
|
-
copyItemsToPasteboard(items);
|
|
1316
1372
|
}
|
|
1317
1373
|
|
|
1318
|
-
- (
|
|
1374
|
+
- (NSInteger)headingLevelForCursorParagraph
|
|
1319
1375
|
{
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
NSString *markdown = [ENRMMarkdownSerializer serializeAttributedString:_textView.textStorage
|
|
1325
|
-
ranges:[self allRangesIncludingTransient]];
|
|
1326
|
-
emitter->onRequestMarkdownResult({
|
|
1327
|
-
.requestId = static_cast<int>(requestId),
|
|
1328
|
-
.markdown = std::string([markdown UTF8String] ?: ""),
|
|
1329
|
-
});
|
|
1376
|
+
return [_blockCoordinator headingLevelAtPosition:_textView.selectedRange.location
|
|
1377
|
+
inText:_textView.textStorage.string];
|
|
1330
1378
|
}
|
|
1331
1379
|
|
|
1332
|
-
- (
|
|
1380
|
+
- (NSString *)adjustStoresForEditAtLocation:(NSUInteger)editLocation
|
|
1381
|
+
deletedLength:(NSUInteger)deletedLength
|
|
1382
|
+
insertedLength:(NSUInteger)insertedLength
|
|
1333
1383
|
{
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
if (selectedRange != nil) {
|
|
1338
|
-
caretRect = [_textView caretRectForPosition:selectedRange.start];
|
|
1339
|
-
}
|
|
1340
|
-
#else
|
|
1341
|
-
NSRange selection = _textView.selectedRange;
|
|
1342
|
-
if (selection.location != NSNotFound) {
|
|
1343
|
-
NSRange glyphRange = [_textView.layoutManager glyphRangeForCharacterRange:NSMakeRange(selection.location, 0)
|
|
1344
|
-
actualCharacterRange:NULL];
|
|
1345
|
-
caretRect = [_textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:_textView.textContainer];
|
|
1346
|
-
caretRect.origin.x += _textView.textContainerInset.left;
|
|
1347
|
-
caretRect.origin.y += _textView.textContainerInset.top;
|
|
1348
|
-
}
|
|
1349
|
-
#endif
|
|
1350
|
-
return caretRect;
|
|
1384
|
+
return [_editPipeline adjustStoresForEditAtLocation:editLocation
|
|
1385
|
+
deletedLength:deletedLength
|
|
1386
|
+
insertedLength:insertedLength];
|
|
1351
1387
|
}
|
|
1352
1388
|
|
|
1353
|
-
- (void)
|
|
1389
|
+
- (void)setLink:(NSString *)url
|
|
1354
1390
|
{
|
|
1355
|
-
|
|
1356
|
-
if (
|
|
1391
|
+
NSRange selection = _textView.selectedRange;
|
|
1392
|
+
if (![_linkCoordinator setLinkURL:url atCursor:selection.location selection:selection]) {
|
|
1357
1393
|
return;
|
|
1358
1394
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
emitter->onRequestCaretRectResult({
|
|
1362
|
-
.requestId = static_cast<int>(requestId),
|
|
1363
|
-
.x = caretRect.origin.x,
|
|
1364
|
-
.y = caretRect.origin.y,
|
|
1365
|
-
.width = caretRect.size.width,
|
|
1366
|
-
.height = caretRect.size.height,
|
|
1367
|
-
});
|
|
1395
|
+
[self applyFormatting];
|
|
1396
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1368
1397
|
}
|
|
1369
1398
|
|
|
1370
|
-
- (void)
|
|
1399
|
+
- (void)insertLink:(NSString *)text url:(NSString *)url
|
|
1371
1400
|
{
|
|
1372
|
-
|
|
1401
|
+
NSString *displayText = text.length > 0 ? text : url;
|
|
1402
|
+
NSRange linkRange = NSMakeRange(0, displayText.length);
|
|
1403
|
+
ENRMFormattingRange *range = [ENRMFormattingRange rangeWithType:ENRMInputStyleTypeLink
|
|
1404
|
+
range:linkRange
|
|
1405
|
+
url:[_linkCoordinator sanitizeURL:url]];
|
|
1406
|
+
[self replaceSelectedTextWith:displayText formattingRanges:@[ range ]];
|
|
1373
1407
|
}
|
|
1374
1408
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
- (BOOL)isEffectiveStyleActive:(ENRMInputStyleType)type atPosition:(NSUInteger)position
|
|
1409
|
+
- (void)insertText:(NSString *)text
|
|
1378
1410
|
{
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if ([_pendingStyleRemovals containsObject:key]) {
|
|
1382
|
-
return NO;
|
|
1383
|
-
}
|
|
1384
|
-
if ([_pendingStyles containsObject:key]) {
|
|
1385
|
-
return YES;
|
|
1411
|
+
if (text.length == 0) {
|
|
1412
|
+
return;
|
|
1386
1413
|
}
|
|
1387
|
-
|
|
1414
|
+
[self pasteMarkdown:text];
|
|
1388
1415
|
}
|
|
1389
1416
|
|
|
1390
|
-
- (void)
|
|
1417
|
+
- (void)startMention:(NSString *)indicator
|
|
1391
1418
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
traits |= UIFontDescriptorTraitBold;
|
|
1395
|
-
}
|
|
1396
|
-
if ([_pendingStyles containsObject:@(ENRMInputStyleTypeEmphasis)]) {
|
|
1397
|
-
traits |= UIFontDescriptorTraitItalic;
|
|
1419
|
+
if (indicator.length == 0 || ![_mentionCoordinator containsIndicator:indicator]) {
|
|
1420
|
+
return;
|
|
1398
1421
|
}
|
|
1399
1422
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
_textView.typingAttributes = attrs;
|
|
1423
|
+
[self replaceSelectedTextWith:indicator formattingRanges:@[]];
|
|
1424
|
+
[self dispatchMentionUpdate];
|
|
1403
1425
|
}
|
|
1404
1426
|
|
|
1405
|
-
- (void)
|
|
1427
|
+
- (void)insertMention:(NSString *)displayText url:(NSString *)url
|
|
1406
1428
|
{
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
static const CFTimeInterval kPostEditGracePeriod = 0.1;
|
|
1410
|
-
BOOL isPostEditAdjustment =
|
|
1411
|
-
(_lastTextChangeTime > 0 && (CACurrentMediaTime() - _lastTextChangeTime) < kPostEditGracePeriod);
|
|
1412
|
-
if (isPostEditAdjustment) {
|
|
1429
|
+
if (displayText.length == 0 || !_mentionCoordinator.isActive ||
|
|
1430
|
+
_mentionCoordinator.activeRange.location == NSNotFound) {
|
|
1413
1431
|
return;
|
|
1414
1432
|
}
|
|
1415
|
-
[_pendingStyles removeAllObjects];
|
|
1416
|
-
[_pendingStyleRemovals removeAllObjects];
|
|
1417
|
-
[self rebuildPendingStylesFromContext];
|
|
1418
|
-
[self syncTypingAttributesWithPendingStyles];
|
|
1419
|
-
}
|
|
1420
1433
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
NSUInteger
|
|
1424
|
-
if (
|
|
1434
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1435
|
+
NSRange activeRange = _mentionCoordinator.activeRange;
|
|
1436
|
+
NSUInteger rangeEnd = NSMaxRange(activeRange);
|
|
1437
|
+
if (rangeEnd > plainText.length) {
|
|
1425
1438
|
return;
|
|
1426
1439
|
}
|
|
1440
|
+
BOOL nextCharIsWhitespace = rangeEnd < plainText.length && [[NSCharacterSet whitespaceAndNewlineCharacterSet]
|
|
1441
|
+
characterIsMember:[plainText
|
|
1442
|
+
characterAtIndex:rangeEnd]];
|
|
1443
|
+
NSString *replacement = nextCharIsWhitespace ? displayText : [displayText stringByAppendingString:@" "];
|
|
1444
|
+
ENRMFormattingRange *linkRange = [ENRMFormattingRange rangeWithType:ENRMInputStyleTypeLink
|
|
1445
|
+
range:NSMakeRange(0, displayText.length)
|
|
1446
|
+
url:[_linkCoordinator sanitizeURL:url]];
|
|
1447
|
+
NSString *indicator = _mentionCoordinator.activeIndicator;
|
|
1427
1448
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
for (NSUInteger i = 0; i < sizeof(inlineStyles) / sizeof(inlineStyles[0]); i++) {
|
|
1434
|
-
ENRMInputStyleType type = inlineStyles[i];
|
|
1435
|
-
if ([_formattingStore isStyleAdjacentBefore:type position:cursor]) {
|
|
1436
|
-
[_pendingStyles addObject:@(type)];
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1449
|
+
[self dispatchMentionEvents:[_mentionCoordinator clearWithIndicatorOverride:indicator]];
|
|
1450
|
+
[self replaceTextInRange:activeRange withText:replacement formattingRanges:@[ linkRange ]];
|
|
1451
|
+
_textView.selectedRange = NSMakeRange(activeRange.location + replacement.length, 0);
|
|
1452
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
1439
1453
|
}
|
|
1440
1454
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
- (void)emitFormattingChanged
|
|
1455
|
+
- (void)removeLink
|
|
1444
1456
|
{
|
|
1445
|
-
[
|
|
1446
|
-
|
|
1447
|
-
[self emitOnChangeMarkdown];
|
|
1457
|
+
if (![_linkCoordinator removeLinkAtPosition:_textView.selectedRange.location]) {
|
|
1458
|
+
return;
|
|
1448
1459
|
}
|
|
1460
|
+
[self applyFormatting];
|
|
1461
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1449
1462
|
}
|
|
1450
1463
|
|
|
1451
|
-
-
|
|
1464
|
+
#pragma mark - Math chip commands
|
|
1465
|
+
|
|
1466
|
+
// A chip is one U+FFFC character in the buffer plus an ENRMMathInlineAttachment
|
|
1467
|
+
// carrying the LaTeX (see ENRMMathChipController). Every text mutation below goes
|
|
1468
|
+
// through the same replaceTextInRange/EditPhase machinery as links and mentions —
|
|
1469
|
+
// a chip is an atomic inline character, not a block, so it needs no ENRMBlockHandler
|
|
1470
|
+
// and no phase of its own.
|
|
1471
|
+
//
|
|
1472
|
+
// No ENRICHED_MARKDOWN_MATH guard here on purpose: codegen declares these three on
|
|
1473
|
+
// RCTEnrichedMarkdownTextInputViewProtocol regardless of the math flag, and
|
|
1474
|
+
// RCTEnrichedMarkdownTextInputHandleCommand sends the selector unconditionally, so
|
|
1475
|
+
// conformance must hold in math-disabled builds. The controller is inert there.
|
|
1476
|
+
|
|
1477
|
+
- (void)insertMath:(NSString *)latex displayMode:(BOOL)displayMode
|
|
1452
1478
|
{
|
|
1453
|
-
if (
|
|
1454
|
-
return
|
|
1479
|
+
if (latex.length == 0 || !ENRMMathChipController.isAvailable) {
|
|
1480
|
+
return;
|
|
1455
1481
|
}
|
|
1456
|
-
return std::static_pointer_cast<EnrichedMarkdownTextInputEventEmitter const>(_eventEmitter);
|
|
1457
|
-
}
|
|
1458
1482
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
return _formattingStore.allRanges;
|
|
1483
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1484
|
+
NSRange selection = _textView.selectedRange;
|
|
1485
|
+
if (NSMaxRange(selection) > plainText.length) {
|
|
1486
|
+
return;
|
|
1464
1487
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1488
|
+
|
|
1489
|
+
// A block chip claims the full row, so it needs a line of its own: add the
|
|
1490
|
+
// separators the caret position is missing rather than pulling the surrounding
|
|
1491
|
+
// text onto the chip's line.
|
|
1492
|
+
BOOL needsLeadingBreak =
|
|
1493
|
+
displayMode && selection.location > 0 && [plainText characterAtIndex:selection.location - 1] != '\n';
|
|
1494
|
+
NSUInteger afterSelection = NSMaxRange(selection);
|
|
1495
|
+
BOOL needsTrailingBreak =
|
|
1496
|
+
displayMode && afterSelection < plainText.length && [plainText characterAtIndex:afterSelection] != '\n';
|
|
1497
|
+
|
|
1498
|
+
NSString *prefix = needsLeadingBreak ? @"\n" : @"";
|
|
1499
|
+
NSString *suffix = needsTrailingBreak ? @"\n" : @"";
|
|
1500
|
+
NSString *replacement = [NSString stringWithFormat:@"%@%@%@", prefix, ENRMObjectReplacementString(), suffix];
|
|
1501
|
+
NSUInteger chipPosition = selection.location + prefix.length;
|
|
1502
|
+
|
|
1503
|
+
// Plain text first, chip second. Going through replaceTextInRange: is what gets
|
|
1504
|
+
// the formatting store, the block store, orphaned-anchor pruning and the
|
|
1505
|
+
// detectors adjusted for the edit; stamping the attachment afterwards only adds
|
|
1506
|
+
// an attribute, so none of that bookkeeping sees a second edit.
|
|
1507
|
+
[self replaceTextInRange:selection withText:replacement formattingRanges:@[]];
|
|
1508
|
+
|
|
1509
|
+
ENRMMathAtom *atom = [[ENRMMathAtom alloc] init];
|
|
1510
|
+
atom.position = chipPosition;
|
|
1511
|
+
atom.latex = latex;
|
|
1512
|
+
atom.displayMode = displayMode;
|
|
1513
|
+
[_mathChipController applyAtoms:@[ atom ] offset:0];
|
|
1514
|
+
|
|
1515
|
+
NSUInteger caret = selection.location + replacement.length;
|
|
1516
|
+
_textView.selectedRange = NSMakeRange(MIN(caret, ENRMGetPlainText(_textView).length), 0);
|
|
1517
|
+
|
|
1518
|
+
// replaceTextInRange: already emitted text/selection/formatting for the edit, but
|
|
1519
|
+
// the caret moved after it and the chip attribute changed the measured height.
|
|
1520
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
1521
|
+
[self requestHeightUpdate];
|
|
1522
|
+
[self scheduleRelayoutIfNeeded];
|
|
1468
1523
|
}
|
|
1469
1524
|
|
|
1470
|
-
- (void)
|
|
1525
|
+
- (void)updateMath:(NSInteger)start latex:(NSString *)latex displayMode:(BOOL)displayMode
|
|
1471
1526
|
{
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1527
|
+
if (start < 0) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
// Formula and mode live on the attachment, so this changes no text: no store
|
|
1531
|
+
// adjustment and no edit phase are needed, only a re-render and a re-measure.
|
|
1532
|
+
if (![_mathChipController updateChipAtPosition:(NSUInteger)start latex:latex displayMode:displayMode]) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1476
1535
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1536
|
+
// Gated like every other producer: `emitOnChangeMarkdown` is not self-guarding
|
|
1537
|
+
// (the check lives at each call site, e.g. `emitFormattingChanged`), and it
|
|
1538
|
+
// serializes the whole document — firing it for an app that never set
|
|
1539
|
+
// `onChangeMarkdown` would be pure cost. Android gates the same way.
|
|
1540
|
+
if (_inputEventEmitter.emitMarkdown) {
|
|
1541
|
+
[_inputEventEmitter emitOnChangeMarkdown];
|
|
1479
1542
|
}
|
|
1543
|
+
[self requestHeightUpdate];
|
|
1544
|
+
[self scheduleRelayoutIfNeeded];
|
|
1480
1545
|
}
|
|
1481
1546
|
|
|
1482
|
-
- (
|
|
1547
|
+
- (void)removeMath:(NSInteger)start
|
|
1483
1548
|
{
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
return nil;
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1490
|
-
NSUInteger cursor = selectedRange.location;
|
|
1491
|
-
if (cursor > plainText.length) {
|
|
1492
|
-
return nil;
|
|
1549
|
+
if (start < 0 || [_mathChipController atomAtPosition:(NSUInteger)start] == nil) {
|
|
1550
|
+
return;
|
|
1493
1551
|
}
|
|
1494
1552
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
matchedIndicator = indicator;
|
|
1501
|
-
break;
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
if (matchedIndicator == nil) {
|
|
1505
|
-
return nil;
|
|
1506
|
-
}
|
|
1553
|
+
// Deleting the character takes the attachment and its paragraph style with it,
|
|
1554
|
+
// so the ordinary text path is enough — no controller-side cleanup like Android
|
|
1555
|
+
// needs for its separate AlignmentSpan, and replaceTextInRange: already emits
|
|
1556
|
+
// the events and re-requests the height.
|
|
1557
|
+
[self replaceTextInRange:NSMakeRange((NSUInteger)start, 1) withText:@"" formattingRanges:@[]];
|
|
1507
1558
|
|
|
1508
|
-
if (
|
|
1509
|
-
|
|
1559
|
+
if (_activeMathChipPosition == (NSUInteger)start) {
|
|
1560
|
+
_activeMathChipPosition = NSNotFound;
|
|
1510
1561
|
}
|
|
1562
|
+
}
|
|
1511
1563
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1564
|
+
/// Chips in document order, as plain data for the serializer. Every path that
|
|
1565
|
+
/// serializes the live buffer must pass this, or each formula leaves the editor as
|
|
1566
|
+
/// a bare placeholder character instead of `$latex$`.
|
|
1567
|
+
- (NSArray<ENRMMathAtom *> *)mathAtomsForSerialization
|
|
1568
|
+
{
|
|
1569
|
+
return [_mathChipController allAtoms];
|
|
1516
1570
|
}
|
|
1517
1571
|
|
|
1518
|
-
|
|
1572
|
+
#if !TARGET_OS_OSX
|
|
1573
|
+
|
|
1574
|
+
- (void)handleMathChipTap:(UITapGestureRecognizer *)gesture
|
|
1519
1575
|
{
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1576
|
+
if (gesture.state != UIGestureRecognizerStateRecognized) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
CGPoint location = [gesture locationInView:_textView];
|
|
1580
|
+
NSUInteger chipPosition = [_mathChipController chipPositionAtPoint:location];
|
|
1581
|
+
if (chipPosition == NSNotFound) {
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
ENRMMathAtom *atom = [_mathChipController atomAtPosition:chipPosition];
|
|
1585
|
+
if (atom == nil) {
|
|
1523
1586
|
return;
|
|
1524
1587
|
}
|
|
1525
1588
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
NSUInteger end = candidate.end;
|
|
1529
|
-
NSString *query = candidate.text;
|
|
1589
|
+
[_mathChipController clearSelectionsExceptPosition:chipPosition];
|
|
1590
|
+
[_mathChipController setSelected:YES atPosition:chipPosition];
|
|
1530
1591
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
_activeMentionRange = NSMakeRange(start, end - start);
|
|
1592
|
+
// Set before moving the caret: textViewDidChangeSelection: reads both to decide
|
|
1593
|
+
// whether the highlight it is about to clear was set by this very tap.
|
|
1594
|
+
_activeMathChipPosition = chipPosition;
|
|
1595
|
+
_suppressMathChipClearOnce = YES;
|
|
1596
|
+
// Caret after the chip, not a length-1 selection: a selection would draw the
|
|
1597
|
+
// system's blue range handles on top of the chip's own selected styling.
|
|
1598
|
+
NSUInteger caret = MIN(chipPosition + 1, ENRMGetPlainText(_textView).length);
|
|
1599
|
+
_textView.selectedRange = NSMakeRange(caret, 0);
|
|
1540
1600
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1601
|
+
// Recomputed rather than reused from the hit-test: selecting the chip
|
|
1602
|
+
// invalidated its layout above.
|
|
1603
|
+
CGRect glyphRect = [_mathChipController chipRectAtPosition:chipPosition];
|
|
1604
|
+
CGRect rectInSelf =
|
|
1605
|
+
CGRectIsNull(glyphRect) ? CGRectZero : [self.contentView convertRect:glyphRect fromView:_textView];
|
|
1606
|
+
|
|
1607
|
+
[_inputEventEmitter emitOnMathPressWithLatex:atom.latex
|
|
1608
|
+
range:NSMakeRange(chipPosition, 1)
|
|
1609
|
+
displayMode:atom.displayMode
|
|
1610
|
+
rect:rectInSelf];
|
|
1545
1611
|
}
|
|
1546
1612
|
|
|
1547
|
-
|
|
1613
|
+
#endif
|
|
1614
|
+
|
|
1615
|
+
#pragma mark -
|
|
1616
|
+
|
|
1617
|
+
- (void)showLinkPrompt
|
|
1548
1618
|
{
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
}
|
|
1619
|
+
ENRMFormattingRange *activeLink = [_linkCoordinator linkAtPosition:_textView.selectedRange.location];
|
|
1620
|
+
NSString *existingURL = activeLink != nil ? activeLink.url : nil;
|
|
1552
1621
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
} else if (range.location > 0) {
|
|
1557
|
-
lookupPosition = range.location - 1;
|
|
1558
|
-
} else {
|
|
1559
|
-
return NO;
|
|
1560
|
-
}
|
|
1622
|
+
__weak EnrichedMarkdownTextInput *weakSelf = self;
|
|
1623
|
+
ENRMShowLinkPrompt(self, existingURL, ^(NSString *url) { [weakSelf setLink:url]; });
|
|
1624
|
+
}
|
|
1561
1625
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1626
|
+
/// Serializes plain text with inline + block formatting, resolving each block's
|
|
1627
|
+
/// markdown line prefix through its registered handler. With no block handlers
|
|
1628
|
+
/// registered the prefix provider is never consulted productively and output
|
|
1629
|
+
/// equals the inline-only serialization.
|
|
1630
|
+
- (NSString *)serializeText:(NSString *)text
|
|
1631
|
+
ranges:(NSArray<ENRMFormattingRange *> *)ranges
|
|
1632
|
+
blockRanges:(NSArray<ENRMBlockRange *> *)blockRanges
|
|
1633
|
+
{
|
|
1634
|
+
return [_clipboardCoordinator serializeText:text
|
|
1635
|
+
mathAtoms:[self mathAtomsForSerialization]
|
|
1636
|
+
ranges:ranges
|
|
1637
|
+
blockRanges:blockRanges];
|
|
1638
|
+
}
|
|
1567
1639
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
return
|
|
1640
|
+
- (nullable NSString *)markdownForSelectedRange
|
|
1641
|
+
{
|
|
1642
|
+
return [_clipboardCoordinator serializeSelectedRange:_textView.selectedRange
|
|
1643
|
+
inText:ENRMGetPlainText(_textView)
|
|
1644
|
+
mathAtoms:[self mathAtomsForSerialization]];
|
|
1571
1645
|
}
|
|
1572
1646
|
|
|
1573
|
-
- (void)
|
|
1647
|
+
- (void)copyToClipboard
|
|
1574
1648
|
{
|
|
1575
|
-
|
|
1576
|
-
if (
|
|
1649
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1650
|
+
if (plainText.length == 0) {
|
|
1577
1651
|
return;
|
|
1578
1652
|
}
|
|
1579
|
-
NSString *
|
|
1580
|
-
|
|
1653
|
+
NSString *markdown = [_clipboardCoordinator serializeFullDocument:plainText
|
|
1654
|
+
mathAtoms:[self mathAtomsForSerialization]];
|
|
1655
|
+
NSMutableDictionary *items = [NSMutableDictionary dictionary];
|
|
1656
|
+
items[kUTIPlainText] = plainText;
|
|
1657
|
+
if (markdown.length > 0) {
|
|
1658
|
+
items[kENRMMarkdownPasteboardType] = markdown;
|
|
1659
|
+
}
|
|
1660
|
+
copyItemsToPasteboard(items);
|
|
1581
1661
|
}
|
|
1582
1662
|
|
|
1583
|
-
- (void)
|
|
1663
|
+
- (void)requestMarkdown:(NSInteger)requestId
|
|
1584
1664
|
{
|
|
1585
|
-
|
|
1586
|
-
if (emitter == nullptr) {
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
NSString *markdown = [ENRMMarkdownSerializer serializeAttributedString:_textView.textStorage
|
|
1590
|
-
ranges:[self allRangesIncludingTransient]];
|
|
1591
|
-
emitter->onChangeMarkdown({.value = std::string([markdown UTF8String] ?: "")});
|
|
1665
|
+
[_inputEventEmitter requestMarkdown:requestId];
|
|
1592
1666
|
}
|
|
1593
1667
|
|
|
1594
|
-
- (
|
|
1668
|
+
- (CGRect)computeCaretRect
|
|
1595
1669
|
{
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1670
|
+
CGRect caretRect = CGRectZero;
|
|
1671
|
+
#if !TARGET_OS_OSX
|
|
1672
|
+
UITextRange *selectedRange = _textView.selectedTextRange;
|
|
1673
|
+
if (selectedRange != nil) {
|
|
1674
|
+
caretRect = [_textView caretRectForPosition:selectedRange.start];
|
|
1599
1675
|
}
|
|
1676
|
+
#else
|
|
1600
1677
|
NSRange selection = _textView.selectedRange;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1678
|
+
if (selection.location != NSNotFound) {
|
|
1679
|
+
NSRange glyphRange = [_textView.layoutManager glyphRangeForCharacterRange:NSMakeRange(selection.location, 0)
|
|
1680
|
+
actualCharacterRange:NULL];
|
|
1681
|
+
caretRect = [_textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:_textView.textContainer];
|
|
1682
|
+
caretRect.origin.x += _textView.textContainerInset.left;
|
|
1683
|
+
caretRect.origin.y += _textView.textContainerInset.top;
|
|
1684
|
+
}
|
|
1685
|
+
#endif
|
|
1686
|
+
return caretRect;
|
|
1605
1687
|
}
|
|
1606
1688
|
|
|
1607
|
-
- (void)
|
|
1689
|
+
- (void)requestCaretRect:(NSInteger)requestId
|
|
1608
1690
|
{
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1691
|
+
[_inputEventEmitter requestCaretRect:requestId];
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
|
|
1695
|
+
{
|
|
1696
|
+
RCTEnrichedMarkdownTextInputHandleCommand(self, commandName, args);
|
|
1697
|
+
}
|
|
1613
1698
|
|
|
1614
|
-
|
|
1615
|
-
BOOL boldActive = [self isEffectiveStyleActive:ENRMInputStyleTypeStrong atPosition:cursor];
|
|
1616
|
-
BOOL italicActive = [self isEffectiveStyleActive:ENRMInputStyleTypeEmphasis atPosition:cursor];
|
|
1617
|
-
BOOL underlineActive = [self isEffectiveStyleActive:ENRMInputStyleTypeUnderline atPosition:cursor];
|
|
1618
|
-
BOOL strikethroughActive = [self isEffectiveStyleActive:ENRMInputStyleTypeStrikethrough atPosition:cursor];
|
|
1619
|
-
BOOL spoilerActive = [self isEffectiveStyleActive:ENRMInputStyleTypeSpoiler atPosition:cursor];
|
|
1620
|
-
BOOL linkActive = [self isEffectiveStyleActive:ENRMInputStyleTypeLink atPosition:cursor];
|
|
1699
|
+
#pragma mark - ENRMInputEventEmitterDataSource
|
|
1621
1700
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
return;
|
|
1701
|
+
- (std::shared_ptr<EnrichedMarkdownTextInputEventEmitter const>)fabricEventEmitter
|
|
1702
|
+
{
|
|
1703
|
+
if (_eventEmitter == nullptr || _editSession.shouldSuppressEvents) {
|
|
1704
|
+
return nullptr;
|
|
1626
1705
|
}
|
|
1706
|
+
return std::static_pointer_cast<EnrichedMarkdownTextInputEventEmitter const>(_eventEmitter);
|
|
1707
|
+
}
|
|
1627
1708
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
_prevState.strikethrough = strikethroughActive;
|
|
1632
|
-
_prevState.spoiler = spoilerActive;
|
|
1633
|
-
_prevState.link = linkActive;
|
|
1634
|
-
_prevState.initialized = YES;
|
|
1635
|
-
|
|
1636
|
-
emitter->onChangeState({
|
|
1637
|
-
.bold = {.isActive = boldActive},
|
|
1638
|
-
.italic = {.isActive = italicActive},
|
|
1639
|
-
.underline = {.isActive = underlineActive},
|
|
1640
|
-
.strikethrough = {.isActive = strikethroughActive},
|
|
1641
|
-
.spoiler = {.isActive = spoilerActive},
|
|
1642
|
-
.link = {.isActive = linkActive},
|
|
1643
|
-
});
|
|
1709
|
+
- (NSRange)selectedRange
|
|
1710
|
+
{
|
|
1711
|
+
return _textView.selectedRange;
|
|
1644
1712
|
}
|
|
1645
1713
|
|
|
1646
|
-
- (
|
|
1714
|
+
- (BOOL)isStyleActive:(ENRMInputStyleType)type inRange:(NSRange)range
|
|
1647
1715
|
{
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1716
|
+
return [_formattingStore isStyleActive:type inRange:range];
|
|
1717
|
+
}
|
|
1652
1718
|
|
|
1653
|
-
|
|
1719
|
+
- (BOOL)isEffectiveStyleActive:(ENRMInputStyleType)type atPosition:(NSUInteger)position
|
|
1720
|
+
{
|
|
1721
|
+
return [_typingController isEffectiveStyleActive:type atPosition:position];
|
|
1722
|
+
}
|
|
1654
1723
|
|
|
1655
|
-
|
|
1656
|
-
return;
|
|
1657
|
-
}
|
|
1724
|
+
#pragma mark - ENRMInputTypingAttributesDataSource
|
|
1658
1725
|
|
|
1659
|
-
|
|
1726
|
+
- (BOOL)isStyleAdjacentBefore:(ENRMInputStyleType)type position:(NSUInteger)position
|
|
1727
|
+
{
|
|
1728
|
+
return [_formattingStore isStyleAdjacentBefore:type position:position];
|
|
1729
|
+
}
|
|
1660
1730
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
.width = caretRect.size.width,
|
|
1665
|
-
.height = caretRect.size.height,
|
|
1666
|
-
});
|
|
1731
|
+
- (BOOL)isStyleActive:(ENRMInputStyleType)type atPosition:(NSUInteger)position
|
|
1732
|
+
{
|
|
1733
|
+
return [_formattingStore isStyleActive:type atPosition:position];
|
|
1667
1734
|
}
|
|
1668
1735
|
|
|
1669
|
-
- (
|
|
1736
|
+
- (NSString *)currentMarkdown
|
|
1670
1737
|
{
|
|
1671
|
-
return
|
|
1738
|
+
return [self serializeText:ENRMGetPlainText(_textView)
|
|
1739
|
+
ranges:[self allRangesIncludingTransient]
|
|
1740
|
+
blockRanges:_blockStore.allRanges];
|
|
1672
1741
|
}
|
|
1673
1742
|
|
|
1674
|
-
- (NSArray<
|
|
1743
|
+
- (NSArray<ENRMFormattingRange *> *)allRangesIncludingTransient
|
|
1675
1744
|
{
|
|
1676
|
-
return
|
|
1745
|
+
return [_clipboardCoordinator allRangesIncludingTransient];
|
|
1677
1746
|
}
|
|
1678
1747
|
|
|
1679
|
-
- (
|
|
1748
|
+
- (void)updateActiveMention
|
|
1680
1749
|
{
|
|
1681
|
-
|
|
1750
|
+
[self dispatchMentionUpdate];
|
|
1682
1751
|
}
|
|
1683
1752
|
|
|
1684
|
-
- (
|
|
1753
|
+
- (void)clearActiveMention:(nullable NSString *)indicatorOverride
|
|
1685
1754
|
{
|
|
1686
|
-
|
|
1755
|
+
[self dispatchMentionEvents:[_mentionCoordinator clearWithIndicatorOverride:indicatorOverride]];
|
|
1687
1756
|
}
|
|
1688
1757
|
|
|
1689
|
-
- (void)
|
|
1758
|
+
- (void)dispatchMentionUpdate
|
|
1690
1759
|
{
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1760
|
+
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1761
|
+
[self dispatchMentionEvents:[_mentionCoordinator updateWithText:plainText selectedRange:_textView.selectedRange]];
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
- (void)dispatchMentionEvents:(NSArray<ENRMMentionEvent *> *)events
|
|
1765
|
+
{
|
|
1766
|
+
for (ENRMMentionEvent *event in events) {
|
|
1767
|
+
switch (event.type) {
|
|
1768
|
+
case ENRMMentionEventStart:
|
|
1769
|
+
[_inputEventEmitter emitOnStartMention:event.indicator];
|
|
1770
|
+
break;
|
|
1771
|
+
case ENRMMentionEventChange:
|
|
1772
|
+
[_inputEventEmitter emitOnChangeMentionWithIndicator:event.indicator text:event.text];
|
|
1773
|
+
break;
|
|
1774
|
+
case ENRMMentionEventEnd:
|
|
1775
|
+
[_inputEventEmitter emitOnEndMention:event.indicator];
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1694
1778
|
}
|
|
1779
|
+
}
|
|
1695
1780
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1781
|
+
- (BOOL)deleteLinkForReplacementRange:(NSRange)range replacementText:(NSString *)text
|
|
1782
|
+
{
|
|
1783
|
+
if (text.length > 0) {
|
|
1784
|
+
return NO;
|
|
1785
|
+
}
|
|
1699
1786
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
}
|
|
1787
|
+
NSUInteger lookupPosition;
|
|
1788
|
+
if (range.length > 0) {
|
|
1789
|
+
lookupPosition = range.location;
|
|
1790
|
+
} else if (range.location > 0) {
|
|
1791
|
+
lookupPosition = range.location - 1;
|
|
1792
|
+
} else {
|
|
1793
|
+
return NO;
|
|
1794
|
+
}
|
|
1706
1795
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
.itemText = std::string(itemText.UTF8String),
|
|
1716
|
-
.selectedText = std::string(selectedText.UTF8String),
|
|
1717
|
-
.selectionStart = static_cast<int>(selectedRange.location),
|
|
1718
|
-
.selectionEnd = static_cast<int>(NSMaxRange(selectedRange)),
|
|
1719
|
-
.styleState =
|
|
1720
|
-
{
|
|
1721
|
-
.bold = {.isActive = boldActive},
|
|
1722
|
-
.italic = {.isActive = italicActive},
|
|
1723
|
-
.underline = {.isActive = underlineActive},
|
|
1724
|
-
.strikethrough = {.isActive = strikethroughActive},
|
|
1725
|
-
.spoiler = {.isActive = spoilerActive},
|
|
1726
|
-
.link = {.isActive = linkActive},
|
|
1727
|
-
},
|
|
1728
|
-
});
|
|
1796
|
+
ENRMFormattingRange *linkRange = [_linkCoordinator linkAtPosition:lookupPosition];
|
|
1797
|
+
if (linkRange == nil) {
|
|
1798
|
+
return NO;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
[self replaceTextInRange:linkRange.range withText:@"" formattingRanges:@[]];
|
|
1802
|
+
[self clearActiveMention:nil];
|
|
1803
|
+
return YES;
|
|
1729
1804
|
}
|
|
1730
1805
|
|
|
1731
|
-
- (
|
|
1806
|
+
- (NSArray<NSString *> *)contextMenuItemTexts
|
|
1732
1807
|
{
|
|
1733
|
-
|
|
1734
|
-
if (emitter == nullptr) {
|
|
1735
|
-
return;
|
|
1736
|
-
}
|
|
1737
|
-
emitter->onInputFocus({});
|
|
1808
|
+
return _contextMenuItemTexts ?: @[];
|
|
1738
1809
|
}
|
|
1739
1810
|
|
|
1740
|
-
- (
|
|
1811
|
+
- (NSArray<NSString *> *)contextMenuItemIcons
|
|
1741
1812
|
{
|
|
1742
|
-
|
|
1743
|
-
if (emitter == nullptr) {
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1746
|
-
emitter->onInputBlur({});
|
|
1813
|
+
return _contextMenuItemIcons ?: @[];
|
|
1747
1814
|
}
|
|
1748
1815
|
|
|
1749
|
-
- (
|
|
1816
|
+
- (ENRMInputSelectionMenuConfig)inputSelectionMenuConfig
|
|
1750
1817
|
{
|
|
1751
|
-
|
|
1752
|
-
if (emitter == nullptr) {
|
|
1753
|
-
return;
|
|
1754
|
-
}
|
|
1755
|
-
emitter->onLinkDetected({
|
|
1756
|
-
.text = std::string([text UTF8String] ?: ""),
|
|
1757
|
-
.url = std::string([url UTF8String] ?: ""),
|
|
1758
|
-
.start = static_cast<int>(range.location),
|
|
1759
|
-
.end = static_cast<int>(range.location + range.length),
|
|
1760
|
-
});
|
|
1818
|
+
return _inputSelectionMenuConfig;
|
|
1761
1819
|
}
|
|
1762
1820
|
|
|
1763
|
-
- (
|
|
1821
|
+
- (ENRMFormatMenuConfig)formatMenuConfig
|
|
1764
1822
|
{
|
|
1765
|
-
|
|
1766
|
-
if (emitter == nullptr) {
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
emitter->onStartMention({.indicator = std::string([indicator UTF8String] ?: "")});
|
|
1823
|
+
return _formatMenuConfig;
|
|
1770
1824
|
}
|
|
1771
1825
|
|
|
1772
|
-
- (
|
|
1826
|
+
- (ENRMInputEventEmitter *)inputEventEmitter
|
|
1773
1827
|
{
|
|
1774
|
-
|
|
1775
|
-
if (emitter == nullptr) {
|
|
1776
|
-
return;
|
|
1777
|
-
}
|
|
1778
|
-
emitter->onChangeMention({
|
|
1779
|
-
.indicator = std::string([indicator UTF8String] ?: ""),
|
|
1780
|
-
.text = std::string([text UTF8String] ?: ""),
|
|
1781
|
-
});
|
|
1828
|
+
return _inputEventEmitter;
|
|
1782
1829
|
}
|
|
1783
1830
|
|
|
1784
|
-
- (
|
|
1831
|
+
- (ENRMInputTypingAttributesController *)typingController
|
|
1785
1832
|
{
|
|
1786
|
-
|
|
1787
|
-
if (emitter == nullptr) {
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
emitter->onEndMention({.indicator = std::string([indicator UTF8String] ?: "")});
|
|
1833
|
+
return _typingController;
|
|
1791
1834
|
}
|
|
1792
1835
|
|
|
1793
1836
|
#pragma mark - Text edit tracking
|
|
1794
1837
|
|
|
1795
1838
|
- (void)handleTextChanged
|
|
1796
1839
|
{
|
|
1797
|
-
if (
|
|
1840
|
+
if (_editSession.isComposing) {
|
|
1798
1841
|
return;
|
|
1799
1842
|
}
|
|
1800
1843
|
|
|
@@ -1824,64 +1867,63 @@ using namespace facebook::react;
|
|
|
1824
1867
|
}
|
|
1825
1868
|
}
|
|
1826
1869
|
|
|
1827
|
-
[
|
|
1870
|
+
ENRMEditContext *context = [[ENRMEditContext alloc] initWithEditLocation:editLocation
|
|
1871
|
+
deletedLength:deletedLength
|
|
1872
|
+
insertedLength:insertedLength
|
|
1873
|
+
preEditReplacedNewline:_preEditReplacedNewline
|
|
1874
|
+
preEditBlockType:_preEditBlockType
|
|
1875
|
+
preEditBlockLevel:_preEditBlockLevel
|
|
1876
|
+
preEditParagraphWasEmpty:_preEditParagraphWasEmpty
|
|
1877
|
+
pendingStyles:_typingController.pendingStyles
|
|
1878
|
+
pendingStyleRemovals:_typingController.pendingStyleRemovals];
|
|
1828
1879
|
|
|
1829
|
-
|
|
1830
|
-
NSRange insertedRange = NSMakeRange(editLocation, insertedLength);
|
|
1831
|
-
|
|
1832
|
-
// Skip applying pending styles when the insertion is only line breaks —
|
|
1833
|
-
// a phantom range over a bare newline corrupts isStyleActive() at the boundary.
|
|
1834
|
-
NSString *plainText = ENRMGetPlainText(_textView);
|
|
1835
|
-
NSUInteger insertedEnd = NSMaxRange(insertedRange);
|
|
1836
|
-
BOOL insertedHasGlyphContent = NO;
|
|
1837
|
-
if (insertedEnd <= plainText.length) {
|
|
1838
|
-
NSCharacterSet *newlines = [NSCharacterSet newlineCharacterSet];
|
|
1839
|
-
for (NSUInteger i = insertedRange.location; i < insertedEnd; i++) {
|
|
1840
|
-
if (![newlines characterIsMember:[plainText characterAtIndex:i]]) {
|
|
1841
|
-
insertedHasGlyphContent = YES;
|
|
1842
|
-
break;
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
if (insertedHasGlyphContent) {
|
|
1848
|
-
for (NSNumber *styleNum in _pendingStyles) {
|
|
1849
|
-
ENRMFormattingRange *newRange = [ENRMFormattingRange rangeWithType:(ENRMInputStyleType)styleNum.integerValue
|
|
1850
|
-
range:insertedRange];
|
|
1851
|
-
[_formattingStore addRange:newRange];
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1880
|
+
BOOL touchedNewline = [_editPipeline processTextChangeWithContext:context];
|
|
1854
1881
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
}
|
|
1882
|
+
// Consumed: clear pre-edit state so stale values can't leak to the next edit.
|
|
1883
|
+
_preEditBlockType = ENRMInputBlockTypeParagraph;
|
|
1884
|
+
_preEditBlockLevel = 0;
|
|
1885
|
+
_preEditParagraphWasEmpty = NO;
|
|
1886
|
+
_preEditReplacedNewline = NO;
|
|
1861
1887
|
|
|
1862
1888
|
_lastTextLength = newLength;
|
|
1863
1889
|
|
|
1864
1890
|
#if !TARGET_OS_OSX
|
|
1865
1891
|
if (newLength == 0) {
|
|
1866
|
-
[self
|
|
1892
|
+
if ([self headingLevelForCursorParagraph] > 0 || [self listBlockForCursorParagraph] != nil) {
|
|
1893
|
+
[_typingController syncWithCursorBlock];
|
|
1894
|
+
} else {
|
|
1895
|
+
[self resetBaseTypingAttributes];
|
|
1896
|
+
}
|
|
1867
1897
|
}
|
|
1868
1898
|
#endif
|
|
1869
1899
|
|
|
1870
|
-
|
|
1900
|
+
if (touchedNewline) {
|
|
1901
|
+
[self applyFormatting];
|
|
1902
|
+
} else {
|
|
1903
|
+
[self applyFormattingScopedToEditAtLocation:editLocation insertedLength:insertedLength];
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (_textView.selectedRange.length == 0) {
|
|
1907
|
+
[_typingController syncWithCursorBlock];
|
|
1908
|
+
}
|
|
1871
1909
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1910
|
+
if ([self listBlockForCursorParagraph] != nil) {
|
|
1911
|
+
[_typingController syncWithCursorBlock];
|
|
1912
|
+
} else if (_textView.typingAttributes[NSParagraphStyleAttributeName] != nil) {
|
|
1913
|
+
[_typingController clearListParagraphStyle];
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
[_editPipeline detectLinksAtLocation:editLocation insertedLength:insertedLength];
|
|
1876
1917
|
|
|
1877
1918
|
[self updatePlaceholderVisibility];
|
|
1878
|
-
[
|
|
1879
|
-
[
|
|
1880
|
-
[
|
|
1919
|
+
[_inputEventEmitter emitOnChangeText];
|
|
1920
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
1921
|
+
[_inputEventEmitter emitFormattingChanged];
|
|
1881
1922
|
[self updateActiveMention];
|
|
1882
|
-
[
|
|
1923
|
+
[_inputEventEmitter emitCaretRectChangeIfNeeded];
|
|
1883
1924
|
[self requestHeightUpdate];
|
|
1884
1925
|
[self scheduleRelayoutIfNeeded];
|
|
1926
|
+
[self updateEmptyBulletMarker];
|
|
1885
1927
|
}
|
|
1886
1928
|
|
|
1887
1929
|
#pragma mark - Text view delegate
|
|
@@ -1919,17 +1961,21 @@ using namespace facebook::react;
|
|
|
1919
1961
|
{
|
|
1920
1962
|
[self stripLinkTypingAttributes];
|
|
1921
1963
|
|
|
1922
|
-
if (_textView.selectedRange.length == 0 &&
|
|
1964
|
+
if (_textView.selectedRange.length == 0 && _editSession.phase == ENRMEditPhaseIdle) {
|
|
1923
1965
|
NSString *text = ENRMGetPlainText(_textView);
|
|
1924
1966
|
if (text.length > 0) {
|
|
1925
1967
|
NSRange paragraphRange = [text paragraphRangeForRange:_textView.selectedRange];
|
|
1926
1968
|
NSString *paragraphText = [text substringWithRange:paragraphRange];
|
|
1927
1969
|
BOOL isEmpty = paragraphText.length == 0 || [paragraphText isEqualToString:@"\n"];
|
|
1928
1970
|
if (isEmpty) {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1971
|
+
if ([self headingLevelForCursorParagraph] > 0 || [self listBlockForCursorParagraph] != nil) {
|
|
1972
|
+
[_typingController syncWithCursorBlock];
|
|
1973
|
+
} else {
|
|
1974
|
+
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
|
|
1975
|
+
attrs[NSFontAttributeName] = _formatterStyle.baseFont;
|
|
1976
|
+
attrs[NSForegroundColorAttributeName] = _formatterStyle.baseTextColor;
|
|
1977
|
+
_textView.typingAttributes = attrs;
|
|
1978
|
+
}
|
|
1933
1979
|
}
|
|
1934
1980
|
}
|
|
1935
1981
|
}
|
|
@@ -1937,125 +1983,154 @@ using namespace facebook::react;
|
|
|
1937
1983
|
|
|
1938
1984
|
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
|
|
1939
1985
|
{
|
|
1986
|
+
[_inputEventEmitter emitOnKeyPress:text];
|
|
1940
1987
|
if ([self deleteLinkForReplacementRange:range replacementText:text]) {
|
|
1941
1988
|
return NO;
|
|
1942
1989
|
}
|
|
1990
|
+
if ([self handleListKeyForReplacementRange:range replacementText:text]) {
|
|
1991
|
+
return NO;
|
|
1992
|
+
}
|
|
1943
1993
|
_preEditSelectedRange = _lastSelectedRange;
|
|
1944
|
-
|
|
1994
|
+
[self capturePreEditBlockForRange:range];
|
|
1995
|
+
_preEditParagraphWasEmpty = [self preEditParagraphWasEmpty:range];
|
|
1996
|
+
_preEditReplacedNewline = [self replacedRangeTouchesNewline:range];
|
|
1997
|
+
[_editSession enterPhase:ENRMEditPhaseProcessing];
|
|
1945
1998
|
[self stripLinkTypingAttributes];
|
|
1946
1999
|
return YES;
|
|
1947
2000
|
}
|
|
1948
2001
|
|
|
1949
2002
|
- (void)textViewDidChange:(UITextView *)textView
|
|
1950
2003
|
{
|
|
1951
|
-
if (
|
|
2004
|
+
if (_editSession.shouldSuppressFormatting) {
|
|
1952
2005
|
return;
|
|
1953
2006
|
}
|
|
1954
2007
|
[self handleTextChanged];
|
|
1955
|
-
|
|
1956
|
-
|
|
2008
|
+
[_editSession exitPhase];
|
|
2009
|
+
[_editSession recordTextChange];
|
|
1957
2010
|
_lastSelectedRange = textView.selectedRange;
|
|
1958
2011
|
}
|
|
1959
2012
|
|
|
1960
2013
|
- (void)textViewDidBeginEditing:(UITextView *)textView
|
|
1961
2014
|
{
|
|
1962
|
-
[
|
|
2015
|
+
[_inputEventEmitter emitOnFocus];
|
|
1963
2016
|
}
|
|
1964
2017
|
|
|
1965
2018
|
- (void)textViewDidEndEditing:(UITextView *)textView
|
|
1966
2019
|
{
|
|
1967
2020
|
[self clearActiveMention:nil];
|
|
1968
|
-
[
|
|
2021
|
+
[_inputEventEmitter emitOnBlur];
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
/// Atomic-link snap clamped to the current text length, so a stale link range
|
|
2025
|
+
/// past the text end can't drive an unbounded snap/clamp loop.
|
|
2026
|
+
- (NSRange)clampedAtomicSelectionForSelection:(NSRange)selection
|
|
2027
|
+
{
|
|
2028
|
+
NSRange adjusted = [_formattingStore selectionAdjustedForAtomicLinks:selection];
|
|
2029
|
+
NSUInteger textLength = _textView.textStorage.length;
|
|
2030
|
+
if (adjusted.location > textLength) {
|
|
2031
|
+
return NSMakeRange(textLength, 0);
|
|
2032
|
+
}
|
|
2033
|
+
if (NSMaxRange(adjusted) > textLength) {
|
|
2034
|
+
adjusted.length = textLength - adjusted.location;
|
|
2035
|
+
}
|
|
2036
|
+
return adjusted;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
/// YES while a UIKit selection gesture (handle drag, long-press loupe) is
|
|
2040
|
+
/// mid-flight; snapping selectedRange then fights the gesture and flickers the
|
|
2041
|
+
/// edit menu, so callers defer the snap until it settles.
|
|
2042
|
+
- (BOOL)selectionGestureIsActive
|
|
2043
|
+
{
|
|
2044
|
+
for (UIGestureRecognizer *recognizer in _textView.gestureRecognizers) {
|
|
2045
|
+
if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged) {
|
|
2046
|
+
return YES;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return NO;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/// Re-applies the atomic-link snap once the selection gesture ends, polling the
|
|
2053
|
+
/// main queue because UIKit may not re-fire the selection delegate at touch-up.
|
|
2054
|
+
- (void)schedulePostGestureAtomicSnap
|
|
2055
|
+
{
|
|
2056
|
+
if (_atomicSnapScheduled) {
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
_atomicSnapScheduled = YES;
|
|
2060
|
+
__weak __typeof(self) weakSelf = self;
|
|
2061
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kENRMAtomicSnapPollInterval * NSEC_PER_SEC)),
|
|
2062
|
+
dispatch_get_main_queue(), ^{
|
|
2063
|
+
__typeof(self) strongSelf = weakSelf;
|
|
2064
|
+
if (strongSelf == nil) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
strongSelf->_atomicSnapScheduled = NO;
|
|
2068
|
+
if (strongSelf->_editSession.shouldSuppressSelectionSideEffects ||
|
|
2069
|
+
strongSelf->_editSession.isComposing) {
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
if ([strongSelf selectionGestureIsActive]) {
|
|
2073
|
+
[strongSelf schedulePostGestureAtomicSnap];
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
NSRange selection = strongSelf->_textView.selectedRange;
|
|
2077
|
+
NSRange adjusted = [strongSelf clampedAtomicSelectionForSelection:selection];
|
|
2078
|
+
if (!NSEqualRanges(adjusted, selection)) {
|
|
2079
|
+
strongSelf->_textView.selectedRange = adjusted;
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
1969
2082
|
}
|
|
1970
2083
|
|
|
1971
2084
|
- (void)textViewDidChangeSelection:(UITextView *)textView
|
|
1972
2085
|
{
|
|
1973
2086
|
NSRange newSelection = textView.selectedRange;
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2087
|
+
if (!_editSession.shouldSuppressSelectionSideEffects && !_editSession.isComposing) {
|
|
2088
|
+
if ([self selectionGestureIsActive]) {
|
|
2089
|
+
[self schedulePostGestureAtomicSnap];
|
|
2090
|
+
} else {
|
|
2091
|
+
NSRange adjusted = [self clampedAtomicSelectionForSelection:newSelection];
|
|
2092
|
+
if (!NSEqualRanges(adjusted, newSelection)) {
|
|
2093
|
+
_textView.selectedRange = adjusted;
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
1981
2096
|
}
|
|
1982
2097
|
}
|
|
1983
2098
|
NSRange previousSelection = _lastSelectedRange;
|
|
1984
2099
|
_lastSelectedRange = newSelection;
|
|
1985
2100
|
|
|
1986
|
-
if (
|
|
2101
|
+
if (_editSession.shouldSuppressSelectionSideEffects) {
|
|
1987
2102
|
return;
|
|
1988
2103
|
}
|
|
1989
2104
|
|
|
1990
|
-
if (
|
|
2105
|
+
if (_editSession.isComposing) {
|
|
1991
2106
|
return;
|
|
1992
2107
|
}
|
|
1993
2108
|
|
|
1994
|
-
|
|
2109
|
+
BOOL selectionMoved =
|
|
2110
|
+
newSelection.location != previousSelection.location || newSelection.length != previousSelection.length;
|
|
2111
|
+
|
|
1995
2112
|
if (_suppressMathChipClearOnce) {
|
|
1996
|
-
//
|
|
1997
|
-
//
|
|
2113
|
+
// This caret move is the one handleMathChipTap: just made — keep its chip
|
|
2114
|
+
// highlighted and consume the suppression.
|
|
1998
2115
|
_suppressMathChipClearOnce = NO;
|
|
1999
|
-
} else {
|
|
2000
|
-
[
|
|
2001
|
-
|
|
2116
|
+
} else if (_activeMathChipPosition != NSNotFound) {
|
|
2117
|
+
[_mathChipController clearSelectionsExceptPosition:NSNotFound];
|
|
2118
|
+
_activeMathChipPosition = NSNotFound;
|
|
2002
2119
|
}
|
|
2003
|
-
#endif
|
|
2004
|
-
|
|
2005
|
-
BOOL selectionMoved =
|
|
2006
|
-
newSelection.location != previousSelection.location || newSelection.length != previousSelection.length;
|
|
2007
2120
|
|
|
2008
2121
|
if (selectionMoved) {
|
|
2009
|
-
[
|
|
2122
|
+
[_typingController resetForSelectionChange];
|
|
2010
2123
|
}
|
|
2011
2124
|
|
|
2012
2125
|
[self manageSelectionBasedChanges];
|
|
2013
2126
|
|
|
2014
|
-
[
|
|
2127
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
2015
2128
|
[self updateActiveMention];
|
|
2016
|
-
[
|
|
2017
|
-
[
|
|
2129
|
+
[_inputEventEmitter emitOnChangeState];
|
|
2130
|
+
[_inputEventEmitter emitCaretRectChangeIfNeeded];
|
|
2131
|
+
[self updateEmptyBulletMarker];
|
|
2018
2132
|
}
|
|
2019
2133
|
|
|
2020
|
-
#if ENRICHED_MARKDOWN_MATH && !TARGET_OS_OSX
|
|
2021
|
-
- (void)clearMathChipSelectionsExcept:(NSRange)keepRange
|
|
2022
|
-
{
|
|
2023
|
-
NSTextStorage *storage = _textView.textStorage;
|
|
2024
|
-
if (storage.length == 0)
|
|
2025
|
-
return;
|
|
2026
|
-
__block BOOL anyChanged = NO;
|
|
2027
|
-
__block NSMutableArray<NSValue *> *invalidationRanges = [NSMutableArray array];
|
|
2028
|
-
[storage enumerateAttribute:NSAttachmentAttributeName
|
|
2029
|
-
inRange:NSMakeRange(0, storage.length)
|
|
2030
|
-
options:0
|
|
2031
|
-
usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
2032
|
-
if (![value isKindOfClass:[ENRMMathInlineAttachment class]])
|
|
2033
|
-
return;
|
|
2034
|
-
ENRMMathInlineAttachment *att = value;
|
|
2035
|
-
BOOL keep = NSEqualRanges(range, keepRange);
|
|
2036
|
-
if (att.isSelected && !keep) {
|
|
2037
|
-
att.isSelected = NO;
|
|
2038
|
-
[att invalidateRender];
|
|
2039
|
-
[invalidationRanges addObject:[NSValue valueWithRange:range]];
|
|
2040
|
-
anyChanged = YES;
|
|
2041
|
-
}
|
|
2042
|
-
}];
|
|
2043
|
-
if (anyChanged) {
|
|
2044
|
-
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
2045
|
-
for (NSValue *v in invalidationRanges) {
|
|
2046
|
-
NSRange r = v.rangeValue;
|
|
2047
|
-
[lm invalidateLayoutForCharacterRange:r actualCharacterRange:NULL];
|
|
2048
|
-
}
|
|
2049
|
-
ENRMSetNeedsDisplay(_textView);
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
#else
|
|
2053
|
-
- (void)clearMathChipSelectionsExcept:(NSRange)keepRange
|
|
2054
|
-
{
|
|
2055
|
-
(void)keepRange;
|
|
2056
|
-
}
|
|
2057
|
-
#endif
|
|
2058
|
-
|
|
2059
2134
|
#else
|
|
2060
2135
|
|
|
2061
2136
|
#pragma mark - RCTBackedTextInputDelegate (macOS)
|
|
@@ -2067,7 +2142,7 @@ using namespace facebook::react;
|
|
|
2067
2142
|
|
|
2068
2143
|
- (void)textInputDidBeginEditing
|
|
2069
2144
|
{
|
|
2070
|
-
[
|
|
2145
|
+
[_inputEventEmitter emitOnFocus];
|
|
2071
2146
|
}
|
|
2072
2147
|
|
|
2073
2148
|
- (BOOL)textInputShouldEndEditing
|
|
@@ -2078,7 +2153,7 @@ using namespace facebook::react;
|
|
|
2078
2153
|
- (void)textInputDidEndEditing
|
|
2079
2154
|
{
|
|
2080
2155
|
[self clearActiveMention:nil];
|
|
2081
|
-
[
|
|
2156
|
+
[_inputEventEmitter emitOnBlur];
|
|
2082
2157
|
}
|
|
2083
2158
|
|
|
2084
2159
|
- (BOOL)textInputShouldReturn
|
|
@@ -2097,31 +2172,36 @@ using namespace facebook::react;
|
|
|
2097
2172
|
|
|
2098
2173
|
- (nullable NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
|
|
2099
2174
|
{
|
|
2175
|
+
[_inputEventEmitter emitOnKeyPress:text];
|
|
2100
2176
|
if ([self deleteLinkForReplacementRange:range replacementText:text]) {
|
|
2101
2177
|
return nil;
|
|
2102
2178
|
}
|
|
2103
2179
|
_preEditSelectedRange = _lastSelectedRange;
|
|
2104
|
-
|
|
2180
|
+
// Capture the same pre-edit block state the iOS path does — otherwise these
|
|
2181
|
+
// ivars carry stale values from a previous edit into reconcileBlockContinuation.
|
|
2182
|
+
[self capturePreEditBlockForRange:range];
|
|
2183
|
+
_preEditParagraphWasEmpty = [self preEditParagraphWasEmpty:range];
|
|
2184
|
+
_preEditReplacedNewline = [self replacedRangeTouchesNewline:range];
|
|
2185
|
+
[_editSession enterPhase:ENRMEditPhaseProcessing];
|
|
2105
2186
|
return text;
|
|
2106
2187
|
}
|
|
2107
2188
|
|
|
2108
2189
|
- (void)textInputDidChange
|
|
2109
2190
|
{
|
|
2110
|
-
if (
|
|
2111
|
-
|
|
2191
|
+
if (_editSession.shouldSuppressFormatting) {
|
|
2192
|
+
[_editSession exitPhase];
|
|
2112
2193
|
return;
|
|
2113
2194
|
}
|
|
2114
2195
|
[self handleTextChanged];
|
|
2115
|
-
|
|
2116
|
-
|
|
2196
|
+
[_editSession exitPhase];
|
|
2197
|
+
[_editSession recordTextChange];
|
|
2117
2198
|
_lastSelectedRange = _textView.selectedRange;
|
|
2118
2199
|
}
|
|
2119
2200
|
|
|
2120
2201
|
- (void)textInputDidChangeSelection
|
|
2121
2202
|
{
|
|
2122
2203
|
NSRange newSelection = _textView.selectedRange;
|
|
2123
|
-
|
|
2124
|
-
if (!_isApplyingFormatting && !_isTextChanging && !ENRMHasMarkedText(_textView)) {
|
|
2204
|
+
if (!_editSession.shouldSuppressSelectionSideEffects && !_editSession.isComposing) {
|
|
2125
2205
|
NSRange adjusted = [_formattingStore selectionAdjustedForAtomicLinks:newSelection];
|
|
2126
2206
|
if (!NSEqualRanges(adjusted, newSelection)) {
|
|
2127
2207
|
_textView.selectedRange = adjusted;
|
|
@@ -2131,11 +2211,11 @@ using namespace facebook::react;
|
|
|
2131
2211
|
NSRange previousSelection = _lastSelectedRange;
|
|
2132
2212
|
_lastSelectedRange = newSelection;
|
|
2133
2213
|
|
|
2134
|
-
if (
|
|
2214
|
+
if (_editSession.shouldSuppressSelectionSideEffects) {
|
|
2135
2215
|
return;
|
|
2136
2216
|
}
|
|
2137
2217
|
|
|
2138
|
-
if (
|
|
2218
|
+
if (_editSession.isComposing) {
|
|
2139
2219
|
return;
|
|
2140
2220
|
}
|
|
2141
2221
|
|
|
@@ -2143,13 +2223,13 @@ using namespace facebook::react;
|
|
|
2143
2223
|
newSelection.location != previousSelection.location || newSelection.length != previousSelection.length;
|
|
2144
2224
|
|
|
2145
2225
|
if (selectionMoved) {
|
|
2146
|
-
[
|
|
2226
|
+
[_typingController resetForSelectionChange];
|
|
2147
2227
|
}
|
|
2148
2228
|
|
|
2149
|
-
[
|
|
2229
|
+
[_inputEventEmitter emitOnChangeSelection];
|
|
2150
2230
|
[self updateActiveMention];
|
|
2151
|
-
[
|
|
2152
|
-
[
|
|
2231
|
+
[_inputEventEmitter emitOnChangeState];
|
|
2232
|
+
[_inputEventEmitter emitCaretRectChangeIfNeeded];
|
|
2153
2233
|
}
|
|
2154
2234
|
|
|
2155
2235
|
// @required stubs for RCTBackedTextInputDelegate — RCTUITextView's internal adapter
|