web: Use util.the for accessing element of single-item lists.

This commit is contained in:
evykassirer 2024-08-19 22:18:02 -07:00 committed by Tim Abbott
parent 113de14547
commit d9f25d01a1
47 changed files with 188 additions and 140 deletions

View File

@ -7,6 +7,7 @@ import * as browser_history from "./browser_history";
import {show_copied_confirmation} from "./copied_tooltip"; import {show_copied_confirmation} from "./copied_tooltip";
import * as overlays from "./overlays"; import * as overlays from "./overlays";
import {realm} from "./state_data"; import {realm} from "./state_data";
import * as util from "./util";
export function launch(): void { export function launch(): void {
overlays.open_overlay({ overlays.open_overlay({
@ -19,12 +20,12 @@ export function launch(): void {
const zulip_version_clipboard = new ClipboardJS("#about-zulip .fa-copy.zulip-version"); const zulip_version_clipboard = new ClipboardJS("#about-zulip .fa-copy.zulip-version");
zulip_version_clipboard.on("success", () => { zulip_version_clipboard.on("success", () => {
show_copied_confirmation($("#about-zulip .fa-copy.zulip-version")[0]!); show_copied_confirmation(util.the($("#about-zulip .fa-copy.zulip-version")));
}); });
const zulip_merge_base_clipboard = new ClipboardJS("#about-zulip .fa-copy.zulip-merge-base"); const zulip_merge_base_clipboard = new ClipboardJS("#about-zulip .fa-copy.zulip-merge-base");
zulip_merge_base_clipboard.on("success", () => { zulip_merge_base_clipboard.on("success", () => {
show_copied_confirmation($("#about-zulip .fa-copy.zulip-merge-base")[0]!); show_copied_confirmation(util.the($("#about-zulip .fa-copy.zulip-merge-base")));
}); });
} }

View File

@ -1,6 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as util from "./util";
export function initialize(): void { export function initialize(): void {
update_notification_sound_source($("audio#user-notification-sound-audio"), user_settings); update_notification_sound_source($("audio#user-notification-sound-audio"), user_settings);
@ -22,6 +23,6 @@ export function update_notification_sound_source(
if (notification_sound !== "none") { if (notification_sound !== "none") {
// Load it so that it is ready to be played; without this the old sound // Load it so that it is ready to be played; without this the old sound
// is played. // is played.
$container_elem[0]!.load(); util.the($container_elem).load();
} }
} }

View File

@ -2,6 +2,7 @@ import $ from "jquery";
import {z} from "zod"; import {z} from "zod";
import * as loading from "../loading"; import * as loading from "../loading";
import * as util from "../util";
export type FormDataObject = Record<string, string>; export type FormDataObject = Record<string, string>;
@ -162,7 +163,7 @@ export function update_discount_details(
} }
export function is_valid_input($elem: JQuery<HTMLFormElement>): boolean { export function is_valid_input($elem: JQuery<HTMLFormElement>): boolean {
return $elem[0]!.checkValidity(); return util.the($elem).checkValidity();
} }
export function redirect_to_billing_with_successful_upgrade(billing_base_url: string): void { export function redirect_to_billing_with_successful_upgrade(billing_base_url: string): void {

View File

@ -171,7 +171,7 @@ import getCaretCoordinates from "textarea-caret";
import * as tippy from "tippy.js"; import * as tippy from "tippy.js";
import * as scroll_util from "./scroll_util"; import * as scroll_util from "./scroll_util";
import {get_string_diff} from "./util"; import {get_string_diff, the} from "./util";
function get_pseudo_keycode( function get_pseudo_keycode(
event: JQuery.KeyDownEvent | JQuery.KeyUpEvent | JQuery.KeyPressEvent, event: JQuery.KeyDownEvent | JQuery.KeyUpEvent | JQuery.KeyPressEvent,
@ -322,7 +322,7 @@ export class Typeahead<ItemType extends string | object> {
} }
select(e?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent): this { select(e?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent): this {
const val = this.values.get(this.$menu.find(".active")[0]!); const val = this.values.get(the(this.$menu.find(".active")));
// It's possible that we got here from pressing enter with nothing highlighted. // It's possible that we got here from pressing enter with nothing highlighted.
if (!this.requireHighlight && val === undefined) { if (!this.requireHighlight && val === undefined) {
return this.hide(); return this.hide();
@ -359,7 +359,7 @@ export class Typeahead<ItemType extends string | object> {
} }
set_value(): void { set_value(): void {
const val = this.values.get(this.$menu.find(".active")[0]!); const val = this.values.get(the(this.$menu.find(".active")));
assert(typeof val === "string"); assert(typeof val === "string");
if (this.input_element.type === "contenteditable") { if (this.input_element.type === "contenteditable") {
this.input_element.$element.text(val); this.input_element.$element.text(val);
@ -392,7 +392,7 @@ export class Typeahead<ItemType extends string | object> {
const input_element = this.input_element; const input_element = this.input_element;
if (!this.non_tippy_parent_element) { if (!this.non_tippy_parent_element) {
this.instance = tippy.default(input_element.$element[0]!, { this.instance = tippy.default(the(input_element.$element), {
// Lets typeahead take the width needed to fit the content // Lets typeahead take the width needed to fit the content
// and wraps it if it overflows the visible container. // and wraps it if it overflows the visible container.
maxWidth: "none", maxWidth: "none",
@ -425,7 +425,7 @@ export class Typeahead<ItemType extends string | object> {
interactive: true, interactive: true,
appendTo: () => document.body, appendTo: () => document.body,
showOnCreate: true, showOnCreate: true,
content: this.$container[0]!, content: the(this.$container),
// We expect the typeahead creator to handle when to hide / show the typeahead. // We expect the typeahead creator to handle when to hide / show the typeahead.
trigger: "manual", trigger: "manual",
arrow: false, arrow: false,
@ -435,8 +435,8 @@ export class Typeahead<ItemType extends string | object> {
if (input_element.type === "textarea") { if (input_element.type === "textarea") {
const caret = getCaretCoordinates( const caret = getCaretCoordinates(
input_element.$element[0]!, the(input_element.$element),
input_element.$element[0]!.selectionStart, the(input_element.$element).selectionStart,
); );
// Used to consider the scroll height of textbox in the vertical offset. // Used to consider the scroll height of textbox in the vertical offset.
const scrollTop = input_element.$element.scrollTop() ?? 0; const scrollTop = input_element.$element.scrollTop() ?? 0;
@ -554,7 +554,7 @@ export class Typeahead<ItemType extends string | object> {
render(final_items: ItemType[], matching_items: ItemType[]): this { render(final_items: ItemType[], matching_items: ItemType[]): this {
const $items: JQuery[] = final_items.map((item) => { const $items: JQuery[] = final_items.map((item) => {
const $i = $(ITEM_HTML); const $i = $(ITEM_HTML);
this.values.set($i[0]!, item); this.values.set(the($i), item);
const item_html = this.highlighter_html(item, this.query) ?? ""; const item_html = this.highlighter_html(item, this.query) ?? "";
const $item_html = $i.find("a").html(item_html); const $item_html = $i.find("a").html(item_html);
@ -743,12 +743,12 @@ export class Typeahead<ItemType extends string | object> {
this.select(e); this.select(e);
if (this.input_element.$element[0]!.id === "stream_message_recipient_topic") { if (the(this.input_element.$element).id === "stream_message_recipient_topic") {
assert(this.input_element.type === "input"); assert(this.input_element.type === "input");
// Move the cursor to the end of the topic // Move the cursor to the end of the topic
const topic_length = this.input_element.$element.val()!.length; const topic_length = this.input_element.$element.val()!.length;
this.input_element.$element[0]!.selectionStart = topic_length; the(this.input_element.$element).selectionStart = topic_length;
this.input_element.$element[0]!.selectionEnd = topic_length; the(this.input_element.$element).selectionEnd = topic_length;
} }
break; break;
@ -775,7 +775,7 @@ export class Typeahead<ItemType extends string | object> {
// when shift (keycode 16) + tabbing to the topic field // when shift (keycode 16) + tabbing to the topic field
if ( if (
pseudo_keycode === 16 && pseudo_keycode === 16 &&
this.input_element.$element[0]!.id === "stream_message_recipient_topic" the(this.input_element.$element).id === "stream_message_recipient_topic"
) { ) {
return; return;
} }

View File

@ -26,6 +26,7 @@ import * as stream_data from "./stream_data";
import type {StreamSubscription} from "./sub_store"; import type {StreamSubscription} from "./sub_store";
import {INTERACTIVE_HOVER_DELAY} from "./tippyjs"; import {INTERACTIVE_HOVER_DELAY} from "./tippyjs";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as util from "./util";
function get_formatted_sub_count(sub_count: number): string { function get_formatted_sub_count(sub_count: number): string {
if (sub_count < 1000) { if (sub_count < 1000) {
@ -172,7 +173,7 @@ export class BuddyList extends BuddyListConf {
// This will default to "bottom" placement for this tooltip. // This will default to "bottom" placement for this tooltip.
placement = "auto"; placement = "auto";
} }
tippy.default($elem[0]!, { tippy.default(util.the($elem), {
// Because the buddy list subheadings are potential click targets // Because the buddy list subheadings are potential click targets
// for purposes having nothing to do with the subscriber count // for purposes having nothing to do with the subscriber count
// (collapsing/expanding), we delay showing the tooltip until the // (collapsing/expanding), we delay showing the tooltip until the
@ -765,9 +766,7 @@ export class BuddyList extends BuddyListConf {
fill_screen_with_content(): void { fill_screen_with_content(): void {
let height = this.height_to_fill(); let height = this.height_to_fill();
const elem = scroll_util const elem = util.the(scroll_util.get_scroll_element($(this.scroll_container_selector)));
.get_scroll_element($(this.scroll_container_selector))
.expectOne()[0]!;
// Add a fudge factor. // Add a fudge factor.
height += 10; height += 10;

View File

@ -2,6 +2,7 @@ import $ from "jquery";
import * as tippy from "tippy.js"; import * as tippy from "tippy.js";
import {$t} from "./i18n"; import {$t} from "./i18n";
import * as util from "./util";
export const status_classes = "alert-error alert-success alert-info alert-warning alert-loading"; export const status_classes = "alert-error alert-success alert-info alert-warning alert-loading";
@ -108,7 +109,7 @@ function set_password_toggle_label(
): void { ): void {
$(password_selector).attr("aria-label", label); $(password_selector).attr("aria-label", label);
if (tippy_tooltips) { if (tippy_tooltips) {
const element: tippy.ReferenceElement = $(password_selector)[0]!; const element: tippy.ReferenceElement = util.the($(password_selector));
const tippy_instance = element._tippy ?? tippy.default(element); const tippy_instance = element._tippy ?? tippy.default(element);
tippy_instance.setContent(label); tippy_instance.setContent(label);
} else { } else {

View File

@ -281,7 +281,7 @@ function on_hidden_callback(): void {
// Always move focus to the topic input even if it's not empty, // Always move focus to the topic input even if it's not empty,
// since it's likely the user will want to update the topic // since it's likely the user will want to update the topic
// after updating the stream. // after updating the stream.
ui_util.place_caret_at_end($("input#stream_message_recipient_topic")[0]!); ui_util.place_caret_at_end(util.the($("input#stream_message_recipient_topic")));
} else { } else {
if (compose_state.private_message_recipient().length === 0) { if (compose_state.private_message_recipient().length === 0) {
$("#private_message_recipient").trigger("focus").trigger("select"); $("#private_message_recipient").trigger("focus").trigger("select");

View File

@ -123,9 +123,9 @@ export function insert_and_scroll_into_view(
// to support `undo`, we can use a faster method. // to support `undo`, we can use a faster method.
$textarea.val(content); $textarea.val(content);
} else if (replace_all) { } else if (replace_all) {
setFieldText($textarea[0]!, content); setFieldText(util.the($textarea), content);
} else { } else {
insertTextIntoField($textarea[0]!, content); insertTextIntoField(util.the($textarea), content);
} }
// Blurring and refocusing ensures the cursor / selection is in view // Blurring and refocusing ensures the cursor / selection is in view
// in chromium browsers. // in chromium browsers.
@ -300,7 +300,7 @@ export function replace_syntax(
// for details. // for details.
const old_text = $textarea.val(); const old_text = $textarea.val();
replaceFieldText($textarea[0]!, old_syntax, () => new_syntax, "after-replacement"); replaceFieldText(util.the($textarea), old_syntax, () => new_syntax, "after-replacement");
const new_text = $textarea.val(); const new_text = $textarea.val();
// When replacing content in a textarea, we need to move the cursor // When replacing content in a textarea, we need to move the cursor

View File

@ -41,6 +41,7 @@ import type {UserGroup} from "./user_groups";
import * as user_pill from "./user_pill"; import * as user_pill from "./user_pill";
import type {UserPillData} from "./user_pill"; import type {UserPillData} from "./user_pill";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as util from "./util";
// ********************************** // **********************************
// AN IMPORTANT NOTE ABOUT TYPEAHEADS // AN IMPORTANT NOTE ABOUT TYPEAHEADS
@ -250,7 +251,7 @@ function handle_bulleting_or_numbering(
if (bulleted_numbered_list_util.strip_bullet(previous_line) === "") { if (bulleted_numbered_list_util.strip_bullet(previous_line) === "") {
// below we select and replace the last 2 characters in the textarea before // below we select and replace the last 2 characters in the textarea before
// the cursor - the bullet syntax - with an empty string // the cursor - the bullet syntax - with an empty string
$textarea[0]!.setSelectionRange($textarea.caret() - 2, $textarea.caret()); util.the($textarea).setSelectionRange($textarea.caret() - 2, $textarea.caret());
compose_ui.insert_and_scroll_into_view("", $textarea); compose_ui.insert_and_scroll_into_view("", $textarea);
e.preventDefault(); e.preventDefault();
return; return;
@ -264,7 +265,7 @@ function handle_bulleting_or_numbering(
if (bulleted_numbered_list_util.strip_numbering(previous_line) === "") { if (bulleted_numbered_list_util.strip_numbering(previous_line) === "") {
// below we select then replaces the last few characters in the textarea before // below we select then replaces the last few characters in the textarea before
// the cursor - the numbering syntax - with an empty string // the cursor - the numbering syntax - with an empty string
$textarea[0]!.setSelectionRange( util.the($textarea).setSelectionRange(
$textarea.caret() - previous_number_string.length - 2, $textarea.caret() - previous_number_string.length - 2,
$textarea.caret(), $textarea.caret(),
); );
@ -297,7 +298,7 @@ export function handle_enter($textarea: JQuery<HTMLTextAreaElement>, e: JQuery.K
// If the selectionStart and selectionEnd are not the same, that // If the selectionStart and selectionEnd are not the same, that
// means that some text was selected. // means that some text was selected.
if ($textarea[0]!.selectionStart !== $textarea[0]!.selectionEnd) { if (util.the($textarea).selectionStart !== util.the($textarea).selectionEnd) {
// Replace it with the newline, remembering to resize the // Replace it with the newline, remembering to resize the
// textarea if needed. // textarea if needed.
compose_ui.insert_and_scroll_into_view("\n", $textarea); compose_ui.insert_and_scroll_into_view("\n", $textarea);
@ -1175,7 +1176,11 @@ export function content_typeahead_selected(
$textbox.caret(beginning.length); $textbox.caret(beginning.length);
compose_ui.autosize_textarea($textbox); compose_ui.autosize_textarea($textbox);
}; };
flatpickr.show_flatpickr(input_element.$element[0]!, on_timestamp_selection, timestamp); flatpickr.show_flatpickr(
util.the(input_element.$element),
on_timestamp_selection,
timestamp,
);
return beginning + rest; return beginning + rest;
} }
} }

View File

@ -6,6 +6,7 @@ import * as message_lists from "./message_lists";
import type {Message} from "./message_store"; import type {Message} from "./message_store";
import * as message_viewport from "./message_viewport"; import * as message_viewport from "./message_viewport";
import * as rows from "./rows"; import * as rows from "./rows";
import * as util from "./util";
/* /*
This library implements two related, similar concepts: This library implements two related, similar concepts:
@ -151,7 +152,7 @@ function get_message_height(elem: HTMLElement): number {
// This needs to be very fast. This function runs hundreds of times // This needs to be very fast. This function runs hundreds of times
// when displaying a message feed view that has hundreds of message // when displaying a message feed view that has hundreds of message
// history, which ideally should render in <100ms. // history, which ideally should render in <100ms.
return $(elem).find(".message_content")[0]!.scrollHeight; return util.the($(elem).find(".message_content")).scrollHeight;
} }
export function hide_message_expander($row: JQuery): void { export function hide_message_expander($row: JQuery): void {

View File

@ -10,6 +10,7 @@ import * as hash_util from "./hash_util";
import * as message_lists from "./message_lists"; import * as message_lists from "./message_lists";
import * as rows from "./rows"; import * as rows from "./rows";
import * as topic_link_util from "./topic_link_util"; import * as topic_link_util from "./topic_link_util";
import * as util from "./util";
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
@ -135,7 +136,7 @@ function select_div($div: JQuery, selection: Selection): void {
background: "#FFF", background: "#FFF",
}).attr("id", "copytempdiv"); }).attr("id", "copytempdiv");
$("body").append($div); $("body").append($div);
selection.selectAllChildren($div[0]!); selection.selectAllChildren(util.the($div));
} }
function remove_div(_div: JQuery, ranges: Range[]): void { function remove_div(_div: JQuery, ranges: Range[]): void {
@ -629,7 +630,9 @@ function is_safe_url_paste_target($textarea: JQuery<HTMLTextAreaElement>): boole
export function cursor_at_markdown_link_marker($textarea: JQuery<HTMLTextAreaElement>): boolean { export function cursor_at_markdown_link_marker($textarea: JQuery<HTMLTextAreaElement>): boolean {
const range = $textarea.range(); const range = $textarea.range();
const possible_markdown_link_markers = $textarea[0]!.value.slice(range.start - 2, range.start); const possible_markdown_link_markers = util
.the($textarea)
.value.slice(range.start - 2, range.start);
return possible_markdown_link_markers === "]("; return possible_markdown_link_markers === "](";
} }

View File

@ -363,11 +363,13 @@ export function restore_message(draft: LocalStorageDraft): ComposeArguments {
function draft_notify(): void { function draft_notify(): void {
// Display a tooltip to notify the user about the saved draft. // Display a tooltip to notify the user about the saved draft.
const instance = tippy.default(".top_left_drafts .unread_count", { const instance = util.the(
tippy.default(".top_left_drafts .unread_count", {
content: $t({defaultMessage: "Saved as draft"}), content: $t({defaultMessage: "Saved as draft"}),
arrow: true, arrow: true,
placement: "right", placement: "right",
})[0]!; }),
);
instance.show(); instance.show();
function remove_instance(): void { function remove_instance(): void {
instance.destroy(); instance.destroy();

View File

@ -592,7 +592,7 @@ function process_keypress(e: JQuery.KeyPressEvent | JQuery.KeyDownEvent): void {
export function emoji_select_tab($elt: JQuery): void { export function emoji_select_tab($elt: JQuery): void {
const scrolltop = $elt.scrollTop()!; const scrolltop = $elt.scrollTop()!;
const scrollheight = $elt[0]!.scrollHeight; const scrollheight = util.the($elt).scrollHeight;
const elt_height = $elt.height()!; const elt_height = $elt.height()!;
let currently_selected = ""; let currently_selected = "";
for (const o of section_head_offsets) { for (const o of section_head_offsets) {

View File

@ -6,6 +6,7 @@ import assert from "minimalistic-assert";
import {$t} from "./i18n"; import {$t} from "./i18n";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as util from "./util";
export let flatpickr_instance: flatpickr.Instance; export let flatpickr_instance: flatpickr.Instance;
@ -25,7 +26,7 @@ export function show_flatpickr(
): flatpickr.Instance { ): flatpickr.Instance {
const $flatpickr_input = $<HTMLInputElement>("<input>").attr("id", "#timestamp_flatpickr"); const $flatpickr_input = $<HTMLInputElement>("<input>").attr("id", "#timestamp_flatpickr");
flatpickr_instance = flatpickr($flatpickr_input[0]!, { flatpickr_instance = flatpickr(util.the($flatpickr_input), {
mode: "single", mode: "single",
enableTime: true, enableTime: true,
clickOpens: false, clickOpens: false,

View File

@ -1438,7 +1438,7 @@ function move_focus_to_visible_area(): void {
} }
const INBOX_ROW_HEIGHT = 30; const INBOX_ROW_HEIGHT = 30;
const position = $("#inbox-filters")[0]!.getBoundingClientRect(); const position = util.the($("#inbox-filters")).getBoundingClientRect();
const inbox_center_x = (position.left + position.right) / 2; const inbox_center_x = (position.left + position.right) / 2;
// We are aiming to get the first row if it is completely visible or the second row. // We are aiming to get the first row if it is completely visible or the second row.
const inbox_row_below_filters = position.bottom + INBOX_ROW_HEIGHT; const inbox_row_below_filters = position.bottom + INBOX_ROW_HEIGHT;

View File

@ -7,6 +7,7 @@ import render_input_pill from "../templates/input_pill.hbs";
import * as keydown_util from "./keydown_util"; import * as keydown_util from "./keydown_util";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as util from "./util";
// See https://zulip.readthedocs.io/en/latest/subsystems/input-pills.html // See https://zulip.readthedocs.io/en/latest/subsystems/input-pills.html
@ -194,9 +195,9 @@ export function create<ItemType extends {type: string}>(
if (idx !== -1) { if (idx !== -1) {
store.pills[idx]!.$element.remove(); store.pills[idx]!.$element.remove();
const pill = store.pills.splice(idx, 1); const pill = util.the(store.pills.splice(idx, 1));
if (store.onPillRemove !== undefined) { if (store.onPillRemove !== undefined) {
store.onPillRemove(pill[0]!, trigger); store.onPillRemove(pill, trigger);
} }
// This is needed to run the "change" event handler registered in // This is needed to run the "change" event handler registered in
@ -231,8 +232,7 @@ export function create<ItemType extends {type: string}>(
while (store.pills.length > 0) { while (store.pills.length > 0) {
this.removeLastPill(trigger, quiet); this.removeLastPill(trigger, quiet);
} }
this.clear(util.the(store.$input));
this.clear(store.$input[0]!);
}, },
insertManyPills(pills: string | string[]) { insertManyPills(pills: string | string[]) {
@ -253,7 +253,7 @@ export function create<ItemType extends {type: string}>(
// when using the `text` insertion feature with jQuery the caret is // when using the `text` insertion feature with jQuery the caret is
// placed at the beginning of the input field, so this moves it to // placed at the beginning of the input field, so this moves it to
// the end. // the end.
ui_util.place_caret_at_end(store.$input[0]!); ui_util.place_caret_at_end(util.the(store.$input));
// this sends a flag if the operation wasn't completely successful, // this sends a flag if the operation wasn't completely successful,
// which in this case is defined as some of the pills not autofilling // which in this case is defined as some of the pills not autofilling
@ -341,7 +341,7 @@ export function create<ItemType extends {type: string}>(
// if the pill is successful, it will create the pill and clear // if the pill is successful, it will create the pill and clear
// the input. // the input.
if (funcs.appendPill(store.$input.text().trim())) { if (funcs.appendPill(store.$input.text().trim())) {
funcs.clear(store.$input[0]!); funcs.clear(util.the(store.$input));
} }
e.preventDefault(); e.preventDefault();
@ -370,7 +370,7 @@ export function create<ItemType extends {type: string}>(
break; break;
case "Backspace": { case "Backspace": {
const $next = $pill.next(); const $next = $pill.next();
funcs.removePill($pill[0]!, "backspace"); funcs.removePill(util.the($pill), "backspace");
$next.trigger("focus"); $next.trigger("focus");
// the "Backspace" key in Firefox will go back a page if you do // the "Backspace" key in Firefox will go back a page if you do
// not prevent it. // not prevent it.
@ -416,8 +416,8 @@ export function create<ItemType extends {type: string}>(
store.$input.trigger("change"); store.$input.trigger("change");
} else { } else {
e.stopPropagation(); e.stopPropagation();
const $pill = $(this).closest(".pill"); const pill = util.the($(this).closest(".pill"));
funcs.removePill($pill[0]!, "close"); funcs.removePill(pill, "close");
} }
// Since removing a pill moves the $input, typeahead needs to refresh // Since removing a pill moves the $input, typeahead needs to refresh
// to appear at the correct position. // to appear at the correct position.

View File

@ -53,7 +53,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
}); });
clipboard.on("success", () => { clipboard.on("success", () => {
show_copied_confirmation( show_copied_confirmation(
$("#generate-integration-url-modal .dialog_submit_button")[0]!, util.the($("#generate-integration-url-modal .dialog_submit_button")),
); );
}); });

View File

@ -30,6 +30,7 @@ import * as stream_pill from "./stream_pill";
import * as timerender from "./timerender"; import * as timerender from "./timerender";
import type {HTMLSelectOneElement} from "./types"; import type {HTMLSelectOneElement} from "./types";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as util from "./util";
let custom_expiration_time_input = 10; let custom_expiration_time_input = 10;
let custom_expiration_time_unit = "days"; let custom_expiration_time_unit = "days";
@ -208,7 +209,7 @@ function submit_invitation_form(): void {
$("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Invite"})); $("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Invite"}));
$("#invite-user-modal .dialog_submit_button").prop("disabled", false); $("#invite-user-modal .dialog_submit_button").prop("disabled", false);
$("#invite-user-modal .dialog_exit_button").prop("disabled", false); $("#invite-user-modal .dialog_exit_button").prop("disabled", false);
$invite_status[0]!.scrollIntoView(); util.the($invite_status).scrollIntoView();
}, },
}); });
} }
@ -228,7 +229,7 @@ function generate_multiuse_invite(): void {
clipboard.on("success", () => { clipboard.on("success", () => {
const tippy_timeout_in_ms = 800; const tippy_timeout_in_ms = 800;
show_copied_confirmation( show_copied_confirmation(
$("#copy_generated_invite_link")[0]!, util.the($("#copy_generated_invite_link")),
() => { () => {
// Do nothing on hide // Do nothing on hide
}, },
@ -243,7 +244,7 @@ function generate_multiuse_invite(): void {
$("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Create link"})); $("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Create link"}));
$("#invite-user-modal .dialog_submit_button").prop("disabled", false); $("#invite-user-modal .dialog_submit_button").prop("disabled", false);
$("#invite-user-modal .dialog_exit_button").prop("disabled", false); $("#invite-user-modal .dialog_exit_button").prop("disabled", false);
$invite_status[0]!.scrollIntoView(); util.the($invite_status).scrollIntoView();
}, },
}); });
} }
@ -302,7 +303,7 @@ function set_streams_to_join_list_visibility(): void {
const realm_has_default_streams = stream_data.get_default_stream_ids().length !== 0; const realm_has_default_streams = stream_data.get_default_stream_ids().length !== 0;
const hide_streams_list = const hide_streams_list =
realm_has_default_streams && realm_has_default_streams &&
$<HTMLInputElement>("input#invite_select_default_streams")[0]!.checked; util.the($<HTMLInputElement>("input#invite_select_default_streams")).checked;
if (hide_streams_list) { if (hide_streams_list) {
$(".add_streams_container").hide(); $(".add_streams_container").hide();
} else { } else {

View File

@ -118,12 +118,12 @@ export class PanZoomControl {
// See https://github.com/anvaka/panzoom/issues/112 for upstream discussion. // See https://github.com/anvaka/panzoom/issues/112 for upstream discussion.
const {scale, x, y} = e.getTransform(); const {scale, x, y} = e.getTransform();
const image_width = $(".zoom-element > img")[0]!.clientWidth * scale; const image_width = util.the($(".zoom-element > img")).clientWidth * scale;
const image_height = $(".zoom-element > img")[0]!.clientHeight * scale; const image_height = util.the($(".zoom-element > img")).clientHeight * scale;
const zoom_element_width = $(".zoom-element")[0]!.clientWidth * scale; const zoom_element_width = util.the($(".zoom-element")).clientWidth * scale;
const zoom_element_height = $(".zoom-element")[0]!.clientHeight * scale; const zoom_element_height = util.the($(".zoom-element")).clientHeight * scale;
const max_translate_x = $(".image-preview")[0]!.clientWidth; const max_translate_x = util.the($(".image-preview")).clientWidth;
const max_translate_y = $(".image-preview")[0]!.clientHeight; const max_translate_y = util.the($(".image-preview")).clientHeight;
// When the image is dragged out of the image-preview container // When the image is dragged out of the image-preview container
// (max_translate) it will be "snapped" back so that the number // (max_translate) it will be "snapped" back so that the number
@ -394,7 +394,7 @@ export function build_open_media_function(
return function ($media: JQuery<HTMLMediaElement | HTMLImageElement>): void { return function ($media: JQuery<HTMLMediaElement | HTMLImageElement>): void {
// This is used both for clicking on media in the messagelist, as well as clicking on images // This is used both for clicking on media in the messagelist, as well as clicking on images
// in the media list under the lightbox when it is open. // in the media list under the lightbox when it is open.
const payload = parse_media_data($media[0]!); const payload = parse_media_data(util.the($media));
assert(payload !== undefined); assert(payload !== undefined);
if (payload.type.match("-video")) { if (payload.type.match("-video")) {
@ -597,7 +597,7 @@ export function initialize(): void {
// Bind the pan/zoom control the newly created element. // Bind the pan/zoom control the newly created element.
const pan_zoom_control = new PanZoomControl( const pan_zoom_control = new PanZoomControl(
$("#lightbox_overlay .image-preview > .zoom-element")[0]!, util.the($("#lightbox_overlay .image-preview > .zoom-element")),
); );
const reset_lightbox_state = function (): void { const reset_lightbox_state = function (): void {

View File

@ -233,7 +233,7 @@ export class MessageListData {
return true; return true;
} }
const recipient_id = Number.parseInt(recipients[0]!, 10); const recipient_id = Number.parseInt(util.the(recipients), 10);
return ( return (
!muted_users.is_user_muted(recipient_id) && !muted_users.is_user_muted(recipient_id) &&
!muted_users.is_user_muted(message.sender_id) !muted_users.is_user_muted(message.sender_id)

View File

@ -78,7 +78,7 @@ export function message_viewport_info(): MessageViewportInfo {
export function at_rendered_bottom(): boolean { export function at_rendered_bottom(): boolean {
const bottom = scrollTop() + height(); const bottom = scrollTop() + height();
// This also includes bottom whitespace. // This also includes bottom whitespace.
const full_height = $scroll_container[0]!.scrollHeight; const full_height = util.the($scroll_container).scrollHeight;
// We only know within a pixel or two if we're // We only know within a pixel or two if we're
// exactly at the bottom, due to browser quirkiness, // exactly at the bottom, due to browser quirkiness,
@ -94,7 +94,7 @@ export function bottom_rendered_message_visible(): boolean {
const $last_row = rows.last_visible(); const $last_row = rows.last_visible();
if ($last_row[0] !== undefined) { if ($last_row[0] !== undefined) {
const message_bottom = $last_row[0].getBoundingClientRect().bottom; const message_bottom = $last_row[0].getBoundingClientRect().bottom;
const bottom_of_feed = $("#compose")[0]!.getBoundingClientRect().top; const bottom_of_feed = util.the($("#compose")).getBoundingClientRect().top;
return bottom_of_feed > message_bottom; return bottom_of_feed > message_bottom;
} }
return false; return false;
@ -214,7 +214,7 @@ const top_of_feed = new util.CachedValue({
const bottom_of_feed = new util.CachedValue({ const bottom_of_feed = new util.CachedValue({
compute_value() { compute_value() {
return $("#compose")[0]!.getBoundingClientRect().top; return util.the($("#compose")).getBoundingClientRect().top;
}, },
}); });

View File

@ -1,6 +1,8 @@
import $ from "jquery"; import $ from "jquery";
import assert from "minimalistic-assert"; import assert from "minimalistic-assert";
import * as util from "./util";
export type Context = { export type Context = {
items_container_selector: string; items_container_selector: string;
items_list_selector: string; items_list_selector: string;
@ -73,11 +75,11 @@ export function modals_handle_events(event_key: string, context: Context): void
export function set_initial_element(element_id: string, context: Context): void { export function set_initial_element(element_id: string, context: Context): void {
if (element_id) { if (element_id) {
const $current_element = get_element_by_id(element_id, context); const current_element = util.the(get_element_by_id(element_id, context));
const focus_element = $current_element[0]!.children[0]; const focus_element = current_element.children[0];
assert(focus_element instanceof HTMLElement); assert(focus_element instanceof HTMLElement);
activate_element(focus_element, context); activate_element(focus_element, context);
$(`.${CSS.escape(context.items_list_selector)}`)[0]!.scrollTop = 0; util.the($(`.${CSS.escape(context.items_list_selector)}`)).scrollTop = 0;
} }
} }
@ -132,7 +134,7 @@ function initialize_focus(event_name: string, context: Context): void {
} }
const $element = get_element_by_id(id, context); const $element = get_element_by_id(id, context);
const focus_element = $element[0]!.children[0]; const focus_element = util.the($element).children[0];
assert(focus_element instanceof HTMLElement); assert(focus_element instanceof HTMLElement);
activate_element(focus_element, context); activate_element(focus_element, context);
} }
@ -152,28 +154,29 @@ function scroll_to_element($element: JQuery, context: Context): void {
const $box_item = $(`.${CSS.escape(context.box_item_selector)}`); const $box_item = $(`.${CSS.escape(context.box_item_selector)}`);
// If focused element is first, scroll to the top. // If focused element is first, scroll to the top.
if ($box_item.first()[0]!.parentElement === $element[0]) { if (util.the($box_item.first()).parentElement === $element[0]) {
$items_list[0]!.scrollTop = 0; util.the($items_list).scrollTop = 0;
} }
// If focused element is last, scroll to the bottom. // If focused element is last, scroll to the bottom.
if ($box_item.last()[0]!.parentElement === $element[0]) { if (util.the($box_item.last()).parentElement === $element[0]) {
$items_list[0]!.scrollTop = $items_list[0]!.scrollHeight - ($items_list.height() ?? 0); util.the($items_list).scrollTop =
util.the($items_list).scrollHeight - ($items_list.height() ?? 0);
} }
// If focused element is cut off from the top, scroll up halfway in modal. // If focused element is cut off from the top, scroll up halfway in modal.
if ($element.position().top < 55) { if ($element.position().top < 55) {
// 55 is the minimum distance from the top that will require extra scrolling. // 55 is the minimum distance from the top that will require extra scrolling.
$items_list[0]!.scrollTop -= $items_list[0]!.clientHeight / 2; util.the($items_list).scrollTop -= util.the($items_list).clientHeight / 2;
} }
// If focused element is cut off from the bottom, scroll down halfway in modal. // If focused element is cut off from the bottom, scroll down halfway in modal.
const dist_from_top = $element.position().top; const dist_from_top = $element.position().top;
const total_dist = dist_from_top + $element[0].clientHeight; const total_dist = dist_from_top + $element[0].clientHeight;
const dist_from_bottom = $items_container[0]!.clientHeight - total_dist; const dist_from_bottom = util.the($items_container).clientHeight - total_dist;
if (dist_from_bottom < -4) { if (dist_from_bottom < -4) {
// -4 is the min dist from the bottom that will require extra scrolling. // -4 is the min dist from the bottom that will require extra scrolling.
$items_list[0]!.scrollTop += $items_list[0]!.clientHeight / 2; util.the($items_list).scrollTop += util.the($items_list).clientHeight / 2;
} }
} }

View File

@ -9,6 +9,7 @@ import * as popover_menus from "./popover_menus";
import * as realm_playground from "./realm_playground"; import * as realm_playground from "./realm_playground";
import type {RealmPlayground} from "./realm_playground"; import type {RealmPlayground} from "./realm_playground";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as util from "./util";
type RealmPlaygroundWithURL = RealmPlayground & {playground_url: string}; type RealmPlaygroundWithURL = RealmPlayground & {playground_url: string};
@ -132,9 +133,9 @@ function register_click_handlers(): void {
const playground_url = url_template.expand({code: extracted_code}); const playground_url = url_template.expand({code: extracted_code});
playground_store.set(playground.id, {...playground, playground_url}); playground_store.set(playground.id, {...playground, playground_url});
} }
const popover_target = $view_in_playground_button.find( const popover_target = util.the(
".playground-links-popover-container", $view_in_playground_button.find(".playground-links-popover-container"),
)[0]!; );
toggle_playground_links_popover(popover_target, playground_store); toggle_playground_links_popover(popover_target, playground_store);
} }
}, },

View File

@ -3,6 +3,8 @@ import SortableJS from "sortablejs";
import render_poll_modal_option from "../templates/poll_modal_option.hbs"; import render_poll_modal_option from "../templates/poll_modal_option.hbs";
import * as util from "./util";
function create_option_row($last_option_row_input: JQuery): void { function create_option_row($last_option_row_input: JQuery): void {
const row_html = render_poll_modal_option(); const row_html = render_poll_modal_option();
const $row_container = $last_option_row_input.closest(".simplebar-content"); const $row_container = $last_option_row_input.closest(".simplebar-content");
@ -46,7 +48,7 @@ export function poll_options_setup(): void {
// setTimeout is needed to here to give time for simplebar to initialise // setTimeout is needed to here to give time for simplebar to initialise
setTimeout(() => { setTimeout(() => {
SortableJS.create($("#add-poll-form .poll-options-list .simplebar-content")[0]!, { SortableJS.create(util.the($("#add-poll-form .poll-options-list .simplebar-content")), {
onUpdate() { onUpdate() {
// Do nothing on drag; the order is only processed on submission. // Do nothing on drag; the order is only processed on submission.
}, },

View File

@ -194,7 +194,7 @@ export const default_popover_props: Partial<tippy.Props> = {
// $tippy_box[0].hasAttribute("data-reference-hidden"); is the real check // $tippy_box[0].hasAttribute("data-reference-hidden"); is the real check
// but linter wants us to write it like this. // but linter wants us to write it like this.
const is_reference_outside_window = Object.hasOwn( const is_reference_outside_window = Object.hasOwn(
$tippy_box[0]!.dataset, util.the($tippy_box).dataset,
"referenceHidden", "referenceHidden",
); );
@ -231,7 +231,7 @@ export const default_popover_props: Partial<tippy.Props> = {
return; return;
} }
const reference_rect = $reference[0]!.getBoundingClientRect(); const reference_rect = util.the($reference).getBoundingClientRect();
// This is the logic we want but since it is too expensive to run // This is the logic we want but since it is too expensive to run
// on every scroll, we run a cheaper version of this to just check if // on every scroll, we run a cheaper version of this to just check if
// compose, sticky header or navbar are not obscuring the reference // compose, sticky header or navbar are not obscuring the reference

View File

@ -5,6 +5,7 @@ import * as tippy from "tippy.js";
import copy_to_clipboard_svg from "../../templates/copy_to_clipboard_svg.hbs"; import copy_to_clipboard_svg from "../../templates/copy_to_clipboard_svg.hbs";
import * as common from "../common"; import * as common from "../common";
import * as util from "../util";
import {activate_correct_tab} from "./tabbed-instructions"; import {activate_correct_tab} from "./tabbed-instructions";
@ -37,7 +38,7 @@ function add_copy_to_clipboard_element($codehilite: JQuery): void {
$($codehilite).append($copy_button); $($codehilite).append($copy_button);
const clipboard = new ClipboardJS($copy_button[0]!, { const clipboard = new ClipboardJS(util.the($copy_button), {
text(copy_element) { text(copy_element) {
// trim to remove trailing whitespace introduced // trim to remove trailing whitespace introduced
// by additional elements inside <pre> // by additional elements inside <pre>
@ -46,14 +47,14 @@ function add_copy_to_clipboard_element($codehilite: JQuery): void {
}); });
// Show a tippy tooltip when the button is hovered // Show a tippy tooltip when the button is hovered
const tooltip_copy = tippy.default($copy_button[0]!, { const tooltip_copy = tippy.default(util.the($copy_button), {
content: "Copy code", content: "Copy code",
trigger: "mouseenter", trigger: "mouseenter",
placement: "top", placement: "top",
}); });
// Show a tippy tooltip when the code is copied // Show a tippy tooltip when the code is copied
const tooltip_copied = tippy.default($copy_button[0]!, { const tooltip_copied = tippy.default(util.the($copy_button), {
content: "Copied!", content: "Copied!",
trigger: "manual", trigger: "manual",
placement: "top", placement: "top",
@ -89,7 +90,7 @@ function render_tabbed_sections(): void {
}); });
} }
new SimpleBar($(".sidebar")[0]!, {tabIndex: -1}); new SimpleBar(util.the($(".sidebar")), {tabIndex: -1});
// Scroll to anchor link when clicked. Note that landing-page.js has a // Scroll to anchor link when clicked. Note that landing-page.js has a
// similar function; this file and landing-page.js are never included // similar function; this file and landing-page.js are never included

View File

@ -3,6 +3,7 @@ import assert from "minimalistic-assert";
import {z} from "zod"; import {z} from "zod";
import * as channel from "../channel"; import * as channel from "../channel";
import * as util from "../util";
// Main JavaScript file for the integrations development panel at // Main JavaScript file for the integrations development panel at
// /devtools/integrations. // /devtools/integrations.
@ -73,13 +74,13 @@ const clear_handlers: ClearHandlers = {
$("#fixture_name").empty(); $("#fixture_name").empty();
}, },
fixture_body() { fixture_body() {
$<HTMLTextAreaElement>("textarea#fixture_body")[0]!.value = ""; util.the($<HTMLTextAreaElement>("textarea#fixture_body")).value = "";
}, },
custom_http_headers() { custom_http_headers() {
$<HTMLTextAreaElement>("textarea#custom_http_headers")[0]!.value = "{}"; util.the($<HTMLTextAreaElement>("textarea#custom_http_headers")).value = "{}";
}, },
results() { results() {
$<HTMLTextAreaElement>("textarea#idp-results")[0]!.value = ""; util.the($<HTMLTextAreaElement>("textarea#idp-results")).value = "";
}, },
}; };
@ -153,7 +154,7 @@ function set_results(response: ServerResponse): void {
} }
data += "\nResponse: " + response.message + "\n\n"; data += "\nResponse: " + response.message + "\n\n";
} }
$<HTMLTextAreaElement>("textarea#idp-results")[0]!.value = data; util.the($<HTMLTextAreaElement>("textarea#idp-results")).value = data;
} }
function load_fixture_body(fixture_name: string): void { function load_fixture_body(fixture_name: string): void {
@ -173,8 +174,8 @@ function load_fixture_body(fixture_name: string): void {
fixture_body = JSON.stringify(fixture_body, null, 4); fixture_body = JSON.stringify(fixture_body, null, 4);
} }
assert(typeof fixture_body === "string"); assert(typeof fixture_body === "string");
$<HTMLTextAreaElement>("textarea#fixture_body")[0]!.value = fixture_body; util.the($<HTMLTextAreaElement>("textarea#fixture_body")).value = fixture_body;
$<HTMLTextAreaElement>("textarea#custom_http_headers")[0]!.value = JSON.stringify( util.the($<HTMLTextAreaElement>("textarea#custom_http_headers")).value = JSON.stringify(
headers, headers,
null, null,
4, 4,
@ -187,9 +188,9 @@ function load_fixture_options(integration_name: string): void {
/* Using the integration name and loaded_fixtures object to set /* Using the integration name and loaded_fixtures object to set
the fixture options for the fixture_names dropdown and also set the fixture options for the fixture_names dropdown and also set
the fixture body to the first fixture by default. */ the fixture body to the first fixture by default. */
const fixtures_options_dropdown = $<HTMLSelectOneElement>( const fixtures_options_dropdown = util.the(
"select:not([multiple])#fixture_name", $<HTMLSelectOneElement>("select:not([multiple])#fixture_name"),
)[0]!; );
const fixtures = loaded_fixtures.get(integration_name); const fixtures = loaded_fixtures.get(integration_name);
assert(fixtures !== undefined); assert(fixtures !== undefined);
const fixtures_names = Object.keys(fixtures).sort(); const fixtures_names = Object.keys(fixtures).sort();
@ -396,10 +397,12 @@ $(() => {
"results", "results",
]); ]);
$<HTMLInputElement>("input#stream_name")[0]!.value = "Denmark"; util.the($<HTMLInputElement>("input#stream_name")).value = "Denmark";
$<HTMLInputElement>("input#topic_name")[0]!.value = "Integrations testing"; util.the($<HTMLInputElement>("input#topic_name")).value = "Integrations testing";
const potential_default_bot = $<HTMLSelectOneElement>("select:not([multiple])#bot_name")[0]![1]; const potential_default_bot = util.the(
$<HTMLSelectOneElement>("select:not([multiple])#bot_name"),
)[1];
assert(potential_default_bot instanceof HTMLOptionElement); assert(potential_default_bot instanceof HTMLOptionElement);
if (potential_default_bot !== undefined) { if (potential_default_bot !== undefined) {
potential_default_bot.selected = true; potential_default_bot.selected = true;

View File

@ -3,6 +3,7 @@ import assert from "minimalistic-assert";
import {z} from "zod"; import {z} from "zod";
import {page_params} from "../base_page_params"; import {page_params} from "../base_page_params";
import * as util from "../util";
import type {UserOS} from "./tabbed-instructions"; import type {UserOS} from "./tabbed-instructions";
import {detect_user_os} from "./tabbed-instructions"; import {detect_user_os} from "./tabbed-instructions";
@ -271,7 +272,7 @@ $(document).on("click", ".comparison-tab", function (this: HTMLElement) {
const tab_label = z const tab_label = z
.enum(["tab-cloud", "tab-hosted", "tab-all"]) .enum(["tab-cloud", "tab-hosted", "tab-all"])
.parse($(this)[0]!.dataset.label); .parse(util.the($(this)).dataset.label);
const plans_columns_count = plans_columns_counts[tab_label]; const plans_columns_count = plans_columns_counts[tab_label];
const visible_plans_id = `showing-${tab_label}`; const visible_plans_id = `showing-${tab_label}`;

View File

@ -2,6 +2,7 @@ import $ from "jquery";
import * as blueslip from "../blueslip"; import * as blueslip from "../blueslip";
import * as common from "../common"; import * as common from "../common";
import * as util from "../util";
export type UserOS = "android" | "ios" | "mac" | "windows" | "linux"; export type UserOS = "android" | "ios" | "mac" | "windows" | "linux";
@ -58,7 +59,7 @@ export function activate_correct_tab($tabbed_section: JQuery): void {
const $active_list_items = $li.filter(".active"); const $active_list_items = $li.filter(".active");
if (!$active_list_items.length) { if (!$active_list_items.length) {
$li.first().addClass("active"); $li.first().addClass("active");
const tab_key = $li.first()[0]!.dataset.tabKey; const tab_key = util.the($li.first()).dataset.tabKey;
if (tab_key) { if (tab_key) {
$blocks.filter("[data-tab-key=" + tab_key + "]").addClass("active"); $blocks.filter("[data-tab-key=" + tab_key + "]").addClass("active");
} else { } else {

View File

@ -13,6 +13,7 @@ import * as message_store from "./message_store";
import * as modals from "./modals"; import * as modals from "./modals";
import * as people from "./people"; import * as people from "./people";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as util from "./util";
const read_receipts_api_response_schema = z.object({ const read_receipts_api_response_schema = z.object({
user_ids: z.array(z.number()), user_ids: z.array(z.number()),
@ -89,7 +90,7 @@ export function show_user_list(message_id: number): void {
$("#read_receipts_modal .read_receipts_list").html( $("#read_receipts_modal .read_receipts_list").html(
render_read_receipts(context), render_read_receipts(context),
); );
new SimpleBar($("#read_receipts_modal .modal__content")[0]!, { new SimpleBar(util.the($("#read_receipts_modal .modal__content")), {
tabIndex: -1, tabIndex: -1,
}); });
} }

View File

@ -44,6 +44,7 @@ import * as unread from "./unread";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as user_status from "./user_status"; import * as user_status from "./user_status";
import * as user_topics from "./user_topics"; import * as user_topics from "./user_topics";
import * as util from "./util";
import * as views_util from "./views_util"; import * as views_util from "./views_util";
type Row = { type Row = {
@ -327,7 +328,7 @@ function set_table_focus(row: number, col: number, using_keyboard = false): bool
$current_focus_elem = "table"; $current_focus_elem = "table";
if (using_keyboard) { if (using_keyboard) {
const scroll_element = $("html")[0]!; const scroll_element = util.the($("html"));
const half_height_of_visible_area = scroll_element.offsetHeight / 2; const half_height_of_visible_area = scroll_element.offsetHeight / 2;
const topic_offset = topic_offset_to_visible_area($topic_row); const topic_offset = topic_offset_to_visible_area($topic_row);
@ -1148,10 +1149,10 @@ function topic_offset_to_visible_area($topic_row: JQuery): string | undefined {
} }
// Rows are only visible below thead bottom and above compose top. // Rows are only visible below thead bottom and above compose top.
const thead_bottom = $("#recent-view-table-headers")[0]!.getBoundingClientRect().bottom; const thead_bottom = util.the($("#recent-view-table-headers")).getBoundingClientRect().bottom;
const compose_top = window.innerHeight - $("#compose").outerHeight(true)!; const compose_top = window.innerHeight - $("#compose").outerHeight(true)!;
const topic_props = $topic_row[0]!.getBoundingClientRect(); const topic_props = util.the($topic_row).getBoundingClientRect();
// Topic is above the visible scroll region. // Topic is above the visible scroll region.
if (topic_props.top < thead_bottom) { if (topic_props.top < thead_bottom) {
@ -1185,7 +1186,7 @@ function recenter_focus_if_off_screen(): void {
if (topic_offset !== "visible") { if (topic_offset !== "visible") {
// Get the element at the center of the table. // Get the element at the center of the table.
const thead_props = $("#recent-view-table-headers")[0]!.getBoundingClientRect(); const thead_props = util.the($("#recent-view-table-headers")).getBoundingClientRect();
const compose_top = window.innerHeight - $("#compose").outerHeight(true)!; const compose_top = window.innerHeight - $("#compose").outerHeight(true)!;
const topic_center_x = (thead_props.left + thead_props.right) / 2; const topic_center_x = (thead_props.left + thead_props.right) / 2;
const topic_center_y = (thead_props.bottom + compose_top) / 2; const topic_center_y = (thead_props.bottom + compose_top) / 2;
@ -1477,7 +1478,7 @@ function down_arrow_navigation(): void {
} }
function get_page_up_down_delta(): number { function get_page_up_down_delta(): number {
const thead_bottom = $("#recent-view-table-headers")[0]!.getBoundingClientRect().bottom; const thead_bottom = util.the($("#recent-view-table-headers")).getBoundingClientRect().bottom;
const compose_box_top = window.innerHeight - $("#compose").outerHeight(true)!; const compose_box_top = window.innerHeight - $("#compose").outerHeight(true)!;
// One usually wants PageDown to move what had been the bottom row // One usually wants PageDown to move what had been the bottom row
// to now be at the top, so one can be confident one will see // to now be at the top, so one can be confident one will see

View File

@ -312,7 +312,7 @@ export const update_elements = ($content: JQuery): void => {
$view_in_playground_button.attr("aria-label", title); $view_in_playground_button.attr("aria-label", title);
} }
const $copy_button = $buttonContainer.find(".copy_codeblock"); const $copy_button = $buttonContainer.find(".copy_codeblock");
const clipboard = new ClipboardJS($copy_button[0]!, { const clipboard = new ClipboardJS(util.the($copy_button), {
text(copy_element) { text(copy_element) {
const $code = $(copy_element).parent().siblings("code"); const $code = $(copy_element).parent().siblings("code");
return $code.text(); return $code.text();
@ -320,7 +320,7 @@ export const update_elements = ($content: JQuery): void => {
}); });
clipboard.on("success", () => { clipboard.on("success", () => {
show_copied_confirmation($copy_button[0]!); show_copied_confirmation(util.the($copy_button));
}); });
$codehilite.addClass("zulip-code-block"); $codehilite.addClass("zulip-code-block");
}); });

View File

@ -1,11 +1,13 @@
import $ from "jquery"; import $ from "jquery";
import SimpleBar from "simplebar"; import SimpleBar from "simplebar";
import * as util from "./util";
// This type is helpful for testing, where we may have a dummy object instead of an actual jquery object. // This type is helpful for testing, where we may have a dummy object instead of an actual jquery object.
type JQueryOrZJQuery = {__zjquery?: true} & JQuery; type JQueryOrZJQuery = {__zjquery?: true} & JQuery;
export function get_content_element($element: JQuery): JQuery { export function get_content_element($element: JQuery): JQuery {
const element = $element.expectOne()[0]!; const element = util.the($element);
const sb = SimpleBar.instances.get(element); const sb = SimpleBar.instances.get(element);
if (sb) { if (sb) {
return $(sb.getContentElement()!); return $(sb.getContentElement()!);
@ -19,7 +21,7 @@ export function get_scroll_element($element: JQueryOrZJQuery): JQuery {
return $element; return $element;
} }
const element = $element.expectOne()[0]!; const element = util.the($element);
const sb = SimpleBar.instances.get(element); const sb = SimpleBar.instances.get(element);
if (sb) { if (sb) {
return $(sb.getScrollElement()!); return $(sb.getScrollElement()!);
@ -32,7 +34,7 @@ export function get_scroll_element($element: JQueryOrZJQuery): JQuery {
} }
export function reset_scrollbar($element: JQuery): void { export function reset_scrollbar($element: JQuery): void {
const element = $element.expectOne()[0]!; const element = util.the($element);
const sb = SimpleBar.instances.get(element); const sb = SimpleBar.instances.get(element);
if (sb) { if (sb) {
sb.getScrollElement()!.scrollTop = 0; sb.getScrollElement()!.scrollTop = 0;

View File

@ -12,6 +12,7 @@ import type {User} from "./people";
import type {NarrowTerm} from "./state_data"; import type {NarrowTerm} from "./state_data";
import * as user_status from "./user_status"; import * as user_status from "./user_status";
import type {UserStatusEmojiInfo} from "./user_status"; import type {UserStatusEmojiInfo} from "./user_status";
import * as util from "./util";
export type SearchUserPill = { export type SearchUserPill = {
type: "search_user"; type: "search_user";
@ -40,9 +41,7 @@ type SearchPill =
export type SearchPillWidget = InputPillContainer<SearchPill>; export type SearchPillWidget = InputPillContainer<SearchPill>;
export function create_item_from_search_string(search_string: string): SearchPill | undefined { export function create_item_from_search_string(search_string: string): SearchPill | undefined {
const search_terms = Filter.parse(search_string); const search_term = util.the(Filter.parse(search_string));
assert(search_terms.length === 1);
const search_term = search_terms[0]!;
if (!Filter.is_valid_search_term(search_term)) { if (!Filter.is_valid_search_term(search_term)) {
// This will cause pill validation to fail and trigger a shake animation. // This will cause pill validation to fail and trigger a shake animation.
return undefined; return undefined;
@ -73,7 +72,7 @@ function on_pill_exit(
if (!$user_pill_container.length) { if (!$user_pill_container.length) {
// This is just a regular search pill, so we don't need to do fancy logic. // This is just a regular search pill, so we don't need to do fancy logic.
const $clicked_pill = $(clicked_element).closest(".pill"); const $clicked_pill = $(clicked_element).closest(".pill");
remove_pill($clicked_pill[0]!); remove_pill(util.the($clicked_pill));
return; return;
} }
// The user-pill-container container class is used exclusively for // The user-pill-container container class is used exclusively for
@ -92,8 +91,8 @@ function on_pill_exit(
// If there's only one user in this pill, delete the whole pill. // If there's only one user in this pill, delete the whole pill.
if (user_container_pill.users.length === 1) { if (user_container_pill.users.length === 1) {
assert(user_container_pill.users[0]!.user_id === user_id); assert(util.the(user_container_pill.users).user_id === user_id);
remove_pill($user_pill_container[0]!); remove_pill(util.the($user_pill_container));
return; return;
} }

View File

@ -390,7 +390,7 @@ function read_select_field_data_from_form(
} }
} }
$profile_field_form.find("div.choice-row").each(function (this: HTMLElement) { $profile_field_form.find("div.choice-row").each(function (this: HTMLElement) {
const text = $(this).find("input")[0]!.value; const text = util.the($(this).find("input")).value;
if (text) { if (text) {
let value = old_option_value_map.get(text); let value = old_option_value_map.get(text);
if (value !== undefined) { if (value !== undefined) {
@ -729,7 +729,7 @@ export function get_auth_method_list_data(): Record<string, boolean> {
for (const method_row of $auth_method_rows) { for (const method_row of $auth_method_rows) {
const method = $(method_row).attr("data-method"); const method = $(method_row).attr("data-method");
assert(method !== undefined); assert(method !== undefined);
new_auth_methods[method] = $(method_row).find<HTMLInputElement>("input")[0]!.checked; new_auth_methods[method] = util.the($(method_row).find<HTMLInputElement>("input")).checked;
} }
return new_auth_methods; return new_auth_methods;
@ -1305,7 +1305,7 @@ function enable_or_disable_save_button($subsection_elem: JQuery): void {
const $button_wrapper = $subsection_elem.find<tippy.PopperElement>( const $button_wrapper = $subsection_elem.find<tippy.PopperElement>(
".subsection-changes-save", ".subsection-changes-save",
); );
const tippy_instance = $button_wrapper[0]!._tippy; const tippy_instance = util.the($button_wrapper)._tippy;
if (disable_save_btn) { if (disable_save_btn) {
// avoid duplication of tippy // avoid duplication of tippy
if (!tippy_instance) { if (!tippy_instance) {
@ -1343,5 +1343,5 @@ export function initialize_disable_btn_hint_popover(
if (hint_text !== undefined) { if (hint_text !== undefined) {
tippy_opts.content = hint_text; tippy_opts.content = hint_text;
} }
tippy.default($btn_wrapper[0]!, tippy_opts); tippy.default(util.the($btn_wrapper), tippy_opts);
} }

View File

@ -265,7 +265,7 @@ function show_modal(): void {
} }
const formData = new FormData(); const formData = new FormData();
const files = $<HTMLInputElement>("input#emoji_file_input")[0]!.files; const files = util.the($<HTMLInputElement>("input#emoji_file_input")).files;
assert(files !== null); assert(files !== null);
for (const [i, file] of [...files].entries()) { for (const [i, file] of [...files].entries()) {
formData.append("file-" + i, file); formData.append("file-" + i, file);

View File

@ -17,6 +17,7 @@ import * as settings_ui from "./settings_ui";
import {current_user, realm} from "./state_data"; import {current_user, realm} from "./state_data";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as util from "./util";
type RealmLinkifiers = typeof realm.realm_linkifiers; type RealmLinkifiers = typeof realm.realm_linkifiers;
@ -108,7 +109,7 @@ function open_linkifier_edit_form(linkifier_id: number): void {
submit_linkifier_form(dialog_widget_id); submit_linkifier_form(dialog_widget_id);
}, },
on_shown() { on_shown() {
ui_util.place_caret_at_end($("#edit-linkifier-pattern")[0]!); ui_util.place_caret_at_end(util.the($("#edit-linkifier-pattern")));
}, },
}); });
} }
@ -197,7 +198,7 @@ export function populate_linkifiers(linkifiers_data: RealmLinkifiers): void {
}); });
if (current_user.is_admin) { if (current_user.is_admin) {
new SortableJS($linkifiers_table[0]!, { new SortableJS(util.the($linkifiers_table), {
onUpdate: update_linkifiers_order, onUpdate: update_linkifiers_order,
handle: ".move-handle", handle: ".move-handle",
filter: "input", filter: "input",

View File

@ -23,6 +23,7 @@ import {current_user, realm} from "./state_data";
import type {HTMLSelectOneElement} from "./types"; import type {HTMLSelectOneElement} from "./types";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import {place_caret_at_end} from "./ui_util"; import {place_caret_at_end} from "./ui_util";
import * as util from "./util";
type FieldChoice = { type FieldChoice = {
value: string; value: string;
@ -424,7 +425,7 @@ function set_up_select_field_edit_form(
// Add blank choice at last // Add blank choice at last
create_choice_row($choice_list); create_choice_row($choice_list);
SortableJS.create($choice_list[0]!, { SortableJS.create(util.the($choice_list), {
onUpdate() { onUpdate() {
// Do nothing on drag. We process the order on submission // Do nothing on drag. We process the order on submission
}, },
@ -577,7 +578,7 @@ function open_edit_form_modal(this: HTMLElement): void {
post_render: set_initial_values_of_profile_field, post_render: set_initial_values_of_profile_field,
loading_spinner: true, loading_spinner: true,
on_shown() { on_shown() {
place_caret_at_end($("#id-custom-profile-field-name")[0]!); place_caret_at_end(util.the($("#id-custom-profile-field-name")));
}, },
}); });
} }
@ -709,7 +710,7 @@ export function do_populate_profile_fields(profile_fields_data: CustomProfileFie
display_in_profile_summary_fields_limit_reached = display_in_profile_summary_fields_count >= 2; display_in_profile_summary_fields_limit_reached = display_in_profile_summary_fields_count >= 2;
if (current_user.is_admin) { if (current_user.is_admin) {
const field_list = $("#admin_profile_fields_table")[0]!; const field_list = util.the($("#admin_profile_fields_table"));
SortableJS.create(field_list, { SortableJS.create(field_list, {
onUpdate: update_field_order, onUpdate: update_field_order,
filter: "input", filter: "input",
@ -727,7 +728,7 @@ function set_up_select_field(): void {
create_choice_row($("#profile_field_choices")); create_choice_row($("#profile_field_choices"));
if (current_user.is_admin) { if (current_user.is_admin) {
const choice_list = $("#profile_field_choices")[0]!; const choice_list = util.the($("#profile_field_choices"));
SortableJS.create(choice_list, { SortableJS.create(choice_list, {
onUpdate() { onUpdate() {
// Do nothing on drag. We process the order on submission // Do nothing on drag. We process the order on submission

View File

@ -8,6 +8,7 @@ import * as dialog_widget from "./dialog_widget";
import {$t_html} from "./i18n"; import {$t_html} from "./i18n";
import {realm} from "./state_data"; import {realm} from "./state_data";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as util from "./util";
type RealmDomain = { type RealmDomain = {
domain: string; domain: string;
@ -112,9 +113,9 @@ export function setup_realm_domains_modal_handlers(): void {
const $realm_domains_info = $(".realm_domains_info"); const $realm_domains_info = $(".realm_domains_info");
const $widget = $("#add-realm-domain-widget"); const $widget = $("#add-realm-domain-widget");
const domain = $widget.find(".new-realm-domain").val(); const domain = $widget.find(".new-realm-domain").val();
const allow_subdomains = $widget.find<HTMLInputElement>( const allow_subdomains = util.the(
"input.new-realm-domain-allow-subdomains", $widget.find<HTMLInputElement>("input.new-realm-domain-allow-subdomains"),
)[0]!.checked; ).checked;
const data = { const data = {
domain, domain,
allow_subdomains: JSON.stringify(allow_subdomains), allow_subdomains: JSON.stringify(allow_subdomains),

View File

@ -2,6 +2,7 @@ import $ from "jquery";
import * as blueslip from "./blueslip"; import * as blueslip from "./blueslip";
import * as loading from "./loading"; import * as loading from "./loading";
import * as util from "./util";
export let page_load_time: number | undefined; export let page_load_time: number | undefined;
@ -16,7 +17,7 @@ $(() => {
}); });
$.fn.get_offset_to_window = function () { $.fn.get_offset_to_window = function () {
return this[0]!.getBoundingClientRect(); return util.the(this).getBoundingClientRect();
}; };
$.fn.expectOne = function () { $.fn.expectOne = function () {

View File

@ -1,5 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import * as util from "./util";
function collapse_spoiler($spoiler: JQuery): void { function collapse_spoiler($spoiler: JQuery): void {
const spoiler_height = $spoiler.height() ?? 0; const spoiler_height = $spoiler.height() ?? 0;
@ -21,7 +23,7 @@ function expand_spoiler($spoiler: JQuery): void {
// of the content). CSS animations do not work with properties set to // of the content). CSS animations do not work with properties set to
// `auto`, so we get the actual height of the content here and temporarily // `auto`, so we get the actual height of the content here and temporarily
// put it explicitly on the element styling to allow the transition to work. // put it explicitly on the element styling to allow the transition to work.
const spoiler_height = $spoiler[0]!.scrollHeight; const spoiler_height = util.the($spoiler).scrollHeight;
$spoiler.height(`${spoiler_height}px`); $spoiler.height(`${spoiler_height}px`);
// The `spoiler-content-open` class has CSS animations defined on it which // The `spoiler-content-open` class has CSS animations defined on it which
// will trigger on the frame after this class change. // will trigger on the frame after this class change.

View File

@ -14,6 +14,7 @@ import * as settings_config from "./settings_config";
import * as stream_data from "./stream_data"; import * as stream_data from "./stream_data";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
import * as util from "./util";
// For tooltips without data-tippy-content, we use the HTML content of // For tooltips without data-tippy-content, we use the HTML content of
// a <template> whose id is given by data-tooltip-template-id. // a <template> whose id is given by data-tooltip-template-id.
@ -353,7 +354,7 @@ export function initialize(): void {
const second_line = $t({defaultMessage: "File name: {filename}"}, {filename}); const second_line = $t({defaultMessage: "File name: {filename}"}, {filename});
$markup.append($("<br>"), $("<span>").text(second_line)); $markup.append($("<br>"), $("<span>").text(second_line));
} }
instance.setContent($markup[0]!); instance.setContent(util.the($markup));
return undefined; return undefined;
}, },
onHidden(instance) { onHidden(instance) {

View File

@ -1,4 +1,5 @@
import {$t} from "./i18n"; import {$t} from "./i18n";
import * as util from "./util";
export type UploadWidget = { export type UploadWidget = {
clear: () => void; clear: () => void;
@ -83,7 +84,7 @@ export function build_widget(
if (files === null || files === undefined || files.length === 0) { if (files === null || files === undefined || files.length === 0) {
return false; return false;
} }
get_file_input()[0]!.files = files; util.the(get_file_input()).files = files;
e.preventDefault(); e.preventDefault();
return false; return false;
}); });
@ -171,7 +172,7 @@ export function build_direct_upload_widget(
if (files === null || files === undefined || files.length === 0) { if (files === null || files === undefined || files.length === 0) {
return false; return false;
} }
get_file_input()[0]!.files = files; util.the(get_file_input()).files = files;
e.preventDefault(); e.preventDefault();
return false; return false;
}); });

View File

@ -745,9 +745,9 @@ export function show_edit_bot_info_modal(user_id: number, $container: JQuery): v
formData.append("config_data", JSON.stringify(config_data)); formData.append("config_data", JSON.stringify(config_data));
} }
const files = $("#bot-edit-form").find<HTMLInputElement>( const files = util.the(
"input.edit_bot_avatar_file_input", $("#bot-edit-form").find<HTMLInputElement>("input.edit_bot_avatar_file_input"),
)[0]!.files; ).files;
assert(files !== null); assert(files !== null);
for (const [i, file] of [...files].entries()) { for (const [i, file] of [...files].entries()) {
formData.append("file-" + i, file); formData.append("file-" + i, file);

View File

@ -47,6 +47,7 @@ function make_textbox(s) {
const $widget = {}; const $widget = {};
$widget.s = s; $widget.s = s;
$widget.length = 1;
$widget[0] = "textarea"; $widget[0] = "textarea";
$widget.focused = false; $widget.focused = false;

View File

@ -89,6 +89,7 @@ function set_up() {
const $pill_input = $.create("pill_input"); const $pill_input = $.create("pill_input");
$pill_input[0] = {}; $pill_input[0] = {};
$pill_input.length = 1;
$pill_input.before = noop; $pill_input.before = noop;
const create_item_from_text = (text) => items[text]; const create_item_from_text = (text) => items[text];
@ -115,6 +116,7 @@ run_test("copy from pill", ({mock_template}) => {
mock_template("input_pill.hbs", true, (data, html) => { mock_template("input_pill.hbs", true, (data, html) => {
assert.ok(["BLUE", "RED"].includes(data.display_value)); assert.ok(["BLUE", "RED"].includes(data.display_value));
$(html)[0] = `<pill-stub ${data.display_value}>`; $(html)[0] = `<pill-stub ${data.display_value}>`;
$(html).length = 1;
return html; return html;
}); });
@ -350,6 +352,7 @@ run_test("insert_remove", ({mock_template}) => {
mock_template("input_pill.hbs", true, (data, html) => { mock_template("input_pill.hbs", true, (data, html) => {
assert.equal(typeof data.display_value, "string"); assert.equal(typeof data.display_value, "string");
assert.ok(html.startsWith, "<div class='pill'"); assert.ok(html.startsWith, "<div class='pill'");
$(html).length = 1;
$(html)[0] = `<pill-stub ${data.display_value}>`; $(html)[0] = `<pill-stub ${data.display_value}>`;
return html; return html;
}); });
@ -437,6 +440,7 @@ run_test("insert_remove", ({mock_template}) => {
const $focus_pill_stub = { const $focus_pill_stub = {
next: () => $next_pill_stub, next: () => $next_pill_stub,
[0]: "<pill-stub BLUE>", [0]: "<pill-stub BLUE>",
length: 1,
}; };
$container.set_find_results(".pill:focus", $focus_pill_stub); $container.set_find_results(".pill:focus", $focus_pill_stub);
@ -456,6 +460,7 @@ run_test("exit button on pill", ({mock_template}) => {
assert.equal(typeof data.display_value, "string"); assert.equal(typeof data.display_value, "string");
assert.ok(html.startsWith, "<div class='pill'"); assert.ok(html.startsWith, "<div class='pill'");
$(html)[0] = `<pill-stub ${data.display_value}>`; $(html)[0] = `<pill-stub ${data.display_value}>`;
$(html).length = 1;
return html; return html;
}); });
$(".narrow_to_compose_recipients").toggleClass = noop; $(".narrow_to_compose_recipients").toggleClass = noop;
@ -477,6 +482,7 @@ run_test("exit button on pill", ({mock_template}) => {
const $curr_pill_stub = { const $curr_pill_stub = {
[0]: "<pill-stub BLUE>", [0]: "<pill-stub BLUE>",
length: 1,
}; };
const exit_button_stub = { const exit_button_stub = {
@ -559,6 +565,7 @@ run_test("appendValue/clear", ({mock_template}) => {
$pill_input.before = noop; $pill_input.before = noop;
$pill_input[0] = {}; $pill_input[0] = {};
$pill_input.length = 1;
const widget = input_pill.create(config); const widget = input_pill.create(config);

View File

@ -23,6 +23,7 @@ function FakeElement(selector, opts) {
const event_store = make_event_store(selector); const event_store = make_event_store(selector);
const $self = { const $self = {
length: 1,
[0]: {textContent: text}, [0]: {textContent: text},
*[Symbol.iterator]() { *[Symbol.iterator]() {
// eslint-disable-next-line unicorn/no-for-loop // eslint-disable-next-line unicorn/no-for-loop