mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	modal: Add options for marking messages as read.
We have updated our read messages confirmation modal to now offer three options for marking messages as read: - Muted topic messages - Messages from topics I don't follow - All unread messages Additionally, the modal now displays the count of unread messages for the selected option. Fixes: #30025.
This commit is contained in:
		
				
					committed by
					
						 Tim Abbott
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							01ce268d64
						
					
				
				
					commit
					bbc842484f
				
			| @@ -46,7 +46,7 @@ function register_mark_all_read_handler( | |||||||
|     >, |     >, | ||||||
| ): void { | ): void { | ||||||
|     const {instance} = event.data; |     const {instance} = event.data; | ||||||
|     unread_ops.confirm_mark_all_as_read(); |     unread_ops.confirm_mark_messages_as_read(); | ||||||
|     popover_menus.hide_current_popover_if_visible(instance); |     popover_menus.hide_current_popover_if_visible(instance); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,6 +41,10 @@ export function clear_old_unreads_missing(): void { | |||||||
|     old_unreads_missing = false; |     old_unreads_missing = false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function set_old_unreads_missing_for_tests(value: boolean): void { | ||||||
|  |     old_unreads_missing = value; | ||||||
|  | } | ||||||
|  |  | ||||||
| export const unread_mentions_counter = new Set<number>(); | export const unread_mentions_counter = new Set<number>(); | ||||||
| export const direct_message_with_mention_count = new Set(); | export const direct_message_with_mention_count = new Set(); | ||||||
| const unread_messages = new Set<number>(); | const unread_messages = new Set<number>(); | ||||||
| @@ -912,6 +916,7 @@ export type FullUnreadCountsData = { | |||||||
|     stream_unread_messages: number; |     stream_unread_messages: number; | ||||||
|     followed_topic_unread_messages_count: number; |     followed_topic_unread_messages_count: number; | ||||||
|     followed_topic_unread_messages_with_mention_count: number; |     followed_topic_unread_messages_with_mention_count: number; | ||||||
|  |     unfollowed_topic_unread_messages_count: number; | ||||||
|     muted_topic_unread_messages_count: number; |     muted_topic_unread_messages_count: number; | ||||||
|     stream_count: Map<number, StreamCountInfo>; |     stream_count: Map<number, StreamCountInfo>; | ||||||
|     streams_with_mentions: number[]; |     streams_with_mentions: number[]; | ||||||
| @@ -939,6 +944,8 @@ export function get_counts(): FullUnreadCountsData { | |||||||
|         followed_topic_unread_messages_count: topic_res.followed_topic_unread_messages, |         followed_topic_unread_messages_count: topic_res.followed_topic_unread_messages, | ||||||
|         followed_topic_unread_messages_with_mention_count: |         followed_topic_unread_messages_with_mention_count: | ||||||
|             unread_topic_counter.get_followed_topic_unread_mentions(), |             unread_topic_counter.get_followed_topic_unread_mentions(), | ||||||
|  |         unfollowed_topic_unread_messages_count: | ||||||
|  |             unread_messages.size - topic_res.followed_topic_unread_messages - pm_res.total_count, | ||||||
|         muted_topic_unread_messages_count: |         muted_topic_unread_messages_count: | ||||||
|             unread_messages.size - topic_res.stream_unread_messages - pm_res.total_count, |             unread_messages.size - topic_res.stream_unread_messages - pm_res.total_count, | ||||||
|         stream_count: topic_res.stream_count, |         stream_count: topic_res.stream_count, | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import _ from "lodash"; | |||||||
| import assert from "minimalistic-assert"; | import assert from "minimalistic-assert"; | ||||||
| import {z} from "zod"; | import {z} from "zod"; | ||||||
|  |  | ||||||
| import render_confirm_mark_all_as_read from "../templates/confirm_dialog/confirm_mark_all_as_read.hbs"; | import render_confirm_mark_messages_as_read from "../templates/confirm_dialog/confirm_mark_all_as_read.hbs"; | ||||||
| import render_confirm_mark_as_unread_from_here from "../templates/confirm_dialog/confirm_mark_as_unread_from_here.hbs"; | import render_confirm_mark_as_unread_from_here from "../templates/confirm_dialog/confirm_mark_as_unread_from_here.hbs"; | ||||||
| import render_inline_decorated_channel_name from "../templates/inline_decorated_channel_name.hbs"; | import render_inline_decorated_channel_name from "../templates/inline_decorated_channel_name.hbs"; | ||||||
| import render_skipped_marking_unread from "../templates/skipped_marking_unread.hbs"; | import render_skipped_marking_unread from "../templates/skipped_marking_unread.hbs"; | ||||||
| @@ -66,17 +66,37 @@ export function is_window_focused(): boolean { | |||||||
|     return window_focused; |     return window_focused; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function confirm_mark_all_as_read(): void { | export function confirm_mark_messages_as_read(): void { | ||||||
|     const html_body = render_confirm_mark_all_as_read(); |     const html_body = render_confirm_mark_messages_as_read(); | ||||||
|  |  | ||||||
|     const modal_id = confirm_dialog.launch({ |     const modal_id = confirm_dialog.launch({ | ||||||
|         html_heading: $t_html({defaultMessage: "Mark all messages as read?"}), |         html_heading: $t_html({defaultMessage: "Choose messages to mark as read"}), | ||||||
|         html_body, |         html_body, | ||||||
|         on_click() { |         on_click() { | ||||||
|             mark_all_as_read(modal_id); |             handle_mark_messages_as_read(modal_id); | ||||||
|         }, |         }, | ||||||
|         loading_spinner: true, |         loading_spinner: true, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // When the user clicks on "Mark messages as read," the dialog box opens with a | ||||||
|  |     // dropdown that, by default, displays the count of unread messages in | ||||||
|  |     // topics that the user does not follow. | ||||||
|  |     const default_messages_count = unread.get_counts().unfollowed_topic_unread_messages_count; | ||||||
|  |     $("#message_count").text(get_message_count_text(default_messages_count)); | ||||||
|  |  | ||||||
|  |     // When the user selects another option from the dropdown, this section is executed. | ||||||
|  |     $("#mark_as_read_option").on("change", function () { | ||||||
|  |         const selected_option = $(this).val(); | ||||||
|  |         let messages_count; | ||||||
|  |         if (selected_option === "muted_topics") { | ||||||
|  |             messages_count = unread.get_counts().muted_topic_unread_messages_count; | ||||||
|  |         } else if (selected_option === "topics_not_followed") { | ||||||
|  |             messages_count = unread.get_counts().unfollowed_topic_unread_messages_count; | ||||||
|  |         } else { | ||||||
|  |             messages_count = unread.get_unread_message_count(); | ||||||
|  |         } | ||||||
|  |         $("#message_count").text(get_message_count_text(messages_count)); | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| const update_flags_for_narrow_response_schema = z.object({ | const update_flags_for_narrow_response_schema = z.object({ | ||||||
| @@ -129,6 +149,24 @@ function handle_skipped_unsubscribed_streams( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function get_message_count_text(count: number): string { | ||||||
|  |     if (unread.old_unreads_missing) { | ||||||
|  |         return $t( | ||||||
|  |             { | ||||||
|  |                 defaultMessage: "{count}+ messages will be marked as read.", | ||||||
|  |             }, | ||||||
|  |             {count}, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |     return $t( | ||||||
|  |         { | ||||||
|  |             defaultMessage: | ||||||
|  |                 "{count, plural, one {# message} other {# messages}} will be marked as read.", | ||||||
|  |         }, | ||||||
|  |         {count}, | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
| function bulk_update_read_flags_for_narrow( | function bulk_update_read_flags_for_narrow( | ||||||
|     narrow: NarrowTerm[], |     narrow: NarrowTerm[], | ||||||
|     op: "add" | "remove", |     op: "add" | "remove", | ||||||
| @@ -329,6 +367,28 @@ function bulk_update_read_flags_for_narrow( | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function handle_mark_messages_as_read(modal_id: string): void { | ||||||
|  |     const selected_option = $("#mark_as_read_option").val(); | ||||||
|  |  | ||||||
|  |     switch (selected_option) { | ||||||
|  |         case "muted_topics": { | ||||||
|  |             mark_muted_topic_messages_as_read(modal_id); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case "topics_not_followed": { | ||||||
|  |             mark_unfollowed_topic_messages_as_read(modal_id); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case "all_messages": { | ||||||
|  |             mark_all_as_read(modal_id); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         default: { | ||||||
|  |             assert(false, `Invalid mark_as_read_option: ${String(selected_option)}`); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function process_newly_read_message( | function process_newly_read_message( | ||||||
|     message: Message, |     message: Message, | ||||||
|     options: {from?: "pointer" | "server"}, |     options: {from?: "pointer" | "server"}, | ||||||
| @@ -838,6 +898,31 @@ export function mark_all_as_read(modal_id?: string): void { | |||||||
|     bulk_update_read_flags_for_narrow(all_unread_messages_narrow, "add", {}, modal_id); |     bulk_update_read_flags_for_narrow(all_unread_messages_narrow, "add", {}, modal_id); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function mark_muted_topic_messages_as_read(modal_id?: string): void { | ||||||
|  |     bulk_update_read_flags_for_narrow( | ||||||
|  |         [ | ||||||
|  |             {operator: "is", operand: "unread", negated: false}, | ||||||
|  |             {operator: "is", operand: "muted", negated: false}, | ||||||
|  |         ], | ||||||
|  |         "add", | ||||||
|  |         {}, | ||||||
|  |         modal_id, | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function mark_unfollowed_topic_messages_as_read(modal_id?: string): void { | ||||||
|  |     bulk_update_read_flags_for_narrow( | ||||||
|  |         [ | ||||||
|  |             {operator: "is", operand: "unread", negated: false}, | ||||||
|  |             {operator: "is", operand: "followed", negated: true}, | ||||||
|  |             {operator: "is", operand: "dm", negated: true}, | ||||||
|  |         ], | ||||||
|  |         "add", | ||||||
|  |         {}, | ||||||
|  |         modal_id, | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
| export function mark_pm_as_read(user_ids_string: string): void { | export function mark_pm_as_read(user_ids_string: string): void { | ||||||
|     // user_ids_string is a stringified list of user ids which are |     // user_ids_string is a stringified list of user ids which are | ||||||
|     // participants in the conversation other than the current |     // participants in the conversation other than the current | ||||||
|   | |||||||
| @@ -367,6 +367,10 @@ select.settings_select { | |||||||
|     & label.checkbox + label { |     & label.checkbox + label { | ||||||
|         cursor: pointer; |         cursor: pointer; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     .message_count { | ||||||
|  |         margin: 10px 0 0; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Class for displaying an input with an | /* Class for displaying an input with an | ||||||
|   | |||||||
| @@ -1,3 +1,11 @@ | |||||||
| <p> | <p> | ||||||
|     {{t "Are you sure you want to mark all messages as read? This action cannot be undone." }} |     {{t "Which messages do you want to mark as read? This action cannot be undone." }} | ||||||
| </p> | </p> | ||||||
|  | <div class="input-group"> | ||||||
|  |     <select id="mark_as_read_option" class="modal_select bootstrap-style-font"> | ||||||
|  |         <option value="muted_topics">{{t "Muted topics" }}</option> | ||||||
|  |         <option value="topics_not_followed" selected>{{t "Topics you don't follow" }}</option> | ||||||
|  |         <option value="all_messages">{{t "All messages" }}</option> | ||||||
|  |     </select> | ||||||
|  |     <p id="message_count" class="message_count"></p> | ||||||
|  | </div> | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|         <li role="none" class="link-item popover-menu-list-item"> |         <li role="none" class="link-item popover-menu-list-item"> | ||||||
|             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> |             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> | ||||||
|                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> |                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> | ||||||
|                 <span class="popover-menu-label">{{t "Mark all messages as read" }}</span> |                 <span class="popover-menu-label">{{t "Mark messages as read" }}</span> | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         {{/if}} |         {{/if}} | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|         <li role="none" class="link-item popover-menu-list-item"> |         <li role="none" class="link-item popover-menu-list-item"> | ||||||
|             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> |             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> | ||||||
|                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> |                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> | ||||||
|                 <span class="popover-menu-label">{{t "Mark all messages as read" }}</span> |                 <span class="popover-menu-label">{{t "Mark messages as read" }}</span> | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         {{/if}} |         {{/if}} | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|         <li role="none" class="link-item popover-menu-list-item"> |         <li role="none" class="link-item popover-menu-list-item"> | ||||||
|             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> |             <a role="menuitem" id="mark_all_messages_as_read" class="popover-menu-link" tabindex="0"> | ||||||
|                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> |                 <i class="popover-menu-icon zulip-icon zulip-icon-mark-as-read" aria-hidden="true"></i> | ||||||
|                 <span class="popover-menu-label">{{t "Mark all messages as read" }}</span> |                 <span class="popover-menu-label">{{t "Mark messages as read" }}</span> | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         {{/if}} |         {{/if}} | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								web/tests/unread_ops.test.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								web/tests/unread_ops.test.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const assert = require("node:assert/strict"); | ||||||
|  |  | ||||||
|  | const {set_global, zrequire} = require("./lib/namespace.cjs"); | ||||||
|  | const {run_test} = require("./lib/test.cjs"); | ||||||
|  |  | ||||||
|  | set_global("document", {hasFocus: () => true}); | ||||||
|  | const unread = zrequire("unread"); | ||||||
|  | const unread_ops = zrequire("unread_ops"); | ||||||
|  |  | ||||||
|  | run_test("get_message_count_text", () => { | ||||||
|  |     unread.set_old_unreads_missing_for_tests(true); | ||||||
|  |     assert.equal( | ||||||
|  |         unread_ops.get_message_count_text(5), | ||||||
|  |         "translated: 5+ messages will be marked as read.", | ||||||
|  |     ); | ||||||
|  |     assert.equal( | ||||||
|  |         unread_ops.get_message_count_text(1), | ||||||
|  |         "translated: 1+ messages will be marked as read.", | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     unread.set_old_unreads_missing_for_tests(false); | ||||||
|  |     assert.equal( | ||||||
|  |         unread_ops.get_message_count_text(5), | ||||||
|  |         "translated: 5 messages will be marked as read.", | ||||||
|  |     ); | ||||||
|  |     assert.equal( | ||||||
|  |         unread_ops.get_message_count_text(1), | ||||||
|  |         "translated: 1 message will be marked as read.", | ||||||
|  |     ); | ||||||
|  | }); | ||||||
		Reference in New Issue
	
	Block a user