UNPKG

11.9 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, 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 table/tableui
7 */
8import { Plugin } from 'ckeditor5/src/core';
9import { addListToDropdown, createDropdown, Model, SplitButtonView, SwitchButtonView } from 'ckeditor5/src/ui';
10import { Collection } from 'ckeditor5/src/utils';
11import InsertTableView from './ui/inserttableview';
12import tableIcon from './../theme/icons/table.svg';
13import tableColumnIcon from './../theme/icons/table-column.svg';
14import tableRowIcon from './../theme/icons/table-row.svg';
15import tableMergeCellIcon from './../theme/icons/table-merge-cell.svg';
16/**
17 * The table UI plugin. It introduces:
18 *
19 * * The `'insertTable'` dropdown,
20 * * The `'tableColumn'` dropdown,
21 * * The `'tableRow'` dropdown,
22 * * The `'mergeTableCells'` split button.
23 *
24 * The `'tableColumn'`, `'tableRow'` and `'mergeTableCells'` dropdowns work best with {@link module:table/tabletoolbar~TableToolbar}.
25 */
26export default class TableUI extends Plugin {
27 /**
28 * @inheritDoc
29 */
30 static get pluginName() {
31 return 'TableUI';
32 }
33 /**
34 * @inheritDoc
35 */
36 init() {
37 const editor = this.editor;
38 const t = this.editor.t;
39 const contentLanguageDirection = editor.locale.contentLanguageDirection;
40 const isContentLtr = contentLanguageDirection === 'ltr';
41 editor.ui.componentFactory.add('insertTable', locale => {
42 const command = editor.commands.get('insertTable');
43 const dropdownView = createDropdown(locale);
44 dropdownView.bind('isEnabled').to(command);
45 // Decorate dropdown's button.
46 dropdownView.buttonView.set({
47 icon: tableIcon,
48 label: t('Insert table'),
49 tooltip: true
50 });
51 let insertTableView;
52 dropdownView.on('change:isOpen', () => {
53 if (insertTableView) {
54 return;
55 }
56 // Prepare custom view for dropdown's panel.
57 insertTableView = new InsertTableView(locale);
58 dropdownView.panelView.children.add(insertTableView);
59 insertTableView.delegate('execute').to(dropdownView);
60 dropdownView.on('execute', () => {
61 editor.execute('insertTable', { rows: insertTableView.rows, columns: insertTableView.columns });
62 editor.editing.view.focus();
63 });
64 });
65 return dropdownView;
66 });
67 editor.ui.componentFactory.add('tableColumn', locale => {
68 const options = [
69 {
70 type: 'switchbutton',
71 model: {
72 commandName: 'setTableColumnHeader',
73 label: t('Header column'),
74 bindIsOn: true
75 }
76 },
77 { type: 'separator' },
78 {
79 type: 'button',
80 model: {
81 commandName: isContentLtr ? 'insertTableColumnLeft' : 'insertTableColumnRight',
82 label: t('Insert column left')
83 }
84 },
85 {
86 type: 'button',
87 model: {
88 commandName: isContentLtr ? 'insertTableColumnRight' : 'insertTableColumnLeft',
89 label: t('Insert column right')
90 }
91 },
92 {
93 type: 'button',
94 model: {
95 commandName: 'removeTableColumn',
96 label: t('Delete column')
97 }
98 },
99 {
100 type: 'button',
101 model: {
102 commandName: 'selectTableColumn',
103 label: t('Select column')
104 }
105 }
106 ];
107 return this._prepareDropdown(t('Column'), tableColumnIcon, options, locale);
108 });
109 editor.ui.componentFactory.add('tableRow', locale => {
110 const options = [
111 {
112 type: 'switchbutton',
113 model: {
114 commandName: 'setTableRowHeader',
115 label: t('Header row'),
116 bindIsOn: true
117 }
118 },
119 { type: 'separator' },
120 {
121 type: 'button',
122 model: {
123 commandName: 'insertTableRowAbove',
124 label: t('Insert row above')
125 }
126 },
127 {
128 type: 'button',
129 model: {
130 commandName: 'insertTableRowBelow',
131 label: t('Insert row below')
132 }
133 },
134 {
135 type: 'button',
136 model: {
137 commandName: 'removeTableRow',
138 label: t('Delete row')
139 }
140 },
141 {
142 type: 'button',
143 model: {
144 commandName: 'selectTableRow',
145 label: t('Select row')
146 }
147 }
148 ];
149 return this._prepareDropdown(t('Row'), tableRowIcon, options, locale);
150 });
151 editor.ui.componentFactory.add('mergeTableCells', locale => {
152 const options = [
153 {
154 type: 'button',
155 model: {
156 commandName: 'mergeTableCellUp',
157 label: t('Merge cell up')
158 }
159 },
160 {
161 type: 'button',
162 model: {
163 commandName: isContentLtr ? 'mergeTableCellRight' : 'mergeTableCellLeft',
164 label: t('Merge cell right')
165 }
166 },
167 {
168 type: 'button',
169 model: {
170 commandName: 'mergeTableCellDown',
171 label: t('Merge cell down')
172 }
173 },
174 {
175 type: 'button',
176 model: {
177 commandName: isContentLtr ? 'mergeTableCellLeft' : 'mergeTableCellRight',
178 label: t('Merge cell left')
179 }
180 },
181 { type: 'separator' },
182 {
183 type: 'button',
184 model: {
185 commandName: 'splitTableCellVertically',
186 label: t('Split cell vertically')
187 }
188 },
189 {
190 type: 'button',
191 model: {
192 commandName: 'splitTableCellHorizontally',
193 label: t('Split cell horizontally')
194 }
195 }
196 ];
197 return this._prepareMergeSplitButtonDropdown(t('Merge cells'), tableMergeCellIcon, options, locale);
198 });
199 }
200 /**
201 * Creates a dropdown view from a set of options.
202 *
203 * @param label The dropdown button label.
204 * @param icon An icon for the dropdown button.
205 * @param options The list of options for the dropdown.
206 */
207 _prepareDropdown(label, icon, options, locale) {
208 const editor = this.editor;
209 const dropdownView = createDropdown(locale);
210 const commands = this._fillDropdownWithListOptions(dropdownView, options);
211 // Decorate dropdown's button.
212 dropdownView.buttonView.set({
213 label,
214 icon,
215 tooltip: true
216 });
217 // Make dropdown button disabled when all options are disabled.
218 dropdownView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => {
219 return areEnabled.some(isEnabled => isEnabled);
220 });
221 this.listenTo(dropdownView, 'execute', evt => {
222 editor.execute(evt.source.commandName);
223 // Toggling a switch button view should not move the focus to the editable.
224 if (!(evt.source instanceof SwitchButtonView)) {
225 editor.editing.view.focus();
226 }
227 });
228 return dropdownView;
229 }
230 /**
231 * Creates a dropdown view with a {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} for
232 * merge (and split)–related commands.
233 *
234 * @param label The dropdown button label.
235 * @param icon An icon for the dropdown button.
236 * @param options The list of options for the dropdown.
237 */
238 _prepareMergeSplitButtonDropdown(label, icon, options, locale) {
239 const editor = this.editor;
240 const dropdownView = createDropdown(locale, SplitButtonView);
241 const mergeCommandName = 'mergeTableCells';
242 // Main command.
243 const mergeCommand = editor.commands.get(mergeCommandName);
244 // Subcommands in the dropdown.
245 const commands = this._fillDropdownWithListOptions(dropdownView, options);
246 dropdownView.buttonView.set({
247 label,
248 icon,
249 tooltip: true,
250 isEnabled: true
251 });
252 // Make dropdown button disabled when all options are disabled together with the main command.
253 dropdownView.bind('isEnabled').toMany([mergeCommand, ...commands], 'isEnabled', (...areEnabled) => {
254 return areEnabled.some(isEnabled => isEnabled);
255 });
256 // Merge selected table cells when the main part of the split button is clicked.
257 this.listenTo(dropdownView.buttonView, 'execute', () => {
258 editor.execute(mergeCommandName);
259 editor.editing.view.focus();
260 });
261 // Execute commands for events coming from the list in the dropdown panel.
262 this.listenTo(dropdownView, 'execute', evt => {
263 editor.execute(evt.source.commandName);
264 editor.editing.view.focus();
265 });
266 return dropdownView;
267 }
268 /**
269 * Injects a {@link module:ui/list/listview~ListView} into the passed dropdown with buttons
270 * which execute editor commands as configured in passed options.
271 *
272 * @param options The list of options for the dropdown.
273 * @returns Commands the list options are interacting with.
274 */
275 _fillDropdownWithListOptions(dropdownView, options) {
276 const editor = this.editor;
277 const commands = [];
278 const itemDefinitions = new Collection();
279 for (const option of options) {
280 addListOption(option, editor, commands, itemDefinitions);
281 }
282 addListToDropdown(dropdownView, itemDefinitions);
283 return commands;
284 }
285}
286/**
287 * Adds an option to a list view.
288 *
289 * @param option A configuration option.
290 * @param commands The list of commands to update.
291 * @param itemDefinitions A collection of dropdown items to update with the given option.
292 */
293function addListOption(option, editor, commands, itemDefinitions) {
294 if (option.type === 'button' || option.type === 'switchbutton') {
295 const model = option.model = new Model(option.model);
296 const { commandName, bindIsOn } = option.model;
297 const command = editor.commands.get(commandName);
298 commands.push(command);
299 model.set({ commandName });
300 model.bind('isEnabled').to(command);
301 if (bindIsOn) {
302 model.bind('isOn').to(command, 'value');
303 }
304 model.set({
305 withText: true
306 });
307 }
308 itemDefinitions.add(option);
309}