compose_validate: Convert module to TypeScript.

This commit is contained in:
Temidayo32 2024-03-06 18:08:03 +00:00 committed by Tim Abbott
parent fd3c7728fc
commit 533a6153b2
3 changed files with 91 additions and 55 deletions

View File

@ -80,7 +80,7 @@ EXEMPT_FILES = make_set(
"web/src/compose_textarea.ts", "web/src/compose_textarea.ts",
"web/src/compose_tooltips.js", "web/src/compose_tooltips.js",
"web/src/compose_ui.ts", "web/src/compose_ui.ts",
"web/src/compose_validate.js", "web/src/compose_validate.ts",
"web/src/composebox_typeahead.js", "web/src/composebox_typeahead.js",
"web/src/condense.ts", "web/src/condense.ts",
"web/src/confirm_dialog.ts", "web/src/confirm_dialog.ts",

View File

@ -1,4 +1,6 @@
import $ from "jquery"; import $ from "jquery";
import assert from "minimalistic-assert";
import {z} from "zod";
import * as resolved_topic from "../shared/src/resolved_topic"; import * as resolved_topic from "../shared/src/resolved_topic";
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs"; import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
@ -26,6 +28,8 @@ import * as settings_data from "./settings_data";
import {current_user, realm} from "./state_data"; import {current_user, realm} from "./state_data";
import * as stream_data from "./stream_data"; import * as stream_data from "./stream_data";
import * as sub_store from "./sub_store"; import * as sub_store from "./sub_store";
import type {StreamSubscription} from "./sub_store";
import type {UserOrMention} from "./typeahead_helper";
import * as util from "./util"; import * as util from "./util";
let user_acknowledged_stream_wildcard = false; let user_acknowledged_stream_wildcard = false;
@ -33,31 +37,42 @@ let upload_in_progress = false;
let message_too_long = false; let message_too_long = false;
let recipient_disallowed = false; let recipient_disallowed = false;
type StreamWildcardOptions = {
stream_id: number;
$banner_container: JQuery;
scheduling_message: boolean;
stream_wildcard_mention: string | null;
};
export let wildcard_mention_threshold = 15; export let wildcard_mention_threshold = 15;
export function set_upload_in_progress(status) { const server_subscription_exists_schema = z.object({
subscribed: z.boolean(),
});
export function set_upload_in_progress(status: boolean): void {
upload_in_progress = status; upload_in_progress = status;
update_send_button_status(); update_send_button_status();
} }
function set_message_too_long(status) { function set_message_too_long(status: boolean): void {
message_too_long = status; message_too_long = status;
update_send_button_status(); update_send_button_status();
} }
export function set_recipient_disallowed(status) { export function set_recipient_disallowed(status: boolean): void {
recipient_disallowed = status; recipient_disallowed = status;
update_send_button_status(); update_send_button_status();
} }
function update_send_button_status() { function update_send_button_status(): void {
$(".message-send-controls").toggleClass( $(".message-send-controls").toggleClass(
"disabled-message-send-controls", "disabled-message-send-controls",
message_too_long || upload_in_progress || recipient_disallowed, message_too_long || upload_in_progress || recipient_disallowed,
); );
} }
export function get_disabled_send_tooltip() { export function get_disabled_send_tooltip(): string {
if (message_too_long) { if (message_too_long) {
return $t({defaultMessage: "Message length shouldn't be greater than 10000 characters."}); return $t({defaultMessage: "Message length shouldn't be greater than 10000 characters."});
} else if (upload_in_progress) { } else if (upload_in_progress) {
@ -66,7 +81,7 @@ export function get_disabled_send_tooltip() {
return ""; return "";
} }
export function needs_subscribe_warning(user_id, stream_id) { export function needs_subscribe_warning(user_id: number, stream_id: number): boolean {
// This returns true if all of these conditions are met: // This returns true if all of these conditions are met:
// * the user is valid // * the user is valid
// * the user is not already subscribed to the stream // * the user is not already subscribed to the stream
@ -100,7 +115,7 @@ export function needs_subscribe_warning(user_id, stream_id) {
return true; return true;
} }
function get_stream_id_for_textarea($textarea) { function get_stream_id_for_textarea($textarea: JQuery<HTMLTextAreaElement>): number | undefined {
// Returns the stream ID, if any, associated with the textarea: // Returns the stream ID, if any, associated with the textarea:
// The recipient of a message being edited, or the target // The recipient of a message being edited, or the target
// recipient of a message being drafted in the compose box. // recipient of a message being drafted in the compose box.
@ -123,7 +138,10 @@ function get_stream_id_for_textarea($textarea) {
return compose_state.stream_id(); return compose_state.stream_id();
} }
export function warn_if_private_stream_is_linked(linked_stream, $textarea) { export function warn_if_private_stream_is_linked(
linked_stream: StreamSubscription,
$textarea: JQuery<HTMLTextAreaElement>,
): void {
const stream_id = get_stream_id_for_textarea($textarea); const stream_id = get_stream_id_for_textarea($textarea);
if (!stream_id) { if (!stream_id) {
@ -184,20 +202,21 @@ export function warn_if_private_stream_is_linked(linked_stream, $textarea) {
} }
} }
export function warn_if_mentioning_unsubscribed_user(mentioned, $textarea) { export function warn_if_mentioning_unsubscribed_user(
mentioned: UserOrMention,
$textarea: JQuery<HTMLTextAreaElement>,
): void {
// Disable for Zephyr mirroring realms, since we never have subscriber lists there // Disable for Zephyr mirroring realms, since we never have subscriber lists there
if (realm.realm_is_zephyr_mirror_realm) { if (realm.realm_is_zephyr_mirror_realm) {
return; return;
} }
const user_id = mentioned.user_id;
if (mentioned.is_broadcast) { if (mentioned.is_broadcast) {
return; // don't check if @all/@everyone/@stream return; // don't check if @all/@everyone/@stream
} }
const user_id = mentioned.user_id;
const stream_id = get_stream_id_for_textarea($textarea); const stream_id = get_stream_id_for_textarea($textarea);
if (!stream_id) { if (!stream_id) {
return; return;
} }
@ -239,12 +258,12 @@ export function warn_if_mentioning_unsubscribed_user(mentioned, $textarea) {
// the warning for composing to a resolved topic, if present. Also clears // the warning for composing to a resolved topic, if present. Also clears
// the state for whether this warning has already been shown in the // the state for whether this warning has already been shown in the
// current narrow. // current narrow.
export function clear_topic_resolved_warning() { export function clear_topic_resolved_warning(): void {
compose_state.set_recipient_viewed_topic_resolved_banner(false); compose_state.set_recipient_viewed_topic_resolved_banner(false);
$(`#compose_banners .${CSS.escape(compose_banner.CLASSNAMES.topic_resolved)}`).remove(); $(`#compose_banners .${CSS.escape(compose_banner.CLASSNAMES.topic_resolved)}`).remove();
} }
export function warn_if_topic_resolved(topic_changed) { export function warn_if_topic_resolved(topic_changed: boolean): void {
// This function is called with topic_changed=false on every // This function is called with topic_changed=false on every
// keypress when typing a message, so it should not do anything // keypress when typing a message, so it should not do anything
// expensive in that case. // expensive in that case.
@ -297,8 +316,9 @@ export function warn_if_topic_resolved(topic_changed) {
} }
} }
export function warn_if_in_search_view() { export function warn_if_in_search_view(): void {
if (narrow_state.filter() && !narrow_state.filter().supports_collapsing_recipients()) { const filter = narrow_state.filter();
if (filter && !filter.supports_collapsing_recipients()) {
const context = { const context = {
banner_type: compose_banner.WARNING, banner_type: compose_banner.WARNING,
banner_text: $t({ banner_text: $t({
@ -314,7 +334,7 @@ export function warn_if_in_search_view() {
} }
} }
function show_stream_wildcard_warnings(opts) { function show_stream_wildcard_warnings(opts: StreamWildcardOptions): void {
const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0; const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0;
const stream_name = sub_store.maybe_get_stream_name(opts.stream_id); const stream_name = sub_store.maybe_get_stream_name(opts.stream_id);
const is_edit_container = opts.$banner_container.closest(".edit_form_banners").length > 0; const is_edit_container = opts.$banner_container.closest(".edit_form_banners").length > 0;
@ -357,16 +377,16 @@ function show_stream_wildcard_warnings(opts) {
user_acknowledged_stream_wildcard = false; user_acknowledged_stream_wildcard = false;
} }
export function clear_stream_wildcard_warnings($banner_container) { export function clear_stream_wildcard_warnings($banner_container: JQuery): void {
const classname = compose_banner.CLASSNAMES.wildcard_warning; const classname = compose_banner.CLASSNAMES.wildcard_warning;
$banner_container.find(`.${CSS.escape(classname)}`).remove(); $banner_container.find(`.${CSS.escape(classname)}`).remove();
} }
export function set_user_acknowledged_stream_wildcard_flag(value) { export function set_user_acknowledged_stream_wildcard_flag(value: boolean): void {
user_acknowledged_stream_wildcard = value; user_acknowledged_stream_wildcard = value;
} }
export function get_invalid_recipient_emails() { export function get_invalid_recipient_emails(): string[] {
const private_recipients = util.extract_pm_recipients( const private_recipients = util.extract_pm_recipients(
compose_state.private_message_recipient(), compose_state.private_message_recipient(),
); );
@ -377,7 +397,10 @@ export function get_invalid_recipient_emails() {
return invalid_recipients; return invalid_recipients;
} }
function check_unsubscribed_stream_for_send(stream_name, autosubscribe) { function check_unsubscribed_stream_for_send(
stream_name: string,
autosubscribe: boolean,
): string | undefined {
let result; let result;
if (!autosubscribe) { if (!autosubscribe) {
return "not-subscribed"; return "not-subscribed";
@ -387,18 +410,19 @@ function check_unsubscribed_stream_for_send(stream_name, autosubscribe) {
// *Synchronously* try to subscribe to the stream before sending // *Synchronously* try to subscribe to the stream before sending
// the message. This is deprecated and we hope to remove it; see // the message. This is deprecated and we hope to remove it; see
// #4650. // #4650.
channel.post({ void channel.post({
url: "/json/subscriptions/exists", url: "/json/subscriptions/exists",
data: {stream: stream_name, autosubscribe: true}, data: {stream: stream_name, autosubscribe: true},
async: false, async: false,
success(data) { success(data) {
if (data.subscribed) { const clean_data = server_subscription_exists_schema.parse(data);
if (clean_data.subscribed) {
result = "subscribed"; result = "subscribed";
} else { } else {
result = "not-subscribed"; result = "not-subscribed";
} }
}, },
error(xhr) { error(xhr: JQuery.jqXHR) {
if (xhr.status === 404) { if (xhr.status === 404) {
result = "does-not-exist"; result = "does-not-exist";
} else { } else {
@ -409,14 +433,18 @@ function check_unsubscribed_stream_for_send(stream_name, autosubscribe) {
return result; return result;
} }
function is_recipient_large_stream() { function is_recipient_large_stream(): boolean {
return ( const stream_id = compose_state.stream_id();
compose_state.stream_id() && if (stream_id === undefined) {
peer_data.get_subscriber_count(compose_state.stream_id()) > wildcard_mention_threshold return false;
); }
return peer_data.get_subscriber_count(stream_id) > wildcard_mention_threshold;
} }
export function topic_participant_count_more_than_threshold(stream_id, topic) { export function topic_participant_count_more_than_threshold(
stream_id: number,
topic: string,
): boolean {
// Topic participants: // Topic participants:
// Users who either sent or reacted to the messages in the topic. // Users who either sent or reacted to the messages in the topic.
const participant_ids = new Set(); const participant_ids = new Set();
@ -455,17 +483,15 @@ export function topic_participant_count_more_than_threshold(stream_id, topic) {
return false; return false;
} }
function is_recipient_large_topic() { function is_recipient_large_topic(): boolean {
return ( const stream_id = compose_state.stream_id();
compose_state.stream_id() && if (stream_id === undefined) {
topic_participant_count_more_than_threshold( return false;
compose_state.stream_id(), }
compose_state.topic(), return topic_participant_count_more_than_threshold(stream_id, compose_state.topic());
)
);
} }
function wildcard_mention_policy_authorizes_user() { function wildcard_mention_policy_authorizes_user(): boolean {
if ( if (
realm.realm_wildcard_mention_policy === realm.realm_wildcard_mention_policy ===
settings_config.wildcard_mention_policy_values.by_everyone.code settings_config.wildcard_mention_policy_values.by_everyone.code
@ -500,8 +526,8 @@ function wildcard_mention_policy_authorizes_user() {
return true; return true;
} }
const person = people.get_by_user_id(current_user.user_id); const person = people.get_by_user_id(current_user.user_id);
const current_datetime = new Date(Date.now()); const current_datetime = new Date(Date.now()).getTime();
const person_date_joined = new Date(person.date_joined); const person_date_joined = new Date(person.date_joined).getTime();
const days = (current_datetime - person_date_joined) / 1000 / 86400; const days = (current_datetime - person_date_joined) / 1000 / 86400;
return days >= realm.realm_waiting_period_threshold && !current_user.is_guest; return days >= realm.realm_waiting_period_threshold && !current_user.is_guest;
@ -509,19 +535,19 @@ function wildcard_mention_policy_authorizes_user() {
return !current_user.is_guest; return !current_user.is_guest;
} }
export function stream_wildcard_mention_allowed() { export function stream_wildcard_mention_allowed(): boolean {
return !is_recipient_large_stream() || wildcard_mention_policy_authorizes_user(); return !is_recipient_large_stream() || wildcard_mention_policy_authorizes_user();
} }
export function topic_wildcard_mention_allowed() { export function topic_wildcard_mention_allowed(): boolean {
return !is_recipient_large_topic() || wildcard_mention_policy_authorizes_user(); return !is_recipient_large_topic() || wildcard_mention_policy_authorizes_user();
} }
export function set_wildcard_mention_threshold(value) { export function set_wildcard_mention_threshold(value: number): void {
wildcard_mention_threshold = value; wildcard_mention_threshold = value;
} }
export function validate_stream_message_mentions(opts) { export function validate_stream_message_mentions(opts: StreamWildcardOptions): boolean {
const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0; const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0;
// If the user is attempting to do a wildcard mention in a large // If the user is attempting to do a wildcard mention in a large
@ -558,7 +584,7 @@ export function validate_stream_message_mentions(opts) {
return true; return true;
} }
export function validation_error(error_type, stream_name) { export function validation_error(error_type: string, stream_name: string): boolean {
const $banner_container = $("#compose_banners"); const $banner_container = $("#compose_banners");
switch (error_type) { switch (error_type) {
case "does-not-exist": case "does-not-exist":
@ -580,6 +606,8 @@ export function validation_error(error_type, stream_name) {
return false; return false;
} }
const sub = stream_data.get_sub(stream_name); const sub = stream_data.get_sub(stream_name);
// We expect this to be a does-not-exist error if it was undefined.
assert(sub !== undefined);
const new_row_html = render_compose_banner({ const new_row_html = render_compose_banner({
banner_type: compose_banner.ERROR, banner_type: compose_banner.ERROR,
banner_text: $t({ banner_text: $t({
@ -601,16 +629,17 @@ export function validation_error(error_type, stream_name) {
return true; return true;
} }
export function validate_stream_message_address_info(stream_name) { export function validate_stream_message_address_info(stream_name: string): boolean {
if (stream_data.is_subscribed_by_name(stream_name)) { if (stream_data.is_subscribed_by_name(stream_name)) {
return true; return true;
} }
const autosubscribe = page_params.narrow_stream !== undefined; const autosubscribe = page_params.narrow_stream !== undefined;
const error_type = check_unsubscribed_stream_for_send(stream_name, autosubscribe); const error_type = check_unsubscribed_stream_for_send(stream_name, autosubscribe);
assert(error_type !== undefined);
return validation_error(error_type, stream_name); return validation_error(error_type, stream_name);
} }
function validate_stream_message(scheduling_message) { function validate_stream_message(scheduling_message: boolean): boolean {
const stream_id = compose_state.stream_id(); const stream_id = compose_state.stream_id();
const $banner_container = $("#compose_banners"); const $banner_container = $("#compose_banners");
if (stream_id === undefined) { if (stream_id === undefined) {
@ -640,7 +669,7 @@ function validate_stream_message(scheduling_message) {
const sub = stream_data.get_sub_by_id(stream_id); const sub = stream_data.get_sub_by_id(stream_id);
if (!sub) { if (!sub) {
return validation_error("does-not-exist", stream_id); return validation_error("does-not-exist", stream_id.toString());
} }
if (!stream_data.can_post_messages_in_stream(sub)) { if (!stream_data.can_post_messages_in_stream(sub)) {
@ -675,7 +704,7 @@ function validate_stream_message(scheduling_message) {
// 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() { function validate_private_message(): boolean {
const user_ids = compose_pm_pill.get_user_ids(); const user_ids = compose_pm_pill.get_user_ids();
const $banner_container = $("#compose_banners"); const $banner_container = $("#compose_banners");
@ -744,7 +773,7 @@ function validate_private_message() {
return true; return true;
} }
export function check_overflow_text() { export function check_overflow_text(): number {
// This function is called when typing every character in the // This function is called when typing every character in the
// compose box, so it's important that it not doing anything // compose box, so it's important that it not doing anything
// expensive. // expensive.
@ -795,7 +824,7 @@ export function check_overflow_text() {
return text.length; return text.length;
} }
export function validate_message_length() { export function validate_message_length(): boolean {
if (compose_state.message_content().length > realm.max_message_length) { if (compose_state.message_content().length > realm.max_message_length) {
$("textarea#compose-textarea").addClass("flash"); $("textarea#compose-textarea").addClass("flash");
setTimeout(() => $("textarea#compose-textarea").removeClass("flash"), 1500); setTimeout(() => $("textarea#compose-textarea").removeClass("flash"), 1500);
@ -804,7 +833,7 @@ export function validate_message_length() {
return true; return true;
} }
export function validate(scheduling_message) { export function validate(scheduling_message: boolean): boolean {
const message_content = compose_state.message_content(); const message_content = compose_state.message_content();
if (/^\s*$/.test(message_content)) { if (/^\s*$/.test(message_content)) {
$("textarea#compose-textarea").toggleClass("invalid", true); $("textarea#compose-textarea").toggleClass("invalid", true);
@ -832,7 +861,11 @@ export function validate(scheduling_message) {
return validate_stream_message(scheduling_message); return validate_stream_message(scheduling_message);
} }
export function convert_mentions_to_silent_in_direct_messages(mention_text, full_name, user_id) { export function convert_mentions_to_silent_in_direct_messages(
mention_text: string,
full_name: string,
user_id: number,
): string {
if (compose_state.get_message_type() !== "private") { if (compose_state.get_message_type() !== "private") {
return mention_text; return mention_text;
} }

View File

@ -106,6 +106,7 @@ export const realm_schema = z.object({
max_avatar_file_size_mib: z.number(), max_avatar_file_size_mib: z.number(),
max_icon_file_size_mib: z.number(), max_icon_file_size_mib: z.number(),
max_logo_file_size_mib: z.number(), max_logo_file_size_mib: z.number(),
max_message_length: z.number(),
max_topic_length: z.number(), max_topic_length: z.number(),
realm_add_custom_emoji_policy: z.number(), realm_add_custom_emoji_policy: z.number(),
realm_allow_edit_history: z.boolean(), realm_allow_edit_history: z.boolean(),
@ -150,6 +151,7 @@ export const realm_schema = z.object({
realm_jitsi_server_url: z.nullable(z.string()), realm_jitsi_server_url: z.nullable(z.string()),
realm_logo_source: z.string(), realm_logo_source: z.string(),
realm_logo_url: z.string(), realm_logo_url: z.string(),
realm_mandatory_topics: z.boolean(),
realm_move_messages_between_streams_policy: z.number(), realm_move_messages_between_streams_policy: z.number(),
realm_name_changes_disabled: z.boolean(), realm_name_changes_disabled: z.boolean(),
realm_name: z.string(), realm_name: z.string(),
@ -166,6 +168,7 @@ export const realm_schema = z.object({
realm_user_group_edit_policy: z.number(), realm_user_group_edit_policy: z.number(),
realm_video_chat_provider: z.number(), realm_video_chat_provider: z.number(),
realm_waiting_period_threshold: z.number(), realm_waiting_period_threshold: z.number(),
realm_wildcard_mention_policy: z.number(),
server_avatar_changes_disabled: z.boolean(), server_avatar_changes_disabled: z.boolean(),
server_jitsi_server_url: z.nullable(z.string()), server_jitsi_server_url: z.nullable(z.string()),
server_name_changes_disabled: z.boolean(), server_name_changes_disabled: z.boolean(),