PoeUI

美化Poe网页版UI样式

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                PoeUI
// @namespace           http://xiaomizha.ltd/
// @version             0.1
// @description         美化Poe网页版UI样式
// @author              xuyou
// @description:en      We embellished the Poe web UI style.
// @description:zh-CN   美化Poe网页版UI样式
// @license             MIT
// @match               https://poe.com/*
// @icon                data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAolBMVEVHcEz6Uvy1QfqUQuefTOPxUPxNVb5GYLh6S9dPVL+2Qvn1UvywQfaLQuONQuP+UfpXRMbQS//8Uvy+QP////+3QPudQeyxQPekQPCUQedZRMeqQfOCQN5iQsxPRcGLQuJHSLvDQ/96QtlyQ9VqQ9DRTf/uV//KR//jVv9GVLbZU//6U/7Oivutfurz6v2YLOy3WfeUb9+/m++zLPvXrfniyfqv1uwAAAAAE3RSTlMA75uUH2/I/WaW66DF68XA6s3NFufBpwAABBtJREFUWIWVl4t2qkoMhqetdmvbfTvIrWItImpVmAr4/q92ZibJ3EA5J6zVtWzN1+TPnwEYc2Mymz//Wb3L+FDxqeJLxo8fz/PZhN2L+VbGSsS7YTgIEfOb6U+FCCIoxMe7IYwiZptiU/iIj6FGRMz6+b82MnoEt41PTXj2839mWTaA6DM0ws3PVGx6iPeeFF8a4fz/9XptGMUtKTw1rf7XMjLB2Jg+toUz0iFXaP3TdK0R/0cKmkUqwydsiGBJ4RO+0D9pehtxe6aSAY6KokjlD/dhzO1XQSU8RREi7CI2Q1L01ZxDAT4hc2sonBoA8YlViP1NksRhZGl1qS7DUXz4WzphM5EPhBQI9eJO1FuPMGOPiQoqgt/NFwS7D8GYs7clEhJVRHQ/f7EonNPm85m9LEWYIqoxwMXMQyL+sKUCaEQzBmhW7kjZMrYIyZgEQoSV6woWx4oAbfBuUXfVcIjZVlVTLzqZbBZEAgDBq3NXL66cRyAH+co9arbXRd1UF0CsNEAioHhOaqY9d6M3oZGC+mBhqPJbyL9yVJNzToiUzK0YxRW+SFIIgCK0IFGXoCu6um44ENZp1tR1B4Bsg3Pa4ppKgGLAr8+JkhOG0aSohMqpsQ8E0JYqQGwAMFK0EwctOXyqYM8NQBGwgtACiMAv0ZKihTI1E/wbnd0sCANFQA1gpGf4hCtGABgIAfC0YQESsAIYKQFwpjcACiEAQLAAsQVQS4oAmCgCyFsSIGMQgKYgEVMzEgFAZwEgDAIbEIOtOlpSGCPvAwSC5VABGalFY13rWnlCumLJpZFoPwiAS8ICIJCVW/RF27ZL66gQzqYzD6wsbaUQsoJcXt8AbsmZsV7TJR16AIDvXWjFWJ7nWMS5u+oSKN86beDslgVcu0bfjjO2zyUCGG0rtv38fScadWLYN1O2V4TcleJO1FzfxRSBlfu9XUQ3BuhITSyClZKw10WcxwAVaKEPLHYsy9IilGOAb+9eyo6SQIh8FFBzM1JJ+Mn+Ho9YxP6/AL7xPkhV/GKvh6ONOKJU5+Hg5Cu8o0dPbHo4HI6mDwB0LW5I0IbiitW1FOaO7fuYyE9mbLI72AgFqDE/oMMCjk3jbWNu8frwoAiIAEBAvrIRgwsSiWek6W5HRQiEBJTGmeqoMITQLAj08SQf03Y7CyEA51zNw4M4jWgl1HPiq00ou27vuRu6GKziER5VTyeDOJRl6bhbMnJbCbsIfFaenmyEdoW9pRYhDvVA9IvP75OHKAcRQRA4iDfzwvDgEA6Ou3M91NCqQuS/2K8sJ4hbCGCIH2Fu1HRfmh4AQIjjwetD90CEF+bFb5dwWwpAvPn5MAtfTE8Ko8W0ny8ddRqRgop4HExXVXhSeH2Ao26ny5hMX/95wD56iJe/j1P/9f9fo6ydU4aSQJkAAAAASUVORK5CYII=
// @grant               GM_registerMenuCommand
// ==/UserScript==

