Greasy Fork

Greasy Fork is available in English.

Tokopedia Select/Unselect All Wishlist

Select/unselect all Tokopedia wishlist items of a page

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Tokopedia Select/Unselect All Wishlist
// @name:id         Tokopedia Pilih Semua/Hapus Semua Pilihan  Wishlist
// @description     Select/unselect all Tokopedia wishlist items of a page
// @description:id  Pilih semua/hapus semua pilihan item wishlist Tokopedia dalam satu halaman
// @icon            https://ecs7.tokopedia.net/assets-tokopedia-lite/prod/icon512.png
// @match           https://www.tokopedia.com/*
// @grant           none
// @version         0.18
// @license         GPL-3.0-only
// @namespace       https://gitlab.com/undrilled
// @author          undrilled
// ==/UserScript==

(function() {
  'use strict';

  // Injection retry duration
  const RUN_INTERVAL = 250;
  // Browser path check
  const PATH_CHECK_REGEX = '^/wishlist/(all|collection/.+)';
  // Select all button properties
  const SELECT_ALL_BUTTON_CLASS_NAME = 'undrilled_select_all_btn';
  const UNSELECT_ALL_BUTTON_CLASS_NAME = 'undrilled_unselect_all_btn';
  const SELECT_ALL_BUTTON_LABEL = 'Centang semua';
  const UNSELECT_ALL_BUTTON_LABEL = 'Hapus centang';
  // Selector targets
  const MANAGE_LABEL = 'Atur';
  const CANCEL_LABEL = 'Batal';
  const MANAGE_SELECTOR = '.manage';
  const PRODUCTS_SELECTOR = '.content__grid .product';
  const PRODUCT_TARGET_SELECTOR = '.target';

  const NOT_READY_MESSAGE = 'Manage button is not yet loaded. Retrying Select All Wishlist injection in ' + RUN_INTERVAL + 'ms.';

  let documentUrl = '';
  let observer = null;

  const isCorrectPage = () => {
    return !! window.location.pathname.match(PATH_CHECK_REGEX);
  }
  const showIsNotReadyMessage = () => {
    console.info(NOT_READY_MESSAGE);
  }
  const getManageButtonContainer = () => {
    return document.querySelector(MANAGE_SELECTOR) ?? null;
  }
  const getProducts = () => {
    return document.querySelectorAll(PRODUCTS_SELECTOR) ?? null;
  }
  const isProductSelected = (product) => {
    return product.querySelector('input').checked;
  }
  const getProductTarget = (product) => {
    return product.querySelector(PRODUCT_TARGET_SELECTOR) ?? null;
  }
  const getAnyManageButton = () => {
    const btnEl = getManageButtonContainer()?.querySelectorAll('button');
    if (!btnEl) {
      return null;
    }
    return Array
            .from(btnEl)
            .find(el => (el.innerText === MANAGE_LABEL || el.innerText === CANCEL_LABEL)) ?? null;
  }
  const getManageButton = () => {
    const el = getAnyManageButton();
    return el?.innerText === MANAGE_LABEL ? el : null;
  }
  const getCancelButton = () => {
    const el = getAnyManageButton();
    return el?.innerText === CANCEL_LABEL ? el : null;
  }
  const getSelectAllButton = () => {
    return document.querySelector('.' + SELECT_ALL_BUTTON_CLASS_NAME);
  }
  const getUnselectAllButton = () => {
    return document.querySelector('.' + UNSELECT_ALL_BUTTON_CLASS_NAME);
  }
  const isAnyManageButtonReady = () => {
    return !! getAnyManageButton();
  }
  const isManageButtonReady = () => {
    return !! getManageButton();
  }
  const isCancelButtonReady = () => {
    return !! getCancelButton();
  }
  const isInjected = () => {
    return !! observer;
  }

  const handleSelectAll = () => {
    getProducts().forEach(el => {
      if (isProductSelected(el)) {
        return;
      }
      getProductTarget(el).click();
    });
  }
  const handleClearSelection = () => {
    getProducts().forEach(el => {
      if (!isProductSelected(el)) {
        return;
      }
      getProductTarget(el).click();
    })
  }
  const inject = () => {
    const manageEl = getAnyManageButton();
    const newButtons = [
      {
        label: SELECT_ALL_BUTTON_LABEL,
        class: SELECT_ALL_BUTTON_CLASS_NAME,
        event: handleSelectAll
      },
      {
        label: UNSELECT_ALL_BUTTON_LABEL,
        class: UNSELECT_ALL_BUTTON_CLASS_NAME,
        event: handleClearSelection,
      }
    ];
    for (const x of newButtons) {
      const el = manageEl.cloneNode(true);
      el.classList.add(x.class);
      el.innerText = x.label;
      el.addEventListener('click', x.event);
      getManageButtonContainer().insertBefore(el, manageEl);
    }
  }
  const removeSelectAll = () => {
    const selectAllButton = getSelectAllButton();
    if (!selectAllButton) {
      return;
    }
    getManageButtonContainer()?.removeChild(selectAllButton);
  }
  const removeUnselectAll = () => {
    const unselectAllButton = getUnselectAllButton();
    if (!unselectAllButton) {
      return;
    }
    getManageButtonContainer()?.removeChild(unselectAllButton);
  }
  const removeAll = () => {
    removeSelectAll();
    removeUnselectAll();
  }

  const addHookManageChange = (fn) => {
    observer = new MutationObserver(fn);
    observer.observe(getAnyManageButton(), {
      characterData: true,
      subtree: true
    });
    // Run it once
    fn();
  }
  const removeHookManageChange = (fn) => {
    if (!observer) {
      return;
    }
    fn();
    observer.disconnect();
    observer = null;
  }

  const patchPathChange = () => {
    const handlePathChange = () => {
      requestAnimationFrame(() => {
        if (documentUrl !== location.href) {
          documentUrl = location.href;
          removeHookManageChange(() => {
            removeAll();
          });
          if (!isCorrectPage()) {
            return;
          }
          setup();
        }
      });
    }
    document.addEventListener('click', handlePathChange);
    document.addEventListener('keyup', handlePathChange);
  }

  const setup = () => {
    const intervalId = setInterval(() => {
      if (!isAnyManageButtonReady()) {
        showIsNotReadyMessage();
        return;
      }
      if (isInjected()) {
        clearInterval(intervalId);
        return;
      }

      addHookManageChange(() => {
        if (!isCancelButtonReady()) {
          removeAll();
          return;
        }
        inject();
      });

      clearInterval(intervalId);
    }, RUN_INTERVAL);
  }

  setup();
  patchPathChange();
})();