@openeditor/core 0.0.1
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 +15 -0
- package/dist/index.d.ts +201 -0
- package/dist/index.js +346 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `@openeditor/core`
|
|
2
|
+
|
|
3
|
+
Canonical SDK contract for OpenEditor documents.
|
|
4
|
+
|
|
5
|
+
## Public surface
|
|
6
|
+
|
|
7
|
+
- `OpenEditorDocument` and related types
|
|
8
|
+
- document creation, normalization, validation, parsing, and compatibility helpers
|
|
9
|
+
- platform-neutral command and transaction helpers
|
|
10
|
+
- top-level block transforms and text extraction
|
|
11
|
+
|
|
12
|
+
## Internal notes
|
|
13
|
+
|
|
14
|
+
- Renderer implementations should adapt at their package boundary, not by inventing their own document shape.
|
|
15
|
+
- This package owns the stable cross-platform contract and should stay free of web/native runtime dependencies.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
3
|
+
type JsonObject = {
|
|
4
|
+
[key: string]: JsonValue | undefined;
|
|
5
|
+
};
|
|
6
|
+
type ProseMirrorAttrs = Record<string, unknown>;
|
|
7
|
+
type ProseMirrorMark = {
|
|
8
|
+
type: string;
|
|
9
|
+
attrs?: ProseMirrorAttrs;
|
|
10
|
+
};
|
|
11
|
+
type OpenEditorNodeMeta = {
|
|
12
|
+
id?: string;
|
|
13
|
+
block?: string;
|
|
14
|
+
custom?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
type ProseMirrorNode = {
|
|
17
|
+
type: string;
|
|
18
|
+
attrs?: ProseMirrorAttrs;
|
|
19
|
+
content?: ProseMirrorNode[];
|
|
20
|
+
marks?: ProseMirrorMark[];
|
|
21
|
+
text?: string;
|
|
22
|
+
};
|
|
23
|
+
type OpenEditorBlock = ProseMirrorNode & {
|
|
24
|
+
attrs?: ProseMirrorAttrs & {
|
|
25
|
+
"data-openeditor-id"?: string;
|
|
26
|
+
"data-openeditor-block"?: string;
|
|
27
|
+
openeditor?: OpenEditorNodeMeta;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type OpenEditorDocumentMeta = {
|
|
31
|
+
id?: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
source?: string;
|
|
34
|
+
createdAt?: string;
|
|
35
|
+
updatedAt?: string;
|
|
36
|
+
schemaVersion?: number;
|
|
37
|
+
platform?: EditorPlatform;
|
|
38
|
+
custom?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
type OpenEditorDocument = {
|
|
41
|
+
type: "doc";
|
|
42
|
+
version: 1;
|
|
43
|
+
content: OpenEditorBlock[];
|
|
44
|
+
meta?: OpenEditorDocumentMeta;
|
|
45
|
+
};
|
|
46
|
+
type ProseMirrorDocument = {
|
|
47
|
+
type: "doc";
|
|
48
|
+
content: ProseMirrorNode[];
|
|
49
|
+
};
|
|
50
|
+
type EditorPlatform = "web" | "native";
|
|
51
|
+
type PlatformSupportLevel = "supported" | "unsupported";
|
|
52
|
+
type PlatformSupport = {
|
|
53
|
+
web: PlatformSupportLevel;
|
|
54
|
+
native: PlatformSupportLevel;
|
|
55
|
+
};
|
|
56
|
+
type BlockSpec = {
|
|
57
|
+
name: string;
|
|
58
|
+
label: string;
|
|
59
|
+
group: "text" | "media" | "layout" | "structure";
|
|
60
|
+
defaultNode: () => OpenEditorBlock;
|
|
61
|
+
matchNode?: (node: ProseMirrorNode) => boolean;
|
|
62
|
+
support?: PlatformSupport;
|
|
63
|
+
};
|
|
64
|
+
type BlockRegistry = ReadonlyMap<string, BlockSpec>;
|
|
65
|
+
type EditorSelection = {
|
|
66
|
+
type: "none";
|
|
67
|
+
} | {
|
|
68
|
+
type: "text";
|
|
69
|
+
anchor: number;
|
|
70
|
+
head: number;
|
|
71
|
+
} | {
|
|
72
|
+
type: "node";
|
|
73
|
+
from: number;
|
|
74
|
+
to: number;
|
|
75
|
+
nodeType?: string;
|
|
76
|
+
} | {
|
|
77
|
+
type: "block";
|
|
78
|
+
blockId: string;
|
|
79
|
+
};
|
|
80
|
+
type OpenEditorMarkName = "bold" | "italic" | "underline" | "strike" | "code" | "link";
|
|
81
|
+
type OpenEditorFeatureName = "headings" | "lists" | "taskLists" | "quotes" | "codeBlocks" | "dividers" | "links" | "images" | "columns" | "tables";
|
|
82
|
+
type OpenEditorFeatureSet = Readonly<Record<OpenEditorFeatureName, boolean>>;
|
|
83
|
+
type SerializedEditorState = {
|
|
84
|
+
document: OpenEditorDocument;
|
|
85
|
+
selection?: EditorSelection;
|
|
86
|
+
};
|
|
87
|
+
type EditorTransaction = {
|
|
88
|
+
before: OpenEditorDocument;
|
|
89
|
+
after: OpenEditorDocument;
|
|
90
|
+
command?: OpenEditorCommand;
|
|
91
|
+
timestamp: string;
|
|
92
|
+
};
|
|
93
|
+
type OpenEditorCommand = {
|
|
94
|
+
type: "setContent";
|
|
95
|
+
document: OpenEditorDocument;
|
|
96
|
+
} | {
|
|
97
|
+
type: "insertBlock";
|
|
98
|
+
block: string;
|
|
99
|
+
at?: number;
|
|
100
|
+
attrs?: ProseMirrorAttrs;
|
|
101
|
+
} | {
|
|
102
|
+
type: "moveBlock";
|
|
103
|
+
from: number;
|
|
104
|
+
to: number;
|
|
105
|
+
} | {
|
|
106
|
+
type: "duplicateBlock";
|
|
107
|
+
index: number;
|
|
108
|
+
} | {
|
|
109
|
+
type: "deleteBlock";
|
|
110
|
+
index: number;
|
|
111
|
+
} | {
|
|
112
|
+
type: "setLink";
|
|
113
|
+
href?: string;
|
|
114
|
+
} | {
|
|
115
|
+
type: "toggleMark";
|
|
116
|
+
mark: OpenEditorMarkName;
|
|
117
|
+
} | {
|
|
118
|
+
type: "undo";
|
|
119
|
+
} | {
|
|
120
|
+
type: "redo";
|
|
121
|
+
} | {
|
|
122
|
+
type: "replaceDocument";
|
|
123
|
+
document: OpenEditorDocument;
|
|
124
|
+
} | {
|
|
125
|
+
type: "setSelection";
|
|
126
|
+
selection: EditorSelection;
|
|
127
|
+
};
|
|
128
|
+
type EditorCommand = OpenEditorCommand;
|
|
129
|
+
type OpenEditorEventHandlers = {
|
|
130
|
+
onChange?: (document: OpenEditorDocument) => void;
|
|
131
|
+
onSelectionChange?: (selection: EditorSelection) => void;
|
|
132
|
+
onFocus?: () => void;
|
|
133
|
+
onBlur?: () => void;
|
|
134
|
+
onReady?: (controller: OpenEditorController) => void;
|
|
135
|
+
onCommand?: (command: OpenEditorCommand, transaction: EditorTransaction) => void;
|
|
136
|
+
};
|
|
137
|
+
type OpenEditorConfig = OpenEditorEventHandlers & {
|
|
138
|
+
document?: OpenEditorDocument;
|
|
139
|
+
editable?: boolean;
|
|
140
|
+
placeholder?: string;
|
|
141
|
+
enabledBlocks?: readonly string[];
|
|
142
|
+
theme?: Record<string, string | number>;
|
|
143
|
+
features?: Partial<OpenEditorFeatureSet>;
|
|
144
|
+
};
|
|
145
|
+
type OpenEditorController = {
|
|
146
|
+
getDocument: () => OpenEditorDocument;
|
|
147
|
+
setDocument: (document: OpenEditorDocument) => void;
|
|
148
|
+
getSelection: () => EditorSelection;
|
|
149
|
+
execute: (command: OpenEditorCommand) => void;
|
|
150
|
+
};
|
|
151
|
+
type DocumentValidationIssue = {
|
|
152
|
+
path: string;
|
|
153
|
+
message: string;
|
|
154
|
+
};
|
|
155
|
+
type DocumentValidationResult = {
|
|
156
|
+
valid: boolean;
|
|
157
|
+
issues: DocumentValidationIssue[];
|
|
158
|
+
};
|
|
159
|
+
type PlatformSupportIssue = {
|
|
160
|
+
path: string;
|
|
161
|
+
block: string;
|
|
162
|
+
platform: EditorPlatform;
|
|
163
|
+
support: PlatformSupportLevel;
|
|
164
|
+
};
|
|
165
|
+
type PlatformSupportResult = {
|
|
166
|
+
platform: EditorPlatform;
|
|
167
|
+
document: OpenEditorDocument;
|
|
168
|
+
issues: PlatformSupportIssue[];
|
|
169
|
+
};
|
|
170
|
+
declare const cloneNode: <T extends ProseMirrorNode>(node: T) => T;
|
|
171
|
+
declare const createDocument: (content?: ProseMirrorNode[], meta?: OpenEditorDocumentMeta) => OpenEditorDocument;
|
|
172
|
+
declare const createEditorState: (document: OpenEditorDocument, selection?: EditorSelection) => SerializedEditorState;
|
|
173
|
+
declare const toProseMirrorDocument: (document: OpenEditorDocument) => ProseMirrorDocument;
|
|
174
|
+
declare const fromProseMirrorDocument: (document: ProseMirrorDocument, meta?: OpenEditorDocumentMeta) => OpenEditorDocument;
|
|
175
|
+
declare const createTextNode: (text: string, marks?: ProseMirrorMark[]) => ProseMirrorNode;
|
|
176
|
+
declare const textBlock: (type: string, text: string, attrs?: ProseMirrorAttrs) => OpenEditorBlock;
|
|
177
|
+
declare const createBlockRegistry: (specs: BlockSpec[]) => BlockRegistry;
|
|
178
|
+
declare const findBlockSpecForNode: (registry: BlockRegistry, node: ProseMirrorNode) => BlockSpec | undefined;
|
|
179
|
+
declare const getNodeMeta: (node: ProseMirrorNode) => OpenEditorNodeMeta | undefined;
|
|
180
|
+
declare const withNodeMeta: <T extends ProseMirrorNode>(node: T, meta: OpenEditorNodeMeta) => T;
|
|
181
|
+
declare const getBlockId: (node: ProseMirrorNode) => string | undefined;
|
|
182
|
+
declare const createBlockId: (prefix?: string) => string;
|
|
183
|
+
declare const ensureBlockIds: (document: OpenEditorDocument, createId?: () => string) => OpenEditorDocument;
|
|
184
|
+
declare const normalizeDocument: (document: OpenEditorDocument) => OpenEditorDocument;
|
|
185
|
+
declare const validateDocument: (document: unknown) => DocumentValidationResult;
|
|
186
|
+
declare const isOpenEditorDocument: (value: unknown) => value is OpenEditorDocument;
|
|
187
|
+
declare const parseOpenEditorDocument: (value: unknown, defaultDocument?: OpenEditorDocument) => OpenEditorDocument;
|
|
188
|
+
declare const serializeEditorState: (state: SerializedEditorState) => string;
|
|
189
|
+
declare const parseEditorState: (value: unknown, defaultState?: SerializedEditorState) => SerializedEditorState;
|
|
190
|
+
declare const getPlatformDocument: (document: OpenEditorDocument, registry: BlockRegistry, platform: EditorPlatform) => OpenEditorDocument;
|
|
191
|
+
declare const getPlatformSupport: (document: OpenEditorDocument, registry: BlockRegistry, platform: EditorPlatform) => PlatformSupportResult;
|
|
192
|
+
declare const getDocumentText: (node: OpenEditorDocument | ProseMirrorNode) => string;
|
|
193
|
+
declare const applyCommand: (document: OpenEditorDocument, registry: BlockRegistry, command: OpenEditorCommand) => OpenEditorDocument;
|
|
194
|
+
declare const replaceTopLevelRange: (document: OpenEditorDocument, start: number, length: number, replacement: ProseMirrorNode[]) => OpenEditorDocument;
|
|
195
|
+
declare const replaceTopLevelNode: (document: OpenEditorDocument, index: number, replacement: ProseMirrorNode) => OpenEditorDocument;
|
|
196
|
+
declare const moveTopLevelBlock: (document: OpenEditorDocument, from: number, to: number) => OpenEditorDocument;
|
|
197
|
+
declare const duplicateTopLevelBlock: (document: OpenEditorDocument, index: number) => OpenEditorDocument;
|
|
198
|
+
declare const deleteTopLevelBlock: (document: OpenEditorDocument, index: number, emptyBlock?: OpenEditorBlock) => OpenEditorDocument;
|
|
199
|
+
declare const createTransaction: (before: OpenEditorDocument, after: OpenEditorDocument, command?: EditorCommand) => EditorTransaction;
|
|
200
|
+
|
|
201
|
+
export { type BlockRegistry, type BlockSpec, type DocumentValidationIssue, type DocumentValidationResult, type EditorCommand, type EditorPlatform, type EditorSelection, type EditorTransaction, type JsonObject, type JsonPrimitive, type JsonValue, type OpenEditorBlock, type OpenEditorCommand, type OpenEditorConfig, type OpenEditorController, type OpenEditorDocument, type OpenEditorDocumentMeta, type OpenEditorEventHandlers, type OpenEditorFeatureName, type OpenEditorFeatureSet, type OpenEditorMarkName, type OpenEditorNodeMeta, type PlatformSupport, type PlatformSupportIssue, type PlatformSupportLevel, type PlatformSupportResult, type ProseMirrorAttrs, type ProseMirrorDocument, type ProseMirrorMark, type ProseMirrorNode, type SerializedEditorState, applyCommand, cloneNode, createBlockId, createBlockRegistry, createDocument, createEditorState, createTextNode, createTransaction, deleteTopLevelBlock, duplicateTopLevelBlock, ensureBlockIds, findBlockSpecForNode, fromProseMirrorDocument, getBlockId, getDocumentText, getNodeMeta, getPlatformDocument, getPlatformSupport, isOpenEditorDocument, moveTopLevelBlock, normalizeDocument, parseEditorState, parseOpenEditorDocument, replaceTopLevelNode, replaceTopLevelRange, serializeEditorState, textBlock, toProseMirrorDocument, validateDocument, withNodeMeta };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var OPENEDITOR_ATTR = "openeditor";
|
|
3
|
+
var OPENEDITOR_ID_ATTR = "data-openeditor-id";
|
|
4
|
+
var OPENEDITOR_BLOCK_ATTR = "data-openeditor-block";
|
|
5
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
var normalizeMeta = (meta) => meta && Object.keys(meta).length ? { ...meta } : void 0;
|
|
7
|
+
var cloneAttrs = (attrs) => attrs ? { ...attrs } : void 0;
|
|
8
|
+
var cloneMark = (mark) => ({
|
|
9
|
+
type: mark.type,
|
|
10
|
+
...mark.attrs ? { attrs: cloneAttrs(mark.attrs) } : {}
|
|
11
|
+
});
|
|
12
|
+
var cloneNode = (node) => ({
|
|
13
|
+
...node,
|
|
14
|
+
...node.attrs ? { attrs: cloneAttrs(node.attrs) } : {},
|
|
15
|
+
...node.marks ? { marks: node.marks.map(cloneMark) } : {},
|
|
16
|
+
...node.content ? { content: node.content.map(cloneNode) } : {}
|
|
17
|
+
});
|
|
18
|
+
var createDocument = (content = [], meta) => ({
|
|
19
|
+
type: "doc",
|
|
20
|
+
version: 1,
|
|
21
|
+
content: content.map(cloneNode),
|
|
22
|
+
...normalizeMeta(meta) ? { meta: normalizeMeta(meta) } : {}
|
|
23
|
+
});
|
|
24
|
+
var createEditorState = (document, selection = { type: "none" }) => ({
|
|
25
|
+
document: normalizeDocument(document),
|
|
26
|
+
selection
|
|
27
|
+
});
|
|
28
|
+
var toProseMirrorDocument = (document) => ({
|
|
29
|
+
type: "doc",
|
|
30
|
+
content: document.content.map(cloneNode)
|
|
31
|
+
});
|
|
32
|
+
var fromProseMirrorDocument = (document, meta) => createDocument(document.content, meta);
|
|
33
|
+
var createTextNode = (text, marks) => ({
|
|
34
|
+
type: "text",
|
|
35
|
+
text,
|
|
36
|
+
...marks?.length ? { marks: marks.map(cloneMark) } : {}
|
|
37
|
+
});
|
|
38
|
+
var textBlock = (type, text, attrs) => ({
|
|
39
|
+
type,
|
|
40
|
+
...attrs ? { attrs: cloneAttrs(attrs) } : {},
|
|
41
|
+
content: text ? [createTextNode(text)] : []
|
|
42
|
+
});
|
|
43
|
+
var createBlockRegistry = (specs) => new Map(specs.map((spec) => [spec.name, spec]));
|
|
44
|
+
var findBlockSpecForNode = (registry, node) => {
|
|
45
|
+
for (const spec of registry.values()) {
|
|
46
|
+
if (spec.matchNode?.(node) || spec.name === node.type) {
|
|
47
|
+
return spec;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
};
|
|
52
|
+
var getNodeMeta = (node) => {
|
|
53
|
+
const attrs = node.attrs;
|
|
54
|
+
const nested = attrs?.[OPENEDITOR_ATTR];
|
|
55
|
+
const id = attrs?.[OPENEDITOR_ID_ATTR];
|
|
56
|
+
const block = attrs?.[OPENEDITOR_BLOCK_ATTR];
|
|
57
|
+
if (isRecord(nested)) {
|
|
58
|
+
return {
|
|
59
|
+
...nested,
|
|
60
|
+
...typeof id === "string" ? { id } : {},
|
|
61
|
+
...typeof block === "string" ? { block } : {}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (typeof id === "string" || typeof block === "string") {
|
|
65
|
+
return {
|
|
66
|
+
...typeof id === "string" ? { id } : {},
|
|
67
|
+
...typeof block === "string" ? { block } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
};
|
|
72
|
+
var withNodeMeta = (node, meta) => {
|
|
73
|
+
const attrs = cloneAttrs(node.attrs) ?? {};
|
|
74
|
+
const previous = getNodeMeta(node);
|
|
75
|
+
const nextMeta = { ...previous, ...meta };
|
|
76
|
+
if (nextMeta.id) {
|
|
77
|
+
attrs[OPENEDITOR_ID_ATTR] = nextMeta.id;
|
|
78
|
+
}
|
|
79
|
+
if (nextMeta.block) {
|
|
80
|
+
attrs[OPENEDITOR_BLOCK_ATTR] = nextMeta.block;
|
|
81
|
+
}
|
|
82
|
+
attrs[OPENEDITOR_ATTR] = nextMeta;
|
|
83
|
+
return { ...node, attrs };
|
|
84
|
+
};
|
|
85
|
+
var getBlockId = (node) => getNodeMeta(node)?.id;
|
|
86
|
+
var createBlockId = (prefix = "oe") => {
|
|
87
|
+
const random = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : Math.random().toString(36).slice(2);
|
|
88
|
+
return `${prefix}_${random.replaceAll("-", "").slice(0, 24)}`;
|
|
89
|
+
};
|
|
90
|
+
var ensureBlockIds = (document, createId = createBlockId) => createDocument(
|
|
91
|
+
document.content.map(
|
|
92
|
+
(node) => getBlockId(node) ? node : withNodeMeta(node, { id: createId() })
|
|
93
|
+
),
|
|
94
|
+
document.meta
|
|
95
|
+
);
|
|
96
|
+
var normalizeNode = (node) => {
|
|
97
|
+
if (node.type === "heading") {
|
|
98
|
+
const level = typeof node.attrs?.level === "number" ? node.attrs.level : 2;
|
|
99
|
+
const content2 = node.content?.map(normalizeNode);
|
|
100
|
+
return {
|
|
101
|
+
...cloneNode(node),
|
|
102
|
+
attrs: { ...node.attrs, level: Math.min(Math.max(level, 1), 6) },
|
|
103
|
+
...content2 ? { content: content2 } : {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (node.type === "columns") {
|
|
107
|
+
const content2 = node.content?.length ? node.content.map(normalizeNode) : [
|
|
108
|
+
{ type: "column", content: [textBlock("paragraph", "")] },
|
|
109
|
+
{ type: "column", content: [textBlock("paragraph", "")] }
|
|
110
|
+
];
|
|
111
|
+
return {
|
|
112
|
+
...cloneNode(node),
|
|
113
|
+
attrs: { ...node.attrs, count: content2.length },
|
|
114
|
+
content: content2
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (node.type === "column" && !node.content?.length) {
|
|
118
|
+
return { ...cloneNode(node), content: [textBlock("paragraph", "")] };
|
|
119
|
+
}
|
|
120
|
+
const content = node.content?.map(normalizeNode);
|
|
121
|
+
return {
|
|
122
|
+
...cloneNode(node),
|
|
123
|
+
...content ? { content } : {}
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
var normalizeDocument = (document) => createDocument(document.content.map(normalizeNode), document.meta);
|
|
127
|
+
var validateDocument = (document) => {
|
|
128
|
+
const issues = [];
|
|
129
|
+
const push = (path, message) => issues.push({ path, message });
|
|
130
|
+
const validateNode = (node, path) => {
|
|
131
|
+
if (!isRecord(node)) {
|
|
132
|
+
push(path, "Node must be an object.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (typeof node.type !== "string" || !node.type) {
|
|
136
|
+
push(`${path}.type`, "Node type must be a non-empty string.");
|
|
137
|
+
}
|
|
138
|
+
if ("text" in node && typeof node.text !== "string") {
|
|
139
|
+
push(`${path}.text`, "Text node content must be a string.");
|
|
140
|
+
}
|
|
141
|
+
if ("attrs" in node && node.attrs !== void 0 && !isRecord(node.attrs)) {
|
|
142
|
+
push(`${path}.attrs`, "Node attrs must be an object.");
|
|
143
|
+
}
|
|
144
|
+
if ("marks" in node && node.marks !== void 0) {
|
|
145
|
+
if (!Array.isArray(node.marks)) {
|
|
146
|
+
push(`${path}.marks`, "Marks must be an array.");
|
|
147
|
+
} else {
|
|
148
|
+
node.marks.forEach((mark, index) => {
|
|
149
|
+
if (!isRecord(mark) || typeof mark.type !== "string" || !mark.type) {
|
|
150
|
+
push(`${path}.marks.${index}`, "Mark must have a non-empty type.");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if ("content" in node && node.content !== void 0) {
|
|
156
|
+
if (!Array.isArray(node.content)) {
|
|
157
|
+
push(`${path}.content`, "Node content must be an array.");
|
|
158
|
+
} else {
|
|
159
|
+
node.content.forEach((child, index) => validateNode(child, `${path}.content.${index}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (!isRecord(document)) {
|
|
164
|
+
return {
|
|
165
|
+
valid: false,
|
|
166
|
+
issues: [{ path: "$", message: "Document must be an object." }]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (document.type !== "doc") {
|
|
170
|
+
push("$.type", 'Document type must be "doc".');
|
|
171
|
+
}
|
|
172
|
+
if (document.version !== 1) {
|
|
173
|
+
push("$.version", "Document version must be 1.");
|
|
174
|
+
}
|
|
175
|
+
if (!Array.isArray(document.content)) {
|
|
176
|
+
push("$.content", "Document content must be an array.");
|
|
177
|
+
} else {
|
|
178
|
+
document.content.forEach((node, index) => validateNode(node, `$.content.${index}`));
|
|
179
|
+
}
|
|
180
|
+
if ("meta" in document && document.meta !== void 0 && !isRecord(document.meta)) {
|
|
181
|
+
push("$.meta", "Document meta must be an object.");
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
valid: issues.length === 0,
|
|
185
|
+
issues
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
var isOpenEditorDocument = (value) => validateDocument(value).valid;
|
|
189
|
+
var parseOpenEditorDocument = (value, defaultDocument = createDocument()) => {
|
|
190
|
+
if (isOpenEditorDocument(value)) {
|
|
191
|
+
return normalizeDocument(value);
|
|
192
|
+
}
|
|
193
|
+
if (isRecord(value) && value.type === "doc" && Array.isArray(value.content)) {
|
|
194
|
+
return fromProseMirrorDocument(value, defaultDocument.meta);
|
|
195
|
+
}
|
|
196
|
+
return defaultDocument;
|
|
197
|
+
};
|
|
198
|
+
var serializeEditorState = (state) => JSON.stringify({
|
|
199
|
+
document: normalizeDocument(state.document),
|
|
200
|
+
...state.selection ? { selection: state.selection } : {}
|
|
201
|
+
});
|
|
202
|
+
var parseEditorState = (value, defaultState = createEditorState(createDocument())) => {
|
|
203
|
+
let parsed;
|
|
204
|
+
try {
|
|
205
|
+
parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
206
|
+
} catch {
|
|
207
|
+
return defaultState;
|
|
208
|
+
}
|
|
209
|
+
if (!isRecord(parsed)) {
|
|
210
|
+
return defaultState;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
document: parseOpenEditorDocument(parsed.document, defaultState.document),
|
|
214
|
+
selection: isRecord(parsed.selection) ? parsed.selection : defaultState.selection
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
var getPlatformDocument = (document, registry, platform) => getPlatformSupport(document, registry, platform).document;
|
|
218
|
+
var getPlatformSupport = (document, registry, platform) => {
|
|
219
|
+
const issues = [];
|
|
220
|
+
const mapNode = (node, path) => {
|
|
221
|
+
const spec = findBlockSpecForNode(registry, node);
|
|
222
|
+
const support = spec?.support?.[platform] ?? "supported";
|
|
223
|
+
if (support !== "supported") {
|
|
224
|
+
issues.push({
|
|
225
|
+
path,
|
|
226
|
+
block: spec?.name ?? node.type,
|
|
227
|
+
platform,
|
|
228
|
+
support
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return normalizeNode({
|
|
232
|
+
...cloneNode(node),
|
|
233
|
+
content: node.content?.map((child, index) => mapNode(child, `${path}.content.${index}`))
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
return {
|
|
237
|
+
platform,
|
|
238
|
+
document: createDocument(
|
|
239
|
+
document.content.map((node, index) => mapNode(node, `$.content.${index}`)),
|
|
240
|
+
{ ...document.meta, platform }
|
|
241
|
+
),
|
|
242
|
+
issues
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
var INLINE_NODE_TYPES = /* @__PURE__ */ new Set(["text", "hardBreak"]);
|
|
246
|
+
var getDocumentText = (node) => {
|
|
247
|
+
if ("text" in node && typeof node.text === "string") {
|
|
248
|
+
return node.text;
|
|
249
|
+
}
|
|
250
|
+
const content = "content" in node ? node.content : void 0;
|
|
251
|
+
if (!content?.length) {
|
|
252
|
+
return "";
|
|
253
|
+
}
|
|
254
|
+
const isInlineContainer = content.every(
|
|
255
|
+
(child) => INLINE_NODE_TYPES.has(child.type) || child.type === "link"
|
|
256
|
+
);
|
|
257
|
+
const joiner = isInlineContainer ? "" : "\n";
|
|
258
|
+
return content.map(getDocumentText).filter(Boolean).join(joiner);
|
|
259
|
+
};
|
|
260
|
+
var applyCommand = (document, registry, command) => {
|
|
261
|
+
if (command.type === "setContent") {
|
|
262
|
+
return normalizeDocument(command.document);
|
|
263
|
+
}
|
|
264
|
+
if (command.type === "replaceDocument") {
|
|
265
|
+
return normalizeDocument(command.document);
|
|
266
|
+
}
|
|
267
|
+
if (command.type === "setSelection") {
|
|
268
|
+
return document;
|
|
269
|
+
}
|
|
270
|
+
if (command.type === "moveBlock") {
|
|
271
|
+
return moveTopLevelBlock(document, command.from, command.to);
|
|
272
|
+
}
|
|
273
|
+
if (command.type === "duplicateBlock") {
|
|
274
|
+
return duplicateTopLevelBlock(document, command.index);
|
|
275
|
+
}
|
|
276
|
+
if (command.type === "deleteBlock") {
|
|
277
|
+
return deleteTopLevelBlock(document, command.index);
|
|
278
|
+
}
|
|
279
|
+
if (command.type === "setLink" || command.type === "toggleMark" || command.type === "undo" || command.type === "redo") {
|
|
280
|
+
return normalizeDocument(document);
|
|
281
|
+
}
|
|
282
|
+
const spec = registry.get(command.block);
|
|
283
|
+
if (!spec) {
|
|
284
|
+
throw new Error(`Unknown block "${command.block}"`);
|
|
285
|
+
}
|
|
286
|
+
const nextContent = [...document.content];
|
|
287
|
+
const index = command.at ?? nextContent.length;
|
|
288
|
+
const defaultNode = spec.defaultNode();
|
|
289
|
+
const node = command.attrs ? { ...defaultNode, attrs: { ...defaultNode.attrs, ...command.attrs } } : defaultNode;
|
|
290
|
+
nextContent.splice(index, 0, node);
|
|
291
|
+
return normalizeDocument(createDocument(nextContent, document.meta));
|
|
292
|
+
};
|
|
293
|
+
var replaceTopLevelRange = (document, start, length, replacement) => normalizeDocument({
|
|
294
|
+
...document,
|
|
295
|
+
content: [
|
|
296
|
+
...document.content.slice(0, start),
|
|
297
|
+
...replacement.map(cloneNode),
|
|
298
|
+
...document.content.slice(start + length)
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
var replaceTopLevelNode = (document, index, replacement) => normalizeDocument({
|
|
302
|
+
...document,
|
|
303
|
+
content: document.content.map((node, nodeIndex) => nodeIndex === index ? cloneNode(replacement) : cloneNode(node))
|
|
304
|
+
});
|
|
305
|
+
var moveTopLevelBlock = (document, from, to) => {
|
|
306
|
+
if (from < 0 || from >= document.content.length || to < 0 || to >= document.content.length || from === to) {
|
|
307
|
+
return normalizeDocument(document);
|
|
308
|
+
}
|
|
309
|
+
const nextContent = document.content.map(cloneNode);
|
|
310
|
+
const [item] = nextContent.splice(from, 1);
|
|
311
|
+
if (!item) {
|
|
312
|
+
return normalizeDocument(document);
|
|
313
|
+
}
|
|
314
|
+
nextContent.splice(to, 0, item);
|
|
315
|
+
return normalizeDocument({ ...document, content: nextContent });
|
|
316
|
+
};
|
|
317
|
+
var duplicateTopLevelBlock = (document, index) => {
|
|
318
|
+
if (index < 0 || index >= document.content.length) {
|
|
319
|
+
return normalizeDocument(document);
|
|
320
|
+
}
|
|
321
|
+
const nextContent = document.content.map(cloneNode);
|
|
322
|
+
nextContent.splice(index + 1, 0, cloneNode(document.content[index]));
|
|
323
|
+
return normalizeDocument({ ...document, content: nextContent });
|
|
324
|
+
};
|
|
325
|
+
var deleteTopLevelBlock = (document, index, emptyBlock = textBlock("paragraph", "")) => {
|
|
326
|
+
if (document.content.length <= 1) {
|
|
327
|
+
return normalizeDocument({ ...document, content: [cloneNode(emptyBlock)] });
|
|
328
|
+
}
|
|
329
|
+
if (index < 0 || index >= document.content.length) {
|
|
330
|
+
return normalizeDocument(document);
|
|
331
|
+
}
|
|
332
|
+
return normalizeDocument({
|
|
333
|
+
...document,
|
|
334
|
+
content: document.content.filter((_, currentIndex) => currentIndex !== index).map(cloneNode)
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
var createTransaction = (before, after, command) => ({
|
|
338
|
+
before,
|
|
339
|
+
after,
|
|
340
|
+
...command ? { command } : {},
|
|
341
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
export { applyCommand, cloneNode, createBlockId, createBlockRegistry, createDocument, createEditorState, createTextNode, createTransaction, deleteTopLevelBlock, duplicateTopLevelBlock, ensureBlockIds, findBlockSpecForNode, fromProseMirrorDocument, getBlockId, getDocumentText, getNodeMeta, getPlatformDocument, getPlatformSupport, isOpenEditorDocument, moveTopLevelBlock, normalizeDocument, parseEditorState, parseOpenEditorDocument, replaceTopLevelNode, replaceTopLevelRange, serializeEditorState, textBlock, toProseMirrorDocument, validateDocument, withNodeMeta };
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
346
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["content"],"mappings":"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","file":"index.js","sourcesContent":["export type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue = JsonPrimitive | JsonObject | JsonValue[];\nexport type JsonObject = { [key: string]: JsonValue | undefined };\n\nexport type ProseMirrorAttrs = Record<string, unknown>;\n\nexport type ProseMirrorMark = {\n type: string;\n attrs?: ProseMirrorAttrs;\n};\n\nexport type OpenEditorNodeMeta = {\n id?: string;\n block?: string;\n custom?: Record<string, unknown>;\n};\n\nexport type ProseMirrorNode = {\n type: string;\n attrs?: ProseMirrorAttrs;\n content?: ProseMirrorNode[];\n marks?: ProseMirrorMark[];\n text?: string;\n};\n\nexport type OpenEditorBlock = ProseMirrorNode & {\n attrs?: ProseMirrorAttrs & {\n \"data-openeditor-id\"?: string;\n \"data-openeditor-block\"?: string;\n openeditor?: OpenEditorNodeMeta;\n };\n};\n\nexport type OpenEditorDocumentMeta = {\n id?: string;\n title?: string;\n source?: string;\n createdAt?: string;\n updatedAt?: string;\n schemaVersion?: number;\n platform?: EditorPlatform;\n custom?: Record<string, unknown>;\n};\n\nexport type OpenEditorDocument = {\n type: \"doc\";\n version: 1;\n content: OpenEditorBlock[];\n meta?: OpenEditorDocumentMeta;\n};\n\nexport type ProseMirrorDocument = {\n type: \"doc\";\n content: ProseMirrorNode[];\n};\n\nexport type EditorPlatform = \"web\" | \"native\";\n\nexport type PlatformSupportLevel = \"supported\" | \"unsupported\";\n\nexport type PlatformSupport = {\n web: PlatformSupportLevel;\n native: PlatformSupportLevel;\n};\n\nexport type BlockSpec = {\n name: string;\n label: string;\n group: \"text\" | \"media\" | \"layout\" | \"structure\";\n defaultNode: () => OpenEditorBlock;\n matchNode?: (node: ProseMirrorNode) => boolean;\n support?: PlatformSupport;\n};\n\nexport type BlockRegistry = ReadonlyMap<string, BlockSpec>;\n\nexport type EditorSelection =\n | { type: \"none\" }\n | { type: \"text\"; anchor: number; head: number }\n | { type: \"node\"; from: number; to: number; nodeType?: string }\n | { type: \"block\"; blockId: string };\n\nexport type OpenEditorMarkName =\n | \"bold\"\n | \"italic\"\n | \"underline\"\n | \"strike\"\n | \"code\"\n | \"link\";\n\nexport type OpenEditorFeatureName =\n | \"headings\"\n | \"lists\"\n | \"taskLists\"\n | \"quotes\"\n | \"codeBlocks\"\n | \"dividers\"\n | \"links\"\n | \"images\"\n | \"columns\"\n | \"tables\";\n\nexport type OpenEditorFeatureSet = Readonly<Record<OpenEditorFeatureName, boolean>>;\n\nexport type SerializedEditorState = {\n document: OpenEditorDocument;\n selection?: EditorSelection;\n};\n\nexport type EditorTransaction = {\n before: OpenEditorDocument;\n after: OpenEditorDocument;\n command?: OpenEditorCommand;\n timestamp: string;\n};\n\nexport type OpenEditorCommand =\n | { type: \"setContent\"; document: OpenEditorDocument }\n | { type: \"insertBlock\"; block: string; at?: number; attrs?: ProseMirrorAttrs }\n | { type: \"moveBlock\"; from: number; to: number }\n | { type: \"duplicateBlock\"; index: number }\n | { type: \"deleteBlock\"; index: number }\n | { type: \"setLink\"; href?: string }\n | { type: \"toggleMark\"; mark: OpenEditorMarkName }\n | { type: \"undo\" }\n | { type: \"redo\" }\n | { type: \"replaceDocument\"; document: OpenEditorDocument }\n | { type: \"setSelection\"; selection: EditorSelection };\n\nexport type EditorCommand = OpenEditorCommand;\n\nexport type OpenEditorEventHandlers = {\n onChange?: (document: OpenEditorDocument) => void;\n onSelectionChange?: (selection: EditorSelection) => void;\n onFocus?: () => void;\n onBlur?: () => void;\n onReady?: (controller: OpenEditorController) => void;\n onCommand?: (command: OpenEditorCommand, transaction: EditorTransaction) => void;\n};\n\nexport type OpenEditorConfig = OpenEditorEventHandlers & {\n document?: OpenEditorDocument;\n editable?: boolean;\n placeholder?: string;\n enabledBlocks?: readonly string[];\n theme?: Record<string, string | number>;\n features?: Partial<OpenEditorFeatureSet>;\n};\n\nexport type OpenEditorController = {\n getDocument: () => OpenEditorDocument;\n setDocument: (document: OpenEditorDocument) => void;\n getSelection: () => EditorSelection;\n execute: (command: OpenEditorCommand) => void;\n};\n\nexport type DocumentValidationIssue = {\n path: string;\n message: string;\n};\n\nexport type DocumentValidationResult = {\n valid: boolean;\n issues: DocumentValidationIssue[];\n};\n\nexport type PlatformSupportIssue = {\n path: string;\n block: string;\n platform: EditorPlatform;\n support: PlatformSupportLevel;\n};\n\nexport type PlatformSupportResult = {\n platform: EditorPlatform;\n document: OpenEditorDocument;\n issues: PlatformSupportIssue[];\n};\n\nconst OPENEDITOR_ATTR = \"openeditor\";\nconst OPENEDITOR_ID_ATTR = \"data-openeditor-id\";\nconst OPENEDITOR_BLOCK_ATTR = \"data-openeditor-block\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst normalizeMeta = (meta?: OpenEditorDocumentMeta): OpenEditorDocumentMeta | undefined =>\n meta && Object.keys(meta).length ? { ...meta } : undefined;\n\nconst cloneAttrs = (attrs?: ProseMirrorAttrs): ProseMirrorAttrs | undefined =>\n attrs ? { ...attrs } : undefined;\n\nconst cloneMark = (mark: ProseMirrorMark): ProseMirrorMark => ({\n type: mark.type,\n ...(mark.attrs ? { attrs: cloneAttrs(mark.attrs) } : {}),\n});\n\nexport const cloneNode = <T extends ProseMirrorNode>(node: T): T => ({\n ...node,\n ...(node.attrs ? { attrs: cloneAttrs(node.attrs) } : {}),\n ...(node.marks ? { marks: node.marks.map(cloneMark) } : {}),\n ...(node.content ? { content: node.content.map(cloneNode) } : {}),\n});\n\nexport const createDocument = (\n content: ProseMirrorNode[] = [],\n meta?: OpenEditorDocumentMeta,\n): OpenEditorDocument => ({\n type: \"doc\",\n version: 1,\n content: content.map(cloneNode) as OpenEditorBlock[],\n ...(normalizeMeta(meta) ? { meta: normalizeMeta(meta) } : {}),\n});\n\nexport const createEditorState = (\n document: OpenEditorDocument,\n selection: EditorSelection = { type: \"none\" },\n): SerializedEditorState => ({\n document: normalizeDocument(document),\n selection,\n});\n\nexport const toProseMirrorDocument = (document: OpenEditorDocument): ProseMirrorDocument => ({\n type: \"doc\",\n content: document.content.map(cloneNode),\n});\n\nexport const fromProseMirrorDocument = (\n document: ProseMirrorDocument,\n meta?: OpenEditorDocumentMeta,\n): OpenEditorDocument => createDocument(document.content, meta);\n\nexport const createTextNode = (text: string, marks?: ProseMirrorMark[]): ProseMirrorNode => ({\n type: \"text\",\n text,\n ...(marks?.length ? { marks: marks.map(cloneMark) } : {}),\n});\n\nexport const textBlock = (type: string, text: string, attrs?: ProseMirrorAttrs): OpenEditorBlock => ({\n type,\n ...(attrs ? { attrs: cloneAttrs(attrs) } : {}),\n content: text ? [createTextNode(text)] : [],\n});\n\nexport const createBlockRegistry = (specs: BlockSpec[]): BlockRegistry =>\n new Map(specs.map((spec) => [spec.name, spec]));\n\nexport const findBlockSpecForNode = (\n registry: BlockRegistry,\n node: ProseMirrorNode,\n): BlockSpec | undefined => {\n for (const spec of registry.values()) {\n if (spec.matchNode?.(node) || spec.name === node.type) {\n return spec;\n }\n }\n\n return undefined;\n};\n\nexport const getNodeMeta = (node: ProseMirrorNode): OpenEditorNodeMeta | undefined => {\n const attrs = node.attrs;\n const nested = attrs?.[OPENEDITOR_ATTR];\n const id = attrs?.[OPENEDITOR_ID_ATTR];\n const block = attrs?.[OPENEDITOR_BLOCK_ATTR];\n\n if (isRecord(nested)) {\n return {\n ...nested,\n ...(typeof id === \"string\" ? { id } : {}),\n ...(typeof block === \"string\" ? { block } : {}),\n } as OpenEditorNodeMeta;\n }\n\n if (typeof id === \"string\" || typeof block === \"string\") {\n return {\n ...(typeof id === \"string\" ? { id } : {}),\n ...(typeof block === \"string\" ? { block } : {}),\n };\n }\n\n return undefined;\n};\n\nexport const withNodeMeta = <T extends ProseMirrorNode>(\n node: T,\n meta: OpenEditorNodeMeta,\n): T => {\n const attrs = cloneAttrs(node.attrs) ?? {};\n const previous = getNodeMeta(node);\n const nextMeta = { ...previous, ...meta };\n\n if (nextMeta.id) {\n attrs[OPENEDITOR_ID_ATTR] = nextMeta.id;\n }\n\n if (nextMeta.block) {\n attrs[OPENEDITOR_BLOCK_ATTR] = nextMeta.block;\n }\n\n attrs[OPENEDITOR_ATTR] = nextMeta;\n return { ...node, attrs } as T;\n};\n\nexport const getBlockId = (node: ProseMirrorNode): string | undefined =>\n getNodeMeta(node)?.id;\n\nexport const createBlockId = (prefix = \"oe\"): string => {\n const random =\n typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : Math.random().toString(36).slice(2);\n\n return `${prefix}_${random.replaceAll(\"-\", \"\").slice(0, 24)}`;\n};\n\nexport const ensureBlockIds = (\n document: OpenEditorDocument,\n createId: () => string = createBlockId,\n): OpenEditorDocument =>\n createDocument(\n document.content.map((node) =>\n getBlockId(node) ? node : withNodeMeta(node, { id: createId() }),\n ),\n document.meta,\n );\n\nconst normalizeNode = (node: ProseMirrorNode): OpenEditorBlock => {\n if (node.type === \"heading\") {\n const level = typeof node.attrs?.level === \"number\" ? node.attrs.level : 2;\n const content = node.content?.map(normalizeNode);\n\n return {\n ...cloneNode(node),\n attrs: { ...node.attrs, level: Math.min(Math.max(level, 1), 6) },\n ...(content ? { content } : {}),\n };\n }\n\n if (node.type === \"columns\") {\n const content = node.content?.length\n ? node.content.map(normalizeNode)\n : [\n { type: \"column\", content: [textBlock(\"paragraph\", \"\")] },\n { type: \"column\", content: [textBlock(\"paragraph\", \"\")] },\n ];\n\n return {\n ...cloneNode(node),\n attrs: { ...node.attrs, count: content.length },\n content,\n };\n }\n\n if (node.type === \"column\" && !node.content?.length) {\n return { ...cloneNode(node), content: [textBlock(\"paragraph\", \"\")] };\n }\n\n const content = node.content?.map(normalizeNode);\n return {\n ...cloneNode(node),\n ...(content ? { content } : {}),\n };\n};\n\nexport const normalizeDocument = (document: OpenEditorDocument): OpenEditorDocument =>\n createDocument(document.content.map(normalizeNode), document.meta);\n\nexport const validateDocument = (document: unknown): DocumentValidationResult => {\n const issues: DocumentValidationIssue[] = [];\n const push = (path: string, message: string) => issues.push({ path, message });\n\n const validateNode = (node: unknown, path: string) => {\n if (!isRecord(node)) {\n push(path, \"Node must be an object.\");\n return;\n }\n\n if (typeof node.type !== \"string\" || !node.type) {\n push(`${path}.type`, \"Node type must be a non-empty string.\");\n }\n\n if (\"text\" in node && typeof node.text !== \"string\") {\n push(`${path}.text`, \"Text node content must be a string.\");\n }\n\n if (\"attrs\" in node && node.attrs !== undefined && !isRecord(node.attrs)) {\n push(`${path}.attrs`, \"Node attrs must be an object.\");\n }\n\n if (\"marks\" in node && node.marks !== undefined) {\n if (!Array.isArray(node.marks)) {\n push(`${path}.marks`, \"Marks must be an array.\");\n } else {\n node.marks.forEach((mark, index) => {\n if (!isRecord(mark) || typeof mark.type !== \"string\" || !mark.type) {\n push(`${path}.marks.${index}`, \"Mark must have a non-empty type.\");\n }\n });\n }\n }\n\n if (\"content\" in node && node.content !== undefined) {\n if (!Array.isArray(node.content)) {\n push(`${path}.content`, \"Node content must be an array.\");\n } else {\n node.content.forEach((child, index) => validateNode(child, `${path}.content.${index}`));\n }\n }\n };\n\n if (!isRecord(document)) {\n return {\n valid: false,\n issues: [{ path: \"$\", message: \"Document must be an object.\" }],\n };\n }\n\n if (document.type !== \"doc\") {\n push(\"$.type\", 'Document type must be \"doc\".');\n }\n\n if (document.version !== 1) {\n push(\"$.version\", \"Document version must be 1.\");\n }\n\n if (!Array.isArray(document.content)) {\n push(\"$.content\", \"Document content must be an array.\");\n } else {\n document.content.forEach((node, index) => validateNode(node, `$.content.${index}`));\n }\n\n if (\"meta\" in document && document.meta !== undefined && !isRecord(document.meta)) {\n push(\"$.meta\", \"Document meta must be an object.\");\n }\n\n return {\n valid: issues.length === 0,\n issues,\n };\n};\n\nexport const isOpenEditorDocument = (value: unknown): value is OpenEditorDocument =>\n validateDocument(value).valid;\n\nexport const parseOpenEditorDocument = (\n value: unknown,\n defaultDocument: OpenEditorDocument = createDocument(),\n): OpenEditorDocument => {\n if (isOpenEditorDocument(value)) {\n return normalizeDocument(value);\n }\n\n if (isRecord(value) && value.type === \"doc\" && Array.isArray(value.content)) {\n return fromProseMirrorDocument(value as ProseMirrorDocument, defaultDocument.meta);\n }\n\n return defaultDocument;\n};\n\nexport const serializeEditorState = (state: SerializedEditorState): string =>\n JSON.stringify({\n document: normalizeDocument(state.document),\n ...(state.selection ? { selection: state.selection } : {}),\n });\n\nexport const parseEditorState = (\n value: unknown,\n defaultState: SerializedEditorState = createEditorState(createDocument()),\n): SerializedEditorState => {\n let parsed: unknown;\n\n try {\n parsed = typeof value === \"string\" ? JSON.parse(value) : value;\n } catch {\n return defaultState;\n }\n\n if (!isRecord(parsed)) {\n return defaultState;\n }\n\n return {\n document: parseOpenEditorDocument(parsed.document, defaultState.document),\n selection: isRecord(parsed.selection) ? (parsed.selection as EditorSelection) : defaultState.selection,\n };\n};\n\nexport const getPlatformDocument = (\n document: OpenEditorDocument,\n registry: BlockRegistry,\n platform: EditorPlatform,\n): OpenEditorDocument => getPlatformSupport(document, registry, platform).document;\n\nexport const getPlatformSupport = (\n document: OpenEditorDocument,\n registry: BlockRegistry,\n platform: EditorPlatform,\n): PlatformSupportResult => {\n const issues: PlatformSupportIssue[] = [];\n\n const mapNode = (node: OpenEditorBlock, path: string): OpenEditorBlock => {\n const spec = findBlockSpecForNode(registry, node);\n const support = spec?.support?.[platform] ?? \"supported\";\n\n if (support !== \"supported\") {\n issues.push({\n path,\n block: spec?.name ?? node.type,\n platform,\n support,\n });\n }\n\n return normalizeNode({\n ...cloneNode(node),\n content: node.content?.map((child, index) => mapNode(child, `${path}.content.${index}`)),\n });\n };\n\n return {\n platform,\n document: createDocument(\n document.content.map((node, index) => mapNode(node, `$.content.${index}`)),\n { ...document.meta, platform },\n ),\n issues,\n };\n};\n\nconst INLINE_NODE_TYPES = new Set([\"text\", \"hardBreak\"]);\n\nexport const getDocumentText = (node: OpenEditorDocument | ProseMirrorNode): string => {\n if (\"text\" in node && typeof node.text === \"string\") {\n return node.text;\n }\n\n const content = \"content\" in node ? node.content : undefined;\n\n if (!content?.length) {\n return \"\";\n }\n\n const isInlineContainer = content.every(\n (child) => INLINE_NODE_TYPES.has(child.type) || child.type === \"link\",\n );\n const joiner = isInlineContainer ? \"\" : \"\\n\";\n return content.map(getDocumentText).filter(Boolean).join(joiner);\n};\n\nexport const applyCommand = (\n document: OpenEditorDocument,\n registry: BlockRegistry,\n command: OpenEditorCommand,\n): OpenEditorDocument => {\n if (command.type === \"setContent\") {\n return normalizeDocument(command.document);\n }\n\n if (command.type === \"replaceDocument\") {\n return normalizeDocument(command.document);\n }\n\n if (command.type === \"setSelection\") {\n return document;\n }\n\n if (command.type === \"moveBlock\") {\n return moveTopLevelBlock(document, command.from, command.to);\n }\n\n if (command.type === \"duplicateBlock\") {\n return duplicateTopLevelBlock(document, command.index);\n }\n\n if (command.type === \"deleteBlock\") {\n return deleteTopLevelBlock(document, command.index);\n }\n\n if (\n command.type === \"setLink\"\n || command.type === \"toggleMark\"\n || command.type === \"undo\"\n || command.type === \"redo\"\n ) {\n return normalizeDocument(document);\n }\n\n const spec = registry.get(command.block);\n if (!spec) {\n throw new Error(`Unknown block \"${command.block}\"`);\n }\n\n const nextContent = [...document.content];\n const index = command.at ?? nextContent.length;\n const defaultNode = spec.defaultNode();\n const node = command.attrs\n ? { ...defaultNode, attrs: { ...defaultNode.attrs, ...command.attrs } }\n : defaultNode;\n\n nextContent.splice(index, 0, node);\n return normalizeDocument(createDocument(nextContent, document.meta));\n};\n\nexport const replaceTopLevelRange = (\n document: OpenEditorDocument,\n start: number,\n length: number,\n replacement: ProseMirrorNode[],\n): OpenEditorDocument =>\n normalizeDocument({\n ...document,\n content: [\n ...document.content.slice(0, start),\n ...replacement.map(cloneNode),\n ...document.content.slice(start + length),\n ],\n });\n\nexport const replaceTopLevelNode = (\n document: OpenEditorDocument,\n index: number,\n replacement: ProseMirrorNode,\n): OpenEditorDocument =>\n normalizeDocument({\n ...document,\n content: document.content.map((node, nodeIndex) => nodeIndex === index ? cloneNode(replacement) : cloneNode(node)),\n });\n\nexport const moveTopLevelBlock = (\n document: OpenEditorDocument,\n from: number,\n to: number,\n): OpenEditorDocument => {\n if (from < 0 || from >= document.content.length || to < 0 || to >= document.content.length || from === to) {\n return normalizeDocument(document);\n }\n\n const nextContent = document.content.map(cloneNode);\n const [item] = nextContent.splice(from, 1);\n if (!item) {\n return normalizeDocument(document);\n }\n\n nextContent.splice(to, 0, item);\n return normalizeDocument({ ...document, content: nextContent });\n};\n\nexport const duplicateTopLevelBlock = (\n document: OpenEditorDocument,\n index: number,\n): OpenEditorDocument => {\n if (index < 0 || index >= document.content.length) {\n return normalizeDocument(document);\n }\n\n const nextContent = document.content.map(cloneNode);\n nextContent.splice(index + 1, 0, cloneNode(document.content[index]));\n return normalizeDocument({ ...document, content: nextContent });\n};\n\nexport const deleteTopLevelBlock = (\n document: OpenEditorDocument,\n index: number,\n emptyBlock: OpenEditorBlock = textBlock(\"paragraph\", \"\"),\n): OpenEditorDocument => {\n if (document.content.length <= 1) {\n return normalizeDocument({ ...document, content: [cloneNode(emptyBlock)] });\n }\n\n if (index < 0 || index >= document.content.length) {\n return normalizeDocument(document);\n }\n\n return normalizeDocument({\n ...document,\n content: document.content.filter((_, currentIndex) => currentIndex !== index).map(cloneNode),\n });\n};\n\nexport const createTransaction = (\n before: OpenEditorDocument,\n after: OpenEditorDocument,\n command?: EditorCommand,\n): EditorTransaction => ({\n before,\n after,\n ...(command ? { command } : {}),\n timestamp: new Date().toISOString(),\n});\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openeditor/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup --config tsup.config.ts",
|
|
23
|
+
"prepack": "pnpm run build",
|
|
24
|
+
"test": "pnpm run build && node --test test/*.test.mjs",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "6.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|