@openeditor/react-native-prose-editor 0.0.6 → 0.0.8
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/android/build.gradle +35 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +2 -2
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +138 -597
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +13 -3
- package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +1086 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1 -1
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +26 -50
- package/android/src/main/java/com/apollohg/editor/NativeImageLoader.kt +89 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +31 -66
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +98 -33
- package/dist/EditorToolbar.d.ts +1 -1
- package/dist/EditorToolbar.js +19 -0
- package/dist/NativeEditorBridge.d.ts +62 -35
- package/dist/NativeEditorBridge.js +122 -79
- package/dist/NativeProseViewer.d.ts +13 -34
- package/dist/NativeProseViewer.js +40 -465
- package/dist/NativeRichTextEditor.d.ts +10 -1
- package/dist/NativeRichTextEditor.js +72 -34
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/schemas.d.ts +11 -0
- package/dist/schemas.js +149 -0
- package/dist/useNativeEditor.d.ts +3 -3
- package/dist/useNativeEditor.js +3 -3
- 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/Generated_editor_core.swift +97 -2
- package/ios/NativeBlockEditorSurface.swift +1393 -0
- package/ios/NativeEditorExpoView.swift +3 -3
- package/ios/NativeEditorModule.swift +30 -45
- package/ios/NativeInputSupport.swift +396 -0
- package/ios/PositionBridge.swift +4 -4
- package/ios/RichTextEditorView.swift +333 -786
- package/ios/editor_coreFFI/editor_coreFFI.h +147 -70
- package/package.json +1 -1
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/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 +2087 -1399
- package/android/src/androidTest/AndroidManifest.xml +0 -8
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +0 -53
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +0 -241
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +0 -338
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +0 -789
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +0 -350
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +0 -5
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +0 -315
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +0 -2172
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +0 -482
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +0 -137
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +0 -321
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +0 -4202
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +0 -2581
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +0 -27
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +0 -197
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +0 -84
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +0 -544
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +0 -461
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +0 -2054
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +0 -1367
- package/ios/EditorLayoutManager.swift +0 -774
- package/ios/NativeProseViewerExpoView.swift +0 -276
- package/ios/RenderBridge.swift +0 -1708
- package/ios/Tests/NativePerformanceTests.swift +0 -687
- package/ios/Tests/PositionBridgeTests.swift +0 -706
- package/ios/Tests/RenderBridgeTests.swift +0 -2236
- package/ios/Tests/RichTextEditorViewTests.swift +0 -6548
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
package com.openeditor.editor
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertEquals
|
|
4
|
-
import org.junit.Assert.assertNull
|
|
5
|
-
import org.junit.Test
|
|
6
|
-
|
|
7
|
-
class NativeEditorModuleTest {
|
|
8
|
-
@Test
|
|
9
|
-
fun `native unsigned argument helpers reject negative values`() {
|
|
10
|
-
assertNull(nativeULong(-1))
|
|
11
|
-
assertNull(nativeUInt(-1))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
@Test
|
|
15
|
-
fun `native unsigned argument helpers keep non-negative values`() {
|
|
16
|
-
assertEquals(0UL, nativeULong(0))
|
|
17
|
-
assertEquals(42UL, nativeULong(42))
|
|
18
|
-
assertEquals(0U, nativeUInt(0))
|
|
19
|
-
assertEquals(42U, nativeUInt(42))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@Test
|
|
23
|
-
fun `native argument error returns bridge parseable json`() {
|
|
24
|
-
assertEquals("{\"error\":\"invalid editor id\"}", nativeArgumentError("editor id"))
|
|
25
|
-
assertEquals("{\"error\":\"invalid position\"}", nativeArgumentError("position"))
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
package com.openeditor.editor
|
|
2
|
-
|
|
3
|
-
import android.graphics.Bitmap
|
|
4
|
-
import android.graphics.Canvas
|
|
5
|
-
import android.graphics.Color
|
|
6
|
-
import android.view.View
|
|
7
|
-
import android.view.ViewGroup
|
|
8
|
-
import org.junit.Assert.assertEquals
|
|
9
|
-
import org.junit.Assert.assertFalse
|
|
10
|
-
import org.junit.Assert.assertNotNull
|
|
11
|
-
import org.junit.Assert.assertTrue
|
|
12
|
-
import org.junit.Test
|
|
13
|
-
import org.junit.runner.RunWith
|
|
14
|
-
import org.robolectric.RobolectricTestRunner
|
|
15
|
-
import org.robolectric.RuntimeEnvironment
|
|
16
|
-
import org.robolectric.annotation.Config
|
|
17
|
-
|
|
18
|
-
@RunWith(RobolectricTestRunner::class)
|
|
19
|
-
@Config(sdk = [34])
|
|
20
|
-
class NativePerformanceTest {
|
|
21
|
-
private val context = RuntimeEnvironment.getApplication()
|
|
22
|
-
private val baseFontSize = 16f
|
|
23
|
-
private val textColor = Color.BLACK
|
|
24
|
-
|
|
25
|
-
@Test
|
|
26
|
-
fun `performance - render bridge large document`() {
|
|
27
|
-
val renderJson = NativePerformanceFixtureFactory.largeRenderJson()
|
|
28
|
-
|
|
29
|
-
val stats = measureOperation("renderBridgeLargeDocument") {
|
|
30
|
-
val spannable = RenderBridge.buildSpannable(
|
|
31
|
-
renderJson,
|
|
32
|
-
baseFontSize,
|
|
33
|
-
textColor,
|
|
34
|
-
density = context.resources.displayMetrics.density
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
assertTrue("rendered spannable should not be empty", spannable.isNotEmpty())
|
|
38
|
-
if (spannable.isNotEmpty()) {
|
|
39
|
-
spannable.getSpans(
|
|
40
|
-
0,
|
|
41
|
-
minOf(spannable.length, 1),
|
|
42
|
-
Any::class.java
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
assertTrue("average render time should be positive", stats.averageMillis > 0.0)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@Test
|
|
51
|
-
fun `performance - apply update json large document`() {
|
|
52
|
-
val updateJson = NativePerformanceFixtureFactory.largeUpdateJson()
|
|
53
|
-
val editText = EditorEditText(context).apply {
|
|
54
|
-
layoutParams = ViewGroup.LayoutParams(
|
|
55
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
56
|
-
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
57
|
-
)
|
|
58
|
-
setBaseStyle(baseFontSize, textColor, Color.WHITE)
|
|
59
|
-
captureApplyUpdateTraceForTesting = true
|
|
60
|
-
}
|
|
61
|
-
val traceSamples = mutableListOf<EditorEditText.ApplyUpdateTrace>()
|
|
62
|
-
|
|
63
|
-
editText.applyUpdateJSON(updateJson, notifyListener = false)
|
|
64
|
-
layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
|
|
65
|
-
|
|
66
|
-
val stats = measureOperation("applyUpdateJsonLargeDocument") {
|
|
67
|
-
editText.applyUpdateJSON(updateJson, notifyListener = false)
|
|
68
|
-
layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
|
|
69
|
-
editText.lastApplyUpdateTrace()?.let(traceSamples::add)
|
|
70
|
-
|
|
71
|
-
assertFalse("edit text should contain rendered content", editText.text.isNullOrEmpty())
|
|
72
|
-
assertNotNull("edit text should have a layout after applying update", editText.layout)
|
|
73
|
-
}
|
|
74
|
-
println(
|
|
75
|
-
ApplyUpdateTraceStats(
|
|
76
|
-
name = "applyUpdateJsonLargeDocument.breakdown",
|
|
77
|
-
traces = traceSamples.takeLast(5)
|
|
78
|
-
).summaryString()
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
assertTrue("average applyUpdateJSON time should be positive", stats.averageMillis > 0.0)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
@Test
|
|
85
|
-
fun `performance - apply render patch large document`() {
|
|
86
|
-
val initialUpdateJson = NativePerformanceFixtureFactory.largeUpdateJson()
|
|
87
|
-
val patchedUpdateJson = NativePerformanceFixtureFactory.largePatchedUpdateJson()
|
|
88
|
-
val editText = EditorEditText(context).apply {
|
|
89
|
-
layoutParams = ViewGroup.LayoutParams(
|
|
90
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
91
|
-
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
92
|
-
)
|
|
93
|
-
setBaseStyle(baseFontSize, textColor, Color.WHITE)
|
|
94
|
-
captureApplyUpdateTraceForTesting = true
|
|
95
|
-
}
|
|
96
|
-
val traceSamples = mutableListOf<EditorEditText.ApplyUpdateTrace>()
|
|
97
|
-
|
|
98
|
-
val stats = measureOperation(
|
|
99
|
-
name = "applyRenderPatchLargeDocument",
|
|
100
|
-
beforeEach = {
|
|
101
|
-
editText.applyUpdateJSON(initialUpdateJson, notifyListener = false)
|
|
102
|
-
layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
|
|
103
|
-
}
|
|
104
|
-
) {
|
|
105
|
-
editText.applyUpdateJSON(patchedUpdateJson, notifyListener = false)
|
|
106
|
-
layoutView(editText, widthPx = 1080, heightPx = 2400, heightMode = View.MeasureSpec.AT_MOST)
|
|
107
|
-
editText.lastApplyUpdateTrace()?.let(traceSamples::add)
|
|
108
|
-
|
|
109
|
-
assertFalse("edit text should contain rendered content", editText.text.isNullOrEmpty())
|
|
110
|
-
}
|
|
111
|
-
println(
|
|
112
|
-
ApplyUpdateTraceStats(
|
|
113
|
-
name = "applyRenderPatchLargeDocument.breakdown",
|
|
114
|
-
traces = traceSamples.takeLast(5)
|
|
115
|
-
).summaryString()
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
assertTrue("average patch apply time should be positive", stats.averageMillis > 0.0)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@Test
|
|
122
|
-
fun `performance - remote selection overlay refresh multi peer large document`() {
|
|
123
|
-
val updateJson = NativePerformanceFixtureFactory.largeUpdateJson()
|
|
124
|
-
val richTextView = RichTextEditorView(context).apply {
|
|
125
|
-
configure(textSizePx = baseFontSize, textColor = textColor, backgroundColor = Color.WHITE)
|
|
126
|
-
setRemoteSelectionEditorIdForTesting(1L)
|
|
127
|
-
setRemoteSelectionScalarResolverForTesting { _, docPos -> docPos }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
richTextView.editorEditText.applyUpdateJSON(updateJson, notifyListener = false)
|
|
131
|
-
layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
|
|
132
|
-
|
|
133
|
-
val totalScalar = richTextView.editorEditText.text?.length ?: 0
|
|
134
|
-
val selections = NativePerformanceFixtureFactory.remoteSelections(
|
|
135
|
-
totalScalar,
|
|
136
|
-
peerCount = 24,
|
|
137
|
-
selectionWidth = 24
|
|
138
|
-
)
|
|
139
|
-
richTextView.setRemoteSelections(selections)
|
|
140
|
-
layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
|
|
141
|
-
|
|
142
|
-
val bitmap = Bitmap.createBitmap(1080, 1600, Bitmap.Config.ARGB_8888)
|
|
143
|
-
val canvas = Canvas(bitmap)
|
|
144
|
-
|
|
145
|
-
val stats = measureOperation("remoteSelectionOverlayRefreshMultiPeerLargeDocument") {
|
|
146
|
-
bitmap.eraseColor(Color.TRANSPARENT)
|
|
147
|
-
richTextView.setRemoteSelections(selections)
|
|
148
|
-
layoutView(richTextView, widthPx = 1080, heightPx = 1600, heightMode = View.MeasureSpec.EXACTLY)
|
|
149
|
-
richTextView.draw(canvas)
|
|
150
|
-
|
|
151
|
-
val snapshots = richTextView.remoteSelectionDebugSnapshotsForTesting()
|
|
152
|
-
assertTrue(
|
|
153
|
-
"remote selection overlay should resolve visible carets",
|
|
154
|
-
snapshots.any { it.caretRect != null }
|
|
155
|
-
)
|
|
156
|
-
assertEquals("expected one snapshot per peer", selections.size, snapshots.size)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
assertTrue("average remote selection refresh time should be positive", stats.averageMillis > 0.0)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private fun layoutView(
|
|
163
|
-
view: View,
|
|
164
|
-
widthPx: Int,
|
|
165
|
-
heightPx: Int,
|
|
166
|
-
heightMode: Int
|
|
167
|
-
) {
|
|
168
|
-
val widthSpec = View.MeasureSpec.makeMeasureSpec(widthPx, View.MeasureSpec.EXACTLY)
|
|
169
|
-
val heightSpec = View.MeasureSpec.makeMeasureSpec(heightPx, heightMode)
|
|
170
|
-
view.measure(widthSpec, heightSpec)
|
|
171
|
-
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private fun measureOperation(
|
|
175
|
-
name: String,
|
|
176
|
-
warmupIterations: Int = 3,
|
|
177
|
-
measuredIterations: Int = 5,
|
|
178
|
-
beforeEach: (() -> Unit)? = null,
|
|
179
|
-
block: () -> Unit
|
|
180
|
-
): TimingStats {
|
|
181
|
-
repeat(warmupIterations) {
|
|
182
|
-
beforeEach?.invoke()
|
|
183
|
-
block()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
val samples = MutableList(measuredIterations) {
|
|
187
|
-
beforeEach?.invoke()
|
|
188
|
-
val startedAt = System.nanoTime()
|
|
189
|
-
block()
|
|
190
|
-
System.nanoTime() - startedAt
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
val stats = TimingStats(name, samples)
|
|
194
|
-
println(stats.summaryString())
|
|
195
|
-
return stats
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
package com.openeditor.editor
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.view.View
|
|
5
|
-
import expo.modules.core.ModuleRegistry
|
|
6
|
-
import expo.modules.kotlin.AppContext
|
|
7
|
-
import expo.modules.kotlin.ModulesProvider
|
|
8
|
-
import expo.modules.kotlin.modules.Module
|
|
9
|
-
import org.junit.Assert.assertEquals
|
|
10
|
-
import org.junit.Assert.assertTrue
|
|
11
|
-
import org.junit.Test
|
|
12
|
-
import org.junit.runner.RunWith
|
|
13
|
-
import org.robolectric.RobolectricTestRunner
|
|
14
|
-
import org.robolectric.RuntimeEnvironment
|
|
15
|
-
import org.robolectric.annotation.Config
|
|
16
|
-
import java.lang.ref.WeakReference
|
|
17
|
-
|
|
18
|
-
@RunWith(RobolectricTestRunner::class)
|
|
19
|
-
@Config(sdk = [34])
|
|
20
|
-
class NativeProseViewerExpoViewTest {
|
|
21
|
-
@Test
|
|
22
|
-
fun `viewer measure ignores stale exact parent height`() {
|
|
23
|
-
val expoContext = testExpoContext(RuntimeEnvironment.getApplication())
|
|
24
|
-
val view = NativeProseViewerExpoView(expoContext.context, expoContext.appContext)
|
|
25
|
-
view.suppressContentHeightEventsForTesting = true
|
|
26
|
-
view.setRenderJson(paragraphRenderJson(LONG_MESSAGE_TEXT))
|
|
27
|
-
|
|
28
|
-
val widthSpec = View.MeasureSpec.makeMeasureSpec(600, View.MeasureSpec.EXACTLY)
|
|
29
|
-
val wrapHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
30
|
-
view.measure(widthSpec, wrapHeightSpec)
|
|
31
|
-
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
|
|
32
|
-
val contentHeight = view.measuredHeight
|
|
33
|
-
|
|
34
|
-
assertTrue(contentHeight > 0)
|
|
35
|
-
|
|
36
|
-
val staleExactHeightSpec = View.MeasureSpec.makeMeasureSpec(
|
|
37
|
-
contentHeight + 480,
|
|
38
|
-
View.MeasureSpec.EXACTLY
|
|
39
|
-
)
|
|
40
|
-
view.measure(widthSpec, staleExactHeightSpec)
|
|
41
|
-
|
|
42
|
-
assertEquals(contentHeight, view.measuredHeight)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private data class TestExpoContext(
|
|
46
|
-
val context: Context,
|
|
47
|
-
val appContext: AppContext
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
private fun testExpoContext(context: Context): TestExpoContext {
|
|
51
|
-
val reactContext = Class
|
|
52
|
-
.forName("com.facebook.react.bridge.BridgeReactContext")
|
|
53
|
-
.getConstructor(Context::class.java)
|
|
54
|
-
.newInstance(context) as Context
|
|
55
|
-
|
|
56
|
-
val modulesProvider = object : ModulesProvider {
|
|
57
|
-
override fun getModulesList(): List<Class<out Module>> = emptyList()
|
|
58
|
-
}
|
|
59
|
-
val constructor = AppContext::class.java.constructors.first { constructor ->
|
|
60
|
-
constructor.parameterTypes.size == 3
|
|
61
|
-
}
|
|
62
|
-
val appContext = constructor.newInstance(
|
|
63
|
-
modulesProvider,
|
|
64
|
-
ModuleRegistry(emptyList(), emptyList()),
|
|
65
|
-
WeakReference(reactContext)
|
|
66
|
-
) as AppContext
|
|
67
|
-
return TestExpoContext(reactContext, appContext)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private fun paragraphRenderJson(text: String): String =
|
|
71
|
-
"""
|
|
72
|
-
[
|
|
73
|
-
{"type":"blockStart","nodeType":"paragraph","depth":0},
|
|
74
|
-
{"type":"textRun","text":"$text","marks":[]},
|
|
75
|
-
{"type":"blockEnd"}
|
|
76
|
-
]
|
|
77
|
-
""".trimIndent()
|
|
78
|
-
|
|
79
|
-
private companion object {
|
|
80
|
-
private val LONG_MESSAGE_TEXT = List(80) {
|
|
81
|
-
"Long Android viewer message"
|
|
82
|
-
}.joinToString(" ")
|
|
83
|
-
}
|
|
84
|
-
}
|