components

Components library for AniList Edit Multiple Media Simultaneously

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.greasyfork.org/scripts/496874/1387742/components.js

// ==UserScript==
// @name        components
// @license     MIT
// @namespace   rtonne
// @match       https://anilist.co/*
// @version     1.0
// @author      Rtonne
// @description Components library for AniList Edit Multiple Media Simultaneously
// ==/UserScript==

/**
 * Creates a select input.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string[]} options The option values for the select input.
 * @returns The select input element.
 */
function createSelectInput(container, options) {
  const input_container_1 = document.createElement("div");
  input_container_1.className = "el-select";
  input_container_1.style.width = "100%";
  container.append(input_container_1);
  const input_container_2 = document.createElement("div");
  input_container_2.className = "el-input el-input--suffix";
  input_container_1.append(input_container_2);
  const input = document.createElement("input");
  input.className = "el-input__inner";
  input.readOnly = true;
  input.autocomplete = "off";
  input_container_2.append(input);
  const input_suffix = document.createElement("span");
  input_suffix.className = "el-input__suffix";
  input_container_2.append(input_suffix);
  const input_suffix_inner = document.createElement("span");
  input_suffix_inner.className = "el-input__suffix-inner";
  input_suffix.append(input_suffix_inner);
  const input_icon = document.createElement("i");
  input_icon.className = "el-select__caret el-input__icon el-icon-arrow-up";
  input_suffix_inner.append(input_icon);

  input.value = options[0];

  const dropdown = document.createElement("div");
  dropdown.className = "el-select-dropdown el-popper";
  dropdown.style.minWidth = "180px";
  dropdown.style.zIndex = "10000";
  dropdown.style.position = "absolute";
  dropdown.style.transition =
    "transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1)";
  dropdown.style.opacity = "0";
  container.append(dropdown);
  const dropdown_arrow = document.createElement("div");
  dropdown_arrow.className = "popper__arrow";
  dropdown_arrow.style.left = "35px";
  dropdown_arrow.setAttribute("x-arrow", "");
  dropdown.append(dropdown_arrow);
  const dropdown_scrollbar = document.createElement("div");
  dropdown_scrollbar.className = "el-scrollbar";
  dropdown.append(dropdown_scrollbar);
  const dropdown_list_container = document.createElement("div");
  dropdown_list_container.className =
    "el-select-dropdown__wrap el-scrollbar__wrap";
  dropdown_list_container.style.overflow = "auto";
  dropdown_scrollbar.append(dropdown_list_container);
  const dropdown_list = document.createElement("ul");
  dropdown_list.className = "el-scrollbar__view el-select-dropdown__list";
  dropdown_list_container.append(dropdown_list);
  for (const option of options) {
    const dropdown_list_item = document.createElement("li");
    dropdown_list_item.className = "el-select-dropdown__item";
    dropdown_list_item.innerText = option;
    dropdown_list_item.onmousedown = () => {
      input.value = option;
    };
    dropdown_list.append(dropdown_list_item);
  }
  const dropdown_dims = dropdown.getBoundingClientRect();
  const dropdown_arrow_dims = dropdown_arrow.getBoundingClientRect();
  const full_dropdown_height =
    dropdown_dims.height + dropdown_arrow_dims.height;
  dropdown.style.transform = "scaleY(0)";

  function setDropdownPosition() {
    const input_dims = input_container_1.getBoundingClientRect();
    const dropdown_fits_below =
      window.innerHeight - input_dims.bottom >= full_dropdown_height;
    if (dropdown_fits_below) {
      dropdown.style.top = `${window.scrollY + input_dims.bottom}px`;
      dropdown.setAttribute("x-placement", "bottom-start");
      dropdown.style.transformOrigin = "center top";
    } else {
      // Subtract 17px for the margins
      dropdown.style.top = `${
        window.scrollY + input_dims.top - dropdown_dims.height - 17
      }px`;
      dropdown.setAttribute("x-placement", "top-start");
      dropdown.style.transformOrigin = "center bottom";
    }
    dropdown.style.left = `${window.scrollX + input_dims.left}px`;
  }

  new IntersectionObserver(
    () => {
      setDropdownPosition();
    },
    {
      rootMargin: `0px 0px -${dropdown_dims.height}px 0px`,
      threshold: 1,
    }
  ).observe(input_container_1);

  let isFocused = false;
  input_container_1.onclick = (event) => {
    if (isFocused) {
      input.blur();
    } else {
      input.focus();
    }
  };
  input.onmousedown = () => false; // Prevent default

  input.onfocus = () => {
    isFocused = true;
    setDropdownPosition();
    input_container_2.className += " is-focus";
    input_icon.className += " is-reverse";

    for (const item of dropdown_list.children) {
      item.classList.remove("selected");
      if (item.innerText === input.value) {
        item.className += " selected";
      }
    }

    dropdown.style.transform = "scaleY(1)";
    dropdown.style.opacity = "1";
  };

  input.onblur = () => {
    isFocused = false;
    input_container_2.classList.remove("is-focus");
    input_icon.classList.remove("is-reverse");
    dropdown.style.transform = "scaleY(0)";
    dropdown.style.opacity = "0";
  };

  // To prevent the input blurring when clicking the icon
  input_icon.onmousedown = (event) => {
    event.preventDefault();
  };

  return input;
}

