// ==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('清除成功')
})