mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	overlays: Extract modal methods in separate file.
This commit is contained in:
		@@ -144,6 +144,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "web/src/message_view_header.js",
 | 
			
		||||
        "web/src/message_viewport.js",
 | 
			
		||||
        "web/src/messages_overlay_ui.ts",
 | 
			
		||||
        "web/src/modals.ts",
 | 
			
		||||
        "web/src/muted_users_ui.js",
 | 
			
		||||
        "web/src/narrow.js",
 | 
			
		||||
        "web/src/narrow_history.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import render_dialog_widget from "../templates/dialog_widget.hbs";
 | 
			
		||||
import type {AjaxRequestHandler} from "./channel";
 | 
			
		||||
import {$t_html} from "./i18n";
 | 
			
		||||
import * as loading from "./loading";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as ui_report from "./ui_report";
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -22,7 +22,7 @@ import * as ui_report from "./ui_report";
 | 
			
		||||
 *         to avoid interference from other elements.
 | 
			
		||||
 *
 | 
			
		||||
 *      3) For settings, we have a click handler in settings.js
 | 
			
		||||
 *         that will close the dialog via overlays.close_active_modal.
 | 
			
		||||
 *         that will close the dialog via modals.close_active_modal.
 | 
			
		||||
 *
 | 
			
		||||
 *      4) We assume that since this is a modal, you will
 | 
			
		||||
 *         only ever have one confirm dialog active at any
 | 
			
		||||
