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 000000000..e78e7dde3
--- /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("<div class='post-action'>");
+
+        const renderActionIf = function(property, dataAttribute, text) {
+          if (!c.get(property)) { return; }
+          buffer.push(" <span class='action-link " + dataAttribute  +"-action'><a href='#' data-" + dataAttribute + "='" + c.get('id') + "'>" + text + "</a>.</span>");
+        };
+
+        // 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("</div>");
+      });
+    }
+
+    const post = this.get('post');
+    if (!post.get('deleted')) {
+      buffer.push("<div class='post-action'>" +
+                  iconHTML('fa-trash-o') + '&nbsp;' +
+                  Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) +
+                  Discourse.Formatter.autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) +
+                  "</div>");
+    }
+  },
+
+  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 ec38fca1a..000000000
--- 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("<div class='post-action'>");
-
-        const renderActionIf = function(property, dataAttribute, text) {
-          if (!c.get(property)) { return; }
-          buffer.push(" <span class='action-link " + dataAttribute  +"-action'><a href='#' data-" + dataAttribute + "='" + c.get('id') + "'>" + text + "</a>.</span>");
-        };
-
-        // TODO multi line expansion for flags
-        let iconsHtml = "";
-        if (c.get('usersExpanded')) {
-          let postUrl;
-          c.get('users').forEach(function(u) {
-            iconsHtml += "<a href=\"" + Discourse.getURL("/users/") + u.get('username_lower') + "\" data-user-card=\"" + u.get('username_lower') + "\">";
-            if (u.post_url) {
-              postUrl = postUrl || u.post_url;
-            }
-            iconsHtml += Discourse.Utilities.avatarImg({
-              size: 'small',
-              avatarTemplate: u.get('avatarTemplate'),
-              title: u.get('username')
-            });
-            iconsHtml += "</a>";
-          });
-
-          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("</div>");
-      });
-    }
-
-    const post = this.get('post');
-    if (post.get('deleted')) {
-      buffer.push("<div class='post-action'>" +
-                  "<i class='fa fa-trash-o'></i>&nbsp;" +
-                  Discourse.Utilities.tinyAvatar(post.get('postDeletedBy.avatar_template'), {title: post.get('postDeletedBy.username')}) +
-                  Discourse.Formatter.autoUpdatingRelativeAge(new Date(post.get('postDeletedAt'))) +
-                  "</div>");
-    }
-  },
-
-  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 e865eec7a..ac3d83fa6 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("<button aria-label=\"" + label +"\" " + "title=\"" + label + "\"");
   if (opts.disabled) { buffer.push(" disabled"); }
@@ -36,21 +37,22 @@ Button.prototype.render = function(buffer) {
   if (opts.shareUrl) { buffer.push(" data-share-url=\"" + opts.shareUrl + "\""); }
   if (opts.postNumber) { buffer.push(" data-post-number=\"" + opts.postNumber + "\""); }
   buffer.push(" data-action=\"" + this.action + "\">");
-  if (this.icon) { buffer.push("<i class=\"fa fa-" + this.icon + "\"></i>"); }
+  if (this.icon) { buffer.push(iconHTML(this.icon)); }
   if (opts.textLabel) { buffer.push(I18n.t(opts.textLabel)); }
   if (opts.innerHTML) { buffer.push(opts.innerHTML); }
   buffer.push("</button>");
 };
 
-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("<nav class='post-controls'>");
     this.renderReplies(post, buffer);
+    this.renderLikes(post, buffer);
     this.renderButtons(post, buffer);
     this.renderAdminPopup(post, buffer);
     buffer.push("</nav>");
   },
 
   // 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("<button class='show-replies' data-action='replies'>");
-    buffer.push("<span class='badge-posts'>" + reply_count + "</span>");
-    buffer.push(I18n.t("post.has_replies", { count: reply_count }));
+    buffer.push("<span class='badge-posts'>" + replyCount + "</span>");
+    buffer.push(I18n.t("post.has_replies", { count: replyCount }));
 
-    var icon = (this.get('post.replies.length') > 0) ? 'fa-chevron-up' : 'fa-chevron-down';
-    return buffer.push("<i class='fa " + icon + "'></i></button>");
+    const icon = (this.get('post.replies.length') > 0) ? 'chevron-up' : 'chevron-down';
+    return buffer.push(iconHTML(icon) + "</button>");
   },
 
-  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("<button class='show-likes' data-action='likes'>");
+    buffer.push("<span class='badge-posts'>" + likeCount + "</span>");
+    buffer.push(I18n.t("post.has_likes", { count: likeCount }));
+
+    const icon = (this.get('likeAction.users.length') > 0) ? 'chevron-up' : 'chevron-down';
+    return buffer.push(iconHTML(icon) + "</button>");
+  },
+
+
+  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('<div class="actions">');
-    visibleButtons.forEach(function (b) {
-      b.render(buffer);
-    });
+    visibleButtons.forEach((b) => b.render(buffer));
     buffer.push("</div>");
   },
 
-  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: "<div class='" + iconClass + "'>"});
   },
 
-  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 = '<i class="fa fa-pencil-square-o"></i>',
-        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 = '<i class="fa fa-shield"></i>',
-        postTypeText = isModerator ? I18n.t('post.controls.revert_to_regular') : I18n.t('post.controls.convert_to_moderator');
-
-    var rebakePostIcon = '<i class="fa fa-cog"></i>',
-        rebakePostText = I18n.t('post.controls.rebake');
-
-    var unhidePostIcon = '<i class="fa fa-eye"></i>',
-        unhidePostText = I18n.t('post.controls.unhide');
-
-    var html = '<div class="post-admin-menu">' +
+    const html = '<div class="post-admin-menu">' +
                  '<h3>' + I18n.t('admin_title') + '</h3>' +
                  '<ul>' +
                    '<li class="btn btn-admin" data-action="toggleWiki">' + wikiIcon + wikiText + '</li>' +
@@ -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 000000000..774d2c6c6
--- /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 += "<a href=\"" + Discourse.getURL("/users/") + u.get('username_lower') + "\" data-user-card=\"" + u.get('username_lower') + "\">";
+        iconsHtml += Discourse.Utilities.avatarImg({
+          size: 'small',
+          avatarTemplate: u.get('avatarTemplate'),
+          title: u.get('username')
+        });
+        iconsHtml += "</a>";
+      });
+      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 7ff592a4c..a5eba0df8 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 4708d2e95..1942934e1 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 21e311821..a3aceef95 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 4d011da75..507ef41dc 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 000000000..94a82ff5c
--- /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 d7ee374c8..c6840cad5 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 b941ae25b..000000000
--- 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 1831da989..b7bc4a497 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 60750cc29..2bb577fc9 100644
--- a/app/assets/javascripts/discourse/templates/post.hbs
+++ b/app/assets/javascripts/discourse/templates/post.hbs
@@ -90,7 +90,20 @@
             <button {{action "expandFirstPost" this}} class='btn expand-post'>{{i18n 'post.show_full'}}&hellip;</button>
           {{/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"}}
       </div>
 
       {{#if replies}}
@@ -101,7 +114,8 @@
         </section>
       {{/if}}
 
-      {{discourse-action-history post=this}}
+      {{actions-summary post=this}}
+      {{who-liked post=this}}
       {{view 'topic-map-container' post=this topic=controller.model}}
     </div>
 
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index 67de20ffe..ddaf3d90c 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 e0b4f0762..f0d8a173e 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 692756022..233513bfd 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 3fa744b2f..9da264cfe 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 e637b4a17..25fe84f2a 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 686f3d201..beb5dd9af 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 e29c25cd2..f93e38798 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) {