mirror of https://github.com/zulip/zulip.git
ts: Migrate `message_viewport` to typescript.
Used function overloading for methods - `make_dimen_wrapper` and `scrollTop`.
This commit is contained in:
parent
f2c700b6e1
commit
6f6795f607
|
@ -144,7 +144,7 @@ EXEMPT_FILES = make_set(
|
|||
"web/src/message_scroll_state.ts",
|
||||
"web/src/message_util.ts",
|
||||
"web/src/message_view_header.js",
|
||||
"web/src/message_viewport.js",
|
||||
"web/src/message_viewport.ts",
|
||||
"web/src/messages_overlay_ui.ts",
|
||||
"web/src/modals.ts",
|
||||
"web/src/muted_users_ui.js",
|
||||
|
|
|
@ -1,30 +1,54 @@
|
|||
import $ from "jquery";
|
||||
import assert from "minimalistic-assert";
|
||||
|
||||
import * as blueslip from "./blueslip";
|
||||
import * as message_lists from "./message_lists";
|
||||
import * as message_scroll_state from "./message_scroll_state";
|
||||
import type {Message} from "./message_store";
|
||||
import * as rows from "./rows";
|
||||
import * as util from "./util";
|
||||
import type {CachedValue} from "./util";
|
||||
|
||||
type MessageViewportInfo = {
|
||||
visible_top: number;
|
||||
visible_bottom: number;
|
||||
visible_height: number;
|
||||
};
|
||||
|
||||
type DimenFunc = JQuery["height"] | JQuery["width"];
|
||||
type DimenWrapper = {
|
||||
(): number;
|
||||
(val: string | number): JQuery;
|
||||
};
|
||||
|
||||
export const $scroll_container = $("html");
|
||||
|
||||
let $jwindow;
|
||||
const dimensions = {};
|
||||
let $jwindow: JQuery<Window & typeof globalThis>;
|
||||
const dimensions: Record<string, CachedValue<number>> = {};
|
||||
let in_stoppable_autoscroll = false;
|
||||
|
||||
function make_dimen_wrapper(dimen_name, dimen_func) {
|
||||
function make_dimen_wrapper(dimen_name: string, dimen_func: DimenFunc): DimenWrapper {
|
||||
dimensions[dimen_name] = new util.CachedValue({
|
||||
compute_value() {
|
||||
return dimen_func();
|
||||
return dimen_func() ?? 0;
|
||||
},
|
||||
});
|
||||
return function viewport_dimension_wrapper(val) {
|
||||
|
||||
// Inner function overload for the case when dimen_func is () => number | undefined
|
||||
function viewport_dimension_wrapper(): number;
|
||||
|
||||
// Inner function overload for the case when dimen_func is (val: string | number) => JQuery
|
||||
function viewport_dimension_wrapper(val: string | number): JQuery;
|
||||
|
||||
function viewport_dimension_wrapper(val?: string | number): number | JQuery {
|
||||
if (val !== undefined) {
|
||||
dimensions[dimen_name].reset();
|
||||
return dimen_func(val);
|
||||
}
|
||||
return dimensions[dimen_name].get();
|
||||
};
|
||||
}
|
||||
|
||||
return viewport_dimension_wrapper;
|
||||
}
|
||||
|
||||
export const height = make_dimen_wrapper("height", $.fn.height.bind($scroll_container));
|
||||
|
@ -33,23 +57,23 @@ export const width = make_dimen_wrapper("width", $.fn.width.bind($scroll_contain
|
|||
// TODO: This function lets us use the DOM API instead of jquery
|
||||
// (<10x faster) for condense.js, but we want to eventually do a
|
||||
// bigger of refactor `height` and `width` above to do the same.
|
||||
export function max_message_height() {
|
||||
return document.querySelector("html").offsetHeight * 0.65;
|
||||
export function max_message_height(): number {
|
||||
return document.querySelector("html")!.offsetHeight * 0.65;
|
||||
}
|
||||
|
||||
// Includes both scroll and arrow events. Negative means scroll up,
|
||||
// positive means scroll down.
|
||||
export let last_movement_direction = 1;
|
||||
|
||||
export function set_last_movement_direction(value) {
|
||||
export function set_last_movement_direction(value: number): void {
|
||||
last_movement_direction = value;
|
||||
}
|
||||
|
||||
export function at_top() {
|
||||
export function at_top(): boolean {
|
||||
return scrollTop() <= 0;
|
||||
}
|
||||
|
||||
export function message_viewport_info() {
|
||||
export function message_viewport_info(): MessageViewportInfo {
|
||||
// Return a structure that tells us details of the viewport
|
||||
// accounting for fixed elements like the top navbar.
|
||||
//
|
||||
|
@ -78,7 +102,7 @@ export function message_viewport_info() {
|
|||
};
|
||||
}
|
||||
|
||||
export function at_bottom() {
|
||||
export function at_bottom(): boolean {
|
||||
const bottom = scrollTop() + height();
|
||||
const full_height = $scroll_container.prop("scrollHeight");
|
||||
|
||||
|
@ -91,7 +115,7 @@ export function at_bottom() {
|
|||
|
||||
// This differs from at_bottom in that it only requires the bottom message to
|
||||
// be visible, but you may be able to scroll down further.
|
||||
export function bottom_message_visible() {
|
||||
export function bottom_message_visible(): boolean {
|
||||
const $last_row = rows.last_visible();
|
||||
if ($last_row.length) {
|
||||
const message_bottom = $last_row[0].getBoundingClientRect().bottom;
|
||||
|
@ -101,11 +125,11 @@ export function bottom_message_visible() {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function is_below_visible_bottom(offset) {
|
||||
return offset > scrollTop() + height() - $("#compose").height();
|
||||
export function is_below_visible_bottom(offset: number): boolean {
|
||||
return offset > scrollTop() + height() - ($("#compose").height() ?? 0);
|
||||
}
|
||||
|
||||
export function is_scrolled_up() {
|
||||
export function is_scrolled_up(): boolean {
|
||||
// Let's determine whether the user was already dealing
|
||||
// with messages off the screen, which can guide auto
|
||||
// scrolling decisions.
|
||||
|
@ -119,7 +143,7 @@ export function is_scrolled_up() {
|
|||
return offset > 0;
|
||||
}
|
||||
|
||||
export function offset_from_bottom($last_row) {
|
||||
export function offset_from_bottom($last_row: JQuery): number {
|
||||
// A positive return value here means the last row is
|
||||
// below the bottom of the feed (i.e. obscured by the compose
|
||||
// box or even further below the bottom).
|
||||
|
@ -129,7 +153,12 @@ export function offset_from_bottom($last_row) {
|
|||
return message_bottom - info.visible_bottom;
|
||||
}
|
||||
|
||||
export function set_message_position(message_top, message_height, viewport_info, ratio) {
|
||||
export function set_message_position(
|
||||
message_top: number,
|
||||
message_height: number,
|
||||
viewport_info: MessageViewportInfo,
|
||||
ratio: number,
|
||||
): void {
|
||||
// message_top = offset of the top of a message that you are positioning
|
||||
// message_height = height of the message that you are positioning
|
||||
// viewport_info = result of calling message_viewport.message_viewport_info
|
||||
|
@ -160,7 +189,12 @@ export function set_message_position(message_top, message_height, viewport_info,
|
|||
scrollTop(new_scroll_top);
|
||||
}
|
||||
|
||||
function in_viewport_or_tall(rect, top_of_feed, bottom_of_feed, require_fully_visible) {
|
||||
function in_viewport_or_tall(
|
||||
rect: DOMRect,
|
||||
top_of_feed: number,
|
||||
bottom_of_feed: number,
|
||||
require_fully_visible: boolean,
|
||||
): boolean {
|
||||
if (require_fully_visible) {
|
||||
return (
|
||||
rect.top > top_of_feed && // Message top is in view and
|
||||
|
@ -171,14 +205,14 @@ function in_viewport_or_tall(rect, top_of_feed, bottom_of_feed, require_fully_vi
|
|||
return rect.bottom > top_of_feed && rect.top < bottom_of_feed;
|
||||
}
|
||||
|
||||
function add_to_visible(
|
||||
$candidates,
|
||||
visible,
|
||||
top_of_feed,
|
||||
bottom_of_feed,
|
||||
require_fully_visible,
|
||||
row_to_id,
|
||||
) {
|
||||
function add_to_visible<T>(
|
||||
$candidates: JQuery,
|
||||
visible: T[],
|
||||
top_of_feed: number,
|
||||
bottom_of_feed: number,
|
||||
require_fully_visible: boolean,
|
||||
row_to_id: ($row: HTMLElement) => T,
|
||||
): void {
|
||||
for (const row of $candidates) {
|
||||
const row_rect = row.getBoundingClientRect();
|
||||
// Mark very tall messages as read once we've gotten past them
|
||||
|
@ -209,13 +243,13 @@ const bottom_of_feed = new util.CachedValue({
|
|||
},
|
||||
});
|
||||
|
||||
function _visible_divs(
|
||||
$selected_row,
|
||||
row_min_height,
|
||||
row_to_output,
|
||||
div_class,
|
||||
require_fully_visible,
|
||||
) {
|
||||
function _visible_divs<T>(
|
||||
$selected_row: JQuery,
|
||||
row_min_height: number,
|
||||
row_to_output: ($row: HTMLElement) => T,
|
||||
div_class: string,
|
||||
require_fully_visible: boolean,
|
||||
): T[] {
|
||||
// Note that when using getBoundingClientRect() we are getting offsets
|
||||
// relative to the visible window, but when using jQuery's offset() we are
|
||||
// getting offsets relative to the full scrollable window. You can't try to
|
||||
|
@ -225,7 +259,7 @@ function _visible_divs(
|
|||
|
||||
// We do this explicitly without merges and without recalculating
|
||||
// the feed bounds to keep this computation as cheap as possible.
|
||||
const visible = [];
|
||||
const visible: T[] = [];
|
||||
const $above_pointer = $selected_row
|
||||
.prevAll(`div.${CSS.escape(div_class)}`)
|
||||
.slice(0, num_neighbors);
|
||||
|
@ -260,7 +294,8 @@ function _visible_divs(
|
|||
return visible;
|
||||
}
|
||||
|
||||
export function visible_groups(require_fully_visible) {
|
||||
export function visible_groups(require_fully_visible: boolean): HTMLElement[] {
|
||||
assert(message_lists.current !== undefined);
|
||||
const $selected_row = message_lists.current.selected_row();
|
||||
if ($selected_row === undefined || $selected_row.length === 0) {
|
||||
return [];
|
||||
|
@ -268,29 +303,45 @@ export function visible_groups(require_fully_visible) {
|
|||
|
||||
const $selected_group = rows.get_message_recipient_row($selected_row);
|
||||
|
||||
function get_row(row) {
|
||||
function get_row(row: HTMLElement): HTMLElement {
|
||||
return row;
|
||||
}
|
||||
|
||||
// Being simplistic about this, the smallest group is about 75 px high.
|
||||
return _visible_divs($selected_group, 75, get_row, "recipient_row", require_fully_visible);
|
||||
return _visible_divs<HTMLElement>(
|
||||
$selected_group,
|
||||
75,
|
||||
get_row,
|
||||
"recipient_row",
|
||||
require_fully_visible,
|
||||
);
|
||||
}
|
||||
|
||||
export function visible_messages(require_fully_visible) {
|
||||
export function visible_messages(require_fully_visible: boolean): Message[] {
|
||||
assert(message_lists.current !== undefined);
|
||||
const $selected_row = message_lists.current.selected_row();
|
||||
|
||||
function row_to_id(row) {
|
||||
return message_lists.current.get(rows.id($(row)));
|
||||
function row_to_id(row: HTMLElement): Message {
|
||||
assert(message_lists.current !== undefined);
|
||||
return message_lists.current.get(rows.id($(row))!)!;
|
||||
}
|
||||
|
||||
// Being simplistic about this, the smallest message is 25 px high.
|
||||
return _visible_divs($selected_row, 25, row_to_id, "message_row", require_fully_visible);
|
||||
return _visible_divs<Message>(
|
||||
$selected_row,
|
||||
25,
|
||||
row_to_id,
|
||||
"message_row",
|
||||
require_fully_visible,
|
||||
);
|
||||
}
|
||||
|
||||
export function scrollTop(target_scrollTop) {
|
||||
export function scrollTop(): number;
|
||||
export function scrollTop(target_scrollTop: number): JQuery;
|
||||
export function scrollTop(target_scrollTop?: number): JQuery | number {
|
||||
const orig_scrollTop = $scroll_container.scrollTop();
|
||||
if (target_scrollTop === undefined) {
|
||||
return orig_scrollTop;
|
||||
return orig_scrollTop ?? 0;
|
||||
}
|
||||
let $ret = $scroll_container.scrollTop(target_scrollTop);
|
||||
const new_scrollTop = $scroll_container.scrollTop();
|
||||
|
@ -327,13 +378,13 @@ export function scrollTop(target_scrollTop) {
|
|||
return $ret;
|
||||
}
|
||||
|
||||
export function stop_auto_scrolling() {
|
||||
export function stop_auto_scrolling(): void {
|
||||
if (in_stoppable_autoscroll) {
|
||||
$scroll_container.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export function system_initiated_animate_scroll(scroll_amount) {
|
||||
export function system_initiated_animate_scroll(scroll_amount: number): void {
|
||||
message_scroll_state.set_update_selection_on_next_scroll(false);
|
||||
const viewport_offset = scrollTop();
|
||||
in_stoppable_autoscroll = true;
|
||||
|
@ -345,7 +396,7 @@ export function system_initiated_animate_scroll(scroll_amount) {
|
|||
});
|
||||
}
|
||||
|
||||
export function user_initiated_animate_scroll(scroll_amount) {
|
||||
export function user_initiated_animate_scroll(scroll_amount: number): void {
|
||||
message_scroll_state.set_update_selection_on_next_scroll(false);
|
||||
in_stoppable_autoscroll = false; // defensive
|
||||
|
||||
|
@ -356,7 +407,10 @@ export function user_initiated_animate_scroll(scroll_amount) {
|
|||
});
|
||||
}
|
||||
|
||||
export function recenter_view($message, {from_scroll = false, force_center = false} = {}) {
|
||||
export function recenter_view(
|
||||
$message: JQuery,
|
||||
{from_scroll = false, force_center = false} = {},
|
||||
): void {
|
||||
// BarnOwl-style recentering: if the pointer is too high, move it to
|
||||
// the 1/2 marks. If the pointer is too low, move it to the 1/7 mark.
|
||||
// See keep_pointer_in_view() for related logic to keep the pointer onscreen.
|
||||
|
@ -394,7 +448,8 @@ export function recenter_view($message, {from_scroll = false, force_center = fal
|
|||
}
|
||||
}
|
||||
|
||||
export function maybe_scroll_to_show_message_top() {
|
||||
export function maybe_scroll_to_show_message_top(): void {
|
||||
assert(message_lists.current !== undefined);
|
||||
// Sets the top of the message to the top of the viewport.
|
||||
// Only applies if the top of the message is out of view above the visible area.
|
||||
const $selected_message = message_lists.current.selected_row();
|
||||
|
@ -406,13 +461,14 @@ export function maybe_scroll_to_show_message_top() {
|
|||
}
|
||||
}
|
||||
|
||||
export function is_message_below_viewport($message_row) {
|
||||
export function is_message_below_viewport($message_row: JQuery): boolean {
|
||||
const info = message_viewport_info();
|
||||
const offset = $message_row.get_offset_to_window();
|
||||
return offset.top >= info.visible_bottom;
|
||||
}
|
||||
|
||||
export function keep_pointer_in_view() {
|
||||
export function keep_pointer_in_view(): void {
|
||||
assert(message_lists.current !== undefined);
|
||||
// See message_viewport.recenter_view() for related logic to keep the pointer onscreen.
|
||||
// This function mostly comes into place for mouse scrollers, and it
|
||||
// keeps the pointer in view. For people who purely scroll with the
|
||||
|
@ -430,7 +486,7 @@ export function keep_pointer_in_view() {
|
|||
const top_threshold = info.visible_top + (1 / 10) * info.visible_height;
|
||||
const bottom_threshold = info.visible_top + (9 / 10) * info.visible_height;
|
||||
|
||||
function message_is_far_enough_down() {
|
||||
function message_is_far_enough_down(): boolean {
|
||||
if (at_top()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -455,11 +511,14 @@ export function keep_pointer_in_view() {
|
|||
return false;
|
||||
}
|
||||
|
||||
function message_is_far_enough_up() {
|
||||
function message_is_far_enough_up(): boolean {
|
||||
return at_bottom() || $next_row.get_offset_to_window().top <= bottom_threshold;
|
||||
}
|
||||
|
||||
function adjust(in_view, get_next_row) {
|
||||
function adjust(
|
||||
in_view: () => boolean,
|
||||
get_next_row: ($message_row: JQuery) => JQuery,
|
||||
): boolean {
|
||||
// return true only if we make an actual adjustment, so
|
||||
// that we know to short circuit the other direction
|
||||
if (in_view()) {
|
||||
|
@ -479,10 +538,11 @@ export function keep_pointer_in_view() {
|
|||
adjust(message_is_far_enough_up, rows.prev_visible);
|
||||
}
|
||||
|
||||
message_lists.current.select_id(rows.id($next_row), {from_scroll: true});
|
||||
message_lists.current.select_id(rows.id($next_row)!, {from_scroll: true});
|
||||
}
|
||||
|
||||
export function scroll_to_selected() {
|
||||
export function scroll_to_selected(): void {
|
||||
assert(message_lists.current !== undefined);
|
||||
const $selected_row = message_lists.current.selected_row();
|
||||
if ($selected_row && $selected_row.length !== 0) {
|
||||
recenter_view($selected_row);
|
||||
|
@ -491,11 +551,11 @@ export function scroll_to_selected() {
|
|||
|
||||
export let scroll_to_selected_planned = false;
|
||||
|
||||
export function plan_scroll_to_selected() {
|
||||
export function plan_scroll_to_selected(): void {
|
||||
scroll_to_selected_planned = true;
|
||||
}
|
||||
|
||||
export function maybe_scroll_to_selected() {
|
||||
export function maybe_scroll_to_selected(): void {
|
||||
// If we have made a plan to scroll to the selected message but
|
||||
// deferred doing so, do so here.
|
||||
if (scroll_to_selected_planned) {
|
||||
|
@ -504,9 +564,8 @@ export function maybe_scroll_to_selected() {
|
|||
}
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
export function initialize(): void {
|
||||
$jwindow = $(window);
|
||||
|
||||
// This handler must be placed before all resize handlers in our application
|
||||
$jwindow.on("resize", () => {
|
||||
dimensions.height.reset();
|
Loading…
Reference in New Issue