JD Relay URL Modifier

Append __wsmode__=9 to relay*.jd.com/file/design?xxx URLs

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         JD Relay URL Modifier
// @namespace    http://tampermonkey.net/
// @version      0.3.7
// @description  Append __wsmode__=9 to relay*.jd.com/file/design?xxx URLs
// @author       Your Name
// @match        https://relay.jd.com/file/*
// @match        *://relay-test.jd.com/file/*
// @match        *://relay0.jd.com/file/*
// @match        *://ling.jd.com/file/*
// @match        *://ling-design.jd.com/file/*
// @match        *://ling-test.jd.com/file/*
// @match        *://ling-pre.jd.com/file/*
// @grant        none
// @license      MIT
// ==/UserScript==

; (function () {
  "use strict"
  const inFile = /\/file\/(design|chat)/.test(window.location.pathname)
  // 从 localStorage 获取状态
  let isEnabled = localStorage.getItem("wsmodeEnabled") === "true"

  // 创建按钮容器
  let container = document.createElement("div")
  container.id = "wsmode-container"
  container.style.position = "fixed"
  container.style.bottom = localStorage.getItem("buttonBottom") || "60px"
  container.style.right = localStorage.getItem("buttonRight") || "10px"
  container.style.zIndex = "1000"
  container.style.display = "flex"
  container.style.gap = "10px"
  document.body.appendChild(container)

  // 创建隐身按钮
  let button = document.createElement("button")
  button.id = "wsmode-button"

  // 幽灵图标
  const ghostIconEnabled = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0-1A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6 3.5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zM8 7a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z"/></svg>'
  const ghostIconDisabled = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0-1A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6 3.5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zM8 7a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z" opacity="0.5"/></svg>'
  const debugIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0-1A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM4 8a.5.5 0 0 1 .5-.5h3v-3a.5.5 0 0 1 1 0v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1z"/></svg>'

  button.innerHTML = isEnabled ? `${ghostIconEnabled}` : `${ghostIconDisabled}`
  button.style.padding = "10px 15px"
  button.style.border = "none"
  button.style.borderRadius = "5px"
  button.style.color = "white"
  button.style.cursor = "pointer"
  button.style.display = "flex"
  button.style.alignItems = "center"
  button.style.gap = "6px"
  button.style.fontSize = "14px"
  button.style.fontWeight = "500"
  button.style.transition = "background-color 0.2s ease, transform 0.1s ease"
  container.appendChild(button)

  // 创建调试按钮
  let debugButton = document.createElement("button")
  debugButton.id = "debug-button"
  debugButton.innerHTML = debugIcon
  debugButton.style.padding = "10px 15px"
  debugButton.style.border = "none"
  debugButton.style.borderRadius = "5px"
  debugButton.style.color = "white"
  debugButton.style.cursor = "pointer"
  debugButton.style.display = "flex"
  debugButton.style.alignItems = "center"
  debugButton.style.gap = "6px"
  debugButton.style.fontSize = "14px"
  debugButton.style.fontWeight = "500"
  debugButton.style.transition = "background-color 0.2s ease, transform 0.1s ease"
  debugButton.style.backgroundColor = "#2196F3"
  container.appendChild(debugButton)

  // 调试按钮点击事件
  debugButton.addEventListener("click", () => {
    if (typeof window.__DEBUG === "function") {
      window.__DEBUG()
    } else {
      console.warn("window.__DEBUG is not defined")
    }
  })

  // 设置按钮初始状态
  button.style.backgroundColor = isEnabled ? "#4CAF50" : "#9E9E9E"

  // 确保按钮在窗口视野内
  function ensureButtonInView() {
    const rect = container.getBoundingClientRect()
    const maxX = window.innerWidth - rect.width
    const maxY = window.innerHeight - rect.height

    let currentRight = parseFloat(container.style.right) || 0
    let currentBottom = parseFloat(container.style.bottom) || 0

    // 如果按钮超出右边界,吸附到右边
    if (currentRight < 0) {
      container.style.right = "10px"
    }
    // 如果按钮超出底边界,吸附到底边
    if (currentBottom < 0) {
      container.style.bottom = "60px"
    }

    // 保存调整后的位置
    localStorage.setItem("buttonRight", container.style.right)
    localStorage.setItem("buttonBottom", container.style.bottom)
  }

  // 窗口缩放时确保按钮在视野内
  window.addEventListener("resize", ensureButtonInView)

  // 页面加载时也检查一次
  setTimeout(ensureButtonInView, 100)

  // 按钮拖拽功能
  let isDragging = false
  let offsetX, offsetY
  let dragStartTime = 0
  let hasMoved = false

  container.addEventListener("mousedown", (e) => {
    isDragging = true
    hasMoved = false
    dragStartTime = Date.now()
    offsetX = e.clientX - container.getBoundingClientRect().left
    offsetY = e.clientY - container.getBoundingClientRect().top
    container.style.transition = "none" // 禁用动画
    container.style.opacity = "0.8" // 拖动时透明度变化
  })

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      hasMoved = true
      const newRight = window.innerWidth - e.clientX - (container.offsetWidth - offsetX)
      const newBottom = window.innerHeight - e.clientY - (container.offsetHeight - offsetY)
      container.style.right = `${Math.max(0, newRight)}px`
      container.style.bottom = `${Math.max(0, newBottom)}px`
    }
  })

  document.addEventListener("mouseup", (e) => {
    if (isDragging) {
      const dragEndTime = Date.now()
      const dragDuration = dragEndTime - dragStartTime

      container.style.opacity = "1" // 恢复透明度
      container.style.transition = "background-color 0.2s ease" // 恢复动画

      // 保存位置到 localStorage
      localStorage.setItem("buttonRight", container.style.right)
      localStorage.setItem("buttonBottom", container.style.bottom)

      // 只有在非拖拽情况下才触发点击事件(短时间内没有移动鼠标)
      if (!hasMoved && dragDuration < 200 && e.target === button) {
        toggleMode()
      }

      isDragging = false
    }
  })

  // 抽取切换模式的逻辑为单独的函数
  function toggleMode() {
    const previousState = isEnabled
    isEnabled = !isEnabled
    localStorage.setItem("wsmodeEnabled", isEnabled)
    button.style.backgroundColor = isEnabled ? "#4CAF50" : "#9E9E9E"
    button.innerHTML = isEnabled ? `${ghostIconEnabled}` : `${ghostIconDisabled}`
    // 通知其他页面
    localStorage.setItem("wsmodeChanged", Date.now())

    // 如果在 inFile 页面并且 wsmode 状态有变化,刷新页面
    if (inFile && previousState !== isEnabled) {
      if (isEnabled && !url.searchParams.has("__wsmode__")) {
        url.searchParams.append("__wsmode__", "9")
      } else if (!isEnabled && url.searchParams.has("__wsmode__")) {
        url.searchParams.delete("__wsmode__")
      }
      window.location.replace(url.toString())
    }
  }

  // 获取当前 URL
  let url = new URL(window.location.href)

  // 检查是否已经有 __wsmode__ 参数并且功能开启
  if (inFile) {
    if (isEnabled && !url.searchParams.has("__wsmode__")) {
      // 添加 __wsmode__ 参数
      url.searchParams.append("__wsmode__", "9")
      // 重定向到新的 URL
      window.location.replace(url.toString())
    }
  }

  // 监听新打开的 /file/design 页面
  window.addEventListener("message", (event) => {
    console.log("Received message from relay.jd.com:", event)
    if (
      event.origin === location.origin &&
      event.data === "checkWsmode"
    ) {
      event.source.postMessage({ wsmodeEnabled: isEnabled }, event.origin)
    }
  })

  // 在 /file/design 页面中检查 __wsmode__ 参数
  if (inFile) {
    window.addEventListener("message", (event) => {
      if (
        event.origin === location.origin &&
        typeof event.data.wsmodeEnabled !== "undefined"
      ) {
        if (event.data.wsmodeEnabled) {
          if (!url.searchParams.has("__wsmode__")) {
            url.searchParams.append("__wsmode__", "9")
            window.location.replace(url.toString())
          }
        } else {
          if (url.searchParams.has("__wsmode__")) {
            url.searchParams.delete("__wsmode__")
            window.location.replace(url.toString())
          }
        }
      }
    })
  }

  // 监听 localStorage 变化
  window.addEventListener("storage", (event) => {
    if (event.key === "wsmodeChanged") {
      isEnabled = localStorage.getItem("wsmodeEnabled") === "true"
      button.style.backgroundColor = isEnabled ? "#4CAF50" : "#9E9E9E"
      button.innerHTML = isEnabled ? `${ghostIconEnabled}` : `${ghostIconDisabled}`
      if (inFile) {
        if (isEnabled && !url.searchParams.has("__wsmode__")) {
          url.searchParams.append("__wsmode__", "9")
          window.location.replace(url.toString())
        } else if (!isEnabled && url.searchParams.has("__wsmode__")) {
          url.searchParams.delete("__wsmode__")
          window.location.replace(url.toString())
        }
      }
    }
  })
})()