UNPKG

@ckeditor/ckeditor5-heading

Version:

Headings feature for CKEditor 5.

172 lines (171 loc) • 7.18 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module heading/headingui */ import { Plugin } from 'ckeditor5/src/core.js'; import { ViewModel, createDropdown, addListToDropdown, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js'; import { Collection } from 'ckeditor5/src/utils.js'; import { getLocalizedOptions } from './utils.js'; import '../theme/heading.css'; /** * The headings UI feature. It introduces the `headings` dropdown. */ export default class HeadingUI extends Plugin { /** * @inheritDoc */ static get pluginName() { return 'HeadingUI'; } /** * @inheritDoc */ static get isOfficialPlugin() { return true; } /** * @inheritDoc */ init() { const editor = this.editor; const t = editor.t; const options = getLocalizedOptions(editor); const defaultTitle = t('Choose heading'); const accessibleLabel = t('Heading'); // Register UI component. editor.ui.componentFactory.add('heading', locale => { const titles = {}; const itemDefinitions = new Collection(); const headingCommand = editor.commands.get('heading'); const paragraphCommand = editor.commands.get('paragraph'); const commands = [headingCommand]; for (const option of options) { const def = { type: 'button', model: new ViewModel({ label: option.title, class: option.class, role: 'menuitemradio', withText: true }) }; if (option.model === 'paragraph') { def.model.bind('isOn').to(paragraphCommand, 'value'); def.model.set('commandName', 'paragraph'); commands.push(paragraphCommand); } else { def.model.bind('isOn').to(headingCommand, 'value', value => value === option.model); def.model.set({ commandName: 'heading', commandValue: option.model }); } // Add the option to the collection. itemDefinitions.add(def); titles[option.model] = option.title; } const dropdownView = createDropdown(locale); addListToDropdown(dropdownView, itemDefinitions, { ariaLabel: accessibleLabel, role: 'menu' }); dropdownView.buttonView.set({ ariaLabel: accessibleLabel, ariaLabelledBy: undefined, isOn: false, withText: true, tooltip: accessibleLabel }); dropdownView.extendTemplate({ attributes: { class: [ 'ck-heading-dropdown' ] } }); dropdownView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => { return areEnabled.some(isEnabled => isEnabled); }); dropdownView.buttonView.bind('label').to(headingCommand, 'value', paragraphCommand, 'value', (heading, paragraph) => { const whichModel = paragraph ? 'paragraph' : heading; if (typeof whichModel === 'boolean') { return defaultTitle; } // If none of the commands is active, display default title. if (!titles[whichModel]) { return defaultTitle; } return titles[whichModel]; }); dropdownView.buttonView.bind('ariaLabel').to(headingCommand, 'value', paragraphCommand, 'value', (heading, paragraph) => { const whichModel = paragraph ? 'paragraph' : heading; if (typeof whichModel === 'boolean') { return accessibleLabel; } // If none of the commands is active, display default title. if (!titles[whichModel]) { return accessibleLabel; } return `${titles[whichModel]}, ${accessibleLabel}`; }); // Execute command when an item from the dropdown is selected. this.listenTo(dropdownView, 'execute', evt => { const { commandName, commandValue } = evt.source; editor.execute(commandName, commandValue ? { value: commandValue } : undefined); editor.editing.view.focus(); }); return dropdownView; }); editor.ui.componentFactory.add('menuBar:heading', locale => { const menuView = new MenuBarMenuView(locale); const headingCommand = editor.commands.get('heading'); const paragraphCommand = editor.commands.get('paragraph'); const commands = [headingCommand]; const listView = new MenuBarMenuListView(locale); menuView.set({ class: 'ck-heading-dropdown' }); listView.set({ ariaLabel: t('Heading'), role: 'menu' }); menuView.buttonView.set({ label: t('Heading') }); menuView.panelView.children.add(listView); for (const option of options) { const listItemView = new MenuBarMenuListItemView(locale, menuView); const buttonView = new MenuBarMenuListItemButtonView(locale); listItemView.children.add(buttonView); listView.items.add(listItemView); buttonView.set({ isToggleable: true, label: option.title, role: 'menuitemradio', class: option.class }); buttonView.delegate('execute').to(menuView); buttonView.on('execute', () => { const commandName = option.model === 'paragraph' ? 'paragraph' : 'heading'; editor.execute(commandName, { value: option.model }); editor.editing.view.focus(); }); if (option.model === 'paragraph') { buttonView.bind('isOn').to(paragraphCommand, 'value'); commands.push(paragraphCommand); } else { buttonView.bind('isOn').to(headingCommand, 'value', value => value === option.model); } } menuView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => { return areEnabled.some(isEnabled => isEnabled); }); return menuView; }); } }