@openeditor/native 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/README.md +2 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +64 -7
- package/dist/toolbar.d.ts +1 -0
- package/dist/toolbar.js +8 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Component-first React Native SDK for OpenEditor.
|
|
4
4
|
|
|
5
|
+
Pass `attachmentRuntime` to use the system picker and host storage lifecycle. The native toolbar exposes “Upload file,” native surfaces render attachment cards, and the viewer delegates opening/sharing to the runtime.
|
|
6
|
+
|
|
5
7
|
## Public surface
|
|
6
8
|
|
|
7
9
|
- `OpenEditorNative` as the primary editing surface
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type OpenEditorDocument, type ProseMirrorNode } from "@openeditor/core";
|
|
1
|
+
import { type OpenEditorDocument, type OpenEditorAttachmentRuntime, type ProseMirrorNode } from "@openeditor/core";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import type { NativeRichTextEditorToolbarPlacement } from "@openeditor/react-native-prose-editor";
|
|
4
4
|
export { normalizeNativeThemeColor } from "./theme.js";
|
|
@@ -16,6 +16,7 @@ export type OpenEditorNativeProps = {
|
|
|
16
16
|
icon?: string | null;
|
|
17
17
|
href?: string | null;
|
|
18
18
|
}) => void;
|
|
19
|
+
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
19
20
|
};
|
|
20
21
|
export type OpenEditorNativeViewerProps = Omit<OpenEditorNativeProps, "editable" | "showToolbar" | "onChange"> & {
|
|
21
22
|
showToolbar?: false;
|
|
@@ -67,8 +68,8 @@ export type OpenEditorNativeTheme = {
|
|
|
67
68
|
toolbar?: OpenEditorNativeToolbarTheme;
|
|
68
69
|
};
|
|
69
70
|
export declare const OpenEditorNativeToolbar: ({ actions, onActionPress, theme, }: OpenEditorNativeToolbarProps) => import("react").JSX.Element;
|
|
70
|
-
export declare const OpenEditorNative: ({ document, editable, placeholder, showToolbar, theme, toolbarPlacement, onChange, onOpenPage, }: OpenEditorNativeProps) => import("react").JSX.Element;
|
|
71
|
-
export declare const OpenEditorNativeViewer: ({ document, theme, renderers, onOpenPage, }: OpenEditorNativeViewerProps) => import("react").JSX.Element;
|
|
71
|
+
export declare const OpenEditorNative: ({ document, editable, placeholder, showToolbar, theme, toolbarPlacement, onChange, onOpenPage, attachmentRuntime, }: OpenEditorNativeProps) => import("react").JSX.Element;
|
|
72
|
+
export declare const OpenEditorNativeViewer: ({ document, theme, renderers, onOpenPage, attachmentRuntime, }: OpenEditorNativeViewerProps) => import("react").JSX.Element;
|
|
72
73
|
export { createNativeEditorDocument, fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
|
|
73
74
|
export { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, nativeToolbarParityCoverage, openEditorNativeToolbarActionKeys, openEditorNativeToolbarItems, } from "./toolbar.js";
|
|
74
75
|
export type { OpenEditorNativeToolbarActionKey } from "./toolbar.js";
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,21 @@ const openEditorNativeSchema = {
|
|
|
86
86
|
href: { default: null },
|
|
87
87
|
},
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: "attachment",
|
|
91
|
+
content: "",
|
|
92
|
+
group: "block",
|
|
93
|
+
role: "block",
|
|
94
|
+
htmlTag: "div",
|
|
95
|
+
isVoid: true,
|
|
96
|
+
attrs: {
|
|
97
|
+
attachmentId: { default: null },
|
|
98
|
+
name: { default: "" },
|
|
99
|
+
mimeType: { default: null },
|
|
100
|
+
size: { default: null },
|
|
101
|
+
url: { default: null },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
89
104
|
{ name: "codeBlock", content: "text*", group: "block", role: "textBlock", htmlTag: "pre" },
|
|
90
105
|
{ name: "horizontalRule", content: "", group: "block", role: "block", htmlTag: "hr", isVoid: true },
|
|
91
106
|
{
|
|
@@ -319,11 +334,11 @@ const nativeSeedDocument = createDocument([
|
|
|
319
334
|
{ type: "horizontalRule" },
|
|
320
335
|
{ type: "image", attrs: { src: "https://placehold.co/960x420/png", alt: "Placeholder" } },
|
|
321
336
|
]);
|
|
322
|
-
const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage) => {
|
|
337
|
+
const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage, attachmentRuntime) => {
|
|
323
338
|
if (node.type === "text") {
|
|
324
339
|
return _jsx(Text, { style: { color: theme.text }, children: node.text ?? "" }, key);
|
|
325
340
|
}
|
|
326
|
-
const children = node.content?.map((child, index) => renderNativeViewerNode(child, `${key}-${index}`, theme, renderers, onOpenPage)) ?? null;
|
|
341
|
+
const children = node.content?.map((child, index) => renderNativeViewerNode(child, `${key}-${index}`, theme, renderers, onOpenPage, attachmentRuntime)) ?? null;
|
|
327
342
|
const blockKey = findBlockSpecForNode(defaultBlockRegistry, node)?.name ?? node.type;
|
|
328
343
|
const customRenderer = renderers?.[blockKey];
|
|
329
344
|
if (customRenderer) {
|
|
@@ -360,6 +375,17 @@ const renderNativeViewerNode = (node, key, theme, renderers, onOpenPage) => {
|
|
|
360
375
|
const content = _jsxs(_Fragment, { children: [_jsx(Text, { style: { fontSize: 18 }, children: icon }), _jsx(Text, { style: { color: theme.text, flex: 1, fontWeight: "600", textDecorationLine: "underline" }, children: title }), _jsx(Text, { style: { color: theme.muted }, children: "\u2192" })] });
|
|
361
376
|
return onOpenPage && pageId ? (_jsx(Pressable, { accessibilityRole: "button", onPress: () => onOpenPage({ pageId, title, icon, href: typeof node.attrs?.href === "string" ? node.attrs.href : null }), style: { alignItems: "center", borderRadius: 8, flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content }, key)) : _jsx(View, { style: { alignItems: "center", flexDirection: "row", gap: 8, marginBottom: 8, padding: 8 }, children: content }, key);
|
|
362
377
|
}
|
|
378
|
+
case "attachment": {
|
|
379
|
+
const snapshot = {
|
|
380
|
+
attachmentId: typeof node.attrs?.attachmentId === "string" ? node.attrs.attachmentId : null,
|
|
381
|
+
name: typeof node.attrs?.name === "string" ? node.attrs.name : "Attachment",
|
|
382
|
+
mimeType: typeof node.attrs?.mimeType === "string" ? node.attrs.mimeType : null,
|
|
383
|
+
size: typeof node.attrs?.size === "number" ? node.attrs.size : null,
|
|
384
|
+
url: typeof node.attrs?.url === "string" ? node.attrs.url : null,
|
|
385
|
+
};
|
|
386
|
+
const content = _jsxs(_Fragment, { children: [_jsx(Text, { style: { fontSize: 22 }, children: "\uD83D\uDCCE" }), _jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { numberOfLines: 1, style: { color: theme.text, fontWeight: "600" }, children: snapshot.name }), _jsx(Text, { style: { color: theme.muted, fontSize: 12 }, children: snapshot.mimeType ?? "File" })] }), _jsx(Text, { style: { color: theme.muted }, children: "Open" })] });
|
|
387
|
+
return attachmentRuntime?.openAttachment ? _jsx(Pressable, { accessibilityLabel: `Open ${snapshot.name}`, accessibilityRole: "button", onPress: () => void attachmentRuntime.openAttachment?.(snapshot), style: { alignItems: "center", borderColor: theme.border, borderRadius: 10, borderWidth: 1, flexDirection: "row", gap: 10, marginBottom: 8, minHeight: 64, padding: 10 }, children: content }, key) : _jsx(View, { style: { alignItems: "center", borderColor: theme.border, borderRadius: 10, borderWidth: 1, flexDirection: "row", gap: 10, marginBottom: 8, minHeight: 64, padding: 10 }, children: content }, key);
|
|
388
|
+
}
|
|
363
389
|
default:
|
|
364
390
|
if (node.type === "column") {
|
|
365
391
|
return _jsx(View, { style: { flex: 1 }, children: children }, key);
|
|
@@ -396,7 +422,7 @@ export const OpenEditorNativeToolbar = ({ actions = [
|
|
|
396
422
|
onActionPress?.(action.key);
|
|
397
423
|
}, style: [styles.toolbarButton, { backgroundColor: resolvedTheme.accent, borderColor: resolvedTheme.borderStrong }], children: _jsx(Text, { style: [styles.toolbarButtonText, { color: resolvedTheme.accentText }], children: action.label }) }, action.key))) }));
|
|
398
424
|
};
|
|
399
|
-
export const OpenEditorNative = ({ document = nativeSeedDocument, editable = true, placeholder = "Start writing...", showToolbar = true, theme, toolbarPlacement = "keyboard", onChange, onOpenPage, }) => {
|
|
425
|
+
export const OpenEditorNative = ({ document = nativeSeedDocument, editable = true, placeholder = "Start writing...", showToolbar = true, theme, toolbarPlacement = "keyboard", onChange, onOpenPage, attachmentRuntime, }) => {
|
|
400
426
|
const editorRef = useRef(null);
|
|
401
427
|
const [currentDocument, setCurrentDocument] = useState(() => normalizeForNativeEditor(document));
|
|
402
428
|
const [selection, setSelection] = useState();
|
|
@@ -404,6 +430,8 @@ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = tru
|
|
|
404
430
|
const [linkUrl, setLinkUrl] = useState("https://");
|
|
405
431
|
const [imageRequest, setImageRequest] = useState(null);
|
|
406
432
|
const [imageUrl, setImageUrl] = useState("https://placehold.co/960x420/png");
|
|
433
|
+
const [attachmentUpload, setAttachmentUpload] = useState(null);
|
|
434
|
+
const attachmentAbortRef = useRef(null);
|
|
407
435
|
const [history, setHistory] = useState({
|
|
408
436
|
undoStack: [],
|
|
409
437
|
redoStack: [],
|
|
@@ -445,6 +473,31 @@ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = tru
|
|
|
445
473
|
}
|
|
446
474
|
syncDocument(moveTopLevelBlock(nativeDocument, selectedIndex, nextIndex), true);
|
|
447
475
|
};
|
|
476
|
+
const uploadAttachment = async () => {
|
|
477
|
+
if (!attachmentRuntime?.selectAttachment || !attachmentRuntime.uploadAttachment)
|
|
478
|
+
return;
|
|
479
|
+
attachmentAbortRef.current?.abort();
|
|
480
|
+
const controller = new AbortController();
|
|
481
|
+
attachmentAbortRef.current = controller;
|
|
482
|
+
try {
|
|
483
|
+
const input = await attachmentRuntime.selectAttachment({ signal: controller.signal });
|
|
484
|
+
if (!input || controller.signal.aborted)
|
|
485
|
+
return;
|
|
486
|
+
setAttachmentUpload({ input, progress: 0, error: null });
|
|
487
|
+
const validation = await attachmentRuntime.validateAttachment?.(input);
|
|
488
|
+
if (validation && !validation.accepted)
|
|
489
|
+
throw new Error(validation.message);
|
|
490
|
+
const snapshot = await attachmentRuntime.uploadAttachment(input, { signal: controller.signal, onProgress: (progress) => setAttachmentUpload((current) => current ? { ...current, progress } : current) });
|
|
491
|
+
if (controller.signal.aborted)
|
|
492
|
+
return;
|
|
493
|
+
editorRef.current?.insertContentJson({ type: "doc", content: [{ type: "attachment", attrs: snapshot }] });
|
|
494
|
+
setAttachmentUpload(null);
|
|
495
|
+
}
|
|
496
|
+
catch (cause) {
|
|
497
|
+
if (!controller.signal.aborted)
|
|
498
|
+
setAttachmentUpload((current) => current ? { ...current, error: cause instanceof Error ? cause.message : "Upload failed." } : current);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
448
501
|
const handleToolbarAction = (key) => {
|
|
449
502
|
if (key === openEditorNativeToolbarActionKeys.dismissKeyboard) {
|
|
450
503
|
dismissKeyboard();
|
|
@@ -458,6 +511,10 @@ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = tru
|
|
|
458
511
|
moveSelectedBlock(1);
|
|
459
512
|
return;
|
|
460
513
|
}
|
|
514
|
+
if (key === openEditorNativeToolbarActionKeys.attachment) {
|
|
515
|
+
void uploadAttachment();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
461
518
|
if (key === openEditorNativeToolbarActionKeys.undo) {
|
|
462
519
|
const previous = history.undoStack[history.undoStack.length - 1];
|
|
463
520
|
if (!previous) {
|
|
@@ -536,11 +593,11 @@ export const OpenEditorNative = ({ document = nativeSeedDocument, editable = tru
|
|
|
536
593
|
}, onRequestImage: (request) => {
|
|
537
594
|
setImageUrl("https://placehold.co/960x420/png");
|
|
538
595
|
setImageRequest(request);
|
|
539
|
-
}, onOpenPage: onOpenPage, onSelectionChange: setSelection, onContentChangeJSON: (json) => {
|
|
596
|
+
}, onOpenPage: onOpenPage, onOpenAttachment: (attachment) => void attachmentRuntime?.openAttachment?.(attachment), onSelectionChange: setSelection, onContentChangeJSON: (json) => {
|
|
540
597
|
syncDocument(contentFromNativeJson(json), false);
|
|
541
|
-
}, style: styles.nativeEditor }), _jsx(UrlDialog, { title: "Link", value: linkUrl, visible: linkRequest !== null, placeholder: "https://", confirmLabel: linkUrl.trim() ? "Apply" : "Remove", onChangeText: setLinkUrl, onCancel: () => setLinkRequest(null), onSubmit: submitLinkDialog, theme: resolvedTheme }), _jsx(UrlDialog, { title: "Image", value: imageUrl, visible: imageRequest !== null, placeholder: "https://", confirmLabel: "Insert", onChangeText: setImageUrl, onCancel: () => setImageRequest(null), onSubmit: submitImageDialog, theme: resolvedTheme })] }));
|
|
598
|
+
}, style: styles.nativeEditor }), _jsx(UrlDialog, { title: "Link", value: linkUrl, visible: linkRequest !== null, placeholder: "https://", confirmLabel: linkUrl.trim() ? "Apply" : "Remove", onChangeText: setLinkUrl, onCancel: () => setLinkRequest(null), onSubmit: submitLinkDialog, theme: resolvedTheme }), _jsx(Modal, { animationType: "fade", onRequestClose: () => { attachmentAbortRef.current?.abort(); setAttachmentUpload(null); }, transparent: true, visible: attachmentUpload !== null, children: _jsx(View, { style: [styles.dialogBackdrop, { backgroundColor: resolvedTheme.backdrop }], children: _jsxs(View, { accessibilityViewIsModal: true, style: [styles.dialog, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: [_jsx(Text, { style: [styles.unavailableTitle, { color: resolvedTheme.text }], children: attachmentUpload?.input.name }), _jsx(Text, { style: [styles.unavailableText, { color: attachmentUpload?.error ? "#b42318" : resolvedTheme.muted }], children: attachmentUpload?.error ?? `Uploading ${Math.round((attachmentUpload?.progress ?? 0) * 100)}%` }), _jsxs(View, { style: styles.dialogActions, children: [attachmentUpload?.error ? _jsx(CommandButton, { label: "Retry", onPress: () => void uploadAttachment(), theme: resolvedTheme }) : null, _jsx(CommandButton, { label: "Cancel", onPress: () => { attachmentAbortRef.current?.abort(); setAttachmentUpload(null); }, theme: resolvedTheme })] })] }) }) }), _jsx(UrlDialog, { title: "Image", value: imageUrl, visible: imageRequest !== null, placeholder: "https://", confirmLabel: "Insert", onChangeText: setImageUrl, onCancel: () => setImageRequest(null), onSubmit: submitImageDialog, theme: resolvedTheme })] }));
|
|
542
599
|
};
|
|
543
|
-
export const OpenEditorNativeViewer = ({ document = nativeSeedDocument, theme, renderers, onOpenPage, }) => {
|
|
600
|
+
export const OpenEditorNativeViewer = ({ document = nativeSeedDocument, theme, renderers, onOpenPage, attachmentRuntime, }) => {
|
|
544
601
|
const resolvedTheme = {
|
|
545
602
|
...defaultNativeTheme,
|
|
546
603
|
...theme,
|
|
@@ -550,7 +607,7 @@ export const OpenEditorNativeViewer = ({ document = nativeSeedDocument, theme, r
|
|
|
550
607
|
},
|
|
551
608
|
};
|
|
552
609
|
const normalizedDocument = normalizeForNativeEditor(document);
|
|
553
|
-
return (_jsx(View, { style: [styles.viewer, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: normalizedDocument.content.map((node, index) => renderNativeViewerNode(node, `native-viewer-${index}`, resolvedTheme, renderers, onOpenPage)) }));
|
|
610
|
+
return (_jsx(View, { style: [styles.viewer, { backgroundColor: resolvedTheme.surface, borderColor: resolvedTheme.border }], children: normalizedDocument.content.map((node, index) => renderNativeViewerNode(node, `native-viewer-${index}`, resolvedTheme, renderers, onOpenPage, attachmentRuntime)) }));
|
|
554
611
|
};
|
|
555
612
|
const CommandButton = ({ label, active = false, primary = false, theme, onPress, }) => (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { selected: active }, onPress: onPress, style: [
|
|
556
613
|
styles.commandButton,
|
package/dist/toolbar.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const openEditorNativeToolbarActionKeys: {
|
|
|
9
9
|
readonly toggleList: "toggleList";
|
|
10
10
|
readonly callout: "callout";
|
|
11
11
|
readonly page: "page";
|
|
12
|
+
readonly attachment: "attachment";
|
|
12
13
|
readonly moveBlockUp: "openeditor:block:moveUp";
|
|
13
14
|
readonly moveBlockDown: "openeditor:block:moveDown";
|
|
14
15
|
readonly undo: "openeditor:history:undo";
|
package/dist/toolbar.js
CHANGED
|
@@ -17,6 +17,7 @@ export const openEditorNativeToolbarActionKeys = {
|
|
|
17
17
|
toggleList: "toggleList",
|
|
18
18
|
callout: "callout",
|
|
19
19
|
page: "page",
|
|
20
|
+
attachment: "attachment",
|
|
20
21
|
moveBlockUp: "openeditor:block:moveUp",
|
|
21
22
|
moveBlockDown: "openeditor:block:moveDown",
|
|
22
23
|
undo: "openeditor:history:undo",
|
|
@@ -90,6 +91,12 @@ export const createOpenEditorNativeToolbarItems = ({ canUndo = true, canRedo = t
|
|
|
90
91
|
{ type: "image", label: "Image", icon: defaultIcon("image") },
|
|
91
92
|
],
|
|
92
93
|
},
|
|
94
|
+
{
|
|
95
|
+
type: "action",
|
|
96
|
+
key: openEditorNativeToolbarActionKeys.attachment,
|
|
97
|
+
label: "Upload file",
|
|
98
|
+
icon: platformIcon("paperclip", "attach-file", "File"),
|
|
99
|
+
},
|
|
93
100
|
{
|
|
94
101
|
type: "action",
|
|
95
102
|
key: openEditorNativeToolbarActionKeys.moveBlockUp,
|
|
@@ -171,6 +178,7 @@ export const nativeToolbarParityCoverage = () => {
|
|
|
171
178
|
["toggleList", openEditorNativeToolbarActionKeys.toggleList],
|
|
172
179
|
["callout", openEditorNativeToolbarActionKeys.callout],
|
|
173
180
|
["page", openEditorNativeToolbarActionKeys.page],
|
|
181
|
+
["attachment", openEditorNativeToolbarActionKeys.attachment],
|
|
174
182
|
]);
|
|
175
183
|
const markControls = new Map([
|
|
176
184
|
["bold", "bold"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/native",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"repository": {
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@expo/vector-icons": "15.0.3",
|
|
30
|
-
"@openeditor/core": "0.0.
|
|
31
|
-
"@openeditor/exporters": "0.0.
|
|
32
|
-
"@openeditor/extensions": "0.0.
|
|
30
|
+
"@openeditor/core": "0.0.13",
|
|
31
|
+
"@openeditor/exporters": "0.0.13",
|
|
32
|
+
"@openeditor/extensions": "0.0.13",
|
|
33
33
|
"react": "19.2.3",
|
|
34
34
|
"react-native": "0.85.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@openeditor/react-native-prose-editor": "0.0.
|
|
37
|
+
"@openeditor/react-native-prose-editor": "0.0.13",
|
|
38
38
|
"react-native-keyboard-controller": ">=1.21",
|
|
39
39
|
"react": ">=19",
|
|
40
40
|
"react-native": ">=0.85"
|