diff --git a/frontend_tests/node_tests/compose_validate.js b/frontend_tests/node_tests/compose_validate.js index 16d562b0c1..fb90ea38d1 100644 --- a/frontend_tests/node_tests/compose_validate.js +++ b/frontend_tests/node_tests/compose_validate.js @@ -16,6 +16,7 @@ const ui_util = mock_esm("../../static/js/ui_util"); const compose_pm_pill = zrequire("compose_pm_pill"); const compose_validate = zrequire("compose_validate"); +const message_edit = zrequire("message_edit"); const peer_data = zrequire("peer_data"); const people = zrequire("people"); const settings_config = zrequire("settings_config"); @@ -753,3 +754,53 @@ test_ui("warn_if_mentioning_unsubscribed_user", ({override, override_rewire, moc assert.equal($("#compose_invite_users").visible(), true); 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}) => { + 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(context.topic_name.startsWith(message_edit.RESOLVED_TOPIC_PREFIX)); + return "fake-compose_resolved_topic"; + }); + + const sub = { + stream_id: 111, + name: "random", + }; + stream_data.add_sub(sub); + + // The error message area where it is shown + const error_area = $("#compose_resolved_topic"); + error_area.html(""); + + compose_state.set_message_type("stream"); + compose_state.stream_name("Do not exist"); + compose_state.topic(message_edit.RESOLVED_TOPIC_PREFIX + "hello"); + + // Do not show a warning if stream name does not exist + compose_validate.warn_if_topic_resolved(); + assert.ok(!error_area.visible()); + + compose_state.stream_name("random"); + + // Show the warning now as stream also exists + compose_validate.warn_if_topic_resolved(); + assert.ok(error_area.visible()); + + compose_state.topic("hello"); + + // The warning will be cleared now + compose_validate.warn_if_topic_resolved(); + assert.ok(!error_area.visible()); +}); diff --git a/static/js/compose.js b/static/js/compose.js index 71aa01f341..5ad77117b3 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -14,6 +14,7 @@ import * as flatpickr from "./flatpickr"; import {$t, $t_html} from "./i18n"; import * as loading from "./loading"; import * as markdown from "./markdown"; +import * as message_edit from "./message_edit"; import * as notifications from "./notifications"; import {page_params} from "./page_params"; import * as people from "./people"; @@ -92,6 +93,7 @@ export function update_fade() { } const msg_type = compose_state.get_message_type(); + compose_validate.warn_if_topic_resolved(); compose_fade.set_focused_recipient(msg_type); compose_fade.update_all(); } @@ -447,6 +449,25 @@ export function initialize() { $("#compose-send-status").hide(); }); + $("#compose_resolved_topic").on("click", ".compose_unresolve_topic", (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"); + + 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(); + }); + }); + + $("#compose_resolved_topic").on("click", ".compose_resolved_topic_close", (event) => { + event.preventDefault(); + + compose_validate.clear_topic_resolved_warning(); + }); + $("#compose_invite_users").on("click", ".compose_invite_link", (event) => { event.preventDefault(); diff --git a/static/js/compose_actions.js b/static/js/compose_actions.js index 93f7295231..02b0e1dea4 100644 --- a/static/js/compose_actions.js +++ b/static/js/compose_actions.js @@ -106,6 +106,7 @@ function clear_box() { compose.clear_invites(); // TODO: Better encapsulate at-mention warnings. + compose_validate.clear_topic_resolved_warning(); compose_validate.clear_all_everyone_warnings(); compose_validate.clear_announce_warnings(); compose.clear_private_stream_alert(); @@ -285,6 +286,9 @@ export function start(msg_type, opts) { // Show either stream/topic fields or "You and" field. show_box(msg_type, opts); + // Show a warning if topic is resolved + compose_validate.warn_if_topic_resolved(); + // Reset the `max-height` property of `compose-textarea` so that the // compose-box do not cover the last messages of the current stream // while writing a long message. @@ -460,6 +464,7 @@ export function on_topic_narrow() { // See #3300 for context--a couple users specifically asked for // this convenience. compose_state.topic(narrow_state.topic()); + compose_validate.warn_if_topic_resolved(); compose_fade.set_focused_recipient("stream"); compose_fade.update_message_list(); $("#compose-textarea").trigger("focus").trigger("select"); diff --git a/static/js/compose_validate.js b/static/js/compose_validate.js index 77c06545f7..0ebce6b64b 100644 --- a/static/js/compose_validate.js +++ b/static/js/compose_validate.js @@ -5,6 +5,7 @@ import render_compose_announce from "../templates/compose_announce.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"; @@ -12,6 +13,7 @@ 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 * as message_edit from "./message_edit"; import {page_params} from "./page_params"; import * as peer_data from "./peer_data"; import * as people from "./people"; @@ -164,6 +166,40 @@ 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(); +} + +export function warn_if_topic_resolved() { + const stream_name = compose_state.stream_name(); + const topic_name = compose_state.topic(); + + const sub = stream_data.get_sub(stream_name); + + if (sub && topic_name.startsWith(message_edit.RESOLVED_TOPIC_PREFIX)) { + const error_area = $("#compose_resolved_topic"); + + if (error_area.html()) { + clear_topic_resolved_warning(); // This warning already exists + } + + const context = { + stream_id: sub.stream_id, + topic_name, + can_move_topic: settings_data.user_can_move_messages_between_streams(), + }; + + const new_row = render_compose_resolved_topic(context); + error_area.append(new_row); + + error_area.show(); + } else { + clear_topic_resolved_warning(); + } +} + function show_all_everyone_warnings(stream_id) { const stream_count = peer_data.get_subscriber_count(stream_id) || 0; diff --git a/static/js/message_events.js b/static/js/message_events.js index 6fbe5c3197..2aa0579a95 100644 --- a/static/js/message_events.js +++ b/static/js/message_events.js @@ -5,6 +5,7 @@ import {all_messages_data} from "./all_messages_data"; import * as channel from "./channel"; import * as compose_fade from "./compose_fade"; import * as compose_state from "./compose_state"; +import * as compose_validate from "./compose_validate"; import * as condense from "./condense"; import * as huddle_data from "./huddle_data"; import * as message_edit from "./message_edit"; @@ -218,6 +219,7 @@ export function update_messages(events) { ) { changed_compose = true; compose_state.topic(new_topic); + compose_validate.warn_if_topic_resolved(); compose_fade.set_focused_recipient("stream"); } diff --git a/static/styles/compose.css b/static/styles/compose.css index 926255d105..95d4019292 100644 --- a/static/styles/compose.css +++ b/static/styles/compose.css @@ -255,6 +255,7 @@ display: none; } +.compose_resolved_topic, .compose_invite_user, .compose_private_stream_alert, .compose-all-everyone, @@ -272,6 +273,7 @@ position: absolute; } +.compose_resolved_topic_close, .compose_invite_close, .compose_private_stream_alert_close { display: inline-block; @@ -280,6 +282,7 @@ width: 10px; } +.compose_resolved_topic_user_controls, .compose-all-everyone-controls, .compose-announce-controls, .compose_invite_user_controls, @@ -289,8 +292,9 @@ } .compose_invite_user p, +.compose_resolved_topic p, .compose_not_subscribed p { - margin: 0 20px; + margin: 5px 20px; display: inline-block; max-width: calc(100% - 100px); } diff --git a/static/templates/compose.hbs b/static/templates/compose.hbs index 858b5d5934..ff2c5e7124 100644 --- a/static/templates/compose.hbs +++ b/static/templates/compose.hbs @@ -43,6 +43,7 @@ × +
diff --git a/static/templates/compose_resolved_topic.hbs b/static/templates/compose_resolved_topic.hbs new file mode 100644 index 0000000000..61ad193bee --- /dev/null +++ b/static/templates/compose_resolved_topic.hbs @@ -0,0 +1,9 @@ +
+

{{#tr}}You are sending a message to a resolved topic. You can send as-is or unresolve the topic first.{{/tr}}

+
+ {{#if can_move_topic}} + + {{/if}} + +
+