AniList Delete Button on List Items

Adds a delete button to all items on lists

Installer ce script?
Script suggéré par l'auteur

Vous pourriez également aimer AniList Edit Multiple Media Simultaneously.

Installer ce script

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name        AniList Delete Button on List Items
// @license     MIT
// @namespace   rtonne
// @match       https://anilist.co/*
// @icon        https://www.google.com/s2/favicons?sz=64&domain=anilist.co
// @version     1.1
// @author      Rtonne
// @description Adds a delete button to all items on lists
// @grant       GM.addStyle
// @grant       GM.registerMenuCommand
// @grant       GM.unregisterMenuCommand
// @grant       GM.getValue
// @grant       GM.setValue
// ==/UserScript==

(async () => {
  const inplace =
    "autoconfirm" ===
    GM.registerMenuCommand(
      (await GM.getValue("autoconfirm", false))
        ? "☑ Auto-confirm Deletion"
        : "☐ Auto-confirm Deletion",
      toggleAutoConfirm,
      { id: "autoconfirm", autoClose: false }
    );
  async function toggleAutoConfirm() {
    const new_value = !(await GM.getValue("autoconfirm", false));
    await GM.setValue("autoconfirm", new_value);
    if (!inplace) {
      GM.unregisterMenuCommand("autoconfirm");
    }
    GM.registerMenuCommand(
      new_value ? "☑ Auto-confirm Deletion" : "☐ Auto-confirm Deletion",
      toggleAutoConfirm,
      { id: "autoconfirm", autoClose: false }
    );
  }
})();

GM.addStyle(`
    .rtonne-anilist-listitem-delete-button {
      background: rgb(var(--color-red)) !important;
    }
    .entry-card .rtonne-anilist-listitem-delete-button {
      top: 42px !important;
    }
    .medialist.table.compact .entry .cover {
      display: flex !important;
      max-width: 82px !important;
      min-width: 82px;
      gap: 2px;
      margin-inline: -41px;
    }
    .medialist.table.compact .entry:not(:hover) .cover .image {
      display: none;
    }
    .medialist.table:not(.compact) .entry:hover .cover {
      display: flex !important;
      max-width: 102px !important;
      min-width: 102px;
      gap: 2px;
    }
    @media (max-width: 760px) {
      .medialist.table:not(.compact) .entry:hover .cover {
        max-width: 82px !important;
        min-width: 82px;
      }
      .medialist.table:not(.compact) .entry:hover {
        padding-left: 97px;
      }
    }
    body.rtonne-anilist-modal-hidden .list-editor-wrap,
    body.rtonne-anilist-messagebox-hidden .el-message-box {
      display: none !important;
    }
  `);

const trash_svg = `
    <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="ellipsis-h" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-ellipsis-h fa-w-16 fa-lg">
      <!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
      <path fill="currentColor" d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
    </svg>
  `;

let current_url = null;

const url_regex =
  /^https:\/\/anilist.co\/user\/.+\/((animelist)|(mangalist))(\/.*)?$/;

// Using observer to run script whenever the body changes
// because anilist doesn't reload when changing page
const observer = new MutationObserver(async () => {
  try {
    let new_url = window.location.href;

    // Because anilist doesn't reload on changing url
    // we have to allow the whole website and check here if we are in a list
    if (!url_regex.test(new_url)) {
      return;
    }

    const elements = await waitForElements(document, ".entry, .entry-card");

    // If the url is different we are in a different playlist
    // Or if the playlist length is different, we loaded more of the same playlist
    if (
      current_url === new_url &&
      elements.length ===
        document.querySelectorAll(".rtonne-anilist-listitem-delete-button")
          .length
    ) {
      return;
    }

    current_url = new_url;

    // If we have actions in the banner, it's not our list and can't edit it
    if (
      document.querySelector(".banner-content .actions").children.length > 0
    ) {
      return;
    }

    elements.forEach((parent) => {
      const element = parent.querySelector(".cover");
      const is_card = parent.classList.contains("entry-card");

      // We return if the item already has a delete button so
      // there isn't an infinite loop where adding a button triggers
      // the observer which adds more buttons
      if (element.querySelector(".rtonne-anilist-listitem-delete-button"))
        return;

      const button = document.createElement("div");
      button.className = "rtonne-anilist-listitem-delete-button edit";
      button.innerHTML = trash_svg;
      element.querySelector(".edit").after(button);

      button.onclick = async () => {
        const autoconfirm = await GM.getValue("autoconfirm", false);
        if (autoconfirm) {
          document.body.classList.add("rtonne-anilist-messagebox-hidden");
        }
        document.body.classList.add("rtonne-anilist-modal-hidden");

        const edit_button = element.querySelector(
          ".edit:not(.rtonne-anilist-listitem-delete-button)"
        );
        edit_button.click();

        const [dialog_delete_button] = await waitForElements(
          document,
          ".list-editor-wrap .delete-btn"
        );
        dialog_delete_button.click();

        const [confirm_ok_button] = await waitForElements(
          document,
          ".el-message-box .el-button--small.el-button--primary"
        );
        // I need to wait for the confirm cancel button as well so it all load properly, somehow
        await waitForElements(
          document,
          ".el-message-box .el-button--small:not(.el-button--primary)"
        );

        if (autoconfirm) {
          const fading_in_confirm_message_container = document.querySelector(
            ".el-message-box__wrapper.msgbox-fad-enter-active"
          );
          // Wait until message container finished fading in
          await waitForElementToBeRemovedOrHidden(
            fading_in_confirm_message_container
          );

          confirm_ok_button.click();

          const new_confirm_cancel_button = document.querySelector(
            ".list-editor-wrap .delete-btn"
          );
          await waitForElementToBeRemovedOrHidden(new_confirm_cancel_button);

          document.body.classList.remove("rtonne-anilist-messagebox-hidden");
          document.body.classList.remove("rtonne-anilist-modal-hidden");
        } else {
          const confirm_message_container = document.querySelector(
            ".el-message-box__wrapper"
          );
          await waitForElementToBeRemovedOrHidden(confirm_message_container);

          const dialog_close_button = document.querySelector(
            ".list-editor-wrap button:has(> i.el-icon-close)"
          );
          dialog_close_button.click();

          document.body.classList.remove("rtonne-anilist-modal-hidden");
        }
      };
    });
  } catch (err) {
    console.log(err);
  }
});
observer.observe(document.body, {
  childList: true,
  subtree: true,
});

// https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
// This function is required because elements take a while to appear sometimes
function waitForElements(node, selector) {
  return new Promise((resolve) => {
    if (node.querySelector(selector)) {
      return resolve(node.querySelectorAll(selector));
    }

    const observer = new MutationObserver(() => {
      if (node.querySelector(selector)) {
        observer.disconnect();
        resolve(node.querySelectorAll(selector));
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  });
}
function waitForElementToBeRemovedOrHidden(element) {
  return new Promise((resolve) => {
    if (!document.contains(element) || element.style.display === "none") {
      return resolve(null);
    }

    const observer = new MutationObserver(() => {
      if (!document.contains(element) || element.style.display === "none") {
        observer.disconnect();
        resolve(null);
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributeOldValue: true,
      attributeFilter: ["style"],
    });
  });
}