ckeditor5 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +10 -0
- data/README.md +63 -11
- data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +4 -4
- data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +22 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/context.mjs +113 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/editable.mjs +113 -0
- data/lib/ckeditor5/rails/assets/{webcomponent.mjs → webcomponents/components/editor.mjs} +104 -479
- data/lib/ckeditor5/rails/assets/webcomponents/components/ui-part.mjs +26 -0
- data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +155 -0
- data/lib/ckeditor5/rails/cdn/ckbox_bundle.rb +17 -8
- data/lib/ckeditor5/rails/cdn/helpers.rb +3 -11
- data/lib/ckeditor5/rails/cdn/url_generator.rb +7 -5
- data/lib/ckeditor5/rails/helpers.rb +0 -2
- data/lib/ckeditor5/rails/presets/preset_builder.rb +21 -2
- data/lib/ckeditor5/rails/version.rb +1 -1
- metadata +8 -4
- data/lib/ckeditor5/rails/cloud/helpers.rb +0 -13
@@ -1,26 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* Custom web component for integrating CKEditor 5 into web applications.
|
3
|
-
*
|
4
|
-
* @class
|
5
|
-
* @extends HTMLElement
|
6
|
-
*
|
7
|
-
* @property {import('ckeditor5').Editor|null} instance - The current CKEditor instance
|
8
|
-
* @property {Record<string, HTMLElement>} editables - Object containing editable elements
|
9
|
-
*
|
10
|
-
* @fires editor-ready - Fired when editor is initialized with the editor instance as detail
|
11
|
-
* @fires editor-error - Fired when initialization fails with the error as detail
|
12
|
-
*
|
13
|
-
* @example
|
14
|
-
* // Basic usage with Classic Editor
|
15
|
-
* <ckeditor-component type="ClassicEditor" config='{"toolbar": ["bold", "italic"]}'>
|
16
|
-
* </ckeditor-component>
|
17
|
-
*
|
18
|
-
* // Multiroot editor with multiple editables
|
19
|
-
* <ckeditor-component type="MultirootEditor">
|
20
|
-
* <ckeditor-editable-component name="title">Title content</ckeditor-editable-component>
|
21
|
-
* <ckeditor-editable-component name="content">Main content</ckeditor-editable-component>
|
22
|
-
* </ckeditor-component>
|
23
|
-
*/
|
24
1
|
class CKEditorComponent extends HTMLElement {
|
25
2
|
/**
|
26
3
|
* List of attributes that trigger updates when changed.
|
@@ -63,6 +40,69 @@ class CKEditorComponent extends HTMLElement {
|
|
63
40
|
/** @type {String} ID of editor within context */
|
64
41
|
#contextEditorId = null;
|
65
42
|
|
43
|
+
/** @type {(event: CustomEvent) => void} Event handler for editor change */
|
44
|
+
get oneditorchange() {
|
45
|
+
return this.#getEventHandler('editorchange');
|
46
|
+
}
|
47
|
+
|
48
|
+
set oneditorchange(handler) {
|
49
|
+
this.#setEventHandler('editorchange', handler);
|
50
|
+
}
|
51
|
+
|
52
|
+
/** @type {(event: CustomEvent) => void} Event handler for editor ready */
|
53
|
+
get oneditorready() {
|
54
|
+
return this.#getEventHandler('editorready');
|
55
|
+
}
|
56
|
+
|
57
|
+
set oneditorready(handler) {
|
58
|
+
this.#setEventHandler('editorready', handler);
|
59
|
+
}
|
60
|
+
|
61
|
+
/** @type {(event: CustomEvent) => void} Event handler for editor error */
|
62
|
+
get oneditorerror() {
|
63
|
+
return this.#getEventHandler('editorerror');
|
64
|
+
}
|
65
|
+
|
66
|
+
set oneditorerror(handler) {
|
67
|
+
this.#setEventHandler('editorerror', handler);
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Gets event handler function from attribute or property
|
72
|
+
*
|
73
|
+
* @private
|
74
|
+
* @param {string} name - Event name without 'on' prefix
|
75
|
+
* @returns {Function|null} Event handler or null
|
76
|
+
*/
|
77
|
+
#getEventHandler(name) {
|
78
|
+
if (this.hasAttribute(`on${name}`)) {
|
79
|
+
const handler = this.getAttribute(`on${name}`);
|
80
|
+
|
81
|
+
if (!isSafeKey(handler)) {
|
82
|
+
throw new Error(`Unsafe event handler attribute value: ${handler}`);
|
83
|
+
}
|
84
|
+
|
85
|
+
return window[handler] || new Function('event', handler);
|
86
|
+
}
|
87
|
+
return this[`#${name}Handler`];
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Sets event handler function
|
92
|
+
*
|
93
|
+
* @private
|
94
|
+
* @param {string} name - Event name without 'on' prefix
|
95
|
+
* @param {Function|string|null} handler - Event handler
|
96
|
+
*/
|
97
|
+
#setEventHandler(name, handler) {
|
98
|
+
if (typeof handler === 'string') {
|
99
|
+
this.setAttribute(`on${name}`, handler);
|
100
|
+
} else {
|
101
|
+
this.removeAttribute(`on${name}`);
|
102
|
+
this[`#${name}Handler`] = handler;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
66
106
|
/**
|
67
107
|
* Lifecycle callback when element is connected to DOM
|
68
108
|
* Initializes the editor when DOM is ready
|
@@ -83,7 +123,11 @@ class CKEditorComponent extends HTMLElement {
|
|
83
123
|
});
|
84
124
|
} catch (error) {
|
85
125
|
console.error('Failed to initialize editor:', error);
|
86
|
-
|
126
|
+
|
127
|
+
const event = new CustomEvent('editor-error', { detail: error });
|
128
|
+
|
129
|
+
this.dispatchEvent(event);
|
130
|
+
this.oneditorerror?.(event);
|
87
131
|
}
|
88
132
|
}
|
89
133
|
|
@@ -250,8 +294,6 @@ class CKEditorComponent extends HTMLElement {
|
|
250
294
|
instance = await Editor.create(content, config);
|
251
295
|
}
|
252
296
|
|
253
|
-
this.dispatchEvent(new CustomEvent('editor-ready', { detail: instance }));
|
254
|
-
|
255
297
|
return {
|
256
298
|
contextId,
|
257
299
|
instance,
|
@@ -299,14 +341,50 @@ class CKEditorComponent extends HTMLElement {
|
|
299
341
|
|
300
342
|
this.#setupContentSync();
|
301
343
|
this.#setupEditableHeight();
|
344
|
+
this.#setupDataChangeListener();
|
302
345
|
|
303
346
|
this.instancePromise.resolve(this.instance);
|
347
|
+
|
348
|
+
// Broadcast editor ready event
|
349
|
+
const event = new CustomEvent('editor-ready', { detail: this.instance });
|
350
|
+
|
351
|
+
this.dispatchEvent(event);
|
352
|
+
this.oneditorready?.(event);
|
304
353
|
} catch (err) {
|
305
354
|
this.instancePromise.reject(err);
|
306
355
|
throw err;
|
307
356
|
}
|
308
357
|
}
|
309
358
|
|
359
|
+
/**
|
360
|
+
* Sets up data change listener that broadcasts content changes
|
361
|
+
*
|
362
|
+
* @private
|
363
|
+
*/
|
364
|
+
#setupDataChangeListener() {
|
365
|
+
const getRootContent = rootName => this.instance.getData({ rootName });
|
366
|
+
const getAllRoots = () =>
|
367
|
+
this.instance.model.document
|
368
|
+
.getRootNames()
|
369
|
+
.reduce((acc, rootName) => ({
|
370
|
+
...acc,
|
371
|
+
[rootName]: getRootContent(rootName)
|
372
|
+
}), {});
|
373
|
+
|
374
|
+
this.instance?.model.document.on('change:data', () => {
|
375
|
+
const event = new CustomEvent('editor-change', {
|
376
|
+
detail: {
|
377
|
+
editor: this.instance,
|
378
|
+
data: getAllRoots(),
|
379
|
+
},
|
380
|
+
bubbles: true
|
381
|
+
});
|
382
|
+
|
383
|
+
this.dispatchEvent(event);
|
384
|
+
this.oneditorchange?.(event);
|
385
|
+
});
|
386
|
+
}
|
387
|
+
|
310
388
|
/**
|
311
389
|
* Checks if current editor is classic type
|
312
390
|
*
|
@@ -515,138 +593,6 @@ class CKEditorComponent extends HTMLElement {
|
|
515
593
|
}
|
516
594
|
}
|
517
595
|
|
518
|
-
/**
|
519
|
-
* Custom element that provides shared CKEditor context for multiple editors.
|
520
|
-
*
|
521
|
-
* @extends HTMLElement
|
522
|
-
* @example
|
523
|
-
*
|
524
|
-
* <ckeditor-context-component plugins='[ ... ]'>
|
525
|
-
* <ckeditor-component type="ClassicEditor" config='{"toolbar": ["bold", "italic"]}'>
|
526
|
-
* <ckeditor-component type="ClassicEditor" config='{"toolbar": ["bold", "italic"]}'>
|
527
|
-
* </ckeditor-component>
|
528
|
-
*/
|
529
|
-
class CKEditorContextComponent extends HTMLElement {
|
530
|
-
static get observedAttributes() {
|
531
|
-
return ['plugins', 'config'];
|
532
|
-
}
|
533
|
-
|
534
|
-
/** @type {import('ckeditor5').Context|null} */
|
535
|
-
instance = null;
|
536
|
-
|
537
|
-
/** @type {Promise<import('ckeditor5').Context>} */
|
538
|
-
instancePromise = Promise.withResolvers();
|
539
|
-
|
540
|
-
/** @type {Set<CKEditorComponent>} */
|
541
|
-
#connectedEditors = new Set();
|
542
|
-
|
543
|
-
async connectedCallback() {
|
544
|
-
try {
|
545
|
-
execIfDOMReady(() => this.#initializeContext());
|
546
|
-
} catch (error) {
|
547
|
-
console.error('Failed to initialize context:', error);
|
548
|
-
this.dispatchEvent(new CustomEvent('context-error', { detail: error }));
|
549
|
-
}
|
550
|
-
}
|
551
|
-
|
552
|
-
async attributeChangedCallback(name, oldValue, newValue) {
|
553
|
-
if (oldValue !== null && oldValue !== newValue) {
|
554
|
-
await this.#initializeContext();
|
555
|
-
}
|
556
|
-
}
|
557
|
-
|
558
|
-
async disconnectedCallback() {
|
559
|
-
if (this.instance) {
|
560
|
-
await this.instance.destroy();
|
561
|
-
this.instance = null;
|
562
|
-
}
|
563
|
-
}
|
564
|
-
|
565
|
-
/**
|
566
|
-
* Register editor component with this context
|
567
|
-
*
|
568
|
-
* @param {CKEditorComponent} editor
|
569
|
-
*/
|
570
|
-
registerEditor(editor) {
|
571
|
-
this.#connectedEditors.add(editor);
|
572
|
-
}
|
573
|
-
|
574
|
-
/**
|
575
|
-
* Unregister editor component from this context
|
576
|
-
*
|
577
|
-
* @param {CKEditorComponent} editor
|
578
|
-
*/
|
579
|
-
unregisterEditor(editor) {
|
580
|
-
this.#connectedEditors.delete(editor);
|
581
|
-
}
|
582
|
-
|
583
|
-
/**
|
584
|
-
* Initialize CKEditor context with shared configuration
|
585
|
-
*
|
586
|
-
* @private
|
587
|
-
*/
|
588
|
-
async #initializeContext() {
|
589
|
-
if (this.instance) {
|
590
|
-
this.instancePromise = Promise.withResolvers();
|
591
|
-
|
592
|
-
await this.instance.destroy();
|
593
|
-
|
594
|
-
this.instance = null;
|
595
|
-
}
|
596
|
-
|
597
|
-
const { Context, ContextWatchdog } = await import('ckeditor5');
|
598
|
-
const plugins = await this.#getPlugins();
|
599
|
-
const config = this.#getConfig();
|
600
|
-
|
601
|
-
this.instance = new ContextWatchdog(Context, {
|
602
|
-
crashNumberLimit: 10
|
603
|
-
});
|
604
|
-
|
605
|
-
await this.instance.create({
|
606
|
-
...config,
|
607
|
-
plugins
|
608
|
-
});
|
609
|
-
|
610
|
-
this.instance.on('itemError', (...args) => {
|
611
|
-
console.error('Context item error:', ...args);
|
612
|
-
});
|
613
|
-
|
614
|
-
this.instancePromise.resolve(this.instance);
|
615
|
-
this.dispatchEvent(new CustomEvent('context-ready', { detail: this.instance }));
|
616
|
-
|
617
|
-
// Reinitialize connected editors.
|
618
|
-
await Promise.all(
|
619
|
-
[...this.#connectedEditors].map(editor => editor.reinitializeEditor())
|
620
|
-
);
|
621
|
-
}
|
622
|
-
|
623
|
-
async #getPlugins() {
|
624
|
-
const raw = this.getAttribute('plugins');
|
625
|
-
|
626
|
-
return loadAsyncImports(raw ? JSON.parse(raw) : []);
|
627
|
-
}
|
628
|
-
|
629
|
-
/**
|
630
|
-
* Gets context configuration with resolved element references.
|
631
|
-
*
|
632
|
-
* @private
|
633
|
-
*/
|
634
|
-
#getConfig() {
|
635
|
-
const config = JSON.parse(this.getAttribute('config') || '{}');
|
636
|
-
|
637
|
-
return resolveElementReferences(config);
|
638
|
-
}
|
639
|
-
}
|
640
|
-
|
641
|
-
/**
|
642
|
-
* Tracks and manages editable roots for CKEditor MultiRoot editor.
|
643
|
-
* Provides a proxy-based API for dynamically managing editable elements with automatic
|
644
|
-
* attachment/detachment of editor roots.
|
645
|
-
*
|
646
|
-
* @class
|
647
|
-
* @property {CKEditorComponent} #editorElement - Reference to parent editor component
|
648
|
-
* @property {Record<string, HTMLElement>} #editables - Map of tracked editable elements
|
649
|
-
*/
|
650
596
|
class CKEditorMultiRootEditablesTracker {
|
651
597
|
#editorElement;
|
652
598
|
#editables;
|
@@ -765,325 +711,4 @@ class CKEditorMultiRootEditablesTracker {
|
|
765
711
|
}
|
766
712
|
}
|
767
713
|
|
768
|
-
/**
|
769
|
-
* Custom HTML element representing an editable region for CKEditor.
|
770
|
-
* Must be used as a child of ckeditor-component element.
|
771
|
-
*
|
772
|
-
* @customElement ckeditor-editable-component
|
773
|
-
* @extends HTMLElement
|
774
|
-
*
|
775
|
-
* @property {string} name - The name of the editable region, accessed via getAttribute
|
776
|
-
* @property {HTMLDivElement} editableElement - The div element containing editable content
|
777
|
-
*
|
778
|
-
* @fires connectedCallback - When the element is added to the DOM
|
779
|
-
* @fires attributeChangedCallback - When element attributes change
|
780
|
-
* @fires disconnectedCallback - When the element is removed from the DOM
|
781
|
-
*
|
782
|
-
* @throws {Error} Throws error if not used as child of ckeditor-component
|
783
|
-
*
|
784
|
-
* @example
|
785
|
-
* <ckeditor-component>
|
786
|
-
* <ckeditor-editable-component name="main">
|
787
|
-
* Content goes here
|
788
|
-
* </ckeditor-editable-component>
|
789
|
-
* </ckeditor-component>
|
790
|
-
*/
|
791
|
-
class CKEditorEditableComponent extends HTMLElement {
|
792
|
-
/**
|
793
|
-
* List of attributes that trigger updates when changed
|
794
|
-
*
|
795
|
-
* @static
|
796
|
-
* @returns {string[]} Array of attribute names to observe
|
797
|
-
*/
|
798
|
-
static get observedAttributes() {
|
799
|
-
return ['name'];
|
800
|
-
}
|
801
|
-
|
802
|
-
/**
|
803
|
-
* Gets the name of this editable region
|
804
|
-
*
|
805
|
-
* @returns {string} The name attribute value
|
806
|
-
*/
|
807
|
-
get name() {
|
808
|
-
// The default value is set mainly for decoupled editors where the name is not required.
|
809
|
-
return this.getAttribute('name') || 'editable';
|
810
|
-
}
|
811
|
-
|
812
|
-
/**
|
813
|
-
* Gets the actual editable DOM element
|
814
|
-
* @returns {HTMLDivElement|null} The div element containing editable content
|
815
|
-
*/
|
816
|
-
get editableElement() {
|
817
|
-
return this.querySelector('div');
|
818
|
-
}
|
819
|
-
|
820
|
-
/**
|
821
|
-
* Lifecycle callback when element is added to DOM
|
822
|
-
* Sets up the editable element and registers it with the parent editor
|
823
|
-
*
|
824
|
-
* @throws {Error} If not used as child of ckeditor-component
|
825
|
-
*/
|
826
|
-
connectedCallback() {
|
827
|
-
execIfDOMReady(() => {
|
828
|
-
const editorComponent = this.#queryEditorElement();
|
829
|
-
|
830
|
-
if (!editorComponent ) {
|
831
|
-
throw new Error('ckeditor-editable-component must be a child of ckeditor-component');
|
832
|
-
}
|
833
|
-
|
834
|
-
this.innerHTML = `<div>${this.innerHTML}</div>`;
|
835
|
-
this.style.display = 'block';
|
836
|
-
|
837
|
-
if (editorComponent.isDecoupled()) {
|
838
|
-
editorComponent.runAfterEditorReady(editor => {
|
839
|
-
this.appendChild(editor.ui.view[this.name].element);
|
840
|
-
});
|
841
|
-
} else {
|
842
|
-
if (!this.name) {
|
843
|
-
throw new Error('Editable component missing required "name" attribute');
|
844
|
-
}
|
845
|
-
|
846
|
-
editorComponent.editables[this.name] = this;
|
847
|
-
}
|
848
|
-
});
|
849
|
-
}
|
850
|
-
|
851
|
-
/**
|
852
|
-
* Lifecycle callback for attribute changes
|
853
|
-
* Handles name changes and propagates other attributes to editable element
|
854
|
-
*
|
855
|
-
* @param {string} name - Name of changed attribute
|
856
|
-
* @param {string|null} oldValue - Previous value
|
857
|
-
* @param {string|null} newValue - New value
|
858
|
-
*/
|
859
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
860
|
-
if (oldValue === newValue) {
|
861
|
-
return;
|
862
|
-
}
|
863
|
-
|
864
|
-
if (name === 'name') {
|
865
|
-
if (!oldValue) {
|
866
|
-
return;
|
867
|
-
}
|
868
|
-
|
869
|
-
const editorComponent = this.#queryEditorElement();
|
870
|
-
|
871
|
-
if (editorComponent) {
|
872
|
-
editorComponent.editables[newValue] = editorComponent.editables[oldValue];
|
873
|
-
delete editorComponent.editables[oldValue];
|
874
|
-
}
|
875
|
-
} else {
|
876
|
-
this.editableElement.setAttribute(name, newValue);
|
877
|
-
}
|
878
|
-
}
|
879
|
-
|
880
|
-
/**
|
881
|
-
* Lifecycle callback when element is removed
|
882
|
-
* Un-registers this editable from the parent editor
|
883
|
-
*/
|
884
|
-
disconnectedCallback() {
|
885
|
-
const editorComponent = this.#queryEditorElement();
|
886
|
-
|
887
|
-
if (editorComponent) {
|
888
|
-
delete editorComponent.editables[this.name];
|
889
|
-
}
|
890
|
-
}
|
891
|
-
|
892
|
-
/**
|
893
|
-
* Finds the parent editor component
|
894
|
-
*
|
895
|
-
* @private
|
896
|
-
* @returns {CKEditorComponent|null} Parent editor component or null if not found
|
897
|
-
*/
|
898
|
-
#queryEditorElement() {
|
899
|
-
return this.closest('ckeditor-component') || document.body.querySelector('ckeditor-component');
|
900
|
-
}
|
901
|
-
}
|
902
|
-
|
903
|
-
/**
|
904
|
-
* Custom HTML element that represents a CKEditor UI part component.
|
905
|
-
* It helpers with management of toolbar and other elements.
|
906
|
-
*
|
907
|
-
* @extends HTMLElement
|
908
|
-
* @customElement ckeditor-ui-part-component
|
909
|
-
* @example
|
910
|
-
* <ckeditor-ui-part-component></ckeditor-ui-part-component>
|
911
|
-
*/
|
912
|
-
class CKEditorUIPartComponent extends HTMLElement {
|
913
|
-
/**
|
914
|
-
* Lifecycle callback when element is added to DOM
|
915
|
-
* Adds the toolbar to the editor UI
|
916
|
-
*/
|
917
|
-
connectedCallback() {
|
918
|
-
execIfDOMReady(async () => {
|
919
|
-
const uiPart = this.getAttribute('name');
|
920
|
-
const editor = await this.#queryEditorElement().instancePromise.promise;
|
921
|
-
|
922
|
-
this.appendChild(editor.ui.view[uiPart].element);
|
923
|
-
});
|
924
|
-
}
|
925
|
-
|
926
|
-
/**
|
927
|
-
* Finds the parent editor component
|
928
|
-
*
|
929
|
-
* @private
|
930
|
-
* @returns {CKEditorComponent|null} Parent editor component or null if not found
|
931
|
-
*/
|
932
|
-
#queryEditorElement() {
|
933
|
-
return this.closest('ckeditor-component') || document.body.querySelector('ckeditor-component');
|
934
|
-
}
|
935
|
-
}
|
936
|
-
|
937
|
-
/**
|
938
|
-
* Executes callback when DOM is ready
|
939
|
-
*
|
940
|
-
* @param {() => void} callback - Function to execute
|
941
|
-
*/
|
942
|
-
function execIfDOMReady(callback) {
|
943
|
-
switch (document.readyState) {
|
944
|
-
case 'loading':
|
945
|
-
document.addEventListener('DOMContentLoaded', callback, { once: true });
|
946
|
-
break;
|
947
|
-
|
948
|
-
case 'interactive':
|
949
|
-
case 'complete':
|
950
|
-
setTimeout(callback, 0);
|
951
|
-
break;
|
952
|
-
|
953
|
-
default:
|
954
|
-
console.warn('Unexpected document.readyState:', document.readyState);
|
955
|
-
setTimeout(callback, 0);
|
956
|
-
}
|
957
|
-
}
|
958
|
-
|
959
|
-
/**
|
960
|
-
* Dynamically imports modules based on configuration
|
961
|
-
*
|
962
|
-
* @param {Array<ImportConfig>} imports - Array of import configurations
|
963
|
-
* @returns {Promise<Array<any>>} Loaded modules
|
964
|
-
*/
|
965
|
-
function loadAsyncImports(imports = []) {
|
966
|
-
const loadInlinePlugin = async ({ name, code }) => {
|
967
|
-
const module = await import(`data:text/javascript,${encodeURIComponent(code)}`);
|
968
|
-
|
969
|
-
if (!module.default) {
|
970
|
-
throw new Error(`Inline plugin "${name}" must export a default class/function!`);
|
971
|
-
}
|
972
|
-
|
973
|
-
return module.default;
|
974
|
-
};
|
975
|
-
|
976
|
-
const loadExternalPlugin = async ({ import_name, import_as, window_name }) => {
|
977
|
-
if (window_name) {
|
978
|
-
if (!Object.prototype.hasOwnProperty.call(window, window_name)) {
|
979
|
-
throw new Error(
|
980
|
-
`Plugin window['${window_name}'] not found in global scope. ` +
|
981
|
-
'Please ensure the plugin is loaded before CKEditor initialization.'
|
982
|
-
);
|
983
|
-
}
|
984
|
-
|
985
|
-
return window[window_name];
|
986
|
-
}
|
987
|
-
|
988
|
-
const module = await import(import_name);
|
989
|
-
const imported = module[import_as || 'default'];
|
990
|
-
|
991
|
-
if (!imported) {
|
992
|
-
throw new Error(`Plugin "${import_as}" not found in the ESM module "${import_name}"!`);
|
993
|
-
}
|
994
|
-
|
995
|
-
return imported;
|
996
|
-
};
|
997
|
-
|
998
|
-
return Promise.all(imports.map(item => {
|
999
|
-
switch(item.type) {
|
1000
|
-
case 'inline':
|
1001
|
-
return loadInlinePlugin(item);
|
1002
|
-
|
1003
|
-
case 'external':
|
1004
|
-
default:
|
1005
|
-
return loadExternalPlugin(item);
|
1006
|
-
}
|
1007
|
-
}));
|
1008
|
-
}
|
1009
|
-
|
1010
|
-
|
1011
|
-
/**
|
1012
|
-
* Checks if a key is safe to use in configuration objects to prevent prototype pollution.
|
1013
|
-
*
|
1014
|
-
* @param {string} key - Key name to check
|
1015
|
-
* @returns {boolean} True if key is safe to use.
|
1016
|
-
*/
|
1017
|
-
function isSafeKey(key) {
|
1018
|
-
return typeof key === 'string' &&
|
1019
|
-
key !== '__proto__' &&
|
1020
|
-
key !== 'constructor' &&
|
1021
|
-
key !== 'prototype';
|
1022
|
-
}
|
1023
|
-
|
1024
|
-
/**
|
1025
|
-
* Resolves element references in configuration object.
|
1026
|
-
* Looks for objects with { $element: "selector" } format and replaces them with actual elements.
|
1027
|
-
*
|
1028
|
-
* @param {Object} obj - Configuration object to process
|
1029
|
-
* @returns {Object} Processed configuration object with resolved element references
|
1030
|
-
*/
|
1031
|
-
function resolveElementReferences(obj) {
|
1032
|
-
if (!obj || typeof obj !== 'object') {
|
1033
|
-
return obj;
|
1034
|
-
}
|
1035
|
-
|
1036
|
-
if (Array.isArray(obj)) {
|
1037
|
-
return obj.map(item => resolveElementReferences(item));
|
1038
|
-
}
|
1039
|
-
|
1040
|
-
const result = Object.create(null);
|
1041
|
-
|
1042
|
-
for (const key of Object.getOwnPropertyNames(obj)) {
|
1043
|
-
if (!isSafeKey(key)) {
|
1044
|
-
console.warn(`Suspicious key "${key}" detected in config, skipping`);
|
1045
|
-
continue;
|
1046
|
-
}
|
1047
|
-
|
1048
|
-
const value = obj[key];
|
1049
|
-
|
1050
|
-
if (value && typeof value === 'object') {
|
1051
|
-
if (value.$element) {
|
1052
|
-
const selector = value.$element;
|
1053
|
-
|
1054
|
-
if (typeof selector !== 'string') {
|
1055
|
-
console.warn(`Invalid selector type for "${key}", expected string`);
|
1056
|
-
continue;
|
1057
|
-
}
|
1058
|
-
|
1059
|
-
const element = document.querySelector(selector);
|
1060
|
-
|
1061
|
-
if (!element) {
|
1062
|
-
console.warn(`Element not found for selector: ${selector}`);
|
1063
|
-
}
|
1064
|
-
|
1065
|
-
result[key] = element || null;
|
1066
|
-
} else {
|
1067
|
-
result[key] = resolveElementReferences(value);
|
1068
|
-
}
|
1069
|
-
} else {
|
1070
|
-
result[key] = value;
|
1071
|
-
}
|
1072
|
-
}
|
1073
|
-
|
1074
|
-
return result;
|
1075
|
-
}
|
1076
|
-
|
1077
|
-
/**
|
1078
|
-
* Custom element that provides shared CKEditor context for multiple editors.
|
1079
|
-
*
|
1080
|
-
* @returns {String} unique id
|
1081
|
-
*/
|
1082
|
-
function uid() {
|
1083
|
-
return Math.random().toString(36).substring(2);
|
1084
|
-
}
|
1085
|
-
|
1086
714
|
customElements.define('ckeditor-component', CKEditorComponent);
|
1087
|
-
customElements.define('ckeditor-editable-component', CKEditorEditableComponent);
|
1088
|
-
customElements.define('ckeditor-ui-part-component', CKEditorUIPartComponent);
|
1089
|
-
customElements.define('ckeditor-context-component', CKEditorContextComponent);
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CKEditorUIPartComponent extends HTMLElement {
|
2
|
+
/**
|
3
|
+
* Lifecycle callback when element is added to DOM
|
4
|
+
* Adds the toolbar to the editor UI
|
5
|
+
*/
|
6
|
+
connectedCallback() {
|
7
|
+
execIfDOMReady(async () => {
|
8
|
+
const uiPart = this.getAttribute('name');
|
9
|
+
const editor = await this.#queryEditorElement().instancePromise.promise;
|
10
|
+
|
11
|
+
this.appendChild(editor.ui.view[uiPart].element);
|
12
|
+
});
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Finds the parent editor component
|
17
|
+
*
|
18
|
+
* @private
|
19
|
+
* @returns {CKEditorComponent|null} Parent editor component or null if not found
|
20
|
+
*/
|
21
|
+
#queryEditorElement() {
|
22
|
+
return this.closest('ckeditor-component') || document.body.querySelector('ckeditor-component');
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
customElements.define('ckeditor-ui-part-component', CKEditorUIPartComponent);
|