Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Backbone View providing the aural view of CKEditor keyboard UX configuration.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@0
|
6 (function ($, Drupal, Backbone, _) {
|
Chris@0
|
7 Drupal.ckeditor.KeyboardView = Backbone.View.extend(/** @lends Drupal.ckeditor.KeyboardView# */{
|
Chris@0
|
8
|
Chris@0
|
9 /**
|
Chris@0
|
10 * Backbone View for CKEditor toolbar configuration; keyboard UX.
|
Chris@0
|
11 *
|
Chris@0
|
12 * @constructs
|
Chris@0
|
13 *
|
Chris@0
|
14 * @augments Backbone.View
|
Chris@0
|
15 */
|
Chris@0
|
16 initialize() {
|
Chris@0
|
17 // Add keyboard arrow support.
|
Chris@0
|
18 this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this));
|
Chris@0
|
19 this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this));
|
Chris@0
|
20 },
|
Chris@0
|
21
|
Chris@0
|
22 /**
|
Chris@0
|
23 * @inheritdoc
|
Chris@0
|
24 */
|
Chris@0
|
25 render() {
|
Chris@0
|
26 },
|
Chris@0
|
27
|
Chris@0
|
28 /**
|
Chris@0
|
29 * Handles keypresses on a CKEditor configuration button.
|
Chris@0
|
30 *
|
Chris@0
|
31 * @param {jQuery.Event} event
|
Chris@0
|
32 * The keypress event triggered.
|
Chris@0
|
33 */
|
Chris@0
|
34 onPressButton(event) {
|
Chris@0
|
35 const upDownKeys = [
|
Chris@0
|
36 38, // Up arrow.
|
Chris@0
|
37 63232, // Safari up arrow.
|
Chris@0
|
38 40, // Down arrow.
|
Chris@0
|
39 63233, // Safari down arrow.
|
Chris@0
|
40 ];
|
Chris@0
|
41 const leftRightKeys = [
|
Chris@0
|
42 37, // Left arrow.
|
Chris@0
|
43 63234, // Safari left arrow.
|
Chris@0
|
44 39, // Right arrow.
|
Chris@0
|
45 63235, // Safari right arrow.
|
Chris@0
|
46 ];
|
Chris@0
|
47
|
Chris@0
|
48 // Respond to an enter key press. Prevent the bubbling of the enter key
|
Chris@0
|
49 // press to the button group parent element.
|
Chris@0
|
50 if (event.keyCode === 13) {
|
Chris@0
|
51 event.stopPropagation();
|
Chris@0
|
52 }
|
Chris@0
|
53
|
Chris@0
|
54 // Only take action when a direction key is pressed.
|
Chris@0
|
55 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
|
Chris@0
|
56 let view = this;
|
Chris@0
|
57 let $target = $(event.currentTarget);
|
Chris@0
|
58 let $button = $target.parent();
|
Chris@0
|
59 const $container = $button.parent();
|
Chris@0
|
60 let $group = $button.closest('.ckeditor-toolbar-group');
|
Chris@0
|
61 let $row;
|
Chris@0
|
62 const containerType = $container.data('drupal-ckeditor-button-sorting');
|
Chris@0
|
63 const $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]');
|
Chris@0
|
64 const $activeButtons = this.$el.find('.ckeditor-toolbar-active');
|
Chris@0
|
65 // The current location of the button, just in case it needs to be put
|
Chris@0
|
66 // back.
|
Chris@0
|
67 const $originalGroup = $group;
|
Chris@0
|
68 let dir;
|
Chris@0
|
69
|
Chris@0
|
70 // Move available buttons between their container and the active
|
Chris@0
|
71 // toolbar.
|
Chris@0
|
72 if (containerType === 'source') {
|
Chris@0
|
73 // Move the button to the active toolbar configuration when the down
|
Chris@0
|
74 // or up keys are pressed.
|
Chris@0
|
75 if (_.indexOf([40, 63233], event.keyCode) > -1) {
|
Chris@0
|
76 // Move the button to the first row, first button group index
|
Chris@0
|
77 // position.
|
Chris@0
|
78 $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
|
Chris@0
|
79 }
|
Chris@0
|
80 }
|
Chris@0
|
81 else if (containerType === 'target') {
|
Chris@0
|
82 // Move buttons between sibling buttons in a group and between groups.
|
Chris@0
|
83 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
|
Chris@0
|
84 // Move left.
|
Chris@0
|
85 const $siblings = $container.children();
|
Chris@0
|
86 const index = $siblings.index($button);
|
Chris@0
|
87 if (_.indexOf([37, 63234], event.keyCode) > -1) {
|
Chris@0
|
88 // Move between sibling buttons.
|
Chris@0
|
89 if (index > 0) {
|
Chris@0
|
90 $button.insertBefore($container.children().eq(index - 1));
|
Chris@0
|
91 }
|
Chris@0
|
92 // Move between button groups and rows.
|
Chris@0
|
93 else {
|
Chris@0
|
94 // Move between button groups.
|
Chris@0
|
95 $group = $container.parent().prev();
|
Chris@0
|
96 if ($group.length > 0) {
|
Chris@0
|
97 $group.find('.ckeditor-toolbar-group-buttons').append($button);
|
Chris@0
|
98 }
|
Chris@0
|
99 // Wrap between rows.
|
Chris@0
|
100 else {
|
Chris@0
|
101 $container
|
Chris@0
|
102 .closest('.ckeditor-row')
|
Chris@0
|
103 .prev()
|
Chris@0
|
104 .find('.ckeditor-toolbar-group')
|
Chris@0
|
105 .not('.placeholder')
|
Chris@0
|
106 .find('.ckeditor-toolbar-group-buttons')
|
Chris@0
|
107 .eq(-1)
|
Chris@0
|
108 .append($button);
|
Chris@0
|
109 }
|
Chris@0
|
110 }
|
Chris@0
|
111 }
|
Chris@0
|
112 // Move right.
|
Chris@0
|
113 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
|
Chris@0
|
114 // Move between sibling buttons.
|
Chris@0
|
115 if (index < ($siblings.length - 1)) {
|
Chris@0
|
116 $button.insertAfter($container.children().eq(index + 1));
|
Chris@0
|
117 }
|
Chris@0
|
118 // Move between button groups. Moving right at the end of a row
|
Chris@0
|
119 // will create a new group.
|
Chris@0
|
120 else {
|
Chris@0
|
121 $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button);
|
Chris@0
|
122 }
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125 // Move buttons between rows and the available button set.
|
Chris@0
|
126 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
|
Chris@0
|
127 dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
|
Chris@0
|
128 $row = $container.closest('.ckeditor-row')[dir]();
|
Chris@0
|
129 // Move the button back into the available button set.
|
Chris@0
|
130 if (dir === 'prev' && $row.length === 0) {
|
Chris@0
|
131 // If this is a divider, just destroy it.
|
Chris@0
|
132 if ($button.data('drupal-ckeditor-type') === 'separator') {
|
Chris@0
|
133 $button
|
Chris@0
|
134 .off()
|
Chris@0
|
135 .remove();
|
Chris@0
|
136 // Focus on the first button in the active toolbar.
|
Chris@0
|
137 $activeButtons
|
Chris@0
|
138 .find('.ckeditor-toolbar-group-buttons')
|
Chris@0
|
139 .eq(0)
|
Chris@0
|
140 .children()
|
Chris@0
|
141 .eq(0)
|
Chris@0
|
142 .children()
|
Chris@0
|
143 .trigger('focus');
|
Chris@0
|
144 }
|
Chris@0
|
145 // Otherwise, move it.
|
Chris@0
|
146 else {
|
Chris@0
|
147 $availableButtons.prepend($button);
|
Chris@0
|
148 }
|
Chris@0
|
149 }
|
Chris@0
|
150 else {
|
Chris@0
|
151 $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
|
Chris@0
|
152 }
|
Chris@0
|
153 }
|
Chris@0
|
154 }
|
Chris@0
|
155 // Move dividers between their container and the active toolbar.
|
Chris@0
|
156 else if (containerType === 'dividers') {
|
Chris@0
|
157 // Move the button to the active toolbar configuration when the down
|
Chris@0
|
158 // or up keys are pressed.
|
Chris@0
|
159 if (_.indexOf([40, 63233], event.keyCode) > -1) {
|
Chris@0
|
160 // Move the button to the first row, first button group index
|
Chris@0
|
161 // position.
|
Chris@0
|
162 $button = $button.clone(true);
|
Chris@0
|
163 $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button);
|
Chris@0
|
164 $target = $button.children();
|
Chris@0
|
165 }
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 view = this;
|
Chris@0
|
169 // Attempt to move the button to the new toolbar position.
|
Chris@0
|
170 Drupal.ckeditor.registerButtonMove(this, $button, (result) => {
|
Chris@0
|
171 // Put the button back if the registration failed.
|
Chris@0
|
172 // If the button was in a row, then it was in the active toolbar
|
Chris@0
|
173 // configuration. The button was probably placed in a new group, but
|
Chris@0
|
174 // that action was canceled.
|
Chris@0
|
175 if (!result && $originalGroup) {
|
Chris@0
|
176 $originalGroup.find('.ckeditor-buttons').append($button);
|
Chris@0
|
177 }
|
Chris@0
|
178 // Otherwise refresh the sortables to acknowledge the new button
|
Chris@0
|
179 // positions.
|
Chris@0
|
180 else {
|
Chris@0
|
181 view.$el.find('.ui-sortable').sortable('refresh');
|
Chris@0
|
182 }
|
Chris@0
|
183 // Refocus the target button so that the user can continue from a
|
Chris@0
|
184 // known place.
|
Chris@0
|
185 $target.trigger('focus');
|
Chris@0
|
186 });
|
Chris@0
|
187
|
Chris@0
|
188 event.preventDefault();
|
Chris@0
|
189 event.stopPropagation();
|
Chris@0
|
190 }
|
Chris@0
|
191 },
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * Handles keypresses on a CKEditor configuration group.
|
Chris@0
|
195 *
|
Chris@0
|
196 * @param {jQuery.Event} event
|
Chris@0
|
197 * The keypress event triggered.
|
Chris@0
|
198 */
|
Chris@0
|
199 onPressGroup(event) {
|
Chris@0
|
200 const upDownKeys = [
|
Chris@0
|
201 38, // Up arrow.
|
Chris@0
|
202 63232, // Safari up arrow.
|
Chris@0
|
203 40, // Down arrow.
|
Chris@0
|
204 63233, // Safari down arrow.
|
Chris@0
|
205 ];
|
Chris@0
|
206 const leftRightKeys = [
|
Chris@0
|
207 37, // Left arrow.
|
Chris@0
|
208 63234, // Safari left arrow.
|
Chris@0
|
209 39, // Right arrow.
|
Chris@0
|
210 63235, // Safari right arrow.
|
Chris@0
|
211 ];
|
Chris@0
|
212
|
Chris@0
|
213 // Respond to an enter key press.
|
Chris@0
|
214 if (event.keyCode === 13) {
|
Chris@0
|
215 const view = this;
|
Chris@0
|
216 // Open the group renaming dialog in the next evaluation cycle so that
|
Chris@0
|
217 // this event can be cancelled and the bubbling wiped out. Otherwise,
|
Chris@0
|
218 // Firefox has issues because the page focus is shifted to the dialog
|
Chris@0
|
219 // along with the keydown event.
|
Chris@0
|
220 window.setTimeout(() => {
|
Chris@0
|
221 Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget));
|
Chris@0
|
222 }, 0);
|
Chris@0
|
223 event.preventDefault();
|
Chris@0
|
224 event.stopPropagation();
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 // Respond to direction key presses.
|
Chris@0
|
228 if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) {
|
Chris@0
|
229 const $group = $(event.currentTarget);
|
Chris@0
|
230 const $container = $group.parent();
|
Chris@0
|
231 const $siblings = $container.children();
|
Chris@0
|
232 let index;
|
Chris@0
|
233 let dir;
|
Chris@0
|
234 // Move groups between sibling groups.
|
Chris@0
|
235 if (_.indexOf(leftRightKeys, event.keyCode) > -1) {
|
Chris@0
|
236 index = $siblings.index($group);
|
Chris@0
|
237 // Move left between sibling groups.
|
Chris@0
|
238 if ((_.indexOf([37, 63234], event.keyCode) > -1)) {
|
Chris@0
|
239 if (index > 0) {
|
Chris@0
|
240 $group.insertBefore($siblings.eq(index - 1));
|
Chris@0
|
241 }
|
Chris@0
|
242 // Wrap between rows. Insert the group before the placeholder group
|
Chris@0
|
243 // at the end of the previous row.
|
Chris@0
|
244 else {
|
Chris@0
|
245 const $rowChildElement = $container
|
Chris@0
|
246 .closest('.ckeditor-row')
|
Chris@0
|
247 .prev()
|
Chris@0
|
248 .find('.ckeditor-toolbar-groups')
|
Chris@0
|
249 .children()
|
Chris@0
|
250 .eq(-1);
|
Chris@0
|
251 $group.insertBefore($rowChildElement);
|
Chris@0
|
252 }
|
Chris@0
|
253 }
|
Chris@0
|
254 // Move right between sibling groups.
|
Chris@0
|
255 else if (_.indexOf([39, 63235], event.keyCode) > -1) {
|
Chris@0
|
256 // Move to the right if the next group is not a placeholder.
|
Chris@0
|
257 if (!$siblings.eq(index + 1).hasClass('placeholder')) {
|
Chris@0
|
258 $group.insertAfter($container.children().eq(index + 1));
|
Chris@0
|
259 }
|
Chris@0
|
260 // Wrap group between rows.
|
Chris@0
|
261 else {
|
Chris@0
|
262 $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group);
|
Chris@0
|
263 }
|
Chris@0
|
264 }
|
Chris@0
|
265 }
|
Chris@0
|
266 // Move groups between rows.
|
Chris@0
|
267 else if (_.indexOf(upDownKeys, event.keyCode) > -1) {
|
Chris@0
|
268 dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next';
|
Chris@0
|
269 $group
|
Chris@0
|
270 .closest('.ckeditor-row')[dir]()
|
Chris@0
|
271 .find('.ckeditor-toolbar-groups')
|
Chris@0
|
272 .eq(0)
|
Chris@0
|
273 .prepend($group);
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 Drupal.ckeditor.registerGroupMove(this, $group);
|
Chris@0
|
277 $group.trigger('focus');
|
Chris@0
|
278 event.preventDefault();
|
Chris@0
|
279 event.stopPropagation();
|
Chris@0
|
280 }
|
Chris@0
|
281 },
|
Chris@0
|
282 });
|
Chris@0
|
283 }(jQuery, Drupal, Backbone, _));
|