@intlayer/design-system 8.7.6 → 8.7.8-canary.0
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/dist/esm/components/Breadcrumb/index.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/Carousel/index.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/BooleanWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/NestedObjectWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/NumberWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/EnumKeyInput.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/KeyPathBreadcrumb.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NodeTypeSelector.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/EditableField/EditableFieldInput.mjs.map +1 -1
- package/dist/esm/components/Flags/Flag.mjs.map +1 -1
- package/dist/esm/components/Form/FormField.mjs.map +1 -1
- package/dist/esm/components/Form/FormItem.mjs.map +1 -1
- package/dist/esm/components/Form/elements/FormElementWrapper.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/Headers/index.mjs.map +1 -1
- package/dist/esm/components/HeightResizer/index.mjs +25 -0
- package/dist/esm/components/HeightResizer/index.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +96 -13
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs +77 -0
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -0
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeConditionalRenderer.mjs +15 -4
- package/dist/esm/components/IDE/CodeConditionalRenderer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeFormatSelector.mjs +5 -4
- package/dist/esm/components/IDE/CodeFormatSelector.mjs.map +1 -1
- package/dist/esm/components/IDE/FileTree.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/codeTransformer.mjs +228 -0
- package/dist/esm/components/IDE/codeTransformer.mjs.map +1 -0
- package/dist/esm/components/Input/OTPInput.mjs.map +1 -1
- package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherDropDown/LocaleSwitcher.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +101 -0
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -0
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +2 -9
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Navbar/index.mjs +1 -3
- package/dist/esm/components/Navbar/index.mjs.map +1 -1
- package/dist/esm/components/Navbar/useNavigation.mjs +3 -0
- package/dist/esm/components/Navbar/useNavigation.mjs.map +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +1 -1
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +3 -3
- package/dist/esm/components/Select/Multiselect.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutocompleteTextArea.mjs.map +1 -1
- package/dist/esm/components/TextArea/ContentEditableTextArea.mjs.map +1 -1
- package/dist/esm/components/Toaster/Toast.mjs +6 -1
- package/dist/esm/components/Toaster/Toast.mjs.map +1 -1
- package/dist/esm/components/WithResizer/index.mjs +24 -0
- package/dist/esm/components/WithResizer/index.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +11 -11
- package/dist/esm/hooks/reactQuery.mjs +49 -2
- package/dist/esm/hooks/reactQuery.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs.map +1 -1
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useHorizontalSwipe.mjs.map +1 -1
- package/dist/esm/hooks/useIsDarkMode.mjs.map +1 -1
- package/dist/esm/hooks/useKeyboardDetector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollBlockage/useScrollBlockageStore.mjs.map +1 -1
- package/dist/esm/libs/auth.mjs +1 -1
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/providers/ReactQueryProvider.mjs.map +1 -1
- package/dist/esm/tailwind.config.mjs.map +1 -1
- package/dist/types/components/Badge/index.d.ts +2 -2
- package/dist/types/components/Button/Button.d.ts +4 -4
- package/dist/types/components/Command/index.d.ts +1 -1
- package/dist/types/components/Container/index.d.ts +7 -7
- package/dist/types/components/HeightResizer/index.d.ts.map +1 -1
- package/dist/types/components/IDE/Code.d.ts +3 -3
- package/dist/types/components/IDE/Code.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts +20 -0
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -0
- package/dist/types/components/IDE/CodeConditionalRenderer.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeFormatSelector.d.ts +5 -1
- package/dist/types/components/IDE/CodeFormatSelector.d.ts.map +1 -1
- package/dist/types/components/IDE/codeTransformer.d.ts +25 -0
- package/dist/types/components/IDE/codeTransformer.d.ts.map +1 -0
- package/dist/types/components/Input/Checkbox.d.ts +2 -2
- package/dist/types/components/Link/Link.d.ts +4 -4
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts +7 -0
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -0
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts +1 -1
- package/dist/types/components/SwitchSelector/index.d.ts +1 -1
- package/dist/types/components/Tab/Tab.d.ts +1 -1
- package/dist/types/components/TabSelector/TabSelector.d.ts +1 -1
- package/dist/types/components/Tag/index.d.ts +3 -3
- package/dist/types/components/Toaster/Toast.d.ts +1 -1
- package/dist/types/components/WithResizer/index.d.ts.map +1 -1
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/reactQuery.d.ts +8 -1
- package/dist/types/hooks/reactQuery.d.ts.map +1 -1
- package/package.json +25 -25
|
@@ -1,54 +1,137 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import { cn } from "../../utils/cn.mjs";
|
|
2
4
|
import { Container } from "../Container/index.mjs";
|
|
3
5
|
import { ExpandCollapse } from "../ExpandCollapse/ExpandCollapse.mjs";
|
|
4
6
|
import { CodeBlock } from "./CodeBlockClient.mjs";
|
|
7
|
+
import { CodeBlockHighlight } from "./CodeBlockHighlight.mjs";
|
|
8
|
+
import { useCodeContext } from "./CodeContext.mjs";
|
|
5
9
|
import { CodeConditionalRender } from "./CodeConditionalRenderer.mjs";
|
|
6
10
|
import { CodeFormatSelector } from "./CodeFormatSelector.mjs";
|
|
7
11
|
import { ContentDeclarationFormatSelector } from "./ContentDeclarationFormatSelector.mjs";
|
|
8
12
|
import { CopyCode } from "./CopyCode.mjs";
|
|
9
13
|
import { PackageManagerSelector } from "./PackageManagerSelector.mjs";
|
|
10
|
-
import {
|
|
14
|
+
import { useEffect, useMemo, useState } from "react";
|
|
15
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
16
|
|
|
12
17
|
//#region src/components/IDE/Code.tsx
|
|
13
18
|
const MIN_HEIGHT = 700;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
/** Languages that use JSX syntax — CommonJS doesn't make sense for these. */
|
|
20
|
+
const JSX_LANGUAGES = new Set(["tsx", "jsx"]);
|
|
21
|
+
/** Parse a codeFormat prop that may be a single value or a JSON-array string. */
|
|
22
|
+
function parseFormats(raw) {
|
|
23
|
+
if (!raw) return void 0;
|
|
24
|
+
if (raw.startsWith("[")) try {
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
if (Array.isArray(parsed)) return parsed;
|
|
27
|
+
} catch {}
|
|
28
|
+
return [raw];
|
|
29
|
+
}
|
|
30
|
+
const Code = ({ children, language, isDarkMode, showHeader = true, showLineNumbers = true, className, fileName, packageManager, codeFormat: rawCodeFormat, contentDeclarationFormat: rawContentDeclarationFormat, isRollable = true, ...props }) => {
|
|
31
|
+
const { codeFormat: selectedCodeFormat, contentDeclarationFormat: selectedContentDeclarationFormat } = useCodeContext();
|
|
32
|
+
const codeFormats = useMemo(() => parseFormats(rawCodeFormat), [rawCodeFormat]);
|
|
33
|
+
const contentFormats = useMemo(() => parseFormats(rawContentDeclarationFormat), [rawContentDeclarationFormat]);
|
|
34
|
+
const isMultiCodeFormat = codeFormats !== void 0 && codeFormats.length > 1 && codeFormats.includes("typescript");
|
|
35
|
+
const isMultiContentFormat = contentFormats !== void 0 && contentFormats.length > 1 && contentFormats.includes("typescript");
|
|
36
|
+
const isMultiFormat = isMultiCodeFormat || isMultiContentFormat;
|
|
37
|
+
const selectedFormat = isMultiContentFormat ? selectedContentDeclarationFormat : selectedCodeFormat;
|
|
38
|
+
const effectiveFormats = useMemo(() => {
|
|
39
|
+
const base = isMultiContentFormat ? contentFormats : codeFormats;
|
|
40
|
+
if (!base) return base;
|
|
41
|
+
let filtered = base.filter((f) => f !== "json");
|
|
42
|
+
if (JSX_LANGUAGES.has(language)) filtered = filtered.filter((f) => f !== "commonjs");
|
|
43
|
+
return filtered;
|
|
44
|
+
}, [
|
|
45
|
+
isMultiContentFormat,
|
|
46
|
+
contentFormats,
|
|
47
|
+
codeFormats,
|
|
48
|
+
language
|
|
49
|
+
]);
|
|
50
|
+
const resolvedFormat = useMemo(() => {
|
|
51
|
+
if (!effectiveFormats || effectiveFormats.includes(selectedFormat)) return selectedFormat;
|
|
52
|
+
return effectiveFormats[effectiveFormats.length - 1] ?? "typescript";
|
|
53
|
+
}, [effectiveFormats, selectedFormat]);
|
|
54
|
+
const [displayedFileName, setDisplayedFileName] = useState(fileName);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isMultiFormat || resolvedFormat === "typescript") {
|
|
57
|
+
setDisplayedFileName(fileName);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!fileName) return;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
(async () => {
|
|
63
|
+
const { deriveFileName } = await import("./codeTransformer.mjs");
|
|
64
|
+
if (!cancelled) setDisplayedFileName(deriveFileName(fileName, resolvedFormat));
|
|
65
|
+
})();
|
|
66
|
+
return () => {
|
|
67
|
+
cancelled = true;
|
|
68
|
+
};
|
|
69
|
+
}, [
|
|
70
|
+
fileName,
|
|
71
|
+
isMultiFormat,
|
|
72
|
+
resolvedFormat
|
|
73
|
+
]);
|
|
74
|
+
const rawCode = useMemo(() => children?.endsWith("\n") ? children.slice(0, -1) : children, [children]);
|
|
75
|
+
const [copyCode, setCopyCode] = useState(rawCode);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!isMultiFormat || resolvedFormat === "typescript") {
|
|
78
|
+
setCopyCode(rawCode);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let cancelled = false;
|
|
82
|
+
(async () => {
|
|
83
|
+
const { transformCode } = await import("./codeTransformer.mjs");
|
|
84
|
+
if (!cancelled) setCopyCode(transformCode(rawCode, resolvedFormat));
|
|
85
|
+
})();
|
|
86
|
+
return () => {
|
|
87
|
+
cancelled = true;
|
|
88
|
+
};
|
|
89
|
+
}, [
|
|
90
|
+
rawCode,
|
|
91
|
+
isMultiFormat,
|
|
92
|
+
resolvedFormat
|
|
93
|
+
]);
|
|
94
|
+
const hadSelectInHeader = packageManager || rawCodeFormat || rawContentDeclarationFormat;
|
|
17
95
|
return /* @__PURE__ */ jsx(CodeConditionalRender, {
|
|
18
96
|
packageManager,
|
|
19
|
-
codeFormat,
|
|
20
|
-
contentDeclarationFormat,
|
|
97
|
+
codeFormat: rawCodeFormat,
|
|
98
|
+
contentDeclarationFormat: rawContentDeclarationFormat,
|
|
21
99
|
children: /* @__PURE__ */ jsxs(Container, {
|
|
22
100
|
className: cn("relative min-w-0 max-w-full text-sm leading-6", showLineNumbers && "with-line-number ml-0", className),
|
|
23
101
|
transparency: "lg",
|
|
24
102
|
...props,
|
|
25
|
-
children: [showHeader && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
103
|
+
children: [showHeader && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
26
104
|
className: "grid w-full grid-cols-[1fr_auto] items-center justify-between rounded-t-xl bg-card/50 py-1.5 pr-12 pl-4 text-neutral text-xs",
|
|
27
105
|
children: [/* @__PURE__ */ jsx("span", {
|
|
28
106
|
className: "truncate",
|
|
29
|
-
children:
|
|
107
|
+
children: displayedFileName ?? language
|
|
30
108
|
}), /* @__PURE__ */ jsxs("div", {
|
|
31
109
|
className: "flex items-center gap-2",
|
|
32
110
|
children: [
|
|
33
111
|
packageManager && /* @__PURE__ */ jsx(PackageManagerSelector, {}),
|
|
34
|
-
|
|
35
|
-
|
|
112
|
+
rawCodeFormat && /* @__PURE__ */ jsx(CodeFormatSelector, { availableFormats: effectiveFormats }),
|
|
113
|
+
rawContentDeclarationFormat && /* @__PURE__ */ jsx(ContentDeclarationFormatSelector, {})
|
|
36
114
|
]
|
|
37
115
|
})]
|
|
38
116
|
}), /* @__PURE__ */ jsx("div", {
|
|
39
117
|
className: "sticky top-46 z-20",
|
|
40
118
|
children: /* @__PURE__ */ jsx("div", {
|
|
41
119
|
className: cn("absolute right-2 bottom-0 flex h-7 items-center", hadSelectInHeader && "h-11"),
|
|
42
|
-
children: /* @__PURE__ */ jsx(CopyCode, { code })
|
|
120
|
+
children: /* @__PURE__ */ jsx(CopyCode, { code: copyCode })
|
|
43
121
|
})
|
|
44
122
|
})] }), /* @__PURE__ */ jsx(ExpandCollapse, {
|
|
45
123
|
minHeight: MIN_HEIGHT,
|
|
46
124
|
isRollable,
|
|
47
125
|
className: "min-w-0 max-w-full overflow-x-auto p-2",
|
|
48
|
-
children: /* @__PURE__ */ jsx(
|
|
126
|
+
children: isMultiFormat ? /* @__PURE__ */ jsx(CodeBlockHighlight, {
|
|
127
|
+
originalLang: language,
|
|
128
|
+
targetFormat: resolvedFormat,
|
|
129
|
+
isDarkMode,
|
|
130
|
+
children: rawCode
|
|
131
|
+
}) : /* @__PURE__ */ jsx(CodeBlock, {
|
|
49
132
|
lang: language,
|
|
50
133
|
isDarkMode,
|
|
51
|
-
children:
|
|
134
|
+
children: rawCode
|
|
52
135
|
})
|
|
53
136
|
})]
|
|
54
137
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Code.mjs","names":[],"sources":["../../../../src/components/IDE/Code.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes } from 'react';\nimport type { BundledLanguage } from 'shiki/bundle/web';\nimport { Container } from '../Container';\nimport { ExpandCollapse } from '../ExpandCollapse';\nimport { CodeBlock } from './CodeBlockClient';\nimport { CodeConditionalRender } from './CodeConditionalRenderer';\nimport type {\n CodeFormat,\n ContentDeclarationFormat,\n PackageManager,\n} from './CodeContext';\nimport { CodeFormatSelector } from './CodeFormatSelector';\nimport { ContentDeclarationFormatSelector } from './ContentDeclarationFormatSelector';\nimport { CopyCode } from './CopyCode';\nimport { PackageManagerSelector } from './PackageManagerSelector';\n\nexport type CodeCompAttributes = {\n fileName?: string;\n packageManager?: PackageManager;\n codeFormat?: CodeFormat;\n contentDeclarationFormat?: ContentDeclarationFormat;\n};\n\ntype CodeCompProps = {\n children: string;\n fileName?: string;\n language: BundledLanguage;\n isDarkMode?: boolean;\n showHeader?: boolean;\n showLineNumbers?: boolean;\n isRollable?: boolean;\n} & CodeCompAttributes &\n HTMLAttributes<HTMLDivElement>;\n\nconst MIN_HEIGHT = 700;\n\nexport const Code: FC<CodeCompProps> = ({\n children,\n language,\n isDarkMode,\n showHeader = true,\n showLineNumbers = true,\n className,\n fileName,\n packageManager,\n codeFormat,\n contentDeclarationFormat,\n isRollable = true,\n ...props\n}) => {\n const code = children?.endsWith('\\n') ? children.slice(0, -1) : children;\n\n const hadSelectInHeader =\n packageManager || codeFormat || contentDeclarationFormat;\n\n return (\n <CodeConditionalRender\n packageManager={packageManager}\n codeFormat={codeFormat}\n contentDeclarationFormat={contentDeclarationFormat}\n >\n <Container\n className={cn(\n 'relative min-w-0 max-w-full text-sm leading-6',\n showLineNumbers && 'with-line-number ml-0',\n className\n )}\n transparency=\"lg\"\n {...props}\n >\n {showHeader && (\n <>\n <div className=\"grid w-full grid-cols-[1fr_auto] items-center justify-between rounded-t-xl bg-card/50 py-1.5 pr-12 pl-4 text-neutral text-xs\">\n <span className=\"truncate\">{fileName ?? language}</span>\n <div className=\"flex items-center gap-2\">\n {packageManager && <PackageManagerSelector />}\n {codeFormat && <CodeFormatSelector />}\n {contentDeclarationFormat && (\n <ContentDeclarationFormatSelector />\n )}\n </div>\n </div>\n <div className=\"sticky top-46 z-20\">\n <div\n className={cn(\n 'absolute right-2 bottom-0 flex h-7 items-center',\n hadSelectInHeader && 'h-11'\n )}\n >\n <CopyCode code={code} />\n </div>\n </div>\n </>\n )}\n <ExpandCollapse\n minHeight={MIN_HEIGHT}\n isRollable={isRollable}\n className=\"min-w-0 max-w-full overflow-x-auto p-2\"\n >\n <CodeBlock lang={language} isDarkMode={isDarkMode}>\n {code}\n </CodeBlock>\n </ExpandCollapse>\n </Container>\n </CodeConditionalRender>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAmCA,MAAM,aAAa;AAEnB,MAAa,QAA2B,EACtC,UACA,UACA,YACA,aAAa,MACb,kBAAkB,MAClB,WACA,UACA,gBACA,YACA,0BACA,aAAa,MACb,GAAG,YACC;CACJ,MAAM,OAAO,UAAU,SAAS,KAAK,GAAG,SAAS,MAAM,GAAG,GAAG,GAAG;CAEhE,MAAM,oBACJ,kBAAkB,cAAc;AAElC,QACE,oBAAC,uBAAD;EACkB;EACJ;EACc;YAE1B,qBAAC,WAAD;GACE,WAAW,GACT,iDACA,mBAAmB,yBACnB,UACD;GACD,cAAa;GACb,GAAI;aAPN,CASG,cACC,4CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KAAM,WAAU;eAAY,YAAY;KAAgB,GACxD,qBAAC,OAAD;KAAK,WAAU;eAAf;MACG,kBAAkB,oBAAC,wBAAD,EAA0B;MAC5C,cAAc,oBAAC,oBAAD,EAAsB;MACpC,4BACC,oBAAC,kCAAD,EAAoC;MAElC;OACF;OACN,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,WAAW,GACT,mDACA,qBAAqB,OACtB;eAED,oBAAC,UAAD,EAAgB,MAAQ;KACpB;IACF,EACL,KAEL,oBAAC,gBAAD;IACE,WAAW;IACC;IACZ,WAAU;cAEV,oBAAC,WAAD;KAAW,MAAM;KAAsB;eACpC;KACS;IACG,EACP;;EACU"}
|
|
1
|
+
{"version":3,"file":"Code.mjs","names":[],"sources":["../../../../src/components/IDE/Code.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes } from 'react';\nimport { useEffect, useMemo, useState } from 'react';\nimport type { BundledLanguage } from 'shiki/bundle/web';\nimport { Container } from '../Container';\nimport { ExpandCollapse } from '../ExpandCollapse';\nimport { CodeBlock } from './CodeBlockClient';\nimport { CodeBlockHighlight } from './CodeBlockHighlight';\nimport { CodeConditionalRender } from './CodeConditionalRenderer';\nimport type {\n CodeFormat,\n ContentDeclarationFormat,\n PackageManager,\n} from './CodeContext';\nimport { useCodeContext } from './CodeContext';\nimport { CodeFormatSelector } from './CodeFormatSelector';\nimport { ContentDeclarationFormatSelector } from './ContentDeclarationFormatSelector';\nimport { CopyCode } from './CopyCode';\nimport { PackageManagerSelector } from './PackageManagerSelector';\n\nexport type CodeCompAttributes = {\n fileName?: string;\n packageManager?: PackageManager;\n /** Single format, or a JSON-array string like `[\"typescript\",\"esm\",\"commonjs\"]`. */\n codeFormat?: CodeFormat | string;\n contentDeclarationFormat?: ContentDeclarationFormat | string;\n};\n\ntype CodeCompProps = {\n children: string;\n fileName?: string;\n language: BundledLanguage;\n isDarkMode?: boolean;\n showHeader?: boolean;\n showLineNumbers?: boolean;\n isRollable?: boolean;\n} & CodeCompAttributes &\n HTMLAttributes<HTMLDivElement>;\n\nconst MIN_HEIGHT = 700;\n\n/** Languages that use JSX syntax — CommonJS doesn't make sense for these. */\nconst JSX_LANGUAGES = new Set(['tsx', 'jsx']);\n\n/** Parse a codeFormat prop that may be a single value or a JSON-array string. */\nfunction parseFormats(raw: string | undefined): CodeFormat[] | undefined {\n if (!raw) return undefined;\n if (raw.startsWith('[')) {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed as CodeFormat[];\n } catch {\n /* ignore */\n }\n }\n return [raw as CodeFormat];\n}\n\nexport const Code: FC<CodeCompProps> = ({\n children,\n language,\n isDarkMode,\n showHeader = true,\n showLineNumbers = true,\n className,\n fileName,\n packageManager,\n codeFormat: rawCodeFormat,\n contentDeclarationFormat: rawContentDeclarationFormat,\n isRollable = true,\n ...props\n}) => {\n const {\n codeFormat: selectedCodeFormat,\n contentDeclarationFormat: selectedContentDeclarationFormat,\n } = useCodeContext();\n\n // Parse whichever attribute is present as an array of formats.\n const codeFormats = useMemo(\n () => parseFormats(rawCodeFormat as string | undefined),\n [rawCodeFormat]\n );\n const contentFormats = useMemo(\n () => parseFormats(rawContentDeclarationFormat as string | undefined),\n [rawContentDeclarationFormat]\n );\n\n // A block is \"multi-format\" when it has multiple formats including TypeScript\n // (the canonical source). Such blocks transform at runtime.\n const isMultiCodeFormat =\n codeFormats !== undefined &&\n codeFormats.length > 1 &&\n codeFormats.includes('typescript');\n\n const isMultiContentFormat =\n contentFormats !== undefined &&\n contentFormats.length > 1 &&\n contentFormats.includes('typescript');\n\n const isMultiFormat = isMultiCodeFormat || isMultiContentFormat;\n\n // Determine which context format drives this block's selection.\n // content declaration blocks use selectedContentDeclarationFormat;\n // regular code blocks use selectedCodeFormat.\n const selectedFormat: CodeFormat = isMultiContentFormat\n ? (selectedContentDeclarationFormat as CodeFormat)\n : selectedCodeFormat;\n\n // The formats actually relevant for transformation (no 'json', no 'commonjs' for JSX).\n const effectiveFormats = useMemo<CodeFormat[] | undefined>(() => {\n const base = isMultiContentFormat ? contentFormats : codeFormats;\n if (!base) return base;\n let filtered = base.filter((f) => f !== 'json') as CodeFormat[];\n if (JSX_LANGUAGES.has(language as string)) {\n filtered = filtered.filter((f) => f !== 'commonjs');\n }\n return filtered;\n }, [isMultiContentFormat, contentFormats, codeFormats, language]);\n\n // When the globally-selected format isn't valid for this block\n // (e.g. CJS selected but this is a JSX file), fall back to the last valid one.\n const resolvedFormat: Exclude<CodeFormat, 'json'> = useMemo(() => {\n if (!effectiveFormats || effectiveFormats.includes(selectedFormat)) {\n return selectedFormat as Exclude<CodeFormat, 'json'>;\n }\n return (\n effectiveFormats[effectiveFormats.length - 1] ?? 'typescript'\n ) as Exclude<CodeFormat, 'json'>;\n }, [effectiveFormats, selectedFormat]);\n\n // ── Async filename derivation (dynamic import of transformer) ──────────────\n // We derive the displayed fileName so the header updates when format changes.\n // deriveFileName is tiny but it lives inside codeTransformer, which is only\n // imported when actually needed (non-TypeScript selection).\n const [displayedFileName, setDisplayedFileName] = useState<\n string | undefined\n >(fileName);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setDisplayedFileName(fileName);\n return;\n }\n if (!fileName) return;\n\n let cancelled = false;\n (async () => {\n const { deriveFileName } = await import('./codeTransformer');\n if (!cancelled) setDisplayedFileName(deriveFileName(fileName, resolvedFormat));\n })();\n return () => { cancelled = true; };\n }, [fileName, isMultiFormat, resolvedFormat]);\n\n // ── Async copy text (transformed code for CopyCode) ───────────────────────\n const rawCode = useMemo(\n () => (children?.endsWith('\\n') ? children.slice(0, -1) : children),\n [children]\n );\n\n const [copyCode, setCopyCode] = useState<string>(rawCode);\n\n useEffect(() => {\n if (!isMultiFormat || resolvedFormat === 'typescript') {\n setCopyCode(rawCode);\n return;\n }\n let cancelled = false;\n (async () => {\n const { transformCode } = await import('./codeTransformer');\n if (!cancelled) setCopyCode(transformCode(rawCode, resolvedFormat));\n })();\n return () => { cancelled = true; };\n }, [rawCode, isMultiFormat, resolvedFormat]);\n\n const hadSelectInHeader =\n packageManager || rawCodeFormat || rawContentDeclarationFormat;\n\n return (\n <CodeConditionalRender\n packageManager={packageManager}\n codeFormat={rawCodeFormat as string | undefined}\n contentDeclarationFormat={\n rawContentDeclarationFormat as string | undefined\n }\n >\n <Container\n className={cn(\n 'relative min-w-0 max-w-full text-sm leading-6',\n showLineNumbers && 'with-line-number ml-0',\n className\n )}\n transparency=\"lg\"\n {...props}\n >\n {showHeader && (\n <>\n <div className=\"grid w-full grid-cols-[1fr_auto] items-center justify-between rounded-t-xl bg-card/50 py-1.5 pr-12 pl-4 text-neutral text-xs\">\n <span className=\"truncate\">\n {displayedFileName ?? language}\n </span>\n <div className=\"flex items-center gap-2\">\n {packageManager && <PackageManagerSelector />}\n {rawCodeFormat && (\n <CodeFormatSelector availableFormats={effectiveFormats} />\n )}\n {rawContentDeclarationFormat && (\n <ContentDeclarationFormatSelector />\n )}\n </div>\n </div>\n <div className=\"sticky top-46 z-20\">\n <div\n className={cn(\n 'absolute right-2 bottom-0 flex h-7 items-center',\n hadSelectInHeader && 'h-11'\n )}\n >\n <CopyCode code={copyCode} />\n </div>\n </div>\n </>\n )}\n <ExpandCollapse\n minHeight={MIN_HEIGHT}\n isRollable={isRollable}\n className=\"min-w-0 max-w-full overflow-x-auto p-2\"\n >\n {isMultiFormat ? (\n /*\n * Multi-format: CodeBlockHighlight manages both the transformation\n * (dynamic import of codeTransformer) and the Shiki highlighting in\n * a single useEffect. The previous highlighted output stays visible\n * while the new one loads — no white-text flash.\n */\n <CodeBlockHighlight\n originalLang={language}\n targetFormat={resolvedFormat}\n isDarkMode={isDarkMode}\n >\n {rawCode}\n </CodeBlockHighlight>\n ) : (\n /*\n * Single-format: use the original Suspense-based async Shiki renderer\n * (good for SSR / static content).\n */\n <CodeBlock lang={language} isDarkMode={isDarkMode}>\n {rawCode}\n </CodeBlock>\n )}\n </ExpandCollapse>\n </Container>\n </CodeConditionalRender>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAyCA,MAAM,aAAa;;AAGnB,MAAM,gBAAgB,IAAI,IAAI,CAAC,OAAO,MAAM,CAAC;;AAG7C,SAAS,aAAa,KAAmD;AACvE,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,IAAI,WAAW,IAAI,CACrB,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,CAAE,QAAO;SAC5B;AAIV,QAAO,CAAC,IAAkB;;AAG5B,MAAa,QAA2B,EACtC,UACA,UACA,YACA,aAAa,MACb,kBAAkB,MAClB,WACA,UACA,gBACA,YAAY,eACZ,0BAA0B,6BAC1B,aAAa,MACb,GAAG,YACC;CACJ,MAAM,EACJ,YAAY,oBACZ,0BAA0B,qCACxB,gBAAgB;CAGpB,MAAM,cAAc,cACZ,aAAa,cAAoC,EACvD,CAAC,cAAc,CAChB;CACD,MAAM,iBAAiB,cACf,aAAa,4BAAkD,EACrE,CAAC,4BAA4B,CAC9B;CAID,MAAM,oBACJ,gBAAgB,UAChB,YAAY,SAAS,KACrB,YAAY,SAAS,aAAa;CAEpC,MAAM,uBACJ,mBAAmB,UACnB,eAAe,SAAS,KACxB,eAAe,SAAS,aAAa;CAEvC,MAAM,gBAAgB,qBAAqB;CAK3C,MAAM,iBAA6B,uBAC9B,mCACD;CAGJ,MAAM,mBAAmB,cAAwC;EAC/D,MAAM,OAAO,uBAAuB,iBAAiB;AACrD,MAAI,CAAC,KAAM,QAAO;EAClB,IAAI,WAAW,KAAK,QAAQ,MAAM,MAAM,OAAO;AAC/C,MAAI,cAAc,IAAI,SAAmB,CACvC,YAAW,SAAS,QAAQ,MAAM,MAAM,WAAW;AAErD,SAAO;IACN;EAAC;EAAsB;EAAgB;EAAa;EAAS,CAAC;CAIjE,MAAM,iBAA8C,cAAc;AAChE,MAAI,CAAC,oBAAoB,iBAAiB,SAAS,eAAe,CAChE,QAAO;AAET,SACE,iBAAiB,iBAAiB,SAAS,MAAM;IAElD,CAAC,kBAAkB,eAAe,CAAC;CAMtC,MAAM,CAAC,mBAAmB,wBAAwB,SAEhD,SAAS;AAEX,iBAAgB;AACd,MAAI,CAAC,iBAAiB,mBAAmB,cAAc;AACrD,wBAAqB,SAAS;AAC9B;;AAEF,MAAI,CAAC,SAAU;EAEf,IAAI,YAAY;AAChB,GAAC,YAAY;GACX,MAAM,EAAE,mBAAmB,MAAM,OAAO;AACxC,OAAI,CAAC,UAAW,sBAAqB,eAAe,UAAU,eAAe,CAAC;MAC5E;AACJ,eAAa;AAAE,eAAY;;IAC1B;EAAC;EAAU;EAAe;EAAe,CAAC;CAG7C,MAAM,UAAU,cACP,UAAU,SAAS,KAAK,GAAG,SAAS,MAAM,GAAG,GAAG,GAAG,UAC1D,CAAC,SAAS,CACX;CAED,MAAM,CAAC,UAAU,eAAe,SAAiB,QAAQ;AAEzD,iBAAgB;AACd,MAAI,CAAC,iBAAiB,mBAAmB,cAAc;AACrD,eAAY,QAAQ;AACpB;;EAEF,IAAI,YAAY;AAChB,GAAC,YAAY;GACX,MAAM,EAAE,kBAAkB,MAAM,OAAO;AACvC,OAAI,CAAC,UAAW,aAAY,cAAc,SAAS,eAAe,CAAC;MACjE;AACJ,eAAa;AAAE,eAAY;;IAC1B;EAAC;EAAS;EAAe;EAAe,CAAC;CAE5C,MAAM,oBACJ,kBAAkB,iBAAiB;AAErC,QACE,oBAAC,uBAAD;EACkB;EAChB,YAAY;EACZ,0BACE;YAGF,qBAAC,WAAD;GACE,WAAW,GACT,iDACA,mBAAmB,yBACnB,UACD;GACD,cAAa;GACb,GAAI;aAPN,CASG,cACC,8CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KAAM,WAAU;eACb,qBAAqB;KACjB,GACP,qBAAC,OAAD;KAAK,WAAU;eAAf;MACG,kBAAkB,oBAAC,wBAAD,EAA0B;MAC5C,iBACC,oBAAC,oBAAD,EAAoB,kBAAkB,kBAAoB;MAE3D,+BACC,oBAAC,kCAAD,EAAoC;MAElC;OACF;OACN,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KACE,WAAW,GACT,mDACA,qBAAqB,OACtB;eAED,oBAAC,UAAD,EAAU,MAAM,UAAY;KACxB;IACF,EACL,KAEL,oBAAC,gBAAD;IACE,WAAW;IACC;IACZ,WAAU;cAET,gBAOC,oBAAC,oBAAD;KACE,cAAc;KACd,cAAc;KACF;eAEX;KACkB,IAMrB,oBAAC,WAAD;KAAW,MAAM;KAAsB;eACpC;KACS;IAEC,EACP;;EACU"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/components/IDE/CodeBlockHighlight.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Client-side Shiki highlighter that also handles TypeScript→ESM/CJS transformation.
|
|
9
|
+
*
|
|
10
|
+
* Everything runs inside a single useEffect so that:
|
|
11
|
+
* - The transformer is only dynamically imported when a non-TypeScript format is selected.
|
|
12
|
+
* - The previous highlighted HTML stays visible while the new one loads (no white-text flash).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Map display language names to Shiki grammar identifiers.
|
|
16
|
+
* Shiki's web bundle does not ship a separate 'jsx' grammar — tsx handles both.
|
|
17
|
+
*/
|
|
18
|
+
const toShikiLang = (lang) => {
|
|
19
|
+
switch (lang) {
|
|
20
|
+
case "jsx": return "tsx";
|
|
21
|
+
case "mjs":
|
|
22
|
+
case "cjs": return "javascript";
|
|
23
|
+
default: return lang;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const CodeBlockHighlight = ({ children, originalLang, targetFormat, isDarkMode }) => {
|
|
27
|
+
const [html, setHtml] = useState(null);
|
|
28
|
+
const prevHtml = useRef(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
(async () => {
|
|
32
|
+
try {
|
|
33
|
+
let code = children;
|
|
34
|
+
let shikiLang = toShikiLang(originalLang);
|
|
35
|
+
if (targetFormat !== "typescript") {
|
|
36
|
+
const { transformCode, deriveLanguage } = await import("./codeTransformer.mjs");
|
|
37
|
+
if (cancelled) return;
|
|
38
|
+
code = transformCode(children, targetFormat);
|
|
39
|
+
shikiLang = toShikiLang(deriveLanguage(originalLang, targetFormat));
|
|
40
|
+
}
|
|
41
|
+
const { codeToHtml } = await import("shiki/bundle/web");
|
|
42
|
+
if (cancelled) return;
|
|
43
|
+
const out = await codeToHtml(code, {
|
|
44
|
+
lang: shikiLang,
|
|
45
|
+
theme: isDarkMode ? "github-dark" : "github-light"
|
|
46
|
+
});
|
|
47
|
+
if (!cancelled) {
|
|
48
|
+
prevHtml.current = out;
|
|
49
|
+
setHtml(out);
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
if (!cancelled) setHtml("");
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
};
|
|
58
|
+
}, [
|
|
59
|
+
children,
|
|
60
|
+
originalLang,
|
|
61
|
+
targetFormat,
|
|
62
|
+
isDarkMode
|
|
63
|
+
]);
|
|
64
|
+
const display = html ?? prevHtml.current;
|
|
65
|
+
if (!display) return /* @__PURE__ */ jsx("pre", {
|
|
66
|
+
className: "min-w-0 max-w-full overflow-x-auto",
|
|
67
|
+
children: /* @__PURE__ */ jsx("code", { children })
|
|
68
|
+
});
|
|
69
|
+
return /* @__PURE__ */ jsx("div", {
|
|
70
|
+
dangerouslySetInnerHTML: { __html: display },
|
|
71
|
+
style: { backgroundColor: "transparent" }
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { CodeBlockHighlight };
|
|
77
|
+
//# sourceMappingURL=CodeBlockHighlight.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlockHighlight.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockHighlight.tsx"],"sourcesContent":["'use client';\n\n/**\n * Client-side Shiki highlighter that also handles TypeScript→ESM/CJS transformation.\n *\n * Everything runs inside a single useEffect so that:\n * - The transformer is only dynamically imported when a non-TypeScript format is selected.\n * - The previous highlighted HTML stays visible while the new one loads (no white-text flash).\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { BundledLanguage } from 'shiki/bundle/web';\nimport type { CodeFormat } from './CodeContext';\n\ntype Props = {\n /** Raw TypeScript source code (the canonical \"source of truth\"). */\n children: string;\n /** Language of the source (e.g. 'tsx', 'typescript'). */\n originalLang: BundledLanguage;\n /** Currently selected format: 'typescript' | 'esm' | 'commonjs'. */\n targetFormat: Exclude<CodeFormat, 'json'>;\n isDarkMode?: boolean;\n};\n\n/**\n * Map display language names to Shiki grammar identifiers.\n * Shiki's web bundle does not ship a separate 'jsx' grammar — tsx handles both.\n */\nconst toShikiLang = (lang: string): string => {\n switch (lang) {\n case 'jsx':\n return 'tsx';\n case 'mjs':\n case 'cjs':\n return 'javascript';\n default:\n return lang;\n }\n};\n\nexport const CodeBlockHighlight = ({\n children,\n originalLang,\n targetFormat,\n isDarkMode,\n}: Props) => {\n const [html, setHtml] = useState<string | null>(null);\n const prevHtml = useRef<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n\n (async () => {\n try {\n let code = children;\n let shikiLang = toShikiLang(originalLang);\n\n // Only import the transformer when we actually need it.\n if (targetFormat !== 'typescript') {\n const { transformCode, deriveLanguage } = await import(\n './codeTransformer'\n );\n if (cancelled) return;\n code = transformCode(children, targetFormat);\n shikiLang = toShikiLang(deriveLanguage(originalLang, targetFormat));\n }\n\n const { codeToHtml } = await import('shiki/bundle/web');\n if (cancelled) return;\n\n const out = await codeToHtml(code, {\n lang: shikiLang,\n theme: isDarkMode ? 'github-dark' : 'github-light',\n });\n\n if (!cancelled) {\n prevHtml.current = out;\n setHtml(out);\n }\n } catch {\n // Shiki failed (unknown language, etc.) — fall through to plain-text.\n if (!cancelled) setHtml('');\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [children, originalLang, targetFormat, isDarkMode]);\n\n // Keep the previous highlighted output visible while the new one is loading.\n // This prevents the white-text flash on format switches.\n const display = html ?? prevHtml.current;\n\n if (!display) {\n return (\n <pre className=\"min-w-0 max-w-full overflow-x-auto\">\n <code>{children}</code>\n </pre>\n );\n }\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: display }}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,MAAM,eAAe,SAAyB;AAC5C,SAAQ,MAAR;EACE,KAAK,MACH,QAAO;EACT,KAAK;EACL,KAAK,MACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,sBAAsB,EACjC,UACA,cACA,cACA,iBACW;CACX,MAAM,CAAC,MAAM,WAAW,SAAwB,KAAK;CACrD,MAAM,WAAW,OAAsB,KAAK;AAE5C,iBAAgB;EACd,IAAI,YAAY;AAEhB,GAAC,YAAY;AACX,OAAI;IACF,IAAI,OAAO;IACX,IAAI,YAAY,YAAY,aAAa;AAGzC,QAAI,iBAAiB,cAAc;KACjC,MAAM,EAAE,eAAe,mBAAmB,MAAM,OAC9C;AAEF,SAAI,UAAW;AACf,YAAO,cAAc,UAAU,aAAa;AAC5C,iBAAY,YAAY,eAAe,cAAc,aAAa,CAAC;;IAGrE,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,QAAI,UAAW;IAEf,MAAM,MAAM,MAAM,WAAW,MAAM;KACjC,MAAM;KACN,OAAO,aAAa,gBAAgB;KACrC,CAAC;AAEF,QAAI,CAAC,WAAW;AACd,cAAS,UAAU;AACnB,aAAQ,IAAI;;WAER;AAEN,QAAI,CAAC,UAAW,SAAQ,GAAG;;MAE3B;AAEJ,eAAa;AACX,eAAY;;IAEb;EAAC;EAAU;EAAc;EAAc;EAAW,CAAC;CAItD,MAAM,UAAU,QAAQ,SAAS;AAEjC,KAAI,CAAC,QACH,QACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,QAAD,EAAO,UAAgB;EACnB;AAIV,QACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,QAAQ,SAAS;EAC5C,OAAO,EAAE,iBAAiB,eAAe;EACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlockServer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockServer.tsx"],"sourcesContent":["import {\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n transformerNotationDiff,\n transformerNotationErrorLevel,\n transformerNotationHighlight,\n transformerNotationWordHighlight,\n} from '@shikijs/transformers';\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, Suspense } from 'react';\nimport {\n type BundledLanguage,\n type BundledTheme,\n type CodeToHastOptions,\n codeToHtml,\n} from 'shiki/bundle/web';\n\nexport const CodeBlockShiki = (async ({\n children,\n lang,\n isDarkMode,\n onChange,\n ...props\n}: CodeBlockProps) => {\n const shikiOptions: CodeToHastOptions<BundledLanguage, BundledTheme> = {\n lang,\n theme: isDarkMode ? 'github-dark' : 'github-light',\n transformers: [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationWordHighlight(),\n transformerNotationErrorLevel(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n ],\n };\n\n const out = await codeToHtml(children, shikiOptions);\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: out }}\n {...props}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n}) as unknown as FC<CodeBlockProps>;\n\nconst CodeDefault: FC<CodeBlockProps> = ({\n children,\n isEditable,\n isDarkMode,\n onChange,\n ...props\n}) => (\n <div contentEditable={isEditable} {...props}>\n <pre>\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span className=\"line block w-full\" key={index}>\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\nexport type CodeBlockProps = {\n children: string;\n lang: BundledLanguage;\n isDarkMode?: boolean;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n ...props\n}) => (\n <Suspense fallback={<CodeDefault {...props} />}>\n <CodeBlockShiki\n className={cn('flex w-full', className)}\n contentEditable={isEditable}\n onInput={(e) => onChange?.(e.currentTarget.textContent ?? '')}\n {...props}\n />\n </Suspense>\n);\n"],"mappings":";;;;;;;AAiBA,MAAa,kBAAkB,OAAO,EACpC,UACA,MACA,YACA,UACA,GAAG,YACiB;AAgBpB,QACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,
|
|
1
|
+
{"version":3,"file":"CodeBlockServer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeBlockServer.tsx"],"sourcesContent":["import {\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n transformerNotationDiff,\n transformerNotationErrorLevel,\n transformerNotationHighlight,\n transformerNotationWordHighlight,\n} from '@shikijs/transformers';\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, Suspense } from 'react';\nimport {\n type BundledLanguage,\n type BundledTheme,\n type CodeToHastOptions,\n codeToHtml,\n} from 'shiki/bundle/web';\n\nexport const CodeBlockShiki = (async ({\n children,\n lang,\n isDarkMode,\n onChange,\n ...props\n}: CodeBlockProps) => {\n const shikiOptions: CodeToHastOptions<BundledLanguage, BundledTheme> = {\n lang,\n theme: isDarkMode ? 'github-dark' : 'github-light',\n transformers: [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationWordHighlight(),\n transformerNotationErrorLevel(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n ],\n };\n\n const out = await codeToHtml(children, shikiOptions);\n\n return (\n <div\n dangerouslySetInnerHTML={{ __html: out }}\n {...props}\n style={{ backgroundColor: 'transparent' }}\n />\n );\n}) as unknown as FC<CodeBlockProps>;\n\nconst CodeDefault: FC<CodeBlockProps> = ({\n children,\n isEditable,\n isDarkMode,\n onChange,\n ...props\n}) => (\n <div contentEditable={isEditable} {...props}>\n <pre>\n <code>\n {typeof children === 'string'\n ? children.split('\\n').map((line, index) => (\n <span className=\"line block w-full\" key={index}>\n {line}\n </span>\n ))\n : children}\n </code>\n </pre>\n </div>\n);\n\nexport type CodeBlockProps = {\n children: string;\n lang: BundledLanguage;\n isDarkMode?: boolean;\n isEditable?: boolean;\n onChange?: (content: string) => void;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>;\n\nexport const CodeBlock: FC<CodeBlockProps> = ({\n className,\n onChange,\n isEditable,\n ...props\n}) => (\n <Suspense fallback={<CodeDefault {...props} />}>\n <CodeBlockShiki\n className={cn('flex w-full', className)}\n contentEditable={isEditable}\n onInput={(e) => onChange?.(e.currentTarget.textContent ?? '')}\n {...props}\n />\n </Suspense>\n);\n"],"mappings":";;;;;;;AAiBA,MAAa,kBAAkB,OAAO,EACpC,UACA,MACA,YACA,UACA,GAAG,YACiB;AAgBpB,QACE,oBAAC,OAAD;EACE,yBAAyB,EAAE,QAAQ,MAJrB,WAAW,UAAU;GAZrC;GACA,OAAO,aAAa,gBAAgB;GACpC,cAAc;IACZ,yBAAyB;IACzB,8BAA8B;IAC9B,kCAAkC;IAClC,+BAA+B;IAC/B,0BAA0B;IAC1B,8BAA8B;IAC/B;GAGgD,CAAC,EAIR;EACxC,GAAI;EACJ,OAAO,EAAE,iBAAiB,eAAe;EACzC;;AAIN,MAAM,eAAmC,EACvC,UACA,YACA,YACA,UACA,GAAG,YAEH,oBAAC,OAAD;CAAK,iBAAiB;CAAY,GAAI;WACpC,oBAAC,OAAD,YACE,oBAAC,QAAD,YACG,OAAO,aAAa,WACjB,SAAS,MAAM,KAAK,CAAC,KAAK,MAAM,UAC9B,oBAAC,QAAD;EAAM,WAAU;YACb;EACI,EAFkC,MAElC,CACP,GACF,UACC,GACH;CACF;AAWR,MAAa,aAAiC,EAC5C,WACA,UACA,YACA,GAAG,YAEH,oBAAC,UAAD;CAAU,UAAU,oBAAC,aAAD,EAAa,GAAI,OAAS;WAC5C,oBAAC,gBAAD;EACE,WAAW,GAAG,eAAe,UAAU;EACvC,iBAAiB;EACjB,UAAU,MAAM,WAAW,EAAE,cAAc,eAAe,GAAG;EAC7D,GAAI;EACJ;CACO"}
|
|
@@ -4,14 +4,25 @@ import { useCodeContext } from "./CodeContext.mjs";
|
|
|
4
4
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
//#region src/components/IDE/CodeConditionalRenderer.tsx
|
|
7
|
+
/** Parse a codeFormat prop that may be a single value or a JSON array string. */
|
|
8
|
+
const parseCodeFormats = (raw) => {
|
|
9
|
+
if (raw === void 0) return void 0;
|
|
10
|
+
if (raw.startsWith("[")) try {
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (Array.isArray(parsed)) return parsed;
|
|
13
|
+
} catch {}
|
|
14
|
+
return [raw];
|
|
15
|
+
};
|
|
7
16
|
const CodeConditionalRender = ({ children, ...props }) => {
|
|
8
17
|
const { packageManager, codeFormat, contentDeclarationFormat } = useCodeContext();
|
|
9
18
|
const isPackageManagerUndefined = typeof props.packageManager === "undefined";
|
|
10
19
|
const isPackageManagerSelected = packageManager === props.packageManager;
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
20
|
+
const formats = parseCodeFormats(props.codeFormat);
|
|
21
|
+
const isCodeFormatUndefined = formats === void 0;
|
|
22
|
+
const isCodeFormatSelected = formats ? formats.includes(codeFormat) : false;
|
|
23
|
+
const contentFormats = parseCodeFormats(props.contentDeclarationFormat);
|
|
24
|
+
const isContentDeclarationFormatUndefined = contentFormats === void 0;
|
|
25
|
+
const isContentDeclarationFormatSelected = contentFormats ? contentFormats.includes(contentDeclarationFormat) : false;
|
|
15
26
|
if ((isPackageManagerUndefined || isPackageManagerSelected) && (isCodeFormatUndefined || isCodeFormatSelected) && (isContentDeclarationFormatUndefined || isContentDeclarationFormatSelected)) return children;
|
|
16
27
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
17
28
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeConditionalRenderer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeConditionalRenderer.tsx"],"sourcesContent":["'use client';\n\nimport type { FC, PropsWithChildren } from 'react';\nimport type { CodeCompAttributes } from './Code';\nimport { useCodeContext } from './CodeContext';\n\nexport const CodeConditionalRender: FC<\n PropsWithChildren<CodeCompAttributes>\n> = ({ children, ...props }) => {\n const { packageManager, codeFormat, contentDeclarationFormat } =\n useCodeContext();\n\n const isPackageManagerUndefined = typeof props.packageManager === 'undefined';\n const isPackageManagerSelected = packageManager === props.packageManager;\n\n const
|
|
1
|
+
{"version":3,"file":"CodeConditionalRenderer.mjs","names":[],"sources":["../../../../src/components/IDE/CodeConditionalRenderer.tsx"],"sourcesContent":["'use client';\n\nimport type { FC, PropsWithChildren } from 'react';\nimport type { CodeCompAttributes } from './Code';\nimport type { CodeFormat } from './CodeContext';\nimport { useCodeContext } from './CodeContext';\n\n/** Parse a codeFormat prop that may be a single value or a JSON array string. */\nconst parseCodeFormats = (\n raw: string | undefined\n): CodeFormat[] | undefined => {\n if (raw === undefined) return undefined;\n // JSON array string: `[\"typescript\",\"esm\",\"commonjs\"]`\n if (raw.startsWith('[')) {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) return parsed as CodeFormat[];\n } catch {\n // fall through\n }\n }\n return [raw as CodeFormat];\n};\n\nexport const CodeConditionalRender: FC<\n PropsWithChildren<CodeCompAttributes>\n> = ({ children, ...props }) => {\n const { packageManager, codeFormat, contentDeclarationFormat } =\n useCodeContext();\n\n const isPackageManagerUndefined = typeof props.packageManager === 'undefined';\n const isPackageManagerSelected = packageManager === props.packageManager;\n\n const formats = parseCodeFormats(props.codeFormat as string | undefined);\n const isCodeFormatUndefined = formats === undefined;\n const isCodeFormatSelected = formats ? formats.includes(codeFormat) : false;\n\n const contentFormats = parseCodeFormats(\n props.contentDeclarationFormat as string | undefined\n );\n const isContentDeclarationFormatUndefined = contentFormats === undefined;\n const isContentDeclarationFormatSelected = contentFormats\n ? contentFormats.includes(contentDeclarationFormat as CodeFormat)\n : false;\n\n if (\n (isPackageManagerUndefined || isPackageManagerSelected) &&\n (isCodeFormatUndefined || isCodeFormatSelected) &&\n (isContentDeclarationFormatUndefined || isContentDeclarationFormatSelected)\n ) {\n return children;\n }\n\n return <></>;\n};\n"],"mappings":";;;;;;;AAQA,MAAM,oBACJ,QAC6B;AAC7B,KAAI,QAAQ,OAAW,QAAO;AAE9B,KAAI,IAAI,WAAW,IAAI,CACrB,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,CAAE,QAAO;SAC5B;AAIV,QAAO,CAAC,IAAkB;;AAG5B,MAAa,yBAER,EAAE,UAAU,GAAG,YAAY;CAC9B,MAAM,EAAE,gBAAgB,YAAY,6BAClC,gBAAgB;CAElB,MAAM,4BAA4B,OAAO,MAAM,mBAAmB;CAClE,MAAM,2BAA2B,mBAAmB,MAAM;CAE1D,MAAM,UAAU,iBAAiB,MAAM,WAAiC;CACxE,MAAM,wBAAwB,YAAY;CAC1C,MAAM,uBAAuB,UAAU,QAAQ,SAAS,WAAW,GAAG;CAEtE,MAAM,iBAAiB,iBACrB,MAAM,yBACP;CACD,MAAM,sCAAsC,mBAAmB;CAC/D,MAAM,qCAAqC,iBACvC,eAAe,SAAS,yBAAuC,GAC/D;AAEJ,MACG,6BAA6B,8BAC7B,yBAAyB,0BACzB,uCAAuC,oCAExC,QAAO;AAGT,QAAO,gCAAK"}
|
|
@@ -6,9 +6,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import { useIntlayer } from "react-intlayer";
|
|
7
7
|
|
|
8
8
|
//#region src/components/IDE/CodeFormatSelector.tsx
|
|
9
|
-
const CodeFormatSelector = () => {
|
|
9
|
+
const CodeFormatSelector = ({ availableFormats }) => {
|
|
10
10
|
const { codeFormat, setCodeFormat, setContentDeclarationFormat } = useCodeContext();
|
|
11
11
|
const content = useIntlayer("code-selectors");
|
|
12
|
+
const show = (format) => !availableFormats || availableFormats.includes(format);
|
|
12
13
|
return /* @__PURE__ */ jsxs(Select, {
|
|
13
14
|
value: codeFormat,
|
|
14
15
|
onValueChange: (value) => {
|
|
@@ -20,15 +21,15 @@ const CodeFormatSelector = () => {
|
|
|
20
21
|
"aria-label": content.codeFormat.label.value,
|
|
21
22
|
children: /* @__PURE__ */ jsx(Select.Value, { placeholder: content.codeFormat.placeholder.value })
|
|
22
23
|
}), /* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
23
|
-
/* @__PURE__ */ jsx(Select.Item, {
|
|
24
|
+
show("typescript") && /* @__PURE__ */ jsx(Select.Item, {
|
|
24
25
|
value: "typescript",
|
|
25
26
|
children: "TypeScript"
|
|
26
27
|
}),
|
|
27
|
-
/* @__PURE__ */ jsx(Select.Item, {
|
|
28
|
+
show("esm") && /* @__PURE__ */ jsx(Select.Item, {
|
|
28
29
|
value: "esm",
|
|
29
30
|
children: "ESM"
|
|
30
31
|
}),
|
|
31
|
-
/* @__PURE__ */ jsx(Select.Item, {
|
|
32
|
+
show("commonjs") && /* @__PURE__ */ jsx(Select.Item, {
|
|
32
33
|
value: "commonjs",
|
|
33
34
|
children: "CommonJS"
|
|
34
35
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeFormatSelector.mjs","names":[],"sources":["../../../../src/components/IDE/CodeFormatSelector.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Select } from '../Select';\nimport { useCodeContext } from './CodeContext';\n\nexport const CodeFormatSelector: FC = () => {\n const { codeFormat, setCodeFormat, setContentDeclarationFormat } =\n useCodeContext();\n const content = useIntlayer('code-selectors');\n\n return (\n <Select\n value={codeFormat}\n onValueChange={(value) => {\n setCodeFormat(value as typeof codeFormat);\n setContentDeclarationFormat(value as typeof codeFormat);\n }}\n >\n <Select.Trigger\n className=\"py-1!\"\n aria-label={content.codeFormat.label.value}\n >\n <Select.Value placeholder={content.codeFormat.placeholder.value} />\n </Select.Trigger>\n <Select.Content>\n <Select.Item value=\"typescript\">TypeScript</Select.Item>\n <Select.Item value=\"esm\">ESM</Select.Item
|
|
1
|
+
{"version":3,"file":"CodeFormatSelector.mjs","names":[],"sources":["../../../../src/components/IDE/CodeFormatSelector.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Select } from '../Select';\nimport type { CodeFormat } from './CodeContext';\nimport { useCodeContext } from './CodeContext';\n\ntype CodeFormatSelectorProps = {\n /** When provided, only the listed formats are shown as options. */\n availableFormats?: CodeFormat[];\n};\n\nexport const CodeFormatSelector: FC<CodeFormatSelectorProps> = ({\n availableFormats,\n}) => {\n const { codeFormat, setCodeFormat, setContentDeclarationFormat } =\n useCodeContext();\n const content = useIntlayer('code-selectors');\n\n const show = (format: CodeFormat) =>\n !availableFormats || availableFormats.includes(format);\n\n return (\n <Select\n value={codeFormat}\n onValueChange={(value) => {\n setCodeFormat(value as typeof codeFormat);\n setContentDeclarationFormat(value as typeof codeFormat);\n }}\n >\n <Select.Trigger\n className=\"py-1!\"\n aria-label={content.codeFormat.label.value}\n >\n <Select.Value placeholder={content.codeFormat.placeholder.value} />\n </Select.Trigger>\n <Select.Content>\n {show('typescript') && (\n <Select.Item value=\"typescript\">TypeScript</Select.Item>\n )}\n {show('esm') && <Select.Item value=\"esm\">ESM</Select.Item>}\n {show('commonjs') && (\n <Select.Item value=\"commonjs\">CommonJS</Select.Item>\n )}\n </Select.Content>\n </Select>\n );\n};\n"],"mappings":";;;;;;;;AAaA,MAAa,sBAAmD,EAC9D,uBACI;CACJ,MAAM,EAAE,YAAY,eAAe,gCACjC,gBAAgB;CAClB,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,QAAQ,WACZ,CAAC,oBAAoB,iBAAiB,SAAS,OAAO;AAExD,QACE,qBAAC,QAAD;EACE,OAAO;EACP,gBAAgB,UAAU;AACxB,iBAAc,MAA2B;AACzC,+BAA4B,MAA2B;;YAJ3D,CAOE,oBAAC,OAAO,SAAR;GACE,WAAU;GACV,cAAY,QAAQ,WAAW,MAAM;aAErC,oBAAC,OAAO,OAAR,EAAc,aAAa,QAAQ,WAAW,YAAY,OAAS;GACpD,GACjB,qBAAC,OAAO,SAAR;GACG,KAAK,aAAa,IACjB,oBAAC,OAAO,MAAR;IAAa,OAAM;cAAa;IAAwB;GAEzD,KAAK,MAAM,IAAI,oBAAC,OAAO,MAAR;IAAa,OAAM;cAAM;IAAiB;GACzD,KAAK,WAAW,IACf,oBAAC,OAAO,MAAR;IAAa,OAAM;cAAW;IAAsB;GAEvC,IACV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTree.mjs","names":[],"sources":["../../../../src/components/IDE/FileTree.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronRight } from 'lucide-react';\nimport { type FC, useState } from 'react';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\nimport { createFileTree, type FilePath } from './createFileTree';\n\ntype FileTreeProps = {\n filesPaths: string[];\n activeFile?: string;\n onClickFile?: (filePath: string) => void;\n prePaths?: string[];\n};\n\nconst concatFilePath = (paths: string[]) => paths.join('/');\n\ntype FileItemProps = {\n filesPaths: string[];\n subPath?: FilePath[];\n path: string;\n onClickFile?: (title: string) => void;\n activeFile?: string;\n prePaths: string[];\n isFile: boolean;\n};\n\nconst FileItem: FC<FileItemProps> = ({\n filesPaths,\n path,\n subPath,\n onClickFile,\n activeFile,\n prePaths,\n isFile,\n}) => {\n const [subPathOpen, setSubPathOpen] = useState(true);\n\n const level = prePaths.length + 1;\n const currentPath = concatFilePath([\n ...prePaths.slice(level - 1, level),\n path,\n ]);\n const totalPath = concatFilePath([...prePaths, path]);\n\n const filteredFilePaths = filesPaths\n .map(\n (path) => path.replace(/^\\/?/, '') // This regex matches the first slash, if it exists, at the start of the string)\n )\n .filter((filePath) => filePath.startsWith(currentPath));\n\n const newPath = filteredFilePaths.map((path) =>\n path.replace(currentPath, '').replace(/^\\/?/, '')\n );\n\n const isActive = totalPath === activeFile;\n\n const indentation = new Array(level).fill(' ').join('');\n\n return (\n <>\n <button\n className={cn(\n 'flex w-full items-start justify-start whitespace-pre text-nowrap px-2 py-1 text-xs transition',\n isActive\n ? 'bg-neutral-200 dark:bg-neutral-700'\n : 'cursor-pointer hover:bg-neutral-300 dark:hover:bg-neutral-900'\n )}\n key={path}\n onClick={() => {\n if (isFile) {\n onClickFile?.(totalPath);\n } else {\n setSubPathOpen(!subPathOpen);\n }\n }}\n >\n <span className={cn('whitespace-pre', isFile && 'ml-2')}>\n {indentation}\n </span>\n\n {!isFile && (\n <ChevronRight\n className={cn(`transition`, subPathOpen && `rotate-90 transform`)}\n size={16}\n />\n )}\n {path}\n </button>\n {subPath && (\n <MaxHeightSmoother\n isHidden={!subPathOpen}\n className=\"overflow-x-hidden\"\n >\n <FileTree\n filesPaths={newPath}\n activeFile={activeFile}\n onClickFile={onClickFile}\n prePaths={[...prePaths, path]}\n />\n </MaxHeightSmoother>\n )}\n </>\n );\n};\n\nexport const FileTree: FC<FileTreeProps> = ({\n filesPaths,\n activeFile,\n onClickFile,\n prePaths = [],\n}) => {\n const fileTree = createFileTree(filesPaths);\n\n return (\n <div className=\"flex size-full flex-col items-start justify-start py-1 text-neutral\">\n {fileTree.map(({ path, subPath, isFile }) => (\n <FileItem\n key={path}\n isFile={isFile}\n subPath={subPath}\n path={path}\n onClickFile={onClickFile}\n activeFile={activeFile}\n prePaths={prePaths}\n filesPaths={filesPaths}\n />\n ))}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AAeA,MAAM,kBAAkB,UAAoB,MAAM,KAAK,IAAI;AAY3D,MAAM,YAA+B,EACnC,YACA,MACA,SACA,aACA,YACA,UACA,aACI;CACJ,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CAEpD,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,cAAc,eAAe,CACjC,GAAG,SAAS,MAAM,QAAQ,GAAG,MAAM,EACnC,KACD,CAAC;CACF,MAAM,YAAY,eAAe,CAAC,GAAG,UAAU,KAAK,CAAC;CAQrD,MAAM,UANoB,WACvB,KACE,SAAS,KAAK,QAAQ,QAAQ,GAAG,CACnC,CACA,QAAQ,aAAa,SAAS,WAAW,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"FileTree.mjs","names":[],"sources":["../../../../src/components/IDE/FileTree.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronRight } from 'lucide-react';\nimport { type FC, useState } from 'react';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\nimport { createFileTree, type FilePath } from './createFileTree';\n\ntype FileTreeProps = {\n filesPaths: string[];\n activeFile?: string;\n onClickFile?: (filePath: string) => void;\n prePaths?: string[];\n};\n\nconst concatFilePath = (paths: string[]) => paths.join('/');\n\ntype FileItemProps = {\n filesPaths: string[];\n subPath?: FilePath[];\n path: string;\n onClickFile?: (title: string) => void;\n activeFile?: string;\n prePaths: string[];\n isFile: boolean;\n};\n\nconst FileItem: FC<FileItemProps> = ({\n filesPaths,\n path,\n subPath,\n onClickFile,\n activeFile,\n prePaths,\n isFile,\n}) => {\n const [subPathOpen, setSubPathOpen] = useState(true);\n\n const level = prePaths.length + 1;\n const currentPath = concatFilePath([\n ...prePaths.slice(level - 1, level),\n path,\n ]);\n const totalPath = concatFilePath([...prePaths, path]);\n\n const filteredFilePaths = filesPaths\n .map(\n (path) => path.replace(/^\\/?/, '') // This regex matches the first slash, if it exists, at the start of the string)\n )\n .filter((filePath) => filePath.startsWith(currentPath));\n\n const newPath = filteredFilePaths.map((path) =>\n path.replace(currentPath, '').replace(/^\\/?/, '')\n );\n\n const isActive = totalPath === activeFile;\n\n const indentation = new Array(level).fill(' ').join('');\n\n return (\n <>\n <button\n className={cn(\n 'flex w-full items-start justify-start whitespace-pre text-nowrap px-2 py-1 text-xs transition',\n isActive\n ? 'bg-neutral-200 dark:bg-neutral-700'\n : 'cursor-pointer hover:bg-neutral-300 dark:hover:bg-neutral-900'\n )}\n key={path}\n onClick={() => {\n if (isFile) {\n onClickFile?.(totalPath);\n } else {\n setSubPathOpen(!subPathOpen);\n }\n }}\n >\n <span className={cn('whitespace-pre', isFile && 'ml-2')}>\n {indentation}\n </span>\n\n {!isFile && (\n <ChevronRight\n className={cn(`transition`, subPathOpen && `rotate-90 transform`)}\n size={16}\n />\n )}\n {path}\n </button>\n {subPath && (\n <MaxHeightSmoother\n isHidden={!subPathOpen}\n className=\"overflow-x-hidden\"\n >\n <FileTree\n filesPaths={newPath}\n activeFile={activeFile}\n onClickFile={onClickFile}\n prePaths={[...prePaths, path]}\n />\n </MaxHeightSmoother>\n )}\n </>\n );\n};\n\nexport const FileTree: FC<FileTreeProps> = ({\n filesPaths,\n activeFile,\n onClickFile,\n prePaths = [],\n}) => {\n const fileTree = createFileTree(filesPaths);\n\n return (\n <div className=\"flex size-full flex-col items-start justify-start py-1 text-neutral\">\n {fileTree.map(({ path, subPath, isFile }) => (\n <FileItem\n key={path}\n isFile={isFile}\n subPath={subPath}\n path={path}\n onClickFile={onClickFile}\n activeFile={activeFile}\n prePaths={prePaths}\n filesPaths={filesPaths}\n />\n ))}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AAeA,MAAM,kBAAkB,UAAoB,MAAM,KAAK,IAAI;AAY3D,MAAM,YAA+B,EACnC,YACA,MACA,SACA,aACA,YACA,UACA,aACI;CACJ,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CAEpD,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,cAAc,eAAe,CACjC,GAAG,SAAS,MAAM,QAAQ,GAAG,MAAM,EACnC,KACD,CAAC;CACF,MAAM,YAAY,eAAe,CAAC,GAAG,UAAU,KAAK,CAAC;CAQrD,MAAM,UANoB,WACvB,KACE,SAAS,KAAK,QAAQ,QAAQ,GAAG,CACnC,CACA,QAAQ,aAAa,SAAS,WAAW,YAAY,CAEvB,CAAC,KAAK,SACrC,KAAK,QAAQ,aAAa,GAAG,CAAC,QAAQ,QAAQ,GAAG,CAClD;CAED,MAAM,WAAW,cAAc;CAE/B,MAAM,cAAc,IAAI,MAAM,MAAM,CAAC,KAAK,KAAK,CAAC,KAAK,GAAG;AAExD,QACE,8CACE,qBAAC,UAAD;EACE,WAAW,GACT,iGACA,WACI,uCACA,gEACL;EAED,eAAe;AACb,OAAI,OACF,eAAc,UAAU;OAExB,gBAAe,CAAC,YAAY;;YAZlC;GAgBE,oBAAC,QAAD;IAAM,WAAW,GAAG,kBAAkB,UAAU,OAAO;cACpD;IACI;GAEN,CAAC,UACA,oBAAC,cAAD;IACE,WAAW,GAAG,cAAc,eAAe,sBAAsB;IACjE,MAAM;IACN;GAEH;GACM;IApBF,KAoBE,EACR,WACC,oBAAC,mBAAD;EACE,UAAU,CAAC;EACX,WAAU;YAEV,oBAAC,UAAD;GACE,YAAY;GACA;GACC;GACb,UAAU,CAAC,GAAG,UAAU,KAAK;GAC7B;EACgB,EAErB;;AAIP,MAAa,YAA+B,EAC1C,YACA,YACA,aACA,WAAW,EAAE,OACT;AAGJ,QACE,oBAAC,OAAD;EAAK,WAAU;YAHA,eAAe,WAInB,CAAC,KAAK,EAAE,MAAM,SAAS,aAC9B,oBAAC,UAAD;GAEU;GACC;GACH;GACO;GACD;GACF;GACE;GACZ,EARK,KAQL,CACF;EACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IDE.mjs","names":[],"sources":["../../../../src/components/IDE/IDE.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, useEffect, useState } from 'react';\nimport { Container } from '../Container';\nimport { WithResizer } from '../WithResizer';\nimport { FileTree } from './FileTree';\nimport { MarkdownRenderer } from './MarkDownRender';\n\nexport type IDEProps = {\n pages: {\n path: string;\n content: string;\n isOpen?: boolean;\n }[];\n isDarkMode?: boolean;\n activeTab?: number;\n} & HTMLAttributes<HTMLDivElement>;\n\nexport const IDE: FC<IDEProps> = ({\n pages: initialPages,\n isDarkMode,\n className,\n activeTab: defaultActiveTab,\n ...props\n}) => {\n const [pages, setPages] = useState(initialPages);\n const tabs = pages.filter(({ isOpen }) => isOpen);\n\n const firstTabIndex = tabs.findIndex(({ isOpen }) => isOpen);\n const [activeTab, setActiveTab] = useState(defaultActiveTab ?? firstTabIndex);\n\n useEffect(() => {\n setActiveTab(defaultActiveTab ?? firstTabIndex);\n }, [initialPages, defaultActiveTab]);\n\n const { content, path } = pages[activeTab] ?? {};\n const filePaths = initialPages.map(({ path: title }) => title);\n\n const handleClickFile = (title: string) => {\n const page = pages.find(({ path: tabTitle }) => tabTitle === title);\n if (!page) return;\n\n const newPages = pages.map((page) => {\n if (page.path === title) {\n return { ...page, isOpen: true };\n }\n return page;\n });\n\n setPages(newPages);\n\n const newPageIndex = newPages.findIndex(\n ({ path: tabTitle }) => tabTitle === title\n );\n\n setActiveTab(newPageIndex);\n };\n\n return (\n <Container\n className={cn(\n 'flex size-full flex-col justify-start overflow-hidden shadow-lg',\n className\n )}\n roundedSize=\"3xl\"\n transparency=\"none\"\n {...props}\n >\n <div className=\"flex w-auto flex-row items-center justify-start gap-1 bg-neutral-200 text-neutral text-xs dark:bg-neutral-950\">\n <div className=\"mx-2 flex items-center justify-start gap-2 p-1\">\n <div className=\"size-3 rounded-full bg-red-500\" />\n <div className=\"size-3 rounded-full bg-yellow-500\" />\n <div className=\"size-3 rounded-full bg-green-500\" />\n </div>\n <div className=\"flex size-full overflow-y-auto\">\n {tabs.map(({ path }, index) => {\n const fullPath = path.split('/');\n const title = fullPath[fullPath.length - 1];\n const isActive = index === activeTab;\n\n return (\n <button\n className={cn(\n 'flex h-8 min-w-20 max-w-30 items-center justify-start truncate text-nowrap px-3 py-1 transition',\n isActive\n ? 'bg-card'\n : 'cursor-pointer bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-950'\n )}\n key={title}\n onClick={() => setActiveTab(index)}\n >\n {title}\n </button>\n );\n })}\n </div>\n </div>\n <div className=\"relative flex size-full flex-1 flex-row justify-start\">\n <div className=\"absolute top-0 left-0 size-full\">\n <div className=\"flex size-full\">\n <WithResizer initialWidth={150}>\n <div className=\"max-h-full flex-1 overflow-y-auto\">\n <FileTree\n filesPaths={filePaths}\n activeFile={path}\n onClickFile={handleClickFile}\n />\n </div>\n </WithResizer>\n\n <div className=\"size-full flex-1 overflow-auto pt-2 text-xs\">\n <MarkdownRenderer isDarkMode={isDarkMode}>\n {content}\n </MarkdownRenderer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n};\n"],"mappings":";;;;;;;;;;;AAmBA,MAAa,OAAqB,EAChC,OAAO,cACP,YACA,WACA,WAAW,kBACX,GAAG,YACC;CACJ,MAAM,CAAC,OAAO,YAAY,SAAS,aAAa;CAChD,MAAM,OAAO,MAAM,QAAQ,EAAE,aAAa,OAAO;CAEjD,MAAM,gBAAgB,KAAK,WAAW,EAAE,aAAa,OAAO;CAC5D,MAAM,CAAC,WAAW,gBAAgB,SAAS,oBAAoB,cAAc;AAE7E,iBAAgB;AACd,eAAa,oBAAoB,cAAc;IAC9C,CAAC,cAAc,iBAAiB,CAAC;CAEpC,MAAM,EAAE,SAAS,SAAS,MAAM,cAAc,EAAE;CAChD,MAAM,YAAY,aAAa,KAAK,EAAE,MAAM,YAAY,MAAM;CAE9D,MAAM,mBAAmB,UAAkB;AAEzC,MAAI,CADS,MAAM,MAAM,EAAE,MAAM,eAAe,aAAa,
|
|
1
|
+
{"version":3,"file":"IDE.mjs","names":[],"sources":["../../../../src/components/IDE/IDE.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, type HTMLAttributes, useEffect, useState } from 'react';\nimport { Container } from '../Container';\nimport { WithResizer } from '../WithResizer';\nimport { FileTree } from './FileTree';\nimport { MarkdownRenderer } from './MarkDownRender';\n\nexport type IDEProps = {\n pages: {\n path: string;\n content: string;\n isOpen?: boolean;\n }[];\n isDarkMode?: boolean;\n activeTab?: number;\n} & HTMLAttributes<HTMLDivElement>;\n\nexport const IDE: FC<IDEProps> = ({\n pages: initialPages,\n isDarkMode,\n className,\n activeTab: defaultActiveTab,\n ...props\n}) => {\n const [pages, setPages] = useState(initialPages);\n const tabs = pages.filter(({ isOpen }) => isOpen);\n\n const firstTabIndex = tabs.findIndex(({ isOpen }) => isOpen);\n const [activeTab, setActiveTab] = useState(defaultActiveTab ?? firstTabIndex);\n\n useEffect(() => {\n setActiveTab(defaultActiveTab ?? firstTabIndex);\n }, [initialPages, defaultActiveTab]);\n\n const { content, path } = pages[activeTab] ?? {};\n const filePaths = initialPages.map(({ path: title }) => title);\n\n const handleClickFile = (title: string) => {\n const page = pages.find(({ path: tabTitle }) => tabTitle === title);\n if (!page) return;\n\n const newPages = pages.map((page) => {\n if (page.path === title) {\n return { ...page, isOpen: true };\n }\n return page;\n });\n\n setPages(newPages);\n\n const newPageIndex = newPages.findIndex(\n ({ path: tabTitle }) => tabTitle === title\n );\n\n setActiveTab(newPageIndex);\n };\n\n return (\n <Container\n className={cn(\n 'flex size-full flex-col justify-start overflow-hidden shadow-lg',\n className\n )}\n roundedSize=\"3xl\"\n transparency=\"none\"\n {...props}\n >\n <div className=\"flex w-auto flex-row items-center justify-start gap-1 bg-neutral-200 text-neutral text-xs dark:bg-neutral-950\">\n <div className=\"mx-2 flex items-center justify-start gap-2 p-1\">\n <div className=\"size-3 rounded-full bg-red-500\" />\n <div className=\"size-3 rounded-full bg-yellow-500\" />\n <div className=\"size-3 rounded-full bg-green-500\" />\n </div>\n <div className=\"flex size-full overflow-y-auto\">\n {tabs.map(({ path }, index) => {\n const fullPath = path.split('/');\n const title = fullPath[fullPath.length - 1];\n const isActive = index === activeTab;\n\n return (\n <button\n className={cn(\n 'flex h-8 min-w-20 max-w-30 items-center justify-start truncate text-nowrap px-3 py-1 transition',\n isActive\n ? 'bg-card'\n : 'cursor-pointer bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-950'\n )}\n key={title}\n onClick={() => setActiveTab(index)}\n >\n {title}\n </button>\n );\n })}\n </div>\n </div>\n <div className=\"relative flex size-full flex-1 flex-row justify-start\">\n <div className=\"absolute top-0 left-0 size-full\">\n <div className=\"flex size-full\">\n <WithResizer initialWidth={150}>\n <div className=\"max-h-full flex-1 overflow-y-auto\">\n <FileTree\n filesPaths={filePaths}\n activeFile={path}\n onClickFile={handleClickFile}\n />\n </div>\n </WithResizer>\n\n <div className=\"size-full flex-1 overflow-auto pt-2 text-xs\">\n <MarkdownRenderer isDarkMode={isDarkMode}>\n {content}\n </MarkdownRenderer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n};\n"],"mappings":";;;;;;;;;;;AAmBA,MAAa,OAAqB,EAChC,OAAO,cACP,YACA,WACA,WAAW,kBACX,GAAG,YACC;CACJ,MAAM,CAAC,OAAO,YAAY,SAAS,aAAa;CAChD,MAAM,OAAO,MAAM,QAAQ,EAAE,aAAa,OAAO;CAEjD,MAAM,gBAAgB,KAAK,WAAW,EAAE,aAAa,OAAO;CAC5D,MAAM,CAAC,WAAW,gBAAgB,SAAS,oBAAoB,cAAc;AAE7E,iBAAgB;AACd,eAAa,oBAAoB,cAAc;IAC9C,CAAC,cAAc,iBAAiB,CAAC;CAEpC,MAAM,EAAE,SAAS,SAAS,MAAM,cAAc,EAAE;CAChD,MAAM,YAAY,aAAa,KAAK,EAAE,MAAM,YAAY,MAAM;CAE9D,MAAM,mBAAmB,UAAkB;AAEzC,MAAI,CADS,MAAM,MAAM,EAAE,MAAM,eAAe,aAAa,MACpD,CAAE;EAEX,MAAM,WAAW,MAAM,KAAK,SAAS;AACnC,OAAI,KAAK,SAAS,MAChB,QAAO;IAAE,GAAG;IAAM,QAAQ;IAAM;AAElC,UAAO;IACP;AAEF,WAAS,SAAS;AAMlB,eAJqB,SAAS,WAC3B,EAAE,MAAM,eAAe,aAAa,MAGd,CAAC;;AAG5B,QACE,qBAAC,WAAD;EACE,WAAW,GACT,mEACA,UACD;EACD,aAAY;EACZ,cAAa;EACb,GAAI;YAPN,CASE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD,EAAK,WAAU,kCAAmC;KAClD,oBAAC,OAAD,EAAK,WAAU,qCAAsC;KACrD,oBAAC,OAAD,EAAK,WAAU,oCAAqC;KAChD;OACN,oBAAC,OAAD;IAAK,WAAU;cACZ,KAAK,KAAK,EAAE,QAAQ,UAAU;KAC7B,MAAM,WAAW,KAAK,MAAM,IAAI;KAChC,MAAM,QAAQ,SAAS,SAAS,SAAS;AAGzC,YACE,oBAAC,UAAD;MACE,WAAW,GACT,mGALW,UAAU,YAOjB,YACA,yEACL;MAED,eAAe,aAAa,MAAM;gBAEjC;MACM,EAJF,MAIE;MAEX;IACE,EACF;MACN,oBAAC,OAAD;GAAK,WAAU;aACb,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,aAAD;MAAa,cAAc;gBACzB,oBAAC,OAAD;OAAK,WAAU;iBACb,oBAAC,UAAD;QACE,YAAY;QACZ,YAAY;QACZ,aAAa;QACb;OACE;MACM,GAEd,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,kBAAD;OAA8B;iBAC3B;OACgB;MACf,EACF;;IACF;GACF,EACI"}
|