@vendure-io/ui 2.0.0-beta.16 → 2.0.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +34 -0
- package/package.json +5 -4
- package/src/components/atoms/sidebar.tsx +1 -1
- package/src/components/molecules/code-block/file-type-icons.tsx +123 -0
- package/src/components/molecules/code-block/transform-command.tsx +177 -0
- package/src/components/molecules/code-block.tsx +23 -385
- package/src/components/molecules/file-dropzone.tsx +15 -5
- package/src/lib/highlight.ts +34 -1
- package/src/lib/utils.ts +0 -19
package/LICENSE.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
Copyright (C) Vendure GmbH
|
|
4
|
+
|
|
5
|
+
This software is available under two different licenses:
|
|
6
|
+
|
|
7
|
+
- GNU General Public License version 3 (GPLv3) as Vendure Community Edition
|
|
8
|
+
- Vendure Commercial License (VCL)
|
|
9
|
+
|
|
10
|
+
The default Vendure license, without a valid Vendure Commercial License agreement, is the Open-Source GPLv3 license.
|
|
11
|
+
|
|
12
|
+
## GNU General Public License version 3 (GPLv3)
|
|
13
|
+
|
|
14
|
+
If you decide to choose the GPLv3 license, you must comply with the following terms:
|
|
15
|
+
|
|
16
|
+
This program is free software: you can redistribute it and/or modify
|
|
17
|
+
it under the terms of the GNU General Public License as published by
|
|
18
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
19
|
+
(at your option) any later version.
|
|
20
|
+
|
|
21
|
+
This program is distributed in the hope that it will be useful,
|
|
22
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
23
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
24
|
+
GNU General Public License for more details.
|
|
25
|
+
|
|
26
|
+
You should have received a copy of the GNU General Public License
|
|
27
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
28
|
+
|
|
29
|
+
## Vendure Commercial License (VCL)
|
|
30
|
+
|
|
31
|
+
Alternatively, commercial and supported versions of the program - also known as
|
|
32
|
+
Commercial Distributions - must be used in accordance with the terms and conditions
|
|
33
|
+
contained in a separate written agreement between you and Vendure GmbH.
|
|
34
|
+
For more information about the Vendure Commercial License (VCL) please contact contact@vendure.io.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure-io/ui",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.17",
|
|
4
4
|
"description": "React component library for Vendure, built on shadcn/ui and Tailwind v4",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"homepage": "https://github.com/vendurehq/design/tree/main/packages/ui",
|
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
"src",
|
|
22
22
|
"!src/**/*.test.ts",
|
|
23
23
|
"!src/**/*.test.tsx",
|
|
24
|
-
"README.md"
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE.md"
|
|
25
26
|
],
|
|
26
27
|
"scripts": {
|
|
27
28
|
"lint": "biome check src/",
|
|
28
|
-
"check-types": "tsc --noEmit",
|
|
29
|
+
"check-types": "tsc --noEmit && tsc --noEmit -p tsconfig.stories.json",
|
|
29
30
|
"test": "bun test",
|
|
30
31
|
"pack:check": "bun pm pack --dry-run"
|
|
31
32
|
},
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"@shikijs/themes": "^3.23.0",
|
|
50
51
|
"@shikijs/transformers": "^3.22.0",
|
|
51
52
|
"@tanstack/react-table": "^8.21.3",
|
|
52
|
-
"@vendure-io/design-tokens": "^2.0.0-beta.
|
|
53
|
+
"@vendure-io/design-tokens": "^2.0.0-beta.8",
|
|
53
54
|
"class-variance-authority": "^0.7.1",
|
|
54
55
|
"clsx": "^2.1.1",
|
|
55
56
|
"cmdk": "^1.1.1",
|
|
@@ -490,7 +490,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
const sidebarMenuButtonVariants = cva(
|
|
493
|
-
"ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg:first-child]:size-4.5 [&>svg:first-child]:[stroke-width:2.125] [&_svg]:shrink-0",
|
|
493
|
+
"ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&>svg:first-child]:size-4.5 [&>svg:first-child]:[stroke-width:2.125] [&_svg]:shrink-0",
|
|
494
494
|
{
|
|
495
495
|
variants: {
|
|
496
496
|
variant: {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand glyphs for common file extensions, shown next to the filename in the
|
|
3
|
+
* header. Path data is inlined from Simple Icons (CC0) so the package takes no
|
|
4
|
+
* icon dependency; glyphs render monochrome via currentColor and inherit the
|
|
5
|
+
* muted header foreground. Unknown extensions fall back to the generic
|
|
6
|
+
* lucide FileIcon.
|
|
7
|
+
*/
|
|
8
|
+
interface FileTypeIconDef {
|
|
9
|
+
/** Brand name of the glyph */
|
|
10
|
+
title: string;
|
|
11
|
+
/** SVG path data on a 24x24 viewBox */
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const typescriptIcon: FileTypeIconDef = {
|
|
16
|
+
title: 'TypeScript',
|
|
17
|
+
path: 'M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const reactIcon: FileTypeIconDef = {
|
|
21
|
+
title: 'React',
|
|
22
|
+
path: 'M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const javascriptIcon: FileTypeIconDef = {
|
|
26
|
+
title: 'JavaScript',
|
|
27
|
+
path: 'M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const jsonIcon: FileTypeIconDef = {
|
|
31
|
+
title: 'JSON',
|
|
32
|
+
path: 'M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const htmlIcon: FileTypeIconDef = {
|
|
36
|
+
title: 'HTML5',
|
|
37
|
+
path: 'M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const cssIcon: FileTypeIconDef = {
|
|
41
|
+
title: 'CSS',
|
|
42
|
+
path: 'M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const markdownIcon: FileTypeIconDef = {
|
|
46
|
+
title: 'Markdown',
|
|
47
|
+
path: 'M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mdxIcon: FileTypeIconDef = {
|
|
51
|
+
title: 'MDX',
|
|
52
|
+
path: 'M.79 7.12h22.42c.436 0 .79.355.79.792v8.176c0 .436-.354.79-.79.79H.79a.79.79 0 0 1-.79-.79V7.912a.79.79 0 0 1 .79-.791V7.12Zm2.507 7.605v-3.122l1.89 1.89L7.12 11.56v3.122h1.055v-5.67l-2.99 2.99L2.24 9.056v5.67h1.055v-.001Zm8.44-1.845-1.474-1.473-.746.746 2.747 2.747 2.745-2.747-.746-.746-1.473 1.473v-4h-1.054v4Zm10.041.987-2.175-2.175 2.22-2.22-.746-.746-2.22 2.22-2.22-2.22-.747.746 2.22 2.22-2.176 2.177.746.746 2.177-2.177 2.176 2.175.745-.746Z',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const yamlIcon: FileTypeIconDef = {
|
|
56
|
+
title: 'YAML',
|
|
57
|
+
path: 'm0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const graphqlIcon: FileTypeIconDef = {
|
|
61
|
+
title: 'GraphQL',
|
|
62
|
+
path: 'M12.002 0a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.54 4.931a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm0 9.862a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm-8.54 4.931a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.276zm-8.542-4.93a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.277zm0-9.863a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.542-3.378L2.953 6.777v10.448l9.049 5.224 9.047-5.224V6.777zm0 1.601 7.66 13.27H4.34zm-1.387.371L3.97 15.037V7.363zm2.774 0 6.646 3.838v7.674zM5.355 17.44h13.293l-6.646 3.836z',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const bashIcon: FileTypeIconDef = {
|
|
66
|
+
title: 'Bash',
|
|
67
|
+
path: 'M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const dockerIcon: FileTypeIconDef = {
|
|
71
|
+
title: 'Docker',
|
|
72
|
+
path: 'M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const FILE_TYPE_ICONS: Record<string, FileTypeIconDef> = {
|
|
76
|
+
ts: typescriptIcon,
|
|
77
|
+
mts: typescriptIcon,
|
|
78
|
+
cts: typescriptIcon,
|
|
79
|
+
tsx: reactIcon,
|
|
80
|
+
jsx: reactIcon,
|
|
81
|
+
js: javascriptIcon,
|
|
82
|
+
mjs: javascriptIcon,
|
|
83
|
+
cjs: javascriptIcon,
|
|
84
|
+
json: jsonIcon,
|
|
85
|
+
jsonc: jsonIcon,
|
|
86
|
+
html: htmlIcon,
|
|
87
|
+
css: cssIcon,
|
|
88
|
+
md: markdownIcon,
|
|
89
|
+
mdx: mdxIcon,
|
|
90
|
+
yml: yamlIcon,
|
|
91
|
+
yaml: yamlIcon,
|
|
92
|
+
graphql: graphqlIcon,
|
|
93
|
+
gql: graphqlIcon,
|
|
94
|
+
sh: bashIcon,
|
|
95
|
+
bash: bashIcon,
|
|
96
|
+
zsh: bashIcon,
|
|
97
|
+
dockerfile: dockerIcon,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the brand icon for a filename by its extension. Matching uses the
|
|
102
|
+
* basename, so directory names never interfere. `Dockerfile` matches by full
|
|
103
|
+
* basename since it has no extension. Returns undefined for unknown extensions
|
|
104
|
+
* and dotfiles; the header then falls back to the generic file icon.
|
|
105
|
+
*/
|
|
106
|
+
function matchFileTypeIcon(filename: string): FileTypeIconDef | undefined {
|
|
107
|
+
const basename = filename.split('/').pop()?.toLowerCase() ?? '';
|
|
108
|
+
if (basename === 'dockerfile') return FILE_TYPE_ICONS.dockerfile;
|
|
109
|
+
const dotIndex = basename.lastIndexOf('.');
|
|
110
|
+
if (dotIndex <= 0) return undefined;
|
|
111
|
+
return FILE_TYPE_ICONS[basename.slice(dotIndex + 1)];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function FileTypeIcon({ icon }: { icon: FileTypeIconDef }) {
|
|
115
|
+
return (
|
|
116
|
+
<svg viewBox="0 0 24 24" className="size-3.5 shrink-0 fill-current" aria-hidden="true">
|
|
117
|
+
<path d={icon.path} />
|
|
118
|
+
</svg>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { FileTypeIcon, matchFileTypeIcon };
|
|
123
|
+
export type { FileTypeIconDef };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
2
|
+
|
|
3
|
+
const PACKAGE_MANAGERS: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detects if a single line is an npm/npx command that can be transformed
|
|
7
|
+
*/
|
|
8
|
+
function isNpmLine(line: string): boolean {
|
|
9
|
+
const trimmed = line.trim();
|
|
10
|
+
return /^(npm\s+(install|i|add|run|exec|create|init|ci|remove|uninstall|rm)|npx\s+)/.test(
|
|
11
|
+
trimmed,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Detects if the code contains any package manager commands that can be transformed
|
|
17
|
+
*/
|
|
18
|
+
function isPackageManagerCommand(code: string): boolean {
|
|
19
|
+
const lines = code.split('\n');
|
|
20
|
+
return lines.some((line) => isNpmLine(line));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Transforms a single npm/npx command line to the equivalent for other package
|
|
25
|
+
* managers, preserving the line's leading whitespace. Returns the line unchanged
|
|
26
|
+
* when no rewrite applies.
|
|
27
|
+
*/
|
|
28
|
+
function transformSingleLine(line: string, targetPm: PackageManager): string {
|
|
29
|
+
if (targetPm === 'npm') return line;
|
|
30
|
+
|
|
31
|
+
const transformed = transformTrimmedCommand(line.trim(), targetPm);
|
|
32
|
+
if (transformed === null) return line;
|
|
33
|
+
|
|
34
|
+
const indent = line.slice(0, line.length - line.trimStart().length);
|
|
35
|
+
return indent + transformed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function transformTrimmedCommand(
|
|
39
|
+
trimmed: string,
|
|
40
|
+
targetPm: Exclude<PackageManager, 'npm'>,
|
|
41
|
+
): string | null {
|
|
42
|
+
// Handle npx commands
|
|
43
|
+
if (trimmed.startsWith('npx ')) {
|
|
44
|
+
const rest = trimmed.slice(4);
|
|
45
|
+
switch (targetPm) {
|
|
46
|
+
case 'pnpm':
|
|
47
|
+
return `pnpm dlx ${rest}`;
|
|
48
|
+
case 'yarn':
|
|
49
|
+
return `yarn dlx ${rest}`;
|
|
50
|
+
case 'bun':
|
|
51
|
+
return `bunx ${rest}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle npm create / npm init
|
|
56
|
+
if (/^npm\s+(create|init)\s+/.test(trimmed)) {
|
|
57
|
+
const match = trimmed.match(/^npm\s+(create|init)\s+(.+)$/);
|
|
58
|
+
if (match) {
|
|
59
|
+
const args = match[2];
|
|
60
|
+
switch (targetPm) {
|
|
61
|
+
case 'pnpm':
|
|
62
|
+
return `pnpm create ${args}`;
|
|
63
|
+
case 'yarn':
|
|
64
|
+
return `yarn create ${args}`;
|
|
65
|
+
case 'bun':
|
|
66
|
+
return `bun create ${args}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle npm install / npm i / npm add (with packages)
|
|
72
|
+
if (/^npm\s+(install|i|add)\s+/.test(trimmed)) {
|
|
73
|
+
const match = trimmed.match(/^npm\s+(install|i|add)\s+(.+)$/);
|
|
74
|
+
if (match) {
|
|
75
|
+
const packages = match[2] ?? '';
|
|
76
|
+
// Match dev flags as whole tokens so package names containing "-D"
|
|
77
|
+
// don't false-positive.
|
|
78
|
+
const tokens = packages.split(/\s+/);
|
|
79
|
+
const devFlag = tokens.includes('-D') || tokens.includes('--save-dev');
|
|
80
|
+
const cleanPackages = tokens
|
|
81
|
+
.filter((token) => token !== '-D' && token !== '--save-dev')
|
|
82
|
+
.join(' ');
|
|
83
|
+
|
|
84
|
+
switch (targetPm) {
|
|
85
|
+
case 'pnpm':
|
|
86
|
+
return devFlag ? `pnpm add -D ${cleanPackages}` : `pnpm add ${cleanPackages}`;
|
|
87
|
+
case 'yarn':
|
|
88
|
+
return devFlag ? `yarn add -D ${cleanPackages}` : `yarn add ${cleanPackages}`;
|
|
89
|
+
case 'bun':
|
|
90
|
+
return devFlag ? `bun add -d ${cleanPackages}` : `bun add ${cleanPackages}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle npm install (without packages - install from package.json)
|
|
96
|
+
if (/^npm\s+(install|i|ci)$/.test(trimmed)) {
|
|
97
|
+
switch (targetPm) {
|
|
98
|
+
case 'pnpm':
|
|
99
|
+
return 'pnpm install';
|
|
100
|
+
case 'yarn':
|
|
101
|
+
return 'yarn';
|
|
102
|
+
case 'bun':
|
|
103
|
+
return 'bun install';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle npm run <script>
|
|
108
|
+
if (/^npm\s+run\s+/.test(trimmed)) {
|
|
109
|
+
const match = trimmed.match(/^npm\s+run\s+(.+)$/);
|
|
110
|
+
if (match) {
|
|
111
|
+
const script = match[1];
|
|
112
|
+
switch (targetPm) {
|
|
113
|
+
case 'pnpm':
|
|
114
|
+
return `pnpm run ${script}`;
|
|
115
|
+
case 'yarn':
|
|
116
|
+
return `yarn ${script}`;
|
|
117
|
+
case 'bun':
|
|
118
|
+
return `bun run ${script}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle npm remove / npm uninstall / npm rm
|
|
124
|
+
if (/^npm\s+(remove|uninstall|rm)\s+/.test(trimmed)) {
|
|
125
|
+
const match = trimmed.match(/^npm\s+(remove|uninstall|rm)\s+(.+)$/);
|
|
126
|
+
if (match) {
|
|
127
|
+
const packages = match[2];
|
|
128
|
+
switch (targetPm) {
|
|
129
|
+
case 'pnpm':
|
|
130
|
+
return `pnpm remove ${packages}`;
|
|
131
|
+
case 'yarn':
|
|
132
|
+
return `yarn remove ${packages}`;
|
|
133
|
+
case 'bun':
|
|
134
|
+
return `bun remove ${packages}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Handle npm exec
|
|
140
|
+
if (/^npm\s+exec\s+/.test(trimmed)) {
|
|
141
|
+
const match = trimmed.match(/^npm\s+exec\s+(.+)$/);
|
|
142
|
+
if (match) {
|
|
143
|
+
const rest = match[1];
|
|
144
|
+
switch (targetPm) {
|
|
145
|
+
case 'pnpm':
|
|
146
|
+
return `pnpm exec ${rest}`;
|
|
147
|
+
case 'yarn':
|
|
148
|
+
return `yarn exec ${rest}`;
|
|
149
|
+
case 'bun':
|
|
150
|
+
return `bun x ${rest}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Transforms all npm/npx commands in a code block to the target package manager.
|
|
160
|
+
* Leaves non-npm lines unchanged.
|
|
161
|
+
*/
|
|
162
|
+
function transformCommand(code: string, targetPm: PackageManager): string {
|
|
163
|
+
if (targetPm === 'npm') return code;
|
|
164
|
+
|
|
165
|
+
const lines = code.split('\n');
|
|
166
|
+
const transformedLines = lines.map((line) => {
|
|
167
|
+
if (isNpmLine(line)) {
|
|
168
|
+
return transformSingleLine(line, targetPm);
|
|
169
|
+
}
|
|
170
|
+
return line;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return transformedLines.join('\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { PACKAGE_MANAGERS, isPackageManagerCommand, transformCommand };
|
|
177
|
+
export type { PackageManager };
|
|
@@ -3,12 +3,23 @@
|
|
|
3
3
|
import { Button } from '@vendure-io/ui/components/atoms/button';
|
|
4
4
|
import { ScrollArea, ScrollBar } from '@vendure-io/ui/components/atoms/scroll-area';
|
|
5
5
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@vendure-io/ui/components/atoms/tooltip';
|
|
6
|
+
import {
|
|
7
|
+
FileTypeIcon,
|
|
8
|
+
matchFileTypeIcon,
|
|
9
|
+
} from '@vendure-io/ui/components/molecules/code-block/file-type-icons';
|
|
10
|
+
import {
|
|
11
|
+
isPackageManagerCommand,
|
|
12
|
+
PACKAGE_MANAGERS,
|
|
13
|
+
type PackageManager,
|
|
14
|
+
transformCommand,
|
|
15
|
+
} from '@vendure-io/ui/components/molecules/code-block/transform-command';
|
|
6
16
|
import { useCopyFeedback } from '@vendure-io/ui/components/molecules/copy-feedback-provider';
|
|
7
17
|
import { useCopy } from '@vendure-io/ui/hooks/use-copy';
|
|
8
18
|
import {
|
|
9
19
|
highlightCode,
|
|
10
20
|
normalizeLanguage,
|
|
11
21
|
type SupportedLanguage,
|
|
22
|
+
supportsNotationComments,
|
|
12
23
|
} from '@vendure-io/ui/lib/highlight';
|
|
13
24
|
import { cn } from '@vendure-io/ui/lib/utils';
|
|
14
25
|
import {
|
|
@@ -21,24 +32,14 @@ import {
|
|
|
21
32
|
TerminalIcon,
|
|
22
33
|
} from 'lucide-react';
|
|
23
34
|
import { motion } from 'motion/react';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
useState,
|
|
33
|
-
} from 'react';
|
|
34
|
-
|
|
35
|
-
// =============================================================================
|
|
36
|
-
// Types
|
|
37
|
-
// =============================================================================
|
|
38
|
-
|
|
39
|
-
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
40
|
-
|
|
41
|
-
type CodeBlockProps = PropsWithChildren<ComponentProps<'div'>> & {
|
|
35
|
+
import { type ComponentProps, type ReactNode, useEffect, useId, useMemo, useState } from 'react';
|
|
36
|
+
|
|
37
|
+
type CodeBlockProps = Omit<ComponentProps<'div'>, 'children'> & {
|
|
38
|
+
/**
|
|
39
|
+
* The code to render, as a plain string. Anything else (elements, numbers)
|
|
40
|
+
* would stringify uselessly, so the type rejects it.
|
|
41
|
+
*/
|
|
42
|
+
children?: string;
|
|
42
43
|
/** Programming language for syntax highlighting */
|
|
43
44
|
language?: string;
|
|
44
45
|
/**
|
|
@@ -71,15 +72,9 @@ interface CodeBlockActionProps {
|
|
|
71
72
|
disabled?: boolean;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
// =============================================================================
|
|
75
|
-
// Constants
|
|
76
|
-
// =============================================================================
|
|
77
|
-
|
|
78
|
-
const PACKAGE_MANAGERS: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
79
75
|
const STORAGE_KEY = 'vendure-ui-package-manager';
|
|
80
76
|
const STORAGE_EVENT = 'vendure-ui-package-manager-change';
|
|
81
77
|
|
|
82
|
-
// Expand/collapse constants
|
|
83
78
|
const COLLAPSE_THRESHOLD = 40; // Lines needed to trigger collapse
|
|
84
79
|
const COLLAPSED_HEIGHT = 300; // px height when collapsed (roughly 15-20 lines)
|
|
85
80
|
|
|
@@ -119,7 +114,6 @@ function setStoredPackageManager(pm: PackageManager): void {
|
|
|
119
114
|
function usePackageManager(): [PackageManager, (pm: PackageManager) => void] {
|
|
120
115
|
const [packageManager, setPackageManager] = useState<PackageManager>('npm');
|
|
121
116
|
|
|
122
|
-
// Initialize from localStorage on mount
|
|
123
117
|
useEffect(() => {
|
|
124
118
|
setPackageManager(getStoredPackageManager());
|
|
125
119
|
}, []);
|
|
@@ -144,201 +138,6 @@ function usePackageManager(): [PackageManager, (pm: PackageManager) => void] {
|
|
|
144
138
|
return [packageManager, updatePackageManager];
|
|
145
139
|
}
|
|
146
140
|
|
|
147
|
-
// =============================================================================
|
|
148
|
-
// Utility Functions
|
|
149
|
-
// =============================================================================
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Detects if a single line is an npm/npx command that can be transformed
|
|
153
|
-
*/
|
|
154
|
-
function isNpmLine(line: string): boolean {
|
|
155
|
-
const trimmed = line.trim();
|
|
156
|
-
return /^(npm\s+(install|i|add|run|exec|create|init|ci|remove|uninstall|rm)|npx\s+)/.test(
|
|
157
|
-
trimmed,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Detects if the code contains any package manager commands that can be transformed
|
|
163
|
-
*/
|
|
164
|
-
function isPackageManagerCommand(code: string): boolean {
|
|
165
|
-
const lines = code.split('\n');
|
|
166
|
-
return lines.some((line) => isNpmLine(line));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Transforms a single npm/npx command line to the equivalent for other package managers
|
|
171
|
-
*/
|
|
172
|
-
function transformSingleLine(line: string, targetPm: PackageManager): string {
|
|
173
|
-
const trimmed = line.trim();
|
|
174
|
-
|
|
175
|
-
if (targetPm === 'npm') return line;
|
|
176
|
-
|
|
177
|
-
// Handle npx commands
|
|
178
|
-
if (trimmed.startsWith('npx ')) {
|
|
179
|
-
const rest = trimmed.slice(4);
|
|
180
|
-
switch (targetPm) {
|
|
181
|
-
case 'pnpm':
|
|
182
|
-
return `pnpm dlx ${rest}`;
|
|
183
|
-
case 'yarn':
|
|
184
|
-
return `yarn dlx ${rest}`;
|
|
185
|
-
case 'bun':
|
|
186
|
-
return `bunx ${rest}`;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Handle npm create / npm init
|
|
191
|
-
if (/^npm\s+(create|init)\s+/.test(trimmed)) {
|
|
192
|
-
const match = trimmed.match(/^npm\s+(create|init)\s+(.+)$/);
|
|
193
|
-
if (match) {
|
|
194
|
-
const args = match[2];
|
|
195
|
-
switch (targetPm) {
|
|
196
|
-
case 'pnpm':
|
|
197
|
-
return `pnpm create ${args}`;
|
|
198
|
-
case 'yarn':
|
|
199
|
-
return `yarn create ${args}`;
|
|
200
|
-
case 'bun':
|
|
201
|
-
return `bun create ${args}`;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Handle npm install / npm i / npm add (with packages)
|
|
207
|
-
if (/^npm\s+(install|i|add)\s+/.test(trimmed)) {
|
|
208
|
-
const match = trimmed.match(/^npm\s+(install|i|add)\s+(.+)$/);
|
|
209
|
-
if (match) {
|
|
210
|
-
const packages = match[2] ?? '';
|
|
211
|
-
// Handle flags
|
|
212
|
-
const devFlag = packages.includes('-D') || packages.includes('--save-dev');
|
|
213
|
-
const cleanPackages = packages
|
|
214
|
-
.replace(/\s*-D\s*/, ' ')
|
|
215
|
-
.replace(/\s*--save-dev\s*/, ' ')
|
|
216
|
-
.trim();
|
|
217
|
-
|
|
218
|
-
switch (targetPm) {
|
|
219
|
-
case 'pnpm':
|
|
220
|
-
return devFlag ? `pnpm add -D ${cleanPackages}` : `pnpm add ${cleanPackages}`;
|
|
221
|
-
case 'yarn':
|
|
222
|
-
return devFlag ? `yarn add -D ${cleanPackages}` : `yarn add ${cleanPackages}`;
|
|
223
|
-
case 'bun':
|
|
224
|
-
return devFlag ? `bun add -d ${cleanPackages}` : `bun add ${cleanPackages}`;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Handle npm install (without packages - install from package.json)
|
|
230
|
-
if (/^npm\s+(install|i|ci)$/.test(trimmed)) {
|
|
231
|
-
switch (targetPm) {
|
|
232
|
-
case 'pnpm':
|
|
233
|
-
return 'pnpm install';
|
|
234
|
-
case 'yarn':
|
|
235
|
-
return 'yarn';
|
|
236
|
-
case 'bun':
|
|
237
|
-
return 'bun install';
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Handle npm run <script>
|
|
242
|
-
if (/^npm\s+run\s+/.test(trimmed)) {
|
|
243
|
-
const match = trimmed.match(/^npm\s+run\s+(.+)$/);
|
|
244
|
-
if (match) {
|
|
245
|
-
const script = match[1];
|
|
246
|
-
switch (targetPm) {
|
|
247
|
-
case 'pnpm':
|
|
248
|
-
return `pnpm run ${script}`;
|
|
249
|
-
case 'yarn':
|
|
250
|
-
return `yarn ${script}`;
|
|
251
|
-
case 'bun':
|
|
252
|
-
return `bun run ${script}`;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Handle npm remove / npm uninstall / npm rm
|
|
258
|
-
if (/^npm\s+(remove|uninstall|rm)\s+/.test(trimmed)) {
|
|
259
|
-
const match = trimmed.match(/^npm\s+(remove|uninstall|rm)\s+(.+)$/);
|
|
260
|
-
if (match) {
|
|
261
|
-
const packages = match[2];
|
|
262
|
-
switch (targetPm) {
|
|
263
|
-
case 'pnpm':
|
|
264
|
-
return `pnpm remove ${packages}`;
|
|
265
|
-
case 'yarn':
|
|
266
|
-
return `yarn remove ${packages}`;
|
|
267
|
-
case 'bun':
|
|
268
|
-
return `bun remove ${packages}`;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Handle npm exec
|
|
274
|
-
if (/^npm\s+exec\s+/.test(trimmed)) {
|
|
275
|
-
const match = trimmed.match(/^npm\s+exec\s+(.+)$/);
|
|
276
|
-
if (match) {
|
|
277
|
-
const rest = match[1];
|
|
278
|
-
switch (targetPm) {
|
|
279
|
-
case 'pnpm':
|
|
280
|
-
return `pnpm exec ${rest}`;
|
|
281
|
-
case 'yarn':
|
|
282
|
-
return `yarn exec ${rest}`;
|
|
283
|
-
case 'bun':
|
|
284
|
-
return `bun x ${rest}`;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return line;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Transforms all npm/npx commands in a code block to the target package manager.
|
|
294
|
-
* Leaves non-npm lines unchanged.
|
|
295
|
-
*/
|
|
296
|
-
function transformCommand(code: string, targetPm: PackageManager): string {
|
|
297
|
-
if (targetPm === 'npm') return code;
|
|
298
|
-
|
|
299
|
-
const lines = code.split('\n');
|
|
300
|
-
const transformedLines = lines.map((line) => {
|
|
301
|
-
if (isNpmLine(line)) {
|
|
302
|
-
return transformSingleLine(line, targetPm);
|
|
303
|
-
}
|
|
304
|
-
return line;
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
return transformedLines.join('\n');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Languages where Shiki transformers work (have // or # comment syntax)
|
|
312
|
-
*/
|
|
313
|
-
const SHIKI_TRANSFORMER_LANGUAGES = new Set([
|
|
314
|
-
'javascript',
|
|
315
|
-
'js',
|
|
316
|
-
'typescript',
|
|
317
|
-
'ts',
|
|
318
|
-
'tsx',
|
|
319
|
-
'jsx',
|
|
320
|
-
'java',
|
|
321
|
-
'c',
|
|
322
|
-
'cpp',
|
|
323
|
-
'csharp',
|
|
324
|
-
'cs',
|
|
325
|
-
'go',
|
|
326
|
-
'rust',
|
|
327
|
-
'swift',
|
|
328
|
-
'kotlin',
|
|
329
|
-
'scala',
|
|
330
|
-
'php',
|
|
331
|
-
'ruby',
|
|
332
|
-
'python',
|
|
333
|
-
'py',
|
|
334
|
-
'bash',
|
|
335
|
-
'shell',
|
|
336
|
-
'sh',
|
|
337
|
-
'zsh',
|
|
338
|
-
'yaml',
|
|
339
|
-
'yml',
|
|
340
|
-
]);
|
|
341
|
-
|
|
342
141
|
/**
|
|
343
142
|
* Process code to extract filename directive and strip Shiki notations for unsupported languages.
|
|
344
143
|
* Supports: // filename: path/to/file.ts (must be first line)
|
|
@@ -349,7 +148,8 @@ const SHIKI_TRANSFORMER_LANGUAGES = new Set([
|
|
|
349
148
|
* - // [!code ++] and // [!code --] for diff
|
|
350
149
|
* - // [!code focus] for focus mode
|
|
351
150
|
*
|
|
352
|
-
* For languages
|
|
151
|
+
* For languages where the notation would not survive highlighting (see
|
|
152
|
+
* `supportsNotationComments`), these notations are stripped.
|
|
353
153
|
*/
|
|
354
154
|
function processCode(
|
|
355
155
|
code: string,
|
|
@@ -362,8 +162,7 @@ function processCode(
|
|
|
362
162
|
const cleanLines: string[] = [];
|
|
363
163
|
let extractedFilename: string | undefined;
|
|
364
164
|
|
|
365
|
-
|
|
366
|
-
const shouldStripNotations = !SHIKI_TRANSFORMER_LANGUAGES.has(language?.toLowerCase() || '');
|
|
165
|
+
const shouldStripNotations = !supportsNotationComments(language);
|
|
367
166
|
|
|
368
167
|
for (let i = 0; i < lines.length; i++) {
|
|
369
168
|
let line = lines[i] ?? '';
|
|
@@ -378,11 +177,8 @@ function processCode(
|
|
|
378
177
|
}
|
|
379
178
|
}
|
|
380
179
|
|
|
381
|
-
// Strip Shiki notations for unsupported languages
|
|
382
180
|
if (shouldStripNotations) {
|
|
383
|
-
// Remove // [!code ...] notations
|
|
384
181
|
line = line.replace(/\s*\/\/\s*\[!code\s+[^\]]+\]\s*$/, '');
|
|
385
|
-
// Remove # [!code ...] notations
|
|
386
182
|
line = line.replace(/\s*#\s*\[!code\s+[^\]]+\]\s*$/, '');
|
|
387
183
|
// Skip lines that are only the notation (e.g., standalone "// [!code highlight]")
|
|
388
184
|
if (trimmedLine.match(/^(\/\/|#)\s*\[!code\s+[^\]]+\]\s*$/)) {
|
|
@@ -390,7 +186,6 @@ function processCode(
|
|
|
390
186
|
}
|
|
391
187
|
}
|
|
392
188
|
|
|
393
|
-
// Add line to clean output
|
|
394
189
|
cleanLines.push(line);
|
|
395
190
|
}
|
|
396
191
|
|
|
@@ -412,135 +207,6 @@ function escapeHtml(text: string): string {
|
|
|
412
207
|
.replace(/'/g, ''');
|
|
413
208
|
}
|
|
414
209
|
|
|
415
|
-
// =============================================================================
|
|
416
|
-
// File Type Icons
|
|
417
|
-
// =============================================================================
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Brand glyphs for common file extensions, shown next to the filename in the
|
|
421
|
-
* header. Path data is inlined from Simple Icons (CC0) so the package takes no
|
|
422
|
-
* icon dependency; glyphs render monochrome via currentColor and inherit the
|
|
423
|
-
* muted header foreground. Unknown extensions fall back to the generic
|
|
424
|
-
* lucide FileIcon.
|
|
425
|
-
*/
|
|
426
|
-
interface FileTypeIconDef {
|
|
427
|
-
/** Brand name of the glyph */
|
|
428
|
-
title: string;
|
|
429
|
-
/** SVG path data on a 24x24 viewBox */
|
|
430
|
-
path: string;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const typescriptIcon: FileTypeIconDef = {
|
|
434
|
-
title: 'TypeScript',
|
|
435
|
-
path: 'M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z',
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const reactIcon: FileTypeIconDef = {
|
|
439
|
-
title: 'React',
|
|
440
|
-
path: 'M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z',
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
const javascriptIcon: FileTypeIconDef = {
|
|
444
|
-
title: 'JavaScript',
|
|
445
|
-
path: 'M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z',
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
const jsonIcon: FileTypeIconDef = {
|
|
449
|
-
title: 'JSON',
|
|
450
|
-
path: 'M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z',
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
const htmlIcon: FileTypeIconDef = {
|
|
454
|
-
title: 'HTML5',
|
|
455
|
-
path: 'M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z',
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
const cssIcon: FileTypeIconDef = {
|
|
459
|
-
title: 'CSS',
|
|
460
|
-
path: 'M0 0v20.16A3.84 3.84 0 0 0 3.84 24h16.32A3.84 3.84 0 0 0 24 20.16V3.84A3.84 3.84 0 0 0 20.16 0Zm14.256 13.08c1.56 0 2.28 1.08 2.304 2.64h-1.608c.024-.288-.048-.6-.144-.84-.096-.192-.288-.264-.552-.264-.456 0-.696.264-.696.84-.024.576.288.888.768 1.08.72.288 1.608.744 1.92 1.296q.432.648.432 1.656c0 1.608-.912 2.592-2.496 2.592-1.656 0-2.4-1.032-2.424-2.688h1.68c0 .792.264 1.176.792 1.176.264 0 .456-.072.552-.24.192-.312.24-1.176-.048-1.512-.312-.408-.912-.6-1.32-.816q-.828-.396-1.224-.936c-.24-.36-.36-.888-.36-1.536 0-1.44.936-2.472 2.424-2.448m5.4 0c1.584 0 2.304 1.08 2.328 2.64h-1.608c0-.288-.048-.6-.168-.84-.096-.192-.264-.264-.528-.264-.48 0-.72.264-.72.84s.288.888.792 1.08c.696.288 1.608.744 1.92 1.296.264.432.408.984.408 1.656.024 1.608-.888 2.592-2.472 2.592-1.68 0-2.424-1.056-2.448-2.688h1.68c0 .744.264 1.176.792 1.176.264 0 .456-.072.552-.24.216-.312.264-1.176-.048-1.512-.288-.408-.888-.6-1.32-.816-.552-.264-.96-.576-1.2-.936s-.36-.888-.36-1.536c-.024-1.44.912-2.472 2.4-2.448m-11.031.018c.711-.006 1.419.198 1.839.63.432.432.672 1.128.648 1.992H9.336c.024-.456-.096-.792-.432-.96-.312-.144-.768-.048-.888.24-.12.264-.192.576-.168.864v3.504c0 .744.264 1.128.768 1.128a.65.65 0 0 0 .552-.264c.168-.24.192-.552.168-.84h1.776c.096 1.632-.984 2.712-2.568 2.688-1.536 0-2.496-.864-2.472-2.472v-4.032c0-.816.24-1.44.696-1.848.432-.408 1.146-.624 1.857-.63',
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
const markdownIcon: FileTypeIconDef = {
|
|
464
|
-
title: 'Markdown',
|
|
465
|
-
path: 'M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z',
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
const mdxIcon: FileTypeIconDef = {
|
|
469
|
-
title: 'MDX',
|
|
470
|
-
path: 'M.79 7.12h22.42c.436 0 .79.355.79.792v8.176c0 .436-.354.79-.79.79H.79a.79.79 0 0 1-.79-.79V7.912a.79.79 0 0 1 .79-.791V7.12Zm2.507 7.605v-3.122l1.89 1.89L7.12 11.56v3.122h1.055v-5.67l-2.99 2.99L2.24 9.056v5.67h1.055v-.001Zm8.44-1.845-1.474-1.473-.746.746 2.747 2.747 2.745-2.747-.746-.746-1.473 1.473v-4h-1.054v4Zm10.041.987-2.175-2.175 2.22-2.22-.746-.746-2.22 2.22-2.22-2.22-.747.746 2.22 2.22-2.176 2.177.746.746 2.177-2.177 2.176 2.175.745-.746Z',
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
const yamlIcon: FileTypeIconDef = {
|
|
474
|
-
title: 'YAML',
|
|
475
|
-
path: 'm0 .97 4.111 6.453v4.09h2.638v-4.09L11.053.969H8.214L5.58 5.125 2.965.969Zm12.093.024-4.47 10.544h2.114l.97-2.345h4.775l.804 2.345h2.26L14.255.994Zm1.133 2.225 1.463 3.87h-3.096zm3.06 9.475v10.29H24v-2.199h-5.454v-8.091zm-12.175.002v10.335h2.217v-7.129l2.32 4.792h1.746l2.4-4.96v7.295h2.127V12.696h-2.904L9.44 17.37l-2.455-4.674Z',
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
const graphqlIcon: FileTypeIconDef = {
|
|
479
|
-
title: 'GraphQL',
|
|
480
|
-
path: 'M12.002 0a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.54 4.931a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm0 9.862a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm-8.54 4.931a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.276zm-8.542-4.93a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.277zm0-9.863a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.542-3.378L2.953 6.777v10.448l9.049 5.224 9.047-5.224V6.777zm0 1.601 7.66 13.27H4.34zm-1.387.371L3.97 15.037V7.363zm2.774 0 6.646 3.838v7.674zM5.355 17.44h13.293l-6.646 3.836z',
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
const bashIcon: FileTypeIconDef = {
|
|
484
|
-
title: 'Bash',
|
|
485
|
-
path: 'M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z',
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
const dockerIcon: FileTypeIconDef = {
|
|
489
|
-
title: 'Docker',
|
|
490
|
-
path: 'M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z',
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const FILE_TYPE_ICONS: Record<string, FileTypeIconDef> = {
|
|
494
|
-
ts: typescriptIcon,
|
|
495
|
-
mts: typescriptIcon,
|
|
496
|
-
cts: typescriptIcon,
|
|
497
|
-
tsx: reactIcon,
|
|
498
|
-
jsx: reactIcon,
|
|
499
|
-
js: javascriptIcon,
|
|
500
|
-
mjs: javascriptIcon,
|
|
501
|
-
cjs: javascriptIcon,
|
|
502
|
-
json: jsonIcon,
|
|
503
|
-
jsonc: jsonIcon,
|
|
504
|
-
html: htmlIcon,
|
|
505
|
-
css: cssIcon,
|
|
506
|
-
md: markdownIcon,
|
|
507
|
-
mdx: mdxIcon,
|
|
508
|
-
yml: yamlIcon,
|
|
509
|
-
yaml: yamlIcon,
|
|
510
|
-
graphql: graphqlIcon,
|
|
511
|
-
gql: graphqlIcon,
|
|
512
|
-
sh: bashIcon,
|
|
513
|
-
bash: bashIcon,
|
|
514
|
-
zsh: bashIcon,
|
|
515
|
-
dockerfile: dockerIcon,
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Resolve the brand icon for a filename by its extension. Matching uses the
|
|
520
|
-
* basename, so directory names never interfere. `Dockerfile` matches by full
|
|
521
|
-
* basename since it has no extension. Returns undefined for unknown extensions
|
|
522
|
-
* and dotfiles; the header then falls back to the generic file icon.
|
|
523
|
-
*/
|
|
524
|
-
function matchFileTypeIcon(filename: string): FileTypeIconDef | undefined {
|
|
525
|
-
const basename = filename.split('/').pop()?.toLowerCase() ?? '';
|
|
526
|
-
if (basename === 'dockerfile') return FILE_TYPE_ICONS.dockerfile;
|
|
527
|
-
const dotIndex = basename.lastIndexOf('.');
|
|
528
|
-
if (dotIndex <= 0) return undefined;
|
|
529
|
-
return FILE_TYPE_ICONS[basename.slice(dotIndex + 1)];
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function FileTypeIcon({ icon }: { icon: FileTypeIconDef }) {
|
|
533
|
-
return (
|
|
534
|
-
<svg viewBox="0 0 24 24" className="size-3.5 shrink-0 fill-current" aria-hidden="true">
|
|
535
|
-
<path d={icon.path} />
|
|
536
|
-
</svg>
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// =============================================================================
|
|
541
|
-
// Style Constants
|
|
542
|
-
// =============================================================================
|
|
543
|
-
|
|
544
210
|
const darkModeClassNames = cn(
|
|
545
211
|
'dark:[&_.shiki]:!text-[var(--shiki-dark)]',
|
|
546
212
|
'dark:[&_.shiki]:![font-style:var(--shiki-dark-font-style)]',
|
|
@@ -581,10 +247,6 @@ const lineFocusedClassNames = cn(
|
|
|
581
247
|
|
|
582
248
|
const wordHighlightClassNames = cn('[&_.highlighted-word]:bg-code-highlight');
|
|
583
249
|
|
|
584
|
-
// =============================================================================
|
|
585
|
-
// Sub-Components
|
|
586
|
-
// =============================================================================
|
|
587
|
-
|
|
588
250
|
/**
|
|
589
251
|
* Tooltip icon-button used for code-block toolbar actions (and the built-in copy
|
|
590
252
|
* button). Compose these into the `actions` slot of {@link CodeBlock}.
|
|
@@ -700,7 +362,6 @@ function CodeBlockHeader({
|
|
|
700
362
|
toolbar,
|
|
701
363
|
id,
|
|
702
364
|
}: CodeBlockHeaderProps) {
|
|
703
|
-
// Determine what label to show
|
|
704
365
|
const getLabel = () => {
|
|
705
366
|
if (filename) {
|
|
706
367
|
const fileTypeIcon = matchFileTypeIcon(filename);
|
|
@@ -727,7 +388,6 @@ function CodeBlockHeader({
|
|
|
727
388
|
);
|
|
728
389
|
}
|
|
729
390
|
|
|
730
|
-
// Default: show language or "Code"
|
|
731
391
|
return (
|
|
732
392
|
<div className="text-muted-foreground flex items-center gap-1.5 text-sm">
|
|
733
393
|
<CodeIcon className="size-3.5" />
|
|
@@ -804,10 +464,6 @@ const codeBlockContentClassName = cn(
|
|
|
804
464
|
'[&_.line]:relative',
|
|
805
465
|
);
|
|
806
466
|
|
|
807
|
-
// =============================================================================
|
|
808
|
-
// Syntax Highlighted Content Component
|
|
809
|
-
// =============================================================================
|
|
810
|
-
|
|
811
467
|
interface SyntaxHighlightedContentProps {
|
|
812
468
|
code: string;
|
|
813
469
|
language: SupportedLanguage;
|
|
@@ -844,10 +500,6 @@ function SyntaxHighlightedContent({ code, language }: SyntaxHighlightedContentPr
|
|
|
844
500
|
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
845
501
|
}
|
|
846
502
|
|
|
847
|
-
// =============================================================================
|
|
848
|
-
// Main Component
|
|
849
|
-
// =============================================================================
|
|
850
|
-
|
|
851
503
|
/**
|
|
852
504
|
* Shiki-highlighted code block with a header, copy button, collapse/expand for
|
|
853
505
|
* long snippets, and an optional npm→pnpm/yarn/bun package-manager switcher.
|
|
@@ -873,35 +525,23 @@ export function CodeBlock({
|
|
|
873
525
|
const copyFeedback = useCopyFeedback();
|
|
874
526
|
const [activePackageManager, setActivePackageManager] = usePackageManager();
|
|
875
527
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
876
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
877
528
|
const instanceId = useId();
|
|
878
529
|
|
|
879
|
-
|
|
880
|
-
const rawCode = useMemo(() => {
|
|
881
|
-
return typeof children === 'string'
|
|
882
|
-
? children
|
|
883
|
-
: Array.isArray(children)
|
|
884
|
-
? children.join('')
|
|
885
|
-
: String(children || '');
|
|
886
|
-
}, [children]);
|
|
530
|
+
const rawCode = children ?? '';
|
|
887
531
|
|
|
888
|
-
// Process code directives (filename extraction, strip notations for unsupported languages)
|
|
889
532
|
const { cleanCode, extractedFilename } = useMemo(
|
|
890
533
|
() => processCode(rawCode, language),
|
|
891
534
|
[rawCode, language],
|
|
892
535
|
);
|
|
893
536
|
|
|
894
|
-
// Use provided filename or extracted one
|
|
895
537
|
const displayFilename = filename || extractedFilename;
|
|
896
538
|
|
|
897
|
-
// Detect if this is a package manager command (only when the switcher is enabled)
|
|
898
539
|
const isShellLanguage =
|
|
899
540
|
language?.toLowerCase() === 'bash' ||
|
|
900
541
|
language?.toLowerCase() === 'shell' ||
|
|
901
542
|
language?.toLowerCase() === 'sh';
|
|
902
543
|
const isCommand = packageManagerSwitcher && isShellLanguage && isPackageManagerCommand(cleanCode);
|
|
903
544
|
|
|
904
|
-
// Get the display code (transformed for selected package manager if applicable)
|
|
905
545
|
const displayCode = useMemo(() => {
|
|
906
546
|
if (isCommand) {
|
|
907
547
|
return transformCommand(cleanCode, activePackageManager);
|
|
@@ -909,7 +549,6 @@ export function CodeBlock({
|
|
|
909
549
|
return cleanCode;
|
|
910
550
|
}, [cleanCode, isCommand, activePackageManager]);
|
|
911
551
|
|
|
912
|
-
// Calculate if the code block is collapsible
|
|
913
552
|
const lineCount = useMemo(() => displayCode.split('\n').length, [displayCode]);
|
|
914
553
|
const isCollapsible = lineCount > COLLAPSE_THRESHOLD;
|
|
915
554
|
const hiddenLineCount = lineCount - Math.floor(COLLAPSED_HEIGHT / 24); // Approximate visible lines based on line height
|
|
@@ -932,7 +571,6 @@ export function CodeBlock({
|
|
|
932
571
|
'bg-card not-prose border-border relative mb-4 w-full contain-inline-size overflow-hidden rounded-md border text-sm lg:text-base',
|
|
933
572
|
className,
|
|
934
573
|
)}
|
|
935
|
-
ref={containerRef}
|
|
936
574
|
>
|
|
937
575
|
{!hideHeader && (
|
|
938
576
|
<CodeBlockHeader
|
|
@@ -91,9 +91,13 @@ function FileDropzone({
|
|
|
91
91
|
const [validating, setValidating] = React.useState(false);
|
|
92
92
|
const [rejections, setRejections] = React.useState<FileRejection[]>([]);
|
|
93
93
|
const dragDepth = React.useRef(0);
|
|
94
|
+
// Synchronous mirror of `validating`: a drop while an async validateFile is
|
|
95
|
+
// in flight would otherwise start a second addFiles against a stale `value`.
|
|
96
|
+
const busy = React.useRef(false);
|
|
94
97
|
|
|
95
98
|
async function addFiles(incoming: readonly File[]) {
|
|
96
|
-
if (disabled || incoming.length === 0) return;
|
|
99
|
+
if (disabled || busy.current || incoming.length === 0) return;
|
|
100
|
+
busy.current = true;
|
|
97
101
|
setValidating(true);
|
|
98
102
|
const accepted: File[] = [];
|
|
99
103
|
const rejected: FileRejection[] = [];
|
|
@@ -118,11 +122,15 @@ function FileDropzone({
|
|
|
118
122
|
|
|
119
123
|
if (incoming.length > remaining) {
|
|
120
124
|
for (const file of incoming.slice(remaining)) {
|
|
121
|
-
rejected.push({
|
|
125
|
+
rejected.push({
|
|
126
|
+
file,
|
|
127
|
+
messages: [`You can select up to ${maxFiles} ${maxFiles === 1 ? 'file' : 'files'}.`],
|
|
128
|
+
});
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
setRejections(rejected);
|
|
133
|
+
busy.current = false;
|
|
126
134
|
setValidating(false);
|
|
127
135
|
if (rejected.length > 0) onRejected?.(rejected);
|
|
128
136
|
if (accepted.length > 0) {
|
|
@@ -211,7 +219,9 @@ function FileDropzone({
|
|
|
211
219
|
{value.length > 0 ? (
|
|
212
220
|
<ul data-slot="file-dropzone-files" className="flex flex-col gap-2">
|
|
213
221
|
{value.map((file, index) => (
|
|
214
|
-
|
|
222
|
+
// Metadata alone can collide (two distinct files with identical
|
|
223
|
+
// name, size and mtime), so the index disambiguates the key.
|
|
224
|
+
<li key={`${file.name}-${file.size}-${file.lastModified}-${index}`}>
|
|
215
225
|
{renderFile ? (
|
|
216
226
|
renderFile(file, () => removeFile(index))
|
|
217
227
|
) : (
|
|
@@ -240,8 +250,8 @@ function FileDropzone({
|
|
|
240
250
|
|
|
241
251
|
{rejections.length > 0 ? (
|
|
242
252
|
<div id={errorId} role="alert" className="text-destructive text-sm">
|
|
243
|
-
{rejections.map(({ file, messages }) => (
|
|
244
|
-
<p key={`${file.name}-${file.size}`}>
|
|
253
|
+
{rejections.map(({ file, messages }, index) => (
|
|
254
|
+
<p key={`${file.name}-${file.size}-${index}`}>
|
|
245
255
|
<span className="font-medium">{file.name}:</span> {messages.join(' ')}
|
|
246
256
|
</p>
|
|
247
257
|
))}
|
package/src/lib/highlight.ts
CHANGED
|
@@ -137,5 +137,38 @@ function normalizeLanguage(lang?: string): SupportedLanguage {
|
|
|
137
137
|
return 'ini';
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Bundled grammars whose `//` / `#` comment syntax lets the `[!code ...]`
|
|
142
|
+
* notation transformers match. Only languages with a loader above can qualify:
|
|
143
|
+
* anything else falls back to the `ini` grammar, which renders the notation
|
|
144
|
+
* comment as literal text instead of consuming it.
|
|
145
|
+
*/
|
|
146
|
+
const NOTATION_TRANSFORMER_LANGUAGES = new Set<SupportedLanguage>([
|
|
147
|
+
'javascript',
|
|
148
|
+
'typescript',
|
|
149
|
+
'jsx',
|
|
150
|
+
'tsx',
|
|
151
|
+
'python',
|
|
152
|
+
'bash',
|
|
153
|
+
'shellscript',
|
|
154
|
+
'yaml',
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Whether `[!code ...]` notations in `lang` survive highlighting. Alias-aware:
|
|
159
|
+
* the check runs on the normalized language, so `ts`, `py`, `sh`, … qualify
|
|
160
|
+
* through their canonical grammar. Callers should strip notations when this
|
|
161
|
+
* returns false, or they render as literal text.
|
|
162
|
+
*/
|
|
163
|
+
function supportsNotationComments(lang?: string): boolean {
|
|
164
|
+
return NOTATION_TRANSFORMER_LANGUAGES.has(normalizeLanguage(lang));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
NOTATION_TRANSFORMER_LANGUAGES,
|
|
169
|
+
getHighlighter,
|
|
170
|
+
highlightCode,
|
|
171
|
+
normalizeLanguage,
|
|
172
|
+
supportsNotationComments,
|
|
173
|
+
};
|
|
141
174
|
export type { SupportedLanguage };
|
package/src/lib/utils.ts
CHANGED
|
@@ -4,22 +4,3 @@ import { twMerge } from 'tailwind-merge';
|
|
|
4
4
|
export function cn(...inputs: ClassValue[]): string {
|
|
5
5
|
return twMerge(clsx(inputs));
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
export function slugify(str: string): string {
|
|
9
|
-
return str
|
|
10
|
-
.toLowerCase()
|
|
11
|
-
.trim()
|
|
12
|
-
.replace(/[^\w\s-]/g, '')
|
|
13
|
-
.replace(/[\s_-]+/g, '-')
|
|
14
|
-
.replace(/^-+|-+$/g, '');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function hashString(str: string): number {
|
|
18
|
-
let hash = 0;
|
|
19
|
-
for (let i = 0; i < str.length; i++) {
|
|
20
|
-
const char = str.charCodeAt(i);
|
|
21
|
-
hash = (hash << 5) - hash + char;
|
|
22
|
-
hash |= 0;
|
|
23
|
-
}
|
|
24
|
-
return Math.abs(hash);
|
|
25
|
-
}
|