/**
 * Creates a number input. Step can be 0 to have step=1 and to not limit values to steppable ones.
 * @returns The number input element.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {number} max_value The maximum value the input can reach.
 * @param {number} step The step value of the input.
 */
function createNumberInput(container, max_value = Infinity, step = 1) {
  const input_container_1 = document.createElement("div");
  input_container_1.className = "el-input-number is-controls-right";
  input_container_1.style.width = "100%";
  container.append(input_container_1);
  const input_decrease_button = document.createElement("span");
  input_decrease_button.className = "el-input-number__decrease is-disabled";
  input_decrease_button.role = "button";
  input_container_1.append(input_decrease_button);
  const input_decrease_button_arrow = document.createElement("i");
  input_decrease_button_arrow.className = "el-icon-arrow-down";
  input_decrease_button.append(input_decrease_button_arrow);
  const input_increase_button = document.createElement("span");
  input_increase_button.className = "el-input-number__increase";
  input_increase_button.role = "button";
  input_container_1.append(input_increase_button);
  const input_increase_button_arrow = document.createElement("i");
  input_increase_button_arrow.className = "el-icon-arrow-up";
  input_increase_button.append(input_increase_button_arrow);
  const input_container_2 = document.createElement("div");
  input_container_2.className = "el-input";
  input_container_1.append(input_container_2);
  const input = document.createElement("input");
  input.className = "el-input__inner";
  input.type = "number";
  input.min = input.value = 0;
  input.max = max_value;
  input.step = step;
  input_container_2.append(input);

  function setButtonDisabledStatus() {
    if (Number(input.value) <= input_min) {
      input_decrease_button.className += " is-disabled";
    } else {
      input_decrease_button.classList.remove("is-disabled");
    }
    if (Number(input.value) >= input_max) {
      input_increase_button.className += " is-disabled";
    } else {
      input_increase_button.classList.remove("is-disabled");
    }
  }
  input_decrease_button.onclick = () => {
    input.stepDown();
    setButtonDisabledStatus();
  };
  input_increase_button.onclick = () => {
    input.stepUp();
    setButtonDisabledStatus();
  };
  input.oninput = setButtonDisabledStatus;

  const input_max = Number(input.max);
  const input_min = Number(input.min);
  function makeValueValid() {
    // https://stackoverflow.com/questions/17369098/simplest-way-of-getting-the-number-of-decimals-in-a-number-in-javascript
    // Step 0 is a special case that should ignore step validation
    if (step !== 0 && Math.floor(Number(input.value)) !== Number(input.value)) {
      let decimalCount;
      if (input.value.indexOf(".") !== -1 && input.value.indexOf("-") !== -1) {
        decimalCount =
          input.value.split(/[.-]/)[1].length + input.value.split("-")[1] || 0;
      } else if (input.value.indexOf(".") !== -1) {
        decimalCount = input.value.split(".")[1].length || 0;
      } else {
        decimalCount = input.value.split("-")[1] || 0;
      }
      // Using Math.round to clean up arithmetic imprecisions
      const remainder =
        Math.round((Number(input.value) % step) * Math.pow(10, decimalCount)) /
        Math.pow(10, decimalCount);
      input.value = Number(input.value) - remainder;
    }
    if (Number(input.value) > input_max) {
      input.value = input_max;
    }
    if (Number(input.value) < input_min) {
      input.value = input_min;
    }
  }
  input.onblur = makeValueValid;
  input.onkeydown = (event) => {
    if (event.key === "Enter") {
      makeValueValid();
    }
  };

  return input;
}

