Add warning for @all / @everyone.

* The warning contains a count of the number of people in the stream.
* An error appears if the warning is ignored and the user tries to
  send the message anyway.
* The message cannot be sent until the warning is acknowledged or @all
  / @everyone is removed.
* This only applies to stream messages and not private messages.

Fixes #853.
This commit is contained in:
Caroline Liu
2016-06-03 19:31:35 -07:00
committed by Tim Abbott
parent 08045241a7
commit 60e5140406
7 changed files with 182 additions and 9 deletions

View File

@@ -0,0 +1,85 @@
var common = require('../casper_lib/common.js').common;
function enter_mention_in_composer(str, item) {
casper.then(function () {
casper.test.info('Start typing @all');
casper.evaluate(function (str, item) {
// Set the value and then send a bogus keyup event to trigger
// the typeahead.
$('#new_message_content')
.focus()
.val(str)
.trigger($.Event('keyup', { which: 0 }));
// Trigger the typeahead.
// Reaching into the guts of Bootstrap Typeahead like this is not
// great, but I found it very hard to do it any other way.
var tah = $('#new_message_content').data().typeahead;
tah.mouseenter({
currentTarget: $('.typeahead:visible li:contains("'+item+'")')[0]
});
tah.select();
}, {str: str, item: item});
});
}
/////////////////////////////////////////////////////
common.start_and_log_in();
casper.verbonse = true;
casper.waitForSelector('#new_message_content', function () {
casper.test.info('compose box visible');
casper.page.sendEvent('keypress', "c"); // brings up the compose box
});
casper.then(function () {
casper.fill('form[action^="/json/messages"]', {
stream: 'Verona',
subject: 'Test mention all'
});
});
enter_mention_in_composer('@all', 'all');
casper.waitForText("Are you sure you want to message all", function () {
casper.test.info('Warning message appears when mentioning @all');
casper.test.assertSelectorHasText('.compose-all-everyone', 'Are you sure you want to message all');
});
casper.then( function () {
casper.test.info('Click Send Button');
casper.click('#compose-send-button');
});
casper.waitForText("Please remove @all", function () {
casper.test.info('Error message appears when attempting to send a message without acknowledging the @all mention warning');
casper.test.assertSelectorHasText('#error-msg', "Please remove @all / @everyone or acknowledge that you will be spamming everyone!");
});
casper.waitForSelector('.compose-all-everyone-confirm', function () {
casper.click('.compose-all-everyone-confirm');
}, function () {
casper.test.error('Could not click confirm button.');
});
casper.waitWhileVisible('.compose-all-everyone-confirm', function () {
casper.test.info('Check that error messages are gone.');
casper.test.assertNotVisible('.compose-all-everyone-msg');
casper.test.assertNotVisible('#error-msg');
});
casper.then( function () {
casper.test.info('Click Send Button');
casper.click('#compose-send-button');
});
casper.then( function () {
common.expected_messages('zhome', ['Verona > Test mention all'],
["<p><span class=\"user-mention user-mention-me\" data-user-email=\"*\">@all</span> </p>"]
);
});
common.then_log_out();
casper.run(function () {
casper.test.done();
});

View File

