@openeditor/custom-block 0.0.46 → 0.0.47
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 +23 -41
- package/dist/index.d.ts +47 -154
- package/dist/index.js +409 -544
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,60 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@openeditor/custom-block`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this package to add trusted, build-time custom blocks to OpenEditor without adding a ProseMirror node type for each block.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
OpenEditor stores every installed block in one atomic `customBlock` node. A block package owns its data parser, version migration, static output, and asset references. OpenEditor owns the envelope, registry, editor and Viewer lifecycle, missing-block fallback, and safe HTML serialization.
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createOpenEditorCustomBlockRegistry,
|
|
10
|
+
defineOpenEditorCustomBlock,
|
|
11
|
+
} from "@openeditor/custom-block";
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
type CardData = { title: string };
|
|
14
|
+
|
|
15
|
+
export const card = defineOpenEditorCustomBlock<CardData>({
|
|
11
16
|
id: "acme.card",
|
|
12
17
|
label: "Card",
|
|
13
18
|
version: 1,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
createData: () => ({ title: "Untitled" }),
|
|
20
|
+
parseData: (value) => {
|
|
21
|
+
if (!value || typeof value !== "object" || !("title" in value))
|
|
22
|
+
throw new Error("Card title is required.");
|
|
23
|
+
if (typeof value.title !== "string")
|
|
24
|
+
throw new Error("Card title must be a string.");
|
|
25
|
+
return { title: value.title };
|
|
19
26
|
},
|
|
20
|
-
initialData: () => ({ title: "Untitled" }),
|
|
21
27
|
toHtml: ({ data }) => ({ tag: "article", children: [data.title] }),
|
|
22
28
|
toText: ({ data }) => data.title,
|
|
23
29
|
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Static HTML uses an OpenEditor-owned safe tree. It does not accept raw HTML. Use the general `{ type: "document" }` data schema and the Editor and Viewer `host.fields.document` adapters for nested rich documents. Use `host.resolveUrl`, `host.links`, `host.icons`, and `host.assets` for product-neutral host facilities.
|
|
27
|
-
|
|
28
|
-
Define authoring and published Viewer adapters in separate files:
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
// card-editor.tsx
|
|
32
|
-
import { defineOpenEditorCustomBlockEditor } from "@openeditor/custom-block/editor";
|
|
33
|
-
import { card } from "./card";
|
|
34
30
|
|
|
35
|
-
export const
|
|
36
|
-
block: card,
|
|
37
|
-
render: ({ data, updateData }) => (
|
|
38
|
-
<input value={data.title} onChange={(event) => updateData({ title: event.target.value })} />
|
|
39
|
-
),
|
|
40
|
-
});
|
|
31
|
+
export const registry = createOpenEditorCustomBlockRegistry([card]);
|
|
41
32
|
```
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
// card-viewer.tsx
|
|
45
|
-
import { defineOpenEditorCustomBlockViewer } from "@openeditor/custom-block/viewer";
|
|
46
|
-
import { card } from "./card";
|
|
47
|
-
|
|
48
|
-
export const cardViewer = defineOpenEditorCustomBlockViewer({
|
|
49
|
-
block: card,
|
|
50
|
-
render: ({ data }) => <article>{data.title}</article>,
|
|
51
|
-
});
|
|
52
|
-
```
|
|
34
|
+
The parser is the authoritative data contract. Use the same registry in the browser, backend, publisher, migration, and exporters. This prevents validation rules from drifting across environments.
|
|
53
35
|
|
|
54
|
-
|
|
36
|
+
Import React editor adapters from `@openeditor/custom-block/editor`. Import published Viewer adapters from `@openeditor/custom-block/viewer`. The Viewer entry point does not import Tiptap or editor code.
|
|
55
37
|
|
|
56
|
-
Use
|
|
38
|
+
Use `migrate` only while a stored data version is active. The function must return the complete next envelope and increase the version. A final application release can remove the migration after all stored documents use the current version.
|
|
57
39
|
|
|
58
|
-
Use `
|
|
40
|
+
Use `assets` to return host-managed asset IDs and JSON paths. The host authorizes and resolves these opaque IDs. Block packages do not receive storage clients or credentials.
|
|
59
41
|
|
|
60
|
-
|
|
42
|
+
Use `conformOpenEditorCustomBlock` in each block package test. It verifies initial data, parsing, and safe static output.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenEditorDocument, ProseMirrorNode } from '@openeditor/core';
|
|
2
2
|
export { JsonObject as OpenEditorCustomBlockJsonObject, JsonValue as OpenEditorCustomBlockJsonValue, OpenEditorDocument } from '@openeditor/core';
|
|
3
3
|
|
|
4
4
|
declare const OPENEDITOR_CUSTOM_BLOCK_NODE: "customBlock";
|
|
5
5
|
|
|
6
6
|
type OpenEditorCustomBlockId = `${string}.${string}`;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
* enforce JSON at runtime. `unknown` values at this type boundary also allow
|
|
10
|
-
* the structurally wider `OpenEditorDocument` type in declared document fields.
|
|
11
|
-
*/
|
|
12
|
-
type OpenEditorCustomBlockData = Record<string, unknown>;
|
|
13
|
-
/** Preserves a block's precise field types, including nested document fields. */
|
|
14
|
-
type OpenEditorCustomBlockDataShape<TData extends object> = TData;
|
|
7
|
+
/** A block-owned object. Runtime parsing still requires JSON-only plain data. */
|
|
8
|
+
type OpenEditorCustomBlockData = object;
|
|
15
9
|
type OpenEditorCustomBlockEnvelope<TData extends OpenEditorCustomBlockData = OpenEditorCustomBlockData> = {
|
|
16
10
|
blockId: OpenEditorCustomBlockId;
|
|
17
11
|
version: number;
|
|
@@ -25,93 +19,19 @@ type OpenEditorCustomBlockDiagnostic = {
|
|
|
25
19
|
path: string;
|
|
26
20
|
message: string;
|
|
27
21
|
};
|
|
28
|
-
type
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
type OpenEditorCustomBlockAssetReference = {
|
|
23
|
+
id: string;
|
|
24
|
+
path: string;
|
|
31
25
|
};
|
|
32
|
-
type OpenEditorCustomBlockDataSchema = PortableSchemaBase & ({
|
|
33
|
-
type: "any";
|
|
34
|
-
} | {
|
|
35
|
-
type: "string";
|
|
36
|
-
minLength?: number;
|
|
37
|
-
maxLength?: number;
|
|
38
|
-
format?: "asset-id";
|
|
39
|
-
} | {
|
|
40
|
-
type: "number";
|
|
41
|
-
integer?: boolean;
|
|
42
|
-
minimum?: number;
|
|
43
|
-
maximum?: number;
|
|
44
|
-
} | {
|
|
45
|
-
type: "boolean";
|
|
46
|
-
} | {
|
|
47
|
-
type: "null";
|
|
48
|
-
} | {
|
|
49
|
-
type: "oneOf";
|
|
50
|
-
variants: readonly OpenEditorCustomBlockDataSchema[];
|
|
51
|
-
} | {
|
|
52
|
-
type: "document";
|
|
53
|
-
} | {
|
|
54
|
-
type: "array";
|
|
55
|
-
items?: OpenEditorCustomBlockDataSchema;
|
|
56
|
-
minItems?: number;
|
|
57
|
-
maxItems?: number;
|
|
58
|
-
} | {
|
|
59
|
-
type: "object";
|
|
60
|
-
properties?: Readonly<Record<string, OpenEditorCustomBlockDataSchema>>;
|
|
61
|
-
required?: readonly string[];
|
|
62
|
-
additionalProperties?: boolean | OpenEditorCustomBlockDataSchema;
|
|
63
|
-
});
|
|
64
26
|
type OpenEditorCustomBlockManifest = {
|
|
65
27
|
id: OpenEditorCustomBlockId;
|
|
66
28
|
label: string;
|
|
67
29
|
version: number;
|
|
68
|
-
dataSchema: OpenEditorCustomBlockDataSchema;
|
|
69
|
-
constraints?: readonly OpenEditorCustomBlockConstraint[];
|
|
70
30
|
};
|
|
71
|
-
type
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
kind: "uniqueBy";
|
|
76
|
-
array: string;
|
|
77
|
-
keys: readonly string[];
|
|
78
|
-
} | {
|
|
79
|
-
kind: "keysIn";
|
|
80
|
-
scope?: string;
|
|
81
|
-
objects: string;
|
|
82
|
-
keys: string;
|
|
83
|
-
requireAll?: boolean;
|
|
84
|
-
} | {
|
|
85
|
-
kind: "reference";
|
|
86
|
-
array: string;
|
|
87
|
-
field: string;
|
|
88
|
-
targetArray: string;
|
|
89
|
-
targetField: string;
|
|
90
|
-
nullable?: boolean;
|
|
91
|
-
} | {
|
|
92
|
-
kind: "acyclic";
|
|
93
|
-
array: string;
|
|
94
|
-
id: string;
|
|
95
|
-
parent: string;
|
|
96
|
-
} | {
|
|
97
|
-
kind: "graph";
|
|
98
|
-
array: string;
|
|
99
|
-
id: string;
|
|
100
|
-
parent: string;
|
|
101
|
-
siblingKeys?: readonly string[];
|
|
102
|
-
} | {
|
|
103
|
-
kind: "url";
|
|
104
|
-
path: string;
|
|
105
|
-
allowRelative?: boolean;
|
|
106
|
-
requireSchemeSeparator?: boolean;
|
|
107
|
-
schemes?: readonly string[];
|
|
108
|
-
denySchemes?: readonly string[];
|
|
109
|
-
when?: {
|
|
110
|
-
field: string;
|
|
111
|
-
equals: JsonValue;
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
|
-
type OpenEditorCustomBlockMigration = (data: Readonly<OpenEditorCustomBlockData>) => OpenEditorCustomBlockData;
|
|
31
|
+
type OpenEditorCustomBlockMigration = (input: {
|
|
32
|
+
version: number;
|
|
33
|
+
data: Readonly<OpenEditorCustomBlockData>;
|
|
34
|
+
}) => OpenEditorCustomBlockEnvelope;
|
|
115
35
|
type OpenEditorCustomBlockStaticContext<TData extends OpenEditorCustomBlockData> = {
|
|
116
36
|
data: Readonly<TData>;
|
|
117
37
|
renderDocument: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
|
|
@@ -122,31 +42,22 @@ type OpenEditorCustomBlockSafeHtml = string | number | null | false | {
|
|
|
122
42
|
attrs?: Readonly<Partial<Record<"aria-label" | "aria-current" | "role" | "title" | "href" | "src" | "alt" | "width" | "height" | "start" | "colspan" | "rowspan" | "scope", string | number | undefined>>>;
|
|
123
43
|
children?: readonly OpenEditorCustomBlockSafeHtml[];
|
|
124
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* The complete non-React contract for one installed block. The block package
|
|
47
|
+
* owns parsing because OpenEditor cannot know a third-party data model.
|
|
48
|
+
*/
|
|
125
49
|
type OpenEditorCustomBlockDefinition<TData extends OpenEditorCustomBlockData = any> = {
|
|
126
50
|
id: OpenEditorCustomBlockId;
|
|
127
51
|
label: string;
|
|
128
52
|
version: number;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
constraints?: readonly OpenEditorCustomBlockConstraint[];
|
|
134
|
-
migrations?: Readonly<Record<number, OpenEditorCustomBlockMigration>>;
|
|
53
|
+
createData: () => TData;
|
|
54
|
+
parseData: (data: unknown) => TData;
|
|
55
|
+
migrate?: OpenEditorCustomBlockMigration;
|
|
56
|
+
assets?: (data: Readonly<TData>) => readonly OpenEditorCustomBlockAssetReference[];
|
|
135
57
|
toHtml: (context: OpenEditorCustomBlockStaticContext<TData>) => OpenEditorCustomBlockSafeHtml;
|
|
136
58
|
toText: (context: OpenEditorCustomBlockStaticContext<TData>) => string;
|
|
137
59
|
manifest: OpenEditorCustomBlockManifest;
|
|
138
60
|
};
|
|
139
|
-
/** Opaque host asset IDs. URLs, paths, whitespace, and control characters are not valid. */
|
|
140
|
-
declare const OPENEDITOR_CUSTOM_BLOCK_ASSET_ID_PATTERN: RegExp;
|
|
141
|
-
/** Validates one value against a portable JSON-only custom-block schema. */
|
|
142
|
-
declare const validateOpenEditorCustomBlockDataValue: (value: unknown, schema: OpenEditorCustomBlockDataSchema) => {
|
|
143
|
-
valid: true;
|
|
144
|
-
diagnostics: readonly [];
|
|
145
|
-
} | {
|
|
146
|
-
valid: false;
|
|
147
|
-
diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
|
|
148
|
-
};
|
|
149
|
-
declare const defineOpenEditorCustomBlock: <TData extends OpenEditorCustomBlockData>(input: Omit<OpenEditorCustomBlockDefinition<TData>, "manifest">) => OpenEditorCustomBlockDefinition<TData>;
|
|
150
61
|
type UnavailableStatus = "missing" | "disabled" | "incompatible" | "invalid";
|
|
151
62
|
type OpenEditorResolvedCustomBlock<TData extends OpenEditorCustomBlockData = OpenEditorCustomBlockData> = {
|
|
152
63
|
status: "ready";
|
|
@@ -166,50 +77,17 @@ type OpenEditorCustomBlockRegistry = {
|
|
|
166
77
|
get: (id: string) => OpenEditorCustomBlockDefinition | undefined;
|
|
167
78
|
isEnabled: (id: string) => boolean;
|
|
168
79
|
resolve: (node: ProseMirrorNode) => OpenEditorResolvedCustomBlock;
|
|
80
|
+
validate: (envelope: unknown) => {
|
|
81
|
+
valid: true;
|
|
82
|
+
envelope: OpenEditorCustomBlockEnvelope;
|
|
83
|
+
} | {
|
|
84
|
+
valid: false;
|
|
85
|
+
diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
|
|
86
|
+
};
|
|
87
|
+
assets: (envelope: unknown) => readonly OpenEditorCustomBlockAssetReference[];
|
|
169
88
|
toHtml: (node: ProseMirrorNode) => string;
|
|
170
89
|
toText: (node: ProseMirrorNode) => string;
|
|
171
90
|
};
|
|
172
|
-
declare const escapeOpenEditorCustomBlockHtml: (value: unknown) => string;
|
|
173
|
-
declare const renderOpenEditorCustomBlockSafeHtml: (value: OpenEditorCustomBlockSafeHtml) => string;
|
|
174
|
-
declare const createOpenEditorCustomBlockRegistry: (definitions: readonly OpenEditorCustomBlockDefinition[], options?: {
|
|
175
|
-
disabled?: readonly string[];
|
|
176
|
-
renderDocument?: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
|
|
177
|
-
documentToText?: (document: OpenEditorDocument) => string;
|
|
178
|
-
}) => OpenEditorCustomBlockRegistry;
|
|
179
|
-
declare const createOpenEditorCustomBlockNode: <TData extends OpenEditorCustomBlockData>(registry: OpenEditorCustomBlockRegistry, id: string, data?: TData, options?: {
|
|
180
|
-
instanceId?: string;
|
|
181
|
-
createInstanceId?: () => string;
|
|
182
|
-
}) => OpenEditorCustomBlockNode<TData>;
|
|
183
|
-
declare const resolveOpenEditorCustomBlockNode: (registry: OpenEditorCustomBlockRegistry, node: ProseMirrorNode) => OpenEditorResolvedCustomBlock<OpenEditorCustomBlockData>;
|
|
184
|
-
declare const validateOpenEditorCustomBlockEnvelope: (value: unknown, manifests: readonly OpenEditorCustomBlockManifest[], options?: {
|
|
185
|
-
mode?: "strict" | "preserve";
|
|
186
|
-
disabled?: readonly string[];
|
|
187
|
-
}) => {
|
|
188
|
-
valid: false;
|
|
189
|
-
diagnostics: {
|
|
190
|
-
path: string;
|
|
191
|
-
message: string;
|
|
192
|
-
}[];
|
|
193
|
-
status?: undefined;
|
|
194
|
-
} | {
|
|
195
|
-
valid: true;
|
|
196
|
-
diagnostics: readonly [];
|
|
197
|
-
status: "preserved";
|
|
198
|
-
} | {
|
|
199
|
-
valid: true;
|
|
200
|
-
diagnostics: OpenEditorCustomBlockDiagnostic[];
|
|
201
|
-
status: "preserved-invalid";
|
|
202
|
-
} | {
|
|
203
|
-
valid: true;
|
|
204
|
-
diagnostics: readonly [];
|
|
205
|
-
status?: undefined;
|
|
206
|
-
};
|
|
207
|
-
type OpenEditorCustomBlockAssetReference = {
|
|
208
|
-
id: string;
|
|
209
|
-
path: string;
|
|
210
|
-
};
|
|
211
|
-
/** Extracts host asset IDs from a portable manifest without loading extension code. */
|
|
212
|
-
declare const extractOpenEditorCustomBlockAssetReferences: (value: unknown, manifests: readonly OpenEditorCustomBlockManifest[]) => readonly OpenEditorCustomBlockAssetReference[];
|
|
213
91
|
type OpenEditorCustomBlockIcon = {
|
|
214
92
|
id: string;
|
|
215
93
|
label: string;
|
|
@@ -246,12 +124,27 @@ type OpenEditorCustomBlockHost = {
|
|
|
246
124
|
} | null>;
|
|
247
125
|
};
|
|
248
126
|
};
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
127
|
+
declare const defineOpenEditorCustomBlock: <TData extends OpenEditorCustomBlockData>(input: Omit<OpenEditorCustomBlockDefinition<TData>, "manifest">) => OpenEditorCustomBlockDefinition<TData>;
|
|
128
|
+
declare const createOpenEditorCustomBlockRegistry: (definitions: readonly OpenEditorCustomBlockDefinition[], options?: {
|
|
129
|
+
disabled?: readonly string[];
|
|
130
|
+
renderDocument?: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
|
|
131
|
+
documentToText?: (document: OpenEditorDocument) => string;
|
|
132
|
+
}) => OpenEditorCustomBlockRegistry;
|
|
133
|
+
declare const createOpenEditorCustomBlockNode: <TData extends OpenEditorCustomBlockData>(registry: OpenEditorCustomBlockRegistry, id: string, data?: TData, options?: {
|
|
134
|
+
instanceId?: string;
|
|
135
|
+
createInstanceId?: () => string;
|
|
136
|
+
}) => OpenEditorCustomBlockNode<TData>;
|
|
137
|
+
declare const resolveOpenEditorCustomBlockNode: (registry: OpenEditorCustomBlockRegistry, node: ProseMirrorNode) => OpenEditorResolvedCustomBlock<object>;
|
|
138
|
+
declare const validateOpenEditorCustomBlockEnvelope: (value: unknown, registry: Pick<OpenEditorCustomBlockRegistry, "validate">) => {
|
|
139
|
+
valid: true;
|
|
140
|
+
envelope: OpenEditorCustomBlockEnvelope;
|
|
141
|
+
} | {
|
|
142
|
+
valid: false;
|
|
143
|
+
diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
|
|
253
144
|
};
|
|
254
|
-
|
|
145
|
+
declare const extractOpenEditorCustomBlockAssetReferences: (value: unknown, registry: Pick<OpenEditorCustomBlockRegistry, "assets">) => readonly OpenEditorCustomBlockAssetReference[];
|
|
255
146
|
declare const conformOpenEditorCustomBlock: (definition: OpenEditorCustomBlockDefinition) => readonly OpenEditorCustomBlockDiagnostic[];
|
|
147
|
+
declare const escapeOpenEditorCustomBlockHtml: (value: unknown) => string;
|
|
148
|
+
declare const renderOpenEditorCustomBlockSafeHtml: (value: OpenEditorCustomBlockSafeHtml) => string;
|
|
256
149
|
|
|
257
|
-
export {
|
|
150
|
+
export { OPENEDITOR_CUSTOM_BLOCK_NODE, type OpenEditorCustomBlockAsset, type OpenEditorCustomBlockAssetReference, type OpenEditorCustomBlockData, type OpenEditorCustomBlockDefinition, type OpenEditorCustomBlockDiagnostic, type OpenEditorCustomBlockEnvelope, type OpenEditorCustomBlockHost, type OpenEditorCustomBlockIcon, type OpenEditorCustomBlockId, type OpenEditorCustomBlockManifest, type OpenEditorCustomBlockMigration, type OpenEditorCustomBlockNode, type OpenEditorCustomBlockRegistry, type OpenEditorCustomBlockSafeHtml, type OpenEditorCustomBlockStaticContext, type OpenEditorResolvedCustomBlock, conformOpenEditorCustomBlock, createOpenEditorCustomBlockNode, createOpenEditorCustomBlockRegistry, defineOpenEditorCustomBlock, escapeOpenEditorCustomBlockHtml, extractOpenEditorCustomBlockAssetReferences, renderOpenEditorCustomBlockSafeHtml, resolveOpenEditorCustomBlockNode, validateOpenEditorCustomBlockEnvelope };
|