shortcuts.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. app.Shortcuts = class Shortcuts extends Events {
  2. constructor() {
  3. super();
  4. this.onKeydown = this.onKeydown.bind(this);
  5. this.onKeypress = this.onKeypress.bind(this);
  6. this.isMac = $.isMac();
  7. this.start();
  8. }
  9. start() {
  10. $.on(document, "keydown", this.onKeydown);
  11. $.on(document, "keypress", this.onKeypress);
  12. }
  13. stop() {
  14. $.off(document, "keydown", this.onKeydown);
  15. $.off(document, "keypress", this.onKeypress);
  16. }
  17. swapArrowKeysBehavior() {
  18. return app.settings.get("arrowScroll");
  19. }
  20. spaceScroll() {
  21. return app.settings.get("spaceScroll");
  22. }
  23. showTip() {
  24. app.showTip("KeyNav");
  25. return (this.showTip = null);
  26. }
  27. spaceTimeout() {
  28. return app.settings.get("spaceTimeout");
  29. }
  30. onKeydown(event) {
  31. if (this.buggyEvent(event)) {
  32. return;
  33. }
  34. const result = (() => {
  35. if (event.ctrlKey || event.metaKey) {
  36. if (!event.altKey && !event.shiftKey) {
  37. return this.handleKeydownSuperEvent(event);
  38. }
  39. } else if (event.shiftKey) {
  40. if (!event.altKey) {
  41. return this.handleKeydownShiftEvent(event);
  42. }
  43. } else if (event.altKey) {
  44. return this.handleKeydownAltEvent(event);
  45. } else {
  46. return this.handleKeydownEvent(event);
  47. }
  48. })();
  49. if (result === false) {
  50. event.preventDefault();
  51. }
  52. }
  53. onKeypress(event) {
  54. if (
  55. this.buggyEvent(event) ||
  56. (event.charCode === 63 && document.activeElement.tagName === "INPUT")
  57. ) {
  58. return;
  59. }
  60. if (!event.ctrlKey && !event.metaKey) {
  61. const result = this.handleKeypressEvent(event);
  62. if (result === false) {
  63. event.preventDefault();
  64. }
  65. }
  66. }
  67. handleKeydownEvent(event, _force) {
  68. if (
  69. !_force &&
  70. [37, 38, 39, 40].includes(event.which) &&
  71. this.swapArrowKeysBehavior()
  72. ) {
  73. return this.handleKeydownAltEvent(event, true);
  74. }
  75. if (
  76. !event.target.form &&
  77. ((48 <= event.which && event.which <= 57) ||
  78. (65 <= event.which && event.which <= 90))
  79. ) {
  80. this.trigger("typing");
  81. return;
  82. }
  83. switch (event.which) {
  84. case 8:
  85. if (!event.target.form) {
  86. return this.trigger("typing");
  87. }
  88. break;
  89. case 13:
  90. return this.trigger("enter");
  91. case 27:
  92. this.trigger("escape");
  93. return false;
  94. case 32:
  95. if (
  96. event.target.type === "search" &&
  97. this.spaceScroll() &&
  98. (!this.lastKeypress ||
  99. this.lastKeypress < Date.now() - this.spaceTimeout() * 1000)
  100. ) {
  101. this.trigger("pageDown");
  102. return false;
  103. }
  104. break;
  105. case 33:
  106. return this.trigger("pageUp");
  107. case 34:
  108. return this.trigger("pageDown");
  109. case 35:
  110. if (!event.target.form) {
  111. return this.trigger("pageBottom");
  112. }
  113. break;
  114. case 36:
  115. if (!event.target.form) {
  116. return this.trigger("pageTop");
  117. }
  118. break;
  119. case 37:
  120. if (!event.target.value) {
  121. return this.trigger("left");
  122. }
  123. break;
  124. case 38:
  125. this.trigger("up");
  126. if (typeof this.showTip === "function") {
  127. this.showTip();
  128. }
  129. return false;
  130. case 39:
  131. if (!event.target.value) {
  132. return this.trigger("right");
  133. }
  134. break;
  135. case 40:
  136. this.trigger("down");
  137. if (typeof this.showTip === "function") {
  138. this.showTip();
  139. }
  140. return false;
  141. case 191:
  142. if (!event.target.form) {
  143. this.trigger("typing");
  144. return false;
  145. }
  146. break;
  147. }
  148. }
  149. handleKeydownSuperEvent(event) {
  150. switch (event.which) {
  151. case 13:
  152. return this.trigger("superEnter");
  153. case 37:
  154. if (this.isMac) {
  155. this.trigger("superLeft");
  156. return false;
  157. }
  158. break;
  159. case 38:
  160. this.trigger("pageTop");
  161. return false;
  162. case 39:
  163. if (this.isMac) {
  164. this.trigger("superRight");
  165. return false;
  166. }
  167. break;
  168. case 40:
  169. this.trigger("pageBottom");
  170. return false;
  171. case 188:
  172. this.trigger("preferences");
  173. return false;
  174. }
  175. }
  176. handleKeydownShiftEvent(event, _force) {
  177. if (
  178. !_force &&
  179. [37, 38, 39, 40].includes(event.which) &&
  180. this.swapArrowKeysBehavior()
  181. ) {
  182. return this.handleKeydownEvent(event, true);
  183. }
  184. if (!event.target.form && 65 <= event.which && event.which <= 90) {
  185. this.trigger("typing");
  186. return;
  187. }
  188. switch (event.which) {
  189. case 32:
  190. this.trigger("pageUp");
  191. return false;
  192. case 38:
  193. if (!getSelection()?.toString()) {
  194. this.trigger("altUp");
  195. return false;
  196. }
  197. break;
  198. case 40:
  199. if (!getSelection()?.toString()) {
  200. this.trigger("altDown");
  201. return false;
  202. }
  203. break;
  204. }
  205. }
  206. handleKeydownAltEvent(event, _force) {
  207. if (
  208. !_force &&
  209. [37, 38, 39, 40].includes(event.which) &&
  210. this.swapArrowKeysBehavior()
  211. ) {
  212. return this.handleKeydownEvent(event, true);
  213. }
  214. switch (event.which) {
  215. case 9:
  216. return this.trigger("altRight", event);
  217. case 37:
  218. if (!this.isMac) {
  219. this.trigger("superLeft");
  220. return false;
  221. }
  222. break;
  223. case 38:
  224. this.trigger("altUp");
  225. return false;
  226. case 39:
  227. if (!this.isMac) {
  228. this.trigger("superRight");
  229. return false;
  230. }
  231. break;
  232. case 40:
  233. this.trigger("altDown");
  234. return false;
  235. case 67:
  236. this.trigger("altC");
  237. return false;
  238. case 68:
  239. this.trigger("altD");
  240. return false;
  241. case 70:
  242. return this.trigger("altF", event);
  243. case 71:
  244. this.trigger("altG");
  245. return false;
  246. case 79:
  247. this.trigger("altO");
  248. return false;
  249. case 82:
  250. this.trigger("altR");
  251. return false;
  252. case 83:
  253. this.trigger("altS");
  254. return false;
  255. }
  256. }
  257. handleKeypressEvent(event) {
  258. if (event.which === 63 && !event.target.value) {
  259. this.trigger("help");
  260. return false;
  261. } else {
  262. return (this.lastKeypress = Date.now());
  263. }
  264. }
  265. buggyEvent(event) {
  266. try {
  267. event.target;
  268. event.ctrlKey;
  269. event.which;
  270. return false;
  271. } catch (error) {
  272. return true;
  273. }
  274. }
  275. };