KeyboardView.es6.js 11 KB

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