Greasy Fork

CIDWWA

Code I don't want to write again

目前为 2022-06-13 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/441792/1060343/CIDWWA.js

// ==UserScript==
// @name         CIDWWA
// @namespace    http://tampermonkey.net/
// @version      0.2.3
// @description  Code I don't want to write again
// @author       Gorbit99
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wanikani.com
// @grant        none
// @license      MIT
// ==/UserScript==
"use strict";

const version = 3;

class Modal {
  #container;
  #itemContainer;
  #modal;

  #open = false;
  #onOpen = [];
  #onClose = [];

  #draggable;

  constructor(config) {
    this.#container = document.createElement("div");
    const dashboard = document.querySelector(".dashboard");
    if (dashboard) {
      dashboard.append(this.#container);
    } else {
      document.body.append(this.#container);
    }
    this.#container.style.position = "fixed";
    this.#container.style.inset = "0";
    this.#container.style.display = "none";
    if ((config.clickOutAction ?? "none") === "none") {
      this.#container.style.pointerEvents = "none";
    } else {
      this.#container.style.pointerEvents = "auto";
    }
    this.#container.style.zIndex = "9999";
    this.#container.style.background =
      config.tintBackground ? "#000a" : "transparent";

    this.#draggable = !config.preventDragging;

    let modalStyle = `
      background: #f4f4f4;
      border: 1px solid black;
      border-radius: 5px;
      padding: 16px 12px 12px;
      pointer-events: auto;
      margin: 0;
      position: absolute;
    `;

    const itemContainerStyle = `
      border-radius: 5px;
      padding: 16px;
      display: flex;
      justify-content: center;
    `;

    const headerStyle = `
      display: flex;
      justify-content: space-between;
      user-select: none;
    `;

    let dragStyle = `
      position: absolute;
      top: 0;
      left: 0;
      height: 48px;
      width: calc(100% - 48px);
    `;

    if (config.preventDragging) {
      modalStyle += `
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      `;
    } else {
      dragStyle += `
        cursor: move;
      `;
    }

    this.#container.innerHTML = `
      <section style="${modalStyle}" class="cidwwa-modal">
        <div style="${headerStyle}" class="cidwwa-header">
          <h3 style="margin: 0 0 10px 12px">${config.title ?? ""}</h3>
          <i class="fa fa-times" style="font-size: 20px; cursor: pointer;"></i>
        </div>
        <div class="cidwwa-drag" style="${dragStyle}"></div>
        <div style="${itemContainerStyle}" class="modal-itemcontainer bg-white">

        </div>
      </div>
    `;

    if (config.clickOutAction === "close") {
      this.#container.addEventListener("click", () => this.close());
    }

    this.#itemContainer = this.#container.querySelector(".modal-itemcontainer");
    this.#container.querySelector("i").addEventListener("click", () => this.close());

    if (!config.preventDragging) {
      const drag = this.#container.querySelector(".cidwwa-drag");
      this.#modal = this.#container.querySelector(".cidwwa-modal");

      let startX;
      let startY;
      let isDragging = false;
      drag.addEventListener("mousedown", (e) => {
        if (e.button !== 0) {
          return;
        }
        startX = e.offsetX;
        startY = e.offsetY;
        isDragging = true;
      });

      window.addEventListener("mousemove", (e) => {
        if (!isDragging) {
          return;
        }
        e.preventDefault();
        const mousePosX = e.clientX;
        const mousePosY = e.clientY;

        let newPosX = mousePosX - startX;
        let newPosY = mousePosY - startY;

        const screenWidth = document.body.clientWidth;
        const screenHeight = document.body.clientHeight;

        newPosX = Math.min(Math.max(newPosX, 0), screenWidth - this.#modal.offsetWidth - 1);
        newPosY = Math.min(Math.max(newPosY, 0), screenHeight - this.#modal.offsetHeight - 1);

        this.#modal.style.left = `${newPosX}px`;
        this.#modal.style.top = `${newPosY}px`;
      });

      window.addEventListener("mouseup", (e) => {
        if (!isDragging) {
          return;
        }

        if (e.button !== 0) {
          return;
        }
        isDragging = false;
      });

    }

    this.#modal.addEventListener("click", (e) => {
      e.stopPropagation();
    });
  }

  toggle() {
    this.#open = !this.#open;
    this.#container.style.display = this.#open ? "block" : "none";

    if (this.#open) {
      this.#onOpen.forEach((callback) => callback());
      if (this.#draggable) {
        const screenWidth = document.body.clientWidth;
        const screenHeight = document.body.clientHeight;

        const modalWidth = this.#modal.offsetWidth;
        const modalHeight = this.#modal.offsetHeight;

        const newPosX = (screenWidth - modalWidth) / 2;
        const newPosY = (screenHeight - modalHeight) / 2;

        this.#modal.style.left = `${newPosX}px`;
        this.#modal.style.top = `${newPosY}px`;
      }
    }
    else {
      this.#onClose.forEach((callback) => callback());
    }
    return this.#open;
  }

  open() {
    if (this.#open) {
      return;
    }
    this.toggle();
  }

  close() {
    if (!this.#open) {
      return;
    }
    this.toggle();
  }

  onOpen(callback) {
    this.#onOpen.push(callback);
  }

  onClose(callback) {
    this.#onClose.push(callback);
  }

  setContent(content) {
    if (typeof content === "string") {
      this.#itemContainer.innerHTML = content;
    } else {
      this.#itemContainer.children.forEach((child) => child.remove());
      this.#itemContainer.append(content);
    }
  }
}

class WKButton {
  #container;
  #button;
  #dropdown;

  #onTurnOn = [];
  #onTurnOff = [];
  #state = false;

  constructor(config) {
    const sitemap = document.querySelector("#sitemap");
    this.#container = document.createElement("li");
    sitemap.insertBefore(this.#container, sitemap.firstChild);
    this.#container.classList.add("sitemap__section");
    this.#container.innerHTML = `
        <button class="sitemap__section-header wk-custom-button">
            <span lang="ja">${config.japaneseText}</span>
            <span lang="en">${config.englishText}</span>
        </button>
    `;

    this.#button = this.#container.querySelector(".wk-custom-button");

    if (config.color) {
      this.#button.dataset.color = config.color;
      this.#button.style.setProperty("--focus-color", config.color);
    }
    if (config.hoverColor) {
      this.#button.dataset.hoverColor = config.hoverColor;
      this.#button.style.setProperty("--hover-color", config.hoverColor);
    }

    this.#button.addEventListener("click", () => {
      this.setState(!this.#state);
      if (this.#state) {
        this.#onTurnOn.forEach((callback) => callback());
      }
      else {
        this.#onTurnOff.forEach((callback) => callback());
      }
    });

    if (config.withDropdown) {
      this.attachDropdown(config.withDropdown);
    }
  }

  attachDropdown(config) {
    if (this.#dropdown) {
      throw "A dropdown is already attached!";
    }

    this.#dropdown = document.createElement("div");
    this.#dropdown.style.zIndex = 899;
    this.#dropdown.classList.add("sitemap__expandable-chunk");
    this.#dropdown.dataset.expanded = false;
    this.#container.append(this.#dropdown);

    this.#dropdown.style.setProperty("--dropdown-background", config.bgColor);

    let clickIn = false;
    document.addEventListener("click", () => {
      if (!clickIn && this.#state) {
        this.setState(false);
        this.#onTurnOff.forEach((callback) => callback());
      }
      clickIn = false;
    });

    this.#dropdown.addEventListener("click", () => {
      clickIn = true;
    });

    this.#button.addEventListener("click", () => {
      clickIn = true;
    });
  }

  setDropdownContent(content) {
    if (typeof content === "string") {
      this.#dropdown.innerHTML = content;
    } else {
      this.#dropdown.children.forEach((child) => child.remove());
      this.#dropdown.append(content);
    }
  }

  attachSubtext() {
    let subtext = this.#button.parentNode.querySelector(".button-subtext");
    if (subtext) {
      return subtext;
    }

    const subtextContainerStyle = `
      width: 100%;
      display: inline-flex;
      justify-content: center;
      position: absolute;
      margin-top: 4px;
    `;

    const subtextStyle = `
      color: #999;
      font-size: 14px;
      line-height: 0;
    `;

    this.#button.insertAdjacentHTML("afterend", `
      <span class="button-subtext-container" style="${subtextContainerStyle}">
        <span class="button-subtext" style="${subtextStyle}"></span>
      </span>
    `);

    subtext = this.#button.parentNode.querySelector(".button-subtext");
    return subtext;
  }

  onTurnOn(callback) {
    this.#onTurnOn.push(callback);
  }

  onTurnOff(callback) {
    this.#onTurnOff.push(callback);
  }

  setState(state) {
    this.#state = state;
    this.#button.dataset.expanded = state;
    if (this.#dropdown) {
      this.#dropdown.dataset.expanded = state;
    }
  }
}

if (!window.cidwwaVersion || window.cidwwaVersion < version) {
  window.cidwwaVersion = version;

  window.createModal = function(config) {
    return new Modal(config);
  };

  window.createButton = function(config) {
    return new WKButton(config);
  };

  const styleElement = document.createElement("style");
  document.head.append(styleElement);
  styleElement.innerHTML = `
    .wk-custom-button[data-color][data-expanded="true"],
    .wk-custom-button[data-color]:focus {
      color: var(--focus-color);
      border-color: var(--focus-color);
    }

    .wk-custom-button[data-hover-color]:not([data-expanded="true"]):not(:focus):hover {
      color: var(--focus-color);
      border-color: var(--hover-color);
    }

    .wk-custom-button + .sitemap__expandable-chunk,
    .wk-custom-button + .sitemap__expandable-chunk:before {
      background-color: var(--dropdown-background);
    }
  `;
}