/**
 * Creates a date input.
 * @param {HTMLElement} container A parent element to append the input to.
 * @returns The date input element.
 */
function createDateInput(container) {
  const input_container = document.createElement("div");
  input_container.className = "el-input el-input--suffix";
  container.append(input_container);
  const input = document.createElement("input");
  input.className = "el-input__inner";
  input.type = "date";
  input.style.width = "100%";
  input_container.append(input);
  const input_icon_container = document.createElement("span");
  input_icon_container.className = "el-input__suffix";
  input_container.append(input_icon_container);
  const input_icon = document.createElement("i");
  input_icon.className = "el-input__icon el-icon-date";
  input_icon.style.pointerEvents = "auto";
  input_icon_container.append(input_icon);

  input_icon.onclick = () => {
    input.showPicker();
  };

  return input;
}

/**
 * Creates a textarea.
 * @param {HTMLElement} container A parent element to append the input to.
 * @returns The textarea element.
 */
function createTextarea(container) {
  const textarea_container = document.createElement("div");
  textarea_container.className = "el-textarea";
  container.append(textarea_container);
  const textarea = document.createElement("textarea");
  textarea.className = "el-textarea__inner";
  textarea.style.minHeight = "34px";
  textarea_container.append(textarea);

  return textarea;
}

/**
 * Creates a checkbox.
 * @param {HTMLElement} container A parent element to append the checkbox to.
 * @param {string} text The text of the checkbox's label.
 * @returns The checkbox element.
 */
function createCheckbox(container, text) {
  const checkbox_container = document.createElement("div");
  checkbox_container.className = "rtonne-anilist-multiselect-checkbox";
  checkbox_container.style.paddingBottom = "4px";
  container.append(checkbox_container);
  const label = document.createElement("label");
  label.innerText = text;
  label.className = "el-checkbox";
  label.style.fontSize = "1.2rem";
  label.style.fontWeight = "400";
  checkbox_container.append(label);
  const middle_label = document.createElement("label");
  middle_label.className = "el-checkbox__input";
  middle_label.style.marginRight = "4px";
  middle_label.style.verticalAlign = "text-top";
  label.prepend(middle_label);
  const button_label = document.createElement("label");
  button_label.className = "el-checkbox__inner";
  button_label.style.cursor = "pointer";
  middle_label.append(button_label);
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.className = "el-checkbox__original";
  button_label.prepend(checkbox);

  checkbox.onchange = () => {
    if (checkbox.checked) {
      label.className += " is-checked";
      middle_label.className += " is-checked";
    } else {
      label.classList.remove("is-checked");
      middle_label.classList.remove("is-checked");
    }
  };

  return checkbox;
}

/**
 * Creates an indeterminate checkbox.
 * When the return checkbox.readOnly or checkbox.indeterminate is true,
 *    it is on a 3rd state where it isn't on nor off.
 * From https://css-tricks.com/indeterminate-checkboxes/.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string} text The text of the checkbox's label.
 * @returns The checkbox element.
 */
function createIndeterminateCheckbox(container, text) {
  const checkbox_container = document.createElement("div");
  checkbox_container.className = "rtonne-anilist-multiselect-checkbox";
  checkbox_container.style.paddingBottom = "4px";
  container.append(checkbox_container);
  const label = document.createElement("label");
  label.innerText = text;
  label.className = "el-checkbox";
  label.style.fontSize = "1.2rem";
  label.style.fontWeight = "400";
  checkbox_container.append(label);
  const middle_label = document.createElement("label");
  middle_label.className = "el-checkbox__input";
  middle_label.style.marginRight = "4px";
  middle_label.style.verticalAlign = "text-top";
  label.prepend(middle_label);
  const button_label = document.createElement("label");
  button_label.className = "el-checkbox__inner";
  button_label.style.cursor = "pointer";
  middle_label.append(button_label);
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.className = "el-checkbox__original";
  checkbox.readOnly = true;
  checkbox.indeterminate = true;
  button_label.prepend(checkbox);

  checkbox.onclick = () => {
    if (checkbox.readOnly) {
      checkbox.checked = checkbox.readOnly = false;
    } else if (!checkbox.checked) {
      checkbox.readOnly = checkbox.indeterminate = true;
    }
  };

  checkbox.onchange = () => {
    if (checkbox.checked) {
      label.className += " is-checked";
      middle_label.className += " is-checked";
    } else {
      label.classList.remove("is-checked");
      middle_label.classList.remove("is-checked");
    }
  };

  return checkbox;
}

