@apollohg/react-native-rich-text-editor 2.0.0
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 +160 -0
- package/README.md +259 -0
- package/RUST-STANDARD-LIBRARY-NOTICES.html +8266 -0
- package/ReactNativeProseEditor.podspec +59 -0
- package/THIRD_PARTY_NOTICES.md +31155 -0
- package/android/build.gradle +188 -0
- package/android/consumer-rules.pro +18 -0
- package/android/expo/build.gradle +17 -0
- package/android/src/debug/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +7 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
- package/android/src/main/java/com/apollohg/editor/AndroidApiCompat.kt +19 -0
- package/android/src/main/java/com/apollohg/editor/AndroidCollaborationTransport.kt +860 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +91 -0
- package/android/src/main/java/com/apollohg/editor/CodeHighlighting.kt +141 -0
- package/android/src/main/java/com/apollohg/editor/DecodedBitmapBudget.kt +250 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/EditorBlockSpacing.kt +51 -0
- package/android/src/main/java/com/apollohg/editor/EditorBoxDrawable.kt +16 -0
- package/android/src/main/java/com/apollohg/editor/EditorBoxDrawing.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/EditorCheckboxSpan.kt +87 -0
- package/android/src/main/java/com/apollohg/editor/EditorCodeHighlighting.kt +120 -0
- package/android/src/main/java/com/apollohg/editor/EditorDecorationLayer.java +18 -0
- package/android/src/main/java/com/apollohg/editor/EditorDocumentLayout.kt +626 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +1042 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextAnnotations.kt +191 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextCaret.kt +100 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextClipboard.kt +101 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextComposition.kt +220 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextConfiguration.kt +316 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextCorrection.kt +303 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDeferredUpdates.kt +327 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDeletion.kt +258 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDiagnostics.kt +83 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDragDrop.kt +67 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextExternalComposition.kt +357 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextHardwareInput.kt +277 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +280 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputLifecycle.kt +183 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputPreparation.kt +79 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputTraits.kt +384 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextLayout.kt +100 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextNativeMutation.kt +243 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextPlaceholder.kt +154 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRenderPatch.kt +347 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRendering.kt +313 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRustCommands.kt +281 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextSelection.kt +352 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextState.kt +113 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextTaskMarkers.kt +92 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextTextCommit.kt +327 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextToolbar.kt +84 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextUpdates.kt +245 -0
- package/android/src/main/java/com/apollohg/editor/EditorGlyphHeightCursorDrawable.kt +42 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +13 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +923 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCoordinates.kt +191 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCorrection.kt +107 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCursorGeometry.kt +128 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputSnapshots.kt +134 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputSurroundingDeletion.kt +321 -0
- package/android/src/main/java/com/apollohg/editor/EditorKeyboardToolbarView.kt +851 -0
- package/android/src/main/java/com/apollohg/editor/EditorMentionStyle.kt +192 -0
- package/android/src/main/java/com/apollohg/editor/EditorReconciliationWatcher.kt +46 -0
- package/android/src/main/java/com/apollohg/editor/EditorStyleSheet.kt +303 -0
- package/android/src/main/java/com/apollohg/editor/EditorStyleSheetSpans.kt +253 -0
- package/android/src/main/java/com/apollohg/editor/EditorSurfaceInputConnection.kt +67 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextDecorationDrawing.kt +143 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurface.kt +712 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceDragDrop.kt +94 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceInteraction.kt +708 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +650 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Adapter.kt +912 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterMutations.kt +402 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterRendering.kt +123 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterSelection.kt +203 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Backend.kt +120 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Driver.kt +87 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Registry.kt +97 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +409 -0
- package/android/src/main/java/com/apollohg/editor/ExpoAutoGrowStyleSizePublisher.kt +34 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +266 -0
- package/android/src/main/java/com/apollohg/editor/ImeTextCoordinateMapper.kt +102 -0
- package/android/src/main/java/com/apollohg/editor/KeyboardToolbarImeAnimationController.kt +101 -0
- package/android/src/main/java/com/apollohg/editor/MentionSuggestionChipView.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/NativeCollaborationTransportRegistry.kt +493 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +813 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewAtoms.kt +68 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewBinding.kt +126 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewDiagnostics.kt +208 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewErrors.kt +146 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewEvents.kt +150 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewExternalComposition.kt +39 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewFocus.kt +176 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewLifecycle.kt +302 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewMentions.kt +259 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewNativeActions.kt +179 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewOutsideTap.kt +214 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPendingUpdates.kt +376 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewProperties.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPropertyRetries.kt +197 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewToolbar.kt +285 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewTypes.kt +28 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewUpdates.kt +271 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +687 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorOutsideTapDispatcher.kt +380 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorV2Numbers.kt +45 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorViewRegistry.kt +372 -0
- package/android/src/main/java/com/apollohg/editor/NativeImagePipeline.kt +22 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +193 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbarIcons.kt +177 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbarItem.kt +274 -0
- package/android/src/main/java/com/apollohg/editor/OkHttpCollaborationSocket.kt +102 -0
- package/android/src/main/java/com/apollohg/editor/OrderedListMarkerFormatter.kt +130 -0
- package/android/src/main/java/com/apollohg/editor/PointerTransparentView.java +18 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +128 -0
- package/android/src/main/java/com/apollohg/editor/ProseViewerView.kt +854 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +314 -0
- package/android/src/main/java/com/apollohg/editor/RenderBlockSpans.kt +314 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +224 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeBlockStyles.kt +407 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeElements.kt +544 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeInlineAndAtoms.kt +631 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeParagraphs.kt +215 -0
- package/android/src/main/java/com/apollohg/editor/RenderImageDecoder.kt +748 -0
- package/android/src/main/java/com/apollohg/editor/RenderImageLoader.kt +900 -0
- package/android/src/main/java/com/apollohg/editor/RenderLayoutConstants.kt +117 -0
- package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +337 -0
- package/android/src/main/java/com/apollohg/editor/RenderParagraphSpans.kt +182 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +951 -0
- package/android/src/main/java/com/apollohg/editor/SharedNativeImagePipeline.kt +78 -0
- package/android/src/main/java/com/apollohg/editor/UniffiEditorV2Backend.kt +261 -0
- package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1656 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FabricAttachmentSidecars.kt +80 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FabricLeaseHandleBridge.kt +59 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FallbackSelectionGeometry.kt +450 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseDrawingView.kt +678 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseInstrumentation.kt +608 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayout.kt +186 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutCache.kt +393 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutRegistry.kt +828 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseTheme.kt +316 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerManager.kt +900 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerPackage.kt +16 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedViewerAtoms.kt +89 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerCodeHighlighting.kt +91 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerDocument.kt +453 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerFontEnvironment.kt +282 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerImagePipeline.kt +460 -0
- package/android/src/main/jni/CMakeLists.txt +85 -0
- package/android/src/main/jni/PreparedProseViewerMeasurementsManager.cpp +334 -0
- package/android/src/main/jni/ReactNativeProseEditorSpec.h +17 -0
- package/android/src/main/jni/reactnativeproseeditor.h +6 -0
- package/android/src/main/res/values/attrs.xml +6 -0
- package/android/src/release/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +9 -0
- package/app.plugin.js +62 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseMeasurementsManager.h +62 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerComponentDescriptor.h +30 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.cpp +192 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.h +46 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerState.h +145 -0
- package/dist/AtomHost.d.ts +17 -0
- package/dist/AtomHost.js +135 -0
- package/dist/DefaultAtomChip.d.ts +2 -0
- package/dist/DefaultAtomChip.js +22 -0
- package/dist/EditorAddon.d.ts +32 -0
- package/dist/EditorAddon.js +29 -0
- package/dist/EditorAddonNormalization.d.ts +6 -0
- package/dist/EditorAddonNormalization.js +48 -0
- package/dist/EditorMentionThemeNormalization.d.ts +8 -0
- package/dist/EditorMentionThemeNormalization.js +22 -0
- package/dist/EditorMentionThemeValidation.d.ts +12 -0
- package/dist/EditorMentionThemeValidation.js +95 -0
- package/dist/EditorStyleSheet.d.ts +4 -0
- package/dist/EditorStyleSheet.js +10 -0
- package/dist/EditorStyleSheetNormalization.d.ts +3 -0
- package/dist/EditorStyleSheetNormalization.js +226 -0
- package/dist/EditorStyleSheetTypes.d.ts +151 -0
- package/dist/EditorStyleSheetTypes.js +2 -0
- package/dist/EditorTheme.d.ts +117 -0
- package/dist/EditorTheme.js +41 -0
- package/dist/EditorToolbar.d.ts +4 -0
- package/dist/EditorToolbar.js +17 -0
- package/dist/EditorToolbarComponent.d.ts +13 -0
- package/dist/EditorToolbarComponent.js +24 -0
- package/dist/EditorToolbarItems.d.ts +9 -0
- package/dist/EditorToolbarItems.js +58 -0
- package/dist/EditorToolbarRegistry.d.ts +59 -0
- package/dist/EditorToolbarRegistry.js +175 -0
- package/dist/EditorToolbarTypes.d.ts +273 -0
- package/dist/EditorToolbarTypes.js +2 -0
- package/dist/EditorToolbarVisuals.d.ts +154 -0
- package/dist/EditorToolbarVisuals.js +228 -0
- package/dist/EditorUpdateRevision.d.ts +8 -0
- package/dist/EditorUpdateRevision.js +22 -0
- package/dist/ExternalTextComposition.d.ts +45 -0
- package/dist/ExternalTextComposition.js +537 -0
- package/dist/ImageLoadingPolicy.d.ts +54 -0
- package/dist/ImageLoadingPolicy.js +60 -0
- package/dist/NativeEditorBoundaryError.d.ts +111 -0
- package/dist/NativeEditorBoundaryError.js +268 -0
- package/dist/NativeEditorBridge.d.ts +9 -0
- package/dist/NativeEditorBridge.js +37 -0
- package/dist/NativeEditorCollaborationTransport.d.ts +154 -0
- package/dist/NativeEditorCollaborationTransport.js +275 -0
- package/dist/NativeEditorCreateConfig.d.ts +16 -0
- package/dist/NativeEditorCreateConfig.js +249 -0
- package/dist/NativeEditorCreateJson.d.ts +76 -0
- package/dist/NativeEditorCreateJson.js +474 -0
- package/dist/NativeEditorCreateValidation.d.ts +32 -0
- package/dist/NativeEditorCreateValidation.js +150 -0
- package/dist/NativeEditorDocumentBridge.d.ts +72 -0
- package/dist/NativeEditorDocumentBridge.js +305 -0
- package/dist/NativeEditorDocumentHandle.d.ts +70 -0
- package/dist/NativeEditorDocumentHandle.js +89 -0
- package/dist/NativeEditorLocalAwareness.d.ts +34 -0
- package/dist/NativeEditorLocalAwareness.js +153 -0
- package/dist/NativeEditorNativeModule.d.ts +48 -0
- package/dist/NativeEditorNativeModule.js +34 -0
- package/dist/NativeEditorRenderNormalization.d.ts +22 -0
- package/dist/NativeEditorRenderNormalization.js +274 -0
- package/dist/NativeEditorResultNormalization.d.ts +108 -0
- package/dist/NativeEditorResultNormalization.js +400 -0
- package/dist/NativeEditorTypes.d.ts +318 -0
- package/dist/NativeEditorTypes.js +15 -0
- package/dist/NativeEditorV2Decimal.d.ts +5 -0
- package/dist/NativeEditorV2Decimal.js +18 -0
- package/dist/NativeProseViewer.d.ts +122 -0
- package/dist/NativeProseViewer.js +130 -0
- package/dist/NativeRichTextEditor.d.ts +3 -0
- package/dist/NativeRichTextEditor.js +6 -0
- package/dist/ResourceLimits.d.ts +100 -0
- package/dist/ResourceLimits.js +80 -0
- package/dist/RichTextEditor.d.ts +4 -0
- package/dist/RichTextEditor.js +25 -0
- package/dist/RichTextEditorNativeTypes.d.ts +122 -0
- package/dist/RichTextEditorNativeTypes.js +2 -0
- package/dist/RichTextEditorNativeView.d.ts +12 -0
- package/dist/RichTextEditorNativeView.js +15 -0
- package/dist/RichTextEditorSerialization.d.ts +53 -0
- package/dist/RichTextEditorSerialization.js +283 -0
- package/dist/RichTextEditorTypes.d.ts +281 -0
- package/dist/RichTextEditorTypes.js +2 -0
- package/dist/ViewerConfiguration.d.ts +22 -0
- package/dist/ViewerConfiguration.js +6 -0
- package/dist/YjsCollaboration.d.ts +146 -0
- package/dist/YjsCollaboration.js +496 -0
- package/dist/addons.d.ts +193 -0
- package/dist/addons.js +122 -0
- package/dist/atomConstants.d.ts +3 -0
- package/dist/atomConstants.js +6 -0
- package/dist/atomInstances.d.ts +35 -0
- package/dist/atomInstances.js +79 -0
- package/dist/atomPolicy.d.ts +4 -0
- package/dist/atomPolicy.js +47 -0
- package/dist/atomUpdates.d.ts +2 -0
- package/dist/atomUpdates.js +38 -0
- package/dist/atoms.d.ts +92 -0
- package/dist/atoms.js +222 -0
- package/dist/attributeValidation.d.ts +4 -0
- package/dist/attributeValidation.js +97 -0
- package/dist/contentExpression.d.ts +6 -0
- package/dist/contentExpression.js +335 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +78 -0
- package/dist/listMarkers.d.ts +15 -0
- package/dist/listMarkers.js +21 -0
- package/dist/schemaDefinition.d.ts +143 -0
- package/dist/schemaDefinition.js +206 -0
- package/dist/schemaDocument.d.ts +17 -0
- package/dist/schemaDocument.js +69 -0
- package/dist/schemaDocumentConstruction.d.ts +5 -0
- package/dist/schemaDocumentConstruction.js +125 -0
- package/dist/schemaNormalization.d.ts +29 -0
- package/dist/schemaNormalization.js +313 -0
- package/dist/schemaPresets.d.ts +68 -0
- package/dist/schemaPresets.js +198 -0
- package/dist/schemaResolution.d.ts +9 -0
- package/dist/schemaResolution.js +211 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +28 -0
- package/dist/specs/PreparedProseViewerNativeComponent.d.ts +37 -0
- package/dist/specs/PreparedProseViewerNativeComponent.js +6 -0
- package/dist/useEditorToolbarInteractions.d.ts +12 -0
- package/dist/useEditorToolbarInteractions.js +218 -0
- package/dist/useEditorToolbarItems.d.ts +8 -0
- package/dist/useEditorToolbarItems.js +280 -0
- package/dist/useEditorToolbarPresentation.d.ts +4 -0
- package/dist/useEditorToolbarPresentation.js +203 -0
- package/dist/useEditorToolbarState.d.ts +63 -0
- package/dist/useEditorToolbarState.js +108 -0
- package/dist/useFocusPreservingFrames.d.ts +17 -0
- package/dist/useFocusPreservingFrames.js +108 -0
- package/dist/useNativeEditor.d.ts +66 -0
- package/dist/useNativeEditor.js +370 -0
- package/dist/useRichTextEditorCommands.d.ts +21 -0
- package/dist/useRichTextEditorCommands.js +299 -0
- package/dist/useRichTextEditorEvents.d.ts +18 -0
- package/dist/useRichTextEditorEvents.js +216 -0
- package/dist/useRichTextEditorMentions.d.ts +11 -0
- package/dist/useRichTextEditorMentions.js +316 -0
- package/dist/useRichTextEditorPresentation.d.ts +5 -0
- package/dist/useRichTextEditorPresentation.js +178 -0
- package/dist/useRichTextEditorState.d.ts +180 -0
- package/dist/useRichTextEditorState.js +405 -0
- package/dist/useRichTextEditorUpdates.d.ts +10 -0
- package/dist/useRichTextEditorUpdates.js +308 -0
- package/dist/useViewerAtoms.d.ts +38 -0
- package/dist/useViewerAtoms.js +288 -0
- package/expo-module.config.json +13 -0
- package/ios/EditorAddons.swift +354 -0
- package/ios/EditorCodeHighlighting.swift +184 -0
- package/ios/EditorCore.xcframework/Info.plist +44 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorInteractionOverlays.swift +697 -0
- package/ios/EditorLayoutManager.swift +971 -0
- package/ios/EditorNodeTypes.swift +37 -0
- package/ios/EditorStyleSheet.swift +461 -0
- package/ios/EditorTextView+AttributedPatch.swift +305 -0
- package/ios/EditorTextView+Binding.swift +61 -0
- package/ios/EditorTextView+Caret.swift +333 -0
- package/ios/EditorTextView+Commands.swift +203 -0
- package/ios/EditorTextView+Delegate.swift +65 -0
- package/ios/EditorTextView+Diagnostics.swift +225 -0
- package/ios/EditorTextView+DragDrop.swift +290 -0
- package/ios/EditorTextView+ExternalComposition.swift +364 -0
- package/ios/EditorTextView+Images.swift +354 -0
- package/ios/EditorTextView+InputTraits.swift +200 -0
- package/ios/EditorTextView+KeyboardInput.swift +318 -0
- package/ios/EditorTextView+Layout.swift +245 -0
- package/ios/EditorTextView+MarkedText.swift +267 -0
- package/ios/EditorTextView+NativeInputLifecycle.swift +372 -0
- package/ios/EditorTextView+NativeMutation.swift +471 -0
- package/ios/EditorTextView+Reconciliation.swift +97 -0
- package/ios/EditorTextView+RenderPatch.swift +573 -0
- package/ios/EditorTextView+Rendering.swift +539 -0
- package/ios/EditorTextView+Selection.swift +542 -0
- package/ios/EditorTextView.swift +953 -0
- package/ios/EditorTheme.swift +626 -0
- package/ios/EditorV2Adapter+Commands.swift +204 -0
- package/ios/EditorV2Adapter+Envelope.swift +238 -0
- package/ios/EditorV2Adapter+Mutation.swift +392 -0
- package/ios/EditorV2Adapter+Registry.swift +185 -0
- package/ios/EditorV2Adapter+RenderState.swift +360 -0
- package/ios/EditorV2Adapter+RenderValidation.swift +420 -0
- package/ios/EditorV2Adapter+Selection.swift +329 -0
- package/ios/EditorV2Adapter+TextCommands.swift +430 -0
- package/ios/EditorV2Adapter.swift +376 -0
- package/ios/EditorV2Shadow.swift +353 -0
- package/ios/Generated_editor_core.swift +2378 -0
- package/ios/NativeCodeHighlighting.swift +159 -0
- package/ios/NativeCollaborationSocket.swift +144 -0
- package/ios/NativeCollaborationTransport+Protocol.swift +258 -0
- package/ios/NativeCollaborationTransport+Socket.swift +379 -0
- package/ios/NativeCollaborationTransport+Validation.swift +103 -0
- package/ios/NativeCollaborationTransport.swift +322 -0
- package/ios/NativeCollaborationTransportRegistry.swift +521 -0
- package/ios/NativeEditorExpoView+AccessoryRetry.swift +219 -0
- package/ios/NativeEditorExpoView+Binding.swift +268 -0
- package/ios/NativeEditorExpoView+Diagnostics.swift +66 -0
- package/ios/NativeEditorExpoView+Events.swift +131 -0
- package/ios/NativeEditorExpoView+Focus.swift +169 -0
- package/ios/NativeEditorExpoView+MentionRetry.swift +288 -0
- package/ios/NativeEditorExpoView+Mentions.swift +288 -0
- package/ios/NativeEditorExpoView+Properties.swift +300 -0
- package/ios/NativeEditorExpoView+Toolbar.swift +86 -0
- package/ios/NativeEditorExpoView+Updates.swift +312 -0
- package/ios/NativeEditorExpoView.swift +349 -0
- package/ios/NativeEditorModule.swift +856 -0
- package/ios/NativeEditorViewRegistry.swift +226 -0
- package/ios/NativeToolbar+ButtonStyle.swift +214 -0
- package/ios/NativeToolbar+Buttons.swift +228 -0
- package/ios/NativeToolbar+Diagnostics.swift +114 -0
- package/ios/NativeToolbar+Menu.swift +101 -0
- package/ios/NativeToolbar+Models.swift +519 -0
- package/ios/NativeToolbar+Placeholder.swift +39 -0
- package/ios/NativeToolbar.swift +612 -0
- package/ios/OrderedListMarkerFormatter.swift +103 -0
- package/ios/PositionBridge.swift +596 -0
- package/ios/ProseViewerView.swift +462 -0
- package/ios/RenderBridge+Atoms.swift +288 -0
- package/ios/RenderBridge+Attachments.swift +246 -0
- package/ios/RenderBridge+BlockBoundaries.swift +313 -0
- package/ios/RenderBridge+Style.swift +343 -0
- package/ios/RenderBridge.swift +704 -0
- package/ios/RichTextEditorView.swift +930 -0
- package/ios/SharedNativeImagePipeline+Cache.swift +189 -0
- package/ios/SharedNativeImagePipeline+Transport.swift +328 -0
- package/ios/SharedNativeImagePipeline.swift +533 -0
- package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +436 -0
- package/ios/Viewer/CoreTextProseLayoutEngine+Models.swift +322 -0
- package/ios/Viewer/CoreTextProseLayoutEngine.swift +635 -0
- package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.h +4 -0
- package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.mm +767 -0
- package/ios/Viewer/Fabric/PreparedProseMeasurementsManager.mm +118 -0
- package/ios/Viewer/NativeSVGImageDecoder.h +9 -0
- package/ios/Viewer/NativeSVGImageDecoder.mm +106 -0
- package/ios/Viewer/NativeSVGPreflight.h +11 -0
- package/ios/Viewer/NativeSVGPreflight.mm +219 -0
- package/ios/Viewer/PreparedProseDrawingView.swift +639 -0
- package/ios/Viewer/PreparedProseInstrumentation.swift +387 -0
- package/ios/Viewer/PreparedProseLayout.swift +326 -0
- package/ios/Viewer/PreparedProseLayoutCache.swift +766 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+Artifacts.swift +377 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+Fabric.swift +582 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+FabricOwnership.swift +128 -0
- package/ios/Viewer/PreparedProseLayoutRegistry.swift +381 -0
- package/ios/Viewer/ViewerDocument.swift +575 -0
- package/ios/Viewer/ViewerFontEnvironment.swift +283 -0
- package/ios/Viewer/ViewerImagePipeline.swift +478 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1023 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/package.json +151 -0
- package/react-native.config.js +17 -0
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +3146 -0
- package/src/AtomHost.tsx +180 -0
- package/src/DefaultAtomChip.tsx +27 -0
- package/src/EditorAddon.ts +65 -0
- package/src/EditorAddonNormalization.ts +49 -0
- package/src/EditorMentionThemeNormalization.ts +25 -0
- package/src/EditorMentionThemeValidation.ts +133 -0
- package/src/EditorStyleSheet.ts +9 -0
- package/src/EditorStyleSheetNormalization.ts +219 -0
- package/src/EditorStyleSheetTypes.ts +189 -0
- package/src/EditorTheme.ts +196 -0
- package/src/EditorToolbar.tsx +31 -0
- package/src/EditorToolbarComponent.tsx +23 -0
- package/src/EditorToolbarItems.tsx +64 -0
- package/src/EditorToolbarRegistry.tsx +254 -0
- package/src/EditorToolbarTypes.tsx +322 -0
- package/src/EditorToolbarVisuals.tsx +270 -0
- package/src/EditorUpdateRevision.ts +26 -0
- package/src/ExternalTextComposition.ts +658 -0
- package/src/ImageLoadingPolicy.ts +125 -0
- package/src/NativeEditorBoundaryError.ts +345 -0
- package/src/NativeEditorBridge.ts +93 -0
- package/src/NativeEditorCollaborationTransport.ts +493 -0
- package/src/NativeEditorCreateConfig.ts +326 -0
- package/src/NativeEditorCreateJson.ts +579 -0
- package/src/NativeEditorCreateValidation.ts +178 -0
- package/src/NativeEditorDocumentBridge.ts +485 -0
- package/src/NativeEditorDocumentHandle.ts +168 -0
- package/src/NativeEditorLocalAwareness.ts +168 -0
- package/src/NativeEditorNativeModule.ts +97 -0
- package/src/NativeEditorRenderNormalization.ts +320 -0
- package/src/NativeEditorResultNormalization.ts +469 -0
- package/src/NativeEditorTypes.ts +366 -0
- package/src/NativeEditorV2Decimal.ts +13 -0
- package/src/NativeProseViewer.tsx +356 -0
- package/src/NativeRichTextEditor.tsx +33 -0
- package/src/ResourceLimits.ts +187 -0
- package/src/RichTextEditor.tsx +27 -0
- package/src/RichTextEditorNativeTypes.tsx +144 -0
- package/src/RichTextEditorNativeView.tsx +21 -0
- package/src/RichTextEditorSerialization.tsx +381 -0
- package/src/RichTextEditorTypes.tsx +327 -0
- package/src/ViewerConfiguration.ts +23 -0
- package/src/YjsCollaboration.ts +692 -0
- package/src/addons.ts +311 -0
- package/src/atomConstants.ts +3 -0
- package/src/atomInstances.ts +123 -0
- package/src/atomPolicy.ts +47 -0
- package/src/atomUpdates.ts +52 -0
- package/src/atoms.ts +379 -0
- package/src/attributeValidation.ts +116 -0
- package/src/contentExpression.ts +338 -0
- package/src/index.ts +295 -0
- package/src/listMarkers.ts +18 -0
- package/src/schemaDefinition.ts +395 -0
- package/src/schemaDocument.ts +92 -0
- package/src/schemaDocumentConstruction.ts +148 -0
- package/src/schemaNormalization.ts +367 -0
- package/src/schemaPresets.ts +250 -0
- package/src/schemaResolution.ts +242 -0
- package/src/schemas.ts +43 -0
- package/src/specs/PreparedProseViewerNativeComponent.ts +40 -0
- package/src/useEditorToolbarInteractions.tsx +329 -0
- package/src/useEditorToolbarItems.tsx +390 -0
- package/src/useEditorToolbarPresentation.tsx +483 -0
- package/src/useEditorToolbarState.tsx +172 -0
- package/src/useFocusPreservingFrames.ts +171 -0
- package/src/useNativeEditor.ts +534 -0
- package/src/useRichTextEditorCommands.tsx +478 -0
- package/src/useRichTextEditorEvents.tsx +342 -0
- package/src/useRichTextEditorMentions.tsx +441 -0
- package/src/useRichTextEditorPresentation.tsx +563 -0
- package/src/useRichTextEditorState.tsx +620 -0
- package/src/useRichTextEditorUpdates.tsx +403 -0
- package/src/useViewerAtoms.tsx +427 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
enum EditorHeightBehavior: String {
|
|
4
|
+
case fixed
|
|
5
|
+
case autoGrow
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// MARK: - RichTextEditorView (Fabric Host)
|
|
9
|
+
|
|
10
|
+
final class AtomHostContainerView: UIView {
|
|
11
|
+
let atomKey: String
|
|
12
|
+
private(set) var hostedView: UIView?
|
|
13
|
+
weak var editorView: RichTextEditorView?
|
|
14
|
+
|
|
15
|
+
private var boundsObservation: NSKeyValueObservation?
|
|
16
|
+
private var isDetachingHostedView = false
|
|
17
|
+
private var lastReportedHeight: CGFloat?
|
|
18
|
+
|
|
19
|
+
init(atomKey: String) {
|
|
20
|
+
self.atomKey = atomKey
|
|
21
|
+
super.init(frame: .zero)
|
|
22
|
+
clipsToBounds = false
|
|
23
|
+
isUserInteractionEnabled = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
required init?(coder: NSCoder) {
|
|
27
|
+
fatalError("init(coder:) has not been implemented")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func host(_ view: UIView) {
|
|
31
|
+
if hostedView !== view {
|
|
32
|
+
_ = detachHostedView()
|
|
33
|
+
}
|
|
34
|
+
view.removeFromSuperview()
|
|
35
|
+
hostedView = view
|
|
36
|
+
addSubview(view)
|
|
37
|
+
boundsObservation = view.observe(\.bounds, options: [.new]) { [weak self] _, _ in
|
|
38
|
+
self?.reportHeightIfNeeded()
|
|
39
|
+
}
|
|
40
|
+
setNeedsLayout()
|
|
41
|
+
reportHeightIfNeeded()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@discardableResult
|
|
45
|
+
func detachHostedView() -> UIView? {
|
|
46
|
+
boundsObservation = nil
|
|
47
|
+
guard let hostedView else { return nil }
|
|
48
|
+
self.hostedView = nil
|
|
49
|
+
lastReportedHeight = nil
|
|
50
|
+
isDetachingHostedView = true
|
|
51
|
+
hostedView.removeFromSuperview()
|
|
52
|
+
isDetachingHostedView = false
|
|
53
|
+
return hostedView
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override func layoutSubviews() {
|
|
57
|
+
super.layoutSubviews()
|
|
58
|
+
reportHeightIfNeeded()
|
|
59
|
+
guard let hostedView, lastReportedHeight != nil else { return }
|
|
60
|
+
let height = hostedView.bounds.height
|
|
61
|
+
let frame = CGRect(x: 0, y: 0, width: bounds.width, height: height)
|
|
62
|
+
if hostedView.frame != frame {
|
|
63
|
+
hostedView.frame = frame
|
|
64
|
+
}
|
|
65
|
+
reportHeightIfNeeded()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override func willRemoveSubview(_ subview: UIView) {
|
|
69
|
+
super.willRemoveSubview(subview)
|
|
70
|
+
guard !isDetachingHostedView, subview === hostedView else { return }
|
|
71
|
+
boundsObservation = nil
|
|
72
|
+
hostedView = nil
|
|
73
|
+
lastReportedHeight = nil
|
|
74
|
+
editorView?.atomHostContainerDidLoseHostedView(self)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private func reportHeightIfNeeded() {
|
|
78
|
+
guard let height = hostedView?.bounds.height,
|
|
79
|
+
height.isFinite,
|
|
80
|
+
height >= 0,
|
|
81
|
+
height > 0 || (hostedView?.bounds.width ?? 0) > 0,
|
|
82
|
+
lastReportedHeight.map({ abs($0 - height) > 0.5 }) ?? true
|
|
83
|
+
else { return }
|
|
84
|
+
lastReportedHeight = height
|
|
85
|
+
editorView?.atomHostContainer(self, didMeasureHeight: height)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// The top-level container view that a Fabric component would own.
|
|
90
|
+
///
|
|
91
|
+
/// Hosts the EditorTextView. In a full Fabric integration, this would be
|
|
92
|
+
/// a `RCTViewComponentView` subclass registered via the component descriptor.
|
|
93
|
+
///
|
|
94
|
+
/// For now, this is a plain UIView that can be used in a UIKit context
|
|
95
|
+
/// and serves as the integration point for the future Fabric component.
|
|
96
|
+
final class RichTextEditorView: UIView {
|
|
97
|
+
|
|
98
|
+
struct HostedLayoutTrace {
|
|
99
|
+
let intrinsicContentSizeNanos: UInt64
|
|
100
|
+
let intrinsicContentSizeCount: Int
|
|
101
|
+
let measuredEditorHeightNanos: UInt64
|
|
102
|
+
let measuredEditorHeightCount: Int
|
|
103
|
+
let layoutSubviewsNanos: UInt64
|
|
104
|
+
let layoutSubviewsCount: Int
|
|
105
|
+
let refreshOverlaysNanos: UInt64
|
|
106
|
+
let refreshOverlaysCount: Int
|
|
107
|
+
let overlayScheduleRequestCount: Int
|
|
108
|
+
let overlayScheduleExecuteCount: Int
|
|
109
|
+
let overlayScheduleSkipCount: Int
|
|
110
|
+
let onHeightMayChangeNanos: UInt64
|
|
111
|
+
let onHeightMayChangeCount: Int
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Properties
|
|
115
|
+
|
|
116
|
+
/// The editor text view that handles input interception.
|
|
117
|
+
let textView: EditorTextView
|
|
118
|
+
private let defaultImageLoadOwner = RenderImageLoadOwner(policy: .default)
|
|
119
|
+
var imageLoadOwner: RenderImageLoadOwner {
|
|
120
|
+
get { textView.imageLoadOwner ?? defaultImageLoadOwner }
|
|
121
|
+
set {
|
|
122
|
+
guard textView.imageLoadOwner !== newValue else { return }
|
|
123
|
+
textView.imageLoadOwner?.cancelAll()
|
|
124
|
+
textView.imageLoadOwner = newValue
|
|
125
|
+
textView.imageLoadingPolicyDidChange()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
private let remoteSelectionOverlayView = RemoteSelectionOverlayView()
|
|
129
|
+
private let taskListMarkerTapOverlayView = TaskListMarkerTapOverlayView()
|
|
130
|
+
private let imageTapOverlayView = ImageTapOverlayView()
|
|
131
|
+
private let imageResizeOverlayView = ImageResizeOverlayView()
|
|
132
|
+
var onHeightMayChange: ((CGFloat) -> Void)?
|
|
133
|
+
var onAtomContentWidthChange: ((CGFloat) -> Void)?
|
|
134
|
+
private var lastAutoGrowWidth: CGFloat = 0
|
|
135
|
+
private var cachedAutoGrowMeasuredHeight: CGFloat = 0
|
|
136
|
+
private var atomRenderConfiguration: AtomRenderConfiguration?
|
|
137
|
+
private var atomHostContainers: [String: AtomHostContainerView] = [:]
|
|
138
|
+
private var measuredAtomHeights: [String: CGFloat] = [:]
|
|
139
|
+
private var fallbackAtomHeights: [String: CGFloat] = [:]
|
|
140
|
+
private var lastAtomContentWidth: CGFloat = 0
|
|
141
|
+
private var lastAtomPositions: [[String: AnyHashable]] = []
|
|
142
|
+
private var lastAtomViewport = CGRect.zero
|
|
143
|
+
private(set) var atomLayoutInvalidationCountForTesting = 0
|
|
144
|
+
private var remoteSelections: [RemoteSelectionDecoration] = []
|
|
145
|
+
private var initialUpdateJSONForNextEditorBind: String?
|
|
146
|
+
private var overlayRefreshScheduled = false
|
|
147
|
+
var captureHostedLayoutTraceForTesting = false
|
|
148
|
+
private var hostedLayoutTraceNanos = (
|
|
149
|
+
intrinsicContentSize: UInt64(0),
|
|
150
|
+
measuredEditorHeight: UInt64(0),
|
|
151
|
+
layoutSubviews: UInt64(0),
|
|
152
|
+
refreshOverlays: UInt64(0),
|
|
153
|
+
onHeightMayChange: UInt64(0)
|
|
154
|
+
)
|
|
155
|
+
private var hostedLayoutTraceCounts = (
|
|
156
|
+
intrinsicContentSize: 0,
|
|
157
|
+
measuredEditorHeight: 0,
|
|
158
|
+
layoutSubviews: 0,
|
|
159
|
+
refreshOverlays: 0,
|
|
160
|
+
overlayScheduleRequest: 0,
|
|
161
|
+
overlayScheduleExecute: 0,
|
|
162
|
+
overlayScheduleSkip: 0,
|
|
163
|
+
onHeightMayChange: 0
|
|
164
|
+
)
|
|
165
|
+
var allowImageResizing = true {
|
|
166
|
+
didSet {
|
|
167
|
+
guard oldValue != allowImageResizing else { return }
|
|
168
|
+
textView.allowImageResizing = allowImageResizing
|
|
169
|
+
textView.refreshSelectionVisualState()
|
|
170
|
+
imageTapOverlayView.isHidden = editorId == 0 || !allowImageResizing
|
|
171
|
+
imageResizeOverlayView.refresh()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
var heightBehavior: EditorHeightBehavior = .fixed {
|
|
176
|
+
didSet {
|
|
177
|
+
guard oldValue != heightBehavior else { return }
|
|
178
|
+
textView.heightBehavior = heightBehavior
|
|
179
|
+
textView.updateAutoGrowHostHeight(heightBehavior == .autoGrow ? bounds.height : 0)
|
|
180
|
+
if heightBehavior != .autoGrow {
|
|
181
|
+
cachedAutoGrowMeasuredHeight = 0
|
|
182
|
+
}
|
|
183
|
+
invalidateIntrinsicContentSize()
|
|
184
|
+
setNeedsLayout()
|
|
185
|
+
if heightBehavior == .autoGrow {
|
|
186
|
+
let measuredHeight = measuredEditorHeight()
|
|
187
|
+
if measuredHeight > 0 {
|
|
188
|
+
cachedAutoGrowMeasuredHeight = measuredHeight
|
|
189
|
+
onHeightMayChange?(measuredHeight)
|
|
190
|
+
} else {
|
|
191
|
+
onHeightMayChange?(0)
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
onHeightMayChange?(0)
|
|
195
|
+
}
|
|
196
|
+
remoteSelectionOverlayView.refresh()
|
|
197
|
+
imageResizeOverlayView.refresh()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// The Rust editor instance ID. Setting this binds/unbinds the editor.
|
|
202
|
+
var editorId: UInt64 = 0 {
|
|
203
|
+
didSet {
|
|
204
|
+
guard oldValue != editorId else { return }
|
|
205
|
+
textView.discardTransientNativeInputForEditorRebind()
|
|
206
|
+
if editorId != 0 {
|
|
207
|
+
let initialUpdateJSON = initialUpdateJSONForNextEditorBind
|
|
208
|
+
initialUpdateJSONForNextEditorBind = nil
|
|
209
|
+
textView.bindEditor(id: editorId, initialUpdateJSON: initialUpdateJSON)
|
|
210
|
+
} else {
|
|
211
|
+
initialUpdateJSONForNextEditorBind = nil
|
|
212
|
+
textView.unbindEditor()
|
|
213
|
+
}
|
|
214
|
+
remoteSelectionOverlayView.update(
|
|
215
|
+
selections: remoteSelections,
|
|
216
|
+
editorId: editorId
|
|
217
|
+
)
|
|
218
|
+
imageTapOverlayView.isHidden = editorId == 0 || !allowImageResizing
|
|
219
|
+
imageResizeOverlayView.refresh()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func bindEditor(id: UInt64, initialUpdateJSON: String?) {
|
|
224
|
+
guard editorId != id else { return }
|
|
225
|
+
initialUpdateJSONForNextEditorBind = initialUpdateJSON
|
|
226
|
+
editorId = id
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// MARK: - Initialization
|
|
230
|
+
|
|
231
|
+
override init(frame: CGRect) {
|
|
232
|
+
textView = EditorTextView(frame: .zero, textContainer: nil)
|
|
233
|
+
super.init(frame: frame)
|
|
234
|
+
setupView()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
required init?(coder: NSCoder) {
|
|
238
|
+
textView = EditorTextView(frame: .zero, textContainer: nil)
|
|
239
|
+
super.init(coder: coder)
|
|
240
|
+
setupView()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private func setupView() {
|
|
244
|
+
// Add the text view as a subview. These views always track the host bounds,
|
|
245
|
+
// so manual layout is cheaper than driving them through Auto Layout.
|
|
246
|
+
textView.imageLoadOwner = defaultImageLoadOwner
|
|
247
|
+
remoteSelectionOverlayView.bind(textView: textView)
|
|
248
|
+
taskListMarkerTapOverlayView.bind(editorView: self)
|
|
249
|
+
imageTapOverlayView.bind(editorView: self)
|
|
250
|
+
imageResizeOverlayView.bind(editorView: self)
|
|
251
|
+
textView.allowImageResizing = allowImageResizing
|
|
252
|
+
imageTapOverlayView.isHidden = editorId == 0 || !allowImageResizing
|
|
253
|
+
textView.onHeightMayChange = { [weak self] measuredHeight in
|
|
254
|
+
guard let self, self.heightBehavior == .autoGrow else { return }
|
|
255
|
+
let startedAt = DispatchTime.now().uptimeNanoseconds
|
|
256
|
+
self.cachedAutoGrowMeasuredHeight = measuredHeight
|
|
257
|
+
self.invalidateIntrinsicContentSize()
|
|
258
|
+
self.onHeightMayChange?(measuredHeight)
|
|
259
|
+
self.recordHostedLayoutTrace(
|
|
260
|
+
durationNanos: DispatchTime.now().uptimeNanoseconds - startedAt,
|
|
261
|
+
keyPath: .onHeightMayChange
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
textView.onViewportMayChange = { [weak self] in
|
|
265
|
+
self?.layoutManagedSubviews()
|
|
266
|
+
self?.refreshOverlaysIfNeeded()
|
|
267
|
+
self?.emitAtomContentWidthIfAvailable()
|
|
268
|
+
}
|
|
269
|
+
textView.onSelectionOrContentMayChange = { [weak self] in
|
|
270
|
+
self?.scheduleRefreshOverlaysIfNeeded()
|
|
271
|
+
}
|
|
272
|
+
addSubview(textView)
|
|
273
|
+
addSubview(remoteSelectionOverlayView)
|
|
274
|
+
addSubview(taskListMarkerTapOverlayView)
|
|
275
|
+
// Image touches must stay inside the scroll view's gesture hierarchy.
|
|
276
|
+
textView.addSubview(imageTapOverlayView)
|
|
277
|
+
addSubview(imageResizeOverlayView)
|
|
278
|
+
layoutManagedSubviews()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
override var intrinsicContentSize: CGSize {
|
|
282
|
+
let startedAt = DispatchTime.now().uptimeNanoseconds
|
|
283
|
+
defer {
|
|
284
|
+
recordHostedLayoutTrace(
|
|
285
|
+
durationNanos: DispatchTime.now().uptimeNanoseconds - startedAt,
|
|
286
|
+
keyPath: .intrinsicContentSize
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
guard heightBehavior == .autoGrow else {
|
|
290
|
+
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let measuredHeight = measuredEditorHeight()
|
|
294
|
+
guard measuredHeight > 0 else {
|
|
295
|
+
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
|
296
|
+
}
|
|
297
|
+
return CGSize(width: UIView.noIntrinsicMetric, height: measuredHeight)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
override func layoutSubviews() {
|
|
301
|
+
let startedAt = DispatchTime.now().uptimeNanoseconds
|
|
302
|
+
defer {
|
|
303
|
+
recordHostedLayoutTrace(
|
|
304
|
+
durationNanos: DispatchTime.now().uptimeNanoseconds - startedAt,
|
|
305
|
+
keyPath: .layoutSubviews
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
super.layoutSubviews()
|
|
309
|
+
updateStyleContentMask()
|
|
310
|
+
layoutManagedSubviews()
|
|
311
|
+
layoutAtomHostContainers()
|
|
312
|
+
refreshOverlaysIfNeeded()
|
|
313
|
+
guard heightBehavior == .autoGrow else { return }
|
|
314
|
+
textView.updateAutoGrowHostHeight(bounds.height)
|
|
315
|
+
let currentWidth = bounds.width.rounded(.towardZero)
|
|
316
|
+
guard currentWidth != lastAutoGrowWidth else { return }
|
|
317
|
+
lastAutoGrowWidth = currentWidth
|
|
318
|
+
cachedAutoGrowMeasuredHeight = 0
|
|
319
|
+
invalidateIntrinsicContentSize()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// MARK: - Configuration
|
|
323
|
+
|
|
324
|
+
/// Configure the editor's appearance.
|
|
325
|
+
///
|
|
326
|
+
/// - Parameters:
|
|
327
|
+
/// - font: Base font for unstyled text.
|
|
328
|
+
/// - textColor: Default text color.
|
|
329
|
+
/// - backgroundColor: Background color for the text view.
|
|
330
|
+
func configure(
|
|
331
|
+
font: UIFont = .systemFont(ofSize: 16),
|
|
332
|
+
textColor: UIColor = .label,
|
|
333
|
+
backgroundColor: UIColor = .systemBackground
|
|
334
|
+
) {
|
|
335
|
+
textView.baseFont = font
|
|
336
|
+
textView.baseTextColor = textColor
|
|
337
|
+
textView.baseBackgroundColor = backgroundColor
|
|
338
|
+
textView.font = font
|
|
339
|
+
textView.textColor = textColor
|
|
340
|
+
textView.backgroundColor = backgroundColor
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@discardableResult
|
|
344
|
+
func applyTheme(_ theme: EditorTheme?) -> Bool {
|
|
345
|
+
guard textView.applyTheme(theme) else { return false }
|
|
346
|
+
let cornerRadius = theme?.borderRadius ?? 0
|
|
347
|
+
layer.cornerRadius = cornerRadius
|
|
348
|
+
clipsToBounds = cornerRadius > 0
|
|
349
|
+
updateStyleContentMask()
|
|
350
|
+
refreshOverlays()
|
|
351
|
+
return true
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private func updateStyleContentMask() {
|
|
355
|
+
guard let box = textView.theme?.styleSheet?.box("content"), box.radii.contains(where: { $0 > 0 }) else {
|
|
356
|
+
layer.mask = nil
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
let mask = (layer.mask as? CAShapeLayer) ?? CAShapeLayer()
|
|
360
|
+
mask.path = box.path(in: bounds).cgPath
|
|
361
|
+
layer.mask = mask
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@discardableResult
|
|
365
|
+
func applyAtomRenderConfiguration(_ configuration: AtomRenderConfiguration?) -> Bool {
|
|
366
|
+
let previousConfiguration = atomRenderConfiguration
|
|
367
|
+
atomRenderConfiguration = configuration
|
|
368
|
+
guard textView.applyAtomRenderConfiguration(resolvedAtomRenderConfiguration()) else {
|
|
369
|
+
atomRenderConfiguration = previousConfiguration
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
layoutAtomHostContainers()
|
|
373
|
+
refreshOverlays()
|
|
374
|
+
return true
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func mountAtomChild(_ child: UIView, atomKey: String) {
|
|
378
|
+
if let existing = atomHostContainers[atomKey] {
|
|
379
|
+
if existing.hostedView === child {
|
|
380
|
+
if child.superview !== existing {
|
|
381
|
+
existing.host(child)
|
|
382
|
+
}
|
|
383
|
+
layoutAtomHostContainers()
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
_ = existing.detachHostedView()
|
|
387
|
+
existing.removeFromSuperview()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let container = AtomHostContainerView(atomKey: atomKey)
|
|
391
|
+
container.editorView = self
|
|
392
|
+
atomHostContainers[atomKey] = container
|
|
393
|
+
textView.addSubview(container)
|
|
394
|
+
container.host(child)
|
|
395
|
+
layoutAtomHostContainers()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@discardableResult
|
|
399
|
+
func unmountAtomChild(_ child: UIView) -> Bool {
|
|
400
|
+
guard let entry = atomHostContainers.first(where: { $0.value.hostedView === child }) else {
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
removeAtomHostContainer(for: entry.key, removeChild: true)
|
|
404
|
+
return true
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
func atomHostContainer(for atomKey: String) -> AtomHostContainerView? {
|
|
408
|
+
atomHostContainers[atomKey]
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
func measuredAtomHeight(for atomKey: String) -> CGFloat? {
|
|
412
|
+
measuredAtomHeights[atomKey]
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
func emitAtomContentWidthIfAvailable(force: Bool = false) {
|
|
416
|
+
let width = atomContentWidth()
|
|
417
|
+
let positions = atomLayoutPositions()
|
|
418
|
+
let viewport = textView.bounds
|
|
419
|
+
guard width > 0,
|
|
420
|
+
force || abs(width - lastAtomContentWidth) > 0.5
|
|
421
|
+
|| positions != lastAtomPositions || viewport != lastAtomViewport
|
|
422
|
+
else { return }
|
|
423
|
+
lastAtomContentWidth = width
|
|
424
|
+
lastAtomPositions = positions
|
|
425
|
+
lastAtomViewport = viewport
|
|
426
|
+
onAtomContentWidthChange?(width)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
func atomLayoutPositions() -> [[String: AnyHashable]] {
|
|
430
|
+
let padding = textView.textContainer.lineFragmentPadding
|
|
431
|
+
return atomAttachmentEntries().sorted { $0.value.1.location < $1.value.1.location }.map { key, entry in
|
|
432
|
+
let (attachment, range) = entry
|
|
433
|
+
textView.layoutManager.ensureLayout(forCharacterRange: range)
|
|
434
|
+
let glyphs = textView.layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
|
|
435
|
+
let rect = textView.layoutManager.boundingRect(forGlyphRange: glyphs, in: textView.textContainer)
|
|
436
|
+
return [
|
|
437
|
+
"key": key,
|
|
438
|
+
"x": Double(textView.textContainerInset.left + padding),
|
|
439
|
+
"y": Double(textView.textContainerInset.top + rect.minY),
|
|
440
|
+
"height": Double(attachment.reservedHeight)
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
fileprivate func atomHostContainer(
|
|
446
|
+
_ container: AtomHostContainerView,
|
|
447
|
+
didMeasureHeight height: CGFloat
|
|
448
|
+
) {
|
|
449
|
+
setAtomHeight(key: container.atomKey, height: height)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fileprivate func atomHostContainerDidLoseHostedView(_ container: AtomHostContainerView) {
|
|
453
|
+
guard atomHostContainers[container.atomKey] === container else { return }
|
|
454
|
+
removeAtomHostContainer(for: container.atomKey, removeChild: false)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
func setAtomHeight(key atomKey: String, height: CGFloat) {
|
|
458
|
+
let previousMeasuredHeight = measuredAtomHeights[atomKey]
|
|
459
|
+
guard height.isFinite,
|
|
460
|
+
height >= 0,
|
|
461
|
+
measuredAtomHeights[atomKey].map({ abs($0 - height) > 0.5 }) ?? true
|
|
462
|
+
else { return }
|
|
463
|
+
|
|
464
|
+
measuredAtomHeights[atomKey] = height
|
|
465
|
+
textView.atomRenderConfiguration = resolvedAtomRenderConfiguration()
|
|
466
|
+
guard let entry = atomAttachmentEntry(for: atomKey) else { return }
|
|
467
|
+
let fallbackHeight = atomRenderConfiguration?.estimatedHeights[entry.attachment.nodeType]
|
|
468
|
+
?? (previousMeasuredHeight == nil && entry.attachment.reservedHeight > 0
|
|
469
|
+
? entry.attachment.reservedHeight
|
|
470
|
+
: nil)
|
|
471
|
+
if let fallbackHeight {
|
|
472
|
+
fallbackAtomHeights[atomKey] = fallbackAtomHeights[atomKey] ?? fallbackHeight
|
|
473
|
+
}
|
|
474
|
+
updateAtomAttachment(entry, height: height)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private func removeAtomHostContainer(for atomKey: String, removeChild: Bool) {
|
|
478
|
+
guard let container = atomHostContainers.removeValue(forKey: atomKey) else { return }
|
|
479
|
+
container.editorView = nil
|
|
480
|
+
let child = container.detachHostedView()
|
|
481
|
+
container.removeFromSuperview()
|
|
482
|
+
if removeChild {
|
|
483
|
+
child?.removeFromSuperview()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
measuredAtomHeights.removeValue(forKey: atomKey)
|
|
487
|
+
textView.atomRenderConfiguration = resolvedAtomRenderConfiguration()
|
|
488
|
+
if let entry = atomAttachmentEntry(for: atomKey) {
|
|
489
|
+
let fallbackHeight = fallbackAtomHeights.removeValue(forKey: atomKey)
|
|
490
|
+
?? atomRenderConfiguration?.estimatedHeights[entry.attachment.nodeType]
|
|
491
|
+
if let fallbackHeight {
|
|
492
|
+
updateAtomAttachment(entry, height: fallbackHeight)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private func resolvedAtomRenderConfiguration() -> AtomRenderConfiguration? {
|
|
498
|
+
guard let atomRenderConfiguration else { return nil }
|
|
499
|
+
var heights = atomRenderConfiguration.measuredHeights
|
|
500
|
+
heights.merge(measuredAtomHeights) { _, measured in measured }
|
|
501
|
+
return AtomRenderConfiguration(
|
|
502
|
+
registeredNodeTypes: atomRenderConfiguration.registeredNodeTypes,
|
|
503
|
+
estimatedHeights: atomRenderConfiguration.estimatedHeights,
|
|
504
|
+
measuredHeights: heights
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private func updateAtomAttachment(
|
|
509
|
+
_ entry: (attachment: AtomBlockAttachment, range: NSRange),
|
|
510
|
+
height: CGFloat
|
|
511
|
+
) {
|
|
512
|
+
guard abs(entry.attachment.reservedHeight - height) > 0.5 else { return }
|
|
513
|
+
entry.attachment.reservedHeight = height
|
|
514
|
+
atomLayoutInvalidationCountForTesting += 1
|
|
515
|
+
textView.layoutManager.invalidateLayout(
|
|
516
|
+
forCharacterRange: entry.range,
|
|
517
|
+
actualCharacterRange: nil
|
|
518
|
+
)
|
|
519
|
+
textView.layoutManager.invalidateDisplay(forCharacterRange: entry.range)
|
|
520
|
+
textView.setNeedsLayout()
|
|
521
|
+
setNeedsLayout()
|
|
522
|
+
layoutAtomHostContainers()
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private func atomAttachmentEntry(
|
|
526
|
+
for atomKey: String
|
|
527
|
+
) -> (attachment: AtomBlockAttachment, range: NSRange)? {
|
|
528
|
+
guard textView.textStorage.length > 0 else { return nil }
|
|
529
|
+
var match: (attachment: AtomBlockAttachment, range: NSRange)?
|
|
530
|
+
textView.textStorage.enumerateAttribute(
|
|
531
|
+
.attachment,
|
|
532
|
+
in: NSRange(location: 0, length: textView.textStorage.length),
|
|
533
|
+
options: [.longestEffectiveRangeNotRequired]
|
|
534
|
+
) { value, range, stop in
|
|
535
|
+
guard let attachment = value as? AtomBlockAttachment,
|
|
536
|
+
attachment.atomKey == atomKey
|
|
537
|
+
else { return }
|
|
538
|
+
match = (attachment, range)
|
|
539
|
+
stop.pointee = true
|
|
540
|
+
}
|
|
541
|
+
return match
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private func atomAttachmentEntries() -> [String: (AtomBlockAttachment, NSRange)] {
|
|
545
|
+
guard textView.textStorage.length > 0 else { return [:] }
|
|
546
|
+
var entries: [String: (AtomBlockAttachment, NSRange)] = [:]
|
|
547
|
+
textView.textStorage.enumerateAttribute(
|
|
548
|
+
.attachment,
|
|
549
|
+
in: NSRange(location: 0, length: textView.textStorage.length),
|
|
550
|
+
options: [.longestEffectiveRangeNotRequired]
|
|
551
|
+
) { value, range, _ in
|
|
552
|
+
guard let attachment = value as? AtomBlockAttachment else { return }
|
|
553
|
+
entries[attachment.atomKey] = (attachment, range)
|
|
554
|
+
}
|
|
555
|
+
return entries
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private func atomContentWidth() -> CGFloat {
|
|
559
|
+
let padding = textView.textContainer.lineFragmentPadding
|
|
560
|
+
return max(0, textView.textContainer.size.width - (padding * 2))
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private func layoutAtomHostContainers() {
|
|
564
|
+
emitAtomContentWidthIfAvailable()
|
|
565
|
+
guard !atomHostContainers.isEmpty else { return }
|
|
566
|
+
let entries = atomAttachmentEntries()
|
|
567
|
+
let padding = textView.textContainer.lineFragmentPadding
|
|
568
|
+
let width = atomContentWidth()
|
|
569
|
+
|
|
570
|
+
for (atomKey, container) in atomHostContainers {
|
|
571
|
+
guard let (attachment, characterRange) = entries[atomKey] else {
|
|
572
|
+
container.isHidden = true
|
|
573
|
+
continue
|
|
574
|
+
}
|
|
575
|
+
textView.layoutManager.ensureLayout(forCharacterRange: characterRange)
|
|
576
|
+
let glyphRange = textView.layoutManager.glyphRange(
|
|
577
|
+
forCharacterRange: characterRange,
|
|
578
|
+
actualCharacterRange: nil
|
|
579
|
+
)
|
|
580
|
+
let attachmentRect = textView.layoutManager.boundingRect(
|
|
581
|
+
forGlyphRange: glyphRange,
|
|
582
|
+
in: textView.textContainer
|
|
583
|
+
)
|
|
584
|
+
let frame = CGRect(
|
|
585
|
+
x: textView.textContainerInset.left + padding,
|
|
586
|
+
y: textView.textContainerInset.top + attachmentRect.minY,
|
|
587
|
+
width: width,
|
|
588
|
+
height: attachment.reservedHeight
|
|
589
|
+
)
|
|
590
|
+
if container.frame != frame {
|
|
591
|
+
container.frame = frame
|
|
592
|
+
container.setNeedsLayout()
|
|
593
|
+
}
|
|
594
|
+
textView.bringSubviewToFront(container)
|
|
595
|
+
container.isHidden = false
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
func setRemoteSelections(_ selections: [RemoteSelectionDecoration]) {
|
|
600
|
+
remoteSelections = selections
|
|
601
|
+
remoteSelectionOverlayView.update(
|
|
602
|
+
selections: selections,
|
|
603
|
+
editorId: editorId
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
func refreshRemoteSelections() {
|
|
608
|
+
guard remoteSelectionOverlayView.hasSelectionsOrVisibleDecorations else { return }
|
|
609
|
+
remoteSelectionOverlayView.refresh()
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
func currentCaretRect() -> CGRect? {
|
|
613
|
+
guard let selectedTextRange = textView.selectedTextRange else { return nil }
|
|
614
|
+
let rect = textView.caretRect(for: selectedTextRange.end)
|
|
615
|
+
guard rect.height > 0 else { return nil }
|
|
616
|
+
return textView.convert(rect, to: self)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
func remoteSelectionOverlaySubviewsForTesting() -> [UIView] {
|
|
620
|
+
remoteSelectionOverlayView.subviews.filter { !$0.isHidden }
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
func resetHostedLayoutTraceForTesting() {
|
|
624
|
+
hostedLayoutTraceNanos = (
|
|
625
|
+
intrinsicContentSize: 0,
|
|
626
|
+
measuredEditorHeight: 0,
|
|
627
|
+
layoutSubviews: 0,
|
|
628
|
+
refreshOverlays: 0,
|
|
629
|
+
onHeightMayChange: 0
|
|
630
|
+
)
|
|
631
|
+
hostedLayoutTraceCounts = (
|
|
632
|
+
intrinsicContentSize: 0,
|
|
633
|
+
measuredEditorHeight: 0,
|
|
634
|
+
layoutSubviews: 0,
|
|
635
|
+
refreshOverlays: 0,
|
|
636
|
+
overlayScheduleRequest: 0,
|
|
637
|
+
overlayScheduleExecute: 0,
|
|
638
|
+
overlayScheduleSkip: 0,
|
|
639
|
+
onHeightMayChange: 0
|
|
640
|
+
)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
func lastHostedLayoutTraceForTesting() -> HostedLayoutTrace {
|
|
644
|
+
HostedLayoutTrace(
|
|
645
|
+
intrinsicContentSizeNanos: hostedLayoutTraceNanos.intrinsicContentSize,
|
|
646
|
+
intrinsicContentSizeCount: hostedLayoutTraceCounts.intrinsicContentSize,
|
|
647
|
+
measuredEditorHeightNanos: hostedLayoutTraceNanos.measuredEditorHeight,
|
|
648
|
+
measuredEditorHeightCount: hostedLayoutTraceCounts.measuredEditorHeight,
|
|
649
|
+
layoutSubviewsNanos: hostedLayoutTraceNanos.layoutSubviews,
|
|
650
|
+
layoutSubviewsCount: hostedLayoutTraceCounts.layoutSubviews,
|
|
651
|
+
refreshOverlaysNanos: hostedLayoutTraceNanos.refreshOverlays,
|
|
652
|
+
refreshOverlaysCount: hostedLayoutTraceCounts.refreshOverlays,
|
|
653
|
+
overlayScheduleRequestCount: hostedLayoutTraceCounts.overlayScheduleRequest,
|
|
654
|
+
overlayScheduleExecuteCount: hostedLayoutTraceCounts.overlayScheduleExecute,
|
|
655
|
+
overlayScheduleSkipCount: hostedLayoutTraceCounts.overlayScheduleSkip,
|
|
656
|
+
onHeightMayChangeNanos: hostedLayoutTraceNanos.onHeightMayChange,
|
|
657
|
+
onHeightMayChangeCount: hostedLayoutTraceCounts.onHeightMayChange
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
func imageResizeOverlayRectForTesting() -> CGRect? {
|
|
662
|
+
imageResizeOverlayView.visibleRectForTesting
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
func imageTapOverlayInterceptsPointForTesting(_ point: CGPoint) -> Bool {
|
|
666
|
+
imageTapOverlayView.interceptsPointForTesting(convert(point, to: imageTapOverlayView))
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
func taskListMarkerTapOverlayInterceptsPointForTesting(_ point: CGPoint) -> Bool {
|
|
670
|
+
taskListMarkerTapOverlayView.interceptsPointForTesting(
|
|
671
|
+
convert(point, to: taskListMarkerTapOverlayView)
|
|
672
|
+
)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
@discardableResult
|
|
676
|
+
func tapTaskListMarkerOverlayForTesting(at point: CGPoint) -> Bool {
|
|
677
|
+
taskListMarkerTapOverlayView.handleTapForTesting(
|
|
678
|
+
convert(point, to: taskListMarkerTapOverlayView)
|
|
679
|
+
)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@discardableResult
|
|
683
|
+
func tapImageOverlayForTesting(at point: CGPoint) -> Bool {
|
|
684
|
+
imageTapOverlayView.handleTapForTesting(convert(point, to: imageTapOverlayView))
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
func imageResizePreviewHasImageForTesting() -> Bool {
|
|
688
|
+
imageResizeOverlayView.previewHasImageForTesting
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
func refreshSelectionVisualStateForTesting() {
|
|
692
|
+
textView.refreshSelectionVisualState()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
func imageResizeOverlayInterceptsPointForTesting(_ point: CGPoint) -> Bool {
|
|
696
|
+
imageResizeOverlayView.interceptsPointForTesting(convert(point, to: imageResizeOverlayView))
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
func maximumImageWidthForTesting() -> CGFloat {
|
|
700
|
+
textView.maximumRenderableImageWidth()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
func resizeSelectedImageForTesting(width: CGFloat, height: CGFloat) {
|
|
704
|
+
imageResizeOverlayView.simulateResizeForTesting(width: width, height: height)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
func previewResizeSelectedImageForTesting(width: CGFloat, height: CGFloat) {
|
|
708
|
+
imageResizeOverlayView.simulatePreviewResizeForTesting(width: width, height: height)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
func commitPreviewResizeForTesting() {
|
|
712
|
+
imageResizeOverlayView.commitPreviewResizeForTesting()
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/// Set initial content from HTML.
|
|
716
|
+
///
|
|
717
|
+
/// - Parameter html: The HTML string to load.
|
|
718
|
+
func setContent(html: String) {
|
|
719
|
+
guard editorId != 0 else { return }
|
|
720
|
+
let updateJSON = EditorV2Shadow.setHtml(id: editorId, html: html)
|
|
721
|
+
if !textView.applyUpdateJSON(updateJSON, notifyDelegate: false) {
|
|
722
|
+
textView.applyUpdateJSON(EditorV2Shadow.getCurrentState(id: editorId), notifyDelegate: false)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/// Set initial content from ProseMirror JSON.
|
|
727
|
+
///
|
|
728
|
+
/// - Parameter json: The JSON string to load.
|
|
729
|
+
func setContent(json: String) {
|
|
730
|
+
guard editorId != 0 else { return }
|
|
731
|
+
let updateJSON = EditorV2Shadow.setJson(id: editorId, json: json)
|
|
732
|
+
if !textView.applyUpdateJSON(updateJSON, notifyDelegate: false) {
|
|
733
|
+
textView.applyUpdateJSON(EditorV2Shadow.getCurrentState(id: editorId), notifyDelegate: false)
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private func measuredEditorHeight() -> CGFloat {
|
|
738
|
+
let startedAt = DispatchTime.now().uptimeNanoseconds
|
|
739
|
+
defer {
|
|
740
|
+
recordHostedLayoutTrace(
|
|
741
|
+
durationNanos: DispatchTime.now().uptimeNanoseconds - startedAt,
|
|
742
|
+
keyPath: .measuredEditorHeight
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
if cachedAutoGrowMeasuredHeight > 0 {
|
|
746
|
+
return cachedAutoGrowMeasuredHeight
|
|
747
|
+
}
|
|
748
|
+
let width = resolvedMeasurementWidth()
|
|
749
|
+
guard width > 0 else { return 0 }
|
|
750
|
+
let measuredHeight = textView.measuredAutoGrowHeightForTesting(width: width)
|
|
751
|
+
if measuredHeight > 0 {
|
|
752
|
+
cachedAutoGrowMeasuredHeight = measuredHeight
|
|
753
|
+
}
|
|
754
|
+
return measuredHeight
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
func remeasureAutoGrowHeight() -> CGFloat {
|
|
758
|
+
guard heightBehavior == .autoGrow else { return 0 }
|
|
759
|
+
cachedAutoGrowMeasuredHeight = 0
|
|
760
|
+
let measuredHeight = measuredEditorHeight()
|
|
761
|
+
invalidateIntrinsicContentSize()
|
|
762
|
+
return measuredHeight
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private func resolvedMeasurementWidth() -> CGFloat {
|
|
766
|
+
if bounds.width > 0 {
|
|
767
|
+
return bounds.width
|
|
768
|
+
}
|
|
769
|
+
if superview?.bounds.width ?? 0 > 0 {
|
|
770
|
+
return superview?.bounds.width ?? 0
|
|
771
|
+
}
|
|
772
|
+
return UIScreen.main.bounds.width
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private func layoutManagedSubviews() {
|
|
776
|
+
let managedFrame = bounds
|
|
777
|
+
if textView.frame != managedFrame {
|
|
778
|
+
textView.frame = managedFrame
|
|
779
|
+
}
|
|
780
|
+
if remoteSelectionOverlayView.frame != managedFrame {
|
|
781
|
+
remoteSelectionOverlayView.frame = managedFrame
|
|
782
|
+
}
|
|
783
|
+
if taskListMarkerTapOverlayView.frame != managedFrame {
|
|
784
|
+
taskListMarkerTapOverlayView.frame = managedFrame
|
|
785
|
+
}
|
|
786
|
+
if imageTapOverlayView.frame != textView.bounds {
|
|
787
|
+
imageTapOverlayView.frame = textView.bounds
|
|
788
|
+
}
|
|
789
|
+
if imageResizeOverlayView.frame != managedFrame {
|
|
790
|
+
imageResizeOverlayView.frame = managedFrame
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
func selectedImageGeometry() -> (docPos: UInt32, rect: CGRect)? {
|
|
795
|
+
guard let geometry = textView.selectedImageGeometry() else { return nil }
|
|
796
|
+
return (
|
|
797
|
+
docPos: geometry.docPos,
|
|
798
|
+
rect: textView.convert(geometry.rect, to: imageResizeOverlayView)
|
|
799
|
+
)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
func setImageResizePreviewActive(_ active: Bool) {
|
|
803
|
+
textView.setImageResizePreviewActive(active)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
func imagePreviewForResize(docPos: UInt32) -> UIImage? {
|
|
807
|
+
textView.imagePreviewForDocPos(docPos)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
func imageResizePreviewBackgroundColor() -> UIColor {
|
|
811
|
+
textView.backgroundColor ?? .systemBackground
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
func maximumImageWidthForResizeGesture() -> CGFloat {
|
|
815
|
+
textView.maximumRenderableImageWidth()
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
func clampedImageSize(_ size: CGSize, maximumWidth: CGFloat? = nil) -> CGSize {
|
|
819
|
+
let aspectRatio = max(size.width / max(size.height, 1), 0.1)
|
|
820
|
+
let maxWidth = max(48, maximumWidth ?? textView.maximumRenderableImageWidth())
|
|
821
|
+
let clampedWidth = min(maxWidth, max(48, size.width))
|
|
822
|
+
let clampedHeight = max(48, clampedWidth / aspectRatio)
|
|
823
|
+
return CGSize(width: clampedWidth, height: clampedHeight)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
func resizeImage(docPos: UInt32, size: CGSize) {
|
|
827
|
+
let clampedSize = clampedImageSize(size)
|
|
828
|
+
let width = max(48, Int(clampedSize.width.rounded()))
|
|
829
|
+
let height = max(48, Int(clampedSize.height.rounded()))
|
|
830
|
+
textView.resizeImageAtDocPos(docPos, width: UInt32(width), height: UInt32(height))
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private func refreshOverlays() {
|
|
834
|
+
let startedAt = DispatchTime.now().uptimeNanoseconds
|
|
835
|
+
defer {
|
|
836
|
+
recordHostedLayoutTrace(
|
|
837
|
+
durationNanos: DispatchTime.now().uptimeNanoseconds - startedAt,
|
|
838
|
+
keyPath: .refreshOverlays
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
layoutAtomHostContainers()
|
|
842
|
+
remoteSelectionOverlayView.refresh()
|
|
843
|
+
imageResizeOverlayView.refresh()
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private func refreshOverlaysIfNeeded() {
|
|
847
|
+
guard shouldRefreshOverlays() else { return }
|
|
848
|
+
refreshOverlays()
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
private func scheduleRefreshOverlaysIfNeeded() {
|
|
852
|
+
if !shouldRefreshOverlays() {
|
|
853
|
+
if captureHostedLayoutTraceForTesting {
|
|
854
|
+
hostedLayoutTraceCounts.overlayScheduleSkip += 1
|
|
855
|
+
}
|
|
856
|
+
return
|
|
857
|
+
}
|
|
858
|
+
scheduleRefreshOverlays()
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
private func scheduleRefreshOverlays() {
|
|
862
|
+
if captureHostedLayoutTraceForTesting {
|
|
863
|
+
hostedLayoutTraceCounts.overlayScheduleRequest += 1
|
|
864
|
+
}
|
|
865
|
+
guard !overlayRefreshScheduled else { return }
|
|
866
|
+
overlayRefreshScheduled = true
|
|
867
|
+
DispatchQueue.main.async { [weak self] in
|
|
868
|
+
guard let self else { return }
|
|
869
|
+
self.overlayRefreshScheduled = false
|
|
870
|
+
if self.captureHostedLayoutTraceForTesting {
|
|
871
|
+
self.hostedLayoutTraceCounts.overlayScheduleExecute += 1
|
|
872
|
+
}
|
|
873
|
+
self.refreshOverlays()
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private func shouldRefreshOverlays() -> Bool {
|
|
878
|
+
if !atomHostContainers.isEmpty {
|
|
879
|
+
return true
|
|
880
|
+
}
|
|
881
|
+
if !remoteSelections.isEmpty || remoteSelectionOverlayView.hasVisibleDecorations {
|
|
882
|
+
return true
|
|
883
|
+
}
|
|
884
|
+
if imageResizeOverlayView.isOverlayVisible {
|
|
885
|
+
return true
|
|
886
|
+
}
|
|
887
|
+
if textView.selectedImageGeometry() != nil {
|
|
888
|
+
return true
|
|
889
|
+
}
|
|
890
|
+
return false
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private enum HostedLayoutTraceKey {
|
|
894
|
+
case intrinsicContentSize
|
|
895
|
+
case measuredEditorHeight
|
|
896
|
+
case layoutSubviews
|
|
897
|
+
case refreshOverlays
|
|
898
|
+
case onHeightMayChange
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
private func recordHostedLayoutTrace(durationNanos: UInt64, keyPath: HostedLayoutTraceKey) {
|
|
902
|
+
guard captureHostedLayoutTraceForTesting else { return }
|
|
903
|
+
switch keyPath {
|
|
904
|
+
case .intrinsicContentSize:
|
|
905
|
+
hostedLayoutTraceNanos.intrinsicContentSize += durationNanos
|
|
906
|
+
hostedLayoutTraceCounts.intrinsicContentSize += 1
|
|
907
|
+
case .measuredEditorHeight:
|
|
908
|
+
hostedLayoutTraceNanos.measuredEditorHeight += durationNanos
|
|
909
|
+
hostedLayoutTraceCounts.measuredEditorHeight += 1
|
|
910
|
+
case .layoutSubviews:
|
|
911
|
+
hostedLayoutTraceNanos.layoutSubviews += durationNanos
|
|
912
|
+
hostedLayoutTraceCounts.layoutSubviews += 1
|
|
913
|
+
case .refreshOverlays:
|
|
914
|
+
hostedLayoutTraceNanos.refreshOverlays += durationNanos
|
|
915
|
+
hostedLayoutTraceCounts.refreshOverlays += 1
|
|
916
|
+
case .onHeightMayChange:
|
|
917
|
+
hostedLayoutTraceNanos.onHeightMayChange += durationNanos
|
|
918
|
+
hostedLayoutTraceCounts.onHeightMayChange += 1
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// MARK: - Cleanup
|
|
923
|
+
|
|
924
|
+
deinit {
|
|
925
|
+
textView.imageLoadOwner?.cancelAll()
|
|
926
|
+
if editorId != 0 {
|
|
927
|
+
textView.unbindEditor()
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|