@@ -99,7 +99,7 @@ export function show_dialog_spinner(): void {
 | 
			
		||||
 | 
			
		||||
// Supports a callback to be called once the modal finishes closing.
 | 
			
		||||
export function close_modal(on_hidden_callback?: () => void): void {
 | 
			
		||||
    overlays.close_modal("dialog_widget_modal", {on_hidden: on_hidden_callback});
 | 
			
		||||
    modals.close_modal("dialog_widget_modal", {on_hidden: on_hidden_callback});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function launch(conf: DialogWidgetConfig): void {
 | 
			
		||||
@@ -210,7 +210,7 @@ export function launch(conf: DialogWidgetConfig): void {
 | 
			
		||||
        conf.on_click(e);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    overlays.open_modal("dialog_widget_modal", {
 | 
			
		||||
    modals.open_modal("dialog_widget_modal", {
 | 
			
		||||
        autoremove: true,
 | 
			
		||||
        on_show() {
 | 
			
		||||
            if (conf.focus_submit_on_open) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ import * as message_edit from "./message_edit";
 | 
			
		||||
import * as message_edit_history from "./message_edit_history";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_scroll_state from "./message_scroll_state";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as narrow from "./narrow";
 | 
			
		||||
import * as narrow_state from "./narrow_state";
 | 
			
		||||
import * as navigate from "./navigate";
 | 
			
		||||
@@ -279,8 +280,8 @@ export function process_escape_key(e) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (overlays.is_modal_open()) {
 | 
			
		||||
        overlays.close_active_modal();
 | 
			
		||||
    if (modals.is_modal_open()) {
 | 
			
		||||
        modals.close_active_modal();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -485,7 +486,7 @@ export function process_enter_key(e) {
 | 
			
		||||
 | 
			
		||||
    // All custom logic for overlays/modals is above; if we're in a
 | 
			
		||||
    // modal at this point, let the browser handle the event.
 | 
			
		||||
    if (overlays.is_modal_open()) {
 | 
			
		||||
    if (modals.is_modal_open()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -694,7 +695,7 @@ export function process_hotkey(e, hotkey) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // `list_util` will process the event in send later modal.
 | 
			
		||||
    if (overlays.is_modal_open() && overlays.active_modal() !== "#send_later_modal") {
 | 
			
		||||
    if (modals.is_modal_open() && modals.active_modal() !== "#send_later_modal") {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										239
									
								
								web/src/modals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								web/src/modals.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import Micromodal from "micromodal";
 | 
			
		||||
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as overlay_util from "./overlay_util";
 | 
			
		||||
 | 
			
		||||
type Hook = () => void;
 | 
			
		||||
 | 
			
		||||
export type ModalConfig = {
 | 
			
		||||
    autoremove?: boolean;
 | 
			
		||||
    on_show?: () => void;
 | 
			
		||||
    on_shown?: () => void;
 | 
			
		||||
    on_hide?: () => void;
 | 
			
		||||
    on_hidden?: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pre_open_hooks: Hook[] = [];
 | 
			
		||||
const pre_close_hooks: Hook[] = [];
 | 
			
		||||
 | 
			
		||||
export function register_pre_open_hook(func: Hook): void {
 | 
			
		||||
    pre_open_hooks.push(func);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function register_pre_close_hook(func: Hook): void {
 | 
			
		||||
    pre_close_hooks.push(func);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function call_hooks(func_list: Hook[]): void {
 | 
			
		||||
    for (const element of func_list) {
 | 
			
		||||
        element();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function is_modal_open(): boolean {
 | 
			
		||||
    return $(".micromodal").hasClass("modal--open");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function active_modal(): string | undefined {
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.error("Programming error — Called active_modal when there is no modal open");
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    return `#${CSS.escape($micromodal.attr("id")!)}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If conf.autoremove is true, the modal element will be removed from the DOM
 | 
			
		||||
// once the modal is hidden.
 | 
			
		||||
// conf also accepts the following optional properties:
 | 
			
		||||
// on_show: Callback to run when the modal is triggered to show.
 | 
			
		||||
// on_shown: Callback to run when the modal is shown.
 | 
			
		||||
// on_hide: Callback to run when the modal is triggered to hide.
 | 
			
		||||
// on_hidden: Callback to run when the modal is hidden.
 | 
			
		||||
export function open_modal(
 | 
			
		||||
    modal_id: string,
 | 
			
		||||
    conf: ModalConfig & {recursive_call_count?: number} = {},
 | 
			
		||||
): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into open_modal");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't accept hash-based selector to enforce modals to have unique ids and
 | 
			
		||||
    // since micromodal doesn't accept hash based selectors.
 | 
			
		||||
    if (modal_id.startsWith("#")) {
 | 
			
		||||
        blueslip.error("hash-based selector passed in to open_modal", {modal_id});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_modal_open()) {
 | 
			
		||||
        /*
 | 
			
		||||
          Our modal system doesn't directly support opening a modal
 | 
			
		||||
          when one is already open, because the `is_modal_open` CSS
 | 
			
		||||
          class doesn't update until Micromodal has finished its
 | 
			
		||||
          animations, which can take 100ms or more.
 | 
			
		||||
 | 
			
		||||
          We can likely fix that, but in the meantime, we should
 | 
			
		||||
          handle this situation correctly, by closing the current
 | 
			
		||||
          modal, waiting for it to finish closing, and then attempting
 | 
			
		||||
          to open the current modal again.
 | 
			
		||||
        */
 | 
			
		||||
        if (!conf.recursive_call_count) {
 | 
			
		||||
            conf.recursive_call_count = 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            conf.recursive_call_count += 1;
 | 
			
		||||
        }
 | 
			
		||||
        if (conf.recursive_call_count > 50) {
 | 
			
		||||
            blueslip.error("Modal incorrectly is still open", {modal_id});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        close_active_modal();
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            open_modal(modal_id, conf);
 | 
			
		||||
        }, 10);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("open modal: " + modal_id);
 | 
			
		||||
 | 
			
		||||
    // Micromodal gets elements using the getElementById DOM function
 | 
			
		||||
    // which doesn't require the hash. We add it manually here.
 | 
			
		||||
    const id_selector = `#${CSS.escape(modal_id)}`;
 | 
			
		||||
    const $micromodal = $(id_selector);
 | 
			
		||||
 | 
			
		||||
    $micromodal.find(".modal__container").on("animationend", (event) => {
 | 
			
		||||
        const animation_name = (event.originalEvent as AnimationEvent).animationName;
 | 
			
		||||
        if (animation_name === "mmfadeIn") {
 | 
			
		||||
            // Micromodal adds the is-open class before the modal animation
 | 
			
		||||
            // is complete, which isn't really helpful since a modal is open after the
 | 
			
		||||
            // animation is complete. So, we manually add a class after the
 | 
			
		||||
            // animation is complete.
 | 
			
		||||
            $micromodal.addClass("modal--open");
 | 
			
		||||
            $micromodal.removeClass("modal--opening");
 | 
			
		||||
 | 
			
		||||
            if (conf.on_shown) {
 | 
			
		||||
                conf.on_shown();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (animation_name === "mmfadeOut") {
 | 
			
		||||
            // Call the on_hidden callback after the modal finishes hiding.
 | 
			
		||||
 | 
			
		||||
            $micromodal.removeClass("modal--open");
 | 
			
		||||
            if (conf.autoremove) {
 | 
			
		||||
                $micromodal.remove();
 | 
			
		||||
            }
 | 
			
		||||
            if (conf.on_hidden) {
 | 
			
		||||
                conf.on_hidden();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $micromodal.find(".modal__overlay").on("click", (e) => {
 | 
			
		||||
        /* Micromodal's data-micromodal-close feature doesn't check for
 | 
			
		||||
           range selections; this means dragging a selection of text in an
 | 
			
		||||
           input inside the modal too far will weirdly close the modal.
 | 
			
		||||
           See https://github.com/ghosh/Micromodal/issues/505.
 | 
			
		||||
           Work around this with our own implementation. */
 | 
			
		||||
        if (!$(e.target).is(".modal__overlay")) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (document.getSelection()?.type === "Range") {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        close_modal(modal_id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function on_show_callback(): void {
 | 
			
		||||
        if (conf.on_show) {
 | 
			
		||||
            conf.on_show();
 | 
			
		||||
        }
 | 
			
		||||
        overlay_util.disable_scrolling();
 | 
			
		||||
        call_hooks(pre_open_hooks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function on_close_callback(): void {
 | 
			
		||||
        if (conf.on_hide) {
 | 
			
		||||
            conf.on_hide();
 | 
			
		||||
        }
 | 
			
		||||
        overlay_util.enable_scrolling();
 | 
			
		||||
        call_hooks(pre_close_hooks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Micromodal.show(modal_id, {
 | 
			
		||||
        disableFocus: true,
 | 
			
		||||
        openClass: "modal--opening",
 | 
			
		||||
        onShow: on_show_callback,
 | 
			
		||||
        onClose: on_close_callback,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// `conf` is an object with the following optional properties:
 | 
			
		||||
// * on_hidden: Callback to run when the modal finishes hiding.
 | 
			
		||||
export function close_modal(modal_id: string, conf: Pick<ModalConfig, "on_hidden"> = {}): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into close_modal");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (active_modal() !== `#${CSS.escape(modal_id)}`) {
 | 
			
		||||
        blueslip.error("Trying to close modal when other is open", {modal_id, active_modal});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("close modal: " + modal_id);
 | 
			
		||||
 | 
			
		||||
    const id_selector = `#${CSS.escape(modal_id)}`;
 | 
			
		||||
    const $micromodal = $(id_selector);
 | 
			
		||||
 | 
			
		||||
    // On-hidden hooks should typically be registered in
 | 
			
		||||
    // overlays.open_modal.  However, we offer this alternative
 | 
			
		||||
    // mechanism as a convenience for hooks only known when
 | 
			
		||||
    // closing the modal.
 | 
			
		||||
    $micromodal.find(".modal__container").on("animationend", (event) => {
 | 
			
		||||
        const animation_name = (event.originalEvent as AnimationEvent).animationName;
 | 
			
		||||
        if (animation_name === "mmfadeOut" && conf.on_hidden) {
 | 
			
		||||
            conf.on_hidden();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Micromodal.close(modal_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_modal_if_open(modal_id: string): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into close_modal_if_open");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    const active_modal_id = CSS.escape(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
    if (active_modal_id === `${CSS.escape(modal_id)}`) {
 | 
			
		||||
        Micromodal.close(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
    } else {
 | 
			
		||||
        blueslip.info(
 | 
			
		||||
            `${active_modal_id} is the currently active modal and ${modal_id} is already closed.`,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_active_modal(): void {
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    Micromodal.close(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import Micromodal from "micromodal";
 | 
			
		||||
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as overlay_util from "./overlay_util";
 | 
			
		||||
@@ -17,20 +16,9 @@ type Overlay = {
 | 
			
		||||
    close_handler: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ModalConfig = {
 | 
			
		||||
    autoremove?: boolean;
 | 
			
		||||
    on_show?: () => void;
 | 
			
		||||
    on_shown?: () => void;
 | 
			
		||||
    on_hide?: () => void;
 | 
			
		||||
    on_hidden?: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let active_overlay: Overlay | undefined;
 | 
			
		||||
let open_overlay_name: string | undefined;
 | 
			
		||||
 | 
			
		||||
// Used for both overlays and modals.
 | 
			
		||||
// We could split these hooks so that they are separate for
 | 
			
		||||
// overlays and modals if we need to.
 | 
			
		||||
const pre_open_hooks: Hook[] = [];
 | 
			
		||||
const pre_close_hooks: Hook[] = [];
 | 
			
		||||
 | 
			
		||||
@@ -57,10 +45,6 @@ export function is_active(): boolean {
 | 
			
		||||
    return Boolean(open_overlay_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function is_modal_open(): boolean {
 | 
			
		||||
    return $(".micromodal").hasClass("modal--open");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function info_overlay_open(): boolean {
 | 
			
		||||
    return open_overlay_name === "informationalOverlays";
 | 
			
		||||
}
 | 
			
		||||
@@ -89,16 +73,6 @@ export function scheduled_messages_open(): boolean {
 | 
			
		||||
    return open_overlay_name === "scheduled";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function active_modal(): string | undefined {
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.error("Programming error — Called active_modal when there is no modal open");
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    return `#${CSS.escape($micromodal.attr("id")!)}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function open_overlay(opts: OverlayOptions): void {
 | 
			
		||||
    call_hooks(pre_open_hooks);
 | 
			
		||||
 | 
			
		||||
@@ -141,131 +115,6 @@ export function open_overlay(opts: OverlayOptions): void {
 | 
			
		||||
    $("#navbar-fixed-container").attr("aria-hidden", "true");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If conf.autoremove is true, the modal element will be removed from the DOM
 | 
			
		||||
// once the modal is hidden.
 | 
			
		||||
// conf also accepts the following optional properties:
 | 
			
		||||
// on_show: Callback to run when the modal is triggered to show.
 | 
			
		||||
// on_shown: Callback to run when the modal is shown.
 | 
			
		||||
// on_hide: Callback to run when the modal is triggered to hide.
 | 
			
		||||
// on_hidden: Callback to run when the modal is hidden.
 | 
			
		||||
export function open_modal(
 | 
			
		||||
    modal_id: string,
 | 
			
		||||
    conf: ModalConfig & {recursive_call_count?: number} = {},
 | 
			
		||||
): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into open_modal");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't accept hash-based selector to enforce modals to have unique ids and
 | 
			
		||||
    // since micromodal doesn't accept hash based selectors.
 | 
			
		||||
    if (modal_id.startsWith("#")) {
 | 
			
		||||
        blueslip.error("hash-based selector passed in to open_modal", {modal_id});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_modal_open()) {
 | 
			
		||||
        /*
 | 
			
		||||
          Our modal system doesn't directly support opening a modal
 | 
			
		||||
          when one is already open, because the `is_modal_open` CSS
 | 
			
		||||
          class doesn't update until Micromodal has finished its
 | 
			
		||||
          animations, which can take 100ms or more.
 | 
			
		||||
 | 
			
		||||
          We can likely fix that, but in the meantime, we should
 | 
			
		||||
          handle this situation correctly, by closing the current
 | 
			
		||||
          modal, waiting for it to finish closing, and then attempting
 | 
			
		||||
          to open the current modal again.
 | 
			
		||||
        */
 | 
			
		||||
        if (!conf.recursive_call_count) {
 | 
			
		||||
            conf.recursive_call_count = 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            conf.recursive_call_count += 1;
 | 
			
		||||
        }
 | 
			
		||||
        if (conf.recursive_call_count > 50) {
 | 
			
		||||
            blueslip.error("Modal incorrectly is still open", {modal_id});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        close_active_modal();
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            open_modal(modal_id, conf);
 | 
			
		||||
        }, 10);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("open modal: " + modal_id);
 | 
			
		||||
 | 
			
		||||
    // Micromodal gets elements using the getElementById DOM function
 | 
			
		||||
    // which doesn't require the hash. We add it manually here.
 | 
			
		||||
    const id_selector = `#${CSS.escape(modal_id)}`;
 | 
			
		||||
    const $micromodal = $(id_selector);
 | 
			
		||||
 | 
			
		||||
    $micromodal.find(".modal__container").on("animationend", (event) => {
 | 
			
		||||
        const animation_name = (event.originalEvent as AnimationEvent).animationName;
 | 
			
		||||
        if (animation_name === "mmfadeIn") {
 | 
			
		||||
            // Micromodal adds the is-open class before the modal animation
 | 
			
		||||
            // is complete, which isn't really helpful since a modal is open after the
 | 
			
		||||
            // animation is complete. So, we manually add a class after the
 | 
			
		||||
            // animation is complete.
 | 
			
		||||
            $micromodal.addClass("modal--open");
 | 
			
		||||
            $micromodal.removeClass("modal--opening");
 | 
			
		||||
 | 
			
		||||
            if (conf.on_shown) {
 | 
			
		||||
                conf.on_shown();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (animation_name === "mmfadeOut") {
 | 
			
		||||
            // Call the on_hidden callback after the modal finishes hiding.
 | 
			
		||||
 | 
			
		||||
            $micromodal.removeClass("modal--open");
 | 
			
		||||
            if (conf.autoremove) {
 | 
			
		||||
                $micromodal.remove();
 | 
			
		||||
            }
 | 
			
		||||
            if (conf.on_hidden) {
 | 
			
		||||
                conf.on_hidden();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $micromodal.find(".modal__overlay").on("click", (e) => {
 | 
			
		||||
        /* Micromodal's data-micromodal-close feature doesn't check for
 | 
			
		||||
           range selections; this means dragging a selection of text in an
 | 
			
		||||
           input inside the modal too far will weirdly close the modal.
 | 
			
		||||
           See https://github.com/ghosh/Micromodal/issues/505.
 | 
			
		||||
           Work around this with our own implementation. */
 | 
			
		||||
        if (!$(e.target).is(".modal__overlay")) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (document.getSelection()?.type === "Range") {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        close_modal(modal_id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function on_show_callback(): void {
 | 
			
		||||
        if (conf.on_show) {
 | 
			
		||||
            conf.on_show();
 | 
			
		||||
        }
 | 
			
		||||
        overlay_util.disable_scrolling();
 | 
			
		||||
        call_hooks(pre_open_hooks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function on_close_callback(): void {
 | 
			
		||||
        if (conf.on_hide) {
 | 
			
		||||
            conf.on_hide();
 | 
			
		||||
        }
 | 
			
		||||
        overlay_util.enable_scrolling();
 | 
			
		||||
        call_hooks(pre_close_hooks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Micromodal.show(modal_id, {
 | 
			
		||||
        disableFocus: true,
 | 
			
		||||
        openClass: "modal--opening",
 | 
			
		||||
        onShow: on_show_callback,
 | 
			
		||||
        onClose: on_close_callback,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_overlay(name: string): void {
 | 
			
		||||
    call_hooks(pre_close_hooks);
 | 
			
		||||
 | 
			
		||||
@@ -304,74 +153,6 @@ export function close_active(): void {
 | 
			
		||||
    close_overlay(open_overlay_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// `conf` is an object with the following optional properties:
 | 
			
		||||
// * on_hidden: Callback to run when the modal finishes hiding.
 | 
			
		||||
export function close_modal(modal_id: string, conf: Pick<ModalConfig, "on_hidden"> = {}): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into close_modal");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (active_modal() !== `#${CSS.escape(modal_id)}`) {
 | 
			
		||||
        blueslip.error("Trying to close modal when other is open", {modal_id, active_modal});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("close modal: " + modal_id);
 | 
			
		||||
 | 
			
		||||
    const id_selector = `#${CSS.escape(modal_id)}`;
 | 
			
		||||
    const $micromodal = $(id_selector);
 | 
			
		||||
 | 
			
		||||
    // On-hidden hooks should typically be registered in
 | 
			
		||||
    // overlays.open_modal.  However, we offer this alternative
 | 
			
		||||
    // mechanism as a convenience for hooks only known when
 | 
			
		||||
    // closing the modal.
 | 
			
		||||
    $micromodal.find(".modal__container").on("animationend", (event) => {
 | 
			
		||||
        const animation_name = (event.originalEvent as AnimationEvent).animationName;
 | 
			
		||||
        if (animation_name === "mmfadeOut" && conf.on_hidden) {
 | 
			
		||||
            conf.on_hidden();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Micromodal.close(modal_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_modal_if_open(modal_id: string): void {
 | 
			
		||||
    if (modal_id === undefined) {
 | 
			
		||||
        blueslip.error("Undefined id was passed into close_modal_if_open");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    const active_modal_id = CSS.escape(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
    if (active_modal_id === `${CSS.escape(modal_id)}`) {
 | 
			
		||||
        Micromodal.close(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
    } else {
 | 
			
		||||
        blueslip.info(
 | 
			
		||||
            `${active_modal_id} is the currently active modal and ${modal_id} is already closed.`,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_active_modal(): void {
 | 
			
		||||
    if (!is_modal_open()) {
 | 
			
		||||
        blueslip.warn("close_active_modal() called without checking is_modal_open()");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $micromodal = $(".micromodal.modal--open");
 | 
			
		||||
    Micromodal.close(`${CSS.escape($micromodal.attr("id") ?? "")}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_for_hash_change(): void {
 | 
			
		||||
    if (open_overlay_name) {
 | 
			
		||||
        close_overlay(open_overlay_name);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import * as overlays from './overlays';
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
 | 
			
		||||
export function any_active(): boolean {
 | 
			
		||||
    return overlays.is_active() || overlays.is_modal_open();
 | 
			
		||||
    return overlays.is_active() || modals.is_modal_open();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import tippy from "tippy.js";
 | 
			
		||||
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import {media_breakpoints_num} from "./css_variables";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as popovers from "./popovers";
 | 
			
		||||
 | 
			
		||||
@@ -259,6 +260,8 @@ export function initialize() {
 | 
			
		||||
    /* Configure popovers to hide when toggling overlays. */
 | 
			
		||||
    overlays.register_pre_open_hook(popovers.hide_all);
 | 
			
		||||
    overlays.register_pre_close_hook(popovers.hide_all);
 | 
			
		||||
    modals.register_pre_open_hook(popovers.hide_all);
 | 
			
		||||
    modals.register_pre_close_hook(popovers.hide_all);
 | 
			
		||||
 | 
			
		||||
    let last_scroll = 0;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,13 @@ import * as channel from "./channel";
 | 
			
		||||
import {$t, $t_html} from "./i18n";
 | 
			
		||||
import * as loading from "./loading";
 | 
			
		||||
import * as message_store from "./message_store";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
import * as ui_report from "./ui_report";
 | 
			
		||||
 | 
			
		||||
export function show_user_list(message_id) {
 | 
			
		||||
    $("body").append(render_read_receipts_modal());
 | 
			
		||||
    overlays.open_modal("read_receipts_modal", {
 | 
			
		||||
    modals.open_modal("read_receipts_modal", {
 | 
			
		||||
        autoremove: true,
 | 
			
		||||
        on_show() {
 | 
			
		||||
            const message = message_store.get(message_id);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import render_send_later_modal_options from "../templates/send_later_modal_optio
 | 
			
		||||
import * as compose from "./compose";
 | 
			
		||||
import * as compose_validate from "./compose_validate";
 | 
			
		||||
import * as flatpickr from "./flatpickr";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as popover_menus from "./popover_menus";
 | 
			
		||||
import * as scheduled_messages from "./scheduled_messages";
 | 
			
		||||
import {parse_html} from "./ui_util";
 | 
			
		||||
@@ -31,7 +31,7 @@ export function open_send_later_menu() {
 | 
			
		||||
    $("body").append(render_send_later_modal(filtered_send_opts));
 | 
			
		||||
    let interval;
 | 
			
		||||
 | 
			
		||||
    overlays.open_modal("send_later_modal", {
 | 
			
		||||
    modals.open_modal("send_later_modal", {
 | 
			
		||||
        autoremove: true,
 | 
			
		||||
        on_show() {
 | 
			
		||||
            interval = setInterval(
 | 
			
		||||
@@ -100,7 +100,7 @@ export function open_send_later_menu() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function do_schedule_message(send_at_time) {
 | 
			
		||||
    overlays.close_modal_if_open("send_later_modal");
 | 
			
		||||
    modals.close_modal_if_open("send_later_modal");
 | 
			
		||||
 | 
			
		||||
    if (!Number.isInteger(send_at_time)) {
 | 
			
		||||
        // Convert to timestamp if this is not a timestamp.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import render_settings_tab from "../templates/settings_tab.hbs";
 | 
			
		||||
import * as browser_history from "./browser_history";
 | 
			
		||||
import * as flatpickr from "./flatpickr";
 | 
			
		||||
import {$t} from "./i18n";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
@@ -25,7 +26,7 @@ export let settings_label;
 | 
			
		||||
 | 
			
		||||
$(() => {
 | 
			
		||||
    $("#settings_overlay_container").on("click", (e) => {
 | 
			
		||||
        if (!overlays.is_modal_open()) {
 | 
			
		||||
        if (!modals.is_modal_open()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ($(e.target).closest(".micromodal").length > 0) {
 | 
			
		||||
@@ -38,7 +39,7 @@ $(() => {
 | 
			
		||||
        // event to the parent container otherwise the modal will not open. This
 | 
			
		||||
        // is so because this event handler will get fired on any click in settings
 | 
			
		||||
        // overlay and subsequently close any open modal.
 | 
			
		||||
        overlays.close_active_modal();
 | 
			
		||||
        modals.close_active_modal();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import * as custom_profile_fields_ui from "./custom_profile_fields_ui";
 | 
			
		||||
import * as dialog_widget from "./dialog_widget";
 | 
			
		||||
import {$t_html} from "./i18n";
 | 
			
		||||
import * as keydown_util from "./keydown_util";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
@@ -361,7 +362,7 @@ export function set_up() {
 | 
			
		||||
        $("body").append(render_settings_api_key_modal());
 | 
			
		||||
        setup_api_key_modal();
 | 
			
		||||
        $("#api_key_status").hide();
 | 
			
		||||
        overlays.open_modal("api_key_modal", {
 | 
			
		||||
        modals.open_modal("api_key_modal", {
 | 
			
		||||
            autoremove: true,
 | 
			
		||||
            on_show() {
 | 
			
		||||
                $("#get_api_key_password").trigger("focus");
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import $ from "jquery";
 | 
			
		||||
import render_login_to_access_modal from "../templates/login_to_access.hbs";
 | 
			
		||||
 | 
			
		||||
import * as browser_history from "./browser_history";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
 | 
			
		||||
export function current_hash_as_next(): string {
 | 
			
		||||
@@ -40,7 +40,7 @@ export function login_to_access(empty_narrow?: boolean): void {
 | 
			
		||||
        }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    overlays.open_modal("login_to_access_modal", {
 | 
			
		||||
    modals.open_modal("login_to_access_modal", {
 | 
			
		||||
        autoremove: true,
 | 
			
		||||
        on_hide() {
 | 
			
		||||
            browser_history.return_to_web_public_hash();
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ import {$t, $t_html} from "./i18n";
 | 
			
		||||
import * as integration_url_modal from "./integration_url_modal";
 | 
			
		||||
import * as ListWidget from "./list_widget";
 | 
			
		||||
import * as loading from "./loading";
 | 
			
		||||
import * as overlays from "./overlays";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
import * as settings_config from "./settings_config";
 | 
			
		||||
@@ -74,7 +74,7 @@ function compare_by_name(a, b) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_user_id_if_user_profile_modal_open() {
 | 
			
		||||
    if (overlays.is_modal_open() && overlays.active_modal() === "#user-profile-modal") {
 | 
			
		||||
    if (modals.is_modal_open() && modals.active_modal() === "#user-profile-modal") {
 | 
			
		||||
        const user_id = $("#user-profile-modal").data("user-id");
 | 
			
		||||
        return user_id;
 | 
			
		||||
    }
 | 
			
		||||
@@ -294,7 +294,7 @@ export function get_custom_profile_field_data(user, field, field_types) {
 | 
			
		||||
 | 
			
		||||
export function hide_user_profile() {
 | 
			
		||||
    user_streams_list_widget = undefined;
 | 
			
		||||
    overlays.close_modal_if_open("user-profile-modal");
 | 
			
		||||
    modals.close_modal_if_open("user-profile-modal");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function show_manage_user_tab(target) {
 | 
			
		||||
@@ -370,7 +370,7 @@ export function show_user_profile(user, default_tab_key = "profile-tab") {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $("#user-profile-modal-holder").html(render_user_profile_modal(args));
 | 
			
		||||
    overlays.open_modal("user-profile-modal", {autoremove: true});
 | 
			
		||||
    modals.open_modal("user-profile-modal", {autoremove: true});
 | 
			
		||||
    $(".tabcontent").hide();
 | 
			
		||||
 | 
			
		||||
    let default_tab = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,10 @@ const narrow_state = mock_esm("../src/narrow_state", {
 | 
			
		||||
    is_message_feed_visible: () => true,
 | 
			
		||||
});
 | 
			
		||||
const navigate = mock_esm("../src/navigate");
 | 
			
		||||
const modals = mock_esm("../src/modals", {
 | 
			
		||||
    is_modal_open: () => false,
 | 
			
		||||
    active_modal: () => undefined,
 | 
			
		||||
});
 | 
			
		||||
const overlays = mock_esm("../src/overlays", {
 | 
			
		||||
    is_active: () => false,
 | 
			
		||||
    settings_open: () => false,
 | 
			
		||||
@@ -63,8 +67,6 @@ const overlays = mock_esm("../src/overlays", {
 | 
			
		||||
    drafts_open: () => false,
 | 
			
		||||
    scheduled_messages_open: () => false,
 | 
			
		||||
    info_overlay_open: () => false,
 | 
			
		||||
    is_modal_open: () => false,
 | 
			
		||||
    active_modal: () => undefined,
 | 
			
		||||
});
 | 
			
		||||
const popovers = mock_esm("../src/user_card_popover", {
 | 
			
		||||
    manage_menu: {
 | 
			
		||||
@@ -348,7 +350,7 @@ run_test("drafts closed launch", ({override}) => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("modal open", ({override}) => {
 | 
			
		||||
    override(overlays, "is_modal_open", () => true);
 | 
			
		||||
    override(modals, "is_modal_open", () => true);
 | 
			
		||||
    test_normal_typing();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user