REFACTOR: Keyboard Shortcuts should send their actions directly to

posts, not by activating clicks on buttons.
This commit is contained in:
Robin Ward 2014-06-11 12:49:57 -04:00
parent c1179014fc
commit 176120cbc1
5 changed files with 169 additions and 148 deletions

View file

@ -49,6 +49,122 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
}, },
actions: { actions: {
// Post related methods
replyToPost: function(post) {
var composerController = this.get('controllers.composer'),
quoteController = this.get('controllers.quote-button'),
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
topic = post ? post.get('topic') : this.get('model');
quoteController.set('buffer', '');
if (composerController.get('content.topic.id') === topic.get('id') &&
composerController.get('content.action') === Discourse.Composer.REPLY) {
composerController.set('content.post', post);
composerController.set('content.composeState', Discourse.Composer.OPEN);
composerController.appendText(quotedText);
} else {
var opts = {
action: Discourse.Composer.REPLY,
draftKey: topic.get('draft_key'),
draftSequence: topic.get('draft_sequence')
};
if(post && post.get("post_number") !== 1){
opts.post = post;
} else {
opts.topic = topic;
}
composerController.open(opts).then(function() {
composerController.appendText(quotedText);
});
}
return false;
},
likePost: function(post) {
var likeAction = post.get('actionByName.like');
if (likeAction && likeAction.get('can_act')) {
likeAction.act();
}
},
recoverPost: function(post) {
// Recovering the first post recovers the topic instead
if (post.get('post_number') === 1) {
this.recoverTopic();
return;
}
post.recover();
},
deletePost: function(post) {
// Deleting the first post deletes the topic
if (post.get('post_number') === 1) {
this.deleteTopic();
return;
}
var user = Discourse.User.current(),
replyCount = post.get('reply_count'),
self = this;
// If the user is staff and the post has replies, ask if they want to delete replies too.
if (user.get('staff') && replyCount > 0) {
bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [
{label: I18n.t("cancel"),
'class': 'btn-danger rightg'},
{label: I18n.t("post.controls.delete_replies.no_value"),
callback: function() {
post.destroy(user);
}
},
{label: I18n.t("post.controls.delete_replies.yes_value"),
'class': 'btn-primary',
callback: function() {
Discourse.Post.deleteMany([post], [post]);
self.get('postStream.posts').forEach(function (p) {
if (p === post || p.get('reply_to_post_number') === post.get('post_number')) {
p.setDeletedState(user);
}
});
}
}
]);
} else {
post.destroy(user).then(null, function(e) {
post.undoDeleteState();
var response = $.parseJSON(e.responseText);
if (response && response.errors) {
bootbox.alert(response.errors[0]);
} else {
bootbox.alert(I18n.t('generic_error'));
}
});
}
},
editPost: function(post) {
this.get('controllers.composer').open({
post: post,
action: Discourse.Composer.EDIT,
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
},
toggleBookmark: function(post) {
if (!Discourse.User.current()) {
alert(I18n.t("bookmarks.not_bookmarked"));
return;
}
post.toggleProperty('bookmarked');
return false;
},
jumpTop: function() { jumpTop: function() {
Discourse.URL.routeTo(this.get('firstPostUrl')); Discourse.URL.routeTo(this.get('firstPostUrl'));
}, },
@ -453,109 +569,11 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
Discourse.MessageBus.unsubscribe('/topic/*'); Discourse.MessageBus.unsubscribe('/topic/*');
}, },
// Post related methods
replyToPost: function(post) {
var composerController = this.get('controllers.composer'),
quoteController = this.get('controllers.quote-button'),
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
topic = post ? post.get('topic') : this.get('model');
quoteController.set('buffer', '');
if (composerController.get('content.topic.id') === topic.get('id') &&
composerController.get('content.action') === Discourse.Composer.REPLY) {
composerController.set('content.post', post);
composerController.set('content.composeState', Discourse.Composer.OPEN);
composerController.appendText(quotedText);
} else {
var opts = {
action: Discourse.Composer.REPLY,
draftKey: topic.get('draft_key'),
draftSequence: topic.get('draft_sequence')
};
if(post && post.get("post_number") !== 1){
opts.post = post;
} else {
opts.topic = topic;
}
composerController.open(opts).then(function() {
composerController.appendText(quotedText);
});
}
return false;
},
// Topic related // Topic related
reply: function() { reply: function() {
this.replyToPost(); this.replyToPost();
}, },
// Edits a post
editPost: function(post) {
this.get('controllers.composer').open({
post: post,
action: Discourse.Composer.EDIT,
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
},
toggleBookmark: function(post) {
if (!Discourse.User.current()) {
alert(I18n.t("bookmarks.not_bookmarked"));
return;
}
post.toggleProperty('bookmarked');
return false;
},
recoverPost: function(post) {
post.recover();
},
deletePost: function(post) {
var user = Discourse.User.current(),
replyCount = post.get('reply_count'),
self = this;
// If the user is staff and the post has replies, ask if they want to delete replies too.
if (user.get('staff') && replyCount > 0) {
bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [
{label: I18n.t("cancel"),
'class': 'btn-danger rightg'},
{label: I18n.t("post.controls.delete_replies.no_value"),
callback: function() {
post.destroy(user);
}
},
{label: I18n.t("post.controls.delete_replies.yes_value"),
'class': 'btn-primary',
callback: function() {
Discourse.Post.deleteMany([post], [post]);
self.get('postStream.posts').forEach(function (p) {
if (p === post || p.get('reply_to_post_number') === post.get('post_number')) {
p.setDeletedState(user);
}
});
}
}
]);
} else {
post.destroy(user).then(null, function(e) {
post.undoDeleteState();
var response = $.parseJSON(e.responseText);
if (response && response.errors) {
bootbox.alert(response.errors[0]);
} else {
bootbox.alert(I18n.t('generic_error'));
}
});
}
},
performTogglePost: function(post) { performTogglePost: function(post) {
var selectedPosts = this.get('selectedPosts'); var selectedPosts = this.get('selectedPosts');
if (this.postSelected(post)) { if (this.postSelected(post)) {

View file

@ -5,7 +5,7 @@
**/ **/
export default { export default {
name: "keyboard-shortcuts", name: "keyboard-shortcuts",
initialize: function() { initialize: function(container) {
Discourse.KeyboardShortcuts.bindEvents(Mousetrap); Discourse.KeyboardShortcuts.bindEvents(Mousetrap, container);
} }
}; };

View file

@ -16,27 +16,30 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
'g t': '/top' 'g t': '/top'
}, },
SELECTED_POST_BINDINGS: {
'b': 'toggleBookmark',
'd': 'deletePost',
'e': 'editPost',
'l': 'likePost',
'r': 'replyToPost',
'!': 'showFlags'
},
CLICK_BINDINGS: { CLICK_BINDINGS: {
'b': '.topic-post.selected button.bookmark', // bookmark current post
'c': '#create-topic', // create new topic 'c': '#create-topic', // create new topic
'd': '.topic-post.selected button.delete', // delete selected post
'e': '.topic-post.selected button.edit', // edit selected post
// star topic // star topic
'f': '#topic-footer-buttons button.star, #topic-list tr.topic-list-item.selected a.star', 'f': '#topic-footer-buttons button.star, #topic-list tr.topic-list-item.selected a.star',
'l': '.topic-post.selected button.like', // like selected post
'm m': 'div.notification-options li[data-id="0"] a', // mark topic as muted 'm m': 'div.notification-options li[data-id="0"] a', // mark topic as muted
'm r': 'div.notification-options li[data-id="1"] a', // mark topic as regular 'm r': 'div.notification-options li[data-id="1"] a', // mark topic as regular
'm t': 'div.notification-options li[data-id="2"] a', // mark topic as tracking 'm t': 'div.notification-options li[data-id="2"] a', // mark topic as tracking
'm w': 'div.notification-options li[data-id="3"] a', // mark topic as watching 'm w': 'div.notification-options li[data-id="3"] a', // mark topic as watching
'n': '#user-notifications', // open notifictions menu 'n': '#user-notifications', // open notifictions menu
'o,enter': '#topic-list tr.selected a.title', // open selected topic 'o,enter': '#topic-list tr.selected a.title', // open selected topic
'shift+r': '#topic-footer-buttons button.create', // reply to topic 'shift+r': '#topic-footer-buttons button.create', // reply to topic
'r': '.topic-post.selected button.create', // reply to selected post 'shift+s': '#topic-footer-buttons button.share', // share topic
'shift+s': '#topic-footer-buttons button.share', // share topic 's': '.topic-post.selected a.post-date' // share post
's': '.topic-post.selected button.share', // share selected post
'!': '.topic-post.selected button.flag' // flag selected post
}, },
FUNCTION_BINDINGS: { FUNCTION_BINDINGS: {
@ -54,10 +57,12 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
'q': 'quoteReply' 'q': 'quoteReply'
}, },
bindEvents: function(keyTrapper) { bindEvents: function(keyTrapper, container) {
this.keyTrapper = keyTrapper; this.keyTrapper = keyTrapper;
this.container = container;
_.each(this.PATH_BINDINGS, this._bindToPath, this); _.each(this.PATH_BINDINGS, this._bindToPath, this);
_.each(this.CLICK_BINDINGS, this._bindToClick, this); _.each(this.CLICK_BINDINGS, this._bindToClick, this);
_.each(this.SELECTED_POST_BINDINGS, this._bindToSelectedPost, this);
_.each(this.FUNCTION_BINDINGS, this._bindToFunction, this); _.each(this.FUNCTION_BINDINGS, this._bindToFunction, this);
}, },
@ -79,7 +84,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
_jumpTo: function(direction) { _jumpTo: function(direction) {
if ($('.container.posts').length) { if ($('.container.posts').length) {
Discourse.__container__.lookup('controller:topic').send(direction); this.container.lookup('controller:topic').send(direction);
} }
}, },
@ -136,6 +141,22 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({
Discourse.__container__.lookup('controller:application').send('showKeyboardShortcutsHelp'); Discourse.__container__.lookup('controller:application').send('showKeyboardShortcutsHelp');
}, },
_bindToSelectedPost: function(action, binding) {
var container = this.container;
this.keyTrapper.bind(binding, function() {
// TODO: We should keep track of the post without a CSS class
var selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
if (selectedPostId) {
var topicController = container.lookup('controller:topic'),
post = topicController.get('postStream.posts').findBy('id', selectedPostId);
if (post) {
topicController.send(action, post);
}
}
});
},
_bindToPath: function(path, binding) { _bindToPath: function(path, binding) {
this.keyTrapper.bind(binding, function() { this.keyTrapper.bind(binding, function() {
Discourse.URL.routeTo(path); Discourse.URL.routeTo(path);

View file

@ -73,7 +73,7 @@ export default Discourse.View.extend({
var handler = this["click" + action.capitalize()]; var handler = this["click" + action.capitalize()];
if (!handler) return; if (!handler) return;
handler.call(this); handler.call(this, this.get('post'));
}, },
// Replies Button // Replies Button
@ -109,7 +109,7 @@ export default Discourse.View.extend({
// Delete button // Delete button
renderDelete: function(post, buffer) { renderDelete: function(post, buffer) {
var label, action, icon; var label, icon;
if (post.get('post_number') === 1) { if (post.get('post_number') === 1) {
// If it's the first post, the delete/undo actions are related to the topic // If it's the first post, the delete/undo actions are related to the topic
@ -117,12 +117,10 @@ export default Discourse.View.extend({
if (topic.get('deleted_at')) { if (topic.get('deleted_at')) {
if (!topic.get('details.can_recover')) { return; } if (!topic.get('details.can_recover')) { return; }
label = "topic.actions.recover"; label = "topic.actions.recover";
action = "recoverTopic";
icon = "undo"; icon = "undo";
} else { } else {
if (!topic.get('details.can_delete')) { return; } if (!topic.get('details.can_delete')) { return; }
label = "topic.actions.delete"; label = "topic.actions.delete";
action = "deleteTopic";
icon = "trash-o"; icon = "trash-o";
} }
@ -131,35 +129,26 @@ export default Discourse.View.extend({
if (post.get('deleted_at') || post.get('user_deleted')) { if (post.get('deleted_at') || post.get('user_deleted')) {
if (!post.get('can_recover')) { return; } if (!post.get('can_recover')) { return; }
label = "post.controls.undelete"; label = "post.controls.undelete";
action = "recover";
icon = "undo"; icon = "undo";
} else { } else {
if (!post.get('can_delete')) { return; } if (!post.get('can_delete')) { return; }
label = "post.controls.delete"; label = "post.controls.delete";
action = "delete";
icon = "trash-o"; icon = "trash-o";
} }
} }
var action = (icon === 'trash-o') ? 'delete' : 'recover';
buffer.push("<button title=\"" + buffer.push("<button title=\"" +
I18n.t(label) + I18n.t(label) +
"\" data-action=\"" + action + "\" class=\"delete\"><i class=\"fa fa-" + icon + "\"></i></button>"); "\" data-action=\"" + action + "\" class=\"delete\"><i class=\"fa fa-" + icon + "\"></i></button>");
}, },
clickDeleteTopic: function() { clickRecover: function(post) {
this.get('controller').deleteTopic(); this.get('controller').send('recoverPost', post);
}, },
clickRecoverTopic: function() { clickDelete: function(post) {
this.get('controller').recoverTopic(); this.get('controller').send('deletePost', post);
},
clickRecover: function() {
this.get('controller').recoverPost(this.get('post'));
},
clickDelete: function() {
this.get('controller').deletePost(this.get('post'));
}, },
// Like button // Like button
@ -170,9 +159,8 @@ export default Discourse.View.extend({
"\" data-action=\"like\" class='like'><i class=\"fa fa-heart\"></i></button>"); "\" data-action=\"like\" class='like'><i class=\"fa fa-heart\"></i></button>");
}, },
clickLike: function() { clickLike: function(post) {
var likeAction = this.get('post.actionByName.like'); this.get('controller').send('likePost', post);
if (likeAction) likeAction.act();
}, },
// Flag button // Flag button
@ -183,8 +171,8 @@ export default Discourse.View.extend({
"\" data-action=\"flag\" class='flag'><i class=\"fa fa-flag\"></i></button>"); "\" data-action=\"flag\" class='flag'><i class=\"fa fa-flag\"></i></button>");
}, },
clickFlag: function() { clickFlag: function(post) {
this.get('controller').send('showFlags', this.get('post')); this.get('controller').send('showFlags', post);
}, },
// Edit button // Edit button
@ -195,8 +183,8 @@ export default Discourse.View.extend({
"\" data-action=\"edit\" class='edit'><i class=\"fa fa-pencil\"></i></button>"); "\" data-action=\"edit\" class='edit'><i class=\"fa fa-pencil\"></i></button>");
}, },
clickEdit: function() { clickEdit: function(post) {
this.get('controller').editPost(this.get('post')); this.get('controller').send('editPost', post);
}, },
// Share button // Share button
@ -216,8 +204,8 @@ export default Discourse.View.extend({
(I18n.t("topic.reply.title")) + "</span></button>"); (I18n.t("topic.reply.title")) + "</span></button>");
}, },
clickReply: function() { clickReply: function(post) {
this.get('controller').replyToPost(this.get('post')); this.get('controller').send('replyToPost', post);
}, },
// Bookmark button // Bookmark button
@ -242,8 +230,8 @@ export default Discourse.View.extend({
"'></div></button>"); "'></div></button>");
}, },
clickBookmark: function() { clickBookmark: function(post) {
this.get('post').toggleProperty('bookmarked'); this.get('controller').send('toggleBookmark', post);
}, },
renderAdmin: function(post, buffer) { renderAdmin: function(post, buffer) {

View file

@ -25,13 +25,7 @@ module("Discourse.KeyboardShortcuts", {
$("#qunit-fixture").html([ $("#qunit-fixture").html([
"<article class='topic-post selected'>", "<article class='topic-post selected'>",
" <button class='bookmark'></button>", "<a class='post-date'></a>" +
" <button class='delete'></button>",
" <button class='edit'></button>",
" <button class='like'></button>",
" <button class='create'></button>",
" <button class='share'></button>",
" <button class='flag'></button>",
"</article>", "</article>",
"<div class='notification-options'>", "<div class='notification-options'>",
" <ul>", " <ul>",