Skip to content
This repository has been archived by the owner on Jan 2, 2020. It is now read-only.

Keyboard shortcuts #755

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# Copyright (c) 2014 ThoughtWorks, Inc.
#
# Pixelated is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pixelated is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.

@wip
Feature: Using keyboard shortcuts to compose and send a mail
As a user of pixelated
I want to use keyboard shortcuts
So I can compose mails

Scenario: User composes a mail and sends it using shortcuts
When I use a shortcut to compose a message with
| subject | body |
| Pixelated rocks! | You should definitely use it. Cheers, User. |
And for the 'To' field I enter '[email protected]'
And I use a shortcut to send it
When I select the tag 'sent'
And I open the first mail in the mail list
Then I see that the subject reads 'Pixelated rocks!'
Then I see that the body reads 'You should definitely use it. Cheers, User.'

19 changes: 18 additions & 1 deletion service/test/functional/features/steps/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
from behave import when
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from time import sleep

from behave import when
from common import *


Expand All @@ -29,6 +31,16 @@ def impl(context):
fill_by_css_selector(context, 'textarea#text-box', row['body'])


@when('I use a shortcut to compose a message with')
def compose_with_shortcut(context):
body = context.browser.find_element_by_tag_name('body')
body.send_keys('c')

for row in context.table:
fill_by_css_selector(context, 'input#subject', row['subject'])
fill_by_css_selector(context, 'textarea#text-box', row['body'])


@when("for the '{recipients_field}' field I enter '{to_type}'")
def enter_address_impl(context, recipients_field, to_type):
_enter_recipient(context, recipients_field, to_type + "\n")
Expand All @@ -47,6 +59,11 @@ def send_impl(context):
send_button.click()


@when('I use a shortcut to send it')
def send_with_shortcut(context):
ActionChains(context.browser).key_down(Keys.CONTROL).send_keys(Keys.ENTER).key_up(Keys.CONTROL).perform()


@when(u'I toggle the cc and bcc fields')
def collapse_cc_bcc_fields(context):
cc_and_bcc_chevron = wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, '#cc-bcc-collapse'))
Expand Down
38 changes: 28 additions & 10 deletions web-ui/app/js/dispatchers/right_pane_dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,23 @@ define(
'mail_view/ui/draft_box',
'mail_view/ui/no_message_selected_pane',
'mail_view/ui/feedback_box',
'page/events'
'page/events',
'mail_view/ui/mail_composition_shortcuts',
'mail_view/ui/mail_view_shortcuts'
],

