@lofcz/platejs-core 52.3.4
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 +21 -0
- package/README.md +11 -0
- package/dist/hotkeys-DI1HPO2Q.js +115 -0
- package/dist/hotkeys-DI1HPO2Q.js.map +1 -0
- package/dist/index-NTp--CEF.d.ts +4294 -0
- package/dist/index-NTp--CEF.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +387 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +2056 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +2582 -0
- package/dist/react/index.js.map +1 -0
- package/dist/static/index.d.ts +2 -0
- package/dist/static/index.js +4 -0
- package/dist/static-CVN6JhaR.js +728 -0
- package/dist/static-CVN6JhaR.js.map +1 -0
- package/dist/withSlate-1B0SfAWG.js +2823 -0
- package/dist/withSlate-1B0SfAWG.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,2823 @@
|
|
|
1
|
+
import { ElementApi, IS_FIREFOX, NodeApi, OperationApi, PathApi, PointApi, RangeApi, TextApi, assignLegacyApi, assignLegacyTransforms, combineMatchOptions, createEditor, queryNode, syncLegacyMethods, withHistory } from "@platejs/slate";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import { bindFirst, isDefined } from "@udecode/utils";
|
|
4
|
+
import merge from "lodash/merge.js";
|
|
5
|
+
import { createVanillaStore } from "zustand-x/vanilla";
|
|
6
|
+
import mergeWith from "lodash/mergeWith.js";
|
|
7
|
+
import defaults from "lodash/defaults.js";
|
|
8
|
+
import kebabCase from "lodash/kebabCase.js";
|
|
9
|
+
import pick from "lodash/pick.js";
|
|
10
|
+
import castArray from "lodash/castArray.js";
|
|
11
|
+
import { Path as Path$1 } from "slate";
|
|
12
|
+
import isEqual from "lodash/isEqual.js";
|
|
13
|
+
import isUndefined from "lodash/isUndefined.js";
|
|
14
|
+
import omitBy from "lodash/omitBy.js";
|
|
15
|
+
import { jsx } from "slate-hyperscript";
|
|
16
|
+
import cloneDeep from "lodash/cloneDeep.js";
|
|
17
|
+
|
|
18
|
+
//#region src/internal/utils/isFunction.ts
|
|
19
|
+
function isFunction(value) {
|
|
20
|
+
return typeof value === "function";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/internal/utils/mergePlugins.ts
|
|
25
|
+
function mergePlugins(basePlugin, ...sourcePlugins) {
|
|
26
|
+
return mergeWith({}, basePlugin, ...sourcePlugins, (objValue, srcValue, key) => {
|
|
27
|
+
if (Array.isArray(srcValue)) return srcValue;
|
|
28
|
+
if (key === "options") return {
|
|
29
|
+
...objValue,
|
|
30
|
+
...srcValue
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/lib/plugin/createSlatePlugin.ts
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new Plate plugin with the given configuration.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* - The plugin's key is required and specified by the K generic.
|
|
42
|
+
* - The `__extensions` array stores functions to be applied when `resolvePlugin`
|
|
43
|
+
* is called with an editor.
|
|
44
|
+
* - The `extend` method adds new extensions to be applied later.
|
|
45
|
+
* - The `extendPlugin` method extends an existing plugin (including nested
|
|
46
|
+
* plugins) or adds a new one if not found.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const myPlugin = createSlatePlugin<
|
|
50
|
+
* 'myPlugin',
|
|
51
|
+
* MyOptions,
|
|
52
|
+
* MyApi,
|
|
53
|
+
* MyTransforms
|
|
54
|
+
* >({
|
|
55
|
+
* key: 'myPlugin',
|
|
56
|
+
* options: { someOption: true },
|
|
57
|
+
* transforms: { someTransform: () => {} },
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* const extendedPlugin = myPlugin.extend({
|
|
61
|
+
* options: { anotherOption: false },
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* const pluginWithNestedExtension = extendedPlugin.extendPlugin(
|
|
65
|
+
* nestedPlugin,
|
|
66
|
+
* { options: { nestedOption: true } }
|
|
67
|
+
* );
|
|
68
|
+
*
|
|
69
|
+
* @template K - The literal type of the plugin key.
|
|
70
|
+
* @template O - The type of the plugin options.
|
|
71
|
+
* @template A - The type of the plugin utilities.
|
|
72
|
+
* @template T - The type of the plugin transforms.
|
|
73
|
+
* @template S - The type of the plugin storage.
|
|
74
|
+
* @param {Partial<SlatePlugin<K, O, A, T, S>>} config - The configuration
|
|
75
|
+
* object for the plugin.
|
|
76
|
+
* @returns {SlatePlugin<K, O, A, T, S>} A new Plate plugin instance with the
|
|
77
|
+
* following properties and methods:
|
|
78
|
+
*
|
|
79
|
+
* - All properties from the input config, merged with default values.
|
|
80
|
+
* - `configure`: A method to create a new plugin instance with updated options.
|
|
81
|
+
* - `extend`: A method to create a new plugin instance with additional
|
|
82
|
+
* configuration.
|
|
83
|
+
* - `extendPlugin`: A method to extend an existing plugin (including nested
|
|
84
|
+
* plugins) or add a new one if not found.
|
|
85
|
+
*/
|
|
86
|
+
function createSlatePlugin(config = {}) {
|
|
87
|
+
let baseConfig;
|
|
88
|
+
let initialExtension;
|
|
89
|
+
if (isFunction(config)) {
|
|
90
|
+
baseConfig = { key: "" };
|
|
91
|
+
initialExtension = (editor) => config(editor);
|
|
92
|
+
} else baseConfig = config;
|
|
93
|
+
const key = baseConfig.key ?? "";
|
|
94
|
+
const plugin = mergePlugins({
|
|
95
|
+
key,
|
|
96
|
+
__apiExtensions: [],
|
|
97
|
+
__configuration: null,
|
|
98
|
+
__extensions: initialExtension ? [initialExtension] : [],
|
|
99
|
+
__selectorExtensions: [],
|
|
100
|
+
api: {},
|
|
101
|
+
dependencies: [],
|
|
102
|
+
editor: {},
|
|
103
|
+
handlers: {},
|
|
104
|
+
inject: {},
|
|
105
|
+
node: { type: key },
|
|
106
|
+
options: {},
|
|
107
|
+
override: {},
|
|
108
|
+
parser: {},
|
|
109
|
+
parsers: {},
|
|
110
|
+
plugins: [],
|
|
111
|
+
priority: 100,
|
|
112
|
+
render: {},
|
|
113
|
+
rules: {},
|
|
114
|
+
shortcuts: {},
|
|
115
|
+
transforms: {}
|
|
116
|
+
}, config);
|
|
117
|
+
if (plugin.node.isLeaf && !isDefined(plugin.node.isDecoration)) plugin.node.isDecoration = true;
|
|
118
|
+
plugin.configure = (config$1) => {
|
|
119
|
+
const newPlugin = { ...plugin };
|
|
120
|
+
newPlugin.__configuration = (ctx) => isFunction(config$1) ? config$1(ctx) : config$1;
|
|
121
|
+
return createSlatePlugin(newPlugin);
|
|
122
|
+
};
|
|
123
|
+
plugin.configurePlugin = (p, config$1) => {
|
|
124
|
+
const newPlugin = { ...plugin };
|
|
125
|
+
const configureNestedPlugin = (plugins) => {
|
|
126
|
+
let found = false;
|
|
127
|
+
const updatedPlugins = plugins.map((nestedPlugin) => {
|
|
128
|
+
if (nestedPlugin.key === p.key) {
|
|
129
|
+
found = true;
|
|
130
|
+
return createSlatePlugin({
|
|
131
|
+
...nestedPlugin,
|
|
132
|
+
__configuration: (ctx) => isFunction(config$1) ? config$1(ctx) : config$1
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (nestedPlugin.plugins && nestedPlugin.plugins.length > 0) {
|
|
136
|
+
const result = configureNestedPlugin(nestedPlugin.plugins);
|
|
137
|
+
if (result.found) {
|
|
138
|
+
found = true;
|
|
139
|
+
return {
|
|
140
|
+
...nestedPlugin,
|
|
141
|
+
plugins: result.plugins
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return nestedPlugin;
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
found,
|
|
149
|
+
plugins: updatedPlugins
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
newPlugin.plugins = configureNestedPlugin(newPlugin.plugins).plugins;
|
|
153
|
+
return createSlatePlugin(newPlugin);
|
|
154
|
+
};
|
|
155
|
+
plugin.extendEditorApi = (extension) => {
|
|
156
|
+
const newPlugin = { ...plugin };
|
|
157
|
+
newPlugin.__apiExtensions = [...newPlugin.__apiExtensions, {
|
|
158
|
+
extension,
|
|
159
|
+
isPluginSpecific: false
|
|
160
|
+
}];
|
|
161
|
+
return createSlatePlugin(newPlugin);
|
|
162
|
+
};
|
|
163
|
+
plugin.extendSelectors = (extension) => {
|
|
164
|
+
const newPlugin = { ...plugin };
|
|
165
|
+
newPlugin.__selectorExtensions = [...newPlugin.__selectorExtensions, extension];
|
|
166
|
+
return createSlatePlugin(newPlugin);
|
|
167
|
+
};
|
|
168
|
+
plugin.extendApi = (extension) => {
|
|
169
|
+
const newPlugin = { ...plugin };
|
|
170
|
+
newPlugin.__apiExtensions = [...newPlugin.__apiExtensions, {
|
|
171
|
+
extension,
|
|
172
|
+
isPluginSpecific: true
|
|
173
|
+
}];
|
|
174
|
+
return createSlatePlugin(newPlugin);
|
|
175
|
+
};
|
|
176
|
+
plugin.extendEditorTransforms = (extension) => {
|
|
177
|
+
const newPlugin = { ...plugin };
|
|
178
|
+
newPlugin.__apiExtensions = [...newPlugin.__apiExtensions, {
|
|
179
|
+
extension,
|
|
180
|
+
isPluginSpecific: false,
|
|
181
|
+
isTransform: true
|
|
182
|
+
}];
|
|
183
|
+
return createSlatePlugin(newPlugin);
|
|
184
|
+
};
|
|
185
|
+
plugin.extendTransforms = (extension) => {
|
|
186
|
+
const newPlugin = { ...plugin };
|
|
187
|
+
newPlugin.__apiExtensions = [...newPlugin.__apiExtensions, {
|
|
188
|
+
extension,
|
|
189
|
+
isPluginSpecific: true,
|
|
190
|
+
isTransform: true
|
|
191
|
+
}];
|
|
192
|
+
return createSlatePlugin(newPlugin);
|
|
193
|
+
};
|
|
194
|
+
plugin.overrideEditor = (extension) => {
|
|
195
|
+
const newPlugin = { ...plugin };
|
|
196
|
+
newPlugin.__apiExtensions = [...newPlugin.__apiExtensions, {
|
|
197
|
+
extension,
|
|
198
|
+
isOverride: true,
|
|
199
|
+
isPluginSpecific: false,
|
|
200
|
+
isTransform: true
|
|
201
|
+
}];
|
|
202
|
+
return createSlatePlugin(newPlugin);
|
|
203
|
+
};
|
|
204
|
+
plugin.extend = (extendConfig) => {
|
|
205
|
+
let newPlugin = { ...plugin };
|
|
206
|
+
if (isFunction(extendConfig)) newPlugin.__extensions = [...newPlugin.__extensions, extendConfig];
|
|
207
|
+
else newPlugin = mergePlugins(newPlugin, extendConfig);
|
|
208
|
+
return createSlatePlugin(newPlugin);
|
|
209
|
+
};
|
|
210
|
+
plugin.clone = () => mergePlugins(plugin);
|
|
211
|
+
plugin.extendPlugin = (p, extendConfig) => {
|
|
212
|
+
const newPlugin = { ...plugin };
|
|
213
|
+
const extendNestedPlugin = (plugins) => {
|
|
214
|
+
let found = false;
|
|
215
|
+
const updatedPlugins = plugins.map((nestedPlugin) => {
|
|
216
|
+
if (nestedPlugin.key === p.key) {
|
|
217
|
+
found = true;
|
|
218
|
+
return createSlatePlugin({
|
|
219
|
+
...nestedPlugin,
|
|
220
|
+
__extensions: [...nestedPlugin.__extensions, (ctx) => isFunction(extendConfig) ? extendConfig(ctx) : extendConfig]
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (nestedPlugin.plugins && nestedPlugin.plugins.length > 0) {
|
|
224
|
+
const result$1 = extendNestedPlugin(nestedPlugin.plugins);
|
|
225
|
+
if (result$1.found) {
|
|
226
|
+
found = true;
|
|
227
|
+
return {
|
|
228
|
+
...nestedPlugin,
|
|
229
|
+
plugins: result$1.plugins
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return nestedPlugin;
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
found,
|
|
237
|
+
plugins: updatedPlugins
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
const result = extendNestedPlugin(newPlugin.plugins);
|
|
241
|
+
newPlugin.plugins = result.plugins;
|
|
242
|
+
if (!result.found) newPlugin.plugins.push(createSlatePlugin({
|
|
243
|
+
key: p.key,
|
|
244
|
+
__extensions: [(ctx) => isFunction(extendConfig) ? extendConfig(ctx) : extendConfig]
|
|
245
|
+
}));
|
|
246
|
+
return createSlatePlugin(newPlugin);
|
|
247
|
+
};
|
|
248
|
+
plugin.withComponent = (component) => plugin.extend({
|
|
249
|
+
node: { component },
|
|
250
|
+
render: { node: component }
|
|
251
|
+
});
|
|
252
|
+
return plugin;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Explicitly typed version of `createSlatePlugin`.
|
|
256
|
+
*
|
|
257
|
+
* @remarks
|
|
258
|
+
* While `createSlatePlugin` uses type inference, this function requires an
|
|
259
|
+
* explicit type parameter. Use this when you need precise control over the
|
|
260
|
+
* plugin's type structure or when type inference doesn't provide the desired
|
|
261
|
+
* result.
|
|
262
|
+
*/
|
|
263
|
+
function createTSlatePlugin(config = {}) {
|
|
264
|
+
return createSlatePlugin(config);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/lib/plugin/getEditorPlugin.ts
|
|
269
|
+
function getEditorPlugin(editor, p) {
|
|
270
|
+
const plugin = editor.getPlugin(p);
|
|
271
|
+
return {
|
|
272
|
+
api: editor.api,
|
|
273
|
+
editor,
|
|
274
|
+
plugin,
|
|
275
|
+
setOption: ((keyOrOptions, value) => editor.setOption(plugin, keyOrOptions, value)),
|
|
276
|
+
setOptions: ((options) => editor.setOptions(plugin, options)),
|
|
277
|
+
tf: editor.transforms,
|
|
278
|
+
type: plugin.node.type,
|
|
279
|
+
getOption: (key, ...args) => editor.getOption(plugin, key, ...args),
|
|
280
|
+
getOptions: () => editor.getOptions(plugin)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/internal/plugin/resolvePlugin.ts
|
|
286
|
+
/**
|
|
287
|
+
* Resolves and finalizes a plugin configuration for use in a Plate editor.
|
|
288
|
+
*
|
|
289
|
+
* This function processes a given plugin configuration, applying any extensions
|
|
290
|
+
* and resolving nested plugins. It prepares the plugin for integration into the
|
|
291
|
+
* Plate editor system by:
|
|
292
|
+
*
|
|
293
|
+
* 1. Cloning the plugin to avoid mutating the original
|
|
294
|
+
* 2. Applying all stored extensions to the plugin
|
|
295
|
+
* 3. Clearing the extensions array after application
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* const plugin = createSlatePlugin({ key: 'myPlugin', ...otherOptions }).extend(...);
|
|
299
|
+
* const resolvedPlugin = resolvePlugin(editor, plugin);
|
|
300
|
+
*/
|
|
301
|
+
const resolvePlugin = (editor, _plugin) => {
|
|
302
|
+
let plugin = mergePlugins({}, _plugin);
|
|
303
|
+
plugin.__resolved = true;
|
|
304
|
+
if (plugin.__configuration) {
|
|
305
|
+
const configResult = plugin.__configuration(getEditorPlugin(editor, plugin));
|
|
306
|
+
plugin = mergePlugins(plugin, configResult);
|
|
307
|
+
plugin.__configuration = void 0;
|
|
308
|
+
}
|
|
309
|
+
if (plugin.__extensions && plugin.__extensions.length > 0) {
|
|
310
|
+
for (const extension of plugin.__extensions) plugin = mergePlugins(plugin, extension(getEditorPlugin(editor, plugin)));
|
|
311
|
+
plugin.__extensions = [];
|
|
312
|
+
}
|
|
313
|
+
const targetPluginToInject = plugin.inject?.targetPluginToInject;
|
|
314
|
+
const targetPlugins = plugin.inject?.targetPlugins;
|
|
315
|
+
if (targetPluginToInject && targetPlugins && targetPlugins.length > 0) {
|
|
316
|
+
plugin.inject = plugin.inject || {};
|
|
317
|
+
plugin.inject.plugins = merge({}, plugin.inject.plugins, Object.fromEntries(targetPlugins.map((targetPlugin) => {
|
|
318
|
+
return [targetPlugin, targetPluginToInject({
|
|
319
|
+
...getEditorPlugin(editor, plugin),
|
|
320
|
+
targetPlugin
|
|
321
|
+
})];
|
|
322
|
+
})));
|
|
323
|
+
}
|
|
324
|
+
if (plugin.node?.component) plugin.render.node = plugin.node.component;
|
|
325
|
+
if (plugin.render?.node) plugin.node.component = plugin.render.node;
|
|
326
|
+
validatePlugin(editor, plugin);
|
|
327
|
+
return plugin;
|
|
328
|
+
};
|
|
329
|
+
const validatePlugin = (editor, plugin) => {
|
|
330
|
+
if (!plugin.__extensions) editor.api.debug.error(`Invalid plugin '${plugin.key}', you should use createSlatePlugin.`, "USE_CREATE_PLUGIN");
|
|
331
|
+
if (plugin.node.isElement && plugin.node.isLeaf) editor.api.debug.error(`Plugin ${plugin.key} cannot be both an element and a leaf.`, "PLUGIN_NODE_TYPE");
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/lib/plugin/getSlatePlugin.ts
|
|
336
|
+
/** Get editor plugin by key or plugin object. */
|
|
337
|
+
function getSlatePlugin(editor, p) {
|
|
338
|
+
let plugin = p;
|
|
339
|
+
const editorPlugin = editor.plugins[p.key];
|
|
340
|
+
if (!editorPlugin) {
|
|
341
|
+
if (!plugin.node) plugin = createSlatePlugin(plugin);
|
|
342
|
+
return plugin.__resolved ? plugin : resolvePlugin(editor, plugin);
|
|
343
|
+
}
|
|
344
|
+
return editorPlugin;
|
|
345
|
+
}
|
|
346
|
+
/** Get editor plugin type by key or plugin object. */
|
|
347
|
+
function getPluginType(editor, key) {
|
|
348
|
+
const p = editor.getPlugin({ key });
|
|
349
|
+
return p.node.type ?? p.key ?? "";
|
|
350
|
+
}
|
|
351
|
+
/** Get editor plugin types by key. */
|
|
352
|
+
const getPluginTypes = (editor, keys) => keys.map((key) => editor.getType(key));
|
|
353
|
+
const getPluginKey = (editor, type) => editor.meta.pluginCache.node.types[type];
|
|
354
|
+
const getPluginKeys = (editor, types) => types.map((type) => {
|
|
355
|
+
return getPluginKey(editor, type) ?? type;
|
|
356
|
+
}).filter(Boolean);
|
|
357
|
+
const getPluginByType = (editor, type) => {
|
|
358
|
+
const key = getPluginKey(editor, type);
|
|
359
|
+
if (!key) return null;
|
|
360
|
+
return editor.getPlugin({ key });
|
|
361
|
+
};
|
|
362
|
+
const getContainerTypes = (editor) => getPluginTypes(editor, editor.meta.pluginCache.node.isContainer);
|
|
363
|
+
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/internal/plugin/resolvePlugins.ts
|
|
366
|
+
const resolvePlugins = (editor, plugins = [], createStore = createVanillaStore) => {
|
|
367
|
+
editor.plugins = {};
|
|
368
|
+
editor.meta.pluginList = [];
|
|
369
|
+
editor.meta.shortcuts = {};
|
|
370
|
+
editor.meta.components = {};
|
|
371
|
+
editor.meta.pluginCache = {
|
|
372
|
+
decorate: [],
|
|
373
|
+
handlers: {
|
|
374
|
+
onChange: [],
|
|
375
|
+
onNodeChange: [],
|
|
376
|
+
onTextChange: []
|
|
377
|
+
},
|
|
378
|
+
inject: { nodeProps: [] },
|
|
379
|
+
node: {
|
|
380
|
+
isContainer: [],
|
|
381
|
+
isLeaf: [],
|
|
382
|
+
isText: [],
|
|
383
|
+
leafProps: [],
|
|
384
|
+
textProps: [],
|
|
385
|
+
types: {}
|
|
386
|
+
},
|
|
387
|
+
normalizeInitialValue: [],
|
|
388
|
+
render: {
|
|
389
|
+
aboveEditable: [],
|
|
390
|
+
aboveNodes: [],
|
|
391
|
+
aboveSlate: [],
|
|
392
|
+
afterContainer: [],
|
|
393
|
+
afterEditable: [],
|
|
394
|
+
beforeContainer: [],
|
|
395
|
+
beforeEditable: [],
|
|
396
|
+
belowNodes: [],
|
|
397
|
+
belowRootNodes: []
|
|
398
|
+
},
|
|
399
|
+
rules: { match: [] },
|
|
400
|
+
useHooks: []
|
|
401
|
+
};
|
|
402
|
+
const resolvedPlugins = resolveAndSortPlugins(editor, plugins);
|
|
403
|
+
applyPluginsToEditor(editor, resolvedPlugins);
|
|
404
|
+
resolvePluginOverrides(editor);
|
|
405
|
+
resolvePluginStores(editor, createStore);
|
|
406
|
+
editor.meta.pluginList.forEach((plugin) => {
|
|
407
|
+
if (plugin.extendEditor) {
|
|
408
|
+
editor = plugin.extendEditor(getEditorPlugin(editor, plugin));
|
|
409
|
+
syncLegacyMethods(editor);
|
|
410
|
+
}
|
|
411
|
+
resolvePluginMethods(editor, plugin);
|
|
412
|
+
if (plugin.node?.isContainer) editor.meta.pluginCache.node.isContainer.push(plugin.key);
|
|
413
|
+
editor.meta.pluginCache.node.types[plugin.node.type] = plugin.key;
|
|
414
|
+
if (plugin.inject?.nodeProps) editor.meta.pluginCache.inject.nodeProps.push(plugin.key);
|
|
415
|
+
if (plugin.render?.node) editor.meta.components[plugin.key] = plugin.render.node;
|
|
416
|
+
if (plugin.node?.isLeaf && (plugin.node?.isDecoration === true || plugin.render.leaf)) editor.meta.pluginCache.node.isLeaf.push(plugin.key);
|
|
417
|
+
if (plugin.node.isLeaf && plugin.node.isDecoration === false) editor.meta.pluginCache.node.isText.push(plugin.key);
|
|
418
|
+
if (plugin.node?.leafProps) editor.meta.pluginCache.node.leafProps.push(plugin.key);
|
|
419
|
+
if (plugin.node.textProps) editor.meta.pluginCache.node.textProps.push(plugin.key);
|
|
420
|
+
if (plugin.render.aboveEditable) editor.meta.pluginCache.render.aboveEditable.push(plugin.key);
|
|
421
|
+
if (plugin.render.aboveSlate) editor.meta.pluginCache.render.aboveSlate.push(plugin.key);
|
|
422
|
+
if (plugin.render.afterEditable) editor.meta.pluginCache.render.afterEditable.push(plugin.key);
|
|
423
|
+
if (plugin.render.beforeEditable) editor.meta.pluginCache.render.beforeEditable.push(plugin.key);
|
|
424
|
+
if (plugin.rules?.match) editor.meta.pluginCache.rules.match.push(plugin.key);
|
|
425
|
+
if (plugin.render.afterContainer) editor.meta.pluginCache.render.afterContainer.push(plugin.key);
|
|
426
|
+
if (plugin.render.beforeContainer) editor.meta.pluginCache.render.beforeContainer.push(plugin.key);
|
|
427
|
+
if (plugin.render.belowRootNodes) editor.meta.pluginCache.render.belowRootNodes.push(plugin.key);
|
|
428
|
+
if (plugin.normalizeInitialValue) editor.meta.pluginCache.normalizeInitialValue.push(plugin.key);
|
|
429
|
+
if (plugin.decorate) editor.meta.pluginCache.decorate.push(plugin.key);
|
|
430
|
+
if (plugin.render.aboveNodes) editor.meta.pluginCache.render.aboveNodes.push(plugin.key);
|
|
431
|
+
if (plugin.render.belowNodes) editor.meta.pluginCache.render.belowNodes.push(plugin.key);
|
|
432
|
+
if (plugin.useHooks) editor.meta.pluginCache.useHooks.push(plugin.key);
|
|
433
|
+
if (plugin.handlers?.onChange) editor.meta.pluginCache.handlers.onChange.push(plugin.key);
|
|
434
|
+
if (plugin.handlers?.onNodeChange) editor.meta.pluginCache.handlers.onNodeChange.push(plugin.key);
|
|
435
|
+
if (plugin.handlers?.onTextChange) editor.meta.pluginCache.handlers.onTextChange.push(plugin.key);
|
|
436
|
+
});
|
|
437
|
+
resolvePluginShortcuts(editor);
|
|
438
|
+
return editor;
|
|
439
|
+
};
|
|
440
|
+
const resolvePluginStores = (editor, createStore) => {
|
|
441
|
+
editor.meta.pluginList.forEach((plugin) => {
|
|
442
|
+
let store = createStore(plugin.options, {
|
|
443
|
+
mutative: true,
|
|
444
|
+
name: plugin.key
|
|
445
|
+
});
|
|
446
|
+
if (plugin.__selectorExtensions && plugin.__selectorExtensions.length > 0) plugin.__selectorExtensions.forEach((extension) => {
|
|
447
|
+
const extendedOptions = extension(getEditorPlugin(editor, plugin));
|
|
448
|
+
store = store.extendSelectors(() => extendedOptions);
|
|
449
|
+
});
|
|
450
|
+
plugin.optionsStore = store;
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const resolvePluginMethods = (editor, plugin) => {
|
|
454
|
+
Object.entries(plugin.api).forEach(([apiKey, apiFunction]) => {
|
|
455
|
+
editor.api[apiKey] = apiFunction;
|
|
456
|
+
});
|
|
457
|
+
if (plugin.__apiExtensions && plugin.__apiExtensions.length > 0) {
|
|
458
|
+
plugin.__apiExtensions.forEach(({ extension, isOverride, isPluginSpecific, isTransform }) => {
|
|
459
|
+
const newExtensions = extension(getEditorPlugin(editor, plugin));
|
|
460
|
+
if (isOverride) {
|
|
461
|
+
if (newExtensions.api) {
|
|
462
|
+
merge(editor.api, newExtensions.api);
|
|
463
|
+
merge(plugin.api, newExtensions.api);
|
|
464
|
+
assignLegacyApi(editor, editor.api);
|
|
465
|
+
}
|
|
466
|
+
if (newExtensions.transforms) {
|
|
467
|
+
merge(editor.transforms, newExtensions.transforms);
|
|
468
|
+
merge(plugin.transforms, newExtensions.transforms);
|
|
469
|
+
assignLegacyTransforms(editor, newExtensions.transforms);
|
|
470
|
+
}
|
|
471
|
+
} else if (isTransform) if (isPluginSpecific) {
|
|
472
|
+
if (!editor.transforms[plugin.key]) editor.transforms[plugin.key] = {};
|
|
473
|
+
if (!plugin.transforms[plugin.key]) plugin.transforms[plugin.key] = {};
|
|
474
|
+
merge(editor.transforms[plugin.key], newExtensions);
|
|
475
|
+
merge(plugin.transforms[plugin.key], newExtensions);
|
|
476
|
+
} else {
|
|
477
|
+
merge(editor.transforms, newExtensions);
|
|
478
|
+
merge(plugin.transforms, newExtensions);
|
|
479
|
+
assignLegacyTransforms(editor, newExtensions);
|
|
480
|
+
}
|
|
481
|
+
else if (isPluginSpecific) {
|
|
482
|
+
if (!editor.api[plugin.key]) editor.api[plugin.key] = {};
|
|
483
|
+
if (!plugin.api[plugin.key]) plugin.api[plugin.key] = {};
|
|
484
|
+
merge(editor.api[plugin.key], newExtensions);
|
|
485
|
+
merge(plugin.api[plugin.key], newExtensions);
|
|
486
|
+
} else {
|
|
487
|
+
merge(editor.api, newExtensions);
|
|
488
|
+
merge(plugin.api, newExtensions);
|
|
489
|
+
assignLegacyApi(editor, editor.api);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
plugin.__apiExtensions = void 0;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const resolvePluginShortcuts = (editor) => {
|
|
496
|
+
editor.meta.shortcuts = {};
|
|
497
|
+
editor.meta.pluginList.forEach((plugin) => {
|
|
498
|
+
Object.entries(plugin.shortcuts).forEach(([originalKey, hotkey]) => {
|
|
499
|
+
const namespacedKey = `${plugin.key}.${originalKey}`;
|
|
500
|
+
if (hotkey === null) delete editor.meta.shortcuts[namespacedKey];
|
|
501
|
+
else if (hotkey && typeof hotkey === "object") {
|
|
502
|
+
const resolvedHotkey = { ...hotkey };
|
|
503
|
+
if (!resolvedHotkey.handler) {
|
|
504
|
+
const pluginSpecificTransforms = plugin.transforms?.[plugin.key];
|
|
505
|
+
const pluginSpecificApi = plugin.api?.[plugin.key];
|
|
506
|
+
if (pluginSpecificTransforms?.[originalKey]) resolvedHotkey.handler = () => pluginSpecificTransforms[originalKey]();
|
|
507
|
+
else if (pluginSpecificApi?.[originalKey]) resolvedHotkey.handler = () => pluginSpecificApi[originalKey]();
|
|
508
|
+
}
|
|
509
|
+
resolvedHotkey.priority = resolvedHotkey.priority ?? plugin.priority;
|
|
510
|
+
editor.meta.shortcuts[namespacedKey] = resolvedHotkey;
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
};
|
|
515
|
+
const flattenAndResolvePlugins = (editor, plugins) => {
|
|
516
|
+
const pluginMap = /* @__PURE__ */ new Map();
|
|
517
|
+
const processPlugin = (plugin) => {
|
|
518
|
+
const resolvedPlugin = resolvePlugin(editor, plugin);
|
|
519
|
+
if (resolvedPlugin.key) {
|
|
520
|
+
const existingPlugin = pluginMap.get(resolvedPlugin.key);
|
|
521
|
+
if (existingPlugin) pluginMap.set(resolvedPlugin.key, mergePlugins(existingPlugin, resolvedPlugin));
|
|
522
|
+
else pluginMap.set(resolvedPlugin.key, resolvedPlugin);
|
|
523
|
+
}
|
|
524
|
+
if (resolvedPlugin.plugins && resolvedPlugin.plugins.length > 0) resolvedPlugin.plugins.forEach(processPlugin);
|
|
525
|
+
};
|
|
526
|
+
plugins.forEach(processPlugin);
|
|
527
|
+
return pluginMap;
|
|
528
|
+
};
|
|
529
|
+
const resolveAndSortPlugins = (editor, plugins) => {
|
|
530
|
+
const pluginMap = flattenAndResolvePlugins(editor, plugins);
|
|
531
|
+
const enabledPlugins = Array.from(pluginMap.values()).filter((plugin) => plugin.enabled !== false);
|
|
532
|
+
enabledPlugins.sort((a, b) => b.priority - a.priority);
|
|
533
|
+
const orderedPlugins = [];
|
|
534
|
+
const visited = /* @__PURE__ */ new Set();
|
|
535
|
+
const visit = (plugin) => {
|
|
536
|
+
if (visited.has(plugin.key)) return;
|
|
537
|
+
visited.add(plugin.key);
|
|
538
|
+
plugin.dependencies?.forEach((depKey) => {
|
|
539
|
+
const depPlugin = pluginMap.get(depKey);
|
|
540
|
+
if (depPlugin) visit(depPlugin);
|
|
541
|
+
else editor.api.debug.warn(`Plugin "${plugin.key}" depends on missing plugin "${depKey}"`, "PLUGIN_DEPENDENCY_MISSING");
|
|
542
|
+
});
|
|
543
|
+
orderedPlugins.push(plugin);
|
|
544
|
+
};
|
|
545
|
+
enabledPlugins.forEach(visit);
|
|
546
|
+
return orderedPlugins;
|
|
547
|
+
};
|
|
548
|
+
const applyPluginsToEditor = (editor, plugins) => {
|
|
549
|
+
editor.meta.pluginList = plugins;
|
|
550
|
+
editor.plugins = Object.fromEntries(plugins.map((plugin) => [plugin.key, plugin]));
|
|
551
|
+
};
|
|
552
|
+
const resolvePluginOverrides = (editor) => {
|
|
553
|
+
const applyOverrides = (plugins) => {
|
|
554
|
+
let overriddenPlugins = [...plugins];
|
|
555
|
+
const enabledOverrides = {};
|
|
556
|
+
const componentOverrides = {};
|
|
557
|
+
const pluginOverrides = {};
|
|
558
|
+
for (const plugin of plugins) {
|
|
559
|
+
if (plugin.override.enabled) Object.assign(enabledOverrides, plugin.override.enabled);
|
|
560
|
+
if (plugin.override.components) Object.entries(plugin.override.components).forEach(([key, component]) => {
|
|
561
|
+
if (!componentOverrides[key] || plugin.priority > componentOverrides[key].priority) componentOverrides[key] = {
|
|
562
|
+
component,
|
|
563
|
+
priority: plugin.priority
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
if (plugin.override.plugins) Object.entries(plugin.override.plugins).forEach(([key, value]) => {
|
|
567
|
+
pluginOverrides[key] = mergePlugins(pluginOverrides[key], value);
|
|
568
|
+
if (value.enabled !== void 0) enabledOverrides[key] = value.enabled;
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
overriddenPlugins = overriddenPlugins.map((p) => {
|
|
572
|
+
let updatedPlugin = { ...p };
|
|
573
|
+
if (pluginOverrides[p.key]) updatedPlugin = mergePlugins(updatedPlugin, pluginOverrides[p.key]);
|
|
574
|
+
if (componentOverrides[p.key] && (!p.render.node && !p.node.component || componentOverrides[p.key].priority > p.priority)) {
|
|
575
|
+
updatedPlugin.render.node = componentOverrides[p.key].component;
|
|
576
|
+
updatedPlugin.node.component = componentOverrides[p.key].component;
|
|
577
|
+
}
|
|
578
|
+
const enabled = enabledOverrides[p.key] ?? updatedPlugin.enabled;
|
|
579
|
+
if (isDefined(enabled)) updatedPlugin.enabled = enabled;
|
|
580
|
+
return updatedPlugin;
|
|
581
|
+
});
|
|
582
|
+
return overriddenPlugins.filter((p) => p.enabled !== false).map((plugin) => ({
|
|
583
|
+
...plugin,
|
|
584
|
+
plugins: applyOverrides(plugin.plugins || [])
|
|
585
|
+
}));
|
|
586
|
+
};
|
|
587
|
+
editor.meta.pluginList = applyOverrides(editor.meta.pluginList);
|
|
588
|
+
editor.plugins = Object.fromEntries(editor.meta.pluginList.map((plugin) => [plugin.key, plugin]));
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/lib/plugins/AstPlugin.ts
|
|
593
|
+
/**
|
|
594
|
+
* Enables support for deserializing inserted content from Slate Ast format to
|
|
595
|
+
* Slate format while apply a small bug fix.
|
|
596
|
+
*/
|
|
597
|
+
const AstPlugin = createSlatePlugin({
|
|
598
|
+
key: "ast",
|
|
599
|
+
parser: {
|
|
600
|
+
format: "application/x-slate-fragment",
|
|
601
|
+
deserialize: ({ data }) => {
|
|
602
|
+
const decoded = decodeURIComponent(window.atob(data));
|
|
603
|
+
let parsed;
|
|
604
|
+
try {
|
|
605
|
+
parsed = JSON.parse(decoded);
|
|
606
|
+
} catch {}
|
|
607
|
+
return parsed;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/lib/plugins/HistoryPlugin.ts
|
|
614
|
+
const withPlateHistory = ({ editor }) => withHistory(editor);
|
|
615
|
+
/** @see {@link withHistory} */
|
|
616
|
+
const HistoryPlugin = createSlatePlugin({
|
|
617
|
+
key: "history",
|
|
618
|
+
extendEditor: withPlateHistory
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/lib/plugins/paragraph/BaseParagraphPlugin.ts
|
|
623
|
+
const BaseParagraphPlugin = createSlatePlugin({
|
|
624
|
+
key: "p",
|
|
625
|
+
node: { isElement: true },
|
|
626
|
+
parsers: { html: { deserializer: {
|
|
627
|
+
rules: [{ validNodeName: "P" }],
|
|
628
|
+
query: ({ element }) => element.style.fontFamily !== "Consolas"
|
|
629
|
+
} } },
|
|
630
|
+
rules: { merge: { removeEmpty: true } }
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/lib/plugins/override/withBreakRules.ts
|
|
635
|
+
const withBreakRules = (ctx) => {
|
|
636
|
+
const { editor, tf: { insertBreak } } = ctx;
|
|
637
|
+
const checkMatchRulesOverride = (rule, blockNode, blockPath) => {
|
|
638
|
+
const matchRulesKeys = editor.meta.pluginCache.rules.match;
|
|
639
|
+
for (const key of matchRulesKeys) {
|
|
640
|
+
const overridePlugin = editor.getPlugin({ key });
|
|
641
|
+
if (overridePlugin.rules?.break && overridePlugin.rules?.match?.({
|
|
642
|
+
...ctx,
|
|
643
|
+
node: blockNode,
|
|
644
|
+
path: blockPath,
|
|
645
|
+
rule
|
|
646
|
+
})) return overridePlugin.rules.break;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
};
|
|
650
|
+
const executeBreakAction = (action, blockPath) => {
|
|
651
|
+
if (action === "reset") {
|
|
652
|
+
editor.tf.resetBlock({ at: blockPath });
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
if (action === "exit") {
|
|
656
|
+
editor.tf.insertExitBreak();
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
if (action === "deleteExit") {
|
|
660
|
+
editor.tf.deleteBackward("character");
|
|
661
|
+
editor.tf.insertExitBreak();
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
if (action === "lineBreak") {
|
|
665
|
+
editor.tf.insertSoftBreak();
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
};
|
|
670
|
+
return { transforms: { insertBreak() {
|
|
671
|
+
if (editor.selection && editor.api.isCollapsed()) {
|
|
672
|
+
const block = editor.api.block();
|
|
673
|
+
if (block) {
|
|
674
|
+
const [blockNode, blockPath] = block;
|
|
675
|
+
const breakRules = getPluginByType(editor, blockNode.type)?.rules.break;
|
|
676
|
+
if (editor.api.isEmpty(editor.selection, { block: true })) {
|
|
677
|
+
const emptyAction = (checkMatchRulesOverride("break.empty", blockNode, blockPath) || breakRules)?.empty;
|
|
678
|
+
if (executeBreakAction(emptyAction, blockPath)) return;
|
|
679
|
+
}
|
|
680
|
+
if (!editor.api.isEmpty(editor.selection, { block: true }) && editor.api.isAt({ end: true })) {
|
|
681
|
+
const range = editor.api.range("before", editor.selection);
|
|
682
|
+
if (range) {
|
|
683
|
+
if (editor.api.string(range) === "\n") {
|
|
684
|
+
const emptyLineEndAction = (checkMatchRulesOverride("break.emptyLineEnd", blockNode, blockPath) || breakRules)?.emptyLineEnd;
|
|
685
|
+
if (executeBreakAction(emptyLineEndAction, blockPath)) return;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const defaultAction = (checkMatchRulesOverride("break.default", blockNode, blockPath) || breakRules)?.default;
|
|
690
|
+
if (executeBreakAction(defaultAction, blockPath)) return;
|
|
691
|
+
if (checkMatchRulesOverride("break.splitReset", blockNode, blockPath)?.splitReset ?? breakRules?.splitReset) {
|
|
692
|
+
const isAtStart = editor.api.isAt({ start: true });
|
|
693
|
+
insertBreak();
|
|
694
|
+
editor.tf.resetBlock({ at: isAtStart ? blockPath : PathApi.next(blockPath) });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
insertBreak();
|
|
700
|
+
} } };
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/lib/plugins/override/withDeleteRules.ts
|
|
705
|
+
const withDeleteRules = (ctx) => {
|
|
706
|
+
const { editor, tf: { deleteBackward, deleteForward, deleteFragment } } = ctx;
|
|
707
|
+
const resetMarks = () => {
|
|
708
|
+
if (editor.api.isAt({ start: true })) editor.tf.removeMarks();
|
|
709
|
+
};
|
|
710
|
+
const checkMatchRulesOverride = (rule, blockNode, blockPath) => {
|
|
711
|
+
const matchRulesKeys = editor.meta.pluginCache.rules.match;
|
|
712
|
+
for (const key of matchRulesKeys) {
|
|
713
|
+
const overridePlugin = editor.getPlugin({ key });
|
|
714
|
+
if (overridePlugin.rules?.delete && overridePlugin.rules?.match?.({
|
|
715
|
+
...ctx,
|
|
716
|
+
node: blockNode,
|
|
717
|
+
path: blockPath,
|
|
718
|
+
rule
|
|
719
|
+
})) return overridePlugin.rules.delete;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
};
|
|
723
|
+
const executeDeleteAction = (action, blockPath) => {
|
|
724
|
+
if (action === "reset") {
|
|
725
|
+
editor.tf.resetBlock({ at: blockPath });
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
};
|
|
730
|
+
return { transforms: {
|
|
731
|
+
deleteBackward(unit) {
|
|
732
|
+
if (editor.selection && editor.api.isCollapsed()) {
|
|
733
|
+
const block = editor.api.block();
|
|
734
|
+
if (block) {
|
|
735
|
+
const [blockNode, blockPath] = block;
|
|
736
|
+
const deleteRules = getPluginByType(editor, blockNode.type)?.rules.delete;
|
|
737
|
+
if (editor.api.isAt({ start: true })) {
|
|
738
|
+
const startAction = (checkMatchRulesOverride("delete.start", blockNode, blockPath) || deleteRules)?.start;
|
|
739
|
+
if (executeDeleteAction(startAction, blockPath)) return;
|
|
740
|
+
}
|
|
741
|
+
if (editor.api.isEmpty(editor.selection, { block: true })) {
|
|
742
|
+
const emptyAction = (checkMatchRulesOverride("delete.empty", blockNode, blockPath) || deleteRules)?.empty;
|
|
743
|
+
if (executeDeleteAction(emptyAction, blockPath)) return;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (PointApi.equals(editor.selection.anchor, editor.api.start([]))) {
|
|
747
|
+
editor.tf.resetBlock({ at: [0] });
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
deleteBackward(unit);
|
|
752
|
+
resetMarks();
|
|
753
|
+
},
|
|
754
|
+
deleteForward(unit) {
|
|
755
|
+
deleteForward(unit);
|
|
756
|
+
resetMarks();
|
|
757
|
+
},
|
|
758
|
+
deleteFragment(options) {
|
|
759
|
+
if (editor.selection && RangeApi.equals(editor.selection, editor.api.range([]))) {
|
|
760
|
+
editor.tf.reset({
|
|
761
|
+
children: true,
|
|
762
|
+
select: true
|
|
763
|
+
});
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
deleteFragment(options);
|
|
767
|
+
resetMarks();
|
|
768
|
+
}
|
|
769
|
+
} };
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/lib/plugins/override/withMergeRules.ts
|
|
774
|
+
const withMergeRules = (ctx) => {
|
|
775
|
+
const { editor, tf: { removeNodes } } = ctx;
|
|
776
|
+
const checkMatchRulesOverride = (rule, blockNode, blockPath) => {
|
|
777
|
+
const matchRulesKeys = editor.meta.pluginCache.rules.match;
|
|
778
|
+
for (const key of matchRulesKeys) {
|
|
779
|
+
const overridePlugin = editor.getPlugin({ key });
|
|
780
|
+
if (overridePlugin.rules.merge && overridePlugin.rules?.match?.({
|
|
781
|
+
...ctx,
|
|
782
|
+
node: blockNode,
|
|
783
|
+
path: blockPath,
|
|
784
|
+
rule
|
|
785
|
+
})) return overridePlugin.rules.merge;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
};
|
|
789
|
+
return {
|
|
790
|
+
api: { shouldMergeNodes(prevNodeEntry, nextNodeEntry, { reverse } = {}) {
|
|
791
|
+
const [prevNode, prevPath] = prevNodeEntry;
|
|
792
|
+
const [, nextPath] = nextNodeEntry;
|
|
793
|
+
const [curNode, curPath] = reverse ? prevNodeEntry : nextNodeEntry;
|
|
794
|
+
const [targetNode, targetPath] = reverse ? nextNodeEntry : prevNodeEntry;
|
|
795
|
+
if (TextApi.isText(prevNode) && prevNode.text === "" && prevPath.at(-1) !== 0) {
|
|
796
|
+
editor.tf.removeNodes({ at: prevPath });
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
const shouldRemove = (node, path) => {
|
|
800
|
+
const plugin = getPluginByType(editor, node.type);
|
|
801
|
+
if (!plugin) return true;
|
|
802
|
+
if (!plugin.rules.merge?.removeEmpty) return false;
|
|
803
|
+
if (checkMatchRulesOverride("merge.removeEmpty", node, path)?.removeEmpty === false) return false;
|
|
804
|
+
return true;
|
|
805
|
+
};
|
|
806
|
+
if (ElementApi.isElement(targetNode) && editor.api.isVoid(targetNode)) {
|
|
807
|
+
if (shouldRemove(targetNode, targetPath)) editor.tf.removeNodes({ at: prevPath });
|
|
808
|
+
else if (ElementApi.isElement(curNode) && editor.api.isEmpty(curNode)) editor.tf.removeNodes({ at: curPath });
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
if (ElementApi.isElement(prevNode) && editor.api.isEmpty(prevNode) && PathApi.isSibling(prevPath, nextPath) && shouldRemove(prevNode, prevPath)) {
|
|
812
|
+
editor.tf.removeNodes({ at: prevPath });
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return true;
|
|
816
|
+
} },
|
|
817
|
+
transforms: { removeNodes(options = {}) {
|
|
818
|
+
if (options.event?.type === "mergeNodes" && options.at) {
|
|
819
|
+
const nodeEntry = editor.api.node(options.at);
|
|
820
|
+
if (nodeEntry) {
|
|
821
|
+
const [node, path] = nodeEntry;
|
|
822
|
+
if (ElementApi.isElement(node)) {
|
|
823
|
+
const plugin = getPluginByType(editor, node.type);
|
|
824
|
+
if (plugin) {
|
|
825
|
+
const mergeRules = plugin.rules.merge;
|
|
826
|
+
if (checkMatchRulesOverride("merge.removeEmpty", node, path)?.removeEmpty === false || mergeRules?.removeEmpty === false) return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
removeNodes(options);
|
|
832
|
+
} }
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
//#endregion
|
|
837
|
+
//#region src/lib/plugins/override/withNormalizeRules.ts
|
|
838
|
+
const withNormalizeRules = (ctx) => {
|
|
839
|
+
const { editor, tf: { normalizeNode } } = ctx;
|
|
840
|
+
const checkMatchRulesOverride = (rule, node, path) => {
|
|
841
|
+
const matchRulesKeys = editor.meta.pluginCache.rules.match;
|
|
842
|
+
for (const key of matchRulesKeys) {
|
|
843
|
+
const overridePlugin = editor.getPlugin({ key });
|
|
844
|
+
if (overridePlugin.rules?.normalize && overridePlugin.rules?.match?.({
|
|
845
|
+
...ctx,
|
|
846
|
+
node,
|
|
847
|
+
path,
|
|
848
|
+
rule
|
|
849
|
+
})) return overridePlugin.rules.normalize;
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
};
|
|
853
|
+
return { transforms: { normalizeNode([node, path]) {
|
|
854
|
+
if (ElementApi.isElement(node) && node.type) {
|
|
855
|
+
const normalizeRules = getPluginByType(editor, node.type)?.rules.normalize;
|
|
856
|
+
if ((checkMatchRulesOverride("normalize.removeEmpty", node, path) || normalizeRules)?.removeEmpty && editor.api.isEmpty(node)) {
|
|
857
|
+
editor.tf.removeNodes({ at: path });
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
normalizeNode([node, path]);
|
|
862
|
+
} } };
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/lib/plugins/override/OverridePlugin.ts
|
|
867
|
+
/**
|
|
868
|
+
* Merge and register all the inline types and void types from the plugins and
|
|
869
|
+
* options, using `editor.api.isInline`, `editor.api.markableVoid` and
|
|
870
|
+
* `editor.api.isVoid`
|
|
871
|
+
*/
|
|
872
|
+
const withOverrides = ({ api: { isInline, isSelectable, isVoid, markableVoid }, editor }) => {
|
|
873
|
+
return { api: {
|
|
874
|
+
create: { block: (node) => ({
|
|
875
|
+
children: [{ text: "" }],
|
|
876
|
+
type: editor.getType(BaseParagraphPlugin.key),
|
|
877
|
+
...node
|
|
878
|
+
}) },
|
|
879
|
+
isInline(element) {
|
|
880
|
+
return getPluginByType(editor, element.type)?.node.isInline ? true : isInline(element);
|
|
881
|
+
},
|
|
882
|
+
isSelectable(element) {
|
|
883
|
+
return getPluginByType(editor, element.type)?.node.isSelectable === false ? false : isSelectable(element);
|
|
884
|
+
},
|
|
885
|
+
isVoid(element) {
|
|
886
|
+
return getPluginByType(editor, element.type)?.node.isVoid ? true : isVoid(element);
|
|
887
|
+
},
|
|
888
|
+
markableVoid(element) {
|
|
889
|
+
return getPluginByType(editor, element.type)?.node.isMarkableVoid ? true : markableVoid(element);
|
|
890
|
+
}
|
|
891
|
+
} };
|
|
892
|
+
};
|
|
893
|
+
/** Override the editor api and transforms based on the plugins. */
|
|
894
|
+
const OverridePlugin = createSlatePlugin({ key: "override" }).overrideEditor(withOverrides).overrideEditor(withBreakRules).overrideEditor(withDeleteRules).overrideEditor(withMergeRules).overrideEditor(withNormalizeRules);
|
|
895
|
+
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/internal/plugin/pipeInsertFragment.ts
|
|
898
|
+
/** Pipe preInsert then insertFragment. */
|
|
899
|
+
const pipeInsertFragment = (editor, injectedPlugins, { fragment, ...options }) => {
|
|
900
|
+
editor.tf.withoutNormalizing(() => {
|
|
901
|
+
injectedPlugins.some((p) => p.parser?.preInsert?.({
|
|
902
|
+
...getEditorPlugin(editor, p),
|
|
903
|
+
fragment,
|
|
904
|
+
...options
|
|
905
|
+
}) === true);
|
|
906
|
+
editor.tf.insertFragment(fragment);
|
|
907
|
+
});
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
//#endregion
|
|
911
|
+
//#region src/internal/plugin/pipeTransformData.ts
|
|
912
|
+
/** Pipe editor.tf.insertData.transformData */
|
|
913
|
+
const pipeTransformData = (editor, plugins, { data, ...options }) => {
|
|
914
|
+
plugins.forEach((p) => {
|
|
915
|
+
const transformData = p.parser?.transformData;
|
|
916
|
+
if (!transformData) return;
|
|
917
|
+
data = transformData({
|
|
918
|
+
...getEditorPlugin(editor, p),
|
|
919
|
+
data,
|
|
920
|
+
...options
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
return data;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region src/internal/plugin/pipeTransformFragment.ts
|
|
928
|
+
/** Pipe editor.tf.insertData.transformFragment */
|
|
929
|
+
const pipeTransformFragment = (editor, plugins, { fragment, ...options }) => {
|
|
930
|
+
plugins.forEach((p) => {
|
|
931
|
+
const transformFragment = p.parser?.transformFragment;
|
|
932
|
+
if (!transformFragment) return;
|
|
933
|
+
fragment = transformFragment({
|
|
934
|
+
fragment,
|
|
935
|
+
...options,
|
|
936
|
+
...getEditorPlugin(editor, p)
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
return fragment;
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
//#endregion
|
|
943
|
+
//#region src/lib/utils/applyDeepToNodes.ts
|
|
944
|
+
/** Recursively apply an operation to children nodes with a query. */
|
|
945
|
+
const applyDeepToNodes = ({ apply, node, path = [], query, source }) => {
|
|
946
|
+
if (queryNode([node, path], query)) if (typeof source === "function") apply(node, source());
|
|
947
|
+
else apply(node, source);
|
|
948
|
+
if (!NodeApi.isAncestor(node)) return;
|
|
949
|
+
node.children.forEach((child, index) => {
|
|
950
|
+
applyDeepToNodes({
|
|
951
|
+
apply,
|
|
952
|
+
node: child,
|
|
953
|
+
path: path.concat([index]),
|
|
954
|
+
query,
|
|
955
|
+
source
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/lib/utils/checkUtils.ts
|
|
962
|
+
const isSlateVoid = (element) => element.dataset.slateVoid === "true";
|
|
963
|
+
const isSlateElement = (element) => element.dataset.slateNode === "element";
|
|
964
|
+
const isSlateText = (element) => element.dataset.slateNode === "text";
|
|
965
|
+
const isSlateString = (element) => element.dataset.slateString === "true";
|
|
966
|
+
const isSlateLeaf = (element) => element.dataset.slateLeaf === "true";
|
|
967
|
+
const isSlateEditor = (element) => element.dataset.slateEditor === "true";
|
|
968
|
+
const isSlateNode = (element) => isSlateLeaf(element) || isSlateElement(element) || isSlateVoid(element) || isSlateString(element) || isSlateText(element);
|
|
969
|
+
const isSlatePluginElement = (element, pluginKey) => element.dataset.slateNode === "element" && element.classList.contains(`slate-${pluginKey}`);
|
|
970
|
+
const isSlatePluginNode = (element, pluginKey) => element.classList.contains(`slate-${pluginKey}`);
|
|
971
|
+
const getSlateElements = (element) => Array.from(element.querySelectorAll("[data-slate-node=\"element\"]"));
|
|
972
|
+
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region src/lib/utils/defaultsDeepToNodes.ts
|
|
975
|
+
/** Recursively merge a source object to children nodes with a query. */
|
|
976
|
+
const defaultsDeepToNodes = (options) => {
|
|
977
|
+
applyDeepToNodes({
|
|
978
|
+
...options,
|
|
979
|
+
apply: defaults
|
|
980
|
+
});
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
//#endregion
|
|
984
|
+
//#region src/lib/utils/getInjectMatch.ts
|
|
985
|
+
const getInjectMatch = (editor, plugin) => {
|
|
986
|
+
return (node, path) => {
|
|
987
|
+
const { inject: { excludeBelowPlugins, excludePlugins, isBlock: _isBlock, isElement: _isElement, isLeaf, maxLevel, targetPlugins } } = plugin;
|
|
988
|
+
const element = ElementApi.isElement(node) ? node : void 0;
|
|
989
|
+
if (_isElement && !element) return false;
|
|
990
|
+
if (_isBlock && (!element || !editor.api.isBlock(element))) return false;
|
|
991
|
+
if (isLeaf && element) return false;
|
|
992
|
+
if (element?.type) {
|
|
993
|
+
if (excludePlugins?.includes(getPluginKey(editor, element.type))) return false;
|
|
994
|
+
if (targetPlugins && !targetPlugins.includes(getPluginKey(editor, element.type))) return false;
|
|
995
|
+
}
|
|
996
|
+
if (excludeBelowPlugins || maxLevel) {
|
|
997
|
+
if (maxLevel && path.length > maxLevel) return false;
|
|
998
|
+
if (excludeBelowPlugins) {
|
|
999
|
+
const excludeTypes = getPluginKeys(editor, excludeBelowPlugins);
|
|
1000
|
+
if (editor.api.above({
|
|
1001
|
+
at: path,
|
|
1002
|
+
match: (n) => ElementApi.isElement(n) && excludeTypes.includes(n.type)
|
|
1003
|
+
})) return false;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return true;
|
|
1007
|
+
};
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/lib/utils/getInjectedPlugins.ts
|
|
1012
|
+
/**
|
|
1013
|
+
* Get all plugins having a defined `inject.plugins[plugin.key]`. It includes
|
|
1014
|
+
* `plugin` itself.
|
|
1015
|
+
*/
|
|
1016
|
+
const getInjectedPlugins = (editor, plugin) => {
|
|
1017
|
+
const injectedPlugins = [];
|
|
1018
|
+
[...editor.meta.pluginList].reverse().forEach((p) => {
|
|
1019
|
+
const injectedPlugin = p.inject.plugins?.[plugin.key];
|
|
1020
|
+
if (injectedPlugin) injectedPlugins.push(injectedPlugin);
|
|
1021
|
+
});
|
|
1022
|
+
return [plugin, ...injectedPlugins];
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/lib/utils/getNodeDataAttributeKeys.ts
|
|
1027
|
+
const getNodeDataAttributeKeys = (node) => Object.keys(node).filter((key) => typeof node[key] !== "object" && (!TextApi.isText(node) || key !== "text")).map((key) => keyToDataAttribute(key));
|
|
1028
|
+
const keyToDataAttribute = (key) => `data-slate-${kebabCase(key)}`;
|
|
1029
|
+
|
|
1030
|
+
//#endregion
|
|
1031
|
+
//#region src/lib/utils/getPluginNodeProps.ts
|
|
1032
|
+
const getPluginNodeProps = ({ attributes: nodeAttributes, node, plugin, props }) => {
|
|
1033
|
+
const newProps = {
|
|
1034
|
+
...props,
|
|
1035
|
+
attributes: { ...props.attributes }
|
|
1036
|
+
};
|
|
1037
|
+
if (plugin?.node.props) {
|
|
1038
|
+
const pluginNodeProps = (typeof plugin.node.props === "function" ? plugin.node.props(newProps) : plugin.node.props) ?? {};
|
|
1039
|
+
newProps.attributes = {
|
|
1040
|
+
...newProps.attributes,
|
|
1041
|
+
...pluginNodeProps
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
if (nodeAttributes && plugin) newProps.attributes = {
|
|
1045
|
+
...newProps.attributes,
|
|
1046
|
+
...pick(
|
|
1047
|
+
nodeAttributes,
|
|
1048
|
+
/**
|
|
1049
|
+
* WARNING: Improper use of `dangerouslyAllowAttributes` WILL make your
|
|
1050
|
+
* application vulnerable to cross-site scripting (XSS) or information
|
|
1051
|
+
* exposure attacks.
|
|
1052
|
+
*
|
|
1053
|
+
* @see {@link BasePluginNode.dangerouslyAllowAttributes}
|
|
1054
|
+
*/
|
|
1055
|
+
...plugin.node.dangerouslyAllowAttributes ?? [],
|
|
1056
|
+
[...node ? getNodeDataAttributeKeys(node) : []]
|
|
1057
|
+
)
|
|
1058
|
+
};
|
|
1059
|
+
Object.keys(newProps.attributes).forEach((key) => {
|
|
1060
|
+
if (newProps.attributes?.[key] === void 0) delete newProps.attributes?.[key];
|
|
1061
|
+
});
|
|
1062
|
+
return newProps;
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region src/lib/utils/getSlateClass.ts
|
|
1067
|
+
/** Get slate class name: slate-<type> */
|
|
1068
|
+
const getSlateClass = (type) => type ? `slate-${type}` : "";
|
|
1069
|
+
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/lib/utils/mergeDeepToNodes.ts
|
|
1072
|
+
/** Recursively merge a source object to children nodes with a query. */
|
|
1073
|
+
const mergeDeepToNodes = (options) => {
|
|
1074
|
+
applyDeepToNodes({
|
|
1075
|
+
...options,
|
|
1076
|
+
apply: merge
|
|
1077
|
+
});
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
//#endregion
|
|
1081
|
+
//#region src/lib/plugins/affinity/queries/getEdgeNodes.ts
|
|
1082
|
+
/**
|
|
1083
|
+
* When the cursor is at a mark edge, this function returns the inward node and
|
|
1084
|
+
* the outward node (if any). If the cursor is at the start of the text, then
|
|
1085
|
+
* the node before the text is returned. If the cursor is at the end of the
|
|
1086
|
+
* text, then the node after the text is returned. Otherwise, null is returned.
|
|
1087
|
+
*/
|
|
1088
|
+
const getEdgeNodes = (editor) => {
|
|
1089
|
+
if (!editor.api.isCollapsed()) return null;
|
|
1090
|
+
const cursor = editor.selection.anchor;
|
|
1091
|
+
const textRange = editor.api.range(cursor.path);
|
|
1092
|
+
if (!textRange) return null;
|
|
1093
|
+
const edge = editor.api.isStart(cursor, textRange) ? "start" : editor.api.isEnd(cursor, textRange) ? "end" : null;
|
|
1094
|
+
if (!edge) return null;
|
|
1095
|
+
const parent = NodeApi.parent(editor, cursor.path) ?? null;
|
|
1096
|
+
/** Inline elements */
|
|
1097
|
+
const isAffinityInlineElement = (() => {
|
|
1098
|
+
if (!parent || !ElementApi.isElement(parent)) return false;
|
|
1099
|
+
const parentAffinity = getPluginByType(editor, parent.type)?.rules.selection?.affinity;
|
|
1100
|
+
return parentAffinity === "hard" || parentAffinity === "directional";
|
|
1101
|
+
})();
|
|
1102
|
+
const nodeEntry = isAffinityInlineElement ? [parent, PathApi.parent(cursor.path)] : [NodeApi.get(editor, cursor.path), cursor.path];
|
|
1103
|
+
if (edge === "start" && cursor.path.at(-1) === 0 && !isAffinityInlineElement) return [null, nodeEntry];
|
|
1104
|
+
const siblingPath = edge === "end" ? Path$1.next(nodeEntry[1]) : Path$1.previous(nodeEntry[1]);
|
|
1105
|
+
const siblingNode = NodeApi.get(editor, siblingPath);
|
|
1106
|
+
const siblingEntry = siblingNode ? [siblingNode, siblingPath] : null;
|
|
1107
|
+
return edge === "end" ? [nodeEntry, siblingEntry] : [siblingEntry, nodeEntry];
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
//#endregion
|
|
1111
|
+
//#region src/lib/plugins/affinity/queries/getMarkBoundaryAffinity.ts
|
|
1112
|
+
const getMarkBoundaryAffinity = (editor, markBoundary) => {
|
|
1113
|
+
const { marks, selection } = editor;
|
|
1114
|
+
if (!selection) return;
|
|
1115
|
+
const marksMatchLeaf = (leaf) => marks && isEqual(NodeApi.extractProps(leaf), marks) && Object.keys(marks).length > 1;
|
|
1116
|
+
const [backwardLeafEntry, forwardLeafEntry] = markBoundary;
|
|
1117
|
+
if (!backwardLeafEntry || !forwardLeafEntry) {
|
|
1118
|
+
const leafEntry = backwardLeafEntry || forwardLeafEntry;
|
|
1119
|
+
if (!marks || marksMatchLeaf(leafEntry[0])) return leafEntry === backwardLeafEntry ? "backward" : "forward";
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const marksDirection = marks && (() => {
|
|
1123
|
+
if (backwardLeafEntry && marksMatchLeaf(backwardLeafEntry[0])) return "backward";
|
|
1124
|
+
if (forwardLeafEntry && marksMatchLeaf(forwardLeafEntry[0])) return "forward";
|
|
1125
|
+
return null;
|
|
1126
|
+
})();
|
|
1127
|
+
const selectionDirection = selection.anchor.offset === 0 ? "forward" : "backward";
|
|
1128
|
+
if (selectionDirection === "backward" && marksDirection === "forward") return "forward";
|
|
1129
|
+
if (IS_FIREFOX && selectionDirection === "forward" && marksDirection !== "backward") return "forward";
|
|
1130
|
+
return "backward";
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/lib/plugins/affinity/queries/isNodeAffinity.ts
|
|
1135
|
+
const isNodeAffinity = (editor, node, affinity) => {
|
|
1136
|
+
const marks = Object.keys(NodeApi.extractProps(node));
|
|
1137
|
+
return (ElementApi.isElement(node) ? [node.type] : marks).some((type) => getPluginByType(editor, type)?.rules.selection?.affinity === affinity);
|
|
1138
|
+
};
|
|
1139
|
+
const isNodesAffinity = (editor, edgeNodes, affinity) => {
|
|
1140
|
+
const [backwardLeafEntry, forwardLeafEntry] = edgeNodes;
|
|
1141
|
+
return backwardLeafEntry && isNodeAffinity(editor, backwardLeafEntry[0], affinity) || forwardLeafEntry && isNodeAffinity(editor, forwardLeafEntry[0], affinity);
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/lib/plugins/affinity/transforms/setAffinitySelection.ts
|
|
1146
|
+
const setAffinitySelection = (editor, edgeNodes, affinity) => {
|
|
1147
|
+
const setMarks = (marks) => {
|
|
1148
|
+
editor.marks = marks;
|
|
1149
|
+
editor.api.onChange();
|
|
1150
|
+
};
|
|
1151
|
+
const select = (point) => {
|
|
1152
|
+
editor.tf.setSelection({
|
|
1153
|
+
anchor: point,
|
|
1154
|
+
focus: point
|
|
1155
|
+
});
|
|
1156
|
+
};
|
|
1157
|
+
const [before, after] = edgeNodes;
|
|
1158
|
+
if (affinity === "backward") {
|
|
1159
|
+
if (before === null) {
|
|
1160
|
+
setMarks({});
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const beforeEnd = editor.api.end(before[1]);
|
|
1164
|
+
if (beforeEnd) select(beforeEnd);
|
|
1165
|
+
if (ElementApi.isElement(before[0])) return;
|
|
1166
|
+
setMarks(null);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (before === null) {
|
|
1170
|
+
setMarks(null);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (after === null) {
|
|
1174
|
+
setMarks({});
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
select(editor.api.end(before[1]));
|
|
1178
|
+
if (ElementApi.isElement(after[0])) return;
|
|
1179
|
+
setMarks(NodeApi.extractProps(after[0]));
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/lib/plugins/affinity/AffinityPlugin.ts
|
|
1184
|
+
const AffinityPlugin = createTSlatePlugin({ key: "affinity" }).overrideEditor(({ editor, tf: { deleteBackward, insertText, move } }) => ({ transforms: {
|
|
1185
|
+
deleteBackward: (unit) => {
|
|
1186
|
+
const apply = () => {
|
|
1187
|
+
if (unit === "character" && editor.api.isCollapsed()) {
|
|
1188
|
+
const [start] = getEdgeNodes(editor) ?? [null];
|
|
1189
|
+
const startText = start && (TextApi.isText(start[0]) ? start[0].text : NodeApi.string(start[0]));
|
|
1190
|
+
deleteBackward(unit);
|
|
1191
|
+
const edgeNodes = getEdgeNodes(editor);
|
|
1192
|
+
if (edgeNodes && isNodesAffinity(editor, edgeNodes, "directional") && !hasElement(edgeNodes)) setAffinitySelection(editor, edgeNodes, startText && startText.length > 1 ? "backward" : "forward");
|
|
1193
|
+
return true;
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
if (apply()) return;
|
|
1197
|
+
deleteBackward(unit);
|
|
1198
|
+
},
|
|
1199
|
+
insertText(text, options) {
|
|
1200
|
+
/** This will be computed only for text nodes with marks. */
|
|
1201
|
+
const applyOutwardAffinity = () => {
|
|
1202
|
+
if (!editor.selection || editor.api.isExpanded()) return;
|
|
1203
|
+
const textPath = editor.selection.focus.path;
|
|
1204
|
+
const textNode = NodeApi.get(editor, textPath);
|
|
1205
|
+
if (!textNode) return;
|
|
1206
|
+
const outwardMarks = Object.keys(NodeApi.extractProps(textNode)).filter((type) => getPluginByType(editor, type)?.rules.selection?.affinity === "outward");
|
|
1207
|
+
if (!outwardMarks.length || !editor.api.isEnd(editor.selection.focus, textPath)) return;
|
|
1208
|
+
const nextPoint = editor.api.start(textPath, { next: true });
|
|
1209
|
+
const marksToRemove = [];
|
|
1210
|
+
let nextTextNode = null;
|
|
1211
|
+
if (nextPoint) {
|
|
1212
|
+
const nextTextPath = nextPoint.path;
|
|
1213
|
+
nextTextNode = NodeApi.get(editor, nextTextPath) || null;
|
|
1214
|
+
}
|
|
1215
|
+
for (const markKey of outwardMarks) {
|
|
1216
|
+
if (!textNode[markKey]) continue;
|
|
1217
|
+
if (!nextTextNode?.[markKey]) marksToRemove.push(markKey);
|
|
1218
|
+
}
|
|
1219
|
+
if (marksToRemove.length > 0) editor.tf.removeMarks(marksToRemove);
|
|
1220
|
+
};
|
|
1221
|
+
applyOutwardAffinity();
|
|
1222
|
+
return insertText(text, options);
|
|
1223
|
+
},
|
|
1224
|
+
move: (options) => {
|
|
1225
|
+
const apply = () => {
|
|
1226
|
+
const { distance = 1, reverse = false, unit = "character" } = options || {};
|
|
1227
|
+
if (unit === "character" && distance === 1 && editor.api.isCollapsed()) {
|
|
1228
|
+
const preEdgeNodes = getEdgeNodes(editor);
|
|
1229
|
+
if (preEdgeNodes && isNodesAffinity(editor, preEdgeNodes, "hard")) {
|
|
1230
|
+
if (preEdgeNodes && preEdgeNodes[reverse ? 0 : 1] === null && getMarkBoundaryAffinity(editor, preEdgeNodes) === (reverse ? "forward" : "backward")) {
|
|
1231
|
+
setAffinitySelection(editor, preEdgeNodes, reverse ? "backward" : "forward");
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
move({
|
|
1235
|
+
...options,
|
|
1236
|
+
unit: "offset"
|
|
1237
|
+
});
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
move(options);
|
|
1241
|
+
const postEdgeNodes = getEdgeNodes(editor);
|
|
1242
|
+
/**
|
|
1243
|
+
* If the move places the cursor at a mark boundary, then the affinity
|
|
1244
|
+
* should be set to the direction the cursor came from.
|
|
1245
|
+
*/
|
|
1246
|
+
if (postEdgeNodes && isNodesAffinity(editor, postEdgeNodes, "directional") && !hasElement(postEdgeNodes)) setAffinitySelection(editor, postEdgeNodes, reverse ? "forward" : "backward");
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
if (apply()) return;
|
|
1251
|
+
move(options);
|
|
1252
|
+
}
|
|
1253
|
+
} }));
|
|
1254
|
+
const hasElement = (edgeNodes) => {
|
|
1255
|
+
const [before, after] = edgeNodes;
|
|
1256
|
+
return before && ElementApi.isElement(before[0]) || after && ElementApi.isElement(after[0]);
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/lib/plugins/chunking/withChunking.ts
|
|
1261
|
+
const withChunking = ({ editor, getOptions }) => {
|
|
1262
|
+
const { chunkSize, query } = getOptions();
|
|
1263
|
+
editor.getChunkSize = (ancestor) => query(ancestor) ? chunkSize : null;
|
|
1264
|
+
return {};
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
//#endregion
|
|
1268
|
+
//#region src/lib/plugins/chunking/ChunkingPlugin.ts
|
|
1269
|
+
const ChunkingPlugin = createTSlatePlugin({
|
|
1270
|
+
key: "chunking",
|
|
1271
|
+
options: {
|
|
1272
|
+
chunkSize: 1e3,
|
|
1273
|
+
contentVisibilityAuto: true,
|
|
1274
|
+
query: NodeApi.isEditor
|
|
1275
|
+
}
|
|
1276
|
+
}).overrideEditor(withChunking);
|
|
1277
|
+
|
|
1278
|
+
//#endregion
|
|
1279
|
+
//#region src/lib/plugins/debug/DebugPlugin.ts
|
|
1280
|
+
var PlateError = class extends Error {
|
|
1281
|
+
type;
|
|
1282
|
+
constructor(message, type = "DEFAULT") {
|
|
1283
|
+
super(`[${type}] ${message}`);
|
|
1284
|
+
this.name = "PlateError";
|
|
1285
|
+
this.type = type;
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
const DebugPlugin = createTSlatePlugin({
|
|
1289
|
+
key: "debug",
|
|
1290
|
+
options: {
|
|
1291
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
1292
|
+
logger: {
|
|
1293
|
+
error: (message, type, details) => console.error(`${type ? `[${type}] ` : ""}${message}`, details),
|
|
1294
|
+
info: (message, type, details) => console.info(`${type ? `[${type}] ` : ""}${message}`, details),
|
|
1295
|
+
log: (message, type, details) => console.log(`${type ? `[${type}] ` : ""}${message}`, details),
|
|
1296
|
+
warn: (message, type, details) => console.warn(`${type ? `[${type}] ` : ""}${message}`, details)
|
|
1297
|
+
},
|
|
1298
|
+
logLevel: process.env.NODE_ENV === "production" ? "error" : "log",
|
|
1299
|
+
throwErrors: true
|
|
1300
|
+
}
|
|
1301
|
+
}).extendEditorApi(({ getOptions }) => {
|
|
1302
|
+
const logLevels = [
|
|
1303
|
+
"error",
|
|
1304
|
+
"warn",
|
|
1305
|
+
"info",
|
|
1306
|
+
"log"
|
|
1307
|
+
];
|
|
1308
|
+
const log = (level, message, type, details) => {
|
|
1309
|
+
if (process.env.NODE_ENV === "production") return;
|
|
1310
|
+
const options = getOptions();
|
|
1311
|
+
if (options.isProduction && level === "log") return;
|
|
1312
|
+
if (logLevels.indexOf(level) <= logLevels.indexOf(options.logLevel)) {
|
|
1313
|
+
if (level === "error" && options.throwErrors) throw new PlateError(message, type);
|
|
1314
|
+
options.logger[level]?.(message, type, details);
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
return { debug: {
|
|
1318
|
+
error: (message, type, details) => log("error", message, type, details),
|
|
1319
|
+
info: (message, type, details) => log("info", message, type, details),
|
|
1320
|
+
log: (message, type, details) => log("log", message, type, details),
|
|
1321
|
+
warn: (message, type, details) => log("warn", message, type, details)
|
|
1322
|
+
} };
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
//#endregion
|
|
1326
|
+
//#region src/lib/plugins/dom/withScrolling.ts
|
|
1327
|
+
const withScrolling = (editor, fn, options) => {
|
|
1328
|
+
const prevOptions = editor.getOptions(DOMPlugin);
|
|
1329
|
+
const prevAutoScroll = AUTO_SCROLL.get(editor) ?? false;
|
|
1330
|
+
if (options) {
|
|
1331
|
+
const ops = {
|
|
1332
|
+
...prevOptions,
|
|
1333
|
+
...omitBy(options, isUndefined)
|
|
1334
|
+
};
|
|
1335
|
+
editor.setOptions(DOMPlugin, ops);
|
|
1336
|
+
}
|
|
1337
|
+
AUTO_SCROLL.set(editor, true);
|
|
1338
|
+
fn();
|
|
1339
|
+
AUTO_SCROLL.set(editor, prevAutoScroll);
|
|
1340
|
+
editor.setOptions(DOMPlugin, prevOptions);
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region src/lib/plugins/dom/DOMPlugin.ts
|
|
1345
|
+
const AUTO_SCROLL = /* @__PURE__ */ new WeakMap();
|
|
1346
|
+
/**
|
|
1347
|
+
* Placeholder plugin for DOM interaction, that could be replaced with
|
|
1348
|
+
* ReactPlugin.
|
|
1349
|
+
*/
|
|
1350
|
+
const DOMPlugin = createTSlatePlugin({
|
|
1351
|
+
key: "dom",
|
|
1352
|
+
options: {
|
|
1353
|
+
scrollMode: "last",
|
|
1354
|
+
scrollOperations: {
|
|
1355
|
+
insert_node: true,
|
|
1356
|
+
insert_text: true
|
|
1357
|
+
},
|
|
1358
|
+
scrollOptions: { scrollMode: "if-needed" }
|
|
1359
|
+
}
|
|
1360
|
+
}).extendEditorApi(({ editor }) => ({ isScrolling: () => AUTO_SCROLL.get(editor) ?? false })).extendEditorTransforms(({ editor }) => ({ withScrolling: bindFirst(withScrolling, editor) })).overrideEditor(({ api, editor, getOption, tf: { apply } }) => ({ transforms: { apply(operation) {
|
|
1361
|
+
if (api.isScrolling()) {
|
|
1362
|
+
apply(operation);
|
|
1363
|
+
const scrollOperations = getOption("scrollOperations");
|
|
1364
|
+
if (!scrollOperations[operation.type]) return;
|
|
1365
|
+
const matched = editor.operations.filter((op) => !!scrollOperations[op.type]);
|
|
1366
|
+
if (matched.length === 0) return;
|
|
1367
|
+
const targetOp = getOption("scrollMode") === "first" ? matched[0] : matched.at(-1);
|
|
1368
|
+
if (!targetOp) return;
|
|
1369
|
+
const { offset, path } = targetOp.path ? targetOp : {};
|
|
1370
|
+
if (!path) return;
|
|
1371
|
+
const scrollOptions = getOption("scrollOptions");
|
|
1372
|
+
const scrollTarget = {
|
|
1373
|
+
offset: offset ?? 0,
|
|
1374
|
+
path
|
|
1375
|
+
};
|
|
1376
|
+
api.scrollIntoView(scrollTarget, scrollOptions);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
return apply(operation);
|
|
1380
|
+
} } })).overrideEditor(({ editor, tf: { apply } }) => ({ transforms: { apply(operation) {
|
|
1381
|
+
if (operation.type === "set_selection") {
|
|
1382
|
+
const { properties } = operation;
|
|
1383
|
+
editor.dom.prevSelection = properties;
|
|
1384
|
+
apply(operation);
|
|
1385
|
+
editor.dom.currentKeyboardEvent = null;
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
apply(operation);
|
|
1389
|
+
} } }));
|
|
1390
|
+
|
|
1391
|
+
//#endregion
|
|
1392
|
+
//#region src/lib/plugins/html/utils/isHtmlElement.ts
|
|
1393
|
+
const isHtmlElement = (node) => node.nodeType === Node.ELEMENT_NODE;
|
|
1394
|
+
|
|
1395
|
+
//#endregion
|
|
1396
|
+
//#region src/lib/plugins/html/utils/isHtmlText.ts
|
|
1397
|
+
const isHtmlText = (node) => node.nodeType === Node.TEXT_NODE;
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/lib/plugins/html/utils/inlineTagNames.ts
|
|
1401
|
+
/**
|
|
1402
|
+
* # Methodology
|
|
1403
|
+
*
|
|
1404
|
+
* ## Step 1. Get the list of all standard tag names
|
|
1405
|
+
*
|
|
1406
|
+
* Go to https://developer.mozilla.org/en-US/docs/Web/HTML/Element and run the
|
|
1407
|
+
* following in the console to generate a JSON array of tag names:
|
|
1408
|
+
*
|
|
1409
|
+
* ```js
|
|
1410
|
+
* JSON.stringify(
|
|
1411
|
+
* Array.from(document.querySelectorAll('article table td:first-child'))
|
|
1412
|
+
* .map((td) => {
|
|
1413
|
+
* const body = document.createElement('body');
|
|
1414
|
+
* body.innerHTML = td.textContent;
|
|
1415
|
+
* return body.firstChild?.tagName;
|
|
1416
|
+
* })
|
|
1417
|
+
* .filter((tagName) => tagName)
|
|
1418
|
+
* );
|
|
1419
|
+
* ```
|
|
1420
|
+
*
|
|
1421
|
+
* Output (as of 2023-11-06):
|
|
1422
|
+
*
|
|
1423
|
+
* ```json
|
|
1424
|
+
* '["BASE","LINK","META","STYLE","TITLE","ADDRESS","ARTICLE","ASIDE","FOOTER","HEADER","H1","HGROUP","MAIN","NAV","SECTION","SEARCH","BLOCKQUOTE","DD","DIV","DL","DT","FIGCAPTION","FIGURE","HR","LI","MENU","OL","P","PRE","UL","A","ABBR","B","BDI","BDO","BR","CITE","CODE","DATA","DFN","EM","I","KBD","MARK","Q","RP","RT","RUBY","S","SAMP","SMALL","SPAN","STRONG","SUB","SUP","TIME","U","VAR","WBR","AREA","AUDIO","IMG","MAP","TRACK","VIDEO","EMBED","IFRAME","OBJECT","PICTURE","PORTAL","SOURCE","svg","math","CANVAS","NOSCRIPT","SCRIPT","DEL","INS","TABLE","BUTTON","DATALIST","FIELDSET","FORM","INPUT","LABEL","LEGEND","METER","OPTGROUP","OPTION","OUTPUT","PROGRESS","SELECT","TEXTAREA","DETAILS","DIALOG","SUMMARY","SLOT","TEMPLATE","ACRONYM","BIG","CENTER","CONTENT","DIR","FONT","IMG","MARQUEE","MENUITEM","NOBR","NOEMBED","NOFRAMES","PARAM","PLAINTEXT","RB","RTC","SHADOW","STRIKE","TT","XMP"]'
|
|
1425
|
+
* ```
|
|
1426
|
+
*
|
|
1427
|
+
* ## Step 2. For each tag name, determine the default browser style
|
|
1428
|
+
*
|
|
1429
|
+
* Open an empty HTML file in the browser and run the following in the console:
|
|
1430
|
+
*
|
|
1431
|
+
* ```js
|
|
1432
|
+
* const tagNames = JSON.parse(<JSON string from step 1>);
|
|
1433
|
+
*
|
|
1434
|
+
* JSON.stringify(
|
|
1435
|
+
* tagNames.filter((tagName) => {
|
|
1436
|
+
* const element = document.createElement(tagName);
|
|
1437
|
+
* document.body.appendChild(element);
|
|
1438
|
+
* const display = window.getComputedStyle(element).display;
|
|
1439
|
+
* element.remove();
|
|
1440
|
+
* return display.startsWith('inline');
|
|
1441
|
+
* })
|
|
1442
|
+
* );
|
|
1443
|
+
* ```
|
|
1444
|
+
*
|
|
1445
|
+
* Place the result in the array below (accurate as of 2023-11-06).
|
|
1446
|
+
*/
|
|
1447
|
+
const inlineTagNames = new Set([
|
|
1448
|
+
"A",
|
|
1449
|
+
"ABBR",
|
|
1450
|
+
"ACRONYM",
|
|
1451
|
+
"B",
|
|
1452
|
+
"BDI",
|
|
1453
|
+
"BDO",
|
|
1454
|
+
"BIG",
|
|
1455
|
+
"BR",
|
|
1456
|
+
"BUTTON",
|
|
1457
|
+
"CANVAS",
|
|
1458
|
+
"CITE",
|
|
1459
|
+
"CODE",
|
|
1460
|
+
"CONTENT",
|
|
1461
|
+
"DATA",
|
|
1462
|
+
"DEL",
|
|
1463
|
+
"DFN",
|
|
1464
|
+
"EM",
|
|
1465
|
+
"EMBED",
|
|
1466
|
+
"FONT",
|
|
1467
|
+
"I",
|
|
1468
|
+
"IFRAME",
|
|
1469
|
+
"IMG",
|
|
1470
|
+
"IMG",
|
|
1471
|
+
"INPUT",
|
|
1472
|
+
"INS",
|
|
1473
|
+
"KBD",
|
|
1474
|
+
"LABEL",
|
|
1475
|
+
"MAP",
|
|
1476
|
+
"MARK",
|
|
1477
|
+
"MARQUEE",
|
|
1478
|
+
"math",
|
|
1479
|
+
"MENUITEM",
|
|
1480
|
+
"METER",
|
|
1481
|
+
"NOBR",
|
|
1482
|
+
"OBJECT",
|
|
1483
|
+
"OUTPUT",
|
|
1484
|
+
"PICTURE",
|
|
1485
|
+
"PORTAL",
|
|
1486
|
+
"PROGRESS",
|
|
1487
|
+
"Q",
|
|
1488
|
+
"S",
|
|
1489
|
+
"SAMP",
|
|
1490
|
+
"SELECT",
|
|
1491
|
+
"SHADOW",
|
|
1492
|
+
"SMALL",
|
|
1493
|
+
"SOURCE",
|
|
1494
|
+
"SPAN",
|
|
1495
|
+
"STRIKE",
|
|
1496
|
+
"STRONG",
|
|
1497
|
+
"SUB",
|
|
1498
|
+
"SUP",
|
|
1499
|
+
"svg",
|
|
1500
|
+
"TEXTAREA",
|
|
1501
|
+
"TIME",
|
|
1502
|
+
"TRACK",
|
|
1503
|
+
"TT",
|
|
1504
|
+
"U",
|
|
1505
|
+
"VAR",
|
|
1506
|
+
"VIDEO",
|
|
1507
|
+
"WBR"
|
|
1508
|
+
]);
|
|
1509
|
+
|
|
1510
|
+
//#endregion
|
|
1511
|
+
//#region src/lib/plugins/html/utils/isHtmlInlineElement.ts
|
|
1512
|
+
const isHtmlInlineElement = (node) => {
|
|
1513
|
+
if (!isHtmlElement(node)) return false;
|
|
1514
|
+
const element = node;
|
|
1515
|
+
const tagNameIsInline = inlineTagNames.has(element.tagName);
|
|
1516
|
+
/**
|
|
1517
|
+
* Valid display values include 'inline flow'. We only care about the first
|
|
1518
|
+
* part.
|
|
1519
|
+
*/
|
|
1520
|
+
const displayProperty = element.style.display.split(" ")[0];
|
|
1521
|
+
if (displayProperty === "") return tagNameIsInline;
|
|
1522
|
+
if (displayProperty.startsWith("inline")) return true;
|
|
1523
|
+
if (displayProperty === "inherit" && element.parentElement) return isHtmlInlineElement(element.parentElement);
|
|
1524
|
+
/**
|
|
1525
|
+
* Handle all special values manually, so that any unhandled values can be
|
|
1526
|
+
* assumed to be block.
|
|
1527
|
+
*
|
|
1528
|
+
* Note: Ideally, content inside `display: none` elements should not be
|
|
1529
|
+
* parsed. However, if such elements are parsed, it's best for their inline or
|
|
1530
|
+
* block status to be left unchanged.
|
|
1531
|
+
*/
|
|
1532
|
+
if ([
|
|
1533
|
+
"contents",
|
|
1534
|
+
"initial",
|
|
1535
|
+
"none",
|
|
1536
|
+
"revert",
|
|
1537
|
+
"revert-layer",
|
|
1538
|
+
"unset"
|
|
1539
|
+
].includes(displayProperty)) return tagNameIsInline;
|
|
1540
|
+
return false;
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/lib/plugins/html/utils/isHtmlBlockElement.ts
|
|
1545
|
+
const isHtmlBlockElement = (node) => {
|
|
1546
|
+
if (!isHtmlElement(node)) return false;
|
|
1547
|
+
return !isHtmlInlineElement(node);
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
//#endregion
|
|
1551
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseString.ts
|
|
1552
|
+
const LEADING_WHITESPACE_REGEX = /^\s+/;
|
|
1553
|
+
const TRAILING_NEWLINE_REGEX = /\n$/;
|
|
1554
|
+
const collapseString = (text, { shouldCollapseWhiteSpace = true, trimEnd = "collapse", trimStart = "collapse", whiteSpaceIncludesNewlines = true } = {}) => {
|
|
1555
|
+
let result = text;
|
|
1556
|
+
if (trimStart === "all") result = result.replace(LEADING_WHITESPACE_REGEX, "");
|
|
1557
|
+
if (trimEnd === "single-newline") result = result.replace(TRAILING_NEWLINE_REGEX, "");
|
|
1558
|
+
if (shouldCollapseWhiteSpace) if (whiteSpaceIncludesNewlines) result = result.replaceAll(/\s+/g, " ");
|
|
1559
|
+
else {
|
|
1560
|
+
result = result.replaceAll(/[^\S\n\r]+/g, " ");
|
|
1561
|
+
/**
|
|
1562
|
+
* Trim horizontal whitespace from the start and end of lines (behavior of
|
|
1563
|
+
* pre-line).
|
|
1564
|
+
*/
|
|
1565
|
+
result = result.replaceAll(/^[^\S\n\r]+/gm, "");
|
|
1566
|
+
result = result.replaceAll(/[^\S\n\r]+$/gm, "");
|
|
1567
|
+
}
|
|
1568
|
+
return result;
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/isLastNonEmptyTextOfInlineFormattingContext.ts
|
|
1573
|
+
const isLastNonEmptyTextOfInlineFormattingContext = (initialText) => {
|
|
1574
|
+
let currentNode = initialText;
|
|
1575
|
+
while (true) {
|
|
1576
|
+
if (currentNode.nextSibling) currentNode = currentNode.nextSibling;
|
|
1577
|
+
else {
|
|
1578
|
+
currentNode = currentNode.parentElement;
|
|
1579
|
+
if (currentNode && isHtmlBlockElement(currentNode)) return true;
|
|
1580
|
+
currentNode = currentNode?.nextSibling || null;
|
|
1581
|
+
}
|
|
1582
|
+
if (!currentNode) return true;
|
|
1583
|
+
if (isHtmlBlockElement(currentNode)) return true;
|
|
1584
|
+
if ((currentNode.textContent || "").length > 0) return false;
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
//#endregion
|
|
1589
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/stateTransforms.ts
|
|
1590
|
+
const upsertInlineFormattingContext = (state) => {
|
|
1591
|
+
if (state.inlineFormattingContext) state.inlineFormattingContext.atStart = false;
|
|
1592
|
+
else state.inlineFormattingContext = {
|
|
1593
|
+
atStart: true,
|
|
1594
|
+
lastHasTrailingWhiteSpace: false
|
|
1595
|
+
};
|
|
1596
|
+
};
|
|
1597
|
+
const endInlineFormattingContext = (state) => {
|
|
1598
|
+
state.inlineFormattingContext = null;
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1601
|
+
//#endregion
|
|
1602
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceText.ts
|
|
1603
|
+
const collapseWhiteSpaceText = (text, state) => {
|
|
1604
|
+
const textContent = text.textContent || "";
|
|
1605
|
+
const isWhiteSpaceOnly = textContent.trim() === "";
|
|
1606
|
+
/**
|
|
1607
|
+
* Do not start an inline formatting context with a text node containing only
|
|
1608
|
+
* white space.
|
|
1609
|
+
*/
|
|
1610
|
+
if (state.inlineFormattingContext || !isWhiteSpaceOnly) upsertInlineFormattingContext(state);
|
|
1611
|
+
const { whiteSpaceRule } = state;
|
|
1612
|
+
/**
|
|
1613
|
+
* Note: Due to the way HTML strings are parsed in htmlStringToDOMNode, up to
|
|
1614
|
+
* one newline is already trimmed from the start of text nodes inside <pre>
|
|
1615
|
+
* elements. If we do so again here, we may remove too many newlines. This
|
|
1616
|
+
* only applies to actual <pre> elements, not elements with the white-space
|
|
1617
|
+
* CSS property.
|
|
1618
|
+
*/
|
|
1619
|
+
const trimStart = (() => {
|
|
1620
|
+
if (whiteSpaceRule !== "normal") return "collapse";
|
|
1621
|
+
if (!state.inlineFormattingContext || state.inlineFormattingContext.atStart || state.inlineFormattingContext.lastHasTrailingWhiteSpace) return "all";
|
|
1622
|
+
return "collapse";
|
|
1623
|
+
})();
|
|
1624
|
+
const trimEnd = (() => {
|
|
1625
|
+
if (whiteSpaceRule === "normal") return "collapse";
|
|
1626
|
+
if (isLastNonEmptyTextOfInlineFormattingContext(text)) return "single-newline";
|
|
1627
|
+
return "collapse";
|
|
1628
|
+
})();
|
|
1629
|
+
const shouldCollapseWhiteSpace = {
|
|
1630
|
+
normal: true,
|
|
1631
|
+
pre: false,
|
|
1632
|
+
"pre-line": true
|
|
1633
|
+
}[whiteSpaceRule];
|
|
1634
|
+
const collapsedTextContent = collapseString(textContent || "", {
|
|
1635
|
+
shouldCollapseWhiteSpace,
|
|
1636
|
+
trimEnd,
|
|
1637
|
+
trimStart,
|
|
1638
|
+
whiteSpaceIncludesNewlines: whiteSpaceRule !== "pre-line"
|
|
1639
|
+
});
|
|
1640
|
+
if (state.inlineFormattingContext && shouldCollapseWhiteSpace) state.inlineFormattingContext.lastHasTrailingWhiteSpace = collapsedTextContent.endsWith(" ");
|
|
1641
|
+
text.textContent = collapsedTextContent;
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
//#endregion
|
|
1645
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceNode.ts
|
|
1646
|
+
const collapseWhiteSpaceNode = (node, state) => {
|
|
1647
|
+
if (isHtmlElement(node)) {
|
|
1648
|
+
collapseWhiteSpaceElement(node, state);
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
if (isHtmlText(node)) {
|
|
1652
|
+
collapseWhiteSpaceText(node, state);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
collapseWhiteSpaceChildren(node, state);
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
//#endregion
|
|
1659
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceChildren.ts
|
|
1660
|
+
const collapseWhiteSpaceChildren = (node, state) => {
|
|
1661
|
+
const childNodes = Array.from(node.childNodes);
|
|
1662
|
+
for (const childNode of childNodes) collapseWhiteSpaceNode(childNode, state);
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
//#endregion
|
|
1666
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/inferWhiteSpaceRule.ts
|
|
1667
|
+
const inferWhiteSpaceRule = (element) => {
|
|
1668
|
+
const whiteSpaceProperty = element.style.whiteSpace;
|
|
1669
|
+
switch (whiteSpaceProperty) {
|
|
1670
|
+
case "break-spaces":
|
|
1671
|
+
case "pre":
|
|
1672
|
+
case "pre-wrap": return "pre";
|
|
1673
|
+
case "normal":
|
|
1674
|
+
case "nowrap": return "normal";
|
|
1675
|
+
case "pre-line": return "pre-line";
|
|
1676
|
+
}
|
|
1677
|
+
if (element.tagName === "PRE") return "pre";
|
|
1678
|
+
if (whiteSpaceProperty === "initial") return "normal";
|
|
1679
|
+
return null;
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
//#endregion
|
|
1683
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpaceElement.ts
|
|
1684
|
+
/**
|
|
1685
|
+
* Note: We do not want to start an inline formatting context until we encounter
|
|
1686
|
+
* a text node.
|
|
1687
|
+
*/
|
|
1688
|
+
const collapseWhiteSpaceElement = (element, state) => {
|
|
1689
|
+
const isInlineElement = isHtmlInlineElement(element);
|
|
1690
|
+
const previousWhiteSpaceRule = state.whiteSpaceRule;
|
|
1691
|
+
const inferredWhiteSpaceRule = inferWhiteSpaceRule(element);
|
|
1692
|
+
if (inferredWhiteSpaceRule) state.whiteSpaceRule = inferredWhiteSpaceRule;
|
|
1693
|
+
if (!isInlineElement) endInlineFormattingContext(state);
|
|
1694
|
+
collapseWhiteSpaceChildren(element, state);
|
|
1695
|
+
if (!isInlineElement) endInlineFormattingContext(state);
|
|
1696
|
+
state.whiteSpaceRule = previousWhiteSpaceRule;
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
//#endregion
|
|
1700
|
+
//#region src/lib/plugins/html/utils/collapse-white-space/collapseWhiteSpace.ts
|
|
1701
|
+
const collapseWhiteSpace = (element) => {
|
|
1702
|
+
const clonedElement = element.cloneNode(true);
|
|
1703
|
+
collapseWhiteSpaceElement(clonedElement, {
|
|
1704
|
+
inlineFormattingContext: null,
|
|
1705
|
+
whiteSpaceRule: "normal"
|
|
1706
|
+
});
|
|
1707
|
+
return clonedElement;
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
//#endregion
|
|
1711
|
+
//#region src/lib/plugins/html/utils/deserializeHtmlNodeChildren.ts
|
|
1712
|
+
const deserializeHtmlNodeChildren = (editor, node, isSlateParent = false) => Array.from(node.childNodes).flatMap((child) => {
|
|
1713
|
+
if (child.nodeType === 1 && !isSlateNode(child) && isSlateParent) return deserializeHtmlNodeChildren(editor, child, isSlateParent);
|
|
1714
|
+
return deserializeHtmlNode(editor)(child);
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
//#endregion
|
|
1718
|
+
//#region src/lib/plugins/html/utils/htmlBodyToFragment.ts
|
|
1719
|
+
/** Deserialize HTML body element to Fragment. */
|
|
1720
|
+
const htmlBodyToFragment = (editor, element) => {
|
|
1721
|
+
if (element.nodeName === "BODY") return jsx("fragment", {}, deserializeHtmlNodeChildren(editor, element));
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
//#endregion
|
|
1725
|
+
//#region src/lib/plugins/html/utils/htmlBrToNewLine.ts
|
|
1726
|
+
/** Deserialize HTML to break line. */
|
|
1727
|
+
const htmlBrToNewLine = (node) => {
|
|
1728
|
+
if (node.nodeName === "BR") return "\n";
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
//#endregion
|
|
1732
|
+
//#region src/lib/plugins/html/utils/getDataNodeProps.ts
|
|
1733
|
+
const getDefaultNodeProps = ({ element, type }) => {
|
|
1734
|
+
if (!isSlatePluginNode(element, type) && !isSlateLeaf(element)) return;
|
|
1735
|
+
const dataAttributes = {};
|
|
1736
|
+
Object.entries(element.dataset).forEach(([key, value]) => {
|
|
1737
|
+
if (key.startsWith("slate") && value && ![
|
|
1738
|
+
"slateInline",
|
|
1739
|
+
"slateLeaf",
|
|
1740
|
+
"slateNode",
|
|
1741
|
+
"slateVoid"
|
|
1742
|
+
].includes(key)) {
|
|
1743
|
+
const attributeKey = key.slice(5).charAt(0).toLowerCase() + key.slice(6);
|
|
1744
|
+
if (value === void 0) return;
|
|
1745
|
+
let parsedValue = value;
|
|
1746
|
+
if (value === "true") parsedValue = true;
|
|
1747
|
+
else if (value === "false") parsedValue = false;
|
|
1748
|
+
else if (!Number.isNaN(Number(value))) parsedValue = Number(value);
|
|
1749
|
+
dataAttributes[attributeKey] = parsedValue;
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
if (Object.keys(dataAttributes).length > 0) return dataAttributes;
|
|
1753
|
+
};
|
|
1754
|
+
const getDataNodeProps = ({ editor, element, plugin }) => {
|
|
1755
|
+
const toNodeProps = plugin.parsers.html?.deserializer?.toNodeProps;
|
|
1756
|
+
const defaultNodeProps = plugin.parsers.html?.deserializer?.disableDefaultNodeProps ?? false ? {} : getDefaultNodeProps({
|
|
1757
|
+
...getEditorPlugin(editor, plugin),
|
|
1758
|
+
element
|
|
1759
|
+
});
|
|
1760
|
+
if (!toNodeProps) return defaultNodeProps;
|
|
1761
|
+
const customNodeProps = toNodeProps({
|
|
1762
|
+
...getEditorPlugin(editor, plugin),
|
|
1763
|
+
element
|
|
1764
|
+
}) ?? {};
|
|
1765
|
+
return {
|
|
1766
|
+
...defaultNodeProps,
|
|
1767
|
+
...customNodeProps
|
|
1768
|
+
};
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
//#endregion
|
|
1772
|
+
//#region src/lib/plugins/html/utils/pluginDeserializeHtml.ts
|
|
1773
|
+
/**
|
|
1774
|
+
* Get a deserializer and add default rules for deserializing plate static
|
|
1775
|
+
* elements
|
|
1776
|
+
*/
|
|
1777
|
+
const getDeserializedWithStaticRules = (plugin) => {
|
|
1778
|
+
let deserializer = plugin.parsers?.html?.deserializer;
|
|
1779
|
+
const rules = deserializer?.rules ?? [];
|
|
1780
|
+
const staticRules = rules.some((rule) => rule.validClassName?.includes(`slate-${plugin.key}`)) ? rules : [{
|
|
1781
|
+
validClassName: `slate-${plugin.key}`,
|
|
1782
|
+
validNodeName: "*"
|
|
1783
|
+
}, ...rules];
|
|
1784
|
+
if (!deserializer) deserializer = { rules: staticRules };
|
|
1785
|
+
deserializer.rules = staticRules;
|
|
1786
|
+
return deserializer;
|
|
1787
|
+
};
|
|
1788
|
+
/** Get a deserializer by type, node names, class names and styles. */
|
|
1789
|
+
const pluginDeserializeHtml = (editor, plugin, { deserializeLeaf, element: el }) => {
|
|
1790
|
+
const { node: { isElement: isElementRoot, isLeaf: isLeafRoot } } = plugin;
|
|
1791
|
+
const deserializer = getDeserializedWithStaticRules(plugin);
|
|
1792
|
+
if (!deserializer) return;
|
|
1793
|
+
const { attributeNames, isElement: isElementRule, isLeaf: isLeafRule, query, rules } = deserializer;
|
|
1794
|
+
let { parse } = deserializer;
|
|
1795
|
+
const isElement = isElementRule || isElementRoot;
|
|
1796
|
+
const isLeaf = isLeafRule || isLeafRoot;
|
|
1797
|
+
if (!deserializeLeaf && !isElement) return;
|
|
1798
|
+
if (deserializeLeaf && !isLeaf) return;
|
|
1799
|
+
if (rules) {
|
|
1800
|
+
if (!rules.some(({ validAttribute, validClassName, validNodeName = "*", validStyle }) => {
|
|
1801
|
+
if (validNodeName) {
|
|
1802
|
+
const validNodeNames = castArray(validNodeName);
|
|
1803
|
+
if (validNodeNames.length > 0 && !validNodeNames.includes(el.nodeName) && validNodeName !== "*") return false;
|
|
1804
|
+
}
|
|
1805
|
+
if (validClassName && !el.classList.contains(validClassName)) return false;
|
|
1806
|
+
if (validStyle) for (const [key, value] of Object.entries(validStyle)) {
|
|
1807
|
+
if (!castArray(value).includes(el.style[key]) && value !== "*") return false;
|
|
1808
|
+
if (value === "*" && !el.style[key]) return false;
|
|
1809
|
+
const defaultNodeValue = plugin.inject.nodeProps?.defaultNodeValue;
|
|
1810
|
+
if (defaultNodeValue && defaultNodeValue === el.style[key]) return false;
|
|
1811
|
+
}
|
|
1812
|
+
if (validAttribute) if (typeof validAttribute === "string") {
|
|
1813
|
+
if (!el.getAttributeNames().includes(validAttribute)) return false;
|
|
1814
|
+
} else for (const [attributeName, attributeValue] of Object.entries(validAttribute)) {
|
|
1815
|
+
const attributeValues = castArray(attributeValue);
|
|
1816
|
+
const elAttribute = el.getAttribute(attributeName);
|
|
1817
|
+
if (!isDefined(elAttribute) || !attributeValues.includes(elAttribute)) return false;
|
|
1818
|
+
}
|
|
1819
|
+
return true;
|
|
1820
|
+
})) return;
|
|
1821
|
+
}
|
|
1822
|
+
if (query && !query({
|
|
1823
|
+
...getEditorPlugin(editor, plugin),
|
|
1824
|
+
element: el
|
|
1825
|
+
})) return;
|
|
1826
|
+
if (!parse) if (isElement) parse = ({ type }) => ({ type });
|
|
1827
|
+
else if (isLeaf) parse = ({ type }) => ({ [type]: true });
|
|
1828
|
+
else return;
|
|
1829
|
+
const parsedNode = (() => {
|
|
1830
|
+
if (isSlateNode(el)) return {};
|
|
1831
|
+
return parse({
|
|
1832
|
+
...getEditorPlugin(editor, plugin),
|
|
1833
|
+
element: el,
|
|
1834
|
+
node: {}
|
|
1835
|
+
}) ?? {};
|
|
1836
|
+
})();
|
|
1837
|
+
const dataNodeProps = getDataNodeProps({
|
|
1838
|
+
editor,
|
|
1839
|
+
element: el,
|
|
1840
|
+
plugin
|
|
1841
|
+
});
|
|
1842
|
+
let node = {
|
|
1843
|
+
...parsedNode,
|
|
1844
|
+
...dataNodeProps
|
|
1845
|
+
};
|
|
1846
|
+
if (Object.keys(node).length === 0) return;
|
|
1847
|
+
getInjectedPlugins(editor, plugin).forEach((injectedPlugin) => {
|
|
1848
|
+
const res = injectedPlugin.parsers?.html?.deserializer?.parse?.({
|
|
1849
|
+
...getEditorPlugin(editor, plugin),
|
|
1850
|
+
element: el,
|
|
1851
|
+
node
|
|
1852
|
+
});
|
|
1853
|
+
if (res && !isSlateNode(el)) node = {
|
|
1854
|
+
...node,
|
|
1855
|
+
...res
|
|
1856
|
+
};
|
|
1857
|
+
});
|
|
1858
|
+
if (attributeNames) {
|
|
1859
|
+
const elementAttributes = {};
|
|
1860
|
+
const elementAttributeNames = el.getAttributeNames();
|
|
1861
|
+
for (const elementAttributeName of elementAttributeNames) if (attributeNames.includes(elementAttributeName)) elementAttributes[elementAttributeName] = el.getAttribute(elementAttributeName);
|
|
1862
|
+
if (Object.keys(elementAttributes).length > 0) node.attributes = elementAttributes;
|
|
1863
|
+
}
|
|
1864
|
+
return {
|
|
1865
|
+
...deserializer,
|
|
1866
|
+
node
|
|
1867
|
+
};
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
//#endregion
|
|
1871
|
+
//#region src/lib/plugins/html/utils/pipeDeserializeHtmlElement.ts
|
|
1872
|
+
const pipeDeserializeHtmlElement = (editor, element) => {
|
|
1873
|
+
let result;
|
|
1874
|
+
[...editor.meta.pluginList].reverse().some((plugin) => {
|
|
1875
|
+
result = pluginDeserializeHtml(editor, plugin, { element });
|
|
1876
|
+
return !!result;
|
|
1877
|
+
});
|
|
1878
|
+
return result;
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
//#endregion
|
|
1882
|
+
//#region src/lib/plugins/html/utils/htmlElementToElement.ts
|
|
1883
|
+
/** Deserialize HTML to Element. */
|
|
1884
|
+
const htmlElementToElement = (editor, element, isSlate = false) => {
|
|
1885
|
+
const deserialized = pipeDeserializeHtmlElement(editor, element);
|
|
1886
|
+
if (deserialized) {
|
|
1887
|
+
const { node, withoutChildren } = deserialized;
|
|
1888
|
+
let descendants = node.children ?? deserializeHtmlNodeChildren(editor, element, isSlate);
|
|
1889
|
+
if (descendants.length === 0 || withoutChildren || isSlateVoid(element)) descendants = [{ text: "" }];
|
|
1890
|
+
return jsx("element", node, descendants);
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
//#endregion
|
|
1895
|
+
//#region src/lib/plugins/html/utils/pipeDeserializeHtmlLeaf.ts
|
|
1896
|
+
const pipeDeserializeHtmlLeaf = (editor, element) => {
|
|
1897
|
+
let node = {};
|
|
1898
|
+
[...editor.meta.pluginList].reverse().forEach((plugin) => {
|
|
1899
|
+
const deserialized = pluginDeserializeHtml(editor, plugin, {
|
|
1900
|
+
deserializeLeaf: true,
|
|
1901
|
+
element
|
|
1902
|
+
});
|
|
1903
|
+
if (!deserialized) return;
|
|
1904
|
+
node = {
|
|
1905
|
+
...node,
|
|
1906
|
+
...deserialized.node
|
|
1907
|
+
};
|
|
1908
|
+
});
|
|
1909
|
+
return node;
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region src/lib/plugins/html/utils/htmlElementToLeaf.ts
|
|
1914
|
+
/**
|
|
1915
|
+
* Deserialize HTML to Descendant[] with marks on Text. Build the leaf from the
|
|
1916
|
+
* leaf deserializers of each plugin.
|
|
1917
|
+
*/
|
|
1918
|
+
const htmlElementToLeaf = (editor, element) => {
|
|
1919
|
+
const node = pipeDeserializeHtmlLeaf(editor, element);
|
|
1920
|
+
return deserializeHtmlNodeChildren(editor, element).reduce((arr, child) => {
|
|
1921
|
+
if (!child) return arr;
|
|
1922
|
+
if (ElementApi.isElement(child)) {
|
|
1923
|
+
if (Object.keys(node).length > 0) mergeDeepToNodes({
|
|
1924
|
+
node: child,
|
|
1925
|
+
query: { filter: ([n]) => TextApi.isText(n) },
|
|
1926
|
+
source: node
|
|
1927
|
+
});
|
|
1928
|
+
arr.push(child);
|
|
1929
|
+
} else {
|
|
1930
|
+
const attributes = { ...node };
|
|
1931
|
+
if (TextApi.isText(child) && child.text) Object.keys(attributes).forEach((key) => {
|
|
1932
|
+
if (attributes[key] && child[key]) attributes[key] = child[key];
|
|
1933
|
+
});
|
|
1934
|
+
arr.push(jsx("text", attributes, child));
|
|
1935
|
+
}
|
|
1936
|
+
return arr;
|
|
1937
|
+
}, []);
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
//#endregion
|
|
1941
|
+
//#region src/lib/plugins/html/utils/htmlTextNodeToString.ts
|
|
1942
|
+
/** Deserialize HTML text node to text. */
|
|
1943
|
+
const htmlTextNodeToString = (node) => {
|
|
1944
|
+
if (isHtmlText(node)) {
|
|
1945
|
+
if (node.parentElement?.dataset.platePreventDeserialization) return "";
|
|
1946
|
+
return node.textContent || "";
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
//#endregion
|
|
1951
|
+
//#region src/lib/plugins/html/utils/deserializeHtmlNode.ts
|
|
1952
|
+
/** Check if a BR tag should be converted to an empty paragraph. */
|
|
1953
|
+
const shouldBrBecomeEmptyParagraph = (node) => {
|
|
1954
|
+
if (node.nodeName !== "BR") return false;
|
|
1955
|
+
if (node.className === "Apple-interchange-newline") return false;
|
|
1956
|
+
const parent = node.parentElement;
|
|
1957
|
+
if (!parent) return false;
|
|
1958
|
+
if (parent.tagName === "P" || parent.tagName === "SPAN") return false;
|
|
1959
|
+
const hasAdjacentText = () => {
|
|
1960
|
+
let sibling = node.previousSibling;
|
|
1961
|
+
while (sibling) {
|
|
1962
|
+
if (sibling.nodeType === Node.TEXT_NODE && sibling.textContent?.trim()) return true;
|
|
1963
|
+
sibling = sibling.previousSibling;
|
|
1964
|
+
}
|
|
1965
|
+
sibling = node.nextSibling;
|
|
1966
|
+
while (sibling) {
|
|
1967
|
+
if (sibling.nodeType === Node.TEXT_NODE && sibling.textContent?.trim()) return true;
|
|
1968
|
+
sibling = sibling.nextSibling;
|
|
1969
|
+
}
|
|
1970
|
+
return false;
|
|
1971
|
+
};
|
|
1972
|
+
if (hasAdjacentText()) return false;
|
|
1973
|
+
return true;
|
|
1974
|
+
};
|
|
1975
|
+
/** Deserialize HTML element or child node. */
|
|
1976
|
+
const deserializeHtmlNode = (editor) => (node) => {
|
|
1977
|
+
const textNode = htmlTextNodeToString(node);
|
|
1978
|
+
if (textNode) return textNode;
|
|
1979
|
+
if (!isHtmlElement(node)) return null;
|
|
1980
|
+
if (shouldBrBecomeEmptyParagraph(node)) return {
|
|
1981
|
+
children: [{ text: "" }],
|
|
1982
|
+
type: editor.getType("p")
|
|
1983
|
+
};
|
|
1984
|
+
if (node.nodeName === "BR" && node.className === "Apple-interchange-newline") return null;
|
|
1985
|
+
const breakLine = htmlBrToNewLine(node);
|
|
1986
|
+
if (breakLine) return breakLine;
|
|
1987
|
+
const fragment = htmlBodyToFragment(editor, node);
|
|
1988
|
+
if (fragment) return fragment;
|
|
1989
|
+
const element = htmlElementToElement(editor, node, isSlateNode(node));
|
|
1990
|
+
if (element) return element;
|
|
1991
|
+
return htmlElementToLeaf(editor, node);
|
|
1992
|
+
};
|
|
1993
|
+
|
|
1994
|
+
//#endregion
|
|
1995
|
+
//#region src/lib/plugins/html/utils/deserializeHtmlElement.ts
|
|
1996
|
+
/** Deserialize HTML element to fragment. */
|
|
1997
|
+
const deserializeHtmlElement = (editor, element) => deserializeHtmlNode(editor)(element);
|
|
1998
|
+
|
|
1999
|
+
//#endregion
|
|
2000
|
+
//#region src/lib/plugins/html/utils/htmlStringToDOMNode.ts
|
|
2001
|
+
/** Convert HTML string into HTML element. */
|
|
2002
|
+
const htmlStringToDOMNode = (rawHtml) => {
|
|
2003
|
+
const node = document.createElement("body");
|
|
2004
|
+
node.innerHTML = rawHtml;
|
|
2005
|
+
return node;
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
//#endregion
|
|
2009
|
+
//#region src/lib/plugins/html/utils/deserializeHtml.ts
|
|
2010
|
+
/** Deserialize HTML element to a valid document fragment. */
|
|
2011
|
+
const deserializeHtml = (editor, { collapseWhiteSpace: shouldCollapseWhiteSpace = true, defaultElementPlugin, element }) => {
|
|
2012
|
+
if (typeof element === "string") element = htmlStringToDOMNode(element);
|
|
2013
|
+
if (shouldCollapseWhiteSpace) element = collapseWhiteSpace(element);
|
|
2014
|
+
return normalizeDescendantsToDocumentFragment(editor, {
|
|
2015
|
+
defaultElementPlugin,
|
|
2016
|
+
descendants: deserializeHtmlElement(editor, element)
|
|
2017
|
+
});
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
//#endregion
|
|
2021
|
+
//#region src/lib/plugins/html/utils/parseHtmlDocument.ts
|
|
2022
|
+
const parseHtmlDocument = (html) => new DOMParser().parseFromString(html, "text/html");
|
|
2023
|
+
|
|
2024
|
+
//#endregion
|
|
2025
|
+
//#region src/lib/plugins/html/HtmlPlugin.ts
|
|
2026
|
+
/**
|
|
2027
|
+
* Enables support for deserializing inserted content from HTML format to Slate
|
|
2028
|
+
* format and serializing Slate content to HTML format.
|
|
2029
|
+
*/
|
|
2030
|
+
const HtmlPlugin = createSlatePlugin({ key: "html" }).extendApi(({ editor }) => ({ deserialize: bindFirst(deserializeHtml, editor) })).extend({ parser: {
|
|
2031
|
+
format: "text/html",
|
|
2032
|
+
deserialize: ({ api, data }) => {
|
|
2033
|
+
const document$1 = parseHtmlDocument(data);
|
|
2034
|
+
return api.html.deserialize({ element: document$1.body });
|
|
2035
|
+
}
|
|
2036
|
+
} });
|
|
2037
|
+
|
|
2038
|
+
//#endregion
|
|
2039
|
+
//#region src/lib/plugins/length/LengthPlugin.ts
|
|
2040
|
+
const LengthPlugin = createTSlatePlugin({ key: "length" }).overrideEditor(({ editor, getOptions, tf: { apply } }) => ({ transforms: { apply(operation) {
|
|
2041
|
+
editor.tf.withoutNormalizing(() => {
|
|
2042
|
+
apply(operation);
|
|
2043
|
+
const options = getOptions();
|
|
2044
|
+
if (options.maxLength) {
|
|
2045
|
+
const length = editor.api.string([]).length;
|
|
2046
|
+
if (length > options.maxLength) {
|
|
2047
|
+
const overflowLength = length - options.maxLength;
|
|
2048
|
+
editor.tf.delete({
|
|
2049
|
+
distance: overflowLength,
|
|
2050
|
+
reverse: true,
|
|
2051
|
+
unit: "character"
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
} } }));
|
|
2057
|
+
|
|
2058
|
+
//#endregion
|
|
2059
|
+
//#region src/lib/plugins/node-id/withNodeId.ts
|
|
2060
|
+
/** Enables support for inserting nodes with an id key. */
|
|
2061
|
+
const withNodeId = ({ editor, getOptions, tf: { apply, insertNode, insertNodes } }) => {
|
|
2062
|
+
const idPropsCreator = () => ({ [getOptions().idKey ?? ""]: getOptions().idCreator() });
|
|
2063
|
+
const filterNode = (nodeEntry) => {
|
|
2064
|
+
const { filter, filterText } = getOptions();
|
|
2065
|
+
return filter(nodeEntry) && (!filterText || nodeEntry[0]?.type !== void 0);
|
|
2066
|
+
};
|
|
2067
|
+
const removeIdFromNodeIfDuplicate = (node) => {
|
|
2068
|
+
const { idKey = "", reuseId } = getOptions();
|
|
2069
|
+
if (!reuseId && editor.api.some({
|
|
2070
|
+
at: [],
|
|
2071
|
+
match: { [idKey]: node[idKey] }
|
|
2072
|
+
})) delete node[idKey];
|
|
2073
|
+
};
|
|
2074
|
+
const overrideIdIfSet = (node) => {
|
|
2075
|
+
const { idKey = "" } = getOptions();
|
|
2076
|
+
if (isDefined(node._id)) {
|
|
2077
|
+
const id = node._id;
|
|
2078
|
+
node._id = void 0;
|
|
2079
|
+
if (!editor.api.some({
|
|
2080
|
+
at: [],
|
|
2081
|
+
match: { [idKey]: id }
|
|
2082
|
+
})) node[idKey] = id;
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
return { transforms: {
|
|
2086
|
+
apply(operation) {
|
|
2087
|
+
const { allow, disableInsertOverrides, exclude, idCreator, idKey = "", reuseId } = getOptions();
|
|
2088
|
+
const query = {
|
|
2089
|
+
allow,
|
|
2090
|
+
exclude,
|
|
2091
|
+
filter: filterNode
|
|
2092
|
+
};
|
|
2093
|
+
if (operation.type === "insert_node") {
|
|
2094
|
+
const node = cloneDeep(operation.node);
|
|
2095
|
+
applyDeepToNodes({
|
|
2096
|
+
apply: removeIdFromNodeIfDuplicate,
|
|
2097
|
+
node,
|
|
2098
|
+
query,
|
|
2099
|
+
source: {}
|
|
2100
|
+
});
|
|
2101
|
+
defaultsDeepToNodes({
|
|
2102
|
+
node,
|
|
2103
|
+
path: operation.path,
|
|
2104
|
+
query,
|
|
2105
|
+
source: idPropsCreator
|
|
2106
|
+
});
|
|
2107
|
+
if (!disableInsertOverrides) applyDeepToNodes({
|
|
2108
|
+
apply: overrideIdIfSet,
|
|
2109
|
+
node,
|
|
2110
|
+
query,
|
|
2111
|
+
source: {}
|
|
2112
|
+
});
|
|
2113
|
+
return apply({
|
|
2114
|
+
...operation,
|
|
2115
|
+
node
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
if (operation.type === "split_node") {
|
|
2119
|
+
const node = operation.properties;
|
|
2120
|
+
let id = operation.properties[idKey];
|
|
2121
|
+
if (queryNode([node, operation.path], query)) {
|
|
2122
|
+
/**
|
|
2123
|
+
* Create a new id if:
|
|
2124
|
+
*
|
|
2125
|
+
* - The id in the new node is already being used in the editor or,
|
|
2126
|
+
* - The node has no id
|
|
2127
|
+
*/
|
|
2128
|
+
if (!reuseId || id === void 0 || editor.api.some({
|
|
2129
|
+
at: [],
|
|
2130
|
+
match: { [idKey]: id }
|
|
2131
|
+
})) id = idCreator();
|
|
2132
|
+
return apply({
|
|
2133
|
+
...operation,
|
|
2134
|
+
properties: {
|
|
2135
|
+
...operation.properties,
|
|
2136
|
+
[idKey]: id
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
if (id) delete operation.properties[idKey];
|
|
2141
|
+
}
|
|
2142
|
+
return apply(operation);
|
|
2143
|
+
},
|
|
2144
|
+
insertNode(node) {
|
|
2145
|
+
const { disableInsertOverrides, idKey = "" } = getOptions();
|
|
2146
|
+
if (!disableInsertOverrides && node[idKey]) {
|
|
2147
|
+
if (!Object.isExtensible(node)) node = cloneDeep(node);
|
|
2148
|
+
node._id = node[idKey];
|
|
2149
|
+
}
|
|
2150
|
+
insertNode(node);
|
|
2151
|
+
},
|
|
2152
|
+
insertNodes(_nodes, options) {
|
|
2153
|
+
const nodes = castArray(_nodes).filter((node) => !!node);
|
|
2154
|
+
if (nodes.length === 0) return;
|
|
2155
|
+
const { disableInsertOverrides, idKey = "" } = getOptions();
|
|
2156
|
+
insertNodes(nodes.map((node) => {
|
|
2157
|
+
if (!disableInsertOverrides && node[idKey]) {
|
|
2158
|
+
if (!Object.isExtensible(node)) node = cloneDeep(node);
|
|
2159
|
+
node._id = node[idKey];
|
|
2160
|
+
}
|
|
2161
|
+
return node;
|
|
2162
|
+
}), options);
|
|
2163
|
+
}
|
|
2164
|
+
} };
|
|
2165
|
+
};
|
|
2166
|
+
|
|
2167
|
+
//#endregion
|
|
2168
|
+
//#region src/lib/plugins/node-id/NodeIdPlugin.ts
|
|
2169
|
+
/**
|
|
2170
|
+
* Normalize node IDs in a value without using editor operations. This is a pure
|
|
2171
|
+
* function that returns a new normalized value.
|
|
2172
|
+
*/
|
|
2173
|
+
const normalizeNodeId = (value, options = {}) => {
|
|
2174
|
+
const { allow, exclude, filter = () => true, filterInline = true, filterText = true, idCreator = () => nanoid(10), idKey = "id" } = options;
|
|
2175
|
+
const normalizeNode = (node, path) => {
|
|
2176
|
+
const clonedNode = { ...node };
|
|
2177
|
+
if (!clonedNode[idKey] && queryNode([clonedNode, path], {
|
|
2178
|
+
allow,
|
|
2179
|
+
exclude,
|
|
2180
|
+
filter: (entry) => {
|
|
2181
|
+
const [node$1] = entry;
|
|
2182
|
+
if (filterText && !ElementApi.isElement(node$1)) return false;
|
|
2183
|
+
if (filterInline && ElementApi.isElement(node$1) && node$1.inline === true) return false;
|
|
2184
|
+
return filter(entry);
|
|
2185
|
+
}
|
|
2186
|
+
})) clonedNode[idKey] = idCreator();
|
|
2187
|
+
if (ElementApi.isElement(clonedNode)) clonedNode.children = clonedNode.children.map((child, index) => normalizeNode(child, [...path, index]));
|
|
2188
|
+
return clonedNode;
|
|
2189
|
+
};
|
|
2190
|
+
return value.map((node, index) => normalizeNode(node, [index]));
|
|
2191
|
+
};
|
|
2192
|
+
/** @see {@link withNodeId} */
|
|
2193
|
+
const NodeIdPlugin = createTSlatePlugin({
|
|
2194
|
+
key: "nodeId",
|
|
2195
|
+
options: {
|
|
2196
|
+
filterInline: true,
|
|
2197
|
+
filterText: true,
|
|
2198
|
+
idKey: "id",
|
|
2199
|
+
normalizeInitialValue: false,
|
|
2200
|
+
filter: () => true,
|
|
2201
|
+
idCreator: () => nanoid(10)
|
|
2202
|
+
}
|
|
2203
|
+
}).extendTransforms(({ editor, getOptions }) => ({ normalize() {
|
|
2204
|
+
const { allow, exclude, filter, filterInline, filterText, idKey } = getOptions();
|
|
2205
|
+
const addNodeId = (entry) => {
|
|
2206
|
+
const [node, path] = entry;
|
|
2207
|
+
if (!node[idKey] && queryNode([node, path], {
|
|
2208
|
+
allow,
|
|
2209
|
+
exclude,
|
|
2210
|
+
filter: (entry$1) => {
|
|
2211
|
+
const [node$1] = entry$1;
|
|
2212
|
+
if (filterText && !ElementApi.isElement(node$1)) return false;
|
|
2213
|
+
if (filterInline && ElementApi.isElement(node$1) && !editor.api.isBlock(node$1)) return false;
|
|
2214
|
+
return filter(entry$1);
|
|
2215
|
+
}
|
|
2216
|
+
})) {
|
|
2217
|
+
if (!editor.api.node(path)) return;
|
|
2218
|
+
editor.tf.withoutSaving(() => {
|
|
2219
|
+
editor.tf.setNodes({ [idKey]: getOptions().idCreator() }, { at: path });
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
if (ElementApi.isElement(node)) node.children.forEach((child, index) => {
|
|
2223
|
+
addNodeId([child, [...path, index]]);
|
|
2224
|
+
});
|
|
2225
|
+
};
|
|
2226
|
+
editor.children.forEach((node, index) => {
|
|
2227
|
+
addNodeId([node, [index]]);
|
|
2228
|
+
});
|
|
2229
|
+
} })).extend({ normalizeInitialValue: ({ editor, getOptions, tf }) => {
|
|
2230
|
+
const { normalizeInitialValue } = getOptions();
|
|
2231
|
+
if (!normalizeInitialValue) {
|
|
2232
|
+
const firstNode = editor.children[0];
|
|
2233
|
+
const lastNode = editor.children.at(-1);
|
|
2234
|
+
if (firstNode?.id && lastNode?.id) return;
|
|
2235
|
+
}
|
|
2236
|
+
tf.nodeId.normalize();
|
|
2237
|
+
} }).overrideEditor(withNodeId);
|
|
2238
|
+
|
|
2239
|
+
//#endregion
|
|
2240
|
+
//#region src/lib/utils/pipeOnNodeChange.ts
|
|
2241
|
+
const pipeOnNodeChange = (editor, node, prevNode, operation) => {
|
|
2242
|
+
return editor.meta.pluginCache.handlers.onNodeChange.some((key) => {
|
|
2243
|
+
const plugin = editor.getPlugin({ key });
|
|
2244
|
+
if (!plugin || editor.dom?.readOnly) return false;
|
|
2245
|
+
const handler = plugin.handlers?.onNodeChange;
|
|
2246
|
+
if (!handler) return false;
|
|
2247
|
+
const shouldTreatEventAsHandled = handler({
|
|
2248
|
+
editor,
|
|
2249
|
+
node,
|
|
2250
|
+
operation,
|
|
2251
|
+
plugin,
|
|
2252
|
+
prevNode
|
|
2253
|
+
});
|
|
2254
|
+
if (shouldTreatEventAsHandled != null) return shouldTreatEventAsHandled;
|
|
2255
|
+
return false;
|
|
2256
|
+
});
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
//#endregion
|
|
2260
|
+
//#region src/lib/utils/pipeOnTextChange.ts
|
|
2261
|
+
const pipeOnTextChange = (editor, node, text, prevText, operation) => {
|
|
2262
|
+
return editor.meta.pluginCache.handlers.onTextChange.some((key) => {
|
|
2263
|
+
const plugin = editor.getPlugin({ key });
|
|
2264
|
+
if (!plugin || editor.dom?.readOnly) return false;
|
|
2265
|
+
const handler = plugin.handlers?.onTextChange;
|
|
2266
|
+
if (!handler) return false;
|
|
2267
|
+
const shouldTreatEventAsHandled = handler({
|
|
2268
|
+
editor,
|
|
2269
|
+
node,
|
|
2270
|
+
operation,
|
|
2271
|
+
plugin,
|
|
2272
|
+
prevText,
|
|
2273
|
+
text
|
|
2274
|
+
});
|
|
2275
|
+
if (shouldTreatEventAsHandled != null) return shouldTreatEventAsHandled;
|
|
2276
|
+
return false;
|
|
2277
|
+
});
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
//#endregion
|
|
2281
|
+
//#region src/internal/plugin/isEditOnlyDisabled.ts
|
|
2282
|
+
const DEFAULT = {
|
|
2283
|
+
handlers: true,
|
|
2284
|
+
inject: true,
|
|
2285
|
+
normalizeInitialValue: false,
|
|
2286
|
+
render: true
|
|
2287
|
+
};
|
|
2288
|
+
/**
|
|
2289
|
+
* Check if a plugin feature is disabled in read-only mode based on editOnly
|
|
2290
|
+
* configuration.
|
|
2291
|
+
*
|
|
2292
|
+
* @param plugin The plugin to check
|
|
2293
|
+
* @param isReadOnly Whether the editor is in read-only mode
|
|
2294
|
+
* @param feature The feature to check ('render' | 'handlers' | 'inject' |
|
|
2295
|
+
* 'normalizeInitialValue')
|
|
2296
|
+
* @returns True if the feature should be disabled
|
|
2297
|
+
*/
|
|
2298
|
+
const isEditOnly = (readOnly, plugin, feature) => {
|
|
2299
|
+
if (!readOnly) return false;
|
|
2300
|
+
if (plugin.editOnly === true) return DEFAULT[feature];
|
|
2301
|
+
if (typeof plugin.editOnly === "object") return plugin.editOnly[feature] ?? DEFAULT[feature];
|
|
2302
|
+
return false;
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
//#endregion
|
|
2306
|
+
//#region src/internal/plugin/pipeNormalizeInitialValue.ts
|
|
2307
|
+
/** Normalize initial value from editor plugins. Set into plate store if diff. */
|
|
2308
|
+
const pipeNormalizeInitialValue = (editor) => {
|
|
2309
|
+
const value = editor.meta.isNormalizing;
|
|
2310
|
+
editor.meta.isNormalizing = true;
|
|
2311
|
+
editor.meta.pluginCache.normalizeInitialValue.forEach((key) => {
|
|
2312
|
+
const p = editor.getPlugin({ key });
|
|
2313
|
+
if (isEditOnly(editor.dom.readOnly, p, "normalizeInitialValue")) return;
|
|
2314
|
+
p.normalizeInitialValue?.({
|
|
2315
|
+
...getEditorPlugin(editor, p),
|
|
2316
|
+
value: editor.children
|
|
2317
|
+
});
|
|
2318
|
+
});
|
|
2319
|
+
editor.meta.isNormalizing = value;
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
//#endregion
|
|
2323
|
+
//#region src/lib/plugins/slate-extension/transforms/init.ts
|
|
2324
|
+
const init = (editor, { autoSelect, selection, shouldNormalizeEditor, value, onReady }) => {
|
|
2325
|
+
const onValueLoaded = (isAsync = false) => {
|
|
2326
|
+
if (!editor.children || editor.children?.length === 0) editor.children = editor.api.create.value();
|
|
2327
|
+
if (selection) editor.selection = selection;
|
|
2328
|
+
else if (autoSelect) {
|
|
2329
|
+
const target = (autoSelect === "start" ? "start" : "end") === "start" ? editor.api.start([]) : editor.api.end([]);
|
|
2330
|
+
editor.tf.select(target);
|
|
2331
|
+
}
|
|
2332
|
+
if (editor.children.length > 0) pipeNormalizeInitialValue(editor);
|
|
2333
|
+
if (shouldNormalizeEditor) editor.tf.normalize({ force: true });
|
|
2334
|
+
if (onReady) onReady({
|
|
2335
|
+
editor,
|
|
2336
|
+
isAsync,
|
|
2337
|
+
value: editor.children
|
|
2338
|
+
});
|
|
2339
|
+
};
|
|
2340
|
+
if (value === null) onValueLoaded();
|
|
2341
|
+
else if (typeof value === "string") {
|
|
2342
|
+
editor.children = editor.api.html.deserialize({ element: value });
|
|
2343
|
+
onValueLoaded();
|
|
2344
|
+
} else if (typeof value === "function") {
|
|
2345
|
+
const result = value(editor);
|
|
2346
|
+
if (result && typeof result.then === "function") result.then((resolvedValue) => {
|
|
2347
|
+
editor.children = resolvedValue;
|
|
2348
|
+
onValueLoaded(true);
|
|
2349
|
+
});
|
|
2350
|
+
else {
|
|
2351
|
+
editor.children = result;
|
|
2352
|
+
onValueLoaded();
|
|
2353
|
+
}
|
|
2354
|
+
} else if (value) {
|
|
2355
|
+
editor.children = value;
|
|
2356
|
+
onValueLoaded();
|
|
2357
|
+
} else onValueLoaded();
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
//#endregion
|
|
2361
|
+
//#region src/lib/plugins/slate-extension/transforms/insertExitBreak.ts
|
|
2362
|
+
/**
|
|
2363
|
+
* Exits the current block structure by creating a new block next to the
|
|
2364
|
+
* appropriate ancestor.
|
|
2365
|
+
*
|
|
2366
|
+
* This function automatically determines the exit point by finding the first
|
|
2367
|
+
* ancestor that doesn't have strict sibling constraints (`isStrictSiblings:
|
|
2368
|
+
* false`), allowing standard text blocks to be inserted as siblings.
|
|
2369
|
+
*
|
|
2370
|
+
* For example:
|
|
2371
|
+
*
|
|
2372
|
+
* - In `column_group > column > codeblock > codeline`, exits after `codeblock`,
|
|
2373
|
+
* then after `column_group`
|
|
2374
|
+
* - In `table > tr > td > p`, exits after `table`
|
|
2375
|
+
*/
|
|
2376
|
+
const insertExitBreak = (editor, { match, reverse } = {}) => {
|
|
2377
|
+
if (!editor.selection || !editor.api.isCollapsed()) return;
|
|
2378
|
+
const block = editor.api.block();
|
|
2379
|
+
if (!block) return;
|
|
2380
|
+
const ancestorPath = editor.api.above({
|
|
2381
|
+
at: block[1],
|
|
2382
|
+
match: combineMatchOptions(editor, (n, p) => p.length === 1 || p.length > 1 && !!n.type && !getPluginByType(editor, n.type)?.node.isStrictSiblings, { match })
|
|
2383
|
+
})?.[1] ?? block[1];
|
|
2384
|
+
const targetPath = reverse ? ancestorPath : PathApi.next(ancestorPath);
|
|
2385
|
+
if (!targetPath) return;
|
|
2386
|
+
editor.tf.insertNodes(editor.api.create.block(), {
|
|
2387
|
+
at: targetPath,
|
|
2388
|
+
select: true
|
|
2389
|
+
});
|
|
2390
|
+
return true;
|
|
2391
|
+
};
|
|
2392
|
+
|
|
2393
|
+
//#endregion
|
|
2394
|
+
//#region src/lib/plugins/slate-extension/transforms/resetBlock.ts
|
|
2395
|
+
/**
|
|
2396
|
+
* Reset the current block to a paragraph, removing all properties except id and
|
|
2397
|
+
* type.
|
|
2398
|
+
*/
|
|
2399
|
+
const resetBlock = (editor, { at } = {}) => {
|
|
2400
|
+
const entry = editor.api.block({ at });
|
|
2401
|
+
if (!entry?.[0]) return;
|
|
2402
|
+
const [block, path] = entry;
|
|
2403
|
+
editor.tf.withoutNormalizing(() => {
|
|
2404
|
+
const { id, type, ...otherProps } = NodeApi.extractProps(block);
|
|
2405
|
+
Object.keys(otherProps).forEach((key) => {
|
|
2406
|
+
editor.tf.unsetNodes(key, { at: path });
|
|
2407
|
+
});
|
|
2408
|
+
const paragraphType = editor.getType(BaseParagraphPlugin.key);
|
|
2409
|
+
if (block.type !== paragraphType) editor.tf.setNodes({ type: paragraphType }, { at: path });
|
|
2410
|
+
});
|
|
2411
|
+
return true;
|
|
2412
|
+
};
|
|
2413
|
+
|
|
2414
|
+
//#endregion
|
|
2415
|
+
//#region src/lib/plugins/slate-extension/transforms/setValue.ts
|
|
2416
|
+
const setValue = (editor, value) => {
|
|
2417
|
+
let children = value;
|
|
2418
|
+
if (typeof value === "string") children = editor.api.html.deserialize({ element: value });
|
|
2419
|
+
else if (!value || value.length === 0) children = editor.api.create.value();
|
|
2420
|
+
editor.tf.replaceNodes(children, {
|
|
2421
|
+
at: [],
|
|
2422
|
+
children: true
|
|
2423
|
+
});
|
|
2424
|
+
};
|
|
2425
|
+
|
|
2426
|
+
//#endregion
|
|
2427
|
+
//#region src/lib/plugins/slate-extension/SlateExtensionPlugin.ts
|
|
2428
|
+
/** Opinionated extension of slate default behavior. */
|
|
2429
|
+
const SlateExtensionPlugin = createTSlatePlugin({
|
|
2430
|
+
key: "slateExtension",
|
|
2431
|
+
options: {
|
|
2432
|
+
onNodeChange: () => {},
|
|
2433
|
+
onTextChange: () => {}
|
|
2434
|
+
}
|
|
2435
|
+
}).extendEditorTransforms(({ editor, getOption, tf: { apply } }) => ({
|
|
2436
|
+
init: bindFirst(init, editor),
|
|
2437
|
+
insertExitBreak: bindFirst(insertExitBreak, editor),
|
|
2438
|
+
resetBlock: bindFirst(resetBlock, editor),
|
|
2439
|
+
setValue: bindFirst(setValue, editor),
|
|
2440
|
+
apply(operation) {
|
|
2441
|
+
const noop = () => {};
|
|
2442
|
+
const hasNodeHandlers = editor.meta.pluginCache.handlers.onNodeChange.length > 0 || getOption("onNodeChange") !== noop;
|
|
2443
|
+
const hasTextHandlers = editor.meta.pluginCache.handlers.onTextChange.length > 0 || getOption("onTextChange") !== noop;
|
|
2444
|
+
if (!hasNodeHandlers && !hasTextHandlers) {
|
|
2445
|
+
apply(operation);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
let prevNode;
|
|
2449
|
+
let node;
|
|
2450
|
+
let prevText;
|
|
2451
|
+
let text;
|
|
2452
|
+
let parentNode;
|
|
2453
|
+
if (OperationApi.isNodeOperation(operation) && hasNodeHandlers) switch (operation.type) {
|
|
2454
|
+
case "insert_node":
|
|
2455
|
+
prevNode = operation.node;
|
|
2456
|
+
node = operation.node;
|
|
2457
|
+
break;
|
|
2458
|
+
case "merge_node":
|
|
2459
|
+
case "move_node":
|
|
2460
|
+
case "set_node":
|
|
2461
|
+
case "split_node":
|
|
2462
|
+
prevNode = NodeApi.get(editor, operation.path);
|
|
2463
|
+
break;
|
|
2464
|
+
case "remove_node":
|
|
2465
|
+
prevNode = operation.node;
|
|
2466
|
+
node = operation.node;
|
|
2467
|
+
break;
|
|
2468
|
+
}
|
|
2469
|
+
else if (OperationApi.isTextOperation(operation) && hasTextHandlers) {
|
|
2470
|
+
const parentPath = PathApi.parent(operation.path);
|
|
2471
|
+
parentNode = NodeApi.get(editor, parentPath);
|
|
2472
|
+
prevText = NodeApi.get(editor, operation.path).text;
|
|
2473
|
+
}
|
|
2474
|
+
apply(operation);
|
|
2475
|
+
if (OperationApi.isNodeOperation(operation) && hasNodeHandlers) {
|
|
2476
|
+
switch (operation.type) {
|
|
2477
|
+
case "insert_node":
|
|
2478
|
+
case "remove_node": break;
|
|
2479
|
+
case "merge_node": {
|
|
2480
|
+
const prevPath = PathApi.previous(operation.path);
|
|
2481
|
+
if (prevPath) node = NodeApi.get(editor, prevPath);
|
|
2482
|
+
break;
|
|
2483
|
+
}
|
|
2484
|
+
case "move_node":
|
|
2485
|
+
node = NodeApi.get(editor, operation.newPath);
|
|
2486
|
+
break;
|
|
2487
|
+
case "set_node":
|
|
2488
|
+
node = NodeApi.get(editor, operation.path);
|
|
2489
|
+
break;
|
|
2490
|
+
case "split_node":
|
|
2491
|
+
node = NodeApi.get(editor, operation.path);
|
|
2492
|
+
break;
|
|
2493
|
+
}
|
|
2494
|
+
if (!node) node = prevNode;
|
|
2495
|
+
if (!pipeOnNodeChange(editor, node, prevNode, operation)) getOption("onNodeChange")({
|
|
2496
|
+
editor,
|
|
2497
|
+
node,
|
|
2498
|
+
operation,
|
|
2499
|
+
prevNode
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
if (OperationApi.isTextOperation(operation) && hasTextHandlers) {
|
|
2503
|
+
const textNodeAfter = NodeApi.get(editor, operation.path);
|
|
2504
|
+
if (textNodeAfter) text = textNodeAfter.text;
|
|
2505
|
+
if (!pipeOnTextChange(editor, parentNode, text, prevText, operation)) getOption("onTextChange")({
|
|
2506
|
+
editor,
|
|
2507
|
+
node: parentNode,
|
|
2508
|
+
operation,
|
|
2509
|
+
prevText,
|
|
2510
|
+
text
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}));
|
|
2515
|
+
|
|
2516
|
+
//#endregion
|
|
2517
|
+
//#region src/lib/utils/normalizeDescendantsToDocumentFragment.ts
|
|
2518
|
+
const isInlineNode = (editor) => (node) => TextApi.isText(node) || ElementApi.isElement(node) && editor.api.isInline(node);
|
|
2519
|
+
const makeBlockLazy = (type) => () => ({
|
|
2520
|
+
children: [],
|
|
2521
|
+
type
|
|
2522
|
+
});
|
|
2523
|
+
const hasDifferentChildNodes = (descendants, isInline) => descendants.some((descendant, index, arr) => {
|
|
2524
|
+
const prevDescendant = arr[index - 1];
|
|
2525
|
+
if (index !== 0) return isInline(descendant) !== isInline(prevDescendant);
|
|
2526
|
+
return false;
|
|
2527
|
+
});
|
|
2528
|
+
/**
|
|
2529
|
+
* Handles 3rd constraint: "Block nodes can only contain other blocks, or inline
|
|
2530
|
+
* and text nodes."
|
|
2531
|
+
*/
|
|
2532
|
+
const normalizeDifferentNodeTypes = (descendants, isInline, makeDefaultBlock) => {
|
|
2533
|
+
const hasDifferentNodes = hasDifferentChildNodes(descendants, isInline);
|
|
2534
|
+
const { fragment } = descendants.reduce((memo, node) => {
|
|
2535
|
+
if (hasDifferentNodes && isInline(node)) {
|
|
2536
|
+
let block = memo.precedingBlock;
|
|
2537
|
+
if (!block) {
|
|
2538
|
+
block = makeDefaultBlock();
|
|
2539
|
+
memo.precedingBlock = block;
|
|
2540
|
+
memo.fragment.push(block);
|
|
2541
|
+
}
|
|
2542
|
+
block.children.push(node);
|
|
2543
|
+
} else {
|
|
2544
|
+
memo.fragment.push(node);
|
|
2545
|
+
memo.precedingBlock = null;
|
|
2546
|
+
}
|
|
2547
|
+
return memo;
|
|
2548
|
+
}, {
|
|
2549
|
+
fragment: [],
|
|
2550
|
+
precedingBlock: null
|
|
2551
|
+
});
|
|
2552
|
+
return fragment;
|
|
2553
|
+
};
|
|
2554
|
+
/**
|
|
2555
|
+
* Handles 1st constraint: "All Element nodes must contain at least one Text
|
|
2556
|
+
* descendant."
|
|
2557
|
+
*/
|
|
2558
|
+
const normalizeEmptyChildren = (descendants) => {
|
|
2559
|
+
if (descendants.length === 0) return [{ text: "" }];
|
|
2560
|
+
return descendants;
|
|
2561
|
+
};
|
|
2562
|
+
const normalize = (descendants, isInline, makeDefaultBlock) => {
|
|
2563
|
+
descendants = normalizeEmptyChildren(descendants);
|
|
2564
|
+
descendants = normalizeDifferentNodeTypes(descendants, isInline, makeDefaultBlock);
|
|
2565
|
+
descendants = descendants.map((node) => {
|
|
2566
|
+
if (ElementApi.isElement(node)) return {
|
|
2567
|
+
...node,
|
|
2568
|
+
children: normalize(node.children, isInline, makeDefaultBlock)
|
|
2569
|
+
};
|
|
2570
|
+
return node;
|
|
2571
|
+
});
|
|
2572
|
+
return descendants;
|
|
2573
|
+
};
|
|
2574
|
+
/** Normalize the descendants to a valid document fragment. */
|
|
2575
|
+
const normalizeDescendantsToDocumentFragment = (editor, { defaultElementPlugin = BaseParagraphPlugin, descendants }) => {
|
|
2576
|
+
return normalize(descendants, isInlineNode(editor), makeBlockLazy(editor.getType(defaultElementPlugin.key)));
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2579
|
+
//#endregion
|
|
2580
|
+
//#region src/lib/utils/pipeInsertDataQuery.ts
|
|
2581
|
+
/** Is the plugin disabled by another plugin. */
|
|
2582
|
+
const pipeInsertDataQuery = (editor, plugins, options) => plugins.every((p) => {
|
|
2583
|
+
const query = p.parser?.query;
|
|
2584
|
+
return !query || query({
|
|
2585
|
+
...getEditorPlugin(editor, p),
|
|
2586
|
+
...options
|
|
2587
|
+
});
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2590
|
+
//#endregion
|
|
2591
|
+
//#region src/lib/plugins/ParserPlugin.ts
|
|
2592
|
+
const ParserPlugin = createSlatePlugin({ key: "parser" }).overrideEditor(({ editor, tf: { insertData } }) => ({ transforms: { insertData(dataTransfer) {
|
|
2593
|
+
if ([...editor.meta.pluginList].reverse().some((plugin) => {
|
|
2594
|
+
const parser = plugin.parser;
|
|
2595
|
+
if (!parser) return false;
|
|
2596
|
+
const injectedPlugins = getInjectedPlugins(editor, plugin);
|
|
2597
|
+
const { deserialize, format, mimeTypes } = parser;
|
|
2598
|
+
if (!format && !mimeTypes) return false;
|
|
2599
|
+
const mimeTypeList = mimeTypes || (Array.isArray(format) ? format : format ? [format] : []).map((fmt) => fmt.includes("/") ? fmt : `text/${fmt}`);
|
|
2600
|
+
for (const mimeType of mimeTypeList) {
|
|
2601
|
+
let data = dataTransfer.getData(mimeType);
|
|
2602
|
+
if (mimeType !== "Files" && !data || mimeType === "Files" && dataTransfer.files.length === 0) continue;
|
|
2603
|
+
if (!pipeInsertDataQuery(editor, injectedPlugins, {
|
|
2604
|
+
data,
|
|
2605
|
+
dataTransfer,
|
|
2606
|
+
mimeType
|
|
2607
|
+
})) continue;
|
|
2608
|
+
data = pipeTransformData(editor, injectedPlugins, {
|
|
2609
|
+
data,
|
|
2610
|
+
dataTransfer,
|
|
2611
|
+
mimeType
|
|
2612
|
+
});
|
|
2613
|
+
let fragment = deserialize?.({
|
|
2614
|
+
...getEditorPlugin(editor, plugin),
|
|
2615
|
+
data,
|
|
2616
|
+
dataTransfer,
|
|
2617
|
+
mimeType
|
|
2618
|
+
});
|
|
2619
|
+
if (!fragment?.length) continue;
|
|
2620
|
+
fragment = pipeTransformFragment(editor, injectedPlugins, {
|
|
2621
|
+
data,
|
|
2622
|
+
dataTransfer,
|
|
2623
|
+
fragment,
|
|
2624
|
+
mimeType
|
|
2625
|
+
});
|
|
2626
|
+
if (fragment.length === 0) continue;
|
|
2627
|
+
pipeInsertFragment(editor, injectedPlugins, {
|
|
2628
|
+
data,
|
|
2629
|
+
dataTransfer,
|
|
2630
|
+
fragment,
|
|
2631
|
+
mimeType
|
|
2632
|
+
});
|
|
2633
|
+
return true;
|
|
2634
|
+
}
|
|
2635
|
+
return false;
|
|
2636
|
+
})) return;
|
|
2637
|
+
insertData(dataTransfer);
|
|
2638
|
+
} } }));
|
|
2639
|
+
|
|
2640
|
+
//#endregion
|
|
2641
|
+
//#region src/lib/plugins/getCorePlugins.ts
|
|
2642
|
+
const getCorePlugins = ({ affinity, chunking, maxLength, nodeId, plugins = [] }) => {
|
|
2643
|
+
let resolvedNodeId = nodeId;
|
|
2644
|
+
if (process.env.NODE_ENV === "test" && nodeId === void 0) resolvedNodeId = false;
|
|
2645
|
+
let corePlugins = [
|
|
2646
|
+
DebugPlugin,
|
|
2647
|
+
SlateExtensionPlugin,
|
|
2648
|
+
DOMPlugin,
|
|
2649
|
+
HistoryPlugin,
|
|
2650
|
+
OverridePlugin,
|
|
2651
|
+
ParserPlugin,
|
|
2652
|
+
maxLength ? LengthPlugin.configure({ options: { maxLength } }) : LengthPlugin,
|
|
2653
|
+
HtmlPlugin,
|
|
2654
|
+
AstPlugin,
|
|
2655
|
+
NodeIdPlugin.configure({
|
|
2656
|
+
enabled: resolvedNodeId !== false,
|
|
2657
|
+
options: resolvedNodeId === false ? void 0 : resolvedNodeId
|
|
2658
|
+
}),
|
|
2659
|
+
AffinityPlugin.configure({ enabled: affinity }),
|
|
2660
|
+
BaseParagraphPlugin,
|
|
2661
|
+
ChunkingPlugin.configure({
|
|
2662
|
+
enabled: chunking !== false,
|
|
2663
|
+
options: typeof chunking === "boolean" ? void 0 : chunking
|
|
2664
|
+
})
|
|
2665
|
+
];
|
|
2666
|
+
const customPluginsMap = new Map(plugins.map((plugin) => [plugin.key, plugin]));
|
|
2667
|
+
corePlugins = corePlugins.map((corePlugin) => {
|
|
2668
|
+
const customPlugin = customPluginsMap.get(corePlugin.key);
|
|
2669
|
+
if (customPlugin) {
|
|
2670
|
+
const index = plugins.findIndex((p) => p.key === corePlugin.key);
|
|
2671
|
+
if (index !== -1) plugins.splice(index, 1);
|
|
2672
|
+
return customPlugin;
|
|
2673
|
+
}
|
|
2674
|
+
return corePlugin;
|
|
2675
|
+
});
|
|
2676
|
+
return corePlugins;
|
|
2677
|
+
};
|
|
2678
|
+
|
|
2679
|
+
//#endregion
|
|
2680
|
+
//#region src/lib/editor/withSlate.ts
|
|
2681
|
+
/**
|
|
2682
|
+
* Applies Plate enhancements to an editor instance (non-React version).
|
|
2683
|
+
*
|
|
2684
|
+
* @remarks
|
|
2685
|
+
* This function supports server-side usage as it doesn't include React-specific
|
|
2686
|
+
* features like component rendering or hooks integration.
|
|
2687
|
+
* @see {@link createSlateEditor} for a higher-level non-React editor creation function.
|
|
2688
|
+
* @see {@link createPlateEditor} for a React-specific version of editor creation.
|
|
2689
|
+
* @see {@link usePlateEditor} for a memoized React version.
|
|
2690
|
+
* @see {@link withPlate} for the React-specific enhancement function.
|
|
2691
|
+
*/
|
|
2692
|
+
const withSlate = (e, { id, affinity = true, autoSelect, chunking = true, maxLength, nodeId, optionsStoreFactory, plugins = [], readOnly = false, rootPlugin, selection, shouldNormalizeEditor, skipInitialization, userId, value, onReady, ...pluginConfig } = {}) => {
|
|
2693
|
+
const editor = e;
|
|
2694
|
+
editor.id = id ?? editor.id ?? nanoid();
|
|
2695
|
+
editor.meta.key = editor.meta.key ?? nanoid();
|
|
2696
|
+
editor.meta.isFallback = false;
|
|
2697
|
+
editor.meta.userId = userId;
|
|
2698
|
+
editor.dom = {
|
|
2699
|
+
composing: false,
|
|
2700
|
+
currentKeyboardEvent: null,
|
|
2701
|
+
focused: false,
|
|
2702
|
+
prevSelection: null,
|
|
2703
|
+
readOnly
|
|
2704
|
+
};
|
|
2705
|
+
editor.getApi = () => editor.api;
|
|
2706
|
+
editor.getTransforms = () => editor.transforms;
|
|
2707
|
+
editor.getPlugin = (plugin) => getSlatePlugin(editor, plugin);
|
|
2708
|
+
editor.getType = (pluginKey) => getPluginType(editor, pluginKey);
|
|
2709
|
+
editor.getInjectProps = (plugin) => {
|
|
2710
|
+
const nodeProps = editor.getPlugin(plugin).inject?.nodeProps ?? {};
|
|
2711
|
+
nodeProps.nodeKey = nodeProps.nodeKey ?? editor.getType(plugin.key);
|
|
2712
|
+
nodeProps.styleKey = nodeProps.styleKey ?? nodeProps.nodeKey;
|
|
2713
|
+
return nodeProps;
|
|
2714
|
+
};
|
|
2715
|
+
editor.getOptionsStore = (plugin) => editor.getPlugin(plugin).optionsStore;
|
|
2716
|
+
editor.getOptions = (plugin) => {
|
|
2717
|
+
if (!editor.getOptionsStore(plugin)) return editor.getPlugin(plugin).options;
|
|
2718
|
+
return editor.getOptionsStore(plugin).get("state");
|
|
2719
|
+
};
|
|
2720
|
+
editor.getOption = (plugin, key, ...args) => {
|
|
2721
|
+
const store = editor.getOptionsStore(plugin);
|
|
2722
|
+
if (!store) return editor.getPlugin(plugin).options[key];
|
|
2723
|
+
if (!(key in store.get("state")) && !(key in store.selectors)) {
|
|
2724
|
+
editor.api.debug.error(`editor.getOption: ${key} option is not defined in plugin ${plugin.key}.`, "OPTION_UNDEFINED");
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
return store.get(key, ...args);
|
|
2728
|
+
};
|
|
2729
|
+
editor.setOption = (plugin, key, ...args) => {
|
|
2730
|
+
const store = editor.getOptionsStore(plugin);
|
|
2731
|
+
if (!store) return;
|
|
2732
|
+
if (!(key in store.get("state"))) {
|
|
2733
|
+
editor.api.debug.error(`editor.setOption: ${key} option is not defined in plugin ${plugin.key}.`, "OPTION_UNDEFINED");
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
store.set(key, ...args);
|
|
2737
|
+
};
|
|
2738
|
+
editor.setOptions = (plugin, options) => {
|
|
2739
|
+
const store = editor.getOptionsStore(plugin);
|
|
2740
|
+
if (!store) return;
|
|
2741
|
+
if (typeof options === "object") store.set("state", (draft) => {
|
|
2742
|
+
Object.assign(draft, options);
|
|
2743
|
+
});
|
|
2744
|
+
else if (typeof options === "function") store.set("state", options);
|
|
2745
|
+
};
|
|
2746
|
+
const corePlugins = getCorePlugins({
|
|
2747
|
+
affinity,
|
|
2748
|
+
chunking,
|
|
2749
|
+
maxLength,
|
|
2750
|
+
nodeId,
|
|
2751
|
+
plugins
|
|
2752
|
+
});
|
|
2753
|
+
let rootPluginInstance = createSlatePlugin({
|
|
2754
|
+
key: "root",
|
|
2755
|
+
priority: 1e4,
|
|
2756
|
+
...pluginConfig,
|
|
2757
|
+
override: {
|
|
2758
|
+
...pluginConfig.override,
|
|
2759
|
+
components: {
|
|
2760
|
+
...pluginConfig.components,
|
|
2761
|
+
...pluginConfig.override?.components
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
plugins: [...corePlugins, ...plugins]
|
|
2765
|
+
});
|
|
2766
|
+
if (rootPlugin) rootPluginInstance = rootPlugin(rootPluginInstance);
|
|
2767
|
+
resolvePlugins(editor, [rootPluginInstance], optionsStoreFactory);
|
|
2768
|
+
/** Ignore normalizeNode overrides if shouldNormalizeNode returns false */
|
|
2769
|
+
const normalizeNode = editor.tf.normalizeNode;
|
|
2770
|
+
editor.tf.normalizeNode = (...args) => {
|
|
2771
|
+
if (!editor.api.shouldNormalizeNode(args[0])) return;
|
|
2772
|
+
return normalizeNode(...args);
|
|
2773
|
+
};
|
|
2774
|
+
editor.normalizeNode = editor.tf.normalizeNode;
|
|
2775
|
+
if (!skipInitialization) editor.tf.init({
|
|
2776
|
+
autoSelect,
|
|
2777
|
+
selection,
|
|
2778
|
+
shouldNormalizeEditor,
|
|
2779
|
+
value,
|
|
2780
|
+
onReady
|
|
2781
|
+
});
|
|
2782
|
+
return editor;
|
|
2783
|
+
};
|
|
2784
|
+
/**
|
|
2785
|
+
* Creates a Slate editor (non-React version).
|
|
2786
|
+
*
|
|
2787
|
+
* This function creates a fully configured Plate editor instance that can be
|
|
2788
|
+
* used in non-React environments or server-side contexts. It applies all the
|
|
2789
|
+
* specified plugins and configurations to create a functional editor.
|
|
2790
|
+
*
|
|
2791
|
+
* Examples:
|
|
2792
|
+
*
|
|
2793
|
+
* ```ts
|
|
2794
|
+
* const editor = createSlateEditor({
|
|
2795
|
+
* plugins: [ParagraphPlugin, HeadingPlugin],
|
|
2796
|
+
* value: [{ type: 'p', children: [{ text: 'Hello world!' }] }],
|
|
2797
|
+
* });
|
|
2798
|
+
*
|
|
2799
|
+
* // Editor with custom configuration
|
|
2800
|
+
* const editor = createSlateEditor({
|
|
2801
|
+
* plugins: [ParagraphPlugin],
|
|
2802
|
+
* maxLength: 1000,
|
|
2803
|
+
* nodeId: { idCreator: () => uuidv4() },
|
|
2804
|
+
* autoSelect: 'end',
|
|
2805
|
+
* });
|
|
2806
|
+
*
|
|
2807
|
+
* // Server-side editor
|
|
2808
|
+
* const editor = createSlateEditor({
|
|
2809
|
+
* plugins: [ParagraphPlugin],
|
|
2810
|
+
* value: '<p>HTML content</p>',
|
|
2811
|
+
* skipInitialization: true,
|
|
2812
|
+
* });
|
|
2813
|
+
* ```
|
|
2814
|
+
*
|
|
2815
|
+
* @see {@link createPlateEditor} for a React-specific version of editor creation.
|
|
2816
|
+
* @see {@link usePlateEditor} for a memoized React version.
|
|
2817
|
+
* @see {@link withSlate} for the underlying function that applies Slate enhancements to an editor.
|
|
2818
|
+
*/
|
|
2819
|
+
const createSlateEditor = ({ editor = createEditor(), ...options } = {}) => withSlate(editor, options);
|
|
2820
|
+
|
|
2821
|
+
//#endregion
|
|
2822
|
+
export { DebugPlugin as $, pluginDeserializeHtml as A, withNormalizeRules as At, collapseWhiteSpaceText as B, getPluginKey as Bt, deserializeHtmlElement as C, isSlatePluginNode as Ct, pipeDeserializeHtmlLeaf as D, applyDeepToNodes as Dt, htmlElementToLeaf as E, isSlateVoid as Et, collapseWhiteSpace as F, HistoryPlugin as Ft, isHtmlBlockElement as G, getEditorPlugin as Gt, upsertInlineFormattingContext as H, getPluginType as Ht, collapseWhiteSpaceElement as I, withPlateHistory as It, isHtmlText as J, isHtmlInlineElement as K, createSlatePlugin as Kt, inferWhiteSpaceRule as L, AstPlugin as Lt, htmlBrToNewLine as M, withDeleteRules as Mt, htmlBodyToFragment as N, withBreakRules as Nt, htmlElementToElement as O, OverridePlugin as Ot, deserializeHtmlNodeChildren as P, BaseParagraphPlugin as Pt, withScrolling as Q, collapseWhiteSpaceChildren as R, getContainerTypes as Rt, htmlStringToDOMNode as S, isSlatePluginElement as St, htmlTextNodeToString as T, isSlateText as Tt, isLastNonEmptyTextOfInlineFormattingContext as U, getPluginTypes as Ut, endInlineFormattingContext as V, getPluginKeys as Vt, collapseString as W, getSlatePlugin as Wt, AUTO_SCROLL as X, isHtmlElement as Y, DOMPlugin as Z, withNodeId as _, getSlateElements as _t, pipeInsertDataQuery as a, isNodeAffinity as at, parseHtmlDocument as b, isSlateLeaf as bt, setValue as c, getEdgeNodes as ct, init as d, getPluginNodeProps as dt, PlateError as et, isEditOnly as f, getNodeDataAttributeKeys as ft, normalizeNodeId as g, defaultsDeepToNodes as gt, NodeIdPlugin as h, getInjectMatch as ht, ParserPlugin as i, setAffinitySelection as it, getDataNodeProps as j, withMergeRules as jt, pipeDeserializeHtmlElement as k, withOverrides as kt, resetBlock as l, mergeDeepToNodes as lt, pipeOnNodeChange as m, getInjectedPlugins as mt, withSlate as n, withChunking as nt, normalizeDescendantsToDocumentFragment as o, isNodesAffinity as ot, pipeOnTextChange as p, keyToDataAttribute as pt, inlineTagNames as q, createTSlatePlugin as qt, getCorePlugins as r, AffinityPlugin as rt, SlateExtensionPlugin as s, getMarkBoundaryAffinity as st, createSlateEditor as t, ChunkingPlugin as tt, insertExitBreak as u, getSlateClass as ut, LengthPlugin as v, isSlateEditor as vt, deserializeHtmlNode as w, isSlateString as wt, deserializeHtml as x, isSlateNode as xt, HtmlPlugin as y, isSlateElement as yt, collapseWhiteSpaceNode as z, getPluginByType as zt };
|
|
2823
|
+
//# sourceMappingURL=withSlate-1B0SfAWG.js.map
|