From 18873e25c7a1939ebf351b0cf8f2869dfd36ef90 Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Wed, 3 Jan 2024 15:35:35 +0800 Subject: [PATCH] feat: less css converter --- .gitignore | 2 +- declare.d.ts | 1 + package-lock.json | 182 ++++++++++++++++++++++++++ package.json | 3 + src/pages/less2css.tsx | 289 +++++++++++++++++++++++++++++++++++++++++ src/utils/tools.ts | 7 + tsconfig.json | 2 +- 7 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 declare.d.ts create mode 100644 src/pages/less2css.tsx diff --git a/.gitignore b/.gitignore index 6bf6273..cf40755 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,8 @@ yarn-error.log* .vercel # typescript -*.tsbuildinfo next-env.d.ts +*.tsbuildinfo .yalc yalc yalc.lock \ No newline at end of file diff --git a/declare.d.ts b/declare.d.ts new file mode 100644 index 0000000..9aa7166 --- /dev/null +++ b/declare.d.ts @@ -0,0 +1 @@ +declare module 'css2less'; diff --git a/package-lock.json b/package-lock.json index fb67145..ba2a3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,12 +31,14 @@ "change-case": "^5.3.0", "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", + "css2less": "^0.1.4", "eslint-config-next": "^14.0.4", "html-entities": "^2.4.0", "ip": "^1.1.8", "js-yaml": "^4.1.0", "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", + "less": "^4.2.0", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -75,6 +77,7 @@ "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", "@types/ip": "^1.1.3", + "@types/less": "^3.0.6", "@types/node": "20.1.5", "@types/node-forge": "^1.3.10", "@types/qs": "^6.9.7", @@ -2055,6 +2058,12 @@ "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/less": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/less/-/less-3.0.6.tgz", + "integrity": "sha512-PecSzorDGdabF57OBeQO/xFbAkYWo88g4Xvnsx7LRwqLC17I7OoKtA3bQB9uXkY6UkMWCOsA8HSVpaoitscdXw==", + "dev": true + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", @@ -3130,6 +3139,14 @@ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dependencies": { + "is-what": "^3.14.1" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -3211,6 +3228,11 @@ "postcss-value-parser": "^3.3.0" } }, + "node_modules/css2less": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/css2less/-/css2less-0.1.4.tgz", + "integrity": "sha512-3fvKdQGpMc+NuFxgYyQHM/HbtVydXXTAt1FH7yihYoWq52ydPwfTF5W5OU/zTx2rNZ4L9Nj4qrlWshsLS4Z3fg==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", @@ -3522,6 +3544,18 @@ "node": ">=10.13.0" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", @@ -4866,6 +4900,18 @@ "node": ">=14" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz", @@ -4880,6 +4926,18 @@ "integrity": "sha512-+F8B0ip7w6DskaN3bd3GyTvlPMiBfd3kom3qo+k/1jFNkIqtIUqrD0t7r97qHaBtDkTp/vp1DLAU3lqyj0szFA==", "peer": true }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5302,6 +5360,11 @@ "get-intrinsic": "^1.1.1" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", @@ -5539,6 +5602,40 @@ "node": ">=0.10" } }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", @@ -5863,6 +5960,28 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/map-obj/-/map-obj-4.3.0.tgz", @@ -6387,6 +6506,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -6528,6 +6659,22 @@ "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/next": { "version": "14.0.4", "resolved": "https://registry.npmmirror.com/next/-/next-14.0.4.tgz", @@ -6856,6 +7003,14 @@ "node": ">=8" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", @@ -6918,6 +7073,15 @@ "node": ">=0.10" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/popmotion": { "version": "9.3.6", "resolved": "https://registry.npmmirror.com/popmotion/-/popmotion-9.3.6.tgz", @@ -7016,6 +7180,12 @@ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -7922,6 +8092,18 @@ "is-regex": "^1.1.4" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "optional": true + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.0.tgz", diff --git a/package.json b/package.json index a963e80..2df8b97 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "js-yaml": "^4.1.0", "jsfuck": "^0.4.0", "json-2-csv": "^5.0.1", + "less": "^4.2.0", "next": "^14.0.4", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^10.0.1", @@ -75,6 +76,7 @@ "slick-carousel": "^1.8.1", "sql-formatter": "^15.0.2", "sql.js": "^1.9.0", + "css2less": "^0.1.4", "swiper": "^8.4.7", "typescript": "5.0.4", "xlsx": "^0.18.5", @@ -87,6 +89,7 @@ "@types/big-integer": "^0.0.31", "@types/crypto-js": "^4.2.0", "@types/ip": "^1.1.3", + "@types/less": "^3.0.6", "@types/node": "20.1.5", "@types/node-forge": "^1.3.10", "@types/qs": "^6.9.7", diff --git a/src/pages/less2css.tsx b/src/pages/less2css.tsx new file mode 100644 index 0000000..a9b0971 --- /dev/null +++ b/src/pages/less2css.tsx @@ -0,0 +1,289 @@ +import MainContent from '@/components/MainContent'; +import alert from '@/components/Alert'; +import { Box, Button, Stack } from '@mui/material'; +import { useCallback, useEffect, useState } from 'react'; +import AceEditor from 'react-ace'; +import SwapHorizontalCircleIcon from '@mui/icons-material/SwapHorizontalCircle'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import LESS from 'less'; +import c2l from 'css2less'; + +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/mode-less'; +import 'ace-builds/src-noconflict/mode-css'; + +/** + * A => Less + * B => CSS + * + * 适用于两种语言的互转模版 + * 1. 更改 import ace-builds/src-noconflict/mode-* + * 2. 更改所有 enum 枚举值 + * 3. 在 a2b() 函数和 b2a() 函数中添加转换逻辑 return 转换结果 + * */ + +// 转换顺序 +enum ConvertType { + A2B = 1, + B2A = 2, +} +// 导出文件 MIME 类型;未知设为空 +enum ConvertFileType { + A2B = 'text/css', + B2A = '', +} +// 导出文件名;未知 MIME 类型补充后缀 +enum ExportType { + A2B = 'lesstocss', + B2A = 'csstoless.less', +} +// 按钮文字 +enum ButtonText { + A2B = 'LESS', + B2A = 'CSS', +} +// 导入限制类型 +enum InputAccept { + A2B = '.less', + B2A = '.css', +} +// ace 编辑器 mode 类型 +enum AceMode { + A2B = 'less', + B2A = 'css', +} + +const _C = () => { + const [a, setA] = useState(''); + const [b, setB] = useState(''); + const [convert, setConvert] = useState(ConvertType.A2B); + const [error, setError] = useState(''); + + // 处理 a2b + const a2b = async (v: string) => { + try { + const res = await LESS.render(v); + setB(res.css); + } catch (e) { + setError(String(e)); + } + }; + + // 处理 b2a + const b2a = async (v: string) => { + try { + const res = await c2l(v, {}); + setA(res); + } catch (e) { + setError(String(e) || '未知错误'); + } + }; + + const saveStringToFile = () => { + const blob = + convert === ConvertType.A2B + ? new Blob([b], { type: ConvertFileType.A2B }) + : new Blob([a], { type: ConvertFileType.B2A }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = + convert === ConvertType.A2B ? ExportType.A2B : ExportType.B2A; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + }; + + const handleClick = useCallback(() => { + alert.success('复制成功'); + }, []); + + const handleButtonClick = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + if ( + file.type === + (convert === ConvertType.A2B + ? ConvertFileType.B2A + : ConvertFileType.A2B) + ) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + if (convert === ConvertType.A2B) { + setA(content); + } else setB(content); + setError(''); + }; + + reader.readAsText(file); + } else { + setA(''); + setError('Invalid file type.'); + } + } + }; + + useEffect(() => { + if (a.trim() === '') { + setB(''); + setError(''); + return; + } + if (convert === ConvertType.A2B) { + try { + a2b(a); + setError(''); + } catch (e) { + setError(String(e)); + } + } + }, [a, convert]); + + useEffect(() => { + if (!b) { + setA(''); + setError(''); + return; + } + if (convert === ConvertType.B2A) { + // 判断是否是数组或对象 + try { + b2a(b); + setError(''); + } catch (e) { + setError(String(e)); + } + } else { + setError(''); + } + }, [b, convert]); + + return ( + + + + + + + {convert === ConvertType.B2A ? ButtonText.B2A : ButtonText.A2B} + + + + setConvert( + convert === ConvertType.B2A ? ConvertType.A2B : ConvertType.B2A + ) + } + > + + + + + {convert === ConvertType.B2A ? ButtonText.A2B : ButtonText.B2A} + + + + + + + + + + + + + + + + + ); +}; + +export default _C; diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 0a054ee..b95f7bc 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -381,4 +381,11 @@ export const allTools: Tool[] = [ key: [], subTitle: '在线颜色吸取器,可以快速生成十种常用颜色的代码', }, + { + label: 'LESS CSS 互转', + tags: [Tags.DEV], + path: '/less2css', + key: [], + subTitle: '支持 less 转 css,css 转 less', + }, ]; diff --git a/tsconfig.json b/tsconfig.json index 6b546c9..4e8ca24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "imgLoader.js"], + "include": ["next-env.d.ts", "*.d.ts", "**/*.ts", "**/*.tsx", "imgLoader.js"], "exclude": ["node_modules"] }