mirror of
https://github.com/zulip/zulip.git
synced 2025-10-29 11:03:54 +00:00
If one of the filters is focused or if we cannot determine the current focus location, any rerender call will scroll user to top. Fixed by only scrolling to top when navigating from other views and when we don't have a cached scroll position. Tested by calling `complete_rerender` at 1s intervals.
180 lines
6.4 KiB
TypeScript
180 lines
6.4 KiB
TypeScript
import $ from "jquery";
|
|
import assert from "minimalistic-assert";
|
|
import type * as tippy from "tippy.js";
|
|
|
|
import * as activity_ui from "./activity_ui.ts";
|
|
import * as compose_actions from "./compose_actions.ts";
|
|
import * as compose_recipient from "./compose_recipient.ts";
|
|
import * as compose_state from "./compose_state.ts";
|
|
import type * as dropdown_widget from "./dropdown_widget.ts";
|
|
import {$t} from "./i18n.ts";
|
|
import * as message_lists from "./message_lists.ts";
|
|
import * as message_view_header from "./message_view_header.ts";
|
|
import * as message_viewport from "./message_viewport.ts";
|
|
import * as modals from "./modals.ts";
|
|
import * as narrow_state from "./narrow_state.ts";
|
|
import * as narrow_title from "./narrow_title.ts";
|
|
import * as overlays from "./overlays.ts";
|
|
import * as pm_list from "./pm_list.ts";
|
|
import * as popovers from "./popovers.ts";
|
|
import * as resize from "./resize.ts";
|
|
import * as sidebar_ui from "./sidebar_ui.ts";
|
|
import * as stream_list from "./stream_list.ts";
|
|
import * as unread_ui from "./unread_ui.ts";
|
|
|
|
export const FILTERS = {
|
|
ALL_TOPICS: "all_topics",
|
|
UNMUTED_TOPICS: "unmuted_topics",
|
|
FOLLOWED_TOPICS: "followed_topics",
|
|
};
|
|
|
|
const TIPPY_PROPS: Partial<tippy.Props> = {
|
|
offset: [0, 2],
|
|
};
|
|
|
|
export const COMMON_DROPDOWN_WIDGET_PARAMS = {
|
|
get_options: filters_dropdown_options,
|
|
tippy_props: TIPPY_PROPS,
|
|
unique_id_type: "string",
|
|
hide_search_box: true,
|
|
disable_for_spectators: true,
|
|
} satisfies Partial<dropdown_widget.DropdownWidgetOptions>;
|
|
|
|
const ALL_TOPICS_OPTION_DESCRIPTION = $t({
|
|
defaultMessage: "Includes muted channels and topics",
|
|
});
|
|
|
|
const ALL_TOPICS_OPTION_DESCRIPTION_FOR_CHANNEL_VIEW = $t({
|
|
defaultMessage: "Includes muted topics",
|
|
});
|
|
|
|
export function filters_dropdown_options(
|
|
current_value: string | number | undefined,
|
|
channel_view = false,
|
|
): dropdown_widget.Option[] {
|
|
return [
|
|
{
|
|
unique_id: FILTERS.FOLLOWED_TOPICS,
|
|
name: $t({defaultMessage: "Followed topics"}),
|
|
description: $t({defaultMessage: "Only topics you follow"}),
|
|
bold_current_selection: current_value === FILTERS.FOLLOWED_TOPICS,
|
|
},
|
|
{
|
|
unique_id: FILTERS.UNMUTED_TOPICS,
|
|
name: $t({defaultMessage: "Standard view"}),
|
|
description: $t({defaultMessage: "All unmuted topics"}),
|
|
bold_current_selection: current_value === FILTERS.UNMUTED_TOPICS,
|
|
},
|
|
{
|
|
unique_id: FILTERS.ALL_TOPICS,
|
|
name: $t({defaultMessage: "All topics"}),
|
|
description: channel_view
|
|
? ALL_TOPICS_OPTION_DESCRIPTION_FOR_CHANNEL_VIEW
|
|
: ALL_TOPICS_OPTION_DESCRIPTION,
|
|
bold_current_selection: current_value === FILTERS.ALL_TOPICS,
|
|
},
|
|
];
|
|
}
|
|
|
|
export function handle_message_view_deactivated(highlight_current_view: () => void): void {
|
|
highlight_current_view();
|
|
stream_list.handle_message_view_deactivated();
|
|
pm_list.handle_message_view_deactivated();
|
|
}
|
|
|
|
export function show(opts: {
|
|
highlight_view_in_left_sidebar: () => void;
|
|
$view: JQuery;
|
|
update_compose: () => void;
|
|
is_visible: () => boolean;
|
|
set_visible: (value: boolean) => void;
|
|
complete_rerender: (coming_from_other_views?: boolean) => void;
|
|
is_recent_view?: boolean;
|
|
}): void {
|
|
if (opts.is_visible()) {
|
|
// If we're already visible, E.g. because the user hit Esc
|
|
// while already in the view, do nothing.
|
|
return;
|
|
}
|
|
|
|
// Hide "middle-column" which has html for rendering
|
|
// a messages narrow. We hide it and show the view.
|
|
$("#message_feed_container").hide();
|
|
opts.$view.show();
|
|
message_lists.update_current_message_list(undefined);
|
|
opts.set_visible(true);
|
|
|
|
// Hide selected elements in the left sidebar.
|
|
opts.highlight_view_in_left_sidebar();
|
|
|
|
unread_ui.hide_unread_banner();
|
|
opts.update_compose();
|
|
narrow_title.update_narrow_title(narrow_state.filter());
|
|
message_view_header.render_title_area();
|
|
compose_recipient.handle_middle_pane_transition();
|
|
opts.complete_rerender(true);
|
|
compose_actions.on_show_navigation_view();
|
|
|
|
// This has to happen after resetting the current narrow filter, so
|
|
// that the buddy list is rendered with the correct narrow state.
|
|
activity_ui.build_user_sidebar();
|
|
|
|
// Misc.
|
|
if (opts.is_recent_view) {
|
|
resize.update_recent_view();
|
|
}
|
|
}
|
|
|
|
export function hide(opts: {$view: JQuery; set_visible: (value: boolean) => void}): void {
|
|
const active_element = document.activeElement;
|
|
if (active_element !== null && opts.$view.has(active_element)) {
|
|
$(active_element).trigger("blur");
|
|
}
|
|
|
|
$("#message_feed_container").show();
|
|
opts.$view.hide();
|
|
opts.set_visible(false);
|
|
|
|
// This solves a bug with message_view_header
|
|
// being broken sometimes when we narrow
|
|
// to a filter and back to view
|
|
// before it completely re-rerenders.
|
|
message_view_header.render_title_area();
|
|
|
|
// Fire our custom event
|
|
$("#message_feed_container").trigger("message_feed_shown");
|
|
|
|
// This makes sure user lands on the selected message
|
|
// and not always at the top of the narrow.
|
|
message_viewport.plan_scroll_to_selected();
|
|
}
|
|
|
|
export function is_in_focus(): boolean {
|
|
return (
|
|
!compose_state.composing() &&
|
|
!popovers.any_active() &&
|
|
!sidebar_ui.any_sidebar_expanded_as_overlay() &&
|
|
!overlays.any_active() &&
|
|
!modals.any_active_or_animating() &&
|
|
!$(".input-element").is(":focus") &&
|
|
!$("#search_query").is(":focus") &&
|
|
!$(".navbar-item").is(":focus")
|
|
);
|
|
}
|
|
|
|
export function is_scroll_position_for_render(): boolean {
|
|
const scroll_position = window.scrollY;
|
|
const window_height = window.innerHeight;
|
|
// We allocate `--max-unmaximized-compose-height` in empty space
|
|
// below the last rendered row in recent view.
|
|
//
|
|
// We don't want user to see this empty space until there are no
|
|
// new rows to render when the user is scrolling to the bottom of
|
|
// the view. So, we render new rows when user has scrolled 2 / 3
|
|
// of (the total scrollable height - the empty space).
|
|
const compose_max_height = $(":root").css("--max-unmaximized-compose-height");
|
|
assert(typeof compose_max_height === "string");
|
|
const scroll_max = document.body.scrollHeight - Number.parseInt(compose_max_height, 10);
|
|
return scroll_position + window_height >= (2 / 3) * scroll_max;
|
|
}
|