libs

JavaScript 库函数,以便调用

Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greasyfork.org/scripts/491971/1356811/libs.js

// ==UserScript==
// @name         libs
// @description  JavaScript 库函数,以便调用
// @namespace    essence/libs
// @version      0.4
// @grant        GM_addStyle
// ==/UserScript==

// 等待
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

/**
 * 等待指定元素出现后,执行回调
 * @param selector 元素选择器。参考`document.querySelector`
 * @param callback 需要执行的回调。传递的参数为目标元素
 */
const waitElem = (selector, callback) => {
  const observer = new MutationObserver(() => {
    const element = document.querySelector(selector)
    if (element) {
      callback(element)
      observer.disconnect()
    }
  })

  observer.observe(document.body, {
    childList: true,
    subtree: true,
  })
}

/**
 * 读取网站存储到 localStorage 的所有值
 * @param excludeRegexp 不读取的键的正则。如 /^Hm_lvt/
 * @return {string} JSON 文本
 */
const readLocalStorageValues = (excludeRegexp = undefined) => {
  const result = {}
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i)
    if (excludeRegexp && excludeRegexp.test(key)) {
      continue
    }

    result[key] = localStorage.getItem(key)
  }

  return JSON.stringify(result)
}

/**
 * 恢复数据到网站的 localStorage
 * @param {string} json
 */
const restoreLocalStorageValues = (json) => {
  const data = JSON.parse(json)

  Object.keys(data).forEach(key => localStorage.setItem(key, data[key]))
}

/**
 * 让容器以 flex row 显示子元素
 * @param elem {HTMLElement} 容器元素
 * @param gap {string} 子元素的间隔。默认"8px"
 * @param alignItems {string} 子元素垂直对齐。默认"center"
 */
const containerToRow = (elem, gap = "8px", alignItems = "center") => {
  elem.style.display = "flex"
  elem.style.flexDirection = "row"
  elem.style.justifyContent = "space-between"
  elem.style.gap = gap
  elem.style.alignItems = alignItems
}

/**
 * 发送通知。可使用 Toast 实例快捷发送消息
 * @param message {string} 消息
 * @param color {string} 消息类型。可选:"default" | "primary" | "success" | warning" | "danger"
 * @param element 右侧操作按钮
 * @param disappear 持续时长(毫秒)
 */
const toast = (message, color = 'primary', element = null, disappear = 5000) => {
  // 创建通知元素
  const toast = document.createElement('div')

  // 设置消息文本
  toast.textContent = message

  // 如果提供了操作按钮元素,添加到通知
  if (element) {
    toast.appendChild(element)
  }

  // 根据消息的类型设置颜色,并显示
  toast.classList.add("toast", "essenceX", color)

  // 将通知元素添加到页面
  document.body.appendChild(toast)

  // 显示 toast
  setTimeout(() => toast.classList.add("show"), 10)

  // toast 在 N 秒后消失
  setTimeout(() => {
    toast.classList.remove("show")
    toast.classList.add("hide")

    // 在消失动画完成后,从页面移除通知元素
    toast.addEventListener('transitionend', () => toast.remove())
  }, disappear)
}

/**
 * 快捷发送通知
 */
const Toast = {
  default: (message, element = null, disappear = 5000) => toast(message, "default", element, disappear),
  primary: (message, element = null, disappear = 5000) => toast(message, "primary", element, disappear),
  success: (message, element = null, disappear = 5000) => toast(message, "success", element, disappear),
  warning: (message, element = null, disappear = 5000) => toast(message, "warning", element, disappear),
  danger: (message, element = null, disappear = 5000) => toast(message, "danger", element, disappear)
}

const removeDialog = (dialog) => {
  dialog.classList.remove("show")
  dialog.classList.add("hide")

  // 在消失动画完成后,从页面移除通知元素
  dialog.addEventListener('transitionend', () => dialog.remove())
}

/**
 * 显示对话框
 * @param title {string} 标题
 * @param body {Element} 内容
 * @param isModel {boolean} 是否为模态对话框
 * @return {HTMLElement} 返回对话框实例,以便 remove()
 */
const showDialog = (title, body, isModel = false) => {
  const dialog = document.createElement('div')
  const content = document.createElement('div')
  const header = document.createElement('div')
  const dialogBody = document.createElement('div')

  dialog.classList.add("dialog", "essenceX")
  content.classList.add("dialog-content", "essenceX")
  header.classList.add("dialog-header", "essenceX")
  dialogBody.classList.add("dialog-body", "essenceX")

  header.textContent = title
  dialogBody.appendChild(body)

  content.append(header, dialogBody)

  dialog.appendChild(content)

  document.body.appendChild(dialog)

  // 使用动画显示对话框
  setTimeout(() => dialog.classList.add("show"), 10)

  // 不是模态对话框时,点击外部即取消
  if (!isModel) {
    window.addEventListener("click", event => {
      if (event.target === dialog) {
        removeDialog(dialog)
      }
    })
  }

  return dialog
}