UNPKG

7.11 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5/**
6 * @module heading/headingui
7 */
8import { Plugin } from 'ckeditor5/src/core.js';
9import { ViewModel, createDropdown, addListToDropdown, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
10import { Collection } from 'ckeditor5/src/utils.js';
11import { getLocalizedOptions } from './utils.js';
12import '../theme/heading.css';
13/**
14 * The headings UI feature. It introduces the `headings` dropdown.
15 */
16export default class HeadingUI extends Plugin {
17 /**
18 * @inheritDoc
19 */
20 static get pluginName() {
21 return 'HeadingUI';
22 }
23 /**
24 * @inheritDoc
25 */
26 init() {
27 const editor = this.editor;
28 const t = editor.t;
29 const options = getLocalizedOptions(editor);
30 const defaultTitle = t('Choose heading');
31 const accessibleLabel = t('Heading');
32 // Register UI component.
33 editor.ui.componentFactory.add('heading', locale => {
34 const titles = {};
35 const itemDefinitions = new Collection();
36 const headingCommand = editor.commands.get('heading');
37 const paragraphCommand = editor.commands.get('paragraph');
38 const commands = [headingCommand];
39 for (const option of options) {
40 const def = {
41 type: 'button',
42 model: new ViewModel({
43 label: option.title,
44 class: option.class,
45 role: 'menuitemradio',
46 withText: true
47 })
48 };
49 if (option.model === 'paragraph') {
50 def.model.bind('isOn').to(paragraphCommand, 'value');
51 def.model.set('commandName', 'paragraph');
52 commands.push(paragraphCommand);
53 }
54 else {
55 def.model.bind('isOn').to(headingCommand, 'value', value => value === option.model);
56 def.model.set({
57 commandName: 'heading',
58 commandValue: option.model
59 });
60 }
61 // Add the option to the collection.
62 itemDefinitions.add(def);
63 titles[option.model] = option.title;
64 }
65 const dropdownView = createDropdown(locale);
66 addListToDropdown(dropdownView, itemDefinitions, {
67 ariaLabel: accessibleLabel,
68 role: 'menu'
69 });
70 dropdownView.buttonView.set({
71 ariaLabel: accessibleLabel,
72 ariaLabelledBy: undefined,
73 isOn: false,
74 withText: true,
75 tooltip: accessibleLabel
76 });
77 dropdownView.extendTemplate({
78 attributes: {
79 class: [
80 'ck-heading-dropdown'
81 ]
82 }
83 });
84 dropdownView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => {
85 return areEnabled.some(isEnabled => isEnabled);
86 });
87 dropdownView.buttonView.bind('label').to(headingCommand, 'value', paragraphCommand, 'value', (heading, paragraph) => {
88 const whichModel = paragraph ? 'paragraph' : heading;
89 if (typeof whichModel === 'boolean') {
90 return defaultTitle;
91 }
92 // If none of the commands is active, display default title.
93 if (!titles[whichModel]) {
94 return defaultTitle;
95 }
96 return titles[whichModel];
97 });
98 dropdownView.buttonView.bind('ariaLabel').to(headingCommand, 'value', paragraphCommand, 'value', (heading, paragraph) => {
99 const whichModel = paragraph ? 'paragraph' : heading;
100 if (typeof whichModel === 'boolean') {
101 return accessibleLabel;
102 }
103 // If none of the commands is active, display default title.
104 if (!titles[whichModel]) {
105 return accessibleLabel;
106 }
107 return `${titles[whichModel]}, ${accessibleLabel}`;
108 });
109 // Execute command when an item from the dropdown is selected.
110 this.listenTo(dropdownView, 'execute', evt => {
111 const { commandName, commandValue } = evt.source;
112 editor.execute(commandName, commandValue ? { value: commandValue } : undefined);
113 editor.editing.view.focus();
114 });
115 return dropdownView;
116 });
117 editor.ui.componentFactory.add('menuBar:heading', locale => {
118 const menuView = new MenuBarMenuView(locale);
119 const headingCommand = editor.commands.get('heading');
120 const paragraphCommand = editor.commands.get('paragraph');
121 const commands = [headingCommand];
122 const listView = new MenuBarMenuListView(locale);
123 menuView.set({
124 class: 'ck-heading-dropdown'
125 });
126 listView.set({
127 ariaLabel: t('Heading'),
128 role: 'menu'
129 });
130 menuView.buttonView.set({
131 label: t('Heading')
132 });
133 menuView.panelView.children.add(listView);
134 for (const option of options) {
135 const listItemView = new MenuBarMenuListItemView(locale, menuView);
136 const buttonView = new MenuBarMenuListItemButtonView(locale);
137 listItemView.children.add(buttonView);
138 listView.items.add(listItemView);
139 buttonView.set({
140 label: option.title,
141 role: 'menuitemradio',
142 class: option.class
143 });
144 buttonView.bind('ariaChecked').to(buttonView, 'isOn');
145 buttonView.delegate('execute').to(menuView);
146 buttonView.on('execute', () => {
147 const commandName = option.model === 'paragraph' ? 'paragraph' : 'heading';
148 editor.execute(commandName, { value: option.model });
149 editor.editing.view.focus();
150 });
151 if (option.model === 'paragraph') {
152 buttonView.bind('isOn').to(paragraphCommand, 'value');
153 commands.push(paragraphCommand);
154 }
155 else {
156 buttonView.bind('isOn').to(headingCommand, 'value', value => value === option.model);
157 }
158 }
159 menuView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => {
160 return areEnabled.some(isEnabled => isEnabled);
161 });
162 return menuView;
163 });
164 }
165}