css-replace

2024/12/29 14:44:23

Устаревшая версия за 10.03.2025. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или 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_setValues
// @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_setValues({
  'css-variable-map': css
})
alert('添加成功')
})
GM_registerMenuCommand('清除css变量映射', () => {
  GM_deleteValue('css-variable-map')
  alert('清除成功')
})