From 7bd40162541881293da551c42bcd2cf0244de08b Mon Sep 17 00:00:00 2001 From: evykassirer Date: Sun, 24 Nov 2024 23:19:54 -0800 Subject: [PATCH] message_events: Convert module to typescript. --- tools/test-js-with-node | 2 +- web/src/compose.js | 2 +- web/src/drafts.ts | 4 +- .../{message_events.js => message_events.ts} | 152 ++++++++++++------ web/src/message_list.ts | 4 +- web/src/message_store.ts | 2 +- web/src/message_util.ts | 2 +- web/src/message_view.ts | 6 +- web/src/server_events.js | 2 +- web/src/server_events_dispatch.js | 2 +- web/src/unread.ts | 2 +- 11 files changed, 116 insertions(+), 64 deletions(-) rename web/src/{message_events.js => message_events.ts} (87%) diff --git a/tools/test-js-with-node b/tools/test-js-with-node index a0092ab306..b2efca0b4c 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -136,7 +136,7 @@ EXEMPT_FILES = make_set( "web/src/message_actions_popover.ts", "web/src/message_edit.ts", "web/src/message_edit_history.ts", - "web/src/message_events.js", + "web/src/message_events.ts", "web/src/message_events_util.ts", "web/src/message_feed_loading.ts", "web/src/message_feed_top_notices.ts", diff --git a/web/src/compose.js b/web/src/compose.js index 07764e30e4..915c34887f 100644 --- a/web/src/compose.js +++ b/web/src/compose.js @@ -14,7 +14,7 @@ import * as compose_ui from "./compose_ui.ts"; import * as compose_validate from "./compose_validate.ts"; import * as drafts from "./drafts.ts"; import * as echo from "./echo.ts"; -import * as message_events from "./message_events.js"; +import * as message_events from "./message_events.ts"; import * as onboarding_steps from "./onboarding_steps.ts"; import * as people from "./people.ts"; import * as scheduled_messages from "./scheduled_messages.ts"; diff --git a/web/src/drafts.ts b/web/src/drafts.ts index 649e10d510..6604cc4e2b 100644 --- a/web/src/drafts.ts +++ b/web/src/drafts.ts @@ -274,8 +274,8 @@ export function confirm_delete_all_drafts(): void { export function rename_stream_recipient( old_stream_id: number, old_topic: string, - new_stream_id: number, - new_topic: string, + new_stream_id: number | undefined, + new_topic: string | undefined, ): void { for (const [draft_id, draft] of Object.entries(draft_model.get())) { if (draft.type !== "stream" || draft.stream_id === undefined) { diff --git a/web/src/message_events.js b/web/src/message_events.ts similarity index 87% rename from web/src/message_events.js rename to web/src/message_events.ts index fe3329932a..2eca809448 100644 --- a/web/src/message_events.js +++ b/web/src/message_events.ts @@ -1,6 +1,7 @@ import $ from "jquery"; import _ from "lodash"; import assert from "minimalistic-assert"; +import {z} from "zod"; import * as activity from "./activity.ts"; import * as alert_words from "./alert_words.ts"; @@ -13,6 +14,7 @@ import * as compose_validate from "./compose_validate.ts"; import * as direct_message_group_data from "./direct_message_group_data.ts"; import * as drafts from "./drafts.ts"; import * as echo from "./echo.ts"; +import type {Filter} from "./filter.ts"; import * as message_edit from "./message_edit.ts"; import * as message_edit_history from "./message_edit_history.ts"; import * as message_events_util from "./message_events_util.ts"; @@ -22,6 +24,7 @@ import * as message_lists from "./message_lists.ts"; import * as message_notifications from "./message_notifications.ts"; import * as message_parser from "./message_parser.ts"; import * as message_store from "./message_store.ts"; +import {type Message, type RawMessage, raw_message_schema} from "./message_store.ts"; import * as message_util from "./message_util.ts"; import * as message_view from "./message_view.ts"; import * as narrow_state from "./narrow_state.ts"; @@ -35,18 +38,19 @@ import {realm} from "./state_data.ts"; import * as stream_list from "./stream_list.ts"; import * as stream_topic_history from "./stream_topic_history.ts"; import * as sub_store from "./sub_store.ts"; +import type {UpdateMessageEvent} from "./types.ts"; import * as unread from "./unread.ts"; import * as unread_ui from "./unread_ui.ts"; import * as util from "./util.ts"; -function filter_has_term_type(filter, term_type) { +function filter_has_term_type(filter: Filter, term_type: string): boolean { return ( filter.sorted_term_types().includes(term_type) || filter.sorted_term_types().includes(`not-${term_type}`) ); } -export function discard_cached_lists_with_term_type(term_type) { +export function discard_cached_lists_with_term_type(term_type: string): void { // Discards cached MessageList and MessageListData which have // `term_type` and `not-term_type`. assert(!term_type.includes("not-")); @@ -74,7 +78,7 @@ export function discard_cached_lists_with_term_type(term_type) { } } -export function update_current_view_for_topic_visibility() { +export function update_current_view_for_topic_visibility(): boolean { // If we have rendered message list / cached data based on topic // visibility policy, we need to rerender it to reflect the changes. It // is easier to just load the narrow from scratch, instead of asking server @@ -84,8 +88,10 @@ export function update_current_view_for_topic_visibility() { // Use `set_timeout to call after we update the topic // visibility policy locally. // Calling this outside `user_topics_ui` to avoid circular imports. + assert(message_lists.current !== undefined); const msg_list_id = message_lists.current.id; setTimeout(() => { + assert(message_lists.current !== undefined); if (message_lists.current.id !== msg_list_id) { // Check if the message list is still the same. return; @@ -103,10 +109,10 @@ export function update_current_view_for_topic_visibility() { } export let update_views_filtered_on_message_property = ( - message_ids, - property_term_type, - property_value, -) => { + message_ids: number[], + property_term_type: string, + property_value: boolean, +): void => { // NOTE: Call this function after updating the message property locally. assert(!property_term_type.includes("not-")); @@ -142,8 +148,8 @@ export let update_views_filtered_on_message_property = ( } // We need the message objects to determine if they match the filter. - const messages_to_fetch = []; - const messages = []; + const messages_to_fetch: number[] = []; + const messages: Message[] = []; for (const message_id of message_ids) { const message = message_store.get(message_id); if (message !== undefined) { @@ -159,8 +165,12 @@ export let update_views_filtered_on_message_property = ( continue; } - const first_id = msg_list.first().id; - const last_id = msg_list.last().id; + const first_message = msg_list.first(); + assert(first_message !== undefined); + const first_id = first_message.id; + const last_message = msg_list.last(); + assert(last_message !== undefined); + const last_id = last_message.id; const has_found_newest = msg_list.data.fetch_status.has_found_newest(); const has_found_oldest = msg_list.data.fetch_status.has_found_oldest(); @@ -185,15 +195,16 @@ export let update_views_filtered_on_message_property = ( narrow: JSON.stringify(filter.terms()), }, success(data) { - const messages_to_add = []; + const messages_to_add: Message[] = []; const messages_to_remove = new Set(message_ids); - for (const raw_message of data.messages) { + for (const raw_message of z + .object({messages: z.array(raw_message_schema)}) + .parse(data).messages) { messages_to_remove.delete(raw_message.id); - let message = message_store.get(raw_message.id); - if (!message) { - message = message_helper.process_new_message(raw_message); - } - messages_to_add.push(message); + const message = message_store.get(raw_message.id); + messages_to_add.push( + message ?? message_helper.process_new_message(raw_message), + ); } msg_list.data.remove([...messages_to_remove]); msg_list.data.add_messages(messages_to_add); @@ -213,10 +224,15 @@ export let update_views_filtered_on_message_property = ( }, // eslint-disable-next-line no-loop-func success(data) { + const parsed_data = z + .object({ + messages: z.array(raw_message_schema), + }) + .parse(data); // `messages_to_fetch` might already be cached locally when // we reach here but `message_helper.process_new_message` // already handles that case. - for (const raw_message of data.messages) { + for (const raw_message of parsed_data.messages) { message_helper.process_new_message(raw_message); } update_views_filtered_on_message_property( @@ -245,7 +261,7 @@ export let update_views_filtered_on_message_property = ( // In most cases, we are only working to update a single message. if (messages.length === 1) { - const message = messages[0]; + const message = messages[0]!; if (filter.predicate()(message)) { msg_list.add_messages(messages); } else { @@ -260,13 +276,19 @@ export let update_views_filtered_on_message_property = ( } }; -export function rewire_update_views_filtered_on_message_property(value) { +export function rewire_update_views_filtered_on_message_property( + value: typeof update_views_filtered_on_message_property, +): void { update_views_filtered_on_message_property = value; } -export function insert_new_messages(messages, sent_by_this_client, deliver_locally) { - messages = messages.map((message) => - message_helper.process_new_message(message, deliver_locally), +export function insert_new_messages( + raw_messages: RawMessage[], + sent_by_this_client: boolean, + deliver_locally: boolean, +): Message[] { + const messages = raw_messages.map((raw_message) => + message_helper.process_new_message(raw_message, deliver_locally), ); const any_untracked_unread_messages = unread.process_loaded_messages(messages, false); @@ -308,7 +330,7 @@ export function insert_new_messages(messages, sent_by_this_client, deliver_local // this message list is the currently visible message list. const is_currently_visible = narrow_state.is_message_feed_visible() && list === message_lists.current; - if (is_currently_visible && render_info && render_info.need_user_to_scroll) { + if (is_currently_visible && render_info?.need_user_to_scroll) { need_user_to_scroll = true; } } @@ -342,7 +364,9 @@ export function insert_new_messages(messages, sent_by_this_client, deliver_local // tracking before we update the stream sidebar, to take advantage // of how stream_topic_history uses the echo data structures. if (deliver_locally) { - messages.map((message) => echo.track_local_message(message)); + for (const message of messages) { + echo.track_local_message(message); + } } activity.set_received_new_messages(true); @@ -353,8 +377,8 @@ export function insert_new_messages(messages, sent_by_this_client, deliver_local return messages; } -export function update_messages(events) { - const messages_to_rerender = []; +export function update_messages(events: UpdateMessageEvent[]): void { + const messages_to_rerender: Message[] = []; let changed_narrow = false; let refreshed_current_narrow = false; let changed_compose = false; @@ -426,6 +450,7 @@ export function update_messages(events) { } if (unread.update_message_for_mention(anchor_message, any_message_content_edited)) { + assert(anchor_message.type === "stream"); const topic_key = recent_view_util.get_topic_key( anchor_message.stream_id, anchor_message.topic, @@ -442,7 +467,8 @@ export function update_messages(events) { const old_stream_id = event.stream_id; // old_stream will be undefined if the message was moved from // a stream that the current user doesn't have access to. - const old_stream = sub_store.get(event.stream_id); + const old_stream = + event.stream_id === undefined ? undefined : sub_store.get(event.stream_id); // A topic or stream edit may affect multiple messages, listed in // event.message_ids. event.message_id is still the first message @@ -461,24 +487,29 @@ export function update_messages(events) { messages_to_rerender.push(anchor_message); } } else { - const going_forward_change = ["change_later", "change_all"].includes( - event.propagate_mode, - ); + // We must be moving stream messages. + assert(old_stream_id !== undefined); + const orig_topic = util.get_edit_event_orig_topic(event); + assert(orig_topic !== undefined); + + const going_forward_change = + event.propagate_mode !== undefined && + ["change_later", "change_all"].includes(event.propagate_mode); const compose_stream_id = compose_state.stream_id(); - const orig_topic = util.get_edit_event_orig_topic(event); - const current_filter = narrow_state.filter(); const current_selected_id = message_lists.current?.selected_id(); const selection_changed_topic = message_lists.current !== undefined && + current_selected_id !== undefined && event.message_ids.includes(current_selected_id); - const event_messages = []; + const event_messages: (Message & {type: "stream"})[] = []; for (const message_id of event.message_ids) { // We don't need to concern ourselves updating data structures // for messages we don't have stored locally. const message = message_store.get(message_id); if (message !== undefined) { + assert(message.type === "stream"); event_messages.push(message); } else { // If we don't have the message locally, we need to @@ -520,7 +551,14 @@ export function update_messages(events) { * history events. This logic ensures that all * messages that were moved are displayed as such * without a browser reload. */ - const edit_history_entry = { + const edit_history_entry: { + user_id: number | null; + timestamp: number; + stream?: number; + prev_stream?: number; + topic?: string; + prev_topic?: string; + } = { user_id: event.user_id, timestamp: event.edit_timestamp, }; @@ -549,10 +587,13 @@ export function update_messages(events) { // Now edit the attributes of our message object. if (topic_edited) { moved_message.topic = new_topic; + assert(event.topic_links !== undefined); moved_message.topic_links = event.topic_links; } if (stream_changed) { - const new_stream_name = sub_store.get(new_stream_id).name; + const new_stream = sub_store.get(new_stream_id); + assert(new_stream !== undefined); + const new_stream_name = new_stream.name; moved_message.stream_id = new_stream_id; moved_message.display_recipient = new_stream_name; } @@ -575,7 +616,7 @@ export function update_messages(events) { stream_id: old_stream_id, topic_name: orig_topic, num_messages, - max_removed_msg_id: event_messages[num_messages - 1].id, + max_removed_msg_id: event_messages[num_messages - 1]!.id, }); } @@ -595,9 +636,7 @@ export function update_messages(events) { // Code further down takes care of the actual rerendering of // messages within a narrow. selection_changed_topic && - current_filter && - old_stream_id && - current_filter.has_topic(old_stream_id, orig_topic) + current_filter?.has_topic(old_stream_id, orig_topic) ) { let new_filter = current_filter; if (new_filter && stream_changed) { @@ -647,7 +686,9 @@ export function update_messages(events) { let moved_message_stream_id_str = old_stream_id.toString(); let moved_message_topic = orig_topic; if (stream_changed) { - moved_message_stream_id_str = sub_store.get(new_stream_id).stream_id.toString(); + const new_stream = sub_store.get(new_stream_id); + assert(new_stream !== undefined); + moved_message_stream_id_str = new_stream.stream_id.toString(); } if (topic_edited) { @@ -721,21 +762,26 @@ export function update_messages(events) { } if (topic_edited || stream_changed) { - // if topic is changed + // We must be moving stream messages. + assert(old_stream_id !== undefined); let pre_edit_topic = util.get_edit_event_orig_topic(event); - let post_edit_topic = new_topic; + assert(pre_edit_topic !== undefined); - if (!topic_edited) { + let post_edit_topic: string; + if (topic_edited) { + assert(new_topic !== undefined); + post_edit_topic = new_topic; + } else { if (anchor_message !== undefined) { + assert(anchor_message.type === "stream"); pre_edit_topic = anchor_message.topic; } post_edit_topic = pre_edit_topic; } // new_stream_id is undefined if this is only a topic edit. - const post_edit_stream_id = new_stream_id || old_stream_id; + const post_edit_stream_id = new_stream_id ?? old_stream_id; - const args = [old_stream_id, pre_edit_topic, post_edit_topic, post_edit_stream_id]; recent_senders.process_topic_edit({ message_ids: event.message_ids, old_stream_id, @@ -744,14 +790,20 @@ export function update_messages(events) { new_topic: post_edit_topic, }); unread.clear_and_populate_unread_mention_topics(); - recent_view_ui.process_topic_edit(...args); + recent_view_ui.process_topic_edit( + old_stream_id, + pre_edit_topic, + post_edit_topic, + post_edit_stream_id, + ); } // Rerender "Message edit history" if it was open to the edited message. if ( anchor_message !== undefined && $("#message-edit-history").parents(".micromodal").hasClass("modal--open") && - anchor_message.id === Number.parseInt($("#message-history").attr("data-message-id"), 10) + anchor_message.id === + Number.parseInt($("#message-history").attr("data-message-id")!, 10) ) { message_edit_history.fetch_and_render_message_history(anchor_message); } @@ -814,7 +866,7 @@ export function update_messages(events) { pm_list.update_private_messages(); } -export function remove_messages(message_ids) { +export function remove_messages(message_ids: number[]): void { // Update the rendered data first since it is most user visible. for (const list of message_lists.all_rendered_message_lists()) { list.remove_and_rerender(message_ids); diff --git a/web/src/message_list.ts b/web/src/message_list.ts index 403b80a301..7ad55b3421 100644 --- a/web/src/message_list.ts +++ b/web/src/message_list.ts @@ -192,7 +192,7 @@ export class MessageList { add_messages( messages: Message[], - append_to_view_opts: {messages_are_new?: boolean}, + append_to_view_opts: {messages_are_new?: boolean} = {}, ): RenderInfo | undefined { // This adds all messages to our data, but only returns // the currently viewable ones. @@ -608,7 +608,7 @@ export class MessageList { // We could avoid a rerender if we can provide that this // narrow cannot have contained messages to muted topics // either before or after the state change. The right place - // to do this is in the message_events.js code path for + // to do this is in the message_events.ts code path for // processing topic edits, since that's the only place we'll // call this frequently anyway. // diff --git a/web/src/message_store.ts b/web/src/message_store.ts index fa93c329b3..334a0148a7 100644 --- a/web/src/message_store.ts +++ b/web/src/message_store.ts @@ -153,7 +153,7 @@ export type Message = ( // The original markup for the message, which we'll have if we // sent it or if we fetched it (usually, because the current user // tried to edit the message). - raw_content?: string; + raw_content?: string | undefined; // Added in `message_helper.process_new_message`. sent_by_me: boolean; diff --git a/web/src/message_util.ts b/web/src/message_util.ts index 2e2330493b..5ba7cb1256 100644 --- a/web/src/message_util.ts +++ b/web/src/message_util.ts @@ -29,7 +29,7 @@ export function do_unread_count_updates(messages: Message[], expect_no_new_unrea export function add_messages( messages: Message[], msg_list: MessageList, - append_to_view_opts: {messages_are_new: boolean}, + append_to_view_opts?: {messages_are_new: boolean}, ): RenderInfo | undefined { if (!messages) { return undefined; diff --git a/web/src/message_view.ts b/web/src/message_view.ts index 59818333d7..c7b7130546 100644 --- a/web/src/message_view.ts +++ b/web/src/message_view.ts @@ -340,13 +340,13 @@ export function try_rendering_locally_for_same_narrow( return true; } -type ShowMessageViewOpts = { +export type ShowMessageViewOpts = { force_rerender?: boolean; force_close?: boolean; change_hash?: boolean; trigger?: string; fetched_target_message?: boolean; - then_select_id?: number; + then_select_id?: number | undefined; then_select_offset?: number | undefined; show_more_topics?: boolean; }; @@ -440,11 +440,11 @@ export let show = (raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): voi const coming_from_inbox = inbox_util.is_visible(); const opts = { - then_select_id: -1, change_hash: true, trigger: "unknown", show_more_topics: false, ...show_opts, + then_select_id: show_opts.then_select_id ?? -1, }; const span_data = { diff --git a/web/src/server_events.js b/web/src/server_events.js index e679711ff6..298bba595b 100644 --- a/web/src/server_events.js +++ b/web/src/server_events.js @@ -5,7 +5,7 @@ import * as blueslip from "./blueslip.ts"; import * as channel from "./channel.ts"; import * as echo from "./echo.ts"; import * as loading from "./loading.ts"; -import * as message_events from "./message_events.js"; +import * as message_events from "./message_events.ts"; import {page_params} from "./page_params.ts"; import * as reload from "./reload.ts"; import * as reload_state from "./reload_state.ts"; diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index 6d2942be57..4c2a28fa95 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -25,7 +25,7 @@ import * as information_density from "./information_density.ts"; import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts"; import * as linkifiers from "./linkifiers.ts"; import * as message_edit from "./message_edit.ts"; -import * as message_events from "./message_events.js"; +import * as message_events from "./message_events.ts"; import * as message_lists from "./message_lists.ts"; import * as message_live_update from "./message_live_update.ts"; import * as message_view from "./message_view.ts"; diff --git a/web/src/unread.ts b/web/src/unread.ts index aebbdd1c61..6b242b0c8a 100644 --- a/web/src/unread.ts +++ b/web/src/unread.ts @@ -803,7 +803,7 @@ export function process_unread_message(message: UnreadMessageData): void { } export function update_message_for_mention( - message: UnreadMessageData, + message: UnreadMessageData | Message, content_edited = false, ): boolean { // Returns true if this is a stream message whose content was