@openeditor/react 0.0.27 → 0.0.29
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 +63 -8
- package/dist/index.d.ts +25 -28
- package/dist/index.js +265 -164
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Pass `attachmentRuntime` to `useOpenEditorController` for editable attachments a
|
|
|
9
9
|
- `useOpenEditorController` for editor state, commands, document exports, and selection state
|
|
10
10
|
- `OpenEditorContent` for the editable Tiptap-backed content surface
|
|
11
11
|
- `OpenEditorViewer` for read-only React rendering
|
|
12
|
+
- `OpenEditorPageHeader` for host-backed page title and icon editing
|
|
12
13
|
- `getDefaultBlockPickerItems` for host-owned block rails, pickers, and command palettes
|
|
13
14
|
- `getDefaultSlashMenuItems` for UI packages that want the built-in insert actions
|
|
14
15
|
- stable-identity block targeting and transaction-backed copy, duplicate, move, and delete commands
|
|
@@ -18,6 +19,40 @@ Pass `attachmentRuntime` to `useOpenEditorController` for editable attachments a
|
|
|
18
19
|
|
|
19
20
|
This package intentionally does not import CSS and does not render styled menus. Use `@openeditor/ui` for the optional styled components.
|
|
20
21
|
|
|
22
|
+
## Read-only rendering and URL policy
|
|
23
|
+
|
|
24
|
+
`OpenEditorViewer` renders the portable document through an explicit registry of
|
|
25
|
+
built-in and structural renderers. Unknown nodes are preserved for forward
|
|
26
|
+
readability but carry `data-openeditor-unknown-node` so unsupported content never
|
|
27
|
+
silently appears to be a fully supported block. Built-in viewer output and safe
|
|
28
|
+
HTML export share stable `oe-*` classes and `data-openeditor-*` hooks for document
|
|
29
|
+
semantics such as columns, tasks, toggles, callouts, tables, pages, and files.
|
|
30
|
+
|
|
31
|
+
Viewer URLs use `openEditorPublicUrlPolicy` by default. The policy covers links,
|
|
32
|
+
images, pages, and attachments, including snapshots returned by page and
|
|
33
|
+
attachment runtimes. It accepts HTTP(S) and ordinary relative references, plus
|
|
34
|
+
`mailto:` and `tel:` for text links, while rejecting executable and local-preview
|
|
35
|
+
schemes. Owner previews can opt into a narrowly scoped policy:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import {
|
|
39
|
+
OpenEditorViewer,
|
|
40
|
+
openEditorPublicUrlPolicy,
|
|
41
|
+
type OpenEditorUrlPolicy,
|
|
42
|
+
} from "@openeditor/react";
|
|
43
|
+
|
|
44
|
+
const previewUrlPolicy: OpenEditorUrlPolicy = (value, context) => {
|
|
45
|
+
if (context === "attachment" && value.startsWith("blob:")) return value;
|
|
46
|
+
return openEditorPublicUrlPolicy(value, context);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
<OpenEditorViewer document={document} urlPolicy={previewUrlPolicy} />;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Custom viewer renderers receive both `urlPolicy` and a typed `resolveUrl` helper.
|
|
53
|
+
The host remains responsible for arbitrary custom renderer output and for actions
|
|
54
|
+
performed inside `openPage` or `openAttachment` callbacks.
|
|
55
|
+
|
|
21
56
|
## Block actions
|
|
22
57
|
|
|
23
58
|
Every addressable node has a persistent `openeditor-id`. The block interaction
|
|
@@ -120,12 +155,24 @@ themeable SVG rendering and retain their canonical Mermaid source in the documen
|
|
|
120
155
|
import { OpenEditorContent, useOpenEditorController } from "@openeditor/react";
|
|
121
156
|
|
|
122
157
|
export function Example() {
|
|
123
|
-
const controller = useOpenEditorController();
|
|
158
|
+
const controller = useOpenEditorController({ initialDocument });
|
|
124
159
|
|
|
125
160
|
return <OpenEditorContent controller={controller} />;
|
|
126
161
|
}
|
|
127
162
|
```
|
|
128
163
|
|
|
164
|
+
Editors are uncontrolled on both web and native. `initialDocument` is read once;
|
|
165
|
+
use `controller.setContent(document)` for an undoable programmatic replacement.
|
|
166
|
+
It emits `onChange` and preserves the current selection where possible. Runtime
|
|
167
|
+
changes to callbacks, `editable`, and `placeholder` are applied without remounting.
|
|
168
|
+
|
|
169
|
+
Exports are lazy. `controller.export("html")` returns publishing-safe built-in
|
|
170
|
+
HTML. `controller.exportUnsafe("html")` opts into trusted custom extension HTML.
|
|
171
|
+
|
|
172
|
+
Pass `enabledBlocks` to restrict authoring without removing nodes from the
|
|
173
|
+
schema. Existing disabled blocks remain readable. Runtime-dependent insertion
|
|
174
|
+
is automatically disabled unless page creation or attachment upload is wired.
|
|
175
|
+
|
|
129
176
|
## Custom blocks
|
|
130
177
|
|
|
131
178
|
Custom blocks belong to the consuming product. OpenEditor hosts them without
|
|
@@ -195,7 +242,11 @@ const productCard = defineOpenEditorReactNode({
|
|
|
195
242
|
keywords: ["product", "commerce", "card"],
|
|
196
243
|
order: 250,
|
|
197
244
|
},
|
|
198
|
-
viewer: ({ node }) =>
|
|
245
|
+
viewer: ({ node, resolveUrl }) => (
|
|
246
|
+
<article>
|
|
247
|
+
<a href={resolveUrl(node.attrs?.href, "link")}>{String(node.attrs?.title ?? "")}</a>
|
|
248
|
+
</article>
|
|
249
|
+
),
|
|
199
250
|
exporters: {
|
|
200
251
|
html: {
|
|
201
252
|
acmeProductCard: ({ node, escapeHtml }) =>
|
|
@@ -215,8 +266,9 @@ export function ProductEditor() {
|
|
|
215
266
|
}
|
|
216
267
|
```
|
|
217
268
|
|
|
218
|
-
Pass the same extension array to `OpenEditorViewer`. The controller
|
|
219
|
-
|
|
269
|
+
Pass the same extension array to `OpenEditorViewer`. The controller uses
|
|
270
|
+
registered exporters only through the explicit `controller.exportUnsafe(...)`
|
|
271
|
+
path, and registered blocks appear
|
|
220
272
|
in `getDefaultSlashMenuItems`, which powers `@openeditor/ui`.
|
|
221
273
|
|
|
222
274
|
Insert-menu entries accept an optional React `icon` component and numeric `order`.
|
|
@@ -225,8 +277,7 @@ blocks can be placed between them (for example, `order: 250` appears between
|
|
|
225
277
|
Heading 1 and Heading 2). Entries with the same order retain their declaration
|
|
226
278
|
order, and entries without an order appear after ordered entries. This metadata
|
|
227
279
|
is shared by slash menus and `getDefaultBlockPickerItems`; use `insertMenu: false`
|
|
228
|
-
to keep a block out of both discovery surfaces. The
|
|
229
|
-
field remains supported. The same fields are available on direct
|
|
280
|
+
to keep a block out of both discovery surfaces. The same fields are available on direct
|
|
230
281
|
`slashMenuItems`; a slash menu's explicit `items` prop is also sorted by this rule.
|
|
231
282
|
|
|
232
283
|
Block `name` is the stable public identifier. Namespace consumer blocks to avoid
|
|
@@ -238,19 +289,23 @@ Duplicate names and node types fail immediately during editor construction.
|
|
|
238
289
|
|
|
239
290
|
The first-party `page` block models a Notion-style child page as a reference to
|
|
240
291
|
a separately persisted document. Supply `pageRuntime` to connect creation,
|
|
241
|
-
resolution,
|
|
292
|
+
resolution, metadata updates, and navigation to your application:
|
|
242
293
|
|
|
243
294
|
```tsx
|
|
244
295
|
const controller = useOpenEditorController({
|
|
245
296
|
pageRuntime: {
|
|
246
297
|
createPage: async ({ title, icon }) => api.pages.create({ title, icon }),
|
|
247
298
|
resolvePage: async (pageId) => api.pages.get(pageId),
|
|
248
|
-
|
|
299
|
+
updatePage: async (pageId, update) => api.pages.update(pageId, update),
|
|
249
300
|
openPage: (page) => router.push(`/pages/${page.pageId}`),
|
|
250
301
|
},
|
|
251
302
|
});
|
|
252
303
|
```
|
|
253
304
|
|
|
305
|
+
Render `OpenEditorPageHeader` on the opened page surface to provide the canonical
|
|
306
|
+
editable title and icon. Both fields call `pageRuntime.updatePage`; page references
|
|
307
|
+
resolve the same page entity instead of owning independent metadata.
|
|
308
|
+
|
|
254
309
|
The document stores only the stable reference and cached presentation. Page
|
|
255
310
|
contents, hierarchy, permissions, routing, and deletion remain host-owned.
|
|
256
311
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, CSSProperties, RefObject, ComponentType } from 'react';
|
|
3
|
-
import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorAttachmentRuntime } from '@openeditor/core';
|
|
4
|
-
export { OpenEditorBlockRef } from '@openeditor/core';
|
|
5
|
-
import {
|
|
3
|
+
import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorUrlPolicy, OpenEditorUrlContext, OpenEditorPageRuntime, OpenEditorAttachmentRuntime, OpenEditorPageSnapshot, OpenEditorAuthoringCapabilities } from '@openeditor/core';
|
|
4
|
+
export { OpenEditorBlockRef, OpenEditorPageRuntime, OpenEditorPageSnapshot, OpenEditorUrlContext, OpenEditorUrlPolicy, openEditorPublicUrlPolicy, openEditorUnsafeUrlPolicy } from '@openeditor/core';
|
|
5
|
+
import { ExportFormat, OpenEditorExporters } from '@openeditor/exporters';
|
|
6
6
|
export { seedDocument } from '@openeditor/extensions';
|
|
7
7
|
import { AnyExtension, NodeConfig } from '@tiptap/core';
|
|
8
8
|
import { ReactNodeViewProps } from '@tiptap/react';
|
|
@@ -17,7 +17,7 @@ type OpenEditorBlockInteractionStore = {
|
|
|
17
17
|
subscribe: (listener: () => void) => () => void;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline"];
|
|
20
|
+
declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline", "bodyFontSize", "bodyLineHeight", "headingFont", "headingLineHeight", "headingWeight", "heading1Size", "heading2Size", "heading3Size", "heading4Size", "heading5Size", "heading6Size"];
|
|
21
21
|
type OpenEditorThemeToken = (typeof openEditorThemeTokenNames)[number];
|
|
22
22
|
type OpenEditorTheme = Partial<Record<OpenEditorThemeToken, string>>;
|
|
23
23
|
type OpenEditorThemeStyle = CSSProperties & Record<`--oe-${string}`, string | undefined>;
|
|
@@ -29,23 +29,7 @@ declare function OpenEditorThemeProvider({ children, className, theme }: {
|
|
|
29
29
|
theme: OpenEditorTheme;
|
|
30
30
|
}): react.JSX.Element;
|
|
31
31
|
|
|
32
|
-
type
|
|
33
|
-
pageId: string;
|
|
34
|
-
title: string;
|
|
35
|
-
icon?: string | null;
|
|
36
|
-
href?: string | null;
|
|
37
|
-
};
|
|
38
|
-
type OpenEditorPageRuntime = {
|
|
39
|
-
createPage?: (input: {
|
|
40
|
-
title: string;
|
|
41
|
-
icon?: string | null;
|
|
42
|
-
}) => Promise<OpenEditorPageSnapshot>;
|
|
43
|
-
resolvePage?: (pageId: string) => Promise<OpenEditorPageSnapshot | null>;
|
|
44
|
-
renamePage?: (pageId: string, title: string) => Promise<void> | void;
|
|
45
|
-
updatePageIcon?: (pageId: string, icon: string) => Promise<void> | void;
|
|
46
|
-
openPage?: (page: OpenEditorPageSnapshot) => Promise<void> | void;
|
|
47
|
-
};
|
|
48
|
-
type OpenEditorReactProps = {
|
|
32
|
+
type OpenEditorReactProps = OpenEditorAuthoringCapabilities & {
|
|
49
33
|
initialDocument?: OpenEditorDocument;
|
|
50
34
|
editable?: boolean;
|
|
51
35
|
placeholder?: string;
|
|
@@ -72,7 +56,8 @@ type OpenEditorBlockAction = {
|
|
|
72
56
|
type OpenEditorReactConfig = OpenEditorReactProps;
|
|
73
57
|
type OpenEditorController = {
|
|
74
58
|
document: OpenEditorDocument;
|
|
75
|
-
|
|
59
|
+
export: (format: ExportFormat) => string;
|
|
60
|
+
exportUnsafe: (format: ExportFormat) => string;
|
|
76
61
|
ready: boolean;
|
|
77
62
|
editable: boolean;
|
|
78
63
|
placeholder: string;
|
|
@@ -95,6 +80,7 @@ type OpenEditorController = {
|
|
|
95
80
|
undo: () => void;
|
|
96
81
|
redo: () => void;
|
|
97
82
|
insertBlock: (blockName?: string, range?: InsertRange) => boolean;
|
|
83
|
+
isBlockEnabled: (blockName: string) => boolean;
|
|
98
84
|
setLink: (url?: string | null) => void;
|
|
99
85
|
getActiveLink: () => string | undefined;
|
|
100
86
|
selectBlock: (block: OpenEditorBlockRef) => boolean;
|
|
@@ -112,6 +98,7 @@ type OpenEditorController = {
|
|
|
112
98
|
extensions: readonly OpenEditorReactExtension[];
|
|
113
99
|
pageRuntime?: OpenEditorPageRuntime;
|
|
114
100
|
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
101
|
+
enabledBlocks?: readonly string[];
|
|
115
102
|
slashState: SlashState | null;
|
|
116
103
|
selectionBubbleState: SelectionBubbleState | null;
|
|
117
104
|
};
|
|
@@ -182,6 +169,8 @@ type OpenEditorBlockPickerItem = {
|
|
|
182
169
|
type OpenEditorViewerRenderer = (context: {
|
|
183
170
|
node: ProseMirrorNode;
|
|
184
171
|
children: ReactNode;
|
|
172
|
+
urlPolicy: OpenEditorUrlPolicy;
|
|
173
|
+
resolveUrl: (value: unknown, context: OpenEditorUrlContext) => string | undefined;
|
|
185
174
|
}) => ReactNode;
|
|
186
175
|
type OpenEditorExtensionMenu = {
|
|
187
176
|
label?: string;
|
|
@@ -206,8 +195,6 @@ type OpenEditorReactExtension = {
|
|
|
206
195
|
blockHandle?: "self" | "nested";
|
|
207
196
|
/** Discovery metadata shared by slash menus and host-owned block pickers. */
|
|
208
197
|
insertMenu?: false | OpenEditorExtensionMenu;
|
|
209
|
-
/** @deprecated Use `insertMenu`. */
|
|
210
|
-
slashMenu?: false | OpenEditorExtensionMenu;
|
|
211
198
|
viewer: OpenEditorViewerRenderer;
|
|
212
199
|
exporters?: OpenEditorExporters;
|
|
213
200
|
};
|
|
@@ -225,6 +212,8 @@ type OpenEditorViewerProps = {
|
|
|
225
212
|
extensions?: readonly OpenEditorReactExtension[];
|
|
226
213
|
pageRuntime?: OpenEditorPageRuntime;
|
|
227
214
|
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
215
|
+
/** Safe by default. Supply an explicit host policy for trusted preview URLs such as `blob:`. */
|
|
216
|
+
urlPolicy?: OpenEditorUrlPolicy;
|
|
228
217
|
};
|
|
229
218
|
declare module "@tiptap/core" {
|
|
230
219
|
interface Commands<ReturnType> {
|
|
@@ -234,7 +223,15 @@ declare module "@tiptap/core" {
|
|
|
234
223
|
}
|
|
235
224
|
}
|
|
236
225
|
declare const formatAttachmentSize: (size: number | null) => string;
|
|
237
|
-
|
|
226
|
+
type OpenEditorPageHeaderProps = {
|
|
227
|
+
page: OpenEditorPageSnapshot;
|
|
228
|
+
runtime: OpenEditorPageRuntime;
|
|
229
|
+
className?: string;
|
|
230
|
+
onPageChange?: (page: OpenEditorPageSnapshot) => void;
|
|
231
|
+
};
|
|
232
|
+
/** Canonical Notion-style page metadata editor shared by host page surfaces. */
|
|
233
|
+
declare const OpenEditorPageHeader: ({ page, runtime, className, onPageChange, }: OpenEditorPageHeaderProps) => react.JSX.Element;
|
|
234
|
+
declare const useOpenEditorController: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, enabledBlocks, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
238
235
|
declare const useOpenEditorBlockInteraction: (controller: OpenEditorController) => OpenEditorBlockInteractionSnapshot;
|
|
239
236
|
type OpenEditorBlockDragHandleProps = {
|
|
240
237
|
controller: OpenEditorController;
|
|
@@ -257,7 +254,7 @@ declare const sortBlockPickerItems: (items: readonly OpenEditorBlockPickerItem[]
|
|
|
257
254
|
declare const getDefaultBlockPickerItems: (controller: OpenEditorController) => OpenEditorBlockPickerItem[];
|
|
258
255
|
declare const getDefaultSlashMenuItems: (controller: OpenEditorController) => OpenEditorSlashMenuItem[];
|
|
259
256
|
declare const OpenEditorContent: ({ controller, className, }: OpenEditorContentProps) => react.JSX.Element;
|
|
260
|
-
declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, }: OpenEditorViewerProps) => react.JSX.Element;
|
|
261
|
-
declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
257
|
+
declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, urlPolicy, }: OpenEditorViewerProps) => react.JSX.Element;
|
|
258
|
+
declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, enabledBlocks, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
262
259
|
|
|
263
|
-
export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect,
|
|
260
|
+
export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, OpenEditorPageHeader, type OpenEditorPageHeaderProps, type OpenEditorReactConfig, type OpenEditorReactExtension, type OpenEditorReactNodeExtension, type OpenEditorReactProps, type OpenEditorSlashMenuIcon, type OpenEditorSlashMenuIconProps, type OpenEditorSlashMenuItem, type OpenEditorTheme, OpenEditorThemeProvider, type OpenEditorThemeStyle, type OpenEditorThemeToken, OpenEditorViewer, type OpenEditorViewerProps, type OpenEditorViewerRenderer, type SelectionBubbleState, type SlashState, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };
|