Merge pull request #4550 from cpradio/cannot-see-mention
FEATURE: Notify user when mention can't see the reply they were mentioned in
This commit is contained in:
commit
32a8d5ed1f
|
@ -137,9 +137,10 @@ export default Ember.Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderUnseenMentions($preview, unseen) {
|
_renderUnseenMentions($preview, unseen) {
|
||||||
fetchUnseenMentions(unseen).then(() => {
|
fetchUnseenMentions(unseen, this.get('topic.id')).then(() => {
|
||||||
linkSeenMentions($preview, this.siteSettings);
|
linkSeenMentions($preview, this.siteSettings);
|
||||||
this._warnMentionedGroups($preview);
|
this._warnMentionedGroups($preview);
|
||||||
|
this._warnCannotSeeMention($preview);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -170,19 +171,33 @@ export default Ember.Component.extend({
|
||||||
|
|
||||||
_warnMentionedGroups($preview) {
|
_warnMentionedGroups($preview) {
|
||||||
Ember.run.scheduleOnce('afterRender', () => {
|
Ember.run.scheduleOnce('afterRender', () => {
|
||||||
this._warnedMentions = this._warnedMentions || [];
|
var found = this.get('warnedGroupMentions') || [];
|
||||||
var found = [];
|
|
||||||
$preview.find('.mention-group.notify').each((idx,e) => {
|
$preview.find('.mention-group.notify').each((idx,e) => {
|
||||||
const $e = $(e);
|
const $e = $(e);
|
||||||
var name = $e.data('name');
|
var name = $e.data('name');
|
||||||
found.push(name);
|
if (found.indexOf(name) === -1){
|
||||||
if (this._warnedMentions.indexOf(name) === -1){
|
|
||||||
this._warnedMentions.push(name);
|
|
||||||
this.sendAction('groupsMentioned', [{name: name, user_count: $e.data('mentionable-user-count')}]);
|
this.sendAction('groupsMentioned', [{name: name, user_count: $e.data('mentionable-user-count')}]);
|
||||||
|
found.push(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._warnedMentions = found;
|
this.set('warnedGroupMentions', found);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_warnCannotSeeMention($preview) {
|
||||||
|
Ember.run.scheduleOnce('afterRender', () => {
|
||||||
|
var found = this.get('warnedCannotSeeMentions') || [];
|
||||||
|
$preview.find('.mention.cannot-see').each((idx,e) => {
|
||||||
|
const $e = $(e);
|
||||||
|
var name = $e.data('name');
|
||||||
|
if (found.indexOf(name) === -1) {
|
||||||
|
this.sendAction('cannotSeeMention', [{name: name}]);
|
||||||
|
found.push(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('warnedCannotSeeMentions', found);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -490,6 +505,7 @@ export default Ember.Component.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this._warnMentionedGroups($preview);
|
this._warnMentionedGroups($preview);
|
||||||
|
this._warnCannotSeeMention($preview);
|
||||||
|
|
||||||
// Paint category hashtags
|
// Paint category hashtags
|
||||||
const unseenCategoryHashtags = linkSeenCategoryHashtags($preview);
|
const unseenCategoryHashtags = linkSeenCategoryHashtags($preview);
|
||||||
|
|
|
@ -344,6 +344,22 @@ export default Ember.Controller.extend({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cannotSeeMention(mentions) {
|
||||||
|
mentions.forEach(mention => {
|
||||||
|
const translation = (this.get('topic.isPrivateMessage')) ?
|
||||||
|
'composer.cannot_see_mention.private' :
|
||||||
|
'composer.cannot_see_mention.category';
|
||||||
|
const body = I18n.t(translation, {
|
||||||
|
username: "@" + mention.name
|
||||||
|
});
|
||||||
|
this.appEvents.trigger('composer-messages:create', {
|
||||||
|
extraClass: 'custom-body',
|
||||||
|
templateName: 'custom-body',
|
||||||
|
body
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import { ajax } from 'discourse/lib/ajax';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
|
||||||
function replaceSpan($e, username, opts) {
|
function replaceSpan($e, username, opts) {
|
||||||
|
let extra = "";
|
||||||
|
let extraClass = "";
|
||||||
|
|
||||||
if (opts && opts.group) {
|
if (opts && opts.group) {
|
||||||
let extra = "";
|
|
||||||
let extraClass = "";
|
|
||||||
if (opts.mentionable) {
|
if (opts.mentionable) {
|
||||||
extra = `data-name='${username}' data-mentionable-user-count='${opts.mentionable.user_count}'`;
|
extra = `data-name='${username}' data-mentionable-user-count='${opts.mentionable.user_count}'`;
|
||||||
extraClass = "notify";
|
extraClass = "notify";
|
||||||
}
|
}
|
||||||
$e.replaceWith(`<a href='${Discourse.getURL("/groups/") + username}' class='mention-group ${extraClass}' ${extra}>@${username}</a>`);
|
$e.replaceWith(`<a href='${Discourse.getURL("/groups/") + username}' class='mention-group ${extraClass}' ${extra}>@${username}</a>`);
|
||||||
} else {
|
} else {
|
||||||
$e.replaceWith(`<a href='${Discourse.getURL("/users/") + username.toLowerCase()}' class='mention'>@${username}</a>`);
|
if (opts && opts.cannot_see) {
|
||||||
|
extra = `data-name='${username}'`;
|
||||||
|
extraClass = "cannot-see";
|
||||||
|
}
|
||||||
|
$e.replaceWith(`<a href='${Discourse.getURL("/users/") + username.toLowerCase()}' class='mention ${extraClass}' ${extra}>@${username}</a>`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +23,7 @@ const found = {};
|
||||||
const foundGroups = {};
|
const foundGroups = {};
|
||||||
const mentionableGroups = {};
|
const mentionableGroups = {};
|
||||||
const checked = {};
|
const checked = {};
|
||||||
|
const cannotSee = [];
|
||||||
|
|
||||||
function updateFound($mentions, usernames) {
|
function updateFound($mentions, usernames) {
|
||||||
Ember.run.scheduleOnce('afterRender', function() {
|
Ember.run.scheduleOnce('afterRender', function() {
|
||||||
|
@ -25,7 +31,7 @@ function updateFound($mentions, usernames) {
|
||||||
const $e = $(e);
|
const $e = $(e);
|
||||||
const username = usernames[i];
|
const username = usernames[i];
|
||||||
if (found[username.toLowerCase()]) {
|
if (found[username.toLowerCase()]) {
|
||||||
replaceSpan($e, username);
|
replaceSpan($e, username, { cannot_see: cannotSee[username] });
|
||||||
} else if (foundGroups[username]) {
|
} else if (foundGroups[username]) {
|
||||||
replaceSpan($e, username, { group: true, mentionable: mentionableGroups[username] });
|
replaceSpan($e, username, { group: true, mentionable: mentionableGroups[username] });
|
||||||
} else if (checked[username]) {
|
} else if (checked[username]) {
|
||||||
|
@ -45,11 +51,12 @@ export function linkSeenMentions($elem, siteSettings) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUnseenMentions(usernames) {
|
export function fetchUnseenMentions(usernames, topic_id) {
|
||||||
return ajax("/users/is_local_username", { data: { usernames } }).then(r => {
|
return ajax("/users/is_local_username", { data: { usernames, topic_id } }).then(r => {
|
||||||
r.valid.forEach(v => found[v] = true);
|
r.valid.forEach(v => found[v] = true);
|
||||||
r.valid_groups.forEach(vg => foundGroups[vg] = true);
|
r.valid_groups.forEach(vg => foundGroups[vg] = true);
|
||||||
r.mentionable_groups.forEach(mg => mentionableGroups[mg] = true);
|
r.mentionable_groups.forEach(mg => mentionableGroups[mg.name] = mg);
|
||||||
|
r.cannot_see.forEach(cs => cannotSee[cs] = true);
|
||||||
usernames.forEach(u => checked[u] = true);
|
usernames.forEach(u => checked[u] = true);
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
draftStatus=model.draftStatus
|
draftStatus=model.draftStatus
|
||||||
isUploading=isUploading
|
isUploading=isUploading
|
||||||
groupsMentioned="groupsMentioned"
|
groupsMentioned="groupsMentioned"
|
||||||
|
cannotSeeMention="cannotSeeMention"
|
||||||
importQuote="importQuote"
|
importQuote="importQuote"
|
||||||
showOptions="showOptions"
|
showOptions="showOptions"
|
||||||
hideOptions="hideOptions"
|
hideOptions="hideOptions"
|
||||||
|
|
|
@ -242,11 +242,18 @@ class UsersController < ApplicationController
|
||||||
usernames -= groups
|
usernames -= groups
|
||||||
usernames.each(&:downcase!)
|
usernames.each(&:downcase!)
|
||||||
|
|
||||||
|
cannot_see = []
|
||||||
|
topic_id = params[:topic_id]
|
||||||
|
unless topic_id.blank?
|
||||||
|
topic = Topic.find_by(id: topic_id)
|
||||||
|
usernames.each{ |username| cannot_see.push(username) unless Guardian.new(User.find_by_username(username)).can_see?(topic) }
|
||||||
|
end
|
||||||
|
|
||||||
result = User.where(staged: false)
|
result = User.where(staged: false)
|
||||||
.where(username_lower: usernames)
|
.where(username_lower: usernames)
|
||||||
.pluck(:username_lower)
|
.pluck(:username_lower)
|
||||||
|
|
||||||
render json: {valid: result, valid_groups: groups, mentionable_groups: mentionable_groups}
|
render json: {valid: result, valid_groups: groups, mentionable_groups: mentionable_groups, cannot_see: cannot_see}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_available_true
|
def render_available_true
|
||||||
|
|
|
@ -1045,6 +1045,9 @@ en:
|
||||||
group_mentioned:
|
group_mentioned:
|
||||||
one: "By mentioning {{group}}, you are about to notify <a href='{{group_link}}'>1 person</a> – are you sure?"
|
one: "By mentioning {{group}}, you are about to notify <a href='{{group_link}}'>1 person</a> – are you sure?"
|
||||||
other: "By mentioning {{group}}, you are about to notify <a href='{{group_link}}'>{{count}} people</a> – are you sure?"
|
other: "By mentioning {{group}}, you are about to notify <a href='{{group_link}}'>{{count}} people</a> – are you sure?"
|
||||||
|
cannot_see_mention:
|
||||||
|
category: "You mentioned {{username}} but they won't be notified because they do not have access to this category. You will need to add them to a group that has access to this category."
|
||||||
|
private: "You mentioned {{username}} but they won't be notified because they are unable to see this personal message. You will need to invite them to this PM."
|
||||||
duplicate_link: "It looks like your link to <b>{{domain}}</b> was already posted in the topic by <b>@{{username}}</b> in <a href='{{post_url}}'>a reply {{ago}}</a> – are you sure you want to post it again?"
|
duplicate_link: "It looks like your link to <b>{{domain}}</b> was already posted in the topic by <b>@{{username}}</b> in <a href='{{post_url}}'>a reply {{ago}}</a> – are you sure you want to post it again?"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
|
|
@ -1602,6 +1602,9 @@ describe UsersController do
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:group) { Fabricate(:group, name: "Discourse") }
|
let(:group) { Fabricate(:group, name: "Discourse") }
|
||||||
|
let(:topic) { Fabricate(:topic) }
|
||||||
|
let(:allowed_user) { Fabricate(:user) }
|
||||||
|
let(:private_topic) { Fabricate(:private_message_topic, user: allowed_user) }
|
||||||
|
|
||||||
it "finds the user" do
|
it "finds the user" do
|
||||||
xhr :get, :is_local_username, username: user.username
|
xhr :get, :is_local_username, username: user.username
|
||||||
|
@ -1632,6 +1635,38 @@ describe UsersController do
|
||||||
expect(json["valid"].size).to eq(0)
|
expect(json["valid"].size).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns user who cannot see topic" do
|
||||||
|
Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
|
||||||
|
xhr :get, :is_local_username, usernames: [user.username], topic_id: topic.id
|
||||||
|
expect(response).to be_success
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["cannot_see"].size).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "never returns a user who can see the topic" do
|
||||||
|
Guardian.any_instance.expects(:can_see?).with(topic).returns(true)
|
||||||
|
xhr :get, :is_local_username, usernames: [user.username], topic_id: topic.id
|
||||||
|
expect(response).to be_success
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["cannot_see"].size).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns user who cannot see a private topic" do
|
||||||
|
Guardian.any_instance.expects(:can_see?).with(private_topic).returns(false)
|
||||||
|
xhr :get, :is_local_username, usernames: [user.username], topic_id: private_topic.id
|
||||||
|
expect(response).to be_success
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["cannot_see"].size).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "never returns a user who can see the topic" do
|
||||||
|
Guardian.any_instance.expects(:can_see?).with(private_topic).returns(true)
|
||||||
|
xhr :get, :is_local_username, usernames: [allowed_user.username], topic_id: private_topic.id
|
||||||
|
expect(response).to be_success
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["cannot_see"].size).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.topic_tracking_state' do
|
describe '.topic_tracking_state' do
|
||||||
|
|
Loading…
Reference in New Issue