@@ -190,6 +190,19 @@ function render(template_name, args) {
assert.equal(button.text(), "Subscribe"); assert.equal(button.text(), "Subscribe");
}()); }());
(function compose_all_everyone() {
var args = {
count: '101',
name: 'all'
};
var html = render('compose_all_everyone', args);
global.write_test_output("compose_all_everyone.handlebars", html);
var button = $(html).find("button:first");
assert.equal(button.text(), "YES");
var error_msg = $(html).find('span.compose-all-everyone-msg').text().trim();
assert.equal(error_msg, "Are you sure you want to message all 101 people in this stream?");
}());
(function compose_notification() { (function compose_notification() {
var args = { var args = {
"note": "You sent a message to a muted topic.", "note": "You sent a message to a muted topic.",

View File

@@ -2,6 +2,14 @@ var compose = (function () {
var exports = {}; var exports = {};
var is_composing_message = false; var is_composing_message = false;
// Track the state of the @all warning. The user must acknowledge that they are spamming the entire stream
// before the warning will go away. If they try to send before explicitly dismissing the warning, they will
// get an error message too.
// undefined: no @all/@everyone in message; false: user typed @all/@everyone; true: user clicked YES
var user_acknowledged_all_everyone;
var all_everyone_re = /(@\*{2}(all|everyone)\*{2})|(@(all|everyone))/;
var message_snapshot; var message_snapshot;
var empty_subject_placeholder = "(no topic)"; var empty_subject_placeholder = "(no topic)";
@@ -85,6 +93,28 @@ function show_box(tabname, focus_area, opts) {
} }
function show_all_everyone_warnings() {
var current_stream = stream_data.get_sub(compose.stream_name());
var stream_count = current_stream.subscribers.num_items();
var all_everyone_template = templates.render("compose_all_everyone", {count: stream_count});
var error_area_all_everyone = $("#compose-all-everyone");
// only show one error for any number of @all or @everyone mentions
if (!error_area_all_everyone.is(':visible')) {
error_area_all_everyone.append(all_everyone_template);
}
error_area_all_everyone.show();
user_acknowledged_all_everyone = false;
}
function clear_all_everyone_warnings() {
$("#compose-all-everyone").hide();
$("#compose-all-everyone").empty();
$("#send-status").hide();
}
function clear_invites() { function clear_invites() {
$("#compose_invite_users").hide(); $("#compose_invite_users").hide();
$("#compose_invite_users").empty(); $("#compose_invite_users").empty();
@@ -93,6 +123,8 @@ function clear_invites() {
function clear_box() { function clear_box() {
exports.snapshot_message(); exports.snapshot_message();
clear_invites(); clear_invites();
clear_all_everyone_warnings();
user_acknowledged_all_everyone = undefined;
$("#compose").find('input[type=text], textarea').val(''); $("#compose").find('input[type=text], textarea').val('');
autosize_textarea(); autosize_textarea();
$("#send-status").hide(0); $("#send-status").hide(0);
@@ -342,6 +374,11 @@ exports.restore_message = function () {
clear_message_snapshot(); clear_message_snapshot();
compose_fade.clear_compose(); compose_fade.clear_compose();
compose.start(snapshot_copy.type, snapshot_copy); compose.start(snapshot_copy.type, snapshot_copy);
if (snapshot_copy.content !== undefined &&
all_everyone_re.test(snapshot_copy.content)) {
show_all_everyone_warnings();
}
}; };
function compose_error(error_text, bad_input) { function compose_error(error_text, bad_input) {
@@ -696,6 +733,23 @@ function validate_stream_message() {
} }
} }
// check if @all or @everyone is in the message
if (all_everyone_re.test(exports.message_content())) {
if (user_acknowledged_all_everyone === undefined ||
user_acknowledged_all_everyone === false) {
// user has not seen a warning message yet if undefined
show_all_everyone_warnings();
// user has not acknowledge the warning message yet
compose_error("Please remove @all / @everyone or acknowledge that you will be spamming everyone!");
return false;
}
} else {
// the message no longer contains @all or @everyone
clear_all_everyone_warnings();
}
// at this point, the user has either acknowledged the warning or removed @all / @everyone
user_acknowledged_all_everyone = undefined;
var response; var response;
if (!stream_data.is_subscribed(stream_name)) { if (!stream_data.is_subscribed(stream_name)) {
@@ -789,6 +843,13 @@ $(function () {
if (data !== undefined && data.mentioned !== undefined) { if (data !== undefined && data.mentioned !== undefined) {
var email = data.mentioned.email; var email = data.mentioned.email;
// warn if @all or @everyone is mentioned
if (data.mentioned.full_name === 'all' || data.mentioned.full_name === 'everyone') {
show_all_everyone_warnings();
return; // don't check if @all or @everyone is subscribed to a stream
}
if (compose_fade.would_receive_message(email) === false) { if (compose_fade.would_receive_message(email) === false) {
var new_row = templates.render("compose-invite-users", {email: email, var new_row = templates.render("compose-invite-users", {email: email,
name: data.mentioned.full_name}); name: data.mentioned.full_name});
@@ -805,6 +866,16 @@ $(function () {
error_area.show(); error_area.show();
} }
} }
});
$("#compose-all-everyone").on('click', '.compose-all-everyone-confirm', function (event) {
event.preventDefault();
$(event.target).parents('.compose-all-everyone').remove();
user_acknowledged_all_everyone = true;
clear_all_everyone_warnings();
$('#new_message_content').focus().select();
}); });
$("#compose_invite_users").on('click', '.compose_invite_link', function (event) { $("#compose_invite_users").on('click', '.compose_invite_link', function (event) {

View File

@@ -272,13 +272,8 @@ exports.content_typeahead_selected = function (item) {
} else if (this.completing === 'mention') { } else if (this.completing === 'mention') {
beginning = (beginning.substring(0, beginning.length - this.token.length-1) beginning = (beginning.substring(0, beginning.length - this.token.length-1)
+ '@**' + item.full_name + '** '); + '@**' + item.full_name + '** ');
// We insert a special `all` item to the autocompleter above
// Don't consider it a user mention
if (item.email !== 'all' && item.email !== "everyone") {
$(document).trigger('usermention_completed.zulip', {mentioned: item}); $(document).trigger('usermention_completed.zulip', {mentioned: item});
} }
}
// Keep the cursor after the newly inserted text, as Bootstrap will call textbox.change() to overwrite the text // Keep the cursor after the newly inserted text, as Bootstrap will call textbox.change() to overwrite the text
// in the textbox. // in the textbox.

View File

@@ -1607,6 +1607,7 @@ blockquote p {
width: 10px; width: 10px;
} }
.compose-all-everyone-controls,
.compose_invite_user_controls { .compose_invite_user_controls {
float: right; float: right;
} }

View File

@@ -0,0 +1,8 @@
<div class="compose-all-everyone">
<span class="compose-all-everyone-msg">
{{#tr this}}Are you sure you want to message all <strong>__count__</strong> people in this stream?{{/tr}}
</span>
<span class="compose-all-everyone-controls">
<button type="button" class="compose-all-everyone-confirm">{{t "YES" }}</button>
</span>
</div>

View File

@@ -25,9 +25,9 @@
<span class="send-status-close">&times;</span> <span class="send-status-close">&times;</span>
<span id="error-msg"></span> <span id="error-msg"></span>
</div> </div>
<div id="compose_invite_users" class="alert" style="display: none"></div> <div id="compose_invite_users" class="alert home-error-bar"></div>
<div id="out-of-view-notification" class="notification-alert"> <div id="compose-all-everyone" class="alert home-error-bar"></div>
</div> <div id="out-of-view-notification" class="notification-alert"></div>
<div class="composition-area"> <div class="composition-area">
<button type="button" class="close" id='compose_close'>×</button> <button type="button" class="close" id='compose_close'>×</button>
<form id="send_message_form" action="/json/messages" method="post"> <form id="send_message_form" action="/json/messages" method="post">