summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/components/boards/boardHeader.jade8
-rw-r--r--client/components/boards/router.js7
-rw-r--r--client/components/cards/details.js8
-rw-r--r--client/components/forms/inlinedform.jade2
-rw-r--r--client/components/forms/inlinedform.js18
-rw-r--r--client/components/lists/main.js2
-rw-r--r--client/components/main/header.styl12
-rw-r--r--client/components/main/popup.js5
-rw-r--r--client/components/main/popup.tpl.jade2
-rw-r--r--client/components/sidebar/sidebar.js2
-rw-r--r--client/components/users/userHeader.jade6
-rw-r--r--client/config/router.js2
-rw-r--r--client/lib/escapeActions.js157
-rw-r--r--client/lib/keyboard.js69
-rw-r--r--client/lib/multiSelection.js9
-rw-r--r--client/lib/popup.js25
-rw-r--r--client/styles/main.styl10
17 files changed, 199 insertions, 145 deletions
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade
index 86fbe255..258fe843 100644
--- a/client/components/boards/boardHeader.jade
+++ b/client/components/boards/boardHeader.jade
@@ -1,7 +1,7 @@
template(name="headerBoard")
- h1.header-board-menu(
- class="{{#if currentUser.isBoardMember}}is-clickable js-edit-board-title{{/if}}")
- = title
+ h1.header-board-menu
+ a(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}")
+ = title
.board-header-btns.left
unless isSandstorm
@@ -12,7 +12,7 @@ template(name="headerBoard")
if showStarCounter
span {{_ 'board-nb-stars' stars}}
- a.board-header-btn.js-change-visibility(class="{{#unless currentUser.isBoardAdmin}}no-edit{{/unless}}")
+ a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}")
i.fa(class="{{#if isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ permission}}
diff --git a/client/components/boards/router.js b/client/components/boards/router.js
index e5ccecdb..1c485225 100644
--- a/client/components/boards/router.js
+++ b/client/components/boards/router.js
@@ -43,6 +43,7 @@ Router.route('/boards/:boardId/:slug/:cardId', {
Sidebar.hide();
}
});
+ EscapeActions.executeUpTo('popup');
var params = this.params;
Session.set('currentBoard', params.boardId);
Session.set('currentCard', params.cardId);
@@ -55,9 +56,3 @@ Router.route('/boards/:boardId/:slug/:cardId', {
return Boards.findOne(this.params.boardId);
}
});
-
-// Close the card details pane by pressing escape
-EscapeActions.register('detailsPane',
- function() { Utils.goBoardId(Session.get('currentBoard')); },
- function() { return ! Session.equals('currentCard', null); }
-);
diff --git a/client/components/cards/details.js b/client/components/cards/details.js
index 6ab7da22..3f141622 100644
--- a/client/components/cards/details.js
+++ b/client/components/cards/details.js
@@ -94,3 +94,11 @@ Template.moveCardPopup.events({
Popup.close();
}
});
+
+// Close the card details pane by pressing escape
+EscapeActions.register('detailsPane',
+ function() { Utils.goBoardId(Session.get('currentBoard')); },
+ function() { return ! Session.equals('currentCard', null); }, {
+ noClickEscapeOn: '.js-card-details'
+ }
+);
diff --git a/client/components/forms/inlinedform.jade b/client/components/forms/inlinedform.jade
index 5ad9039e..40e1c35c 100644
--- a/client/components/forms/inlinedform.jade
+++ b/client/components/forms/inlinedform.jade
@@ -1,6 +1,6 @@
template(name='inlinedForm')
if isOpen.get
- form(id=id class=classNames)
+ form.js-inlined-form(id=id class=classNames)
+Template.contentBlock
else
+Template.elseBlock
diff --git a/client/components/forms/inlinedform.js b/client/components/forms/inlinedform.js
index e4331892..2988738c 100644
--- a/client/components/forms/inlinedform.js
+++ b/client/components/forms/inlinedform.js
@@ -36,7 +36,7 @@ BlazeComponent.extendComponent({
open: function() {
// Close currently opened form, if any
- EscapeActions.executeLowerThan('inlinedForm');
+ EscapeActions.executeUpTo('inlinedForm');
this.isOpen.set(true);
currentlyOpenedForm.set(this);
},
@@ -61,18 +61,6 @@ BlazeComponent.extendComponent({
'click .js-close-inlined-form': this.close,
'click .js-open-inlined-form': this.open,
- // Close the inlined form by pressing escape.
- //
- // Keydown (and not keypress) in necessary here because the `keyCode`
- // property is consistent in all browsers, (there is not keyCode for the
- // `keypress` event in firefox)
- 'keydown form input, keydown form textarea': function(evt) {
- if (evt.keyCode === 27) {
- evt.preventDefault();
- EscapeActions.executeLowest();
- }
- },
-
// Pressing Ctrl+Enter should submit the form
'keydown form textarea': function(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
@@ -98,5 +86,7 @@ BlazeComponent.extendComponent({
// Press escape to close the currently opened inlinedForm
EscapeActions.register('inlinedForm',
function() { currentlyOpenedForm.get().close(); },
- function() { return currentlyOpenedForm.get() !== null; }
+ function() { return currentlyOpenedForm.get() !== null; }, {
+ noClickEscapeOn: '.js-inlined-form'
+ }
);
diff --git a/client/components/lists/main.js b/client/components/lists/main.js
index bcdba7c4..2b07c3ee 100644
--- a/client/components/lists/main.js
+++ b/client/components/lists/main.js
@@ -50,7 +50,7 @@ BlazeComponent.extendComponent({
placeholder: 'minicard-wrapper placeholder',
start: function(evt, ui) {
ui.placeholder.height(ui.helper.height());
- EscapeActions.executeLowerThan('popup');
+ EscapeActions.executeUpTo('popup');
boardComponent.setIsDragging(true);
},
stop: function(evt, ui) {
diff --git a/client/components/main/header.styl b/client/components/main/header.styl
index eaf391f7..76a1bc8c 100644
--- a/client/components/main/header.styl
+++ b/client/components/main/header.styl
@@ -12,16 +12,15 @@
font-size: 12px
display: flex
- #header-user-bar
+ #header-user-bar,
ul li
color: darken(white, 17%)
- a, .fa
+ .fa
color: inherit
- text-decoration: none
- &:hover
- color: white
+ a:hover, a.is-active
+ color: white
ul
flex: 1
@@ -76,9 +75,6 @@
float: left
border-radius: 3px
- &.is-clickable
- cursor: pointer
-
.board-header-btns
display: block
margin-top: 3px
diff --git a/client/components/main/popup.js b/client/components/main/popup.js
index 8672d08a..8cb12dd0 100644
--- a/client/components/main/popup.js
+++ b/client/components/main/popup.js
@@ -18,11 +18,6 @@ function whichTransitionEvent() {
var transitionEvent = whichTransitionEvent();
Popup.template.events({
- click: function(evt) {
- if (evt.originalEvent) {
- evt.originalEvent.clickInPopup = true;
- }
- },
'click .js-back-view': function() {
Popup.back();
},
diff --git a/client/components/main/popup.tpl.jade b/client/components/main/popup.tpl.jade
index be528f46..e6cc982a 100644
--- a/client/components/main/popup.tpl.jade
+++ b/client/components/main/popup.tpl.jade
@@ -1,4 +1,4 @@
-.pop-over(
+.pop-over.js-pop-over(
class="{{#unless title}}miniprofile{{/unless}}"
class=currentBoard.colorClass
class="{{#unless title}}no-title{{/unless}}"
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index f3844fcd..cfd38c89 100644
--- a/client/components/sidebar/sidebar.js
+++ b/client/components/sidebar/sidebar.js
@@ -111,7 +111,7 @@ BlazeComponent.extendComponent({
snap: false,
snapMode: 'both',
start: function() {
- EscapeActions.executeLowerThan('popup');
+ EscapeActions.executeUpTo('popup');
}
});
});
diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade
index b8201cb6..960264a9 100644
--- a/client/components/users/userHeader.jade
+++ b/client/components/users/userHeader.jade
@@ -1,12 +1,12 @@
template(name="headerUserBar")
- a#header-user-bar
- .header-user-bar-name.js-open-header-member-menu
+ #header-user-bar
+ a.header-user-bar-name.js-open-header-member-menu
i.fa.fa-chevron-down
if currentUser.profile.name
= currentUser.profile.name
else
= currentUser.username
- .header-user-bar-avatar.js-change-avatar
+ a.header-user-bar-avatar.js-change-avatar
+userAvatar(user=currentUser)
template(name="memberMenuPopup")
diff --git a/client/config/router.js b/client/config/router.js
index 8fa74bee..97871e23 100644
--- a/client/config/router.js
+++ b/client/config/router.js
@@ -24,7 +24,7 @@ Router.configure({
return this.redirect('atSignIn');
}
- // We want to execute our EscapeActions.executeLowerThan method any time the
+ // We want to execute our EscapeActions.executeUpTo method any time the
// route is changed, but not if the stays the same but only the parameters
// change (eg when a user is navigation from a card A to a card B). Iron-
// Router onBeforeAction is a reactive context (which is a bad desig choice
diff --git a/client/lib/escapeActions.js b/client/lib/escapeActions.js
new file mode 100644
index 00000000..3759f441
--- /dev/null
+++ b/client/lib/escapeActions.js
@@ -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');
+ }
+});
diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js
index 8b105c28..bd78390a 100644
--- a/client/lib/keyboard.js
+++ b/client/lib/keyboard.js
@@ -33,72 +33,3 @@ Mousetrap.bind(['down', 'up'], function(evt, key) {
Utils.goCardId(nextCardId);
}
});
-
-// 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 condition is valid and his the the
-// highest in the label hierarchy.
-EscapeActions = {
- _actions: [],
-
- // Executed in order
- hierarchy: [
- 'textcomplete',
- 'popup',
- 'inlinedForm',
- 'multiselection-disable',
- 'sidebarView',
- 'detailsPane',
- 'multiselection-reset'
- ],
-
- register: function(label, action, condition) {
- if (_.isUndefined(condition))
- condition = function() { return true; };
-
- // 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
- });
- // XXX Rewrite this with ES6: => function
- this._actions = _.sortBy(this._actions, function(a) { return a.priority; });
- },
-
- executeLowest: function() {
- var topActiveAction = _.find(this._actions, function(a) {
- return !! a.condition();
- });
- return topActiveAction && topActiveAction.action();
- },
-
- executeLowerThan: function(label) {
- var maxPriority, currentAction;
- if (! label)
- maxPriority = Infinity;
- else
- maxPriority = this.hierarchy.indexOf(label);
-
- for (var i = 0; i < this._actions.length; i++) {
- currentAction = this._actions[i];
- if (currentAction.priority > maxPriority)
- return;
- if (!! currentAction.condition())
- currentAction.action();
- }
- },
-
- executeAll: function() {
- return this.executeLowerThan();
- }
-};
-
-Mousetrap.bind('esc', function() {
- EscapeActions.executeLowest();
-});
diff --git a/client/lib/multiSelection.js b/client/lib/multiSelection.js
index 53c16da0..2f96e199 100644
--- a/client/lib/multiSelection.js
+++ b/client/lib/multiSelection.js
@@ -78,7 +78,7 @@ MultiSelection = {
activate: function() {
if (! this.isActive()) {
- EscapeActions.executeLowerThan('detailsPane');
+ EscapeActions.executeUpTo('detailsPane');
this._isActive.set(true);
Sidebar.setView(this.sidebarView);
Tracker.flush();
@@ -91,6 +91,7 @@ MultiSelection = {
if (Sidebar && Sidebar.getView() === this.sidebarView) {
Sidebar.setView();
}
+ this.reset();
}
},
@@ -149,11 +150,7 @@ MultiSelection = {
Blaze.registerHelper('MultiSelection', MultiSelection);
-EscapeActions.register('multiselection-disable',
+EscapeActions.register('multiselection',
function() { MultiSelection.disable(); },
function() { return MultiSelection.isActive(); }
);
-
-EscapeActions.register('multiselection-reset',
- function() { MultiSelection.reset(); }
-);
diff --git a/client/lib/popup.js b/client/lib/popup.js
index fe8b581b..a1f4def2 100644
--- a/client/lib/popup.js
+++ b/client/lib/popup.js
@@ -40,11 +40,8 @@ Popup = {
self._stack = [];
openerElement = evt.currentTarget;
}
- $(openerElement).addClass('is-active');
- // We modify the event to prevent the popup being closed when the event
- // bubble up to the document element.
- evt.originalEvent.clickInPopup = true;
+ $(openerElement).addClass('is-active');
evt.preventDefault();
// We push our popup data to the stack. The top of the stack is always
@@ -201,19 +198,11 @@ Popup = {
}
};
-// We automatically close a potential opened popup on any left click on the
-// document. To avoid closing it unexpectedly we modify the bubbled event in
-// case the click event happen in the popup or in a button that open a popup.
-$(document).on('click', function(evt) {
- if (evt.which === 1 && ! (evt.originalEvent &&
- evt.originalEvent.clickInPopup)) {
- Popup.close();
- }
-});
-
-// Press escape to go back, or close the popup.
-var bindPopup = function(f) { return _.bind(f, Popup); };
+// We close a potential opened popup on any left click on the document, or go
+// one step back by pressing escape.
EscapeActions.register('popup',
- bindPopup(Popup.back),
- bindPopup(Popup.isOpen)
+ function(evt) { Popup[evt.type === 'click' ? 'close' : 'back'](); },
+ _.bind(Popup.isOpen, Popup), {
+ noClickEscapeOn: '.js-pop-over'
+ }
);
diff --git a/client/styles/main.styl b/client/styles/main.styl
index cc66576b..e6c0eb13 100644
--- a/client/styles/main.styl
+++ b/client/styles/main.styl
@@ -63,16 +63,12 @@ h3, h4, h5, h6
color: #aa8f09
a
- color: #444
+ color: inherit
cursor: pointer
text-decoration: none
- &:hover
- color: #111
-
- &.disabled,
- &.disabled:hover
- color: #8c8c8c
+ &.is-disabled,
+ &.is-disabled:hover
cursor: default
text-decoration: none