@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.
Files changed (358) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +140 -3
  3. package/ReactNativeRenderRichText.podspec +23 -0
  4. package/android/build.gradle +86 -0
  5. package/android/consumer-rules.pro +3 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/jni-adapter.cpp +191 -0
  8. package/android/src/main/java/com/renderrichtext/EnrichedTiptapText.kt +673 -0
  9. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextLayoutManager.kt +24 -0
  10. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextManager.kt +405 -0
  11. package/android/src/main/java/com/renderrichtext/EnrichedTiptapTextPackage.kt +13 -0
  12. package/android/src/main/java/com/renderrichtext/MeasurementStore.kt +644 -0
  13. package/android/src/main/java/com/renderrichtext/accessibility/AccessibilityLabels.kt +17 -0
  14. package/android/src/main/java/com/renderrichtext/accessibility/AccessibleTiptapTextView.kt +112 -0
  15. package/android/src/main/java/com/renderrichtext/accessibility/TiptapAccessibilityHelper.kt +576 -0
  16. package/android/src/main/java/com/renderrichtext/events/CodeBlockPressEvent.kt +24 -0
  17. package/android/src/main/java/com/renderrichtext/events/ContextMenuItemPressEvent.kt +28 -0
  18. package/android/src/main/java/com/renderrichtext/events/CopyPressEvent.kt +24 -0
  19. package/android/src/main/java/com/renderrichtext/events/FactoidPressEvent.kt +23 -0
  20. package/android/src/main/java/com/renderrichtext/events/ImagePressEvent.kt +25 -0
  21. package/android/src/main/java/com/renderrichtext/events/LatexErrorEvent.kt +29 -0
  22. package/android/src/main/java/com/renderrichtext/events/LinkLongPressEvent.kt +23 -0
  23. package/android/src/main/java/com/renderrichtext/events/LinkPressEvent.kt +23 -0
  24. package/android/src/main/java/com/renderrichtext/events/MentionLongPressEvent.kt +25 -0
  25. package/android/src/main/java/com/renderrichtext/events/MentionPressEvent.kt +25 -0
  26. package/android/src/main/java/com/renderrichtext/events/TaskListItemPressEvent.kt +26 -0
  27. package/android/src/main/java/com/renderrichtext/math/LatexErrorReporter.kt +9 -0
  28. package/android/src/main/java/com/renderrichtext/parser/Parser.kt +59 -0
  29. package/android/src/main/java/com/renderrichtext/parser/TextLinkMatching.kt +35 -0
  30. package/android/src/main/java/com/renderrichtext/parser/TiptapASTNode.kt +69 -0
  31. package/android/src/main/java/com/renderrichtext/renderer/BaselineShiftRenderer.kt +45 -0
  32. package/android/src/main/java/com/renderrichtext/renderer/BlankLineRenderer.kt +39 -0
  33. package/android/src/main/java/com/renderrichtext/renderer/BlockStyleContext.kt +181 -0
  34. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteRenderer.kt +243 -0
  35. package/android/src/main/java/com/renderrichtext/renderer/BlockquoteTextRenderer.kt +56 -0
  36. package/android/src/main/java/com/renderrichtext/renderer/CodeBlockRenderer.kt +135 -0
  37. package/android/src/main/java/com/renderrichtext/renderer/CodeRenderer.kt +37 -0
  38. package/android/src/main/java/com/renderrichtext/renderer/EmphasisRenderer.kt +28 -0
  39. package/android/src/main/java/com/renderrichtext/renderer/FactoidRenderer.kt +97 -0
  40. package/android/src/main/java/com/renderrichtext/renderer/HeadingRenderer.kt +81 -0
  41. package/android/src/main/java/com/renderrichtext/renderer/HighlightRenderer.kt +26 -0
  42. package/android/src/main/java/com/renderrichtext/renderer/LineBreakRenderer.kt +17 -0
  43. package/android/src/main/java/com/renderrichtext/renderer/LinkRenderer.kt +90 -0
  44. package/android/src/main/java/com/renderrichtext/renderer/ListContextManager.kt +109 -0
  45. package/android/src/main/java/com/renderrichtext/renderer/ListItemRenderer.kt +211 -0
  46. package/android/src/main/java/com/renderrichtext/renderer/ListRenderer.kt +133 -0
  47. package/android/src/main/java/com/renderrichtext/renderer/MentionRenderer.kt +85 -0
  48. package/android/src/main/java/com/renderrichtext/renderer/NodeRenderer.kt +207 -0
  49. package/android/src/main/java/com/renderrichtext/renderer/ParagraphRenderer.kt +91 -0
  50. package/android/src/main/java/com/renderrichtext/renderer/Renderer.kt +148 -0
  51. package/android/src/main/java/com/renderrichtext/renderer/SoftBreakRenderer.kt +17 -0
  52. package/android/src/main/java/com/renderrichtext/renderer/SpanStyleCache.kt +143 -0
  53. package/android/src/main/java/com/renderrichtext/renderer/StrikethroughRenderer.kt +28 -0
  54. package/android/src/main/java/com/renderrichtext/renderer/StrongRenderer.kt +28 -0
  55. package/android/src/main/java/com/renderrichtext/renderer/TextRenderer.kt +31 -0
  56. package/android/src/main/java/com/renderrichtext/renderer/ThematicBreakRenderer.kt +46 -0
  57. package/android/src/main/java/com/renderrichtext/renderer/UnderlineRenderer.kt +28 -0
  58. package/android/src/main/java/com/renderrichtext/spans/BaseListSpan.kt +164 -0
  59. package/android/src/main/java/com/renderrichtext/spans/BaselineShiftSpan.kt +27 -0
  60. package/android/src/main/java/com/renderrichtext/spans/BlockquoteSpan.kt +302 -0
  61. package/android/src/main/java/com/renderrichtext/spans/CodeBackgroundSpan.kt +205 -0
  62. package/android/src/main/java/com/renderrichtext/spans/CodeBlockSpan.kt +216 -0
  63. package/android/src/main/java/com/renderrichtext/spans/CodeSpan.kt +32 -0
  64. package/android/src/main/java/com/renderrichtext/spans/EmphasisSpan.kt +42 -0
  65. package/android/src/main/java/com/renderrichtext/spans/FactoidBackgroundSpan.kt +265 -0
  66. package/android/src/main/java/com/renderrichtext/spans/FactoidPaddingSpan.kt +258 -0
  67. package/android/src/main/java/com/renderrichtext/spans/FactoidSpan.kt +32 -0
  68. package/android/src/main/java/com/renderrichtext/spans/FadeInSpan.kt +29 -0
  69. package/android/src/main/java/com/renderrichtext/spans/HeadingSpan.kt +38 -0
  70. package/android/src/main/java/com/renderrichtext/spans/HighlightSpan.kt +80 -0
  71. package/android/src/main/java/com/renderrichtext/spans/ImageSpan.kt +481 -0
  72. package/android/src/main/java/com/renderrichtext/spans/InlineBackgroundGeometry.kt +42 -0
  73. package/android/src/main/java/com/renderrichtext/spans/LineHeightSpan.kt +39 -0
  74. package/android/src/main/java/com/renderrichtext/spans/LinkPillIconCache.kt +154 -0
  75. package/android/src/main/java/com/renderrichtext/spans/LinkPillSpan.kt +381 -0
  76. package/android/src/main/java/com/renderrichtext/spans/LinkSpan.kt +68 -0
  77. package/android/src/main/java/com/renderrichtext/spans/ListItemSpacingSpan.kt +33 -0
  78. package/android/src/main/java/com/renderrichtext/spans/ListMarkerAnchorSpan.kt +38 -0
  79. package/android/src/main/java/com/renderrichtext/spans/MarginBottomSpan.kt +76 -0
  80. package/android/src/main/java/com/renderrichtext/spans/MentionChipSpan.kt +132 -0
  81. package/android/src/main/java/com/renderrichtext/spans/MentionSpan.kt +46 -0
  82. package/android/src/main/java/com/renderrichtext/spans/OrderedListSpan.kt +127 -0
  83. package/android/src/main/java/com/renderrichtext/spans/SpoilerSpan.kt +33 -0
  84. package/android/src/main/java/com/renderrichtext/spans/StrikethroughSpan.kt +12 -0
  85. package/android/src/main/java/com/renderrichtext/spans/StrongSpan.kt +39 -0
  86. package/android/src/main/java/com/renderrichtext/spans/TaskListSpan.kt +143 -0
  87. package/android/src/main/java/com/renderrichtext/spans/TextSpan.kt +26 -0
  88. package/android/src/main/java/com/renderrichtext/spans/ThematicBreakSpan.kt +69 -0
  89. package/android/src/main/java/com/renderrichtext/spans/UnorderedListSpan.kt +96 -0
  90. package/android/src/main/java/com/renderrichtext/spoiler/SpoilerStubs.kt +46 -0
  91. package/android/src/main/java/com/renderrichtext/styles/BaseBlockStyle.kt +11 -0
  92. package/android/src/main/java/com/renderrichtext/styles/BlockquoteStyle.kt +83 -0
  93. package/android/src/main/java/com/renderrichtext/styles/CodeBlockStyle.kt +80 -0
  94. package/android/src/main/java/com/renderrichtext/styles/CodeStyle.kt +26 -0
  95. package/android/src/main/java/com/renderrichtext/styles/EmphasisStyle.kt +21 -0
  96. package/android/src/main/java/com/renderrichtext/styles/FactoidStyle.kt +21 -0
  97. package/android/src/main/java/com/renderrichtext/styles/HeadingStyle.kt +33 -0
  98. package/android/src/main/java/com/renderrichtext/styles/HighlightStyle.kt +19 -0
  99. package/android/src/main/java/com/renderrichtext/styles/ImageStyle.kt +29 -0
  100. package/android/src/main/java/com/renderrichtext/styles/InlineImageStyle.kt +17 -0
  101. package/android/src/main/java/com/renderrichtext/styles/InlineMathStyle.kt +17 -0
  102. package/android/src/main/java/com/renderrichtext/styles/LinkPillContent.kt +36 -0
  103. package/android/src/main/java/com/renderrichtext/styles/LinkPillStyle.kt +44 -0
  104. package/android/src/main/java/com/renderrichtext/styles/LinkStyle.kt +23 -0
  105. package/android/src/main/java/com/renderrichtext/styles/LinkVariantEntry.kt +34 -0
  106. package/android/src/main/java/com/renderrichtext/styles/ListStyle.kt +65 -0
  107. package/android/src/main/java/com/renderrichtext/styles/MathStyle.kt +29 -0
  108. package/android/src/main/java/com/renderrichtext/styles/MentionStyle.kt +19 -0
  109. package/android/src/main/java/com/renderrichtext/styles/ParagraphStyle.kt +33 -0
  110. package/android/src/main/java/com/renderrichtext/styles/SpoilerStyle.kt +27 -0
  111. package/android/src/main/java/com/renderrichtext/styles/StrikethroughStyle.kt +17 -0
  112. package/android/src/main/java/com/renderrichtext/styles/StrongStyle.kt +21 -0
  113. package/android/src/main/java/com/renderrichtext/styles/StyleConfig.kt +411 -0
  114. package/android/src/main/java/com/renderrichtext/styles/StyleParser.kt +92 -0
  115. package/android/src/main/java/com/renderrichtext/styles/SubscriptStyle.kt +16 -0
  116. package/android/src/main/java/com/renderrichtext/styles/SuperscriptStyle.kt +16 -0
  117. package/android/src/main/java/com/renderrichtext/styles/TableStyle.kt +75 -0
  118. package/android/src/main/java/com/renderrichtext/styles/TaskListStyle.kt +29 -0
  119. package/android/src/main/java/com/renderrichtext/styles/TextAlignment.kt +32 -0
  120. package/android/src/main/java/com/renderrichtext/styles/ThematicBreakStyle.kt +23 -0
  121. package/android/src/main/java/com/renderrichtext/styles/UnderlineStyle.kt +17 -0
  122. package/android/src/main/java/com/renderrichtext/styles/VideoStyle.kt +31 -0
  123. package/android/src/main/java/com/renderrichtext/utils/common/BorderStyleUtils.kt +86 -0
  124. package/android/src/main/java/com/renderrichtext/utils/common/BreakStrategyUtils.kt +32 -0
  125. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockHighlighter.kt +36 -0
  126. package/android/src/main/java/com/renderrichtext/utils/common/CodeBlockNode.kt +42 -0
  127. package/android/src/main/java/com/renderrichtext/utils/common/EllipsizeUtils.kt +27 -0
  128. package/android/src/main/java/com/renderrichtext/utils/common/EnrichedTiptapAncestorUtils.kt +10 -0
  129. package/android/src/main/java/com/renderrichtext/utils/common/FeatureFlags.kt +9 -0
  130. package/android/src/main/java/com/renderrichtext/utils/common/LinkRecognitionProps.kt +31 -0
  131. package/android/src/main/java/com/renderrichtext/utils/common/LinkRegexConfig.kt +56 -0
  132. package/android/src/main/java/com/renderrichtext/utils/common/MotionPreferences.kt +18 -0
  133. package/android/src/main/java/com/renderrichtext/utils/common/ReadableMapExtensions.kt +28 -0
  134. package/android/src/main/java/com/renderrichtext/utils/common/TiptapViewManagerUtils.kt +242 -0
  135. package/android/src/main/java/com/renderrichtext/utils/common/layout/RTLUtils.kt +6 -0
  136. package/android/src/main/java/com/renderrichtext/utils/common/serialization/TiptapASTSerializer.kt +368 -0
  137. package/android/src/main/java/com/renderrichtext/utils/text/ImageCache.kt +88 -0
  138. package/android/src/main/java/com/renderrichtext/utils/text/ImageDownloader.kt +203 -0
  139. package/android/src/main/java/com/renderrichtext/utils/text/LocalImageLoader.kt +167 -0
  140. package/android/src/main/java/com/renderrichtext/utils/text/TailFadeInAnimator.kt +84 -0
  141. package/android/src/main/java/com/renderrichtext/utils/text/conversion/HTMLGenerator.kt +1105 -0
  142. package/android/src/main/java/com/renderrichtext/utils/text/conversion/TiptapExtractor.kt +423 -0
  143. package/android/src/main/java/com/renderrichtext/utils/text/extensions/ParagraphUtils.kt +9 -0
  144. package/android/src/main/java/com/renderrichtext/utils/text/extensions/SpannableExtensions.kt +9 -0
  145. package/android/src/main/java/com/renderrichtext/utils/text/extensions/TextPaintExtensions.kt +76 -0
  146. package/android/src/main/java/com/renderrichtext/utils/text/interaction/CheckboxTouchHelper.kt +61 -0
  147. package/android/src/main/java/com/renderrichtext/utils/text/interaction/TaskListTapUtils.kt +286 -0
  148. package/android/src/main/java/com/renderrichtext/utils/text/span/ApplyLineHeight.kt +39 -0
  149. package/android/src/main/java/com/renderrichtext/utils/text/span/BlockSpacing.kt +142 -0
  150. package/android/src/main/java/com/renderrichtext/utils/text/span/SpanFlags.kt +43 -0
  151. package/android/src/main/java/com/renderrichtext/utils/text/span/WidthAwareSpans.kt +30 -0
  152. package/android/src/main/java/com/renderrichtext/utils/text/view/CodeBlockPressHost.kt +15 -0
  153. package/android/src/main/java/com/renderrichtext/utils/text/view/ImagePressHost.kt +19 -0
  154. package/android/src/main/java/com/renderrichtext/utils/text/view/LinkEvents.kt +88 -0
  155. package/android/src/main/java/com/renderrichtext/utils/text/view/LinkLongPressMovementMethod.kt +449 -0
  156. package/android/src/main/java/com/renderrichtext/utils/text/view/SelectionActionMode.kt +235 -0
  157. package/android/src/main/java/com/renderrichtext/utils/text/view/TextSelectionColors.kt +48 -0
  158. package/android/src/main/java/com/renderrichtext/utils/text/view/TextViewSetup.kt +119 -0
  159. package/android/src/main/java/com/renderrichtext/views/ContextMenuPopup.kt +217 -0
  160. package/android/src/main/java/com/renderrichtext/views/CopyGlyph.kt +101 -0
  161. package/android/src/main/java/com/renderrichtext/views/WrapGlyph.kt +40 -0
  162. package/android/src/main/jni/CMakeLists.txt +52 -0
  163. package/android/src/main/jni/react/renderer/components/RichTextSpec/ComponentDescriptors.h +30 -0
  164. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextComponentDescriptor.h +29 -0
  165. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.cpp +45 -0
  166. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextMeasurementManager.h +21 -0
  167. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.cpp +38 -0
  168. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextShadowNode.h +53 -0
  169. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.cpp +9 -0
  170. package/android/src/main/jni/react/renderer/components/RichTextSpec/TiptapTextState.h +24 -0
  171. package/android/src/main/jni/react/renderer/components/RichTextSpec/conversions.h +40 -0
  172. package/cpp/parser/TiptapASTNode.hpp +68 -0
  173. package/cpp/parser/TiptapParser.cpp +233 -0
  174. package/cpp/parser/TiptapParser.hpp +13 -0
  175. package/cpp/vendor/nlohmann/json.hpp +24765 -0
  176. package/ios/EnrichedTiptapText.h +27 -0
  177. package/ios/EnrichedTiptapText.mm +1222 -0
  178. package/ios/attachments/ENRMImageAttachment.h +51 -0
  179. package/ios/attachments/ENRMImageAttachment.m +398 -0
  180. package/ios/attachments/ENRMLinkPillAttachment.h +47 -0
  181. package/ios/attachments/ENRMLinkPillAttachment.m +324 -0
  182. package/ios/attachments/ENRMLinkPillIconCache.h +28 -0
  183. package/ios/attachments/ENRMLinkPillIconCache.m +181 -0
  184. package/ios/attachments/ENRMLinkPillText.h +27 -0
  185. package/ios/attachments/ENRMLinkPillText.m +118 -0
  186. package/ios/attachments/ENRMMathInlineAttachment.h +7 -0
  187. package/ios/attachments/ENRMMathInlineAttachment.m +4 -0
  188. package/ios/attachments/ThematicBreakAttachment.h +15 -0
  189. package/ios/attachments/ThematicBreakAttachment.m +33 -0
  190. package/ios/internals/ENRMViewFreeMeasurement.h +208 -0
  191. package/ios/internals/EnrichedTiptapTextComponentDescriptor.h +19 -0
  192. package/ios/internals/EnrichedTiptapTextShadowNode.h +43 -0
  193. package/ios/internals/EnrichedTiptapTextShadowNode.mm +69 -0
  194. package/ios/internals/EnrichedTiptapTextState.h +24 -0
  195. package/ios/internals/MeasurementCache.h +308 -0
  196. package/ios/internals/ShadowMeasurementUtils.h +145 -0
  197. package/ios/parser/ENRMTextLinkAttributes.h +7 -0
  198. package/ios/parser/ENRMTiptapParser.h +33 -0
  199. package/ios/parser/ENRMTiptapParser.mm +73 -0
  200. package/ios/parser/TiptapASTNode.h +57 -0
  201. package/ios/parser/TiptapASTNode.m +52 -0
  202. package/ios/parser/TiptapParserBridge.mm +179 -0
  203. package/ios/renderer/AttributedRenderer.h +23 -0
  204. package/ios/renderer/AttributedRenderer.m +229 -0
  205. package/ios/renderer/BaseRenderer.h +33 -0
  206. package/ios/renderer/BaseRenderer.m +46 -0
  207. package/ios/renderer/BaselineShiftTextAttributes.h +10 -0
  208. package/ios/renderer/BaselineShiftTextAttributes.m +26 -0
  209. package/ios/renderer/BlankLineRenderer.h +4 -0
  210. package/ios/renderer/BlankLineRenderer.m +51 -0
  211. package/ios/renderer/BlockquoteRenderer.h +6 -0
  212. package/ios/renderer/BlockquoteRenderer.m +258 -0
  213. package/ios/renderer/CodeBlockRenderer.h +9 -0
  214. package/ios/renderer/CodeBlockRenderer.m +99 -0
  215. package/ios/renderer/CodeRenderer.h +9 -0
  216. package/ios/renderer/CodeRenderer.m +69 -0
  217. package/ios/renderer/ENRMBlockquoteTextRenderer.h +25 -0
  218. package/ios/renderer/ENRMBlockquoteTextRenderer.m +48 -0
  219. package/ios/renderer/EmphasisRenderer.h +5 -0
  220. package/ios/renderer/EmphasisRenderer.m +100 -0
  221. package/ios/renderer/FactoidRenderer.h +9 -0
  222. package/ios/renderer/FactoidRenderer.m +130 -0
  223. package/ios/renderer/HeadingRenderer.h +6 -0
  224. package/ios/renderer/HeadingRenderer.m +103 -0
  225. package/ios/renderer/HighlightRenderer.h +7 -0
  226. package/ios/renderer/HighlightRenderer.m +33 -0
  227. package/ios/renderer/LinkRenderer.h +6 -0
  228. package/ios/renderer/LinkRenderer.m +158 -0
  229. package/ios/renderer/ListItemRenderer.h +24 -0
  230. package/ios/renderer/ListItemRenderer.m +283 -0
  231. package/ios/renderer/ListRenderer.h +13 -0
  232. package/ios/renderer/ListRenderer.m +126 -0
  233. package/ios/renderer/MentionRenderer.h +9 -0
  234. package/ios/renderer/MentionRenderer.m +96 -0
  235. package/ios/renderer/NodeRenderer.h +8 -0
  236. package/ios/renderer/ParagraphRenderer.h +6 -0
  237. package/ios/renderer/ParagraphRenderer.m +85 -0
  238. package/ios/renderer/RenderContext.h +145 -0
  239. package/ios/renderer/RenderContext.m +403 -0
  240. package/ios/renderer/RendererFactory.h +12 -0
  241. package/ios/renderer/RendererFactory.m +158 -0
  242. package/ios/renderer/StrikethroughRenderer.h +5 -0
  243. package/ios/renderer/StrikethroughRenderer.m +30 -0
  244. package/ios/renderer/StrongRenderer.h +5 -0
  245. package/ios/renderer/StrongRenderer.m +88 -0
  246. package/ios/renderer/TextRenderer.h +6 -0
  247. package/ios/renderer/TextRenderer.m +18 -0
  248. package/ios/renderer/ThematicBreakRenderer.h +4 -0
  249. package/ios/renderer/ThematicBreakRenderer.m +42 -0
  250. package/ios/renderer/UnderlineRenderer.h +4 -0
  251. package/ios/renderer/UnderlineRenderer.m +41 -0
  252. package/ios/styles/ENRMFontSlot.h +12 -0
  253. package/ios/styles/ENRMFontSlot.m +24 -0
  254. package/ios/styles/StyleConfig.h +488 -0
  255. package/ios/styles/StyleConfig.mm +2820 -0
  256. package/ios/utils/AccessibilityInfo.h +35 -0
  257. package/ios/utils/AccessibilityInfo.m +24 -0
  258. package/ios/utils/BlockquoteBorder.h +26 -0
  259. package/ios/utils/BlockquoteBorder.m +294 -0
  260. package/ios/utils/CodeBackground.h +19 -0
  261. package/ios/utils/CodeBackground.m +197 -0
  262. package/ios/utils/CodeBlockBackground.h +17 -0
  263. package/ios/utils/CodeBlockBackground.m +103 -0
  264. package/ios/utils/ContextMenuUtils.h +76 -0
  265. package/ios/utils/ContextMenuUtils.mm +75 -0
  266. package/ios/utils/ENRMAccessibilityLabels.h +40 -0
  267. package/ios/utils/ENRMAccessibilityLabels.m +5 -0
  268. package/ios/utils/ENRMAsyncRenderCoordinator.h +24 -0
  269. package/ios/utils/ENRMAsyncRenderCoordinator.m +37 -0
  270. package/ios/utils/ENRMAtomicSize.h +65 -0
  271. package/ios/utils/ENRMCodeBlockContent.h +37 -0
  272. package/ios/utils/ENRMCodeBlockContent.mm +89 -0
  273. package/ios/utils/ENRMFeatureFlags.h +25 -0
  274. package/ios/utils/ENRMImageDownloader.h +30 -0
  275. package/ios/utils/ENRMImageDownloader.m +168 -0
  276. package/ios/utils/ENRMLatexErrorCoordinator.h +27 -0
  277. package/ios/utils/ENRMLatexErrorCoordinator.mm +75 -0
  278. package/ios/utils/ENRMLatexErrorReporting.h +13 -0
  279. package/ios/utils/ENRMLinkContextMenus.h +93 -0
  280. package/ios/utils/ENRMLinkContextMenus.m +178 -0
  281. package/ios/utils/ENRMLinkRegexConfig.h +30 -0
  282. package/ios/utils/ENRMLinkRegexConfig.mm +104 -0
  283. package/ios/utils/ENRMLinkRegexProps.h +23 -0
  284. package/ios/utils/ENRMLocalImageLoader.h +35 -0
  285. package/ios/utils/ENRMLocalImageLoader.m +111 -0
  286. package/ios/utils/ENRMMenuAction.h +27 -0
  287. package/ios/utils/ENRMMenuAction.m +33 -0
  288. package/ios/utils/ENRMSpoilerOverlayManager.h +15 -0
  289. package/ios/utils/ENRMSpoilerOverlayManager.m +19 -0
  290. package/ios/utils/ENRMSpoilerTapUtils.h +6 -0
  291. package/ios/utils/ENRMSpoilerTapUtils.m +11 -0
  292. package/ios/utils/ENRMTailFadeInAnimator.h +16 -0
  293. package/ios/utils/ENRMTailFadeInAnimator.m +161 -0
  294. package/ios/utils/ENRMTextHitTest.h +23 -0
  295. package/ios/utils/ENRMTextHitTest.m +20 -0
  296. package/ios/utils/ENRMTextInteractionUtils.h +20 -0
  297. package/ios/utils/ENRMTextInteractionUtils.m +15 -0
  298. package/ios/utils/ENRMTextRenderer.h +38 -0
  299. package/ios/utils/ENRMTextRenderer.m +54 -0
  300. package/ios/utils/ENRMTextViewSetup.h +132 -0
  301. package/ios/utils/ENRMUIKit.h +429 -0
  302. package/ios/utils/EditMenuUtils.h +105 -0
  303. package/ios/utils/EditMenuUtils.m +143 -0
  304. package/ios/utils/FactoidBackground.h +20 -0
  305. package/ios/utils/FactoidBackground.m +161 -0
  306. package/ios/utils/FontScaleObserver.h +10 -0
  307. package/ios/utils/FontScaleObserver.m +55 -0
  308. package/ios/utils/FontUtils.h +31 -0
  309. package/ios/utils/FontUtils.m +63 -0
  310. package/ios/utils/HTMLGenerator.h +25 -0
  311. package/ios/utils/HTMLGenerator.m +1041 -0
  312. package/ios/utils/HeightUpdateUtils.h +25 -0
  313. package/ios/utils/ImageRequestHeaderUtils.h +35 -0
  314. package/ios/utils/LastElementUtils.h +97 -0
  315. package/ios/utils/LinkContextMenuUtils.h +55 -0
  316. package/ios/utils/LinkPillContentUtils.h +46 -0
  317. package/ios/utils/LinkTapUtils.h +56 -0
  318. package/ios/utils/LinkTapUtils.m +139 -0
  319. package/ios/utils/ListMarkerDrawer.h +15 -0
  320. package/ios/utils/ListMarkerDrawer.m +239 -0
  321. package/ios/utils/MentionBackground.h +26 -0
  322. package/ios/utils/MentionBackground.m +86 -0
  323. package/ios/utils/ParagraphStyleUtils.h +87 -0
  324. package/ios/utils/ParagraphStyleUtils.m +484 -0
  325. package/ios/utils/PasteboardUtils.h +48 -0
  326. package/ios/utils/PasteboardUtils.m +166 -0
  327. package/ios/utils/RTFExportUtils.h +24 -0
  328. package/ios/utils/RTFExportUtils.m +299 -0
  329. package/ios/utils/RuntimeKeys.h +44 -0
  330. package/ios/utils/RuntimeKeys.m +13 -0
  331. package/ios/utils/SelectionColorUtils.h +14 -0
  332. package/ios/utils/StylePropsUtils.h +1340 -0
  333. package/ios/utils/TaskListTapUtils.h +6 -0
  334. package/ios/utils/TaskListTapUtils.m +14 -0
  335. package/ios/utils/TextViewLayoutManager.h +14 -0
  336. package/ios/utils/TextViewLayoutManager.mm +198 -0
  337. package/ios/utils/TiptapAccessibilityElementBuilder.h +43 -0
  338. package/ios/utils/TiptapAccessibilityElementBuilder.m +432 -0
  339. package/ios/utils/TiptapExtractor.h +17 -0
  340. package/ios/utils/TiptapExtractor.m +367 -0
  341. package/ios/views/ENRMTiptapTextView.h +25 -0
  342. package/ios/views/ENRMTiptapTextView.m +247 -0
  343. package/package.json +70 -6
  344. package/react-native.config.js +10 -0
  345. package/src/EnrichedTiptapTextNativeComponent.ts +739 -0
  346. package/src/RichText.tsx +164 -0
  347. package/src/admonitionDefaults.ts +58 -0
  348. package/src/darkModeTextStyle.ts +99 -0
  349. package/src/headingDefaults.ts +21 -0
  350. package/src/index.tsx +4 -0
  351. package/src/linkVariantUtils.ts +120 -0
  352. package/src/normalizeTiptapStyle.ts +397 -0
  353. package/src/rn-globals.d.ts +1 -0
  354. package/src/styleUtils.ts +107 -0
  355. package/src/types/TiptapStyle.ts +532 -0
  356. package/src/types/TiptapStyleInternal.ts +299 -0
  357. package/src/types/linkPill.ts +9 -0
  358. package/src/utils/isDev.ts +4 -0