/**
 * Creates a button.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string} text The text inside the button.
 * @returns The button element.
 */
function createButton(container, text) {
  const button = document.createElement("button");
  button.innerText = text;
  button.style.backgroundColor = "rgb(var(--color-blue-600))";
  button.style.color = "rgb(var(--color-text-bright))";
  button.style.border = "none";
  button.style.borderRadius = "3px";
  button.style.cursor = "pointer";
  button.style.fontSize = "12px";
  button.style.padding = "9px 15px";
  button.style.transition = ".2s";
  container.append(button);
  return button;
}

/**
 * Creates a cancel button.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string} text The text inside the button.
 * @returns The button element.
 */
function createCancelButton(container, text) {
  const button = createButton(container, text);
  button.style.backgroundColor = "rgba(var(--color-background),.8)";
  button.style.color = "rgb(var(--color-text))";
  return button;
}

/**
 * Creates a cancel button with a lighter background.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string} text The text inside the button.
 * @returns The button element.
 */
function createCancelLighterButton(container, text) {
  const button = createButton(container, text);
  button.style.backgroundColor = "rgba(var(--color-foreground),.8)";
  button.style.color = "rgb(var(--color-text))";
  return button;
}

/**
 * Creates a danger button.
 * @param {HTMLElement} container A parent element to append the input to.
 * @param {string} text The text inside the button.
 * @returns The button element.
 */
function createDangerButton(container, text) {
  const button = createButton(container, text);
  button.style.backgroundColor = "rgba(var(--color-red),.8)";
  button.style.color = "rgb(var(--color-white))";
  return button;
}

/**
 * Creates a confirmation popup.
 * @param {string} title_text A text/html title for the popup.
 * @param {string} message_text The text/html content of the popup.
 * @returns The confirm button of the popup.
 */
function createConfirmPopup(title_text, message_text) {
  const modal = document.createElement("div");
  modal.className = "v-modal";
  modal.style.zIndex = "99999";
  document.body.append(modal);

  const wrapper = document.createElement("div");
  wrapper.className = "el-message-box__wrapper";
  wrapper.style.zIndex = "100000";
  document.body.append(wrapper);
  const container = document.createElement("div");
  container.className = "el-message-box";
  wrapper.append(container);

  const header = document.createElement("div");
  header.className = "el-message-box__header";
  container.append(header);
  const title = document.createElement("div");
  title.className = "el-message-box__title";
  title.innerHTML = `<span>${title_text}</span>`;
  header.append(title);
  const close_button = document.createElement("button");
  close_button.className = "el-message-box__headerbtn";
  header.append(close_button);
  const close_button_icon = document.createElement("i");
  close_button_icon.className = "el-message-box__close el-icon-close";
  close_button.append(close_button_icon);

  const content = document.createElement("div");
  content.className = "el-message-box__content";
  container.append(content);
  const message = document.createElement("div");
  message.className = "el-message-box__message";
  message.innerHTML = `<p>${message_text}</p>`;
  content.append(message);

  const buttons = document.createElement("div");
  buttons.className = "el-message-box__btns";
  container.append(buttons);
  const cancel_button = createCancelButton(buttons, "Cancel");
  const confirm_button = createButton(buttons, "Confirm");

  wrapper.addEventListener("click", (e) => {
    // e.stopPropagation() doesn't seem to work so this condition is here
    if (e.target !== wrapper) {
      return;
    }
    modal.remove();
    wrapper.remove();
  });
  close_button.onclick = cancel_button.onclick = () => {
    modal.remove();
    wrapper.remove();
  };

  // Used .addEventListener instead of .onclick
  // so either can be used outside this function
  confirm_button.addEventListener("click", () => {
    modal.remove();
    wrapper.remove();
  });

  return confirm_button;
}

