@ckeditor/ckeditor5-editor-multi-root 48.1.1 → 48.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +112 -53
- package/dist/index.js.map +1 -1
- package/dist/multirooteditor.d.ts +35 -6
- package/dist/multirooteditoruiview.d.ts +3 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import { Editor, normalizeMultiRootEditorConstructorParams, normalizeRootsConfig, secureSourceElement, registerAndInitializeRootConfigAttributes, verifyRootElements } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
5
|
+
import { rootAcceptsBlocks, Editor, normalizeMultiRootEditorConstructorParams, normalizeRootsConfig, secureSourceElement, registerAndInitializeRootConfigAttributes, normalizeViewRootElementDefinition, verifyRootElements } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
6
|
import { CKEditorError, logWarning, decodeLicenseKey, isFeatureBlockedByLicenseKey, setDataInElement } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
7
7
|
import { EditorUI, EditorUIView, ToolbarView, MenuBarView, InlineEditableUIView } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
8
8
|
import { enableViewPlaceholder } from '@ckeditor/ckeditor5-engine/dist/index.js';
|
|
@@ -31,6 +31,14 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
31
31
|
* Initializes the UI.
|
|
32
32
|
*/ init() {
|
|
33
33
|
const view = this.view;
|
|
34
|
+
const editor = this.editor;
|
|
35
|
+
// Resolved during UI init rather than in the editor constructor: by this point the plugin
|
|
36
|
+
// initialization phase has finished, so the schema is fully populated and the check below
|
|
37
|
+
// reflects any plugin-registered root types or additional content rules.
|
|
38
|
+
// Done before `view.render()` so the CSS class lands on the DOM from the start.
|
|
39
|
+
for (const editable of Object.values(this.view.editables)){
|
|
40
|
+
editable.isInlineRoot = !rootAcceptsBlocks(editor, editable.name);
|
|
41
|
+
}
|
|
34
42
|
view.render();
|
|
35
43
|
// Keep track of the last focused editable element. Knowing which one was focused
|
|
36
44
|
// is useful when the focus moves from editable to other UI components like balloons
|
|
@@ -157,7 +165,7 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
157
165
|
enableViewPlaceholder({
|
|
158
166
|
view: editingView,
|
|
159
167
|
element: editingRoot,
|
|
160
|
-
isDirectHost:
|
|
168
|
+
isDirectHost: editable.isInlineRoot,
|
|
161
169
|
keepOnFocus: true
|
|
162
170
|
});
|
|
163
171
|
}
|
|
@@ -318,6 +326,7 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
318
326
|
super(editorConfig);
|
|
319
327
|
normalizeRootsConfig(sourceElementsOrData, this.config, false);
|
|
320
328
|
normalizeRootsAttributesConfig(this.config);
|
|
329
|
+
normalizeRootEditableOptionsConfig(this.config);
|
|
321
330
|
if (this.config.get('lazyRoots')) {
|
|
322
331
|
/**
|
|
323
332
|
* Using deprecated `config.lazyRoots` configuration option.
|
|
@@ -329,16 +338,17 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
329
338
|
// From this point use only normalized `roots.<rootName>.element`, etc.
|
|
330
339
|
const rootsConfig = Object.entries(this.config.get('roots'));
|
|
331
340
|
this.sourceElements = {};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.sourceElements[rootName] =
|
|
340
|
-
secureSourceElement(this,
|
|
341
|
+
const editableElements = {};
|
|
342
|
+
for (const [rootName, rootConfig] of rootsConfig){
|
|
343
|
+
const editableElement = getRootEditableElement(rootConfig);
|
|
344
|
+
if (!editableElement) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (isElement(editableElement)) {
|
|
348
|
+
this.sourceElements[rootName] = editableElement;
|
|
349
|
+
secureSourceElement(this, editableElement);
|
|
341
350
|
}
|
|
351
|
+
editableElements[rootName] = editableElement;
|
|
342
352
|
}
|
|
343
353
|
this.editing.view.document.roots.on('add', (evt, viewRoot)=>{
|
|
344
354
|
// Here we change the standard binding of readOnly flag by adding
|
|
@@ -366,37 +376,14 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
366
376
|
root._isLoaded = false;
|
|
367
377
|
}
|
|
368
378
|
}
|
|
379
|
+
// Register `$rootEditableOptions` unconditionally, so it is always returned by `getRootAttributes()` (e.g. for RH).
|
|
380
|
+
// The value is set via `config.roots.<rootName>.modelAttributes.$rootEditableOptions` (see `normalizeRootEditableOptionsConfig`),
|
|
381
|
+
// which also makes it round-trip through RTC's initial-data path.
|
|
382
|
+
this.registerRootAttribute('$rootEditableOptions');
|
|
369
383
|
registerAndInitializeRootConfigAttributes(this);
|
|
370
|
-
// Registering `$rootEditableOptions` attribute to make it available in the editor model.
|
|
371
|
-
// This allows to store editable options for each root in the model, and make them available on other RTC clients.
|
|
372
|
-
// We do not use `registerRootAttribute()` method here, as this attribute is used internally
|
|
373
|
-
// and should not be returned by `getRootsAttributes()` method.
|
|
374
|
-
this.editing.model.schema.extend('$root', {
|
|
375
|
-
allowAttributes: '$rootEditableOptions'
|
|
376
|
-
});
|
|
377
|
-
this.data.on('init', ()=>{
|
|
378
|
-
this.model.enqueueChange({
|
|
379
|
-
isUndoable: false
|
|
380
|
-
}, (writer)=>{
|
|
381
|
-
for (const [rootName, rootConfig] of rootsConfig){
|
|
382
|
-
const root = this.model.document.getRoot(rootName);
|
|
383
|
-
// Set editable config for consistency with `addRoot()` method. This will allow features
|
|
384
|
-
// to use the same configuration for both initially loaded and dynamically added roots.
|
|
385
|
-
const rootEditableOptions = {
|
|
386
|
-
...rootConfig.placeholder && {
|
|
387
|
-
placeholder: rootConfig.placeholder
|
|
388
|
-
},
|
|
389
|
-
...rootConfig.label && {
|
|
390
|
-
label: rootConfig.label
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
writer.setAttribute('$rootEditableOptions', rootEditableOptions, root);
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
384
|
const options = {
|
|
398
385
|
shouldToolbarGroupWhenFull: !this.config.get('toolbar.shouldNotGroupWhenFull'),
|
|
399
|
-
editableElements
|
|
386
|
+
editableElements,
|
|
400
387
|
label: extractRootsConfigField(this.config.get('roots'), 'label')
|
|
401
388
|
};
|
|
402
389
|
const view = new MultiRootEditorUIView(this.locale, this.editing.view, getNonLazyLoadRootsNames(rootsConfig), options);
|
|
@@ -524,12 +511,16 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
524
511
|
}
|
|
525
512
|
addRoot(rootName, options = {}) {
|
|
526
513
|
const initialData = options.initialData || options.data || '';
|
|
527
|
-
const modelAttributes =
|
|
514
|
+
const modelAttributes = {
|
|
515
|
+
...options.modelAttributes || options.attributes
|
|
516
|
+
};
|
|
517
|
+
// eslint-disable-next-line ckeditor5-rules/no-literal-dollar-root -- public API default for `addRoot()`
|
|
528
518
|
const modelElement = options.modelElement || options.elementName || '$root';
|
|
529
519
|
if (!this.model.schema.isLimit(modelElement)) {
|
|
530
520
|
/**
|
|
531
521
|
* The model root element must be a {@link module:engine/model/schema~ModelSchemaItemDefinition#isLimit limit element}.
|
|
532
|
-
* The element name specified in {@link ~MultiRootEditor#addRoot
|
|
522
|
+
* The element name specified in {@link module:editor-multi-root/multirooteditor~MultiRootEditor#addRoot:ROOT_CONFIG addRoot()}
|
|
523
|
+
* options must be registered in the schema
|
|
533
524
|
* with `isLimit` set to `true`.
|
|
534
525
|
*
|
|
535
526
|
* @error multi-root-editor-add-root-element-is-not-limit
|
|
@@ -542,9 +533,20 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
542
533
|
}
|
|
543
534
|
if (isElement(options.element)) {
|
|
544
535
|
/**
|
|
545
|
-
*
|
|
536
|
+
* Passing an existing DOM element as the `element` option of
|
|
537
|
+
* {@link ~MultiRootEditor#addRoot:ROOT_CONFIG `addRoot()`} is not supported and will be ignored. The
|
|
538
|
+
* `addRoot()` method only registers the model root; the DOM editable is created later by
|
|
539
|
+
* {@link ~MultiRootEditor#createEditable `createEditable()`}.
|
|
540
|
+
*
|
|
541
|
+
* Pass a tag name string (e.g. `'h1'`) or a
|
|
542
|
+
* {@link module:engine/view/elementdefinition~ViewElementDefinition view element definition}
|
|
543
|
+
* instead, or omit the option to create a default `<div>`.
|
|
544
|
+
*
|
|
545
|
+
* @error multi-root-editor-add-root-element-option-ignored
|
|
546
546
|
*/ logWarning('multi-root-editor-add-root-element-option-ignored');
|
|
547
547
|
}
|
|
548
|
+
// Persist editable options as a root attribute so they are available on other RTC clients.
|
|
549
|
+
setRootEditableOptions(modelAttributes, options);
|
|
548
550
|
const _addRoot = (writer)=>{
|
|
549
551
|
const root = writer.addRoot(rootName, modelElement);
|
|
550
552
|
if (initialData) {
|
|
@@ -554,16 +556,6 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
554
556
|
this.registerRootAttribute(key);
|
|
555
557
|
writer.setAttribute(key, modelAttributes[key], root);
|
|
556
558
|
}
|
|
557
|
-
// Storing editable options as a root attribute to make them available on other RTC clients.
|
|
558
|
-
const rootEditableOptions = {
|
|
559
|
-
...options.placeholder && {
|
|
560
|
-
placeholder: options.placeholder
|
|
561
|
-
},
|
|
562
|
-
...options.label && {
|
|
563
|
-
label: options.label
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
writer.setAttribute('$rootEditableOptions', rootEditableOptions, root);
|
|
567
559
|
};
|
|
568
560
|
if (options.isUndoable) {
|
|
569
561
|
this.model.change(_addRoot);
|
|
@@ -637,14 +629,21 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
637
629
|
}
|
|
638
630
|
createEditable(root, optionsOrPlaceholder, label) {
|
|
639
631
|
let placeholder;
|
|
632
|
+
let element;
|
|
640
633
|
if (!optionsOrPlaceholder || typeof optionsOrPlaceholder === 'string') {
|
|
641
634
|
placeholder = optionsOrPlaceholder;
|
|
642
635
|
} else {
|
|
643
636
|
placeholder = optionsOrPlaceholder?.placeholder;
|
|
644
637
|
label = optionsOrPlaceholder?.label;
|
|
638
|
+
element = optionsOrPlaceholder?.element;
|
|
645
639
|
}
|
|
646
640
|
const rootEditableConfig = root.getAttribute('$rootEditableOptions') || {};
|
|
647
|
-
|
|
641
|
+
// Both the call-site `element` and the stored `rootEditableConfig.element` may be a tag name string
|
|
642
|
+
// or an `HTMLElement` and need normalization - `setRootEditableOptions()` does this at write time, but
|
|
643
|
+
// callers can also pre-supply `$rootEditableOptions` directly without going through it.
|
|
644
|
+
const editableElement = normalizeViewRootElementDefinition(element || rootEditableConfig.element);
|
|
645
|
+
const editable = this.ui.view.createEditable(root.rootName, editableElement, label || rootEditableConfig.label);
|
|
646
|
+
editable.isInlineRoot = !rootAcceptsBlocks(this, root.rootName);
|
|
648
647
|
this.ui.addEditable(editable, placeholder || rootEditableConfig.placeholder);
|
|
649
648
|
this.editing.view.forceRender();
|
|
650
649
|
return editable.element;
|
|
@@ -920,9 +919,69 @@ import { isElement as isElement$1 } from 'es-toolkit/compat';
|
|
|
920
919
|
}
|
|
921
920
|
}
|
|
922
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Normalize `placeholder` and `label` from `config.roots.<rootName>` into the `$rootEditableOptions` root model attribute,
|
|
924
|
+
* stored under `config.roots.<rootName>.modelAttributes`. This way the attribute is registered, set on initial data load
|
|
925
|
+
* and shipped through RTC initial-data path together with the rest of `modelAttributes`.
|
|
926
|
+
*
|
|
927
|
+
* This is also required by the revision history feature: on editor load, RH compares the latest revision data against
|
|
928
|
+
* `initialData` and `modelAttributes` passed to the editor and logs a warning if they do not match. Because `$rootEditableOptions`
|
|
929
|
+
* ends up in the revision data, it must also be present in `modelAttributes` (even as an empty object when no options
|
|
930
|
+
* are configured), otherwise the comparison reports a spurious mismatch.
|
|
931
|
+
*/ function normalizeRootEditableOptionsConfig(config) {
|
|
932
|
+
const rootsConfig = config.get('roots');
|
|
933
|
+
for (const [rootName, rootConfig] of Object.entries(rootsConfig)){
|
|
934
|
+
if (rootConfig.modelAttributes?.$rootEditableOptions) {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const modelAttributes = {
|
|
938
|
+
...rootConfig.modelAttributes
|
|
939
|
+
};
|
|
940
|
+
setRootEditableOptions(modelAttributes, rootConfig);
|
|
941
|
+
config.set(`roots.${rootName}.modelAttributes`, modelAttributes);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Mutates the given `modelAttributes` map by adding the `$rootEditableOptions` entry derived from `placeholder`, `label`
|
|
946
|
+
* and `element`. If `$rootEditableOptions` is already present, the map is left untouched.
|
|
947
|
+
*
|
|
948
|
+
* The `element` is normalized into canonical form ({@link module:core/editor/editorconfig~ViewRootElementDefinition})
|
|
949
|
+
* before being persisted. A raw DOM element is local to this editor instance - it cannot be replicated through
|
|
950
|
+
* RTC, so it is silently dropped here. Callers that want to surface a warning (e.g. `addRoot()`) should do so before
|
|
951
|
+
* invoking this function.
|
|
952
|
+
*/ function setRootEditableOptions(modelAttributes, { placeholder, label, element }) {
|
|
953
|
+
if ('$rootEditableOptions' in modelAttributes) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
// In the `else` branch `element` cannot be an `HTMLElement`, but the normalizer's return type still
|
|
957
|
+
// includes it — the cast narrows it back to the canonical descriptor form.
|
|
958
|
+
const storageElement = isElement(element) ? undefined : normalizeViewRootElementDefinition(element);
|
|
959
|
+
modelAttributes.$rootEditableOptions = {
|
|
960
|
+
...placeholder && {
|
|
961
|
+
placeholder
|
|
962
|
+
},
|
|
963
|
+
...label && {
|
|
964
|
+
label
|
|
965
|
+
},
|
|
966
|
+
...storageElement && {
|
|
967
|
+
element: storageElement
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
}
|
|
923
971
|
function isElement(value) {
|
|
924
972
|
return isElement$1(value);
|
|
925
973
|
}
|
|
974
|
+
/**
|
|
975
|
+
* Returns the canonical editable element descriptor for the given root config.
|
|
976
|
+
*
|
|
977
|
+
* Falls back to `$rootEditableOptions.element` so remote RTC clients - which do not see the originator's
|
|
978
|
+
* `config.roots.<name>.element` - can recreate the configured editable shape from the model attributes
|
|
979
|
+
* they receive. The result is normalized here in case the attribute was pre-supplied without going
|
|
980
|
+
* through `setRootEditableOptions()` (e.g. a caller writing `modelAttributes.$rootEditableOptions` directly).
|
|
981
|
+
*/ function getRootEditableElement(rootConfig) {
|
|
982
|
+
const rootEditableOptions = rootConfig.modelAttributes?.$rootEditableOptions;
|
|
983
|
+
return normalizeViewRootElementDefinition(rootConfig.element || rootEditableOptions?.element);
|
|
984
|
+
}
|
|
926
985
|
|
|
927
986
|
export { MultiRootEditor, MultiRootEditorUI, MultiRootEditorUIView };
|
|
928
987
|
//# sourceMappingURL=index.js.map
|