compose: Create shared compose banner and use for resolved topic warning.

This is the beginning of a fix for #22524 which converts several
banners to a new style. As a part of that set of changes, this
commit creates the shared template and warning styling. The
resolved topic warning was picked (for no particular reason)
to migrate first. Further commits updating other banners
to follow.
This commit is contained in:
evykassirer 2022-08-19 12:36:33 -07:00 committed by Tim Abbott
parent dc4f933f4f
commit 85cbd324eb
15 changed files with 233 additions and 77 deletions

View File

@ -11,6 +11,8 @@ const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");
const {page_params, user_settings} = require("../zjsunit/zpage_params");
const {mock_banners} = require("./lib/compose_banner");
const noop = () => {};
set_global("document", {
@ -110,6 +112,7 @@ function initialize_handlers({override}) {
}
test_ui("send_message_success", ({override_rewire}) => {
mock_banners();
$("#compose-textarea").val("foobarfoobar");
$("#compose-textarea").trigger("blur");
$("#compose-send-status").show();
@ -133,6 +136,7 @@ test_ui("send_message_success", ({override_rewire}) => {
});
test_ui("send_message", ({override, override_rewire}) => {
mock_banners();
MockDate.set(new Date(fake_now * 1000));
override(sent_messages, "start_tracking_message", () => {});
@ -283,6 +287,7 @@ test_ui("send_message", ({override, override_rewire}) => {
});
test_ui("enter_with_preview_open", ({override, override_rewire}) => {
mock_banners();
override(notifications, "clear_compose_notifications", () => {});
override(reminder, "is_deferred_delivery", () => false);
override(document, "to_$", () => $("document-stub"));
@ -331,6 +336,7 @@ test_ui("enter_with_preview_open", ({override, override_rewire}) => {
});
test_ui("finish", ({override, override_rewire}) => {
mock_banners();
override(notifications, "clear_compose_notifications", () => {});
override(reminder, "is_deferred_delivery", () => false);
override(document, "to_$", () => $("document-stub"));

View File

@ -7,6 +7,8 @@ const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");
const {page_params} = require("../zjsunit/zpage_params");
const {mock_banners} = require("./lib/compose_banner");
const settings_config = zrequire("settings_config");
const noop = () => {};
@ -105,6 +107,7 @@ test("initial_state", () => {
});
test("start", ({override, override_rewire}) => {
mock_banners();
override_private_message_recipient({override});
override_rewire(compose_actions, "autosize_message_content", () => {});
override_rewire(compose_actions, "expand_compose_box", () => {});
@ -228,6 +231,7 @@ test("start", ({override, override_rewire}) => {
});
test("respond_to_message", ({override, override_rewire}) => {
mock_banners();
override_rewire(compose_actions, "set_focus", () => {});
override_rewire(compose_actions, "complete_starting_tasks", () => {});
override_rewire(compose_actions, "clear_textarea", () => {});
@ -268,6 +272,7 @@ test("respond_to_message", ({override, override_rewire}) => {
});
test("reply_with_mention", ({override, override_rewire}) => {
mock_banners();
compose_state.set_message_type("stream");
override_rewire(compose_actions, "set_focus", () => {});
override_rewire(compose_actions, "complete_starting_tasks", () => {});
@ -314,6 +319,7 @@ test("reply_with_mention", ({override, override_rewire}) => {
});
test("quote_and_reply", ({disallow, override, override_rewire}) => {
mock_banners();
compose_state.set_message_type("stream");
const steve = {
user_id: 90,

View File

@ -2,19 +2,22 @@
const {strict: assert} = require("assert");
const {$t_html} = require("../zjsunit/i18n");
const {$t, $t_html} = require("../zjsunit/i18n");
const {mock_esm, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const $ = require("../zjsunit/zjquery");
const {page_params} = require("../zjsunit/zpage_params");
const {mock_banners} = require("./lib/compose_banner");
const channel = mock_esm("../../static/js/channel");
const compose_actions = mock_esm("../../static/js/compose_actions");
const compose_state = zrequire("compose_state");
const ui_util = mock_esm("../../static/js/ui_util");
const compose_error = zrequire("compose_error");
const compose_pm_pill = zrequire("compose_pm_pill");
const compose_state = zrequire("compose_state");
const compose_validate = zrequire("compose_validate");
const peer_data = zrequire("peer_data");
const people = zrequire("people");
@ -761,23 +764,23 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, override_rewire, moc
assert.ok(looked_for_existing);
});
test_ui("test clear_topic_resolved_warning", () => {
$("#compose_resolved_topic").show();
$("#compose-send-status").show();
compose_validate.clear_topic_resolved_warning();
assert.ok(!$("#compose_resolved_topic").visible());
assert.ok(!$("#compose-send-status").visible());
});
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);
mock_template("compose_resolved_topic.hbs", false, (context) => {
assert.ok(context.can_move_topic);
assert.ok(resolved_topic.is_resolved(context.topic_name));
return "fake-compose_resolved_topic";
let error_shown = false;
mock_template("compose_banner/compose_banner.hbs", false, (data) => {
assert.equal(data.classname, compose_error.CLASSNAMES.topic_resolved);
assert.equal(
data.banner_text,
$t({
defaultMessage:
"You are sending a message to a resolved topic. You can send as-is or unresolve the topic first.",
}),
);
error_shown = true;
return "topic_resolved_warning_stub";
});
const sub = {
@ -786,40 +789,36 @@ test_ui("test warn_if_topic_resolved", ({override, mock_template}) => {
};
stream_data.add_sub(sub);
// The error message area where it is shown
const $error_area = $("#compose_resolved_topic");
compose_validate.clear_topic_resolved_warning();
// Hack to make this empty for zjquery; this is conceptually done
// in the previous line.
$error_area.empty();
assert.ok(!$error_area.visible());
compose_state.set_message_type("stream");
compose_state.stream_name("Do not exist");
compose_state.topic(resolved_topic.resolve_name("hello"));
compose_state.message_content("content");
// Do not show a warning if stream name does not exist
error_shown = false;
compose_validate.warn_if_topic_resolved(true);
assert.ok(!$error_area.visible());
assert.ok(!error_shown);
compose_state.stream_name("random");
// Show the warning now as stream also exists
error_shown = false;
compose_validate.warn_if_topic_resolved(true);
assert.ok($error_area.visible());
assert.ok(error_shown);
// Call it again with false; this should be a noop.
// Call it again with false; this should do the same thing.
error_shown = false;
compose_validate.warn_if_topic_resolved(false);
assert.ok($error_area.visible());
assert.ok(error_shown);
compose_state.topic("hello");
// The warning will be cleared now
error_shown = false;
compose_validate.warn_if_topic_resolved(true);
assert.ok(!$error_area.visible());
assert.ok(!error_shown);
// Calling with false won't do anything.
error_shown = false;
compose_validate.warn_if_topic_resolved(false);
assert.ok(!$error_area.visible());
assert.ok(!error_shown);
});

View File

@ -0,0 +1,12 @@
"use strict";
const compose_banner = require("../../../static/js/compose_error");
const $ = require("../../zjsunit/zjquery");
exports.mock_banners = () => {
// zjquery doesn't support `remove`, which is used when clearing the compose box.
// TODO: improve how we test this so that we don't have to mock things like this.
for (const classname of Object.values(compose_banner.CLASSNAMES)) {
$(`#compose_banners .${classname}`).remove = () => {};
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

View File

@ -198,6 +198,7 @@ export function clear_compose_box() {
$("#compose-textarea").removeData("draft-id");
compose_ui.autosize_textarea($("#compose-textarea"));
$("#compose-send-status").hide(0);
$("#compose_banners").empty();
compose_ui.hide_compose_spinner();
}
@ -473,24 +474,22 @@ export function initialize() {
$("#compose-send-status").hide();
});
$("#compose_resolved_topic").on("click", ".compose_unresolve_topic", (event) => {
event.preventDefault();
$("#compose_banners").on(
"click",
`.${compose_error.CLASSNAMES.topic_resolved} .compose_banner_action_button`,
(event) => {
event.preventDefault();
const $target = $(event.target).parents(".compose_resolved_topic");
const stream_id = Number.parseInt($target.attr("data-stream-id"), 10);
const topic_name = $target.attr("data-topic-name");
const $target = $(event.target).parents(".compose_banner");
const stream_id = Number.parseInt($target.attr("data-stream-id"), 10);
const topic_name = $target.attr("data-topic-name");
message_edit.with_first_message_id(stream_id, topic_name, (message_id) => {
message_edit.toggle_resolve_topic(message_id, topic_name);
compose_validate.clear_topic_resolved_warning(true);
});
});
$("#compose_resolved_topic").on("click", ".compose_resolved_topic_close", (event) => {
event.preventDefault();
compose_validate.clear_topic_resolved_warning(true);
});
message_edit.with_first_message_id(stream_id, topic_name, (message_id) => {
message_edit.toggle_resolve_topic(message_id, topic_name);
compose_validate.clear_topic_resolved_warning(true);
});
},
);
$("#compose_invite_users").on("click", ".compose_invite_link", (event) => {
event.preventDefault();
@ -551,6 +550,18 @@ export function initialize() {
},
);
for (const classname of Object.values(compose_error.CLASSNAMES)) {
const classname_selector = `.${classname}`;
$("#compose_banners").on(
"click",
`${classname_selector} .compose_banner_close_button`,
(event) => {
event.preventDefault();
$(event.target).parents(classname_selector).remove();
},
);
}
// Click event binding for "Attach files" button
// Triggers a click on a hidden file input field

View File

@ -92,6 +92,7 @@ function show_compose_box(msg_type, opts) {
$("#private_message_toggle").addClass("active");
}
$("#compose-send-status").removeClass(common.status_classes).hide();
$("#compose_banners").empty();
$("#compose").css({visibility: "visible"});
// When changing this, edit the 42px in _maybe_autoscroll
$(".new_message_textarea").css("min-height", "3em");
@ -118,6 +119,7 @@ function clear_box() {
$("#compose-textarea").removeData("draft-id");
compose_ui.autosize_textarea($("#compose-textarea"));
$("#compose-send-status").hide(0);
$("#compose_banners").empty();
}
export function autosize_message_content() {

View File

@ -2,6 +2,14 @@ import $ from "jquery";
import * as common from "./common";
// banner types
export const WARNING = "warning";
export const CLASSNAMES = {
// warnings
topic_resolved: "topic_resolved",
};
export function show(error_html: string, $bad_input?: JQuery, alert_class = "alert-error"): void {
$("#compose-send-status")
.removeClass(common.status_classes)

View File

@ -2,17 +2,17 @@ import $ from "jquery";
import * as resolved_topic from "../shared/js/resolved_topic";
import render_compose_all_everyone from "../templates/compose_all_everyone.hbs";
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
import render_compose_invite_users from "../templates/compose_invite_users.hbs";
import render_compose_not_subscribed from "../templates/compose_not_subscribed.hbs";
import render_compose_private_stream_alert from "../templates/compose_private_stream_alert.hbs";
import render_compose_resolved_topic from "../templates/compose_resolved_topic.hbs";
import * as channel from "./channel";
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_html} from "./i18n";
import {$t, $t_html} from "./i18n";
import {page_params} from "./page_params";
import * as peer_data from "./peer_data";
import * as people from "./people";
@ -164,9 +164,7 @@ export function warn_if_mentioning_unsubscribed_user(mentioned) {
}
export function clear_topic_resolved_warning() {
$("#compose_resolved_topic").hide();
$("#compose_resolved_topic").empty();
$("#compose-send-status").hide();
$(`#compose_banners .${compose_error.CLASSNAMES.topic_resolved}`).remove();
}
export function warn_if_topic_resolved(topic_changed) {
@ -191,29 +189,34 @@ export function warn_if_topic_resolved(topic_changed) {
const stream_name = compose_state.stream_name();
const message_content = compose_state.message_content();
const sub = stream_data.get_sub(stream_name);
const $resolved_notice_area = $("#compose_resolved_topic");
const $compose_banner_area = $("#compose_banners");
if (sub && message_content !== "" && resolved_topic.is_resolved(topic_name)) {
if ($resolved_notice_area.html()) {
if ($(`#compose_banners .${compose_error.CLASSNAMES.topic_resolved}`).length) {
// Error is already displayed; no action required.
return;
}
const button_text = settings_data.user_can_edit_topic_of_any_message()
? $t({defaultMessage: "Unresolve topic"})
: null;
const context = {
banner_type: compose_error.WARNING,
stream_id: sub.stream_id,
topic_name,
can_move_topic: settings_data.user_can_edit_topic_of_any_message(),
banner_text: $t({
defaultMessage:
"You are sending a message to a resolved topic. You can send as-is or unresolve the topic first.",
}),
button_text,
classname: compose_error.CLASSNAMES.topic_resolved,
};
const new_row = render_compose_resolved_topic(context);
$resolved_notice_area.append(new_row);
$resolved_notice_area.show();
const new_row = render_compose_banner(context);
$compose_banner_area.append(new_row);
} else {
// Only clear the notice if already displayed.
if ($resolved_notice_area.html()) {
clear_topic_resolved_warning();
}
clear_topic_resolved_warning();
}
}

View File

@ -309,6 +309,12 @@ export function process_escape_key(e) {
return true;
}
// Clear open compose banners, if present.
if ($(".compose_banner").length) {
$("#compose_banners").empty();
return true;
}
// If the user hit the Esc key, cancel the current compose
compose_actions.cancel();
return true;

View File

@ -277,7 +277,79 @@
display: none;
}
.compose_resolved_topic,
.compose_banner {
margin-bottom: 20px;
border-radius: 5px;
padding-right: 11px;
border: 1px solid;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 15px;
line-height: 18px;
p {
margin: 0; /* override bootstrap */
/* 5px right padding + 10px left-margin of the neighbouring button will match the left padding */
padding: 12px 5px 12px 15px;
}
.banner_content {
flex-grow: 1;
}
.compose_banner_action_button {
border: none;
border-radius: 4px;
padding: 5px 10px;
font-weight: 600;
margin-left: 10px;
margin-top: 4.5px;
margin-bottom: 4.5px;
height: 32px;
white-space: nowrap;
}
.compose_banner_close_button {
font-size: 17px;
text-decoration: none;
margin-left: 10px;
margin-top: 12px;
margin-bottom: 12px;
}
&.warning {
background-color: hsl(50, 75%, 92%);
border-color: hsla(38, 44%, 27%, 0.4);
color: hsl(38, 44%, 27%);
.compose_banner_close_button {
color: hsla(38, 44%, 27%, 0.5);
&:hover {
color: hsl(38, 44%, 27%);
}
&:active {
color: hsla(38, 44%, 27%, 0.75);
}
}
.compose_banner_action_button {
background-color: hsla(38, 44%, 27%, 0.1);
color: inherit;
&:hover {
background-color: hsla(38, 44%, 27%, 0.12);
}
&:active {
background-color: hsla(38, 44%, 27%, 0.15);
}
}
}
}
.compose_invite_user,
.compose_private_stream_alert,
.compose-all-everyone,
@ -294,7 +366,6 @@
position: absolute;
}
.compose_resolved_topic_user_controls .compose_resolved_topic_close,
.compose_invite_user_controls .compose_invite_close,
.compose_private_stream_alert_controls .compose_private_stream_alert_close {
display: inline-block;
@ -304,7 +375,6 @@
width: 10px;
}
.compose_resolved_topic_user_controls,
.compose-all-everyone-controls,
.compose_invite_user_controls,
.compose_private_stream_alert_controls {
@ -313,7 +383,6 @@
}
.compose_invite_user p,
.compose_resolved_topic p,
.compose_not_subscribed p {
margin: 5px 20px;
display: inline-block;

View File

@ -171,6 +171,39 @@
opacity: 0.4;
}
.compose_banner {
&.warning {
background-color: hsl(53, 100%, 11%);
border-color: hsla(38, 44%, 60%, 0.4);
color: hsl(50, 45%, 61%);
.compose_banner_close_button {
color: hsl(50, 45%, 61%, 0.5);
&:hover {
color: hsl(50, 45%, 61%);
}
&:active {
color: hsl(50, 45%, 61%, 0.75);
}
}
.compose_banner_action_button {
background-color: hsla(50, 45%, 61%, 0.1);
color: inherit;
&:hover {
background-color: hsla(50, 45%, 61%, 0.15);
}
&:active {
background-color: hsla(50, 45%, 61%, 0.2);
}
}
}
}
.message_embed .data-container::after {
background: linear-gradient(0deg, hsl(212, 28%, 18%), transparent 100%);
}

View File

@ -46,11 +46,11 @@
</div>
</div>
<div class="message_comp">
<div id="compose_banners"></div>
<div class="alert" id="compose-send-status">
<span class="compose-send-status-close">&times;</span>
<span id="compose-error-msg"></span>
</div>
<div id="compose_resolved_topic" class="alert home-error-bar"></div>
<div id="compose_invite_users" class="alert home-error-bar"></div>
<div id="compose-all-everyone" class="alert home-error-bar"></div>
<div id="compose_not_subscribed" class="alert home-error-bar"></div>

View File

@ -0,0 +1,10 @@
<div
class="compose_banner {{banner_type}} {{classname}}"
{{#if stream_id}}data-stream-id="{{stream_id}}"{{/if}}
{{#if topic_name}}data-topic-name="{{topic_name}}"{{/if}}>
<p class="banner_content">{{banner_text}}</p>
{{#if button_text}}
<button class="compose_banner_action_button" >{{button_text}}</button>
{{/if}}
<a role="button" class="zulip-icon zulip-icon-close compose_banner_close_button"></a>
</div>

View File

@ -1,9 +0,0 @@
<div class="compose_resolved_topic" data-stream-id="{{stream_id}}" data-topic-name="{{topic_name}}">
<p>{{#tr}}You are sending a message to a resolved topic. You can send as-is or unresolve the topic first.{{/tr}}</p>
<div class="compose_resolved_topic_user_controls">
{{#if can_move_topic}}
<button class="btn btn-warning compose_unresolve_topic" >{{t "Unresolve topic" }}</button>
{{/if}}
<button type="button" class="compose_resolved_topic_close close">&times;</button>
</div>
</div>