/**
 * Creates an updatable cancel popup.
 * @param {string} initial_title
 * @param {HTMLElement} initial_content
 * @returns The two elements that close the popup with the click event, two functions to update the popup, and a function to close the popup.
 */
function createUpdatableCancelPopup(initial_title, initial_content) {
  const modal = document.createElement("div");
  modal.className = "v-modal";
  modal.style.zIndex = "99999";
  document.body.append(modal);

  const wrapper = document.createElement("div");
  wrapper.className = "el-message-box__wrapper";
  wrapper.style.zIndex = "100000";
  document.body.append(wrapper);
  const container = document.createElement("div");
  container.className = "el-message-box";
  wrapper.append(container);

  const header = document.createElement("div");
  header.className = "el-message-box__header";
  container.append(header);
  const title_element = document.createElement("div");
  title_element.className = "el-message-box__title";
  title_element.innerHTML = `<span>${initial_title}</span>`;
  header.append(title_element);

  const content_element = document.createElement("div");
  content_element.className = "el-message-box__content";
  container.append(content_element);
  const message = document.createElement("div");
  message.className = "el-message-box__message";
  message.replaceChildren(initial_content);
  content_element.append(message);

  const buttons = document.createElement("div");
  buttons.className = "el-message-box__btns";
  container.append(buttons);
  const cancel_button = createCancelButton(buttons, "Cancel");

  function closePopup() {
    modal.remove();
    wrapper.remove();
  }

  // Used .addEventListener instead of .onclick
  // so either can be used outside this function
  wrapper.addEventListener("click", (e) => {
    // e.stopPropagation() doesn't seem to work so this condition is here
    if (e.target !== wrapper) {
      return;
    }
    closePopup();
  });
  cancel_button.addEventListener("click", () => {
    closePopup();
  });

  function changeTitle(title) {
    title_element.innerHTML = `<span>${title}</span>`;
  }

  function changeContent(content) {
    message.replaceChildren(content);
  }

  return {
    popup_wrapper: wrapper,
    popup_cancel_button: cancel_button,
    changePopupTitle: changeTitle,
    changePopupContent: changeContent,
    closePopup: closePopup,
  };
}

/**
 * Creates entry specific content to add to a popup.
 * @param {string} text Text to display to the left of the cover.
 * @param {string} cover The cover to add as a background-image style.
 * @param {number} current_index The index of the current entry being show.
 * @param {number} total The total entries going to be shown.
 * @returns
 */
function createEntryPopupContent(text, cover, current_index, total) {
  const content = document.createElement("div");
  content.style.display = "flex";
  content.style.flexWrap = "wrap";
  content.style.flexDirection = "column";
  content.style.gap = "10px";
  content.style.justifyContent = "center";
  content.style.alignItems = "center";
  content.style.textAlign = "center";
  const content_text = document.createElement("span");
  content_text.innerHTML = text;
  content_text.style.flexGrow = "1";
  content.append(content_text);
  const content_image = document.createElement("div");
  content_image.style.backgroundImage = cover;
  content_image.style.backgroundPosition = "50%";
  content_image.style.backgroundRepeat = "no-repeat";
  content_image.style.backgroundSize = "cover";
  content_image.style.borderRadius = "3px";
  content_image.style.minHeight = "210px";
  content_image.style.minWidth = "150px";
  content.append(content_image);
  const bar = document.createElement("div");
  bar.style.width = "100%";
  bar.style.height = "24px";
  bar.style.display = "flex";
  bar.style.justifyContent = "center";
  bar.style.alignItems = "center";
  bar.style.textAlign = "center";
  bar.style.background = `linear-gradient(90deg, rgb(var(--color-blue)) ${Math.floor(
    (current_index / total) * 100
  )}%, rgb(var(--color-background)) 0)`;
  bar.style.borderRadius = "3px";
  content.append(bar);
  const bar_text = document.createElement("span");
  bar_text.innerText = `${current_index} / ${total}`;
  bar_text.style.boxShadow = `inset 0 0 0 100vw rgb(var(--color-background)),
                              0 0 0 2px rgb(var(--color-background)),
                              0 0 3px 3px rgb(var(--color-background))`;
  bar_text.style.borderRadius = "1px";
  bar.appendChild(bar_text);
  return content;
}

