diff --git a/app/assets/javascripts/discourse/components/actions-summary.js.es6 b/app/assets/javascripts/discourse/components/actions-summary.js.es6 new file mode 100644 index 00000000000..e78e7dde35a --- /dev/null +++ b/app/assets/javascripts/discourse/components/actions-summary.js.es6 @@ -0,0 +1,63 @@ +import StringBuffer from 'discourse/mixins/string-buffer'; +import { iconHTML } from 'discourse/helpers/fa-icon'; + +export default Ember.Component.extend(StringBuffer, { + tagName: 'section', + classNameBindings: [':post-actions', 'hidden'], + actionsSummary: Em.computed.alias('post.actionsWithoutLikes'), + emptySummary: Em.computed.empty('actionsSummary'), + hidden: Em.computed.and('emptySummary', 'post.notDeleted'), + rerenderTriggers: ['actionsSummary.@each', 'post.deleted'], + + // This was creating way too many bound ifs and subviews in the handlebars version. + renderString(buffer) { + if (!this.get('emptySummary')) { + this.get('actionsSummary').forEach(function(c) { + buffer.push("
"); + + const renderActionIf = function(property, dataAttribute, text) { + if (!c.get(property)) { return; } + buffer.push(" " + text + "."); + }; + + // TODO multi line expansion for flags + buffer.push(c.get('description') + '.'); + renderActionIf('can_undo', 'undo', I18n.t("post.actions.undo." + c.get('actionType.name_key'))); + renderActionIf('can_defer_flags', 'defer-flags', I18n.t("post.actions.defer_flags", { count: c.count })); + buffer.push("
"); + }); + } + + const post = this.get('post'); + if (!post.get('deleted')) { + buffer.push("
" + + iconHTML('fa-trash-o') + ' ' + + Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) + + Discourse.Formatter.autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) + + "
"); + } + }, + + actionTypeById(actionTypeId) { + return this.get('actionsSummary').findProperty('id', actionTypeId); + }, + + click(e) { + const $target = $(e.target); + let actionTypeId; + + const post = this.get('post'); + + if (actionTypeId = $target.data('defer-flags')) { + this.actionTypeById(actionTypeId).deferFlags(post); + return false; + } + + if (actionTypeId = $target.data('undo')) { + this.get('actionsSummary').findProperty('id', actionTypeId).undo(post); + return false; + } + + return false; + } +}); diff --git a/app/assets/javascripts/discourse/components/discourse-action-history.js.es6 b/app/assets/javascripts/discourse/components/discourse-action-history.js.es6 deleted file mode 100644 index ec38fca1ad7..00000000000 --- a/app/assets/javascripts/discourse/components/discourse-action-history.js.es6 +++ /dev/null @@ -1,98 +0,0 @@ -import StringBuffer from 'discourse/mixins/string-buffer'; - -export default Em.Component.extend(StringBuffer, { - tagName: 'section', - classNameBindings: [':post-actions', 'hidden'], - actionsHistory: Em.computed.alias('post.actionsHistory'), - emptyHistory: Em.computed.empty('actionsHistory'), - hidden: Em.computed.and('emptyHistory', 'post.notDeleted'), - - rerenderTriggers: ['actionsHistory.@each', 'actionsHistory.users.length', 'post.deleted'], - - // This was creating way too many bound ifs and subviews in the handlebars version. - renderString(buffer) { - if (!this.get('emptyHistory')) { - this.get('actionsHistory').forEach(function(c) { - buffer.push("
"); - - const renderActionIf = function(property, dataAttribute, text) { - if (!c.get(property)) { return; } - buffer.push(" " + text + "."); - }; - - // TODO multi line expansion for flags - let iconsHtml = ""; - if (c.get('usersExpanded')) { - let postUrl; - c.get('users').forEach(function(u) { - iconsHtml += ""; - if (u.post_url) { - postUrl = postUrl || u.post_url; - } - iconsHtml += Discourse.Utilities.avatarImg({ - size: 'small', - avatarTemplate: u.get('avatarTemplate'), - title: u.get('username') - }); - iconsHtml += ""; - }); - - let key = 'post.actions.people.' + c.get('actionType.name_key'); - if (postUrl) { key = key + "_with_url"; } - - // TODO postUrl might be uninitialized? pick a good default - buffer.push(" " + I18n.t(key, { icons: iconsHtml, postUrl: postUrl}) + "."); - } - renderActionIf('usersCollapsed', 'who-acted', c.get('description')); - renderActionIf('canAlsoAction', 'act', I18n.t("post.actions.it_too." + c.get('actionType.name_key'))); - renderActionIf('can_undo', 'undo', I18n.t("post.actions.undo." + c.get('actionType.name_key'))); - renderActionIf('can_defer_flags', 'defer-flags', I18n.t("post.actions.defer_flags", { count: c.count })); - - buffer.push("
"); - }); - } - - const post = this.get('post'); - if (post.get('deleted')) { - buffer.push("
" + - " " + - Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) + - Discourse.Formatter.autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) + - "
"); - } - }, - - actionTypeById(actionTypeId) { - return this.get('actionsHistory').findProperty('id', actionTypeId); - }, - - click(e) { - const $target = $(e.target); - let actionTypeId; - - const post = this.get('post'); - - if (actionTypeId = $target.data('defer-flags')) { - this.actionTypeById(actionTypeId).deferFlags(post); - return false; - } - - // User wants to know who actioned it - if (actionTypeId = $target.data('who-acted')) { - this.actionTypeById(actionTypeId).loadUsers(post); - return false; - } - - if (actionTypeId = $target.data('act')) { - this.get('actionsHistory').findProperty('id', actionTypeId).act(post); - return false; - } - - if (actionTypeId = $target.data('undo')) { - this.get('actionsHistory').findProperty('id', actionTypeId).undo(post); - return false; - } - - return false; - } -}); diff --git a/app/assets/javascripts/discourse/views/post-menu.js.es6 b/app/assets/javascripts/discourse/components/post-menu.js.es6 similarity index 60% rename from app/assets/javascripts/discourse/views/post-menu.js.es6 rename to app/assets/javascripts/discourse/components/post-menu.js.es6 index e865eec7a26..ac3d83fa6e5 100644 --- a/app/assets/javascripts/discourse/views/post-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/post-menu.js.es6 @@ -1,7 +1,8 @@ import StringBuffer from 'discourse/mixins/string-buffer'; +import { iconHTML } from 'discourse/helpers/fa-icon'; // Helper class for rendering a button -export var Button = function(action, label, icon, opts) { +export const Button = function(action, label, icon, opts) { this.action = action; this.label = label; @@ -18,7 +19,7 @@ function animateHeart($elem, start, end, complete) { .css('textIndent', start) .animate({ textIndent: end }, { complete: complete, - step: function(now) { + step(now) { $(this).css('transform','scale('+now+')'); }, duration: 150 @@ -26,9 +27,9 @@ function animateHeart($elem, start, end, complete) { } Button.prototype.render = function(buffer) { - var opts = this.opts; + const opts = this.opts; - var label = I18n.t(this.label); + const label = I18n.t(this.label); buffer.push(""); }; -var hiddenButtons; +let hiddenButtons; -var PostMenuView = Discourse.View.extend(StringBuffer, { +const PostMenuView = Ember.Component.extend(StringBuffer, { tagName: 'section', classNames: ['post-menu-area', 'clearfix'], rerenderTriggers: [ 'post.deleted_at', - 'post.like_count', + 'likeAction.count', + 'likeAction.users.length', 'post.reply_count', 'post.showRepliesBelow', 'post.can_delete', @@ -62,53 +64,71 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { 'post.post_type', 'collapsed'], + likeAction: function() { + return this.get('post.actionByName.like'); + }.property('post.actionByName.like'), + _collapsedByDefault: function() { this.set('collapsed', true); }.on('init'), - renderString: function(buffer) { - var post = this.get('post'); + renderString(buffer) { + const post = this.get('post'); buffer.push(""); }, // Delegate click actions - click: function(e) { - var $target = $(e.target), + click(e) { + const $target = $(e.target), action = $target.data('action') || $target.parent().data('action'); if (!action) return; - var handler = this["click" + action.capitalize()]; + const handler = this["click" + action.capitalize()]; if (!handler) return; handler.call(this, this.get('post')); }, // Replies Button - renderReplies: function(post, buffer) { + renderReplies(post, buffer) { if (!post.get('showRepliesBelow')) return; - var reply_count = post.get('reply_count'); + const replyCount = post.get('reply_count'); buffer.push(""); + const icon = (this.get('post.replies.length') > 0) ? 'chevron-up' : 'chevron-down'; + return buffer.push(iconHTML(icon) + ""); }, - renderButtons: function(post, buffer) { - var self = this, - allButtons = [], - visibleButtons = []; + renderLikes(post, buffer) { + const likeCount = this.get('likeAction.count') || 0; + if (likeCount === 0) { return; } + + buffer.push(""); + }, + + + renderButtons(post, buffer) { + const self = this; + const allButtons = []; + let visibleButtons = []; if (typeof hiddenButtons === "undefined") { - if (!Em.isEmpty(Discourse.SiteSettings.post_menu_hidden_items)) { - hiddenButtons = Discourse.SiteSettings.post_menu_hidden_items.split('|'); + if (!Em.isEmpty(this.siteSettings.post_menu_hidden_items)) { + hiddenButtons = this.siteSettings.post_menu_hidden_items.split('|'); } else { hiddenButtons = []; } @@ -118,11 +138,11 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { hiddenButtons.removeObject("bookmark"); } - var yours = post.get('yours'); - Discourse.SiteSettings.post_menu.split("|").forEach(function(i) { - var creator = self["buttonFor" + i.replace(/\+/, '').capitalize()]; + const yours = post.get('yours'); + this.siteSettings.post_menu.split("|").forEach(function(i) { + const creator = self["buttonFor" + i.replace(/\+/, '').capitalize()]; if (creator) { - var button = creator.call(self, post); + const button = creator.call(self, post); if (button) { allButtons.push(button); if ((yours && button.opts.alwaysShowYours) || @@ -136,7 +156,7 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { // Only show ellipsis if there is more than one button hidden // if there are no more buttons, we are not collapsed - var collapsed = this.get('collapsed'); + const collapsed = this.get('collapsed'); if (!collapsed || (allButtons.length <= visibleButtons.length + 1)) { visibleButtons = allButtons; if (collapsed) { this.set('collapsed', false); } @@ -144,7 +164,7 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { visibleButtons.splice(visibleButtons.length - 1, 0, this.buttonForShowMoreActions(post)); } - var callbacks = PostMenuView._registerButtonCallbacks; + const callbacks = PostMenuView._registerButtonCallbacks; if (callbacks) { _.each(callbacks, function(callback) { callback.apply(self, [visibleButtons]); @@ -152,13 +172,23 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { } buffer.push('
'); - visibleButtons.forEach(function (b) { - b.render(buffer); - }); + visibleButtons.forEach((b) => b.render(buffer)); buffer.push("
"); }, - clickReplies: function() { + clickLikes() { + const likeAction = this.get('post.actionByName.like'); + if (likeAction) { + const users = likeAction.get('users'); + if (users && users.length) { + users.clear(); + } else { + likeAction.loadUsers(this.get('post')); + } + } + }, + + clickReplies() { if (this.get('post.replies.length') > 0) { this.set('post.replies', []); } else { @@ -167,12 +197,12 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }, // Delete button - buttonForDelete: function(post) { - var label, icon; + buttonForDelete(post) { + let label, icon; if (post.get('post_number') === 1) { // If it's the first post, the delete/undo actions are related to the topic - var topic = post.get('topic'); + const topic = post.get('topic'); if (topic.get('deleted_at')) { if (!topic.get('details.can_recover')) { return; } label = "topic.actions.recover"; @@ -195,50 +225,50 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { icon = "trash-o"; } } - var action = (icon === 'trash-o') ? 'delete' : 'recover'; - var opts; + const action = (icon === 'trash-o') ? 'delete' : 'recover'; + let opts; if (icon === "trash-o"){ opts = {className: 'delete'}; } return new Button(action, label, icon, opts); }, - clickRecover: function(post) { - this.get('controller').send('recoverPost', post); + clickRecover(post) { + this.sendAction('recoverPost', post); }, - clickDelete: function(post) { - this.get('controller').send('deletePost', post); + clickDelete(post) { + this.sendAction('deletePost', post); }, // Like button - buttonForLike: function(post) { - var likeAction = post.get('actionByName.like'); + buttonForLike(post) { + const likeAction = this.get('likeAction'); if (!likeAction) { return; } - var className = likeAction.get('acted') ? 'has-like' : 'like'; + const className = likeAction.get('acted') ? 'has-like' : 'like'; if (likeAction.get('canToggle')) { - var descKey = likeAction.get('acted') ? 'post.controls.undo_like' : 'post.controls.like'; + const descKey = likeAction.get('acted') ? 'post.controls.undo_like' : 'post.controls.like'; return new Button('like', descKey, 'heart', {className: className}); } else if (likeAction.get('acted')) { return new Button('like', 'post.controls.has_liked', 'heart', {className: className, disabled: true}); } }, - clickLike: function(post) { - var $heart = this.$('.fa-heart'), - controller = this.get('controller'), - $likeButton = this.$('button[data-action=like]'); + clickLike(post) { + const $heart = this.$('.fa-heart'), + $likeButton = this.$('button[data-action=like]'), + acted = post.get('actionByName.like.acted'), + self = this; - var acted = post.get('actionByName.like.acted'); if (acted) { - controller.send('toggleLike', post); + this.sendAction('toggleLike', post); $likeButton.removeClass('has-like').addClass('like'); } else { - var scale = [1.0, 1.5]; + const scale = [1.0, 1.5]; animateHeart($heart, scale[0], scale[1], function() { animateHeart($heart, scale[1], scale[0], function() { - controller.send('toggleLike', post); + self.sendAction('toggleLike', post); $likeButton.removeClass('like').addClass('has-like'); }); }); @@ -246,17 +276,17 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }, // Flag button - buttonForFlag: function(post) { + buttonForFlag(post) { if (Em.isEmpty(post.get('flagsAvailable'))) return; return new Button('flag', 'post.controls.flag', 'flag'); }, - clickFlag: function(post) { - this.get('controller').send('showFlags', post); + clickFlag(post) { + this.sendAction('showFlags', post); }, // Edit button - buttonForEdit: function(post) { + buttonForEdit(post) { if (!post.get('can_edit')) return; return new Button('edit', 'post.controls.edit', 'pencil', { alwaysShowYours: true, @@ -264,14 +294,14 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }); }, - clickEdit: function(post) { - this.get('controller').send('editPost', post); + clickEdit(post) { + this.sendAction('editPost', post); }, // Share button - buttonForShare: function(post) { + buttonForShare(post) { if (!Discourse.User.current()) return; - var options = { + const options = { shareUrl: post.get('shareUrl'), postNumber: post.get('post_number') }; @@ -279,9 +309,9 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }, // Reply button - buttonForReply: function() { - if (!this.get('controller.model.details.can_create_post')) return; - var options = {className: 'create'}; + buttonForReply() { + if (!this.get('canCreatePost')) return; + const options = {className: 'create'}; if(!Discourse.Mobile.mobileView) { options.textLabel = 'topic.reply.title'; @@ -290,15 +320,15 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { return new Button('reply', 'post.controls.reply', 'reply', options); }, - clickReply: function(post) { - this.get('controller').send('replyToPost', post); + clickReply(post) { + this.sendAction('replyToPost', post); }, // Bookmark button - buttonForBookmark: function(post) { + buttonForBookmark(post) { if (!Discourse.User.current()) return; - var iconClass = 'read-icon', + let iconClass = 'read-icon', buttonClass = 'bookmark', tooltip = 'bookmarks.not_bookmarked'; @@ -311,33 +341,30 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { return new Button('bookmark', tooltip, {className: buttonClass, innerHTML: "
"}); }, - clickBookmark: function(post) { - this.get('controller').send('toggleBookmark', post); + clickBookmark(post) { + this.sendAction('toggleBookmark', post); }, - buttonForAdmin: function() { + buttonForAdmin() { if (!Discourse.User.currentProp('canManageTopic')) { return; } return new Button('admin', 'post.controls.admin', 'wrench'); }, - renderAdminPopup: function(post, buffer) { + renderAdminPopup(post, buffer) { if (!Discourse.User.currentProp('canManageTopic')) { return; } - var isWiki = post.get('wiki'), - wikiIcon = '', - wikiText = isWiki ? I18n.t('post.controls.unwiki') : I18n.t('post.controls.wiki'); + const isWiki = post.get('wiki'), + wikiIcon = iconHTML('pencil-square-o'), + wikiText = isWiki ? I18n.t('post.controls.unwiki') : I18n.t('post.controls.wiki'), + isModerator = post.get('post_type') === this.site.get('post_types.moderator_action'), + postTypeIcon = iconHTML('shield'), + postTypeText = isModerator ? I18n.t('post.controls.revert_to_regular') : I18n.t('post.controls.convert_to_moderator'), + rebakePostIcon = iconHTML('cog'), + rebakePostText = I18n.t('post.controls.rebake'), + unhidePostIcon = iconHTML('eye'), + unhidePostText = I18n.t('post.controls.unhide'); - var isModerator = post.get('post_type') === Discourse.Site.currentProp('post_types.moderator_action'), - postTypeIcon = '', - postTypeText = isModerator ? I18n.t('post.controls.revert_to_regular') : I18n.t('post.controls.convert_to_moderator'); - - var rebakePostIcon = '', - rebakePostText = I18n.t('post.controls.rebake'); - - var unhidePostIcon = '', - unhidePostText = I18n.t('post.controls.unhide'); - - var html = '
' + + const html = '
' + '

' + I18n.t('admin_title') + '

' + '
    ' + '
  • ' + wikiIcon + wikiText + '
  • ' + @@ -350,8 +377,8 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { buffer.push(html); }, - clickAdmin: function() { - var $postAdminMenu = this.$(".post-admin-menu"); + clickAdmin() { + const $postAdminMenu = this.$(".post-admin-menu"); $postAdminMenu.show(); $("html").on("mouseup.post-admin-menu", function() { $postAdminMenu.hide(); @@ -359,34 +386,34 @@ var PostMenuView = Discourse.View.extend(StringBuffer, { }); }, - clickToggleWiki: function() { - this.get('controller').send('toggleWiki', this.get('post')); + clickToggleWiki() { + this.sendAction('toggleWiki', this.get('post')); }, - clickTogglePostType: function () { - this.get("controller").send("togglePostType", this.get("post")); + clickTogglePostType() { + this.sendAction("togglePostType", this.get("post")); }, - clickRebakePost: function () { - this.get("controller").send("rebakePost", this.get("post")); + clickRebakePost() { + this.sendAction("rebakePost", this.get("post")); }, - clickUnhidePost: function () { - this.get("controller").send("unhidePost", this.get("post")); + clickUnhidePost() { + this.sendAction("unhidePost", this.get("post")); }, - buttonForShowMoreActions: function() { + buttonForShowMoreActions() { return new Button('showMoreActions', 'show_more', 'ellipsis-h'); }, - clickShowMoreActions: function() { + clickShowMoreActions() { this.set('collapsed', false); } }); PostMenuView.reopenClass({ - registerButton: function(callback){ + registerButton(callback){ this._registerButtonCallbacks = this._registerButtonCallbacks || []; this._registerButtonCallbacks.push(callback); } diff --git a/app/assets/javascripts/discourse/components/who-liked.js.es6 b/app/assets/javascripts/discourse/components/who-liked.js.es6 new file mode 100644 index 00000000000..774d2c6c6fc --- /dev/null +++ b/app/assets/javascripts/discourse/components/who-liked.js.es6 @@ -0,0 +1,24 @@ +import StringBuffer from 'discourse/mixins/string-buffer'; + +export default Ember.Component.extend(StringBuffer, { + classNames: ['who-liked'], + likedUsers: Ember.computed.alias('post.actionByName.like.users'), + rerenderTriggers: ['likedUsers.length'], + + renderString(buffer) { + const likedUsers = this.get('likedUsers'); + if (likedUsers) { + let iconsHtml = ""; + likedUsers.forEach(function(u) { + iconsHtml += ""; + iconsHtml += Discourse.Utilities.avatarImg({ + size: 'small', + avatarTemplate: u.get('avatarTemplate'), + title: u.get('username') + }); + iconsHtml += ""; + }); + buffer.push(iconsHtml); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/flag-action-type.js.es6 b/app/assets/javascripts/discourse/controllers/flag-action-type.js.es6 index 7ff592a4c19..a5eba0df84a 100644 --- a/app/assets/javascripts/discourse/controllers/flag-action-type.js.es6 +++ b/app/assets/javascripts/discourse/controllers/flag-action-type.js.es6 @@ -1,4 +1,5 @@ import ObjectController from 'discourse/controllers/object'; +import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type'; // Supports logic for flags in the modal export default ObjectController.extend({ @@ -38,7 +39,7 @@ export default ObjectController.extend({ return I18n.t("flagging.custom_message.more", { n: minLen - len }); } else { return I18n.t("flagging.custom_message.left", { - n: Discourse.PostActionType.MAX_MESSAGE_LENGTH - len + n: MAX_MESSAGE_LENGTH - len }); } }.property('message.length') diff --git a/app/assets/javascripts/discourse/controllers/flag.js.es6 b/app/assets/javascripts/discourse/controllers/flag.js.es6 index 4708d2e9505..1942934e111 100644 --- a/app/assets/javascripts/discourse/controllers/flag.js.es6 +++ b/app/assets/javascripts/discourse/controllers/flag.js.es6 @@ -1,5 +1,6 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; import ObjectController from 'discourse/controllers/object'; +import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type'; export default ObjectController.extend(ModalFunctionality, { userDetails: null, @@ -42,7 +43,7 @@ export default ObjectController.extend(ModalFunctionality, { if (selected.get('is_custom_flag')) { const len = this.get('message.length') || 0; return len >= Discourse.SiteSettings.min_private_message_post_length && - len <= Discourse.PostActionType.MAX_MESSAGE_LENGTH; + len <= MAX_MESSAGE_LENGTH; } return true; }.property('selected.is_custom_flag', 'message.length'), diff --git a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 index 21e311821d9..a3aceef9504 100644 --- a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 +++ b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 @@ -24,6 +24,9 @@ export default { injectAll(app, 'appEvents'); Discourse.URL.appEvents = appEvents; + app.register('store:main', Store); + inject(app, 'store', 'route', 'controller'); + // Inject Discourse.Site to avoid using Discourse.Site.current() const site = Discourse.Site.current(); app.register('site:main', site, { instantiate: false }); @@ -37,9 +40,6 @@ export default { app.register('session:main', Session.current(), { instantiate: false }); injectAll(app, 'session'); - app.register('store:main', Store); - inject(app, 'store', 'route', 'controller'); - app.register('current-user:main', Discourse.User.current(), { instantiate: false }); inject(app, 'currentUser', 'component', 'route', 'controller'); diff --git a/app/assets/javascripts/discourse/models/action-summary.js.es6 b/app/assets/javascripts/discourse/models/action-summary.js.es6 index 4d011da75f7..507ef41dcfa 100644 --- a/app/assets/javascripts/discourse/models/action-summary.js.es6 +++ b/app/assets/javascripts/discourse/models/action-summary.js.es6 @@ -5,7 +5,7 @@ export default RestModel.extend({ // Description for the action description: function() { - var action = this.get('actionType.name_key'); + const action = this.get('actionType.name_key'); if (this.get('acted')) { if (this.get('count') <= 1) { return I18n.t('post.actions.by_you.' + action); @@ -17,7 +17,6 @@ export default RestModel.extend({ } }.property('count', 'acted', 'actionType'), - canAlsoAction: Em.computed.and('can_act', 'actionType.notCustomFlag'), usersCollapsed: Em.computed.not('usersExpanded'), usersExpanded: Em.computed.gt('users.length', 0), @@ -51,7 +50,7 @@ export default RestModel.extend({ act: function(post, opts) { if (!opts) opts = {}; - var action = this.get('actionType.name_key'); + const action = this.get('actionType.name_key'); // Mark it as acted this.setProperties({ @@ -72,7 +71,7 @@ export default RestModel.extend({ } // Create our post action - var self = this; + const self = this; return Discourse.ajax("/post_actions", { type: 'POST', @@ -109,7 +108,7 @@ export default RestModel.extend({ }, deferFlags: function(post) { - var self = this; + const self = this; return Discourse.ajax("/post_actions/defer_flags", { type: "POST", data: { @@ -122,16 +121,16 @@ export default RestModel.extend({ }, loadUsers: function(post) { - var self = this; + const self = this; Discourse.ajax("/post_actions/users", { data: { id: post.get('id'), post_action_type_id: this.get('id') } }).then(function (result) { - var users = Em.A(); + const users = []; self.set('users', users); - _.each(result,function(user) { + result.forEach(function(user) { if (user.id === Discourse.User.currentProp('id')) { users.pushObject(Discourse.User.current()); } else { diff --git a/app/assets/javascripts/discourse/models/post-action-type.js.es6 b/app/assets/javascripts/discourse/models/post-action-type.js.es6 new file mode 100644 index 00000000000..94a82ff5cb7 --- /dev/null +++ b/app/assets/javascripts/discourse/models/post-action-type.js.es6 @@ -0,0 +1,9 @@ +import RestModel from 'discourse/models/rest'; + +const PostActionType = RestModel.extend({ + notCustomFlag: Em.computed.not('is_custom_flag') +}); + +export const MAX_MESSAGE_LENGTH = 500; + +export default PostActionType; diff --git a/app/assets/javascripts/discourse/models/post.js.es6 b/app/assets/javascripts/discourse/models/post.js.es6 index d7ee374c869..c6840cad5db 100644 --- a/app/assets/javascripts/discourse/models/post.js.es6 +++ b/app/assets/javascripts/discourse/models/post.js.es6 @@ -108,11 +108,12 @@ const Post = RestModel.extend({ }); }.property('actions_summary.@each.can_act'), - actionsHistory: function() { + actionsWithoutLikes: function() { if (!this.present('actions_summary')) return null; return this.get('actions_summary').filter(function(i) { if (i.get('count') === 0) return false; + if (i.get('actionType.name_key') === 'like') { return false; } if (i.get('users') && i.get('users').length > 0) return true; return !i.get('hidden'); }); diff --git a/app/assets/javascripts/discourse/models/post_action_type.js b/app/assets/javascripts/discourse/models/post_action_type.js deleted file mode 100644 index b941ae25b2e..00000000000 --- a/app/assets/javascripts/discourse/models/post_action_type.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - A data model representing action types (flags, likes) against a Post - - @class PostActionType - @extends Discourse.Model - @namespace Discourse - @module Discourse -**/ -Discourse.PostActionType = Discourse.Model.extend({ - notCustomFlag: Em.computed.not('is_custom_flag') -}); - -Discourse.PostActionType.reopenClass({ - MAX_MESSAGE_LENGTH: 500 -}); diff --git a/app/assets/javascripts/discourse/models/site.js.es6 b/app/assets/javascripts/discourse/models/site.js.es6 index 1831da98956..b7bc4a4977c 100644 --- a/app/assets/javascripts/discourse/models/site.js.es6 +++ b/app/assets/javascripts/discourse/models/site.js.es6 @@ -1,3 +1,5 @@ +import PostActionType from 'discourse/models/post-action-type'; + const Site = Discourse.Model.extend({ isReadOnly: Em.computed.alias('is_readonly'), @@ -102,7 +104,7 @@ Site.reopenClass(Discourse.Singleton, { if (result.post_action_types) { result.postActionByIdLookup = Em.Object.create(); result.post_action_types = _.map(result.post_action_types,function(p) { - const actionType = Discourse.PostActionType.create(p); + const actionType = PostActionType.create(p); result.postActionByIdLookup.set("action" + p.id, actionType); return actionType; }); @@ -111,7 +113,7 @@ Site.reopenClass(Discourse.Singleton, { if (result.topic_flag_types) { result.topicFlagByIdLookup = Em.Object.create(); result.topic_flag_types = _.map(result.topic_flag_types,function(p) { - const actionType = Discourse.PostActionType.create(p); + const actionType = PostActionType.create(p); result.topicFlagByIdLookup.set("action" + p.id, actionType); return actionType; }); diff --git a/app/assets/javascripts/discourse/templates/post.hbs b/app/assets/javascripts/discourse/templates/post.hbs index 60750cc29ab..2bb577fc988 100644 --- a/app/assets/javascripts/discourse/templates/post.hbs +++ b/app/assets/javascripts/discourse/templates/post.hbs @@ -90,7 +90,20 @@ {{/if}} {{/if}} - {{view 'post-menu' post=this adminMenu=view.adminMenu}} + + {{post-menu post=this + canCreatePost=controller.model.details.can_create_post + replyToPost="replyToPost" + recoverPost="recoverPost" + deletePost="deletePost" + toggleLike="toggleLike" + showFlags="showFlags" + editPost="editPost" + toggleBookmark="toggleBookmark" + toggleWiki="toggleWiki" + togglePostType="togglePostType" + rebakePost="rebakePost" + unhidePost="unhidePost"}}
{{#if replies}} @@ -101,7 +114,8 @@ {{/if}} - {{discourse-action-history post=this}} + {{actions-summary post=this}} + {{who-liked post=this}} {{view 'topic-map-container' post=this topic=controller.model}}
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js index 67de20ffe60..ddaf3d90c31 100644 --- a/app/assets/javascripts/main_include.js +++ b/app/assets/javascripts/main_include.js @@ -27,6 +27,7 @@ //= require_tree ./discourse/adapters //= require ./discourse/models/rest //= require ./discourse/models/model +//= require ./discourse/models/post-action-type //= require ./discourse/models/post //= require ./discourse/models/post-stream //= require ./discourse/models/topic-details diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index e0b4f0762d8..f0d8a173e14 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -233,3 +233,11 @@ blockquote > *:last-child { } } + +.who-liked { + a { + margin: 0 0.25em 0.5em 0; + display: inline-block; + } +} + diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss index 692756022ed..233513bfd63 100644 --- a/app/assets/stylesheets/desktop/topic-post.scss +++ b/app/assets/stylesheets/desktop/topic-post.scss @@ -95,7 +95,7 @@ nav.post-controls { } } - .show-replies { + .show-replies, .show-likes { margin-left: 0; font-size: inherit; span.badge-posts {color: scale-color($primary, $lightness: 60%);} diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss index 3fa744b2fee..9da264cfeb1 100644 --- a/app/assets/stylesheets/mobile/topic-post.scss +++ b/app/assets/stylesheets/mobile/topic-post.scss @@ -27,6 +27,21 @@ span.badge-posts { display: none; } +.show-likes { + margin-left: 0; + padding-left: 0; + padding-right: 0; + font-size: inherit; + span.badge-posts {color: scale-color($primary, $lightness: 60%);} + &:hover { + background: dark-light-diff($primary, $secondary, 90%, -65%); + span.badge-posts {color: $primary;} + } + i { + display: none; + } +} + nav.post-controls { clear: both; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e637b4a17bb..25fe84f2a55 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1233,6 +1233,9 @@ en: has_replies: one: "Reply" other: "Replies" + has_likes: + one: "Like" + other: "Likes" errors: create: "Sorry, there was an error creating your post. Please try again." diff --git a/test/javascripts/controllers/flag-test.js.es6 b/test/javascripts/controllers/flag-test.js.es6 index 686f3d20151..beb5dd9af6e 100644 --- a/test/javascripts/controllers/flag-test.js.es6 +++ b/test/javascripts/controllers/flag-test.js.es6 @@ -1,3 +1,5 @@ +import createStore from 'helpers/create-store'; + var buildPost = function(args) { return Discourse.Post.create(_.merge({ id: 1, @@ -18,14 +20,21 @@ moduleFor("controller:flag", "controller:flag", { }); test("canDeleteSpammer not staff", function(){ + const store = createStore(); + var flagController = this.subject({ model: buildPost() }); sandbox.stub(Discourse.User, 'currentProp').withArgs('staff').returns(false); - flagController.set('selected', Discourse.PostActionType.create({name_key: 'spam'})); + + const spamFlag = store.createRecord('post-action-type', {name_key: 'spam'}); + flagController.set('selected', spamFlag); equal(flagController.get('canDeleteSpammer'), false, 'false if current user is not staff'); }); var canDeleteSpammer = function(flagController, postActionType, expected, testName) { - flagController.set('selected', Discourse.PostActionType.create({name_key: postActionType})); + const store = createStore(); + const flag = store.createRecord('post-action-type', {name_key: postActionType}); + flagController.set('selected', flag); + equal(flagController.get('canDeleteSpammer'), expected, testName); }; diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index e29c25cd249..f93e3879814 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -81,15 +81,19 @@ var origDebounce = Ember.run.debounce, flushMap = require('discourse/models/store', null, null, false).flushMap, server; +function dup(obj) { + return jQuery.extend(true, {}, obj); +} + QUnit.testStart(function(ctx) { server = createPretendServer(); // Allow our tests to change site settings and have them reset before the next test - Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal); + Discourse.SiteSettings = dup(Discourse.SiteSettingsOriginal); Discourse.BaseUri = "/"; Discourse.BaseUrl = "localhost"; Discourse.User.resetCurrent(); - Discourse.Site.resetCurrent(Discourse.Site.create(fixtures['site.json'].site)); + Discourse.Site.resetCurrent(Discourse.Site.create(dup(fixtures['site.json'].site))); Discourse.URL.redirectedTo = null; Discourse.URL.redirectTo = function(url) {