@openeditor/native 0.0.33 → 0.0.35
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 +81 -58
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +254 -0
- package/dist/index.d.ts +6 -101
- package/dist/index.js +4 -910
- package/dist/native-editor.d.ts +48 -0
- package/dist/native-editor.js +214 -0
- package/dist/native-layout.d.ts +20 -0
- package/dist/native-layout.js +15 -0
- package/dist/native-provider.d.ts +9 -0
- package/dist/native-provider.js +9 -0
- package/dist/theme.d.ts +32 -2
- package/dist/theme.js +56 -37
- package/dist/toolbar-host.d.ts +20 -0
- package/dist/toolbar-host.js +211 -0
- package/dist/toolbar-items.d.ts +109 -0
- package/dist/toolbar-items.js +57 -0
- package/dist/toolbar-surface.d.ts +17 -0
- package/dist/toolbar-surface.js +33 -0
- package/package.json +18 -19
- package/plugin.cjs +79 -0
- package/dist/document.d.ts +0 -7
- package/dist/document.js +0 -69
- package/dist/emoji-button.d.ts +0 -8
- package/dist/emoji-button.js +0 -14
- package/dist/emoji-data.d.ts +0 -44
- package/dist/emoji-data.js +0 -50
- package/dist/emoji-picker.d.ts +0 -12
- package/dist/emoji-picker.js +0 -67
- package/dist/selection.d.ts +0 -3
- package/dist/selection.js +0 -32
- package/dist/toolbar.d.ts +0 -30
- package/dist/toolbar.js +0 -282
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { BottomSheet as UniversalBottomSheet, Button as NativeButton, Column as NativeColumn, ListItem as NativeListItem, RNHostView as NativeRNHostView, Row as NativeRow, ScrollView as NativeScrollView, Spacer as NativeSpacer, Text as NativeText, } from "@expo/ui";
|
|
3
|
+
import { BottomSheet as SwiftUIBottomSheet, Group as SwiftUIGroup, Host as SwiftUIHost, } from "@expo/ui/swift-ui";
|
|
4
|
+
import { accessibilityLabel as nativeAccessibilityLabel, frame as nativeFrame, glassEffect, padding as nativePadding, presentationDetents, presentationDragIndicator, } from "@expo/ui/swift-ui/modifiers";
|
|
5
|
+
import { openEditorIcons } from "@openeditor/icons";
|
|
6
|
+
import { HugeiconsIcon } from "@hugeicons/react-native";
|
|
7
|
+
import { memo, useRef, useState } from "react";
|
|
8
|
+
import { Keyboard, Modal, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from "react-native";
|
|
9
|
+
import { defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, openEditorNativeTableToolbarItems, } from "./toolbar-items.js";
|
|
10
|
+
export { defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, } from "./toolbar-items.js";
|
|
11
|
+
const isItemActive = (item, state) => (item.activeWhen?.block !== undefined &&
|
|
12
|
+
state.activeNodes.includes(item.activeWhen.block) &&
|
|
13
|
+
(item.activeWhen?.headingLevel === undefined ||
|
|
14
|
+
state.headingLevel === item.activeWhen.headingLevel)) ||
|
|
15
|
+
(item.activeWhen?.mark !== undefined &&
|
|
16
|
+
state.activeMarks.includes(item.activeWhen.mark)) ||
|
|
17
|
+
(item.activeWhen?.table !== undefined &&
|
|
18
|
+
state.table?.[item.activeWhen.table] === true);
|
|
19
|
+
const isItemDisabled = (item, state) => !state.editable ||
|
|
20
|
+
(item.disabledWhen === "cannotUndo" && !state.canUndo) ||
|
|
21
|
+
(item.disabledWhen === "cannotRedo" && !state.canRedo) ||
|
|
22
|
+
(item.disabledWhen === "cannotMergeCells" && !state.table?.canMergeCells);
|
|
23
|
+
const TOOLBAR_BUTTON_SIZE = 32;
|
|
24
|
+
const IOS_BLOCK_PICKER_PRESENTATION_MODIFIERS = [
|
|
25
|
+
nativeFrame({ alignment: "topLeading", maxWidth: Infinity }),
|
|
26
|
+
nativePadding({ leading: 16, top: 16, trailing: 16 }),
|
|
27
|
+
presentationDragIndicator("visible"),
|
|
28
|
+
presentationDetents(["medium", "large"]),
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Expo's universal wrapper maps `onDismiss` to the binding-change event on
|
|
32
|
+
* iOS, losing SwiftUI's actual post-animation dismissal callback. Editor
|
|
33
|
+
* commands need that real lifecycle boundary so focus-owning native effects do
|
|
34
|
+
* not race the outgoing sheet.
|
|
35
|
+
*/
|
|
36
|
+
const NativeBlockPickerSheet = ({ children, isPresented, onDismiss, onIsPresentedChange, }) => {
|
|
37
|
+
if (Platform.OS !== "ios") {
|
|
38
|
+
return (_jsx(UniversalBottomSheet, { isPresented: isPresented, onDismiss: onDismiss, showDragIndicator: true, snapPoints: ["half", "full"], testID: "openeditor-block-picker", children: children }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsx(SwiftUIHost, { pointerEvents: "none", style: { position: "absolute" }, children: _jsx(SwiftUIBottomSheet, { isPresented: isPresented, onDismiss: onDismiss, onIsPresentedChange: onIsPresentedChange, testID: "openeditor-block-picker", children: _jsx(SwiftUIGroup, { modifiers: IOS_BLOCK_PICKER_PRESENTATION_MODIFIERS, children: children }) }) }));
|
|
41
|
+
};
|
|
42
|
+
export const OpenEditorNativeToolbar = memo(function OpenEditorNativeToolbar({ availableNativeEffects = [], controller, state, items = defaultOpenEditorNativeToolbarItems, theme, style, onCommandError, visible = true, blockHandlesVisible = false, onBlockHandlesVisibleChange, }) {
|
|
43
|
+
const [blockPickerOpen, setBlockPickerOpen] = useState(false);
|
|
44
|
+
const [linkEditorOpen, setLinkEditorOpen] = useState(false);
|
|
45
|
+
const [linkValue, setLinkValue] = useState("");
|
|
46
|
+
const pendingBlockRef = useRef(null);
|
|
47
|
+
const pickerSelectionRef = useRef(undefined);
|
|
48
|
+
const runCommand = (command) => {
|
|
49
|
+
void command.catch((cause) => {
|
|
50
|
+
onCommandError?.(cause instanceof Error
|
|
51
|
+
? cause
|
|
52
|
+
: new Error("OpenEditor command failed."));
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const colors = {
|
|
56
|
+
background: theme?.surface ?? "#ffffff",
|
|
57
|
+
border: theme?.border ?? "#e5e7eb",
|
|
58
|
+
text: theme?.text ?? "#171717",
|
|
59
|
+
muted: theme?.muted ?? "#737373",
|
|
60
|
+
accent: theme?.accent ?? "#171717",
|
|
61
|
+
accentText: theme?.accentText ?? "#ffffff",
|
|
62
|
+
};
|
|
63
|
+
const blockPickerItems = openEditorNativeBlockPickerItems.filter((item) => {
|
|
64
|
+
if (item.key === "image")
|
|
65
|
+
return availableNativeEffects.includes("pickImage");
|
|
66
|
+
if (item.key === "attachment")
|
|
67
|
+
return availableNativeEffects.includes("pickAttachment");
|
|
68
|
+
if (item.key === "page")
|
|
69
|
+
return availableNativeEffects.includes("createPage");
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
const blockPickerGroups = blockPickerItems.reduce((groups, item) => {
|
|
73
|
+
const current = groups.at(-1);
|
|
74
|
+
if (current?.label === item.group)
|
|
75
|
+
current.items.push(item);
|
|
76
|
+
else
|
|
77
|
+
groups.push({ label: item.group, items: [item] });
|
|
78
|
+
return groups;
|
|
79
|
+
}, []);
|
|
80
|
+
const visibleToolbarItems = state.activeNodes.includes("table")
|
|
81
|
+
? [
|
|
82
|
+
...defaultOpenEditorNativeToolbarItems.filter((item) => item.action === "toggleBlockHandles"),
|
|
83
|
+
...openEditorNativeTableToolbarItems.filter((item) => item.key === "table-split-cell"
|
|
84
|
+
? state.table?.canSplitCell
|
|
85
|
+
: item.key !== "table-merge-cells" || !state.table?.canSplitCell),
|
|
86
|
+
]
|
|
87
|
+
: items;
|
|
88
|
+
return (_jsxs(_Fragment, { children: [visible ? (_jsx(View, { style: [styles.root, style], children: _jsx(ScrollView, { contentContainerStyle: styles.content, horizontal: true, keyboardShouldPersistTaps: "always", showsHorizontalScrollIndicator: false, children: visibleToolbarItems.map((item) => {
|
|
89
|
+
const active = item.action === "toggleBlockHandles"
|
|
90
|
+
? blockHandlesVisible
|
|
91
|
+
: isItemActive(item, state);
|
|
92
|
+
const disabled = isItemDisabled(item, state);
|
|
93
|
+
const accessibilityLabel = item.action === "toggleBlockHandles" && blockHandlesVisible
|
|
94
|
+
? "Hide block handles"
|
|
95
|
+
: item.label;
|
|
96
|
+
return (_jsx(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", accessibilityState: { disabled, selected: active }, disabled: disabled, hitSlop: 6, onPress: () => {
|
|
97
|
+
if (item.action === "toggleBlockHandles") {
|
|
98
|
+
onBlockHandlesVisibleChange?.(!blockHandlesVisible);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (item.action === "openBlockPicker") {
|
|
102
|
+
pickerSelectionRef.current =
|
|
103
|
+
state.selection.type === "text"
|
|
104
|
+
? {
|
|
105
|
+
anchor: state.selection.anchor,
|
|
106
|
+
head: state.selection.head,
|
|
107
|
+
}
|
|
108
|
+
: undefined;
|
|
109
|
+
setBlockPickerOpen(true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (item.action === "openLinkEditor") {
|
|
113
|
+
setLinkValue("");
|
|
114
|
+
setLinkEditorOpen(true);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!item.command) {
|
|
118
|
+
Keyboard.dismiss();
|
|
119
|
+
runCommand(controller.blur());
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
runCommand(controller.command(item.command));
|
|
123
|
+
}, style: ({ pressed }) => [
|
|
124
|
+
styles.button,
|
|
125
|
+
active && { backgroundColor: colors.accent },
|
|
126
|
+
pressed && styles.pressed,
|
|
127
|
+
disabled && styles.disabled,
|
|
128
|
+
], children: _jsx(HugeiconsIcon, { accessible: false, color: active
|
|
129
|
+
? colors.accentText
|
|
130
|
+
: disabled
|
|
131
|
+
? colors.muted
|
|
132
|
+
: colors.text, icon: openEditorIcons[item.icon], size: 20, strokeWidth: 1.8 }) }, item.key));
|
|
133
|
+
}) }) })) : null, _jsx(NativeBlockPickerSheet, { isPresented: blockPickerOpen, onDismiss: () => {
|
|
134
|
+
const block = pendingBlockRef.current;
|
|
135
|
+
pendingBlockRef.current = null;
|
|
136
|
+
pickerSelectionRef.current = undefined;
|
|
137
|
+
if (block) {
|
|
138
|
+
runCommand(controller.command(block).then(() => controller.focus()));
|
|
139
|
+
}
|
|
140
|
+
}, onIsPresentedChange: setBlockPickerOpen, children: _jsxs(NativeColumn, { alignment: "start", spacing: 0, style: { height: "100%", width: "100%" }, children: [_jsxs(NativeRow, { alignment: "center", spacing: 8, style: { paddingBottom: 10, width: "100%" }, children: [_jsx(NativeButton, { modifiers: Platform.OS === "ios"
|
|
141
|
+
? [
|
|
142
|
+
nativeAccessibilityLabel("Cancel"),
|
|
143
|
+
glassEffect({
|
|
144
|
+
glass: { interactive: true, variant: "clear" },
|
|
145
|
+
shape: "circle",
|
|
146
|
+
}),
|
|
147
|
+
]
|
|
148
|
+
: undefined, onPress: () => setBlockPickerOpen(false), style: { borderRadius: 18, height: 36, width: 36 }, testID: "close-block-picker", variant: "text", children: _jsx(NativeRNHostView, { matchContents: true, children: _jsx(View, { style: { height: 18, width: 18 }, children: _jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons.close, size: 18, strokeWidth: 1.8 }) }) }) }), _jsx(NativeSpacer, { flexible: true }), _jsx(NativeText, { testID: "block-picker-title", textStyle: { fontSize: 17, fontWeight: "600" }, children: "Insert block" }), _jsx(NativeSpacer, { flexible: true }), _jsx(NativeButton, { modifiers: Platform.OS === "ios"
|
|
149
|
+
? [
|
|
150
|
+
nativeAccessibilityLabel("Done"),
|
|
151
|
+
glassEffect({
|
|
152
|
+
glass: { interactive: true, variant: "clear" },
|
|
153
|
+
shape: "circle",
|
|
154
|
+
}),
|
|
155
|
+
]
|
|
156
|
+
: undefined, onPress: () => setBlockPickerOpen(false), style: { borderRadius: 18, height: 36, width: 36 }, testID: "done-block-picker", variant: "text", children: _jsx(NativeRNHostView, { matchContents: true, children: _jsx(View, { style: { height: 18, width: 18 }, children: _jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons.check, size: 18, strokeWidth: 1.8 }) }) }) })] }), _jsx(NativeScrollView, { showsIndicators: true, style: { height: "100%", width: "100%" }, children: _jsx(NativeColumn, { alignment: "start", spacing: 20, style: { width: "100%" }, children: blockPickerGroups.map((group) => (_jsxs(NativeColumn, { alignment: "start", spacing: 4, style: { width: "100%" }, children: [_jsx(NativeText, { textStyle: {
|
|
157
|
+
color: colors.muted,
|
|
158
|
+
fontSize: 12,
|
|
159
|
+
fontWeight: "600",
|
|
160
|
+
}, children: group.label }), group.items.map((item) => (_jsx(NativeListItem, { leading: (_jsx(HugeiconsIcon, { accessible: false, color: colors.text, icon: openEditorIcons[item.icon], size: 20, strokeWidth: 1.8 })), modifiers: Platform.OS === "ios"
|
|
161
|
+
? [nativePadding({ vertical: 10 })]
|
|
162
|
+
: undefined, onPress: () => {
|
|
163
|
+
pendingBlockRef.current = {
|
|
164
|
+
type: "insertBlock",
|
|
165
|
+
block: item.key,
|
|
166
|
+
selection: pickerSelectionRef.current,
|
|
167
|
+
};
|
|
168
|
+
setBlockPickerOpen(false);
|
|
169
|
+
}, testID: `insert-${item.key}`, trailing: (_jsx(HugeiconsIcon, { accessible: false, color: colors.muted, icon: openEditorIcons.chevronRight, size: 16, strokeWidth: 1.8 })), children: _jsx(NativeText, { children: item.label }, `${item.key}-label`) }, item.key)))] }, group.label))) }) })] }) }), _jsx(Modal, { animationType: "fade", onRequestClose: () => setLinkEditorOpen(false), presentationStyle: "formSheet", visible: linkEditorOpen, children: _jsxs(View, { style: [styles.linkSheet, { backgroundColor: colors.background }], children: [_jsx(Text, { accessibilityRole: "header", style: [styles.sheetTitle, { color: colors.text }], children: "Edit link" }), _jsx(TextInput, { accessibilityLabel: "Link URL", autoCapitalize: "none", autoCorrect: false, onChangeText: setLinkValue, placeholder: "https://example.com", placeholderTextColor: colors.muted, style: [
|
|
170
|
+
styles.linkInput,
|
|
171
|
+
{ borderColor: colors.border, color: colors.text },
|
|
172
|
+
], value: linkValue }), _jsxs(View, { style: styles.linkActions, children: [_jsx(Pressable, { accessibilityLabel: "Cancel link editing", accessibilityRole: "button", onPress: () => setLinkEditorOpen(false), style: styles.linkAction, children: _jsx(Text, { style: { color: colors.text }, children: "Cancel" }) }), _jsx(Pressable, { accessibilityLabel: "Remove", accessibilityRole: "button", onPress: () => {
|
|
173
|
+
setLinkEditorOpen(false);
|
|
174
|
+
runCommand(controller.command({ type: "setLink", href: null }));
|
|
175
|
+
}, style: styles.linkAction, children: _jsx(Text, { style: { color: colors.text }, children: "Remove" }) }), _jsx(Pressable, { accessibilityLabel: "Apply", accessibilityRole: "button", disabled: !linkValue.trim(), onPress: () => {
|
|
176
|
+
const href = linkValue.trim();
|
|
177
|
+
setLinkEditorOpen(false);
|
|
178
|
+
runCommand(controller.command({ type: "setLink", href }));
|
|
179
|
+
}, style: [styles.linkAction, !linkValue.trim() && styles.disabled], children: _jsx(Text, { style: { color: colors.accent }, children: "Apply" }) })] })] }) })] }));
|
|
180
|
+
});
|
|
181
|
+
const styles = StyleSheet.create({
|
|
182
|
+
root: {
|
|
183
|
+
minHeight: 46,
|
|
184
|
+
},
|
|
185
|
+
content: {
|
|
186
|
+
alignItems: "center",
|
|
187
|
+
gap: 6,
|
|
188
|
+
paddingHorizontal: 8,
|
|
189
|
+
paddingVertical: 7,
|
|
190
|
+
},
|
|
191
|
+
button: {
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
borderRadius: TOOLBAR_BUTTON_SIZE / 2,
|
|
194
|
+
height: TOOLBAR_BUTTON_SIZE,
|
|
195
|
+
justifyContent: "center",
|
|
196
|
+
width: TOOLBAR_BUTTON_SIZE,
|
|
197
|
+
},
|
|
198
|
+
pressed: { opacity: 0.64 },
|
|
199
|
+
disabled: { opacity: 0.42 },
|
|
200
|
+
sheetTitle: { fontSize: 18, fontWeight: "700" },
|
|
201
|
+
linkSheet: { flex: 1, gap: 20, justifyContent: "center", padding: 24 },
|
|
202
|
+
linkInput: {
|
|
203
|
+
borderRadius: 10,
|
|
204
|
+
borderWidth: 1,
|
|
205
|
+
fontSize: 16,
|
|
206
|
+
minHeight: 48,
|
|
207
|
+
paddingHorizontal: 14,
|
|
208
|
+
},
|
|
209
|
+
linkActions: { flexDirection: "row", justifyContent: "flex-end", gap: 18 },
|
|
210
|
+
linkAction: { minHeight: 44, justifyContent: "center", paddingHorizontal: 4 },
|
|
211
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { OpenEditorRuntimeCommand } from "@openeditor/embedded-runtime";
|
|
2
|
+
import type { OpenEditorIconName } from "@openeditor/icons";
|
|
3
|
+
export type OpenEditorNativeToolbarItem = {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: OpenEditorIconName;
|
|
7
|
+
command?: OpenEditorRuntimeCommand;
|
|
8
|
+
action?: "openBlockPicker" | "openLinkEditor" | "toggleBlockHandles" | "dismissKeyboard";
|
|
9
|
+
activeWhen?: {
|
|
10
|
+
block?: string;
|
|
11
|
+
headingLevel?: number;
|
|
12
|
+
mark?: string;
|
|
13
|
+
table?: "headerRow" | "headerColumn";
|
|
14
|
+
};
|
|
15
|
+
disabledWhen?: "cannotUndo" | "cannotRedo" | "cannotMergeCells";
|
|
16
|
+
};
|
|
17
|
+
export declare const defaultOpenEditorNativeToolbarItems: readonly OpenEditorNativeToolbarItem[];
|
|
18
|
+
export declare const openEditorNativeBlockPickerItems: readonly [{
|
|
19
|
+
readonly key: "paragraph";
|
|
20
|
+
readonly label: "Text";
|
|
21
|
+
readonly group: "Text";
|
|
22
|
+
readonly icon: "text";
|
|
23
|
+
}, {
|
|
24
|
+
readonly key: "heading1";
|
|
25
|
+
readonly label: "Heading 1";
|
|
26
|
+
readonly group: "Text";
|
|
27
|
+
readonly icon: "heading1";
|
|
28
|
+
}, {
|
|
29
|
+
readonly key: "heading2";
|
|
30
|
+
readonly label: "Heading 2";
|
|
31
|
+
readonly group: "Text";
|
|
32
|
+
readonly icon: "heading2";
|
|
33
|
+
}, {
|
|
34
|
+
readonly key: "heading3";
|
|
35
|
+
readonly label: "Heading 3";
|
|
36
|
+
readonly group: "Text";
|
|
37
|
+
readonly icon: "heading3";
|
|
38
|
+
}, {
|
|
39
|
+
readonly key: "bulletList";
|
|
40
|
+
readonly label: "Bullet list";
|
|
41
|
+
readonly group: "Text";
|
|
42
|
+
readonly icon: "bulletList";
|
|
43
|
+
}, {
|
|
44
|
+
readonly key: "orderedList";
|
|
45
|
+
readonly label: "Numbered list";
|
|
46
|
+
readonly group: "Text";
|
|
47
|
+
readonly icon: "orderedList";
|
|
48
|
+
}, {
|
|
49
|
+
readonly key: "taskList";
|
|
50
|
+
readonly label: "Task list";
|
|
51
|
+
readonly group: "Text";
|
|
52
|
+
readonly icon: "taskList";
|
|
53
|
+
}, {
|
|
54
|
+
readonly key: "toggleList";
|
|
55
|
+
readonly label: "Toggle list";
|
|
56
|
+
readonly group: "Text";
|
|
57
|
+
readonly icon: "toggleList";
|
|
58
|
+
}, {
|
|
59
|
+
readonly key: "blockquote";
|
|
60
|
+
readonly label: "Quote";
|
|
61
|
+
readonly group: "Text";
|
|
62
|
+
readonly icon: "blockquote";
|
|
63
|
+
}, {
|
|
64
|
+
readonly key: "codeBlock";
|
|
65
|
+
readonly label: "Code block";
|
|
66
|
+
readonly group: "Text";
|
|
67
|
+
readonly icon: "codeBlock";
|
|
68
|
+
}, {
|
|
69
|
+
readonly key: "divider";
|
|
70
|
+
readonly label: "Divider";
|
|
71
|
+
readonly group: "Structure";
|
|
72
|
+
readonly icon: "divider";
|
|
73
|
+
}, {
|
|
74
|
+
readonly key: "columns";
|
|
75
|
+
readonly label: "Columns";
|
|
76
|
+
readonly group: "Layout";
|
|
77
|
+
readonly icon: "columns";
|
|
78
|
+
}, {
|
|
79
|
+
readonly key: "table";
|
|
80
|
+
readonly label: "Table";
|
|
81
|
+
readonly group: "Layout";
|
|
82
|
+
readonly icon: "table";
|
|
83
|
+
}, {
|
|
84
|
+
readonly key: "callout";
|
|
85
|
+
readonly label: "Callout";
|
|
86
|
+
readonly group: "Embed";
|
|
87
|
+
readonly icon: "callout";
|
|
88
|
+
}, {
|
|
89
|
+
readonly key: "diagram";
|
|
90
|
+
readonly label: "Diagram";
|
|
91
|
+
readonly group: "Embed";
|
|
92
|
+
readonly icon: "diagram";
|
|
93
|
+
}, {
|
|
94
|
+
readonly key: "page";
|
|
95
|
+
readonly label: "Page";
|
|
96
|
+
readonly group: "Embed";
|
|
97
|
+
readonly icon: "page";
|
|
98
|
+
}, {
|
|
99
|
+
readonly key: "image";
|
|
100
|
+
readonly label: "Image";
|
|
101
|
+
readonly group: "Media";
|
|
102
|
+
readonly icon: "image";
|
|
103
|
+
}, {
|
|
104
|
+
readonly key: "attachment";
|
|
105
|
+
readonly label: "File";
|
|
106
|
+
readonly group: "Media";
|
|
107
|
+
readonly icon: "attachment";
|
|
108
|
+
}];
|
|
109
|
+
export declare const openEditorNativeTableToolbarItems: readonly OpenEditorNativeToolbarItem[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const defaultOpenEditorNativeToolbarItems = [
|
|
2
|
+
{ key: "insert", label: "Insert block", icon: "addBlock", action: "openBlockPicker" },
|
|
3
|
+
{ key: "handles", label: "Show block handles", icon: "dragHandle", action: "toggleBlockHandles" },
|
|
4
|
+
{ key: "paragraph", label: "Text", icon: "text", command: { type: "setParagraph" }, activeWhen: { block: "paragraph" } },
|
|
5
|
+
{ key: "heading-1", label: "Heading 1", icon: "heading1", command: { type: "toggleHeading", level: 1 }, activeWhen: { block: "heading", headingLevel: 1 } },
|
|
6
|
+
{ key: "heading-2", label: "Heading 2", icon: "heading2", command: { type: "toggleHeading", level: 2 }, activeWhen: { block: "heading", headingLevel: 2 } },
|
|
7
|
+
{ key: "bold", label: "Bold", icon: "bold", command: { type: "toggleMark", mark: "bold" }, activeWhen: { mark: "bold" } },
|
|
8
|
+
{ key: "italic", label: "Italic", icon: "italic", command: { type: "toggleMark", mark: "italic" }, activeWhen: { mark: "italic" } },
|
|
9
|
+
{ key: "underline", label: "Underline", icon: "underline", command: { type: "toggleMark", mark: "underline" }, activeWhen: { mark: "underline" } },
|
|
10
|
+
{ key: "strike", label: "Strikethrough", icon: "strike", command: { type: "toggleMark", mark: "strike" }, activeWhen: { mark: "strike" } },
|
|
11
|
+
{ key: "code", label: "Inline code", icon: "code", command: { type: "toggleMark", mark: "code" }, activeWhen: { mark: "code" } },
|
|
12
|
+
{ key: "link", label: "Link", icon: "link", action: "openLinkEditor", activeWhen: { mark: "link" } },
|
|
13
|
+
{ key: "bullet-list", label: "Bullet list", icon: "bulletList", command: { type: "toggleList", list: "bullet" }, activeWhen: { block: "bulletList" } },
|
|
14
|
+
{ key: "ordered-list", label: "Numbered list", icon: "orderedList", command: { type: "toggleList", list: "ordered" }, activeWhen: { block: "orderedList" } },
|
|
15
|
+
{ key: "task-list", label: "Task list", icon: "taskList", command: { type: "toggleList", list: "task" }, activeWhen: { block: "taskList" } },
|
|
16
|
+
{ key: "quote", label: "Quote", icon: "blockquote", command: { type: "toggleBlockquote" }, activeWhen: { block: "blockquote" } },
|
|
17
|
+
{ key: "code-block", label: "Code block", icon: "codeBlock", command: { type: "toggleCodeBlock" }, activeWhen: { block: "codeBlock" } },
|
|
18
|
+
{ key: "undo", label: "Undo", icon: "undo", command: { type: "undo" }, disabledWhen: "cannotUndo" },
|
|
19
|
+
{ key: "redo", label: "Redo", icon: "redo", command: { type: "redo" }, disabledWhen: "cannotRedo" },
|
|
20
|
+
{ key: "keyboard", label: "Dismiss keyboard", icon: "keyboard", action: "dismissKeyboard" },
|
|
21
|
+
];
|
|
22
|
+
export const openEditorNativeBlockPickerItems = [
|
|
23
|
+
{ key: "paragraph", label: "Text", group: "Text", icon: "text" },
|
|
24
|
+
{ key: "heading1", label: "Heading 1", group: "Text", icon: "heading1" },
|
|
25
|
+
{ key: "heading2", label: "Heading 2", group: "Text", icon: "heading2" },
|
|
26
|
+
{ key: "heading3", label: "Heading 3", group: "Text", icon: "heading3" },
|
|
27
|
+
{ key: "bulletList", label: "Bullet list", group: "Text", icon: "bulletList" },
|
|
28
|
+
{ key: "orderedList", label: "Numbered list", group: "Text", icon: "orderedList" },
|
|
29
|
+
{ key: "taskList", label: "Task list", group: "Text", icon: "taskList" },
|
|
30
|
+
{ key: "toggleList", label: "Toggle list", group: "Text", icon: "toggleList" },
|
|
31
|
+
{ key: "blockquote", label: "Quote", group: "Text", icon: "blockquote" },
|
|
32
|
+
{ key: "codeBlock", label: "Code block", group: "Text", icon: "codeBlock" },
|
|
33
|
+
{ key: "divider", label: "Divider", group: "Structure", icon: "divider" },
|
|
34
|
+
{ key: "columns", label: "Columns", group: "Layout", icon: "columns" },
|
|
35
|
+
{ key: "table", label: "Table", group: "Layout", icon: "table" },
|
|
36
|
+
{ key: "callout", label: "Callout", group: "Embed", icon: "callout" },
|
|
37
|
+
{ key: "diagram", label: "Diagram", group: "Embed", icon: "diagram" },
|
|
38
|
+
{ key: "page", label: "Page", group: "Embed", icon: "page" },
|
|
39
|
+
{ key: "image", label: "Image", group: "Media", icon: "image" },
|
|
40
|
+
{ key: "attachment", label: "File", group: "Media", icon: "attachment" },
|
|
41
|
+
];
|
|
42
|
+
export const openEditorNativeTableToolbarItems = [
|
|
43
|
+
{ key: "table-row-before", label: "Insert row above", icon: "tableRowInsertBefore", command: { type: "addTableRow", after: false } },
|
|
44
|
+
{ key: "table-row-after", label: "Insert row below", icon: "tableRowInsertAfter", command: { type: "addTableRow" } },
|
|
45
|
+
{ key: "table-delete-row", label: "Delete row", icon: "tableRowDelete", command: { type: "deleteTableRow" } },
|
|
46
|
+
{ key: "table-column-before", label: "Insert column left", icon: "tableColumnInsertBefore", command: { type: "addTableColumn", after: false } },
|
|
47
|
+
{ key: "table-column-after", label: "Insert column right", icon: "tableColumnInsertAfter", command: { type: "addTableColumn" } },
|
|
48
|
+
{ key: "table-delete-column", label: "Delete column", icon: "tableColumnDelete", command: { type: "deleteTableColumn" } },
|
|
49
|
+
{ key: "table-header-row", label: "Toggle header row", icon: "tableHeaderRow", command: { type: "toggleTableHeaderRow" }, activeWhen: { table: "headerRow" } },
|
|
50
|
+
{ key: "table-header-column", label: "Toggle header column", icon: "tableHeaderColumn", command: { type: "toggleTableHeaderColumn" }, activeWhen: { table: "headerColumn" } },
|
|
51
|
+
{ key: "table-merge-cells", label: "Merge selected cells", icon: "mergeCells", command: { type: "mergeTableCells" }, disabledWhen: "cannotMergeCells" },
|
|
52
|
+
{ key: "table-split-cell", label: "Split cell", icon: "splitCells", command: { type: "splitTableCell" } },
|
|
53
|
+
{ key: "table-delete", label: "Delete table", icon: "delete", command: { type: "deleteTable" } },
|
|
54
|
+
{ key: "table-undo", label: "Undo", icon: "undo", command: { type: "undo" }, disabledWhen: "cannotUndo" },
|
|
55
|
+
{ key: "table-redo", label: "Redo", icon: "redo", command: { type: "redo" }, disabledWhen: "cannotRedo" },
|
|
56
|
+
{ key: "table-keyboard", label: "Dismiss keyboard", icon: "keyboard", action: "dismissKeyboard" },
|
|
57
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type GlassColorScheme } from "expo-glass-effect";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
4
|
+
import type { OpenEditorNativeTheme } from "./native-editor.js";
|
|
5
|
+
export type OpenEditorNativeToolbarSurfaceProps = {
|
|
6
|
+
active?: boolean;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
colorScheme?: GlassColorScheme;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
theme?: OpenEditorNativeTheme;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The platform material surrounding OpenEditor's keyboard toolbar.
|
|
14
|
+
* iOS uses the system glass implementation when available; other runtimes
|
|
15
|
+
* receive the same geometry with a theme-aware opaque fallback.
|
|
16
|
+
*/
|
|
17
|
+
export declare function OpenEditorNativeToolbarSurface({ active, children, colorScheme, style, theme, }: OpenEditorNativeToolbarSurfaceProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { GlassView, isGlassEffectAPIAvailable, } from "expo-glass-effect";
|
|
3
|
+
import { Platform, StyleSheet, View, } from "react-native";
|
|
4
|
+
import { OPENEDITOR_NATIVE_TOOLBAR_HEIGHT, OPENEDITOR_NATIVE_TOOLBAR_RADIUS, } from "./native-layout.js";
|
|
5
|
+
/**
|
|
6
|
+
* The platform material surrounding OpenEditor's keyboard toolbar.
|
|
7
|
+
* iOS uses the system glass implementation when available; other runtimes
|
|
8
|
+
* receive the same geometry with a theme-aware opaque fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function OpenEditorNativeToolbarSurface({ active = true, children, colorScheme = "auto", style, theme, }) {
|
|
11
|
+
if (Platform.OS === "ios" && isGlassEffectAPIAvailable()) {
|
|
12
|
+
return (_jsx(GlassView, { colorScheme: colorScheme, glassEffectStyle: active ? "regular" : "none", isInteractive: true, style: [styles.surface, style], children: children }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsx(View, { style: [
|
|
15
|
+
styles.surface,
|
|
16
|
+
styles.fallback,
|
|
17
|
+
{
|
|
18
|
+
backgroundColor: theme?.surfaceRaised ?? theme?.surfaceMuted ?? theme?.surface ?? "#ffffff",
|
|
19
|
+
borderColor: theme?.border ?? "#e5e7eb",
|
|
20
|
+
},
|
|
21
|
+
style,
|
|
22
|
+
], children: children }));
|
|
23
|
+
}
|
|
24
|
+
const styles = StyleSheet.create({
|
|
25
|
+
surface: {
|
|
26
|
+
borderRadius: OPENEDITOR_NATIVE_TOOLBAR_RADIUS,
|
|
27
|
+
height: OPENEDITOR_NATIVE_TOOLBAR_HEIGHT,
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
},
|
|
30
|
+
fallback: {
|
|
31
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
32
|
+
},
|
|
33
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"plugin.cjs"
|
|
19
20
|
],
|
|
20
21
|
"exports": {
|
|
21
22
|
".": {
|
|
@@ -23,28 +24,26 @@
|
|
|
23
24
|
"types": "./dist/index.d.ts",
|
|
24
25
|
"import": "./dist/index.js"
|
|
25
26
|
},
|
|
27
|
+
"./plugin": "./plugin.cjs",
|
|
26
28
|
"./package.json": "./package.json"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
|
-
"@expo/
|
|
30
|
-
"@expo/
|
|
31
|
-
"@
|
|
32
|
-
"@openeditor/
|
|
33
|
-
"@openeditor/
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"react": "19.2.3",
|
|
37
|
-
"react-native": "0.85.3"
|
|
31
|
+
"@expo/config-plugins": "57.0.6",
|
|
32
|
+
"@expo/ui": "~57.0.7",
|
|
33
|
+
"@hugeicons/react-native": "^1.0.15",
|
|
34
|
+
"@openeditor/core": "0.0.35",
|
|
35
|
+
"@openeditor/embedded-runtime": "0.0.35",
|
|
36
|
+
"@openeditor/embedded-surface": "0.0.35",
|
|
37
|
+
"@openeditor/icons": "0.0.35"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"
|
|
41
|
-
"react-native-keyboard-controller": ">=1.21",
|
|
40
|
+
"expo": ">=57",
|
|
42
41
|
"react": ">=19",
|
|
43
|
-
"react-native": ">=0.85"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"react-native-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
"react-native": ">=0.85",
|
|
43
|
+
"expo-glass-effect": ">=57",
|
|
44
|
+
"react-native-keyboard-controller": ">=1.21",
|
|
45
|
+
"react-native-reanimated": ">=3",
|
|
46
|
+
"react-native-svg": ">=15",
|
|
47
|
+
"react-native-webview": ">=13"
|
|
49
48
|
}
|
|
50
49
|
}
|
package/plugin.cjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { withAppDelegate } = require("@expo/config-plugins");
|
|
2
|
+
|
|
3
|
+
const WEBKIT_IMPORT = "import WebKit\n";
|
|
4
|
+
const OBSERVER_PROPERTY =
|
|
5
|
+
" private var openEditorKeyboardWillShowObserver: NSObjectProtocol?\n";
|
|
6
|
+
const OBSERVER_SETUP = ` openEditorKeyboardWillShowObserver = NotificationCenter.default.addObserver(
|
|
7
|
+
forName: UIResponder.keyboardWillShowNotification,
|
|
8
|
+
object: nil,
|
|
9
|
+
queue: .main
|
|
10
|
+
) { [weak self] _ in
|
|
11
|
+
guard let rootView = self?.window?.rootViewController?.view else { return }
|
|
12
|
+
self?.enableOpenEditorInteractiveKeyboardDismissal(in: rootView)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
`;
|
|
16
|
+
const DISMISSAL_METHOD = ` deinit {
|
|
17
|
+
if let openEditorKeyboardWillShowObserver {
|
|
18
|
+
NotificationCenter.default.removeObserver(openEditorKeyboardWillShowObserver)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private func enableOpenEditorInteractiveKeyboardDismissal(in view: UIView) {
|
|
23
|
+
if let webView = view as? WKWebView {
|
|
24
|
+
webView.scrollView.keyboardDismissMode = .interactive
|
|
25
|
+
}
|
|
26
|
+
view.subviews.forEach { enableOpenEditorInteractiveKeyboardDismissal(in: $0) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const applyOpenEditorKeyboardDismissal = (contents) => {
|
|
32
|
+
for (const anchor of [
|
|
33
|
+
"import React\n",
|
|
34
|
+
"class AppDelegate: ExpoAppDelegate {\n",
|
|
35
|
+
" return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n",
|
|
36
|
+
" // Linking API\n",
|
|
37
|
+
]) {
|
|
38
|
+
if (!contents.includes(anchor)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`OpenEditor's Expo plugin could not find ${JSON.stringify(anchor)} in AppDelegate.swift.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let next = contents;
|
|
46
|
+
if (!next.includes("import WebKit")) {
|
|
47
|
+
next = next.replace("import React\n", `import React\n${WEBKIT_IMPORT}`);
|
|
48
|
+
}
|
|
49
|
+
if (!next.includes("openEditorKeyboardWillShowObserver")) {
|
|
50
|
+
next = next.replace(
|
|
51
|
+
"class AppDelegate: ExpoAppDelegate {\n",
|
|
52
|
+
`class AppDelegate: ExpoAppDelegate {\n${OBSERVER_PROPERTY}`,
|
|
53
|
+
);
|
|
54
|
+
next = next.replace(
|
|
55
|
+
" return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n",
|
|
56
|
+
`${OBSERVER_SETUP} return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n`,
|
|
57
|
+
);
|
|
58
|
+
next = next.replace(
|
|
59
|
+
" // Linking API\n",
|
|
60
|
+
`${DISMISSAL_METHOD} // Linking API\n`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return next;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const withOpenEditorNative = (config) =>
|
|
67
|
+
withAppDelegate(config, (mod) => {
|
|
68
|
+
if (mod.modResults.language !== "swift") {
|
|
69
|
+
throw new Error("OpenEditor's Expo plugin requires a Swift AppDelegate.");
|
|
70
|
+
}
|
|
71
|
+
mod.modResults.contents = applyOpenEditorKeyboardDismissal(
|
|
72
|
+
mod.modResults.contents,
|
|
73
|
+
);
|
|
74
|
+
return mod;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
module.exports = withOpenEditorNative;
|
|
78
|
+
module.exports.applyOpenEditorKeyboardDismissal =
|
|
79
|
+
applyOpenEditorKeyboardDismissal;
|
package/dist/document.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type OpenEditorDocument, type OpenEditorDocumentMeta, type ProseMirrorDocument, type ProseMirrorNode } from "@openeditor/core";
|
|
2
|
-
export declare const sanitizeNativeEditorDocument: (document: OpenEditorDocument, meta?: OpenEditorDocumentMeta | undefined) => OpenEditorDocument;
|
|
3
|
-
export declare const toNativeEditorNode: (node: ProseMirrorNode) => ProseMirrorNode;
|
|
4
|
-
export declare const fromNativeEditorNode: (node: ProseMirrorNode) => ProseMirrorNode;
|
|
5
|
-
export declare const toNativeEditorDocument: (document: OpenEditorDocument) => ProseMirrorDocument;
|
|
6
|
-
export declare const fromNativeEditorDocument: (document: ProseMirrorDocument, meta?: OpenEditorDocumentMeta) => OpenEditorDocument;
|
|
7
|
-
export declare const createNativeEditorDocument: (content: ProseMirrorNode[], meta?: OpenEditorDocumentMeta) => ProseMirrorDocument;
|