@chaitrabhairappa/react-native-rich-text-editor 1.0.7 → 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/README.md +31 -0
- package/android/build.gradle +13 -6
- package/android/src/main/java/com/richtext/editor/FloatingToolbar.kt +4 -4
- package/android/src/main/java/com/richtext/editor/RichTextEditorPackage.kt +12 -4
- package/android/src/main/java/com/richtext/editor/RichTextEditorView.kt +184 -4
- package/android/src/main/java/com/richtext/editor/RichTextEditorViewManager.kt +35 -141
- package/ios/RichTextEditorView.swift +115 -5
- package/ios/RichTextEditorViewManager.m +2 -1
- package/lib/commonjs/RichTextEditorViewNativeComponent.js +19 -0
- package/lib/commonjs/RichTextEditorViewNativeComponent.js.map +1 -0
- package/lib/commonjs/index.js +74 -55
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/RichTextEditorViewNativeComponent.js +13 -0
- package/lib/module/RichTextEditorViewNativeComponent.js.map +1 -0
- package/lib/module/index.js +73 -55
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/RichTextEditorViewNativeComponent.d.ts +60 -0
- package/lib/typescript/src/RichTextEditorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +15 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +10 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +13 -4
- package/react-native-richtext-editor.podspec +9 -2
package/README.md
CHANGED
|
@@ -27,6 +27,37 @@ Unlike other rich text editor packages that rely on HTML and WebView, this libra
|
|
|
27
27
|
- Floating toolbar with customizable options
|
|
28
28
|
- Two variants: outlined and flat
|
|
29
29
|
- Auto-growing height
|
|
30
|
+
- **Delta-based content updates** for optimized performance
|
|
31
|
+
- **Synchronous style detection** via `onActiveStylesChange`
|
|
32
|
+
|
|
33
|
+
## Why Delta-Based Updates?
|
|
34
|
+
|
|
35
|
+
Unlike other editors that send the **entire document** on every keystroke, this library includes **delta information** — only what changed.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
onContentChange={(event) => {
|
|
39
|
+
// Full content (for saving)
|
|
40
|
+
console.log(event.nativeEvent.text);
|
|
41
|
+
console.log(event.nativeEvent.blocks);
|
|
42
|
+
|
|
43
|
+
// Delta (for optimized processing)
|
|
44
|
+
console.log(event.nativeEvent.delta);
|
|
45
|
+
// { type: "insert", position: 50, text: "a" }
|
|
46
|
+
}}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Delta Type | When | Data |
|
|
50
|
+
|------------|------|------|
|
|
51
|
+
| `insert` | User types | `position`, `text` |
|
|
52
|
+
| `delete` | User deletes | `position`, `length` |
|
|
53
|
+
| `replace` | Selection replaced | `position`, `length`, `text` |
|
|
54
|
+
| `format` | Style applied | `position`, `length`, `style` |
|
|
55
|
+
|
|
56
|
+
**Benefits:**
|
|
57
|
+
- **Server sync** — Send only deltas instead of full document
|
|
58
|
+
- **Collaborative editing** — Apply remote changes efficiently
|
|
59
|
+
- **Analytics** — Track exactly what users type/delete
|
|
60
|
+
- **Performance** — Process small changes without parsing entire content
|
|
30
61
|
|
|
31
62
|
## Installation
|
|
32
63
|
|
package/android/build.gradle
CHANGED
|
@@ -12,22 +12,23 @@ buildscript {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
def isNewArchitectureEnabled() {
|
|
16
|
-
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
apply plugin: 'com.android.library'
|
|
20
16
|
apply plugin: 'kotlin-android'
|
|
17
|
+
apply plugin: 'com.facebook.react'
|
|
21
18
|
|
|
22
19
|
android {
|
|
23
20
|
namespace "com.richtext.editor"
|
|
24
21
|
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
25
22
|
|
|
26
23
|
defaultConfig {
|
|
27
|
-
minSdkVersion safeExtGet('minSdkVersion',
|
|
24
|
+
minSdkVersion safeExtGet('minSdkVersion', 23)
|
|
28
25
|
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
29
26
|
}
|
|
30
27
|
|
|
28
|
+
buildFeatures {
|
|
29
|
+
buildConfig true
|
|
30
|
+
}
|
|
31
|
+
|
|
31
32
|
buildTypes {
|
|
32
33
|
release {
|
|
33
34
|
minifyEnabled false
|
|
@@ -56,6 +57,12 @@ repositories {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
dependencies {
|
|
59
|
-
implementation "com.facebook.react:react-
|
|
60
|
+
implementation "com.facebook.react:react-android:+"
|
|
60
61
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.8.0')}"
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
react {
|
|
65
|
+
jsRootDir = file("../src/")
|
|
66
|
+
libraryName = "RichTextEditorSpec"
|
|
67
|
+
codegenJavaPackageName = "com.richtext.editor"
|
|
68
|
+
}
|
|
@@ -113,12 +113,12 @@ class FloatingToolbar(context: Context) : LinearLayout(context) {
|
|
|
113
113
|
private fun createArrowButton(text: String): TextView {
|
|
114
114
|
return TextView(context).apply {
|
|
115
115
|
this.text = text
|
|
116
|
-
textSize =
|
|
116
|
+
textSize = 20f
|
|
117
117
|
setTextColor(Color.parseColor("#FFFFFF"))
|
|
118
118
|
gravity = Gravity.CENTER
|
|
119
119
|
isClickable = true
|
|
120
120
|
isFocusable = true
|
|
121
|
-
val params = LayoutParams((
|
|
121
|
+
val params = LayoutParams((16 * density).toInt(), ViewGroup.LayoutParams.MATCH_PARENT)
|
|
122
122
|
layoutParams = params
|
|
123
123
|
// Add touch feedback
|
|
124
124
|
val bg = GradientDrawable().apply {
|
|
@@ -190,7 +190,7 @@ class FloatingToolbar(context: Context) : LinearLayout(context) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
private val iconSize = (20 * density).toInt()
|
|
193
|
-
private val iconPadding = (
|
|
193
|
+
private val iconPadding = (10 * density).toInt()
|
|
194
194
|
|
|
195
195
|
private fun createButton(option: String): ImageView? {
|
|
196
196
|
val drawableResId = getDrawableResId(option)
|
|
@@ -290,7 +290,7 @@ class FloatingToolbar(context: Context) : LinearLayout(context) {
|
|
|
290
290
|
val screenWidth = context.resources.displayMetrics.widthPixels
|
|
291
291
|
val maxWidth = (screenWidth * 0.9).toInt()
|
|
292
292
|
val buttonCount = enabledOptions.size
|
|
293
|
-
val calculatedWidth = (buttonCount * buttonSize) + ((buttonCount - 1) * buttonSpacing) + (
|
|
293
|
+
val calculatedWidth = (buttonCount * buttonSize) + ((buttonCount - 1) * buttonSpacing) + (48 * density).toInt()
|
|
294
294
|
return minOf(calculatedWidth, maxWidth)
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
package com.richtext.editor
|
|
2
2
|
|
|
3
|
-
import com.facebook.react.
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
6
8
|
import com.facebook.react.uimanager.ViewManager
|
|
7
9
|
|
|
8
|
-
class RichTextEditorPackage :
|
|
9
|
-
override fun
|
|
10
|
-
return
|
|
10
|
+
class RichTextEditorPackage : TurboReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
16
|
+
return ReactModuleInfoProvider {
|
|
17
|
+
mapOf()
|
|
18
|
+
}
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
@@ -45,6 +45,9 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
45
45
|
private var minHeightPx: Float = 0f
|
|
46
46
|
private var isInitialized = false
|
|
47
47
|
|
|
48
|
+
private var previousText: String = ""
|
|
49
|
+
private var pendingDelta: Map<String, Any>? = null
|
|
50
|
+
|
|
48
51
|
// For flat variant bottom border
|
|
49
52
|
private val bottomBorderPaint = Paint().apply {
|
|
50
53
|
color = Color.parseColor("#E0E0E0")
|
|
@@ -112,13 +115,57 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
112
115
|
setupToolbar()
|
|
113
116
|
|
|
114
117
|
addTextChangedListener(object : TextWatcher {
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
private var changeStart = 0
|
|
119
|
+
private var removedCount = 0
|
|
120
|
+
private var addedCount = 0
|
|
121
|
+
|
|
122
|
+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
123
|
+
changeStart = start
|
|
124
|
+
removedCount = count
|
|
125
|
+
addedCount = after
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
129
|
+
// Capture delta information
|
|
130
|
+
if (!isInternalChange && s != null) {
|
|
131
|
+
val newText = s.toString()
|
|
132
|
+
val deltaMap = mutableMapOf<String, Any>()
|
|
133
|
+
|
|
134
|
+
when {
|
|
135
|
+
removedCount == 0 && addedCount > 0 -> {
|
|
136
|
+
// Insert
|
|
137
|
+
deltaMap["type"] = "insert"
|
|
138
|
+
deltaMap["position"] = changeStart
|
|
139
|
+
deltaMap["text"] = newText.substring(start, start + count)
|
|
140
|
+
}
|
|
141
|
+
removedCount > 0 && addedCount == 0 -> {
|
|
142
|
+
// Delete
|
|
143
|
+
deltaMap["type"] = "delete"
|
|
144
|
+
deltaMap["position"] = changeStart
|
|
145
|
+
deltaMap["length"] = removedCount
|
|
146
|
+
}
|
|
147
|
+
removedCount > 0 && addedCount > 0 -> {
|
|
148
|
+
// Replace
|
|
149
|
+
deltaMap["type"] = "replace"
|
|
150
|
+
deltaMap["position"] = changeStart
|
|
151
|
+
deltaMap["length"] = removedCount
|
|
152
|
+
deltaMap["text"] = newText.substring(start, start + count)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (deltaMap.isNotEmpty()) {
|
|
157
|
+
pendingDelta = deltaMap
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
117
162
|
override fun afterTextChanged(s: Editable?) {
|
|
118
163
|
if (!isInternalChange) {
|
|
119
|
-
|
|
164
|
+
sendContentChangeWithDelta()
|
|
120
165
|
saveToUndoStack()
|
|
166
|
+
pendingDelta = null
|
|
121
167
|
}
|
|
168
|
+
previousText = s?.toString() ?: ""
|
|
122
169
|
post { updateContentSize() }
|
|
123
170
|
}
|
|
124
171
|
})
|
|
@@ -213,6 +260,9 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
213
260
|
map.putInt("end", selEnd)
|
|
214
261
|
sendEvent("onSelectionChange", map)
|
|
215
262
|
|
|
263
|
+
// Emit active styles synchronously for instant toolbar updates
|
|
264
|
+
emitActiveStyles()
|
|
265
|
+
|
|
216
266
|
// Show/hide toolbar based on selection
|
|
217
267
|
if (selStart != selEnd && showToolbar && hasFocus()) {
|
|
218
268
|
android.util.Log.d("RichTextEditor", "Should show toolbar - selection exists")
|
|
@@ -231,6 +281,78 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
231
281
|
}
|
|
232
282
|
}
|
|
233
283
|
|
|
284
|
+
// Synchronous style detection - emits current active styles to JS
|
|
285
|
+
private fun emitActiveStyles() {
|
|
286
|
+
val start = selectionStart
|
|
287
|
+
val end = selectionEnd
|
|
288
|
+
val spannable = text as? Spanned
|
|
289
|
+
|
|
290
|
+
var hasBold = false
|
|
291
|
+
var hasItalic = false
|
|
292
|
+
var hasUnderline = false
|
|
293
|
+
var hasStrikethrough = false
|
|
294
|
+
var hasCode = false
|
|
295
|
+
var hasHighlight = false
|
|
296
|
+
var blockType = "paragraph"
|
|
297
|
+
var alignment = "left"
|
|
298
|
+
|
|
299
|
+
if (spannable != null && start <= end) {
|
|
300
|
+
// Check for style spans
|
|
301
|
+
spannable.getSpans(start, end.coerceAtLeast(start + 1), StyleSpan::class.java).forEach { span ->
|
|
302
|
+
when (span.style) {
|
|
303
|
+
Typeface.BOLD -> hasBold = true
|
|
304
|
+
Typeface.ITALIC -> hasItalic = true
|
|
305
|
+
Typeface.BOLD_ITALIC -> {
|
|
306
|
+
hasBold = true
|
|
307
|
+
hasItalic = true
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
hasUnderline = spannable.getSpans(start, end.coerceAtLeast(start + 1), UnderlineSpan::class.java).isNotEmpty()
|
|
313
|
+
hasStrikethrough = spannable.getSpans(start, end.coerceAtLeast(start + 1), StrikethroughSpan::class.java).isNotEmpty()
|
|
314
|
+
hasCode = spannable.getSpans(start, end.coerceAtLeast(start + 1), TypefaceSpan::class.java).any { it.family == "monospace" }
|
|
315
|
+
|
|
316
|
+
// Check highlight (but not code background)
|
|
317
|
+
val bgSpans = spannable.getSpans(start, end.coerceAtLeast(start + 1), BackgroundColorSpan::class.java)
|
|
318
|
+
hasHighlight = bgSpans.any {
|
|
319
|
+
val color = it.backgroundColor
|
|
320
|
+
color == Color.parseColor("#80FFFF00") || color == Color.YELLOW
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check block type from line content
|
|
324
|
+
val lineText = getCurrentLineText()
|
|
325
|
+
blockType = when {
|
|
326
|
+
lineText.startsWith("• ") -> "bullet"
|
|
327
|
+
lineText.matches(Regex("^\\d+\\.\\s.*")) -> "numbered"
|
|
328
|
+
lineText.startsWith("☐ ") || lineText.startsWith("☑ ") -> "checklist"
|
|
329
|
+
lineText.startsWith("\"") && lineText.endsWith("\"") -> "quote"
|
|
330
|
+
spannable.getSpans(start, end.coerceAtLeast(start + 1), RelativeSizeSpan::class.java).any { it.sizeChange > 1.2f } -> "heading"
|
|
331
|
+
else -> "paragraph"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Check alignment
|
|
335
|
+
spannable.getSpans(start, end.coerceAtLeast(start + 1), AlignmentSpan.Standard::class.java).firstOrNull()?.let { span ->
|
|
336
|
+
alignment = when (span.alignment) {
|
|
337
|
+
Layout.Alignment.ALIGN_CENTER -> "center"
|
|
338
|
+
Layout.Alignment.ALIGN_OPPOSITE -> "right"
|
|
339
|
+
else -> "left"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
val map = Arguments.createMap()
|
|
345
|
+
map.putBoolean("bold", hasBold)
|
|
346
|
+
map.putBoolean("italic", hasItalic)
|
|
347
|
+
map.putBoolean("underline", hasUnderline)
|
|
348
|
+
map.putBoolean("strikethrough", hasStrikethrough)
|
|
349
|
+
map.putBoolean("code", hasCode)
|
|
350
|
+
map.putBoolean("highlight", hasHighlight)
|
|
351
|
+
map.putString("blockType", blockType)
|
|
352
|
+
map.putString("alignment", alignment)
|
|
353
|
+
sendEvent("onActiveStylesChange", map)
|
|
354
|
+
}
|
|
355
|
+
|
|
234
356
|
private val hideToolbarRunnable = Runnable {
|
|
235
357
|
android.util.Log.d("RichTextEditor", "hideToolbarRunnable: selectionStart=$selectionStart, selectionEnd=$selectionEnd")
|
|
236
358
|
if (selectionStart == selectionEnd) {
|
|
@@ -583,16 +705,44 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
583
705
|
}
|
|
584
706
|
|
|
585
707
|
private fun sendContentChange() {
|
|
708
|
+
sendContentChangeWithDelta(null)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private fun sendContentChangeWithDelta(delta: Map<String, Any>? = pendingDelta) {
|
|
586
712
|
try {
|
|
587
713
|
val map = Arguments.createMap()
|
|
588
714
|
map.putString("text", text?.toString() ?: "")
|
|
589
|
-
|
|
715
|
+
// Serialize blocks to JSON string (codegen doesn't support nested arrays)
|
|
716
|
+
map.putString("blocksJson", getBlocksJsonString())
|
|
717
|
+
|
|
718
|
+
// Include delta information if available
|
|
719
|
+
if (delta != null) {
|
|
720
|
+
val deltaMap = Arguments.createMap()
|
|
721
|
+
delta["type"]?.let { deltaMap.putString("type", it as String) }
|
|
722
|
+
delta["position"]?.let { deltaMap.putInt("position", it as Int) }
|
|
723
|
+
delta["length"]?.let { deltaMap.putInt("length", it as Int) }
|
|
724
|
+
delta["text"]?.let { deltaMap.putString("text", it as String) }
|
|
725
|
+
delta["style"]?.let { deltaMap.putString("style", it as String) }
|
|
726
|
+
map.putMap("delta", deltaMap)
|
|
727
|
+
}
|
|
728
|
+
|
|
590
729
|
sendEvent("onContentChange", map)
|
|
591
730
|
} catch (e: Exception) {
|
|
592
731
|
e.printStackTrace()
|
|
593
732
|
}
|
|
594
733
|
}
|
|
595
734
|
|
|
735
|
+
// Send format delta when applying styles
|
|
736
|
+
private fun sendFormatDelta(style: String, start: Int, end: Int) {
|
|
737
|
+
val delta = mapOf(
|
|
738
|
+
"type" to "format",
|
|
739
|
+
"position" to start,
|
|
740
|
+
"length" to (end - start),
|
|
741
|
+
"style" to style
|
|
742
|
+
)
|
|
743
|
+
sendContentChangeWithDelta(delta)
|
|
744
|
+
}
|
|
745
|
+
|
|
596
746
|
private fun saveToUndoStack() {
|
|
597
747
|
val currentText = text?.toString() ?: ""
|
|
598
748
|
if (currentText != lastSavedText) {
|
|
@@ -717,6 +867,19 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
717
867
|
|
|
718
868
|
fun getTextContent(): String = text.toString()
|
|
719
869
|
|
|
870
|
+
// Fabric command response methods
|
|
871
|
+
fun emitGetTextResponse() {
|
|
872
|
+
val map = Arguments.createMap()
|
|
873
|
+
map.putString("text", text?.toString() ?: "")
|
|
874
|
+
sendEvent("onGetTextResponse", map)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
fun emitGetBlocksResponse() {
|
|
878
|
+
val map = Arguments.createMap()
|
|
879
|
+
map.putArray("blocks", getBlocksArray())
|
|
880
|
+
sendEvent("onGetBlocksResponse", map)
|
|
881
|
+
}
|
|
882
|
+
|
|
720
883
|
fun getBlocksArray(): WritableArray {
|
|
721
884
|
val blocks = Arguments.createArray()
|
|
722
885
|
val textContent = text?.toString() ?: ""
|
|
@@ -734,6 +897,23 @@ class RichTextEditorView(context: Context) : androidx.appcompat.widget.AppCompat
|
|
|
734
897
|
return blocks
|
|
735
898
|
}
|
|
736
899
|
|
|
900
|
+
fun getBlocksJsonString(): String {
|
|
901
|
+
val textContent = text?.toString() ?: ""
|
|
902
|
+
if (textContent.isEmpty()) {
|
|
903
|
+
return "[]"
|
|
904
|
+
}
|
|
905
|
+
val jsonArray = org.json.JSONArray()
|
|
906
|
+
val lines = textContent.split("\n")
|
|
907
|
+
lines.forEach { line ->
|
|
908
|
+
val block = org.json.JSONObject()
|
|
909
|
+
block.put("type", "paragraph")
|
|
910
|
+
block.put("text", line)
|
|
911
|
+
block.put("styles", org.json.JSONArray())
|
|
912
|
+
jsonArray.put(block)
|
|
913
|
+
}
|
|
914
|
+
return jsonArray.toString()
|
|
915
|
+
}
|
|
916
|
+
|
|
737
917
|
fun clearContent() {
|
|
738
918
|
isInternalChange = true
|
|
739
919
|
text?.clear()
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
package com.richtext.editor
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReadableArray
|
|
4
|
-
import com.facebook.react.
|
|
4
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
5
5
|
import com.facebook.react.uimanager.SimpleViewManager
|
|
6
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
7
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
8
8
|
|
|
9
|
+
@ReactModule(name = RichTextEditorViewManager.NAME)
|
|
9
10
|
class RichTextEditorViewManager : SimpleViewManager<RichTextEditorView>() {
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
companion object {
|
|
13
|
+
const val NAME = "RichTextEditorView"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun getName(): String = NAME
|
|
12
17
|
|
|
13
18
|
override fun createViewInstance(reactContext: ThemedReactContext): RichTextEditorView {
|
|
14
19
|
return RichTextEditorView(reactContext)
|
|
@@ -33,9 +38,9 @@ class RichTextEditorViewManager : SimpleViewManager<RichTextEditorView>() {
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
@ReactProp(name = "maxHeight")
|
|
36
|
-
fun setMaxHeight(view: RichTextEditorView, maxHeight:
|
|
41
|
+
fun setMaxHeight(view: RichTextEditorView, maxHeight: Double) {
|
|
37
42
|
try {
|
|
38
|
-
view.setMaxHeightValue(maxHeight)
|
|
43
|
+
view.setMaxHeightValue(maxHeight.toInt())
|
|
39
44
|
} catch (e: Exception) {
|
|
40
45
|
e.printStackTrace()
|
|
41
46
|
}
|
|
@@ -76,36 +81,34 @@ class RichTextEditorViewManager : SimpleViewManager<RichTextEditorView>() {
|
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
@ReactProp(name = "
|
|
80
|
-
fun
|
|
81
|
-
if (
|
|
84
|
+
@ReactProp(name = "initialContentJson")
|
|
85
|
+
fun setInitialContentJson(view: RichTextEditorView, initialContentJson: String?) {
|
|
86
|
+
if (initialContentJson.isNullOrEmpty()) return
|
|
82
87
|
|
|
83
88
|
try {
|
|
89
|
+
val json = org.json.JSONArray(initialContentJson)
|
|
84
90
|
val blocksList = mutableListOf<Map<String, Any>>()
|
|
85
|
-
for (i in 0 until
|
|
86
|
-
val block =
|
|
91
|
+
for (i in 0 until json.length()) {
|
|
92
|
+
val block = json.getJSONObject(i)
|
|
87
93
|
val blockMap = mutableMapOf<String, Any>()
|
|
88
|
-
blockMap["text"] = block.
|
|
89
|
-
blockMap["type"] = block.
|
|
94
|
+
blockMap["text"] = block.optString("text", "")
|
|
95
|
+
blockMap["type"] = block.optString("type", "paragraph")
|
|
90
96
|
|
|
91
97
|
val stylesList = mutableListOf<Map<String, Any>>()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
stylesList.add(styleMap)
|
|
102
|
-
}
|
|
98
|
+
val styles = block.optJSONArray("styles")
|
|
99
|
+
if (styles != null) {
|
|
100
|
+
for (j in 0 until styles.length()) {
|
|
101
|
+
val style = styles.getJSONObject(j)
|
|
102
|
+
val styleMap = mutableMapOf<String, Any>()
|
|
103
|
+
styleMap["style"] = style.optString("style", "")
|
|
104
|
+
styleMap["start"] = style.optInt("start", 0)
|
|
105
|
+
styleMap["end"] = style.optInt("end", 0)
|
|
106
|
+
stylesList.add(styleMap)
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
blockMap["styles"] = stylesList
|
|
106
110
|
blocksList.add(blockMap)
|
|
107
111
|
}
|
|
108
|
-
// Delay setting content until view is ready
|
|
109
112
|
view.post {
|
|
110
113
|
view.setContent(blocksList)
|
|
111
114
|
}
|
|
@@ -114,123 +117,14 @@ class RichTextEditorViewManager : SimpleViewManager<RichTextEditorView>() {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
override fun getExportedCustomDirectEventTypeConstants():
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
override fun getCommandsMap(): Map<String, Int>? {
|
|
128
|
-
return MapBuilder.builder<String, Int>()
|
|
129
|
-
// Content management
|
|
130
|
-
.put("setContent", 1)
|
|
131
|
-
.put("getText", 2)
|
|
132
|
-
.put("getBlocks", 3)
|
|
133
|
-
.put("clear", 4)
|
|
134
|
-
// Focus management
|
|
135
|
-
.put("focus", 5)
|
|
136
|
-
.put("blur", 6)
|
|
137
|
-
// Text styles
|
|
138
|
-
.put("toggleBold", 10)
|
|
139
|
-
.put("toggleItalic", 11)
|
|
140
|
-
.put("toggleUnderline", 12)
|
|
141
|
-
.put("toggleStrikethrough", 13)
|
|
142
|
-
.put("toggleCode", 14)
|
|
143
|
-
.put("toggleHighlight", 15)
|
|
144
|
-
// Block types
|
|
145
|
-
.put("setHeading", 16)
|
|
146
|
-
.put("setBulletList", 17)
|
|
147
|
-
.put("setNumberedList", 18)
|
|
148
|
-
.put("setQuote", 19)
|
|
149
|
-
.put("setChecklist", 20)
|
|
150
|
-
.put("setParagraph", 21)
|
|
151
|
-
// Actions
|
|
152
|
-
.put("insertLink", 7)
|
|
153
|
-
.put("undo", 8)
|
|
154
|
-
.put("redo", 9)
|
|
155
|
-
.put("clearFormatting", 22)
|
|
156
|
-
// Indentation
|
|
157
|
-
.put("indent", 23)
|
|
158
|
-
.put("outdent", 24)
|
|
159
|
-
// Alignment
|
|
160
|
-
.put("setAlignment", 25)
|
|
161
|
-
// Checklist
|
|
162
|
-
.put("toggleChecklistItem", 26)
|
|
163
|
-
.build()
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
override fun receiveCommand(view: RichTextEditorView, commandId: Int, args: ReadableArray?) {
|
|
167
|
-
when (commandId) {
|
|
168
|
-
1 -> {
|
|
169
|
-
val blocks = args?.getArray(0)
|
|
170
|
-
if (blocks != null) {
|
|
171
|
-
val blocksList = mutableListOf<Map<String, Any>>()
|
|
172
|
-
for (i in 0 until blocks.size()) {
|
|
173
|
-
val block = blocks.getMap(i)
|
|
174
|
-
val blockMap = mutableMapOf<String, Any>()
|
|
175
|
-
blockMap["text"] = block?.getString("text") ?: ""
|
|
176
|
-
blockMap["type"] = block?.getString("type") ?: "paragraph"
|
|
177
|
-
|
|
178
|
-
val styles = block?.getArray("styles")
|
|
179
|
-
if (styles != null) {
|
|
180
|
-
val stylesList = mutableListOf<Map<String, Any>>()
|
|
181
|
-
for (j in 0 until styles.size()) {
|
|
182
|
-
val style = styles.getMap(j)
|
|
183
|
-
val styleMap = mutableMapOf<String, Any>()
|
|
184
|
-
styleMap["style"] = style?.getString("style") ?: ""
|
|
185
|
-
styleMap["start"] = style?.getInt("start") ?: 0
|
|
186
|
-
styleMap["end"] = style?.getInt("end") ?: 0
|
|
187
|
-
stylesList.add(styleMap)
|
|
188
|
-
}
|
|
189
|
-
blockMap["styles"] = stylesList
|
|
190
|
-
}
|
|
191
|
-
blocksList.add(blockMap)
|
|
192
|
-
}
|
|
193
|
-
view.setContent(blocksList)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
4 -> view.clearContent()
|
|
197
|
-
5 -> view.focusEditor()
|
|
198
|
-
6 -> view.blurEditor()
|
|
199
|
-
7 -> {
|
|
200
|
-
val url = args?.getString(0) ?: ""
|
|
201
|
-
val text = args?.getString(1) ?: ""
|
|
202
|
-
view.insertLink(url, text)
|
|
203
|
-
}
|
|
204
|
-
8 -> view.undo()
|
|
205
|
-
9 -> view.redo()
|
|
206
|
-
10 -> view.toggleBold()
|
|
207
|
-
11 -> view.toggleItalic()
|
|
208
|
-
12 -> view.toggleUnderline()
|
|
209
|
-
13 -> view.toggleStrikethrough()
|
|
210
|
-
14 -> view.toggleCode()
|
|
211
|
-
15 -> {
|
|
212
|
-
val color = args?.getString(0)
|
|
213
|
-
view.toggleHighlight(color)
|
|
214
|
-
}
|
|
215
|
-
16 -> view.setHeading()
|
|
216
|
-
17 -> view.toggleBulletList()
|
|
217
|
-
18 -> view.toggleNumberedList()
|
|
218
|
-
19 -> view.setQuote()
|
|
219
|
-
20 -> view.setChecklist()
|
|
220
|
-
21 -> view.setParagraph()
|
|
221
|
-
22 -> view.clearFormatting()
|
|
222
|
-
23 -> view.indent()
|
|
223
|
-
24 -> view.outdent()
|
|
224
|
-
25 -> {
|
|
225
|
-
val alignment = args?.getString(0) ?: "left"
|
|
226
|
-
val layoutAlignment = when (alignment) {
|
|
227
|
-
"center" -> android.text.Layout.Alignment.ALIGN_CENTER
|
|
228
|
-
"right" -> android.text.Layout.Alignment.ALIGN_OPPOSITE
|
|
229
|
-
else -> android.text.Layout.Alignment.ALIGN_NORMAL
|
|
230
|
-
}
|
|
231
|
-
view.setAlignment(layoutAlignment)
|
|
232
|
-
}
|
|
233
|
-
26 -> view.toggleChecklistItem()
|
|
234
|
-
}
|
|
120
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
121
|
+
val events = mutableMapOf<String, Any>()
|
|
122
|
+
events["onContentChange"] = mapOf("registrationName" to "onContentChange")
|
|
123
|
+
events["onSelectionChange"] = mapOf("registrationName" to "onSelectionChange")
|
|
124
|
+
events["onEditorFocus"] = mapOf("registrationName" to "onEditorFocus")
|
|
125
|
+
events["onEditorBlur"] = mapOf("registrationName" to "onEditorBlur")
|
|
126
|
+
events["onSizeChange"] = mapOf("registrationName" to "onSizeChange")
|
|
127
|
+
events["onActiveStylesChange"] = mapOf("registrationName" to "onActiveStylesChange")
|
|
128
|
+
return events
|
|
235
129
|
}
|
|
236
130
|
}
|