AniList Delete Button on List Items

Adds a delete button to all items on lists

Εγκατάσταση αυτού του κώδικαΒοήθεια
Κώδικας προτεινόμενος από τον δημιιουργό

Μπορεί, επίσης, να σας αρέσει ο κώδικας AniList Edit Multiple Media Simultaneously.

Εγκατάσταση αυτού του κώδικα

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==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"],
    });
  });
}