@openleaf-editor/core 0.1.0-beta.0
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/LICENSE +202 -0
- package/dist/commands.d.ts +82 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +327 -0
- package/dist/commands.js.map +1 -0
- package/dist/extensions.d.ts +91 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +281 -0
- package/dist/extensions.js.map +1 -0
- package/dist/html.d.ts +22 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +117 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/keymap.d.ts +49 -0
- package/dist/keymap.d.ts.map +1 -0
- package/dist/keymap.js +106 -0
- package/dist/keymap.js.map +1 -0
- package/dist/plugins.d.ts +29 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +91 -0
- package/dist/plugins.js.map +1 -0
- package/dist/preserve.d.ts +55 -0
- package/dist/preserve.d.ts.map +1 -0
- package/dist/preserve.js +288 -0
- package/dist/preserve.js.map +1 -0
- package/dist/schema.d.ts +23 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +268 -0
- package/dist/schema.js.map +1 -0
- package/dist/tables.d.ts +43 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +172 -0
- package/dist/tables.js.map +1 -0
- package/dist/url.d.ts +26 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +78 -0
- package/dist/url.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema extensions: how a plugin contributes node and mark types.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this could not be a simple registry
|
|
5
|
+
*
|
|
6
|
+
* A ProseMirror `Schema` is immutable, and `EditorState.reconfigure` -- the
|
|
7
|
+
* mechanism that lets a late-loading plugin add behaviour to an open editor --
|
|
8
|
+
* cannot change it. Verified in prosemirror-state: `reconfigure` builds
|
|
9
|
+
* `new Configuration(this.schema, config.plugins)`, taking the schema from the
|
|
10
|
+
* old state. So schema extension is not "register and the editors update"; it is
|
|
11
|
+
* "register before an editor is built, or wait for the next one".
|
|
12
|
+
*
|
|
13
|
+
* That constraint decides the shape of everything here.
|
|
14
|
+
*
|
|
15
|
+
* ## Append-only, and why it is not a preference
|
|
16
|
+
*
|
|
17
|
+
* Extension nodes are appended, never prepended, and there is no positioning
|
|
18
|
+
* hint. Measured: prepending a `group: 'block'` node makes it the document's
|
|
19
|
+
* `defaultType`, so `topNodeType.createAndFill()` produces
|
|
20
|
+
* `{"type":"doc","content":[{"type":"widget"}]}` instead of an empty paragraph.
|
|
21
|
+
* Every new document, and every gap the editor fills, would start with a
|
|
22
|
+
* plugin's widget.
|
|
23
|
+
*
|
|
24
|
+
* ## No priority field
|
|
25
|
+
*
|
|
26
|
+
* The preservation layer's catch-all rules sit at priority 0 and 1, which makes
|
|
27
|
+
* them the last two rules in the parse table -- so an extension rule at the
|
|
28
|
+
* default priority already wins, for free. Offering a priority knob would invite
|
|
29
|
+
* an author to set `priority: 0` to be polite and thereby tie with the
|
|
30
|
+
* catch-all, where the winner is decided by map insertion order. Instead,
|
|
31
|
+
* `createSchema` rejects any rule at priority <= 1 and says why.
|
|
32
|
+
*
|
|
33
|
+
* ## Collisions throw
|
|
34
|
+
*
|
|
35
|
+
* Deliberately the opposite of `registerToolbarItem`, which is last-wins because
|
|
36
|
+
* a button is UI and replacing one is a feature. A node type is a *storage
|
|
37
|
+
* format*: two definitions of `footnote` mean two serializations of the same
|
|
38
|
+
* content chosen by script-tag order, and whichever loses has already written
|
|
39
|
+
* documents in its shape. `replaces` is the explicit opt-in.
|
|
40
|
+
*/
|
|
41
|
+
import { Schema, type MarkSpec, type NodeSpec } from 'prosemirror-model';
|
|
42
|
+
export interface SchemaExtension {
|
|
43
|
+
/** Stable and unique. Namespace it: `openleaf/footnote`. */
|
|
44
|
+
readonly id: string;
|
|
45
|
+
/** Node types by schema name, appended after the core nodes. */
|
|
46
|
+
readonly nodes?: Readonly<Record<string, NodeSpec>>;
|
|
47
|
+
/** Mark types by schema name. */
|
|
48
|
+
readonly marks?: Readonly<Record<string, MarkSpec>>;
|
|
49
|
+
/** Names this extension deliberately replaces. Absent means a clash throws. */
|
|
50
|
+
readonly replaces?: readonly string[];
|
|
51
|
+
/**
|
|
52
|
+
* Re-emit attributes the spec does not model. Defaults to true.
|
|
53
|
+
*
|
|
54
|
+
* Adding a node type strictly *reduces* fidelity for the tag it claims: before
|
|
55
|
+
* the node existed, the preservation layer kept the element and every
|
|
56
|
+
* attribute on it; afterwards the spec keeps only what it declares. A callout
|
|
57
|
+
* node modelling `class` silently drops `id` and `data-analytics` that used to
|
|
58
|
+
* survive.
|
|
59
|
+
*
|
|
60
|
+
* So unmodelled attributes are captured on parse and merged back on
|
|
61
|
+
* serialize, by default, at schema-build time -- which means an author cannot
|
|
62
|
+
* opt out by forgetting.
|
|
63
|
+
*/
|
|
64
|
+
readonly carryUnknownAttributes?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** Where carried attributes live. Underscored: it is not for plugin authors. */
|
|
67
|
+
export declare const CARRIED_ATTR = "__openleafCarried";
|
|
68
|
+
/**
|
|
69
|
+
* Register a schema extension.
|
|
70
|
+
*
|
|
71
|
+
* Must happen before the editor that should have it is constructed. The element
|
|
72
|
+
* defers building its view until the document's scripts have run, which covers
|
|
73
|
+
* every documented integration; anything registering later applies to editors
|
|
74
|
+
* created after it, not to open ones.
|
|
75
|
+
*/
|
|
76
|
+
export declare function registerSchemaExtension(extension: SchemaExtension): () => void;
|
|
77
|
+
export declare function registeredSchemaExtensions(): readonly SchemaExtension[];
|
|
78
|
+
export declare function onSchemaExtensionsChange(listener: () => void): () => void;
|
|
79
|
+
/** Testing seam. Not part of the public API. */
|
|
80
|
+
export declare function clearSchemaExtensions(): void;
|
|
81
|
+
export declare function createSchema(list?: readonly SchemaExtension[]): Schema;
|
|
82
|
+
/**
|
|
83
|
+
* The schema for the currently registered extensions.
|
|
84
|
+
*
|
|
85
|
+
* A function rather than a constant, and this is the point: a `const` reads as
|
|
86
|
+
* "bind to this" and would be captured at import by every consumer, which is
|
|
87
|
+
* exactly what made the schema impossible to extend. Memoized, and invalidated
|
|
88
|
+
* whenever the registry changes.
|
|
89
|
+
*/
|
|
90
|
+
export declare function coreSchema(): Schema;
|
|
91
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,EACL,MAAM,EAEN,KAAK,QAAQ,EACb,KAAK,QAAQ,EAGd,MAAM,mBAAmB,CAAA;AAI1B,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IACnD,iCAAiC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IACnD,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAA;CAC1C;AAED,gFAAgF;AAChF,eAAO,MAAM,YAAY,sBAAsB,CAAA;AAmB/C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,IAAI,CAe9E;AAED,wBAAgB,0BAA0B,IAAI,SAAS,eAAe,EAAE,CAEvE;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAKzE;AAED,gDAAgD;AAChD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAgKD,wBAAgB,YAAY,CAAC,IAAI,GAAE,SAAS,eAAe,EAAO,GAAG,MAAM,CAyB1E;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAGnC"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema extensions: how a plugin contributes node and mark types.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this could not be a simple registry
|
|
5
|
+
*
|
|
6
|
+
* A ProseMirror `Schema` is immutable, and `EditorState.reconfigure` -- the
|
|
7
|
+
* mechanism that lets a late-loading plugin add behaviour to an open editor --
|
|
8
|
+
* cannot change it. Verified in prosemirror-state: `reconfigure` builds
|
|
9
|
+
* `new Configuration(this.schema, config.plugins)`, taking the schema from the
|
|
10
|
+
* old state. So schema extension is not "register and the editors update"; it is
|
|
11
|
+
* "register before an editor is built, or wait for the next one".
|
|
12
|
+
*
|
|
13
|
+
* That constraint decides the shape of everything here.
|
|
14
|
+
*
|
|
15
|
+
* ## Append-only, and why it is not a preference
|
|
16
|
+
*
|
|
17
|
+
* Extension nodes are appended, never prepended, and there is no positioning
|
|
18
|
+
* hint. Measured: prepending a `group: 'block'` node makes it the document's
|
|
19
|
+
* `defaultType`, so `topNodeType.createAndFill()` produces
|
|
20
|
+
* `{"type":"doc","content":[{"type":"widget"}]}` instead of an empty paragraph.
|
|
21
|
+
* Every new document, and every gap the editor fills, would start with a
|
|
22
|
+
* plugin's widget.
|
|
23
|
+
*
|
|
24
|
+
* ## No priority field
|
|
25
|
+
*
|
|
26
|
+
* The preservation layer's catch-all rules sit at priority 0 and 1, which makes
|
|
27
|
+
* them the last two rules in the parse table -- so an extension rule at the
|
|
28
|
+
* default priority already wins, for free. Offering a priority knob would invite
|
|
29
|
+
* an author to set `priority: 0` to be polite and thereby tie with the
|
|
30
|
+
* catch-all, where the winner is decided by map insertion order. Instead,
|
|
31
|
+
* `createSchema` rejects any rule at priority <= 1 and says why.
|
|
32
|
+
*
|
|
33
|
+
* ## Collisions throw
|
|
34
|
+
*
|
|
35
|
+
* Deliberately the opposite of `registerToolbarItem`, which is last-wins because
|
|
36
|
+
* a button is UI and replacing one is a feature. A node type is a *storage
|
|
37
|
+
* format*: two definitions of `footnote` mean two serializations of the same
|
|
38
|
+
* content chosen by script-tag order, and whichever loses has already written
|
|
39
|
+
* documents in its shape. `replaces` is the explicit opt-in.
|
|
40
|
+
*/
|
|
41
|
+
import OrderedMap from 'orderedmap';
|
|
42
|
+
import { Schema, } from 'prosemirror-model';
|
|
43
|
+
import { coreMarks, coreNodes } from './schema.js';
|
|
44
|
+
import { URL_ATTRIBUTES, isEventHandlerAttribute, isSafeUrl } from './url.js';
|
|
45
|
+
/** Where carried attributes live. Underscored: it is not for plugin authors. */
|
|
46
|
+
export const CARRIED_ATTR = '__openleafCarried';
|
|
47
|
+
/* ------------------------------------------------------------------ *
|
|
48
|
+
* Registry
|
|
49
|
+
* ------------------------------------------------------------------ */
|
|
50
|
+
const extensions = new Map();
|
|
51
|
+
const listeners = new Set();
|
|
52
|
+
function notify() {
|
|
53
|
+
for (const listener of listeners) {
|
|
54
|
+
try {
|
|
55
|
+
listener();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('@openleaf-editor/core: a schema-extension listener threw', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register a schema extension.
|
|
64
|
+
*
|
|
65
|
+
* Must happen before the editor that should have it is constructed. The element
|
|
66
|
+
* defers building its view until the document's scripts have run, which covers
|
|
67
|
+
* every documented integration; anything registering later applies to editors
|
|
68
|
+
* created after it, not to open ones.
|
|
69
|
+
*/
|
|
70
|
+
export function registerSchemaExtension(extension) {
|
|
71
|
+
if (extensions.has(extension.id)) {
|
|
72
|
+
console.warn(`@openleaf-editor/core: schema extension "${extension.id}" is already registered. ` +
|
|
73
|
+
'The second registration was ignored.');
|
|
74
|
+
return () => undefined;
|
|
75
|
+
}
|
|
76
|
+
extensions.set(extension.id, extension);
|
|
77
|
+
notify();
|
|
78
|
+
return () => {
|
|
79
|
+
// Removing an extension cannot un-extend a schema that is already built --
|
|
80
|
+
// a document may contain its nodes. It affects editors created afterwards.
|
|
81
|
+
if (extensions.delete(extension.id))
|
|
82
|
+
notify();
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function registeredSchemaExtensions() {
|
|
86
|
+
return [...extensions.values()];
|
|
87
|
+
}
|
|
88
|
+
export function onSchemaExtensionsChange(listener) {
|
|
89
|
+
listeners.add(listener);
|
|
90
|
+
return () => {
|
|
91
|
+
listeners.delete(listener);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Testing seam. Not part of the public API. */
|
|
95
|
+
export function clearSchemaExtensions() {
|
|
96
|
+
extensions.clear();
|
|
97
|
+
notify();
|
|
98
|
+
}
|
|
99
|
+
/* ------------------------------------------------------------------ *
|
|
100
|
+
* Carrying unmodelled attributes
|
|
101
|
+
* ------------------------------------------------------------------ */
|
|
102
|
+
function isPlainObject(value) {
|
|
103
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Residue a spec has already encoded into a modelled attribute, and so must not
|
|
107
|
+
* carry a second copy of.
|
|
108
|
+
*
|
|
109
|
+
* `code_block` reads `language-js` from either `<pre>` or `<code>` and re-emits
|
|
110
|
+
* it on `<code>` -- read both, write one. Carrying the `<pre>`'s class verbatim
|
|
111
|
+
* writes it twice, so the language token is dropped from the residue while any
|
|
112
|
+
* other class the author put there is kept. Keyed by node name because the
|
|
113
|
+
* overlap is a property of the spec, not of the attribute.
|
|
114
|
+
*/
|
|
115
|
+
const CARRY_SCRUB = {
|
|
116
|
+
code_block(carried) {
|
|
117
|
+
const cls = carried['class'];
|
|
118
|
+
if (cls === undefined)
|
|
119
|
+
return;
|
|
120
|
+
const kept = cls.split(/\s+/).filter((c) => c && !/^(?:language|lang)-/i.test(c));
|
|
121
|
+
if (kept.length > 0)
|
|
122
|
+
carried['class'] = kept.join(' ');
|
|
123
|
+
else
|
|
124
|
+
delete carried['class'];
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Wrap a node spec so attributes it does not model survive the round trip.
|
|
129
|
+
*
|
|
130
|
+
* Applied to extension nodes unconditionally: they only ever claim markup the
|
|
131
|
+
* preservation layer previously kept in full, so carrying the residue is a pure
|
|
132
|
+
* improvement over the alternative of silently dropping it.
|
|
133
|
+
*/
|
|
134
|
+
function withCarriedAttributes(name, spec) {
|
|
135
|
+
const modelled = new Set(Object.keys(spec.attrs ?? {}));
|
|
136
|
+
const attrs = { ...(spec.attrs ?? {}), [CARRIED_ATTR]: { default: null } };
|
|
137
|
+
// A node's parse rules are always tag rules -- only marks may match styles --
|
|
138
|
+
// so the narrower type is the accurate one and keeps the map total.
|
|
139
|
+
const parseDOM = (spec.parseDOM ?? []).map((rule) => {
|
|
140
|
+
const original = rule.getAttrs;
|
|
141
|
+
return {
|
|
142
|
+
...rule,
|
|
143
|
+
getAttrs(dom) {
|
|
144
|
+
const base = original ? original.call(rule, dom) : (rule.attrs ?? {});
|
|
145
|
+
if (base === false || base === null || base === undefined)
|
|
146
|
+
return base;
|
|
147
|
+
const carried = {};
|
|
148
|
+
for (const attr of Array.from(dom.attributes ?? [])) {
|
|
149
|
+
if (modelled.has(attr.name))
|
|
150
|
+
continue;
|
|
151
|
+
// Same scrub as the preservation layer: carrying `onclick` or a
|
|
152
|
+
// `javascript:` URL would reintroduce exactly the executable content
|
|
153
|
+
// core promises to drop.
|
|
154
|
+
if (isEventHandlerAttribute(attr.name))
|
|
155
|
+
continue;
|
|
156
|
+
if (URL_ATTRIBUTES.has(attr.name.toLowerCase()) && !isSafeUrl(attr.value))
|
|
157
|
+
continue;
|
|
158
|
+
carried[attr.name] = attr.value;
|
|
159
|
+
}
|
|
160
|
+
CARRY_SCRUB[name]?.(carried);
|
|
161
|
+
return {
|
|
162
|
+
...base,
|
|
163
|
+
[CARRIED_ATTR]: Object.keys(carried).length > 0 ? carried : null,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const originalToDOM = spec.toDOM;
|
|
169
|
+
const toDOM = originalToDOM
|
|
170
|
+
? (node) => {
|
|
171
|
+
const out = originalToDOM(node);
|
|
172
|
+
const carried = node.attrs[CARRIED_ATTR];
|
|
173
|
+
if (!carried || !Array.isArray(out))
|
|
174
|
+
return out;
|
|
175
|
+
const result = [...out];
|
|
176
|
+
if (isPlainObject(result[1])) {
|
|
177
|
+
// Modelled attributes win: an author's spec is the authority on the
|
|
178
|
+
// names it declared.
|
|
179
|
+
result[1] = { ...carried, ...result[1] };
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
result.splice(1, 0, carried);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
: originalToDOM;
|
|
187
|
+
return { ...spec, attrs, parseDOM, ...(toDOM ? { toDOM } : {}) };
|
|
188
|
+
}
|
|
189
|
+
/* ------------------------------------------------------------------ *
|
|
190
|
+
* Building
|
|
191
|
+
* ------------------------------------------------------------------ */
|
|
192
|
+
function assertRulePriorities(extensionId, name, spec) {
|
|
193
|
+
for (const rule of (spec.parseDOM ?? [])) {
|
|
194
|
+
if (rule.priority !== undefined && rule.priority <= 1) {
|
|
195
|
+
throw new Error(`@openleaf-editor/core: extension "${extensionId}" gives "${name}" a parse rule at ` +
|
|
196
|
+
`priority ${rule.priority}. The preservation layer's catch-all rules sit at ` +
|
|
197
|
+
'priority 0 and 1, so this rule would tie with them and the winner would be ' +
|
|
198
|
+
'decided by insertion order. Remove the priority: the default already wins.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function claim(claimed, kind, name, extension, existsInCore) {
|
|
203
|
+
const replaces = new Set(extension.replaces ?? []);
|
|
204
|
+
const previous = claimed.get(`${kind}:${name}`);
|
|
205
|
+
if (previous && !replaces.has(name)) {
|
|
206
|
+
throw new Error(`@openleaf-editor/core: extensions "${previous}" and "${extension.id}" both define the ` +
|
|
207
|
+
`${kind} "${name}". A ${kind} type is a storage format, not a preference: two ` +
|
|
208
|
+
'definitions mean two serializations of the same content chosen by load order. ' +
|
|
209
|
+
`If replacing it is intended, declare replaces: ['${name}'].`);
|
|
210
|
+
}
|
|
211
|
+
if (existsInCore && !replaces.has(name)) {
|
|
212
|
+
throw new Error(`@openleaf-editor/core: extension "${extension.id}" defines the ${kind} "${name}", which ` +
|
|
213
|
+
`already exists in the base schema. If replacing it is intended, declare ` +
|
|
214
|
+
`replaces: ['${name}'].`);
|
|
215
|
+
}
|
|
216
|
+
claimed.set(`${kind}:${name}`, extension.id);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Build a schema from the base types plus these extensions.
|
|
220
|
+
*
|
|
221
|
+
* Pure: it reads no registry. That is deliberate -- a registry-reading default
|
|
222
|
+
* would make the fidelity suite depend on whichever other test file happened to
|
|
223
|
+
* register an extension first.
|
|
224
|
+
*/
|
|
225
|
+
/**
|
|
226
|
+
* Node types whose attributes are already the whole story — wrapping them
|
|
227
|
+
* would duplicate markup (unknown_*) or add a phantom attr to nodes that
|
|
228
|
+
* never parse from the DOM (doc, text).
|
|
229
|
+
*/
|
|
230
|
+
const SKIP_CARRY = new Set(['doc', 'text', 'unknown_block', 'unknown_inline']);
|
|
231
|
+
function coreNodesWithCarriedAttributes() {
|
|
232
|
+
// Claimed tags used to drop every attribute they do not model. Extension
|
|
233
|
+
// nodes already carry the residue; core nodes were the remaining hole, and
|
|
234
|
+
// it is how `<p class="lead">` became `<p>` on the first save.
|
|
235
|
+
let nodes = OrderedMap.from({});
|
|
236
|
+
for (const [name, spec] of Object.entries(coreNodes)) {
|
|
237
|
+
nodes = nodes.addToEnd(name, SKIP_CARRY.has(name) ? spec : withCarriedAttributes(name, spec));
|
|
238
|
+
}
|
|
239
|
+
return nodes;
|
|
240
|
+
}
|
|
241
|
+
export function createSchema(list = []) {
|
|
242
|
+
let nodes = coreNodesWithCarriedAttributes();
|
|
243
|
+
let marks = OrderedMap.from(coreMarks);
|
|
244
|
+
const claimed = new Map();
|
|
245
|
+
for (const extension of list) {
|
|
246
|
+
for (const [name, spec] of Object.entries(extension.nodes ?? {})) {
|
|
247
|
+
assertRulePriorities(extension.id, name, spec);
|
|
248
|
+
claim(claimed, 'node', name, extension, Object.hasOwn(coreNodes, name));
|
|
249
|
+
const prepared = extension.carryUnknownAttributes === false
|
|
250
|
+
? spec
|
|
251
|
+
: withCarriedAttributes(name, spec);
|
|
252
|
+
// addToEnd, never prepend: a leading block node becomes the document's
|
|
253
|
+
// defaultType and every new document would start with it.
|
|
254
|
+
nodes = nodes.remove(name).addToEnd(name, prepared);
|
|
255
|
+
}
|
|
256
|
+
for (const [name, spec] of Object.entries(extension.marks ?? {})) {
|
|
257
|
+
assertRulePriorities(extension.id, name, spec);
|
|
258
|
+
claim(claimed, 'mark', name, extension, Object.hasOwn(coreMarks, name));
|
|
259
|
+
marks = marks.remove(name).addToEnd(name, spec);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return new Schema({ nodes, marks });
|
|
263
|
+
}
|
|
264
|
+
let cached = null;
|
|
265
|
+
/**
|
|
266
|
+
* The schema for the currently registered extensions.
|
|
267
|
+
*
|
|
268
|
+
* A function rather than a constant, and this is the point: a `const` reads as
|
|
269
|
+
* "bind to this" and would be captured at import by every consumer, which is
|
|
270
|
+
* exactly what made the schema impossible to extend. Memoized, and invalidated
|
|
271
|
+
* whenever the registry changes.
|
|
272
|
+
*/
|
|
273
|
+
export function coreSchema() {
|
|
274
|
+
if (!cached)
|
|
275
|
+
cached = createSchema(registeredSchemaExtensions());
|
|
276
|
+
return cached;
|
|
277
|
+
}
|
|
278
|
+
onSchemaExtensionsChange(() => {
|
|
279
|
+
cached = null;
|
|
280
|
+
});
|
|
281
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"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"}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML in, HTML out.
|
|
3
|
+
*
|
|
4
|
+
* OpenLeaf's storage format is HTML, not a proprietary JSON document
|
|
5
|
+
* model. A CMS that adopts OpenLeaf and later drops it should be left
|
|
6
|
+
* with content it can still render, which rules out formats that require
|
|
7
|
+
* our code to interpret.
|
|
8
|
+
*/
|
|
9
|
+
import { type Node as PMNode, type Schema } from 'prosemirror-model';
|
|
10
|
+
export interface HtmlIOOptions {
|
|
11
|
+
/** DOM implementation to use. Defaults to the global `document`. */
|
|
12
|
+
document?: Document;
|
|
13
|
+
/** Schema to parse against. Defaults to the built-in one. */
|
|
14
|
+
schema?: Schema;
|
|
15
|
+
}
|
|
16
|
+
/** Parse an HTML string into an OpenLeaf document. */
|
|
17
|
+
export declare function parseHtml(html: string, opts?: HtmlIOOptions): PMNode;
|
|
18
|
+
/** Serialize an OpenLeaf document back to an HTML string. */
|
|
19
|
+
export declare function serializeHtml(node: PMNode, opts?: HtmlIOOptions): string;
|
|
20
|
+
/** Convenience: one full parse/serialize cycle. */
|
|
21
|
+
export declare function roundTrip(html: string, opts?: HtmlIOOptions): string;
|
|
22
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAA4B,KAAK,IAAI,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAqC9F,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAaD,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAOpE;AAqCD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAaxE;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAEpE"}
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML in, HTML out.
|
|
3
|
+
*
|
|
4
|
+
* OpenLeaf's storage format is HTML, not a proprietary JSON document
|
|
5
|
+
* model. A CMS that adopts OpenLeaf and later drops it should be left
|
|
6
|
+
* with content it can still render, which rules out formats that require
|
|
7
|
+
* our code to interpret.
|
|
8
|
+
*/
|
|
9
|
+
import { DOMParser, DOMSerializer } from 'prosemirror-model';
|
|
10
|
+
import { isInsidePreserved, withSerializationDocument } from './preserve.js';
|
|
11
|
+
import { coreSchema } from './extensions.js';
|
|
12
|
+
/**
|
|
13
|
+
* Parsers and serializers are resolved per schema rather than built once.
|
|
14
|
+
*
|
|
15
|
+
* `DOMSerializer.fromSchema` builds a map keyed by node NAME at construction, so
|
|
16
|
+
* a serializer built from one schema throws `this.nodes[node.type.name] is not a
|
|
17
|
+
* function` the moment it meets a node type a plugin added. Module-level
|
|
18
|
+
* instances were therefore a hard ceiling on extensibility, not just an
|
|
19
|
+
* optimisation.
|
|
20
|
+
*
|
|
21
|
+
* ProseMirror caches these on the schema object itself, so a WeakMap here is
|
|
22
|
+
* belt-and-braces -- it costs nothing and makes the intent explicit.
|
|
23
|
+
*/
|
|
24
|
+
const parsers = new WeakMap();
|
|
25
|
+
const serializers = new WeakMap();
|
|
26
|
+
function parserFor(target) {
|
|
27
|
+
let found = parsers.get(target);
|
|
28
|
+
if (!found) {
|
|
29
|
+
found = DOMParser.fromSchema(target);
|
|
30
|
+
parsers.set(target, found);
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
function serializerFor(target) {
|
|
35
|
+
let found = serializers.get(target);
|
|
36
|
+
if (!found) {
|
|
37
|
+
found = DOMSerializer.fromSchema(target);
|
|
38
|
+
serializers.set(target, found);
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
function resolveDocument(opts) {
|
|
43
|
+
const doc = opts?.document ?? (typeof document !== 'undefined' ? document : undefined);
|
|
44
|
+
if (!doc) {
|
|
45
|
+
throw new Error('@openleaf-editor/core: no Document available. Pass { document } when ' +
|
|
46
|
+
'running outside a browser.');
|
|
47
|
+
}
|
|
48
|
+
return doc;
|
|
49
|
+
}
|
|
50
|
+
/** Parse an HTML string into an OpenLeaf document. */
|
|
51
|
+
export function parseHtml(html, opts) {
|
|
52
|
+
const doc = resolveDocument(opts);
|
|
53
|
+
const tpl = doc.createElement('template');
|
|
54
|
+
tpl.innerHTML = html;
|
|
55
|
+
return parserFor(opts?.schema ?? coreSchema()).parse(tpl.content, {
|
|
56
|
+
preserveWhitespace: false,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Collapse `<td><p>text</p></td>` back to `<td>text</td>`.
|
|
61
|
+
*
|
|
62
|
+
* Table cells hold `block+` content, because real tables contain paragraphs and
|
|
63
|
+
* lists. The consequence is that parsing the overwhelmingly common legacy form
|
|
64
|
+
* `<td>text</td>` produces a cell containing a paragraph, and serializing it
|
|
65
|
+
* back would write `<td><p>text</p></td>` -- rewriting every cell of every table
|
|
66
|
+
* in a CMS the first time each post is opened and saved.
|
|
67
|
+
*
|
|
68
|
+
* That is a normalization rather than information loss, but "we changed every
|
|
69
|
+
* table in your archive" is not a thing this project gets to do quietly. So a
|
|
70
|
+
* cell holding exactly one attribute-free paragraph is unwrapped on the way out.
|
|
71
|
+
*
|
|
72
|
+
* The asymmetry is deliberate and worth stating: a cell that was authored as
|
|
73
|
+
* `<td><p>text</p></td>` also comes back as `<td>text</td>`. That form is rare
|
|
74
|
+
* in the content this editor inherits, and the alternative is rewriting the
|
|
75
|
+
* common case instead of the rare one.
|
|
76
|
+
*/
|
|
77
|
+
function unwrapSoleCellParagraph(host) {
|
|
78
|
+
for (const cell of Array.from(host.querySelectorAll('td, th'))) {
|
|
79
|
+
// Never reach inside preserved markup. A table nested in an unrecognised
|
|
80
|
+
// wrapper is content we undertook to return byte-identical, and a
|
|
81
|
+
// normalization that is right for our own tables is a broken promise there.
|
|
82
|
+
if (isInsidePreserved(cell))
|
|
83
|
+
continue;
|
|
84
|
+
if (cell.childElementCount !== 1)
|
|
85
|
+
continue;
|
|
86
|
+
const only = cell.firstElementChild;
|
|
87
|
+
if (!only || only.nodeName !== 'P' || only.attributes.length > 0)
|
|
88
|
+
continue;
|
|
89
|
+
// Only when the paragraph is the cell's entire content; a stray text node
|
|
90
|
+
// beside it means the markup is doing something we should not touch.
|
|
91
|
+
if (cell.childNodes.length !== 1)
|
|
92
|
+
continue;
|
|
93
|
+
while (only.firstChild)
|
|
94
|
+
cell.insertBefore(only.firstChild, only);
|
|
95
|
+
cell.removeChild(only);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Serialize an OpenLeaf document back to an HTML string. */
|
|
99
|
+
export function serializeHtml(node, opts) {
|
|
100
|
+
const doc = resolveDocument(opts);
|
|
101
|
+
return withSerializationDocument(doc, () => {
|
|
102
|
+
// Taken from the document itself, so a document built on an extended schema
|
|
103
|
+
// serializes with a serializer that knows its node types. Passing the wrong
|
|
104
|
+
// schema explicitly is still possible, but the default is now correct.
|
|
105
|
+
const target = opts?.schema ?? node.type.schema;
|
|
106
|
+
const fragment = serializerFor(target).serializeFragment(node.content, { document: doc });
|
|
107
|
+
const host = doc.createElement('div');
|
|
108
|
+
host.appendChild(fragment);
|
|
109
|
+
unwrapSoleCellParagraph(host);
|
|
110
|
+
return host.innerHTML;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Convenience: one full parse/serialize cycle. */
|
|
114
|
+
export function roundTrip(html, opts) {
|
|
115
|
+
return serializeHtml(parseHtml(html, opts), opts);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=html.js.map
|
package/dist/html.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAoC,MAAM,mBAAmB,CAAA;AAC9F,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAqB,CAAA;AAChD,MAAM,WAAW,GAAG,IAAI,OAAO,EAAyB,CAAA;AAExD,SAAS,SAAS,CAAC,MAAc;IAC/B,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACxC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AASD,SAAS,eAAe,CAAC,IAAoB;IAC3C,MAAM,GAAG,GAAG,IAAI,EAAE,QAAQ,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACtF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,4BAA4B,CAC/B,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAoB;IAC1D,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;IACzC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAA;IACpB,OAAO,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE;QAChE,kBAAkB,EAAE,KAAK;KAC1B,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,uBAAuB,CAAC,IAAa;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC/D,yEAAyE;QACzE,kEAAkE;QAClE,4EAA4E;QAC5E,IAAI,iBAAiB,CAAC,IAAI,CAAC;YAAE,SAAQ;QACrC,IAAI,IAAI,CAAC,iBAAiB,KAAK,CAAC;YAAE,SAAQ;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAA;QACnC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,SAAQ;QAC1E,0EAA0E;QAC1E,qEAAqE;QACrE,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC1C,OAAO,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QAChE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IACxB,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAoB;IAC9D,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACjC,OAAO,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE;QACzC,4EAA4E;QAC5E,4EAA4E;QAC5E,uEAAuE;QACvE,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;QAC/C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAA;QACzF,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QACrC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;QAC1B,uBAAuB,CAAC,IAAI,CAAC,CAAA;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAoB;IAC1D,OAAO,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;AACnD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { baseSchema, coreMarks, coreNodes } from './schema.js';
|
|
2
|
+
export { parseHtml, serializeHtml, roundTrip, type HtmlIOOptions } from './html.js';
|
|
3
|
+
export { isLosslesslyUnwrappable, unknownBlock, unknownInline } from './preserve.js';
|
|
4
|
+
export { URL_ATTRIBUTES, isEventHandlerAttribute, isSafeUrl, safeUrlOrNull, } from './url.js';
|
|
5
|
+
export { activeHeadingLevel, activeLink, canInsert, canRedo, canUndo, isMarkActive, isNodeActive, toggleBold, toggleInlineCode, toggleItalic, toggleStrike, toggleUnderline, insertHorizontalRule, setHeading, setParagraph, toggleBlockquote, toggleCodeBlock, toggleHeading, wrapInBlockquote, indentListItem, outdentListItem, splitListItemCommand, toggleBulletList, toggleOrderedList, insertImage, setLink, unsetLink, type ImageAttrs, type LinkAttrs, redo, undo, } from './commands.js';
|
|
6
|
+
export { buildKeymap, shortcutFor, shortcuts, type Shortcut, } from './keymap.js';
|
|
7
|
+
export { createRegisteredPlugins, onEditorPluginsChange, registerEditorPlugin, type EditorPluginFactory, } from './plugins.js';
|
|
8
|
+
export { table, table_cell, table_header, table_row } from './tables.js';
|
|
9
|
+
export { CARRIED_ATTR, clearSchemaExtensions, coreSchema, createSchema, onSchemaExtensionsChange, registerSchemaExtension, registeredSchemaExtensions, type SchemaExtension, } from './extensions.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AACpF,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,SAAS,EACT,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,OAAO,EAEL,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,YAAY,EACZ,YAAY,EAEZ,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,eAAe,EAEf,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,gBAAgB,EAEhB,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EAEjB,WAAW,EACX,OAAO,EACP,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EAEd,IAAI,EACJ,IAAI,GACL,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,WAAW,EACX,WAAW,EACX,SAAS,EACT,KAAK,QAAQ,GACd,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACxE,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { baseSchema, coreMarks, coreNodes } from './schema.js';
|
|
2
|
+
export { parseHtml, serializeHtml, roundTrip } from './html.js';
|
|
3
|
+
export { isLosslesslyUnwrappable, unknownBlock, unknownInline } from './preserve.js';
|
|
4
|
+
export { URL_ATTRIBUTES, isEventHandlerAttribute, isSafeUrl, safeUrlOrNull, } from './url.js';
|
|
5
|
+
export {
|
|
6
|
+
// predicates
|
|
7
|
+
activeHeadingLevel, activeLink, canInsert, canRedo, canUndo, isMarkActive, isNodeActive,
|
|
8
|
+
// marks
|
|
9
|
+
toggleBold, toggleInlineCode, toggleItalic, toggleStrike, toggleUnderline,
|
|
10
|
+
// blocks
|
|
11
|
+
insertHorizontalRule, setHeading, setParagraph, toggleBlockquote, toggleCodeBlock, toggleHeading, wrapInBlockquote,
|
|
12
|
+
// lists
|
|
13
|
+
indentListItem, outdentListItem, splitListItemCommand, toggleBulletList, toggleOrderedList,
|
|
14
|
+
// links and images
|
|
15
|
+
insertImage, setLink, unsetLink,
|
|
16
|
+
// history
|
|
17
|
+
redo, undo, } from './commands.js';
|
|
18
|
+
export { buildKeymap, shortcutFor, shortcuts, } from './keymap.js';
|
|
19
|
+
export { createRegisteredPlugins, onEditorPluginsChange, registerEditorPlugin, } from './plugins.js';
|
|
20
|
+
export { table, table_cell, table_header, table_row } from './tables.js';
|
|
21
|
+
export { CARRIED_ATTR, clearSchemaExtensions, coreSchema, createSchema, onSchemaExtensionsChange, registerSchemaExtension, registeredSchemaExtensions, } from './extensions.js';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAsB,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AACpF,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,SAAS,EACT,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,OAAO;AACL,aAAa;AACb,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,YAAY,EACZ,YAAY;AACZ,QAAQ;AACR,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,eAAe;AACf,SAAS;AACT,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,gBAAgB;AAChB,QAAQ;AACR,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB;AACjB,mBAAmB;AACnB,WAAW,EACX,OAAO,EACP,SAAS;AAGT,UAAU;AACV,IAAI,EACJ,IAAI,GACL,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,WAAW,EACX,WAAW,EACX,SAAS,GAEV,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,GAErB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACxE,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,GAE3B,MAAM,iBAAiB,CAAA"}
|
package/dist/keymap.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard shortcuts.
|
|
3
|
+
*
|
|
4
|
+
* Bindings follow whatever convention the largest number of users already have
|
|
5
|
+
* in their fingers, which usually means Word and Google Docs rather than
|
|
6
|
+
* anything invented here. Where those disagree, the web convention wins,
|
|
7
|
+
* because this editor lives in a browser.
|
|
8
|
+
*
|
|
9
|
+
* `Mod` resolves to Cmd on macOS and Ctrl elsewhere.
|
|
10
|
+
*
|
|
11
|
+
* ## On Tab
|
|
12
|
+
*
|
|
13
|
+
* Tab is deliberately NOT bound to list indentation, even though Word does it
|
|
14
|
+
* and users ask for it. Inside a `contenteditable`, capturing Tab removes the
|
|
15
|
+
* only way a keyboard user has to leave the editor, which is a WCAG 2.1.2
|
|
16
|
+
* keyboard-trap failure -- and for the institutional users who most need a free
|
|
17
|
+
* editor, that is a procurement blocker rather than a rough edge.
|
|
18
|
+
*
|
|
19
|
+
* Indentation uses `Mod-[` and `Mod-]` instead, matching Google Docs and
|
|
20
|
+
* VS Code. If Tab is ever added it must come with a documented escape (Escape
|
|
21
|
+
* then Tab, or a first-Tab-escapes heuristic) and real screen reader testing.
|
|
22
|
+
*/
|
|
23
|
+
import type { Command } from 'prosemirror-state';
|
|
24
|
+
/** A shortcut, its command, and a human label for the help surface. */
|
|
25
|
+
export interface Shortcut {
|
|
26
|
+
keys: string;
|
|
27
|
+
command: Command;
|
|
28
|
+
label: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The default shortcut table.
|
|
32
|
+
*
|
|
33
|
+
* Exported as data rather than as a finished keymap so that an integrator can
|
|
34
|
+
* remove a binding that collides with their own application, and so the help
|
|
35
|
+
* dialog and the toolbar tooltips can render the real bindings instead of a
|
|
36
|
+
* hand-maintained duplicate list that drifts.
|
|
37
|
+
*/
|
|
38
|
+
export declare const shortcuts: Shortcut[];
|
|
39
|
+
/**
|
|
40
|
+
* Build the keymap bindings object.
|
|
41
|
+
*
|
|
42
|
+
* `Enter` chains: splitting a list item must be tried before ProseMirror's
|
|
43
|
+
* default paragraph split, or pressing Enter in a list creates a paragraph
|
|
44
|
+
* instead of the next bullet.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildKeymap(custom?: Record<string, Command>): Record<string, Command>;
|
|
47
|
+
/** Human-readable shortcut for a label, with the platform's modifier symbol. */
|
|
48
|
+
export declare function shortcutFor(label: string, isMac?: boolean): string | null;
|
|
49
|
+
//# sourceMappingURL=keymap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAmBhD,uEAAuE;AACvE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,EA+B/B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,UAAc,GAAG,MAAM,GAAG,IAAI,CAQ7E"}
|