diff --git a/frontend_tests/node_tests/compose_actions.js b/frontend_tests/node_tests/compose_actions.js index 2efd0a8380..bc12baca94 100644 --- a/frontend_tests/node_tests/compose_actions.js +++ b/frontend_tests/node_tests/compose_actions.js @@ -30,6 +30,10 @@ mock_esm("../../static/js/notifications", { mock_esm("../../static/js/reload_state", { is_in_progress: () => false, }); +mock_esm("../../static/js/recent_topics", { + is_visible: noop, + set_compose_defaults: noop, +}); mock_esm("../../static/js/drafts", { update_draft: noop, }); diff --git a/frontend_tests/node_tests/compose_closed_ui.js b/frontend_tests/node_tests/compose_closed_ui.js index dd14bd4638..1483f47e43 100644 --- a/frontend_tests/node_tests/compose_closed_ui.js +++ b/frontend_tests/node_tests/compose_closed_ui.js @@ -86,3 +86,11 @@ run_test("reply_label", () => { test_reply_label(expected_label); } }); + +run_test("test_custom_message_input", () => { + compose_closed_ui.update_reply_recipient_label({ + stream: "stream test", + topic: "topic test", + }); + test_reply_label("#stream test > topic test"); +}); diff --git a/frontend_tests/node_tests/recent_topics.js b/frontend_tests/node_tests/recent_topics.js index 388785578f..d19af18710 100644 --- a/frontend_tests/node_tests/recent_topics.js +++ b/frontend_tests/node_tests/recent_topics.js @@ -65,11 +65,8 @@ const ListWidget = mock_esm("../../static/js/list_widget", { render_item: (item) => ListWidget.modifier(item), }); -mock_esm("../../static/js/compose_actions", { - cancel: noop, -}); -mock_esm("../../static/js/drafts", { - update_draft: noop, +mock_esm("../../static/js/compose", { + update_closed_compose_buttons_for_recent_topics: noop, }); mock_esm("../../static/js/hash_util", { by_stream_uri: () => "https://www.example.com", diff --git a/static/js/compose.js b/static/js/compose.js index 9065f308ae..7b404f8c12 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -200,6 +200,11 @@ export function update_closed_compose_buttons_for_stream() { update_closed_compose_buttons(text_stream); } +export function update_closed_compose_buttons_for_recent_topics() { + const text_stream = $t({defaultMessage: "Compose message"}); + update_closed_compose_buttons(text_stream); +} + function update_fade() { if (!compose_state.composing()) { return; diff --git a/static/js/compose_actions.js b/static/js/compose_actions.js index be9582e559..db11273479 100644 --- a/static/js/compose_actions.js +++ b/static/js/compose_actions.js @@ -19,10 +19,10 @@ import * as narrow_state from "./narrow_state"; import * as notifications from "./notifications"; import {page_params} from "./page_params"; import * as people from "./people"; +import * as recent_topics from "./recent_topics"; import * as reload_state from "./reload_state"; import * as stream_bar from "./stream_bar"; import * as stream_data from "./stream_data"; -import * as ui_util from "./ui_util"; import * as unread_ops from "./unread_ops"; export function blur_compose_inputs() { @@ -132,7 +132,6 @@ export function complete_starting_tasks(msg_type, opts) { // makes testing a bit easier. maybe_scroll_up_selected_message(); - ui_util.change_tab_to("#message_feed_container"); compose_fade.start_compose(msg_type); stream_bar.decorate(opts.stream, $("#stream-message .message_header_stream"), true); $(document).trigger(new $.Event("compose_started.zulip", opts)); @@ -215,6 +214,7 @@ export function start(msg_type, opts) { expand_compose_box(); opts = fill_in_opts_from_current_narrowed_view(msg_type, opts); + // If we are invoked by a compose hotkey (c or x) or new topic // button, do not assume that we know what the message's topic or // PM recipient should be. @@ -290,47 +290,55 @@ export function cancel() { } export function respond_to_message(opts) { - let msg_type; // Before initiating a reply to a message, if there's an // in-progress composition, snapshot it. drafts.update_draft(); - const message = message_lists.current.selected_message(); - - if (message === undefined) { - // empty narrow implementation - if ( - !narrow_state.narrowed_by_pm_reply() && - !narrow_state.narrowed_by_stream_reply() && - !narrow_state.narrowed_by_topic_reply() - ) { - compose.nonexistent_stream_reply_error(); + let message; + let msg_type; + if (recent_topics.is_visible()) { + message = recent_topics.get_focused_row_message(); + if (message === undefined) { return; } - const current_filter = narrow_state.filter(); - const first_term = current_filter.operators()[0]; - const first_operator = first_term.operator; - const first_operand = first_term.operand; + } else { + message = message_lists.current.selected_message(); - if (first_operator === "stream" && !stream_data.is_subscribed(first_operand)) { - compose.nonexistent_stream_reply_error(); + if (message === undefined) { + // empty narrow implementation + if ( + !narrow_state.narrowed_by_pm_reply() && + !narrow_state.narrowed_by_stream_reply() && + !narrow_state.narrowed_by_topic_reply() + ) { + compose.nonexistent_stream_reply_error(); + return; + } + const current_filter = narrow_state.filter(); + const first_term = current_filter.operators()[0]; + const first_operator = first_term.operator; + const first_operand = first_term.operand; + + if (first_operator === "stream" && !stream_data.is_subscribed(first_operand)) { + compose.nonexistent_stream_reply_error(); + return; + } + + // Set msg_type to stream by default in the case of an empty + // home view. + msg_type = "stream"; + if (narrow_state.narrowed_by_pm_reply()) { + msg_type = "private"; + } + + const new_opts = fill_in_opts_from_current_narrowed_view(msg_type, opts); + start(new_opts.message_type, new_opts); return; } - // Set msg_type to stream by default in the case of an empty - // home view. - msg_type = "stream"; - if (narrow_state.narrowed_by_pm_reply()) { - msg_type = "private"; + if (message_lists.current.can_mark_messages_read()) { + unread_ops.notify_server_message_read(message); } - - const new_opts = fill_in_opts_from_current_narrowed_view(msg_type, opts); - start(new_opts.message_type, new_opts); - return; - } - - if (message_lists.current.can_mark_messages_read()) { - unread_ops.notify_server_message_read(message); } // Important note: A reply_type of 'personal' is for the R hotkey diff --git a/static/js/compose_closed_ui.js b/static/js/compose_closed_ui.js index f0ba9ef3b7..df1a0454c0 100644 --- a/static/js/compose_closed_ui.js +++ b/static/js/compose_closed_ui.js @@ -4,8 +4,8 @@ import * as compose_actions from "./compose_actions"; import * as message_lists from "./message_lists"; import * as popovers from "./popovers"; -function update_reply_recipient_label() { - const message = message_lists.current.selected_message(); +export function update_reply_recipient_label(message) { + message = message || message_lists.current.selected_message(); let recipient_label = ""; if (message) { if (message.stream && message.topic) { diff --git a/static/js/hotkey.js b/static/js/hotkey.js index 891ee3105f..ee7d20cfd0 100644 --- a/static/js/hotkey.js +++ b/static/js/hotkey.js @@ -778,15 +778,13 @@ export function process_hotkey(e, hotkey) { return true; } - // We don't want hotkeys below this to work when recent topics is - // open. These involve compose box hotkeys and hotkeys that can only - // be done performed on a message. - if (recent_topics.is_visible()) { - return false; - } - // Shortcuts that are useful with an empty message feed, like opening compose. switch (event_name) { + case "reply_message": // 'r': respond to message + // Note that you can "Enter" to respond to messages as well, + // but that is handled in process_enter_key(). + compose_actions.respond_to_message({trigger: "hotkey"}); + return true; case "compose": // 'c': compose compose_actions.start("stream", {trigger: "compose_hotkey"}); return true; @@ -796,11 +794,6 @@ export function process_hotkey(e, hotkey) { case "open_drafts": drafts.launch(); return true; - case "reply_message": // 'r': respond to message - // Note that you can "Enter" to respond to messages as well, - // but that is handled in process_enter_key(). - compose_actions.respond_to_message({trigger: "hotkey"}); - return true; case "C_deprecated": ui.maybe_show_deprecation_notice("C"); return true; @@ -809,6 +802,12 @@ export function process_hotkey(e, hotkey) { return true; } + // We don't want hotkeys below this to work when recent topics is + // open. These involve hotkeys that can only be performed on a message. + if (recent_topics.is_visible()) { + return false; + } + if (message_lists.current.empty()) { return false; } diff --git a/static/js/recent_topics.js b/static/js/recent_topics.js index 0a6b59c583..c4eb3a6922 100644 --- a/static/js/recent_topics.js +++ b/static/js/recent_topics.js @@ -4,8 +4,9 @@ import render_recent_topic_row from "../templates/recent_topic_row.hbs"; import render_recent_topics_filters from "../templates/recent_topics_filters.hbs"; import render_recent_topics_body from "../templates/recent_topics_table.hbs"; -import * as compose_actions from "./compose_actions"; -import * as drafts from "./drafts"; +import * as compose from "./compose"; +import * as compose_closed_ui from "./compose_closed_ui"; +import * as compose_state from "./compose_state"; import * as hash_util from "./hash_util"; import * as hashchange from "./hashchange"; import * as ListWidget from "./list_widget"; @@ -75,6 +76,7 @@ export function is_in_focus() { // recent topics. return ( hashchange.in_recent_topics_hash() && + !compose_state.composing() && !popovers.any_active() && !overlays.is_active() && !$(".home-page-input").is(":focus") @@ -120,9 +122,28 @@ function set_table_focus(row, col) { topic_rows.eq(row).find(".recent_topics_focusable").eq(col).children().trigger("focus"); }, 0); current_focus_elem = "table"; + + const topic_row = topic_rows.eq(row); + const message = { + stream: topic_row.find(".recent_topic_stream a").text(), + topic: topic_row.find(".recent_topic_name a").text(), + }; + compose_closed_ui.update_reply_recipient_label(message); return true; } +export function get_focused_row_message() { + if (is_table_focused()) { + const recent_topic_id_prefix_len = "recent_topic:".length; + const topic_rows = $("#recent_topics_table table tbody tr"); + const topic_row = topic_rows.eq(row_focus); + const topic_id = topic_row.attr("id").slice(recent_topic_id_prefix_len); + const topic_last_msg_id = topics.get(topic_id).last_msg_id; + return message_store.get(topic_last_msg_id); + } + return undefined; +} + function revive_current_focus() { // After re-render, the current_focus_elem is no longer linked // to the focused element, this function attempts to revive the @@ -538,13 +559,10 @@ export function show() { $("#message_view_header_underpadding").hide(); $(".header").css("padding-bottom", "0px"); - // Save text in compose box if open. - drafts.update_draft(); - // Close the compose box, this removes - // any arbitrary bug for compose box in recent topics. - // This is required since, Recent Topics is the only view - // with no compose box. - compose_actions.cancel(); + // We want to show `new stream message` instead of + // `new topic`, which we are already doing in this + // function. So, we reuse it here. + compose.update_closed_compose_buttons_for_recent_topics(); narrow_state.reset_current_filter(); narrow.set_narrow_title("Recent topics"); diff --git a/static/styles/recent_topics.css b/static/styles/recent_topics.css index c044d643ea..6918872aeb 100644 --- a/static/styles/recent_topics.css +++ b/static/styles/recent_topics.css @@ -72,6 +72,17 @@ .table_fix_head table { /* To keep border properties to the thead th. */ border-collapse: separate; + /* For visual reasons, in a message feed, we require a large + * bottom_whitespace to make it convenient to display new + * messages as they come in and prevent occluding the last + * message with an open compose box. Here, the bottom item + * is rarely interesting context for a message one is + * composing, but we do need at least 41px to allow the + * close-compose-box UI element (including border) to not + * overlap content. Add some more margin so that user + * can clearly see the end of the topics. + */ + margin-bottom: 100px; } #recent_topics_filter_buttons { diff --git a/templates/zerver/app/home.html b/templates/zerver/app/home.html index cb7b5483ff..851138377b 100644 --- a/templates/zerver/app/home.html +++ b/templates/zerver/app/home.html @@ -156,8 +156,6 @@ {% include "zerver/app/message_history.html" %} {% include "zerver/app/delete_message.html" %} -
-