DFavCouponFilter

DLsite内のお気に入り作品一覧で、クーポン対象の作品のみをフィルターする機能を追加します

// ==UserScript==
// @name         DFavCouponFilter
// @namespace    sgthr7/monkey-script
// @version      0.0.2
// @author       SGThr7
// @description  DLsite内のお気に入り作品一覧で、クーポン対象の作品のみをフィルターする機能を追加します
// @license      MIT
// @match        https://www.dlsite.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js
// @grant        GM_addStyle
// ==/UserScript==

(t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const a=document.createElement("style");a.textContent=t,document.head.append(a)})(" .expand_button[data-v-950d95b9]{width:183px;font-size:12px;display:flex;justify-content:space-between;padding:3px 8px 6px}.expand_icon[data-v-950d95b9]{display:inline-block;width:12px;margin-top:1px;transition:transform .05s}.expand_button[aria-expanded=true] .expand_icon[data-v-950d95b9]{transform:rotate(180deg)}.expand_button[aria-expanded=true]~.content[data-v-950d95b9]{display:block}.expand_button[aria-expanded=false]~.content[data-v-950d95b9]{display:none}.content[data-v-950d95b9]{border:1px solid gray;border-radius:0 10px 10px;padding:5px 10px}.coupons[data-v-3eec1958]{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}.no-target-coupons[data-v-3eec1958]{margin-top:20px} ");

