diff --git a/app/assets/javascripts/discourse/app/widgets/actions-summary.js b/app/assets/javascripts/discourse/app/widgets/actions-summary.js index 9e15f6d5233..7ca85f01f9e 100644 --- a/app/assets/javascripts/discourse/app/widgets/actions-summary.js +++ b/app/assets/javascripts/discourse/app/widgets/actions-summary.js @@ -6,17 +6,18 @@ import { h } from "virtual-dom"; import { userPath } from "discourse/lib/url"; import hbs from "discourse/widgets/hbs-compiler"; -export function avatarAtts(user) { +export function smallUserAtts(user) { return { template: user.avatar_template, username: user.username, post_url: user.post_url, - url: userPath(user.username_lower) + url: userPath(user.username_lower), + unknown: user.unknown }; } createWidget("small-user-list", { - tagName: "div.clearfix", + tagName: "div.clearfix.small-user-list", buildClasses(atts) { return atts.listClassName; @@ -30,7 +31,7 @@ createWidget("small-user-list", { atts.addSelf && !users.some(u => u.username === currentUser.username) ) { - users = users.concat(avatarAtts(currentUser)); + users = users.concat(smallUserAtts(currentUser)); } let description = null; @@ -43,7 +44,13 @@ createWidget("small-user-list", { let postUrl; const icons = users.map(u => { postUrl = postUrl || u.post_url; - return avatarFor.call(this, "small", u); + if (u.unknown) { + return h("div.unknown", { + attributes: { title: I18n.t("post.unknown_user") } + }); + } else { + return avatarFor.call(this, "small", u); + } }); if (postUrl) { diff --git a/app/assets/javascripts/discourse/app/widgets/emoji.js b/app/assets/javascripts/discourse/app/widgets/emoji.js index 2fe63226c00..a3f302c08e1 100644 --- a/app/assets/javascripts/discourse/app/widgets/emoji.js +++ b/app/assets/javascripts/discourse/app/widgets/emoji.js @@ -12,8 +12,13 @@ export default createWidget("emoji", { tagName: "img.emoji", buildAttributes(attrs) { - let result = { src: emojiUrlFor(attrs.name), alt: `:${attrs.name}:` }; - if (attrs.title) result.title = attrs.name; + let result = { + src: emojiUrlFor(attrs.name), + alt: `:${attrs.alt || attrs.name}:` + }; + if (attrs.title) { + result.title = typeof attrs.title === "string" ? attrs.title : attrs.name; + } return result; } }); diff --git a/app/assets/javascripts/discourse/app/widgets/post-menu.js b/app/assets/javascripts/discourse/app/widgets/post-menu.js index fa18b45aeec..d014a67e60f 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-menu.js +++ b/app/assets/javascripts/discourse/app/widgets/post-menu.js @@ -1,6 +1,6 @@ import { next, run } from "@ember/runloop"; import { applyDecorators, createWidget } from "discourse/widgets/widget"; -import { avatarAtts } from "discourse/widgets/actions-summary"; +import { smallUserAtts } from "discourse/widgets/actions-summary"; import { h } from "virtual-dom"; import showModal from "discourse/lib/show-modal"; import { Promise } from "rsvp"; @@ -696,7 +696,7 @@ export default createWidget("post-menu", { post_action_type_id: LIKE_ACTION }) .then(users => { - state.likedUsers = users.map(avatarAtts); + state.likedUsers = users.map(smallUserAtts); state.total = users.totalRows; }); }, @@ -705,7 +705,7 @@ export default createWidget("post-menu", { const { attrs, state } = this; return this.store.find("post-reader", { id: attrs.id }).then(users => { - state.readers = users.map(avatarAtts); + state.readers = users.map(smallUserAtts); state.totalReaders = users.totalRows; }); }, diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 5f56f612267..3b2424c2ac8 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -264,6 +264,16 @@ blockquote { } } +.small-user-list .unknown { + display: inline-block; + background-color: $primary-low; + width: 25px; + height: 25px; + border-radius: 50%; + vertical-align: middle; + margin-right: 0.25em; +} + .post-hidden { .topic-avatar, .cooked, diff --git a/app/controllers/post_action_users_controller.rb b/app/controllers/post_action_users_controller.rb index fe42fd3b9ca..0c59b763ccc 100644 --- a/app/controllers/post_action_users_controller.rb +++ b/app/controllers/post_action_users_controller.rb @@ -15,6 +15,16 @@ class PostActionUsersController < ApplicationController post = finder.first guardian.ensure_can_see!(post) + unknown_user_ids = Set.new + if current_user.present? + result = DB.query_single(<<~SQL, user_id: current_user.id) + SELECT mu.muted_user_id AS id FROM muted_users AS mu WHERE mu.user_id = :user_id + UNION + SELECT iu.ignored_user_id AS id FROM ignored_users AS iu WHERE iu.user_id = :user_id + SQL + unknown_user_ids.merge(result) + end + post_actions = post.post_actions.where(post_action_type_id: post_action_type_id) .includes(:user) .offset(page * page_size) @@ -29,7 +39,13 @@ class PostActionUsersController < ApplicationController action_type = PostActionType.types.key(post_action_type_id) total_count = post["#{action_type}_count"].to_i - data = { post_action_users: serialize_data(post_actions.to_a, PostActionUserSerializer) } + data = { + post_action_users: serialize_data( + post_actions.to_a, + PostActionUserSerializer, + unknown_user_ids: unknown_user_ids + ) + } if total_count > page_size data[:total_rows_post_action_users] = total_count diff --git a/app/serializers/post_action_user_serializer.rb b/app/serializers/post_action_user_serializer.rb index c1413c40459..40d81451ac5 100644 --- a/app/serializers/post_action_user_serializer.rb +++ b/app/serializers/post_action_user_serializer.rb @@ -2,7 +2,8 @@ class PostActionUserSerializer < BasicUserSerializer attributes :post_url, - :username_lower + :username_lower, + :unknown def id object.user.id @@ -24,4 +25,12 @@ class PostActionUserSerializer < BasicUserSerializer object.related_post.url if object.related_post_id && object.related_post end + def unknown + true + end + + def include_unknown? + (@options[:unknown_user_ids] || []).include?(object.user.id) + end + end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index aacd7c256bc..301e9d522b9 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2603,6 +2603,7 @@ en: one: "%{count} Reply" other: "%{count} Replies" + unknown_user: "(unknown/deleted user)" has_likes_title: one: "%{count} person liked this post" other: "%{count} people liked this post" diff --git a/spec/requests/post_action_users_controller_spec.rb b/spec/requests/post_action_users_controller_spec.rb index bc053524fe9..ec6bc28592b 100644 --- a/spec/requests/post_action_users_controller_spec.rb +++ b/spec/requests/post_action_users_controller_spec.rb @@ -59,6 +59,22 @@ describe PostActionUsersController do expect(response.status).to eq(200) end + it 'will return an unknown attribute for muted users' do + ignored_user = Fabricate(:user) + PostActionCreator.like(ignored_user, post) + regular_user = Fabricate(:user) + PostActionCreator.like(regular_user, post) + IgnoredUser.create(user: user, ignored_user: ignored_user) + + get "/post_action_users.json", params: { + id: post.id, post_action_type_id: PostActionType.types[:like] + } + expect(response.status).to eq(200) + json_users = response.parsed_body['post_action_users'] + expect(json_users.find { |u| u['id'] == regular_user.id }['unknown']).to be_blank + expect(json_users.find { |u| u['id'] == ignored_user.id }['unknown']).to eq(true) + end + it "paginates post actions" do user_ids = [] 5.times do diff --git a/test/javascripts/widgets/small-user-list-test.js b/test/javascripts/widgets/small-user-list-test.js new file mode 100644 index 00000000000..c4d8ee4aa5c --- /dev/null +++ b/test/javascripts/widgets/small-user-list-test.js @@ -0,0 +1,20 @@ +import { moduleForWidget, widgetTest } from "helpers/widget-test"; + +moduleForWidget("small-user-list"); + +widgetTest("renders avatars and support for unknown", { + template: '{{mount-widget widget="small-user-list" args=args}}', + beforeEach() { + this.set("args", { + users: [ + { id: 456, username: "eviltrout" }, + { id: 457, username: "someone", unknown: true } + ] + }); + }, + async test(assert) { + assert.ok(find("[data-user-card=eviltrout]").length === 1); + assert.ok(find("[data-user-card=someone]").length === 0); + assert.ok(find(".unknown").length, "includes unkown user"); + } +});