diff --git a/tools/test-js-with-node b/tools/test-js-with-node index c5d0f5c759..38458980f1 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -128,7 +128,7 @@ EXEMPT_FILES = make_set( "web/src/localstorage.ts", "web/src/message_actions_popover.js", "web/src/message_edit.js", - "web/src/message_edit_history.js", + "web/src/message_edit_history.ts", "web/src/message_events.js", "web/src/message_feed_loading.ts", "web/src/message_feed_top_notices.ts", diff --git a/web/src/message_edit_history.js b/web/src/message_edit_history.ts similarity index 54% rename from web/src/message_edit_history.js rename to web/src/message_edit_history.ts index 80652e8cec..cd6d1dd451 100644 --- a/web/src/message_edit_history.js +++ b/web/src/message_edit_history.ts @@ -1,4 +1,6 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; +import {z} from "zod"; import render_message_edit_history from "../templates/message_edit_history.hbs"; import render_message_history_modal from "../templates/message_history_modal.hbs"; @@ -7,6 +9,7 @@ import * as channel from "./channel"; import * as dialog_widget from "./dialog_widget"; import {$t, $t_html} from "./i18n"; import * as message_lists from "./message_lists"; +import type {Message} from "./message_store"; import {page_params} from "./page_params"; import * as people from "./people"; import * as rendered_markdown from "./rendered_markdown"; @@ -19,30 +22,62 @@ import * as timerender from "./timerender"; import * as ui_report from "./ui_report"; import {user_settings} from "./user_settings"; -export function fetch_and_render_message_history(message) { - channel.get({ +type EditHistoryEntry = { + timestamp: string; + display_date: string; + show_date_row: boolean; + edited_by_notice: string; + body_to_render?: string; + topic_edited?: boolean; + prev_topic?: string; + new_topic?: string; + stream_changed?: boolean; + prev_stream?: string; + new_stream?: string; +}; + +const server_message_history_schema = z.object({ + message_history: z.array( + z.object({ + content: z.string(), + rendered_content: z.string(), + timestamp: z.number(), + topic: z.string(), + user_id: z.number().or(z.null()), + prev_topic: z.string().optional(), + stream: z.number().optional(), + prev_stream: z.number().optional(), + prev_content: z.string().optional(), + prev_rendered_content: z.string().optional(), + content_html_diff: z.string().optional(), + }), + ), +}); + +export function fetch_and_render_message_history(message: Message): void { + void channel.get({ url: "/json/messages/" + message.id + "/history", data: {message_id: JSON.stringify(message.id)}, success(data) { - const content_edit_history = []; + const clean_data = server_message_history_schema.parse(data); + + const content_edit_history: EditHistoryEntry[] = []; let prev_time = null; - let prev_stream_item = null; + let prev_stream_item: EditHistoryEntry | null = null; const date_time_format = new Intl.DateTimeFormat(user_settings.default_language, { year: "numeric", month: "long", day: "numeric", }); - for (const [index, msg] of data.message_history.entries()) { + for (const [index, msg] of clean_data.message_history.entries()) { // Format times and dates nicely for display const time = new Date(msg.timestamp * 1000); - const item = { - timestamp: timerender.stringify_time(time), - display_date: date_time_format.format(time), - show_date_row: - prev_time === null || - !is_same_day(time, prev_time, timerender.display_time_zone), - }; + const timestamp = timerender.stringify_time(time); + const display_date = date_time_format.format(time); + const show_date_row = + prev_time === null || + !is_same_day(time, prev_time, timerender.display_time_zone); if (!msg.user_id) { continue; @@ -51,81 +86,88 @@ export function fetch_and_render_message_history(message) { const person = people.get_user_by_id_assert_valid(msg.user_id); const full_name = person.full_name; + let edited_by_notice; + let body_to_render; + let topic_edited; + let prev_topic; + let new_topic; + let stream_changed; + let prev_stream; + if (index === 0) { - item.edited_by_notice = $t( - {defaultMessage: "Posted by {full_name}"}, - {full_name}, - ); - item.body_to_render = msg.rendered_content; + edited_by_notice = $t({defaultMessage: "Posted by {full_name}"}, {full_name}); + body_to_render = msg.rendered_content; } else if (msg.prev_topic && msg.prev_content) { - item.edited_by_notice = $t( - {defaultMessage: "Edited by {full_name}"}, - {full_name}, - ); - item.body_to_render = msg.content_html_diff; - item.topic_edited = true; - item.prev_topic = msg.prev_topic; - item.new_topic = msg.topic; + edited_by_notice = $t({defaultMessage: "Edited by {full_name}"}, {full_name}); + body_to_render = msg.content_html_diff; + topic_edited = true; + prev_topic = msg.prev_topic; + new_topic = msg.topic; } else if (msg.prev_topic && msg.prev_stream) { const sub = sub_store.get(msg.prev_stream); - item.edited_by_notice = $t( - {defaultMessage: "Moved by {full_name}"}, - {full_name}, - ); - item.topic_edited = true; - item.prev_topic = msg.prev_topic; - item.new_topic = msg.topic; - item.stream_changed = true; + edited_by_notice = $t({defaultMessage: "Moved by {full_name}"}, {full_name}); + topic_edited = true; + prev_topic = msg.prev_topic; + new_topic = msg.topic; + stream_changed = true; if (!sub) { - item.prev_stream = $t({defaultMessage: "Unknown stream"}); + prev_stream = $t({defaultMessage: "Unknown stream"}); } else { - item.prev_stream = sub_store.maybe_get_stream_name(msg.prev_stream); + prev_stream = sub_store.maybe_get_stream_name(msg.prev_stream); } if (prev_stream_item !== null) { prev_stream_item.new_stream = sub_store.maybe_get_stream_name( msg.prev_stream, ); } - prev_stream_item = item; } else if (msg.prev_topic) { - item.edited_by_notice = $t( - {defaultMessage: "Moved by {full_name}"}, - {full_name}, - ); - item.topic_edited = true; - item.prev_topic = msg.prev_topic; - item.new_topic = msg.topic; + edited_by_notice = $t({defaultMessage: "Moved by {full_name}"}, {full_name}); + topic_edited = true; + prev_topic = msg.prev_topic; + new_topic = msg.topic; } else if (msg.prev_stream) { const sub = sub_store.get(msg.prev_stream); - item.edited_by_notice = $t( - {defaultMessage: "Moved by {full_name}"}, - {full_name}, - ); - item.stream_changed = true; + edited_by_notice = $t({defaultMessage: "Moved by {full_name}"}, {full_name}); + stream_changed = true; if (!sub) { - item.prev_stream = $t({defaultMessage: "Unknown stream"}); + prev_stream = $t({defaultMessage: "Unknown stream"}); } else { - item.prev_stream = sub_store.maybe_get_stream_name(msg.prev_stream); + prev_stream = sub_store.maybe_get_stream_name(msg.prev_stream); } if (prev_stream_item !== null) { prev_stream_item.new_stream = sub_store.maybe_get_stream_name( msg.prev_stream, ); } - prev_stream_item = item; } else { // just a content edit - item.edited_by_notice = $t( - {defaultMessage: "Edited by {full_name}"}, - {full_name}, - ); - item.body_to_render = msg.content_html_diff; + edited_by_notice = $t({defaultMessage: "Edited by {full_name}"}, {full_name}); + body_to_render = msg.content_html_diff; + } + + const item: EditHistoryEntry = { + timestamp, + display_date, + show_date_row, + edited_by_notice, + body_to_render, + topic_edited, + prev_topic, + new_topic, + stream_changed, + prev_stream, + new_stream: undefined, + }; + + if (msg.prev_stream) { + prev_stream_item = item; } content_edit_history.push(item); prev_time = time; } if (prev_stream_item !== null) { + assert(message.type === "stream"); prev_stream_item.new_stream = sub_store.maybe_get_stream_name(message.stream_id); } $("#message-history").attr("data-message-id", message.id); @@ -152,7 +194,7 @@ export function fetch_and_render_message_history(message) { }); } -export function show_history(message) { +export function show_history(message: Message): void { const rendered_message_history = render_message_history_modal(); dialog_widget.launch({ @@ -160,7 +202,9 @@ export function show_history(message) { html_body: rendered_message_history, html_submit_button: $t_html({defaultMessage: "Close"}), id: "message-edit-history", - on_click() {}, + on_click() { + /* do nothing */ + }, close_on_submit: true, focus_submit_on_open: true, single_footer_button: true, @@ -170,7 +214,7 @@ export function show_history(message) { }); } -export function initialize() { +export function initialize(): void { $("body").on("mouseenter", ".message_edit_notice", (e) => { if (realm.realm_allow_edit_history) { $(e.currentTarget).addClass("message_edit_notice_hover"); @@ -188,8 +232,13 @@ export function initialize() { e.preventDefault(); const message_id = rows.id($(e.currentTarget).closest(".message_row")); + assert(message_id !== undefined); + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(message_id); - const message = message_lists.current.get(rows.id($row)); + const row_id = rows.id($row); + assert(row_id !== undefined); + const message = message_lists.current.get(row_id); + assert(message !== undefined); if (page_params.is_spectator) { spectators.login_to_access();