function(defineComponent, ComposeBox, MailView, ReplySection, DraftBox, NoMessageSelectedPane, FeedbackBox, events) {
function(
defineComponent,
ComposeBox,
MailView,
ReplySection,
DraftBox,
NoMessageSelectedPane,
FeedbackBox,
events,
mailCompositionShortcuts,
mailViewShortcuts
) {
'use strict';

return defineComponent(rightPaneDispatcher);
Expand All @@ -53,13 +66,14 @@ define(
this.reset = function (newContainer) {
this.trigger(document, events.dispatchers.rightPane.clear);
this.select('rightPane').empty();
var stage = this.createAndAttach(newContainer);
return stage;
return this.createAndAttach(newContainer);
};

this.openComposeBox = function() {
var stage = this.reset(this.attr.composeBox);
ComposeBox.attachTo(stage, {currentTag: this.attr.currentTag});
var stageId = this.reset(this.attr.composeBox);
ComposeBox.attachTo(stageId, {currentTag: this.attr.currentTag});
mailCompositionShortcuts.attachTo(stageId);
mailViewShortcuts.attachTo(stageId);
};

this.openFeedbackBox = function() {
Expand All @@ -68,11 +82,13 @@ define(
};

this.openMail = function(ev, data) {
var stage = this.reset(this.attr.mailView);
MailView.attachTo(stage, data);
var stageId = this.reset(this.attr.mailView);
MailView.attachTo(stageId, data);

var replySectionContainer = this.createAndAttach(this.attr.replySection);
ReplySection.attachTo(replySectionContainer, { ident: data.ident });

mailViewShortcuts.attachTo(stageId);
};

this.initializeNoMessageSelectedPane = function () {
Expand All @@ -88,8 +104,10 @@ define(
};

this.openDraft = function (ev, data) {
var stage = this.reset(this.attr.draftBox);
DraftBox.attachTo(stage, { mailIdent: data.ident, currentTag: this.attr.currentTag });
var stageId = this.reset(this.attr.draftBox);
DraftBox.attachTo(stageId, { mailIdent: data.ident, currentTag: this.attr.currentTag });
mailCompositionShortcuts.attachTo(stageId);
mailViewShortcuts.attachTo(stageId);
};

this.selectTag = function(ev, data) {
Expand Down
67 changes: 67 additions & 0 deletions web-ui/app/js/mail_view/ui/mail_composition_shortcuts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2014 ThoughtWorks, Inc.
*
* Pixelated is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pixelated is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
*/

define([
'flight/lib/component',
'page/events'
],
function (defineComponent, events) {
'use strict';

return defineComponent(mailViewShortcuts);

function mailViewShortcuts() {
var keyCodes = {
ENTER: 13
};
var modifierKeys = {
META: "META",
CTRL: "CTRL"
};

// make constants public
this.keyCodes = keyCodes;

this.after('initialize', function () {
this.on('keydown', _.partial(tryKeyEvents, _.bind(this.trigger, this, document)));
});

function tryKeyEvents(triggerFunc, event) {
var keyEvents = {};
keyEvents[modifierKeys.CTRL + keyCodes.ENTER] = events.ui.mail.send;
keyEvents[modifierKeys.META + keyCodes.ENTER] = events.ui.mail.send;

if (!keyEvents.hasOwnProperty(modifierKey(event) + event.which)) {
return;
}

event.preventDefault();
return triggerFunc(keyEvents[modifierKey(event) + event.which]);
}

function modifierKey(event) {
var modifierKey = "";
if (event.ctrlKey === true) {
modifierKey = modifierKeys.CTRL;
}
if (event.metaKey === true) {
modifierKey = modifierKeys.META;
}
return modifierKey;
}
}
});
51 changes: 51 additions & 0 deletions web-ui/app/js/mail_view/ui/mail_view_shortcuts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2014 ThoughtWorks, Inc.
*
* Pixelated is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pixelated is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
*/

define([
'flight/lib/component',
'page/events'
],
function (defineComponent, events) {
'use strict';

return defineComponent(mailViewShortcuts);

function mailViewShortcuts() {
var keyCodes = {
ESC: 27
};

// make constants public
this.keyCodes = keyCodes;

this.after('initialize', function () {
this.on(document, 'keydown', _.partial(tryKeyEvents, _.bind(this.trigger, this, document)));
});

function tryKeyEvents(triggerFunc, event) {
var keyEvents = {};
keyEvents[keyCodes.ESC] = events.dispatchers.rightPane.openNoMessageSelected;

if (!keyEvents.hasOwnProperty(event.which)) {
return;
}

event.preventDefault();
return triggerFunc(keyEvents[event.which]);
}
}
});
8 changes: 6 additions & 2 deletions web-ui/app/js/page/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ define(
'page/version',
'page/unread_count_title',
'page/pix_logo',
'helpers/browser'
'helpers/browser',
'page/shortcuts'
],

function (
Expand Down Expand Up @@ -92,7 +93,8 @@ define(
version,
unreadCountTitle,
pixLogo,
browser) {
browser,
shortcuts) {

'use strict';
function initialize(path) {
Expand Down Expand Up @@ -137,6 +139,8 @@ define(

pixLogo.attachTo(document);

shortcuts.attachTo(document);

$.ajaxSetup({headers: {'X-XSRF-TOKEN': browser.getCookie('XSRF-TOKEN')}});
});
}
Expand Down
3 changes: 2 additions & 1 deletion web-ui/app/js/page/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ define(function () {
results: 'search:results',
empty: 'search:empty',
highlightResults: 'search:highlightResults',
resetHighlight: 'search:resetHighlight'
resetHighlight: 'search:resetHighlight',
focus: 'search:focus'
},
feedback: {
submit: 'feedback:submit',
Expand Down
69 changes: 69 additions & 0 deletions web-ui/app/js/page/shortcuts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2014 ThoughtWorks, Inc.
*
* Pixelated is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pixelated is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
*/

define([
'flight/lib/component',
'page/events'
],
function (defineComponent, events) {
'use strict';

return defineComponent(shortcuts);

function shortcuts() {
var composeBoxId = 'compose-box';
var keyCodes = {
C: 67,
FORWARD_SLASH: 191,
S: 83
};

// make constants public
this.keyCodes = keyCodes;
this.composeBoxId = composeBoxId;

this.after('initialize', function () {
this.on('keydown', _.partial(tryMailHandlingKeyEvents, _.bind(this.trigger, this, document)));
});

function tryMailHandlingKeyEvents(triggerFunc, event) {
if (isTriggeredOnInputField(event.target) || composeBoxIsShown()) {
return;
}

var mailHandlingKeyEvents = {};
mailHandlingKeyEvents[keyCodes.S] = events.search.focus;
mailHandlingKeyEvents[keyCodes.FORWARD_SLASH] = events.search.focus;
mailHandlingKeyEvents[keyCodes.C] = events.dispatchers.rightPane.openComposeBox;

if (!mailHandlingKeyEvents.hasOwnProperty(event.which)) {
return;
}

event.preventDefault();
return triggerFunc(mailHandlingKeyEvents[event.which]);
}

function isTriggeredOnInputField(element) {
return $(element).is('input') || $(element).is('textarea');
}

function composeBoxIsShown() {
return $('#' + composeBoxId).length;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very happy with this. Is there any way to get that state of the application other than by accessing the DOM?

Copy link
Member

@bwagnerr bwagnerr Aug 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@treppo Not really, we just try to minimize the impact of messing with the dom by only changing things rendered inside the specific component. In this case, I would use the key combos to trigger events to specific components, that can deal with the dom manipulation it needs to do the action

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bwagnerr i do trigger events on the specific components. only these events here have to be triggered on document. and therefore i have to check whether the compose box is open by accessing the dom…

}
}
});
5 changes: 5 additions & 0 deletions web-ui/app/js/search/search_trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,18 @@ define(
}
};

this.focus = function () {
this.select('input').focus();
};

this.after('initialize', function () {
this.render();
this.on(this.select('form'), 'submit', this.search);
this.on(this.select('input'), 'focus', this.showOnlySearchTerms);
this.on(this.select('input'), 'blur', this.showSearchTermsAndPlaceHolder);
this.on(document, events.ui.tag.selected, this.clearInput);
this.on(document, events.ui.tag.select, this.clearInput);
this.on(document, events.search.focus, this.focus);
});
}
}
Expand Down
Loading