css-replace

2024/12/29 14:44:23

当前为 2025-03-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name css-replace
  3. // @namespace css
  4. // @match https://codesign.qq.com/app/design/*
  5. // @version 1.1
  6. // @author Gorvey
  7. // @description 2024/12/29 14:44:23
  8. // @run-at document-idle
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_setValue
  11. // @grant GM_deleteValue
  12. // @grant GM_getValue
  13. // @grant GM_addStyle
  14. // @license MIT
  15. // ==/UserScript==
  16. //--------------------设置------------------- S//
  17.  
  18. //#region
  19. GM_addStyle(
  20. `
  21. .base-node{
  22. display: none;
  23. }
  24. .node-item[data-label="字体"],
  25. .node-item[data-label="段落对齐"],
  26. .node-item[data-label="垂直对齐"],
  27. .node-item[data-label="字号"],
  28. .node-item[data-label="字重"],
  29. .node-item[data-label="行高"],
  30. .node-item[data-label="颜色"],
  31. .node-item[data-label="宽度"]
  32. {
  33. display: none !important;
  34. }
  35. // .node-box{
  36. // display: none !important;
  37. // }
  38. .node-box:last-child{
  39. display: block !important;
  40. }
  41. .node-item {
  42. margin-bottom: 2px !important;
  43. }
  44. .node-box {
  45. margin-bottom: 0px !important;
  46. }
  47. `
  48. );
  49. GM_addStyle(
  50. `
  51. .tailwind-line-wrapper{
  52. display: flex;
  53. align-items: center;
  54. justify-content: space-between;
  55. }
  56. `
  57. )
  58. //#endregion
  59.  
  60. document.addEventListener('click', (e) => {
  61. if(e.target.classList.contains('icon-v2-copy')){
  62. return
  63. }
  64. const wrapper = document.querySelector(".css-node__codes");
  65. if (!wrapper) return;
  66. onCssChange()
  67. })
  68. const copyToClipboard = (text) => {
  69. const textarea = document.createElement("textarea");
  70. textarea.value = text;
  71. document.body.appendChild(textarea);
  72. textarea.select();
  73. document.execCommand("copy");
  74. document.body.removeChild(textarea);
  75. };
  76.  
  77. const processCssVariables = (value) => {
  78. const cssVarMap = GM_getValue('css-variable-map', '')
  79. if (!cssVarMap) return value
  80.  
  81.  
  82. // 格式化 CSS 变量映射字符串
  83. const formatVarMap = cssVarMap
  84. .replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
  85. .replace(/(:root|:root\[.*?\])\s*{/g, '')
  86. .replace(/}/g, '')
  87. .split(';')
  88. .filter(line => line.trim())
  89. .map(line => {
  90. const [key, val] = line.split(':').map(s => s.trim())
  91. return [key, val]
  92. })
  93. .filter(([key, val]) => key && val)
  94.  
  95.  
  96. // 查找匹配的变量
  97. const matchedVar = formatVarMap.find(([_, val]) => {
  98. const cleanVal = val.replace(/\s*!important\s*$/, '').trim()
  99. return cleanVal.toLowerCase() === value
  100. })
  101.  
  102. return matchedVar ? `var(${matchedVar[0]})` : value
  103. }
  104.  
  105. const cssToTailwind = (css) => {
  106.  
  107.  
  108. const cssMap = css.map(item => {
  109. const [name, value] = item.split(':').map(s => s.trim())
  110. const processedValue = processCssVariables(value)
  111. return [name, processedValue]
  112. })
  113.  
  114. const rules = [
  115. // ['width', 'w-[#]'],
  116. // ['height', 'h-[#]'],
  117. ['font-size', 'text-[#]'],
  118. // ['font-style', '#'],
  119. ['font-weight', (value) =>{
  120. const maps={
  121. '300': 'font-light',
  122. '400': 'font-normal',
  123. '500': 'font-medium',
  124. '600': 'font-semibold',
  125. '700': 'font-bold',
  126. '800': 'font-extrabold',
  127. }
  128. return maps[value]
  129. }],
  130. ['color', 'text-[#]'],
  131. ['line-height', 'leading-[#]'],
  132. ['border-radius', 'rounded-[#]'],
  133. ['border', (value) => {
  134. const [width, style, color] = value.split(' ')
  135. return `border-[${width}] border-${style} border-[${color}]`
  136. }],
  137. ['letter-spacing', 'tracking-[#]'],
  138. ['opacity', (value) => `opacity-${Math.round(parseFloat(value) * 100)}`],
  139. // ['text-decoration', '#'],
  140. // ['text-align', 'text-#'],
  141. ['background', 'bg-[#]'],
  142. // ['padding', 'p-[#]'],
  143. // ['margin', 'm-[#]'],
  144. // ['display', '#'],
  145. // ['position', '#'],
  146. // ['top', 'top-[#]'],
  147. // ['left', 'left-[#]'],
  148. // ['right', 'right-[#]'],
  149. // ['bottom', 'bottom-[#]'],
  150. ]
  151.  
  152. // 转换 CSS 属性到 Tailwind
  153. const result = cssMap.map(([prop, value]) => {
  154. const rule = rules.find(([cssName]) => cssName === prop)
  155. if(value === '') return null
  156. if (!rule) return null
  157.  
  158. const [, template] = rule
  159. if (typeof template === 'function') {
  160. return template(value)
  161. }
  162. return template.replace('#', value)
  163. }).filter(item => {
  164. if(!item) return false
  165. // 清理默认值
  166. let defaultValues = ['font-normal', 'text-[14px]', 'tracking-[0]']
  167. if(defaultValues.includes(item)) return false
  168. return true
  169. })
  170. return result.join(' ')
  171. }
  172. const genCopyButton = (css) => {
  173. const button = document.createElement('div')
  174. button.innerHTML='<i style="font-size: 24px;" class="com-icon iconfont-v2 icon-v2-copy"></i>'
  175. button.classList.add('tailwind-copy-button')
  176. button.style.cursor = 'pointer'
  177. button.style.color = '#000'
  178. button.style.marginLeft = '8px'
  179. button.addEventListener('click', () => {
  180. copyToClipboard(css)
  181. // 获取对应的代码块元素
  182. const codeBlock = button.previousElementSibling
  183. // 添加复制成功的边框样式
  184. codeBlock.style.borderColor = '#22c55e'
  185. // 3秒后恢复原样
  186. setTimeout(() => {
  187. codeBlock.style.borderColor = 'var(--td-brand-color)'
  188. }, 3000)
  189. })
  190. return button
  191. }
  192. const textToHtmlCodeElement = (text) => {
  193. const node = document.createElement('div')
  194. node.classList.add('tailwind-code-block')
  195. node.innerText = text
  196. node.style.width = '100%'
  197. node.style.marginTop = '8px'
  198. node.style.padding = '8px 12px'
  199. node.style.whiteSpace = 'pre-wrap'
  200. node.style.wordBreak = 'break-all'
  201. node.style.minHeight = '32px'
  202. node.style.maxHeight = '190px'
  203. node.style.overflow = 'auto'
  204. node.style.borderRadius = '4px'
  205. node.style.cursor = 'text'
  206. node.style.backgroundColor = 'rgba(0, 0, 0, .04)'
  207. node.style.position = 'relative'
  208. node.style.border = '3px solid var(--td-brand-color)'
  209. return node
  210. }
  211. const onCssChange = async () => {
  212. await new Promise((resolve) => setTimeout(resolve, 20));
  213. try {
  214. const wrapper = document.querySelector(".css-node__codes");
  215. if (!wrapper) return;
  216. const cssNode = wrapper.querySelectorAll(".css-node__code--item")[0];
  217. const content = cssNode.innerText;
  218. const hasMultipleClasses = content.includes('{');
  219. let tailwind = [];
  220. if (hasMultipleClasses) {
  221. // 处理多个类声明
  222. const classBlocks = content.split('}').filter(block => block.trim());
  223. const results = classBlocks.map(block => {
  224. const styles = block
  225. .replace(/.*{/, '') // 移除类名和{
  226. .split(';\n')
  227. .filter(item => item.trim())
  228. .map(item => item.trim().replace(';', ''));
  229. return cssToTailwind(styles);
  230. });
  231. tailwind = results;
  232. } else {
  233. // 处理单个声明
  234. const styles = content
  235. .split(';\n')
  236. .filter(item => item.trim())
  237. .map(item => item.trim().replace(';', ''));
  238. tailwind =[cssToTailwind(styles)]
  239. }
  240. let prevNodes = document.querySelectorAll('.tailwind-line-wrapper')
  241. prevNodes.forEach(node => {
  242. node.remove()
  243. })
  244. tailwind.map(item =>{
  245. const node= textToHtmlCodeElement(item)
  246. const copyButton = genCopyButton(item)
  247. const lineWrapper = document.createElement('div')
  248. lineWrapper.classList.add('tailwind-line-wrapper')
  249. lineWrapper.appendChild(node)
  250. lineWrapper.appendChild(copyButton)
  251. wrapper.insertBefore(lineWrapper,cssNode)
  252. })
  253.  
  254. } catch (error) {
  255. console.error(error);
  256. }
  257. };
  258.  
  259. GM_registerMenuCommand('添加css变量映射', () => {
  260. const css = prompt('请输入css变量映射')
  261. if(!css) return
  262. GM_setValue('css-variable-map', css)
  263. alert('添加成功')
  264. })
  265. GM_registerMenuCommand('清除css变量映射', () => {
  266. GM_deleteValue('css-variable-map')
  267. alert('清除成功')
  268. })