diff --git a/tools/test-js-with-node b/tools/test-js-with-node index d8c06bf8fd..a19763311d 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -90,6 +90,7 @@ EXEMPT_FILES = make_set( "web/src/desktop_integration.js", "web/src/dialog_widget.ts", "web/src/drafts.js", + "web/src/drafts_overlay_ui.js", "web/src/dropdown_widget.js", "web/src/echo.js", "web/src/emoji_picker.js", diff --git a/web/src/drafts.js b/web/src/drafts.js index 82c1c79ff3..83e819083e 100644 --- a/web/src/drafts.js +++ b/web/src/drafts.js @@ -5,22 +5,14 @@ import _ from "lodash"; import tippy from "tippy.js"; import render_confirm_delete_all_drafts from "../templates/confirm_dialog/confirm_delete_all_drafts.hbs"; -import render_draft_table_body from "../templates/draft_table_body.hbs"; import * as blueslip from "./blueslip"; -import * as browser_history from "./browser_history"; -import * as compose_actions from "./compose_actions"; import * as compose_state from "./compose_state"; import * as confirm_dialog from "./confirm_dialog"; import {$t, $t_html} from "./i18n"; import {localstorage} from "./localstorage"; import * as markdown from "./markdown"; -import * as messages_overlay_ui from "./messages_overlay_ui"; -import * as narrow from "./narrow"; -import * as narrow_state from "./narrow_state"; -import * as overlays from "./overlays"; import * as people from "./people"; -import * as rendered_markdown from "./rendered_markdown"; import * as stream_color from "./stream_color"; import * as stream_data from "./stream_data"; import * as sub_store from "./sub_store"; @@ -28,7 +20,7 @@ import * as timerender from "./timerender"; import * as ui_util from "./ui_util"; import * as util from "./util"; -function set_count(count) { +export function set_count(count) { const $drafts_li = $(".top_left_drafts"); ui_util.update_unread_count_in_dom($drafts_li, count); } @@ -274,37 +266,7 @@ export function update_draft(opts = {}) { return new_draft_id; } -export function restore_draft(draft_id) { - const draft = draft_model.getDraft(draft_id); - if (!draft) { - return; - } - - const compose_args = {...restore_message(draft), draft_id}; - - if (compose_args.type === "stream") { - if (draft.stream_id !== "" && draft.topic !== "") { - narrow.activate( - [ - {operator: "stream", operand: compose_args.stream_name}, - {operator: "topic", operand: compose_args.topic}, - ], - {trigger: "restore draft"}, - ); - } - } else { - if (compose_args.private_message_recipient !== "") { - narrow.activate([{operator: "dm", operand: compose_args.private_message_recipient}], { - trigger: "restore draft", - }); - } - } - - overlays.close_overlay("drafts"); - compose_actions.start(compose_args.type, compose_args); -} - -const DRAFT_LIFETIME = 30; +export const DRAFT_LIFETIME = 30; export function remove_old_drafts() { const old_date = subDays(new Date(), DRAFT_LIFETIME).getTime(); @@ -399,334 +361,6 @@ export function format_draft(draft) { return formatted; } -function remove_draft($draft_row) { - // Deletes the draft and removes it from the list - const draft_id = $draft_row.data("draft-id"); - - draft_model.deleteDraft(draft_id); - - $draft_row.remove(); - - if ($("#drafts_table .overlay-message-row").length === 0) { - $("#drafts_table .no-drafts").show(); - } - update_rendered_drafts( - $("#drafts-from-conversation .overlay-message-row").length > 0, - $("#other-drafts .overlay-message-row").length > 0, - ); -} - -function update_rendered_drafts(has_drafts_from_conversation, has_other_drafts) { - if (has_drafts_from_conversation) { - $("#drafts-from-conversation").show(); - } else { - // Since there are no relevant drafts from this conversation left, switch to the "all drafts" view and remove headers. - $("#drafts-from-conversation").hide(); - $("#other-drafts-header").hide(); - } - - if (!has_other_drafts) { - $("#other-drafts").hide(); - } -} - -function current_recipient_data() { - // Prioritize recipients from the compose box first. If the compose - // box isn't open, just return data from the current narrow. - if (!compose_state.composing()) { - const stream_name = narrow_state.stream_name(); - return { - stream_name, - topic: narrow_state.topic(), - private_recipients: narrow_state.pm_emails_string(), - }; - } - - if (compose_state.get_message_type() === "stream") { - const stream_name = compose_state.stream_name(); - return { - stream_name, - topic: compose_state.topic(), - private_recipients: undefined, - }; - } else if (compose_state.get_message_type() === "private") { - return { - stream_name: undefined, - topic: undefined, - private_recipients: compose_state.private_message_recipient(), - }; - } - return { - stream_name: undefined, - topic: undefined, - private_recipients: undefined, - }; -} - -function filter_drafts_by_compose_box_and_recipient(drafts) { - const {stream_name, topic, private_recipients} = current_recipient_data(); - const stream_id = stream_name ? stream_data.get_stream_id(stream_name) : undefined; - const narrow_drafts_ids = []; - for (const [id, draft] of Object.entries(drafts)) { - // Match by stream and topic. - if ( - stream_id && - topic && - draft.topic && - util.same_recipient(draft, {type: "stream", stream_id, topic}) - ) { - narrow_drafts_ids.push(id); - } - // Match by only stream. - else if (draft.type === "stream" && stream_id && !topic && draft.stream_id === stream_id) { - narrow_drafts_ids.push(id); - } - // Match by direct message recipient. - else if ( - draft.type === "private" && - private_recipients && - _.isEqual( - draft.private_message_recipient - .split(",") - .map((s) => s.trim()) - .sort(), - private_recipients - .split(",") - .map((s) => s.trim()) - .sort(), - ) - ) { - narrow_drafts_ids.push(id); - } - } - return _.pick(drafts, narrow_drafts_ids); -} - -const keyboard_handling_context = { - get_items_ids() { - const draft_arrow = draft_model.get(); - return Object.getOwnPropertyNames(draft_arrow); - }, - on_enter() { - // This handles when pressing Enter while looking at drafts. - // It restores draft that is focused. - const draft_id_arrow = this.get_items_ids(); - const focused_draft_id = messages_overlay_ui.get_focused_element_id(this); - if (Object.hasOwn(document.activeElement.parentElement.dataset, "draftId")) { - restore_draft(focused_draft_id); - } else { - const first_draft = draft_id_arrow.at(-1); - restore_draft(first_draft); - } - }, - on_delete() { - // Allows user to delete drafts with Backspace - const focused_element_id = messages_overlay_ui.get_focused_element_id(this); - if (focused_element_id === undefined) { - return; - } - const $focused_row = messages_overlay_ui.row_with_focus(this); - messages_overlay_ui.focus_on_sibling_element(this); - remove_draft($focused_row); - }, - items_container_selector: "drafts-container", - items_list_selector: "drafts-list", - row_item_selector: "overlay-message-row", - box_item_selector: "overlay-message-info-box", - id_attribute_name: "data-draft-id", -}; - -export function handle_keyboard_events(event_key) { - messages_overlay_ui.modals_handle_events(event_key, keyboard_handling_context); -} - -export function launch() { - function format_drafts(data) { - for (const [id, draft] of Object.entries(data)) { - draft.id = id; - } - - const unsorted_raw_drafts = Object.values(data); - - const sorted_raw_drafts = unsorted_raw_drafts.sort( - (draft_a, draft_b) => draft_b.updatedAt - draft_a.updatedAt, - ); - - const sorted_formatted_drafts = sorted_raw_drafts - .map((draft_row) => format_draft(draft_row)) - .filter(Boolean); - - return sorted_formatted_drafts; - } - - function get_header_for_narrow_drafts() { - const {stream_name, topic, private_recipients} = current_recipient_data(); - if (private_recipients) { - return $t( - {defaultMessage: "Drafts from conversation with {recipient}"}, - { - recipient: people.emails_to_full_names_string(private_recipients.split(",")), - }, - ); - } - const recipient = topic ? `#${stream_name} > ${topic}` : `#${stream_name}`; - return $t({defaultMessage: "Drafts from {recipient}"}, {recipient}); - } - - function render_widgets(narrow_drafts, other_drafts) { - $("#drafts_table").empty(); - - const narrow_drafts_header = get_header_for_narrow_drafts(); - - const rendered = render_draft_table_body({ - narrow_drafts_header, - narrow_drafts, - other_drafts, - draft_lifetime: DRAFT_LIFETIME, - }); - const $drafts_table = $("#drafts_table"); - $drafts_table.append(rendered); - if ($("#drafts_table .overlay-message-row").length > 0) { - $("#drafts_table .no-drafts").hide(); - // Update possible dynamic elements. - const $rendered_drafts = $drafts_table.find( - ".message_content.rendered_markdown.restore-overlay-message", - ); - $rendered_drafts.each(function () { - rendered_markdown.update_elements($(this)); - }); - } - update_rendered_drafts(narrow_drafts.length > 0, other_drafts.length > 0); - update_bulk_delete_ui(); - } - - function setup_event_handlers() { - $("#drafts_table .restore-overlay-message").on("click", function (e) { - if (document.getSelection().type === "Range") { - return; - } - - e.stopPropagation(); - - const $draft_row = $(this).closest(".overlay-message-row"); - const $draft_id = $draft_row.data("draft-id"); - restore_draft($draft_id); - }); - - $("#drafts_table .overlay_message_controls .delete-overlay-message").on( - "click", - function () { - const $draft_row = $(this).closest(".overlay-message-row"); - - remove_draft($draft_row); - update_bulk_delete_ui(); - }, - ); - - $("#drafts_table .overlay_message_controls .draft-selection-checkbox").on("click", (e) => { - const is_checked = is_checkbox_icon_checked($(e.target)); - toggle_checkbox_icon_state($(e.target), !is_checked); - update_bulk_delete_ui(); - }); - - $(".select-drafts-button").on("click", (e) => { - e.preventDefault(); - const $unchecked_checkboxes = $(".draft-selection-checkbox").filter(function () { - return !is_checkbox_icon_checked($(this)); - }); - const check_boxes = $unchecked_checkboxes.length > 0; - $(".draft-selection-checkbox").each(function () { - toggle_checkbox_icon_state($(this), check_boxes); - }); - update_bulk_delete_ui(); - }); - - $(".delete-selected-drafts-button").on("click", () => { - $(".drafts-list") - .find(".draft-selection-checkbox.fa-check-square") - .closest(".overlay-message-row") - .each(function () { - remove_draft($(this)); - }); - update_bulk_delete_ui(); - }); - } - - const drafts = draft_model.get(); - const narrow_drafts = filter_drafts_by_compose_box_and_recipient(drafts); - const other_drafts = _.pick( - drafts, - _.difference(Object.keys(drafts), Object.keys(narrow_drafts)), - ); - const formatted_narrow_drafts = format_drafts(narrow_drafts); - const formatted_other_drafts = format_drafts(other_drafts); - - render_widgets(formatted_narrow_drafts, formatted_other_drafts); - - // We need to force a style calculation on the newly created - // element in order for the CSS transition to take effect. - $("#draft_overlay").css("opacity"); - - open_overlay(); - const first_element_id = [...formatted_narrow_drafts, ...formatted_other_drafts][0]?.draft_id; - messages_overlay_ui.set_initial_element(first_element_id, keyboard_handling_context); - setup_event_handlers(); -} - -function update_bulk_delete_ui() { - const $unchecked_checkboxes = $(".draft-selection-checkbox").filter(function () { - return !is_checkbox_icon_checked($(this)); - }); - const $checked_checkboxes = $(".draft-selection-checkbox").filter(function () { - return is_checkbox_icon_checked($(this)); - }); - const $select_drafts_button = $(".select-drafts-button"); - const $select_state_indicator = $(".select-drafts-button .select-state-indicator"); - const $delete_selected_drafts_button = $(".delete-selected-drafts-button"); - - if ($checked_checkboxes.length > 0) { - $delete_selected_drafts_button.prop("disabled", false); - if ($unchecked_checkboxes.length === 0) { - toggle_checkbox_icon_state($select_state_indicator, true); - } else { - toggle_checkbox_icon_state($select_state_indicator, false); - } - } else { - if ($unchecked_checkboxes.length > 0) { - toggle_checkbox_icon_state($select_state_indicator, false); - $delete_selected_drafts_button.prop("disabled", true); - } else { - $select_drafts_button.hide(); - $delete_selected_drafts_button.hide(); - } - } -} - -function open_overlay() { - sync_count(); - overlays.open_overlay({ - name: "drafts", - $overlay: $("#draft_overlay"), - on_close() { - browser_history.exit_overlay(); - sync_count(); - }, - }); -} - -function is_checkbox_icon_checked($checkbox) { - return $checkbox.hasClass("fa-check-square"); -} - -function toggle_checkbox_icon_state($checkbox, checked) { - $checkbox.parent().attr("aria-checked", checked); - if (checked) { - $checkbox.removeClass("fa-square-o").addClass("fa-check-square"); - } else { - $checkbox.removeClass("fa-check-square").addClass("fa-square-o"); - } -} - export function initialize() { remove_old_drafts(); @@ -739,8 +373,4 @@ export function initialize() { }); set_count(Object.keys(draft_model.get()).length); - - $("body").on("focus", "#drafts_table .overlay-message-info-box", (e) => { - messages_overlay_ui.activate_element(e.target, keyboard_handling_context); - }); } diff --git a/web/src/drafts_overlay_ui.js b/web/src/drafts_overlay_ui.js new file mode 100644 index 0000000000..68a8fd6167 --- /dev/null +++ b/web/src/drafts_overlay_ui.js @@ -0,0 +1,382 @@ +import $ from "jquery"; +import _ from "lodash"; + +import render_draft_table_body from "../templates/draft_table_body.hbs"; + +import * as browser_history from "./browser_history"; +import * as compose_actions from "./compose_actions"; +import * as compose_state from "./compose_state"; +import * as drafts from "./drafts"; +import {$t} from "./i18n"; +import * as messages_overlay_ui from "./messages_overlay_ui"; +import * as narrow from "./narrow"; +import * as narrow_state from "./narrow_state"; +import * as overlays from "./overlays"; +import * as people from "./people"; +import * as rendered_markdown from "./rendered_markdown"; +import * as stream_data from "./stream_data"; +import * as util from "./util"; + +function restore_draft(draft_id) { + const draft = drafts.draft_model.getDraft(draft_id); + if (!draft) { + return; + } + + const compose_args = {...drafts.restore_message(draft), draft_id}; + + if (compose_args.type === "stream") { + if (draft.stream_id !== "" && draft.topic !== "") { + narrow.activate( + [ + {operator: "stream", operand: compose_args.stream_name}, + {operator: "topic", operand: compose_args.topic}, + ], + {trigger: "restore draft"}, + ); + } + } else { + if (compose_args.private_message_recipient !== "") { + narrow.activate([{operator: "dm", operand: compose_args.private_message_recipient}], { + trigger: "restore draft", + }); + } + } + + overlays.close_overlay("drafts"); + compose_actions.start(compose_args.type, compose_args); +} + +function remove_draft($draft_row) { + // Deletes the draft and removes it from the list + const draft_id = $draft_row.data("draft-id"); + + drafts.draft_model.deleteDraft(draft_id); + + $draft_row.remove(); + + if ($("#drafts_table .overlay-message-row").length === 0) { + $("#drafts_table .no-drafts").show(); + } + update_rendered_drafts( + $("#drafts-from-conversation .overlay-message-row").length > 0, + $("#other-drafts .overlay-message-row").length > 0, + ); +} + +function update_rendered_drafts(has_drafts_from_conversation, has_other_drafts) { + if (has_drafts_from_conversation) { + $("#drafts-from-conversation").show(); + } else { + // Since there are no relevant drafts from this conversation left, switch to the "all drafts" view and remove headers. + $("#drafts-from-conversation").hide(); + $("#other-drafts-header").hide(); + } + + if (!has_other_drafts) { + $("#other-drafts").hide(); + } +} + +function current_recipient_data() { + // Prioritize recipients from the compose box first. If the compose + // box isn't open, just return data from the current narrow. + if (!compose_state.composing()) { + const stream_name = narrow_state.stream_name(); + return { + stream_name, + topic: narrow_state.topic(), + private_recipients: narrow_state.pm_emails_string(), + }; + } + + if (compose_state.get_message_type() === "stream") { + const stream_name = compose_state.stream_name(); + return { + stream_name, + topic: compose_state.topic(), + private_recipients: undefined, + }; + } else if (compose_state.get_message_type() === "private") { + return { + stream_name: undefined, + topic: undefined, + private_recipients: compose_state.private_message_recipient(), + }; + } + return { + stream_name: undefined, + topic: undefined, + private_recipients: undefined, + }; +} + +function filter_drafts_by_compose_box_and_recipient(drafts) { + const {stream_name, topic, private_recipients} = current_recipient_data(); + const stream_id = stream_name ? stream_data.get_stream_id(stream_name) : undefined; + const narrow_drafts_ids = []; + for (const [id, draft] of Object.entries(drafts)) { + // Match by stream and topic. + if ( + stream_id && + topic && + draft.topic && + util.same_recipient(draft, {type: "stream", stream_id, topic}) + ) { + narrow_drafts_ids.push(id); + } + // Match by only stream. + else if (draft.type === "stream" && stream_id && !topic && draft.stream_id === stream_id) { + narrow_drafts_ids.push(id); + } + // Match by direct message recipient. + else if ( + draft.type === "private" && + private_recipients && + _.isEqual( + draft.private_message_recipient + .split(",") + .map((s) => s.trim()) + .sort(), + private_recipients + .split(",") + .map((s) => s.trim()) + .sort(), + ) + ) { + narrow_drafts_ids.push(id); + } + } + return _.pick(drafts, narrow_drafts_ids); +} + +const keyboard_handling_context = { + get_items_ids() { + const draft_arrow = drafts.draft_model.get(); + return Object.getOwnPropertyNames(draft_arrow); + }, + on_enter() { + // This handles when pressing Enter while looking at drafts. + // It restores draft that is focused. + const draft_id_arrow = this.get_items_ids(); + const focused_draft_id = messages_overlay_ui.get_focused_element_id(this); + if (Object.hasOwn(document.activeElement.parentElement.dataset, "draftId")) { + restore_draft(focused_draft_id); + } else { + const first_draft = draft_id_arrow.at(-1); + restore_draft(first_draft); + } + }, + on_delete() { + // Allows user to delete drafts with Backspace + const focused_element_id = messages_overlay_ui.get_focused_element_id(this); + if (focused_element_id === undefined) { + return; + } + const $focused_row = messages_overlay_ui.row_with_focus(this); + messages_overlay_ui.focus_on_sibling_element(this); + remove_draft($focused_row); + }, + items_container_selector: "drafts-container", + items_list_selector: "drafts-list", + row_item_selector: "overlay-message-row", + box_item_selector: "overlay-message-info-box", + id_attribute_name: "data-draft-id", +}; + +export function handle_keyboard_events(event_key) { + messages_overlay_ui.modals_handle_events(event_key, keyboard_handling_context); +} + +export function launch() { + function format_drafts(data) { + for (const [id, draft] of Object.entries(data)) { + draft.id = id; + } + + const unsorted_raw_drafts = Object.values(data); + + const sorted_raw_drafts = unsorted_raw_drafts.sort( + (draft_a, draft_b) => draft_b.updatedAt - draft_a.updatedAt, + ); + + const sorted_formatted_drafts = sorted_raw_drafts + .map((draft_row) => drafts.format_draft(draft_row)) + .filter(Boolean); + + return sorted_formatted_drafts; + } + + function get_header_for_narrow_drafts() { + const {stream_name, topic, private_recipients} = current_recipient_data(); + if (private_recipients) { + return $t( + {defaultMessage: "Drafts from conversation with {recipient}"}, + { + recipient: people.emails_to_full_names_string(private_recipients.split(",")), + }, + ); + } + const recipient = topic ? `#${stream_name} > ${topic}` : `#${stream_name}`; + return $t({defaultMessage: "Drafts from {recipient}"}, {recipient}); + } + + function render_widgets(narrow_drafts, other_drafts) { + $("#drafts_table").empty(); + + const narrow_drafts_header = get_header_for_narrow_drafts(); + + const rendered = render_draft_table_body({ + narrow_drafts_header, + narrow_drafts, + other_drafts, + draft_lifetime: drafts.DRAFT_LIFETIME, + }); + const $drafts_table = $("#drafts_table"); + $drafts_table.append(rendered); + if ($("#drafts_table .overlay-message-row").length > 0) { + $("#drafts_table .no-drafts").hide(); + // Update possible dynamic elements. + const $rendered_drafts = $drafts_table.find( + ".message_content.rendered_markdown.restore-overlay-message", + ); + $rendered_drafts.each(function () { + rendered_markdown.update_elements($(this)); + }); + } + update_rendered_drafts(narrow_drafts.length > 0, other_drafts.length > 0); + update_bulk_delete_ui(); + } + + function setup_event_handlers() { + $("#drafts_table .restore-overlay-message").on("click", function (e) { + if (document.getSelection().type === "Range") { + return; + } + + e.stopPropagation(); + + const $draft_row = $(this).closest(".overlay-message-row"); + const $draft_id = $draft_row.data("draft-id"); + restore_draft($draft_id); + }); + + $("#drafts_table .overlay_message_controls .delete-overlay-message").on( + "click", + function () { + const $draft_row = $(this).closest(".overlay-message-row"); + + remove_draft($draft_row); + update_bulk_delete_ui(); + }, + ); + + $("#drafts_table .overlay_message_controls .draft-selection-checkbox").on("click", (e) => { + const is_checked = is_checkbox_icon_checked($(e.target)); + toggle_checkbox_icon_state($(e.target), !is_checked); + update_bulk_delete_ui(); + }); + + $(".select-drafts-button").on("click", (e) => { + e.preventDefault(); + const $unchecked_checkboxes = $(".draft-selection-checkbox").filter(function () { + return !is_checkbox_icon_checked($(this)); + }); + const check_boxes = $unchecked_checkboxes.length > 0; + $(".draft-selection-checkbox").each(function () { + toggle_checkbox_icon_state($(this), check_boxes); + }); + update_bulk_delete_ui(); + }); + + $(".delete-selected-drafts-button").on("click", () => { + $(".drafts-list") + .find(".draft-selection-checkbox.fa-check-square") + .closest(".overlay-message-row") + .each(function () { + remove_draft($(this)); + }); + update_bulk_delete_ui(); + }); + } + + const all_drafts = drafts.draft_model.get(); + const narrow_drafts = filter_drafts_by_compose_box_and_recipient(all_drafts); + const other_drafts = _.pick( + all_drafts, + _.difference(Object.keys(all_drafts), Object.keys(narrow_drafts)), + ); + const formatted_narrow_drafts = format_drafts(narrow_drafts); + const formatted_other_drafts = format_drafts(other_drafts); + + render_widgets(formatted_narrow_drafts, formatted_other_drafts); + + // We need to force a style calculation on the newly created + // element in order for the CSS transition to take effect. + $("#draft_overlay").css("opacity"); + + open_overlay(); + const first_element_id = [...formatted_narrow_drafts, ...formatted_other_drafts][0]?.draft_id; + messages_overlay_ui.set_initial_element(first_element_id, keyboard_handling_context); + setup_event_handlers(); +} + +export function update_bulk_delete_ui() { + const $unchecked_checkboxes = $(".draft-selection-checkbox").filter(function () { + return !is_checkbox_icon_checked($(this)); + }); + const $checked_checkboxes = $(".draft-selection-checkbox").filter(function () { + return is_checkbox_icon_checked($(this)); + }); + const $select_drafts_button = $(".select-drafts-button"); + const $select_state_indicator = $(".select-drafts-button .select-state-indicator"); + const $delete_selected_drafts_button = $(".delete-selected-drafts-button"); + + if ($checked_checkboxes.length > 0) { + $delete_selected_drafts_button.prop("disabled", false); + if ($unchecked_checkboxes.length === 0) { + toggle_checkbox_icon_state($select_state_indicator, true); + } else { + toggle_checkbox_icon_state($select_state_indicator, false); + } + } else { + if ($unchecked_checkboxes.length > 0) { + toggle_checkbox_icon_state($select_state_indicator, false); + $delete_selected_drafts_button.prop("disabled", true); + } else { + $select_drafts_button.hide(); + $delete_selected_drafts_button.hide(); + } + } +} + +export function open_overlay() { + drafts.sync_count(); + overlays.open_overlay({ + name: "drafts", + $overlay: $("#draft_overlay"), + on_close() { + browser_history.exit_overlay(); + drafts.sync_count(); + }, + }); +} + +export function is_checkbox_icon_checked($checkbox) { + return $checkbox.hasClass("fa-check-square"); +} + +export function toggle_checkbox_icon_state($checkbox, checked) { + $checkbox.parent().attr("aria-checked", checked); + if (checked) { + $checkbox.removeClass("fa-square-o").addClass("fa-check-square"); + } else { + $checkbox.removeClass("fa-check-square").addClass("fa-square-o"); + } +} + +export function initialize() { + $("body").on("focus", "#drafts_table .overlay-message-info-box", (e) => { + messages_overlay_ui.activate_element(e.target, keyboard_handling_context); + }); +} diff --git a/web/src/hashchange.js b/web/src/hashchange.js index a8f498a42e..f3e8730829 100644 --- a/web/src/hashchange.js +++ b/web/src/hashchange.js @@ -4,7 +4,7 @@ import * as about_zulip from "./about_zulip"; import * as admin from "./admin"; import * as blueslip from "./blueslip"; import * as browser_history from "./browser_history"; -import * as drafts from "./drafts"; +import * as drafts_overlay_ui from "./drafts_overlay_ui"; import * as hash_parser from "./hash_parser"; import * as hash_util from "./hash_util"; import {$t_html} from "./i18n"; @@ -400,7 +400,7 @@ function do_hashchange_overlay(old_hash) { } if (base === "drafts") { - drafts.launch(); + drafts_overlay_ui.launch(); return; } diff --git a/web/src/hotkey.js b/web/src/hotkey.js index ca985768f8..65f9ce32c1 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -13,7 +13,7 @@ import * as compose_textarea from "./compose_textarea"; import * as condense from "./condense"; import * as copy_and_paste from "./copy_and_paste"; import * as deprecated_feature_notice from "./deprecated_feature_notice"; -import * as drafts from "./drafts"; +import * as drafts_overlay_ui from "./drafts_overlay_ui"; import * as emoji from "./emoji"; import * as emoji_picker from "./emoji_picker"; import * as feedback_widget from "./feedback_widget"; @@ -460,7 +460,7 @@ export function process_enter_key(e) { // This handles when pressing Enter while looking at drafts. // It restores draft that is focused. if (overlays.drafts_open()) { - drafts.handle_keyboard_events("enter"); + drafts_overlay_ui.handle_keyboard_events("enter"); return true; } @@ -705,7 +705,7 @@ export function process_hotkey(e, hotkey) { case "backspace": case "delete": if (overlays.drafts_open()) { - drafts.handle_keyboard_events(event_name); + drafts_overlay_ui.handle_keyboard_events(event_name); return true; } if (overlays.scheduled_messages_open()) { diff --git a/web/src/ui_init.js b/web/src/ui_init.js index f1e5e08dde..d368900596 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -34,6 +34,7 @@ import * as condense from "./condense"; import * as copy_and_paste from "./copy_and_paste"; import * as dark_theme from "./dark_theme"; import * as drafts from "./drafts"; +import * as drafts_overlay_ui from "./drafts_overlay_ui"; import * as echo from "./echo"; import * as emoji from "./emoji"; import * as emoji_picker from "./emoji_picker"; @@ -697,6 +698,7 @@ export function initialize_everything() { }); topic_zoom.initialize(); drafts.initialize(); + drafts_overlay_ui.initialize(); sent_messages.initialize(); hotspots.initialize(); typing.initialize(); diff --git a/web/tests/drafts.test.js b/web/tests/drafts.test.js index ebf1491845..6e7bbf94df 100644 --- a/web/tests/drafts.test.js +++ b/web/tests/drafts.test.js @@ -63,6 +63,7 @@ user_settings.twenty_four_hour_time = false; const {localstorage} = zrequire("localstorage"); const drafts = zrequire("drafts"); +const drafts_overlay_ui = zrequire("drafts_overlay_ui"); const messages_overlay_ui = zrequire("messages_overlay_ui"); const timerender = zrequire("timerender"); @@ -214,6 +215,7 @@ test("initialize", ({override_rewire}) => { $(".top_left_drafts").set_find_results(".unread_count", $unread_count); drafts.initialize(); + drafts_overlay_ui.initialize(); }); test("remove_old_drafts", () => { @@ -603,7 +605,7 @@ test("format_drafts", ({override_rewire, mock_template}) => { $.create("#drafts_table .overlay-message-row", {children: []}); $(".draft-selection-checkbox").filter = () => []; - drafts.launch(); + drafts_overlay_ui.launch(); $.clear_all_elements(); $.create("#drafts_table .overlay-message-row", {children: []}); @@ -622,7 +624,7 @@ test("format_drafts", ({override_rewire, mock_template}) => { $(".top_left_drafts").set_find_results(".unread_count", $unread_count); $(".draft-selection-checkbox").filter = () => []; - drafts.launch(); + drafts_overlay_ui.launch(); }); test("filter_drafts", ({override_rewire, mock_template}) => { @@ -770,5 +772,5 @@ test("filter_drafts", ({override_rewire, mock_template}) => { $.create("#drafts_table .overlay-message-row", {children: []}); $(".draft-selection-checkbox").filter = () => []; - drafts.launch(); + drafts_overlay_ui.launch(); }); diff --git a/web/tests/hashchange.test.js b/web/tests/hashchange.test.js index 8b09af74d4..651f5da219 100644 --- a/web/tests/hashchange.test.js +++ b/web/tests/hashchange.test.js @@ -15,7 +15,7 @@ set_global("document", "document-stub"); const history = set_global("history", {}); const admin = mock_esm("../src/admin"); -const drafts = mock_esm("../src/drafts"); +const drafts_overlay_ui = mock_esm("../src/drafts_overlay_ui"); const info_overlay = mock_esm("../src/info_overlay"); const message_viewport = mock_esm("../src/message_viewport"); const narrow = mock_esm("../src/narrow"); @@ -132,7 +132,7 @@ function test_helper({override, change_tab}) { stub(admin, "launch"); stub(admin, "build_page"); - stub(drafts, "launch"); + stub(drafts_overlay_ui, "launch"); stub(message_viewport, "stop_auto_scrolling"); stub(narrow, "deactivate"); stub(overlays, "close_for_hash_change"); @@ -300,7 +300,7 @@ run_test("hash_interactions", ({override}) => { $window_stub.trigger("hashchange"); helper.assert_events([ [overlays, "close_for_hash_change"], - [drafts, "launch"], + [drafts_overlay_ui, "launch"], ]); window.location.hash = "#settings/alert-words"; diff --git a/web/tests/hotkey.test.js b/web/tests/hotkey.test.js index 0d29314084..9e08a3dd67 100644 --- a/web/tests/hotkey.test.js +++ b/web/tests/hotkey.test.js @@ -35,7 +35,7 @@ set_global("document", "document-stub"); const browser_history = mock_esm("../src/browser_history"); const compose_actions = mock_esm("../src/compose_actions"); const condense = mock_esm("../src/condense"); -const drafts = mock_esm("../src/drafts"); +const drafts_overlay_ui = mock_esm("../src/drafts_overlay_ui"); const emoji_picker = mock_esm("../src/emoji_picker", { is_open: () => false, toggle_emoji_popover() {}, @@ -539,8 +539,8 @@ run_test("motion_keys", () => { delete overlays.is_active; overlays.drafts_open = () => true; - assert_mapping("up_arrow", drafts, "handle_keyboard_events"); - assert_mapping("down_arrow", drafts, "handle_keyboard_events"); + assert_mapping("up_arrow", drafts_overlay_ui, "handle_keyboard_events"); + assert_mapping("down_arrow", drafts_overlay_ui, "handle_keyboard_events"); delete overlays.is_active; delete overlays.drafts_open; });