UNPKG

5.15 kBJavaScriptView Raw
1/**
2 * This extension allows you to record a sequence using Mousetrap.
3 *
4 * @author Dan Tao <daniel.tao@gmail.com>
5 */
6(function(Mousetrap) {
7 /**
8 * the sequence currently being recorded
9 *
10 * @type {Array}
11 */
12 var _recordedSequence = [],
13
14 /**
15 * a callback to invoke after recording a sequence
16 *
17 * @type {Function|null}
18 */
19 _recordedSequenceCallback = null,
20
21 /**
22 * a list of all of the keys currently held down
23 *
24 * @type {Array}
25 */
26 _currentRecordedKeys = [],
27
28 /**
29 * temporary state where we remember if we've already captured a
30 * character key in the current combo
31 *
32 * @type {boolean}
33 */
34 _recordedCharacterKey = false,
35
36 /**
37 * a handle for the timer of the current recording
38 *
39 * @type {null|number}
40 */
41 _recordTimer = null,
42
43 /**
44 * the original handleKey method to override when Mousetrap.record() is
45 * called
46 *
47 * @type {Function}
48 */
49 _origHandleKey = Mousetrap.handleKey;
50
51 /**
52 * handles a character key event
53 *
54 * @param {string} character
55 * @param {Array} modifiers
56 * @param {Event} e
57 * @returns void
58 */
59 function _handleKey(character, modifiers, e) {
60 // remember this character if we're currently recording a sequence
61 if (e.type == 'keydown') {
62 if (character.length === 1 && _recordedCharacterKey) {
63 _recordCurrentCombo();
64 }
65
66 for (i = 0; i < modifiers.length; ++i) {
67 _recordKey(modifiers[i]);
68 }
69 _recordKey(character);
70
71 // once a key is released, all keys that were held down at the time
72 // count as a keypress
73 } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
74 _recordCurrentCombo();
75 }
76 }
77
78 /**
79 * marks a character key as held down while recording a sequence
80 *
81 * @param {string} key
82 * @returns void
83 */
84 function _recordKey(key) {
85 var i;
86
87 // one-off implementation of Array.indexOf, since IE6-9 don't support it
88 for (i = 0; i < _currentRecordedKeys.length; ++i) {
89 if (_currentRecordedKeys[i] === key) {
90 return;
91 }
92 }
93
94 _currentRecordedKeys.push(key);
95
96 if (key.length === 1) {
97 _recordedCharacterKey = true;
98 }
99 }
100
101 /**
102 * marks whatever key combination that's been recorded so far as finished
103 * and gets ready for the next combo
104 *
105 * @returns void
106 */
107 function _recordCurrentCombo() {
108 _recordedSequence.push(_currentRecordedKeys);
109 _currentRecordedKeys = [];
110 _recordedCharacterKey = false;
111 _restartRecordTimer();
112 }
113
114 /**
115 * ensures each combo in a sequence is in a predictable order and formats
116 * key combos to be '+'-delimited
117 *
118 * modifies the sequence in-place
119 *
120 * @param {Array} sequence
121 * @returns void
122 */
123 function _normalizeSequence(sequence) {
124 var i;
125
126 for (i = 0; i < sequence.length; ++i) {
127 sequence[i].sort(function(x, y) {
128 // modifier keys always come first, in alphabetical order
129 if (x.length > 1 && y.length === 1) {
130 return -1;
131 } else if (x.length === 1 && y.length > 1) {
132 return 1;
133 }
134
135 // character keys come next (list should contain no duplicates,
136 // so no need for equality check)
137 return x > y ? 1 : -1;
138 });
139
140 sequence[i] = sequence[i].join('+');
141 }
142 }
143
144 /**
145 * finishes the current recording, passes the recorded sequence to the stored
146 * callback, and sets Mousetrap.handleKey back to its original function
147 *
148 * @returns void
149 */
150 function _finishRecording() {
151 if (_recordedSequenceCallback) {
152 _normalizeSequence(_recordedSequence);
153 _recordedSequenceCallback(_recordedSequence);
154 }
155
156 // reset all recorded state
157 _recordedSequence = [];
158 _recordedSequenceCallback = null;
159 _currentRecordedKeys = [];
160
161 Mousetrap.handleKey = _origHandleKey;
162 }
163
164 /**
165 * called to set a 1 second timeout on the current recording
166 *
167 * this is so after each key press in the sequence the recording will wait for
168 * 1 more second before executing the callback
169 *
170 * @returns void
171 */
172 function _restartRecordTimer() {
173 clearTimeout(_recordTimer);
174 _recordTimer = setTimeout(_finishRecording, 1000);
175 }
176
177 /**
178 * records the next sequence and passes it to a callback once it's
179 * completed
180 *
181 * @param {Function} callback
182 * @returns void
183 */
184 Mousetrap.record = function(callback) {
185 Mousetrap.handleKey = _handleKey;
186 _recordedSequenceCallback = callback;
187 };
188
189})(Mousetrap);