Chris@0: /** Chris@0: * @file Chris@0: * Backbone View providing the aural view of CKEditor keyboard UX configuration. Chris@0: */ Chris@0: Chris@0: (function ($, Drupal, Backbone, _) { Chris@0: Drupal.ckeditor.KeyboardView = Backbone.View.extend(/** @lends Drupal.ckeditor.KeyboardView# */{ Chris@0: Chris@0: /** Chris@0: * Backbone View for CKEditor toolbar configuration; keyboard UX. Chris@0: * Chris@0: * @constructs Chris@0: * Chris@0: * @augments Backbone.View Chris@0: */ Chris@0: initialize() { Chris@0: // Add keyboard arrow support. Chris@0: this.$el.on('keydown.ckeditor', '.ckeditor-buttons a, .ckeditor-multiple-buttons a', this.onPressButton.bind(this)); Chris@0: this.$el.on('keydown.ckeditor', '[data-drupal-ckeditor-type="group"]', this.onPressGroup.bind(this)); Chris@0: }, Chris@0: Chris@0: /** Chris@0: * @inheritdoc Chris@0: */ Chris@0: render() { Chris@0: }, Chris@0: Chris@0: /** Chris@0: * Handles keypresses on a CKEditor configuration button. Chris@0: * Chris@0: * @param {jQuery.Event} event Chris@0: * The keypress event triggered. Chris@0: */ Chris@0: onPressButton(event) { Chris@0: const upDownKeys = [ Chris@0: 38, // Up arrow. Chris@0: 63232, // Safari up arrow. Chris@0: 40, // Down arrow. Chris@0: 63233, // Safari down arrow. Chris@0: ]; Chris@0: const leftRightKeys = [ Chris@0: 37, // Left arrow. Chris@0: 63234, // Safari left arrow. Chris@0: 39, // Right arrow. Chris@0: 63235, // Safari right arrow. Chris@0: ]; Chris@0: Chris@0: // Respond to an enter key press. Prevent the bubbling of the enter key Chris@0: // press to the button group parent element. Chris@0: if (event.keyCode === 13) { Chris@0: event.stopPropagation(); Chris@0: } Chris@0: Chris@0: // Only take action when a direction key is pressed. Chris@0: if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) { Chris@0: let view = this; Chris@0: let $target = $(event.currentTarget); Chris@0: let $button = $target.parent(); Chris@0: const $container = $button.parent(); Chris@0: let $group = $button.closest('.ckeditor-toolbar-group'); Chris@0: let $row; Chris@0: const containerType = $container.data('drupal-ckeditor-button-sorting'); Chris@0: const $availableButtons = this.$el.find('[data-drupal-ckeditor-button-sorting="source"]'); Chris@0: const $activeButtons = this.$el.find('.ckeditor-toolbar-active'); Chris@0: // The current location of the button, just in case it needs to be put Chris@0: // back. Chris@0: const $originalGroup = $group; Chris@0: let dir; Chris@0: Chris@0: // Move available buttons between their container and the active Chris@0: // toolbar. Chris@0: if (containerType === 'source') { Chris@0: // Move the button to the active toolbar configuration when the down Chris@0: // or up keys are pressed. Chris@0: if (_.indexOf([40, 63233], event.keyCode) > -1) { Chris@0: // Move the button to the first row, first button group index Chris@0: // position. Chris@0: $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button); Chris@0: } Chris@0: } Chris@0: else if (containerType === 'target') { Chris@0: // Move buttons between sibling buttons in a group and between groups. Chris@0: if (_.indexOf(leftRightKeys, event.keyCode) > -1) { Chris@0: // Move left. Chris@0: const $siblings = $container.children(); Chris@0: const index = $siblings.index($button); Chris@0: if (_.indexOf([37, 63234], event.keyCode) > -1) { Chris@0: // Move between sibling buttons. Chris@0: if (index > 0) { Chris@0: $button.insertBefore($container.children().eq(index - 1)); Chris@0: } Chris@0: // Move between button groups and rows. Chris@0: else { Chris@0: // Move between button groups. Chris@0: $group = $container.parent().prev(); Chris@0: if ($group.length > 0) { Chris@0: $group.find('.ckeditor-toolbar-group-buttons').append($button); Chris@0: } Chris@0: // Wrap between rows. Chris@0: else { Chris@0: $container Chris@0: .closest('.ckeditor-row') Chris@0: .prev() Chris@0: .find('.ckeditor-toolbar-group') Chris@0: .not('.placeholder') Chris@0: .find('.ckeditor-toolbar-group-buttons') Chris@0: .eq(-1) Chris@0: .append($button); Chris@0: } Chris@0: } Chris@0: } Chris@0: // Move right. Chris@0: else if (_.indexOf([39, 63235], event.keyCode) > -1) { Chris@0: // Move between sibling buttons. Chris@0: if (index < ($siblings.length - 1)) { Chris@0: $button.insertAfter($container.children().eq(index + 1)); Chris@0: } Chris@0: // Move between button groups. Moving right at the end of a row Chris@0: // will create a new group. Chris@0: else { Chris@0: $container.parent().next().find('.ckeditor-toolbar-group-buttons').prepend($button); Chris@0: } Chris@0: } Chris@0: } Chris@0: // Move buttons between rows and the available button set. Chris@0: else if (_.indexOf(upDownKeys, event.keyCode) > -1) { Chris@0: dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next'; Chris@0: $row = $container.closest('.ckeditor-row')[dir](); Chris@0: // Move the button back into the available button set. Chris@0: if (dir === 'prev' && $row.length === 0) { Chris@0: // If this is a divider, just destroy it. Chris@0: if ($button.data('drupal-ckeditor-type') === 'separator') { Chris@0: $button Chris@0: .off() Chris@0: .remove(); Chris@0: // Focus on the first button in the active toolbar. Chris@0: $activeButtons Chris@0: .find('.ckeditor-toolbar-group-buttons') Chris@0: .eq(0) Chris@0: .children() Chris@0: .eq(0) Chris@0: .children() Chris@0: .trigger('focus'); Chris@0: } Chris@0: // Otherwise, move it. Chris@0: else { Chris@0: $availableButtons.prepend($button); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $row.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button); Chris@0: } Chris@0: } Chris@0: } Chris@0: // Move dividers between their container and the active toolbar. Chris@0: else if (containerType === 'dividers') { Chris@0: // Move the button to the active toolbar configuration when the down Chris@0: // or up keys are pressed. Chris@0: if (_.indexOf([40, 63233], event.keyCode) > -1) { Chris@0: // Move the button to the first row, first button group index Chris@0: // position. Chris@0: $button = $button.clone(true); Chris@0: $activeButtons.find('.ckeditor-toolbar-group-buttons').eq(0).prepend($button); Chris@0: $target = $button.children(); Chris@0: } Chris@0: } Chris@0: Chris@0: view = this; Chris@0: // Attempt to move the button to the new toolbar position. Chris@0: Drupal.ckeditor.registerButtonMove(this, $button, (result) => { Chris@0: // Put the button back if the registration failed. Chris@0: // If the button was in a row, then it was in the active toolbar Chris@0: // configuration. The button was probably placed in a new group, but Chris@0: // that action was canceled. Chris@0: if (!result && $originalGroup) { Chris@0: $originalGroup.find('.ckeditor-buttons').append($button); Chris@0: } Chris@0: // Otherwise refresh the sortables to acknowledge the new button Chris@0: // positions. Chris@0: else { Chris@0: view.$el.find('.ui-sortable').sortable('refresh'); Chris@0: } Chris@0: // Refocus the target button so that the user can continue from a Chris@0: // known place. Chris@0: $target.trigger('focus'); Chris@0: }); Chris@0: Chris@0: event.preventDefault(); Chris@0: event.stopPropagation(); Chris@0: } Chris@0: }, Chris@0: Chris@0: /** Chris@0: * Handles keypresses on a CKEditor configuration group. Chris@0: * Chris@0: * @param {jQuery.Event} event Chris@0: * The keypress event triggered. Chris@0: */ Chris@0: onPressGroup(event) { Chris@0: const upDownKeys = [ Chris@0: 38, // Up arrow. Chris@0: 63232, // Safari up arrow. Chris@0: 40, // Down arrow. Chris@0: 63233, // Safari down arrow. Chris@0: ]; Chris@0: const leftRightKeys = [ Chris@0: 37, // Left arrow. Chris@0: 63234, // Safari left arrow. Chris@0: 39, // Right arrow. Chris@0: 63235, // Safari right arrow. Chris@0: ]; Chris@0: Chris@0: // Respond to an enter key press. Chris@0: if (event.keyCode === 13) { Chris@0: const view = this; Chris@0: // Open the group renaming dialog in the next evaluation cycle so that Chris@0: // this event can be cancelled and the bubbling wiped out. Otherwise, Chris@0: // Firefox has issues because the page focus is shifted to the dialog Chris@0: // along with the keydown event. Chris@0: window.setTimeout(() => { Chris@0: Drupal.ckeditor.openGroupNameDialog(view, $(event.currentTarget)); Chris@0: }, 0); Chris@0: event.preventDefault(); Chris@0: event.stopPropagation(); Chris@0: } Chris@0: Chris@0: // Respond to direction key presses. Chris@0: if (_.indexOf(_.union(upDownKeys, leftRightKeys), event.keyCode) > -1) { Chris@0: const $group = $(event.currentTarget); Chris@0: const $container = $group.parent(); Chris@0: const $siblings = $container.children(); Chris@0: let index; Chris@0: let dir; Chris@0: // Move groups between sibling groups. Chris@0: if (_.indexOf(leftRightKeys, event.keyCode) > -1) { Chris@0: index = $siblings.index($group); Chris@0: // Move left between sibling groups. Chris@0: if ((_.indexOf([37, 63234], event.keyCode) > -1)) { Chris@0: if (index > 0) { Chris@0: $group.insertBefore($siblings.eq(index - 1)); Chris@0: } Chris@0: // Wrap between rows. Insert the group before the placeholder group Chris@0: // at the end of the previous row. Chris@0: else { Chris@0: const $rowChildElement = $container Chris@0: .closest('.ckeditor-row') Chris@0: .prev() Chris@0: .find('.ckeditor-toolbar-groups') Chris@0: .children() Chris@0: .eq(-1); Chris@0: $group.insertBefore($rowChildElement); Chris@0: } Chris@0: } Chris@0: // Move right between sibling groups. Chris@0: else if (_.indexOf([39, 63235], event.keyCode) > -1) { Chris@0: // Move to the right if the next group is not a placeholder. Chris@0: if (!$siblings.eq(index + 1).hasClass('placeholder')) { Chris@0: $group.insertAfter($container.children().eq(index + 1)); Chris@0: } Chris@0: // Wrap group between rows. Chris@0: else { Chris@0: $container.closest('.ckeditor-row').next().find('.ckeditor-toolbar-groups').prepend($group); Chris@0: } Chris@0: } Chris@0: } Chris@0: // Move groups between rows. Chris@0: else if (_.indexOf(upDownKeys, event.keyCode) > -1) { Chris@0: dir = (_.indexOf([38, 63232], event.keyCode) > -1) ? 'prev' : 'next'; Chris@0: $group Chris@0: .closest('.ckeditor-row')[dir]() Chris@0: .find('.ckeditor-toolbar-groups') Chris@0: .eq(0) Chris@0: .prepend($group); Chris@0: } Chris@0: Chris@0: Drupal.ckeditor.registerGroupMove(this, $group); Chris@0: $group.trigger('focus'); Chris@0: event.preventDefault(); Chris@0: event.stopPropagation(); Chris@0: } Chris@0: }, Chris@0: }); Chris@0: }(jQuery, Drupal, Backbone, _));