mirror of
https://github.com/zulip/zulip.git
synced 2025-11-14 10:57:58 +00:00
dialog_widget: Migrate modal to Micromodal.
Also removed the `danger_submit_button` config option from the dialog_widget since it isn't needed in the new modals.
This commit is contained in:
@@ -496,6 +496,16 @@ class CommonUtils {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async wait_for_micromodal_to_open(page: Page): Promise<void> {
|
||||||
|
// We manually add the `modal--open` class to the modal after the modal animation completes.
|
||||||
|
await page.waitForFunction(() => document.querySelector(".modal--open") !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async wait_for_micromodal_to_close(page: Page): Promise<void> {
|
||||||
|
// This function will ensure that the mouse events are enabled for the background for further tests.
|
||||||
|
await page.waitForFunction(() => document.querySelector(".modal--open") === null);
|
||||||
|
}
|
||||||
|
|
||||||
async run_test_async(test_function: (page: Page) => Promise<void>): Promise<void> {
|
async run_test_async(test_function: (page: Page) => Promise<void>): Promise<void> {
|
||||||
// Pass a page instance to test so we can take
|
// Pass a page instance to test so we can take
|
||||||
// a screenshot of it when the test fails.
|
// a screenshot of it when the test fails.
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ async function delete_message_test(page: Page): Promise<void> {
|
|||||||
const messages_quantitiy = await page.evaluate(() => $("#zhome .message_row").length);
|
const messages_quantitiy = await page.evaluate(() => $("#zhome .message_row").length);
|
||||||
const last_message_id = await click_delete_and_return_last_msg_id(page);
|
const last_message_id = await click_delete_and_return_last_msg_id(page);
|
||||||
|
|
||||||
await page.waitForSelector("#dialog_widget_modal", {visible: true});
|
await common.wait_for_micromodal_to_open(page);
|
||||||
await page.click(".dialog_submit_button");
|
await page.evaluate(() => {
|
||||||
|
(document.querySelector(".dialog_submit_button") as HTMLButtonElement)?.click();
|
||||||
const confirm_span = ".dialog_submit_button span";
|
});
|
||||||
await page.waitForSelector(confirm_span, {hidden: true});
|
await common.wait_for_micromodal_to_close(page);
|
||||||
|
|
||||||
await page.waitForSelector("#dialog_widget_modal", {hidden: true});
|
|
||||||
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
(expected_length: number) => $("#zhome .message_row").length === expected_length,
|
(expected_length: number) => $("#zhome .message_row").length === expected_length,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async function test_add_invalid_linkifier_pattern(page: Page): Promise<void> {
|
|||||||
|
|
||||||
async function test_edit_linkifier(page: Page): Promise<void> {
|
async function test_edit_linkifier(page: Page): Promise<void> {
|
||||||
await page.click(".linkifier_row .edit");
|
await page.click(".linkifier_row .edit");
|
||||||
await page.waitForFunction(() => document.activeElement?.id === "dialog_widget_modal");
|
await common.wait_for_micromodal_to_open(page);
|
||||||
await common.fill_form(page, "form.linkifier-edit-form", {
|
await common.fill_form(page, "form.linkifier-edit-form", {
|
||||||
pattern: "(?P<num>[0-9a-f]{40})",
|
pattern: "(?P<num>[0-9a-f]{40})",
|
||||||
url_format_string: "https://trac.example.com/commit/%(num)s",
|
url_format_string: "https://trac.example.com/commit/%(num)s",
|
||||||
@@ -64,7 +64,7 @@ async function test_edit_linkifier(page: Page): Promise<void> {
|
|||||||
await page.click(".dialog_submit_button");
|
await page.click(".dialog_submit_button");
|
||||||
|
|
||||||
await page.waitForSelector("#dialog_widget_modal", {hidden: true});
|
await page.waitForSelector("#dialog_widget_modal", {hidden: true});
|
||||||
await common.wait_for_modal_to_close(page);
|
await common.wait_for_micromodal_to_close(page);
|
||||||
|
|
||||||
await page.waitForSelector(".linkifier_row", {visible: true});
|
await page.waitForSelector(".linkifier_row", {visible: true});
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
@@ -81,7 +81,7 @@ async function test_edit_linkifier(page: Page): Promise<void> {
|
|||||||
|
|
||||||
async function test_edit_invalid_linkifier(page: Page): Promise<void> {
|
async function test_edit_invalid_linkifier(page: Page): Promise<void> {
|
||||||
await page.click(".linkifier_row .edit");
|
await page.click(".linkifier_row .edit");
|
||||||
await page.waitForFunction(() => document.activeElement?.id === "dialog_widget_modal");
|
await common.wait_for_micromodal_to_open(page);
|
||||||
await common.fill_form(page, "form.linkifier-edit-form", {
|
await common.fill_form(page, "form.linkifier-edit-form", {
|
||||||
pattern: "#(?P<id>d????)",
|
pattern: "#(?P<id>d????)",
|
||||||
url_format_string: "????",
|
url_format_string: "????",
|
||||||
@@ -107,7 +107,7 @@ async function test_edit_invalid_linkifier(page: Page): Promise<void> {
|
|||||||
);
|
);
|
||||||
assert.strictEqual(edit_linkifier_format_status, "Failed: Enter a valid URL.");
|
assert.strictEqual(edit_linkifier_format_status, "Failed: Enter a valid URL.");
|
||||||
|
|
||||||
await page.click(".close-modal-btn");
|
await page.click(".dialog_cancel_button");
|
||||||
await page.waitForSelector("#dialog_widget_modal", {hidden: true});
|
await page.waitForSelector("#dialog_widget_modal", {hidden: true});
|
||||||
|
|
||||||
await page.waitForSelector(".linkifier_row", {visible: true});
|
await page.waitForSelector(".linkifier_row", {visible: true});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ async function test_deactivate_user(page: Page): Promise<void> {
|
|||||||
await page.waitForSelector(cordelia_user_row, {visible: true});
|
await page.waitForSelector(cordelia_user_row, {visible: true});
|
||||||
await page.waitForSelector(cordelia_user_row + " .fa-user-times");
|
await page.waitForSelector(cordelia_user_row + " .fa-user-times");
|
||||||
await page.click(cordelia_user_row + " .deactivate");
|
await page.click(cordelia_user_row + " .deactivate");
|
||||||
await page.waitForSelector("#dialog_widget_modal", {visible: true});
|
await common.wait_for_micromodal_to_open(page);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
await common.get_text_from_selector(page, ".dialog_heading"),
|
await common.get_text_from_selector(page, ".dialog_heading"),
|
||||||
@@ -36,7 +36,7 @@ async function test_deactivate_user(page: Page): Promise<void> {
|
|||||||
"Deactivate button has incorrect text.",
|
"Deactivate button has incorrect text.",
|
||||||
);
|
);
|
||||||
await page.click("#dialog_widget_modal .dialog_submit_button");
|
await page.click("#dialog_widget_modal .dialog_submit_button");
|
||||||
await page.waitForSelector("#user-field-status", {hidden: true});
|
await common.wait_for_micromodal_to_close(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function test_reactivate_user(page: Page): Promise<void> {
|
async function test_reactivate_user(page: Page): Promise<void> {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"jquery-validation": "^1.19.0",
|
"jquery-validation": "^1.19.0",
|
||||||
"katex": "^0.13.2",
|
"katex": "^0.13.2",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
|
"micromodal": "^0.4.6",
|
||||||
"mini-css-extract-plugin": "^2.2.2",
|
"mini-css-extract-plugin": "^2.2.2",
|
||||||
"plotly.js": "^2.0.0",
|
"plotly.js": "^2.0.0",
|
||||||
"postcss": "^8.0.3",
|
"postcss": "^8.0.3",
|
||||||
|
|||||||
@@ -872,6 +872,7 @@ export function initialize() {
|
|||||||
!$(e.target).closest(".overlay").length &&
|
!$(e.target).closest(".overlay").length &&
|
||||||
!$(e.target).closest(".popover").length &&
|
!$(e.target).closest(".popover").length &&
|
||||||
!$(e.target).closest(".modal").length &&
|
!$(e.target).closest(".modal").length &&
|
||||||
|
!$(e.target).closest(".micromodal").length &&
|
||||||
!$(e.target).closest("[data-tippy-root]").length &&
|
!$(e.target).closest("[data-tippy-root]").length &&
|
||||||
!$(e.target).closest(".modal-backdrop").length &&
|
!$(e.target).closest(".modal-backdrop").length &&
|
||||||
$(e.target).closest("body").length
|
$(e.target).closest("body").length
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export function launch(conf) {
|
|||||||
dialog_widget.launch({
|
dialog_widget.launch({
|
||||||
...conf,
|
...conf,
|
||||||
close_on_submit: true,
|
close_on_submit: true,
|
||||||
danger_submit_button: true,
|
|
||||||
focus_submit_on_open: true,
|
focus_submit_on_open: true,
|
||||||
html_submit_button: $t_html({defaultMessage: "Confirm"}),
|
html_submit_button: $t_html({defaultMessage: "Confirm"}),
|
||||||
// Used to control button colors in the template.
|
// Used to control button colors in the template.
|
||||||
|
|||||||
@@ -1,62 +1,66 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import Micromodal from "micromodal";
|
||||||
|
|
||||||
import render_dialog_widget from "../templates/dialog_widget.hbs";
|
import render_dialog_widget from "../templates/dialog_widget.hbs";
|
||||||
import render_dialog_heading from "../templates/dialog_widget_heading.hbs";
|
|
||||||
|
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import {$t_html} from "./i18n";
|
import {$t_html} from "./i18n";
|
||||||
import * as loading from "./loading";
|
import * as loading from "./loading";
|
||||||
import * as overlays from "./overlays";
|
import * as overlays from "./overlays";
|
||||||
import * as settings_data from "./settings_data";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Look for dialog_widget in settings_users
|
* Look for confirm_dialog in settings_user_groups
|
||||||
to see an example of how to use this widget. It's
|
* to see an example of how to use this widget. It's
|
||||||
pretty simple to use!
|
* pretty simple to use!
|
||||||
|
*
|
||||||
Some things to note:
|
* Some things to note:
|
||||||
|
* 1) We create DOM on the fly, and we remove
|
||||||
1) We create DOM on the fly, and we remove
|
* the DOM once it's closed.
|
||||||
the DOM once it's closed.
|
*
|
||||||
|
* 2) We attach the DOM for the modal to the body element
|
||||||
2) We attach the DOM for the modal to the body element
|
* to avoid interference from other elements.
|
||||||
to avoid style interference from other elements.
|
*
|
||||||
|
* 3) For settings, we have a click handler in settings.js
|
||||||
3) The cancel button is driven by bootstrap.js.
|
* that will close the dialog via overlays.close_active_modal.
|
||||||
|
*
|
||||||
4) For settings, we have a click handler in settings.js
|
* 4) We assume that since this is a modal, you will
|
||||||
that will close the dialog via overlays.close_active_modal.
|
* only ever have one confirm dialog active at any
|
||||||
|
* time.
|
||||||
5) We assume that since this is a modal, you will
|
*
|
||||||
only ever have one dialog active at any
|
* 5) If a modal wants a loading spinner, it should pass loading_spinner: true.
|
||||||
time.
|
* This will show a loading spinner when the yes button is clicked.
|
||||||
|
* The caller is responsible for calling hide_confirm_dialog_spinner()
|
||||||
6) If a modal wants a loading spinner, it should pass loading_spinner: true.
|
* to hide the spinner in both success and error handlers.
|
||||||
This will show a loading spinner when the yes button is clicked.
|
*
|
||||||
The caller is responsible for calling hide_dialog_spinner()
|
* 6) If loading_spinner is used, don't hide it on `success`. This modal has a fade out
|
||||||
to hide the spinner in both success and error handlers.
|
* animation. This causes the `Confirm` button to be shown for a split second if the
|
||||||
|
* spinner is hidden.
|
||||||
7) If a caller needs to run code after the modal body is added
|
* Just close the modal. This will remove the whole modal from the DOM without
|
||||||
to DOM, it can do so by passing a post_render hook.
|
* needing to remove the spinner.
|
||||||
|
*
|
||||||
|
* 7) If a caller needs to run code after the modal body is added
|
||||||
|
* to DOM, it can do so by passing a post_render hook.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function hide_dialog_spinner() {
|
export function hide_dialog_spinner() {
|
||||||
$(".dialog_submit_button .loader").hide();
|
|
||||||
$(".dialog_submit_button span").show();
|
$(".dialog_submit_button span").show();
|
||||||
$(".dialog_submit_button").prop("disabled", false);
|
$("#dialog_widget_modal .modal__btn").prop("disabled", false);
|
||||||
$("#dialog_widget_modal .close-modal-btn").prop("disabled", false);
|
|
||||||
|
const spinner = $("#dialog_widget_modal .modal__spinner");
|
||||||
|
loading.destroy_indicator(spinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function show_dialog_spinner() {
|
export function show_dialog_spinner() {
|
||||||
const using_dark_theme = settings_data.using_dark_theme();
|
|
||||||
loading.show_button_spinner($(".dialog_submit_button .loader"), using_dark_theme);
|
|
||||||
$(".dialog_submit_button span").hide();
|
$(".dialog_submit_button span").hide();
|
||||||
$(".dialog_submit_button").prop("disabled", true);
|
// Disable both the buttons.
|
||||||
$("#dialog_widget_modal .close-modal-btn").prop("disabled", true);
|
$("#dialog_widget_modal .modal__btn").prop("disabled", true);
|
||||||
|
|
||||||
|
const spinner = $("#dialog_widget_modal .modal__spinner");
|
||||||
|
loading.make_indicator(spinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function close_modal() {
|
export function close_modal() {
|
||||||
overlays.close_modal("#dialog_widget_modal");
|
Micromodal.close("dialog_widget_modal");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function launch(conf) {
|
export function launch(conf) {
|
||||||
@@ -73,7 +77,6 @@ export function launch(conf) {
|
|||||||
// * html_submit_button: Submit button text.
|
// * html_submit_button: Submit button text.
|
||||||
// * close_on_submit: Whether to close modal on clicking submit.
|
// * close_on_submit: Whether to close modal on clicking submit.
|
||||||
// * focus_submit_on_open: Whether to focus submit button on open.
|
// * focus_submit_on_open: Whether to focus submit button on open.
|
||||||
// * danger_submit_button: Whether to use danger button styling for submit button.
|
|
||||||
// * help_link: A help link in the heading area.
|
// * help_link: A help link in the heading area.
|
||||||
|
|
||||||
for (const f of mandatory_fields) {
|
for (const f of mandatory_fields) {
|
||||||
@@ -89,15 +92,11 @@ export function launch(conf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const html_submit_button = conf.html_submit_button || $t_html({defaultMessage: "Save changes"});
|
const html_submit_button = conf.html_submit_button || $t_html({defaultMessage: "Save changes"});
|
||||||
const html_dialog_heading = render_dialog_heading({
|
const html = render_dialog_widget({
|
||||||
heading_text: conf.html_heading,
|
heading_text: conf.html_heading,
|
||||||
link: conf.help_link,
|
link: conf.help_link,
|
||||||
});
|
|
||||||
const html = render_dialog_widget({
|
|
||||||
html_submit_button,
|
html_submit_button,
|
||||||
html_dialog_heading,
|
|
||||||
html_body: conf.html_body,
|
html_body: conf.html_body,
|
||||||
danger_submit_button: conf.danger_submit_button,
|
|
||||||
});
|
});
|
||||||
const dialog = $(html);
|
const dialog = $(html);
|
||||||
$("body").append(dialog);
|
$("body").append(dialog);
|
||||||
@@ -118,16 +117,15 @@ export function launch(conf) {
|
|||||||
conf.on_click(e);
|
conf.on_click(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.on("hidden.bs.modal", () => {
|
overlays.open_modal("dialog_widget_modal", {
|
||||||
dialog.remove();
|
autoremove: true,
|
||||||
});
|
micromodal: true,
|
||||||
|
micromodal_opts: {
|
||||||
|
onShow: () => {
|
||||||
if (conf.focus_submit_on_open) {
|
if (conf.focus_submit_on_open) {
|
||||||
dialog.on("shown.bs.modal", () => {
|
|
||||||
submit_button.trigger("focus");
|
submit_button.trigger("focus");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the modal
|
|
||||||
overlays.open_modal("#dialog_widget_modal");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import Micromodal from "micromodal";
|
||||||
|
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import * as browser_history from "./browser_history";
|
import * as browser_history from "./browser_history";
|
||||||
@@ -19,7 +20,8 @@ export function is_active() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function is_modal_open() {
|
export function is_modal_open() {
|
||||||
return $(".modal").hasClass("in");
|
// Check for both Bootstrap and Micromodal modals.
|
||||||
|
return $(".modal").hasClass("in") || $(".micromodal").hasClass("modal--open");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function info_overlay_open() {
|
export function info_overlay_open() {
|
||||||
@@ -65,6 +67,12 @@ export function active_modal() {
|
|||||||
blueslip.error("Programming error — Called active_modal when there is no modal open");
|
blueslip.error("Programming error — Called active_modal when there is no modal open");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for Micromodal modals.
|
||||||
|
const micromodal = $(".micromodal.modal--open");
|
||||||
|
if (micromodal.length) {
|
||||||
|
return `#${CSS.escape(micromodal.attr("id"))}`;
|
||||||
|
}
|
||||||
return `#${CSS.escape($(".modal.in").attr("id"))}`;
|
return `#${CSS.escape($(".modal.in").attr("id"))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,17 +121,25 @@ export function open_overlay(opts) {
|
|||||||
|
|
||||||
// If conf.autoremove is true, the modal element will be removed from the DOM
|
// If conf.autoremove is true, the modal element will be removed from the DOM
|
||||||
// once the modal is hidden.
|
// once the modal is hidden.
|
||||||
|
// If conf.micromodal is true, open a micromodal modal else open a bootstrap modal
|
||||||
export function open_modal(selector, conf) {
|
export function open_modal(selector, conf) {
|
||||||
if (selector === undefined) {
|
if (selector === undefined) {
|
||||||
blueslip.error("Undefined selector was passed into open_modal");
|
blueslip.error("Undefined selector was passed into open_modal");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selector[0] !== "#") {
|
if ((!conf || (conf && !conf.micromodal)) && selector[0] !== "#") {
|
||||||
blueslip.error("Non-id-based selector passed in to open_modal: " + selector);
|
blueslip.error("Non-id-based selector passed in to open_modal: " + selector);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't accept hash-based selector to enforce modals to have unique ids and
|
||||||
|
// since micromodal doesn't accept hash based selectors.
|
||||||
|
if (conf && conf.micromodal && selector[0] === "#") {
|
||||||
|
blueslip.error("hash-based selector passed in to micromodal-based open_modal: " + selector);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_modal_open()) {
|
if (is_modal_open()) {
|
||||||
blueslip.error("open_modal() was called while " + active_modal() + " modal was open.");
|
blueslip.error("open_modal() was called while " + active_modal() + " modal was open.");
|
||||||
return;
|
return;
|
||||||
@@ -131,6 +147,46 @@ export function open_modal(selector, conf) {
|
|||||||
|
|
||||||
blueslip.debug("open modal: " + selector);
|
blueslip.debug("open modal: " + selector);
|
||||||
|
|
||||||
|
// Show a modal using micromodal.
|
||||||
|
if (conf && conf.micromodal) {
|
||||||
|
// Micromodal gets elements using the getElementById DOM function
|
||||||
|
// which doesn't require the hash. We add it manually here.
|
||||||
|
const id_selector = `#${selector}`;
|
||||||
|
const micromodal = $(id_selector);
|
||||||
|
|
||||||
|
micromodal.find(".modal__container").on("animationend", (event) => {
|
||||||
|
// Micromodal doesn't support Bootstrap-style `shown.bs.modal` and
|
||||||
|
// `hidden.bs.modal` events. We workaround this by using the animationName
|
||||||
|
// from the native event and running the required functions after the
|
||||||
|
// animation ends.
|
||||||
|
const animation_name = event.originalEvent.animationName;
|
||||||
|
if (animation_name === "mmfadeIn") {
|
||||||
|
// Equivalent to bootstrap's "shown.bs.modal" event
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
} else if (animation_name === "mmfadeOut") {
|
||||||
|
// Equivalent to bootstrap's "hidden.bs.modal" event
|
||||||
|
|
||||||
|
micromodal.removeClass("modal--open");
|
||||||
|
if (conf.autoremove) {
|
||||||
|
micromodal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Micromodal.show(selector, {
|
||||||
|
disableFocus: true,
|
||||||
|
openClass: "modal--opening",
|
||||||
|
...conf.micromodal_opts,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elem = $(selector).expectOne();
|
const elem = $(selector).expectOne();
|
||||||
elem.modal("show").attr("aria-hidden", false);
|
elem.modal("show").attr("aria-hidden", false);
|
||||||
// Disable background mouse events when modal is active
|
// Disable background mouse events when modal is active
|
||||||
@@ -185,7 +241,8 @@ export function close_active() {
|
|||||||
close_overlay(open_overlay_name);
|
close_overlay(open_overlay_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function close_modal(selector) {
|
// If conf.micromodal is true, close a micromodal modal else close a bootstrap modal
|
||||||
|
export function close_modal(selector, conf) {
|
||||||
if (selector === undefined) {
|
if (selector === undefined) {
|
||||||
blueslip.error("Undefined selector was passed into close_modal");
|
blueslip.error("Undefined selector was passed into close_modal");
|
||||||
return;
|
return;
|
||||||
@@ -196,7 +253,10 @@ export function close_modal(selector) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (active_modal() !== selector) {
|
if (
|
||||||
|
(!conf && active_modal() !== selector) ||
|
||||||
|
(conf && conf.micromodal && active_modal() !== `#${selector}`)
|
||||||
|
) {
|
||||||
blueslip.error(
|
blueslip.error(
|
||||||
"Trying to close " + selector + " modal when " + active_modal() + " is open.",
|
"Trying to close " + selector + " modal when " + active_modal() + " is open.",
|
||||||
);
|
);
|
||||||
@@ -205,6 +265,11 @@ export function close_modal(selector) {
|
|||||||
|
|
||||||
blueslip.debug("close modal: " + selector);
|
blueslip.debug("close modal: " + selector);
|
||||||
|
|
||||||
|
if (conf && conf.micromodal) {
|
||||||
|
Micromodal.close(selector);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elem = $(selector).expectOne();
|
const elem = $(selector).expectOne();
|
||||||
elem.modal("hide").attr("aria-hidden", true);
|
elem.modal("hide").attr("aria-hidden", true);
|
||||||
}
|
}
|
||||||
@@ -215,6 +280,13 @@ export function close_active_modal() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for Micromodal modals.
|
||||||
|
const micromodal = $(".micromodal.modal--open");
|
||||||
|
if (micromodal.length) {
|
||||||
|
Micromodal.close(`${CSS.escape(micromodal.attr("id"))}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$(".modal.in").modal("hide").attr("aria-hidden", true);
|
$(".modal.in").modal("hide").attr("aria-hidden", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ $("body").ready(() => {
|
|||||||
if (!overlays.is_modal_open()) {
|
if (!overlays.is_modal_open()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($(e.target).closest(".modal").length > 0) {
|
if ($(e.target).closest(".modal, .micromodal").length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -35,3 +35,170 @@
|
|||||||
.modal-bg {
|
.modal-bg {
|
||||||
background-color: hsl(0, 0%, 98%);
|
background-color: hsl(0, 0%, 98%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Styles for the Micromodal-based modals */
|
||||||
|
.modal__overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: hsla(0, 0%, 0%, 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 105;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: hsl(0, 0%, 100%);
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
|
max-height: 96%;
|
||||||
|
width: 32.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__header {
|
||||||
|
padding: 16px 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.375rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__close {
|
||||||
|
&::before {
|
||||||
|
content: "\2715";
|
||||||
|
}
|
||||||
|
margin-right: -4px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: hsl(0, 0%, 90%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__content {
|
||||||
|
font-size: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: hsl(0, 0%, 90%);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border-width: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: button;
|
||||||
|
text-transform: none;
|
||||||
|
overflow: visible;
|
||||||
|
outline: none !important;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
transition: transform 0.25s ease-out;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: hsl(198, 76%, 47%) 0 0 0 1px,
|
||||||
|
hsla(198, 76%, 47%, 0.3) 0 0 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn:focus,
|
||||||
|
.modal__btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog_cancel_button {
|
||||||
|
background: hsl(0, 0%, 100%);
|
||||||
|
border: 1px solid hsla(300, 2%, 11%, 0.3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: hsl(0, 0%, 97%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog_submit_button {
|
||||||
|
margin-left: 12px;
|
||||||
|
background-color: hsl(214, 100%, 31%);
|
||||||
|
color: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mmfadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mmfadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal.modal--opening,
|
||||||
|
.micromodal.modal--open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal[aria-hidden="true"] .modal__overlay {
|
||||||
|
animation: mmfadeOut 75ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal[aria-hidden="false"] .modal__overlay {
|
||||||
|
animation: mmfadeIn 120ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal[aria-hidden="true"] .modal__container {
|
||||||
|
animation: mmfadeOut 75ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal[aria-hidden="false"] .modal__container {
|
||||||
|
animation: mmfadeIn 120ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micromodal .modal__container,
|
||||||
|
.micromodal .modal__overlay {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__spinner .loading_indicator_spinner {
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,10 +128,21 @@ body.night-mode {
|
|||||||
border-color: hsla(0, 0%, 100%, 0.4);
|
border-color: hsla(0, 0%, 100%, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-bg {
|
.modal-bg,
|
||||||
|
.modal__container {
|
||||||
background-color: hsl(212, 28%, 18%);
|
background-color: hsl(212, 28%, 18%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal__close {
|
||||||
|
&::before {
|
||||||
|
color: hsl(236, 33%, 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: hsla(0, 0%, 91%, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.streams_popover .sp-container {
|
.streams_popover .sp-container {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
|||||||
@@ -1577,17 +1577,6 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dialog widgets should be centered, which this roughly achieves. */
|
|
||||||
#dialog_widget_modal {
|
|
||||||
top: calc(50% - 120px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* In the settings overlay, we need slightly different CSS for alignment. */
|
|
||||||
#settings_overlay_container #dialog_widget_modal {
|
|
||||||
top: 50%;
|
|
||||||
vertical-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* These have enough space for all the options in German. */
|
/* These have enough space for all the options in German. */
|
||||||
.setting_desktop_icon_count_display,
|
.setting_desktop_icon_count_display,
|
||||||
#id_realm_waiting_period_setting,
|
#id_realm_waiting_period_setting,
|
||||||
@@ -1618,16 +1607,6 @@ input[type="checkbox"] {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog_submit_button .loader {
|
|
||||||
display: none;
|
|
||||||
vertical-align: top;
|
|
||||||
position: relative;
|
|
||||||
height: 30px;
|
|
||||||
margin-top: -10px;
|
|
||||||
top: 5px;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-list-widget {
|
.dropdown-list-widget {
|
||||||
button {
|
button {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
<div class="modal modal-bg new-style hide" id="dialog_widget_modal" tabindex="-1" role="dialog" aria-labelledby="dialog_widget_modal" aria-hidden="true">
|
<div class="micromodal" id="dialog_widget_modal" aria-hidden="true">
|
||||||
<div class="modal-header">
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
<button type="button" class="close close-modal-btn" data-dismiss="modal" aria-label="{{t 'Close' }}"><span aria-hidden="true">×</span></button>
|
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="dialog_title">
|
||||||
<div class="dialog_heading">{{{ html_dialog_heading }}}</div>
|
<header class="modal__header">
|
||||||
</div>
|
<h1 class="modal__title dialog_heading">
|
||||||
<div class="modal-body dialog_body">
|
{{{ heading_text }}}
|
||||||
<div id="dialog_error" class="alert"></div>
|
{{#if link}}
|
||||||
|
{{> help_link_widget }}
|
||||||
|
{{/if}}
|
||||||
|
</h1>
|
||||||
|
<button class="modal__close" aria-label="{{t 'Close modal' }}" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<div class="alert" id="dialog_error"></div>
|
||||||
{{{ html_body }}}
|
{{{ html_body }}}
|
||||||
</div>
|
</main>
|
||||||
<div class="modal-footer">
|
<footer class="modal__footer">
|
||||||
<button class="button rounded close-modal-btn" data-dismiss="modal">{{t "Cancel" }}</button>
|
<button class="modal__btn dialog_cancel_button" aria-label="{{t 'Close this dialog window' }}" data-micromodal-close>{{t "Cancel" }}</button>
|
||||||
<button class="button rounded {{#if danger_submit_button}}btn-danger{{else}}sea-green{{/if}} dialog_submit_button">
|
<button class="modal__btn dialog_submit_button">
|
||||||
<img class="loader" alt="" src="" />
|
|
||||||
<span>{{{ html_submit_button }}}</span>
|
<span>{{{ html_submit_button }}}</span>
|
||||||
|
<div class="modal__spinner"></div>
|
||||||
</button>
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<h3>
|
|
||||||
{{{ heading_text }}}
|
|
||||||
{{#if link}}
|
|
||||||
{{> help_link_widget }}
|
|
||||||
{{/if}}
|
|
||||||
</h3>
|
|
||||||
@@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 106
|
|||||||
# historical commits sharing the same major version, in which case a
|
# historical commits sharing the same major version, in which case a
|
||||||
# minor version bump suffices.
|
# minor version bump suffices.
|
||||||
|
|
||||||
PROVISION_VERSION = "164.1"
|
PROVISION_VERSION = "164.2"
|
||||||
|
|||||||
@@ -7985,6 +7985,11 @@ micromist@1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash.camelcase "^4.3.0"
|
lodash.camelcase "^4.3.0"
|
||||||
|
|
||||||
|
micromodal@^0.4.6:
|
||||||
|
version "0.4.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/micromodal/-/micromodal-0.4.6.tgz#0425ad026c47923208cf826de6b58ed0693cb25a"
|
||||||
|
integrity sha512-2VDso2a22jWPpqwuWT/4RomVpoU3Bl9qF9D01xzwlNp5UVsImeA0gY4nSpF44vqcQtQOtkiMUV9EZkAJSRxBsg==
|
||||||
|
|
||||||
mime-db@1.50.0, "mime-db@>= 1.43.0 < 2":
|
mime-db@1.50.0, "mime-db@>= 1.43.0 < 2":
|
||||||
version "1.50.0"
|
version "1.50.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
|
||||||
|
|||||||
Reference in New Issue
Block a user