From bb174e9b6497c64afc0edd5de43460fd97bcae56 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Tue, 26 Nov 2024 13:47:30 -0800 Subject: [PATCH] gear_menu: Convert module to TypeScript. Signed-off-by: Anders Kaseorg --- tools/test-js-with-node | 2 +- web/src/{gear_menu.js => gear_menu.ts} | 44 ++++++++++++++---- web/src/hotkey.js | 2 +- web/src/navbar_menus.js | 2 +- web/src/server_events_dispatch.js | 2 +- web/src/types/winchan.d.ts | 64 ++++++++++++++++++++++++++ web/src/ui_init.js | 2 +- 7 files changed, 103 insertions(+), 15 deletions(-) rename web/src/{gear_menu.js => gear_menu.ts} (78%) create mode 100644 web/src/types/winchan.d.ts diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 44b970009c..0894e51671 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -108,7 +108,7 @@ EXEMPT_FILES = make_set( "web/src/feedback_widget.ts", "web/src/fetch_status.ts", "web/src/flatpickr.ts", - "web/src/gear_menu.js", + "web/src/gear_menu.ts", "web/src/giphy.ts", "web/src/giphy_state.ts", "web/src/global.ts", diff --git a/web/src/gear_menu.js b/web/src/gear_menu.ts similarity index 78% rename from web/src/gear_menu.js rename to web/src/gear_menu.ts index abc816d9d2..43ee91eb05 100644 --- a/web/src/gear_menu.js +++ b/web/src/gear_menu.ts @@ -1,5 +1,8 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; +import type * as tippy from "tippy.js"; import WinChan from "winchan"; +import {z} from "zod"; import render_navbar_gear_menu_popover from "../templates/popovers/navbar/navbar_gear_menu_popover.hbs"; @@ -84,7 +87,7 @@ The click handler uses "[data-overlay-trigger]" as the selector and then calls browser_history.go_to_location. */ -function render(instance) { +function render(instance: tippy.Instance): void { const rendered_gear_menu = render_navbar_gear_menu_popover( popover_menus_data.get_gear_menu_content_context(), ); @@ -92,7 +95,7 @@ function render(instance) { $("#gear-menu").addClass("active-navbar-menu"); } -export function initialize() { +export function initialize(): void { popover_menus.register_popover_menu("#gear-menu", { theme: "popover-menu", placement: "bottom", @@ -123,13 +126,32 @@ export function initialize() { principal, }, }, - (err, r) => { - if (err) { + (err, raw_response) => { + if (err !== null) { blueslip.warn(err); return; } + + // https://github.com/davidben/webathena/blob/0be20d9b1d62c19b4f94f77e621bd8721e504446/app/scripts-src/request_ticket.js + const response_schema = z.discriminatedUnion("status", [ + z.object({ + status: z.literal("OK"), + session: z.unknown(), + }), + z.object({ + status: z.literal("ERROR"), + code: z.string(), + message: z.string(), + }), + z.object({ + status: z.literal("DENIED"), + code: z.string(), + message: z.string(), + }), + ]); + const r = response_schema.parse(raw_response); if (r.status !== "OK") { - blueslip.warn(r); + blueslip.warn(`Webathena: ${r.status}: ${r.message}`); return; } @@ -158,7 +180,7 @@ export function initialize() { }); $popper.on("change", "input[name='theme-select']", (e) => { - const theme_code = Number.parseInt($(e.currentTarget).attr("data-theme-code"), 10); + const theme_code = Number.parseInt($(e.currentTarget).attr("data-theme-code")!, 10); requestAnimationFrame(() => { theme.set_theme_for_spectator(theme_code); }); @@ -168,12 +190,12 @@ export function initialize() { onHidden(instance) { $("#gear-menu").removeClass("active-navbar-menu"); instance.destroy(); - popover_menus.popover_instances.gear_menu = undefined; + popover_menus.popover_instances.gear_menu = null; }, }); } -export function toggle() { +export function toggle(): void { if (popover_menus.is_gear_menu_popover_displayed()) { popovers.hide_all(); return; @@ -188,8 +210,10 @@ export function toggle() { $("#gear-menu").trigger("click"); } -export function rerender() { +export function rerender(): void { if (popover_menus.is_gear_menu_popover_displayed()) { - render(popover_menus.get_gear_menu_instance()); + const instance = popover_menus.get_gear_menu_instance(); + assert(instance !== null); + render(instance); } } diff --git a/web/src/hotkey.js b/web/src/hotkey.js index 1a7f5f3b43..674963f1d0 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -20,7 +20,7 @@ import * as drafts_overlay_ui from "./drafts_overlay_ui.js"; import * as emoji from "./emoji.ts"; import * as emoji_picker from "./emoji_picker.ts"; import * as feedback_widget from "./feedback_widget.ts"; -import * as gear_menu from "./gear_menu.js"; +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"; diff --git a/web/src/navbar_menus.js b/web/src/navbar_menus.js index 225876b7e0..4fd6348a00 100644 --- a/web/src/navbar_menus.js +++ b/web/src/navbar_menus.js @@ -1,6 +1,6 @@ import $ from "jquery"; -import * as gear_menu from "./gear_menu.js"; +import * as gear_menu from "./gear_menu.ts"; import * as navbar_help_menu from "./navbar_help_menu.ts"; import * as personal_menu_popover from "./personal_menu_popover.ts"; import * as popover_menus from "./popover_menus.ts"; diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index f0fa6cd03e..e77f5312ea 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -19,7 +19,7 @@ import * as compose_state from "./compose_state.ts"; import {electron_bridge} from "./electron_bridge.ts"; import * as emoji from "./emoji.ts"; import * as emoji_picker from "./emoji_picker.ts"; -import * as gear_menu from "./gear_menu.js"; +import * as gear_menu from "./gear_menu.ts"; import * as giphy from "./giphy.ts"; import * as information_density from "./information_density.ts"; import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts"; diff --git a/web/src/types/winchan.d.ts b/web/src/types/winchan.d.ts new file mode 100644 index 0000000000..c6cda08d7b --- /dev/null +++ b/web/src/types/winchan.d.ts @@ -0,0 +1,64 @@ +// Types for https://www.npmjs.com/package/winchan + +/** + * Opens a window and initiates a cross-domain request. Call this from the + * "untrusted" or "client" site. + * + * @param options - Options for `open` + * @param responseCallback - Called when the request completes with a response + * or an error + */ +export function open( + options: { + /** + * URL of the "trusted" window + */ + url: string; + /** + * URL of the `relay.html` iframe (used for Internet Explorer support) + */ + relay_url: string; + /** + * Target name passed to `window.open` + */ + window_name?: string | undefined; + /** + * Comma-separated features passed to `window.open` + */ + window_features?: string | undefined; + /** + * Application-defined parameters for the request + */ + params?: unknown; + }, + responseCallback: (err: string | null, response?: unknown) => void, +): { + /** + * Closes the window. + */ + close: () => void; + /** + * Tries to move the focus to the window. + */ + focus: () => void; +}; + +/** + * Listens for a request. Call this from the "trusted" site providing the + * window. + * + * @param requestCallback - Called to initiate the request + */ +export function onOpen( + requestCallback: ( + origin: string, + params: unknown, + sendResponse: (response: unknown) => void, + ) => void, +): { + /** + * Detaches the channel without sending a response yet. Call this before + * navigating to another page in a multi-page dialog. + */ + detach: () => void; +}; diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 816e5fd387..9245a6435b 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -43,7 +43,7 @@ import * as echo from "./echo.ts"; import * as emoji from "./emoji.ts"; import * as emoji_picker from "./emoji_picker.ts"; import * as emojisets from "./emojisets.ts"; -import * as gear_menu from "./gear_menu.js"; +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";