mirror of https://github.com/zulip/zulip.git
unread_ops: Let server do the mark as unread for us.
Previous algorithm was not correct if we didn't have full data for the current narrow loaded from the server. #23512 adds the support to mark messages in a narrow unread after a give message_id. Added a new alert banner to indicate loading and success states of an ongoing request. This is useful when requests can take a long time to complete.
This commit is contained in:
parent
0ba63826dd
commit
c876e12b86
|
@ -4,7 +4,7 @@ import tippy from "tippy.js";
|
||||||
|
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
|
|
||||||
export const status_classes = "alert-error alert-success alert-info alert-warning";
|
export const status_classes = "alert-error alert-success alert-info alert-warning alert-loading";
|
||||||
|
|
||||||
// TODO: Move this to the portico codebase.
|
// TODO: Move this to the portico codebase.
|
||||||
export function autofocus(selector: string): void {
|
export function autofocus(selector: string): void {
|
||||||
|
|
|
@ -90,3 +90,21 @@ export function hide_error($target: JQuery): void {
|
||||||
export function show_error($target: JQuery): void {
|
export function show_error($target: JQuery): void {
|
||||||
$target.addClass("show");
|
$target.addClass("show");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loading(
|
||||||
|
response_html: string,
|
||||||
|
$status_box: JQuery,
|
||||||
|
successfully_loaded: boolean = false,
|
||||||
|
): void {
|
||||||
|
$status_box.find(".alert-content").html(response_html);
|
||||||
|
if (!successfully_loaded) {
|
||||||
|
$status_box.removeClass(common.status_classes).addClass("alert-loading").stop(true);
|
||||||
|
} else {
|
||||||
|
$status_box.removeClass(common.status_classes).addClass("alert-success").stop(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
$status_box.removeClass("show");
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_box.addClass("show");
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import $ from "jquery";
|
||||||
|
|
||||||
|
import * as blueslip from "./blueslip";
|
||||||
import * as channel from "./channel";
|
import * as channel from "./channel";
|
||||||
|
import {$t_html} from "./i18n";
|
||||||
|
import * as loading from "./loading";
|
||||||
import * as message_flags from "./message_flags";
|
import * as message_flags from "./message_flags";
|
||||||
import * as message_list from "./message_list";
|
import * as message_list from "./message_list";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
|
@ -10,9 +15,13 @@ import * as people from "./people";
|
||||||
import * as recent_topics_ui from "./recent_topics_ui";
|
import * as recent_topics_ui from "./recent_topics_ui";
|
||||||
import * as recent_topics_util from "./recent_topics_util";
|
import * as recent_topics_util from "./recent_topics_util";
|
||||||
import * as reload from "./reload";
|
import * as reload from "./reload";
|
||||||
|
import * as ui_report from "./ui_report";
|
||||||
import * as unread from "./unread";
|
import * as unread from "./unread";
|
||||||
import * as unread_ui from "./unread_ui";
|
import * as unread_ui from "./unread_ui";
|
||||||
|
|
||||||
|
const NUM_OF_MESSAGES_UPDATED_PER_BATCH = 5000;
|
||||||
|
let loading_indicator_displayed = false;
|
||||||
|
|
||||||
export function mark_all_as_read() {
|
export function mark_all_as_read() {
|
||||||
unread.declare_bankruptcy();
|
unread.declare_bankruptcy();
|
||||||
unread_ui.update_unread_counts();
|
unread_ui.update_unread_counts();
|
||||||
|
@ -47,17 +56,98 @@ function process_newly_read_message(message, options) {
|
||||||
recent_topics_ui.update_topic_unread_count(message);
|
recent_topics_ui.update_topic_unread_count(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mark_as_unread_from_here(message_id) {
|
export function mark_as_unread_from_here(
|
||||||
/* TODO: This algorithm is not correct if we don't have full data for
|
message_id,
|
||||||
the current narrow loaded from the server.
|
include_message = true,
|
||||||
|
messages_marked_unread_till_now = 0,
|
||||||
Currently, we turn off the feature when fetch_status suggests
|
narrow,
|
||||||
we are in that that situation, but we plan to replace this
|
) {
|
||||||
logic with asking the server to do the marking as unread.
|
if (narrow === undefined) {
|
||||||
*/
|
narrow = JSON.stringify(message_lists.current.data.filter.operators());
|
||||||
const message_ids = message_lists.current.ids_greater_or_equal_than(message_id);
|
}
|
||||||
message_lists.current.prevent_reading();
|
message_lists.current.prevent_reading();
|
||||||
message_flags.mark_as_unread(message_ids);
|
const opts = {
|
||||||
|
anchor: message_id,
|
||||||
|
include_anchor: include_message,
|
||||||
|
num_before: 0,
|
||||||
|
num_after: NUM_OF_MESSAGES_UPDATED_PER_BATCH,
|
||||||
|
narrow,
|
||||||
|
op: "remove",
|
||||||
|
flag: "read",
|
||||||
|
};
|
||||||
|
channel.post({
|
||||||
|
url: "/json/messages/flags/narrow",
|
||||||
|
data: opts,
|
||||||
|
success(data) {
|
||||||
|
messages_marked_unread_till_now += data.updated_count;
|
||||||
|
|
||||||
|
if (!data.found_newest) {
|
||||||
|
// If we weren't able to complete the request fully in
|
||||||
|
// the current batch, show a progress indicator.
|
||||||
|
ui_report.loading(
|
||||||
|
$t_html(
|
||||||
|
{
|
||||||
|
defaultMessage: "Working… {N} messages marked as unread so far.",
|
||||||
|
},
|
||||||
|
{N: messages_marked_unread_till_now},
|
||||||
|
),
|
||||||
|
$("#request-progress-status-banner"),
|
||||||
|
);
|
||||||
|
if (!loading_indicator_displayed) {
|
||||||
|
loading.make_indicator(
|
||||||
|
$("#request-progress-status-banner .loading-indicator"),
|
||||||
|
{abs_positioned: true},
|
||||||
|
);
|
||||||
|
loading_indicator_displayed = true;
|
||||||
|
}
|
||||||
|
mark_as_unread_from_here(
|
||||||
|
data.last_processed_id,
|
||||||
|
false,
|
||||||
|
messages_marked_unread_till_now,
|
||||||
|
narrow,
|
||||||
|
);
|
||||||
|
} else if (loading_indicator_displayed) {
|
||||||
|
// If we were showing a loading indicator, then
|
||||||
|
// display that we finished. For the common case where
|
||||||
|
// the operation succeeds in a single batch, we don't
|
||||||
|
// bother distracting the user with the indication;
|
||||||
|
// the success will be obvious from the UI updating.
|
||||||
|
loading_indicator_displayed = false;
|
||||||
|
ui_report.loading(
|
||||||
|
$t_html(
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
"Done! {messages_marked_unread_till_now} messages marked as unread.",
|
||||||
|
},
|
||||||
|
{messages_marked_unread_till_now},
|
||||||
|
),
|
||||||
|
$("#request-progress-status-banner"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error(xhr) {
|
||||||
|
// If we hit the rate limit, just continue without showing any error.
|
||||||
|
if (xhr.responseJSON.code === "RATE_LIMIT_HIT") {
|
||||||
|
const milliseconds_to_wait = 1000 * xhr.responseJSON["retry-after"];
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
mark_as_unread_from_here(
|
||||||
|
message_id,
|
||||||
|
false,
|
||||||
|
messages_marked_unread_till_now,
|
||||||
|
narrow,
|
||||||
|
),
|
||||||
|
milliseconds_to_wait,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// TODO: Ideally, this case would communicate the
|
||||||
|
// failure to the user, with some manual retry
|
||||||
|
// offered, since the most likely cause is a 502.
|
||||||
|
blueslip.error("Unexpected error marking messages as unread: " + xhr.responseText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resume_reading() {
|
export function resume_reading() {
|
||||||
|
|
|
@ -230,3 +230,87 @@ $alert-error-red: hsl(0, 80%, 40%);
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#request-progress-status-banner {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
grid-template-columns: 80px auto 50px;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: grid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert-loading {
|
||||||
|
.alert-zulip-logo,
|
||||||
|
.loading-indicator {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide exit button since clicking it will be useless in
|
||||||
|
this scenario. */
|
||||||
|
.exit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert-success .success-indicator {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert-loading,
|
||||||
|
&.alert-success {
|
||||||
|
border-color: hsl(156, 28%, 70%);
|
||||||
|
box-shadow: 0 0 2px hsl(156, 28%, 70%);
|
||||||
|
|
||||||
|
.exit {
|
||||||
|
color: hsl(156, 30%, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-zulip-logo {
|
||||||
|
display: none;
|
||||||
|
margin: auto;
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator {
|
||||||
|
display: none;
|
||||||
|
margin: auto;
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-indicator {
|
||||||
|
display: none;
|
||||||
|
margin: auto;
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
grid-row-start: 1;
|
||||||
|
padding: 7px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 0;
|
||||||
|
color: hsl(156, 30%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-content {
|
||||||
|
grid-column: 2 / 3;
|
||||||
|
grid-row-start: 1;
|
||||||
|
color: hsl(0, 0%, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit {
|
||||||
|
float: unset;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
grid-column: 3 / 4;
|
||||||
|
grid-row-start: 1;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1013,6 +1013,14 @@ body.dark-theme {
|
||||||
border: 1px solid hsl(49, 38%, 46%);
|
border: 1px solid hsl(49, 38%, 46%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#request-progress-status-banner {
|
||||||
|
background-color: hsl(212, 32%, 14%);
|
||||||
|
|
||||||
|
.alert-content {
|
||||||
|
color: hsl(236, 33%, 90%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.alert.home-error-bar {
|
.alert.home-error-bar {
|
||||||
color: hsl(236, 33%, 90%);
|
color: hsl(236, 33%, 90%);
|
||||||
background-color: hsla(35, 84%, 62%, 0.25);
|
background-color: hsla(35, 84%, 62%, 0.25);
|
||||||
|
@ -1160,6 +1168,7 @@ body.dark-theme {
|
||||||
background-color: hsl(212, 28%, 18%);
|
background-color: hsl(212, 28%, 18%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-zulip-logo,
|
||||||
.top-messages-logo,
|
.top-messages-logo,
|
||||||
.bottom-messages-logo {
|
.bottom-messages-logo {
|
||||||
svg path {
|
svg path {
|
||||||
|
@ -1229,6 +1238,7 @@ body.dark-theme {
|
||||||
color: hsl(200, 79%, 66%);
|
color: hsl(200, 79%, 66%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#request-progress-status-banner .loading-indicator,
|
||||||
#loading_older_messages_indicator,
|
#loading_older_messages_indicator,
|
||||||
#recent_topics_loading_messages_indicator {
|
#recent_topics_loading_messages_indicator {
|
||||||
path {
|
path {
|
||||||
|
|
|
@ -216,6 +216,7 @@ p.n-margin {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-zulip-logo,
|
||||||
.top-messages-logo,
|
.top-messages-logo,
|
||||||
.bottom-messages-logo {
|
.bottom-messages-logo {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
|
|
@ -132,6 +132,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert_sidebar alert-error home-error-bar" id="home-error"></div>
|
<div class="alert alert_sidebar alert-error home-error-bar" id="home-error"></div>
|
||||||
<div class="alert alert_sidebar alert-error home-error-bar" id="reloading-application"></div>
|
<div class="alert alert_sidebar alert-error home-error-bar" id="reloading-application"></div>
|
||||||
|
<div class="alert alert_sidebar" id="request-progress-status-banner">
|
||||||
|
<div class="alert-zulip-logo">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 773.12 773.12">
|
||||||
|
<circle cx="386.56" cy="386.56" r="386.56"></circle>
|
||||||
|
<path d="M566.66 527.25c0 33.03-24.23 60.05-53.84 60.05H260.29c-29.61 0-53.84-27.02-53.84-60.05 0-20.22 9.09-38.2 22.93-49.09l134.37-120c2.5-2.14 5.74 1.31 3.94 4.19l-49.29 98.69c-1.38 2.76.41 6.16 3.25 6.16h191.18c29.61 0 53.83 27.03 53.83 60.05zm0-281.39c0 20.22-9.09 38.2-22.93 49.09l-134.37 120c-2.5 2.14-5.74-1.31-3.94-4.19l49.29-98.69c1.38-2.76-.41-6.16-3.25-6.16H260.29c-29.61 0-53.84-27.02-53.84-60.05s24.23-60.05 53.84-60.05h252.54c29.61 0 53.83 27.02 53.83 60.05z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="loading-indicator"></div>
|
||||||
|
<div class="success-indicator">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="alert-content"></div>
|
||||||
|
<div class="exit"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-main">
|
<div class="app-main">
|
||||||
<div class="column-left" id="left-sidebar-container">
|
<div class="column-left" id="left-sidebar-container">
|
||||||
|
|
Loading…
Reference in New Issue