I’m developing a custom CKEditor 5 plugin for Drupal 10.4.5. The JavaScript for the plugin seems to be loading correctly, and I’ve defined it in my module’s .ckeditor5.yml file. The plugin aims to add a “Small Text” button to the toolbar, wrapping selected text in tags.
However, I’m encountering the following JavaScript error in the browser console when trying to load the CKEditor 5 instance (both on node edit pages and where a CKEditor 5 instance might be present elsewhere):
Uncaught TypeError: plugins.map is not a function
at selectPlugins (ckeditor5.js?swXXXX:XXX)
at Object.attach (ckeditor5.js?swXXXX:XXX)
... (rest of the stack trace)
Here are the relevant parts of my module’s files:
Module folder is here: /web/modules/custom/small_text_ckeditor
/web/modules/custom/small_text_ckeditor/small_text_ckeditor.info.yml:
name: Small Text CKEditor
type: module
description: Provides a 'Small Text' plugin for CKEditor 5.
core_version_requirement: ^10
dependencies:
- drupal:ckeditor5
/web/modules/custom/small_text_ckeditor/small_text_ckeditor.libraries.yml:
small_text:
version: VERSION
js:
js/small_text.js: {}
dependencies:
- core/ckeditor5
- core/ckeditor5-plugin
/web/modules/custom/small_text_ckeditor/small_text_ckeditor.ckeditor5.yml:
small_text_ckeditor_small_text:
ckeditor5:
plugins:
SmallText: {}
drupal:
label: 'Small Text'
library: small_text_ckeditor/small_text
toolbar_items:
smallText:
label: 'Small Text'
elements:
- '<small>'
/web/modules/custom/small_text_ckeditor/js/small_text.js:
/**
* @file
* Defines the 'Small Text' plugin for CKEditor 5.
*/
(function (Drupal) {
Drupal.behaviors.smallTextCKEditor = {
attach: function (context, settings) {
const editorPromise = Drupal.CKEditor5Instances.get('edit-body-0-value');
if (editorPromise) {
editorPromise.then(editor => {
class SmallText {
static get pluginName() {
return 'SmallText';
}
init() {
editor.model.schema.register('small', {
allowWhere: '$text',
isInline: true,
});
editor.conversion.for('downcast').elementToElement({ model: 'small', view: 'small' });
editor.conversion.for('upcast').elementToElement({ view: 'small', model: 'small' });
}
}
editor.commands.add('smallText', {
execute: () => {
const model = editor.model;
const selection = model.document.selection;
model.change(function (writer) {
const ranges = selection.getRanges();
for (const range of ranges) {
const innerText = Array.from(range.getItems('inline'));
const isSmall = innerText.every(item => item.hasAttribute('small'));
for (const item of innerText) {
if (isSmall) {
writer.removeAttribute('small', item);
} else {
writer.setAttribute('small', true, item);
}
}
}
});
},
refresh: function() {
const model = editor.model;
const selection = model.document.selection;
const firstRange = selection.getFirstRange();
if (firstRange) {
const selectedElements = Array.from(firstRange.getItems('inline'));
this.isEnabled = selectedElements.length > 0 || !selection.isCollapsed;
this.value = selectedElements.every(element => element.hasAttribute('small'));
} else {
this.isEnabled = false;
this.value = false;
}
}
});
editor.ui.componentFactory.add('smallText', locale => {
const button = editor.ui.componentFactory.create('button', locale);
button.set({
label: 'Small Text',
icon: '<svg viewBox="0 0 20 20"><text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" style="font-size: 10px;">sm</text></svg>',
tooltip: 'Small Text',
withText: false,
});
this.listenTo(button, 'execute', () => {
editor.execute('smallText');
});
this.listenTo(editor, 'change:selection', () => {
button.isEnabled = editor.commands.get('smallText').isEnabled;
button.isOn = editor.commands.get('smallText').value;
});
return button;
});
});
}
}
};
})(Drupal);
I’ve tried various configurations in the .ckeditor5.yml file, including listing SmallText as an array item, but the plugins.map is not a function error persists.
What is the correct way to declare a custom CKEditor 5 plugin in a Drupal 10 module to avoid this error?