1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import { Plugin } from 'ckeditor5/src/core.js';
|
9 | import { ViewModel, createDropdown, addListToDropdown, MenuBarMenuListItemView, MenuBarMenuListView, MenuBarMenuView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
|
10 | import { Collection } from 'ckeditor5/src/utils.js';
|
11 | import { getLocalizedOptions } from './utils.js';
|
12 | import '../theme/heading.css';
|
13 |
|
14 |
|
15 |
|
16 | export default class HeadingUI extends Plugin {
|
17 | |
18 |
|
19 |
|
20 | static get pluginName() {
|
21 | return 'HeadingUI';
|
22 | }
|
23 | |
24 |
|
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 |
|
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 |
|
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 |
|
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 |
|
104 | if (!titles[whichModel]) {
|
105 | return accessibleLabel;
|
106 | }
|
107 | return `${titles[whichModel]}, ${accessibleLabel}`;
|
108 | });
|
109 |
|
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 | }
|