@openeditor/react-native-prose-editor 0.0.12 → 0.0.13
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/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +34 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +8 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +2 -0
- package/dist/NativeRichTextEditor.d.ts +8 -0
- package/dist/NativeRichTextEditor.js +11 -1
- package/dist/addons.d.ts +7 -0
- package/ios/NativeBlockEditorSurface.swift +59 -0
- package/ios/NativeEditorExpoView.swift +10 -0
- package/ios/RichTextEditorView.swift +4 -0
- package/package.json +1 -1
|
@@ -48,6 +48,7 @@ internal data class SelectedImageGeometry(val docPos: Int, val rect: RectF)
|
|
|
48
48
|
|
|
49
49
|
class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
50
50
|
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
51
|
+
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
51
52
|
var isScrollingEnabled: Boolean = true
|
|
52
53
|
|
|
53
54
|
internal var geometryRevision: Long = 0
|
|
@@ -618,6 +619,9 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
618
619
|
internal fun tableFillColorsForTesting(): List<Int> = tableCells.map { it.baseFillColor }
|
|
619
620
|
|
|
620
621
|
private fun viewForNode(node: JSONObject, depth: Int, path: List<Int>): View {
|
|
622
|
+
if (node.optString("nodeType") == "attachment") {
|
|
623
|
+
return attachmentView(node, depth).also { nodeViews[path] = it }
|
|
624
|
+
}
|
|
621
625
|
if (node.optString("nodeType") == "page") {
|
|
622
626
|
return pageView(node, depth).also { nodeViews[path] = it }
|
|
623
627
|
}
|
|
@@ -960,6 +964,36 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
960
964
|
return row
|
|
961
965
|
}
|
|
962
966
|
|
|
967
|
+
private fun attachmentView(node: JSONObject, depth: Int): View {
|
|
968
|
+
val density = resources.displayMetrics.density
|
|
969
|
+
val attrs = node.optJSONObject("attrs")
|
|
970
|
+
return LinearLayout(context).apply {
|
|
971
|
+
orientation = LinearLayout.HORIZONTAL
|
|
972
|
+
gravity = android.view.Gravity.CENTER_VERTICAL
|
|
973
|
+
val padding = (10 * density).toInt()
|
|
974
|
+
setPadding(padding + (depth * 8 * density).toInt(), padding, padding, padding)
|
|
975
|
+
background = borderDrawable(Color.TRANSPARENT, currentTheme?.table?.borderColor ?: baseTextColor, 1f)
|
|
976
|
+
addView(TextView(context).apply { text = "📎"; setTextSize(TypedValue.COMPLEX_UNIT_SP, 22f) }, LinearLayout.LayoutParams((38 * density).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT))
|
|
977
|
+
addView(LinearLayout(context).apply {
|
|
978
|
+
orientation = LinearLayout.VERTICAL
|
|
979
|
+
addView(TextView(context).apply { text = attrs?.optString("name").orEmpty().ifEmpty { "Attachment" }; setTextColor(baseTextColor); setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f); setTypeface(typeface, Typeface.BOLD) })
|
|
980
|
+
addView(TextView(context).apply { text = attrs?.optString("mimeType").orEmpty().ifEmpty { "File" }; setTextColor(currentTheme?.placeholderColor ?: baseTextColor); setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) })
|
|
981
|
+
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
982
|
+
isClickable = true
|
|
983
|
+
isFocusable = true
|
|
984
|
+
contentDescription = "Open ${attrs?.optString("name").orEmpty().ifEmpty { "attachment" }}"
|
|
985
|
+
setOnClickListener {
|
|
986
|
+
onAttachmentOpen?.invoke(
|
|
987
|
+
attrs?.optString("attachmentId")?.takeIf(String::isNotEmpty),
|
|
988
|
+
attrs?.optString("name").orEmpty().ifEmpty { "Attachment" },
|
|
989
|
+
attrs?.optString("mimeType")?.takeIf(String::isNotEmpty),
|
|
990
|
+
attrs?.optLong("size")?.takeIf { attrs.has("size") && !attrs.isNull("size") },
|
|
991
|
+
attrs?.optString("url")?.takeIf(String::isNotEmpty),
|
|
992
|
+
)
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
963
997
|
private fun configureTextLeaf(leaf: TextLeaf, node: JSONObject) {
|
|
964
998
|
val rendered = attributedInlineContent(node)
|
|
965
999
|
val hadFocus = leaf.hasFocus()
|
|
@@ -733,6 +733,14 @@ class NativeEditorExpoView(
|
|
|
733
733
|
href?.let { event["href"] = it }
|
|
734
734
|
emitAddonEvent(mapOf("eventJson" to JSONObject(event).toString()))
|
|
735
735
|
}
|
|
736
|
+
richTextView.onAttachmentOpen = { attachmentId, name, mimeType, size, url ->
|
|
737
|
+
val event = mutableMapOf<String, Any>("type" to "attachmentOpen", "name" to name)
|
|
738
|
+
attachmentId?.let { event["attachmentId"] = it }
|
|
739
|
+
mimeType?.let { event["mimeType"] = it }
|
|
740
|
+
size?.let { event["size"] = it }
|
|
741
|
+
url?.let { event["url"] = it }
|
|
742
|
+
emitAddonEvent(mapOf("eventJson" to JSONObject(event).toString()))
|
|
743
|
+
}
|
|
736
744
|
richTextView.onBeforeDetachedFromWindow = {
|
|
737
745
|
prepareForDetachFromWindow()
|
|
738
746
|
}
|
|
@@ -39,6 +39,7 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
39
39
|
var onEditorUpdate: ((String) -> Unit)? = null
|
|
40
40
|
var onFocusChange: ((Boolean) -> Unit)? = null
|
|
41
41
|
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
42
|
+
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
42
43
|
|
|
43
44
|
var editorId: Long
|
|
44
45
|
get() = currentEditorId
|
|
@@ -62,6 +63,7 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
62
63
|
blockSurface.onEditorUpdate = { update -> onEditorUpdate?.invoke(update) }
|
|
63
64
|
blockSurface.onFocusChange = { focused -> onFocusChange?.invoke(focused) }
|
|
64
65
|
blockSurface.onPageOpen = { pageId, title, icon, href -> onPageOpen?.invoke(pageId, title, icon, href) }
|
|
66
|
+
blockSurface.onAttachmentOpen = { attachmentId, name, mimeType, size, url -> onAttachmentOpen?.invoke(attachmentId, name, mimeType, size, url) }
|
|
65
67
|
updateAppearance()
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -88,6 +88,14 @@ export interface NativeRichTextEditorProps {
|
|
|
88
88
|
icon?: string | null;
|
|
89
89
|
href?: string | null;
|
|
90
90
|
}) => void;
|
|
91
|
+
/** Called when an Attachment block is pressed. */
|
|
92
|
+
onOpenAttachment?: (attachment: {
|
|
93
|
+
attachmentId: string | null;
|
|
94
|
+
name: string;
|
|
95
|
+
mimeType: string | null;
|
|
96
|
+
size: number | null;
|
|
97
|
+
url: string | null;
|
|
98
|
+
}) => void;
|
|
91
99
|
/** Whether plain URLs typed or pasted into the editor should be converted into link marks automatically. */
|
|
92
100
|
autoDetectLinks?: boolean;
|
|
93
101
|
/** Whether `data:image/...` sources are accepted for image insertion and HTML parsing. */
|
|
@@ -559,7 +559,7 @@ function doesLiveMentionQueryConflictWithNativeSelectRequest(request, currentQue
|
|
|
559
559
|
currentQuery.range.anchor !== request.range.anchor ||
|
|
560
560
|
currentQuery.range.head !== request.range.head);
|
|
561
561
|
}
|
|
562
|
-
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, valueJSONUpdateMode = 'replace', preserveSelectionOnValueJSONReset = false, selectionOnValueJSONReset, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, keyboardAppearance, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onOpenPage, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
562
|
+
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, valueJSONUpdateMode = 'replace', preserveSelectionOnValueJSONReset = false, selectionOnValueJSONReset, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, keyboardAppearance, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onOpenPage, onOpenAttachment, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
563
563
|
const bridgeRef = (0, react_1.useRef)(null);
|
|
564
564
|
const nativeViewRef = (0, react_1.useRef)(null);
|
|
565
565
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
@@ -2054,6 +2054,16 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
2054
2054
|
}
|
|
2055
2055
|
if (!parsed)
|
|
2056
2056
|
return;
|
|
2057
|
+
if (parsed.type === 'attachmentOpen') {
|
|
2058
|
+
onOpenAttachment?.({
|
|
2059
|
+
attachmentId: parsed.attachmentId ?? null,
|
|
2060
|
+
name: parsed.name,
|
|
2061
|
+
mimeType: parsed.mimeType ?? null,
|
|
2062
|
+
size: parsed.size ?? null,
|
|
2063
|
+
url: parsed.url ?? null,
|
|
2064
|
+
});
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2057
2067
|
if (parsed.type === 'pageOpen') {
|
|
2058
2068
|
onOpenPage?.({
|
|
2059
2069
|
pageId: parsed.pageId,
|
package/dist/addons.d.ts
CHANGED
|
@@ -65,6 +65,13 @@ export interface SerializedEditorAddons {
|
|
|
65
65
|
mentions?: SerializedMentionsAddonConfig;
|
|
66
66
|
}
|
|
67
67
|
export type EditorAddonEvent = {
|
|
68
|
+
type: 'attachmentOpen';
|
|
69
|
+
attachmentId?: string | null;
|
|
70
|
+
name: string;
|
|
71
|
+
mimeType?: string | null;
|
|
72
|
+
size?: number | null;
|
|
73
|
+
url?: string | null;
|
|
74
|
+
} | {
|
|
68
75
|
type: 'pageOpen';
|
|
69
76
|
pageId: string;
|
|
70
77
|
title: string;
|
|
@@ -2,6 +2,17 @@ import UIKit
|
|
|
2
2
|
|
|
3
3
|
final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestureRecognizerDelegate {
|
|
4
4
|
var onPageOpen: ((String, String, String?, String?) -> Void)?
|
|
5
|
+
var onAttachmentOpen: ((String?, String, String?, NSNumber?, String?) -> Void)?
|
|
6
|
+
private final class AttachmentRow: UIStackView {
|
|
7
|
+
var open: (() -> Void)?
|
|
8
|
+
override init(frame: CGRect) {
|
|
9
|
+
super.init(frame: frame)
|
|
10
|
+
isUserInteractionEnabled = true
|
|
11
|
+
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleOpen)))
|
|
12
|
+
}
|
|
13
|
+
required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
14
|
+
@objc private func handleOpen() { open?() }
|
|
15
|
+
}
|
|
5
16
|
private final class TextLeafView: UITextView {
|
|
6
17
|
struct PositionSegment {
|
|
7
18
|
let range: NSRange
|
|
@@ -697,6 +708,11 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
697
708
|
}
|
|
698
709
|
|
|
699
710
|
private func view(for node: [String: Any], depth: Int, path: [Int]) -> UIView {
|
|
711
|
+
if node["nodeType"] as? String == "attachment" {
|
|
712
|
+
let rendered = attachmentView(for: node, depth: depth)
|
|
713
|
+
nodeViews[path] = rendered
|
|
714
|
+
return rendered
|
|
715
|
+
}
|
|
700
716
|
if node["nodeType"] as? String == "page" {
|
|
701
717
|
let rendered = pageView(for: node, depth: depth)
|
|
702
718
|
nodeViews[path] = rendered
|
|
@@ -1088,6 +1104,49 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1088
1104
|
return row
|
|
1089
1105
|
}
|
|
1090
1106
|
|
|
1107
|
+
private func attachmentView(for node: [String: Any], depth: Int) -> UIView {
|
|
1108
|
+
let row = AttachmentRow()
|
|
1109
|
+
row.axis = .horizontal
|
|
1110
|
+
row.alignment = .center
|
|
1111
|
+
row.spacing = 10
|
|
1112
|
+
row.layoutMargins = UIEdgeInsets(top: 10, left: CGFloat(depth) * 8 + 10, bottom: 10, right: 10)
|
|
1113
|
+
row.isLayoutMarginsRelativeArrangement = true
|
|
1114
|
+
row.layer.cornerRadius = 10
|
|
1115
|
+
row.layer.borderWidth = 1
|
|
1116
|
+
row.layer.borderColor = UIColor.separator.cgColor
|
|
1117
|
+
let icon = UILabel()
|
|
1118
|
+
icon.text = "📎"
|
|
1119
|
+
icon.font = .systemFont(ofSize: 22)
|
|
1120
|
+
row.addArrangedSubview(icon)
|
|
1121
|
+
let details = UIStackView()
|
|
1122
|
+
details.axis = .vertical
|
|
1123
|
+
details.spacing = 2
|
|
1124
|
+
let name = UILabel()
|
|
1125
|
+
name.text = stringAttr(node, "name") ?? "Attachment"
|
|
1126
|
+
name.font = .systemFont(ofSize: 15, weight: .semibold)
|
|
1127
|
+
name.textColor = baseTextColor
|
|
1128
|
+
name.lineBreakMode = .byTruncatingMiddle
|
|
1129
|
+
let meta = UILabel()
|
|
1130
|
+
meta.text = stringAttr(node, "mimeType") ?? "File"
|
|
1131
|
+
meta.font = .systemFont(ofSize: 12)
|
|
1132
|
+
meta.textColor = .secondaryLabel
|
|
1133
|
+
details.addArrangedSubview(name)
|
|
1134
|
+
details.addArrangedSubview(meta)
|
|
1135
|
+
row.addArrangedSubview(details)
|
|
1136
|
+
row.accessibilityTraits = .button
|
|
1137
|
+
row.accessibilityLabel = "Open \(name.text ?? "attachment")"
|
|
1138
|
+
row.open = { [weak self] in
|
|
1139
|
+
self?.onAttachmentOpen?(
|
|
1140
|
+
self?.stringAttr(node, "attachmentId"),
|
|
1141
|
+
self?.stringAttr(node, "name") ?? "Attachment",
|
|
1142
|
+
self?.stringAttr(node, "mimeType"),
|
|
1143
|
+
(node["attrs"] as? [String: Any])?["size"] as? NSNumber,
|
|
1144
|
+
self?.stringAttr(node, "url")
|
|
1145
|
+
)
|
|
1146
|
+
}
|
|
1147
|
+
return row
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1091
1150
|
private func configureTextLeaf(_ leaf: TextLeafView, for node: [String: Any]) {
|
|
1092
1151
|
let wasFirstResponder = leaf.isFirstResponder
|
|
1093
1152
|
let previousSelection = leaf.selectedRange
|
|
@@ -2109,6 +2109,16 @@ class NativeEditorExpoView: ExpoView, UIGestureRecognizerDelegate {
|
|
|
2109
2109
|
let json = String(data: data, encoding: .utf8) else { return }
|
|
2110
2110
|
self?.onAddonEvent(["eventJson": json])
|
|
2111
2111
|
}
|
|
2112
|
+
richTextView.onAttachmentOpen = { [weak self] attachmentId, name, mimeType, size, url in
|
|
2113
|
+
var event: [String: Any] = ["type": "attachmentOpen", "name": name]
|
|
2114
|
+
if let attachmentId { event["attachmentId"] = attachmentId }
|
|
2115
|
+
if let mimeType { event["mimeType"] = mimeType }
|
|
2116
|
+
if let size { event["size"] = size }
|
|
2117
|
+
if let url { event["url"] = url }
|
|
2118
|
+
guard let data = try? JSONSerialization.data(withJSONObject: event),
|
|
2119
|
+
let json = String(data: data, encoding: .utf8) else { return }
|
|
2120
|
+
self?.onAddonEvent(["eventJson": json])
|
|
2121
|
+
}
|
|
2112
2122
|
configureAccessoryToolbar()
|
|
2113
2123
|
|
|
2114
2124
|
addSubview(richTextView)
|
|
@@ -647,6 +647,7 @@ final class RichTextEditorView: UIView {
|
|
|
647
647
|
var onEditorUpdate: ((String) -> Void)?
|
|
648
648
|
var onFocusChange: ((Bool) -> Void)?
|
|
649
649
|
var onPageOpen: ((String, String, String?, String?) -> Void)?
|
|
650
|
+
var onAttachmentOpen: ((String?, String, String?, NSNumber?, String?) -> Void)?
|
|
650
651
|
private var lastAutoGrowWidth: CGFloat = 0
|
|
651
652
|
private var cachedAutoGrowMeasuredHeight: CGFloat = 0
|
|
652
653
|
private var remoteSelections: [RemoteSelectionDecoration] = []
|
|
@@ -764,6 +765,9 @@ final class RichTextEditorView: UIView {
|
|
|
764
765
|
blockSurface.onPageOpen = { [weak self] pageId, title, icon, href in
|
|
765
766
|
self?.onPageOpen?(pageId, title, icon, href)
|
|
766
767
|
}
|
|
768
|
+
blockSurface.onAttachmentOpen = { [weak self] attachmentId, name, mimeType, size, url in
|
|
769
|
+
self?.onAttachmentOpen?(attachmentId, name, mimeType, size, url)
|
|
770
|
+
}
|
|
767
771
|
blockSurface.onContentHeightMayChange = { [weak self] measuredHeight in
|
|
768
772
|
guard let self, self.heightBehavior == .autoGrow else { return }
|
|
769
773
|
self.cachedAutoGrowMeasuredHeight = measuredHeight
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/react-native-prose-editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "OpenEditor-owned native rich text editor engine for React Native",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/EasyLink-HQ/openeditor",
|