Merge pull request #5602 from Faldrian/more-shortcuts

Add shortcuts for reshare, expand Post and open first link in a post

Conflicts:
	config/locales/diaspora/en.yml
This commit is contained in:
Steffen van Bergerem 2015-02-07 14:38:26 +01:00
commit 8b33b9eb18
5 changed files with 136 additions and 32 deletions

View file

@ -158,6 +158,7 @@ diaspora.yml file**. The existing settings from 0.4.x and before will not work a
* Hide user setting if the community spotlight is not enabled on the pod [#5562](https://github.com/diaspora/diaspora/pull/5562) * Hide user setting if the community spotlight is not enabled on the pod [#5562](https://github.com/diaspora/diaspora/pull/5562)
* Add HTML view for pod statistics [#5464](https://github.com/diaspora/diaspora/pull/5464) * Add HTML view for pod statistics [#5464](https://github.com/diaspora/diaspora/pull/5464)
* Added/Moved hide, block user, report and delete button in SPV [#5547](https://github.com/diaspora/diaspora/pull/5547) * Added/Moved hide, block user, report and delete button in SPV [#5547](https://github.com/diaspora/diaspora/pull/5547)
* Added keyboard shortcuts r(reshare), m(expand Post), o(open first link in post) [#5602](https://github.com/diaspora/diaspora/pull/5602)
# 0.4.1.2 # 0.4.1.2

View file

@ -13,6 +13,9 @@ app.views.StreamShortcuts = {
this.on('hotkey:gotoPrev', this.gotoPrev, this); this.on('hotkey:gotoPrev', this.gotoPrev, this);
this.on('hotkey:likeSelected', this.likeSelected, this); this.on('hotkey:likeSelected', this.likeSelected, this);
this.on('hotkey:commentSelected', this.commentSelected, this); this.on('hotkey:commentSelected', this.commentSelected, this);
this.on('hotkey:reshareSelected', this.reshareSelected, this);
this.on('hotkey:expandSelected', this.expandSelected, this);
this.on('hotkey:openFirstLinkSelected', this.openFirstLinkSelected, this);
}, },
_onHotkeyDown: function(event) { _onHotkeyDown: function(event) {
@ -21,7 +24,7 @@ app.views.StreamShortcuts = {
if(jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1){ if(jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1){
return; return;
} }
// trigger the events based on what key was pressed // trigger the events based on what key was pressed
switch (String.fromCharCode( event.which ).toLowerCase()) { switch (String.fromCharCode( event.which ).toLowerCase()) {
case "j": case "j":
@ -33,7 +36,7 @@ app.views.StreamShortcuts = {
default: default:
} }
}, },
_onHotkeyUp: function(event) { _onHotkeyUp: function(event) {
//make sure that the user is not typing in an input field //make sure that the user is not typing in an input field
var textAcceptingInputTypes = ["textarea", "select", "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; var textAcceptingInputTypes = ["textarea", "select", "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"];
@ -49,6 +52,15 @@ app.views.StreamShortcuts = {
case "l": case "l":
this.trigger('hotkey:likeSelected'); this.trigger('hotkey:likeSelected');
break; break;
case "r":
this.trigger('hotkey:reshareSelected');
break;
case "m":
this.trigger('hotkey:expandSelected');
break;
case "o":
this.trigger('hotkey:openFirstLinkSelected');
break;
default: default:
} }
}, },
@ -57,7 +69,7 @@ app.views.StreamShortcuts = {
// select next post: take the first post under the header // select next post: take the first post under the header
var stream_elements = this.$('div.stream_element.loaded'); var stream_elements = this.$('div.stream_element.loaded');
var posUser = window.pageYOffset; var posUser = window.pageYOffset;
for (var i = 0; i < stream_elements.length; i++) { for (var i = 0; i < stream_elements.length; i++) {
if(stream_elements[i].offsetTop>posUser+this._headerSize){ if(stream_elements[i].offsetTop>posUser+this._headerSize){
this.selectPost(stream_elements[i]); this.selectPost(stream_elements[i]);
@ -74,7 +86,7 @@ app.views.StreamShortcuts = {
// select previous post: take the first post above the header // select previous post: take the first post above the header
var stream_elements = this.$('div.stream_element.loaded'); var stream_elements = this.$('div.stream_element.loaded');
var posUser = window.pageYOffset; var posUser = window.pageYOffset;
for (var i = stream_elements.length-1; i >=0; i--) { for (var i = stream_elements.length-1; i >=0; i--) {
if(stream_elements[i].offsetTop<posUser+this._headerSize){ if(stream_elements[i].offsetTop<posUser+this._headerSize){
this.selectPost(stream_elements[i]); this.selectPost(stream_elements[i]);
@ -86,15 +98,31 @@ app.views.StreamShortcuts = {
this.selectPost(stream_elements[0]); this.selectPost(stream_elements[0]);
} }
}, },
commentSelected: function() { commentSelected: function() {
$('a.focus_comment_textarea',this.$('div.stream_element.loaded.shortcut_selected')).click(); $('a.focus_comment_textarea',this.$('div.stream_element.loaded.shortcut_selected')).click();
}, },
likeSelected: function() { likeSelected: function() {
$('a.like:first',this.$('div.stream_element.loaded.shortcut_selected')).click(); $('a.like:first',this.$('div.stream_element.loaded.shortcut_selected')).click();
}, },
reshareSelected: function() {
$('a.reshare:first',this.$('div.stream_element.loaded.shortcut_selected')).click();
},
expandSelected: function() {
$('div.expander:first',this.$('div.stream_element.loaded.shortcut_selected')).click();
},
openFirstLinkSelected: function() {
var link = $('div.collapsible a[target="_blank"]:first',this.$('div.stream_element.loaded.shortcut_selected'));
if(link.length > 0) {
// click does only work with vanilla javascript
link[0].click();
}
},
selectPost: function(element){ selectPost: function(element){
//remove the selection and selected-class from all posts //remove the selection and selected-class from all posts
var selected=this.$('div.stream_element.loaded.shortcut_selected'); var selected=this.$('div.stream_element.loaded.shortcut_selected');
@ -102,7 +130,7 @@ app.views.StreamShortcuts = {
//move to new post //move to new post
window.scrollTo(window.pageXOffset, element.offsetTop-this._headerSize); window.scrollTo(window.pageXOffset, element.offsetTop-this._headerSize);
//add the selection and selected-class to new post //add the selection and selected-class to new post
element.className+=" shortcut_selected highlighted"; element.className+=" shortcut_selected highlighted";
}, },
}; };
// @license-end // @license-end

View file

@ -10,6 +10,9 @@
<li>{{ keyboard_shortcuts_li2 }}</li> <li>{{ keyboard_shortcuts_li2 }}</li>
<li>{{ keyboard_shortcuts_li3 }}</li> <li>{{ keyboard_shortcuts_li3 }}</li>
<li>{{ keyboard_shortcuts_li4 }}</li> <li>{{ keyboard_shortcuts_li4 }}</li>
<li>{{ keyboard_shortcuts_li5 }}</li>
<li>{{ keyboard_shortcuts_li6 }}</li>
<li>{{ keyboard_shortcuts_li7 }}</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -565,13 +565,16 @@ en:
filter_tags_q: "How can I filter/exclude some tags from my stream?" filter_tags_q: "How can I filter/exclude some tags from my stream?"
filter_tags_a: "This is not yet available directly through diaspora*, but some %{third_party_tools} have been written that might provide this." filter_tags_a: "This is not yet available directly through diaspora*, but some %{third_party_tools} have been written that might provide this."
keyboard_shortcuts: keyboard_shortcuts:
title: "Keyboard shortcuts"
keyboard_shortcuts_q: "What keyboard shortcuts are available?" keyboard_shortcuts_q: "What keyboard shortcuts are available?"
keyboard_shortcuts_a1: "In the stream view you can use the following keyboard shortcuts:" keyboard_shortcuts_a1: "In the stream view you can use the following keyboard shortcuts:"
keyboard_shortcuts_li1: "j - Jump to the next post" keyboard_shortcuts_li1: "j - Jump to the next post"
keyboard_shortcuts_li2: "k - Jump to the previous post" keyboard_shortcuts_li2: "k - Jump to the previous post"
keyboard_shortcuts_li3: "c - Comment on the current post" keyboard_shortcuts_li3: "c - Comment on the current post"
keyboard_shortcuts_li4: "l - Like the current post" keyboard_shortcuts_li4: "l - Like the current post"
title: "Keyboard shortcuts" keyboard_shortcuts_li5: "r - Reshare the current post"
keyboard_shortcuts_li6: "m - Expand the current post"
keyboard_shortcuts_li7: "o - Open the first link in the current post"
miscellaneous: miscellaneous:
title: "Miscellaneous" title: "Miscellaneous"
back_to_top_q: "Is there a quick way to go back to the top of a page after I scroll down?" back_to_top_q: "Is there a quick way to go back to the top of a page after I scroll down?"

View file

@ -1,27 +1,27 @@
describe("app.views.StreamShortcuts", function () { describe("app.views.StreamShortcuts", function () {
beforeEach(function() { beforeEach(function() {
this.post1 = factory.post({author : factory.author({name : "Rebecca Black", id : 1492})}) this.post1 = factory.post({author : factory.author({name : "Rebecca Black", id : 1492})});
this.post2 = factory.post({author : factory.author({name : "John Stamos", id : 1987})}) this.post2 = factory.post({author : factory.author({name : "John Stamos", id : 1987})});
this.stream = new app.models.Stream(); this.stream = new app.models.Stream();
this.stream.add([this.post1, this.post2]); this.stream.add([this.post1, this.post2]);
this.view = new app.views.Stream({model : this.stream}); this.view = new app.views.Stream({model : this.stream});
this.view.render(); this.view.render();
expect(this.view.$('div.stream_element.loaded').length).toBe(2); expect(this.view.$('div.stream_element.loaded').length).toBe(2);
}); });
describe("loading the stream", function(){ describe("loading the stream", function(){
it("should setup the shortcuts", function(){ it("should setup the shortcuts", function(){
spyOn(this.view, 'setupShortcuts'); spyOn(this.view, 'setupShortcuts');
this.view.initialize(); this.view.initialize();
expect(this.view.setupShortcuts).toHaveBeenCalled(); expect(this.view.setupShortcuts).toHaveBeenCalled();
}); });
}); });
describe("pressing 'j'", function(){ describe("pressing 'j'", function(){
it("should call 'gotoNext' if not pressed in an input field", function(){ it("should call 'gotoNext' if not pressed in an input field", function(){
spyOn(this.view, 'gotoNext'); spyOn(this.view, 'gotoNext');
this.view.initialize(); this.view.initialize();
@ -31,13 +31,13 @@ describe("app.views.StreamShortcuts", function () {
this.view._onHotkeyDown(e); this.view._onHotkeyDown(e);
expect(this.view.gotoNext).toHaveBeenCalled(); expect(this.view.gotoNext).toHaveBeenCalled();
}); });
it("'gotoNext' should call 'selectPost'", function(){ it("'gotoNext' should call 'selectPost'", function(){
spyOn(this.view, 'selectPost'); spyOn(this.view, 'selectPost');
this.view.gotoNext(); this.view.gotoNext();
expect(this.view.selectPost).toHaveBeenCalled(); expect(this.view.selectPost).toHaveBeenCalled();
}); });
it("shouldn't do anything if the user types in an input field", function(){ it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'gotoNext'); spyOn(this.view, 'gotoNext');
spyOn(this.view, 'selectPost'); spyOn(this.view, 'selectPost');
@ -50,7 +50,7 @@ describe("app.views.StreamShortcuts", function () {
expect(this.view.selectPost).not.toHaveBeenCalled(); expect(this.view.selectPost).not.toHaveBeenCalled();
}); });
}); });
describe("pressing 'k'", function(){ describe("pressing 'k'", function(){
it("should call 'gotoPrev' if not pressed in an input field", function(){ it("should call 'gotoPrev' if not pressed in an input field", function(){
@ -62,13 +62,13 @@ describe("app.views.StreamShortcuts", function () {
this.view._onHotkeyDown(e); this.view._onHotkeyDown(e);
expect(this.view.gotoPrev).toHaveBeenCalled(); expect(this.view.gotoPrev).toHaveBeenCalled();
}); });
it("'gotoPrev' should call 'selectPost'", function(){ it("'gotoPrev' should call 'selectPost'", function(){
spyOn(this.view, 'selectPost'); spyOn(this.view, 'selectPost');
this.view.gotoPrev(); this.view.gotoPrev();
expect(this.view.selectPost).toHaveBeenCalled(); expect(this.view.selectPost).toHaveBeenCalled();
}); });
it("shouldn't do anything if the user types in an input field", function(){ it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'gotoPrev'); spyOn(this.view, 'gotoPrev');
spyOn(this.view, 'selectPost'); spyOn(this.view, 'selectPost');
@ -81,7 +81,7 @@ describe("app.views.StreamShortcuts", function () {
expect(this.view.selectPost).not.toHaveBeenCalled(); expect(this.view.selectPost).not.toHaveBeenCalled();
}); });
}); });
describe("pressing 'c'", function(){ describe("pressing 'c'", function(){
it("should click on the comment-button if not pressed in an input field", function(){ it("should click on the comment-button if not pressed in an input field", function(){
@ -93,7 +93,7 @@ describe("app.views.StreamShortcuts", function () {
this.view._onHotkeyUp(e); this.view._onHotkeyUp(e);
expect(this.view.commentSelected).toHaveBeenCalled(); expect(this.view.commentSelected).toHaveBeenCalled();
}); });
it("shouldn't do anything if the user types in an input field", function(){ it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'commentSelected'); spyOn(this.view, 'commentSelected');
this.view.initialize(); this.view.initialize();
@ -104,9 +104,9 @@ describe("app.views.StreamShortcuts", function () {
expect(this.view.commentSelected).not.toHaveBeenCalled(); expect(this.view.commentSelected).not.toHaveBeenCalled();
}); });
}); });
describe("pressing 'l'", function(){ describe("pressing 'l'", function(){
it("should click on the like-button if not pressed in an input field", function(){ it("should click on the like-button if not pressed in an input field", function(){
spyOn(this.view, 'likeSelected'); spyOn(this.view, 'likeSelected');
this.view.initialize(); this.view.initialize();
@ -116,7 +116,7 @@ describe("app.views.StreamShortcuts", function () {
this.view._onHotkeyUp(e); this.view._onHotkeyUp(e);
expect(this.view.likeSelected).toHaveBeenCalled(); expect(this.view.likeSelected).toHaveBeenCalled();
}); });
it("shouldn't do anything if the user types in an input field", function(){ it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'likeSelected'); spyOn(this.view, 'likeSelected');
this.view.initialize(); this.view.initialize();
@ -127,4 +127,73 @@ describe("app.views.StreamShortcuts", function () {
expect(this.view.likeSelected).not.toHaveBeenCalled(); expect(this.view.likeSelected).not.toHaveBeenCalled();
}); });
}); });
})
describe("pressing 'r'", function(){
it("should click on the reshare-button if not pressed in an input field", function(){
spyOn(this.view, 'reshareSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 82, target: {type: "div"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('r');
this.view._onHotkeyUp(e);
expect(this.view.reshareSelected).toHaveBeenCalled();
});
it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'reshareSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 82, target: {type: "textarea"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('r');
this.view._onHotkeyUp(e);
expect(this.view.reshareSelected).not.toHaveBeenCalled();
});
});
describe("pressing 'm'", function(){
it("should click on the more-button if not pressed in an input field", function(){
spyOn(this.view, 'expandSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 77, target: {type: "div"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('m');
this.view._onHotkeyUp(e);
expect(this.view.expandSelected).toHaveBeenCalled();
});
it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'expandSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 77, target: {type: "textarea"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('m');
this.view._onHotkeyUp(e);
expect(this.view.expandSelected).not.toHaveBeenCalled();
});
});
describe("pressing 'o'", function(){
it("should click on the more-button if not pressed in an input field", function(){
spyOn(this.view, 'openFirstLinkSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 79, target: {type: "div"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('o');
this.view._onHotkeyUp(e);
expect(this.view.openFirstLinkSelected).toHaveBeenCalled();
});
it("shouldn't do anything if the user types in an input field", function(){
spyOn(this.view, 'openFirstLinkSelected');
this.view.initialize();
var e = $.Event("keyup", { which: 79, target: {type: "textarea"} });
//verify that the test is correct
expect(String.fromCharCode( e.which ).toLowerCase()).toBe('o');
this.view._onHotkeyUp(e);
expect(this.view.openFirstLinkSelected).not.toHaveBeenCalled();
});
});
});