css-replace

2024/12/29 14:44:23

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        css-replace
// @namespace   css
// @match       https://codesign.qq.com/app/design/*
// @version     1.1
// @author      Gorvey
// @description 2024/12/29 14:44:23
// @run-at      document-idle
// @grant       GM_registerMenuCommand
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_getValue
// @grant       GM_addStyle
// @license MIT
// ==/UserScript==
//--------------------设置------------------- S//

//#region
GM_addStyle(
  `
  .base-node{
    display: none;
  }
  .node-item[data-label="字体"],
  .node-item[data-label="段落对齐"],
  .node-item[data-label="垂直对齐"],
  .node-item[data-label="字号"],
  .node-item[data-label="字重"],
  .node-item[data-label="行高"],
  .node-item[data-label="颜色"],
  .node-item[data-label="宽度"]
  {
    display: none !important;
  }
  // .node-box{
  //   display: none !important;
  // }
  .node-box:last-child{
    display: block !important;
  }
  .node-item {
    margin-bottom: 2px !important;
  }
  .node-box {
    margin-bottom: 0px !important;
  }
  `
);
GM_addStyle(
  `
  .tailwind-line-wrapper{
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  `
)
//#endregion

document.addEventListener('click', (e) => {
  if(e.target.classList.contains('icon-v2-copy')){
    return
  }
  const wrapper = document.querySelector(".css-node__codes");
  if (!wrapper) return;
  onCssChange()
})
const copyToClipboard = (text) => {
  const textarea = document.createElement("textarea");
  textarea.value = text;
  document.body.appendChild(textarea);
  textarea.select();
  document.execCommand("copy");
  document.body.removeChild(textarea);
};

const processCssVariables = (value) => {
  const cssVarMap = GM_getValue('css-variable-map', '')
  if (!cssVarMap) return value
  


  // 格式化 CSS 变量映射字符串
  const formatVarMap = cssVarMap
    .replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
    .replace(/(:root|:root\[.*?\])\s*{/g, '')
    .replace(/}/g, '')
    .split(';')
    .filter(line => line.trim())
    .map(line => {
      const [key, val] = line.split(':').map(s => s.trim())
     
      return [key, val]
    })
    .filter(([key, val]) => key && val)


  // 查找匹配的变量
  const matchedVar = formatVarMap.find(([_, val]) => {
    const cleanVal = val.replace(/\s*!important\s*$/, '').trim()
    return cleanVal.toLowerCase() === value
  })

  return matchedVar ? `var(${matchedVar[0]})` : value
}

