|
@@ -0,0 +1,157 @@
|
|
|
+// Pressing `Escape` should close the last opened “element” and only the last
|
|
|
+// one. Components can register themselves using a label a condition, and an
|
|
|
+// action. This is used by Popup or inlinedForm for instance. When we press
|
|
|
+// escape we execute the action which have a valid condition and his the highest
|
|
|
+// in the label hierarchy.
|
|
|
+EscapeActions = {
|
|
|
+ _actions: [],
|
|
|
+
|
|
|
+ // Executed in order
|
|
|
+ hierarchy: [
|
|
|
+ 'textcomplete',
|
|
|
+ 'popup',
|
|
|
+ 'inlinedForm',
|
|
|
+ 'detailsPane',
|
|
|
+ 'multiselection',
|
|
|
+ 'sidebarView'
|
|
|
+ ],
|
|
|
+
|
|
|
+ register: function(label, action, condition, options) {
|
|
|
+ condition = condition || function() { return true; };
|
|
|
+ options = options || {};
|
|
|
+
|
|
|
+ // XXX Rewrite this with ES6: .push({ priority, condition, action })
|
|
|
+ var priority = this.hierarchy.indexOf(label);
|
|
|
+ if (priority === -1) {
|
|
|
+ throw Error('You must define the label in the EscapeActions hierarchy');
|
|
|
+ }
|
|
|
+
|
|
|
+ this._actions.push({
|
|
|
+ priority: priority,
|
|
|
+ condition: condition,
|
|
|
+ action: action,
|
|
|
+ noClickEscapeOn: options.noClickEscapeOn
|
|
|
+ });
|
|
|
+ // XXX Rewrite this with ES6: => function
|
|
|
+ this._actions = _.sortBy(this._actions, function(a) { return a.priority; });
|
|
|
+ },
|
|
|
+
|
|
|
+ executeLowest: function() {
|
|
|
+ return this._execute({
|
|
|
+ multipleAction: false
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ executeAll: function() {
|
|
|
+ return this._execute({
|
|
|
+ multipleActions: true
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ executeUpTo: function(maxLabel) {
|
|
|
+ return this._execute({
|
|
|
+ maxLabel: maxLabel,
|
|
|
+ multipleActions: true
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ clickExecute: function(evt, maxLabel) {
|
|
|
+ return this._execute({
|
|
|
+ maxLabel: maxLabel,
|
|
|
+ multipleActions: false,
|
|
|
+ evt: evt
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ _stopClick: function(action, clickTarget) {
|
|
|
+ if (! _.isString(action.noClickEscapeOn))
|
|
|
+ return false;
|
|
|
+ else
|
|
|
+ return $(clickTarget).closest(action.noClickEscapeOn).length > 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ _execute: function(options) {
|
|
|
+ var maxLabel = options.maxLabel;
|
|
|
+ var evt = options.evt || {};
|
|
|
+ var multipleActions = options.multipleActions;
|
|
|
+
|
|
|
+ var maxPriority, currentAction;
|
|
|
+ var executedAtLeastOne = false;
|
|
|
+ if (! maxLabel)
|
|
|
+ maxPriority = Infinity;
|
|
|
+ else
|
|
|
+ maxPriority = this.hierarchy.indexOf(maxLabel);
|
|
|
+
|
|
|
+ for (var i = 0; i < this._actions.length; i++) {
|
|
|
+ currentAction = this._actions[i];
|
|
|
+ if (currentAction.priority > maxPriority)
|
|
|
+ return executedAtLeastOne;
|
|
|
+
|
|
|
+ if (evt.type === 'click' && this._stopClick(currentAction, evt.target))
|
|
|
+ return executedAtLeastOne;
|
|
|
+
|
|
|
+ if (currentAction.condition()) {
|
|
|
+ currentAction.action(evt);
|
|
|
+ executedAtLeastOne = true;
|
|
|
+ if (! multipleActions)
|
|
|
+ return executedAtLeastOne;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return executedAtLeastOne;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// MouseTrap plugin bindGlobal plugin. 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);
|
|
|
+//
|
|
|
+// source:
|
|
|
+// https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind
|
|
|
+var _globalCallbacks = {};
|
|
|
+var _originalStopCallback = Mousetrap.stopCallback;
|
|
|
+
|
|
|
+Mousetrap.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.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;
|
|
|
+};
|
|
|
+
|
|
|
+// Pressing escape to execute one escape action. We use `bindGloabal` vecause
|
|
|
+// the shortcut sould work on textarea and inputs as well.
|
|
|
+Mousetrap.bindGlobal('esc', function() {
|
|
|
+ EscapeActions.executeLowest();
|
|
|
+});
|
|
|
+
|
|
|
+// On a left click on the document, we try to exectute one escape action (eg,
|
|
|
+// close the popup). We don't execute any action if the user has clicked on a
|
|
|
+// link or a button.
|
|
|
+$(document).on('click', function(evt) {
|
|
|
+ if (evt.which === 1 && $(evt.target).closest('a,button').length === 0) {
|
|
|
+ EscapeActions.clickExecute(evt, 'detailsPane');
|
|
|
+ }
|
|
|
+});
|