mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
It turns out that the bug this call hopes to fix only happens when the user first loads the page to recent_topics and then navigates to a view with a message list (any other view), but we'd make this call every time the recent topics table was hidden. Hence, this commit makes it such that we only make that call if (1) the page is loaded to recent_topics and (2) we're switching from recent_topics to a message list view for the first time. We achieve (1) via binding a handler via ui_init.initialize_everything and (2) by binding the handler as `.one`, so that it's unbound after its first invocation. Additionally, we use window.requestAnimationFrame to prevent this forcing the browser to do a reflow unnecessarily. Combined with other commits in this series, this fixes a major performance problem when leaving recent topics for another view. See #20255 for details.
313 lines
11 KiB
JavaScript
313 lines
11 KiB
JavaScript
import autosize from "autosize";
|
|
import $ from "jquery";
|
|
|
|
import * as blueslip from "./blueslip";
|
|
import * as condense from "./condense";
|
|
import * as message_lists from "./message_lists";
|
|
import * as message_viewport from "./message_viewport";
|
|
import * as navbar_alerts from "./navbar_alerts";
|
|
import * as navigate from "./navigate";
|
|
import * as popovers from "./popovers";
|
|
import * as recent_topics_util from "./recent_topics_util";
|
|
import * as ui from "./ui";
|
|
import {user_settings} from "./user_settings";
|
|
import * as util from "./util";
|
|
|
|
let narrow_window = false;
|
|
|
|
function confine_to_range(lo, val, hi) {
|
|
if (val < lo) {
|
|
return lo;
|
|
}
|
|
if (val > hi) {
|
|
return hi;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
function size_blocks(blocks, usable_height) {
|
|
let sum_height = 0;
|
|
|
|
for (const block of blocks) {
|
|
sum_height += block.real_height;
|
|
}
|
|
|
|
for (const block of blocks) {
|
|
let ratio = block.real_height / sum_height;
|
|
ratio = confine_to_range(0.05, ratio, 0.85);
|
|
block.max_height = confine_to_range(80, usable_height * ratio, 1.2 * block.real_height);
|
|
}
|
|
}
|
|
|
|
function get_new_heights() {
|
|
const res = {};
|
|
const viewport_height = message_viewport.height();
|
|
const top_navbar_height = $("#top_navbar").safeOuterHeight(true);
|
|
const right_sidebar_shorcuts_height = $(".right-sidebar-shortcuts").safeOuterHeight(true) || 0;
|
|
|
|
res.bottom_whitespace_height = viewport_height * 0.4;
|
|
|
|
res.main_div_min_height = viewport_height - top_navbar_height;
|
|
|
|
res.stream_filters_max_height =
|
|
viewport_height -
|
|
Number.parseInt($("#left-sidebar").css("marginTop"), 10) -
|
|
Number.parseInt($(".narrows_panel").css("marginTop"), 10) -
|
|
Number.parseInt($(".narrows_panel").css("marginBottom"), 10) -
|
|
$("#global_filters").safeOuterHeight(true) -
|
|
$("#streams_header").safeOuterHeight(true);
|
|
|
|
// Don't let us crush the stream sidebar completely out of view
|
|
res.stream_filters_max_height = Math.max(80, res.stream_filters_max_height);
|
|
|
|
// RIGHT SIDEBAR
|
|
|
|
const usable_height =
|
|
viewport_height -
|
|
Number.parseInt($("#right-sidebar").css("marginTop"), 10) -
|
|
$("#userlist-header").safeOuterHeight(true) -
|
|
$("#user_search_section").safeOuterHeight(true) -
|
|
right_sidebar_shorcuts_height;
|
|
|
|
res.buddy_list_wrapper_max_height = Math.max(80, usable_height);
|
|
|
|
return res;
|
|
}
|
|
|
|
function left_userlist_get_new_heights() {
|
|
const res = {};
|
|
const viewport_height = message_viewport.height();
|
|
const viewport_width = message_viewport.width();
|
|
res.viewport_height = viewport_height;
|
|
res.viewport_width = viewport_width;
|
|
|
|
// main div
|
|
const top_navbar_height = $(".header").safeOuterHeight(true);
|
|
res.bottom_whitespace_height = viewport_height * 0.4;
|
|
res.main_div_min_height = viewport_height - top_navbar_height;
|
|
|
|
// left sidebar
|
|
const $stream_filters = $("#stream_filters").expectOne();
|
|
const $buddy_list_wrapper = $("#buddy_list_wrapper").expectOne();
|
|
|
|
const stream_filters_real_height = $stream_filters.prop("scrollHeight");
|
|
const user_list_real_height = ui.get_scroll_element($buddy_list_wrapper).prop("scrollHeight");
|
|
|
|
res.total_leftlist_height =
|
|
viewport_height -
|
|
Number.parseInt($("#left-sidebar").css("marginTop"), 10) -
|
|
Number.parseInt($(".narrows_panel").css("marginTop"), 10) -
|
|
Number.parseInt($(".narrows_panel").css("marginBottom"), 10) -
|
|
$("#global_filters").safeOuterHeight(true) -
|
|
$("#streams_header").safeOuterHeight(true) -
|
|
$("#userlist-header").safeOuterHeight(true) -
|
|
$("#user_search_section").safeOuterHeight(true) -
|
|
Number.parseInt($stream_filters.css("marginBottom"), 10);
|
|
|
|
const blocks = [
|
|
{
|
|
real_height: stream_filters_real_height,
|
|
},
|
|
{
|
|
real_height: user_list_real_height,
|
|
},
|
|
];
|
|
|
|
size_blocks(blocks, res.total_leftlist_height);
|
|
|
|
res.stream_filters_max_height = blocks[0].max_height;
|
|
res.buddy_list_wrapper_max_height = blocks[1].max_height;
|
|
|
|
return res;
|
|
}
|
|
|
|
export function watch_manual_resize(element) {
|
|
const box = document.querySelector(element);
|
|
|
|
if (!box) {
|
|
blueslip.error("Bad selector in watch_manual_resize: " + element);
|
|
return undefined;
|
|
}
|
|
|
|
const meta = {
|
|
box,
|
|
height: null,
|
|
mousedown: false,
|
|
};
|
|
|
|
const box_handler = function () {
|
|
meta.mousedown = true;
|
|
meta.height = meta.box.clientHeight;
|
|
};
|
|
meta.box.addEventListener("mousedown", box_handler);
|
|
|
|
// If the user resizes the textarea manually, we use the
|
|
// callback to stop autosize from adjusting the height.
|
|
// It will be re-enabled when this component is next opened.
|
|
const body_handler = function () {
|
|
if (meta.mousedown === true) {
|
|
meta.mousedown = false;
|
|
if (meta.height !== meta.box.clientHeight) {
|
|
meta.height = meta.box.clientHeight;
|
|
autosize.destroy($(element)).height(meta.height + "px");
|
|
}
|
|
}
|
|
};
|
|
document.body.addEventListener("mouseup", body_handler);
|
|
|
|
return [box_handler, body_handler];
|
|
}
|
|
|
|
export function reset_compose_message_max_height(bottom_whitespace_height) {
|
|
// If the compose-box is open, we set the `max-height` property of
|
|
// `compose-textarea` and `preview-textarea`, so that the
|
|
// compose-box's maximum extent does not overlap the last message
|
|
// in the current stream. We also leave a tiny bit of space after
|
|
// the last message of the current stream.
|
|
|
|
// Compute bottom_whitespace_height if not provided by caller.
|
|
if (bottom_whitespace_height === undefined) {
|
|
const h = narrow_window ? left_userlist_get_new_heights() : get_new_heights();
|
|
bottom_whitespace_height = h.bottom_whitespace_height;
|
|
}
|
|
|
|
// Take properties of the whichever message area is visible.
|
|
const $visible_textarea = $("#compose-textarea:visible, #preview_message_area:visible");
|
|
const compose_height = Number.parseInt($("#compose").outerHeight(), 10);
|
|
const compose_textarea_height = Number.parseInt($visible_textarea.outerHeight(), 10);
|
|
const compose_non_textarea_height = compose_height - compose_textarea_height;
|
|
|
|
// The `preview_message_area` can have a slightly different height
|
|
// than `compose-textarea` based on operating system. We just
|
|
// ensure that the last message is not overlapped by compose box.
|
|
$visible_textarea.css(
|
|
"max-height",
|
|
// The 10 here leaves space for the selected message border.
|
|
bottom_whitespace_height - compose_non_textarea_height - 10,
|
|
);
|
|
}
|
|
|
|
export function resize_bottom_whitespace(h) {
|
|
$("#bottom_whitespace").height(h.bottom_whitespace_height);
|
|
|
|
// The height of the compose box is tied to that of
|
|
// bottom_whitespace, so update it if necessary.
|
|
//
|
|
// reset_compose_message_max_height cannot compute the right
|
|
// height correctly while compose is hidden. This is OK, because
|
|
// we also resize compose every time it is opened.
|
|
if ($(".message_comp").is(":visible")) {
|
|
reset_compose_message_max_height(h.bottom_whitespace_height);
|
|
}
|
|
}
|
|
|
|
export function resize_stream_filters_container(h) {
|
|
h = narrow_window ? left_userlist_get_new_heights() : get_new_heights();
|
|
resize_bottom_whitespace(h);
|
|
$("#stream-filters-container").css("max-height", h.stream_filters_max_height);
|
|
}
|
|
|
|
export function resize_sidebars() {
|
|
let $sidebar;
|
|
|
|
if (user_settings.left_side_userlist) {
|
|
const css_narrow_mode = message_viewport.is_narrow();
|
|
|
|
$("#top_navbar").removeClass("rightside-userlist");
|
|
|
|
const $right_items = $(".right-sidebar-items").expectOne();
|
|
|
|
if (css_narrow_mode && !narrow_window) {
|
|
// move stuff to the left sidebar (skinny mode)
|
|
narrow_window = true;
|
|
popovers.set_userlist_placement("left");
|
|
$sidebar = $("#left-sidebar").expectOne();
|
|
$sidebar.append($right_items);
|
|
$("#buddy_list_wrapper").css("margin", "0px");
|
|
$("#userlist-toggle").css("display", "none");
|
|
$("#invite-user-link").hide();
|
|
} else if (!css_narrow_mode && narrow_window) {
|
|
// move stuff to the right sidebar (wide mode)
|
|
narrow_window = false;
|
|
popovers.set_userlist_placement("right");
|
|
$sidebar = $("#right-sidebar").expectOne();
|
|
$sidebar.append($right_items);
|
|
$("#buddy_list_wrapper").css("margin", "");
|
|
$("#userlist-toggle").css("display", "");
|
|
$("#invite-user-link").show();
|
|
}
|
|
}
|
|
|
|
const h = narrow_window ? left_userlist_get_new_heights() : get_new_heights();
|
|
|
|
$("#buddy_list_wrapper").css("max-height", h.buddy_list_wrapper_max_height);
|
|
$("#stream-filters-container").css("max-height", h.stream_filters_max_height);
|
|
|
|
return h;
|
|
}
|
|
|
|
export function resize_page_components() {
|
|
navbar_alerts.resize_app();
|
|
const h = resize_sidebars();
|
|
resize_bottom_whitespace(h);
|
|
}
|
|
|
|
let _old_width = $(window).width();
|
|
|
|
export function handler() {
|
|
const new_width = $(window).width();
|
|
|
|
// On mobile web, we want to avoid hiding a popover here on height change,
|
|
// especially if this resize was triggered by a virtual keyboard
|
|
// popping up when the user opened that very popover.
|
|
const mobile = util.is_mobile();
|
|
if (!mobile || new_width !== _old_width) {
|
|
popovers.hide_all();
|
|
}
|
|
|
|
if (new_width !== _old_width) {
|
|
_old_width = new_width;
|
|
condense.clear_message_content_height_cache();
|
|
}
|
|
resize_page_components();
|
|
|
|
// Re-compute and display/remove [More] links to messages
|
|
condense.condense_and_collapse($(".message_table .message_row"));
|
|
|
|
// This function might run onReady (if we're in a narrow window),
|
|
// but before we've loaded in the messages; in that case, don't
|
|
// try to scroll to one.
|
|
if (message_lists.current.selected_id() !== -1) {
|
|
if (mobile) {
|
|
popovers.set_suppress_scroll_hide();
|
|
}
|
|
|
|
navigate.scroll_to_selected();
|
|
}
|
|
}
|
|
|
|
export function initialize() {
|
|
// Hack: If the app is loaded directly to recent topics, then we
|
|
// need to arrange to call navbar_alerts.resize_app when we first
|
|
// visit a message list. This is a workaround for bugs where the
|
|
// floating recipient bar will be invisible (as well as other
|
|
// alignment issues) when they are initially rendered in the
|
|
// background because recent topics is displayed.
|
|
|
|
if (recent_topics_util.is_visible()) {
|
|
// We bind the handler for the message_feed_container shown event, such
|
|
// that it will only get executed once.
|
|
//
|
|
// The selector here is based on #gear-menu, to take advantage
|
|
// of the Bootstrap the 'show' event handler on that legacy
|
|
// data-toggle element.
|
|
$('#gear-menu a[data-toggle="tab"][href="#message_feed_container"]').one("show", () => {
|
|
// We use a requestAnimationFrame here to prevent this call from
|
|
// causing a forced reflow.
|
|
window.requestAnimationFrame(() => {
|
|
navbar_alerts.resize_app();
|
|
});
|
|
});
|
|
}
|
|
}
|