const cssToTailwind = (css) => {


  const cssMap = css.map(item => {
    const [name, value] = item.split(':').map(s => s.trim())
    const processedValue = processCssVariables(value)
    return [name, processedValue]
  })

  const rules = [
    // ['width', 'w-[#]'],
    // ['height', 'h-[#]'],
    ['font-size', 'text-[#]'],
    // ['font-style', '#'],
    ['font-weight', (value) =>{
      const maps={
        '300': 'font-light',
        '400': 'font-normal',
        '500': 'font-medium',
        '600': 'font-semibold',
        '700': 'font-bold',
        '800': 'font-extrabold',
      }
      return maps[value]
    }],
    ['color', 'text-[#]'],
    ['line-height', 'leading-[#]'],
    ['border-radius', 'rounded-[#]'],
    ['border', (value) => {
      const [width, style, color] = value.split(' ')
      return `border-[${width}] border-${style} border-[${color}]`
    }],
    ['letter-spacing', 'tracking-[#]'],
    ['opacity', (value) => `opacity-${Math.round(parseFloat(value) * 100)}`],
    // ['text-decoration', '#'],
    // ['text-align', 'text-#'],
    ['background', 'bg-[#]'],
    // ['padding', 'p-[#]'],
    // ['margin', 'm-[#]'],
    // ['display', '#'],
    // ['position', '#'],
    // ['top', 'top-[#]'],
    // ['left', 'left-[#]'],
    // ['right', 'right-[#]'],
    // ['bottom', 'bottom-[#]'],
  ]

  // 转换 CSS 属性到 Tailwind
  const result = cssMap.map(([prop, value]) => {
    const rule = rules.find(([cssName]) => cssName === prop)
    if(value === '') return null
    if (!rule) return null

    const [, template] = rule
    if (typeof template === 'function') {
      return template(value)
    }
    
    return template.replace('#', value)
  }).filter(item => {
    if(!item) return false
    // 清理默认值
    let defaultValues = ['font-normal', 'text-[14px]', 'tracking-[0]']
    if(defaultValues.includes(item)) return false
    return true
  })
  
  return result.join(' ')
}
const genCopyButton = (css) => {
  const button = document.createElement('div')
  button.innerHTML='<i style="font-size: 24px;" class="com-icon iconfont-v2 icon-v2-copy"></i>'
  button.classList.add('tailwind-copy-button')
  button.style.cursor = 'pointer'
  button.style.color = '#000'
  button.style.marginLeft = '8px'
  button.addEventListener('click', () => {
    copyToClipboard(css)
    // 获取对应的代码块元素
    const codeBlock = button.previousElementSibling
    // 添加复制成功的边框样式
    codeBlock.style.borderColor = '#22c55e'
    // 3秒后恢复原样
    setTimeout(() => {
      codeBlock.style.borderColor = 'var(--td-brand-color)'
    }, 3000)
  })
  return button
}
const textToHtmlCodeElement = (text) => {
  const node = document.createElement('div')
  node.classList.add('tailwind-code-block')
  node.innerText = text
  node.style.width = '100%'
  node.style.marginTop = '8px'
  node.style.padding = '8px 12px'
  node.style.whiteSpace = 'pre-wrap'
  node.style.wordBreak = 'break-all'
  node.style.minHeight = '32px'
  node.style.maxHeight = '190px'
  node.style.overflow = 'auto'
  node.style.borderRadius = '4px'
  node.style.cursor = 'text'
  node.style.backgroundColor = 'rgba(0, 0, 0, .04)'
  node.style.position = 'relative'
  node.style.border = '3px solid var(--td-brand-color)'
  return node
}
const onCssChange = async () => {
  await new Promise((resolve) => setTimeout(resolve, 20));
  try {
    const wrapper = document.querySelector(".css-node__codes");
    if (!wrapper) return;
    const cssNode = wrapper.querySelectorAll(".css-node__code--item")[0];
    const content = cssNode.innerText;
    const hasMultipleClasses = content.includes('{');
    let tailwind = [];
    
    if (hasMultipleClasses) {
      // 处理多个类声明
      const classBlocks = content.split('}').filter(block => block.trim());
      const results = classBlocks.map(block => {
        const styles = block
          .replace(/.*{/, '') // 移除类名和{
          .split(';\n')
          .filter(item => item.trim())
          .map(item => item.trim().replace(';', ''));
        return cssToTailwind(styles);
      });
      tailwind = results;
    } else {
      // 处理单个声明
      const styles = content
        .split(';\n')
        .filter(item => item.trim())
        .map(item => item.trim().replace(';', ''));
      tailwind =[cssToTailwind(styles)]
    }
    let prevNodes = document.querySelectorAll('.tailwind-line-wrapper')
    prevNodes.forEach(node => {
      node.remove()
    })
  
    tailwind.map(item =>{
     const node= textToHtmlCodeElement(item)
     const copyButton = genCopyButton(item)
     const lineWrapper = document.createElement('div')
     lineWrapper.classList.add('tailwind-line-wrapper')
     lineWrapper.appendChild(node)
     lineWrapper.appendChild(copyButton)
     wrapper.insertBefore(lineWrapper,cssNode)
    })

  } catch (error) {
    console.error(error);
  }
};

GM_registerMenuCommand('添加css变量映射', () => {
 const css = prompt('请输入css变量映射')
 if(!css) return
GM_setValue('css-variable-map', css)
alert('添加成功')
})
GM_registerMenuCommand('清除css变量映射', () => {
  GM_deleteValue('css-variable-map')
  alert('清除成功')
})