@mathers101/react-native-render-rich-text 0.0.0-stage → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +140 -3
- package/ReactNativeRenderRichText.podspec +23 -0
- package/android/build.gradle +86 -0
- package/android/consumer-rules.pro +3 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/jni-adapter.cpp +191 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapText.kt +673 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextManager.kt +405 -0
- package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -0
- package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -0
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -0
- package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -0
- package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -0
- package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -0
- package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -0
- package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -0
- package/android/src/main/java/com/renderrichtext/events/FactoidPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -0
- package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -0
- package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -0
- package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -0
- package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -0
- package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -0
- package/android/src/main/java/com/renderrichtext/parser/TiptapASTNode.kt +69 -0
- package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockStyleContext.kt +181 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -0
- package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -0
- package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -0
- package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -0
- package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/FactoidRenderer.kt +97 -0
- package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -0
- package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -0
- package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -0
- package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -0
- package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -0
- package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -0
- package/android/src/main/java/com/renderrichtext/renderer/NodeRenderer.kt +207 -0
- package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -0
- package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -0
- package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -0
- package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -0
- package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -0
- package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -0
- package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -0
- package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -0
- package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -0
- package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -0
- package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -0
- package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidBackgroundSpan.kt +265 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidPaddingSpan.kt +258 -0
- package/android/src/main/java/com/renderrichtext/spans/FactoidSpan.kt +32 -0
- package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -0
- package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -0
- package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -0
- package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -0
- package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -0
- package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -0
- package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -0
- package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -0
- package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -0
- package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -0
- package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -0
- package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -0
- package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -0
- package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -0
- package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -0
- package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -0
- package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -0
- package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -0
- package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -0
- package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -0
- package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -0
- package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -0
- package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -0
- package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -0
- package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -0
- package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -0
- package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -0
- package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -0
- package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -0
- package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -0
- package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -0
- package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -0
- package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -0
- package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -0
- package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -0
- package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -0
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -0
- package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -0
- package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -0
- package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -0
- package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -0
- package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -0
- package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -0
- package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -0
- package/android/src/main/java/com/renderrichtext/utils/common/TiptapViewManagerUtils.kt +242 -0
- package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -0
- package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -0
- package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -0
- package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -0
- package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -0
- package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -0
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -0
- package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -0
- package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -0
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -0
- package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -0
- package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/LinkEvents.kt +88 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/LinkLongPressMovementMethod.kt +449 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -0
- package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -0
- package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -0
- package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -0
- package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -0
- package/android/src/main/jni/CMakeLists.txt +52 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -0
- package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -0
- package/cpp/parser/TiptapASTNode.hpp +68 -0
- package/cpp/parser/TiptapParser.cpp +233 -0
- package/cpp/parser/TiptapParser.hpp +13 -0
- package/cpp/vendor/nlohmann/json.hpp +24765 -0
- package/ios/EnrichedTiptapText.h +27 -0
- package/ios/EnrichedTiptapText.mm +1222 -0
- package/ios/attachments/ENRMImageAttachment.h +51 -0
- package/ios/attachments/ENRMImageAttachment.m +398 -0
- package/ios/attachments/ENRMLinkPillAttachment.h +47 -0
- package/ios/attachments/ENRMLinkPillAttachment.m +324 -0
- package/ios/attachments/ENRMLinkPillIconCache.h +28 -0
- package/ios/attachments/ENRMLinkPillIconCache.m +181 -0
- package/ios/attachments/ENRMLinkPillText.h +27 -0
- package/ios/attachments/ENRMLinkPillText.m +118 -0
- package/ios/attachments/ENRMMathInlineAttachment.h +7 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +4 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/internals/ENRMViewFreeMeasurement.h +208 -0
- package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -0
- package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -0
- package/ios/internals/EnrichedTiptapTextState.h +24 -0
- package/ios/internals/MeasurementCache.h +308 -0
- package/ios/internals/ShadowMeasurementUtils.h +145 -0
- package/ios/parser/ENRMTextLinkAttributes.h +7 -0
- package/ios/parser/ENRMTiptapParser.h +33 -0
- package/ios/parser/ENRMTiptapParser.mm +73 -0
- package/ios/parser/TiptapASTNode.h +57 -0
- package/ios/parser/TiptapASTNode.m +52 -0
- package/ios/parser/TiptapParserBridge.mm +179 -0
- package/ios/renderer/AttributedRenderer.h +23 -0
- package/ios/renderer/AttributedRenderer.m +229 -0
- package/ios/renderer/BaseRenderer.h +33 -0
- package/ios/renderer/BaseRenderer.m +46 -0
- package/ios/renderer/BaselineShiftTextAttributes.h +10 -0
- package/ios/renderer/BaselineShiftTextAttributes.m +26 -0
- package/ios/renderer/BlankLineRenderer.h +4 -0
- package/ios/renderer/BlankLineRenderer.m +51 -0
- package/ios/renderer/BlockquoteRenderer.h +6 -0
- package/ios/renderer/BlockquoteRenderer.m +258 -0
- package/ios/renderer/CodeBlockRenderer.h +9 -0
- package/ios/renderer/CodeBlockRenderer.m +99 -0
- package/ios/renderer/CodeRenderer.h +9 -0
- package/ios/renderer/CodeRenderer.m +69 -0
- package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -0
- package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -0
- package/ios/renderer/EmphasisRenderer.h +5 -0
- package/ios/renderer/EmphasisRenderer.m +100 -0
- package/ios/renderer/FactoidRenderer.h +9 -0
- package/ios/renderer/FactoidRenderer.m +130 -0
- package/ios/renderer/HeadingRenderer.h +6 -0
- package/ios/renderer/HeadingRenderer.m +103 -0
- package/ios/renderer/HighlightRenderer.h +7 -0
- package/ios/renderer/HighlightRenderer.m +33 -0
- package/ios/renderer/LinkRenderer.h +6 -0
- package/ios/renderer/LinkRenderer.m +158 -0
- package/ios/renderer/ListItemRenderer.h +24 -0
- package/ios/renderer/ListItemRenderer.m +283 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +126 -0
- package/ios/renderer/MentionRenderer.h +9 -0
- package/ios/renderer/MentionRenderer.m +96 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +6 -0
- package/ios/renderer/ParagraphRenderer.m +85 -0
- package/ios/renderer/RenderContext.h +145 -0
- package/ios/renderer/RenderContext.m +403 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +158 -0
- package/ios/renderer/StrikethroughRenderer.h +5 -0
- package/ios/renderer/StrikethroughRenderer.m +30 -0
- package/ios/renderer/StrongRenderer.h +5 -0
- package/ios/renderer/StrongRenderer.m +88 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +18 -0
- package/ios/renderer/ThematicBreakRenderer.h +4 -0
- package/ios/renderer/ThematicBreakRenderer.m +42 -0
- package/ios/renderer/UnderlineRenderer.h +4 -0
- package/ios/renderer/UnderlineRenderer.m +41 -0
- package/ios/styles/ENRMFontSlot.h +12 -0
- package/ios/styles/ENRMFontSlot.m +24 -0
- package/ios/styles/StyleConfig.h +488 -0
- package/ios/styles/StyleConfig.mm +2820 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/BlockquoteBorder.h +26 -0
- package/ios/utils/BlockquoteBorder.m +294 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +197 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +103 -0
- package/ios/utils/ContextMenuUtils.h +76 -0
- package/ios/utils/ContextMenuUtils.mm +75 -0
- package/ios/utils/ENRMAccessibilityLabels.h +40 -0
- package/ios/utils/ENRMAccessibilityLabels.m +5 -0
- package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -0
- package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -0
- package/ios/utils/ENRMAtomicSize.h +65 -0
- package/ios/utils/ENRMCodeBlockContent.h +37 -0
- package/ios/utils/ENRMCodeBlockContent.mm +89 -0
- package/ios/utils/ENRMFeatureFlags.h +25 -0
- package/ios/utils/ENRMImageDownloader.h +30 -0
- package/ios/utils/ENRMImageDownloader.m +168 -0
- package/ios/utils/ENRMLatexErrorCoordinator.h +27 -0
- package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -0
- package/ios/utils/ENRMLatexErrorReporting.h +13 -0
- package/ios/utils/ENRMLinkContextMenus.h +93 -0
- package/ios/utils/ENRMLinkContextMenus.m +178 -0
- package/ios/utils/ENRMLinkRegexConfig.h +30 -0
- package/ios/utils/ENRMLinkRegexConfig.mm +104 -0
- package/ios/utils/ENRMLinkRegexProps.h +23 -0
- package/ios/utils/ENRMLocalImageLoader.h +35 -0
- package/ios/utils/ENRMLocalImageLoader.m +111 -0
- package/ios/utils/ENRMMenuAction.h +27 -0
- package/ios/utils/ENRMMenuAction.m +33 -0
- package/ios/utils/ENRMSpoilerOverlayManager.h +15 -0
- package/ios/utils/ENRMSpoilerOverlayManager.m +19 -0
- package/ios/utils/ENRMSpoilerTapUtils.h +6 -0
- package/ios/utils/ENRMSpoilerTapUtils.m +11 -0
- package/ios/utils/ENRMTailFadeInAnimator.h +16 -0
- package/ios/utils/ENRMTailFadeInAnimator.m +161 -0
- package/ios/utils/ENRMTextHitTest.h +23 -0
- package/ios/utils/ENRMTextHitTest.m +20 -0
- package/ios/utils/ENRMTextInteractionUtils.h +20 -0
- package/ios/utils/ENRMTextInteractionUtils.m +15 -0
- package/ios/utils/ENRMTextRenderer.h +38 -0
- package/ios/utils/ENRMTextRenderer.m +54 -0
- package/ios/utils/ENRMTextViewSetup.h +132 -0
- package/ios/utils/ENRMUIKit.h +429 -0
- package/ios/utils/EditMenuUtils.h +105 -0
- package/ios/utils/EditMenuUtils.m +143 -0
- package/ios/utils/FactoidBackground.h +20 -0
- package/ios/utils/FactoidBackground.m +161 -0
- package/ios/utils/FontScaleObserver.h +10 -0
- package/ios/utils/FontScaleObserver.m +55 -0
- package/ios/utils/FontUtils.h +31 -0
- package/ios/utils/FontUtils.m +63 -0
- package/ios/utils/HTMLGenerator.h +25 -0
- package/ios/utils/HTMLGenerator.m +1041 -0
- package/ios/utils/HeightUpdateUtils.h +25 -0
- package/ios/utils/ImageRequestHeaderUtils.h +35 -0
- package/ios/utils/LastElementUtils.h +97 -0
- package/ios/utils/LinkContextMenuUtils.h +55 -0
- package/ios/utils/LinkPillContentUtils.h +46 -0
- package/ios/utils/LinkTapUtils.h +56 -0
- package/ios/utils/LinkTapUtils.m +139 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +239 -0
- package/ios/utils/MentionBackground.h +26 -0
- package/ios/utils/MentionBackground.m +86 -0
- package/ios/utils/ParagraphStyleUtils.h +87 -0
- package/ios/utils/ParagraphStyleUtils.m +484 -0
- package/ios/utils/PasteboardUtils.h +48 -0
- package/ios/utils/PasteboardUtils.m +166 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +299 -0
- package/ios/utils/RuntimeKeys.h +44 -0
- package/ios/utils/RuntimeKeys.m +13 -0
- package/ios/utils/SelectionColorUtils.h +14 -0
- package/ios/utils/StylePropsUtils.h +1340 -0
- package/ios/utils/TaskListTapUtils.h +6 -0
- package/ios/utils/TaskListTapUtils.m +14 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +198 -0
- package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -0
- package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -0
- package/ios/utils/TiptapExtractor.h +17 -0
- package/ios/utils/TiptapExtractor.m +367 -0
- package/ios/views/ENRMTiptapTextView.h +25 -0
- package/ios/views/ENRMTiptapTextView.m +247 -0
- package/package.json +70 -6
- package/react-native.config.js +10 -0
- package/src/EnrichedTiptapTextNativeComponent.ts +739 -0
- package/src/RichText.tsx +164 -0
- package/src/admonitionDefaults.ts +58 -0
- package/src/darkModeTextStyle.ts +99 -0
- package/src/headingDefaults.ts +21 -0
- package/src/index.tsx +4 -0
- package/src/linkVariantUtils.ts +120 -0
- package/src/normalizeTiptapStyle.ts +397 -0
- package/src/rn-globals.d.ts +1 -0
- package/src/styleUtils.ts +107 -0
- package/src/types/TiptapStyle.ts +532 -0
- package/src/types/TiptapStyleInternal.ts +299 -0
- package/src/types/linkPill.ts +9 -0
- package/src/utils/isDev.ts +4 -0
|
@@ -0,0 +1,1222 @@
|
|
|
1
|
+
#import "EnrichedTiptapText.h"
|
|
2
|
+
#import "CodeBlockBackground.h"
|
|
3
|
+
#import "ContextMenuUtils.h"
|
|
4
|
+
#import "ENRMAccessibilityLabels.h"
|
|
5
|
+
#import "ENRMAsyncRenderCoordinator.h"
|
|
6
|
+
#import "ENRMAtomicSize.h"
|
|
7
|
+
#if TARGET_OS_OSX
|
|
8
|
+
#import "ENRMContextMenuTextView+macOS.h"
|
|
9
|
+
#endif
|
|
10
|
+
#import "ENRMImageAttachment.h"
|
|
11
|
+
#import "ENRMLatexErrorCoordinator.h"
|
|
12
|
+
#import "ENRMLinkContextMenus.h"
|
|
13
|
+
#import "ENRMLinkRegexProps.h"
|
|
14
|
+
#import "ENRMTiptapParser.h"
|
|
15
|
+
#import "ENRMTiptapTextView.h"
|
|
16
|
+
#import "ENRMSpoilerOverlayManager.h"
|
|
17
|
+
#import "ENRMSpoilerTapUtils.h"
|
|
18
|
+
#import "ENRMTailFadeInAnimator.h"
|
|
19
|
+
#import "ENRMTextInteractionUtils.h"
|
|
20
|
+
#import "ENRMTextRenderer.h"
|
|
21
|
+
#import "ENRMTextViewSetup.h"
|
|
22
|
+
#import "ENRMUIKit.h"
|
|
23
|
+
#import "EditMenuUtils.h"
|
|
24
|
+
#import "FontScaleObserver.h"
|
|
25
|
+
#import "FontUtils.h"
|
|
26
|
+
#import "HeightUpdateUtils.h"
|
|
27
|
+
#import "ImageRequestHeaderUtils.h"
|
|
28
|
+
#import "LinkContextMenuUtils.h"
|
|
29
|
+
#import "LinkPillContentUtils.h"
|
|
30
|
+
#import "LinkTapUtils.h"
|
|
31
|
+
#import "TiptapASTNode.h"
|
|
32
|
+
#import "TiptapAccessibilityElementBuilder.h"
|
|
33
|
+
#import "TiptapExtractor.h"
|
|
34
|
+
#import "MeasurementCache.h"
|
|
35
|
+
#import "ParagraphStyleUtils.h"
|
|
36
|
+
#import "PasteboardUtils.h"
|
|
37
|
+
#import "RenderContext.h"
|
|
38
|
+
#import "RuntimeKeys.h"
|
|
39
|
+
#import "SelectionColorUtils.h"
|
|
40
|
+
#import "StylePropsUtils.h"
|
|
41
|
+
#import "TaskListTapUtils.h"
|
|
42
|
+
|
|
43
|
+
#import <ReactNativeRenderRichText/EnrichedTiptapTextComponentDescriptor.h>
|
|
44
|
+
#import <ReactNativeRenderRichText/EventEmitters.h>
|
|
45
|
+
#import <ReactNativeRenderRichText/Props.h>
|
|
46
|
+
#import <ReactNativeRenderRichText/RCTComponentViewHelpers.h>
|
|
47
|
+
|
|
48
|
+
#import "RCTFabricComponentsPlugins.h"
|
|
49
|
+
#import <React/RCTConversions.h>
|
|
50
|
+
#import <React/RCTFont.h>
|
|
51
|
+
#import <React/RCTI18nUtil.h>
|
|
52
|
+
|
|
53
|
+
using namespace facebook::react;
|
|
54
|
+
|
|
55
|
+
typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
|
|
56
|
+
ENRMDirtyNone = 0,
|
|
57
|
+
ENRMDirtyRender = 1 << 0,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
@interface EnrichedTiptapText () <RCTEnrichedTiptapTextViewProtocol, UITextViewDelegate, ENRMImageLayoutObserver
|
|
61
|
+
#if !TARGET_OS_OSX
|
|
62
|
+
,
|
|
63
|
+
UIGestureRecognizerDelegate
|
|
64
|
+
#endif
|
|
65
|
+
>
|
|
66
|
+
+ (ENRMMd4cFlags *)flagsFromProps:(const EnrichedTiptapTextMd4cFlagsStruct &)props;
|
|
67
|
+
- (void)setupTextView;
|
|
68
|
+
- (void)renderTiptapContent:(NSString *)tiptapString;
|
|
69
|
+
- (void)applyRenderedText:(NSMutableAttributedString *)attributedText;
|
|
70
|
+
- (void)textTapped:(ENRMTapRecognizer *)recognizer;
|
|
71
|
+
- (void)setupLayoutManager;
|
|
72
|
+
- (void)emitLinkPress:(NSString *)url;
|
|
73
|
+
- (void)emitFactoidPress:(NSString *)factoidId;
|
|
74
|
+
- (void)emitMentionPress:(NSString *)mentionId label:(NSString *)label;
|
|
75
|
+
- (void)emitMentionLongPress:(NSString *)mentionId label:(NSString *)label;
|
|
76
|
+
- (void)emitLinkLongPress:(NSString *)url;
|
|
77
|
+
- (void)emitLinkContextMenuItemPress:(NSString *)itemText pattern:(NSString *)pattern url:(NSString *)url;
|
|
78
|
+
- (void)emitImagePress:(NSString *)url altText:(NSString *)altText;
|
|
79
|
+
- (void)emitCodeBlockPress:(NSString *)code language:(NSString *)language;
|
|
80
|
+
- (void)emitTaskListItemPress:(NSInteger)index checked:(BOOL)checked text:(NSString *)text;
|
|
81
|
+
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
82
|
+
selectedText:(NSString *)selectedText
|
|
83
|
+
selectionStart:(NSUInteger)selectionStart
|
|
84
|
+
selectionEnd:(NSUInteger)selectionEnd;
|
|
85
|
+
@end
|
|
86
|
+
|
|
87
|
+
#if !TARGET_OS_OSX
|
|
88
|
+
@interface EnrichedTiptapText () <ENRMLinkContextMenuSource>
|
|
89
|
+
@end
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
@implementation EnrichedTiptapText {
|
|
93
|
+
ENRMPlatformTextView *_textView;
|
|
94
|
+
#if !TARGET_OS_OSX
|
|
95
|
+
UILongPressGestureRecognizer *_mentionLongPress;
|
|
96
|
+
#endif
|
|
97
|
+
ENRMTiptapParser *_parser;
|
|
98
|
+
NSString *_cachedTiptap;
|
|
99
|
+
NSString *_renderedTiptap;
|
|
100
|
+
StyleConfig *_config;
|
|
101
|
+
ENRMMd4cFlags *_md4cFlags;
|
|
102
|
+
BOOL _isGFM;
|
|
103
|
+
ENRMLinkRegexConfig *_linkRegex;
|
|
104
|
+
ENRMLinkRegexConfig *_inlineCodeLinkRegex;
|
|
105
|
+
|
|
106
|
+
ENRMAsyncRenderCoordinator *_renderCoordinator;
|
|
107
|
+
|
|
108
|
+
EnrichedTiptapTextShadowNode::ConcreteState::Shared _state;
|
|
109
|
+
int _heightUpdateCounter;
|
|
110
|
+
|
|
111
|
+
FontScaleObserver *_fontScaleObserver;
|
|
112
|
+
CGFloat _maxFontSizeMultiplier;
|
|
113
|
+
|
|
114
|
+
CGFloat _lastElementMarginBottom;
|
|
115
|
+
BOOL _allowTrailingMargin;
|
|
116
|
+
BOOL _enableLinkPreview;
|
|
117
|
+
BOOL _enableTaskListItemToggle;
|
|
118
|
+
BOOL _enableImagePress;
|
|
119
|
+
BOOL _enableCodeBlockPress;
|
|
120
|
+
BOOL _streamingAnimation;
|
|
121
|
+
BOOL _forceHeightUpdateOnNextRender;
|
|
122
|
+
|
|
123
|
+
size_t _renderedStyleFingerprint;
|
|
124
|
+
size_t _pendingStyleFingerprint;
|
|
125
|
+
|
|
126
|
+
NSUInteger _previousTextLength;
|
|
127
|
+
ENRMTailFadeInAnimator *_fadeAnimator;
|
|
128
|
+
|
|
129
|
+
AccessibilityInfo *_accessibilityInfo;
|
|
130
|
+
ENRMAccessibilityLabels *_accessibilityLabels;
|
|
131
|
+
#if !TARGET_OS_OSX
|
|
132
|
+
NSMutableArray<UIAccessibilityElement *> *_accessibilityElements;
|
|
133
|
+
#else
|
|
134
|
+
NSMutableArray *_accessibilityElements;
|
|
135
|
+
#endif
|
|
136
|
+
BOOL _accessibilityNeedsRebuild;
|
|
137
|
+
|
|
138
|
+
ENRMLinkContextMenus *_linkContextMenus;
|
|
139
|
+
NSArray<NSString *> *_contextMenuItemTexts;
|
|
140
|
+
NSArray<NSString *> *_contextMenuItemIcons;
|
|
141
|
+
ENRMSelectionMenuConfig _selectionMenuConfig;
|
|
142
|
+
ENRMSelectionMenuLabels _selectionMenuLabels;
|
|
143
|
+
|
|
144
|
+
ENRMSpoilerOverlayManager *_spoilerManager;
|
|
145
|
+
|
|
146
|
+
NSLineBreakStrategy _lineBreakStrategy;
|
|
147
|
+
|
|
148
|
+
NSInteger _numberOfLines;
|
|
149
|
+
NSLineBreakMode _ellipsizeLineBreakMode;
|
|
150
|
+
|
|
151
|
+
ENRMWritingDirectionMode _writingDirectionMode;
|
|
152
|
+
NSWritingDirection _resolvedLayoutDirection;
|
|
153
|
+
|
|
154
|
+
ENRMDirtyFlags _dirtyFlags;
|
|
155
|
+
|
|
156
|
+
ENRMAtomicSize _lastCommittedSize;
|
|
157
|
+
|
|
158
|
+
ENRMLatexErrorCoordinator *_latexErrorCoordinator;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
162
|
+
{
|
|
163
|
+
return concreteComponentDescriptorProvider<EnrichedTiptapTextComponentDescriptor>();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
+ (ENRMMd4cFlags *)flagsFromProps:(const EnrichedTiptapTextMd4cFlagsStruct &)props
|
|
167
|
+
{
|
|
168
|
+
ENRMMd4cFlags *flags = [ENRMMd4cFlags defaultFlags];
|
|
169
|
+
flags.underline = props.underline;
|
|
170
|
+
flags.superscript = props.superscript;
|
|
171
|
+
flags.subscript = props.subscript;
|
|
172
|
+
flags.latexMath = props.latexMath;
|
|
173
|
+
flags.highlight = props.highlight;
|
|
174
|
+
flags.hardSoftBreaks = props.hardSoftBreaks;
|
|
175
|
+
flags.preserveBlankLines = props.preserveBlankLines;
|
|
176
|
+
flags.admonitions = props.admonitions;
|
|
177
|
+
return flags;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#pragma mark - Measuring and State
|
|
181
|
+
|
|
182
|
+
/// The box the text actually lives in: `self.bounds` inset by the
|
|
183
|
+
/// `containerStyle` border and padding.
|
|
184
|
+
///
|
|
185
|
+
/// Yoga hands `measureContent` an available size that already excludes those
|
|
186
|
+
/// insets, and `RCTViewComponentView` sizes `contentView` (our text view) to
|
|
187
|
+
/// `layoutMetrics.getContentFrame()`, so the shadow node's height and the text
|
|
188
|
+
/// view's frame are both content-box quantities. Measuring at `self.bounds`
|
|
189
|
+
/// instead overshoots by the horizontal insets, and because
|
|
190
|
+
/// `ENRMMeasureTextLayout` measures by resizing the live display container and
|
|
191
|
+
/// leaves it there (UIKit never re-syncs it - `widthTracksTextView` does not
|
|
192
|
+
/// apply with `scrollEnabled = NO`), the overshoot becomes the layout the user
|
|
193
|
+
/// sees: glyphs spill past the text view and get clipped, while the taller
|
|
194
|
+
/// content-width height Yoga committed shows up as blank space at the bottom.
|
|
195
|
+
///
|
|
196
|
+
/// The text view's frame is authoritative, so this is zero-sized both before the
|
|
197
|
+
/// first layout metrics arrive and when the insets consume the whole component
|
|
198
|
+
/// width. Neither may fall back to `self.bounds`: that is the padded border box,
|
|
199
|
+
/// and in the second case it is a real layout the text would then be drawn
|
|
200
|
+
/// across. Callers skip measuring and laying out at a zero width instead.
|
|
201
|
+
///
|
|
202
|
+
/// The frame, not the bounds: a `UITextView` is a scroll view, so its
|
|
203
|
+
/// `bounds.origin` is the content offset rather than the inset the segments of
|
|
204
|
+
/// `EnrichedTiptap` get from the matching method.
|
|
205
|
+
- (CGRect)contentBounds
|
|
206
|
+
{
|
|
207
|
+
return _textView.frame;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
- (CGSize)measureSize:(CGFloat)maxWidth
|
|
211
|
+
{
|
|
212
|
+
CGSize size = ENRMMeasureTiptapText(_textView, maxWidth, _config, _allowTrailingMargin, _lastElementMarginBottom);
|
|
213
|
+
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
|
214
|
+
CGFloat defaultHeight = UIFontLineHeight([UIFont systemFontOfSize:16.0]);
|
|
215
|
+
return CGSizeMake(maxWidth, defaultHeight);
|
|
216
|
+
}
|
|
217
|
+
return size;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
- (CGSize)lastCommittedLayoutSize
|
|
221
|
+
{
|
|
222
|
+
return _lastCommittedSize.load();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
- (BOOL)hasRenderedTiptap:(NSString *)tiptap
|
|
226
|
+
{
|
|
227
|
+
return _renderedTiptap != nil && [_renderedTiptap isEqualToString:tiptap];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
- (BOOL)hasRenderedWithStyleFingerprint:(size_t)fingerprint
|
|
231
|
+
{
|
|
232
|
+
return _renderedStyleFingerprint == fingerprint;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
- (void)updateState:(const facebook::react::State::Shared &)state
|
|
236
|
+
oldState:(const facebook::react::State::Shared &)oldState
|
|
237
|
+
{
|
|
238
|
+
_state = std::static_pointer_cast<const EnrichedTiptapTextShadowNode::ConcreteState>(state);
|
|
239
|
+
|
|
240
|
+
if (oldState == nullptr) {
|
|
241
|
+
[self requestHeightUpdate];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Publishes the committed size to the streaming fast path's mailbox, and picks
|
|
246
|
+
/// up the Yoga-resolved direction inherited from any ancestor `direction` style.
|
|
247
|
+
/// In FirstStrong mode the direction feeds the neutral-paragraph fallback, so a
|
|
248
|
+
/// change requires a re-render of the cached tiptap.
|
|
249
|
+
///
|
|
250
|
+
/// The mailbox holds a content size, not the border box: `measureContent`
|
|
251
|
+
/// returns it straight to Yoga, which adds the `containerStyle` insets back on
|
|
252
|
+
/// top of whatever it gets, so storing `frame.size` would double-count them.
|
|
253
|
+
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
|
254
|
+
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
|
|
255
|
+
{
|
|
256
|
+
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
257
|
+
|
|
258
|
+
const auto contentSize = layoutMetrics.getContentFrame().size;
|
|
259
|
+
_lastCommittedSize.store(CGSizeMake(contentSize.width, contentSize.height));
|
|
260
|
+
|
|
261
|
+
NSWritingDirection resolved = _resolvedLayoutDirection;
|
|
262
|
+
if (layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) {
|
|
263
|
+
resolved = NSWritingDirectionRightToLeft;
|
|
264
|
+
} else if (layoutMetrics.layoutDirection == LayoutDirection::LeftToRight) {
|
|
265
|
+
resolved = NSWritingDirectionLeftToRight;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (resolved != _resolvedLayoutDirection) {
|
|
269
|
+
_resolvedLayoutDirection = resolved;
|
|
270
|
+
if (_writingDirectionMode == ENRMWritingDirectionModeFirstStrong && _cachedTiptap.length > 0) {
|
|
271
|
+
[self renderTiptapContent:_cachedTiptap];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
- (void)requestHeightUpdate
|
|
277
|
+
{
|
|
278
|
+
ENRMRequestHeightUpdate<EnrichedTiptapTextState>(_state, _heightUpdateCounter, self);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// A block image resolved its box height after loading (maxHeight/aspectRatio
|
|
282
|
+
// sizing). The shadow node measured — and cached — this tiptap with the
|
|
283
|
+
// pre-load fallback height, so drop those entries and re-measure.
|
|
284
|
+
- (void)imageAttachmentDidResolveLayout
|
|
285
|
+
{
|
|
286
|
+
// The text around the attachment moved, and the spoiler overlays with it.
|
|
287
|
+
[_spoilerManager setNeedsUpdate];
|
|
288
|
+
[_spoilerManager updateIfNeeded];
|
|
289
|
+
|
|
290
|
+
if (_renderedTiptap.length > 0) {
|
|
291
|
+
facebook::react::MeasurementCache::shared().removeMatchingTiptap(std::string(_renderedTiptap.UTF8String));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
CGRect contentBounds = [self contentBounds];
|
|
295
|
+
if (contentBounds.size.width > 0) {
|
|
296
|
+
CGSize measured = [self measureSize:contentBounds.size.width];
|
|
297
|
+
if (needsHeightUpdate(measured, contentBounds)) {
|
|
298
|
+
[self requestHeightUpdate];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
304
|
+
{
|
|
305
|
+
if (self = [super initWithFrame:frame]) {
|
|
306
|
+
static const auto defaultProps = std::make_shared<const EnrichedTiptapTextProps>();
|
|
307
|
+
_props = defaultProps;
|
|
308
|
+
|
|
309
|
+
self.backgroundColor = [RCTUIColor clearColor];
|
|
310
|
+
_parser = [[ENRMTiptapParser alloc] init];
|
|
311
|
+
_linkContextMenus = [[ENRMLinkContextMenus alloc] init];
|
|
312
|
+
__weak EnrichedTiptapText *weakMenuSelf = self;
|
|
313
|
+
_linkContextMenus.onPress = ^(NSString *url, NSString *pattern, NSString *itemText) {
|
|
314
|
+
[weakMenuSelf emitLinkContextMenuItemPress:itemText pattern:pattern url:url];
|
|
315
|
+
};
|
|
316
|
+
_md4cFlags = [EnrichedTiptapText flagsFromProps:defaultProps->md4cFlags];
|
|
317
|
+
_isGFM = defaultProps->isGFM;
|
|
318
|
+
_linkRegex = ENRMLinkRegexConfigFromProps(defaultProps->linkRecognition.text);
|
|
319
|
+
_inlineCodeLinkRegex = ENRMLinkRegexConfigFromProps(defaultProps->linkRecognition.inlineCode);
|
|
320
|
+
|
|
321
|
+
_renderCoordinator =
|
|
322
|
+
[[ENRMAsyncRenderCoordinator alloc] initWithQueueLabel:"com.swmansion.enriched.tiptap.render"];
|
|
323
|
+
|
|
324
|
+
__weak __typeof(self) weakLatexSelf = self;
|
|
325
|
+
_latexErrorCoordinator =
|
|
326
|
+
[[ENRMLatexErrorCoordinator alloc] initWithEmit:^BOOL(NSString *source, NSString *message, BOOL displayMode) {
|
|
327
|
+
__typeof(self) strongSelf = weakLatexSelf;
|
|
328
|
+
if (!strongSelf)
|
|
329
|
+
return NO;
|
|
330
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(strongSelf->_eventEmitter);
|
|
331
|
+
if (!emitter)
|
|
332
|
+
return NO;
|
|
333
|
+
emitter->onLatexError({
|
|
334
|
+
.source = std::string(source.UTF8String ?: ""),
|
|
335
|
+
.message = std::string(message.UTF8String ?: ""),
|
|
336
|
+
.displayMode = displayMode ? true : false,
|
|
337
|
+
});
|
|
338
|
+
return YES;
|
|
339
|
+
}];
|
|
340
|
+
_maxFontSizeMultiplier = 0;
|
|
341
|
+
_allowTrailingMargin = NO;
|
|
342
|
+
_enableLinkPreview = YES;
|
|
343
|
+
_enableTaskListItemToggle = YES;
|
|
344
|
+
_enableImagePress = NO;
|
|
345
|
+
_enableCodeBlockPress = NO;
|
|
346
|
+
_forceHeightUpdateOnNextRender = NO;
|
|
347
|
+
_selectionMenuConfig = (ENRMSelectionMenuConfig){.copyAsTiptap = YES, .copyImageURL = YES};
|
|
348
|
+
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
349
|
+
_numberOfLines = 0;
|
|
350
|
+
_ellipsizeLineBreakMode = NSLineBreakByTruncatingTail;
|
|
351
|
+
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
352
|
+
_resolvedLayoutDirection =
|
|
353
|
+
[[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
|
|
354
|
+
_dirtyFlags = ENRMDirtyNone;
|
|
355
|
+
|
|
356
|
+
_fontScaleObserver = [[FontScaleObserver alloc] init];
|
|
357
|
+
__weak EnrichedTiptapText *weakSelf = self;
|
|
358
|
+
_fontScaleObserver.onChange = ^{
|
|
359
|
+
EnrichedTiptapText *strongSelf = weakSelf;
|
|
360
|
+
if (!strongSelf)
|
|
361
|
+
return;
|
|
362
|
+
if (strongSelf->_config != nil) {
|
|
363
|
+
[strongSelf->_config setFontScaleMultiplier:strongSelf->_fontScaleObserver.effectiveFontScale];
|
|
364
|
+
}
|
|
365
|
+
if (strongSelf->_cachedTiptap != nil && strongSelf->_cachedTiptap.length > 0) {
|
|
366
|
+
strongSelf->_forceHeightUpdateOnNextRender = YES;
|
|
367
|
+
[strongSelf renderTiptapContent:strongSelf->_cachedTiptap];
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
[self setupTextView];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return self;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
- (void)setupTextView
|
|
378
|
+
{
|
|
379
|
+
#if !TARGET_OS_OSX
|
|
380
|
+
ENRMTiptapTextView *tiptapTextView = [[ENRMTiptapTextView alloc] init];
|
|
381
|
+
__weak EnrichedTiptapText *weakCopyOwner = self;
|
|
382
|
+
tiptapTextView.copySelectionHandler = ^(NSRange range) {
|
|
383
|
+
EnrichedTiptapText *owner = weakCopyOwner;
|
|
384
|
+
if (!owner)
|
|
385
|
+
return;
|
|
386
|
+
NSAttributedString *text = owner->_textView.textStorage;
|
|
387
|
+
copyAttributedStringToPasteboard([text attributedSubstringFromRange:range],
|
|
388
|
+
tiptapForRange(text, range, owner->_cachedTiptap), owner -> _config);
|
|
389
|
+
};
|
|
390
|
+
_textView = tiptapTextView;
|
|
391
|
+
_textView.text = @"";
|
|
392
|
+
#else
|
|
393
|
+
_textView = [[ENRMContextMenuTextView alloc] init];
|
|
394
|
+
_textView.string = @"";
|
|
395
|
+
#endif
|
|
396
|
+
ENRMConfigureTiptapTextView(_textView);
|
|
397
|
+
_textView.delegate = self;
|
|
398
|
+
_textView.hidden = YES;
|
|
399
|
+
|
|
400
|
+
#if TARGET_OS_OSX
|
|
401
|
+
__weak EnrichedTiptapText *weakSelf = self;
|
|
402
|
+
((ENRMContextMenuTextView *)_textView).contextMenuProvider =
|
|
403
|
+
^NSMenu *_Nullable(NSMenu *baseMenu, NSTextView *textView)
|
|
404
|
+
{
|
|
405
|
+
EnrichedTiptapText *strongSelf = weakSelf;
|
|
406
|
+
if (!strongSelf) {
|
|
407
|
+
return baseMenu;
|
|
408
|
+
}
|
|
409
|
+
NSArray<NSMenuItem *> *customItems = ENRMBuildContextMenuItems(
|
|
410
|
+
strongSelf->_contextMenuItemTexts, strongSelf->_contextMenuItemIcons, textView,
|
|
411
|
+
^(NSString *itemText, NSString *selectedText, NSUInteger selectionStart, NSUInteger selectionEnd) {
|
|
412
|
+
[strongSelf emitContextMenuItemPress:itemText
|
|
413
|
+
selectedText:selectedText
|
|
414
|
+
selectionStart:selectionStart
|
|
415
|
+
selectionEnd:selectionEnd];
|
|
416
|
+
});
|
|
417
|
+
return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, strongSelf->_cachedTiptap,
|
|
418
|
+
strongSelf->_config, @[ baseMenu ], customItems,
|
|
419
|
+
strongSelf -> _selectionMenuConfig);
|
|
420
|
+
};
|
|
421
|
+
#endif
|
|
422
|
+
|
|
423
|
+
ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
|
|
424
|
+
#if !TARGET_OS_OSX
|
|
425
|
+
// Fails immediately unless the touch is on a mention, so text selection is unchanged
|
|
426
|
+
// everywhere else. A mention long-press wins over the text view's selection gesture.
|
|
427
|
+
_mentionLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(mentionLongPressed:)];
|
|
428
|
+
_mentionLongPress.delegate = self;
|
|
429
|
+
[_textView addGestureRecognizer:_mentionLongPress];
|
|
430
|
+
[tapRecognizer requireGestureRecognizerToFail:_mentionLongPress];
|
|
431
|
+
#endif
|
|
432
|
+
[_textView addGestureRecognizer:tapRecognizer];
|
|
433
|
+
|
|
434
|
+
self.contentView = _textView;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
- (void)didAddSubview:(RCTUIView *)subview
|
|
438
|
+
{
|
|
439
|
+
[super didAddSubview:subview];
|
|
440
|
+
|
|
441
|
+
if (subview == _textView) {
|
|
442
|
+
[self setupLayoutManager];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
- (void)willRemoveSubview:(RCTUIView *)subview
|
|
447
|
+
{
|
|
448
|
+
if (subview == _textView) {
|
|
449
|
+
ENRMDetachLayoutManager(_textView);
|
|
450
|
+
}
|
|
451
|
+
[super willRemoveSubview:subview];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
- (void)setupLayoutManager
|
|
455
|
+
{
|
|
456
|
+
ENRMAttachLayoutManager(_textView, _config);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
- (void)renderTiptapContent:(NSString *)tiptapString
|
|
460
|
+
{
|
|
461
|
+
if (_renderCoordinator.blockAsyncRender)
|
|
462
|
+
return;
|
|
463
|
+
|
|
464
|
+
_cachedTiptap = [tiptapString copy];
|
|
465
|
+
|
|
466
|
+
StyleConfig *config = [_config copy];
|
|
467
|
+
ENRMTiptapParser *parser = _parser;
|
|
468
|
+
ENRMMd4cFlags *md4cFlags = [_md4cFlags copy];
|
|
469
|
+
BOOL isGFM = _isGFM;
|
|
470
|
+
ENRMLinkRegexConfig *linkRegex = _linkRegex;
|
|
471
|
+
ENRMLinkRegexConfig *inlineCodeLinkRegex = _inlineCodeLinkRegex;
|
|
472
|
+
|
|
473
|
+
BOOL allowFontScaling = _fontScaleObserver.allowFontScaling;
|
|
474
|
+
CGFloat maxFontSizeMultiplier = _maxFontSizeMultiplier;
|
|
475
|
+
BOOL allowTrailingMargin = _allowTrailingMargin;
|
|
476
|
+
NSLineBreakStrategy lineBreakStrategy = _lineBreakStrategy;
|
|
477
|
+
ENRMWritingDirectionMode writingDirectionMode = _writingDirectionMode;
|
|
478
|
+
NSWritingDirection resolvedLayoutDirection = _resolvedLayoutDirection;
|
|
479
|
+
|
|
480
|
+
__block ENRMRenderResult *result = nil;
|
|
481
|
+
|
|
482
|
+
[_renderCoordinator
|
|
483
|
+
scheduleRender:^BOOL {
|
|
484
|
+
TiptapASTNode *ast = [parser parseTiptap:tiptapString
|
|
485
|
+
flags:md4cFlags
|
|
486
|
+
isGFM:isGFM
|
|
487
|
+
linkRegex:linkRegex
|
|
488
|
+
inlineCodeLinkRegex:inlineCodeLinkRegex];
|
|
489
|
+
if (!ast)
|
|
490
|
+
return NO;
|
|
491
|
+
|
|
492
|
+
result = ENRMRenderASTNodes(ast.children, config, allowTrailingMargin, allowFontScaling, maxFontSizeMultiplier,
|
|
493
|
+
lineBreakStrategy);
|
|
494
|
+
ENRMApplyWritingDirectionMode(result.attributedText, writingDirectionMode, resolvedLayoutDirection);
|
|
495
|
+
return YES;
|
|
496
|
+
}
|
|
497
|
+
apply:^{
|
|
498
|
+
self->_lastElementMarginBottom = result.lastElementMarginBottom;
|
|
499
|
+
self->_accessibilityInfo = result.accessibilityInfo;
|
|
500
|
+
self->_renderedStyleFingerprint = self->_pendingStyleFingerprint;
|
|
501
|
+
[self->_latexErrorCoordinator wireReporters:result.context.mathReporters];
|
|
502
|
+
[self applyRenderedText:result.attributedText];
|
|
503
|
+
}];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
- (NSMutableAttributedString *)parseAndRenderTiptap:(NSString *)tiptapString
|
|
507
|
+
{
|
|
508
|
+
TiptapASTNode *ast = [_parser parseTiptap:tiptapString
|
|
509
|
+
flags:_md4cFlags
|
|
510
|
+
isGFM:_isGFM
|
|
511
|
+
linkRegex:_linkRegex
|
|
512
|
+
inlineCodeLinkRegex:_inlineCodeLinkRegex];
|
|
513
|
+
if (!ast) {
|
|
514
|
+
return nil;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
ENRMRenderResult *result =
|
|
518
|
+
ENRMRenderASTNodes(ast.children, _config, _allowTrailingMargin, _fontScaleObserver.allowFontScaling,
|
|
519
|
+
_maxFontSizeMultiplier, _lineBreakStrategy);
|
|
520
|
+
ENRMApplyWritingDirectionMode(result.attributedText, _writingDirectionMode, _resolvedLayoutDirection);
|
|
521
|
+
|
|
522
|
+
_lastElementMarginBottom = result.lastElementMarginBottom;
|
|
523
|
+
_accessibilityInfo = result.accessibilityInfo;
|
|
524
|
+
|
|
525
|
+
return result.attributedText;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/// Synchronous rendering for mock view measurement (no UI updates needed).
|
|
529
|
+
- (void)renderTiptapSynchronously:(NSString *)tiptapString
|
|
530
|
+
{
|
|
531
|
+
if (!tiptapString || tiptapString.length == 0) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
_renderCoordinator.blockAsyncRender = YES;
|
|
536
|
+
_cachedTiptap = [tiptapString copy];
|
|
537
|
+
|
|
538
|
+
NSMutableAttributedString *attributedText = [self parseAndRenderTiptap:tiptapString];
|
|
539
|
+
if (!attributedText) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
_textView.attributedText = attributedText;
|
|
544
|
+
_renderedTiptap = [_cachedTiptap copy];
|
|
545
|
+
_renderedStyleFingerprint = _pendingStyleFingerprint;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Kept in sync with the view-free measurement so the rendered line count matches
|
|
549
|
+
// the measured height: both clamp a layout performed at the content width (see
|
|
550
|
+
// contentBounds), so the truncation falls on the same word.
|
|
551
|
+
// numberOfLines == 0 restores the unlimited default.
|
|
552
|
+
- (void)applyLineClampToTextContainer
|
|
553
|
+
{
|
|
554
|
+
if (_numberOfLines > 0) {
|
|
555
|
+
_textView.textContainer.maximumNumberOfLines = _numberOfLines;
|
|
556
|
+
_textView.textContainer.lineBreakMode = _ellipsizeLineBreakMode;
|
|
557
|
+
} else {
|
|
558
|
+
_textView.textContainer.maximumNumberOfLines = 0;
|
|
559
|
+
_textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
- (void)applyRenderedText:(NSMutableAttributedString *)attributedText
|
|
564
|
+
{
|
|
565
|
+
NSUInteger tailStart = _previousTextLength;
|
|
566
|
+
|
|
567
|
+
NSLayoutManager *layoutManager = _textView.layoutManager;
|
|
568
|
+
if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
|
|
569
|
+
[layoutManager setValue:_config forKey:@"config"];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
objc_setAssociatedObject(_textView.textContainer, kTextViewKey, _textView, OBJC_ASSOCIATION_ASSIGN);
|
|
573
|
+
|
|
574
|
+
// Ensure the text container has unlimited height before setting content.
|
|
575
|
+
// updateLayoutMetrics may have shrunk the frame (and thus the text container)
|
|
576
|
+
// from a previous layout pass, which would clip the new attributed text.
|
|
577
|
+
CGRect contentBounds = [self contentBounds];
|
|
578
|
+
CGFloat containerWidth = contentBounds.size.width;
|
|
579
|
+
if (containerWidth <= 0) {
|
|
580
|
+
containerWidth = _textView.textContainer.size.width;
|
|
581
|
+
}
|
|
582
|
+
_textView.textContainer.size = CGSizeMake(containerWidth, CGFLOAT_MAX);
|
|
583
|
+
[self applyLineClampToTextContainer];
|
|
584
|
+
|
|
585
|
+
_accessibilityElements = nil;
|
|
586
|
+
_accessibilityNeedsRebuild = YES;
|
|
587
|
+
|
|
588
|
+
_textView.attributedText = attributedText;
|
|
589
|
+
_renderedTiptap = [_cachedTiptap copy];
|
|
590
|
+
|
|
591
|
+
[_textView.layoutManager invalidateLayoutForCharacterRange:NSMakeRange(0, attributedText.length)
|
|
592
|
+
actualCharacterRange:NULL];
|
|
593
|
+
|
|
594
|
+
// When bounds width is zero (recycled view not yet laid out), skip layout
|
|
595
|
+
// and measurement — layoutSubviews will handle it once the view has real
|
|
596
|
+
// bounds. Measuring with width=0 produces a bogus single-line measurement
|
|
597
|
+
// that corrupts the height sent to Yoga.
|
|
598
|
+
[_spoilerManager setNeedsUpdate];
|
|
599
|
+
|
|
600
|
+
if (contentBounds.size.width > 0) {
|
|
601
|
+
// Font/style changes can produce the same measured size before UIKit has
|
|
602
|
+
// fully refreshed layout, so force one Yoga update after those renders.
|
|
603
|
+
BOOL forceHeightUpdate = _forceHeightUpdateOnNextRender;
|
|
604
|
+
_forceHeightUpdateOnNextRender = NO;
|
|
605
|
+
|
|
606
|
+
[_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
|
|
607
|
+
ENRMSetNeedsDisplay(_textView);
|
|
608
|
+
#if !TARGET_OS_OSX
|
|
609
|
+
[self setNeedsLayout];
|
|
610
|
+
#endif
|
|
611
|
+
|
|
612
|
+
[_spoilerManager updateIfNeeded];
|
|
613
|
+
|
|
614
|
+
CGSize measured = [self measureSize:contentBounds.size.width];
|
|
615
|
+
if (forceHeightUpdate || needsHeightUpdate(measured, contentBounds)) {
|
|
616
|
+
[self requestHeightUpdate];
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (_textView.hidden) {
|
|
621
|
+
dispatch_async(dispatch_get_main_queue(), ^{ self->_textView.hidden = NO; });
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (_streamingAnimation) {
|
|
625
|
+
if (!_fadeAnimator) {
|
|
626
|
+
_fadeAnimator = [[ENRMTailFadeInAnimator alloc] initWithTextView:_textView];
|
|
627
|
+
}
|
|
628
|
+
[_fadeAnimator animateFrom:tailStart to:attributedText.length];
|
|
629
|
+
_previousTextLength = attributedText.length;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#if !TARGET_OS_OSX
|
|
634
|
+
- (void)layoutSubviews
|
|
635
|
+
{
|
|
636
|
+
[super layoutSubviews];
|
|
637
|
+
[_spoilerManager updateIfNeeded];
|
|
638
|
+
}
|
|
639
|
+
#endif
|
|
640
|
+
|
|
641
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
642
|
+
{
|
|
643
|
+
const auto &oldViewProps = *std::static_pointer_cast<EnrichedTiptapTextProps const>(_props);
|
|
644
|
+
const auto &newViewProps = *std::static_pointer_cast<EnrichedTiptapTextProps const>(props);
|
|
645
|
+
|
|
646
|
+
if (oldViewProps.document != newViewProps.document) {
|
|
647
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (_config == nil) {
|
|
651
|
+
_config = [[StyleConfig alloc] init];
|
|
652
|
+
[_config setFontScaleMultiplier:_fontScaleObserver.effectiveFontScale];
|
|
653
|
+
_spoilerManager = [[ENRMSpoilerOverlayManager alloc] initWithTextView:_textView config:_config];
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (applyTiptapStyleToConfig(_config, newViewProps.tiptapStyle, oldViewProps.tiptapStyle)) {
|
|
657
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
658
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (ENRMImageRequestHeadersChanged(oldViewProps.imageRequestHeaders, newViewProps.imageRequestHeaders)) {
|
|
662
|
+
[_config setImageRequestHeaders:ENRMImageRequestHeadersFromProps(newViewProps.imageRequestHeaders)];
|
|
663
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Pill labels change layout, so treat new content like a style change.
|
|
667
|
+
if (ENRMLinkPillContentChanged(oldViewProps.linkPillContent, newViewProps.linkPillContent)) {
|
|
668
|
+
[_config setLinkPillContent:ENRMLinkPillContentFromProps(newViewProps.linkPillContent)];
|
|
669
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
670
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
NSLayoutManager *layoutManager = _textView.layoutManager;
|
|
674
|
+
if ([layoutManager isKindOfClass:[TextViewLayoutManager class]]) {
|
|
675
|
+
StyleConfig *currentConfig = [layoutManager valueForKey:@"config"];
|
|
676
|
+
if (currentConfig != _config) {
|
|
677
|
+
[layoutManager setValue:_config forKey:@"config"];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (_textView.selectable != newViewProps.selectable) {
|
|
682
|
+
_textView.selectable = newViewProps.selectable;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (newViewProps.selectionColor != oldViewProps.selectionColor) {
|
|
686
|
+
ENRMApplySelectionColor(_textView, newViewProps.selectionColor);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) {
|
|
690
|
+
_fontScaleObserver.allowFontScaling = newViewProps.allowFontScaling;
|
|
691
|
+
|
|
692
|
+
if (_config != nil) {
|
|
693
|
+
[_config setFontScaleMultiplier:_fontScaleObserver.effectiveFontScale];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
697
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (newViewProps.maxFontSizeMultiplier != oldViewProps.maxFontSizeMultiplier) {
|
|
701
|
+
_maxFontSizeMultiplier = newViewProps.maxFontSizeMultiplier;
|
|
702
|
+
|
|
703
|
+
if (_config != nil) {
|
|
704
|
+
[_config setMaxFontSizeMultiplier:_maxFontSizeMultiplier];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
708
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (newViewProps.allowTrailingMargin != oldViewProps.allowTrailingMargin) {
|
|
712
|
+
_allowTrailingMargin = newViewProps.allowTrailingMargin;
|
|
713
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
714
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (!ENRMLinkRegexPropsEqual(oldViewProps.linkRecognition.text, newViewProps.linkRecognition.text)) {
|
|
718
|
+
_linkRegex = ENRMLinkRegexConfigFromProps(newViewProps.linkRecognition.text);
|
|
719
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
720
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
721
|
+
}
|
|
722
|
+
if (!ENRMLinkRegexPropsEqual(oldViewProps.linkRecognition.inlineCode, newViewProps.linkRecognition.inlineCode)) {
|
|
723
|
+
_inlineCodeLinkRegex = ENRMLinkRegexConfigFromProps(newViewProps.linkRecognition.inlineCode);
|
|
724
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
725
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (newViewProps.md4cFlags.underline != oldViewProps.md4cFlags.underline ||
|
|
729
|
+
newViewProps.md4cFlags.superscript != oldViewProps.md4cFlags.superscript ||
|
|
730
|
+
newViewProps.md4cFlags.subscript != oldViewProps.md4cFlags.subscript ||
|
|
731
|
+
newViewProps.md4cFlags.latexMath != oldViewProps.md4cFlags.latexMath ||
|
|
732
|
+
newViewProps.md4cFlags.highlight != oldViewProps.md4cFlags.highlight ||
|
|
733
|
+
newViewProps.md4cFlags.hardSoftBreaks != oldViewProps.md4cFlags.hardSoftBreaks ||
|
|
734
|
+
newViewProps.md4cFlags.preserveBlankLines != oldViewProps.md4cFlags.preserveBlankLines ||
|
|
735
|
+
newViewProps.md4cFlags.admonitions != oldViewProps.md4cFlags.admonitions) {
|
|
736
|
+
_md4cFlags = [EnrichedTiptapText flagsFromProps:newViewProps.md4cFlags];
|
|
737
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
738
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (newViewProps.isGFM != oldViewProps.isGFM) {
|
|
742
|
+
_isGFM = newViewProps.isGFM;
|
|
743
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
744
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
_enableLinkPreview = newViewProps.enableLinkPreview;
|
|
748
|
+
_enableTaskListItemToggle = newViewProps.enableTaskListItemToggle;
|
|
749
|
+
_enableImagePress = newViewProps.enableImagePress;
|
|
750
|
+
_enableCodeBlockPress = newViewProps.enableCodeBlockPress;
|
|
751
|
+
|
|
752
|
+
if (ENRMLinkContextMenuItemsChanged(oldViewProps.linkContextMenuItems, newViewProps.linkContextMenuItems)) {
|
|
753
|
+
_linkContextMenus.entries = ENRMLinkContextMenuEntriesFromProps(newViewProps.linkContextMenuItems);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (ENRMContextMenuItemsChanged(oldViewProps.contextMenuItems, newViewProps.contextMenuItems)) {
|
|
757
|
+
_contextMenuItemTexts = ENRMContextMenuTextsFromItems(newViewProps.contextMenuItems);
|
|
758
|
+
_contextMenuItemIcons = ENRMContextMenuIconsFromItems(newViewProps.contextMenuItems);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
_selectionMenuLabels = ENRMParseSelectionMenuLabels(newViewProps.selectionMenuConfig);
|
|
762
|
+
_selectionMenuConfig =
|
|
763
|
+
ENRMBuildSelectionMenuConfig(_selectionMenuLabels, newViewProps.selectionMenuConfig.copyAsTiptap,
|
|
764
|
+
newViewProps.selectionMenuConfig.copyImageUrl);
|
|
765
|
+
|
|
766
|
+
if (ENRMAccessibilityLabelsChanged(oldViewProps.accessibilityLabels, newViewProps.accessibilityLabels)) {
|
|
767
|
+
_accessibilityLabels = [[ENRMAccessibilityLabels alloc] init];
|
|
768
|
+
_accessibilityLabels.bulletPoint =
|
|
769
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.list.bulletPoint.c_str()];
|
|
770
|
+
_accessibilityLabels.nestedBulletPoint =
|
|
771
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.list.nestedBulletPoint.c_str()];
|
|
772
|
+
_accessibilityLabels.orderedItem =
|
|
773
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.list.orderedItem.c_str()];
|
|
774
|
+
_accessibilityLabels.nestedOrderedItem =
|
|
775
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.list.nestedOrderedItem.c_str()];
|
|
776
|
+
_accessibilityLabels.blockquote =
|
|
777
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.blockquote.quote.c_str()];
|
|
778
|
+
_accessibilityLabels.nestedBlockquote =
|
|
779
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.blockquote.nestedQuote.c_str()];
|
|
780
|
+
_accessibilityLabels.tableRow =
|
|
781
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.table.row.c_str()];
|
|
782
|
+
_accessibilityLabels.mathEquation =
|
|
783
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.math.equation.c_str()];
|
|
784
|
+
_accessibilityLabels.codeBlockWrap =
|
|
785
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.codeBlock.wrap.c_str()];
|
|
786
|
+
_accessibilityLabels.codeBlockUnwrap =
|
|
787
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.codeBlock.unwrap.c_str()];
|
|
788
|
+
_accessibilityLabels.rotorHeadings =
|
|
789
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.rotor.headings.c_str()];
|
|
790
|
+
_accessibilityLabels.rotorLinks =
|
|
791
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.rotor.links.c_str()];
|
|
792
|
+
_accessibilityLabels.rotorImages =
|
|
793
|
+
[[NSString alloc] initWithUTF8String:newViewProps.accessibilityLabels.rotor.images.c_str()];
|
|
794
|
+
_accessibilityNeedsRebuild = YES;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (newViewProps.streamingAnimation != oldViewProps.streamingAnimation) {
|
|
798
|
+
_streamingAnimation = newViewProps.streamingAnimation;
|
|
799
|
+
if (_streamingAnimation) {
|
|
800
|
+
_previousTextLength = ENRMGetAttributedText(_textView).length;
|
|
801
|
+
} else {
|
|
802
|
+
[_fadeAnimator cancel];
|
|
803
|
+
_fadeAnimator = nil;
|
|
804
|
+
_previousTextLength = 0;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (newViewProps.spoilerOverlay != oldViewProps.spoilerOverlay) {
|
|
809
|
+
NSString *modeStr = [[NSString alloc] initWithUTF8String:newViewProps.spoilerOverlay.c_str()];
|
|
810
|
+
_spoilerManager.spoilerOverlay = ENRMSpoilerOverlayFromString(modeStr);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (newViewProps.lineBreakStrategyIOS != oldViewProps.lineBreakStrategyIOS) {
|
|
814
|
+
NSString *strategy = [[NSString alloc] initWithUTF8String:newViewProps.lineBreakStrategyIOS.c_str()];
|
|
815
|
+
_lineBreakStrategy = ENRMResolveLineBreakStrategy(strategy);
|
|
816
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
817
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (newViewProps.writingDirection != oldViewProps.writingDirection) {
|
|
821
|
+
NSString *value = [[NSString alloc] initWithUTF8String:newViewProps.writingDirection.c_str()];
|
|
822
|
+
_writingDirectionMode = ENRMResolveWritingDirectionMode(value);
|
|
823
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
824
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (newViewProps.numberOfLines != oldViewProps.numberOfLines) {
|
|
828
|
+
_numberOfLines = (NSInteger)newViewProps.numberOfLines;
|
|
829
|
+
[self applyLineClampToTextContainer];
|
|
830
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
831
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (newViewProps.ellipsizeMode != oldViewProps.ellipsizeMode) {
|
|
835
|
+
_ellipsizeLineBreakMode =
|
|
836
|
+
ENRMResolveEllipsizeLineBreakMode([[NSString alloc] initWithUTF8String:newViewProps.ellipsizeMode.c_str()]);
|
|
837
|
+
[self applyLineClampToTextContainer];
|
|
838
|
+
_forceHeightUpdateOnNextRender = YES;
|
|
839
|
+
_dirtyFlags |= ENRMDirtyRender;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (_dirtyFlags & ENRMDirtyRender) {
|
|
843
|
+
_pendingStyleFingerprint =
|
|
844
|
+
computeStyleFingerprint(newViewProps.tiptapStyle) ^ std::hash<bool>{}(newViewProps.allowTrailingMargin);
|
|
845
|
+
NSString *tiptapString = [[NSString alloc] initWithUTF8String:newViewProps.document.c_str()];
|
|
846
|
+
[self renderTiptapContent:tiptapString];
|
|
847
|
+
_dirtyFlags &= ~ENRMDirtyRender;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
[super updateProps:props oldProps:oldProps];
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
- (void)didMoveToWindow
|
|
854
|
+
{
|
|
855
|
+
[super didMoveToWindow];
|
|
856
|
+
|
|
857
|
+
if (self.window && _renderedTiptap != nil) {
|
|
858
|
+
_textView.hidden = NO;
|
|
859
|
+
// Refresh in place: RCTViewComponentView owns the text view's frame and has
|
|
860
|
+
// already set it to the content frame, so re-assigning self.bounds here would
|
|
861
|
+
// drop the containerStyle insets (see contentBounds).
|
|
862
|
+
ENRMRefreshTextViewLayout(_textView);
|
|
863
|
+
|
|
864
|
+
[_spoilerManager setNeedsUpdate];
|
|
865
|
+
[_spoilerManager updateIfNeeded];
|
|
866
|
+
|
|
867
|
+
CGRect contentBounds = [self contentBounds];
|
|
868
|
+
if (contentBounds.size.width > 0) {
|
|
869
|
+
CGSize measured = [self measureSize:contentBounds.size.width];
|
|
870
|
+
if (needsHeightUpdate(measured, contentBounds)) {
|
|
871
|
+
[self requestHeightUpdate];
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
- (void)prepareForRecycle
|
|
878
|
+
{
|
|
879
|
+
const auto resetProps = std::make_shared<const EnrichedTiptapTextProps>();
|
|
880
|
+
_props = resetProps;
|
|
881
|
+
[_renderCoordinator invalidate];
|
|
882
|
+
|
|
883
|
+
[_fadeAnimator cancel];
|
|
884
|
+
_fadeAnimator = nil;
|
|
885
|
+
_previousTextLength = 0;
|
|
886
|
+
_streamingAnimation = NO;
|
|
887
|
+
_forceHeightUpdateOnNextRender = NO;
|
|
888
|
+
_cachedTiptap = nil;
|
|
889
|
+
_renderedTiptap = nil;
|
|
890
|
+
_config = nil;
|
|
891
|
+
_md4cFlags = [EnrichedTiptapText flagsFromProps:resetProps->md4cFlags];
|
|
892
|
+
_isGFM = resetProps->isGFM;
|
|
893
|
+
_linkRegex = ENRMLinkRegexConfigFromProps(resetProps->linkRecognition.text);
|
|
894
|
+
_inlineCodeLinkRegex = ENRMLinkRegexConfigFromProps(resetProps->linkRecognition.inlineCode);
|
|
895
|
+
_maxFontSizeMultiplier = 0;
|
|
896
|
+
_lastElementMarginBottom = 0;
|
|
897
|
+
_allowTrailingMargin = NO;
|
|
898
|
+
_lineBreakStrategy = NSLineBreakStrategyNone;
|
|
899
|
+
_writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
|
|
900
|
+
_numberOfLines = 0;
|
|
901
|
+
_ellipsizeLineBreakMode = NSLineBreakByTruncatingTail;
|
|
902
|
+
[self applyLineClampToTextContainer];
|
|
903
|
+
_renderedStyleFingerprint = 0;
|
|
904
|
+
_pendingStyleFingerprint = 0;
|
|
905
|
+
_linkContextMenus.entries = @[];
|
|
906
|
+
_contextMenuItemTexts = nil;
|
|
907
|
+
_contextMenuItemIcons = nil;
|
|
908
|
+
_fontScaleObserver.allowFontScaling = resetProps->allowFontScaling;
|
|
909
|
+
_accessibilityLabels = nil;
|
|
910
|
+
_accessibilityElements = nil;
|
|
911
|
+
_accessibilityInfo = nil;
|
|
912
|
+
_accessibilityNeedsRebuild = NO;
|
|
913
|
+
[_spoilerManager removeAllOverlays];
|
|
914
|
+
_spoilerManager = nil;
|
|
915
|
+
if (_textView != nil) {
|
|
916
|
+
ENRMSetAttributedText(_textView, [[NSAttributedString alloc] initWithString:@""]);
|
|
917
|
+
_textView.hidden = YES;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
[super prepareForRecycle];
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
Class<RCTComponentViewProtocol> EnrichedTiptapTextCls(void)
|
|
924
|
+
{
|
|
925
|
+
return EnrichedTiptapText.class;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
- (facebook::react::SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
|
|
929
|
+
{
|
|
930
|
+
if (_textView) {
|
|
931
|
+
CGPoint textViewPoint = [self convertPoint:point toView:_textView];
|
|
932
|
+
if (isPointOnInteractiveElement(_textView, textViewPoint, _enableImagePress, _enableCodeBlockPress)) {
|
|
933
|
+
return nil;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return [super touchEventEmitterAtPoint:point];
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
- (void)emitLinkPress:(NSString *)url
|
|
941
|
+
{
|
|
942
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
943
|
+
if (emitter)
|
|
944
|
+
emitter->onLinkPress({.url = std::string(url.UTF8String)});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
- (void)emitMentionPress:(NSString *)mentionId label:(NSString *)label
|
|
948
|
+
{
|
|
949
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
950
|
+
if (emitter)
|
|
951
|
+
emitter->onMentionPress(
|
|
952
|
+
{.mentionId = std::string(mentionId.UTF8String ?: ""), .label = std::string(label.UTF8String ?: "")});
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
- (void)emitMentionLongPress:(NSString *)mentionId label:(NSString *)label
|
|
956
|
+
{
|
|
957
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
958
|
+
if (emitter)
|
|
959
|
+
emitter->onMentionLongPress(
|
|
960
|
+
{.mentionId = std::string(mentionId.UTF8String ?: ""), .label = std::string(label.UTF8String ?: "")});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
- (void)emitLinkLongPress:(NSString *)url
|
|
964
|
+
{
|
|
965
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
966
|
+
if (emitter)
|
|
967
|
+
emitter->onLinkLongPress({.url = std::string(url.UTF8String)});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
- (void)emitLinkContextMenuItemPress:(NSString *)itemText pattern:(NSString *)pattern url:(NSString *)url
|
|
971
|
+
{
|
|
972
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
973
|
+
if (emitter) {
|
|
974
|
+
emitter->onLinkContextMenuItemPress({.url = std::string(url.UTF8String),
|
|
975
|
+
.pattern = std::string(pattern.UTF8String),
|
|
976
|
+
.itemText = std::string(itemText.UTF8String)});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
- (void)emitImagePress:(NSString *)url altText:(NSString *)altText
|
|
981
|
+
{
|
|
982
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
983
|
+
if (emitter)
|
|
984
|
+
emitter->onImagePress({.url = std::string(url.UTF8String ?: ""), .altText = std::string(altText.UTF8String ?: "")});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
- (void)emitCodeBlockPress:(NSString *)code language:(NSString *)language
|
|
988
|
+
{
|
|
989
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
990
|
+
if (emitter)
|
|
991
|
+
emitter->onCodeBlockPress(
|
|
992
|
+
{.code = std::string(code.UTF8String ?: ""), .language = std::string(language.UTF8String ?: "")});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
- (void)emitTaskListItemPress:(NSInteger)index checked:(BOOL)checked text:(NSString *)text
|
|
996
|
+
{
|
|
997
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
998
|
+
if (emitter)
|
|
999
|
+
emitter->onTaskListItemPress({.index = (int)index, .checked = checked, .text = std::string(text.UTF8String ?: "")});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
- (void)emitContextMenuItemPress:(NSString *)itemText
|
|
1003
|
+
selectedText:(NSString *)selectedText
|
|
1004
|
+
selectionStart:(NSUInteger)selectionStart
|
|
1005
|
+
selectionEnd:(NSUInteger)selectionEnd
|
|
1006
|
+
{
|
|
1007
|
+
auto emitter = std::static_pointer_cast<EnrichedTiptapTextEventEmitter const>(_eventEmitter);
|
|
1008
|
+
if (emitter)
|
|
1009
|
+
emitter->onContextMenuItemPress({
|
|
1010
|
+
.itemText = std::string(itemText.UTF8String),
|
|
1011
|
+
.selectedText = std::string(selectedText.UTF8String),
|
|
1012
|
+
.selectionStart = (int)selectionStart,
|
|
1013
|
+
.selectionEnd = (int)selectionEnd,
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
- (void)updateEventEmitter:(const facebook::react::EventEmitter::Shared &)eventEmitter
|
|
1018
|
+
{
|
|
1019
|
+
[super updateEventEmitter:eventEmitter];
|
|
1020
|
+
[_latexErrorCoordinator flushPending];
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
- (void)textTapped:(ENRMTapRecognizer *)recognizer
|
|
1024
|
+
{
|
|
1025
|
+
ENRMPlatformTextView *textView = (ENRMPlatformTextView *)recognizer.view;
|
|
1026
|
+
|
|
1027
|
+
if (_enableTaskListItemToggle &&
|
|
1028
|
+
handleTaskListTapWithSharedLogic(
|
|
1029
|
+
textView, recognizer, &self->_cachedTiptap, self->_config,
|
|
1030
|
+
^(NSInteger index, BOOL checked, NSString *itemText) {
|
|
1031
|
+
[self emitTaskListItemPress:index checked:checked text:itemText];
|
|
1032
|
+
},
|
|
1033
|
+
^(NSString *updatedTiptap) { [self renderTiptapContent:updatedTiptap]; })) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (handleSpoilerTap(textView, recognizer, _spoilerManager)) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (ENRMHandleTapOnTextView(textView, recognizer, ^(NSString *url) { [self emitLinkPress:url]; })) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
NSDictionary<NSString *, NSString *> *mention = mentionAtTapLocation(textView, recognizer);
|
|
1046
|
+
if (mention) {
|
|
1047
|
+
[self emitMentionPress:mention[@"id"] label:mention[@"label"]];
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (_enableImagePress) {
|
|
1052
|
+
NSDictionary<NSString *, NSString *> *image = imageAtTapLocation(textView, recognizer);
|
|
1053
|
+
if (image) {
|
|
1054
|
+
[self emitImagePress:image[@"url"] altText:image[@"altText"]];
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Skip while text is selected so the tap clears the selection instead of
|
|
1060
|
+
// firing the press (matches the web guard).
|
|
1061
|
+
if (_enableCodeBlockPress && textView.selectedRange.length == 0) {
|
|
1062
|
+
NSDictionary<NSString *, NSString *> *codeBlock = codeBlockAtTapLocation(textView, recognizer);
|
|
1063
|
+
if (codeBlock) {
|
|
1064
|
+
[self emitCodeBlockPress:codeBlock[@"code"] language:codeBlock[@"language"]];
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
#if !TARGET_OS_OSX
|
|
1071
|
+
- (void)mentionLongPressed:(UILongPressGestureRecognizer *)recognizer
|
|
1072
|
+
{
|
|
1073
|
+
if (recognizer.state != UIGestureRecognizerStateBegan)
|
|
1074
|
+
return;
|
|
1075
|
+
NSDictionary<NSString *, NSString *> *mention = mentionAtPoint(_textView, [recognizer locationInView:_textView]);
|
|
1076
|
+
if (!mention)
|
|
1077
|
+
return;
|
|
1078
|
+
[self emitMentionLongPress:mention[@"id"] label:mention[@"label"]];
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
1082
|
+
{
|
|
1083
|
+
if (gestureRecognizer != _mentionLongPress)
|
|
1084
|
+
return YES;
|
|
1085
|
+
return mentionAtPoint(_textView, [_mentionLongPress locationInView:_textView]) != nil;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
1089
|
+
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
1090
|
+
{
|
|
1091
|
+
if (gestureRecognizer == _mentionLongPress &&
|
|
1092
|
+
[otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
|
|
1093
|
+
return YES;
|
|
1094
|
+
return NO;
|
|
1095
|
+
}
|
|
1096
|
+
#endif
|
|
1097
|
+
|
|
1098
|
+
#pragma mark - UITextViewDelegate (Link Interaction)
|
|
1099
|
+
|
|
1100
|
+
#if !TARGET_OS_OSX
|
|
1101
|
+
- (UITextItemMenuConfiguration *)textView:(UITextView *)textView
|
|
1102
|
+
menuConfigurationForTextItem:(UITextItem *)textItem
|
|
1103
|
+
defaultMenu:(UIMenu *)defaultMenu API_AVAILABLE(ios(17.0))
|
|
1104
|
+
{
|
|
1105
|
+
__weak EnrichedTiptapText *weakSelf = self;
|
|
1106
|
+
return ENRMLinkMenuConfigurationForTextItem(textView, textItem, defaultMenu, _linkContextMenus, _enableLinkPreview,
|
|
1107
|
+
^(NSString *url) { [weakSelf emitLinkLongPress:url]; });
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
- (ENRMLinkContextMenus *)linkContextMenusForTextView:(UITextView *)textView
|
|
1111
|
+
{
|
|
1112
|
+
return _linkContextMenus;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
- (BOOL)textView:(ENRMPlatformTextView *)textView
|
|
1116
|
+
shouldInteractWithURL:(NSURL *)URL
|
|
1117
|
+
inRange:(NSRange)characterRange
|
|
1118
|
+
interaction:(UITextItemInteraction)interaction
|
|
1119
|
+
{
|
|
1120
|
+
if (interaction != UITextItemInteractionPresentActions) {
|
|
1121
|
+
return YES;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
NSString *urlString = linkURLAtRange(textView, characterRange);
|
|
1125
|
+
|
|
1126
|
+
// A link with a menu is presented by the iOS 17 callback above and must not be vetoed here.
|
|
1127
|
+
if ([_linkContextMenus hasMenuForURL:urlString])
|
|
1128
|
+
return YES;
|
|
1129
|
+
|
|
1130
|
+
if (!urlString || _enableLinkPreview) {
|
|
1131
|
+
return YES;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
[self emitLinkLongPress:urlString];
|
|
1135
|
+
return NO;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
#pragma mark - UITextViewDelegate (Edit Menu)
|
|
1139
|
+
|
|
1140
|
+
// TODO: Remove API_AVAILABLE(ios(16.0)) guard when the minimum iOS deployment target in RN is bumped to 16.
|
|
1141
|
+
- (UIMenu *)textView:(ENRMPlatformTextView *)textView
|
|
1142
|
+
editMenuForTextInRange:(NSRange)range
|
|
1143
|
+
suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0))
|
|
1144
|
+
{
|
|
1145
|
+
__weak EnrichedTiptapText *weakSelf = self;
|
|
1146
|
+
ENRMContextMenuPressHandler handler =
|
|
1147
|
+
^(NSString *itemText, NSString *selectedText, NSUInteger selectionStart, NSUInteger selectionEnd) {
|
|
1148
|
+
EnrichedTiptapText *strongSelf = weakSelf;
|
|
1149
|
+
if (strongSelf)
|
|
1150
|
+
[strongSelf emitContextMenuItemPress:itemText
|
|
1151
|
+
selectedText:selectedText
|
|
1152
|
+
selectionStart:selectionStart
|
|
1153
|
+
selectionEnd:selectionEnd];
|
|
1154
|
+
};
|
|
1155
|
+
NSMutableArray<UIAction *> *customActions =
|
|
1156
|
+
ENRMBuildContextMenuActions(_contextMenuItemTexts, _contextMenuItemIcons, textView, range, handler);
|
|
1157
|
+
|
|
1158
|
+
return buildEditMenuForSelection(textView.attributedText, range, _cachedTiptap, _config, suggestedActions,
|
|
1159
|
+
customActions, _selectionMenuConfig);
|
|
1160
|
+
}
|
|
1161
|
+
#endif
|
|
1162
|
+
|
|
1163
|
+
#pragma mark - Accessibility (VoiceOver Navigation)
|
|
1164
|
+
|
|
1165
|
+
- (void)rebuildAccessibilityElementsIfNeeded
|
|
1166
|
+
{
|
|
1167
|
+
if (!_accessibilityNeedsRebuild) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
_accessibilityNeedsRebuild = NO;
|
|
1171
|
+
#if !TARGET_OS_OSX
|
|
1172
|
+
_accessibilityElements = [TiptapAccessibilityElementBuilder buildElementsForTextView:_textView
|
|
1173
|
+
info:_accessibilityInfo
|
|
1174
|
+
labels:_accessibilityLabels
|
|
1175
|
+
container:self];
|
|
1176
|
+
#else
|
|
1177
|
+
_accessibilityElements = [NSMutableArray array];
|
|
1178
|
+
#endif
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
- (BOOL)isAccessibilityElement
|
|
1182
|
+
{
|
|
1183
|
+
return NO;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
- (NSInteger)accessibilityElementCount
|
|
1187
|
+
{
|
|
1188
|
+
[self rebuildAccessibilityElementsIfNeeded];
|
|
1189
|
+
return _accessibilityElements.count;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
- (id)accessibilityElementAtIndex:(NSInteger)index
|
|
1193
|
+
{
|
|
1194
|
+
[self rebuildAccessibilityElementsIfNeeded];
|
|
1195
|
+
if (index < 0 || index >= (NSInteger)_accessibilityElements.count) {
|
|
1196
|
+
return nil;
|
|
1197
|
+
}
|
|
1198
|
+
return _accessibilityElements[index];
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
- (NSInteger)indexOfAccessibilityElement:(id)element
|
|
1202
|
+
{
|
|
1203
|
+
[self rebuildAccessibilityElementsIfNeeded];
|
|
1204
|
+
return [_accessibilityElements indexOfObject:element];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
- (NSArray *)accessibilityElements
|
|
1208
|
+
{
|
|
1209
|
+
[self rebuildAccessibilityElementsIfNeeded];
|
|
1210
|
+
return _accessibilityElements;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
#if !TARGET_OS_OSX
|
|
1214
|
+
- (NSArray<UIAccessibilityCustomRotor *> *)accessibilityCustomRotors
|
|
1215
|
+
{
|
|
1216
|
+
[self rebuildAccessibilityElementsIfNeeded];
|
|
1217
|
+
return [TiptapAccessibilityElementBuilder buildRotorsFromElements:_accessibilityElements
|
|
1218
|
+
labels:_accessibilityLabels];
|
|
1219
|
+
}
|
|
1220
|
+
#endif
|
|
1221
|
+
|
|
1222
|
+
@end
|