mirror of
https://github.com/zulip/zulip.git
synced 2025-11-12 18:06:44 +00:00
compose: Add tooltip support for invalid messages.
This commit adds tooltip support for various invalid conditions mentioned in issue 32115. A `show_banner` positional argument is added in the `validate` method which has a default value of true. The reason behind introducing this is to not trigger banners on hovering the disabled send button, since the tooltip message is also determined using the same validate method. We want to only disable the button on hover, which is why the update_send_button_status() method is called only on "mouseenter" event, which is added to the send button in compose_setup.js To incorporate this change a new param is introduced which determines whether to enable/disable send_button by running update_send_button_status Earlier, typing something in the textarea or recipient box would also trigger `update_send_button_status` which doesn't work well since we've introduced a lot of new booleans which determine whether send button gets disabled causing send button to get disabled while typing instead while hovering Hence this change.
This commit is contained in:
committed by
Tim Abbott
parent
8496a40564
commit
0b12c51771
@@ -70,6 +70,14 @@ export function initialize() {
|
|||||||
compose_ui.handle_keyup(event, $("textarea#compose-textarea").expectOne());
|
compose_ui.handle_keyup(event, $("textarea#compose-textarea").expectOne());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#compose-send-button").on("mouseenter", () => {
|
||||||
|
compose_validate.validate(false, false);
|
||||||
|
compose_validate.update_send_button_status();
|
||||||
|
});
|
||||||
|
$("#compose-send-button").on("mouseleave", () => {
|
||||||
|
$(".message-send-controls").removeClass("disabled-message-send-controls");
|
||||||
|
});
|
||||||
|
|
||||||
$("textarea#compose-textarea").on("input propertychange", () => {
|
$("textarea#compose-textarea").on("input propertychange", () => {
|
||||||
compose_validate.warn_if_topic_resolved(false);
|
compose_validate.warn_if_topic_resolved(false);
|
||||||
const compose_text_length = compose_validate.check_overflow_text($("#send_message_form"));
|
const compose_text_length = compose_validate.check_overflow_text($("#send_message_form"));
|
||||||
|
|||||||
@@ -275,6 +275,12 @@ export function initialize(): void {
|
|||||||
target: ".disabled-message-send-controls",
|
target: ".disabled-message-send-controls",
|
||||||
// 350px at 14px/1em
|
// 350px at 14px/1em
|
||||||
maxWidth: "25em",
|
maxWidth: "25em",
|
||||||
|
onTrigger(instance) {
|
||||||
|
instance.setContent(
|
||||||
|
compose_recipient.get_posting_policy_error_message() ||
|
||||||
|
compose_validate.get_disabled_send_tooltip(),
|
||||||
|
);
|
||||||
|
},
|
||||||
content: () =>
|
content: () =>
|
||||||
compose_recipient.get_posting_policy_error_message() ||
|
compose_recipient.get_posting_policy_error_message() ||
|
||||||
compose_validate.get_disabled_send_tooltip(),
|
compose_validate.get_disabled_send_tooltip(),
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ import * as util from "./util.ts";
|
|||||||
|
|
||||||
let user_acknowledged_stream_wildcard = false;
|
let user_acknowledged_stream_wildcard = false;
|
||||||
let upload_in_progress = false;
|
let upload_in_progress = false;
|
||||||
|
let no_channel_selected = false;
|
||||||
|
let missing_topic = false;
|
||||||
|
let no_private_recipient = true;
|
||||||
|
let no_message_content = false;
|
||||||
let message_too_long = false;
|
let message_too_long = false;
|
||||||
let recipient_disallowed = false;
|
let recipient_disallowed = false;
|
||||||
|
|
||||||
@@ -63,9 +67,24 @@ export function set_upload_in_progress(status: boolean): void {
|
|||||||
update_send_button_status();
|
update_send_button_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_no_channel_selected(status: boolean): void {
|
||||||
|
no_channel_selected = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_missing_topic(status: boolean): void {
|
||||||
|
missing_topic = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_missing_direct_message_recipient(status: boolean): void {
|
||||||
|
no_private_recipient = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_no_message_content(status: boolean): void {
|
||||||
|
no_message_content = status;
|
||||||
|
}
|
||||||
|
|
||||||
function set_message_too_long_for_compose(status: boolean): void {
|
function set_message_too_long_for_compose(status: boolean): void {
|
||||||
message_too_long = status;
|
message_too_long = status;
|
||||||
update_send_button_status();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_message_too_long_for_edit(status: boolean, $container: JQuery): void {
|
function set_message_too_long_for_edit(status: boolean, $container: JQuery): void {
|
||||||
@@ -81,18 +100,33 @@ function set_message_too_long_for_edit(status: boolean, $container: JQuery): voi
|
|||||||
|
|
||||||
export function set_recipient_disallowed(status: boolean): void {
|
export function set_recipient_disallowed(status: boolean): void {
|
||||||
recipient_disallowed = status;
|
recipient_disallowed = status;
|
||||||
update_send_button_status();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_send_button_status(): void {
|
export function update_send_button_status(): void {
|
||||||
|
const recipient_type = compose_state.get_message_type();
|
||||||
$(".message-send-controls").toggleClass(
|
$(".message-send-controls").toggleClass(
|
||||||
"disabled-message-send-controls",
|
"disabled-message-send-controls",
|
||||||
message_too_long || upload_in_progress || recipient_disallowed,
|
upload_in_progress ||
|
||||||
|
no_channel_selected ||
|
||||||
|
(missing_topic && recipient_type === "stream") ||
|
||||||
|
(no_private_recipient && recipient_type === "private") ||
|
||||||
|
message_too_long ||
|
||||||
|
recipient_disallowed ||
|
||||||
|
no_message_content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_disabled_send_tooltip(): string {
|
export function get_disabled_send_tooltip(): string {
|
||||||
if (message_too_long) {
|
const recipient_type = compose_state.get_message_type();
|
||||||
|
if (no_channel_selected && recipient_type === "stream") {
|
||||||
|
return NO_CHANNEL_SELECTED_ERROR_MESSAGE;
|
||||||
|
} else if (missing_topic && recipient_type === "stream") {
|
||||||
|
return TOPICS_REQUIRED_ERROR_MESSAGE;
|
||||||
|
} else if (no_private_recipient && recipient_type === "private") {
|
||||||
|
return NO_PRIVATE_RECIPIENT_ERROR_MESSAGE;
|
||||||
|
} else if (no_message_content) {
|
||||||
|
return NO_MESSAGE_CONTENT_ERROR_MESSAGE;
|
||||||
|
} else if (message_too_long) {
|
||||||
return get_message_too_long_for_compose_error();
|
return get_message_too_long_for_compose_error();
|
||||||
} else if (upload_in_progress) {
|
} else if (upload_in_progress) {
|
||||||
return $t({defaultMessage: "Cannot send message while files are being uploaded."});
|
return $t({defaultMessage: "Cannot send message while files are being uploaded."});
|
||||||
@@ -649,16 +683,18 @@ export function validate_stream_message_address_info(sub: StreamSubscription): b
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate_stream_message(scheduling_message: boolean): boolean {
|
function validate_stream_message(scheduling_message: boolean, show_banner = true): boolean {
|
||||||
const stream_id = compose_state.stream_id();
|
|
||||||
const $banner_container = $("#compose_banners");
|
const $banner_container = $("#compose_banners");
|
||||||
|
const stream_id = compose_state.stream_id();
|
||||||
const no_channel_selected = stream_id === undefined;
|
const no_channel_selected = stream_id === undefined;
|
||||||
|
set_no_channel_selected(no_channel_selected);
|
||||||
if (no_channel_selected) {
|
if (no_channel_selected) {
|
||||||
compose_banner.show_error_message(
|
report_validation_error(
|
||||||
NO_CHANNEL_SELECTED_ERROR_MESSAGE,
|
NO_CHANNEL_SELECTED_ERROR_MESSAGE,
|
||||||
compose_banner.CLASSNAMES.missing_stream,
|
compose_banner.CLASSNAMES.missing_stream,
|
||||||
$banner_container,
|
$banner_container,
|
||||||
$("#compose_select_recipient_widget_wrapper"),
|
$("#compose_select_recipient_widget_wrapper"),
|
||||||
|
show_banner,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -666,12 +702,14 @@ function validate_stream_message(scheduling_message: boolean): boolean {
|
|||||||
if (realm.realm_mandatory_topics) {
|
if (realm.realm_mandatory_topics) {
|
||||||
const topic = compose_state.topic();
|
const topic = compose_state.topic();
|
||||||
const missing_topic = topic === "";
|
const missing_topic = topic === "";
|
||||||
|
set_missing_topic(missing_topic);
|
||||||
if (missing_topic) {
|
if (missing_topic) {
|
||||||
compose_banner.show_error_message(
|
report_validation_error(
|
||||||
TOPICS_REQUIRED_ERROR_MESSAGE,
|
TOPICS_REQUIRED_ERROR_MESSAGE,
|
||||||
compose_banner.CLASSNAMES.topic_missing,
|
compose_banner.CLASSNAMES.topic_missing,
|
||||||
$banner_container,
|
$banner_container,
|
||||||
$("input#stream_message_recipient_topic"),
|
$("input#stream_message_recipient_topic"),
|
||||||
|
show_banner,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -715,18 +753,20 @@ function validate_stream_message(scheduling_message: boolean): boolean {
|
|||||||
|
|
||||||
// The function checks whether the recipients are users of the realm or cross realm users (bots
|
// The function checks whether the recipients are users of the realm or cross realm users (bots
|
||||||
// for now)
|
// for now)
|
||||||
function validate_private_message(): boolean {
|
function validate_private_message(show_banner = true): boolean {
|
||||||
const user_ids = compose_pm_pill.get_user_ids();
|
const user_ids = compose_pm_pill.get_user_ids();
|
||||||
const user_ids_string = util.sorted_ids(user_ids).join(",");
|
const user_ids_string = util.sorted_ids(user_ids).join(",");
|
||||||
const $banner_container = $("#compose_banners");
|
const $banner_container = $("#compose_banners");
|
||||||
const missing_direct_message_recipient = compose_state.private_message_recipient().length === 0;
|
const missing_direct_message_recipient = compose_state.private_message_recipient().length === 0;
|
||||||
|
|
||||||
|
set_missing_direct_message_recipient(missing_direct_message_recipient);
|
||||||
if (missing_direct_message_recipient) {
|
if (missing_direct_message_recipient) {
|
||||||
compose_banner.show_error_message(
|
report_validation_error(
|
||||||
NO_PRIVATE_RECIPIENT_ERROR_MESSAGE,
|
NO_PRIVATE_RECIPIENT_ERROR_MESSAGE,
|
||||||
compose_banner.CLASSNAMES.missing_private_message_recipient,
|
compose_banner.CLASSNAMES.missing_private_message_recipient,
|
||||||
$banner_container,
|
$banner_container,
|
||||||
$("#private_message_recipient"),
|
$("#private_message_recipient"),
|
||||||
|
show_banner,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
} else if (realm.realm_is_zephyr_mirror_realm) {
|
} else if (realm.realm_is_zephyr_mirror_realm) {
|
||||||
@@ -837,7 +877,7 @@ export function check_overflow_text($container: JQuery): number {
|
|||||||
return text.length;
|
return text.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validate_message_length($container: JQuery): boolean {
|
export function validate_message_length($container: JQuery, trigger_flash = true): boolean {
|
||||||
const $textarea = $container.find<HTMLTextAreaElement>(".message-textarea");
|
const $textarea = $container.find<HTMLTextAreaElement>(".message-textarea");
|
||||||
// Match the behavior of compose_state.message_content of trimming trailing whitespace
|
// Match the behavior of compose_state.message_content of trimming trailing whitespace
|
||||||
const text = $textarea.val()!.trimEnd();
|
const text = $textarea.val()!.trimEnd();
|
||||||
@@ -848,33 +888,53 @@ export function validate_message_length($container: JQuery): boolean {
|
|||||||
set_message_too_long_for_compose(message_too_long_for_compose);
|
set_message_too_long_for_compose(message_too_long_for_compose);
|
||||||
|
|
||||||
if (message_too_long_for_compose) {
|
if (message_too_long_for_compose) {
|
||||||
|
if (trigger_flash) {
|
||||||
$textarea.addClass("flash");
|
$textarea.addClass("flash");
|
||||||
setTimeout(() => $textarea.removeClass("flash"), 1500);
|
setTimeout(() => $textarea.removeClass("flash"), 1500);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validate(scheduling_message: boolean): boolean {
|
function report_validation_error(
|
||||||
|
message: string,
|
||||||
|
classname: string,
|
||||||
|
$container: JQuery,
|
||||||
|
$bad_input: JQuery,
|
||||||
|
show_banner: boolean,
|
||||||
|
precursor?: () => void,
|
||||||
|
): void {
|
||||||
|
if (show_banner) {
|
||||||
|
if (precursor) {
|
||||||
|
precursor();
|
||||||
|
}
|
||||||
|
compose_banner.show_error_message(message, classname, $container, $bad_input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function validate(scheduling_message: boolean, show_banner = true): boolean {
|
||||||
const message_content = compose_state.message_content();
|
const message_content = compose_state.message_content();
|
||||||
// The validation checks in this function are in a specific priority order. Don't
|
// The validation checks in this function are in a specific priority order. Don't
|
||||||
// change their order unless you want to change which priority they're shown in.
|
// change their order unless you want to change which priority they're shown in.
|
||||||
|
|
||||||
if (
|
if (
|
||||||
compose_state.get_message_type() !== "private" &&
|
compose_state.get_message_type() !== "private" &&
|
||||||
!validate_stream_message(scheduling_message)
|
!validate_stream_message(scheduling_message, show_banner)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compose_state.get_message_type() === "private" && !validate_private_message()) {
|
if (compose_state.get_message_type() === "private" && !validate_private_message(show_banner)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const no_message_content = /^\s*$/.test(message_content);
|
const no_message_content = /^\s*$/.test(message_content);
|
||||||
|
set_no_message_content(no_message_content);
|
||||||
if (no_message_content) {
|
if (no_message_content) {
|
||||||
|
if (show_banner) {
|
||||||
$("textarea#compose-textarea").toggleClass("invalid", true);
|
$("textarea#compose-textarea").toggleClass("invalid", true);
|
||||||
$("textarea#compose-textarea").trigger("focus");
|
$("textarea#compose-textarea").trigger("focus");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,7 +949,9 @@ export function validate(scheduling_message: boolean): boolean {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!validate_message_length($("#send_message_form"))) {
|
// TODO: This doesn't actually show a banner, it triggers a flash
|
||||||
|
const trigger_flash = show_banner;
|
||||||
|
if (!validate_message_length($("#send_message_form"), trigger_flash)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class FakeComposeBox {
|
|||||||
|
|
||||||
assert.ok(this.$content_textarea.hasClass("textarea-over-limit"));
|
assert.ok(this.$content_textarea.hasClass("textarea-over-limit"));
|
||||||
assert.ok($(".message-limit-indicator").hasClass("textarea-over-limit"));
|
assert.ok($(".message-limit-indicator").hasClass("textarea-over-limit"));
|
||||||
assert.ok($(".message-send-controls").hasClass("disabled-message-send-controls"));
|
assert.ok(!$(".message-send-controls").hasClass("disabled-message-send-controls"));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_message_size_is_under_the_limit(desired_html) {
|
assert_message_size_is_under_the_limit(desired_html) {
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ test("upload_files", async ({mock_template, override, override_rewire}) => {
|
|||||||
banner_shown = true;
|
banner_shown = true;
|
||||||
return "<banner-stub>";
|
return "<banner-stub>";
|
||||||
});
|
});
|
||||||
|
override(compose_state, "get_message_type", () => "stream");
|
||||||
await upload.upload_files(uppy, config, files);
|
await upload.upload_files(uppy, config, files);
|
||||||
assert.ok($(".message-send-controls").hasClass("disabled-message-send-controls"));
|
assert.ok($(".message-send-controls").hasClass("disabled-message-send-controls"));
|
||||||
assert.ok(banner_shown);
|
assert.ok(banner_shown);
|
||||||
@@ -455,7 +456,7 @@ test("copy_paste", ({override, override_rewire}) => {
|
|||||||
assert.equal(upload_files_called, false);
|
assert.equal(upload_files_called, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uppy_events", ({override_rewire, mock_template}) => {
|
test("uppy_events", ({override, override_rewire, mock_template}) => {
|
||||||
$("#compose_banners .upload_banner .moving_bar").css = noop;
|
$("#compose_banners .upload_banner .moving_bar").css = noop;
|
||||||
$("#compose_banners .upload_banner").length = 0;
|
$("#compose_banners .upload_banner").length = 0;
|
||||||
override_rewire(compose_ui, "smart_insert_inline", noop);
|
override_rewire(compose_ui, "smart_insert_inline", noop);
|
||||||
@@ -517,6 +518,7 @@ test("uppy_events", ({override_rewire, mock_template}) => {
|
|||||||
override_rewire(compose_ui, "autosize_textarea", () => {
|
override_rewire(compose_ui, "autosize_textarea", () => {
|
||||||
compose_ui_autosize_textarea_called = true;
|
compose_ui_autosize_textarea_called = true;
|
||||||
});
|
});
|
||||||
|
override(compose_state, "get_message_type", () => "stream");
|
||||||
on_upload_success_callback(file, response);
|
on_upload_success_callback(file, response);
|
||||||
|
|
||||||
assert.ok(compose_ui_replace_syntax_called);
|
assert.ok(compose_ui_replace_syntax_called);
|
||||||
|
|||||||
Reference in New Issue
Block a user