@aprovan/patchwork-editor 0.1.0 → 0.1.2-dev.03aaf5b
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/.turbo/turbo-build.log +4 -7
- package/dist/components/CodeBlockExtension.d.ts +2 -0
- package/dist/components/CodePreview.d.ts +13 -0
- package/dist/components/MarkdownEditor.d.ts +10 -0
- package/dist/components/ServicesInspector.d.ts +49 -0
- package/dist/components/edit/CodeBlockView.d.ts +7 -0
- package/dist/components/edit/EditHistory.d.ts +10 -0
- package/dist/components/edit/EditModal.d.ts +20 -0
- package/dist/components/edit/FileTree.d.ts +8 -0
- package/dist/components/edit/MediaPreview.d.ts +6 -0
- package/dist/components/edit/SaveConfirmDialog.d.ts +9 -0
- package/dist/components/edit/api.d.ts +8 -0
- package/dist/components/edit/fileTypes.d.ts +14 -0
- package/dist/components/edit/index.d.ts +10 -0
- package/dist/components/edit/types.d.ts +41 -0
- package/dist/components/edit/useEditSession.d.ts +9 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/index.d.ts +3 -331
- package/dist/index.js +812 -142
- package/dist/lib/code-extractor.d.ts +44 -0
- package/dist/lib/diff.d.ts +90 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/vfs.d.ts +36 -0
- package/package.json +5 -4
- package/src/components/CodeBlockExtension.tsx +1 -1
- package/src/components/CodePreview.tsx +64 -4
- package/src/components/edit/CodeBlockView.tsx +188 -0
- package/src/components/edit/EditModal.tsx +172 -30
- package/src/components/edit/FileTree.tsx +67 -13
- package/src/components/edit/MediaPreview.tsx +124 -0
- package/src/components/edit/SaveConfirmDialog.tsx +60 -0
- package/src/components/edit/fileTypes.ts +125 -0
- package/src/components/edit/index.ts +4 -0
- package/src/components/edit/types.ts +3 -0
- package/src/components/edit/useEditSession.ts +56 -11
- package/src/index.ts +17 -0
- package/src/lib/diff.ts +2 -1
- package/src/lib/vfs.ts +28 -10
- package/tsup.config.ts +10 -5
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Node, textblockTypeInputRule } from '@tiptap/core';
|
|
|
2
2
|
import { ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent, useEditor, EditorContent } from '@tiptap/react';
|
|
3
3
|
import { useRef, useCallback, useMemo, useState, useEffect } from 'react';
|
|
4
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import { Pencil, Loader2, RotateCcw, FileCode, FolderTree, Eye, Code,
|
|
6
|
-
import { createSingleFileProject,
|
|
5
|
+
import { AlertCircle, FileImage, FileVideo, Pencil, Loader2, RotateCcw, FileCode, FolderTree, Eye, Code, Save, X, Send, MessageSquare, Cloud, Check, Server, ChevronDown, ChevronRight, Folder, File, Upload } from 'lucide-react';
|
|
6
|
+
import { createSingleFileProject, HttpBackend, VFSStore, detectMainFile, createProjectFromFiles } from '@aprovan/patchwork-compiler';
|
|
7
7
|
import Markdown from 'react-markdown';
|
|
8
8
|
import remarkGfm from 'remark-gfm';
|
|
9
9
|
import StarterKit from '@tiptap/starter-kit';
|
|
@@ -11,6 +11,7 @@ import Placeholder from '@tiptap/extension-placeholder';
|
|
|
11
11
|
import Typography from '@tiptap/extension-typography';
|
|
12
12
|
import { Markdown as Markdown$1 } from 'tiptap-markdown';
|
|
13
13
|
import { TextSelection } from '@tiptap/pm/state';
|
|
14
|
+
import { createHighlighter } from 'shiki';
|
|
14
15
|
import { Bobbin, serializeChangesToYAML } from '@aprovan/bobbin';
|
|
15
16
|
import { clsx } from 'clsx';
|
|
16
17
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -119,7 +120,7 @@ var CodeBlockExtension = Node.create({
|
|
|
119
120
|
default: null,
|
|
120
121
|
parseHTML: (element) => {
|
|
121
122
|
const { languageClassPrefix } = this.options;
|
|
122
|
-
const classNames =
|
|
123
|
+
const classNames = Array.from(element.firstElementChild?.classList || []);
|
|
123
124
|
const languages = classNames.filter((className) => className.startsWith(languageClassPrefix)).map((className) => className.replace(languageClassPrefix, ""));
|
|
124
125
|
return languages[0] || null;
|
|
125
126
|
},
|
|
@@ -340,6 +341,7 @@ function applyDiffs(code, diffs, options = {}) {
|
|
|
340
341
|
return { code: result, applied, failed, warning };
|
|
341
342
|
}
|
|
342
343
|
function hasDiffBlocks(text) {
|
|
344
|
+
DIFF_BLOCK_REGEX.lastIndex = 0;
|
|
343
345
|
return DIFF_BLOCK_REGEX.test(text);
|
|
344
346
|
}
|
|
345
347
|
function extractTextWithoutDiffs(text) {
|
|
@@ -435,11 +437,23 @@ function cloneProject(project) {
|
|
|
435
437
|
};
|
|
436
438
|
}
|
|
437
439
|
function useEditSession(options) {
|
|
438
|
-
const {
|
|
440
|
+
const {
|
|
441
|
+
originalCode,
|
|
442
|
+
originalProject: providedProject,
|
|
443
|
+
compile,
|
|
444
|
+
apiEndpoint
|
|
445
|
+
} = options;
|
|
446
|
+
console.log(
|
|
447
|
+
"[useEditSession] providedProject:",
|
|
448
|
+
providedProject?.id,
|
|
449
|
+
"files:",
|
|
450
|
+
providedProject ? Array.from(providedProject.files.keys()) : "none"
|
|
451
|
+
);
|
|
439
452
|
const originalProject = useMemo(
|
|
440
|
-
() => createSingleFileProject(originalCode),
|
|
441
|
-
[originalCode]
|
|
453
|
+
() => providedProject ?? createSingleFileProject(originalCode ?? ""),
|
|
454
|
+
[providedProject, originalCode]
|
|
442
455
|
);
|
|
456
|
+
const lastSyncedProjectRef = useRef(originalProject);
|
|
443
457
|
const [project, setProject] = useState(originalProject);
|
|
444
458
|
const [activeFile, setActiveFile] = useState(originalProject.entry);
|
|
445
459
|
const [history, setHistory] = useState([]);
|
|
@@ -447,6 +461,16 @@ function useEditSession(options) {
|
|
|
447
461
|
const [error, setError] = useState(null);
|
|
448
462
|
const [streamingNotes, setStreamingNotes] = useState([]);
|
|
449
463
|
const [pendingPrompt, setPendingPrompt] = useState(null);
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (originalProject !== lastSyncedProjectRef.current) {
|
|
466
|
+
lastSyncedProjectRef.current = originalProject;
|
|
467
|
+
setProject(originalProject);
|
|
468
|
+
setActiveFile(originalProject.entry);
|
|
469
|
+
setHistory([]);
|
|
470
|
+
setError(null);
|
|
471
|
+
setStreamingNotes([]);
|
|
472
|
+
}
|
|
473
|
+
}, [originalProject]);
|
|
450
474
|
const performEdit = useCallback(
|
|
451
475
|
async (currentCode2, prompt, isRetry = false) => {
|
|
452
476
|
const entries = [];
|
|
@@ -536,6 +560,21 @@ Please fix this error.`;
|
|
|
536
560
|
},
|
|
537
561
|
[activeFile]
|
|
538
562
|
);
|
|
563
|
+
const replaceFile = useCallback(
|
|
564
|
+
(path, content, encoding = "utf8") => {
|
|
565
|
+
setProject((prev) => {
|
|
566
|
+
const updated = cloneProject(prev);
|
|
567
|
+
const file = updated.files.get(path);
|
|
568
|
+
if (file) {
|
|
569
|
+
updated.files.set(path, { ...file, content, encoding });
|
|
570
|
+
} else {
|
|
571
|
+
updated.files.set(path, { path, content, encoding });
|
|
572
|
+
}
|
|
573
|
+
return updated;
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
[]
|
|
577
|
+
);
|
|
539
578
|
const clearError = useCallback(() => {
|
|
540
579
|
setError(null);
|
|
541
580
|
}, []);
|
|
@@ -552,7 +591,8 @@ Please fix this error.`;
|
|
|
552
591
|
revert,
|
|
553
592
|
updateActiveFile,
|
|
554
593
|
setActiveFile,
|
|
555
|
-
clearError
|
|
594
|
+
clearError,
|
|
595
|
+
replaceFile
|
|
556
596
|
};
|
|
557
597
|
}
|
|
558
598
|
function ProgressNote({ text, isLatest }) {
|
|
@@ -811,6 +851,110 @@ function MarkdownEditor({
|
|
|
811
851
|
}
|
|
812
852
|
);
|
|
813
853
|
}
|
|
854
|
+
|
|
855
|
+
// src/components/edit/fileTypes.ts
|
|
856
|
+
var COMPILABLE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
|
|
857
|
+
var MEDIA_EXTENSIONS = [".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".mp4", ".mov", ".webm"];
|
|
858
|
+
var TEXT_EXTENSIONS = [".json", ".yaml", ".yml", ".md", ".txt", ".css", ".html", ".xml", ".toml"];
|
|
859
|
+
var EXTENSION_TO_LANGUAGE = {
|
|
860
|
+
".tsx": "tsx",
|
|
861
|
+
".jsx": "jsx",
|
|
862
|
+
".ts": "typescript",
|
|
863
|
+
".js": "javascript",
|
|
864
|
+
".json": "json",
|
|
865
|
+
".yaml": "yaml",
|
|
866
|
+
".yml": "yaml",
|
|
867
|
+
".md": "markdown",
|
|
868
|
+
".txt": "text",
|
|
869
|
+
".css": "css",
|
|
870
|
+
".html": "html",
|
|
871
|
+
".xml": "xml",
|
|
872
|
+
".toml": "toml",
|
|
873
|
+
".svg": "xml"
|
|
874
|
+
};
|
|
875
|
+
var EXTENSION_TO_MIME = {
|
|
876
|
+
".tsx": "text/typescript-jsx",
|
|
877
|
+
".jsx": "text/javascript-jsx",
|
|
878
|
+
".ts": "text/typescript",
|
|
879
|
+
".js": "text/javascript",
|
|
880
|
+
".json": "application/json",
|
|
881
|
+
".yaml": "text/yaml",
|
|
882
|
+
".yml": "text/yaml",
|
|
883
|
+
".md": "text/markdown",
|
|
884
|
+
".txt": "text/plain",
|
|
885
|
+
".css": "text/css",
|
|
886
|
+
".html": "text/html",
|
|
887
|
+
".xml": "application/xml",
|
|
888
|
+
".toml": "text/toml",
|
|
889
|
+
".svg": "image/svg+xml",
|
|
890
|
+
".png": "image/png",
|
|
891
|
+
".jpg": "image/jpeg",
|
|
892
|
+
".jpeg": "image/jpeg",
|
|
893
|
+
".gif": "image/gif",
|
|
894
|
+
".webp": "image/webp",
|
|
895
|
+
".mp4": "video/mp4",
|
|
896
|
+
".mov": "video/quicktime",
|
|
897
|
+
".webm": "video/webm"
|
|
898
|
+
};
|
|
899
|
+
function getExtension(path) {
|
|
900
|
+
const lastDot = path.lastIndexOf(".");
|
|
901
|
+
if (lastDot === -1) return "";
|
|
902
|
+
return path.slice(lastDot).toLowerCase();
|
|
903
|
+
}
|
|
904
|
+
function getFileType(path) {
|
|
905
|
+
const ext = getExtension(path);
|
|
906
|
+
if (COMPILABLE_EXTENSIONS.includes(ext)) {
|
|
907
|
+
return {
|
|
908
|
+
category: "compilable",
|
|
909
|
+
language: EXTENSION_TO_LANGUAGE[ext] ?? null,
|
|
910
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain"
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
if (TEXT_EXTENSIONS.includes(ext)) {
|
|
914
|
+
return {
|
|
915
|
+
category: "text",
|
|
916
|
+
language: EXTENSION_TO_LANGUAGE[ext] ?? null,
|
|
917
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain"
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
if (MEDIA_EXTENSIONS.includes(ext)) {
|
|
921
|
+
return {
|
|
922
|
+
category: "media",
|
|
923
|
+
language: ext === ".svg" ? "xml" : null,
|
|
924
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "application/octet-stream"
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
category: "binary",
|
|
929
|
+
language: null,
|
|
930
|
+
mimeType: "application/octet-stream"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function isCompilable(path) {
|
|
934
|
+
return COMPILABLE_EXTENSIONS.includes(getExtension(path));
|
|
935
|
+
}
|
|
936
|
+
function isMediaFile(path) {
|
|
937
|
+
return MEDIA_EXTENSIONS.includes(getExtension(path));
|
|
938
|
+
}
|
|
939
|
+
function isTextFile(path) {
|
|
940
|
+
return TEXT_EXTENSIONS.includes(getExtension(path));
|
|
941
|
+
}
|
|
942
|
+
function getLanguageFromExt(path) {
|
|
943
|
+
const ext = getExtension(path);
|
|
944
|
+
return EXTENSION_TO_LANGUAGE[ext] ?? null;
|
|
945
|
+
}
|
|
946
|
+
function getMimeType(path) {
|
|
947
|
+
const ext = getExtension(path);
|
|
948
|
+
return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
|
|
949
|
+
}
|
|
950
|
+
function isImageFile(path) {
|
|
951
|
+
const ext = getExtension(path);
|
|
952
|
+
return [".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext);
|
|
953
|
+
}
|
|
954
|
+
function isVideoFile(path) {
|
|
955
|
+
const ext = getExtension(path);
|
|
956
|
+
return [".mp4", ".mov", ".webm"].includes(ext);
|
|
957
|
+
}
|
|
814
958
|
function buildTree(files) {
|
|
815
959
|
const root = { name: "", path: "", isDir: true, children: [] };
|
|
816
960
|
for (const file of files) {
|
|
@@ -839,8 +983,26 @@ function buildTree(files) {
|
|
|
839
983
|
});
|
|
840
984
|
return root;
|
|
841
985
|
}
|
|
842
|
-
function TreeNodeComponent({ node, activeFile, onSelect, depth = 0 }) {
|
|
986
|
+
function TreeNodeComponent({ node, activeFile, onSelect, onReplaceFile, depth = 0 }) {
|
|
843
987
|
const [expanded, setExpanded] = useState(true);
|
|
988
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
989
|
+
const fileInputRef = useRef(null);
|
|
990
|
+
const handleUploadClick = useCallback((e) => {
|
|
991
|
+
e.stopPropagation();
|
|
992
|
+
fileInputRef.current?.click();
|
|
993
|
+
}, []);
|
|
994
|
+
const handleFileChange = useCallback((e) => {
|
|
995
|
+
const file = e.target.files?.[0];
|
|
996
|
+
if (!file || !onReplaceFile) return;
|
|
997
|
+
const reader = new FileReader();
|
|
998
|
+
reader.onload = () => {
|
|
999
|
+
const result = reader.result;
|
|
1000
|
+
const base64 = result.split(",")[1] ?? "";
|
|
1001
|
+
onReplaceFile(node.path, base64, "base64");
|
|
1002
|
+
};
|
|
1003
|
+
reader.readAsDataURL(file);
|
|
1004
|
+
e.target.value = "";
|
|
1005
|
+
}, [node.path, onReplaceFile]);
|
|
844
1006
|
if (!node.name) {
|
|
845
1007
|
return /* @__PURE__ */ jsx(Fragment, { children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
846
1008
|
TreeNodeComponent,
|
|
@@ -848,12 +1010,15 @@ function TreeNodeComponent({ node, activeFile, onSelect, depth = 0 }) {
|
|
|
848
1010
|
node: child,
|
|
849
1011
|
activeFile,
|
|
850
1012
|
onSelect,
|
|
1013
|
+
onReplaceFile,
|
|
851
1014
|
depth
|
|
852
1015
|
},
|
|
853
1016
|
child.path
|
|
854
1017
|
)) });
|
|
855
1018
|
}
|
|
856
1019
|
const isActive = node.path === activeFile;
|
|
1020
|
+
const isMedia = !node.isDir && isMediaFile(node.path);
|
|
1021
|
+
const showUpload = isMedia && isHovered && onReplaceFile;
|
|
857
1022
|
if (node.isDir) {
|
|
858
1023
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
859
1024
|
/* @__PURE__ */ jsxs(
|
|
@@ -875,6 +1040,7 @@ function TreeNodeComponent({ node, activeFile, onSelect, depth = 0 }) {
|
|
|
875
1040
|
node: child,
|
|
876
1041
|
activeFile,
|
|
877
1042
|
onSelect,
|
|
1043
|
+
onReplaceFile,
|
|
878
1044
|
depth: depth + 1
|
|
879
1045
|
},
|
|
880
1046
|
child.path
|
|
@@ -882,19 +1048,48 @@ function TreeNodeComponent({ node, activeFile, onSelect, depth = 0 }) {
|
|
|
882
1048
|
] });
|
|
883
1049
|
}
|
|
884
1050
|
return /* @__PURE__ */ jsxs(
|
|
885
|
-
"
|
|
1051
|
+
"div",
|
|
886
1052
|
{
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1053
|
+
className: "relative",
|
|
1054
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1055
|
+
onMouseLeave: () => setIsHovered(false),
|
|
890
1056
|
children: [
|
|
891
|
-
/* @__PURE__ */
|
|
892
|
-
|
|
1057
|
+
/* @__PURE__ */ jsxs(
|
|
1058
|
+
"button",
|
|
1059
|
+
{
|
|
1060
|
+
onClick: () => onSelect(node.path),
|
|
1061
|
+
className: `flex items-center gap-1 w-full px-2 py-1 text-left text-sm hover:bg-muted/50 rounded ${isActive ? "bg-primary/10 text-primary" : ""}`,
|
|
1062
|
+
style: { paddingLeft: `${depth * 12 + 20}px` },
|
|
1063
|
+
children: [
|
|
1064
|
+
/* @__PURE__ */ jsx(File, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
|
|
1065
|
+
/* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: node.name }),
|
|
1066
|
+
showUpload && /* @__PURE__ */ jsx(
|
|
1067
|
+
"span",
|
|
1068
|
+
{
|
|
1069
|
+
onClick: handleUploadClick,
|
|
1070
|
+
className: "p-1 hover:bg-primary/20 rounded cursor-pointer",
|
|
1071
|
+
title: "Replace file",
|
|
1072
|
+
children: /* @__PURE__ */ jsx(Upload, { className: "h-3 w-3 text-primary" })
|
|
1073
|
+
}
|
|
1074
|
+
)
|
|
1075
|
+
]
|
|
1076
|
+
}
|
|
1077
|
+
),
|
|
1078
|
+
isMedia && /* @__PURE__ */ jsx(
|
|
1079
|
+
"input",
|
|
1080
|
+
{
|
|
1081
|
+
ref: fileInputRef,
|
|
1082
|
+
type: "file",
|
|
1083
|
+
className: "hidden",
|
|
1084
|
+
accept: "image/*,video/*",
|
|
1085
|
+
onChange: handleFileChange
|
|
1086
|
+
}
|
|
1087
|
+
)
|
|
893
1088
|
]
|
|
894
1089
|
}
|
|
895
1090
|
);
|
|
896
1091
|
}
|
|
897
|
-
function FileTree({ files, activeFile, onSelectFile }) {
|
|
1092
|
+
function FileTree({ files, activeFile, onSelectFile, onReplaceFile }) {
|
|
898
1093
|
const tree = useMemo(() => buildTree(files), [files]);
|
|
899
1094
|
return /* @__PURE__ */ jsxs("div", { className: "w-48 border-r bg-muted/30 overflow-auto text-foreground", children: [
|
|
900
1095
|
/* @__PURE__ */ jsx("div", { className: "p-2 border-b text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Files" }),
|
|
@@ -903,11 +1098,299 @@ function FileTree({ files, activeFile, onSelectFile }) {
|
|
|
903
1098
|
{
|
|
904
1099
|
node: tree,
|
|
905
1100
|
activeFile,
|
|
906
|
-
onSelect: onSelectFile
|
|
1101
|
+
onSelect: onSelectFile,
|
|
1102
|
+
onReplaceFile
|
|
907
1103
|
}
|
|
908
1104
|
) })
|
|
909
1105
|
] });
|
|
910
1106
|
}
|
|
1107
|
+
function SaveConfirmDialog({
|
|
1108
|
+
isOpen,
|
|
1109
|
+
isSaving,
|
|
1110
|
+
error,
|
|
1111
|
+
onSave,
|
|
1112
|
+
onDiscard,
|
|
1113
|
+
onCancel
|
|
1114
|
+
}) {
|
|
1115
|
+
if (!isOpen) return null;
|
|
1116
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/80", children: /* @__PURE__ */ jsxs("div", { className: "bg-background border rounded-lg shadow-lg w-full max-w-md", children: [
|
|
1117
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
1118
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: "Unsaved Changes" }),
|
|
1119
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "You have unsaved changes. Would you like to save them before closing?" }),
|
|
1120
|
+
error && /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-3", children: [
|
|
1121
|
+
"Save failed: ",
|
|
1122
|
+
error
|
|
1123
|
+
] })
|
|
1124
|
+
] }),
|
|
1125
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 p-6 pt-0", children: [
|
|
1126
|
+
/* @__PURE__ */ jsx(
|
|
1127
|
+
"button",
|
|
1128
|
+
{
|
|
1129
|
+
onClick: onCancel,
|
|
1130
|
+
disabled: isSaving,
|
|
1131
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50",
|
|
1132
|
+
children: "Cancel"
|
|
1133
|
+
}
|
|
1134
|
+
),
|
|
1135
|
+
/* @__PURE__ */ jsx(
|
|
1136
|
+
"button",
|
|
1137
|
+
{
|
|
1138
|
+
onClick: onDiscard,
|
|
1139
|
+
disabled: isSaving,
|
|
1140
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 py-2 border border-input bg-background text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1141
|
+
children: "Discard"
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
/* @__PURE__ */ jsx(
|
|
1145
|
+
"button",
|
|
1146
|
+
{
|
|
1147
|
+
onClick: onSave,
|
|
1148
|
+
disabled: isSaving,
|
|
1149
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
1150
|
+
children: isSaving ? "Saving..." : "Save"
|
|
1151
|
+
}
|
|
1152
|
+
)
|
|
1153
|
+
] })
|
|
1154
|
+
] }) });
|
|
1155
|
+
}
|
|
1156
|
+
var highlighterPromise = null;
|
|
1157
|
+
var COMMON_LANGUAGES = [
|
|
1158
|
+
"typescript",
|
|
1159
|
+
"javascript",
|
|
1160
|
+
"tsx",
|
|
1161
|
+
"jsx",
|
|
1162
|
+
"json",
|
|
1163
|
+
"html",
|
|
1164
|
+
"css",
|
|
1165
|
+
"markdown",
|
|
1166
|
+
"yaml",
|
|
1167
|
+
"python",
|
|
1168
|
+
"bash",
|
|
1169
|
+
"sql"
|
|
1170
|
+
];
|
|
1171
|
+
function getHighlighter() {
|
|
1172
|
+
if (!highlighterPromise) {
|
|
1173
|
+
highlighterPromise = createHighlighter({
|
|
1174
|
+
themes: ["github-light"],
|
|
1175
|
+
langs: COMMON_LANGUAGES
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
return highlighterPromise;
|
|
1179
|
+
}
|
|
1180
|
+
function normalizeLanguage(lang) {
|
|
1181
|
+
if (!lang) return "typescript";
|
|
1182
|
+
const normalized = lang.toLowerCase();
|
|
1183
|
+
const mapping = {
|
|
1184
|
+
ts: "typescript",
|
|
1185
|
+
tsx: "tsx",
|
|
1186
|
+
js: "javascript",
|
|
1187
|
+
jsx: "jsx",
|
|
1188
|
+
json: "json",
|
|
1189
|
+
html: "html",
|
|
1190
|
+
css: "css",
|
|
1191
|
+
md: "markdown",
|
|
1192
|
+
markdown: "markdown",
|
|
1193
|
+
yml: "yaml",
|
|
1194
|
+
yaml: "yaml",
|
|
1195
|
+
py: "python",
|
|
1196
|
+
python: "python",
|
|
1197
|
+
sh: "bash",
|
|
1198
|
+
bash: "bash",
|
|
1199
|
+
sql: "sql",
|
|
1200
|
+
typescript: "typescript",
|
|
1201
|
+
javascript: "javascript"
|
|
1202
|
+
};
|
|
1203
|
+
return mapping[normalized] || "typescript";
|
|
1204
|
+
}
|
|
1205
|
+
function CodeBlockView({ content, language, editable = false, onChange }) {
|
|
1206
|
+
const textareaRef = useRef(null);
|
|
1207
|
+
const containerRef = useRef(null);
|
|
1208
|
+
const [highlighter, setHighlighter] = useState(null);
|
|
1209
|
+
useEffect(() => {
|
|
1210
|
+
let mounted = true;
|
|
1211
|
+
getHighlighter().then((h) => {
|
|
1212
|
+
if (mounted) setHighlighter(h);
|
|
1213
|
+
});
|
|
1214
|
+
return () => {
|
|
1215
|
+
mounted = false;
|
|
1216
|
+
};
|
|
1217
|
+
}, []);
|
|
1218
|
+
useEffect(() => {
|
|
1219
|
+
if (textareaRef.current) {
|
|
1220
|
+
textareaRef.current.style.height = "auto";
|
|
1221
|
+
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
|
1222
|
+
}
|
|
1223
|
+
}, [content]);
|
|
1224
|
+
const handleChange = useCallback(
|
|
1225
|
+
(e) => {
|
|
1226
|
+
onChange?.(e.target.value);
|
|
1227
|
+
},
|
|
1228
|
+
[onChange]
|
|
1229
|
+
);
|
|
1230
|
+
const handleKeyDown = useCallback(
|
|
1231
|
+
(e) => {
|
|
1232
|
+
if (e.key === "Tab") {
|
|
1233
|
+
e.preventDefault();
|
|
1234
|
+
const target = e.target;
|
|
1235
|
+
const start = target.selectionStart;
|
|
1236
|
+
const end = target.selectionEnd;
|
|
1237
|
+
const value = target.value;
|
|
1238
|
+
const newValue = value.substring(0, start) + " " + value.substring(end);
|
|
1239
|
+
onChange?.(newValue);
|
|
1240
|
+
requestAnimationFrame(() => {
|
|
1241
|
+
target.selectionStart = target.selectionEnd = start + 2;
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
[onChange]
|
|
1246
|
+
);
|
|
1247
|
+
const langLabel = language || "text";
|
|
1248
|
+
const shikiLang = useMemo(() => normalizeLanguage(language), [language]);
|
|
1249
|
+
const highlightedHtml = useMemo(() => {
|
|
1250
|
+
if (!highlighter) return null;
|
|
1251
|
+
try {
|
|
1252
|
+
return highlighter.codeToHtml(content, {
|
|
1253
|
+
lang: shikiLang,
|
|
1254
|
+
theme: "github-light"
|
|
1255
|
+
});
|
|
1256
|
+
} catch {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
}, [highlighter, content, shikiLang]);
|
|
1260
|
+
return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col bg-[#ffffff]", children: [
|
|
1261
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-2 bg-[#f6f8fa] border-b border-[#d0d7de] text-xs", children: /* @__PURE__ */ jsx("span", { className: "font-mono text-[#57606a]", children: langLabel }) }),
|
|
1262
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden", children: editable ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-full", children: [
|
|
1263
|
+
/* @__PURE__ */ jsx(
|
|
1264
|
+
"div",
|
|
1265
|
+
{
|
|
1266
|
+
ref: containerRef,
|
|
1267
|
+
className: "absolute top-0 left-0 right-0 pointer-events-none p-4",
|
|
1268
|
+
"aria-hidden": "true",
|
|
1269
|
+
children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1270
|
+
"div",
|
|
1271
|
+
{
|
|
1272
|
+
className: "highlighted-code font-mono text-xs leading-relaxed whitespace-pre-wrap break-words [&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:!p-0 [&_pre]:whitespace-pre-wrap [&_code]:!bg-transparent [&_code]:whitespace-pre-wrap [&_code]:break-words",
|
|
1273
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1274
|
+
}
|
|
1275
|
+
) : /* @__PURE__ */ jsx("pre", { className: "text-xs font-mono whitespace-pre-wrap break-words text-[#24292f] m-0 leading-relaxed", children: /* @__PURE__ */ jsx("code", { children: content }) })
|
|
1276
|
+
}
|
|
1277
|
+
),
|
|
1278
|
+
/* @__PURE__ */ jsx(
|
|
1279
|
+
"textarea",
|
|
1280
|
+
{
|
|
1281
|
+
ref: textareaRef,
|
|
1282
|
+
value: content,
|
|
1283
|
+
onChange: handleChange,
|
|
1284
|
+
onKeyDown: handleKeyDown,
|
|
1285
|
+
className: "relative w-full min-h-full font-mono text-xs leading-relaxed bg-transparent border-none outline-none resize-none p-4 text-transparent whitespace-pre-wrap break-words",
|
|
1286
|
+
spellCheck: false,
|
|
1287
|
+
style: {
|
|
1288
|
+
tabSize: 2,
|
|
1289
|
+
caretColor: "#24292f",
|
|
1290
|
+
wordBreak: "break-word",
|
|
1291
|
+
overflowWrap: "break-word"
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
)
|
|
1295
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1296
|
+
"div",
|
|
1297
|
+
{
|
|
1298
|
+
className: "highlighted-code font-mono text-xs leading-relaxed whitespace-pre-wrap break-words [&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:!p-0 [&_pre]:whitespace-pre-wrap [&_code]:!bg-transparent [&_code]:whitespace-pre-wrap [&_code]:break-words",
|
|
1299
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1300
|
+
}
|
|
1301
|
+
) : /* @__PURE__ */ jsx("pre", { className: "text-xs font-mono whitespace-pre-wrap break-words m-0 leading-relaxed text-[#24292f]", children: /* @__PURE__ */ jsx("code", { children: content }) }) }) })
|
|
1302
|
+
] });
|
|
1303
|
+
}
|
|
1304
|
+
function formatFileSize(bytes) {
|
|
1305
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1306
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1307
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1308
|
+
}
|
|
1309
|
+
function isUrl(content) {
|
|
1310
|
+
return content.startsWith("/") || content.startsWith("http://") || content.startsWith("https://") || content.startsWith("./") || content.startsWith("../");
|
|
1311
|
+
}
|
|
1312
|
+
function getDataUrl(content, mimeType) {
|
|
1313
|
+
if (content.startsWith("data:")) {
|
|
1314
|
+
return content;
|
|
1315
|
+
}
|
|
1316
|
+
if (isUrl(content)) {
|
|
1317
|
+
return content;
|
|
1318
|
+
}
|
|
1319
|
+
return `data:${mimeType};base64,${content}`;
|
|
1320
|
+
}
|
|
1321
|
+
function MediaPreview({ content, mimeType, fileName }) {
|
|
1322
|
+
const [dimensions, setDimensions] = useState(null);
|
|
1323
|
+
const [error, setError] = useState(null);
|
|
1324
|
+
const dataUrl = getDataUrl(content, mimeType);
|
|
1325
|
+
const isImage = isImageFile(fileName);
|
|
1326
|
+
const isVideo = isVideoFile(fileName);
|
|
1327
|
+
const isUrlContent = isUrl(content);
|
|
1328
|
+
const estimatedBytes = isUrlContent ? null : content.startsWith("data:") ? Math.floor((content.split(",")[1]?.length ?? 0) * 0.75) : Math.floor(content.length * 0.75);
|
|
1329
|
+
useEffect(() => {
|
|
1330
|
+
setDimensions(null);
|
|
1331
|
+
setError(null);
|
|
1332
|
+
if (isImage) {
|
|
1333
|
+
const img = new Image();
|
|
1334
|
+
img.onload = () => {
|
|
1335
|
+
setDimensions({ width: img.naturalWidth, height: img.naturalHeight });
|
|
1336
|
+
};
|
|
1337
|
+
img.onerror = () => {
|
|
1338
|
+
setError("Failed to load image");
|
|
1339
|
+
};
|
|
1340
|
+
img.src = dataUrl;
|
|
1341
|
+
}
|
|
1342
|
+
}, [dataUrl, isImage]);
|
|
1343
|
+
if (error) {
|
|
1344
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 text-muted-foreground", children: [
|
|
1345
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "h-12 w-12 mb-4 text-destructive" }),
|
|
1346
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", children: error })
|
|
1347
|
+
] });
|
|
1348
|
+
}
|
|
1349
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 bg-muted/20", children: [
|
|
1350
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center justify-center w-full max-h-[60vh] overflow-hidden", children: [
|
|
1351
|
+
isImage && /* @__PURE__ */ jsx(
|
|
1352
|
+
"img",
|
|
1353
|
+
{
|
|
1354
|
+
src: dataUrl,
|
|
1355
|
+
alt: fileName,
|
|
1356
|
+
className: "max-w-full max-h-full object-contain rounded shadow-sm",
|
|
1357
|
+
style: { maxHeight: "calc(60vh - 2rem)" }
|
|
1358
|
+
}
|
|
1359
|
+
),
|
|
1360
|
+
isVideo && /* @__PURE__ */ jsx(
|
|
1361
|
+
"video",
|
|
1362
|
+
{
|
|
1363
|
+
src: dataUrl,
|
|
1364
|
+
controls: true,
|
|
1365
|
+
className: "max-w-full max-h-full rounded shadow-sm",
|
|
1366
|
+
style: { maxHeight: "calc(60vh - 2rem)" },
|
|
1367
|
+
children: "Your browser does not support video playback."
|
|
1368
|
+
}
|
|
1369
|
+
),
|
|
1370
|
+
!isImage && !isVideo && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-muted-foreground", children: [
|
|
1371
|
+
/* @__PURE__ */ jsx(FileImage, { className: "h-16 w-16 mb-4" }),
|
|
1372
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", children: "Preview not available for this file type" })
|
|
1373
|
+
] })
|
|
1374
|
+
] }),
|
|
1375
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 text-center text-sm text-muted-foreground space-y-1", children: [
|
|
1376
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
1377
|
+
isImage && /* @__PURE__ */ jsx(FileImage, { className: "h-4 w-4" }),
|
|
1378
|
+
isVideo && /* @__PURE__ */ jsx(FileVideo, { className: "h-4 w-4" }),
|
|
1379
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: fileName })
|
|
1380
|
+
] }),
|
|
1381
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs space-x-3", children: [
|
|
1382
|
+
dimensions && /* @__PURE__ */ jsxs("span", { children: [
|
|
1383
|
+
dimensions.width,
|
|
1384
|
+
" \xD7 ",
|
|
1385
|
+
dimensions.height,
|
|
1386
|
+
" px"
|
|
1387
|
+
] }),
|
|
1388
|
+
estimatedBytes !== null && /* @__PURE__ */ jsx("span", { children: formatFileSize(estimatedBytes) }),
|
|
1389
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60", children: mimeType })
|
|
1390
|
+
] })
|
|
1391
|
+
] })
|
|
1392
|
+
] });
|
|
1393
|
+
}
|
|
911
1394
|
function hashCode(str) {
|
|
912
1395
|
let hash = 0;
|
|
913
1396
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -918,22 +1401,38 @@ function hashCode(str) {
|
|
|
918
1401
|
function EditModal({
|
|
919
1402
|
isOpen,
|
|
920
1403
|
onClose,
|
|
1404
|
+
onSave,
|
|
1405
|
+
onSaveProject,
|
|
921
1406
|
renderPreview,
|
|
922
1407
|
renderLoading,
|
|
923
1408
|
renderError,
|
|
924
1409
|
previewError,
|
|
925
1410
|
previewLoading,
|
|
1411
|
+
initialState = {},
|
|
1412
|
+
hideFileTree = false,
|
|
926
1413
|
...sessionOptions
|
|
927
1414
|
}) {
|
|
928
|
-
const [showPreview, setShowPreview] = useState(true);
|
|
929
|
-
const [showTree, setShowTree] = useState(
|
|
1415
|
+
const [showPreview, setShowPreview] = useState(initialState?.showPreview ?? true);
|
|
1416
|
+
const [showTree, setShowTree] = useState(
|
|
1417
|
+
hideFileTree ? false : initialState?.showTree ?? false
|
|
1418
|
+
);
|
|
930
1419
|
const [editInput, setEditInput] = useState("");
|
|
931
1420
|
const [bobbinChanges, setBobbinChanges] = useState([]);
|
|
932
1421
|
const [previewContainer, setPreviewContainer] = useState(null);
|
|
1422
|
+
const [pillContainer, setPillContainer] = useState(null);
|
|
1423
|
+
const [showConfirm, setShowConfirm] = useState(false);
|
|
1424
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
1425
|
+
const [saveError, setSaveError] = useState(null);
|
|
1426
|
+
const [pendingClose, setPendingClose] = useState(null);
|
|
1427
|
+
const currentCodeRef = useRef("");
|
|
933
1428
|
const session = useEditSession(sessionOptions);
|
|
934
1429
|
const code = getActiveContent(session);
|
|
1430
|
+
currentCodeRef.current = code;
|
|
935
1431
|
const files = useMemo(() => getFiles(session.project), [session.project]);
|
|
936
1432
|
const hasChanges = code !== (session.originalProject.files.get(session.activeFile)?.content ?? "");
|
|
1433
|
+
const fileType = useMemo(() => getFileType(session.activeFile), [session.activeFile]);
|
|
1434
|
+
const isCompilableFile = isCompilable(session.activeFile);
|
|
1435
|
+
const showPreviewToggle = isCompilableFile;
|
|
937
1436
|
const handleBobbinChanges = useCallback((changes) => {
|
|
938
1437
|
setBobbinChanges(changes);
|
|
939
1438
|
}, []);
|
|
@@ -954,146 +1453,253 @@ ${bobbinYaml}
|
|
|
954
1453
|
setEditInput("");
|
|
955
1454
|
setBobbinChanges([]);
|
|
956
1455
|
};
|
|
957
|
-
const
|
|
1456
|
+
const hasSaveHandler = onSave || onSaveProject;
|
|
1457
|
+
const handleClose = useCallback(() => {
|
|
958
1458
|
const editCount = session.history.length;
|
|
959
1459
|
const finalCode = code;
|
|
1460
|
+
const hasUnsavedChanges = editCount > 0 && finalCode !== (session.originalProject.files.get(session.activeFile)?.content ?? "");
|
|
1461
|
+
if (hasUnsavedChanges && hasSaveHandler) {
|
|
1462
|
+
setPendingClose({ code: finalCode, count: editCount });
|
|
1463
|
+
setShowConfirm(true);
|
|
1464
|
+
} else {
|
|
1465
|
+
setEditInput("");
|
|
1466
|
+
session.clearError();
|
|
1467
|
+
onClose(finalCode, editCount);
|
|
1468
|
+
}
|
|
1469
|
+
}, [code, session, hasSaveHandler, onClose]);
|
|
1470
|
+
const handleSaveAndClose = useCallback(async () => {
|
|
1471
|
+
if (!pendingClose || !hasSaveHandler) return;
|
|
1472
|
+
setIsSaving(true);
|
|
1473
|
+
setSaveError(null);
|
|
1474
|
+
try {
|
|
1475
|
+
if (onSaveProject) {
|
|
1476
|
+
await onSaveProject(session.project);
|
|
1477
|
+
} else if (onSave) {
|
|
1478
|
+
await onSave(pendingClose.code);
|
|
1479
|
+
}
|
|
1480
|
+
setShowConfirm(false);
|
|
1481
|
+
setEditInput("");
|
|
1482
|
+
session.clearError();
|
|
1483
|
+
onClose(pendingClose.code, pendingClose.count);
|
|
1484
|
+
setPendingClose(null);
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
setSaveError(e instanceof Error ? e.message : "Save failed");
|
|
1487
|
+
} finally {
|
|
1488
|
+
setIsSaving(false);
|
|
1489
|
+
}
|
|
1490
|
+
}, [pendingClose, onSave, onSaveProject, session, onClose]);
|
|
1491
|
+
const handleDiscard = useCallback(() => {
|
|
1492
|
+
if (!pendingClose) return;
|
|
1493
|
+
setShowConfirm(false);
|
|
960
1494
|
setEditInput("");
|
|
961
1495
|
session.clearError();
|
|
962
|
-
onClose(
|
|
963
|
-
|
|
1496
|
+
onClose(pendingClose.code, pendingClose.count);
|
|
1497
|
+
setPendingClose(null);
|
|
1498
|
+
}, [pendingClose, session, onClose]);
|
|
1499
|
+
const handleCancelClose = useCallback(() => {
|
|
1500
|
+
setShowConfirm(false);
|
|
1501
|
+
setPendingClose(null);
|
|
1502
|
+
setSaveError(null);
|
|
1503
|
+
}, []);
|
|
1504
|
+
const handleDirectSave = useCallback(async () => {
|
|
1505
|
+
if (!hasSaveHandler) return;
|
|
1506
|
+
setIsSaving(true);
|
|
1507
|
+
setSaveError(null);
|
|
1508
|
+
try {
|
|
1509
|
+
if (onSaveProject) {
|
|
1510
|
+
await onSaveProject(session.project);
|
|
1511
|
+
} else if (onSave && currentCodeRef.current) {
|
|
1512
|
+
await onSave(currentCodeRef.current);
|
|
1513
|
+
}
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
setSaveError(e instanceof Error ? e.message : "Save failed");
|
|
1516
|
+
} finally {
|
|
1517
|
+
setIsSaving(false);
|
|
1518
|
+
}
|
|
1519
|
+
}, [onSave, onSaveProject, session.project]);
|
|
964
1520
|
if (!isOpen) return null;
|
|
965
|
-
return /* @__PURE__ */
|
|
966
|
-
/* @__PURE__ */
|
|
967
|
-
/* @__PURE__ */
|
|
968
|
-
|
|
969
|
-
/* @__PURE__ */
|
|
970
|
-
|
|
1521
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1522
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-8", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-background rounded-lg shadow-xl w-full h-full max-w-6xl max-h-[90vh] overflow-hidden", children: [
|
|
1523
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 bg-background border-b-2", children: [
|
|
1524
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-primary" }),
|
|
1525
|
+
session.isApplying && /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-primary flex items-center gap-1 ml-2", children: [
|
|
1526
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
1527
|
+
"Applying edits..."
|
|
1528
|
+
] }),
|
|
1529
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex gap-2", children: [
|
|
1530
|
+
hasChanges && /* @__PURE__ */ jsx(
|
|
1531
|
+
"button",
|
|
1532
|
+
{
|
|
1533
|
+
onClick: session.revert,
|
|
1534
|
+
className: "px-2 py-1 text-xs rounded flex items-center gap-1 hover:bg-primary/20 text-primary",
|
|
1535
|
+
title: "Revert to original",
|
|
1536
|
+
children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" })
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
!hideFileTree && /* @__PURE__ */ jsx(
|
|
1540
|
+
"button",
|
|
1541
|
+
{
|
|
1542
|
+
onClick: () => setShowTree(!showTree),
|
|
1543
|
+
className: `px-2 py-1 text-xs rounded flex items-center gap-1 ${showTree ? "bg-primary text-primary-foreground" : "hover:bg-primary/20 text-primary"}`,
|
|
1544
|
+
title: showTree ? "Single file" : "File tree",
|
|
1545
|
+
children: showTree ? /* @__PURE__ */ jsx(FileCode, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(FolderTree, { className: "h-3 w-3" })
|
|
1546
|
+
}
|
|
1547
|
+
),
|
|
1548
|
+
showPreviewToggle && /* @__PURE__ */ jsxs(
|
|
1549
|
+
"button",
|
|
1550
|
+
{
|
|
1551
|
+
onClick: () => setShowPreview(!showPreview),
|
|
1552
|
+
className: `px-2 py-1 text-xs rounded flex items-center gap-1 ${showPreview ? "bg-primary text-primary-foreground" : "hover:bg-primary/20 text-primary"}`,
|
|
1553
|
+
children: [
|
|
1554
|
+
showPreview ? /* @__PURE__ */ jsx(Eye, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Code, { className: "h-3 w-3" }),
|
|
1555
|
+
showPreview ? "Preview" : "Code"
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
hasSaveHandler && /* @__PURE__ */ jsxs(
|
|
1560
|
+
"button",
|
|
1561
|
+
{
|
|
1562
|
+
onClick: handleDirectSave,
|
|
1563
|
+
disabled: isSaving,
|
|
1564
|
+
className: "px-2 py-1 text-xs rounded flex items-center gap-1 hover:bg-primary/20 text-primary disabled:opacity-50",
|
|
1565
|
+
title: "Save changes",
|
|
1566
|
+
children: [
|
|
1567
|
+
isSaving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "h-3 w-3" }),
|
|
1568
|
+
"Save"
|
|
1569
|
+
]
|
|
1570
|
+
}
|
|
1571
|
+
),
|
|
1572
|
+
/* @__PURE__ */ jsxs(
|
|
1573
|
+
"button",
|
|
1574
|
+
{
|
|
1575
|
+
onClick: handleClose,
|
|
1576
|
+
className: "px-2 py-1 text-xs rounded flex items-center gap-1 bg-primary text-primary-foreground hover:bg-primary/90",
|
|
1577
|
+
title: "Exit edit mode",
|
|
1578
|
+
children: [
|
|
1579
|
+
/* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
|
|
1580
|
+
"Done"
|
|
1581
|
+
]
|
|
1582
|
+
}
|
|
1583
|
+
)
|
|
1584
|
+
] })
|
|
971
1585
|
] }),
|
|
972
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
973
|
-
|
|
974
|
-
|
|
1586
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 border-b-2 overflow-hidden flex", children: [
|
|
1587
|
+
!hideFileTree && showTree && /* @__PURE__ */ jsx(
|
|
1588
|
+
FileTree,
|
|
975
1589
|
{
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1590
|
+
files,
|
|
1591
|
+
activeFile: session.activeFile,
|
|
1592
|
+
onSelectFile: session.setActiveFile,
|
|
1593
|
+
onReplaceFile: session.replaceFile
|
|
980
1594
|
}
|
|
981
1595
|
),
|
|
982
|
-
/* @__PURE__ */ jsx(
|
|
983
|
-
"
|
|
1596
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", ref: setPillContainer, children: fileType.category === "compilable" && showPreview ? /* @__PURE__ */ jsxs("div", { className: "bg-white h-full relative", ref: setPreviewContainer, children: [
|
|
1597
|
+
previewError && renderError ? renderError(previewError) : previewError ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-sm text-destructive flex items-center gap-2", children: [
|
|
1598
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4 shrink-0" }),
|
|
1599
|
+
/* @__PURE__ */ jsx("span", { children: previewError })
|
|
1600
|
+
] }) : previewLoading && renderLoading ? renderLoading() : previewLoading ? /* @__PURE__ */ jsxs("div", { className: "p-4 flex items-center gap-2 text-muted-foreground", children: [
|
|
1601
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
|
|
1602
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: "Rendering preview..." })
|
|
1603
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: renderPreview(code) }, hashCode(code)),
|
|
1604
|
+
!renderLoading && !renderError && !previewLoading && /* @__PURE__ */ jsx(
|
|
1605
|
+
Bobbin,
|
|
1606
|
+
{
|
|
1607
|
+
container: previewContainer,
|
|
1608
|
+
pillContainer,
|
|
1609
|
+
defaultActive: false,
|
|
1610
|
+
showInspector: true,
|
|
1611
|
+
onChanges: handleBobbinChanges,
|
|
1612
|
+
exclude: [".bobbin-pill", "[data-bobbin]"]
|
|
1613
|
+
}
|
|
1614
|
+
)
|
|
1615
|
+
] }) : fileType.category === "compilable" && !showPreview ? /* @__PURE__ */ jsx(
|
|
1616
|
+
CodeBlockView,
|
|
984
1617
|
{
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1618
|
+
content: code,
|
|
1619
|
+
language: fileType.language,
|
|
1620
|
+
editable: true,
|
|
1621
|
+
onChange: session.updateActiveFile
|
|
989
1622
|
}
|
|
990
|
-
)
|
|
991
|
-
|
|
992
|
-
"button",
|
|
1623
|
+
) : fileType.category === "text" ? /* @__PURE__ */ jsx(
|
|
1624
|
+
CodeBlockView,
|
|
993
1625
|
{
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
showPreview ? "Preview" : "Code"
|
|
999
|
-
]
|
|
1626
|
+
content: code,
|
|
1627
|
+
language: fileType.language,
|
|
1628
|
+
editable: true,
|
|
1629
|
+
onChange: session.updateActiveFile
|
|
1000
1630
|
}
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
|
-
"button",
|
|
1631
|
+
) : fileType.category === "media" ? /* @__PURE__ */ jsx(
|
|
1632
|
+
MediaPreview,
|
|
1004
1633
|
{
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
children: [
|
|
1009
|
-
/* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
|
|
1010
|
-
"Done"
|
|
1011
|
-
]
|
|
1634
|
+
content: code,
|
|
1635
|
+
mimeType: getMimeType(session.activeFile),
|
|
1636
|
+
fileName: session.activeFile.split("/").pop() ?? session.activeFile
|
|
1012
1637
|
}
|
|
1013
|
-
)
|
|
1014
|
-
] })
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
showTree && /* @__PURE__ */ jsx(
|
|
1018
|
-
FileTree,
|
|
1638
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-muted-foreground", children: /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Preview not available for this file type" }) }) })
|
|
1639
|
+
] }),
|
|
1640
|
+
/* @__PURE__ */ jsx(
|
|
1641
|
+
EditHistory,
|
|
1019
1642
|
{
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1643
|
+
entries: session.history,
|
|
1644
|
+
streamingNotes: session.streamingNotes,
|
|
1645
|
+
isStreaming: session.isApplying,
|
|
1646
|
+
pendingPrompt: session.pendingPrompt,
|
|
1647
|
+
className: "h-48"
|
|
1023
1648
|
}
|
|
1024
1649
|
),
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1650
|
+
(session.error || saveError) && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-destructive/10 text-destructive text-sm flex items-center gap-2 border-t-2 border-destructive", children: [
|
|
1651
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4 shrink-0" }),
|
|
1652
|
+
session.error || saveError
|
|
1653
|
+
] }),
|
|
1654
|
+
bobbinChanges.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-blue-50 text-blue-700 text-sm flex items-center gap-2 border-t", children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1656
|
+
bobbinChanges.length,
|
|
1657
|
+
" visual change",
|
|
1658
|
+
bobbinChanges.length !== 1 ? "s" : ""
|
|
1659
|
+
] }),
|
|
1660
|
+
/* @__PURE__ */ jsx(
|
|
1661
|
+
"button",
|
|
1035
1662
|
{
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
showInspector: true,
|
|
1040
|
-
onChanges: handleBobbinChanges,
|
|
1041
|
-
exclude: [".bobbin-pill", "[data-bobbin]"]
|
|
1663
|
+
onClick: () => setBobbinChanges([]),
|
|
1664
|
+
className: "text-xs underline hover:no-underline",
|
|
1665
|
+
children: "Clear"
|
|
1042
1666
|
}
|
|
1043
1667
|
)
|
|
1044
|
-
] })
|
|
1045
|
-
|
|
1668
|
+
] }),
|
|
1669
|
+
/* @__PURE__ */ jsxs("div", { className: "p-4 border-t-2 bg-primary/5 flex gap-2 items-end", children: [
|
|
1670
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
1671
|
+
MarkdownEditor,
|
|
1672
|
+
{
|
|
1673
|
+
value: editInput,
|
|
1674
|
+
onChange: setEditInput,
|
|
1675
|
+
onSubmit: handleSubmit,
|
|
1676
|
+
placeholder: "Describe changes...",
|
|
1677
|
+
disabled: session.isApplying
|
|
1678
|
+
}
|
|
1679
|
+
) }),
|
|
1680
|
+
/* @__PURE__ */ jsx(
|
|
1681
|
+
"button",
|
|
1682
|
+
{
|
|
1683
|
+
onClick: handleSubmit,
|
|
1684
|
+
disabled: !editInput.trim() && bobbinChanges.length === 0 || session.isApplying,
|
|
1685
|
+
className: "px-3 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 flex items-center gap-1 shrink-0",
|
|
1686
|
+
children: session.isApplying ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
|
|
1687
|
+
}
|
|
1688
|
+
)
|
|
1689
|
+
] })
|
|
1690
|
+
] }) }),
|
|
1046
1691
|
/* @__PURE__ */ jsx(
|
|
1047
|
-
|
|
1692
|
+
SaveConfirmDialog,
|
|
1048
1693
|
{
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1694
|
+
isOpen: showConfirm,
|
|
1695
|
+
isSaving,
|
|
1696
|
+
error: saveError,
|
|
1697
|
+
onSave: handleSaveAndClose,
|
|
1698
|
+
onDiscard: handleDiscard,
|
|
1699
|
+
onCancel: handleCancelClose
|
|
1054
1700
|
}
|
|
1055
|
-
)
|
|
1056
|
-
|
|
1057
|
-
/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4 shrink-0" }),
|
|
1058
|
-
session.error
|
|
1059
|
-
] }),
|
|
1060
|
-
bobbinChanges.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-blue-50 text-blue-700 text-sm flex items-center gap-2 border-t", children: [
|
|
1061
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
1062
|
-
bobbinChanges.length,
|
|
1063
|
-
" visual change",
|
|
1064
|
-
bobbinChanges.length !== 1 ? "s" : ""
|
|
1065
|
-
] }),
|
|
1066
|
-
/* @__PURE__ */ jsx(
|
|
1067
|
-
"button",
|
|
1068
|
-
{
|
|
1069
|
-
onClick: () => setBobbinChanges([]),
|
|
1070
|
-
className: "text-xs underline hover:no-underline",
|
|
1071
|
-
children: "Clear"
|
|
1072
|
-
}
|
|
1073
|
-
)
|
|
1074
|
-
] }),
|
|
1075
|
-
/* @__PURE__ */ jsxs("div", { className: "p-4 border-t-2 bg-primary/5 flex gap-2 items-end", children: [
|
|
1076
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
1077
|
-
MarkdownEditor,
|
|
1078
|
-
{
|
|
1079
|
-
value: editInput,
|
|
1080
|
-
onChange: setEditInput,
|
|
1081
|
-
onSubmit: handleSubmit,
|
|
1082
|
-
placeholder: "Describe changes...",
|
|
1083
|
-
disabled: session.isApplying
|
|
1084
|
-
}
|
|
1085
|
-
) }),
|
|
1086
|
-
/* @__PURE__ */ jsx(
|
|
1087
|
-
"button",
|
|
1088
|
-
{
|
|
1089
|
-
onClick: handleSubmit,
|
|
1090
|
-
disabled: !editInput.trim() && bobbinChanges.length === 0 || session.isApplying,
|
|
1091
|
-
className: "px-3 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 flex items-center gap-1 shrink-0",
|
|
1092
|
-
children: session.isApplying ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
|
|
1093
|
-
}
|
|
1094
|
-
)
|
|
1095
|
-
] })
|
|
1096
|
-
] }) });
|
|
1701
|
+
)
|
|
1702
|
+
] });
|
|
1097
1703
|
}
|
|
1098
1704
|
var VFS_BASE_URL = "/vfs";
|
|
1099
1705
|
var vfsConfigCache = null;
|
|
@@ -1112,8 +1718,11 @@ async function getVFSConfig() {
|
|
|
1112
1718
|
var storeInstance = null;
|
|
1113
1719
|
function getVFSStore() {
|
|
1114
1720
|
if (!storeInstance) {
|
|
1115
|
-
const
|
|
1116
|
-
storeInstance = new VFSStore(
|
|
1721
|
+
const provider = new HttpBackend({ baseUrl: VFS_BASE_URL });
|
|
1722
|
+
storeInstance = new VFSStore(provider, {
|
|
1723
|
+
sync: true,
|
|
1724
|
+
conflictStrategy: "local-wins"
|
|
1725
|
+
});
|
|
1117
1726
|
}
|
|
1118
1727
|
return storeInstance;
|
|
1119
1728
|
}
|
|
@@ -1135,9 +1744,17 @@ async function listProjects() {
|
|
|
1135
1744
|
}
|
|
1136
1745
|
return Array.from(projectIds);
|
|
1137
1746
|
}
|
|
1138
|
-
async function saveFile(
|
|
1747
|
+
async function saveFile(path, content) {
|
|
1748
|
+
const store = getVFSStore();
|
|
1749
|
+
await store.writeFile(path, content);
|
|
1750
|
+
}
|
|
1751
|
+
async function loadFile(path, encoding) {
|
|
1752
|
+
const store = getVFSStore();
|
|
1753
|
+
return store.readFile(path, encoding);
|
|
1754
|
+
}
|
|
1755
|
+
function subscribeToChanges(callback) {
|
|
1139
1756
|
const store = getVFSStore();
|
|
1140
|
-
|
|
1757
|
+
return store.on("change", callback);
|
|
1141
1758
|
}
|
|
1142
1759
|
async function isVFSAvailable() {
|
|
1143
1760
|
try {
|
|
@@ -1207,12 +1824,17 @@ function useCodeCompiler(compiler, code, enabled, services) {
|
|
|
1207
1824
|
}, [code, compiler, enabled, services]);
|
|
1208
1825
|
return { containerRef, loading, error };
|
|
1209
1826
|
}
|
|
1210
|
-
function CodePreview({ code: originalCode, compiler, services, filePath }) {
|
|
1827
|
+
function CodePreview({ code: originalCode, compiler, services, filePath, entrypoint = "index.ts" }) {
|
|
1211
1828
|
const [isEditing, setIsEditing] = useState(false);
|
|
1212
1829
|
const [showPreview, setShowPreview] = useState(true);
|
|
1213
1830
|
const [currentCode, setCurrentCode] = useState(originalCode);
|
|
1214
1831
|
const [editCount, setEditCount] = useState(0);
|
|
1215
1832
|
const [saveStatus, setSaveStatus] = useState("unsaved");
|
|
1833
|
+
const [lastSavedCode, setLastSavedCode] = useState(originalCode);
|
|
1834
|
+
const [vfsPath, setVfsPath] = useState(null);
|
|
1835
|
+
const currentCodeRef = useRef(currentCode);
|
|
1836
|
+
const lastSavedRef = useRef(lastSavedCode);
|
|
1837
|
+
const isEditingRef = useRef(isEditing);
|
|
1216
1838
|
const fallbackId = useMemo(() => crypto.randomUUID(), []);
|
|
1217
1839
|
const getProjectId = useCallback(async () => {
|
|
1218
1840
|
if (filePath) {
|
|
@@ -1230,10 +1852,57 @@ function CodePreview({ code: originalCode, compiler, services, filePath }) {
|
|
|
1230
1852
|
const getEntryFile = useCallback(() => {
|
|
1231
1853
|
if (filePath) {
|
|
1232
1854
|
const parts = filePath.split("/");
|
|
1233
|
-
return parts[parts.length - 1] ||
|
|
1855
|
+
return parts[parts.length - 1] || entrypoint;
|
|
1234
1856
|
}
|
|
1235
|
-
return
|
|
1857
|
+
return entrypoint;
|
|
1236
1858
|
}, [filePath]);
|
|
1859
|
+
useEffect(() => {
|
|
1860
|
+
currentCodeRef.current = currentCode;
|
|
1861
|
+
}, [currentCode]);
|
|
1862
|
+
useEffect(() => {
|
|
1863
|
+
lastSavedRef.current = lastSavedCode;
|
|
1864
|
+
}, [lastSavedCode]);
|
|
1865
|
+
useEffect(() => {
|
|
1866
|
+
isEditingRef.current = isEditing;
|
|
1867
|
+
}, [isEditing]);
|
|
1868
|
+
useEffect(() => {
|
|
1869
|
+
let active = true;
|
|
1870
|
+
void (async () => {
|
|
1871
|
+
const projectId = await getProjectId();
|
|
1872
|
+
const entryFile = getEntryFile();
|
|
1873
|
+
if (!active) return;
|
|
1874
|
+
setVfsPath(`${projectId}/${entryFile}`);
|
|
1875
|
+
})();
|
|
1876
|
+
return () => {
|
|
1877
|
+
active = false;
|
|
1878
|
+
};
|
|
1879
|
+
}, [getProjectId, getEntryFile]);
|
|
1880
|
+
useEffect(() => {
|
|
1881
|
+
if (!vfsPath) return;
|
|
1882
|
+
const unsubscribe = subscribeToChanges(async (record) => {
|
|
1883
|
+
if (record.path !== vfsPath) return;
|
|
1884
|
+
if (record.type === "delete") {
|
|
1885
|
+
setSaveStatus("unsaved");
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
if (isEditingRef.current) return;
|
|
1889
|
+
try {
|
|
1890
|
+
const remote = await loadFile(vfsPath);
|
|
1891
|
+
if (currentCodeRef.current !== lastSavedRef.current) {
|
|
1892
|
+
setSaveStatus("unsaved");
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (remote !== currentCodeRef.current) {
|
|
1896
|
+
setCurrentCode(remote);
|
|
1897
|
+
setLastSavedCode(remote);
|
|
1898
|
+
setSaveStatus("saved");
|
|
1899
|
+
}
|
|
1900
|
+
} catch {
|
|
1901
|
+
setSaveStatus("error");
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
return () => unsubscribe();
|
|
1905
|
+
}, [vfsPath]);
|
|
1237
1906
|
const handleSave = useCallback(async () => {
|
|
1238
1907
|
setSaveStatus("saving");
|
|
1239
1908
|
try {
|
|
@@ -1241,6 +1910,7 @@ function CodePreview({ code: originalCode, compiler, services, filePath }) {
|
|
|
1241
1910
|
const entryFile = getEntryFile();
|
|
1242
1911
|
const project = createSingleFileProject(currentCode, entryFile, projectId);
|
|
1243
1912
|
await saveProject(project);
|
|
1913
|
+
setLastSavedCode(currentCode);
|
|
1244
1914
|
setSaveStatus("saved");
|
|
1245
1915
|
} catch (err) {
|
|
1246
1916
|
console.warn("[VFS] Failed to save project:", err);
|
|
@@ -1594,4 +2264,4 @@ function cn(...inputs) {
|
|
|
1594
2264
|
return twMerge(clsx(inputs));
|
|
1595
2265
|
}
|
|
1596
2266
|
|
|
1597
|
-
export { CodeBlockExtension, CodePreview, EditHistory, EditModal, FileTree, MarkdownEditor, ServicesInspector, applyDiffs, cn, extractCodeBlocks, extractProject, extractSummary, extractTextWithoutDiffs, findDiffMarkers, findFirstCodeBlock, getActiveContent, getCodeBlockLanguages, getFiles, getVFSConfig, getVFSStore, hasCodeBlock, hasDiffBlocks, isVFSAvailable, listProjects, loadProject, parseCodeBlockAttributes, parseCodeBlocks, parseDiffs, parseEditResponse, sanitizeDiffMarkers, saveFile, saveProject, sendEditRequest, useEditSession };
|
|
2267
|
+
export { CodeBlockExtension, CodeBlockView, CodePreview, EditHistory, EditModal, FileTree, MarkdownEditor, MediaPreview, SaveConfirmDialog, ServicesInspector, applyDiffs, cn, extractCodeBlocks, extractProject, extractSummary, extractTextWithoutDiffs, findDiffMarkers, findFirstCodeBlock, getActiveContent, getCodeBlockLanguages, getFileType, getFiles, getLanguageFromExt, getMimeType, getVFSConfig, getVFSStore, hasCodeBlock, hasDiffBlocks, isCompilable, isImageFile, isMediaFile, isTextFile, isVFSAvailable, isVideoFile, listProjects, loadProject, parseCodeBlockAttributes, parseCodeBlocks, parseDiffs, parseEditResponse, sanitizeDiffMarkers, saveFile, saveProject, sendEditRequest, useEditSession };
|