/**
 * Creates an error popup.
 * @param {string} text The message/content of the popup.
 * @returns {Promise<boolean>} A promise that returns if the user asked to cancel.
 */
function createErrorPopup(text) {
  const modal = document.createElement("div");
  modal.className = "v-modal";
  modal.style.zIndex = "99999";
  document.body.append(modal);

  const wrapper = document.createElement("div");
  wrapper.className = "el-message-box__wrapper";
  wrapper.style.zIndex = "100000";
  document.body.append(wrapper);
  const container = document.createElement("div");
  container.className = "el-message-box";
  wrapper.append(container);

  const header = document.createElement("div");
  header.className = "el-message-box__header";
  container.append(header);
  const title = document.createElement("div");
  title.className = "el-message-box__title";
  title.innerHTML = `<span>ERROR<span>`;
  header.append(title);
  const close_button = document.createElement("button");
  close_button.className = "el-message-box__headerbtn";
  header.append(close_button);
  const close_button_icon = document.createElement("i");
  close_button_icon.className = "el-message-box__close el-icon-close";
  close_button.append(close_button_icon);

  const content = document.createElement("div");
  content.className = "el-message-box__content";
  container.append(content);
  const message = document.createElement("div");
  message.className = "el-message-box__message";
  message.innerHTML = `<p>${text}</p>`;
  content.append(message);

  const buttons = document.createElement("div");
  buttons.className = "el-message-box__btns";
  container.append(buttons);
  const cancel_button = createDangerButton(buttons, "Cancel");
  const retry_button = createButton(buttons, "Retry");

  return new Promise((resolve) => {
    wrapper.onclick = (e) => {
      // e.stopPropagation() doesn't seem to work so this condition is here
      if (e.target !== wrapper) {
        return;
      }
      modal.remove();
      wrapper.remove();
      resolve(true);
    };
    close_button.onclick = cancel_button.onclick = () => {
      modal.remove();
      wrapper.remove();
      resolve(true);
    };

    retry_button.onclick = () => {
      modal.remove();
      wrapper.remove();
      resolve(false);
    };
  });
}

/**
 * Creates a popup.
 * @param {string} title_text A text/html title for the popup.
 * @param {string} message_text The text/html content of the popup.
 * @returns {Promise<void>}
 */
function createPopup(title_text, message_text) {
  const modal = document.createElement("div");
  modal.className = "v-modal";
  modal.style.zIndex = "99999";
  document.body.append(modal);

  const wrapper = document.createElement("div");
  wrapper.className = "el-message-box__wrapper";
  wrapper.style.zIndex = "100000";
  document.body.append(wrapper);
  const container = document.createElement("div");
  container.className = "el-message-box";
  wrapper.append(container);

  const header = document.createElement("div");
  header.className = "el-message-box__header";
  container.append(header);
  const title = document.createElement("div");
  title.className = "el-message-box__title";
  title.innerHTML = `<span>${title_text}</span>`;
  header.append(title);
  const close_button = document.createElement("button");
  close_button.className = "el-message-box__headerbtn";
  header.append(close_button);
  const close_button_icon = document.createElement("i");
  close_button_icon.className = "el-message-box__close el-icon-close";
  close_button.append(close_button_icon);

  const content = document.createElement("div");
  content.className = "el-message-box__content";
  container.append(content);
  const message = document.createElement("div");
  message.className = "el-message-box__message";
  message.innerHTML = `<p>${message_text}</p>`;
  content.append(message);

  const buttons = document.createElement("div");
  buttons.className = "el-message-box__btns";
  container.append(buttons);
  const ok_button = createButton(buttons, "Ok");

  return new Promise((resolve) => {
    wrapper.onclick = (e) => {
      // e.stopPropagation() doesn't seem to work so this condition is here
      if (e.target !== wrapper) {
        return;
      }
      modal.remove();
      wrapper.remove();
      resolve();
    };
    close_button.onclick = ok_button.onclick = () => {
      modal.remove();
      wrapper.remove();
      resolve();
    };
  });
}