hashchange: Convert module to TypeScript. (#32670)

This commit is contained in:
Anders Kaseorg
2024-12-11 09:15:18 -08:00
committed by GitHub
parent 38ad1e8bdc
commit 666ce4519d
12 changed files with 61 additions and 36 deletions

View File

@@ -17,7 +17,7 @@ Some examples are:
channel.)
The main module in the frontend that manages this all is
`web/src/hashchange.js` (plus `hash_util.js` for all the parsing
`web/src/hashchange.ts` (plus `hash_util.js` for all the parsing
code), which is unfortunately one of our thorniest modules. Part of
the reason that it's thorny is that it needs to support a lot of
different flows:

View File

@@ -113,7 +113,7 @@ EXEMPT_FILES = make_set(
"web/src/global.ts",
"web/src/group_setting_pill.ts",
"web/src/hash_util.ts",
"web/src/hashchange.js",
"web/src/hashchange.ts",
"web/src/hbs.d.ts",
"web/src/hotkey.js",
"web/src/inbox_ui.ts",

View File

@@ -285,7 +285,7 @@ export function build_page(): void {
}
}
export function launch(section: string, user_settings_tab: string): void {
export function launch(section: string, user_settings_tab: string | undefined): void {
settings_sections.reset_sections();
settings.open_settings_overlay();

View File

@@ -8,7 +8,7 @@ import {user_settings} from "./user_settings.ts";
export const state: {
is_internal_change: boolean;
hash_before_overlay: string | null;
hash_before_overlay: string | null | undefined;
old_hash: string;
changing_hash: boolean;
spectator_old_hash: string | null;
@@ -36,7 +36,7 @@ export function old_hash(): string {
return state.old_hash;
}
export function set_hash_before_overlay(hash: string): void {
export function set_hash_before_overlay(hash: string | undefined): void {
state.hash_before_overlay = hash;
}

View File

@@ -15,7 +15,7 @@ import * as compose_state from "./compose_state.ts";
import {media_breakpoints_num} from "./css_variables.ts";
import * as emoji_picker from "./emoji_picker.ts";
import * as hash_util from "./hash_util.ts";
import * as hashchange from "./hashchange.js";
import * as hashchange from "./hashchange.ts";
import * as message_edit from "./message_edit.ts";
import * as message_lists from "./message_lists.ts";
import * as message_store from "./message_store.ts";

View File

@@ -68,7 +68,7 @@ links:
#invite
When you click on the links there is a function
called hashchanged() in web/src/hashchange.js
called hashchanged() in web/src/hashchange.ts
that gets invoked. (We register this as a listener
for the hashchange event.) This function then
launches the appropriate modal for each menu item.

View File

@@ -46,7 +46,7 @@ export function is_same_server_message_link(url: string): boolean {
);
}
export function is_overlay_hash(hash: string): boolean {
export function is_overlay_hash(hash: string | undefined): boolean {
// Hash changes within this list are overlays and should not unnarrow (etc.)
const overlay_list = [
// In 2024, stream was renamed to channel in the Zulip API and UI.

View File

@@ -37,7 +37,23 @@ import {user_settings} from "./user_settings.ts";
// Read https://zulip.readthedocs.io/en/latest/subsystems/hashchange-system.html
// or locally: docs/subsystems/hashchange-system.md
function maybe_hide_recent_view() {
export type JQueryHashChangeEvent<TDelegateTarget, TData, TCurrentTarget, TTarget> =
JQuery.EventBase<TDelegateTarget, TData, TCurrentTarget, TTarget> & {
type: "hashchanged";
originalEvent: HashChangeEvent;
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JQuery {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface TypeToTriggeredEventMap<TDelegateTarget, TData, TCurrentTarget, TTarget> {
hashchange: JQueryHashChangeEvent<TDelegateTarget, TData, TCurrentTarget, TTarget>;
}
}
}
function maybe_hide_recent_view(): boolean {
if (recent_view_util.is_visible()) {
recent_view_ui.hide();
return true;
@@ -45,7 +61,7 @@ function maybe_hide_recent_view() {
return false;
}
function maybe_hide_inbox() {
function maybe_hide_inbox(): boolean {
if (inbox_util.is_visible()) {
inbox_ui.hide();
return true;
@@ -53,26 +69,27 @@ function maybe_hide_inbox() {
return false;
}
function show_all_message_view() {
function show_all_message_view(): void {
// Don't export this function outside of this module since
// `change_hash` is false here which means it is should only
// be called after hash is updated in the URL.
const current_state = browser_history.state_data_schema.nullable().parse(window.history.state);
message_view.show([{operator: "in", operand: "home"}], {
trigger: "hashchange",
change_hash: false,
then_select_id: window.history.state?.narrow_pointer,
then_select_offset: window.history.state?.narrow_offset,
then_select_id: current_state?.narrow_pointer,
then_select_offset: current_state?.narrow_offset,
});
}
function is_somebody_else_profile_open() {
function is_somebody_else_profile_open(): boolean {
return (
user_profile.get_user_id_if_user_profile_modal_open() !== undefined &&
user_profile.get_user_id_if_user_profile_modal_open() !== people.my_current_user_id()
);
}
function handle_invalid_users_section_url(user_settings_tab) {
function handle_invalid_users_section_url(user_settings_tab: string): string {
const valid_user_settings_tab_values = new Set(["active", "deactivated", "invitations"]);
if (!valid_user_settings_tab_values.has(user_settings_tab)) {
const valid_users_section_url = "#organization/users/active";
@@ -82,7 +99,7 @@ function handle_invalid_users_section_url(user_settings_tab) {
return user_settings_tab;
}
function get_user_settings_tab(section) {
function get_user_settings_tab(section: string): string | undefined {
if (section === "users") {
const current_user_settings_tab = hash_parser.get_current_nth_hash_section(2);
return handle_invalid_users_section_url(current_user_settings_tab);
@@ -90,7 +107,7 @@ function get_user_settings_tab(section) {
return undefined;
}
export function set_hash_to_home_view(triggered_by_escape_key = false) {
export function set_hash_to_home_view(triggered_by_escape_key = false): void {
if (browser_history.is_current_hash_home_view()) {
return;
}
@@ -118,7 +135,7 @@ export function set_hash_to_home_view(triggered_by_escape_key = false) {
hashchanged(false);
}
function show_home_view() {
function show_home_view(): void {
// This function should only be called from the hashchange
// handlers, as it does not set the hash to "".
//
@@ -153,7 +170,7 @@ function show_home_view() {
}
// Returns true if this function performed a narrow
function do_hashchange_normal(from_reload, restore_selected_id) {
function do_hashchange_normal(from_reload: boolean, restore_selected_id: boolean): boolean {
message_viewport.stop_auto_scrolling();
// NB: In Firefox, window.location.hash is URI-decoded.
@@ -184,7 +201,7 @@ function do_hashchange_normal(from_reload, restore_selected_id) {
show_home_view();
return false;
}
const narrow_opts = {
const narrow_opts: message_view.ShowMessageViewOpts = {
change_hash: false, // already set
trigger: "hash change",
show_more_topics: false,
@@ -197,7 +214,9 @@ function do_hashchange_normal(from_reload, restore_selected_id) {
}
}
const data_for_hash = window.history.state;
const data_for_hash = browser_history.state_data_schema
.nullable()
.parse(window.history.state);
if (restore_selected_id && data_for_hash) {
narrow_opts.then_select_id = data_for_hash.narrow_pointer;
narrow_opts.then_select_offset = data_for_hash.narrow_offset;
@@ -251,7 +270,7 @@ function do_hashchange_normal(from_reload, restore_selected_id) {
case "#settings":
case "#about-zulip":
case "#scheduled":
blueslip.error("overlay logic skipped for: " + hash);
blueslip.error("overlay logic skipped for: " + hash[0]);
break;
default:
show_home_view();
@@ -259,7 +278,7 @@ function do_hashchange_normal(from_reload, restore_selected_id) {
return false;
}
function do_hashchange_overlay(old_hash) {
function do_hashchange_overlay(old_hash: string | undefined): void {
if (old_hash === undefined) {
// The user opened the app with an overlay hash; we need to
// show the user's home view behind it.
@@ -486,9 +505,13 @@ function do_hashchange_overlay(old_hash) {
}
}
function hashchanged(from_reload, e, restore_selected_id = true) {
function hashchanged(
from_reload: boolean,
e?: JQuery.Event | HashChangeEvent,
restore_selected_id = true,
): boolean | undefined {
const current_hash = window.location.hash;
const old_hash = e && (e.oldURL ? new URL(e.oldURL).hash : browser_history.old_hash());
const old_hash = e && ("oldURL" in e ? new URL(e.oldURL).hash : browser_history.old_hash());
const is_hash_web_public_compatible = browser_history.update_web_public_hash(current_hash);
const was_internal_change = browser_history.save_old_hash();
@@ -528,7 +551,7 @@ function hashchanged(from_reload, e, restore_selected_id = true) {
return ret;
}
export function initialize() {
export function initialize(): void {
// We don't want browser to restore the scroll
// position of the new hash in the current hash.
window.history.scrollRestoration = "manual";
@@ -538,8 +561,8 @@ export function initialize() {
});
hashchanged(true);
$("body").on("click", "a", (e) => {
const href = e.currentTarget.getAttribute("href");
$("body").on("click", "a", function (this: HTMLAnchorElement, e: JQuery.ClickEvent) {
const href = this.href;
if (href === window.location.hash && href.includes("/near/")) {
// The clicked on a link, perhaps a "said" reference, that
// matches the current view. Such a click doesn't trigger

View File

@@ -23,7 +23,7 @@ import * as feedback_widget from "./feedback_widget.ts";
import * as gear_menu from "./gear_menu.ts";
import * as giphy from "./giphy.ts";
import * as hash_util from "./hash_util.ts";
import * as hashchange from "./hashchange.js";
import * as hashchange from "./hashchange.ts";
import * as inbox_ui from "./inbox_ui.ts";
import * as lightbox from "./lightbox.ts";
import * as list_util from "./list_util.ts";

View File

@@ -1,4 +1,5 @@
import $ from "jquery";
import assert from "minimalistic-assert";
import * as blueslip from "./blueslip.ts";
import * as browser_history from "./browser_history.ts";
@@ -59,7 +60,7 @@ export class SettingsPanelMenu {
hash_prefix: string;
$curr_li: JQuery;
current_tab: string;
current_user_settings_tab: string;
current_user_settings_tab: string | undefined;
org_user_settings_toggler: Toggle;
constructor(opts: {$main_elem: JQuery; hash_prefix: string}) {
@@ -176,13 +177,13 @@ export class SettingsPanelMenu {
this.current_tab = tab;
}
set_user_settings_tab(tab: string): void {
set_user_settings_tab(tab: string | undefined): void {
this.current_user_settings_tab = tab;
}
activate_section_or_default(
section: string,
user_settings_tab: string,
section: string | undefined,
user_settings_tab?: string,
activate_section_for_mobile = true,
): void {
popovers.hide_all();
@@ -220,6 +221,7 @@ export class SettingsPanelMenu {
browser_history.update_hash_internally_if_required(settings_section_hash);
}
if (section === "users" && this.org_user_settings_toggler !== undefined) {
assert(user_settings_tab !== undefined);
this.show_org_user_settings_toggler();
this.org_user_settings_toggler.goto(user_settings_tab);
}

View File

@@ -46,7 +46,7 @@ import * as emojisets from "./emojisets.ts";
import * as gear_menu from "./gear_menu.ts";
import * as giphy from "./giphy.ts";
import * as giphy_state from "./giphy_state.ts";
import * as hashchange from "./hashchange.js";
import * as hashchange from "./hashchange.ts";
import * as hotkey from "./hotkey.js";
import * as i18n from "./i18n.ts";
import * as inbox_ui from "./inbox_ui.ts";
@@ -659,7 +659,7 @@ export function initialize_everything(state_data) {
},
});
// All overlays, and also activity_ui, must be initialized before hashchange.js
// All overlays, and also activity_ui, must be initialized before hashchange.ts
hashchange.initialize();
emoji_picker.initialize();

View File

@@ -11,7 +11,7 @@ let $window_stub;
set_global("to_$", () => $window_stub);
set_global("document", "document-stub");
const history = set_global("history", {});
const history = set_global("history", {state: null});
const admin = mock_esm("../src/admin");
const drafts_overlay_ui = mock_esm("../src/drafts_overlay_ui");