From 95b15acb1ef6608ecb1c2c954791769d111c4f59 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 8 Sep 2021 14:48:13 +0200 Subject: [PATCH] DEV: uses forked Mousetrap to avoid leaking listeners (#14198) --- app/assets/javascripts/discourse-shims.js | 9 +- app/assets/javascripts/discourse/app/app.js | 6 - .../discourse/app/components/bookmark.js | 12 +- .../discourse/app/components/d-editor.js | 12 +- .../app/components/edit-topic-timer-form.js | 9 +- .../discourse/app/components/site-header.js | 11 +- .../app/components/time-shortcut-picker.js | 7 +- .../app/initializers/keyboard-shortcuts.js | 8 +- .../discourse/app/lib/keyboard-shortcuts.js | 8 +- .../app/templates/components/bookmark.hbs | 2 +- .../components/edit-topic-timer-form.hbs | 2 +- app/assets/javascripts/discourse/package.json | 5 +- .../discourse/tests/theme_qunit_vendor.js | 3 +- app/assets/javascripts/vendor.js | 3 +- app/assets/javascripts/yarn.lock | 15 +- lib/tasks/javascript.rake | 4 +- package.json | 6 +- vendor/assets/javascripts/itsatrap.js | 1159 +++++++++++++++++ .../javascripts/mousetrap-global-bind.js | 46 - vendor/assets/javascripts/mousetrap.js | 1058 --------------- yarn.lock | 9 +- 21 files changed, 1217 insertions(+), 1177 deletions(-) create mode 100644 vendor/assets/javascripts/itsatrap.js delete mode 100644 vendor/assets/javascripts/mousetrap-global-bind.js delete mode 100644 vendor/assets/javascripts/mousetrap.js diff --git a/app/assets/javascripts/discourse-shims.js b/app/assets/javascripts/discourse-shims.js index e93da4e080a..006a98ed696 100644 --- a/app/assets/javascripts/discourse-shims.js +++ b/app/assets/javascripts/discourse-shims.js @@ -2,11 +2,6 @@ define("message-bus-client", ["exports"], function (__exports__) { __exports__.default = window.MessageBus; }); -define("mousetrap-global-bind", ["exports"], function (__exports__) { - // In the Rails app it's applied from the vendored file - __exports__.default = {}; -}); - define("ember-buffered-proxy/proxy", ["exports"], function (__exports__) { __exports__.default = window.BufferedProxy; }); @@ -19,8 +14,8 @@ define("xss", ["exports"], function (__exports__) { __exports__.default = window.filterXSS; }); -define("mousetrap", ["exports"], function (__exports__) { - __exports__.default = window.Mousetrap; +define("@discourse/itsatrap", ["exports"], function (__exports__) { + __exports__.default = window.ItsATrap; }); define("@popperjs/core", ["exports"], function (__exports__) { diff --git a/app/assets/javascripts/discourse/app/app.js b/app/assets/javascripts/discourse/app/app.js index ddcc134df98..8d0aa0b14f3 100644 --- a/app/assets/javascripts/discourse/app/app.js +++ b/app/assets/javascripts/discourse/app/app.js @@ -1,5 +1,4 @@ import Application from "@ember/application"; -import Mousetrap from "mousetrap"; import { buildResolver } from "discourse-common/resolver"; import { isTesting } from "discourse-common/config/environment"; @@ -13,11 +12,6 @@ const Discourse = Application.extend({ paste: "paste", }, - reset() { - this._super(...arguments); - Mousetrap.reset(); - }, - Resolver: buildResolver("discourse"), _prepareInitializer(moduleName) { diff --git a/app/assets/javascripts/discourse/app/components/bookmark.js b/app/assets/javascripts/discourse/app/components/bookmark.js index d51cb7baa98..25157f2eaca 100644 --- a/app/assets/javascripts/discourse/app/components/bookmark.js +++ b/app/assets/javascripts/discourse/app/components/bookmark.js @@ -11,7 +11,7 @@ import { AUTO_DELETE_PREFERENCES } from "discourse/models/bookmark"; import Component from "@ember/component"; import I18n from "I18n"; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; -import Mousetrap from "mousetrap"; +import ItsATrap from "@discourse/itsatrap"; import { Promise } from "rsvp"; import { TIME_SHORTCUT_TYPES } from "discourse/lib/time-shortcut"; import { action } from "@ember/object"; @@ -37,6 +37,7 @@ export default Component.extend({ _savingBookmarkManually: null, _saving: null, _deleting: null, + _itsatrap: null, postDetectedLocalDate: null, postDetectedLocalTime: null, postDetectedLocalTimezone: null, @@ -44,7 +45,6 @@ export default Component.extend({ userTimezone: null, showOptions: null, model: null, - afterSave: null, @on("init") @@ -62,6 +62,7 @@ export default Component.extend({ prefilledDatetime: null, userTimezone: this.currentUser.resolvedTimezone(this.currentUser), showOptions: false, + _itsatrap: new ItsATrap(), }); this.registerOnCloseHandler(this._onModalClose.bind(this)); @@ -123,9 +124,8 @@ export default Component.extend({ _bindKeyboardShortcuts() { KeyboardShortcuts.pause(); - this._mousetrap = new Mousetrap(); Object.keys(BOOKMARK_BINDINGS).forEach((shortcut) => { - this._mousetrap.bind(shortcut, () => { + this._itsatrap.bind(shortcut, () => { let binding = BOOKMARK_BINDINGS[shortcut]; this.send(binding.handler); return false; @@ -266,7 +266,9 @@ export default Component.extend({ willDestroyElement() { this._super(...arguments); - this._mousetrap.reset(); + + this._itsatrap?.destroy(); + this.set("_itsatrap", null); KeyboardShortcuts.unpause(); }, diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index 1d337cb0ba9..09b90e5246e 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -9,7 +9,7 @@ import { emojiUrlFor, generateCookFunction } from "discourse/lib/text"; import { later, schedule, scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; import I18n from "I18n"; -import Mousetrap from "mousetrap"; +import ItsATrap from "@discourse/itsatrap"; import { Promise } from "rsvp"; import { SKIP } from "discourse/lib/autocomplete"; import { categoryHashtagTriggerRule } from "discourse/lib/category-hashtags"; @@ -238,7 +238,7 @@ export default Component.extend(TextareaTextManipulation, { classNames: ["d-editor"], ready: false, lastSel: null, - _mouseTrap: null, + _itsatrap: null, showLink: true, emojiPickerIsActive: false, emojiStore: service("emoji-store"), @@ -278,12 +278,12 @@ export default Component.extend(TextareaTextManipulation, { scheduleOnce("afterRender", this, this._readyNow); - this._mouseTrap = new Mousetrap(this._textarea); + this._itsatrap = new ItsATrap(this._textarea); const shortcuts = this.get("toolbar.shortcuts"); Object.keys(shortcuts).forEach((sc) => { const button = shortcuts[sc]; - this._mouseTrap.bind(sc, () => { + this._itsatrap.bind(sc, () => { button.action(button); return false; }); @@ -335,7 +335,9 @@ export default Component.extend(TextareaTextManipulation, { this.appEvents.off("composer:replace-text", this, "_replaceText"); } - this._mouseTrap.reset(); + this._itsatrap?.destroy(); + this._itsatrap = null; + $(this.element.querySelector(".d-editor-preview")).off("click.preview"); if (isTesting()) { diff --git a/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js b/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js index 65ab8b6b92c..1c07aceb5c1 100644 --- a/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js +++ b/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js @@ -21,7 +21,7 @@ import { thisWeekend, } from "discourse/lib/time-utils"; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; -import Mousetrap from "mousetrap"; +import ItsATrap from "@discourse/itsatrap"; export default Component.extend({ statusType: readOnly("topicTimer.status_type"), @@ -43,12 +43,13 @@ export default Component.extend({ "autoCloseAfterLastPost" ), duration: null, + _itsatrap: null, init() { this._super(...arguments); KeyboardShortcuts.pause(); - this._mousetrap = new Mousetrap(); + this.set("_itsatrap", new ItsATrap()); this.set("duration", this.initialDuration); }, @@ -65,7 +66,9 @@ export default Component.extend({ willDestroyElement() { this._super(...arguments); - this._mousetrap.reset(); + + this._itsatrap.destroy(); + this.set("_itsatrap", null); KeyboardShortcuts.unpause(); }, diff --git a/app/assets/javascripts/discourse/app/components/site-header.js b/app/assets/javascripts/discourse/app/components/site-header.js index 9ac49c9cfec..d78e9c7d49b 100644 --- a/app/assets/javascripts/discourse/app/components/site-header.js +++ b/app/assets/javascripts/discourse/app/components/site-header.js @@ -5,7 +5,7 @@ import PanEvents, { import { cancel, later, schedule } from "@ember/runloop"; import Docking from "discourse/mixins/docking"; import MountWidget from "discourse/components/mount-widget"; -import Mousetrap from "mousetrap"; +import ItsATrap from "@discourse/itsatrap"; import RerenderOnDoNotDisturbChange from "discourse/mixins/rerender-on-do-not-disturb-change"; import { observes } from "discourse-common/utils/decorators"; import { topicTitleDecorators } from "discourse/components/topic-title"; @@ -24,7 +24,7 @@ const SiteHeaderComponent = MountWidget.extend( _panMenuOffset: 0, _scheduledRemoveAnimate: null, _topic: null, - _mousetrap: null, + _itsatrap: null, @observes( "currentUser.unread_notifications", @@ -258,8 +258,8 @@ const SiteHeaderComponent = MountWidget.extend( } const header = document.querySelector("header.d-header"); - this._mousetrap = new Mousetrap(header); - this._mousetrap.bind(["right", "left"], (e) => { + this._itsatrap = new ItsATrap(header); + this._itsatrap.bind(["right", "left"], (e) => { const activeTab = document.querySelector(".glyphs .menu-link.active"); if (activeTab) { @@ -294,7 +294,8 @@ const SiteHeaderComponent = MountWidget.extend( cancel(this._scheduledRemoveAnimate); - this._mousetrap.reset(); + this._itsatrap?.destroy(); + this._itsatrap = null; document.removeEventListener("click", this._dismissFirstNotification); }, diff --git a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js index 7733cbab85c..62869a39dd6 100644 --- a/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js +++ b/app/assets/javascripts/discourse/app/components/time-shortcut-picker.js @@ -67,6 +67,8 @@ export default Component.extend({ customDate: null, customTime: null, + _itsatrap: null, + defaultCustomReminderTime: `0${START_OF_DAY_HOUR}:00`, @on("init") @@ -101,7 +103,8 @@ export default Component.extend({ willDestroyElement() { this._super(...arguments); - this.mousetrap.unbind(Object.keys(BINDINGS)); + + this._itsatrap.unbind(Object.keys(BINDINGS)); }, parsePrefilledDatetime() { @@ -143,7 +146,7 @@ export default Component.extend({ _bindKeyboardShortcuts() { Object.keys(BINDINGS).forEach((shortcut) => { - this.mousetrap.bind(shortcut, () => { + this._itsatrap.bind(shortcut, () => { let binding = BINDINGS[shortcut]; this.send(binding.handler, ...binding.args); return false; diff --git a/app/assets/javascripts/discourse/app/initializers/keyboard-shortcuts.js b/app/assets/javascripts/discourse/app/initializers/keyboard-shortcuts.js index b5dda8a2378..b484497e320 100644 --- a/app/assets/javascripts/discourse/app/initializers/keyboard-shortcuts.js +++ b/app/assets/javascripts/discourse/app/initializers/keyboard-shortcuts.js @@ -1,15 +1,11 @@ import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; -import Mousetrap from "mousetrap"; -import bindGlobal from "mousetrap-global-bind"; +import ItsATrap from "@discourse/itsatrap"; export default { name: "keyboard-shortcuts", initialize(container) { - // Ensure mousetrap-global-bind is executed - void bindGlobal; - - KeyboardShortcuts.init(Mousetrap, container); + KeyboardShortcuts.init(ItsATrap, container); KeyboardShortcuts.bindEvents(); }, diff --git a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js index 4bc5223fdfb..8355a935ca7 100644 --- a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js +++ b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js @@ -122,10 +122,8 @@ export default { }, teardown() { - if (this.keyTrapper) { - this.keyTrapper.reset(); - this.keyTrapper = null; - } + this.keyTrapper?.destroy(); + this.keyTrapper = null; this.container = null; }, @@ -207,7 +205,7 @@ export default { **/ addShortcut(shortcut, callback, opts = {}) { // we trim but leave whitespace between characters, as shortcuts - // like `z z` are valid for Mousetrap + // like `z z` are valid for ItsATrap shortcut = shortcut.trim(); let newBinding = Object.assign({ handler: callback }, opts); this.bindKey(shortcut, newBinding); diff --git a/app/assets/javascripts/discourse/app/templates/components/bookmark.hbs b/app/assets/javascripts/discourse/app/templates/components/bookmark.hbs index 9882daa5b35..00119efd9dc 100644 --- a/app/assets/javascripts/discourse/app/templates/components/bookmark.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/bookmark.hbs @@ -40,7 +40,7 @@ onTimeSelected=(action "onTimeSelected") customOptions=customTimeShortcutOptions additionalOptionsToShow=additionalTimeShortcutOptions - mousetrap=_mousetrap + _itsatrap=_itsatrap }} {{else}}
{{html-safe (i18n "bookmarks.no_timezone" basePath=(base-path))}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/edit-topic-timer-form.hbs b/app/assets/javascripts/discourse/app/templates/components/edit-topic-timer-form.hbs index f2b4aca37cd..c960e1a3a1f 100644 --- a/app/assets/javascripts/discourse/app/templates/components/edit-topic-timer-form.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/edit-topic-timer-form.hbs @@ -24,7 +24,7 @@ onTimeSelected=onTimeSelected customOptions=customTimeShortcutOptions hiddenOptions=hiddenTimeShortcutOptions - mousetrap=_mousetrap + _itsatrap=_itsatrap }} {{/if}} {{#if useDuration}} diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 5d8808047f4..d6128cac445 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -53,8 +53,6 @@ "loader.js": "^4.7.0", "message-bus-client": "^3.3.0", "messageformat": "0.1.5", - "mousetrap": "^1.6.5", - "mousetrap-global-bind": "^1.1.0", "pretender": "^3.4.7", "pretty-text": "^1.0.0", "qunit": "^2.14.0", @@ -62,7 +60,8 @@ "sass": "^1.32.8", "select-kit": "^1.0.0", "sinon": "^9.2.0", - "virtual-dom": "^2.1.1" + "virtual-dom": "^2.1.1", + "@discourse/itsatrap": "^2.0.10" }, "engines": { "node": ">= 12.*", diff --git a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js index 08f31dee8e2..58e2674ef5e 100644 --- a/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js +++ b/app/assets/javascripts/discourse/tests/theme_qunit_vendor.js @@ -25,8 +25,7 @@ //= require jquery.tagsinput.js //= require jquery.sortable.js //= require lodash.js -//= require mousetrap.js -//= require mousetrap-global-bind.js +//= require itsatrap.js //= require rsvp.js //= require show-html.js //= require uppy.js diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index 1e0b0c6d1cf..f6ce6c790bd 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -18,8 +18,7 @@ //= require jquery.tagsinput.js //= require jquery.sortable.js //= require lodash.js -//= require mousetrap.js -//= require mousetrap-global-bind.js +//= require itsatrap.js //= require rsvp.js //= require show-html.js //= require uppy.js diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index ffb5e3c1e14..e18fca6d24f 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -928,6 +928,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@discourse/itsatrap@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@discourse/itsatrap/-/itsatrap-2.0.10.tgz#c7e750eeb32b54e769e952c4ecc472213eb1385a" + integrity sha512-Jn1gdiyHMGUsmUfLFf4Q7VnTAv0l7NePbegU6pKhKHEmbzV3FosGxq30fTOYgVyTS1bxqGjlA6LvQttJpv3ROw== + "@ember-data/rfc395-data@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843" @@ -8586,16 +8591,6 @@ morgan@^1.10.0: on-finished "~2.3.0" on-headers "~1.0.2" -mousetrap-global-bind@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd" - integrity sha1-zX3pIivQZG+i4BDVTISnTCaojt0= - -mousetrap@^1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" - integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== - mout@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/mout/-/mout-1.2.2.tgz#c9b718a499806a0632cede178e80f436259e777d" diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index 2914940a788..c876449e692 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -123,7 +123,7 @@ def dependencies }, { source: 'markdown-it/dist/markdown-it.js' }, { - source: 'mousetrap/mousetrap.js' + source: '@discourse/itsatrap/itsatrap.js' }, { source: 'moment/moment.js' }, { @@ -138,8 +138,6 @@ def dependencies }, { source: 'moment-timezone-names-translations/locales/.', destination: 'moment-timezone-names-locale' - }, { - source: 'mousetrap/plugins/global-bind/mousetrap-global-bind.js' }, { source: 'resumablejs/resumable.js' }, { diff --git a/package.json b/package.json index 64fb5f33264..18021b9d5b9 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "@highlightjs/cdn-assets": "^10.6.0", "@json-editor/json-editor": "^2.5.2", "@popperjs/core": "v2.9.3", - "@uppy/core": "^2.0.1", "@uppy/aws-s3": "^2.0.1", "@uppy/aws-s3-multipart": "^2.0.2", + "@uppy/core": "^2.0.1", "@uppy/drop-target": "^1.0.1", "@uppy/xhr-upload": "^2.0.1", "ace-builds": "1.4.12", @@ -33,7 +33,6 @@ "moment": "2.29.1", "moment-timezone": "0.5.31", "moment-timezone-names-translations": "https://github.com/discourse/moment-timezone-names-translations", - "mousetrap": "https://github.com/discourse/mousetrap#firefox-alt-key", "pikaday": "1.8.0", "resumablejs": "1.1.0", "spectrum-colorpicker": "1.8.0", @@ -42,7 +41,8 @@ "workbox-expiration": "^4.3.1", "workbox-routing": "^4.3.1", "workbox-strategies": "^4.3.1", - "workbox-sw": "^4.3.1" + "workbox-sw": "^4.3.1", + "@discourse/itsatrap": "^2.0.10" }, "devDependencies": { "@arkweid/lefthook": "^0.7.2", diff --git a/vendor/assets/javascripts/itsatrap.js b/vendor/assets/javascripts/itsatrap.js new file mode 100644 index 00000000000..1616fb29e27 --- /dev/null +++ b/vendor/assets/javascripts/itsatrap.js @@ -0,0 +1,1159 @@ +/*global define:false */ +/** + * Copyright 2012-2017 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ItsATrap is a simple keyboard shortcut library for Javascript with + * no external dependencies + * + * @version 2.0.1 + * @url github.com/discourse/itsatrap + */ +(function(window, document, undefined) { + // Check if itsatrap is used inside browser, if not, return + if (!window) { + return; + } + + /** + * mapping of special keycodes to their corresponding keys + * + * everything in this dictionary cannot use keypress events + * so it has to be here to map to the correct keycodes for + * keyup/keydown events + * + * @type {Object} + */ + var _MAP = { + 8: "backspace", + 9: "tab", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 20: "capslock", + 27: "esc", + 32: "space", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "ins", + 46: "del", + 91: "meta", + 93: "meta", + 224: "meta" + }; + + /** + * mapping for special characters so they can support + * + * this dictionary is only used incase you want to bind a + * keyup or keydown event to one of these keys + * + * @type {Object} + */ + var _KEYCODE_MAP = { + 106: "*", + 107: "+", + 109: "-", + 110: ".", + 111: "/", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "'" + }; + + /** + * this is a mapping of keys that require shift on a US keypad + * back to the non shift equivelents + * + * this is so you can use keyup events with these keys + * + * note that this will only work reliably on US keyboards + * + * @type {Object} + */ + var _SHIFT_MAP = { + "~": "`", + "!": "1", + "@": "2", + "#": "3", + $: "4", + "%": "5", + "^": "6", + "&": "7", + "*": "8", + "(": "9", + ")": "0", + _: "-", + "+": "=", + ":": ";", + '"': "'", + "<": ",", + ">": ".", + "?": "/", + "|": "\\" + }; + + /** + * this is a list of special strings you can use to map + * to modifier keys when you specify your keyboard shortcuts + * + * @type {Object} + */ + var _SPECIAL_ALIASES = { + option: "alt", + command: "meta", + return: "enter", + escape: "esc", + plus: "+", + mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "meta" : "ctrl" + }; + + /** + * variable to store the flipped version of _MAP from above + * needed to check if we should use keypress or not when no action + * is specified + * + * @type {Object|undefined} + */ + var _REVERSE_MAP; + + /** + * holds a reference to global bindings + * + * @type {Object|undefined} + */ + var _globalCallbacks = {}; + + /** + * loop through the f keys, f1 to f19 and add them to the map + * programatically + */ + for (var i = 1; i < 20; ++i) { + _MAP[111 + i] = "f" + i; + } + + /** + * loop through to map numbers on the numeric keypad + */ + for (i = 0; i <= 9; ++i) { + // This needs to use a string cause otherwise since 0 is falsey + // itsatrap will never fire for numpad 0 pressed as part of a keydown + // event. + // + // @see https://github.com/ccampbell/itsatrap/pull/258 + _MAP[i + 96] = i.toString(); + } + + /** + * cross browser add event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {EventListenerOrEventListenerObject} callback + * @returns void + */ + function _addEvent(object, type, callback) { + if (object.addEventListener) { + object.addEventListener(type, callback, false); + return; + } + + object.attachEvent("on" + type, callback); + } + + /** + * cross browser remove event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {EventListenerOrEventListenerObject} callback + * @returns void + */ + function _removeEvent(object, type, callback) { + if (object.removeEventListener) { + object.removeEventListener(type, callback, false); + return; + } + object.detachEvent("on" + type, callback); + } + + /** + * takes the event and returns the key character + * + * @param {Event} e + * @return {string} + */ + function _characterFromEvent(e) { + // for keypress events we should return the character as is + if (e.type == "keypress") { + var character = String.fromCharCode(e.which); + + // if the shift key is not pressed then it is safe to assume + // that we want the character to be lowercase. this means if + // you accidentally have caps lock on then your key bindings + // will continue to work + // + // the only side effect that might not be desired is if you + // bind something like 'A' cause you want to trigger an + // event when capital A is pressed caps lock will no longer + // trigger the event. shift+a will though. + if (!e.shiftKey) { + character = character.toLowerCase(); + } + + return character; + } + + // for non keypress events the special maps are needed + if (_MAP[e.which]) { + return _MAP[e.which]; + } + + if (_KEYCODE_MAP[e.which]) { + return _KEYCODE_MAP[e.which]; + } + + // if it is not in the special map + + // with keydown and keyup events the character seems to always + // come in as an uppercase character whether you are pressing shift + // or not. we should make sure it is always lowercase for comparisons + return String.fromCharCode(e.which).toLowerCase(); + } + + /** + * checks if two arrays are equal + * + * @param {Array} modifiers1 + * @param {Array} modifiers2 + * @returns {boolean} + */ + function _modifiersMatch(modifiers1, modifiers2) { + return modifiers1.sort().join(",") === modifiers2.sort().join(","); + } + + /** + * takes a key event and figures out what the modifiers are + * + * @param {Event} e + * @returns {Array} + */ + function _eventModifiers(e) { + var modifiers = []; + + if (e.shiftKey) { + modifiers.push("shift"); + } + + if (e.altKey) { + modifiers.push("alt"); + } + + if (e.ctrlKey) { + modifiers.push("ctrl"); + } + + if (e.metaKey) { + modifiers.push("meta"); + } + + return modifiers; + } + + /** + * prevents default for this event + * + * @param {Event} e + * @returns void + */ + function _preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + return; + } + + e.returnValue = false; + } + + /** + * stops propogation for this event + * + * @param {Event} e + * @returns void + */ + function _stopPropagation(e) { + if (e.stopPropagation) { + e.stopPropagation(); + return; + } + + e.cancelBubble = true; + } + + /** + * determines if the keycode specified is a modifier key or not + * + * @param {string} key + * @returns {boolean} + */ + function _isModifier(key) { + return key == "shift" || key == "ctrl" || key == "alt" || key == "meta"; + } + + /** + * reverses the map lookup so that we can look for specific keys + * to see what can and can't use keypress + * + * @return {Object} + */ + function _getReverseMap() { + if (!_REVERSE_MAP) { + _REVERSE_MAP = {}; + for (var key in _MAP) { + // pull out the numeric keypad from here cause keypress should + // be able to detect the keys from the character + if (key > 95 && key < 112) { + continue; + } + + if (_MAP.hasOwnProperty(key)) { + _REVERSE_MAP[_MAP[key]] = key; + } + } + } + return _REVERSE_MAP; + } + + /** + * picks the best action based on the key combination + * + * @param {string} key - character for key + * @param {Array} modifiers + * @param {string=} action passed in + */ + function _pickBestAction(key, modifiers, action) { + // if no action was picked in we should try to pick the one + // that we think would work best for this key + if (!action) { + action = _getReverseMap()[key] ? "keydown" : "keypress"; + } + + // modifier keys don't work as expected with keypress, + // switch to keydown + if (action == "keypress" && modifiers.length) { + action = "keydown"; + } + + return action; + } + + /** + * Converts from a string key combination to an array + * + * @param {string} combination like "command+shift+l" + * @return {Array} + */ + function _keysFromString(combination) { + if (combination === "+") { + return ["+"]; + } + + combination = combination.replace(/\+{2}/g, "+plus"); + return combination.split("+"); + } + + /** + * Gets info for a specific key combination + * + * @param {string} combination key combination ("command+s" or "a" or "*") + * @param {string=} action + * @returns {Object} + */ + function _getKeyInfo(combination, action) { + var keys; + var key; + var i; + var modifiers = []; + + // take the keys from this pattern and figure out what the actual + // pattern is all about + keys = _keysFromString(combination); + + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + + // normalize key names + if (_SPECIAL_ALIASES[key]) { + key = _SPECIAL_ALIASES[key]; + } + + // if this is not a keypress event then we should + // be smart about using shift keys + // this will only work for US keyboards however + if (action && action != "keypress" && _SHIFT_MAP[key]) { + key = _SHIFT_MAP[key]; + modifiers.push("shift"); + } + + // if this key is a modifier then add it to the list of modifiers + if (_isModifier(key)) { + modifiers.push(key); + } + } + + // depending on what the key combination is + // we will try to pick the best event for it + action = _pickBestAction(key, modifiers, action); + + return { + key: key, + modifiers: modifiers, + action: action + }; + } + + function _belongsTo(element, ancestor) { + if (element === null || element === document) { + return false; + } + + if (element === ancestor) { + return true; + } + + return _belongsTo(element.parentNode, ancestor); + } + + function ItsATrap(targetElement) { + var self = this; + + targetElement = targetElement || document; + + if (!(self instanceof ItsATrap)) { + return new ItsATrap(targetElement); + } + + /** + * element to attach key events to + * + * @type {Element} + */ + self.target = targetElement; + + /** + * a list of all the callbacks setup via ItsATrap.bind() + * + * @type {Object} + */ + self._callbacks = {}; + + /** + * direct map of string combinations to callbacks used for trigger() + * + * @type {Object} + */ + self._directMap = {}; + + /** + * keeps track of what level each sequence is at since multiple + * sequences can start out with the same sequence + * + * @type {Object} + */ + var _sequenceLevels = {}; + + /** + * variable to store the setTimeout call + * + * @type {null|number} + */ + var _resetTimer; + + /** + * temporary state where we will ignore the next keyup + * + * @type {boolean|string} + */ + var _ignoreNextKeyup = false; + + /** + * temporary state where we will ignore the next keypress + * + * @type {boolean} + */ + var _ignoreNextKeypress = false; + + /** + * are we currently inside of a sequence? + * type of action ("keyup" or "keydown" or "keypress") or false + * + * @type {boolean|string} + */ + var _nextExpectedAction = false; + + /** + * resets all sequence counters except for the ones passed in + * + * @param {Object} doNotReset + * @returns void + */ + function _resetSequences(doNotReset) { + doNotReset = doNotReset || {}; + + var activeSequences = false, + key; + + for (key in _sequenceLevels) { + if (doNotReset[key]) { + activeSequences = true; + continue; + } + _sequenceLevels[key] = 0; + } + + if (!activeSequences) { + _nextExpectedAction = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + * + * @param {string} character + * @param {Array} modifiers + * @param {Event|Object} e + * @param {string=} sequenceName - name of the sequence we are looking for + * @param {string=} combination + * @param {number=} level + * @returns {Array} + */ + function _getMatches( + character, + modifiers, + e, + sequenceName, + combination, + level + ) { + var i; + var callback; + var matches = []; + var action = e.type; + + // if there are no events related to this keycode + if (!self._callbacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action == "keyup" && _isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < self._callbacks[character].length; ++i) { + callback = self._callbacks[character][i]; + + // if a sequence name is not specified, but this is a sequence at + // the wrong level then move onto the next match + if ( + !sequenceName && + callback.seq && + _sequenceLevels[callback.seq] != callback.level + ) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action != callback.action) { + continue; + } + + // if this is a keypress event and the meta key and control key + // are not pressed that means that we need to only look at the + // character, otherwise check the modifiers as well + // + // chrome will not fire a keypress if meta or control is down + // safari will fire a keypress if meta or meta+shift is down + // firefox will fire a keypress if meta or control is down + if ( + (action == "keypress" && !e.metaKey && !e.ctrlKey) || + _modifiersMatch(modifiers, callback.modifiers) + ) { + // when you bind a combination or sequence a second time it + // should overwrite the first one. if a sequenceName or + // combination is specified in this call it does just that + // + // @todo make deleting its own method? + var deleteCombo = !sequenceName && callback.combo == combination; + var deleteSequence = + sequenceName && + callback.seq == sequenceName && + callback.level == level; + if (deleteCombo || deleteSequence) { + self._callbacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + + return matches; + } + + /** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event + * + * @param {Function} callback + * @param {Event} e + * @returns void + */ + function _fireCallback(callback, e, combo, sequence) { + // if this event should not happen stop here + if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { + return; + } + + if (callback(e, combo) === false) { + _preventDefault(e); + _stopPropagation(e); + } + } + + /** + * handles a character key event + * + * @param {string} character + * @param {Array} modifiers + * @param {Event} e + * @returns void + */ + self._handleKey = function(character, modifiers, e) { + var callbacks = _getMatches(character, modifiers, e); + var i; + var doNotReset = {}; + var maxLevel = 0; + var processedSequenceCallback = false; + + // Calculate the maxLevel for sequences so we can only execute the longest callback sequence + for (i = 0; i < callbacks.length; ++i) { + if (callbacks[i].seq) { + maxLevel = Math.max(maxLevel, callbacks[i].level); + } + } + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + // only fire callbacks for the maxLevel to prevent + // subsequences from also firing + // + // for example 'a option b' should not cause 'option b' to fire + // even though 'option b' is part of the other sequence + // + // any sequences that do not match here will be discarded + // below by the _resetSequences call + if (callbacks[i].level != maxLevel) { + continue; + } + + processedSequenceCallback = true; + + // keep a list of which sequences were matches for later + doNotReset[callbacks[i].seq] = 1; + _fireCallback( + callbacks[i].callback, + e, + callbacks[i].combo, + callbacks[i].seq + ); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processedSequenceCallback) { + _fireCallback(callbacks[i].callback, e, callbacks[i].combo); + } + } + + // if the key you pressed matches the type of sequence without + // being a modifier (ie "keyup" or "keypress") then we should + // reset all sequences that were not matched by this event + // + // this is so, for example, if you have the sequence "h a t" and you + // type "h e a r t" it does not match. in this case the "e" will + // cause the sequence to reset + // + // modifier keys are ignored because you can have a sequence + // that contains modifiers such as "enter ctrl+space" and in most + // cases the modifier key will be pressed before the next key + // + // also if you have a sequence such as "ctrl+b a" then pressing the + // "b" key will trigger a "keypress" and a "keydown" + // + // the "keydown" is expected when there is a modifier, but the + // "keypress" ends up matching the _nextExpectedAction since it occurs + // after and that causes the sequence to reset + // + // we ignore keypresses in a sequence that directly follow a keydown + // for the same character + var ignoreThisKeypress = e.type == "keypress" && _ignoreNextKeypress; + if ( + e.type == _nextExpectedAction && + !_isModifier(character) && + !ignoreThisKeypress + ) { + _resetSequences(doNotReset); + } + + _ignoreNextKeypress = processedSequenceCallback && e.type == "keydown"; + }; + + /** + * handles a keydown event + * + * @param {Event} e + * @returns void + */ + self._handleKeyEvent = function(e) { + // normalize e.which for key events + // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion + if (typeof e.which !== "number") { + e.which = e.keyCode; + } + + var character = _characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + // need to use === for the character check because the character can be 0 + if (e.type == "keyup" && _ignoreNextKeyup === character) { + _ignoreNextKeyup = false; + return; + } + + self.handleKey(character, _eventModifiers(e), e); + }; + + /** + * called to set a 1 second timeout on the specified sequence + * + * this is so after each key press in the sequence you have 1 second + * to press the next key before you have to start over + * + * @returns void + */ + function _resetSequenceTimer() { + clearTimeout(_resetTimer); + _resetTimer = setTimeout(_resetSequences, 1000); + } + + /** + * binds a key sequence to an event + * + * @param {string} combo - combo specified in bind call + * @param {Array} keys + * @param {Function} callback + * @param {string=} action + * @returns void + */ + function _bindSequence(combo, keys, callback, action) { + // start off by adding a sequence level record for this combination + // and setting the level to 0 + _sequenceLevels[combo] = 0; + + /** + * callback to increase the sequence level for this sequence and reset + * all other sequences that were active + * + * @param {string} nextAction + * @returns {Function} + */ + function _increaseSequence(nextAction) { + return function() { + _nextExpectedAction = nextAction; + ++_sequenceLevels[combo]; + _resetSequenceTimer(); + }; + } + + /** + * wraps the specified callback inside of another function in order + * to reset all sequence counters as soon as this sequence is done + * + * @param {Event} e + * @returns void + */ + function _callbackAndReset(e) { + _fireCallback(callback, e, combo); + + // we should ignore the next key up if the action is key down + // or keypress. this is so if you finish a sequence and + // release the key the final key will not trigger a keyup + if (action !== "keyup") { + _ignoreNextKeyup = _characterFromEvent(e); + } + + // weird race condition if a sequence ends with the key + // another sequence begins with + setTimeout(_resetSequences, 10); + } + + // loop through keys one at a time and bind the appropriate callback + // function. for any key leading up to the final one it should + // increase the sequence. after the final, it should reset all sequences + // + // if an action is specified in the original bind call then that will + // be used throughout. otherwise we will pass the action that the + // next key in the sequence should match. this allows a sequence + // to mix and match keypress and keydown events depending on which + // ones are better suited to the key provided + for (var i = 0; i < keys.length; ++i) { + var isFinal = i + 1 === keys.length; + var wrappedCallback = isFinal + ? _callbackAndReset + : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); + _bindSingle(keys[i], wrappedCallback, action, combo, i); + } + } + + /** + * binds a single keyboard combination + * + * @param {string} combination + * @param {Function} callback + * @param {string=} action + * @param {string=} sequenceName - name of sequence if part of sequence + * @param {number=} level - what part of the sequence the command is + * @returns void + */ + function _bindSingle(combination, callback, action, sequenceName, level) { + // store a direct mapped reference for use with ItsATrap.trigger + self._directMap[combination + ":" + action] = callback; + + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, " "); + + var sequence = combination.split(" "); + var info; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + _bindSequence(combination, sequence, callback, action); + return; + } + + info = _getKeyInfo(combination, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + self._callbacks[info.key] = self._callbacks[info.key] || []; + + // remove an existing match if there is one + _getMatches( + info.key, + info.modifiers, + { type: info.action }, + sequenceName, + combination, + level + ); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + self._callbacks[info.key][sequenceName ? "unshift" : "push"]({ + callback: callback, + modifiers: info.modifiers, + action: info.action, + seq: sequenceName, + level: level, + combo: combination + }); + } + + /** + * binds multiple combinations to the same callback + * + * @param {Array} combinations + * @param {Function} callback + * @param {string|undefined} action + * @returns void + */ + self._bindMultiple = function(combinations, callback, action) { + for (var i = 0; i < combinations.length; ++i) { + _bindSingle(combinations[i], callback, action); + } + }; + + // start! + _addEvent(targetElement, "keypress", self._handleKeyEvent); + _addEvent(targetElement, "keydown", self._handleKeyEvent); + _addEvent(targetElement, "keyup", self._handleKeyEvent); + } + + /** + * binds an event to itsatrap + * + * can be a single key, a combination of keys separated with +, + * an array of keys, or a sequence of keys separated by spaces + * + * be sure to list the modifier keys first to make sure that the + * correct key ends up getting bound (the last key in the pattern) + * + * @param {string|Array} keys + * @param {Function} callback + * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @returns void + */ + ItsATrap.prototype.bind = function(keys, callback, action) { + var self = this; + keys = keys instanceof Array ? keys : [keys]; + self._bindMultiple.call(self, keys, callback, action); + return self; + }; + + /** + * unbinds an event to itsatrap + * + * the unbinding sets the callback function of the specified key combo + * to an empty function and deletes the corresponding key in the + * _directMap dict. + * + * TODO: actually remove this from the _callbacks dictionary instead + * of binding an empty function + * + * the keycombo+action has to be exactly the same as + * it was defined in the bind method + * + * @param {string|Array} keys + * @param {string} action + * @returns void + */ + ItsATrap.prototype.unbind = function(keys, action) { + var self = this; + return self.bind.call(self, keys, function() {}, action); + }; + + /** + * triggers an event that has already been bound + * + * @param {string} keys + * @param {string=} action + * @returns void + */ + ItsATrap.prototype.trigger = function(keys, action) { + var self = this; + if (self._directMap[keys + ":" + action]) { + self._directMap[keys + ":" + action]({}, keys); + } + return self; + }; + + /** + * resets the library back to its initial state. this is useful + * if you want to clear out the current keyboard shortcuts and bind + * new ones - for example if you switch to another page + * + * @returns void + */ + ItsATrap.prototype.reset = function() { + var self = this; + self._callbacks = {}; + self._directMap = {}; + return self; + }; + + /** + * destroy itsatrap object + * + * - call reset on the itsatrap object ( removing all binding ) + * - remove all javascript event listener from target element or document + * - remove all reference to target + * + * @return void + */ + + ItsATrap.prototype.destroy = function() { + var self = this; + + self.reset(); + + _removeEvent(self.target, "keypress", self._handleKeyEvent); + _removeEvent(self.target, "keydown", self._handleKeyEvent); + _removeEvent(self.target, "keyup", self._handleKeyEvent); + + self.target = undefined; + self._handleKeyEvent = undefined; + }; + + /** + * should we stop this event before firing off callbacks + * + * @param {Event} e + * @param {Element} element + * @return {boolean} + */ + ItsATrap.prototype.stopCallback = function(e, element, combo, sequence) { + var self = this; + + if (self.paused) { + return true; + } + + if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { + return false; + } + + // if the element has the class "itsatrap" then no need to stop + if ((" " + element.className + " ").indexOf(" itsatrap ") > -1) { + return false; + } + + if (_belongsTo(element, self.target)) { + return false; + } + + // Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host, + // not the initial event target in the shadow tree. Note that not all events cross the + // shadow boundary. + // For shadow trees with `mode: 'open'`, the initial event target is the first element in + // the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event + // target cannot be obtained. + if ("composedPath" in e && typeof e.composedPath === "function") { + // For open shadow trees, update `element` so that the following check works. + var initialEventTarget = e.composedPath()[0]; + if (initialEventTarget !== e.target) { + element = initialEventTarget; + } + } + + // stop for input, select, and textarea + return ( + element.tagName == "INPUT" || + element.tagName == "SELECT" || + element.tagName == "TEXTAREA" || + element.isContentEditable + ); + }; + + /** + * exposes _handleKey publicly so it can be overwritten by extensions + */ + ItsATrap.prototype.handleKey = function() { + var self = this; + return self._handleKey.apply(self, arguments); + }; + + /** + * allow custom key mappings + */ + ItsATrap.addKeycodes = function(object) { + for (var key in object) { + if (object.hasOwnProperty(key)) { + _MAP[key] = object[key]; + } + } + _REVERSE_MAP = null; + }; + + /** + * adds a pause and unpause method to ItsATrap + * this allows you to enable or disable keyboard shortcuts + * without having to reset ItsATrap and rebind everything + */ + ItsATrap.prototype.pause = function() { + var self = this; + self.paused = true; + }; + + ItsATrap.prototype.unpause = function() { + var self = this; + self.paused = false; + }; + + /** + * adds a bindGlobal method to ItsATrap that allows you to + * bind specific keyboard shortcuts that will still work + * inside a text input field + * + * usage: + * ItsATrap.bindGlobal('ctrl+s', _saveChanges); + */ + ItsATrap.prototype.bindGlobal = function(keys, callback, action) { + var self = this; + self.bind(keys, callback, action); + + if (keys instanceof Array) { + for (var i = 0; i < keys.length; i++) { + _globalCallbacks[keys[i]] = true; + } + return; + } + + _globalCallbacks[keys] = true; + }; + + // expose itsatrap to the global object + window.ItsATrap = ItsATrap; + + // expose as a common js module + if (typeof module !== "undefined" && module.exports) { + module.exports = ItsATrap; + } + + // expose itsatrap as an AMD module + if (typeof define === "function" && define.amd) { + define(function() { + return ItsATrap; + }); + } +})( + typeof window !== "undefined" ? window : null, + typeof window !== "undefined" ? document : null +); diff --git a/vendor/assets/javascripts/mousetrap-global-bind.js b/vendor/assets/javascripts/mousetrap-global-bind.js deleted file mode 100644 index 6b86a99fbfa..00000000000 --- a/vendor/assets/javascripts/mousetrap-global-bind.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * adds a bindGlobal method to Mousetrap that allows you to - * bind specific keyboard shortcuts that will still work - * inside a text input field - * - * usage: - * Mousetrap.bindGlobal('ctrl+s', _saveChanges); - */ -/* global Mousetrap:true */ -(function(Mousetrap) { - if (! Mousetrap) { - return; - } - var _globalCallbacks = {}; - var _originalStopCallback = Mousetrap.prototype.stopCallback; - - Mousetrap.prototype.stopCallback = function(e, element, combo, sequence) { - var self = this; - - if (self.paused) { - return true; - } - - if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { - return false; - } - - return _originalStopCallback.call(self, e, element, combo); - }; - - Mousetrap.prototype.bindGlobal = function(keys, callback, action) { - var self = this; - self.bind(keys, callback, action); - - if (keys instanceof Array) { - for (var i = 0; i < keys.length; i++) { - _globalCallbacks[keys[i]] = true; - } - return; - } - - _globalCallbacks[keys] = true; - }; - - Mousetrap.init(); -}) (typeof Mousetrap !== "undefined" ? Mousetrap : undefined); diff --git a/vendor/assets/javascripts/mousetrap.js b/vendor/assets/javascripts/mousetrap.js deleted file mode 100644 index bd469fcea87..00000000000 --- a/vendor/assets/javascripts/mousetrap.js +++ /dev/null @@ -1,1058 +0,0 @@ -/*global define:false */ -/** - * Copyright 2012-2017 Craig Campbell - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Mousetrap is a simple keyboard shortcut library for Javascript with - * no external dependencies - * - * @version 1.6.5 - * @url craig.is/killing/mice - */ -(function(window, document, undefined) { - - // Check if mousetrap is used inside browser, if not, return - if (!window) { - return; - } - - /** - * mapping of special keycodes to their corresponding keys - * - * everything in this dictionary cannot use keypress events - * so it has to be here to map to the correct keycodes for - * keyup/keydown events - * - * @type {Object} - */ - var _MAP = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 16: 'shift', - 17: 'ctrl', - 18: 'alt', - 20: 'capslock', - 27: 'esc', - 32: 'space', - 33: 'pageup', - 34: 'pagedown', - 35: 'end', - 36: 'home', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 45: 'ins', - 46: 'del', - 91: 'meta', - 93: 'meta', - 224: 'meta' - }; - - /** - * mapping for special characters so they can support - * - * this dictionary is only used incase you want to bind a - * keyup or keydown event to one of these keys - * - * @type {Object} - */ - var _KEYCODE_MAP = { - 106: '*', - 107: '+', - 109: '-', - 110: '.', - 111 : '/', - 186: ';', - 187: '=', - 188: ',', - 189: '-', - 190: '.', - 191: '/', - 192: '`', - 219: '[', - 220: '\\', - 221: ']', - 222: '\'' - }; - - /** - * this is a mapping of keys that require shift on a US keypad - * back to the non shift equivelents - * - * this is so you can use keyup events with these keys - * - * note that this will only work reliably on US keyboards - * - * @type {Object} - */ - var _SHIFT_MAP = { - '~': '`', - '!': '1', - '@': '2', - '#': '3', - '$': '4', - '%': '5', - '^': '6', - '&': '7', - '*': '8', - '(': '9', - ')': '0', - '_': '-', - '+': '=', - ':': ';', - '\"': '\'', - '<': ',', - '>': '.', - '?': '/', - '|': '\\' - }; - - /** - * this is a list of special strings you can use to map - * to modifier keys when you specify your keyboard shortcuts - * - * @type {Object} - */ - var _SPECIAL_ALIASES = { - 'option': 'alt', - 'command': 'meta', - 'return': 'enter', - 'escape': 'esc', - 'plus': '+', - 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' - }; - - /** - * variable to store the flipped version of _MAP from above - * needed to check if we should use keypress or not when no action - * is specified - * - * @type {Object|undefined} - */ - var _REVERSE_MAP; - - /** - * loop through the f keys, f1 to f19 and add them to the map - * programatically - */ - for (var i = 1; i < 20; ++i) { - _MAP[111 + i] = 'f' + i; - } - - /** - * loop through to map numbers on the numeric keypad - */ - for (i = 0; i <= 9; ++i) { - - // This needs to use a string cause otherwise since 0 is falsey - // mousetrap will never fire for numpad 0 pressed as part of a keydown - // event. - // - // @see https://github.com/ccampbell/mousetrap/pull/258 - _MAP[i + 96] = i.toString(); - } - - /** - * cross browser add event method - * - * @param {Element|HTMLDocument} object - * @param {string} type - * @param {Function} callback - * @returns void - */ - function _addEvent(object, type, callback) { - if (object.addEventListener) { - object.addEventListener(type, callback, false); - return; - } - - object.attachEvent('on' + type, callback); - } - - /** - * takes the event and returns the key character - * - * @param {Event} e - * @return {string} - */ - function _characterFromEvent(e) { - - // for keypress events we should return the character as is - if (e.type == 'keypress') { - var character = String.fromCharCode(e.which); - - // if the shift key is not pressed then it is safe to assume - // that we want the character to be lowercase. this means if - // you accidentally have caps lock on then your key bindings - // will continue to work - // - // the only side effect that might not be desired is if you - // bind something like 'A' cause you want to trigger an - // event when capital A is pressed caps lock will no longer - // trigger the event. shift+a will though. - if (!e.shiftKey) { - character = character.toLowerCase(); - } - - return character; - } - - // for non keypress events the special maps are needed - if (_MAP[e.which]) { - return _MAP[e.which]; - } - - if (_KEYCODE_MAP[e.which]) { - return _KEYCODE_MAP[e.which]; - } - - // if it is not in the special map - - // with keydown and keyup events the character seems to always - // come in as an uppercase character whether you are pressing shift - // or not. we should make sure it is always lowercase for comparisons - return String.fromCharCode(e.which).toLowerCase(); - } - - /** - * checks if two arrays are equal - * - * @param {Array} modifiers1 - * @param {Array} modifiers2 - * @returns {boolean} - */ - function _modifiersMatch(modifiers1, modifiers2) { - return modifiers1.sort().join(',') === modifiers2.sort().join(','); - } - - /** - * takes a key event and figures out what the modifiers are - * - * @param {Event} e - * @returns {Array} - */ - function _eventModifiers(e) { - var modifiers = []; - - if (e.shiftKey) { - modifiers.push('shift'); - } - - if (e.altKey) { - modifiers.push('alt'); - } - - if (e.ctrlKey) { - modifiers.push('ctrl'); - } - - if (e.metaKey) { - modifiers.push('meta'); - } - - return modifiers; - } - - /** - * prevents default for this event - * - * @param {Event} e - * @returns void - */ - function _preventDefault(e) { - if (e.preventDefault) { - e.preventDefault(); - return; - } - - e.returnValue = false; - } - - /** - * stops propogation for this event - * - * @param {Event} e - * @returns void - */ - function _stopPropagation(e) { - if (e.stopPropagation) { - e.stopPropagation(); - return; - } - - e.cancelBubble = true; - } - - /** - * determines if the keycode specified is a modifier key or not - * - * @param {string} key - * @returns {boolean} - */ - function _isModifier(key) { - return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; - } - - /** - * reverses the map lookup so that we can look for specific keys - * to see what can and can't use keypress - * - * @return {Object} - */ - function _getReverseMap() { - if (!_REVERSE_MAP) { - _REVERSE_MAP = {}; - for (var key in _MAP) { - - // pull out the numeric keypad from here cause keypress should - // be able to detect the keys from the character - if (key > 95 && key < 112) { - continue; - } - - if (_MAP.hasOwnProperty(key)) { - _REVERSE_MAP[_MAP[key]] = key; - } - } - } - return _REVERSE_MAP; - } - - /** - * picks the best action based on the key combination - * - * @param {string} key - character for key - * @param {Array} modifiers - * @param {string=} action passed in - */ - function _pickBestAction(key, modifiers, action) { - - // if no action was picked in we should try to pick the one - // that we think would work best for this key - if (!action) { - action = _getReverseMap()[key] ? 'keydown' : 'keypress'; - } - - // modifier keys don't work as expected with keypress, - // switch to keydown - if (action == 'keypress' && modifiers.length) { - action = 'keydown'; - } - - return action; - } - - /** - * Converts from a string key combination to an array - * - * @param {string} combination like "command+shift+l" - * @return {Array} - */ - function _keysFromString(combination) { - if (combination === '+') { - return ['+']; - } - - combination = combination.replace(/\+{2}/g, '+plus'); - return combination.split('+'); - } - - /** - * Gets info for a specific key combination - * - * @param {string} combination key combination ("command+s" or "a" or "*") - * @param {string=} action - * @returns {Object} - */ - function _getKeyInfo(combination, action) { - var keys; - var key; - var i; - var modifiers = []; - - // take the keys from this pattern and figure out what the actual - // pattern is all about - keys = _keysFromString(combination); - - for (i = 0; i < keys.length; ++i) { - key = keys[i]; - - // normalize key names - if (_SPECIAL_ALIASES[key]) { - key = _SPECIAL_ALIASES[key]; - } - - // if this is not a keypress event then we should - // be smart about using shift keys - // this will only work for US keyboards however - if (action && action != 'keypress' && _SHIFT_MAP[key]) { - key = _SHIFT_MAP[key]; - modifiers.push('shift'); - } - - // if this key is a modifier then add it to the list of modifiers - if (_isModifier(key)) { - modifiers.push(key); - } - } - - // depending on what the key combination is - // we will try to pick the best event for it - action = _pickBestAction(key, modifiers, action); - - return { - key: key, - modifiers: modifiers, - action: action - }; - } - - function _belongsTo(element, ancestor) { - if (element === null || element === document) { - return false; - } - - if (element === ancestor) { - return true; - } - - return _belongsTo(element.parentNode, ancestor); - } - - function Mousetrap(targetElement) { - var self = this; - - targetElement = targetElement || document; - - if (!(self instanceof Mousetrap)) { - return new Mousetrap(targetElement); - } - - /** - * element to attach key events to - * - * @type {Element} - */ - self.target = targetElement; - - /** - * a list of all the callbacks setup via Mousetrap.bind() - * - * @type {Object} - */ - self._callbacks = {}; - - /** - * direct map of string combinations to callbacks used for trigger() - * - * @type {Object} - */ - self._directMap = {}; - - /** - * keeps track of what level each sequence is at since multiple - * sequences can start out with the same sequence - * - * @type {Object} - */ - var _sequenceLevels = {}; - - /** - * variable to store the setTimeout call - * - * @type {null|number} - */ - var _resetTimer; - - /** - * temporary state where we will ignore the next keyup - * - * @type {boolean|string} - */ - var _ignoreNextKeyup = false; - - /** - * temporary state where we will ignore the next keypress - * - * @type {boolean} - */ - var _ignoreNextKeypress = false; - - /** - * are we currently inside of a sequence? - * type of action ("keyup" or "keydown" or "keypress") or false - * - * @type {boolean|string} - */ - var _nextExpectedAction = false; - - /** - * resets all sequence counters except for the ones passed in - * - * @param {Object} doNotReset - * @returns void - */ - function _resetSequences(doNotReset) { - doNotReset = doNotReset || {}; - - var activeSequences = false, - key; - - for (key in _sequenceLevels) { - if (doNotReset[key]) { - activeSequences = true; - continue; - } - _sequenceLevels[key] = 0; - } - - if (!activeSequences) { - _nextExpectedAction = false; - } - } - - /** - * finds all callbacks that match based on the keycode, modifiers, - * and action - * - * @param {string} character - * @param {Array} modifiers - * @param {Event|Object} e - * @param {string=} sequenceName - name of the sequence we are looking for - * @param {string=} combination - * @param {number=} level - * @returns {Array} - */ - function _getMatches(character, modifiers, e, sequenceName, combination, level) { - var i; - var callback; - var matches = []; - var action = e.type; - - // if there are no events related to this keycode - if (!self._callbacks[character]) { - return []; - } - - // if a modifier key is coming up on its own we should allow it - if (action == 'keyup' && _isModifier(character)) { - modifiers = [character]; - } - - // loop through all callbacks for the key that was pressed - // and see if any of them match - for (i = 0; i < self._callbacks[character].length; ++i) { - callback = self._callbacks[character][i]; - - // if a sequence name is not specified, but this is a sequence at - // the wrong level then move onto the next match - if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { - continue; - } - - // if the action we are looking for doesn't match the action we got - // then we should keep going - if (action != callback.action) { - continue; - } - - // if this is a keypress event and the meta key and control key - // are not pressed that means that we need to only look at the - // character, otherwise check the modifiers as well - // - // chrome will not fire a keypress if meta or control is down - // safari will fire a keypress if meta or meta+shift is down - // firefox will fire a keypress if meta, alt or control is down - if ((action == 'keypress' && !e.metaKey && !e.altKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { - - // when you bind a combination or sequence a second time it - // should overwrite the first one. if a sequenceName or - // combination is specified in this call it does just that - // - // @todo make deleting its own method? - var deleteCombo = !sequenceName && callback.combo == combination; - var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; - if (deleteCombo || deleteSequence) { - self._callbacks[character].splice(i, 1); - } - - matches.push(callback); - } - } - - return matches; - } - - /** - * actually calls the callback function - * - * if your callback function returns false this will use the jquery - * convention - prevent default and stop propogation on the event - * - * @param {Function} callback - * @param {Event} e - * @returns void - */ - function _fireCallback(callback, e, combo, sequence) { - - // if this event should not happen stop here - if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { - return; - } - - if (callback(e, combo) === false) { - _preventDefault(e); - _stopPropagation(e); - } - } - - /** - * handles a character key event - * - * @param {string} character - * @param {Array} modifiers - * @param {Event} e - * @returns void - */ - self._handleKey = function(character, modifiers, e) { - var callbacks = _getMatches(character, modifiers, e); - var i; - var doNotReset = {}; - var maxLevel = 0; - var processedSequenceCallback = false; - - // Calculate the maxLevel for sequences so we can only execute the longest callback sequence - for (i = 0; i < callbacks.length; ++i) { - if (callbacks[i].seq) { - maxLevel = Math.max(maxLevel, callbacks[i].level); - } - } - - // loop through matching callbacks for this key event - for (i = 0; i < callbacks.length; ++i) { - - // fire for all sequence callbacks - // this is because if for example you have multiple sequences - // bound such as "g i" and "g t" they both need to fire the - // callback for matching g cause otherwise you can only ever - // match the first one - if (callbacks[i].seq) { - - // only fire callbacks for the maxLevel to prevent - // subsequences from also firing - // - // for example 'a option b' should not cause 'option b' to fire - // even though 'option b' is part of the other sequence - // - // any sequences that do not match here will be discarded - // below by the _resetSequences call - if (callbacks[i].level != maxLevel) { - continue; - } - - processedSequenceCallback = true; - - // keep a list of which sequences were matches for later - doNotReset[callbacks[i].seq] = 1; - _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); - continue; - } - - // if there were no sequence matches but we are still here - // that means this is a regular match so we should fire that - if (!processedSequenceCallback) { - _fireCallback(callbacks[i].callback, e, callbacks[i].combo); - } - } - - // if the key you pressed matches the type of sequence without - // being a modifier (ie "keyup" or "keypress") then we should - // reset all sequences that were not matched by this event - // - // this is so, for example, if you have the sequence "h a t" and you - // type "h e a r t" it does not match. in this case the "e" will - // cause the sequence to reset - // - // modifier keys are ignored because you can have a sequence - // that contains modifiers such as "enter ctrl+space" and in most - // cases the modifier key will be pressed before the next key - // - // also if you have a sequence such as "ctrl+b a" then pressing the - // "b" key will trigger a "keypress" and a "keydown" - // - // the "keydown" is expected when there is a modifier, but the - // "keypress" ends up matching the _nextExpectedAction since it occurs - // after and that causes the sequence to reset - // - // we ignore keypresses in a sequence that directly follow a keydown - // for the same character - var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; - if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { - _resetSequences(doNotReset); - } - - _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; - }; - - /** - * handles a keydown event - * - * @param {Event} e - * @returns void - */ - function _handleKeyEvent(e) { - - // normalize e.which for key events - // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion - if (typeof e.which !== 'number') { - e.which = e.keyCode; - } - - var character = _characterFromEvent(e); - - // no character found then stop - if (!character) { - return; - } - - // need to use === for the character check because the character can be 0 - if (e.type == 'keyup' && _ignoreNextKeyup === character) { - _ignoreNextKeyup = false; - return; - } - - self.handleKey(character, _eventModifiers(e), e); - } - - /** - * called to set a 1 second timeout on the specified sequence - * - * this is so after each key press in the sequence you have 1 second - * to press the next key before you have to start over - * - * @returns void - */ - function _resetSequenceTimer() { - clearTimeout(_resetTimer); - _resetTimer = setTimeout(_resetSequences, 1000); - } - - /** - * binds a key sequence to an event - * - * @param {string} combo - combo specified in bind call - * @param {Array} keys - * @param {Function} callback - * @param {string=} action - * @returns void - */ - function _bindSequence(combo, keys, callback, action) { - - // start off by adding a sequence level record for this combination - // and setting the level to 0 - _sequenceLevels[combo] = 0; - - /** - * callback to increase the sequence level for this sequence and reset - * all other sequences that were active - * - * @param {string} nextAction - * @returns {Function} - */ - function _increaseSequence(nextAction) { - return function() { - _nextExpectedAction = nextAction; - ++_sequenceLevels[combo]; - _resetSequenceTimer(); - }; - } - - /** - * wraps the specified callback inside of another function in order - * to reset all sequence counters as soon as this sequence is done - * - * @param {Event} e - * @returns void - */ - function _callbackAndReset(e) { - _fireCallback(callback, e, combo); - - // we should ignore the next key up if the action is key down - // or keypress. this is so if you finish a sequence and - // release the key the final key will not trigger a keyup - if (action !== 'keyup') { - _ignoreNextKeyup = _characterFromEvent(e); - } - - // weird race condition if a sequence ends with the key - // another sequence begins with - setTimeout(_resetSequences, 10); - } - - // loop through keys one at a time and bind the appropriate callback - // function. for any key leading up to the final one it should - // increase the sequence. after the final, it should reset all sequences - // - // if an action is specified in the original bind call then that will - // be used throughout. otherwise we will pass the action that the - // next key in the sequence should match. this allows a sequence - // to mix and match keypress and keydown events depending on which - // ones are better suited to the key provided - for (var i = 0; i < keys.length; ++i) { - var isFinal = i + 1 === keys.length; - var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); - _bindSingle(keys[i], wrappedCallback, action, combo, i); - } - } - - /** - * binds a single keyboard combination - * - * @param {string} combination - * @param {Function} callback - * @param {string=} action - * @param {string=} sequenceName - name of sequence if part of sequence - * @param {number=} level - what part of the sequence the command is - * @returns void - */ - function _bindSingle(combination, callback, action, sequenceName, level) { - - // store a direct mapped reference for use with Mousetrap.trigger - self._directMap[combination + ':' + action] = callback; - - // make sure multiple spaces in a row become a single space - combination = combination.replace(/\s+/g, ' '); - - var sequence = combination.split(' '); - var info; - - // if this pattern is a sequence of keys then run through this method - // to reprocess each pattern one key at a time - if (sequence.length > 1) { - _bindSequence(combination, sequence, callback, action); - return; - } - - info = _getKeyInfo(combination, action); - - // make sure to initialize array if this is the first time - // a callback is added for this key - self._callbacks[info.key] = self._callbacks[info.key] || []; - - // remove an existing match if there is one - _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); - - // add this call back to the array - // if it is a sequence put it at the beginning - // if not put it at the end - // - // this is important because the way these are processed expects - // the sequence ones to come first - self._callbacks[info.key][sequenceName ? 'unshift' : 'push']({ - callback: callback, - modifiers: info.modifiers, - action: info.action, - seq: sequenceName, - level: level, - combo: combination - }); - } - - /** - * binds multiple combinations to the same callback - * - * @param {Array} combinations - * @param {Function} callback - * @param {string|undefined} action - * @returns void - */ - self._bindMultiple = function(combinations, callback, action) { - for (var i = 0; i < combinations.length; ++i) { - _bindSingle(combinations[i], callback, action); - } - }; - - // start! - _addEvent(targetElement, 'keypress', _handleKeyEvent); - _addEvent(targetElement, 'keydown', _handleKeyEvent); - _addEvent(targetElement, 'keyup', _handleKeyEvent); - } - - /** - * binds an event to mousetrap - * - * can be a single key, a combination of keys separated with +, - * an array of keys, or a sequence of keys separated by spaces - * - * be sure to list the modifier keys first to make sure that the - * correct key ends up getting bound (the last key in the pattern) - * - * @param {string|Array} keys - * @param {Function} callback - * @param {string=} action - 'keypress', 'keydown', or 'keyup' - * @returns void - */ - Mousetrap.prototype.bind = function(keys, callback, action) { - var self = this; - keys = keys instanceof Array ? keys : [keys]; - self._bindMultiple.call(self, keys, callback, action); - return self; - }; - - /** - * unbinds an event to mousetrap - * - * the unbinding sets the callback function of the specified key combo - * to an empty function and deletes the corresponding key in the - * _directMap dict. - * - * TODO: actually remove this from the _callbacks dictionary instead - * of binding an empty function - * - * the keycombo+action has to be exactly the same as - * it was defined in the bind method - * - * @param {string|Array} keys - * @param {string} action - * @returns void - */ - Mousetrap.prototype.unbind = function(keys, action) { - var self = this; - return self.bind.call(self, keys, function() {}, action); - }; - - /** - * triggers an event that has already been bound - * - * @param {string} keys - * @param {string=} action - * @returns void - */ - Mousetrap.prototype.trigger = function(keys, action) { - var self = this; - if (self._directMap[keys + ':' + action]) { - self._directMap[keys + ':' + action]({}, keys); - } - return self; - }; - - /** - * resets the library back to its initial state. this is useful - * if you want to clear out the current keyboard shortcuts and bind - * new ones - for example if you switch to another page - * - * @returns void - */ - Mousetrap.prototype.reset = function() { - var self = this; - self._callbacks = {}; - self._directMap = {}; - return self; - }; - - /** - * should we stop this event before firing off callbacks - * - * @param {Event} e - * @param {Element} element - * @return {boolean} - */ - Mousetrap.prototype.stopCallback = function(e, element) { - var self = this; - - // if the element has the class "mousetrap" then no need to stop - if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { - return false; - } - - if (_belongsTo(element, self.target)) { - return false; - } - - // Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host, - // not the initial event target in the shadow tree. Note that not all events cross the - // shadow boundary. - // For shadow trees with `mode: 'open'`, the initial event target is the first element in - // the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event - // target cannot be obtained. - if ('composedPath' in e && typeof e.composedPath === 'function') { - // For open shadow trees, update `element` so that the following check works. - var initialEventTarget = e.composedPath()[0]; - if (initialEventTarget !== e.target) { - element = initialEventTarget; - } - } - - // stop for input, select, and textarea - return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; - }; - - /** - * exposes _handleKey publicly so it can be overwritten by extensions - */ - Mousetrap.prototype.handleKey = function() { - var self = this; - return self._handleKey.apply(self, arguments); - }; - - /** - * allow custom key mappings - */ - Mousetrap.addKeycodes = function(object) { - for (var key in object) { - if (object.hasOwnProperty(key)) { - _MAP[key] = object[key]; - } - } - _REVERSE_MAP = null; - }; - - /** - * Init the global mousetrap functions - * - * This method is needed to allow the global mousetrap functions to work - * now that mousetrap is a constructor function. - */ - Mousetrap.init = function() { - var documentMousetrap = Mousetrap(document); - for (var method in documentMousetrap) { - if (method.charAt(0) !== '_') { - Mousetrap[method] = (function(method) { - return function() { - return documentMousetrap[method].apply(documentMousetrap, arguments); - }; - } (method)); - } - } - }; - - Mousetrap.init(); - - // expose mousetrap to the global object - window.Mousetrap = Mousetrap; - - // expose as a common js module - if (typeof module !== 'undefined' && module.exports) { - module.exports = Mousetrap; - } - - // expose mousetrap as an AMD module - if (typeof define === 'function' && define.amd) { - define(function() { - return Mousetrap; - }); - } -}) (typeof window !== 'undefined' ? window : null, typeof window !== 'undefined' ? document : null); diff --git a/yarn.lock b/yarn.lock index 2636311b107..7bf8c178c36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -245,6 +245,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@discourse/itsatrap@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@discourse/itsatrap/-/itsatrap-2.0.10.tgz#c7e750eeb32b54e769e952c4ecc472213eb1385a" + integrity sha512-Jn1gdiyHMGUsmUfLFf4Q7VnTAv0l7NePbegU6pKhKHEmbzV3FosGxq30fTOYgVyTS1bxqGjlA6LvQttJpv3ROw== + "@ember-data/rfc395-data@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843" @@ -3116,10 +3121,6 @@ moment@2.29.1: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425" integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA== -"mousetrap@https://github.com/discourse/mousetrap#firefox-alt-key": - version "1.6.5" - resolved "https://github.com/discourse/mousetrap#cc8e2c0b9229e1a01ce68de4f339b6fd35503041" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"