mirror of https://github.com/zulip/zulip.git
compose: Show banner to explain interleaved view messages fading.
In an interleaved view when composing a message we fade messages which the user is not replying to, to reduce the chance they send a message to a recipient they didn't intend to. Also, it reduces the visual/cognitive processing required to figure out where their message is going to go. But, it's not necessarily clear to users that what the fading means, so this commit adds a one-time compose banner to explain what's going on the first time this comes up. Fixes part of #29076.
This commit is contained in:
parent
35380b095f
commit
df7ed437c2
|
@ -6,6 +6,7 @@ import $ from "jquery";
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import * as compose_banner from "./compose_banner";
|
import * as compose_banner from "./compose_banner";
|
||||||
import * as compose_fade from "./compose_fade";
|
import * as compose_fade from "./compose_fade";
|
||||||
|
import * as compose_notifications from "./compose_notifications";
|
||||||
import * as compose_pm_pill from "./compose_pm_pill";
|
import * as compose_pm_pill from "./compose_pm_pill";
|
||||||
import * as compose_recipient from "./compose_recipient";
|
import * as compose_recipient from "./compose_recipient";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
|
@ -174,6 +175,13 @@ export function complete_starting_tasks(opts: ComposeActionsOpts): void {
|
||||||
$(document).trigger(new $.Event("compose_started.zulip", opts));
|
$(document).trigger(new $.Event("compose_started.zulip", opts));
|
||||||
compose_recipient.update_placeholder_text();
|
compose_recipient.update_placeholder_text();
|
||||||
compose_recipient.update_narrow_to_recipient_visibility();
|
compose_recipient.update_narrow_to_recipient_visibility();
|
||||||
|
// We explicitly call this function here apart from compose_setup.js
|
||||||
|
// as this helps to show banner when responding in an interleaved view.
|
||||||
|
// While responding, the compose box opens before fading resulting in
|
||||||
|
// the function call in compose_setup.js not displaying banner.
|
||||||
|
if (!narrow_state.narrowed_by_reply()) {
|
||||||
|
compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maybe_scroll_up_selected_message(opts: ComposeActionsStartOpts): void {
|
export function maybe_scroll_up_selected_message(opts: ComposeActionsStartOpts): void {
|
||||||
|
@ -408,6 +416,7 @@ export function cancel(): void {
|
||||||
clear_box();
|
clear_box();
|
||||||
compose_banner.clear_message_sent_banners();
|
compose_banner.clear_message_sent_banners();
|
||||||
compose_banner.clear_non_interleaved_view_messages_fading_banner();
|
compose_banner.clear_non_interleaved_view_messages_fading_banner();
|
||||||
|
compose_banner.clear_interleaved_view_messages_fading_banner();
|
||||||
call_hooks(compose_cancel_hooks);
|
call_hooks(compose_cancel_hooks);
|
||||||
compose_state.set_message_type(undefined);
|
compose_state.set_message_type(undefined);
|
||||||
compose_pm_pill.clear();
|
compose_pm_pill.clear();
|
||||||
|
|
|
@ -35,6 +35,7 @@ const MESSAGE_SENT_CLASSNAMES = {
|
||||||
export const CLASSNAMES = {
|
export const CLASSNAMES = {
|
||||||
...MESSAGE_SENT_CLASSNAMES,
|
...MESSAGE_SENT_CLASSNAMES,
|
||||||
non_interleaved_view_messages_fading: "non_interleaved_view_messages_fading",
|
non_interleaved_view_messages_fading: "non_interleaved_view_messages_fading",
|
||||||
|
interleaved_view_messages_fading: "interleaved_view_messages_fading",
|
||||||
// unmute topic notifications are styled like warnings but have distinct behaviour
|
// unmute topic notifications are styled like warnings but have distinct behaviour
|
||||||
unmute_topic_notification: "unmute_topic_notification warning-style",
|
unmute_topic_notification: "unmute_topic_notification warning-style",
|
||||||
// warnings
|
// warnings
|
||||||
|
@ -152,6 +153,10 @@ export function clear_non_interleaved_view_messages_fading_banner(): void {
|
||||||
$(`#compose_banners .${CSS.escape(CLASSNAMES.non_interleaved_view_messages_fading)}`).remove();
|
$(`#compose_banners .${CSS.escape(CLASSNAMES.non_interleaved_view_messages_fading)}`).remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clear_interleaved_view_messages_fading_banner(): void {
|
||||||
|
$(`#compose_banners .${CSS.escape(CLASSNAMES.interleaved_view_messages_fading)}`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
export function clear_all(): void {
|
export function clear_all(): void {
|
||||||
scroll_util.get_content_element($(`#compose_banners`)).empty();
|
scroll_util.get_content_element($(`#compose_banners`)).empty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,8 +306,9 @@ export function notify_messages_outside_current_search(messages: Message[]): voi
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maybe_show_one_time_non_interleaved_view_messages_fading_banner(): void {
|
export function maybe_show_one_time_non_interleaved_view_messages_fading_banner(): void {
|
||||||
// Remove message fading banner if exists. Helps in live-updating banner.
|
// Remove message fading banners if exists. Helps in live-updating banner.
|
||||||
compose_banner.clear_non_interleaved_view_messages_fading_banner();
|
compose_banner.clear_non_interleaved_view_messages_fading_banner();
|
||||||
|
compose_banner.clear_interleaved_view_messages_fading_banner();
|
||||||
|
|
||||||
if (!onboarding_steps.ONE_TIME_NOTICES_TO_DISPLAY.has("non_interleaved_view_messages_fading")) {
|
if (!onboarding_steps.ONE_TIME_NOTICES_TO_DISPLAY.has("non_interleaved_view_messages_fading")) {
|
||||||
return;
|
return;
|
||||||
|
@ -334,6 +335,39 @@ export function maybe_show_one_time_non_interleaved_view_messages_fading_banner(
|
||||||
compose_banner.append_compose_banner_to_banner_list($(new_row_html), $("#compose_banners"));
|
compose_banner.append_compose_banner_to_banner_list($(new_row_html), $("#compose_banners"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maybe_show_one_time_interleaved_view_messages_fading_banner(): void {
|
||||||
|
// Remove message fading banners if exists. Helps in live-updating banner.
|
||||||
|
compose_banner.clear_non_interleaved_view_messages_fading_banner();
|
||||||
|
compose_banner.clear_interleaved_view_messages_fading_banner();
|
||||||
|
|
||||||
|
if (!onboarding_steps.ONE_TIME_NOTICES_TO_DISPLAY.has("interleaved_view_messages_fading")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait to display the banner the first time until there's actually fading.
|
||||||
|
const faded_messages_exist = $(".focused-message-list .recipient_row").hasClass("message-fade");
|
||||||
|
if (!faded_messages_exist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Introduce two variants of the banner_text depending on whether
|
||||||
|
// sending a message to the current recipient would appear in the view you're in.
|
||||||
|
// See: https://github.com/zulip/zulip/pull/29634#issuecomment-2073274029
|
||||||
|
const context = {
|
||||||
|
banner_type: compose_banner.INFO,
|
||||||
|
classname: compose_banner.CLASSNAMES.interleaved_view_messages_fading,
|
||||||
|
banner_text: $t({
|
||||||
|
defaultMessage:
|
||||||
|
"To make it easier to tell where your message will be sent, messages in conversations you are not composing to are faded.",
|
||||||
|
}),
|
||||||
|
button_text: $t({defaultMessage: "Got it"}),
|
||||||
|
hide_close_button: true,
|
||||||
|
};
|
||||||
|
const new_row_html = render_compose_banner(context);
|
||||||
|
|
||||||
|
compose_banner.append_compose_banner_to_banner_list($(new_row_html), $("#compose_banners"));
|
||||||
|
}
|
||||||
|
|
||||||
export function reify_message_id(opts: {old_id: number; new_id: number}): void {
|
export function reify_message_id(opts: {old_id: number; new_id: number}): void {
|
||||||
const old_id = opts.old_id;
|
const old_id = opts.old_id;
|
||||||
const new_id = opts.new_id;
|
const new_id = opts.new_id;
|
||||||
|
|
|
@ -331,6 +331,17 @@ export function initialize() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const interleaved_view_messages_fading_banner_selector = `.${CSS.escape(compose_banner.CLASSNAMES.interleaved_view_messages_fading)}`;
|
||||||
|
$("body").on(
|
||||||
|
"click",
|
||||||
|
`${interleaved_view_messages_fading_banner_selector} .main-view-banner-action-button`,
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
$(event.target).parents(`${interleaved_view_messages_fading_banner_selector}`).remove();
|
||||||
|
onboarding_steps.post_onboarding_step_as_read("interleaved_view_messages_fading");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
for (const classname of Object.values(compose_banner.CLASSNAMES)) {
|
for (const classname of Object.values(compose_banner.CLASSNAMES)) {
|
||||||
const classname_selector = `.${CSS.escape(classname)}`;
|
const classname_selector = `.${CSS.escape(classname)}`;
|
||||||
$("body").on("click", `${classname_selector} .main-view-banner-close-button`, (event) => {
|
$("body").on("click", `${classname_selector} .main-view-banner-close-button`, (event) => {
|
||||||
|
@ -492,7 +503,11 @@ export function initialize() {
|
||||||
|
|
||||||
$("textarea#compose-textarea").on("focus", () => {
|
$("textarea#compose-textarea").on("focus", () => {
|
||||||
compose_recipient.update_placeholder_text();
|
compose_recipient.update_placeholder_text();
|
||||||
compose_notifications.maybe_show_one_time_non_interleaved_view_messages_fading_banner();
|
if (narrow_state.narrowed_by_reply()) {
|
||||||
|
compose_notifications.maybe_show_one_time_non_interleaved_view_messages_fading_banner();
|
||||||
|
} else {
|
||||||
|
compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#compose_recipient_box").on("click", "#recipient_box_clear_topic_button", () => {
|
$("#compose_recipient_box").on("click", "#recipient_box_clear_topic_button", () => {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as channel from "./channel";
|
||||||
import * as compose_actions from "./compose_actions";
|
import * as compose_actions from "./compose_actions";
|
||||||
import * as compose_banner from "./compose_banner";
|
import * as compose_banner from "./compose_banner";
|
||||||
import * as compose_closed_ui from "./compose_closed_ui";
|
import * as compose_closed_ui from "./compose_closed_ui";
|
||||||
|
import * as compose_notifications from "./compose_notifications";
|
||||||
import * as compose_recipient from "./compose_recipient";
|
import * as compose_recipient from "./compose_recipient";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
import * as condense from "./condense";
|
import * as condense from "./condense";
|
||||||
|
@ -1277,6 +1278,13 @@ export function to_compose_target() {
|
||||||
|
|
||||||
function handle_post_view_change(msg_list, opts) {
|
function handle_post_view_change(msg_list, opts) {
|
||||||
const filter = msg_list.data.filter;
|
const filter = msg_list.data.filter;
|
||||||
|
|
||||||
|
if (narrow_state.narrowed_by_reply()) {
|
||||||
|
compose_notifications.maybe_show_one_time_non_interleaved_view_messages_fading_banner();
|
||||||
|
} else {
|
||||||
|
compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner();
|
||||||
|
}
|
||||||
|
|
||||||
scheduled_messages_feed_ui.update_schedule_message_indicator();
|
scheduled_messages_feed_ui.update_schedule_message_indicator();
|
||||||
typing_events.render_notifications_for_narrow();
|
typing_events.render_notifications_for_narrow();
|
||||||
|
|
||||||
|
|
|
@ -796,6 +796,7 @@ test_ui("on_events", ({override, override_rewire}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
override_rewire(compose_recipient, "update_placeholder_text", noop);
|
override_rewire(compose_recipient, "update_placeholder_text", noop);
|
||||||
|
override(narrow_state, "narrowed_by_reply", () => true);
|
||||||
override(
|
override(
|
||||||
compose_notifications,
|
compose_notifications,
|
||||||
"maybe_show_one_time_non_interleaved_view_messages_fading_banner",
|
"maybe_show_one_time_non_interleaved_view_messages_fading_banner",
|
||||||
|
|
|
@ -19,6 +19,7 @@ const compose_actions = mock_esm("../src/compose_actions");
|
||||||
const compose_banner = mock_esm("../src/compose_banner");
|
const compose_banner = mock_esm("../src/compose_banner");
|
||||||
const compose_closed_ui = mock_esm("../src/compose_closed_ui");
|
const compose_closed_ui = mock_esm("../src/compose_closed_ui");
|
||||||
const compose_recipient = mock_esm("../src/compose_recipient");
|
const compose_recipient = mock_esm("../src/compose_recipient");
|
||||||
|
const compose_notifications = mock_esm("../src/compose_notifications");
|
||||||
const message_fetch = mock_esm("../src/message_fetch");
|
const message_fetch = mock_esm("../src/message_fetch");
|
||||||
const message_list = mock_esm("../src/message_list");
|
const message_list = mock_esm("../src/message_list");
|
||||||
const message_lists = mock_esm("../src/message_lists", {
|
const message_lists = mock_esm("../src/message_lists", {
|
||||||
|
@ -212,6 +213,12 @@ run_test("basics", ({override}) => {
|
||||||
opts.cont();
|
opts.cont();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
override(
|
||||||
|
compose_notifications,
|
||||||
|
"maybe_show_one_time_interleaved_view_messages_fading_banner",
|
||||||
|
noop,
|
||||||
|
);
|
||||||
|
|
||||||
message_view.show(terms, {
|
message_view.show(terms, {
|
||||||
then_select_id: selected_id,
|
then_select_id: selected_id,
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,6 +38,9 @@ ONE_TIME_NOTICES: list[OneTimeNotice] = [
|
||||||
OneTimeNotice(
|
OneTimeNotice(
|
||||||
name="non_interleaved_view_messages_fading",
|
name="non_interleaved_view_messages_fading",
|
||||||
),
|
),
|
||||||
|
OneTimeNotice(
|
||||||
|
name="interleaved_view_messages_fading",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# We may introduce onboarding step of types other than 'one time notice'
|
# We may introduce onboarding step of types other than 'one time notice'
|
||||||
|
|
|
@ -25,11 +25,12 @@ class TestGetNextOnboardingSteps(ZulipTestCase):
|
||||||
|
|
||||||
do_mark_onboarding_step_as_read(self.user, "intro_inbox_view_modal")
|
do_mark_onboarding_step_as_read(self.user, "intro_inbox_view_modal")
|
||||||
onboarding_steps = get_next_onboarding_steps(self.user)
|
onboarding_steps = get_next_onboarding_steps(self.user)
|
||||||
self.assert_length(onboarding_steps, 4)
|
self.assert_length(onboarding_steps, 5)
|
||||||
self.assertEqual(onboarding_steps[0]["name"], "intro_recent_view_modal")
|
self.assertEqual(onboarding_steps[0]["name"], "intro_recent_view_modal")
|
||||||
self.assertEqual(onboarding_steps[1]["name"], "first_stream_created_banner")
|
self.assertEqual(onboarding_steps[1]["name"], "first_stream_created_banner")
|
||||||
self.assertEqual(onboarding_steps[2]["name"], "jump_to_conversation_banner")
|
self.assertEqual(onboarding_steps[2]["name"], "jump_to_conversation_banner")
|
||||||
self.assertEqual(onboarding_steps[3]["name"], "non_interleaved_view_messages_fading")
|
self.assertEqual(onboarding_steps[3]["name"], "non_interleaved_view_messages_fading")
|
||||||
|
self.assertEqual(onboarding_steps[4]["name"], "interleaved_view_messages_fading")
|
||||||
|
|
||||||
with self.settings(TUTORIAL_ENABLED=False):
|
with self.settings(TUTORIAL_ENABLED=False):
|
||||||
onboarding_steps = get_next_onboarding_steps(self.user)
|
onboarding_steps = get_next_onboarding_steps(self.user)
|
||||||
|
|
Loading…
Reference in New Issue