Problem/Motivation
After Drupal 10 CKEditor 5 custom module creating according to available manuals in final step you may get browser console error "toolbarview-item-unavailable"
Steps to reproduce
Try to create simple Drupal 10 CKEditor 5 module. For example module, that add to CKE 5 button to wrapping text with small HTML tag.
Manuals:
1. https://www.tothenew.com/blog/how-to-create-custom-plugin-in-drupal-cked...
2. https://lembergsolutions.com/blog/how-integrate-ckeditor5-plugin-drupal-... (attention to starter template, please)
Create folder for your custom module in /web/modules/custom/small
Files and folders structure:
small/
├── js/
│ └── plugins/
│ └── small/
│ ├── src/
│ │ ├── index.js
│ │ ├── small.js
│ │ ├── smallediting.js
│ │ ├── smallui.js
│ │ └── utils.js
│ └── icon.svg
├── dist/
│ └── small.js ← Generated by Webpack
├── small.info.yml
├── small.libraries.yml
├── small.ckeditor5.yml
├── package.json
└── webpack.config.js
small.info.yml:
name: 'CKEditor5 Small Text'
type: module
description: 'Adds a <small> text style button to CKEditor 5.'
core_version_requirement: ^10
package: 'Custom'
dependencies:
- ckeditor5
small.libraries.yml:
ckeditor5_small:
js:
dist/small.js: {}
dependencies:
- core/drupal
small.ckeditor5.yml:
small_small:
ckeditor5:
plugins:
- Small: {}
drupal:
label: Small
library: small/ckeditor5_small
admin_library: small/ckeditor5_small
toolbar_items:
small:
label: Small
elements:
- '<small>'
package.json:
Maybe you will have some errors after "npm run build", just install package via command "npm install --save-dev your-package-name"
{
"name": "ckeditor5-small-plugin",
"version": "1.0.0",
"description": "CKEditor 5 Small Text Plugin for Drupal",
"main": "dist/small.js",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"css-loader": "^7.1.2",
"postcss-custom-properties": "^14.0.4",
"postcss-import": "^16.1.0",
"postcss-loader": "^8.1.1",
"postcss-mixins": "^11.0.3",
"postcss-nesting": "^13.0.1",
"raw-loader": "^4.0.2",
"style-loader": "^4.0.0",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@ckeditor/ckeditor5-core": "^45.0.0",
"@ckeditor/ckeditor5-engine": "^45.0.0",
"@ckeditor/ckeditor5-ui": "^45.0.0"
}
}
webpack.config.js:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './js/plugins/small/src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'small.js',
library: 'Small',
libraryTarget: 'umd', // <- This is the key!
},
module: {
rules: [
{
test: /\.svg$/,
use: ['raw-loader']
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
'postcss-loader'
]
}
]
},
resolve: {
extensions: ['.js', '.css', '.svg']
},
externals: {
'@ckeditor/ckeditor5-core': {
commonjs: '@ckeditor/ckeditor5-core',
commonjs2: '@ckeditor/ckeditor5-core',
amd: '@ckeditor/ckeditor5-core',
root: ['CKEditor5', 'core']
},
'@ckeditor/ckeditor5-core/src/plugin': {
commonjs: '@ckeditor/ckeditor5-core/src/plugin',
commonjs2: '@ckeditor/ckeditor5-core/src/plugin',
amd: '@ckeditor/ckeditor5-core/src/plugin',
root: ['CKEditor5', 'core', 'Plugin']
},
'@ckeditor/ckeditor5-engine': {
commonjs: '@ckeditor/ckeditor5-engine',
commonjs2: '@ckeditor/ckeditor5-engine',
amd: '@ckeditor/ckeditor5-engine',
root: ['CKEditor5', 'engine']
},
'@ckeditor/ckeditor5-ui': {
commonjs: '@ckeditor/ckeditor5-ui',
commonjs2: '@ckeditor/ckeditor5-ui',
amd: '@ckeditor/ckeditor5-ui',
root: ['CKEditor5', 'ui']
},
'@ckeditor/ckeditor5-ui/src/button/buttonview': {
commonjs: '@ckeditor/ckeditor5-ui/src/button/buttonview',
commonjs2: '@ckeditor/ckeditor5-ui/src/button/buttonview',
amd: '@ckeditor/ckeditor5-ui/src/button/buttonview',
root: ['CKEditor5', 'ui', 'ButtonView']
}
},
mode: 'production'
};
postcss.config.js:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-mixins'),
require('postcss-nesting'),
require('postcss-custom-properties')
]
};
\js\plugins\small\src\index.js:
import Small from './small';
export default {
Small,
};
\js\plugins\small\src\small.js:
import SmallEditing from './smallediting.js';
import SmallUI from './smallui.js';
import { Plugin } from 'ckeditor5/src/core';
export default class Small extends Plugin {
static get requires() {
return [SmallEditing, SmallUI];
}
}
\js\plugins\small\src\smallediting.js:
import { Plugin } from 'ckeditor5/src/core';
export default class SmallEditing extends Plugin {
init() {
const editor = this.editor;
// Allow <small> element in schema
editor.model.schema.extend('$text', { allowAttributes: 'small' });
// Upcast from view to model
editor.conversion.for('upcast').elementToAttribute({
view: 'small',
model: {
key: 'small',
value: true
}
});
// Downcast from model to view
editor.conversion.for('downcast').attributeToElement({
model: 'small',
view: (modelAttributeValue, writer) => {
if (!modelAttributeValue) {
return;
}
return writer.createAttributeElement('small', {}, { priority: 5 });
}
});
}
}
\js\plugins\small\src\smallui.js:
import { Plugin } from 'ckeditor5/src/core';
import { createButton } from './utils.js';
import icon from '../icon.svg';
export default class SmallUI extends Plugin {
init() {
const editor = this.editor;
const t = editor.t;
editor.ui.componentFactory.add('small', locale => {
const command = editor.commands.get('small');
const button = createButton(locale, {
label: t('Small'),
icon,
tooltip: true
});
button.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
this.listenTo(button, 'execute', () => {
editor.execute('small');
editor.editing.view.focus();
});
return button;
});
editor.commands.add('small', {
execute() {
const model = editor.model;
const selection = model.document.selection;
model.change(writer => {
const isApplied = selection.hasAttribute('small');
if (isApplied) {
writer.removeSelectionAttribute('small');
} else {
writer.setSelectionAttribute('small', true);
}
});
},
refresh() {
const model = editor.model;
const selection = model.document.selection;
this.value = selection.hasAttribute('small');
this.isEnabled = model.schema.checkAttributeInSelection(selection, 'small');
}
});
}
}
\js\plugins\small\src\utils.js:
import { ButtonView } from 'ckeditor5/src/ui';
export function createButton(locale, options) {
const view = new ButtonView(locale);
view.set({
label: options.label,
icon: options.icon,
tooltip: options.tooltip,
withText: true,
});
return view;
}
So. After you can run "npm install" (maybe will be needed "npm install --save-dev your-package-name"). After that "npm run build" (to get compiled js file in "dist" folder. After that you can install the module. After installation you need remove button to active CKEditor panel /admin/config/content/formats and save format.
Please, clear all cache after that.
Now, you can try to edit node, for example at /node/1/edit - The CKEditor is enabled in Body field, but button of custom module "Small" is not showing.
In browser console I have one warning: toolbarview-item-unavailable (item: "small").
But, in small.ckeditor5.yml toolbar_items section is presented:
toolbar_items:
small:
label: Small
Name of classes in js files was created according to template (index, class, classediting, classui, utils).
Compiled small,js is loaded in tabs "Network" and "Sources". Where is no "small" in CKEditor 5 Inspector (CKEditor 5 Dev tools module).
How I can resolve this problem?