diff --git a/web/src/drafts.js b/web/src/drafts.js index 1ac86a08fe..86c15390f6 100644 --- a/web/src/drafts.js +++ b/web/src/drafts.js @@ -617,6 +617,7 @@ export function launch() { }); } update_rendered_drafts(narrow_drafts.length > 0, other_drafts.length > 0); + update_bulk_delete_ui(); } function setup_event_handlers() { @@ -638,8 +639,37 @@ export function launch() { 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(); @@ -663,6 +693,35 @@ export function launch() { 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({ @@ -675,6 +734,19 @@ function open_overlay() { }); } +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(); diff --git a/web/src/tippyjs.js b/web/src/tippyjs.js index 10cb5ef387..3bc09e7650 100644 --- a/web/src/tippyjs.js +++ b/web/src/tippyjs.js @@ -143,13 +143,46 @@ export function initialize() { // below specify the target directly, elements using those should // not have the tippy-zulip-tooltip class. - $("body").on("blur", ".message_control_button", (e) => { - // Remove tooltip when user is trying to tab through all the icons. - // If user tabs slowly, tooltips are displayed otherwise they are - // destroyed before they can be displayed. - e.currentTarget?._tippy?.destroy(); + delegate("body", { + target: ".draft-selection-tooltip", + delay: LONG_HOVER_DELAY, + appendTo: () => document.body, + onShow(instance) { + let content = $t({defaultMessage: "Select draft"}); + const $elem = $(instance.reference); + if ($($elem).parent().find(".draft-selection-checkbox").hasClass("fa-check-square")) { + content = $t({defaultMessage: "Deselect draft"}); + } + instance.setContent(content); + return true; + }, }); + delegate("body", { + target: ".delete-selected-drafts-button-container", + appendTo: () => document.body, + onShow(instance) { + let content = $t({defaultMessage: "Delete all selected drafts"}); + const $elem = $(instance.reference); + if ($($elem).find(".delete-selected-drafts-button").is(":disabled")) { + content = $t({defaultMessage: "No drafts selected"}); + } + instance.setContent(content); + return true; + }, + }); + + $("body").on( + "blur", + ".message_control_button, .delete-selected-drafts-button-container", + (e) => { + // Remove tooltip when user is trying to tab through all the icons. + // If user tabs slowly, tooltips are displayed otherwise they are + // destroyed before they can be displayed. + e.currentTarget?._tippy?.destroy(); + }, + ); + delegate("body", { target: [ "#streams_header .sidebar-title", diff --git a/web/styles/dark_theme.css b/web/styles/dark_theme.css index d1258935ad..85f3e9b7a8 100644 --- a/web/styles/dark_theme.css +++ b/web/styles/dark_theme.css @@ -879,6 +879,10 @@ } } + .drafts-container .header-body .delete-drafts-group > *:focus { + background-color: hsl(228deg 11% 17%); + } + & thead, .drafts-container .drafts-header, .nav > li > a:focus, diff --git a/web/styles/drafts.css b/web/styles/drafts.css index bb5678b209..c293411c8f 100644 --- a/web/styles/drafts.css +++ b/web/styles/drafts.css @@ -1,4 +1,60 @@ .drafts-container { + .header-body { + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-between; + gap: 5px; + + .removed-drafts { + text-align: left; + margin-left: 25px; + + @media (width < $lg_min) { + text-align: center; + margin-left: 0; + } + } + + .delete-drafts-group { + display: flex; + justify-content: flex-end; + gap: 10px; + + .delete-selected-drafts-button { + &:focus { + background-color: hsl(0deg 0% 93%); + } + } + + .select-drafts-button { + display: flex; + align-items: center; + gap: 5px; + margin-right: 25px; + padding-left: 15px; + padding-right: 15px; + + &:focus { + background-color: hsl(0deg 0% 93%); + } + } + + .select-state-indicator { + width: 15px; + } + + @media (width < $lg_min) { + margin-top: 5px; + width: 100%; + } + } + + @media (width < $lg_min) { + display: block; + } + } + .drafts-list { & h2 { font-size: 1.1em; @@ -6,4 +62,12 @@ margin-bottom: 5px; } } + + .draft-selection-checkbox { + margin-top: 5px; + /* Required to make sure that the checkbox icon stays inside + the grid. Any value greater than 13px (original width of + the checkbox icon) will work. */ + width: 15px; + } } diff --git a/web/templates/draft.hbs b/web/templates/draft.hbs index 73bc01eb57..6cffd4e206 100644 --- a/web/templates/draft.hbs +++ b/web/templates/draft.hbs @@ -37,6 +37,9 @@
diff --git a/web/templates/draft_table_body.hbs b/web/templates/draft_table_body.hbs index 15d162d9ee..3fd70f16c7 100644 --- a/web/templates/draft_table_body.hbs +++ b/web/templates/draft_table_body.hbs @@ -6,10 +6,23 @@