mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
message_feed: Notify users when skipping unsubscribed channels.
Zulip has an invariant that all unread messages must be in channels the user is subscribed to. Therefore, in this commit, we add a feedback widget to notify users when a channel to which they are not subscribed is skipped while marking messages as unread in an interleaved view. Fixes #23470. Co-authored-by: Hemant Umre <hemantumre12@gmail.com>.
This commit is contained in:
@@ -4,14 +4,17 @@ 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_all_as_read from "../templates/confirm_dialog/confirm_mark_all_as_read.hbs";
|
||||||
|
import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs";
|
||||||
|
import render_skipped_marking_unread from "../templates/skipped_marking_unread.hbs";
|
||||||
|
|
||||||
import * as blueslip from "./blueslip.ts";
|
import * as blueslip from "./blueslip.ts";
|
||||||
import * as channel from "./channel.ts";
|
import * as channel from "./channel.ts";
|
||||||
import * as confirm_dialog from "./confirm_dialog.ts";
|
import * as confirm_dialog from "./confirm_dialog.ts";
|
||||||
import * as desktop_notifications from "./desktop_notifications.ts";
|
import * as desktop_notifications from "./desktop_notifications.ts";
|
||||||
import * as dialog_widget from "./dialog_widget.ts";
|
import * as dialog_widget from "./dialog_widget.ts";
|
||||||
|
import * as feedback_widget from "./feedback_widget.ts";
|
||||||
import {Filter} from "./filter.ts";
|
import {Filter} from "./filter.ts";
|
||||||
import {$t_html} from "./i18n.ts";
|
import {$t, $t_html} from "./i18n.ts";
|
||||||
import * as loading from "./loading.ts";
|
import * as loading from "./loading.ts";
|
||||||
import * as message_flags from "./message_flags.ts";
|
import * as message_flags from "./message_flags.ts";
|
||||||
import * as message_lists from "./message_lists.ts";
|
import * as message_lists from "./message_lists.ts";
|
||||||
@@ -24,11 +27,14 @@ import * as people from "./people.ts";
|
|||||||
import * as recent_view_ui from "./recent_view_ui.ts";
|
import * as recent_view_ui from "./recent_view_ui.ts";
|
||||||
import type {MessageDetails} from "./server_event_types.ts";
|
import type {MessageDetails} from "./server_event_types.ts";
|
||||||
import type {NarrowTerm} from "./state_data.ts";
|
import type {NarrowTerm} from "./state_data.ts";
|
||||||
|
import * as sub_store from "./sub_store.ts";
|
||||||
import * as ui_report from "./ui_report.ts";
|
import * as ui_report from "./ui_report.ts";
|
||||||
import * as unread from "./unread.ts";
|
import * as unread from "./unread.ts";
|
||||||
import * as unread_ui from "./unread_ui.ts";
|
import * as unread_ui from "./unread_ui.ts";
|
||||||
|
import * as util from "./util.ts";
|
||||||
|
|
||||||
let loading_indicator_displayed = false;
|
let loading_indicator_displayed = false;
|
||||||
|
let unsubscribed_ignored_channels: number[] = [];
|
||||||
|
|
||||||
// We might want to use a slightly smaller batch for the first
|
// We might want to use a slightly smaller batch for the first
|
||||||
// request, because empirically, the first request can be
|
// request, because empirically, the first request can be
|
||||||
@@ -70,8 +76,49 @@ const update_flags_for_narrow_response_schema = z.object({
|
|||||||
last_processed_id: z.number().nullable(),
|
last_processed_id: z.number().nullable(),
|
||||||
found_oldest: z.boolean(),
|
found_oldest: z.boolean(),
|
||||||
found_newest: z.boolean(),
|
found_newest: z.boolean(),
|
||||||
|
ignored_because_not_subscribed_channels: z.array(z.number()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const update_flags_for_response_schema = z.object({
|
||||||
|
ignored_because_not_subscribed_channels: z.array(z.number()),
|
||||||
|
});
|
||||||
|
|
||||||
|
function handle_skipped_unsubscribed_streams(
|
||||||
|
ignored_because_not_subscribed_channels: number[],
|
||||||
|
): void {
|
||||||
|
if (ignored_because_not_subscribed_channels.length > 0) {
|
||||||
|
// Zulip has an invariant that all unread messages must be in streams
|
||||||
|
// the user is subscribed to. Notify the user if messages from
|
||||||
|
// unsubscribed streams are ignored by the server.
|
||||||
|
const stream_names_with_privacy_symbol_html = ignored_because_not_subscribed_channels.map(
|
||||||
|
(stream_id) => {
|
||||||
|
const stream = sub_store.get(stream_id);
|
||||||
|
const decorated_stream_name = render_inline_decorated_stream_name({stream});
|
||||||
|
return `<span class="white-space-nowrap">${decorated_stream_name}</span>`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const populate: (element: JQuery) => void = ($container) => {
|
||||||
|
const formatted_stream_list_text = util.format_array_as_list(
|
||||||
|
stream_names_with_privacy_symbol_html,
|
||||||
|
"long",
|
||||||
|
"conjunction",
|
||||||
|
);
|
||||||
|
const rendered_html = render_skipped_marking_unread({
|
||||||
|
streams: formatted_stream_list_text,
|
||||||
|
});
|
||||||
|
$container.html(rendered_html);
|
||||||
|
};
|
||||||
|
|
||||||
|
const title_text = $t({defaultMessage: "Skipped unsubscribed channels"});
|
||||||
|
|
||||||
|
feedback_widget.show({
|
||||||
|
populate,
|
||||||
|
title_text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function bulk_update_read_flags_for_narrow(
|
function bulk_update_read_flags_for_narrow(
|
||||||
narrow: NarrowTerm[],
|
narrow: NarrowTerm[],
|
||||||
op: "add" | "remove",
|
op: "add" | "remove",
|
||||||
@@ -330,6 +377,12 @@ function do_mark_unread_by_narrow(
|
|||||||
success(raw_data) {
|
success(raw_data) {
|
||||||
const data = update_flags_for_narrow_response_schema.parse(raw_data);
|
const data = update_flags_for_narrow_response_schema.parse(raw_data);
|
||||||
messages_marked_unread_till_now += data.updated_count;
|
messages_marked_unread_till_now += data.updated_count;
|
||||||
|
unsubscribed_ignored_channels = [
|
||||||
|
...new Set([
|
||||||
|
...unsubscribed_ignored_channels,
|
||||||
|
...data.ignored_because_not_subscribed_channels,
|
||||||
|
]),
|
||||||
|
];
|
||||||
if (!data.found_newest) {
|
if (!data.found_newest) {
|
||||||
assert(data.last_processed_id !== null);
|
assert(data.last_processed_id !== null);
|
||||||
// If we weren't able to complete the request fully in
|
// If we weren't able to complete the request fully in
|
||||||
@@ -358,9 +411,15 @@ function do_mark_unread_by_narrow(
|
|||||||
FOLLOWUP_BATCH_SIZE,
|
FOLLOWUP_BATCH_SIZE,
|
||||||
narrow,
|
narrow,
|
||||||
);
|
);
|
||||||
} else if (loading_indicator_displayed) {
|
} else {
|
||||||
|
if (loading_indicator_displayed) {
|
||||||
finish_loading(messages_marked_unread_till_now);
|
finish_loading(messages_marked_unread_till_now);
|
||||||
}
|
}
|
||||||
|
if (unsubscribed_ignored_channels.length > 0) {
|
||||||
|
handle_skipped_unsubscribed_streams(unsubscribed_ignored_channels);
|
||||||
|
unsubscribed_ignored_channels = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error(xhr) {
|
error(xhr) {
|
||||||
handle_mark_unread_from_here_error(xhr, {
|
handle_mark_unread_from_here_error(xhr, {
|
||||||
@@ -382,10 +441,16 @@ function do_mark_unread_by_ids(message_ids_to_update: number[]): void {
|
|||||||
void channel.post({
|
void channel.post({
|
||||||
url: "/json/messages/flags",
|
url: "/json/messages/flags",
|
||||||
data: {messages: JSON.stringify(message_ids_to_update), op: "remove", flag: "read"},
|
data: {messages: JSON.stringify(message_ids_to_update), op: "remove", flag: "read"},
|
||||||
success() {
|
success(raw_data) {
|
||||||
if (loading_indicator_displayed) {
|
if (loading_indicator_displayed) {
|
||||||
finish_loading(message_ids_to_update.length);
|
finish_loading(message_ids_to_update.length);
|
||||||
}
|
}
|
||||||
|
const data = update_flags_for_response_schema.parse(raw_data);
|
||||||
|
const ignored_because_not_subscribed_channels =
|
||||||
|
data.ignored_because_not_subscribed_channels;
|
||||||
|
if (ignored_because_not_subscribed_channels.length > 0) {
|
||||||
|
handle_skipped_unsubscribed_streams(ignored_because_not_subscribed_channels);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error(xhr) {
|
error(xhr) {
|
||||||
handle_mark_unread_from_here_error(xhr, {
|
handle_mark_unread_from_here_error(xhr, {
|
||||||
|
|||||||
@@ -389,6 +389,10 @@ body.has-overlay-scrollbar {
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.white-space-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set flex display on login buttons only in the
|
/* Set flex display on login buttons only in the
|
||||||
spectator view. We want them to display none
|
spectator view. We want them to display none
|
||||||
otherwise. */
|
otherwise. */
|
||||||
|
|||||||
4
web/templates/skipped_marking_unread.hbs
Normal file
4
web/templates/skipped_marking_unread.hbs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{{#tr}}
|
||||||
|
Because you are not subscribed to <z-streams></z-streams>, messages in this channel were not marked as unread.
|
||||||
|
{{#*inline "z-streams"}}<strong>{{{streams}}}</strong>{{/inline}}
|
||||||
|
{{/tr}}
|
||||||
Reference in New Issue
Block a user