compose: Migrate generic error to use shared banner template.

Error banners were rendered in a different place than warnings.
Now compose errors and warnings share the same template and styling
in compose_banner.hbs

Testing notes:
* I removed test_message_overflow since it seemed not to test
  anything that test_check_overflow_text wasn't already testing.
* private_message_recipient() can't be called to set emails to non-valid
  emails, so `invalid_recipient` cannot be tested (and is likely very
  difficult to trigger in production, if possible at all).
This commit is contained in:
evykassirer
2022-08-19 13:24:06 -07:00
committed by Tim Abbott
parent 85cbd324eb
commit f1c6fc13e7
11 changed files with 405 additions and 226 deletions

View File

@@ -4,7 +4,7 @@ const {strict: assert} = require("assert");
const MockDate = require("mockdate");
const {$t, $t_html} = require("../zjsunit/i18n");
const {$t} = require("../zjsunit/i18n");
const {mock_esm, set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
@@ -135,7 +135,7 @@ test_ui("send_message_success", ({override_rewire}) => {
assert.ok(reify_message_id_checked);
});
test_ui("send_message", ({override, override_rewire}) => {
test_ui("send_message", ({override, override_rewire, mock_template}) => {
mock_banners();
MockDate.set(new Date(fake_now * 1000));
@@ -258,6 +258,12 @@ test_ui("send_message", ({override, override_rewire}) => {
})();
(function test_error_codepath_local_id_undefined() {
let banner_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, "generic_compose_error");
assert.equal(data.banner_text, "Error sending message: Server says 408");
banner_rendered = true;
});
stub_state = initialize_state_stub_dict();
$("#compose-textarea").val("foobarfoobar");
$("#compose-textarea").trigger("blur");
@@ -278,7 +284,7 @@ test_ui("send_message", ({override, override_rewire}) => {
};
assert.deepEqual(stub_state, state);
assert.ok(!echo_error_msg_checked);
assert.equal($("#compose-error-msg").html(), "Error sending message: Server says 408");
assert.ok(banner_rendered);
assert.equal($("#compose-textarea").val(), "foobarfoobar");
assert.ok($("#compose-textarea").is_focused());
assert.ok($("#compose-send-status").visible());
@@ -335,7 +341,7 @@ test_ui("enter_with_preview_open", ({override, override_rewire}) => {
assert.equal($("#compose-error-msg").html(), "never-been-set");
});
test_ui("finish", ({override, override_rewire}) => {
test_ui("finish", ({override, override_rewire, mock_template}) => {
mock_banners();
override(notifications, "clear_compose_notifications", () => {});
override(reminder, "is_deferred_delivery", () => false);
@@ -347,6 +353,10 @@ test_ui("finish", ({override, override_rewire}) => {
});
(function test_when_compose_validation_fails() {
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, "empty_message");
assert.equal(data.banner_text, $t({defaultMessage: "You have nothing to send!"}));
});
$("#compose_invite_users").show();
$("#compose-send-button").prop("disabled", false);
$("#compose-send-button").trigger("focus");
@@ -357,10 +367,6 @@ test_ui("finish", ({override, override_rewire}) => {
assert.equal(res, false);
assert.ok(!$("#compose_invite_users").visible());
assert.ok(!$("#compose-send-button .loader").visible());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You have nothing to send!"}),
);
assert.ok(show_button_spinner_called);
})();

View File

@@ -2,7 +2,7 @@
const {strict: assert} = require("assert");
const {$t, $t_html} = require("../zjsunit/i18n");
const {$t} = require("../zjsunit/i18n");
const {mock_esm, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
@@ -13,7 +13,6 @@ const {mock_banners} = require("./lib/compose_banner");
const channel = mock_esm("../../static/js/channel");
const compose_actions = mock_esm("../../static/js/compose_actions");
const ui_util = mock_esm("../../static/js/ui_util");
const compose_error = zrequire("compose_error");
const compose_pm_pill = zrequire("compose_pm_pill");
@@ -70,6 +69,7 @@ function test_ui(label, f) {
}
test_ui("validate_stream_message_address_info", ({mock_template}) => {
mock_banners();
const sub = {
stream_id: 101,
name: "social",
@@ -103,25 +103,31 @@ test_ui("validate_stream_message_address_info", ({mock_template}) => {
assert.ok(!compose_validate.validate_stream_message_address_info("Frontend"));
assert.equal($("#compose-error-msg").html(), "compose_not_subscribed_stub");
let stream_does_not_exist_rendered = false;
mock_template("compose_banner/stream_does_not_exist_error.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.stream_does_not_exist);
assert.equal(data.stream_name, "Frontend");
stream_does_not_exist_rendered = true;
});
channel.post = (payload) => {
assert.equal(payload.data.stream, "Frontend");
payload.error({status: 404});
};
assert.ok(!compose_validate.validate_stream_message_address_info("Frontend"));
assert.equal(
$("#compose-error-msg").html(),
"translated HTML: <p>The stream <b>Frontend</b> does not exist.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>",
);
assert.ok(stream_does_not_exist_rendered);
let subscription_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, "subscription_error");
assert.equal(data.banner_text, $t({defaultMessage: "Error checking subscription."}));
subscription_error_rendered = true;
});
channel.post = (payload) => {
assert.equal(payload.data.stream, "social");
payload.error({status: 500});
};
assert.ok(!compose_validate.validate_stream_message_address_info("social"));
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Error checking subscription"}),
);
assert.ok(subscription_error_rendered);
});
test_ui("validate", ({override, mock_template}) => {
@@ -142,26 +148,87 @@ test_ui("validate", ({override, mock_template}) => {
compose_pm_pill.initialize();
ui_util.place_caret_at_end = () => {};
$("#zephyr-mirror-error").is = () => {};
mock_template("input_pill.hbs", false, () => "<div>pill-html</div>");
mock_banners();
}
function add_content_to_compose_box() {
$("#compose-textarea").val("foobarfoobar");
}
// test validating private messages
compose_state.set_message_type("private");
initialize_pm_pill();
add_content_to_compose_box();
compose_state.private_message_recipient("");
let pm_recipient_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.missing_private_message_recipient);
assert.equal(
data.banner_text,
$t({defaultMessage: "Please specify at least one valid recipient."}),
);
pm_recipient_error_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(pm_recipient_error_rendered);
pm_recipient_error_rendered = false;
people.add_active_user(bob);
compose_state.private_message_recipient("bob@example.com");
assert.ok(compose_validate.validate());
assert.ok(!pm_recipient_error_rendered);
people.deactivate(bob);
let deactivated_user_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.deactivated_user);
assert.equal(
data.banner_text,
$t({defaultMessage: "You cannot send messages to deactivated users."}),
);
deactivated_user_error_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(deactivated_user_error_rendered);
page_params.realm_is_zephyr_mirror_realm = true;
assert.ok(compose_validate.validate());
page_params.realm_is_zephyr_mirror_realm = false;
initialize_pm_pill();
add_content_to_compose_box();
compose_state.private_message_recipient("welcome-bot@example.com");
assert.ok(compose_validate.validate());
let zephyr_error_rendered = false;
let empty_message_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
if (data.classname === compose_error.CLASSNAMES.zephyr_not_running) {
assert.equal(
data.banner_text,
$t({
defaultMessage:
"You need to be running Zephyr mirroring in order to send messages!",
}),
);
zephyr_error_rendered = true;
} else if (data.classname === compose_error.CLASSNAMES.empty_message) {
assert.equal(data.banner_text, $t({defaultMessage: "You have nothing to send!"}));
empty_message_error_rendered = true;
}
});
initialize_pm_pill();
compose_state.private_message_recipient("welcome-bot@example.com");
assert.ok(!compose_validate.validate());
assert.ok(!$("#compose-send-button .loader").visible());
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You have nothing to send!"}),
);
assert.ok(empty_message_error_rendered);
compose_validate.validate();
add_content_to_compose_box();
@@ -173,88 +240,42 @@ test_ui("validate", ({override, mock_template}) => {
};
assert.ok(!compose_validate.validate());
assert.ok(zephyr_checked);
assert.equal(
$("#compose-error-msg").html(),
$t_html({
defaultMessage: "You need to be running Zephyr mirroring in order to send messages!",
}),
);
assert.ok(zephyr_error_rendered);
initialize_pm_pill();
add_content_to_compose_box();
// test validating private messages
compose_state.set_message_type("private");
compose_state.private_message_recipient("");
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Please specify at least one valid recipient"}),
);
initialize_pm_pill();
add_content_to_compose_box();
compose_state.private_message_recipient("foo@zulip.com");
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Please specify at least one valid recipient"}),
);
compose_state.private_message_recipient("foo@zulip.com,alice@zulip.com");
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Please specify at least one valid recipient"}),
);
people.add_active_user(bob);
compose_state.private_message_recipient("bob@example.com");
assert.ok(compose_validate.validate());
people.deactivate(bob);
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You cannot send messages to deactivated users."}),
);
page_params.realm_is_zephyr_mirror_realm = true;
assert.ok(compose_validate.validate());
page_params.realm_is_zephyr_mirror_realm = false;
initialize_pm_pill();
add_content_to_compose_box();
compose_state.private_message_recipient("welcome-bot@example.com");
assert.ok(compose_validate.validate());
// test validating stream messages
compose_state.set_message_type("stream");
compose_state.stream_name("");
let empty_stream_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.missing_stream);
assert.equal(data.banner_text, $t({defaultMessage: "Please specify a stream."}));
empty_stream_error_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Please specify a stream"}),
);
assert.ok(empty_stream_error_rendered);
compose_state.stream_name("Denmark");
page_params.realm_mandatory_topics = true;
compose_state.topic("");
assert.ok(!compose_validate.validate());
let missing_topic_error_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.topic_missing);
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Topics are required in this organization"}),
data.banner_text,
$t({defaultMessage: "Topics are required in this organization."}),
);
missing_topic_error_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(missing_topic_error_rendered);
missing_topic_error_rendered = false;
compose_state.topic("(no topic)");
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "Topics are required in this organization"}),
);
assert.ok(missing_topic_error_rendered);
});
test_ui("get_invalid_recipient_emails", ({override_rewire}) => {
@@ -334,6 +355,7 @@ test_ui("validate_stream_message", ({override_rewire, mock_template}) => {
// primarily used to get coverage over functions called from validate()
// we are separating it up in different test. Though their relative position
// of execution should not be changed.
mock_banners();
page_params.user_id = me.user_id;
page_params.realm_mandatory_topics = false;
const sub = {
@@ -368,21 +390,30 @@ test_ui("validate_stream_message", ({override_rewire, mock_template}) => {
assert.equal(compose_content, "compose_all_everyone_stub");
assert.ok($("#compose-all-everyone").visible());
override_rewire(compose_validate, "wildcard_mention_allowed", () => false);
assert.ok(!compose_validate.validate());
let wildcards_not_allowed_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.wildcards_not_allowed);
assert.equal(
$("#compose-error-msg").html(),
$t_html({
defaultMessage: "You do not have permission to use wildcard mentions in this stream.",
data.banner_text,
$t({
defaultMessage:
"You do not have permission to use wildcard mentions in this stream.",
}),
);
wildcards_not_allowed_rendered = true;
return "wildcard_warning_stub";
});
override_rewire(compose_validate, "wildcard_mention_allowed", () => false);
assert.ok(!compose_validate.validate());
assert.ok(wildcards_not_allowed_rendered);
});
test_ui("test_validate_stream_message_post_policy_admin_only", () => {
test_ui("test_validate_stream_message_post_policy_admin_only", ({mock_template}) => {
// This test is in continuation with test_validate but it has been separated out
// for better readability. Their relative position of execution should not be changed.
// Although the position with respect to test_validate_stream_message does not matter
// as different stream is used for this test.
mock_banners();
page_params.is_admin = false;
const sub = {
stream_id: 102,
@@ -394,11 +425,20 @@ test_ui("test_validate_stream_message_post_policy_admin_only", () => {
compose_state.topic("topic102");
compose_state.stream_name("stream102");
stream_data.add_sub(sub);
assert.ok(!compose_validate.validate());
let banner_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.no_post_permissions);
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You do not have permission to post in this stream."}),
data.banner_text,
$t({
defaultMessage: "You do not have permission to post in this stream.",
}),
);
banner_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(banner_rendered);
// Reset error message.
compose_state.stream_name("social");
@@ -408,14 +448,13 @@ test_ui("test_validate_stream_message_post_policy_admin_only", () => {
compose_state.topic("topic102");
compose_state.stream_name("stream102");
banner_rendered = false;
assert.ok(!compose_validate.validate());
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You do not have permission to post in this stream."}),
);
assert.ok(banner_rendered);
});
test_ui("test_validate_stream_message_post_policy_moderators_only", () => {
test_ui("test_validate_stream_message_post_policy_moderators_only", ({mock_template}) => {
mock_banners();
page_params.is_admin = false;
page_params.is_moderator = false;
page_params.is_guest = false;
@@ -430,27 +469,29 @@ test_ui("test_validate_stream_message_post_policy_moderators_only", () => {
compose_state.topic("topic104");
compose_state.stream_name("stream104");
stream_data.add_sub(sub);
assert.ok(!compose_validate.validate());
let banner_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.no_post_permissions);
assert.equal(
$("#compose-error-msg").html(),
$t_html({
data.banner_text,
$t({
defaultMessage: "You do not have permission to post in this stream.",
}),
);
banner_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(banner_rendered);
// Reset error message.
compose_state.stream_name("social");
page_params.is_guest = true;
assert.equal(
$("#compose-error-msg").html(),
$t_html({
defaultMessage: "You do not have permission to post in this stream.",
}),
);
assert.ok(!compose_validate.validate());
assert.ok(banner_rendered);
});
test_ui("test_validate_stream_message_post_policy_full_members_only", () => {
test_ui("test_validate_stream_message_post_policy_full_members_only", ({mock_template}) => {
mock_banners();
page_params.is_admin = false;
page_params.is_guest = true;
const sub = {
@@ -463,19 +504,39 @@ test_ui("test_validate_stream_message_post_policy_full_members_only", () => {
compose_state.topic("topic103");
compose_state.stream_name("stream103");
stream_data.add_sub(sub);
assert.ok(!compose_validate.validate());
let banner_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.no_post_permissions);
assert.equal(
$("#compose-error-msg").html(),
$t_html({defaultMessage: "You do not have permission to post in this stream."}),
data.banner_text,
$t({
defaultMessage: "You do not have permission to post in this stream.",
}),
);
banner_rendered = true;
});
assert.ok(!compose_validate.validate());
assert.ok(banner_rendered);
});
test_ui("test_check_overflow_text", () => {
test_ui("test_check_overflow_text", ({mock_template}) => {
mock_banners();
page_params.max_message_length = 10000;
const $textarea = $("#compose-textarea");
const $indicator = $("#compose_limit_indicator");
const $send_button = $("#compose-send-button");
let banner_rendered = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.message_too_long);
assert.equal(
data.banner_text,
$t({
defaultMessage: "Message length shouldn't be greater than 10000 characters.",
}),
);
banner_rendered = true;
});
// Indicator should show red colored text
$textarea.val("a".repeat(10000 + 1));
@@ -483,52 +544,27 @@ test_ui("test_check_overflow_text", () => {
assert.ok($indicator.hasClass("over_limit"));
assert.equal($indicator.text(), "10001/10000");
assert.ok($textarea.hasClass("over_limit"));
assert.equal(
$("#compose-error-msg").html(),
"translated HTML: Message length shouldn't be greater than 10000 characters.",
);
assert.ok(banner_rendered);
assert.ok($send_button.prop("disabled"));
$("#compose-send-status").stop = () => ({fadeOut() {}});
// Indicator should show orange colored text
banner_rendered = false;
$textarea.val("a".repeat(9000 + 1));
compose_validate.check_overflow_text();
assert.ok(!$indicator.hasClass("over_limit"));
assert.equal($indicator.text(), "9001/10000");
assert.ok(!$textarea.hasClass("over_limit"));
assert.ok(!$send_button.prop("disabled"));
assert.ok(!banner_rendered);
// Indicator must be empty
banner_rendered = false;
$textarea.val("a".repeat(9000));
compose_validate.check_overflow_text();
assert.ok(!$indicator.hasClass("over_limit"));
assert.equal($indicator.text(), "");
assert.ok(!$textarea.hasClass("over_limit"));
});
test_ui("test_message_overflow", () => {
page_params.max_message_length = 10000;
const sub = {
stream_id: 101,
name: "social",
subscribed: true,
};
stream_data.add_sub(sub);
page_params.user_id = 30;
const message = "a".repeat(10000 + 1);
compose_state.stream_name("social");
compose_state.topic("priyam");
$("#compose-textarea").val(message);
assert.ok(!compose_validate.validate());
assert.equal($("#compose-error-msg").html(), "never-been-set");
$("#compose-textarea").val("a");
assert.ok(compose_validate.validate());
assert.ok(!banner_rendered);
});
test_ui("needs_subscribe_warning", () => {
@@ -767,7 +803,7 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, override_rewire, moc
test_ui("test warn_if_topic_resolved", ({override, mock_template}) => {
mock_banners();
$("#compose_banners .topic_resolved").length = 0;
override(settings_data, "user_can_move_messages_between_streams", () => true);
override(settings_data, "user_can_edit_topic_of_any_message", () => true);
let error_shown = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {

View File

@@ -253,7 +253,11 @@ export function send_message(request = create_message_object()) {
// If we're not local echo'ing messages, or if this message was not
// locally echoed, show error in compose box
if (!locally_echoed) {
compose_error.show(_.escape(response), $("#compose-textarea"));
compose_error.show_error_message(
response,
compose_error.CLASSNAMES.generic_compose_error,
$("#compose-textarea"),
);
return;
}
@@ -510,7 +514,11 @@ export function initialize() {
function failure(error_msg) {
clear_invites();
compose_error.show(_.escape(error_msg), $("#compose-textarea"));
compose_error.show_error_message(
error_msg,
compose_error.CLASSNAMES.generic_compose_error,
$("#compose-textarea"),
);
$(event.target).prop("disabled", true);
}

View File

@@ -1,15 +1,63 @@
import $ from "jquery";
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
import render_stream_does_not_exist_error from "../templates/compose_banner/stream_does_not_exist_error.hbs";
import * as common from "./common";
// banner types
export const WARNING = "warning";
export const ERROR = "error";
export const CLASSNAMES = {
// warnings
topic_resolved: "topic_resolved",
// errors
empty_message: "empty_message",
wildcards_not_allowed: "wildcards_not_allowed",
subscription_error: "subscription_error",
stream_does_not_exist: "stream_does_not_exist",
missing_stream: "missing_stream",
no_post_permissions: "no_post_permissions",
private_messages_disabled: "private_messages_disabled",
missing_private_message_recipient: "missing_private_message_recipient",
invalid_recipient: "invalid_recipient",
invalid_recipients: "invalid_recipients",
deactivated_user: "deactivated_user",
message_too_long: "message_too_long",
topic_missing: "topic_missing",
zephyr_not_running: "zephyr_not_running",
generic_compose_error: "generic_compose_error",
};
// TODO: Replace with compose_ui.hide_compose_spinner() when it is converted to ts.
function hide_compose_spinner(): void {
$("#compose-send-button .loader").hide();
$("#compose-send-button span").show();
$("#compose-send-button").removeClass("disable-btn");
}
export function show_error_message(message: string, classname: string, $bad_input?: JQuery): void {
$(`#compose_banners .${classname}`).remove();
const new_row = render_compose_banner({
banner_type: ERROR,
stream_id: null,
topic_name: null,
banner_text: message,
button_text: null,
classname,
});
const $compose_banner_area = $("#compose_banners");
$compose_banner_area.append(new_row);
hide_compose_spinner();
if ($bad_input !== undefined) {
$bad_input.trigger("focus").trigger("select");
}
}
export function show(error_html: string, $bad_input?: JQuery, alert_class = "alert-error"): void {
$("#compose-send-status")
.removeClass(common.status_classes)
@@ -17,16 +65,28 @@ export function show(error_html: string, $bad_input?: JQuery, alert_class = "ale
.stop(true)
.fadeTo(0, 1);
$("#compose-error-msg").html(error_html);
// TODO: Replace with compose_ui.hide_compose_spinner() when it is converted to ts.
$("#compose-send-button .loader").hide();
$("#compose-send-button span").show();
$("#compose-send-button").removeClass("disable-btn");
hide_compose_spinner();
if ($bad_input !== undefined) {
$bad_input.trigger("focus").trigger("select");
}
}
export function show_stream_does_not_exist_error(stream_name: string): void {
// Remove any existing banners with this warning.
$(`#compose_banners .${CLASSNAMES.stream_does_not_exist}`).remove();
const new_row = render_stream_does_not_exist_error({
banner_type: ERROR,
stream_name,
classname: CLASSNAMES.stream_does_not_exist,
});
const $compose_banner_area = $("#compose_banners");
$compose_banner_area.append(new_row);
hide_compose_spinner();
$("#stream_message_recipient_stream").trigger("focus").trigger("select");
}
export function show_not_subscribed(error_html: string, $bad_input?: JQuery): void {
show(error_html, $bad_input, "home-error-bar");
$(".compose-send-status-close").hide();

View File

@@ -12,7 +12,7 @@ import * as compose_error from "./compose_error";
import * as compose_pm_pill from "./compose_pm_pill";
import * as compose_state from "./compose_state";
import * as compose_ui from "./compose_ui";
import {$t, $t_html} from "./i18n";
import {$t} from "./i18n";
import {page_params} from "./page_params";
import * as peer_data from "./peer_data";
import * as people from "./people";
@@ -346,11 +346,12 @@ function validate_stream_message_mentions(stream_id) {
// stream, check if they permission to do so.
if (wildcard_mention !== null && stream_count > wildcard_mention_large_stream_threshold) {
if (!wildcard_mention_allowed()) {
compose_error.show(
$t_html({
compose_error.show_error_message(
$t({
defaultMessage:
"You do not have permission to use wildcard mentions in this stream.",
}),
compose_error.CLASSNAMES.wildcards_not_allowed,
);
return false;
}
@@ -377,26 +378,14 @@ function validate_stream_message_mentions(stream_id) {
}
export function validation_error(error_type, stream_name) {
let response;
switch (error_type) {
case "does-not-exist":
response = $t_html(
{
defaultMessage:
"<p>The stream <b>{stream_name}</b> does not exist.</p><p>Manage your subscriptions <z-link>on your Streams page</z-link>.</p>",
},
{
stream_name,
"z-link": (content_html) =>
`<a href='#streams/all'>${content_html.join("")}</a>`,
},
);
compose_error.show(response, $("#stream_message_recipient_stream"));
compose_error.show_stream_does_not_exist_error(stream_name);
return false;
case "error":
compose_error.show(
$t_html({defaultMessage: "Error checking subscription"}),
compose_error.show_error_message(
$t({defaultMessage: "Error checking subscription."}),
compose_error.CLASSNAMES.subscription_error,
$("#stream_message_recipient_stream"),
);
return false;
@@ -424,8 +413,9 @@ export function validate_stream_message_address_info(stream_name) {
function validate_stream_message() {
const stream_name = compose_state.stream_name();
if (stream_name === "") {
compose_error.show(
$t_html({defaultMessage: "Please specify a stream"}),
compose_error.show_error_message(
$t({defaultMessage: "Please specify a stream."}),
compose_error.CLASSNAMES.missing_stream,
$("#stream_message_recipient_stream"),
);
return false;
@@ -436,8 +426,9 @@ function validate_stream_message() {
// TODO: We plan to migrate the empty topic to only using the
// `""` representation for i18n reasons, but have not yet done so.
if (topic === "" || topic === "(no topic)") {
compose_error.show(
$t_html({defaultMessage: "Topics are required in this organization"}),
compose_error.show_error_message(
$t({defaultMessage: "Topics are required in this organization."}),
compose_error.CLASSNAMES.topic_missing,
$("#stream_message_recipient_topic"),
);
return false;
@@ -450,10 +441,11 @@ function validate_stream_message() {
}
if (!stream_data.can_post_messages_in_stream(sub)) {
compose_error.show(
$t_html({
compose_error.show_error_message(
$t({
defaultMessage: "You do not have permission to post in this stream.",
}),
compose_error.CLASSNAMES.no_post_permissions,
);
return false;
}
@@ -484,16 +476,18 @@ function validate_private_message() {
(user_ids.length !== 1 || !people.get_by_user_id(user_ids[0]).is_bot)
) {
// Unless we're composing to a bot
compose_error.show(
$t_html({defaultMessage: "Private messages are disabled in this organization."}),
compose_error.show_error_message(
$t({defaultMessage: "Private messages are disabled in this organization."}),
compose_error.CLASSNAMES.private_messages_disabled,
$("#private_message_recipient"),
);
return false;
}
if (compose_state.private_message_recipient().length === 0) {
compose_error.show(
$t_html({defaultMessage: "Please specify at least one valid recipient"}),
compose_error.show_error_message(
$t({defaultMessage: "Please specify at least one valid recipient."}),
compose_error.CLASSNAMES.missing_private_message_recipient,
$("#private_message_recipient"),
);
return false;
@@ -507,15 +501,17 @@ function validate_private_message() {
let context = {};
if (invalid_recipients.length === 1) {
context = {recipient: invalid_recipients.join(",")};
compose_error.show(
$t_html({defaultMessage: "The recipient {recipient} is not valid"}, context),
compose_error.show_error_message(
$t({defaultMessage: "The recipient {recipient} is not valid."}, context),
compose_error.CLASSNAMES.invalid_recipient,
$("#private_message_recipient"),
);
return false;
} else if (invalid_recipients.length > 1) {
context = {recipients: invalid_recipients.join(",")};
compose_error.show(
$t_html({defaultMessage: "The recipients {recipients} are not valid"}, context),
compose_error.show_error_message(
$t({defaultMessage: "The recipients {recipients} are not valid."}, context),
compose_error.CLASSNAMES.invalid_recipients,
$("#private_message_recipient"),
);
return false;
@@ -524,11 +520,9 @@ function validate_private_message() {
for (const user_id of user_ids) {
if (!people.is_person_active(user_id)) {
context = {full_name: people.get_by_user_id(user_id).full_name};
compose_error.show(
$t_html(
{defaultMessage: "You cannot send messages to deactivated users."},
context,
),
compose_error.show_error_message(
$t({defaultMessage: "You cannot send messages to deactivated users."}, context),
compose_error.CLASSNAMES.deactivated_user,
$("#private_message_recipient"),
);
@@ -551,14 +545,15 @@ export function check_overflow_text() {
$indicator.addClass("over_limit");
$("#compose-textarea").addClass("over_limit");
$indicator.text(text.length + "/" + max_length);
compose_error.show(
$t_html(
compose_error.show_error_message(
$t(
{
defaultMessage:
"Message length shouldn't be greater than {max_length} characters.",
},
{max_length},
),
compose_error.CLASSNAMES.message_too_long,
);
$("#compose-send-button").prop("disabled", true);
} else if (text.length > 0.9 * max_length) {
@@ -567,17 +562,13 @@ export function check_overflow_text() {
$indicator.text(text.length + "/" + max_length);
$("#compose-send-button").prop("disabled", false);
if ($("#compose-send-status").hasClass("alert-error")) {
$("#compose-send-status").stop(true).fadeOut();
}
$(`#compose_banners .${compose_error.CLASSNAMES.message_too_long}`).remove();
} else {
$indicator.text("");
$("#compose-textarea").removeClass("over_limit");
$("#compose-send-button").prop("disabled", false);
if ($("#compose-send-status").hasClass("alert-error")) {
$("#compose-send-status").stop(true).fadeOut();
}
$(`#compose_banners .${compose_error.CLASSNAMES.message_too_long}`).remove();
}
return text.length;
@@ -598,20 +589,23 @@ export function validate() {
// Avoid showing an error message when "enter sends" is enabled,
// as it is more likely that the user has hit "Enter" accidentally.
if (!user_settings.enter_sends) {
compose_error.show(
$t_html({defaultMessage: "You have nothing to send!"}),
compose_error.show_error_message(
$t({defaultMessage: "You have nothing to send!"}),
compose_error.CLASSNAMES.empty_message,
$("#compose-textarea"),
);
}
return false;
}
$(`#compose_banners .${compose_error.CLASSNAMES.empty_message}`).remove();
if ($("#zephyr-mirror-error").is(":visible")) {
compose_error.show(
$t_html({
compose_error.show_error_message(
$t({
defaultMessage:
"You need to be running Zephyr mirroring in order to send messages!",
}),
compose_error.CLASSNAMES.zephyr_not_running,
);
return false;
}

View File

@@ -1,5 +1,4 @@
import $ from "jquery";
import _ from "lodash";
import * as channel from "./channel";
import * as compose from "./compose";
@@ -72,7 +71,11 @@ export function schedule_message(request = compose.create_message_object()) {
}
if (error_message) {
compose_error.show(error_message, $("#compose-textarea"));
compose_error.show_error_message(
error_message,
compose_error.CLASSNAMES.generic_compose_error,
$("#compose-textarea"),
);
$("#compose-textarea").prop("disabled", false);
return;
}
@@ -96,7 +99,11 @@ export function schedule_message(request = compose.create_message_object()) {
};
const error = function (response) {
$("#compose-textarea").prop("disabled", false);
compose_error.show(_.escape(response), $("#compose-textarea"));
compose_error.show_error_message(
response,
compose_error.CLASSNAMES.generic_compose_error,
$("#compose-textarea"),
);
};
/* We are adding a disable on compose under this block because we
want slash commands to be blocking in nature. */

View File

@@ -1,7 +1,5 @@
import $ from "jquery";
import * as channel from "./channel";
import * as common from "./common";
import * as compose_error from "./compose_error";
import * as dark_theme from "./dark_theme";
import * as feedback_widget from "./feedback_widget";
import {$t} from "./i18n";
@@ -50,12 +48,7 @@ export function send(opts) {
export function tell_user(msg) {
// This is a bit hacky, but we don't have a super easy API now
// for just telling users stuff.
$("#compose-send-status")
.removeClass(common.status_classes)
.addClass("alert-error")
.stop(true)
.fadeTo(0, 1);
$("#compose-error-msg").text(msg);
compose_error.show_error_message(msg, compose_error.CLASSNAMES.generic_compose_error);
}
export function switch_to_light_theme() {

View File

@@ -348,6 +348,37 @@
}
}
}
&.error {
background-color: hsl(4, 35%, 90%);
border-color: hsla(3, 57%, 33%, 0.4);
color: hsl(4, 58%, 33%);
.compose_banner_close_button {
color: hsla(4, 58%, 33%, 0.5);
&:hover {
color: hsl(4, 58%, 33%);
}
&:active {
color: hsla(4, 58%, 33%, 0.75);
}
}
.compose_banner_action_button {
background-color: hsla(3, 57%, 33%, 0.1);
color: inherit;
&:hover {
background-color: hsla(3, 57%, 33%, 0.12);
}
&:active {
background-color: hsla(3, 57%, 33%, 0.15);
}
}
}
}
.compose_invite_user,

View File

@@ -202,6 +202,37 @@
}
}
}
&.error {
background-color: hsl(0, 60%, 19%);
border-color: hsla(3, 73%, 74%, 0.4);
color: hsl(3, 73%, 74%);
.compose_banner_close_button {
color: hsla(3, 73%, 74%, 0.5);
&:hover {
color: hsl(3, 73%, 74%);
}
&:active {
color: hsl(3, 73%, 74%, 0.75);
}
}
.compose_banner_action_button {
background-color: hsla(3, 73%, 74%, 0.1);
color: inherit;
&:hover {
background: hsla(3, 73%, 74%, 0.15);
}
&:active {
background: hsla(3, 73%, 74%, 0.2);
}
}
}
}
.message_embed .data-container::after {

View File

@@ -2,7 +2,11 @@
class="compose_banner {{banner_type}} {{classname}}"
{{#if stream_id}}data-stream-id="{{stream_id}}"{{/if}}
{{#if topic_name}}data-topic-name="{{topic_name}}"{{/if}}>
{{#if banner_text}}
<p class="banner_content">{{banner_text}}</p>
{{else}}
<div class="banner_content">{{> @partial-block}}</div>
{{/if}}
{{#if button_text}}
<button class="compose_banner_action_button" >{{button_text}}</button>
{{/if}}

View File

@@ -0,0 +1,9 @@
{{#> compose_banner }}
<p>
{{#tr}}
The stream <b>#{stream_name}</b> does not exist. Manage your subscriptions
<z-link>on your Streams page</z-link>.
{{#*inline "z-link"}}<a href='#streams/all'>{{> @partial-block}}</a>{{/inline}}
{{/tr}}
</p>
{{/compose_banner}}