(function (vue) {
  'use strict';

  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  async function fetchCoupons() {
    const couponsUrl = "https://www.dlsite.com/books/mypage/coupon/list/ajax";
    const couponsRaw = await fetch(couponsUrl);
    if (!couponsRaw.ok) {
      throw new Error(`HTTP error: status=${couponsRaw.status}`);
    }
    const coupons = await couponsRaw.json();
    return coupons;
  }
  async function fetchProductInfo(productId) {
    const info = await fetchProductInfos([productId]);
    return info[productId];
  }
  async function fetchProductInfos(productIds) {
    const baseUrl = "https://www.dlsite.com/maniax/product/info/ajax";
    const productSearchParam = "product_id";
    const separator = ",";
    const requestIds = productIds.join(separator);
    const searchParams = new URLSearchParams({
      [productSearchParam]: requestIds
    });
    const url = new URL(`${baseUrl}?${searchParams}`);
    const infosRes = await fetch(url);
    if (!infosRes.ok) {
      throw new Error(`Failed to fetch product info: ${url}`);
    }
    const products = await infosRes.json();
    return products;
  }
  function parseProductId(content) {
    var _a, _b;
    const contentInfoDom = content.querySelector("dl.work_1col");
    const contentUrlRaw = (_a = contentInfoDom == null ? void 0 : contentInfoDom.querySelector("a")) == null ? void 0 : _a.getAttribute("href");
    if (contentUrlRaw == null) {
      console.error("Content URL not found", content);
      return null;
    }
    const contentUrl = new URL(contentUrlRaw);
    const productId = (_b = contentUrl.pathname.split("/").at(-1)) == null ? void 0 : _b.split(".").at(0);
    if (productId == null) {
      console.error(`Failed to parse product ID: ${contentUrlRaw}`);
      return null;
    }
    return productId;
  }
  function parseId(productDom) {
    var _a;
    const productLink = productDom.querySelector(".work_name > a");
    const urlRaw = productLink == null ? void 0 : productLink.getAttribute("href");
    if (urlRaw == null) {
      console.warn("Failed to find product link", productDom);
      return null;
    }
    const productUrl = new URL(urlRaw);
    const productId = (_a = productUrl.pathname.split("/").at(-1)) == null ? void 0 : _a.split(".").at(0);
    if (productId == null) {
      console.warn(`Failed to parse product ID: ${urlRaw}`);
      return null;
    }
    return productId;
  }
  function parseTitle(productDom) {
    const productLink = productDom.querySelector(".work_name > a");
    const title = (productLink == null ? void 0 : productLink.getAttribute("title")) ?? (productLink == null ? void 0 : productLink.textContent);
    if (title == null) {
      console.warn("Failed to find product link", productDom);
      return null;
    }
    return title;
  }
  class DProduct {
    // MARK: Constructor
    constructor(productId, dom) {
      __publicField(this, "id");
      // TODO: 自動ページ送り拡張などを使うとDOMが複数になることがあるため、複数DOMを操作できるようにしたい
      __publicField(this, "dom");
      __publicField(this, "info");
      __publicField(this, "promiseFetchInfo");
      this.id = productId;
      this.dom = dom;
      this.info = void 0;
    }
    /**
     * @returns DOMから取得した作品IDを持つDProduct。作品IDが取得できなかった場合はnullを返す。
     */
    static tryFromDom(dom) {
      const productId = parseId(dom);
      if (productId == null) {
        console.error("Failed to find product ID", dom);
        return null;
      }
      return new DProduct(productId, dom);
    }
    // MARK: 初期化
    /**
     * 非同期で作品情報を取得
     */
    async asyncFetchInfo() {
      if (this.promiseFetchInfo == null) {
        this.info = void 0;
        this.promiseFetchInfo = new Promise(async (resolve) => {
          const resInfo = await fetchProductInfo(this.id);
          this.info = resInfo;
          resolve();
        });
      }
      await this.promiseFetchInfo;
      return this.info;
    }
    // MARK: DOM操作
    isVisible() {
      return !this.dom.hidden;
    }
    setIsVisible(isVisible) {
      this.dom.hidden = !isVisible;
    }
    // MARK: Info (async)
    /**
     * @note `async`版は`asyncFetchInfo()`を使用する
     */
    getInfo() {
      return this.info;
    }
    setInfo(val) {
      this.info = val;
    }
    // 外部からPromiseを登録する
    registerFetchInfoPromise(promise) {
      this.promiseFetchInfo = promise;
    }
    // MARK: Accessor
    getId() {
      return this.id;
    }
    getTitle() {
      var _a;
      return ((_a = this.info) == null ? void 0 : _a.title_name) ?? parseTitle(this.dom) ?? "[No Title]";
    }
    async asyncGetMakerId() {
      await this.asyncFetchInfo();
      return this.getMakerIdCache();
    }
    getMakerIdCache() {
      var _a;
      console.assert(this.info != null, "Product info has not been fetched yet");
      return ((_a = this.info) == null ? void 0 : _a.maker_id) ?? "";
    }
    async asyncGetCustomGenres() {
      await this.asyncFetchInfo();
      return this.getCustomGenresCache();
    }
    getCustomGenresCache() {
      var _a;
      console.assert(this.info != null, "Product info has not been fetched yet");
      return ((_a = this.info) == null ? void 0 : _a.custom_genres) ?? [];
    }
    async getSiteId() {
      await this.asyncFetchInfo();
      return this.getSiteIdCache();
    }
    getSiteIdCache() {
      var _a;
      console.assert(this.info != null, "Product info has not been fetched yet");
      return (_a = this.info) == null ? void 0 : _a.site_id;
    }
    getDom() {
      return this.dom;
    }
  }
  function compareDiscountType(a, b) {
    switch (a) {
      case "rate": {
        switch (b) {
          case "rate": {
            return 0;
          }
          case "price": {
            return -1;
          }
        }
      }
      case "price": {
        switch (b) {
          case "rate": {
            return 1;
          }
          case "price": {
            return 0;
          }
        }
      }
    }
    console.trace(`Unexpected discount type (${a}, ${b})`);
    return 0;
  }
  class DCoupon {
    constructor(info) {
      __publicField(this, "info");
      this.info = info;
    }
    getId() {
      return this.info.coupon_id;
    }
    getName() {
      return this.info.coupon_name;
    }
    getDiscountRate() {
      return parseInt(this.info.discount);
    }
    getDiscountType() {
      return this.info.discount_type;
    }
    /**
     * @return クーポンの使用期限
     */
    getUseLimitDate() {
      const limitDateSec = this.info.limit_date;
      const limitDateMsec = limitDateSec * 1e3;
      const limitDate = new Date(limitDateMsec);
      return limitDate;
    }
    /**
     * @returns 有効なクーポンかどうか
     */
    isAvailable() {
      const hasPresented = true;
      const limitDate = this.getUseLimitDate();
      const currentDate = /* @__PURE__ */ new Date();
      const isInLimitDate = limitDate != null ? currentDate <= limitDate : false;
      return hasPresented && isInLimitDate;
    }
    /**
     * @param product 対象の作品
     * @returns クーポン対象の作品かどうか
     */
    async canDiscount(product) {
      var _a, _b, _c;
      switch (this.info.condition_type) {
        case "id_all": {
          return ((_a = this.info.conditions.product_all) == null ? void 0 : _a.some(
            (productId) => productId === product.getId()
          )) ?? false;
        }
        case "custom_genre": {
          const pGenres = await product.asyncGetCustomGenres();
          return ((_b = this.info.conditions.custom_genre) == null ? void 0 : _b.some(
            (cGenre) => pGenres.some((pGenre) => cGenre === pGenre)
          )) ?? false;
        }
        case "site_ids": {
          const pSiteId = await product.getSiteId();
          return ((_c = this.info.conditions.site_ids) == null ? void 0 : _c.includes(pSiteId)) ?? false;
        }
      }
      console.trace(`Unexpected condition type "${this.info.condition_type}"`);
      return false;
    }
    compare(other) {
      const cmpDiscountType = compareDiscountType(this.getDiscountType(), other.getDiscountType());
      if (cmpDiscountType !== 0) return cmpDiscountType;
      const cmpDiscount = other.getDiscountRate() - this.getDiscountRate();
      if (cmpDiscount !== 0) return cmpDiscount;
      const maxDate = /* @__PURE__ */ new Date(864e13);
      const cmpLimitDate = (this.getUseLimitDate() ?? maxDate).getTime() - (other.getUseLimitDate() ?? maxDate).getTime();
      return cmpLimitDate;
    }
  }
  class MultiFactorBooleans {
    constructor() {
      /**
       * ある要因に基づいたBooleanの値
       */
      __publicField(this, "factors");
      /**
       * Booleanをまとめた結果のキャッシュ
       */
      __publicField(this, "result");
      this.factors = /* @__PURE__ */ new Map();
      this.result = this.getInitialValue();
    }
    /**
     * Booleanをまとめる際の演算子
     * @param lhs 左辺
     * @param rhs 右辺
     * @returns まとめた演算結果
     */
    operate(lhs, rhs) {
      console.error("Not implemented `operate` method");
      return false;
    }
    getInitialValue() {
      console.error("Not implemented `initialValue` method");
      return false;
    }
    /**
     * 演算結果のキャッシュを再演算し、その結果を返す
     * 
     * @returns まとめた演算結果
     */
    recalculateResult() {
      this.result = this.factors.values().reduce((acc, val) => this.operate(acc, val), this.getInitialValue());
      return this.result;
    }
    /**
     * 指定した要因の値をセットする
     * @param factor 要因名
     * @param value 値
     * @returns まとめた演算結果
     */
    setValue(factor, value) {
      this.setValueImpl(factor, value);
      return this.recalculateResult();
    }
    setValueImpl(factor, value) {
      this.factors.set(factor, value);
    }
    /**
     * 指定した要因の値を取得する
     * @param factor 要因名
     * @returns 指定した要因の値。要因が設定されていない場合は`false`を返す
     */
    getValue(factor) {
      return this.factors.get(factor) ?? false;
    }
    /**
     * @returns まとめた演算結果
     */
    getResult() {
      return this.result;
    }
    /**
     * @param factor 要因名
     * @returns 要因が設定されているかどうか
     */
    hasFactor(factor) {
      return this.factors.has(factor);
    }
    /**
     * 指定したFactorを削除する
     * @param factor 要因名
     * @returns 削除したあとのまとめた演算結果
     */
    removeFactor(factor) {
      this.removeFactorImpl(factor);
      return this.recalculateResult();
    }
    removeFactorImpl(factor) {
      this.factors.delete(factor);
    }
    /**
     * @returns 任意の要因が設定されているかどうか
     */
    hasAnyFactor() {
      return this.factors.size > 0;
    }
    /**
     * すべての要因を削除する
     */
    clearFactors() {
      this.factors.clear();
      this.result = this.getInitialValue();
    }
  }
  class OrBooleans extends MultiFactorBooleans {
    operate(lhs, rhs) {
      return lhs || rhs;
    }
    getInitialValue() {
      return false;
    }
    recalculateResult() {
      this.result = this.factors.values().some((val) => val);
      return this.result;
    }
    setValue(factor, value) {
      if (this.hasFactor(factor) && !value) {
        return super.setValue(factor, value);
      } else {
        this.setValueImpl(factor, value);
        this.result = this.operate(this.result, value);
        return this.result;
      }
    }
    removeFactor(factor) {
      if (!this.getValue(factor)) {
        this.removeFactorImpl(factor);
        return this.result;
      } else {
        return super.removeFactor(factor);
      }
    }
  }
  function objEntryIter(obj) {
    return Iterator.from(Object.entries(obj));
  }
  function isValidEntryKey(entry) {
    return entry[0] != null;
  }
  const _DPCManager = class _DPCManager {
    constructor() {
      __publicField(this, "products");
      __publicField(this, "couponFilter");
      __publicField(this, "coupons");
      __publicField(this, "discountableCouponMap");
      __publicField(this, "filterCoupons");
      __publicField(this, "observerAddWishlistDom");
      __publicField(this, "promiseFetchCoupons");
      this.products = /* @__PURE__ */ new Map();
      this.couponFilter = /* @__PURE__ */ new Map();
      this.coupons = /* @__PURE__ */ new Set();
      this.discountableCouponMap = /* @__PURE__ */ new Map();
      this.filterCoupons = /* @__PURE__ */ new Set();
    }
    //MARK: 初期化
    init() {
      console.log("init");
      this.bindOnAddedWishlistDom();
      this.asyncFetchCoupons();
      this.collectAndRegisterProducts(document);
      this.asyncInitLink();
    }
    clear() {
      this.unbindOnAddedWishlistDom();
      Iterator.from(this.products.values()).forEach((product) => product.setIsVisible(true));
      this.products.clear();
      this.couponFilter.clear();
      this.coupons.clear();
    }
    // MARK: 作品管理
    /**
     * 作品を管理対象へ追加
     * @param product 追加する作品
     */
    registerProduct(product) {
      const productId = product.getId();
      const productExists = this.products.has(productId);
      console.assert(!productExists, `Exists product: ID=${productId}`);
      if (!productExists) {
        console.log(`Register product: "${product.getTitle()}"`, product, product.getDom());
        this.products.set(productId, product);
        this.couponFilter.set(productId, new OrBooleans());
      }
    }
    /**
     * 作品を管理対象へ追加
     * @param products 追加する作品のリスト
     */
    registerProducts(products) {
      const productIds = Iterator.from(products.values()).map((product) => product.getId()).toArray();
      const mapProducts = ([productIds2, info]) => [this.products.get(productIds2), info];
      const fetchPromise = fetchProductInfos(productIds).then((infos) => {
        objEntryIter(infos).map(mapProducts).filter(isValidEntryKey).forEach(([product, info]) => product.setInfo(info));
      });
      Iterator.from(products.values()).forEach((product) => {
        this.registerProduct(product);
        product.registerFetchInfoPromise(fetchPromise);
      });
    }
    async asyncInitLink() {
      const linkPromises = Iterator.from(this.products.values()).map(async (product) => {
        await this.asyncLinkToCouponsCache(product);
      });
      await Promise.allSettled(linkPromises);
    }
    /**
     * 作品と所持中のクーポンを対応付ける
     */
    async asyncLinkToCouponsCache(product) {
      await this.asyncWaitFetchCoupons();
      const asyncIter = Iterator.from(this.coupons.values()).map((coupon) => this.asyncLink(product, coupon));
      await Promise.allSettled(asyncIter);
    }
    /**
     * 作品とクーポンを対応付ける
     */
    async asyncLink(product, coupon) {
      const filterResult = this.getCouponFilterResult(product.getId());
      const canDiscount = await coupon.canDiscount(product);
      filterResult.setValue(coupon.getId(), canDiscount);
      if (canDiscount) {
        const targets = this.discountableCouponMap.get(coupon.getId());
        targets == null ? void 0 : targets.add(product.getId());
      }
    }
    // MARK: DOM追加の監視
    // 自動ページ送り拡張など用
    /**
     * 自動ページ送りなどで追加された作品を検知する
     */
    bindOnAddedWishlistDom() {
      const container = document.querySelector("div#wishlist");
      if (container == null) {
        console.error("Failed to find wishlist container");
        return;
      }
      this.observerAddWishlistDom = new MutationObserver((records, observer) => this.onAddWishlistDom(records, observer));
      this.observerAddWishlistDom.observe(container, {
        subtree: false,
        childList: true
      });
    }
    onAddWishlistDom(records, _observer) {
      console.groupCollapsed("On add wishlist dom");
      console.log(records);
      records.forEach((record) => {
        record.addedNodes.values().filter(_DPCManager.isWishlistContainer).forEach(async (dom) => {
          const addedProducts = this.collectAndRegisterProducts(dom);
          const asyncLinkIter = addedProducts.values().map((product) => this.asyncLinkToCouponsCache(product));
          const asyncLink = Promise.allSettled(asyncLinkIter);
          await asyncLink;
          this.updateProductsVisibility(addedProducts.values());
        });
      });
      console.groupEnd();
    }
    unbindOnAddedWishlistDom() {
      var _a;
      (_a = this.observerAddWishlistDom) == null ? void 0 : _a.disconnect();
    }
    static isWishlistContainer(node) {
      return node instanceof HTMLElement && node.id === _DPCManager.WISHLIST_CONTAINER_ID;
    }
    // MARK: DOM操作
    /**
     * 作品の一覧を取得して登録する
     * @returns 追加した作品一覧
     */
    collectAndRegisterProducts(container) {
      const addedProduct = [];
      console.groupCollapsed("Collect and register products");
      _DPCManager.collectProductDoms(container).map(([productId, dom]) => new DProduct(productId, dom)).forEach((product) => {
        this.registerProduct(product);
        addedProduct.push(product);
      });
      console.groupEnd();
      return addedProduct;
    }
    /**
     * 作品のDOMを取得
     * @param container 取得時のルートDOM
     * @returns 作品DOMのIterator
     */
    static collectProductDoms(container) {
      const products = container.querySelectorAll("form#edit_wishlist > div#wishlist_work > table.n_worklist > tbody > tr._favorite_item");
      const productDomsIter = products.values().map((content) => {
        const product_id = parseProductId(content);
        if (product_id == null) {
          console.error("Failed to find product ID", content);
          return null;
        }
        return [product_id, content];
      }).filter((val) => val != null);
      return productDomsIter;
    }
    /**
     * 現在管理対象の作品の可視性を更新する
     */
    updateAllProductsVisibility() {
      this.updateProductsVisibility(this.products.values());
    }
    /**
     * 指定した管理対象作品の可視性を更新する
     * @param products 対象の管理中作品
     */
    updateProductsVisibility(products) {
      const isNoFilter = this.filterCoupons.size === 0;
      const targetProductsIter = Iterator.from(this.filterCoupons.values()).map((couponId) => this.discountableCouponMap.get(couponId)).filter((targetProducts2) => targetProducts2 != null).flatMap((targetProducts2) => targetProducts2);
      const targetProducts = new Set(targetProductsIter);
      Iterator.from(products).forEach((product) => {
        const isVisible = isNoFilter || targetProducts.has(product.getId());
        product.setIsVisible(isVisible);
      });
    }
    // MARK: クーポン
    /**
     * クーポンの取得を非同期で開始する
     */
    async asyncFetchCoupons() {
      if (this.promiseFetchCoupons == null) {
        this.promiseFetchCoupons = fetchCoupons();
        const resCoupons = await this.promiseFetchCoupons;
        const couponIter = resCoupons.values().map((coupon) => new DCoupon(coupon)).filter((coupon) => coupon.isAvailable());
        this.coupons = new Set(couponIter);
        const discountableCouponsMapIter = Iterator.from(this.coupons.values()).map((coupon) => [coupon.getId(), /* @__PURE__ */ new Set()]);
        this.discountableCouponMap = new Map(discountableCouponsMapIter);
      } else {
        await this.promiseFetchCoupons;
      }
      return this.coupons;
    }
    /**
     * 既に開始しているクーポンの取得を`await`する用の関数
     */
    async asyncWaitFetchCoupons() {
      if (this.promiseFetchCoupons == null) {
        console.warn("No fetching promise");
        return;
      }
      await this.promiseFetchCoupons;
    }
    //MARK: アクセサー
    getCouponFilterResult(productId) {
      console.assert(this.couponFilter.has(productId), `Not registered product: ID=${productId}`);
      return this.couponFilter.get(productId);
    }
    /**
     * クーポンの一覧を取得する。
     * クーポンがまだ取得できていない可能性があるため、必要に応じて`await asyncWaitFetchCoupons()`で取得を待機する必要がある。
     */
    getCoupons() {
      return this.coupons;
    }
    /**
     * クーポン対象の作品一覧を取得する。
     * 情報がまだ未収集の場合はカラのコンテナーが返る。
     */
    getDiscountableCouponMap(couponId) {
      return this.discountableCouponMap.get(couponId) ?? /* @__PURE__ */ new Set();
    }
    addCouponFilter(couponId) {
      this.filterCoupons.add(couponId);
      this.updateAllProductsVisibility();
    }
    removeCouponFilter(couponId) {
      this.filterCoupons.delete(couponId);
      this.updateAllProductsVisibility();
    }
  };
  __publicField(_DPCManager, "WISHLIST_CONTAINER_ID", "edit_wishlist");
  let DPCManager = _DPCManager;
  const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
    __name: "CouponCheckbox",
    props: {
      coupon: {},
      discountTargetCount: {}
    },
    emits: ["onChecked"],
    setup(__props, { emit: __emit }) {
      const name = vue.computed(() => __props.coupon.getName());
      const emit = __emit;
      function onChecked(e) {
        const isChecked = e.target.checked;
        emit("onChecked", isChecked, __props.coupon);
      }
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createElementBlock("div", null, [
          vue.createElementVNode("label", null, [
            vue.createElementVNode("input", {
              type: "checkbox",
              onChange: onChecked
            }, null, 32),
            vue.createElementVNode("span", null, vue.toDisplayString(name.value) + " (" + vue.toDisplayString(_ctx.discountTargetCount) + ") ", 1)
          ])
        ]);
      };
    }
  });
  const CollapseIconFile = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20aria-hidden='true'%20role='img'%20class='collapse-icon'%20width='8'%20height='5'%20preserveAspectRatio='xMidYMid%20meet'%20viewBox='0%200%208%205'%3e%3cpath%20stroke='white'%20fill='transparent'%20stroke-linecap='square'%20d='M1%201L4%204L7%201'%3e%3c/path%3e%3c/svg%3e";
  const _hoisted_1$2 = ["aria-expanded", "aria-controls"];
  const _hoisted_2$1 = ["src"];
  const _hoisted_3$1 = ["id"];
  const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
    __name: "CollapseMenu",
    setup(__props) {
      const contentId = vue.useId();
      const isExpanded = vue.ref(false);
      function onClickButton(_e) {
        isExpanded.value = !isExpanded.value;
      }
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createElementBlock("div", null, [
          vue.createElementVNode("button", {
            type: "button",
            class: "expand_button",
            "aria-expanded": isExpanded.value,
            "aria-controls": vue.unref(contentId),
            onClick: onClickButton
          }, [
            vue.renderSlot(_ctx.$slots, "title", {}, () => [
              _cache[0] || (_cache[0] = vue.createTextVNode(" 表示切り替え "))
            ], true),
            vue.createElementVNode("img", {
              src: vue.unref(CollapseIconFile),
              class: "expand_icon"
            }, null, 8, _hoisted_2$1)
          ], 8, _hoisted_1$2),
          vue.createElementVNode("div", {
            id: vue.unref(contentId),
            class: "content"
          }, [
            vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
          ], 8, _hoisted_3$1)
        ]);
      };
    }
  });
  const _export_sfc = (sfc, props) => {
    const target = sfc.__vccOpts || sfc;
    for (const [key, val] of props) {
      target[key] = val;
    }
    return target;
  };
  const CollapseMenu = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-950d95b9"]]);
  const _hoisted_1$1 = { class: "wrapper" };
  const _hoisted_2 = { key: 0 };
  const _hoisted_3 = { class: "coupons" };
  const _hoisted_4 = { class: "coupons" };
  const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
    __name: "AsyncCouponList",
    async setup(__props) {
      let __temp, __restore;
      const dpcManager = vue.reactive(new DPCManager());
      dpcManager.init();
      vue.onBeforeUnmount(() => {
        dpcManager.clear();
      });
      [__temp, __restore] = vue.withAsyncContext(() => dpcManager.asyncWaitFetchCoupons()), __temp = await __temp, __restore();
      const allCoupons = vue.computed(() => dpcManager.getCoupons());
      const discountCoupons = vue.computed(() => Iterator.from(allCoupons.value.values()).filter((coupon) => getDiscountableCount(coupon) > 0).toArray().sort((a, b) => a.compare(b)));
      const noDiscountCoupons = vue.computed(() => Iterator.from(allCoupons.value.values()).filter((coupon) => getDiscountableCount(coupon) === 0).toArray().sort((a, b) => a.compare(b)));
      function getDiscountableCount(coupon) {
        return dpcManager.getDiscountableCouponMap(coupon.getId()).size;
      }
      function onCouponChecked(isChecked, coupon) {
        if (isChecked) {
          dpcManager.addCouponFilter(coupon.getId());
        } else {
          dpcManager.removeCouponFilter(coupon.getId());
        }
      }
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [
          allCoupons.value.size <= 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2, "所持クーポン無し")) : vue.createCommentVNode("", true),
          vue.createElementVNode("div", _hoisted_3, [
            (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(discountCoupons.value, (coupon) => {
              return vue.openBlock(), vue.createBlock(_sfc_main$3, {
                key: coupon.getId(),
                coupon,
                discountTargetCount: getDiscountableCount(coupon),
                onOnChecked: onCouponChecked
              }, null, 8, ["coupon", "discountTargetCount"]);
            }), 128))
          ]),
          noDiscountCoupons.value.length > 0 ? (vue.openBlock(), vue.createBlock(CollapseMenu, {
            key: 1,
            class: "no-target-coupons"
          }, {
            title: vue.withCtx(() => _cache[0] || (_cache[0] = [
              vue.createTextVNode("割引対象無しクーポン一覧")
            ])),
            default: vue.withCtx(() => [
              vue.createElementVNode("div", _hoisted_4, [
                (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(noDiscountCoupons.value, (coupon) => {
                  return vue.openBlock(), vue.createBlock(_sfc_main$3, {
                    key: coupon.getId(),
                    coupon,
                    discountTargetCount: getDiscountableCount(coupon),
                    onOnChecked: onCouponChecked
                  }, null, 8, ["coupon", "discountTargetCount"]);
                }), 128))
              ])
            ]),
            _: 1
          })) : vue.createCommentVNode("", true)
        ]);
      };
    }
  });
  const AsyncCouponList = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-3eec1958"]]);
  const _hoisted_1 = { class: "border_b" };
  const _sfc_main = /* @__PURE__ */ vue.defineComponent({
    __name: "CouponFilter",
    setup(__props) {
      return (_ctx, _cache) => {
        return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
          (vue.openBlock(), vue.createBlock(vue.Suspense, null, {
            default: vue.withCtx(() => [
              vue.createVNode(AsyncCouponList)
            ]),
            fallback: vue.withCtx(() => _cache[0] || (_cache[0] = [
              vue.createElementVNode("div", { class: "loading" }, "Loading...", -1)
            ])),
            _: 1
          }))
        ]);
      };
    }
  });
  main();
  function main() {
    const bookmarkUrlPattern = new RegExp("^https?://(www.)?dlsite.com/(\\w+)/mypage/wishlist/?.*", "i");
    const currentUrl = window.location.href;
    if (bookmarkUrlPattern.test(currentUrl)) {
      createFilterBox();
    }
  }
  function createFilterBox() {
    var _a;
    const filterBoxRoot = document.createElement("div");
    const filterBox = vue.createApp(_sfc_main);
    filterBox.mount(filterBoxRoot);
    const insertAnchor = document.querySelector("div#wishlist > form#showList");
    if (insertAnchor == null) return;
    (_a = insertAnchor.parentNode) == null ? void 0 : _a.insertBefore(filterBoxRoot, insertAnchor.nextElementSibling);
  }

})(Vue);