@mathers101/react-native-render-rich-text 0.0.0-stage → 0.1.9
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
package/android/src/main/java/com/renderrichtext/utils/text/view/LinkLongPressMovementMethod.kt
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
package com.renderrichtext.utils.text.view
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import android.text.Spannable
|
|
6
|
+
import android.text.method.ArrowKeyMovementMethod
|
|
7
|
+
import android.view.MotionEvent
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewConfiguration
|
|
10
|
+
import android.view.ViewParent
|
|
11
|
+
import android.widget.TextView
|
|
12
|
+
import com.renderrichtext.spans.CodeBlockSpan
|
|
13
|
+
import com.renderrichtext.spans.FactoidSpan
|
|
14
|
+
import com.renderrichtext.spans.ImageSpan
|
|
15
|
+
import com.renderrichtext.spans.LinkSpan
|
|
16
|
+
import com.renderrichtext.spans.MentionChipSpan
|
|
17
|
+
import com.renderrichtext.spans.MentionSpan
|
|
18
|
+
import com.renderrichtext.spans.SpoilerSpan
|
|
19
|
+
import com.renderrichtext.spoiler.SpoilerCapable
|
|
20
|
+
import kotlin.math.abs
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Movement method that adds link tap / long-press and spoiler tap handling on
|
|
24
|
+
* top of [ArrowKeyMovementMethod], the method [setTextIsSelectable] installs.
|
|
25
|
+
*
|
|
26
|
+
* Must never mutate the buffer's Selection spans — the platform Editor
|
|
27
|
+
* manages them during long-press gestures, and removing or overwriting
|
|
28
|
+
* them mid-gesture crashes on some OEM skins.
|
|
29
|
+
*/
|
|
30
|
+
class LinkLongPressMovementMethod : ArrowKeyMovementMethod() {
|
|
31
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
32
|
+
private var longPressRunnable: Runnable? = null
|
|
33
|
+
|
|
34
|
+
private var startX = 0f
|
|
35
|
+
private var startY = 0f
|
|
36
|
+
private var pressedLink: LinkSpan? = null
|
|
37
|
+
private var pressedImage: ImageSpan? = null
|
|
38
|
+
private var pressedFactoid: FactoidSpan? = null
|
|
39
|
+
private var pressedMention: MentionSpan? = null
|
|
40
|
+
private var pressedCodeBlock: CodeBlockSpan? = null
|
|
41
|
+
|
|
42
|
+
var isLinkTouchActive: Boolean = false
|
|
43
|
+
private set
|
|
44
|
+
private var isTouchWithinTextBounds: Boolean = true
|
|
45
|
+
private var disallowsParentIntercept = false
|
|
46
|
+
|
|
47
|
+
override fun onTouchEvent(
|
|
48
|
+
widget: TextView,
|
|
49
|
+
buffer: Spannable,
|
|
50
|
+
event: MotionEvent,
|
|
51
|
+
): Boolean {
|
|
52
|
+
when (event.action) {
|
|
53
|
+
MotionEvent.ACTION_DOWN -> {
|
|
54
|
+
startX = event.x
|
|
55
|
+
startY = event.y
|
|
56
|
+
|
|
57
|
+
pressedLink = findLinkSpan(widget, buffer, event)
|
|
58
|
+
pressedImage =
|
|
59
|
+
if (pressedLink == null && imagePressHost(widget)?.imagePressEnabled == true) {
|
|
60
|
+
findImageSpan(widget, buffer, event)
|
|
61
|
+
} else {
|
|
62
|
+
null
|
|
63
|
+
}
|
|
64
|
+
pressedFactoid =
|
|
65
|
+
if (pressedLink == null && pressedImage == null) {
|
|
66
|
+
findFactoidSpan(widget, buffer, event)
|
|
67
|
+
} else {
|
|
68
|
+
null
|
|
69
|
+
}
|
|
70
|
+
pressedMention =
|
|
71
|
+
if (pressedLink == null && pressedImage == null && pressedFactoid == null) {
|
|
72
|
+
findMentionSpan(widget, buffer, event)
|
|
73
|
+
} else {
|
|
74
|
+
null
|
|
75
|
+
}
|
|
76
|
+
pressedCodeBlock =
|
|
77
|
+
if (pressedLink == null &&
|
|
78
|
+
pressedImage == null &&
|
|
79
|
+
pressedFactoid == null &&
|
|
80
|
+
pressedMention == null &&
|
|
81
|
+
// Skip while text is selected so the tap clears the selection
|
|
82
|
+
// instead of firing the press (matches the web guard).
|
|
83
|
+
!widget.hasSelection() &&
|
|
84
|
+
codeBlockPressHost(widget)?.codeBlockPressEnabled == true
|
|
85
|
+
) {
|
|
86
|
+
findCodeBlockSpan(widget, buffer, event)
|
|
87
|
+
} else {
|
|
88
|
+
null
|
|
89
|
+
}
|
|
90
|
+
isLinkTouchActive =
|
|
91
|
+
pressedLink != null || pressedImage != null || pressedFactoid != null ||
|
|
92
|
+
pressedMention != null || pressedCodeBlock != null
|
|
93
|
+
setFactoidPressed(widget, pressedFactoid, pressed = true)
|
|
94
|
+
setMentionPressed(widget, pressedMention, pressed = true)
|
|
95
|
+
pressedLink?.let { scheduleLongPress(widget, it) }
|
|
96
|
+
pressedMention?.let { scheduleMentionLongPress(widget, it) }
|
|
97
|
+
// Keeps RNGH Pressable and scrolling parents from stealing the press before ACTION_UP.
|
|
98
|
+
disallowsParentIntercept = isLinkTouchActive
|
|
99
|
+
if (isLinkTouchActive) widget.parent?.requestDisallowInterceptTouchEvent(true)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
MotionEvent.ACTION_MOVE -> {
|
|
103
|
+
val config = ViewConfiguration.get(widget.context)
|
|
104
|
+
if (abs(event.x - startX) > config.scaledTouchSlop ||
|
|
105
|
+
abs(event.y - startY) > config.scaledTouchSlop
|
|
106
|
+
) {
|
|
107
|
+
cancelLongPress()
|
|
108
|
+
isLinkTouchActive = false
|
|
109
|
+
setFactoidPressed(widget, pressedFactoid, pressed = false)
|
|
110
|
+
setMentionPressed(widget, pressedMention, pressed = false)
|
|
111
|
+
pressedLink = null
|
|
112
|
+
pressedImage = null
|
|
113
|
+
pressedFactoid = null
|
|
114
|
+
pressedMention = null
|
|
115
|
+
pressedCodeBlock = null
|
|
116
|
+
releaseParentIntercept(widget)
|
|
117
|
+
} else if (pressedFactoid != null) {
|
|
118
|
+
setFactoidPressed(widget, pressedFactoid, findFactoidSpan(widget, buffer, event) === pressedFactoid)
|
|
119
|
+
} else if (pressedMention != null) {
|
|
120
|
+
setMentionPressed(widget, pressedMention, findMentionSpan(widget, buffer, event) === pressedMention)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
MotionEvent.ACTION_UP -> {
|
|
125
|
+
cancelLongPress()
|
|
126
|
+
val tappedLink = pressedLink
|
|
127
|
+
val tappedImage = pressedImage
|
|
128
|
+
val tappedFactoid = pressedFactoid
|
|
129
|
+
val tappedMention = pressedMention
|
|
130
|
+
val tappedCodeBlock = pressedCodeBlock
|
|
131
|
+
isLinkTouchActive = false
|
|
132
|
+
setFactoidPressed(widget, tappedFactoid, pressed = false)
|
|
133
|
+
setMentionPressed(widget, tappedMention, pressed = false)
|
|
134
|
+
pressedLink = null
|
|
135
|
+
pressedImage = null
|
|
136
|
+
pressedFactoid = null
|
|
137
|
+
pressedMention = null
|
|
138
|
+
pressedCodeBlock = null
|
|
139
|
+
releaseParentIntercept(widget)
|
|
140
|
+
|
|
141
|
+
if (handleSpoilerTap(widget, buffer, event)) {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// LinkSpan.onClick itself swallows the click that follows a completed
|
|
146
|
+
// long-press (and resets its internal flag), so it is always invoked
|
|
147
|
+
// for a tap that started and ended on the same link.
|
|
148
|
+
if (tappedLink != null && findLinkSpan(widget, buffer, event) === tappedLink) {
|
|
149
|
+
tappedLink.onClick(widget)
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (tappedImage != null && findImageSpan(widget, buffer, event) === tappedImage) {
|
|
154
|
+
imagePressHost(widget)?.emitOnImagePress(tappedImage.imageUrl, tappedImage.altText)
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (tappedFactoid != null && findFactoidSpan(widget, buffer, event) === tappedFactoid) {
|
|
159
|
+
tappedFactoid.onClick(widget)
|
|
160
|
+
return true
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (tappedMention != null && findMentionSpan(widget, buffer, event) === tappedMention) {
|
|
164
|
+
tappedMention.onClick(widget)
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (tappedCodeBlock != null && findCodeBlockSpan(widget, buffer, event) === tappedCodeBlock) {
|
|
169
|
+
codeBlockPressHost(widget)?.emitOnCodeBlockPress(tappedCodeBlock.code, tappedCodeBlock.language)
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
MotionEvent.ACTION_CANCEL -> {
|
|
175
|
+
cancelLongPress()
|
|
176
|
+
isLinkTouchActive = false
|
|
177
|
+
setFactoidPressed(widget, pressedFactoid, pressed = false)
|
|
178
|
+
setMentionPressed(widget, pressedMention, pressed = false)
|
|
179
|
+
pressedLink = null
|
|
180
|
+
pressedImage = null
|
|
181
|
+
pressedCodeBlock = null
|
|
182
|
+
pressedFactoid = null
|
|
183
|
+
pressedMention = null
|
|
184
|
+
releaseParentIntercept(widget)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// getOffsetForHorizontal snaps to the nearest character, so without this
|
|
189
|
+
// guard taps in empty space past the end of a line would still be treated
|
|
190
|
+
// as text touches. Let them fall through to the parent (e.g. RNGH
|
|
191
|
+
// Pressable) instead.
|
|
192
|
+
if (!isTouchWithinTextBounds) {
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return super.onTouchEvent(widget, buffer, event)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun setFactoidPressed(
|
|
200
|
+
widget: TextView,
|
|
201
|
+
span: FactoidSpan?,
|
|
202
|
+
pressed: Boolean,
|
|
203
|
+
) {
|
|
204
|
+
if (span == null || span.pressed == pressed) return
|
|
205
|
+
span.pressed = pressed
|
|
206
|
+
widget.invalidate()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private fun setMentionPressed(
|
|
210
|
+
widget: TextView,
|
|
211
|
+
span: MentionSpan?,
|
|
212
|
+
pressed: Boolean,
|
|
213
|
+
) {
|
|
214
|
+
if (span == null || span.pressed == pressed) return
|
|
215
|
+
span.pressed = pressed
|
|
216
|
+
val text = widget.text as? Spannable
|
|
217
|
+
val start = text?.getSpanStart(span) ?: -1
|
|
218
|
+
val end = if (text != null && start >= 0) text.getSpanEnd(span) else -1
|
|
219
|
+
val chip =
|
|
220
|
+
if (text != null && end > start) {
|
|
221
|
+
text.getSpans(start, end, MentionChipSpan::class.java).firstOrNull { candidate ->
|
|
222
|
+
text.getSpanStart(candidate) == start && text.getSpanEnd(candidate) == end
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
null
|
|
226
|
+
}
|
|
227
|
+
// A factoid fill is a line background, so invalidate() draws it again. The mention
|
|
228
|
+
// is a replacement run, and a selectable TextView keeps that run in a display list
|
|
229
|
+
// that invalidate() does not rebuild. Re-setting the chip records the darker fill.
|
|
230
|
+
if (text != null && chip != null) {
|
|
231
|
+
chip.pressed = pressed
|
|
232
|
+
text.setSpan(chip, start, end, text.getSpanFlags(chip))
|
|
233
|
+
} else {
|
|
234
|
+
widget.invalidate()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private fun scheduleLongPress(
|
|
239
|
+
widget: TextView,
|
|
240
|
+
span: LinkSpan,
|
|
241
|
+
) {
|
|
242
|
+
cancelLongPress()
|
|
243
|
+
// The view's own long-press races the runnable below; cancel it so only the link fires.
|
|
244
|
+
widget.cancelLongPress()
|
|
245
|
+
|
|
246
|
+
longPressRunnable =
|
|
247
|
+
Runnable {
|
|
248
|
+
// Execute the long click logic on the span
|
|
249
|
+
if (span.onLongClick(widget)) {
|
|
250
|
+
// If consumed, cancel the system's own long-press logic (like context menus)
|
|
251
|
+
widget.cancelLongPress()
|
|
252
|
+
}
|
|
253
|
+
longPressRunnable = null
|
|
254
|
+
}.also {
|
|
255
|
+
handler.postDelayed(it, ViewConfiguration.getLongPressTimeout().toLong())
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private fun scheduleMentionLongPress(
|
|
260
|
+
widget: TextView,
|
|
261
|
+
span: MentionSpan,
|
|
262
|
+
) {
|
|
263
|
+
cancelLongPress()
|
|
264
|
+
widget.cancelLongPress()
|
|
265
|
+
|
|
266
|
+
longPressRunnable =
|
|
267
|
+
Runnable {
|
|
268
|
+
if (span.onLongClick(widget)) {
|
|
269
|
+
widget.cancelLongPress()
|
|
270
|
+
}
|
|
271
|
+
longPressRunnable = null
|
|
272
|
+
}.also {
|
|
273
|
+
handler.postDelayed(it, ViewConfiguration.getLongPressTimeout().toLong())
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private fun cancelLongPress() {
|
|
278
|
+
longPressRunnable?.let(handler::removeCallbacks)
|
|
279
|
+
longPressRunnable = null
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Undo only our own request: a long-press drag selection relies on the Editor's
|
|
283
|
+
// request staying in place until ACTION_UP, or a scrolling parent steals the drag.
|
|
284
|
+
private fun releaseParentIntercept(widget: TextView) {
|
|
285
|
+
if (!disallowsParentIntercept) return
|
|
286
|
+
disallowsParentIntercept = false
|
|
287
|
+
widget.parent?.requestDisallowInterceptTouchEvent(false)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private fun charOffsetAt(
|
|
291
|
+
widget: TextView,
|
|
292
|
+
event: MotionEvent,
|
|
293
|
+
): Int? {
|
|
294
|
+
val x = event.x - widget.totalPaddingLeft + widget.scrollX
|
|
295
|
+
val y = event.y - widget.totalPaddingTop + widget.scrollY
|
|
296
|
+
val layout = widget.layout ?: return null
|
|
297
|
+
|
|
298
|
+
// getLineForVertical clamps to the first/last line for out-of-range
|
|
299
|
+
// values, so taps in vertical padding would silently map to a real
|
|
300
|
+
// line. Reject them before proceeding.
|
|
301
|
+
if (y < 0f || y > layout.height) {
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
val line = layout.getLineForVertical(y.toInt())
|
|
306
|
+
|
|
307
|
+
// getOffsetForHorizontal snaps to the nearest character even when the
|
|
308
|
+
// tap is outside the actual text content (e.g. empty space after the
|
|
309
|
+
// last word on a line). Guard against that by checking the tap falls
|
|
310
|
+
// within the line's text bounds.
|
|
311
|
+
if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) {
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return layout.getOffsetForHorizontal(line, x)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun findLinkSpan(
|
|
319
|
+
widget: TextView,
|
|
320
|
+
buffer: Spannable,
|
|
321
|
+
event: MotionEvent,
|
|
322
|
+
): LinkSpan? {
|
|
323
|
+
val offset = charOffsetAt(widget, event) ?: return null
|
|
324
|
+
return buffer.getSpans(offset, offset, LinkSpan::class.java).firstOrNull()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private fun findMentionSpan(
|
|
328
|
+
widget: TextView,
|
|
329
|
+
buffer: Spannable,
|
|
330
|
+
event: MotionEvent,
|
|
331
|
+
): MentionSpan? {
|
|
332
|
+
val offset = charOffsetAt(widget, event) ?: return null
|
|
333
|
+
return buffer
|
|
334
|
+
.getSpans(offset, offset, MentionSpan::class.java)
|
|
335
|
+
.minByOrNull { buffer.getSpanEnd(it) - buffer.getSpanStart(it) }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private fun findFactoidSpan(
|
|
339
|
+
widget: TextView,
|
|
340
|
+
buffer: Spannable,
|
|
341
|
+
event: MotionEvent,
|
|
342
|
+
): FactoidSpan? {
|
|
343
|
+
val offset = charOffsetAt(widget, event) ?: return null
|
|
344
|
+
return buffer
|
|
345
|
+
.getSpans(offset, offset, FactoidSpan::class.java)
|
|
346
|
+
.minByOrNull { buffer.getSpanEnd(it) - buffer.getSpanStart(it) }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private fun findImageSpan(
|
|
350
|
+
widget: TextView,
|
|
351
|
+
buffer: Spannable,
|
|
352
|
+
event: MotionEvent,
|
|
353
|
+
): ImageSpan? {
|
|
354
|
+
val offset = charOffsetAt(widget, event) ?: return null
|
|
355
|
+
return buffer.getSpans(offset, offset, ImageSpan::class.java).firstOrNull()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private fun imagePressHost(widget: View): ImagePressHost? {
|
|
359
|
+
if (widget is ImagePressHost) return widget
|
|
360
|
+
var parent: ViewParent? = widget.parent
|
|
361
|
+
while (parent != null && parent !is ImagePressHost) {
|
|
362
|
+
parent = parent.parent
|
|
363
|
+
}
|
|
364
|
+
return parent as? ImagePressHost
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// A code block is a whole-line box, so this matches a tap anywhere on the line
|
|
368
|
+
// (ignoring x), unlike charOffsetAt which needs an exact glyph.
|
|
369
|
+
private fun findCodeBlockSpan(
|
|
370
|
+
widget: TextView,
|
|
371
|
+
buffer: Spannable,
|
|
372
|
+
event: MotionEvent,
|
|
373
|
+
): CodeBlockSpan? {
|
|
374
|
+
val layout = widget.layout ?: return null
|
|
375
|
+
val y = event.y - widget.totalPaddingTop + widget.scrollY
|
|
376
|
+
if (y < 0f || y > layout.height) return null
|
|
377
|
+
val line = layout.getLineForVertical(y.toInt())
|
|
378
|
+
val lineStart = layout.getLineStart(line)
|
|
379
|
+
val lineEnd = layout.getLineEnd(line)
|
|
380
|
+
return buffer.getSpans(lineStart, lineEnd, CodeBlockSpan::class.java).firstOrNull()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private fun codeBlockPressHost(widget: View): CodeBlockPressHost? {
|
|
384
|
+
if (widget is CodeBlockPressHost) return widget
|
|
385
|
+
var parent: ViewParent? = widget.parent
|
|
386
|
+
while (parent != null && parent !is CodeBlockPressHost) {
|
|
387
|
+
parent = parent.parent
|
|
388
|
+
}
|
|
389
|
+
return parent as? CodeBlockPressHost
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private fun handleSpoilerTap(
|
|
393
|
+
widget: TextView,
|
|
394
|
+
buffer: Spannable,
|
|
395
|
+
event: MotionEvent,
|
|
396
|
+
): Boolean {
|
|
397
|
+
val offset = charOffsetAt(widget, event) ?: return false
|
|
398
|
+
val tappedSpan =
|
|
399
|
+
buffer
|
|
400
|
+
.getSpans(offset, offset, SpoilerSpan::class.java)
|
|
401
|
+
.firstOrNull { !it.revealed && !it.revealing } ?: return false
|
|
402
|
+
|
|
403
|
+
val drawer = (widget as? SpoilerCapable)?.spoilerOverlayDrawer ?: return false
|
|
404
|
+
val spans = expandContiguousSpoilers(buffer, tappedSpan)
|
|
405
|
+
val remaining = intArrayOf(spans.size)
|
|
406
|
+
|
|
407
|
+
for (span in spans) {
|
|
408
|
+
drawer.revealSpan(span) {
|
|
409
|
+
remaining[0]--
|
|
410
|
+
if (remaining[0] <= 0) widget.invalidate()
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
widget.invalidate()
|
|
414
|
+
return true
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private fun expandContiguousSpoilers(
|
|
418
|
+
buffer: Spannable,
|
|
419
|
+
seed: SpoilerSpan,
|
|
420
|
+
): List<SpoilerSpan> {
|
|
421
|
+
val allSpans = buffer.getSpans(0, buffer.length, SpoilerSpan::class.java)
|
|
422
|
+
if (allSpans.size <= 1) return listOf(seed)
|
|
423
|
+
|
|
424
|
+
val result = mutableSetOf(seed)
|
|
425
|
+
var rangeStart = buffer.getSpanStart(seed)
|
|
426
|
+
var rangeEnd = buffer.getSpanEnd(seed)
|
|
427
|
+
var changed = true
|
|
428
|
+
while (changed) {
|
|
429
|
+
changed = false
|
|
430
|
+
for (span in allSpans) {
|
|
431
|
+
if (span in result) continue
|
|
432
|
+
val spanStart = buffer.getSpanStart(span)
|
|
433
|
+
val spanEnd = buffer.getSpanEnd(span)
|
|
434
|
+
if (spanEnd >= rangeStart && spanStart <= rangeEnd) {
|
|
435
|
+
result.add(span)
|
|
436
|
+
if (spanStart < rangeStart) rangeStart = spanStart
|
|
437
|
+
if (spanEnd > rangeEnd) rangeEnd = spanEnd
|
|
438
|
+
changed = true
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return result.sortedBy { buffer.getSpanStart(it) }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
companion object {
|
|
446
|
+
@JvmStatic
|
|
447
|
+
fun createInstance(): LinkLongPressMovementMethod = LinkLongPressMovementMethod()
|
|
448
|
+
}
|
|
449
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
package com.renderrichtext.utils.text.view
|
|
2
|
+
|
|
3
|
+
import android.content.ClipData
|
|
4
|
+
import android.content.ClipboardManager
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.text.Spannable
|
|
7
|
+
import android.view.ActionMode
|
|
8
|
+
import android.view.Menu
|
|
9
|
+
import android.view.MenuItem
|
|
10
|
+
import android.view.ViewParent
|
|
11
|
+
import android.widget.TextView
|
|
12
|
+
import com.renderrichtext.EnrichedTiptapText
|
|
13
|
+
import com.renderrichtext.spans.ImageSpan
|
|
14
|
+
import com.renderrichtext.spans.stripFactoidPadding
|
|
15
|
+
import com.renderrichtext.styles.StyleConfig
|
|
16
|
+
import com.renderrichtext.utils.common.layout.isLayoutRTL
|
|
17
|
+
import com.renderrichtext.utils.text.conversion.HTMLGenerator
|
|
18
|
+
import com.renderrichtext.utils.text.conversion.TiptapExtractor
|
|
19
|
+
|
|
20
|
+
private const val MENU_ITEM_COPY_TIPTAP = 1000
|
|
21
|
+
private const val MENU_ITEM_COPY_IMAGE_URL = 1001
|
|
22
|
+
private const val MENU_ITEM_CUSTOM_BASE = 2000
|
|
23
|
+
private const val MENU_ITEM_CUSTOM_GROUP = 2001
|
|
24
|
+
|
|
25
|
+
data class SelectionMenuConfig(
|
|
26
|
+
val copyAsTiptap: Boolean = true,
|
|
27
|
+
val copyImageUrl: Boolean = true,
|
|
28
|
+
val copyLabel: String = "",
|
|
29
|
+
val copyAsTiptapLabel: String = "",
|
|
30
|
+
val copyImageUrlLabel: String = "",
|
|
31
|
+
val copyImageUrlsLabel: String = "",
|
|
32
|
+
// Plural templates indexed by image count (0..100). Empty when no pluralLabels
|
|
33
|
+
// are set; counts > 100 use copyImageUrlsLabel (the "other" form).
|
|
34
|
+
val copyImageUrlPluralTemplates: List<String> = emptyList(),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the "Copy Image URL(s)" menu title for the given image count. Uses the
|
|
39
|
+
* precomputed plural templates when present (counts > 100 use the "other" form),
|
|
40
|
+
* otherwise the singular/`{count}` templates. All labels are resolved JS-side.
|
|
41
|
+
*/
|
|
42
|
+
private fun SelectionMenuConfig.imageUrlsTitle(count: Int): String {
|
|
43
|
+
val template =
|
|
44
|
+
when {
|
|
45
|
+
copyImageUrlPluralTemplates.isNotEmpty() && count in 0..100 -> copyImageUrlPluralTemplates[count]
|
|
46
|
+
count == 1 -> copyImageUrlLabel
|
|
47
|
+
else -> copyImageUrlsLabel
|
|
48
|
+
}
|
|
49
|
+
return template.replace("{count}", count.toString())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates an ActionMode.Callback that adds custom copy options and
|
|
54
|
+
* overrides the default "Copy" action to include HTML for rich text support.
|
|
55
|
+
*/
|
|
56
|
+
fun createSelectionActionModeCallback(
|
|
57
|
+
textView: TextView,
|
|
58
|
+
getCustomItemTexts: () -> List<String> = { emptyList() },
|
|
59
|
+
getSelectionMenuConfig: () -> SelectionMenuConfig = { SelectionMenuConfig() },
|
|
60
|
+
onCustomItemPress: (itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit =
|
|
61
|
+
{ _, _, _, _ -> },
|
|
62
|
+
): ActionMode.Callback =
|
|
63
|
+
object : ActionMode.Callback {
|
|
64
|
+
override fun onCreateActionMode(
|
|
65
|
+
mode: ActionMode?,
|
|
66
|
+
menu: Menu?,
|
|
67
|
+
): Boolean = true
|
|
68
|
+
|
|
69
|
+
override fun onPrepareActionMode(
|
|
70
|
+
mode: ActionMode?,
|
|
71
|
+
menu: Menu?,
|
|
72
|
+
): Boolean {
|
|
73
|
+
if (menu == null) return false
|
|
74
|
+
|
|
75
|
+
menu.removeItem(MENU_ITEM_COPY_TIPTAP)
|
|
76
|
+
menu.removeItem(MENU_ITEM_COPY_IMAGE_URL)
|
|
77
|
+
menu.removeGroup(MENU_ITEM_CUSTOM_GROUP)
|
|
78
|
+
|
|
79
|
+
val selectionMenuConfig = getSelectionMenuConfig()
|
|
80
|
+
|
|
81
|
+
// The system Copy item is added by the platform; relabel it so it
|
|
82
|
+
// matches the rest of the localized menu (iOS rebuilds the menu so
|
|
83
|
+
// gets this for free; Android only intercepts the click).
|
|
84
|
+
if (selectionMenuConfig.copyLabel.isNotEmpty()) {
|
|
85
|
+
menu.findItem(android.R.id.copy)?.title = selectionMenuConfig.copyLabel
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
selectionMenuConfig.copyAsTiptap &&
|
|
90
|
+
textView.selectionStart >= 0 &&
|
|
91
|
+
textView.selectionEnd > textView.selectionStart
|
|
92
|
+
) {
|
|
93
|
+
menu.add(
|
|
94
|
+
Menu.NONE,
|
|
95
|
+
MENU_ITEM_COPY_TIPTAP,
|
|
96
|
+
Menu.NONE,
|
|
97
|
+
selectionMenuConfig.copyAsTiptapLabel,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (textView.selectionStart >= 0 && textView.selectionEnd > textView.selectionStart) {
|
|
102
|
+
val customItems = getCustomItemTexts()
|
|
103
|
+
customItems.forEachIndexed { index, text ->
|
|
104
|
+
menu
|
|
105
|
+
.add(MENU_ITEM_CUSTOM_GROUP, MENU_ITEM_CUSTOM_BASE + index, index, text)
|
|
106
|
+
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
val imageUrls =
|
|
111
|
+
if (selectionMenuConfig.copyImageUrl) {
|
|
112
|
+
textView.getImageUrlsInSelection()
|
|
113
|
+
} else {
|
|
114
|
+
emptyList()
|
|
115
|
+
}
|
|
116
|
+
if (imageUrls.isNotEmpty()) {
|
|
117
|
+
val title = selectionMenuConfig.imageUrlsTitle(imageUrls.size)
|
|
118
|
+
menu.add(Menu.NONE, MENU_ITEM_COPY_IMAGE_URL, Menu.NONE, title)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun onActionItemClicked(
|
|
125
|
+
mode: ActionMode?,
|
|
126
|
+
item: MenuItem?,
|
|
127
|
+
): Boolean {
|
|
128
|
+
val itemId = item?.itemId ?: return false
|
|
129
|
+
|
|
130
|
+
when (itemId) {
|
|
131
|
+
android.R.id.copy -> {
|
|
132
|
+
textView.copyWithHTML()
|
|
133
|
+
mode?.finish()
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
MENU_ITEM_COPY_TIPTAP -> {
|
|
138
|
+
textView.copyTiptapToClipboard()
|
|
139
|
+
mode?.finish()
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
MENU_ITEM_COPY_IMAGE_URL -> {
|
|
144
|
+
textView.copyImageUrlsToClipboard()
|
|
145
|
+
mode?.finish()
|
|
146
|
+
return true
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
val customItems = getCustomItemTexts()
|
|
151
|
+
val customIndex = itemId - MENU_ITEM_CUSTOM_BASE
|
|
152
|
+
if (customIndex in customItems.indices) {
|
|
153
|
+
val start = textView.selectionStart
|
|
154
|
+
val end = textView.selectionEnd
|
|
155
|
+
val selectedText =
|
|
156
|
+
if (start >= 0 && end > start) textView.text.subSequence(start, end).stripFactoidPadding() else ""
|
|
157
|
+
onCustomItemPress(customItems[customIndex], selectedText, start, end)
|
|
158
|
+
mode?.finish()
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override fun onDestroyActionMode(mode: ActionMode?) {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Copies selection as both plain text and HTML with inline styles. */
|
|
169
|
+
private fun TextView.copyWithHTML() {
|
|
170
|
+
val start = selectionStart
|
|
171
|
+
val end = selectionEnd
|
|
172
|
+
if (start < 0 || end < 0 || start >= end) return
|
|
173
|
+
|
|
174
|
+
val spannable = text as? Spannable ?: return
|
|
175
|
+
val selectedText = spannable.subSequence(start, end)
|
|
176
|
+
val plainText = selectedText.stripFactoidPadding()
|
|
177
|
+
|
|
178
|
+
val styleConfig =
|
|
179
|
+
(this as? EnrichedTiptapText)?.tiptapStyle
|
|
180
|
+
?: findParentTiptapStyle()
|
|
181
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
182
|
+
|
|
183
|
+
if (styleConfig != null && selectedText is Spannable) {
|
|
184
|
+
// Density values convert device pixels back to CSS pixels
|
|
185
|
+
val displayMetrics = context.resources.displayMetrics
|
|
186
|
+
val isRTL = context.resources.isLayoutRTL()
|
|
187
|
+
val html =
|
|
188
|
+
HTMLGenerator.generateHTML(
|
|
189
|
+
selectedText,
|
|
190
|
+
styleConfig,
|
|
191
|
+
displayMetrics.scaledDensity,
|
|
192
|
+
displayMetrics.density,
|
|
193
|
+
isRTL,
|
|
194
|
+
)
|
|
195
|
+
clipboard.setPrimaryClip(ClipData.newHtmlText("EnrichedTiptap", plainText, html))
|
|
196
|
+
} else {
|
|
197
|
+
clipboard.setPrimaryClip(ClipData.newPlainText("Text", plainText))
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private fun TextView.copyTiptapToClipboard() {
|
|
202
|
+
val tiptap = TiptapExtractor.getTiptapForSelection(this) ?: return
|
|
203
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
204
|
+
clipboard.setPrimaryClip(ClipData.newPlainText("Tiptap", tiptap))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Returns remote image URLs (http/https only) from the current selection. */
|
|
208
|
+
private fun TextView.getImageUrlsInSelection(): List<String> {
|
|
209
|
+
val start = selectionStart
|
|
210
|
+
val end = selectionEnd
|
|
211
|
+
if (start < 0 || end < 0 || start >= end) return emptyList()
|
|
212
|
+
|
|
213
|
+
val spannable = text as? Spannable ?: return emptyList()
|
|
214
|
+
return spannable
|
|
215
|
+
.getSpans(start, end, ImageSpan::class.java)
|
|
216
|
+
.mapNotNull { it.imageUrl }
|
|
217
|
+
.filter { it.startsWith("http://") || it.startsWith("https://") }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun TextView.copyImageUrlsToClipboard() {
|
|
221
|
+
val urls = getImageUrlsInSelection()
|
|
222
|
+
if (urls.isEmpty()) return
|
|
223
|
+
|
|
224
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
225
|
+
clipboard.setPrimaryClip(ClipData.newPlainText("Image URLs", urls.joinToString("\n")))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private fun TextView.findParentTiptapStyle(): StyleConfig? {
|
|
229
|
+
var current: ViewParent? = parent
|
|
230
|
+
while (current != null) {
|
|
231
|
+
if (current is EnrichedTiptapText) return current.tiptapStyle
|
|
232
|
+
current = current.parent
|
|
233
|
+
}
|
|
234
|
+
return null
|
|
235
|
+
}
|