;(() => {
  ;('use strict')

  // 重构 Login UI

  // 默认配置
  const borderRadius = '4px'
  const paddingCustom = '16px'
  const marginCustom = '20px'
  const colorCustomLi = '#EFEFFC'
  const colorCustom = '#5D5CDE'
  const borderCustom = '1px solid #e6e7e9'
  const fontFamilyLists = [
    {
      name: 'Loto',
      href: 'https://fonts.googleapis.com/css2?family=Lato&display=swap'
    },
    {
      name: 'Pathway Extreme',
      href: 'https://fonts.googleapis.com/css2?family=Pathway+Extreme&display=swap'
    }
  ]
  const fontFamily = 'Lato, "Pathway Extreme", "Microsoft YaHei", sans-serif'

  // 修改初始样式
  // Modify initial style
  const setInitialStyle = () => {
    // 创建<style>标签
    let styles = document.createElement('style')
    // 设置字体样式
    let css = `
    * {
        transition                 : all .3s;
        caret-color                : ${colorCustom};
        font-family                : ${fontFamily} !important;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;
        text-shadow                : 0 0 1.15px #a4a09ad9, 0 0 1px #7b7b7bcc, 0 0 0.75px #302f2d45 !important;
    }
    ::selection {
        color      : #FFFFFF !important;
        background : ${colorCustom} !important;
        text-shadow: none !important;
    }
    `
    // 添加CSS规则
    styles.append(document.createTextNode(css))
    // 将<style>标签加到head中
    document.head.appendChild(styles)
    // 动态添加 @import 规则
    fontFamilyLists.forEach((item) => {
      styles.sheet.insertRule(`@import url(${item.href});`)
    })
  }

  // 美化登录页
  const setLoginStyle = () => {
    let buttonBase = document.querySelectorAll('[class^=Button_buttonBase__]')
    buttonBase.forEach((elem) => {
      setCss(elem, {
        borderRadius: borderRadius,
        paddingTop: paddingCustom,
        paddingBottom: paddingCustom
      })
    })
  }

  // 全选所有历史记录
  GM_registerMenuCommand('全选所有历史记录', () => {
    // 执行全选操作
    let checkbox = document.querySelectorAll('.Checkbox_checkbox__zM_Lo')
    for (var i = 0; i < checkbox.length; i++) {
      if (!checkbox[i].checked) {
        checkbox[i].click()
      }
    }
  })

  // 聊天区最大化
  const setChatStyle = () => {
    let leftSidebar = document.querySelector(
      '[class^=PageWithSidebarLayout_leftSidebar__]'
    )
    setCss(leftSidebar, { flexGrow: 1 })
    let mainSection = document.querySelector(
      '[class^=PageWithSidebarLayout_mainSection__]'
    )
    setCss(mainSection, { flexGrow: 2, maxWidth: 'initial' })
    let rightSidebar = document.querySelector(
      '[class^=PageWithSidebarLayout_rightSidebar__]'
    )
    setCss(rightSidebar, { display: 'none' })
    let welcomeButtonsContainer = document.querySelectorAll(
      '[class^=ChatWelcomeView_welcomeButtonsContainer__]'
    )
    welcomeButtonsContainer.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius })
    })
    let humanMessageBubble = document.querySelectorAll(
      '[class^=Message_humanMessageBubble__]'
    )
    humanMessageBubble.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius })
    })
    let botMessageBubble = document.querySelectorAll(
      '[class^=Message_botMessageBubble__]'
    )
    botMessageBubble.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius })
    })
    let textInput = document.querySelector(
      '[class^=ChatMessageInputView_textInput__]'
    )
    setCss(textInput, { paddingTop: marginCustom, borderRadius: borderRadius })
    let sendButtonWrapper = document.querySelector(
      '[class^=ChatMessageInputView_sendButtonWrapper__]'
    )
    setCss(sendButtonWrapper, {
      bottom: 'initial',
      top: '50%',
      transform: 'translateY(-50%)',
      right: '10px'
    })
    let sendButton = document.querySelector(
      '[class*=ChatMessageInputView_sendButton__]'
    )
    setCss(sendButton, { borderRadius: '50%' })
    let messageOverflowButton = document.querySelectorAll(
      '[class^=ChatMessage_messageOverflowButton__]'
    )
    messageOverflowButton.forEach((elem) => {
      setCss(elem, { alignSelf: 'flex-end' })
    })
    let itemList = document.querySelectorAll(
      '[class^=DropdownMenuItemList_itemList__]'
    )
    itemList.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius })
    })
  }

  // 美化侧边栏
  const setChatSidebarStyle = () => {
    let sidebarContainer = document.querySelector(
      '[class^=ChatPageSidebar_sidebarContainer__]'
    )
    setCss(sidebarContainer, { maxWidth: 'initial' })
    let sidebar = document.querySelector('[class^=ChatPageSidebar_sidebar__]')
    setCss(sidebar, {
      maxWidth: 'initial',
      paddingLeft: 'initial',
      paddingRight: 'initial',
      gap: 'initial'
    })
    let section = document.querySelectorAll(
      '[class^=PageWithSidebarNavGroup_section__]'
    )
    section.forEach((elem) => {
      setCss(elem, {
        borderRadius: 'initial',
        borderBottom: borderCustom
      })
    })
    lastSection = section[section.length - 1]
    setCss(lastSection, { borderBottom: 'initial' })
    let logo = document.querySelector('[class^=ChatPageSidebar_logo__]')
    setCss(logo, { marginBottom: paddingCustom })
    let navItem = document.querySelectorAll(
      '[class^=PageWithSidebarNavItem_navItem__]'
    )
    navItem.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius, backgroundColor: 'initial' })
    })
    let active = document.querySelector(
      '[class*=PageWithSidebarNavItem_active__]'
    )
    setCss(active, { backgroundColor: colorCustomLi })
    let downloadButton = document.querySelector(
      '[class*=ChatPageDownloadLinks_downloadButton__]'
    )
    setCss(downloadButton, {
      margin: `${marginCustom} 0`,
      borderRadius: borderRadius,
      paddingTop: paddingCustom,
      paddingBottom: paddingCustom
    })
  }

  // 其他UI
  const setOtherStyle = () => {
    let reactModal = document.querySelector('[class^=ReactModal__Content]')
    setCss(reactModal, { borderRadius: borderRadius })
    let container = document.querySelectorAll(
      '[class^=SettingsSection_container__]'
    )
    container.forEach((elem) => {
      setCss(elem, { borderRadius: borderRadius })
    })
    let sectionBubble = document.querySelector(
      '[class^=SettingsSubscriptionSection_sectionBubble__]'
    )
    setCss(sectionBubble, { borderRadius: borderRadius })
  }

  // 自定义方法
  // 判断是否是DOM元素
  const isElement = (selector) => {
    if (typeof selector === 'string' || selector instanceof String) {
      return !!document.querySelector(selector)
    } else if (selector instanceof Element) {
      return true
    } else if (
      selector instanceof NodeList ||
      selector instanceof HTMLCollection
    ) {
      return (
        selector.length > 0 &&
        Array.from(selector).every((elem) => elem instanceof Element)
      )
    } else if (Array.isArray(selector)) {
      return selector.length > 0 && selector.every((elem) => isElement(elem))
    } else {
      return false
    }
  }

  // 修改CSS
  // 调用: setCss(['#box1', '#box2'], { background: 'white', color: 'black' })
  const setCss = (selectors, cssObj) => {
    // 处理selectors为数组的情况
    if (!Array.isArray(selectors)) {
      selectors = [selectors]
    }
    selectors.forEach((selector) => {
      let elem = isElement(selector)
        ? selector
        : document.querySelector(selector)
      if (elem) {
        for (let prop in cssObj) {
          elem.style[prop] = cssObj[prop]
        }
      }
    })
  }

  // 修改属性
  // 调用: setAttr(['#box1', '#box2'], { class: 'a', href: 'http://example.com' })
  const setAttr = (selectors, attrObj) => {
    // 处理selectors为数组的情况
    if (!Array.isArray(selectors)) {
      selectors = [selectors]
    }
    selectors.forEach((selector) => {
      let elem = isElement(selector)
        ? selector
        : document.querySelector(selector)
      if (elem) {
        for (let attr in attrObj) {
          elem.setAttribute(attr, attrObj[attr])
        }
      }
    })
  }

  // 初始化
  const initial = () => {
    setInitialStyle()

    let timer = setInterval(() => {
      setLoginStyle()
      setChatStyle()
      setChatSidebarStyle()
      setOtherStyle()
    }, 1000)
  }

  // 网页加载成功
  window.onload = () => {
    initial()
  }
})()