@openeditor/react-native-prose-editor 0.0.11 → 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 +74 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +4 -0
- package/dist/NativeRichTextEditor.d.ts +15 -0
- package/dist/NativeRichTextEditor.js +21 -1
- package/dist/addons.d.ts +13 -0
- package/ios/NativeBlockEditorSurface.swift +101 -0
- package/ios/NativeEditorExpoView.swift +18 -0
- package/ios/RichTextEditorView.swift +8 -0
- package/package.json +1 -1
|
@@ -47,6 +47,8 @@ import uniffi.editor_core.editorUpdateNodeAttrs
|
|
|
47
47
|
internal data class SelectedImageGeometry(val docPos: Int, val rect: RectF)
|
|
48
48
|
|
|
49
49
|
class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
50
|
+
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
51
|
+
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
50
52
|
var isScrollingEnabled: Boolean = true
|
|
51
53
|
|
|
52
54
|
internal var geometryRevision: Long = 0
|
|
@@ -617,6 +619,12 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
617
619
|
internal fun tableFillColorsForTesting(): List<Int> = tableCells.map { it.baseFillColor }
|
|
618
620
|
|
|
619
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
|
+
}
|
|
625
|
+
if (node.optString("nodeType") == "page") {
|
|
626
|
+
return pageView(node, depth).also { nodeViews[path] = it }
|
|
627
|
+
}
|
|
620
628
|
val rendered = when (node.optString("kind")) {
|
|
621
629
|
"list" -> listView(node, depth, path)
|
|
622
630
|
"listItem" -> listItemView(node, 0, null, depth, path)
|
|
@@ -920,6 +928,72 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
920
928
|
return TextLeaf(context).also { configureTextLeaf(it, node) }
|
|
921
929
|
}
|
|
922
930
|
|
|
931
|
+
private fun pageView(node: JSONObject, depth: Int): View {
|
|
932
|
+
val density = resources.displayMetrics.density
|
|
933
|
+
val attrs = node.optJSONObject("attrs")
|
|
934
|
+
val row = LinearLayout(context).apply {
|
|
935
|
+
orientation = LinearLayout.HORIZONTAL
|
|
936
|
+
gravity = android.view.Gravity.CENTER_VERTICAL
|
|
937
|
+
val horizontal = (6 * density).toInt()
|
|
938
|
+
val vertical = (5 * density).toInt()
|
|
939
|
+
setPadding(horizontal + (depth * 8 * density).toInt(), vertical, horizontal, vertical)
|
|
940
|
+
}
|
|
941
|
+
row.addView(TextView(context).apply {
|
|
942
|
+
text = attrs?.optString("icon", "📄") ?: "📄"
|
|
943
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
944
|
+
}, LinearLayout.LayoutParams((28 * density).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT))
|
|
945
|
+
val title = textBlockView(node, depth) as TextLeaf
|
|
946
|
+
title.setTypeface(title.typeface, Typeface.BOLD)
|
|
947
|
+
row.addView(title, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
948
|
+
row.addView(TextView(context).apply {
|
|
949
|
+
text = "→"
|
|
950
|
+
contentDescription = "Open page"
|
|
951
|
+
gravity = android.view.Gravity.CENTER
|
|
952
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
953
|
+
setTypeface(typeface, Typeface.BOLD)
|
|
954
|
+
setOnClickListener {
|
|
955
|
+
val pageId = attrs?.optString("pageId").orEmpty()
|
|
956
|
+
if (pageId.isNotEmpty()) onPageOpen?.invoke(
|
|
957
|
+
pageId,
|
|
958
|
+
title.text?.toString().orEmpty().ifEmpty { "Untitled" },
|
|
959
|
+
attrs?.optString("icon")?.takeIf(String::isNotEmpty),
|
|
960
|
+
attrs?.optString("href")?.takeIf(String::isNotEmpty),
|
|
961
|
+
)
|
|
962
|
+
}
|
|
963
|
+
}, LinearLayout.LayoutParams((40 * density).toInt(), (40 * density).toInt()))
|
|
964
|
+
return row
|
|
965
|
+
}
|
|
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
|
+
|
|
923
997
|
private fun configureTextLeaf(leaf: TextLeaf, node: JSONObject) {
|
|
924
998
|
val rendered = attributedInlineContent(node)
|
|
925
999
|
val hadFocus = leaf.hasFocus()
|
|
@@ -727,6 +727,20 @@ class NativeEditorExpoView(
|
|
|
727
727
|
addView(richTextView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
728
728
|
richTextView.onSelectionChange = ::onSelectionChanged
|
|
729
729
|
richTextView.onEditorUpdate = ::onEditorUpdate
|
|
730
|
+
richTextView.onPageOpen = { pageId, title, icon, href ->
|
|
731
|
+
val event = mutableMapOf<String, Any>("type" to "pageOpen", "pageId" to pageId, "title" to title)
|
|
732
|
+
icon?.let { event["icon"] = it }
|
|
733
|
+
href?.let { event["href"] = it }
|
|
734
|
+
emitAddonEvent(mapOf("eventJson" to JSONObject(event).toString()))
|
|
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
|
+
}
|
|
730
744
|
richTextView.onBeforeDetachedFromWindow = {
|
|
731
745
|
prepareForDetachFromWindow()
|
|
732
746
|
}
|
|
@@ -38,6 +38,8 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
38
38
|
var onSelectionChange: ((Int, Int) -> Unit)? = null
|
|
39
39
|
var onEditorUpdate: ((String) -> Unit)? = null
|
|
40
40
|
var onFocusChange: ((Boolean) -> Unit)? = null
|
|
41
|
+
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
42
|
+
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
41
43
|
|
|
42
44
|
var editorId: Long
|
|
43
45
|
get() = currentEditorId
|
|
@@ -60,6 +62,8 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
60
62
|
blockSurface.onSelectionChange = { anchor, head -> onSelectionChange?.invoke(anchor, head) }
|
|
61
63
|
blockSurface.onEditorUpdate = { update -> onEditorUpdate?.invoke(update) }
|
|
62
64
|
blockSurface.onFocusChange = { focused -> onFocusChange?.invoke(focused) }
|
|
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) }
|
|
63
67
|
updateAppearance()
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -81,6 +81,21 @@ export interface NativeRichTextEditorProps {
|
|
|
81
81
|
onRequestLink?: (context: LinkRequestContext) => void;
|
|
82
82
|
/** Called when a toolbar image item is pressed so the host can choose an image source. */
|
|
83
83
|
onRequestImage?: (context: ImageRequestContext) => void;
|
|
84
|
+
/** Called when the disclosure control on a Page block is pressed. */
|
|
85
|
+
onOpenPage?: (page: {
|
|
86
|
+
pageId: string;
|
|
87
|
+
title: string;
|
|
88
|
+
icon?: string | null;
|
|
89
|
+
href?: string | null;
|
|
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;
|
|
84
99
|
/** Whether plain URLs typed or pasted into the editor should be converted into link marks automatically. */
|
|
85
100
|
autoDetectLinks?: boolean;
|
|
86
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, 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,25 @@ 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
|
+
}
|
|
2067
|
+
if (parsed.type === 'pageOpen') {
|
|
2068
|
+
onOpenPage?.({
|
|
2069
|
+
pageId: parsed.pageId,
|
|
2070
|
+
title: parsed.title,
|
|
2071
|
+
icon: parsed.icon,
|
|
2072
|
+
href: parsed.href,
|
|
2073
|
+
});
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2057
2076
|
let parsedDocumentVersion = typeof parsed.documentVersion === 'number' ? parsed.documentVersion : undefined;
|
|
2058
2077
|
if (typeof parsedDocumentVersion !== 'number' &&
|
|
2059
2078
|
parsed.type === 'mentionsSelectRequest' &&
|
|
@@ -2169,6 +2188,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
2169
2188
|
resolveMentionInsertionAttrs,
|
|
2170
2189
|
runAndApplyWithCommandRetry,
|
|
2171
2190
|
setMentionQueryEventState,
|
|
2191
|
+
onOpenPage,
|
|
2172
2192
|
syncPreflightUpdateFromNativeEvent,
|
|
2173
2193
|
]);
|
|
2174
2194
|
(0, react_1.useImperativeHandle)(ref, () => ({
|
package/dist/addons.d.ts
CHANGED
|
@@ -65,6 +65,19 @@ 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
|
+
} | {
|
|
75
|
+
type: 'pageOpen';
|
|
76
|
+
pageId: string;
|
|
77
|
+
title: string;
|
|
78
|
+
icon?: string | null;
|
|
79
|
+
href?: string | null;
|
|
80
|
+
} | {
|
|
68
81
|
type: 'mentionsQueryChange';
|
|
69
82
|
query: string;
|
|
70
83
|
trigger: string;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
|
|
3
3
|
final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestureRecognizerDelegate {
|
|
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
|
+
}
|
|
4
16
|
private final class TextLeafView: UITextView {
|
|
5
17
|
struct PositionSegment {
|
|
6
18
|
let range: NSRange
|
|
@@ -696,6 +708,16 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
696
708
|
}
|
|
697
709
|
|
|
698
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
|
+
}
|
|
716
|
+
if node["nodeType"] as? String == "page" {
|
|
717
|
+
let rendered = pageView(for: node, depth: depth)
|
|
718
|
+
nodeViews[path] = rendered
|
|
719
|
+
return rendered
|
|
720
|
+
}
|
|
699
721
|
let rendered: UIView = switch node["kind"] as? String {
|
|
700
722
|
case "list":
|
|
701
723
|
listView(for: node, depth: depth, path: path)
|
|
@@ -1046,6 +1068,85 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1046
1068
|
return leaf
|
|
1047
1069
|
}
|
|
1048
1070
|
|
|
1071
|
+
private func pageView(for node: [String: Any], depth: Int) -> UIView {
|
|
1072
|
+
let row = UIStackView()
|
|
1073
|
+
row.axis = .horizontal
|
|
1074
|
+
row.alignment = .center
|
|
1075
|
+
row.spacing = 8
|
|
1076
|
+
row.layoutMargins = UIEdgeInsets(top: 5, left: CGFloat(depth) * 8 + 6, bottom: 5, right: 6)
|
|
1077
|
+
row.isLayoutMarginsRelativeArrangement = true
|
|
1078
|
+
row.layer.cornerRadius = 8
|
|
1079
|
+
|
|
1080
|
+
let icon = UILabel()
|
|
1081
|
+
icon.text = stringAttr(node, "icon") ?? "📄"
|
|
1082
|
+
icon.font = .systemFont(ofSize: 18)
|
|
1083
|
+
icon.setContentHuggingPriority(.required, for: .horizontal)
|
|
1084
|
+
row.addArrangedSubview(icon)
|
|
1085
|
+
|
|
1086
|
+
let title = textBlockView(for: node, depth: depth)
|
|
1087
|
+
if let leaf = title as? UITextView {
|
|
1088
|
+
leaf.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
1089
|
+
leaf.textColor = baseTextColor
|
|
1090
|
+
}
|
|
1091
|
+
row.addArrangedSubview(title)
|
|
1092
|
+
|
|
1093
|
+
let open = UIButton(type: .system)
|
|
1094
|
+
open.setTitle("→", for: .normal)
|
|
1095
|
+
open.titleLabel?.font = .systemFont(ofSize: 18, weight: .semibold)
|
|
1096
|
+
open.accessibilityLabel = "Open page"
|
|
1097
|
+
open.setContentHuggingPriority(.required, for: .horizontal)
|
|
1098
|
+
open.addAction(UIAction { [weak self, weak title] _ in
|
|
1099
|
+
guard let pageId = self?.stringAttr(node, "pageId"), !pageId.isEmpty else { return }
|
|
1100
|
+
let pageTitle = (title as? UITextView)?.text ?? "Untitled"
|
|
1101
|
+
self?.onPageOpen?(pageId, pageTitle, self?.stringAttr(node, "icon"), self?.stringAttr(node, "href"))
|
|
1102
|
+
}, for: .touchUpInside)
|
|
1103
|
+
row.addArrangedSubview(open)
|
|
1104
|
+
return row
|
|
1105
|
+
}
|
|
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
|
+
|
|
1049
1150
|
private func configureTextLeaf(_ leaf: TextLeafView, for node: [String: Any]) {
|
|
1050
1151
|
let wasFirstResponder = leaf.isFirstResponder
|
|
1051
1152
|
let previousSelection = leaf.selectedRange
|
|
@@ -2101,6 +2101,24 @@ class NativeEditorExpoView: ExpoView, UIGestureRecognizerDelegate {
|
|
|
2101
2101
|
if focused { self?.handleTextDidBeginEditing() }
|
|
2102
2102
|
else { self?.handleTextDidEndEditing() }
|
|
2103
2103
|
}
|
|
2104
|
+
richTextView.onPageOpen = { [weak self] pageId, title, icon, href in
|
|
2105
|
+
var event: [String: Any] = ["type": "pageOpen", "pageId": pageId, "title": title]
|
|
2106
|
+
if let icon { event["icon"] = icon }
|
|
2107
|
+
if let href { event["href"] = href }
|
|
2108
|
+
guard let data = try? JSONSerialization.data(withJSONObject: event),
|
|
2109
|
+
let json = String(data: data, encoding: .utf8) else { return }
|
|
2110
|
+
self?.onAddonEvent(["eventJson": json])
|
|
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
|
+
}
|
|
2104
2122
|
configureAccessoryToolbar()
|
|
2105
2123
|
|
|
2106
2124
|
addSubview(richTextView)
|
|
@@ -646,6 +646,8 @@ final class RichTextEditorView: UIView {
|
|
|
646
646
|
var onSelectionChange: ((UInt32, UInt32) -> Void)?
|
|
647
647
|
var onEditorUpdate: ((String) -> Void)?
|
|
648
648
|
var onFocusChange: ((Bool) -> Void)?
|
|
649
|
+
var onPageOpen: ((String, String, String?, String?) -> Void)?
|
|
650
|
+
var onAttachmentOpen: ((String?, String, String?, NSNumber?, String?) -> Void)?
|
|
649
651
|
private var lastAutoGrowWidth: CGFloat = 0
|
|
650
652
|
private var cachedAutoGrowMeasuredHeight: CGFloat = 0
|
|
651
653
|
private var remoteSelections: [RemoteSelectionDecoration] = []
|
|
@@ -760,6 +762,12 @@ final class RichTextEditorView: UIView {
|
|
|
760
762
|
blockSurface.onFocusChange = { [weak self] focused in
|
|
761
763
|
self?.onFocusChange?(focused)
|
|
762
764
|
}
|
|
765
|
+
blockSurface.onPageOpen = { [weak self] pageId, title, icon, href in
|
|
766
|
+
self?.onPageOpen?(pageId, title, icon, href)
|
|
767
|
+
}
|
|
768
|
+
blockSurface.onAttachmentOpen = { [weak self] attachmentId, name, mimeType, size, url in
|
|
769
|
+
self?.onAttachmentOpen?(attachmentId, name, mimeType, size, url)
|
|
770
|
+
}
|
|
763
771
|
blockSurface.onContentHeightMayChange = { [weak self] measuredHeight in
|
|
764
772
|
guard let self, self.heightBehavior == .autoGrow else { return }
|
|
765
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",
|