user_settings: Add option to not mark messages as read on scroll.

These changes introduces an new user option where it allows
users read messages and scroll through their feed without
marking those messages as read.

Fixes: #22804.
This commit is contained in:
Joelute
2023-03-07 20:27:45 -05:00
committed by Tim Abbott
parent ffbe6e870a
commit 938b7917d3
14 changed files with 115 additions and 7 deletions

View File

@@ -168,6 +168,8 @@ export function build_page() {
settings_config.common_message_policy_values.by_admins_only.code,
...settings_org.get_organization_settings_options(),
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
web_mark_read_on_scroll_policy_values:
settings_config.web_mark_read_on_scroll_policy_values,
user_list_style_values: settings_config.user_list_style_values,
color_scheme_values: settings_config.color_scheme_values,
default_view_values: settings_config.default_view_values,

View File

@@ -7,7 +7,9 @@ import {MessageListView} from "./message_list_view";
import * as narrow_banner from "./narrow_banner";
import * as narrow_state from "./narrow_state";
import {page_params} from "./page_params";
import {web_mark_read_on_scroll_policy_values} from "./settings_config";
import * as stream_data from "./stream_data";
import {user_settings} from "./user_settings";
export class MessageList {
// A MessageList is the main interface for a message feed that is
@@ -173,11 +175,29 @@ export class MessageList {
can_mark_messages_read() {
/* Automatically marking messages as read can be disabled for
two different reasons:
three different reasons:
* The view is structurally a search view, encoded in the
properties of the message_list_data object.
* The user recently marked messages in the view as unread, and
we don't want to lose that state.
* The user has "Mark messages as read on scroll" option
turned on in their user settings.
*/
return (
this.data.can_mark_messages_read() &&
!this.reading_prevented &&
!(
user_settings.web_mark_read_on_scroll_policy ===
web_mark_read_on_scroll_policy_values.never.code
)
);
}
can_mark_messages_read_without_setting() {
/*
Similar to can_mark_messages_read() above, this is a helper
function to check if messages can be automatically read without
the "Do not mark messages as read on scroll" setting.
*/
return this.data.can_mark_messages_read() && !this.reading_prevented;
}

View File

@@ -34,6 +34,7 @@ export type RealmDefaultSettings = {
translate_emoticons: boolean;
twenty_four_hour_time: boolean;
user_list_style: boolean;
web_mark_read_on_scroll_policy: number;
wildcard_mentions_notify: boolean;
};

View File

@@ -67,6 +67,7 @@ import * as sub_store from "./sub_store";
import * as submessage from "./submessage";
import * as typing_events from "./typing_events";
import * as unread_ops from "./unread_ops";
import * as unread_ui from "./unread_ui";
import * as user_events from "./user_events";
import * as user_group_edit from "./user_group_edit";
import * as user_groups from "./user_groups";
@@ -625,6 +626,7 @@ export function dispatch_normal_event(event) {
"default_view",
"demote_inactive_streams",
"dense_mode",
"web_mark_read_on_scroll_policy",
"emojiset",
"escape_navigates_to_default_view",
"fluid_layout_width",
@@ -675,6 +677,9 @@ export function dispatch_normal_event(event) {
$("body").toggleClass("less_dense_mode");
$("body").toggleClass("more_dense_mode");
}
if (event.property === "web_mark_read_on_scroll_policy") {
unread_ui.update_unread_banner();
}
if (event.property === "color_scheme") {
$("body").fadeOut(300);
setTimeout(() => {

View File

@@ -88,6 +88,8 @@ export function build_page() {
can_create_new_bots: settings_bots.can_create_new_bots(),
settings_label,
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
web_mark_read_on_scroll_policy_values:
settings_config.web_mark_read_on_scroll_policy_values,
user_list_style_values: settings_config.user_list_style_values,
color_scheme_values: settings_config.color_scheme_values,
default_view_values: settings_config.default_view_values,

View File

@@ -39,6 +39,22 @@ export const demote_inactive_streams_values = {
},
};
export const web_mark_read_on_scroll_policy_values = {
always: {
code: 1,
description: $t({defaultMessage: "Always"}),
},
// The `conversation_only` option is not yet implemented.
// conversation_only: {
// code: 2,
// description: $t({defaultMessage: "Only in conversation views"}),
// },
never: {
code: 3,
description: $t({defaultMessage: "Never"}),
},
};
export const user_list_style_values = {
compact: {
code: 1,

View File

@@ -166,6 +166,9 @@ export function set_up(settings_panel) {
$container
.find(".setting_twenty_four_hour_time")
.val(JSON.stringify(settings_object.twenty_four_hour_time));
$container
.find(".setting_web_mark_read_on_scroll_policy")
.val(settings_object.web_mark_read_on_scroll_policy);
$container
.find(`.setting_emojiset_choice[value="${CSS.escape(settings_object.emojiset)}"]`)
.prop("checked", true);

View File

@@ -1,5 +1,6 @@
import $ from "jquery";
import render_mark_as_read_disabled_banner from "../templates/mark_as_read_disabled_banner.hbs";
import render_mark_as_read_turned_off_banner from "../templates/mark_as_read_turned_off_banner.hbs";
import * as activity from "./activity";
@@ -7,13 +8,31 @@ import * as message_lists from "./message_lists";
import * as notifications from "./notifications";
import {page_params} from "./page_params";
import * as pm_list from "./pm_list";
import {web_mark_read_on_scroll_policy_values} from "./settings_config";
import * as stream_list from "./stream_list";
import * as top_left_corner from "./top_left_corner";
import * as topic_list from "./topic_list";
import * as unread from "./unread";
import {notify_server_messages_read} from "./unread_ops";
import {user_settings} from "./user_settings";
let user_closed_unread_banner = false;
export function update_unread_banner() {
if (
user_settings.web_mark_read_on_scroll_policy ===
web_mark_read_on_scroll_policy_values.never.code
) {
$("#mark_as_read_turned_off_banner").html(render_mark_as_read_disabled_banner());
} else {
$("#mark_as_read_turned_off_banner").html(render_mark_as_read_turned_off_banner());
if (message_lists.current.can_mark_messages_read_without_setting()) {
hide_unread_banner();
}
}
}
export function hide_unread_banner() {
// Use visibility instead of hide() to prevent messages on the screen from
// shifting vertically.
@@ -96,10 +115,7 @@ export function should_display_bankruptcy_banner() {
export function initialize() {
update_unread_counts();
$("#mark_as_read_turned_off_banner").html(render_mark_as_read_turned_off_banner());
hide_unread_banner();
$("#mark_view_read").on("click", () => {
$("body").on("click", "#mark_view_read", () => {
// Mark all messages in the current view as read.
//
// BUG: This logic only supports marking messages visible in
@@ -114,8 +130,15 @@ export function initialize() {
hide_unread_banner();
});
$("#mark_as_read_close").on("click", () => {
$("body").on("click", "#mark_as_read_close", () => {
hide_unread_banner();
user_closed_unread_banner = true;
});
// The combination of these functions in sequence ensures we have
// at least one copy of the unread banner in the DOM, invisible;
// this somewhat strange pattern allows our CSS to reserve space for
// the banner, to avoid scroll position jumps when it is shown/hidden.
update_unread_banner();
hide_unread_banner();
}

View File

@@ -20,6 +20,7 @@ export type UserSettings = (StreamNotificationSettings & PmNotificationSettings)
desktop_icon_count_display: number;
demote_inactive_streams: number;
dense_mode: boolean;
web_mark_read_on_scroll_policy: number;
email_notifications_batching_period_seconds: number;
emojiset: string;
enable_digest_emails: boolean;

View File

@@ -0,0 +1,12 @@
<p id="mark_as_read_turned_off_content">
{{#tr}}
Your Zulip app is <z-link>configured</z-link> to not mark messages as read on scroll.
{{#*inline "z-link"}}<a href='help/marking-messages-as-read'>{{> @partial-block}}</a>{{/inline}}
{{/tr}}
</p>
<div id="mark_as_read_controls">
<button id="mark_view_read" class="btn btn-warning">
{{t 'Mark as read' }}
</button>
</div>
<button type="button" id="mark_as_read_close" class="close">×</button>

View File

@@ -2,7 +2,7 @@
{{t 'To preserve your reading state, this view does not mark messages as read.' }}
</p>
<div id="mark_as_read_controls">
<button id="mark_view_read" class="btn btn-warning" title="{{t 'Mark as read' }}">
<button id="mark_view_read" class="btn btn-warning">
{{t 'Mark as read' }}
</button>
</div>

View File

@@ -80,6 +80,15 @@
{{> settings_save_discard_widget section_name="advanced-settings" show_only_indicator=(not for_realm_settings) }}
</div>
<div class="input-group">
<label for="web_mark_read_on_scroll_policy" class="dropdown-title">{{t "Mark messages as read on scroll" }}
{{> ../help_link_widget link="/help/marking-messages-as-read" }}
</label>
<select name="web_mark_read_on_scroll_policy" class="setting_web_mark_read_on_scroll_policy prop-element settings_select bootstrap-focus-style" id="{{prefix}}web_mark_read_on_scroll_policy" data-setting-widget-type="number">
{{> dropdown_options_widget option_values=web_mark_read_on_scroll_policy_values}}
</select>
</div>
<div class="input-group">
<label class="title">{{t "User list style" }}</label>
<div class="user_list_style_values grey-box prop-element" id="{{prefix}}user_list_style" data-setting-widget-type="radio-group" data-setting-choice-type="number">

View File

@@ -71,6 +71,7 @@ mock_esm("../src/top_left_corner", {
const typing_events = mock_esm("../src/typing_events");
const ui = mock_esm("../src/ui");
const unread_ops = mock_esm("../src/unread_ops");
const unread_ui = mock_esm("../src/unread_ui");
const user_events = mock_esm("../src/user_events");
const user_groups = mock_esm("../src/user_groups");
const user_group_edit = mock_esm("../src/user_group_edit");
@@ -823,6 +824,12 @@ run_test("user_settings", ({override}) => {
assert_same(user_settings.high_contrast_mode, true);
assert_same(toggled, ["high-contrast"]);
event = event_fixtures.user_settings__web_mark_read_on_scroll_policy;
user_settings.web_mark_read_on_scroll_policy = 3;
override(unread_ui, "update_unread_banner", noop);
dispatch(event);
assert_same(user_settings.web_mark_read_on_scroll_policy, 1);
event = event_fixtures.user_settings__dense_mode;
user_settings.dense_mode = false;
toggled = [];

View File

@@ -963,6 +963,13 @@ exports.fixtures = {
value: 2,
},
user_settings__web_mark_read_on_scroll_policy: {
type: "user_settings",
op: "update",
property: "web_mark_read_on_scroll_policy",
value: 1,
},
user_status__set_status_emoji: {
type: "user_status",
user_id: test_user.user_id,