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