diff --git a/package.json b/package.json index 7f9ee7f..d618329 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "remark-gfm": "4.0.1", "shiki": "3.7.0", "svelte-sonner": "0.3.28", + "svgo": "^4.0.0", "tailwind-merge": "2.6.0", "tailwind-variants": "0.3.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07209ad..4982c22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: svelte-sonner: specifier: 0.3.28 version: 0.3.28(svelte@4.2.20) + svgo: + specifier: ^4.0.0 + version: 4.0.0 tailwind-merge: specifier: 2.6.0 version: 2.6.0 @@ -1100,6 +1103,9 @@ packages: peerDependencies: svelte: ^4.0.0 || ^5.0.0 + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1190,6 +1196,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1228,15 +1238,34 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -1276,6 +1305,19 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -1773,9 +1815,15 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdsvex@0.12.5: resolution: {integrity: sha512-JQy8CBbGF1IvpxZTmGJigRiD1s2BKfLKS9xCLPKngjHToY8WMYLZ8WFGRpuR6x4C4bxipSuLm2LctwL2fVXaEQ==} peerDependencies: @@ -1930,6 +1978,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2271,6 +2322,9 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2420,6 +2474,11 @@ packages: svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -3584,6 +3643,8 @@ snapshots: nanoid: 5.1.2 svelte: 4.2.20 + boolbase@1.0.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3679,6 +3740,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} + commander@4.1.1: {} commondir@1.0.1: {} @@ -3716,13 +3779,37 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + css-tree@2.3.1: dependencies: mdn-data: 2.0.30 source-map-js: 1.2.1 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + cssesc@3.0.0: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -3749,6 +3836,24 @@ snapshots: dlv@1.1.3: {} + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -4372,8 +4477,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.0.28: {} + mdn-data@2.0.30: {} + mdn-data@2.12.2: {} + mdsvex@0.12.5(svelte@4.2.20): dependencies: '@types/mdast': 4.0.4 @@ -4625,6 +4734,10 @@ snapshots: normalize-range@0.1.2: {} + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -4946,6 +5059,8 @@ snapshots: safe-buffer@5.1.2: {} + sax@1.4.1: {} + semver@6.3.1: {} semver@7.7.1: {} @@ -5111,6 +5226,16 @@ snapshots: svg-parser@2.0.4: {} + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.1 + tabbable@6.2.0: {} tailwind-merge@2.5.4: {} diff --git a/src/components/copySvg.svelte b/src/components/copySvg.svelte index f7db1fd..6bfda42 100644 --- a/src/components/copySvg.svelte +++ b/src/components/copySvg.svelte @@ -12,6 +12,7 @@ import { cn } from '@/utils/cn'; import { clipboard } from '@/utils/clipboard'; import { copyToClipboard as figmaCopyToClipboard } from '@/figma/copy-to-clipboard'; + import { getPrefixFromSvgUrl, prefixSvgIds } from '@/utils/prefixSvgIds'; // Templates: import { getSource } from '@/templates/getSource'; @@ -84,10 +85,14 @@ const svgUrlToCopy = getSvgUrl(); optionsOpen = false; - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + if (isInFigma) { figmaCopyToClipboard(content); } @@ -125,9 +130,14 @@ isLoading = true; const title = svgInfo.title.split(' ').join(''); - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + const dataComponent = { code: content, typescript: tsx, name: title }; const { data, error } = await getReactCode(dataComponent); @@ -156,10 +166,14 @@ optionsOpen = false; - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + const copyCode = getVueCode({ content: content, lang: ts ? 'ts' : 'js' @@ -189,10 +203,14 @@ optionsOpen = false; - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + const copyCode = getSvelteCode({ content: content, lang: ts ? 'ts' : 'js' @@ -222,10 +240,14 @@ const title = svgInfo.title.split(' ').join(''); const svgUrlToCopy = getSvgUrl(); - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + if (!content) { toast.error('Failed to fetch the SVG content', { duration: 5000 @@ -255,10 +277,14 @@ const title = svgInfo.title.split(' ').join(''); const svgUrlToCopy = getSvgUrl(); - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + if (!content) { toast.error('Failed to fetch the SVG content', { duration: 5000 @@ -287,10 +313,14 @@ optionsOpen = false; const svgUrlToCopy = getSvgUrl(); - const content = await getSource({ + let content = await getSource({ url: svgUrlToCopy }); + if (svgUrlToCopy) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(svgUrlToCopy)); + } + if (!content) { toast.error('Failed to fetch the SVG content', { duration: 5000 diff --git a/src/components/downloadSvg.svelte b/src/components/downloadSvg.svelte index 1528fbd..cb784a6 100644 --- a/src/components/downloadSvg.svelte +++ b/src/components/downloadSvg.svelte @@ -17,6 +17,7 @@ } from '@/ui/dialog'; import { buttonStyles } from '@/ui/styles'; import { cn } from '@/utils/cn'; + import { getPrefixFromSvgUrl, prefixSvgIds } from '@/utils/prefixSvgIds'; // Props: export let svgInfo: iSVG; @@ -31,8 +32,14 @@ 'flex w-full h-full flex-col p-4 rounded-md shadow-sm dark:bg-neutral-800/20 bg-neutral-200/10 border border-neutral-200 dark:border-neutral-800 space-y-2'; // Functions: - const downloadSvg = (url?: string) => { - download(url || ''); + const downloadSvg = async (url?: string) => { + let content = await getSource({ + url: url + }); + if (url) { + content = prefixSvgIds(content, getPrefixFromSvgUrl(url)); + } + download(content || '', url?.split('/').pop() || '', 'image/svg+xml'); const category = Array.isArray(svgInfo.category) ? svgInfo.category.sort().join(' - ') @@ -55,13 +62,22 @@ }) => { const zip = new JSZip(); - const lightSvg = await getSource({ + let lightSvg = await getSource({ url: lightRoute }); - const darkSvg = await getSource({ + let darkSvg = await getSource({ url: darkRoute }); + lightSvg = prefixSvgIds( + lightSvg, + svgInfo.title.toLowerCase() + (isWordmark ? '_wordmark_light' : '_light') + ); + darkSvg = prefixSvgIds( + darkSvg, + svgInfo.title.toLowerCase() + (isWordmark ? '_wordmark_dark' : '_dark') + ); + if (isWordmark) { zip.file(`${svgInfo.title}_wordmark_light.svg`, lightSvg); zip.file(`${svgInfo.title}_wordmark_dark.svg`, darkSvg); diff --git a/src/utils/prefixSvgIds.ts b/src/utils/prefixSvgIds.ts new file mode 100644 index 0000000..db5f6d6 --- /dev/null +++ b/src/utils/prefixSvgIds.ts @@ -0,0 +1,21 @@ +import { optimize } from 'svgo'; + +export const getPrefixFromSvgUrl = (svgUrl: string) => { + return svgUrl.split('/').pop()!.replace('.svg', '').split('-').join('_'); +}; + +export const prefixSvgIds = (content: string, prefix: string): string => { + const result = optimize(content, { + plugins: [ + { + name: 'prefixIds', + params: { + prefix + } + } + ], + multipass: false + }); + + return (result as { data: string }).data; +};