From 176120cbc18f0f6ef5795ded4f3a16bde2581c68 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 11 Jun 2014 12:49:57 -0400 Subject: [PATCH] REFACTOR: Keyboard Shortcuts should send their actions directly to posts, not by activating clicks on buttons. --- .../discourse/controllers/topic_controller.js | 214 ++++++++++-------- .../initializers/keyboard-shortcuts.js.es6 | 4 +- .../discourse/lib/keyboard_shortcuts.js | 45 +++- .../discourse/views/post-menu.js.es6 | 46 ++-- .../keyboard_shortcuts_component_test.js | 8 +- 5 files changed, 169 insertions(+), 148 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index e77a672d6..1ea8ffabc 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -49,6 +49,122 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected }, 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() { Discourse.URL.routeTo(this.get('firstPostUrl')); }, @@ -453,109 +569,11 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected 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 reply: function() { 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) { var selectedPosts = this.get('selectedPosts'); if (this.postSelected(post)) { diff --git a/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 index 7cc8e63d2..5ae89cdf2 100644 --- a/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/initializers/keyboard-shortcuts.js.es6 @@ -5,7 +5,7 @@ **/ export default { name: "keyboard-shortcuts", - initialize: function() { - Discourse.KeyboardShortcuts.bindEvents(Mousetrap); + initialize: function(container) { + Discourse.KeyboardShortcuts.bindEvents(Mousetrap, container); } }; diff --git a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js index 967220976..e1f1af80c 100644 --- a/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js +++ b/app/assets/javascripts/discourse/lib/keyboard_shortcuts.js @@ -16,27 +16,30 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ 'g t': '/top' }, + SELECTED_POST_BINDINGS: { + 'b': 'toggleBookmark', + 'd': 'deletePost', + 'e': 'editPost', + 'l': 'likePost', + 'r': 'replyToPost', + '!': 'showFlags' + }, + CLICK_BINDINGS: { - 'b': '.topic-post.selected button.bookmark', // bookmark current post '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 '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 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 w': 'div.notification-options li[data-id="3"] a', // mark topic as watching 'n': '#user-notifications', // open notifictions menu - 'o,enter': '#topic-list tr.selected a.title', // open selected 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 - 's': '.topic-post.selected button.share', // share selected post - '!': '.topic-post.selected button.flag' // flag selected post + 'o,enter': '#topic-list tr.selected a.title', // open selected topic + 'shift+r': '#topic-footer-buttons button.create', // reply to topic + 'shift+s': '#topic-footer-buttons button.share', // share topic + 's': '.topic-post.selected a.post-date' // share post }, FUNCTION_BINDINGS: { @@ -54,10 +57,12 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ 'q': 'quoteReply' }, - bindEvents: function(keyTrapper) { + bindEvents: function(keyTrapper, container) { this.keyTrapper = keyTrapper; + this.container = container; _.each(this.PATH_BINDINGS, this._bindToPath, this); _.each(this.CLICK_BINDINGS, this._bindToClick, this); + _.each(this.SELECTED_POST_BINDINGS, this._bindToSelectedPost, this); _.each(this.FUNCTION_BINDINGS, this._bindToFunction, this); }, @@ -79,7 +84,7 @@ Discourse.KeyboardShortcuts = Ember.Object.createWithMixins({ _jumpTo: function(direction) { 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'); }, + _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) { this.keyTrapper.bind(binding, function() { Discourse.URL.routeTo(path); diff --git a/app/assets/javascripts/discourse/views/post-menu.js.es6 b/app/assets/javascripts/discourse/views/post-menu.js.es6 index f6f97046c..5a583cf52 100644 --- a/app/assets/javascripts/discourse/views/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/views/post-menu.js.es6 @@ -73,7 +73,7 @@ export default Discourse.View.extend({ var handler = this["click" + action.capitalize()]; if (!handler) return; - handler.call(this); + handler.call(this, this.get('post')); }, // Replies Button @@ -109,7 +109,7 @@ export default Discourse.View.extend({ // Delete button renderDelete: function(post, buffer) { - var label, action, icon; + var label, icon; if (post.get('post_number') === 1) { // 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('details.can_recover')) { return; } label = "topic.actions.recover"; - action = "recoverTopic"; icon = "undo"; } else { if (!topic.get('details.can_delete')) { return; } label = "topic.actions.delete"; - action = "deleteTopic"; icon = "trash-o"; } @@ -131,35 +129,26 @@ export default Discourse.View.extend({ if (post.get('deleted_at') || post.get('user_deleted')) { if (!post.get('can_recover')) { return; } label = "post.controls.undelete"; - action = "recover"; icon = "undo"; } else { if (!post.get('can_delete')) { return; } label = "post.controls.delete"; - action = "delete"; icon = "trash-o"; } } + var action = (icon === 'trash-o') ? 'delete' : 'recover'; buffer.push(""); }, - clickDeleteTopic: function() { - this.get('controller').deleteTopic(); + clickRecover: function(post) { + this.get('controller').send('recoverPost', post); }, - clickRecoverTopic: function() { - this.get('controller').recoverTopic(); - }, - - clickRecover: function() { - this.get('controller').recoverPost(this.get('post')); - }, - - clickDelete: function() { - this.get('controller').deletePost(this.get('post')); + clickDelete: function(post) { + this.get('controller').send('deletePost', post); }, // Like button @@ -170,9 +159,8 @@ export default Discourse.View.extend({ "\" data-action=\"like\" class='like'>"); }, - clickLike: function() { - var likeAction = this.get('post.actionByName.like'); - if (likeAction) likeAction.act(); + clickLike: function(post) { + this.get('controller').send('likePost', post); }, // Flag button @@ -183,8 +171,8 @@ export default Discourse.View.extend({ "\" data-action=\"flag\" class='flag'>"); }, - clickFlag: function() { - this.get('controller').send('showFlags', this.get('post')); + clickFlag: function(post) { + this.get('controller').send('showFlags', post); }, // Edit button @@ -195,8 +183,8 @@ export default Discourse.View.extend({ "\" data-action=\"edit\" class='edit'>"); }, - clickEdit: function() { - this.get('controller').editPost(this.get('post')); + clickEdit: function(post) { + this.get('controller').send('editPost', post); }, // Share button @@ -216,8 +204,8 @@ export default Discourse.View.extend({ (I18n.t("topic.reply.title")) + ""); }, - clickReply: function() { - this.get('controller').replyToPost(this.get('post')); + clickReply: function(post) { + this.get('controller').send('replyToPost', post); }, // Bookmark button @@ -242,8 +230,8 @@ export default Discourse.View.extend({ "'>"); }, - clickBookmark: function() { - this.get('post').toggleProperty('bookmarked'); + clickBookmark: function(post) { + this.get('controller').send('toggleBookmark', post); }, renderAdmin: function(post, buffer) { diff --git a/test/javascripts/components/keyboard_shortcuts_component_test.js b/test/javascripts/components/keyboard_shortcuts_component_test.js index 5d9efbbdb..da84e5883 100644 --- a/test/javascripts/components/keyboard_shortcuts_component_test.js +++ b/test/javascripts/components/keyboard_shortcuts_component_test.js @@ -25,13 +25,7 @@ module("Discourse.KeyboardShortcuts", { $("#qunit-fixture").html([ "
", - " ", - " ", - " ", - " ", - " ", - " ", - " ", + "" + "
", "
", "