compose_setup: Convert module to typescript.

This commit is contained in:
Evy Kassirer
2025-09-19 11:15:55 -07:00
committed by Tim Abbott
parent 02f1a624f7
commit 7c90d2cee5
7 changed files with 79 additions and 70 deletions

View File

@@ -230,10 +230,10 @@ export let complete_starting_tasks = (opts: ComposeActionsOpts): void => {
if (is_new_topic_triggered) {
compose_recipient.set_high_attention_recipient_row();
}
// We explicitly call this function here apart from compose_setup.js
// We explicitly call this function here apart from compose_setup.ts
// 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.
// the function call in compose_setup.ts not displaying banner.
if (!narrow_state.narrowed_by_reply()) {
compose_notifications.maybe_show_one_time_interleaved_view_messages_fading_banner();
}

View File

@@ -79,7 +79,7 @@ export let update_recipient_row_attention_level = (): void => {
// row is focused, that puts users outside the low-attention
// recipient-row state--including the `c` hotkey or the
// Start new conversation button being clicked. But that
// logic is handled via the event handlers in compose_setup.js
// logic is handled via the event handlers in compose_setup.ts
// that call set_high_attention_recipient_row().
if (
(composing_to_current_topic_narrow() ||

View File

@@ -1,5 +1,7 @@
import $ from "jquery";
import _ from "lodash";
import assert from "minimalistic-assert";
import * as z from "zod/mini";
import {unresolve_name} from "../shared/src/resolved_topic.ts";
import render_add_poll_modal from "../templates/add_poll_modal.hbs";
@@ -41,11 +43,11 @@ import * as user_topics from "./user_topics.ts";
import * as util from "./util.ts";
import * as widget_modal from "./widget_modal.ts";
export function abort_xhr() {
export function abort_xhr(): void {
upload.compose_upload_cancel();
}
function setup_compose_actions_hooks() {
function setup_compose_actions_hooks(): void {
compose_actions.register_compose_box_clear_hook(compose.clear_invites);
compose_actions.register_compose_box_clear_hook(compose.clear_private_stream_alert);
compose_actions.register_compose_box_clear_hook(compose.clear_preview_area);
@@ -54,7 +56,7 @@ function setup_compose_actions_hooks() {
compose_actions.register_compose_cancel_hook(compose_call.abort_video_callbacks);
}
export function initialize() {
export function initialize(): void {
// Register hooks for compose_actions.
setup_compose_actions_hooks();
@@ -66,10 +68,16 @@ export function initialize() {
);
$("textarea#compose-textarea").on("keydown", (event) => {
compose_ui.handle_keydown(event, $("textarea#compose-textarea").expectOne());
compose_ui.handle_keydown(
event,
$<HTMLTextAreaElement>("textarea#compose-textarea").expectOne(),
);
});
$("textarea#compose-textarea").on("keyup", (event) => {
compose_ui.handle_keyup(event, $("textarea#compose-textarea").expectOne());
compose_ui.handle_keyup(
event,
$<HTMLTextAreaElement>("textarea#compose-textarea").expectOne(),
);
});
$("textarea#compose-textarea").on("input", () => {
@@ -116,9 +124,12 @@ export function initialize() {
resize.reset_compose_message_max_height();
});
});
update_compose_max_height.observe(document.querySelector("#compose"));
update_compose_max_height.observe(document.querySelector("#compose")!);
function get_input_info(event) {
function get_input_info(event: JQuery.ClickEvent): {
is_edit_input: boolean;
$banner_container: JQuery;
} {
const $edit_banners_container = $(event.target).closest(".edit_form_banners");
const is_edit_input = $edit_banners_container.length > 0;
const $banner_container = is_edit_input ? $edit_banners_container : $("#compose_banners");
@@ -133,11 +144,12 @@ export function initialize() {
(event) => {
event.preventDefault();
const {$banner_container, is_edit_input} = get_input_info(event);
assert(event.target instanceof HTMLElement);
const $row = $(event.target).closest(".message_row");
compose_validate.clear_stream_wildcard_warnings($banner_container);
compose_validate.set_user_acknowledged_stream_wildcard_flag(true);
if (is_edit_input) {
message_edit.save_message_row_edit($row);
void message_edit.save_message_row_edit($row);
} else if (event.target.dataset.validationTrigger === "schedule") {
compose_send_menu_popover.open_schedule_message_menu(
undefined,
@@ -168,6 +180,7 @@ export function initialize() {
return;
}
const sub = stream_data.get_sub_by_id(stream_id);
assert(sub !== undefined);
stream_settings_components.sub_or_unsub(sub);
$(user_not_subscribed_selector).remove();
},
@@ -180,8 +193,8 @@ export function initialize() {
event.preventDefault();
const $target = $(event.target).parents(".main-view-banner");
const stream_id = Number.parseInt($target.attr("data-stream-id"), 10);
const topic_name = $target.attr("data-topic-name");
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) => {
if (message_id === undefined) {
@@ -214,7 +227,7 @@ export function initialize() {
} else {
message_edit.toggle_resolve_topic(message_id, topic_name, true);
}
compose_validate.clear_topic_resolved_warning(true);
compose_validate.clear_topic_resolved_warning();
});
},
);
@@ -228,8 +241,8 @@ export function initialize() {
event.preventDefault();
const $target = $(event.target).parents(".main-view-banner");
const stream_id = Number.parseInt($target.attr("data-stream-id"), 10);
const topic_name = $target.attr("data-topic-name");
const stream_id = Number.parseInt($target.attr("data-stream-id")!, 10);
const topic_name = $target.attr("data-topic-name")!;
user_topics.set_user_topic_visibility_policy(
stream_id,
@@ -248,9 +261,7 @@ export function initialize() {
(event) => {
event.preventDefault();
if ($(event.target).attr("data-action") === "mark-as-read") {
$(event.target)
.parents(`${automatic_new_visibility_policy_banner_selector}`)
.remove();
$(event.target).parents(automatic_new_visibility_policy_banner_selector).remove();
onboarding_steps.post_onboarding_step_as_read("visibility_policy_banner");
return;
}
@@ -266,6 +277,10 @@ export function initialize() {
(event) => {
event.preventDefault();
const send_at_timestamp = scheduled_messages.get_selected_send_later_timestamp();
// When clicking the button to reschedule, the send later timestamp from the
// recently unscheduled message is saved in `selected_send_later_timestamp` and
// won't be undefined.
assert(send_at_timestamp !== undefined);
compose_send_menu_popover.do_schedule_message(send_at_timestamp);
},
);
@@ -283,14 +298,15 @@ export function initialize() {
const user_id = Number($invite_row.attr("data-user-id"));
const stream_id = Number($invite_row.attr("data-stream-id"));
function success() {
function success(): void {
$invite_row.remove();
}
function xhr_failure(xhr) {
function xhr_failure(xhr: JQuery.jqXHR<unknown>): void {
let error_message = "Failed to subscribe user!";
if (xhr.responseJSON?.msg) {
error_message = xhr.responseJSON.msg;
const parsed = z.object({msg: z.string()}).safeParse(xhr.responseJSON);
if (parsed.success) {
error_message = parsed.data.msg;
}
compose.clear_invites();
compose_banner.show_error_message(
@@ -303,6 +319,7 @@ export function initialize() {
}
const sub = sub_store.get(stream_id);
assert(sub !== undefined);
subscriber_api.add_user_ids_to_stream([user_id], sub, true, success, xhr_failure);
},
@@ -323,7 +340,7 @@ export function initialize() {
`${jump_to_conversation_banner_selector} .main-view-banner-action-button`,
(event) => {
event.preventDefault();
$(event.target).parents(`${jump_to_conversation_banner_selector}`).remove();
$(event.target).parents(jump_to_conversation_banner_selector).remove();
onboarding_steps.post_onboarding_step_as_read("jump_to_conversation_banner");
},
);
@@ -334,9 +351,7 @@ export function initialize() {
`${non_interleaved_view_messages_fading_banner_selector} .main-view-banner-action-button`,
(event) => {
event.preventDefault();
$(event.target)
.parents(`${non_interleaved_view_messages_fading_banner_selector}`)
.remove();
$(event.target).parents(non_interleaved_view_messages_fading_banner_selector).remove();
onboarding_steps.post_onboarding_step_as_read("non_interleaved_view_messages_fading");
},
);
@@ -347,7 +362,7 @@ export function initialize() {
`${interleaved_view_messages_fading_banner_selector} .main-view-banner-action-button`,
(event) => {
event.preventDefault();
$(event.target).parents(`${interleaved_view_messages_fading_banner_selector}`).remove();
$(event.target).parents(interleaved_view_messages_fading_banner_selector).remove();
onboarding_steps.post_onboarding_step_as_read("interleaved_view_messages_fading");
},
);
@@ -370,7 +385,7 @@ export function initialize() {
$("#compose .file_input").trigger("click");
});
$("body").on("click", ".video_link", (e) => {
$("body").on("click", ".video_link", function (this: HTMLElement, e): void {
e.preventDefault();
e.stopPropagation();
@@ -380,10 +395,10 @@ export function initialize() {
return;
}
compose_call_ui.generate_and_insert_audio_or_video_call_link($(e.target), false);
compose_call_ui.generate_and_insert_audio_or_video_call_link($(this), false);
});
$("body").on("click", ".audio_link", (e) => {
$("body").on("click", ".audio_link", function (this: HTMLElement, e): void {
e.preventDefault();
e.stopPropagation();
@@ -393,31 +408,34 @@ export function initialize() {
return;
}
compose_call_ui.generate_and_insert_audio_or_video_call_link($(e.target), true);
compose_call_ui.generate_and_insert_audio_or_video_call_link($(this), true);
});
$("body").on("click", ".time_pick", function (e) {
$("body").on("click", ".time_pick", function (this: HTMLElement, e) {
e.preventDefault();
e.stopPropagation();
let $target_textarea;
let edit_message_id;
const $compose_click_target = $(this);
if ($compose_click_target.parents(".message_edit_form").length === 1) {
edit_message_id = rows.id($compose_click_target.parents(".message_row"));
$target_textarea = $(`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`);
const edit_message_id = rows.id($compose_click_target.parents(".message_row"));
$target_textarea = $<HTMLTextAreaElement>(
`#edit_form_${edit_message_id} textarea.message_edit_content`,
);
} else {
$target_textarea = $compose_click_target.closest("form").find("textarea");
$target_textarea = $compose_click_target
.closest("form")
.find<HTMLTextAreaElement>("textarea");
}
if (!flatpickr.is_open()) {
const on_timestamp_selection = (val) => {
const timestr = `<time:${val}> `;
const on_timestamp_selection = (time: string): void => {
const timestr = `<time:${time}> `;
compose_ui.insert_syntax_and_focus(timestr, $target_textarea);
};
flatpickr.show_flatpickr(
$compose_click_target[0],
util.the($compose_click_target),
on_timestamp_selection,
get_timestamp_for_flatpickr(),
{
@@ -425,7 +443,7 @@ export function initialize() {
position: "auto center",
// Since we want to handle close of flatpickr manually, we don't want
// flatpickr to hide automatically on clicking its trigger element.
ignoredFocusElements: [e.currentTarget],
ignoredFocusElements: [this],
},
);
} else {
@@ -437,8 +455,8 @@ export function initialize() {
e.preventDefault();
e.stopPropagation();
function validate_input() {
const question = $("#poll-question-input").val().trim();
function validate_input(): boolean {
const question = $<HTMLInputElement>("#poll-question-input").val()!.trim();
if (question === "") {
ui_report.error(
@@ -494,7 +512,7 @@ export function initialize() {
e.preventDefault();
e.stopPropagation();
function validate_input(e) {
function validate_input(e: JQuery.ClickEvent): boolean {
let is_valid = true;
e.preventDefault();
e.stopPropagation();
@@ -584,7 +602,7 @@ export function initialize() {
// input is blurred, we immediately update the topic's
// displayed text and compose-area placeholder when the
// compose textarea is focused.
const $input = $("input#stream_message_recipient_topic");
const $input = $<HTMLInputElement>("input#stream_message_recipient_topic");
compose_recipient.update_topic_displayed_text($input.val());
compose_recipient.update_compose_area_placeholder_text();
compose_fade.do_update_all();
@@ -597,7 +615,7 @@ export function initialize() {
$(".compose-scrollable-buttons").on(
"scroll",
_.throttle((e) => {
_.throttle((e: JQuery.ScrollEvent) => {
compose_ui.handle_scrolling_formatting_buttons(e);
}, 150),
);
@@ -618,14 +636,14 @@ export function initialize() {
// To track delayed effects originating from the "blur" event
// and its use of setTimeout, we need to set up a variable to
// reference the timeout's ID across events.
let recipient_focused_timeout;
let recipient_focused_timeout: ReturnType<typeof setTimeout>;
$("input#stream_message_recipient_topic").on("focus", () => {
// We don't want the `recently-focused` class removed via
// a setTimeout from the "blur" event, if we're suddenly
// focused again.
clearTimeout(recipient_focused_timeout);
const $compose_recipient = $("#compose-recipient");
const $input = $("input#stream_message_recipient_topic");
const $input = $<HTMLInputElement>("input#stream_message_recipient_topic");
compose_recipient.update_topic_displayed_text($input.val(), true);
compose_recipient.update_compose_area_placeholder_text();
// When the topic input is focused, we no longer treat
@@ -657,7 +675,7 @@ export function initialize() {
$("input#stream_message_recipient_topic, #private_message_recipient").on("blur", () => {
const $compose_recipient = $("#compose-recipient");
const $input = $("input#stream_message_recipient_topic");
const $input = $<HTMLInputElement>("input#stream_message_recipient_topic");
// To correct for an edge case when clearing the topic box
// via the left sidebar, we do the following actions after a
// delay; these will not have an effect for DMs, and so can
@@ -683,7 +701,7 @@ export function initialize() {
$("body").on("click", ".formatting_button", function (e) {
const $compose_click_target = $(this);
const $textarea = $compose_click_target.closest("form").find("textarea");
const format_type = $(this).attr("data-format-type");
const format_type = $(this).attr("data-format-type")!;
compose_ui.format_text($textarea, format_type);
popovers.hide_all();
$textarea.trigger("focus");

View File

@@ -73,7 +73,7 @@ export type DialogWidgetConfig = {
id?: string;
single_footer_button?: boolean;
form_id?: string;
validate_input?: (e: unknown) => boolean;
validate_input?: (e: JQuery.ClickEvent) => boolean;
on_show?: () => void;
on_shown?: () => void;
on_hide?: () => void;
@@ -247,7 +247,7 @@ export function launch(conf: DialogWidgetConfig): string {
}
// Set up handlers.
$submit_button.on("click", (e) => {
$submit_button.on("click", (e: JQuery.ClickEvent) => {
e.preventDefault();
if (conf.validate_input && !conf.validate_input(e)) {

View File

@@ -34,7 +34,7 @@ import * as compose_pm_pill from "./compose_pm_pill.ts";
import * as compose_recipient from "./compose_recipient.ts";
import * as compose_reply from "./compose_reply.ts";
import * as compose_send_menu_popover from "./compose_send_menu_popover.ts";
import * as compose_setup from "./compose_setup.js";
import * as compose_setup from "./compose_setup.ts";
import * as compose_textarea from "./compose_textarea.ts";
import * as compose_tooltips from "./compose_tooltips.ts";
import * as compose_validate from "./compose_validate.ts";

View File

@@ -113,9 +113,6 @@ test("videos", ({override}) => {
const ev = {
preventDefault() {},
stopPropagation() {},
target: {
to_$: () => $textarea,
},
};
override(compose_ui, "insert_syntax_and_focus", (syntax) => {
@@ -139,7 +136,7 @@ test("videos", ({override}) => {
override(realm, "realm_jitsi_server_url", null);
override(realm, "server_jitsi_server_url", "https://server.example.com");
handler(ev);
handler.call($textarea, ev);
// video link ids consist of 15 random digits
let video_link_regex =
/\[translated: Join video call\.]\(https:\/\/server.example.com\/\d{15}#config.startWithVideoMuted=false\)/;
@@ -148,7 +145,7 @@ test("videos", ({override}) => {
override(realm, "realm_jitsi_server_url", "https://realm.example.com");
override(realm, "server_jitsi_server_url", null);
handler(ev);
handler.call($textarea, ev);
video_link_regex =
/\[translated: Join video call\.]\(https:\/\/realm.example.com\/\d{15}#config.startWithVideoMuted=false\)/;
assert.ok(called);
@@ -156,7 +153,7 @@ test("videos", ({override}) => {
override(realm, "realm_jitsi_server_url", "https://realm.example.com");
override(realm, "server_jitsi_server_url", "https://server.example.com");
handler(ev);
handler.call($textarea, ev);
video_link_regex =
/\[translated: Join video call\.]\(https:\/\/realm.example.com\/\d{15}#config.startWithVideoMuted=false\)/;
assert.ok(called);
@@ -173,9 +170,6 @@ test("videos", ({override}) => {
const ev = {
preventDefault() {},
stopPropagation() {},
target: {
to_$: () => $textarea,
},
};
override(compose_ui, "insert_syntax_and_focus", (syntax) => {
@@ -206,14 +200,14 @@ test("videos", ({override}) => {
$("textarea#compose-textarea").val("");
const video_handler = $("body").get_on_handler("click", ".video_link");
video_handler(ev);
video_handler.call($textarea, ev);
const video_link_regex = /\[translated: Join video call\.]\(example\.zoom\.com\)/;
assert.ok(called);
assert.match(syntax_to_insert, video_link_regex);
$("textarea#compose-textarea").val("");
const audio_handler = $("body").get_on_handler("click", ".audio_link");
audio_handler(ev);
audio_handler.call($textarea, ev);
const audio_link_regex = /\[translated: Join voice call\.]\(example\.zoom\.com\)/;
assert.ok(called);
assert.match(syntax_to_insert, audio_link_regex);
@@ -229,9 +223,6 @@ test("videos", ({override}) => {
const ev = {
preventDefault() {},
stopPropagation() {},
target: {
to_$: () => $textarea,
},
};
override(compose_ui, "insert_syntax_and_focus", (syntax) => {
@@ -265,14 +256,14 @@ test("videos", ({override}) => {
$("textarea#compose-textarea").val("");
const video_handler = $("body").get_on_handler("click", ".video_link");
video_handler(ev);
video_handler.call($textarea, ev);
const video_link_regex =
/\[translated: Join video call\.]\(\/calls\/bigbluebutton\/join\?meeting_id=%22zulip-1%22&moderator=%22AAAAAAAAAA%22&lock_settings_disable_cam=false&checksum=%2232702220bff2a22a44aee72e96cfdb4c4091752e%22\)/;
assert.ok(called);
assert.match(syntax_to_insert, video_link_regex);
const audio_handler = $("body").get_on_handler("click", ".audio_link");
audio_handler(ev);
audio_handler.call($textarea, ev);
const audio_link_regex =
/\[translated: Join voice call\.]\(\/calls\/bigbluebutton\/join\?meeting_id=%22zulip-1%22&moderator=%22AAAAAAAAAA%22&lock_settings_disable_cam=true&checksum=%2232702220bff2a22a44aee72e96cfdb4c4091752e%22\)/;
assert.ok(called);