css-replace

2024/12/29 14:44:23

اعتبارا من 11-03-2025. شاهد أحدث إصدار.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل 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('清除成功')
})