@@ -0,0 +1,367 @@
1
+ #import "TiptapExtractor.h"
2
+ #import "BaselineShiftTextAttributes.h"
3
+ #import "BlockquoteBorder.h"
4
+ #import "CodeBackground.h"
5
+ #import "ENRMFeatureFlags.h"
6
+ #import "ENRMImageAttachment.h"
7
+ #import "ENRMLinkPillText.h"
8
+ #import "ENRMTextLinkAttributes.h"
9
+ #import "ENRMUIKit.h"
10
+ #import "HighlightRenderer.h"
11
+ #include <TargetConditionals.h>
12
+ #if ENRICHED_TIPTAP_MATH
13
+ #import "ENRMMathInlineAttachment.h"
14
+ #endif
15
+ #import "LastElementUtils.h"
16
+ #import "ListItemRenderer.h"
17
+ #import "RuntimeKeys.h"
18
+ #import "ThematicBreakAttachment.h"
19
+
20
+ #pragma mark - Extraction Context
21
+
22
+ typedef struct {
23
+ NSInteger blockquoteDepth; // -1 = not in blockquote
24
+ NSInteger listDepth; // -1 = not in list
25
+ BOOL needsBlankLine;
26
+ } ExtractionState;
27
+
28
+ #pragma mark - Helper Functions
29
+
30
+ static void ensureBlankLine(NSMutableString *result)
31
+ {
32
+ if (result.length == 0)
33
+ return;
34
+ if ([result hasSuffix:@"\n\n"])
35
+ return;
36
+
37
+ [result appendString:[result hasSuffix:@"\n"] ? @"\n" : @"\n\n"];
38
+ }
39
+
40
+ static BOOL isAtLineStart(NSMutableString *result)
41
+ {
42
+ return result.length == 0 || [result hasSuffix:@"\n"];
43
+ }
44
+
45
+ /// Depth 0 = "> ", Depth 1 = "> > ", etc.
46
+ static NSString *buildBlockquotePrefix(NSInteger depth)
47
+ {
48
+ NSMutableString *prefix = [NSMutableString string];
49
+ for (NSInteger i = 0; i <= depth; i++) {
50
+ [prefix appendString:@"> "];
51
+ }
52
+ return prefix;
53
+ }
54
+
55
+ static NSString *buildListPrefix(NSInteger depth, BOOL isOrdered, NSInteger itemNumber)
56
+ {
57
+ NSString *indent = [@"" stringByPaddingToLength:(depth * 2) withString:@" " startingAtIndex:0];
58
+ NSString *marker = isOrdered ? [NSString stringWithFormat:@"%ld.", (long)itemNumber] : @"-";
59
+ return [NSString stringWithFormat:@"%@%@ ", indent, marker];
60
+ }
61
+
62
+ static NSString *buildHeadingPrefix(NSInteger level)
63
+ {
64
+ return [NSString stringWithFormat:@"%@ ", [@"" stringByPaddingToLength:level withString:@"#" startingAtIndex:0]];
65
+ }
66
+
67
+ static void extractFontTraits(NSDictionary *attrs, BOOL *isBold, BOOL *isItalic, BOOL *isMonospace)
68
+ {
69
+ UIFont *font = attrs[NSFontAttributeName];
70
+ *isBold = NO;
71
+ *isItalic = NO;
72
+ *isMonospace = NO;
73
+
74
+ if (font) {
75
+ UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
76
+ *isBold = (traits & UIFontDescriptorTraitBold) != 0;
77
+ *isItalic = (traits & UIFontDescriptorTraitItalic) != 0;
78
+ #if !TARGET_OS_OSX
79
+ *isMonospace = (traits & UIFontDescriptorTraitMonoSpace) != 0;
80
+ #else
81
+ *isMonospace = (traits & NSFontDescriptorTraitMonoSpace) != 0;
82
+ #endif
83
+ }
84
+ }
85
+
86
+ static NSString *applyInlineFormatting(NSString *text, BOOL isBold, BOOL isItalic, BOOL isMonospace,
87
+ BOOL isStrikethrough, BOOL isUnderline, BOOL isSuperscript, BOOL isSubscript,
88
+ BOOL isHighlight, NSString *linkURL)
89
+ {
90
+ NSMutableString *result = [NSMutableString stringWithString:text];
91
+
92
+ // Innermost first
93
+ if (isMonospace && !linkURL) {
94
+ result = [NSMutableString stringWithFormat:@"`%@`", result];
95
+ }
96
+ if (isStrikethrough) {
97
+ result = [NSMutableString stringWithFormat:@"~~%@~~", result];
98
+ }
99
+ if (isSubscript) {
100
+ result = [NSMutableString stringWithFormat:@"~%@~", result];
101
+ }
102
+ if (isSuperscript) {
103
+ result = [NSMutableString stringWithFormat:@"^%@^", result];
104
+ }
105
+ if (isUnderline && !linkURL) {
106
+ result = [NSMutableString stringWithFormat:@"<u>%@</u>", result];
107
+ }
108
+ if (isItalic) {
109
+ result = [NSMutableString stringWithFormat:@"*%@*", result];
110
+ }
111
+ if (isBold) {
112
+ result = [NSMutableString stringWithFormat:@"**%@**", result];
113
+ }
114
+ if (linkURL) {
115
+ result = [NSMutableString stringWithFormat:@"[%@](%@)", result, linkURL];
116
+ }
117
+ if (isHighlight) {
118
+ result = [NSMutableString stringWithFormat:@"==%@==", result];
119
+ }
120
+
121
+ return result;
122
+ }
123
+
124
+ #pragma mark - Main Extraction Function
125
+
126
+ NSString *_Nullable extractTiptapFromAttributedString(NSAttributedString *attributedText, NSRange range)
127
+ {
128
+ if (!attributedText || range.length == 0 || range.location >= attributedText.length) {
129
+ return nil;
130
+ }
131
+
132
+ range.length = MIN(range.length, attributedText.length - range.location);
133
+ attributedText = ENRMAttributedStringByExpandingLinkPills(attributedText, &range);
134
+
135
+ NSMutableString *result = [NSMutableString string];
136
+
137
+ // Headings may span multiple attribute runs
138
+ __block NSString *currentHeadingType = nil;
139
+ __block NSMutableString *headingContent = nil;
140
+ __block ExtractionState state = {.blockquoteDepth = -1, .listDepth = -1, .needsBlankLine = NO};
141
+
142
+ void (^flushHeading)(void) = ^{
143
+ if (!currentHeadingType || headingContent.length == 0)
144
+ return;
145
+
146
+ ensureBlankLine(result);
147
+ NSInteger level = [[currentHeadingType substringFromIndex:8] integerValue];
148
+ [result appendFormat:@"%@%@\n", buildHeadingPrefix(level), headingContent];
149
+
150
+ currentHeadingType = nil;
151
+ headingContent = nil;
152
+ state.needsBlankLine = YES;
153
+ };
154
+
155
+ [attributedText
156
+ enumerateAttributesInRange:range
157
+ options:0
158
+ usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange attrRange, BOOL *stop) {
159
+ NSString *text = [[attributedText attributedSubstringFromRange:attrRange] string];
160
+ if (text.length == 0)
161
+ return;
162
+
163
+ // Images and Thematic Breaks
164
+ NSTextAttachment *attachment = attrs[NSAttachmentAttributeName];
165
+ if ([attachment isKindOfClass:[ENRMImageAttachment class]]) {
166
+ ENRMImageAttachment *img = (ENRMImageAttachment *)attachment;
167
+ if (!img.imageURL)
168
+ return;
169
+
170
+ if (img.isInline) {
171
+ [result appendFormat:@"![image](%@)", img.imageURL];
172
+ } else {
173
+ ensureBlankLine(result);
174
+ [result appendFormat:@"![image](%@)\n", img.imageURL];
175
+ state.needsBlankLine = YES;
176
+ state.blockquoteDepth = -1;
177
+ state.listDepth = -1;
178
+ }
179
+ return;
180
+ }
181
+
182
+ if ([attachment isKindOfClass:[ThematicBreakAttachment class]]) {
183
+ ensureBlankLine(result);
184
+ [result appendString:@"---\n"];
185
+ state.needsBlankLine = YES;
186
+ state.blockquoteDepth = -1;
187
+ state.listDepth = -1;
188
+ return;
189
+ }
190
+
191
+ #if ENRICHED_TIPTAP_MATH
192
+ if ([attachment isKindOfClass:[ENRMMathInlineAttachment class]]) {
193
+ ENRMMathInlineAttachment *math = (ENRMMathInlineAttachment *)attachment;
194
+ if (math.latex.length > 0) {
195
+ [result appendFormat:@"$%@$", math.latex];
196
+ }
197
+ return;
198
+ }
199
+ #endif
200
+
201
+ if ([text isEqualToString:@"\uFFFC"])
202
+ return;
203
+
204
+ // Newlines
205
+ if ([text isEqualToString:@"\n"] || [text isEqualToString:@"\n\n"]) {
206
+ NSNumber *bqDepth = attrs[BlockquoteDepthAttributeName];
207
+ NSNumber *listDepth = attrs[ListDepthAttribute];
208
+ BOOL inBlockquote = (bqDepth != nil);
209
+ BOOL inList = (listDepth != nil);
210
+
211
+ if (!inBlockquote && state.blockquoteDepth >= 0) {
212
+ ensureBlankLine(result);
213
+ state.blockquoteDepth = -1;
214
+ return;
215
+ }
216
+
217
+ if (!inList && state.listDepth >= 0) {
218
+ ensureBlankLine(result);
219
+ state.listDepth = -1;
220
+ return;
221
+ }
222
+
223
+ if (inBlockquote || inList) {
224
+ if (![result hasSuffix:@"\n"]) {
225
+ [result appendString:@"\n"];
226
+ }
227
+ return;
228
+ }
229
+
230
+ ensureBlankLine(result);
231
+ return;
232
+ }
233
+
234
+ // Headings
235
+ NSString *tiptapType = attrs[TiptapTypeAttributeName];
236
+
237
+ if (tiptapType && [tiptapType hasPrefix:@"heading-"]) {
238
+ if (![tiptapType isEqualToString:currentHeadingType]) {
239
+ flushHeading();
240
+ currentHeadingType = tiptapType;
241
+ headingContent = [NSMutableString string];
242
+ }
243
+ [headingContent
244
+ appendString:[text stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]];
245
+ return;
246
+ } else if (currentHeadingType) {
247
+ flushHeading();
248
+ }
249
+
250
+ // Code blocks
251
+ NSNumber *isCodeBlock = attrs[CodeBlockAttributeName];
252
+ if ([isCodeBlock boolValue]) {
253
+ if (state.needsBlankLine) {
254
+ ensureBlankLine(result);
255
+ state.needsBlankLine = NO;
256
+ }
257
+
258
+ BOOL needsFence = (result.length == 0) || [result hasSuffix:@"\n\n"];
259
+ if (needsFence) {
260
+ [result appendString:@"```\n"];
261
+ }
262
+
263
+ [result appendString:text];
264
+
265
+ if ([text hasSuffix:@"\n"]) {
266
+ [result appendString:@"```\n"];
267
+ state.needsBlankLine = YES;
268
+ }
269
+ return;
270
+ }
271
+
272
+ // Blockquotes
273
+ NSNumber *bqDepthNum = attrs[BlockquoteDepthAttributeName];
274
+ NSInteger currentBqDepth = bqDepthNum ? [bqDepthNum integerValue] : -1;
275
+ NSString *blockquotePrefix = nil;
276
+
277
+ if (currentBqDepth >= 0) {
278
+ blockquotePrefix = buildBlockquotePrefix(currentBqDepth);
279
+ state.blockquoteDepth = currentBqDepth;
280
+ } else if (state.blockquoteDepth >= 0) {
281
+ ensureBlankLine(result);
282
+ state.blockquoteDepth = -1;
283
+ }
284
+
285
+ // Lists
286
+ NSNumber *listDepthNum = attrs[ListDepthAttribute];
287
+ NSNumber *listTypeNum = attrs[ListTypeAttribute];
288
+ NSNumber *listItemNum = attrs[ListItemNumberAttribute];
289
+ BOOL isTaskItem = [attrs[TaskItemAttribute] boolValue];
290
+ BOOL isTaskChecked = [attrs[TaskCheckedAttribute] boolValue];
291
+ NSInteger currentListDepth = listDepthNum ? [listDepthNum integerValue] : -1;
292
+
293
+ if (currentListDepth >= 0) {
294
+ state.listDepth = currentListDepth;
295
+ } else if (state.listDepth >= 0) {
296
+ ensureBlankLine(result);
297
+ state.listDepth = -1;
298
+ }
299
+
300
+ // Inline formatting
301
+ BOOL isBold, isItalic, isMonospace;
302
+ BOOL recognizedLink = [attrs[ENRMRecognizedLinkAttributeName] boolValue];
303
+ extractFontTraits(attrs, &isBold, &isItalic, &isMonospace);
304
+ if (recognizedLink)
305
+ isMonospace = [attrs[CodeAttributeName] boolValue];
306
+
307
+ NSNumber *strikethroughStyle = attrs[NSStrikethroughStyleAttributeName];
308
+ BOOL isStrikethrough = (strikethroughStyle != nil && [strikethroughStyle integerValue] != 0);
309
+ NSNumber *underlineStyle = recognizedLink
310
+ ? attrs[ENRMRecognizedLinkOriginalUnderlineAttributeName]
311
+ : attrs[NSUnderlineStyleAttributeName];
312
+ BOOL isUnderline = (underlineStyle != nil && [underlineStyle integerValue] != 0);
313
+ // Line-height alignment also shifts baselines. Only semantic script spans
314
+ // should produce superscript/subscript Tiptap on the clipboard.
315
+ NSString *script = attrs[ENRMScriptAttributeName];
316
+ BOOL isSuperscript = [script isEqualToString:ENRMScriptValueSuperscript];
317
+ BOOL isSubscript = [script isEqualToString:ENRMScriptValueSubscript];
318
+
319
+ BOOL isHighlight = [attrs[HighlightAttributeName] boolValue];
320
+ NSString *linkURL = recognizedLink ? nil : attrs[NSLinkAttributeName];
321
+ NSString *segment =
322
+ applyInlineFormatting(text, isBold, isItalic, isMonospace, isStrikethrough, isUnderline,
323
+ isSuperscript, isSubscript, isHighlight, linkURL);
324
+
325
+ // Add block prefixes at line start
326
+ if (isAtLineStart(result)) {
327
+ NSMutableString *prefixedSegment = [NSMutableString string];
328
+
329
+ if (listDepthNum && ![text hasPrefix:@"\n"]) {
330
+ if (isTaskItem) {
331
+ NSString *indent = [@"" stringByPaddingToLength:(currentListDepth * 2)
332
+ withString:@" "
333
+ startingAtIndex:0];
334
+ NSString *checkbox = isTaskChecked ? @"[x]" : @"[ ]";
335
+ [prefixedSegment appendFormat:@"%@- %@ ", indent, checkbox];
336
+ } else {
337
+ BOOL isOrdered = ([listTypeNum integerValue] == 1);
338
+ NSInteger itemNumber = listItemNum ? [listItemNum integerValue] : 1;
339
+ [prefixedSegment appendString:buildListPrefix(currentListDepth, isOrdered, itemNumber)];
340
+ }
341
+ }
342
+
343
+ if (blockquotePrefix) {
344
+ [prefixedSegment insertString:blockquotePrefix atIndex:0];
345
+ }
346
+
347
+ [prefixedSegment appendString:segment];
348
+ segment = prefixedSegment;
349
+ }
350
+
351
+ if (state.needsBlankLine && result.length > 0) {
352
+ ensureBlankLine(result);
353
+ state.needsBlankLine = NO;
354
+ }
355
+
356
+ [result appendString:segment];
357
+ }];
358
+
359
+ // Flush remaining heading
360
+ if (currentHeadingType && headingContent.length > 0) {
361
+ ensureBlankLine(result);
362
+ NSInteger level = [[currentHeadingType substringFromIndex:8] integerValue];
363
+ [result appendFormat:@"%@%@\n", buildHeadingPrefix(level), headingContent];
364
+ }
365
+
366
+ return result.length > 0 ? result : nil;
367
+ }
@@ -0,0 +1,25 @@
1
+ #pragma once
2
+ #import "ENRMUIKit.h"
3
+
4
+ #import "ENRMLinkContextMenus.h"
5
+
6
+ #if !TARGET_OS_OSX
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ /**
10
+ * The read-only text view tiptap renders into. A link pill is one placeholder character
11
+ * in text storage; what the system reads for the selection (Look Up, Translate, Share,
12
+ * drag, the Copy key command) is answered here with the link text the pill stands for.
13
+ *
14
+ * It also presents the menu of a pill that has one, taken from a delegate that is an
15
+ * `ENRMLinkContextMenuSource`: UIKit lifts a pressed text item on a tinted box of its own,
16
+ * which hides the pill's look.
17
+ */
18
+ @interface ENRMTiptapTextView : UITextView <ENRMLinkMenuPresenting>
19
+ /// Copies a selection the way the menu Copy does. Set by the hosting view, which knows the
20
+ /// source Tiptap and style; without it the Copy key command writes plain text and RTF only.
21
+ @property (nonatomic, copy, nullable) void (^copySelectionHandler)(NSRange selectedRange);
22
+ @end
23
+
24
+ NS_ASSUME_NONNULL_END
25
+ #endif
@@ -0,0 +1,247 @@
1
+ #import "ENRMTiptapTextView.h"
2
+ #import "ENRMLinkPillAttachment.h"
3
+ #import "ENRMLinkPillText.h"
4
+ #import "ENRMTextHitTest.h"
5
+ #import "LinkTapUtils.h"
6
+ #import "PasteboardUtils.h"
7
+
8
+ #if !TARGET_OS_OSX
9
+ @interface ENRMTiptapTextView () <UIContextMenuInteractionDelegate>
10
+ @end
11
+
12
+ @implementation ENRMTiptapTextView {
13
+ ENRMLinkMenuLift *_pillLift;
14
+ // The pill whose menu is being presented. It draws nothing for as long as its lifted image
15
+ // stands in for it: it would show through that image.
16
+ ENRMLinkPillAttachment *_liftedPill;
17
+ NSUInteger _pillMenuIndex;
18
+ }
19
+
20
+ - (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer
21
+ {
22
+ if (self = [super initWithFrame:frame textContainer:textContainer]) {
23
+ _pillLift = [[ENRMLinkMenuLift alloc] init];
24
+ __weak ENRMTiptapTextView *weakSelf = self;
25
+ _pillLift.onEnd = ^{ [weakSelf restoreLiftedPill]; };
26
+ [self addInteraction:[[UIContextMenuInteraction alloc] initWithDelegate:self]];
27
+ }
28
+ return self;
29
+ }
30
+
31
+ /// A tap places the caret, and UITextView then scrolls that caret into view. This view is
32
+ /// already sized to its text and sits in the screen's scroll view, so that scroll shifts
33
+ /// the page and the tapped line jumps toward the top. Scrolling stays off, and these
34
+ /// requests are dropped. Copy and selection are unchanged.
35
+ - (void)setContentOffset:(CGPoint)contentOffset
36
+ {
37
+ if (!self.scrollEnabled && !CGPointEqualToPoint(contentOffset, CGPointZero)) {
38
+ if (!CGPointEqualToPoint(self.contentOffset, CGPointZero)) {
39
+ [super setContentOffset:CGPointZero];
40
+ }
41
+ return;
42
+ }
43
+ [super setContentOffset:contentOffset];
44
+ }
45
+
46
+ - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
47
+ {
48
+ if (!self.scrollEnabled) {
49
+ [self setContentOffset:CGPointZero];
50
+ return;
51
+ }
52
+ [super setContentOffset:contentOffset animated:animated];
53
+ }
54
+
55
+ - (void)scrollRangeToVisible:(NSRange)range
56
+ {
57
+ if (self.scrollEnabled)
58
+ [super scrollRangeToVisible:range];
59
+ }
60
+
61
+ - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
62
+ {
63
+ if (self.scrollEnabled)
64
+ [super scrollRectToVisible:rect animated:animated];
65
+ }
66
+
67
+ /// New text moves or replaces the pill under its lifted image.
68
+ - (void)setAttributedText:(NSAttributedString *)attributedText
69
+ {
70
+ [_pillLift end];
71
+ [super setAttributedText:attributedText];
72
+ }
73
+
74
+ /// The selection with pills expanded, or nil when `range` is not the selection or holds no pill.
75
+ /// Only the selection is rewritten: UIKit also reads arbitrary ranges to find word boundaries,
76
+ /// and those must keep matching storage offsets.
77
+ - (nullable NSAttributedString *)expandedSelectionForRange:(UITextRange *)range
78
+ {
79
+ UITextRange *selection = self.selectedTextRange;
80
+ if (!range || !selection || selection.isEmpty || ![range isEqual:selection])
81
+ return nil;
82
+ NSRange selected = self.selectedRange;
83
+ NSTextStorage *storage = self.textStorage;
84
+ if (selected.location == NSNotFound || NSMaxRange(selected) > storage.length)
85
+ return nil;
86
+ NSAttributedString *text = [storage attributedSubstringFromRange:selected];
87
+ NSAttributedString *expanded = ENRMAttributedStringByExpandingLinkPills(text, NULL);
88
+ return expanded == text ? nil : expanded;
89
+ }
90
+
91
+ - (NSString *)textInRange:(UITextRange *)range
92
+ {
93
+ return [self expandedSelectionForRange:range].string ?: [super textInRange:range];
94
+ }
95
+
96
+ - (NSAttributedString *)attributedTextInRange:(UITextRange *)range
97
+ {
98
+ return [self expandedSelectionForRange:range] ?: [super attributedTextInRange:range];
99
+ }
100
+
101
+ /// The menu's Copy is already replaced by the library's own action; this covers the key command.
102
+ - (void)copy:(id)sender
103
+ {
104
+ NSRange selected = self.selectedRange;
105
+ if (self.copySelectionHandler && selected.location != NSNotFound && selected.length > 0) {
106
+ self.copySelectionHandler(selected);
107
+ return;
108
+ }
109
+ NSAttributedString *expanded = [self expandedSelectionForRange:self.selectedTextRange];
110
+ if (!expanded) {
111
+ [super copy:sender];
112
+ return;
113
+ }
114
+ copyAttributedStringToPasteboard(expanded, nil, nil);
115
+ }
116
+
117
+ #pragma mark - Pill menus
118
+
119
+ - (nullable ENRMLinkContextMenus *)linkContextMenus
120
+ {
121
+ id delegate = self.delegate;
122
+ return [delegate conformsToProtocol:@protocol(ENRMLinkContextMenuSource)]
123
+ ? [(id<ENRMLinkContextMenuSource>)delegate linkContextMenusForTextView:self]
124
+ : nil;
125
+ }
126
+
127
+ /// Where the pill at `index` is drawn, in the text view's coordinates.
128
+ - (CGRect)pillFrameAtIndex:(NSUInteger)index
129
+ {
130
+ NSLayoutManager *layoutManager = self.layoutManager;
131
+ NSUInteger glyph = [layoutManager glyphIndexForCharacterAtIndex:index];
132
+ CGSize size = [layoutManager attachmentSizeForGlyphAtIndex:glyph];
133
+ if (size.width <= 0 || size.height <= 0)
134
+ return CGRectNull;
135
+ CGRect line = [layoutManager lineFragmentRectForGlyphAtIndex:glyph effectiveRange:NULL];
136
+ // TextKit places an attachment glyph at the bottom edge of its bounds.
137
+ CGPoint location = [layoutManager locationForGlyphAtIndex:glyph];
138
+ UIEdgeInsets inset = self.textContainerInset;
139
+ return CGRectMake(inset.left + line.origin.x + location.x, inset.top + line.origin.y + location.y - size.height,
140
+ size.width, size.height);
141
+ }
142
+
143
+ /// Whether the character at `index` is a pill whose link has a menu.
144
+ - (BOOL)presentsLinkMenuAtIndex:(NSUInteger)index
145
+ {
146
+ ENRMLinkContextMenus *menus = [self linkContextMenus];
147
+ NSTextStorage *storage = self.textStorage;
148
+ if (menus.entries.count == 0 || index >= storage.length)
149
+ return NO;
150
+ id attachment = [storage attribute:NSAttachmentAttributeName atIndex:index effectiveRange:NULL];
151
+ return [attachment isKindOfClass:ENRMLinkPillAttachment.class] &&
152
+ [menus hasMenuForURL:linkURLAtRange(self, NSMakeRange(index, 1))];
153
+ }
154
+
155
+ /// The character index of the pill under `point` when its link has a menu, else NSNotFound.
156
+ - (NSUInteger)menuPillIndexAtPoint:(CGPoint)point
157
+ {
158
+ if ([self linkContextMenus].entries.count == 0)
159
+ return NSNotFound;
160
+ NSUInteger index = ENRMCharacterIndexAtPoint(self, point);
161
+ if (index == NSNotFound || ![self presentsLinkMenuAtIndex:index] ||
162
+ !CGRectContainsPoint([self pillFrameAtIndex:index], point))
163
+ return NSNotFound;
164
+ return index;
165
+ }
166
+
167
+ - (void)setPill:(ENRMLinkPillAttachment *)pill lifted:(BOOL)lifted
168
+ {
169
+ pill.lifted = lifted;
170
+ NSTextStorage *storage = self.textStorage;
171
+ [storage enumerateAttribute:NSAttachmentAttributeName
172
+ inRange:NSMakeRange(0, storage.length)
173
+ options:0
174
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
175
+ if (value != pill)
176
+ return;
177
+ [self.layoutManager invalidateDisplayForCharacterRange:range];
178
+ *stop = YES;
179
+ }];
180
+ }
181
+
182
+ - (void)restoreLiftedPill
183
+ {
184
+ if (_liftedPill)
185
+ [self setPill:_liftedPill lifted:NO];
186
+ _liftedPill = nil;
187
+ }
188
+
189
+ - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
190
+ configurationForMenuAtLocation:(CGPoint)location
191
+ {
192
+ [_pillLift end];
193
+ _pillMenuIndex = [self menuPillIndexAtPoint:location];
194
+ if (_pillMenuIndex == NSNotFound)
195
+ return nil;
196
+ UIMenu *menu = [[self linkContextMenus] menuForURL:linkURLAtRange(self, NSMakeRange(_pillMenuIndex, 1))
197
+ title:linkTitleAtIndex(self.textStorage, _pillMenuIndex)];
198
+ if (!menu)
199
+ return nil;
200
+ return [UIContextMenuConfiguration
201
+ configurationWithIdentifier:nil
202
+ previewProvider:nil
203
+ actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) { return menu; }];
204
+ }
205
+
206
+ /// The pill is lifted as an image of itself, see `ENRMLinkMenuLift`.
207
+ - (UITargetedPreview *)pillMenuPreview
208
+ {
209
+ if (![_pillLift isOverView:self]) {
210
+ NSTextStorage *storage = self.textStorage;
211
+ if (self.window == nil || _pillMenuIndex >= storage.length)
212
+ return nil;
213
+ id attachment = [storage attribute:NSAttachmentAttributeName atIndex:_pillMenuIndex effectiveRange:NULL];
214
+ CGRect frame = [self pillFrameAtIndex:_pillMenuIndex];
215
+ if (![attachment isKindOfClass:ENRMLinkPillAttachment.class] || CGRectIsNull(frame))
216
+ return nil;
217
+ UIImage *image = [(ENRMLinkPillAttachment *)attachment imageForBounds:(CGRect){CGPointZero, frame.size}
218
+ textContainer:self.textContainer
219
+ characterIndex:_pillMenuIndex];
220
+ [_pillLift layImage:image atFrame:frame overView:self];
221
+ _liftedPill = attachment;
222
+ [self setPill:_liftedPill lifted:YES];
223
+ }
224
+ return [_pillLift previewWithCornerRadius:_liftedPill.cornerRadius backgroundColor:UIColor.clearColor];
225
+ }
226
+
227
+ - (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
228
+ previewForHighlightingMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
229
+ {
230
+ return [self pillMenuPreview];
231
+ }
232
+
233
+ - (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
234
+ previewForDismissingMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
235
+ {
236
+ return [self pillMenuPreview];
237
+ }
238
+
239
+ - (void)contextMenuInteraction:(UIContextMenuInteraction *)interaction
240
+ willEndForConfiguration:(UIContextMenuConfiguration *)configuration
241
+ animator:(id<UIContextMenuInteractionAnimating>)animator
242
+ {
243
+ [_pillLift endWithAnimator:animator];
244
+ }
245
+
246
+ @end
247
+ #endif