@openeditor/react-native-prose-editor 0.0.2
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 +180 -0
- package/android/build.gradle +69 -0
- package/android/consumer-rules.pro +18 -0
- package/android/src/androidTest/AndroidManifest.xml +8 -0
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +53 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +241 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +338 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +789 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +350 -0
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +5 -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/CaretGeometry.kt +50 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +135 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +4527 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +999 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +474 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +3137 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +561 -0
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +1413 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +315 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +2172 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +399 -0
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +482 -0
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +137 -0
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +321 -0
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +4202 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +2581 -0
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +27 -0
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +197 -0
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +84 -0
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +544 -0
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +461 -0
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +2054 -0
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +1367 -0
- package/app.plugin.js +62 -0
- package/dist/EditorTheme.d.ts +113 -0
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +191 -0
- package/dist/EditorToolbar.js +1095 -0
- package/dist/NativeEditorBridge.d.ts +325 -0
- package/dist/NativeEditorBridge.js +933 -0
- package/dist/NativeProseViewer.d.ts +53 -0
- package/dist/NativeProseViewer.js +480 -0
- package/dist/NativeRichTextEditor.d.ts +183 -0
- package/dist/NativeRichTextEditor.js +2574 -0
- package/dist/YjsCollaboration.d.ts +90 -0
- package/dist/YjsCollaboration.js +743 -0
- package/dist/addons.d.ts +100 -0
- package/dist/addons.js +82 -0
- package/dist/heightCache.d.ts +6 -0
- package/dist/heightCache.js +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +30 -0
- package/dist/schemas.d.ts +37 -0
- package/dist/schemas.js +263 -0
- package/dist/useNativeEditor.d.ts +42 -0
- package/dist/useNativeEditor.js +124 -0
- package/expo-module.config.json +9 -0
- package/ios/EditorAddons.swift +327 -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/EditorLayoutManager.swift +774 -0
- package/ios/EditorTheme.swift +515 -0
- package/ios/Generated_editor_core.swift +1513 -0
- package/ios/NativeEditorExpoView.swift +3593 -0
- package/ios/NativeEditorModule.swift +624 -0
- package/ios/NativeProseViewerExpoView.swift +276 -0
- package/ios/PositionBridge.swift +558 -0
- package/ios/ReactNativeProseEditor.podspec +49 -0
- package/ios/RenderBridge.swift +1708 -0
- package/ios/RichTextEditorView.swift +6150 -0
- package/ios/Tests/NativePerformanceTests.swift +687 -0
- package/ios/Tests/PositionBridgeTests.swift +706 -0
- package/ios/Tests/RenderBridgeTests.swift +2236 -0
- package/ios/Tests/RichTextEditorViewTests.swift +6548 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1289 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/ios/editor_coreFFI.h +904 -0
- package/ios/editor_coreFFI.modulemap +7 -0
- package/package.json +74 -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_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2563 -0
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package com.openeditor.editor
|
|
2
|
+
|
|
3
|
+
import android.graphics.Paint
|
|
4
|
+
import android.text.Layout
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Vertical geometry for the text caret, clipped to the rendered glyph height.
|
|
8
|
+
*
|
|
9
|
+
* Android's [android.widget.Editor] draws the native caret from
|
|
10
|
+
* `Layout.getLineTop(line)` to `Layout.getLineBottom(line)`. When a
|
|
11
|
+
* [ParagraphSpacerSpan] inflates a line's descent to create inter-block
|
|
12
|
+
* spacing, `getLineBottom` includes that gap and the caret stretches into it.
|
|
13
|
+
* `getLineBottomWithoutSpacing` cannot help: the inflation lives in the line's
|
|
14
|
+
* DESCENT column, not the line-spacing EXTRA column it subtracts.
|
|
15
|
+
*
|
|
16
|
+
* The baseline is provably independent of descent inflation
|
|
17
|
+
* (`getLineBaseline(line) == getLineTop(line) - ascent`), so anchoring the
|
|
18
|
+
* caret bottom at `baseline + raw font descent` clips it to the glyph height.
|
|
19
|
+
* This mirrors the trim already used for blockquote stripes
|
|
20
|
+
* ([BlockquoteSpan.resolvedStripeBottom]).
|
|
21
|
+
*/
|
|
22
|
+
object CaretGeometry {
|
|
23
|
+
data class VerticalBounds(val top: Float, val bottom: Float)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether the manually-drawn caret should be visible. The native caret is
|
|
27
|
+
* suppressed, so this gates our replacement: only when the field is focused,
|
|
28
|
+
* its window is focused, and the selection is a collapsed insertion point
|
|
29
|
+
* (a range selection shows the selection highlight instead).
|
|
30
|
+
*/
|
|
31
|
+
fun shouldRender(
|
|
32
|
+
focused: Boolean,
|
|
33
|
+
windowFocused: Boolean,
|
|
34
|
+
selectionStart: Int,
|
|
35
|
+
selectionEnd: Int
|
|
36
|
+
): Boolean = focused &&
|
|
37
|
+
windowFocused &&
|
|
38
|
+
selectionStart >= 0 &&
|
|
39
|
+
selectionStart == selectionEnd
|
|
40
|
+
|
|
41
|
+
fun verticalBounds(layout: Layout, offset: Int, paint: Paint): VerticalBounds {
|
|
42
|
+
val line = layout.getLineForOffset(offset.coerceIn(0, layout.text.length))
|
|
43
|
+
val top = layout.getLineTop(line).toFloat()
|
|
44
|
+
// Anchor the bottom at the glyph descent below the baseline. The baseline
|
|
45
|
+
// is independent of any ReplacementSpan descent inflation, so this clips
|
|
46
|
+
// the caret to the rendered text height instead of the inflated line bottom.
|
|
47
|
+
val bottom = layout.getLineBaseline(line) + paint.fontMetrics.descent
|
|
48
|
+
return VerticalBounds(top, bottom)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
package com.openeditor.editor
|
|
2
|
+
|
|
3
|
+
import org.json.JSONObject
|
|
4
|
+
|
|
5
|
+
data class NativeMentionSuggestion(
|
|
6
|
+
val key: String,
|
|
7
|
+
val title: String,
|
|
8
|
+
val subtitle: String?,
|
|
9
|
+
val label: String,
|
|
10
|
+
val attrs: JSONObject
|
|
11
|
+
) {
|
|
12
|
+
companion object {
|
|
13
|
+
fun fromJson(json: JSONObject?): NativeMentionSuggestion? {
|
|
14
|
+
json ?: return null
|
|
15
|
+
val key = json.optString("key", "")
|
|
16
|
+
val title = json.optString("title", "")
|
|
17
|
+
val label = json.optString("label", "")
|
|
18
|
+
if (key.isBlank() || title.isBlank() || label.isBlank()) return null
|
|
19
|
+
return NativeMentionSuggestion(
|
|
20
|
+
key = key,
|
|
21
|
+
title = title,
|
|
22
|
+
subtitle = json.takeUnless { it.isNull("subtitle") }?.optString("subtitle"),
|
|
23
|
+
label = label,
|
|
24
|
+
attrs = json.optJSONObject("attrs") ?: JSONObject()
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
data class NativeMentionsAddonConfig(
|
|
31
|
+
val trigger: String,
|
|
32
|
+
val suggestions: List<NativeMentionSuggestion>,
|
|
33
|
+
val theme: EditorMentionTheme?,
|
|
34
|
+
val resolveSelectionAttrs: Boolean,
|
|
35
|
+
val resolveTheme: Boolean
|
|
36
|
+
) {
|
|
37
|
+
companion object {
|
|
38
|
+
fun fromJson(json: JSONObject?): NativeMentionsAddonConfig? {
|
|
39
|
+
json ?: return null
|
|
40
|
+
val trigger = json.optString("trigger", "@").ifBlank { "@" }
|
|
41
|
+
val rawSuggestions = json.optJSONArray("suggestions")
|
|
42
|
+
val suggestions = mutableListOf<NativeMentionSuggestion>()
|
|
43
|
+
if (rawSuggestions != null) {
|
|
44
|
+
for (index in 0 until rawSuggestions.length()) {
|
|
45
|
+
val suggestion = NativeMentionSuggestion.fromJson(
|
|
46
|
+
rawSuggestions.optJSONObject(index)
|
|
47
|
+
)
|
|
48
|
+
if (suggestion != null) {
|
|
49
|
+
suggestions.add(suggestion)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return NativeMentionsAddonConfig(
|
|
54
|
+
trigger = trigger,
|
|
55
|
+
suggestions = suggestions,
|
|
56
|
+
theme = EditorMentionTheme.fromJson(json.optJSONObject("theme")),
|
|
57
|
+
resolveSelectionAttrs = json.optBoolean("resolveSelectionAttrs", false),
|
|
58
|
+
resolveTheme = json.optBoolean("resolveTheme", false)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
data class NativeEditorAddons(
|
|
65
|
+
val mentions: NativeMentionsAddonConfig?
|
|
66
|
+
) {
|
|
67
|
+
companion object {
|
|
68
|
+
fun fromJson(json: String?): NativeEditorAddons {
|
|
69
|
+
if (json.isNullOrBlank()) return NativeEditorAddons(null)
|
|
70
|
+
val root = try {
|
|
71
|
+
JSONObject(json)
|
|
72
|
+
} catch (_: Exception) {
|
|
73
|
+
return NativeEditorAddons(null)
|
|
74
|
+
}
|
|
75
|
+
return NativeEditorAddons(
|
|
76
|
+
mentions = NativeMentionsAddonConfig.fromJson(root.optJSONObject("mentions"))
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
data class MentionQueryState(
|
|
83
|
+
val query: String,
|
|
84
|
+
val trigger: String,
|
|
85
|
+
val anchor: Int,
|
|
86
|
+
val head: Int
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
internal fun isMentionIdentifierCodePoint(codePoint: Int): Boolean {
|
|
90
|
+
return Character.isLetterOrDigit(codePoint) || codePoint == '_'.code || codePoint == '-'.code
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
internal fun resolveMentionQueryState(
|
|
94
|
+
text: String,
|
|
95
|
+
cursorScalar: Int,
|
|
96
|
+
trigger: String,
|
|
97
|
+
isCaretInsideMention: Boolean
|
|
98
|
+
): MentionQueryState? {
|
|
99
|
+
if (isCaretInsideMention) return null
|
|
100
|
+
|
|
101
|
+
val scalars = text.codePoints().toArray()
|
|
102
|
+
if (cursorScalar > scalars.size) return null
|
|
103
|
+
val triggerCodePoint = trigger.codePointAt(0)
|
|
104
|
+
|
|
105
|
+
var start = cursorScalar
|
|
106
|
+
while (start > 0) {
|
|
107
|
+
val previous = scalars[start - 1]
|
|
108
|
+
if (Character.isWhitespace(previous) ||
|
|
109
|
+
previous == '\n'.code ||
|
|
110
|
+
previous == 0xFFFC ||
|
|
111
|
+
(!isMentionIdentifierCodePoint(previous) && previous != triggerCodePoint)
|
|
112
|
+
) {
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
start -= 1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (start >= scalars.size || scalars[start] != triggerCodePoint) return null
|
|
119
|
+
if (start > 0) {
|
|
120
|
+
val previous = scalars[start - 1]
|
|
121
|
+
if (isMentionIdentifierCodePoint(previous)) {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
val query = String(scalars, start + 1, cursorScalar - (start + 1))
|
|
127
|
+
if (query.any { it.isWhitespace() }) return null
|
|
128
|
+
|
|
129
|
+
return MentionQueryState(
|
|
130
|
+
query = query,
|
|
131
|
+
trigger = trigger,
|
|
132
|
+
anchor = start,
|
|
133
|
+
head = cursorScalar
|
|
134
|
+
)
|
|
135
|
+
}
|