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