@aprovan/patchwork-editor 0.1.2-dev.4a481e4 → 0.1.2-dev.4d82df8
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 +3 -3
- package/dist/components/CodePreview.d.ts +2 -3
- package/dist/components/MarkdownEditor.d.ts +1 -1
- package/dist/components/MarkdownPreview.d.ts +1 -1
- package/dist/components/SaveStatusButton.d.ts +1 -1
- package/dist/components/ServicesInspector.d.ts +1 -1
- package/dist/components/WidgetPreview.d.ts +1 -1
- package/dist/components/edit/CodeBlockView.d.ts +1 -1
- package/dist/components/edit/EditHistory.d.ts +1 -1
- package/dist/components/edit/EditModal.d.ts +1 -1
- package/dist/components/edit/FileTree.d.ts +1 -1
- package/dist/components/edit/MediaPreview.d.ts +1 -1
- package/dist/components/edit/SaveConfirmDialog.d.ts +1 -1
- package/dist/components/edit/index.d.ts +1 -0
- package/dist/components/edit/useEditSession.d.ts +1 -1
- package/dist/components/edit/useProjectState.d.ts +17 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +414 -403
- package/dist/lib/vfs.d.ts +2 -3
- package/package.json +6 -4
- package/src/components/CodePreview.tsx +7 -10
- package/src/components/MarkdownEditor.tsx +3 -3
- package/src/components/MarkdownPreview.tsx +2 -2
- package/src/components/ServicesInspector.tsx +1 -1
- package/src/components/WidgetPreview.tsx +1 -1
- package/src/components/edit/CodeBlockView.tsx +6 -2
- package/src/components/edit/EditHistory.tsx +1 -1
- package/src/components/edit/EditModal.tsx +6 -6
- package/src/components/edit/FileTree.tsx +2 -3
- package/src/components/edit/MediaPreview.tsx +1 -1
- package/src/components/edit/index.ts +4 -0
- package/src/components/edit/useEditSession.ts +19 -109
- package/src/components/edit/useProjectState.ts +122 -0
- package/src/index.ts +15 -18
- package/src/lib/code-extractor.ts +1 -1
- package/src/lib/vfs.ts +4 -5
- package/tsconfig.json +2 -1
- package/dist/components/index.d.ts +0 -7
- package/dist/lib/index.d.ts +0 -4
- package/src/components/index.ts +0 -7
- package/src/lib/index.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -2,17 +2,18 @@ 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 { HttpBackend, VFSStore, createSingleFileProject, detectMainFile, createProjectFromFiles } from '@aprovan/patchwork-compiler';
|
|
5
6
|
import { Folder, Pin, X, Loader2, AlertCircle, FileImage, FileVideo, Pencil, RotateCcw, FileCode, FolderTree, Eye, Code, Send, MessageSquare, Server, ChevronDown, File, PinOff, ChevronRight, ChevronsDown, Upload, AlertTriangle, Save } 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
|
-
import
|
|
9
|
+
import { Bobbin, serializeChangesToYAML } from '@aprovan/bobbin';
|
|
10
10
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
11
11
|
import Typography from '@tiptap/extension-typography';
|
|
12
|
-
import { Markdown as Markdown$1 } from 'tiptap-markdown';
|
|
13
12
|
import { TextSelection } from '@tiptap/pm/state';
|
|
13
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
14
|
+
import { Markdown as Markdown$1 } from 'tiptap-markdown';
|
|
15
|
+
import DOMPurify from 'dompurify';
|
|
14
16
|
import { createHighlighter } from 'shiki';
|
|
15
|
-
import { Bobbin, serializeChangesToYAML } from '@aprovan/bobbin';
|
|
16
17
|
import { clsx } from 'clsx';
|
|
17
18
|
import { twMerge } from 'tailwind-merge';
|
|
18
19
|
|
|
@@ -180,6 +181,69 @@ var CodeBlockExtension = Node.create({
|
|
|
180
181
|
};
|
|
181
182
|
}
|
|
182
183
|
});
|
|
184
|
+
var VFS_BASE_URL = "/vfs";
|
|
185
|
+
var vfsConfigCache = null;
|
|
186
|
+
async function getVFSConfig() {
|
|
187
|
+
if (vfsConfigCache) return vfsConfigCache;
|
|
188
|
+
try {
|
|
189
|
+
const res = await fetch(`${VFS_BASE_URL}/config`);
|
|
190
|
+
if (res.ok) {
|
|
191
|
+
vfsConfigCache = await res.json();
|
|
192
|
+
return vfsConfigCache;
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return { usePaths: false };
|
|
197
|
+
}
|
|
198
|
+
var storeInstance = null;
|
|
199
|
+
function getVFSStore() {
|
|
200
|
+
if (!storeInstance) {
|
|
201
|
+
const provider = new HttpBackend({ baseUrl: VFS_BASE_URL });
|
|
202
|
+
storeInstance = new VFSStore(provider, {
|
|
203
|
+
sync: true,
|
|
204
|
+
conflictStrategy: "local-wins"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return storeInstance;
|
|
208
|
+
}
|
|
209
|
+
async function saveProject(project) {
|
|
210
|
+
const store = getVFSStore();
|
|
211
|
+
await store.saveProject(project);
|
|
212
|
+
}
|
|
213
|
+
async function loadProject(id) {
|
|
214
|
+
const store = getVFSStore();
|
|
215
|
+
return store.loadProject(id);
|
|
216
|
+
}
|
|
217
|
+
async function listProjects() {
|
|
218
|
+
const store = getVFSStore();
|
|
219
|
+
const files = await store.listFiles();
|
|
220
|
+
const projectIds = /* @__PURE__ */ new Set();
|
|
221
|
+
for (const file of files) {
|
|
222
|
+
const id = file.split("/")[0];
|
|
223
|
+
if (id) projectIds.add(id);
|
|
224
|
+
}
|
|
225
|
+
return Array.from(projectIds);
|
|
226
|
+
}
|
|
227
|
+
async function saveFile(path, content) {
|
|
228
|
+
const store = getVFSStore();
|
|
229
|
+
await store.writeFile(path, content);
|
|
230
|
+
}
|
|
231
|
+
async function loadFile(path, encoding) {
|
|
232
|
+
const store = getVFSStore();
|
|
233
|
+
return store.readFile(path, encoding);
|
|
234
|
+
}
|
|
235
|
+
function subscribeToChanges(callback) {
|
|
236
|
+
const store = getVFSStore();
|
|
237
|
+
return store.on("change", callback);
|
|
238
|
+
}
|
|
239
|
+
async function isVFSAvailable() {
|
|
240
|
+
try {
|
|
241
|
+
const res = await fetch(VFS_BASE_URL, { method: "HEAD" });
|
|
242
|
+
return res.ok || res.status === 404;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
183
247
|
|
|
184
248
|
// src/components/edit/types.ts
|
|
185
249
|
function getActiveContent(state) {
|
|
@@ -440,20 +504,12 @@ function cloneProject(project) {
|
|
|
440
504
|
files: new Map(project.files)
|
|
441
505
|
};
|
|
442
506
|
}
|
|
443
|
-
function
|
|
507
|
+
function useProjectState(options) {
|
|
444
508
|
const {
|
|
445
509
|
originalCode,
|
|
446
510
|
originalProject: providedProject,
|
|
447
|
-
initialActiveFile
|
|
448
|
-
compile,
|
|
449
|
-
apiEndpoint
|
|
511
|
+
initialActiveFile
|
|
450
512
|
} = options;
|
|
451
|
-
console.log(
|
|
452
|
-
"[useEditSession] providedProject:",
|
|
453
|
-
providedProject?.id,
|
|
454
|
-
"files:",
|
|
455
|
-
providedProject ? Array.from(providedProject.files.keys()) : "none"
|
|
456
|
-
);
|
|
457
513
|
const originalProject = useMemo(
|
|
458
514
|
() => providedProject ?? createSingleFileProject(originalCode ?? ""),
|
|
459
515
|
[providedProject, originalCode]
|
|
@@ -464,10 +520,6 @@ function useEditSession(options) {
|
|
|
464
520
|
initialActiveFile && originalProject.files.has(initialActiveFile) ? initialActiveFile : originalProject.entry
|
|
465
521
|
);
|
|
466
522
|
const [history, setHistory] = useState([]);
|
|
467
|
-
const [isApplying, setIsApplying] = useState(false);
|
|
468
|
-
const [error, setError] = useState(null);
|
|
469
|
-
const [streamingNotes, setStreamingNotes] = useState([]);
|
|
470
|
-
const [pendingPrompt, setPendingPrompt] = useState(null);
|
|
471
523
|
useEffect(() => {
|
|
472
524
|
if (originalProject !== lastSyncedProjectRef.current) {
|
|
473
525
|
lastSyncedProjectRef.current = originalProject;
|
|
@@ -476,15 +528,74 @@ function useEditSession(options) {
|
|
|
476
528
|
initialActiveFile && originalProject.files.has(initialActiveFile) ? initialActiveFile : originalProject.entry
|
|
477
529
|
);
|
|
478
530
|
setHistory([]);
|
|
479
|
-
setError(null);
|
|
480
|
-
setStreamingNotes([]);
|
|
481
531
|
}
|
|
482
532
|
}, [originalProject, initialActiveFile]);
|
|
533
|
+
const currentCode = useMemo(
|
|
534
|
+
() => project.files.get(activeFile)?.content ?? "",
|
|
535
|
+
[project, activeFile]
|
|
536
|
+
);
|
|
537
|
+
const revert = useCallback(() => {
|
|
538
|
+
setProject(originalProject);
|
|
539
|
+
setActiveFile(originalProject.entry);
|
|
540
|
+
setHistory([]);
|
|
541
|
+
}, [originalProject]);
|
|
542
|
+
const updateActiveFile = useCallback(
|
|
543
|
+
(content) => {
|
|
544
|
+
setProject((prev) => {
|
|
545
|
+
const updated = cloneProject(prev);
|
|
546
|
+
const file = updated.files.get(activeFile);
|
|
547
|
+
if (file) {
|
|
548
|
+
updated.files.set(activeFile, { ...file, content });
|
|
549
|
+
}
|
|
550
|
+
return updated;
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
[activeFile]
|
|
554
|
+
);
|
|
555
|
+
const replaceFile = useCallback(
|
|
556
|
+
(path, content, encoding = "utf8") => {
|
|
557
|
+
setProject((prev) => {
|
|
558
|
+
const updated = cloneProject(prev);
|
|
559
|
+
const file = updated.files.get(path);
|
|
560
|
+
if (file) {
|
|
561
|
+
updated.files.set(path, { ...file, content, encoding });
|
|
562
|
+
} else {
|
|
563
|
+
updated.files.set(path, { path, content, encoding });
|
|
564
|
+
}
|
|
565
|
+
return updated;
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
[]
|
|
569
|
+
);
|
|
570
|
+
return {
|
|
571
|
+
project,
|
|
572
|
+
originalProject,
|
|
573
|
+
activeFile,
|
|
574
|
+
setActiveFile,
|
|
575
|
+
history,
|
|
576
|
+
setHistory,
|
|
577
|
+
setProject,
|
|
578
|
+
revert,
|
|
579
|
+
updateActiveFile,
|
|
580
|
+
replaceFile,
|
|
581
|
+
currentCode
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function useEditSession(options) {
|
|
585
|
+
const {
|
|
586
|
+
compile,
|
|
587
|
+
apiEndpoint
|
|
588
|
+
} = options;
|
|
589
|
+
const state = useProjectState(options);
|
|
590
|
+
const [isApplying, setIsApplying] = useState(false);
|
|
591
|
+
const [error, setError] = useState(null);
|
|
592
|
+
const [streamingNotes, setStreamingNotes] = useState([]);
|
|
593
|
+
const [pendingPrompt, setPendingPrompt] = useState(null);
|
|
483
594
|
const performEdit = useCallback(
|
|
484
|
-
async (
|
|
595
|
+
async (currentCode, prompt, isRetry = false) => {
|
|
485
596
|
const entries = [];
|
|
486
597
|
const response = await sendEditRequest(
|
|
487
|
-
{ code:
|
|
598
|
+
{ code: currentCode, prompt },
|
|
488
599
|
{
|
|
489
600
|
endpoint: apiEndpoint,
|
|
490
601
|
onProgress: (note) => setStreamingNotes((prev) => [...prev, note])
|
|
@@ -517,10 +628,6 @@ Please fix this error.`;
|
|
|
517
628
|
},
|
|
518
629
|
[compile, apiEndpoint]
|
|
519
630
|
);
|
|
520
|
-
const currentCode = useMemo(
|
|
521
|
-
() => project.files.get(activeFile)?.content ?? "",
|
|
522
|
-
[project, activeFile]
|
|
523
|
-
);
|
|
524
631
|
const submitEdit = useCallback(
|
|
525
632
|
async (prompt) => {
|
|
526
633
|
if (!prompt.trim() || isApplying) return;
|
|
@@ -529,16 +636,16 @@ Please fix this error.`;
|
|
|
529
636
|
setStreamingNotes([]);
|
|
530
637
|
setPendingPrompt(prompt);
|
|
531
638
|
try {
|
|
532
|
-
const result = await performEdit(currentCode, prompt);
|
|
533
|
-
setProject((prev) => {
|
|
534
|
-
const updated =
|
|
535
|
-
const file = updated.files.get(activeFile);
|
|
639
|
+
const result = await performEdit(state.currentCode, prompt);
|
|
640
|
+
state.setProject((prev) => {
|
|
641
|
+
const updated = { ...prev, files: new Map(prev.files) };
|
|
642
|
+
const file = updated.files.get(state.activeFile);
|
|
536
643
|
if (file) {
|
|
537
|
-
updated.files.set(activeFile, { ...file, content: result.newCode });
|
|
644
|
+
updated.files.set(state.activeFile, { ...file, content: result.newCode });
|
|
538
645
|
}
|
|
539
646
|
return updated;
|
|
540
647
|
});
|
|
541
|
-
setHistory((prev) => [...prev, ...result.entries]);
|
|
648
|
+
state.setHistory((prev) => [...prev, ...result.entries]);
|
|
542
649
|
} catch (err) {
|
|
543
650
|
setError(err instanceof Error ? err.message : "Edit failed");
|
|
544
651
|
} finally {
|
|
@@ -547,61 +654,26 @@ Please fix this error.`;
|
|
|
547
654
|
setPendingPrompt(null);
|
|
548
655
|
}
|
|
549
656
|
},
|
|
550
|
-
[currentCode, activeFile, isApplying, performEdit]
|
|
551
|
-
);
|
|
552
|
-
const revert = useCallback(() => {
|
|
553
|
-
setProject(originalProject);
|
|
554
|
-
setActiveFile(originalProject.entry);
|
|
555
|
-
setHistory([]);
|
|
556
|
-
setError(null);
|
|
557
|
-
setStreamingNotes([]);
|
|
558
|
-
}, [originalProject]);
|
|
559
|
-
const updateActiveFile = useCallback(
|
|
560
|
-
(content) => {
|
|
561
|
-
setProject((prev) => {
|
|
562
|
-
const updated = cloneProject(prev);
|
|
563
|
-
const file = updated.files.get(activeFile);
|
|
564
|
-
if (file) {
|
|
565
|
-
updated.files.set(activeFile, { ...file, content });
|
|
566
|
-
}
|
|
567
|
-
return updated;
|
|
568
|
-
});
|
|
569
|
-
},
|
|
570
|
-
[activeFile]
|
|
571
|
-
);
|
|
572
|
-
const replaceFile = useCallback(
|
|
573
|
-
(path, content, encoding = "utf8") => {
|
|
574
|
-
setProject((prev) => {
|
|
575
|
-
const updated = cloneProject(prev);
|
|
576
|
-
const file = updated.files.get(path);
|
|
577
|
-
if (file) {
|
|
578
|
-
updated.files.set(path, { ...file, content, encoding });
|
|
579
|
-
} else {
|
|
580
|
-
updated.files.set(path, { path, content, encoding });
|
|
581
|
-
}
|
|
582
|
-
return updated;
|
|
583
|
-
});
|
|
584
|
-
},
|
|
585
|
-
[]
|
|
657
|
+
[state.currentCode, state.activeFile, state.setProject, state.setHistory, isApplying, performEdit]
|
|
586
658
|
);
|
|
587
659
|
const clearError = useCallback(() => {
|
|
588
660
|
setError(null);
|
|
589
661
|
}, []);
|
|
590
662
|
return {
|
|
591
|
-
project,
|
|
592
|
-
originalProject,
|
|
593
|
-
activeFile,
|
|
594
|
-
history,
|
|
663
|
+
project: state.project,
|
|
664
|
+
originalProject: state.originalProject,
|
|
665
|
+
activeFile: state.activeFile,
|
|
666
|
+
history: state.history,
|
|
595
667
|
isApplying,
|
|
596
668
|
error,
|
|
597
669
|
streamingNotes,
|
|
598
670
|
pendingPrompt,
|
|
599
671
|
submitEdit,
|
|
600
|
-
revert,
|
|
601
|
-
updateActiveFile,
|
|
602
|
-
setActiveFile,
|
|
672
|
+
revert: state.revert,
|
|
673
|
+
updateActiveFile: state.updateActiveFile,
|
|
674
|
+
setActiveFile: state.setActiveFile,
|
|
603
675
|
clearError,
|
|
604
|
-
replaceFile
|
|
676
|
+
replaceFile: state.replaceFile
|
|
605
677
|
};
|
|
606
678
|
}
|
|
607
679
|
function ProgressNote({ text, isLatest }) {
|
|
@@ -1010,69 +1082,221 @@ function SaveStatusButton({
|
|
|
1010
1082
|
}
|
|
1011
1083
|
);
|
|
1012
1084
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
"
|
|
1018
|
-
"
|
|
1019
|
-
"
|
|
1020
|
-
"
|
|
1021
|
-
"
|
|
1022
|
-
"
|
|
1023
|
-
"
|
|
1024
|
-
"
|
|
1025
|
-
"
|
|
1026
|
-
|
|
1027
|
-
var TEXT_EXTENSIONS = [
|
|
1028
|
-
".json",
|
|
1029
|
-
".yaml",
|
|
1030
|
-
".yml",
|
|
1031
|
-
".md",
|
|
1032
|
-
".txt",
|
|
1033
|
-
".css",
|
|
1034
|
-
".html",
|
|
1035
|
-
".xml",
|
|
1036
|
-
".toml"
|
|
1085
|
+
var highlighterPromise = null;
|
|
1086
|
+
var COMMON_LANGUAGES = [
|
|
1087
|
+
"typescript",
|
|
1088
|
+
"javascript",
|
|
1089
|
+
"tsx",
|
|
1090
|
+
"jsx",
|
|
1091
|
+
"json",
|
|
1092
|
+
"html",
|
|
1093
|
+
"css",
|
|
1094
|
+
"markdown",
|
|
1095
|
+
"yaml",
|
|
1096
|
+
"python",
|
|
1097
|
+
"bash",
|
|
1098
|
+
"sql"
|
|
1037
1099
|
];
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1100
|
+
function getHighlighter() {
|
|
1101
|
+
if (!highlighterPromise) {
|
|
1102
|
+
highlighterPromise = createHighlighter({
|
|
1103
|
+
themes: ["github-light"],
|
|
1104
|
+
langs: COMMON_LANGUAGES
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return highlighterPromise;
|
|
1108
|
+
}
|
|
1109
|
+
function normalizeLanguage(lang) {
|
|
1110
|
+
if (!lang) return "typescript";
|
|
1111
|
+
const normalized = lang.toLowerCase();
|
|
1112
|
+
const mapping = {
|
|
1113
|
+
ts: "typescript",
|
|
1114
|
+
tsx: "tsx",
|
|
1115
|
+
js: "javascript",
|
|
1116
|
+
jsx: "jsx",
|
|
1117
|
+
json: "json",
|
|
1118
|
+
html: "html",
|
|
1119
|
+
css: "css",
|
|
1120
|
+
md: "markdown",
|
|
1121
|
+
markdown: "markdown",
|
|
1122
|
+
yml: "yaml",
|
|
1123
|
+
yaml: "yaml",
|
|
1124
|
+
py: "python",
|
|
1125
|
+
python: "python",
|
|
1126
|
+
sh: "bash",
|
|
1127
|
+
bash: "bash",
|
|
1128
|
+
sql: "sql",
|
|
1129
|
+
typescript: "typescript",
|
|
1130
|
+
javascript: "javascript"
|
|
1131
|
+
};
|
|
1132
|
+
return mapping[normalized] || "typescript";
|
|
1133
|
+
}
|
|
1134
|
+
function CodeBlockView({ content, language, editable = false, onChange }) {
|
|
1135
|
+
const textareaRef = useRef(null);
|
|
1136
|
+
const containerRef = useRef(null);
|
|
1137
|
+
const [highlighter, setHighlighter] = useState(null);
|
|
1138
|
+
useEffect(() => {
|
|
1139
|
+
let mounted = true;
|
|
1140
|
+
getHighlighter().then((h) => {
|
|
1141
|
+
if (mounted) setHighlighter(h);
|
|
1142
|
+
});
|
|
1143
|
+
return () => {
|
|
1144
|
+
mounted = false;
|
|
1145
|
+
};
|
|
1146
|
+
}, []);
|
|
1147
|
+
useEffect(() => {
|
|
1148
|
+
if (textareaRef.current) {
|
|
1149
|
+
textareaRef.current.style.height = "auto";
|
|
1150
|
+
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
|
1151
|
+
}
|
|
1152
|
+
}, [content]);
|
|
1153
|
+
const handleChange = useCallback(
|
|
1154
|
+
(e) => {
|
|
1155
|
+
onChange?.(e.target.value);
|
|
1156
|
+
},
|
|
1157
|
+
[onChange]
|
|
1158
|
+
);
|
|
1159
|
+
const handleKeyDown = useCallback(
|
|
1160
|
+
(e) => {
|
|
1161
|
+
if (e.key === "Tab") {
|
|
1162
|
+
e.preventDefault();
|
|
1163
|
+
const target = e.target;
|
|
1164
|
+
const start = target.selectionStart;
|
|
1165
|
+
const end = target.selectionEnd;
|
|
1166
|
+
const value = target.value;
|
|
1167
|
+
const newValue = value.substring(0, start) + " " + value.substring(end);
|
|
1168
|
+
onChange?.(newValue);
|
|
1169
|
+
requestAnimationFrame(() => {
|
|
1170
|
+
target.selectionStart = target.selectionEnd = start + 2;
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
[onChange]
|
|
1175
|
+
);
|
|
1176
|
+
const langLabel = language || "text";
|
|
1177
|
+
const shikiLang = useMemo(() => normalizeLanguage(language), [language]);
|
|
1178
|
+
const highlightedHtml = useMemo(() => {
|
|
1179
|
+
if (!highlighter) return null;
|
|
1180
|
+
try {
|
|
1181
|
+
const raw = highlighter.codeToHtml(content, {
|
|
1182
|
+
lang: shikiLang,
|
|
1183
|
+
theme: "github-light"
|
|
1184
|
+
});
|
|
1185
|
+
return DOMPurify.sanitize(raw, {
|
|
1186
|
+
ALLOWED_TAGS: ["pre", "code", "span", "div"],
|
|
1187
|
+
ALLOWED_ATTR: ["class", "style"]
|
|
1188
|
+
});
|
|
1189
|
+
} catch {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}, [highlighter, content, shikiLang]);
|
|
1193
|
+
return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col bg-[#ffffff]", children: [
|
|
1194
|
+
/* @__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 }) }),
|
|
1195
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden", children: editable ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-full", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx(
|
|
1197
|
+
"div",
|
|
1198
|
+
{
|
|
1199
|
+
ref: containerRef,
|
|
1200
|
+
className: "absolute top-0 left-0 right-0 pointer-events-none p-4",
|
|
1201
|
+
"aria-hidden": "true",
|
|
1202
|
+
children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1203
|
+
"div",
|
|
1204
|
+
{
|
|
1205
|
+
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",
|
|
1206
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1207
|
+
}
|
|
1208
|
+
) : /* @__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 }) })
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
/* @__PURE__ */ jsx(
|
|
1212
|
+
"textarea",
|
|
1213
|
+
{
|
|
1214
|
+
ref: textareaRef,
|
|
1215
|
+
value: content,
|
|
1216
|
+
onChange: handleChange,
|
|
1217
|
+
onKeyDown: handleKeyDown,
|
|
1218
|
+
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",
|
|
1219
|
+
spellCheck: false,
|
|
1220
|
+
style: {
|
|
1221
|
+
tabSize: 2,
|
|
1222
|
+
caretColor: "#24292f",
|
|
1223
|
+
wordBreak: "break-word",
|
|
1224
|
+
overflowWrap: "break-word"
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
)
|
|
1228
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1229
|
+
"div",
|
|
1230
|
+
{
|
|
1231
|
+
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",
|
|
1232
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1233
|
+
}
|
|
1234
|
+
) : /* @__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 }) }) }) })
|
|
1235
|
+
] });
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/components/edit/fileTypes.ts
|
|
1239
|
+
var COMPILABLE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
|
|
1240
|
+
var MEDIA_EXTENSIONS = [
|
|
1241
|
+
".svg",
|
|
1242
|
+
".png",
|
|
1243
|
+
".jpg",
|
|
1244
|
+
".jpeg",
|
|
1245
|
+
".gif",
|
|
1246
|
+
".webp",
|
|
1247
|
+
".mp4",
|
|
1248
|
+
".mov",
|
|
1249
|
+
".webm"
|
|
1250
|
+
];
|
|
1251
|
+
var TEXT_EXTENSIONS = [
|
|
1252
|
+
".json",
|
|
1253
|
+
".yaml",
|
|
1254
|
+
".yml",
|
|
1255
|
+
".md",
|
|
1256
|
+
".txt",
|
|
1257
|
+
".css",
|
|
1258
|
+
".html",
|
|
1259
|
+
".xml",
|
|
1260
|
+
".toml"
|
|
1261
|
+
];
|
|
1262
|
+
var EXTENSION_TO_LANGUAGE = {
|
|
1263
|
+
".tsx": "tsx",
|
|
1264
|
+
".jsx": "jsx",
|
|
1265
|
+
".ts": "typescript",
|
|
1266
|
+
".js": "javascript",
|
|
1267
|
+
".json": "json",
|
|
1268
|
+
".yaml": "yaml",
|
|
1269
|
+
".yml": "yaml",
|
|
1270
|
+
".md": "markdown",
|
|
1271
|
+
".txt": "text",
|
|
1272
|
+
".css": "css",
|
|
1273
|
+
".html": "html",
|
|
1274
|
+
".xml": "xml",
|
|
1275
|
+
".toml": "toml",
|
|
1276
|
+
".svg": "xml"
|
|
1277
|
+
};
|
|
1278
|
+
var EXTENSION_TO_MIME = {
|
|
1279
|
+
".tsx": "text/typescript-jsx",
|
|
1280
|
+
".jsx": "text/javascript-jsx",
|
|
1281
|
+
".ts": "text/typescript",
|
|
1282
|
+
".js": "text/javascript",
|
|
1283
|
+
".json": "application/json",
|
|
1284
|
+
".yaml": "text/yaml",
|
|
1285
|
+
".yml": "text/yaml",
|
|
1286
|
+
".md": "text/markdown",
|
|
1287
|
+
".txt": "text/plain",
|
|
1288
|
+
".css": "text/css",
|
|
1289
|
+
".html": "text/html",
|
|
1290
|
+
".xml": "application/xml",
|
|
1291
|
+
".toml": "text/toml",
|
|
1292
|
+
".svg": "image/svg+xml",
|
|
1293
|
+
".png": "image/png",
|
|
1294
|
+
".jpg": "image/jpeg",
|
|
1295
|
+
".jpeg": "image/jpeg",
|
|
1296
|
+
".gif": "image/gif",
|
|
1297
|
+
".webp": "image/webp",
|
|
1298
|
+
".mp4": "video/mp4",
|
|
1299
|
+
".mov": "video/quicktime",
|
|
1076
1300
|
".webm": "video/webm"
|
|
1077
1301
|
};
|
|
1078
1302
|
function getExtension(path) {
|
|
@@ -1693,203 +1917,6 @@ function FileTree({
|
|
|
1693
1917
|
) })
|
|
1694
1918
|
] });
|
|
1695
1919
|
}
|
|
1696
|
-
function SaveConfirmDialog({
|
|
1697
|
-
isOpen,
|
|
1698
|
-
isSaving,
|
|
1699
|
-
error,
|
|
1700
|
-
onSave,
|
|
1701
|
-
onDiscard,
|
|
1702
|
-
onCancel
|
|
1703
|
-
}) {
|
|
1704
|
-
if (!isOpen) return null;
|
|
1705
|
-
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: [
|
|
1706
|
-
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
1707
|
-
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: "Unsaved Changes" }),
|
|
1708
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "You have unsaved changes. Would you like to save them before closing?" }),
|
|
1709
|
-
error && /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-3", children: [
|
|
1710
|
-
"Save failed: ",
|
|
1711
|
-
error
|
|
1712
|
-
] })
|
|
1713
|
-
] }),
|
|
1714
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 p-6 pt-0", children: [
|
|
1715
|
-
/* @__PURE__ */ jsx(
|
|
1716
|
-
"button",
|
|
1717
|
-
{
|
|
1718
|
-
onClick: onCancel,
|
|
1719
|
-
disabled: isSaving,
|
|
1720
|
-
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",
|
|
1721
|
-
children: "Cancel"
|
|
1722
|
-
}
|
|
1723
|
-
),
|
|
1724
|
-
/* @__PURE__ */ jsx(
|
|
1725
|
-
"button",
|
|
1726
|
-
{
|
|
1727
|
-
onClick: onDiscard,
|
|
1728
|
-
disabled: isSaving,
|
|
1729
|
-
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",
|
|
1730
|
-
children: "Discard"
|
|
1731
|
-
}
|
|
1732
|
-
),
|
|
1733
|
-
/* @__PURE__ */ jsx(
|
|
1734
|
-
"button",
|
|
1735
|
-
{
|
|
1736
|
-
onClick: onSave,
|
|
1737
|
-
disabled: isSaving,
|
|
1738
|
-
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",
|
|
1739
|
-
children: isSaving ? "Saving..." : "Save"
|
|
1740
|
-
}
|
|
1741
|
-
)
|
|
1742
|
-
] })
|
|
1743
|
-
] }) });
|
|
1744
|
-
}
|
|
1745
|
-
var highlighterPromise = null;
|
|
1746
|
-
var COMMON_LANGUAGES = [
|
|
1747
|
-
"typescript",
|
|
1748
|
-
"javascript",
|
|
1749
|
-
"tsx",
|
|
1750
|
-
"jsx",
|
|
1751
|
-
"json",
|
|
1752
|
-
"html",
|
|
1753
|
-
"css",
|
|
1754
|
-
"markdown",
|
|
1755
|
-
"yaml",
|
|
1756
|
-
"python",
|
|
1757
|
-
"bash",
|
|
1758
|
-
"sql"
|
|
1759
|
-
];
|
|
1760
|
-
function getHighlighter() {
|
|
1761
|
-
if (!highlighterPromise) {
|
|
1762
|
-
highlighterPromise = createHighlighter({
|
|
1763
|
-
themes: ["github-light"],
|
|
1764
|
-
langs: COMMON_LANGUAGES
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
return highlighterPromise;
|
|
1768
|
-
}
|
|
1769
|
-
function normalizeLanguage(lang) {
|
|
1770
|
-
if (!lang) return "typescript";
|
|
1771
|
-
const normalized = lang.toLowerCase();
|
|
1772
|
-
const mapping = {
|
|
1773
|
-
ts: "typescript",
|
|
1774
|
-
tsx: "tsx",
|
|
1775
|
-
js: "javascript",
|
|
1776
|
-
jsx: "jsx",
|
|
1777
|
-
json: "json",
|
|
1778
|
-
html: "html",
|
|
1779
|
-
css: "css",
|
|
1780
|
-
md: "markdown",
|
|
1781
|
-
markdown: "markdown",
|
|
1782
|
-
yml: "yaml",
|
|
1783
|
-
yaml: "yaml",
|
|
1784
|
-
py: "python",
|
|
1785
|
-
python: "python",
|
|
1786
|
-
sh: "bash",
|
|
1787
|
-
bash: "bash",
|
|
1788
|
-
sql: "sql",
|
|
1789
|
-
typescript: "typescript",
|
|
1790
|
-
javascript: "javascript"
|
|
1791
|
-
};
|
|
1792
|
-
return mapping[normalized] || "typescript";
|
|
1793
|
-
}
|
|
1794
|
-
function CodeBlockView({ content, language, editable = false, onChange }) {
|
|
1795
|
-
const textareaRef = useRef(null);
|
|
1796
|
-
const containerRef = useRef(null);
|
|
1797
|
-
const [highlighter, setHighlighter] = useState(null);
|
|
1798
|
-
useEffect(() => {
|
|
1799
|
-
let mounted = true;
|
|
1800
|
-
getHighlighter().then((h) => {
|
|
1801
|
-
if (mounted) setHighlighter(h);
|
|
1802
|
-
});
|
|
1803
|
-
return () => {
|
|
1804
|
-
mounted = false;
|
|
1805
|
-
};
|
|
1806
|
-
}, []);
|
|
1807
|
-
useEffect(() => {
|
|
1808
|
-
if (textareaRef.current) {
|
|
1809
|
-
textareaRef.current.style.height = "auto";
|
|
1810
|
-
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
|
1811
|
-
}
|
|
1812
|
-
}, [content]);
|
|
1813
|
-
const handleChange = useCallback(
|
|
1814
|
-
(e) => {
|
|
1815
|
-
onChange?.(e.target.value);
|
|
1816
|
-
},
|
|
1817
|
-
[onChange]
|
|
1818
|
-
);
|
|
1819
|
-
const handleKeyDown = useCallback(
|
|
1820
|
-
(e) => {
|
|
1821
|
-
if (e.key === "Tab") {
|
|
1822
|
-
e.preventDefault();
|
|
1823
|
-
const target = e.target;
|
|
1824
|
-
const start = target.selectionStart;
|
|
1825
|
-
const end = target.selectionEnd;
|
|
1826
|
-
const value = target.value;
|
|
1827
|
-
const newValue = value.substring(0, start) + " " + value.substring(end);
|
|
1828
|
-
onChange?.(newValue);
|
|
1829
|
-
requestAnimationFrame(() => {
|
|
1830
|
-
target.selectionStart = target.selectionEnd = start + 2;
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
},
|
|
1834
|
-
[onChange]
|
|
1835
|
-
);
|
|
1836
|
-
const langLabel = language || "text";
|
|
1837
|
-
const shikiLang = useMemo(() => normalizeLanguage(language), [language]);
|
|
1838
|
-
const highlightedHtml = useMemo(() => {
|
|
1839
|
-
if (!highlighter) return null;
|
|
1840
|
-
try {
|
|
1841
|
-
return highlighter.codeToHtml(content, {
|
|
1842
|
-
lang: shikiLang,
|
|
1843
|
-
theme: "github-light"
|
|
1844
|
-
});
|
|
1845
|
-
} catch {
|
|
1846
|
-
return null;
|
|
1847
|
-
}
|
|
1848
|
-
}, [highlighter, content, shikiLang]);
|
|
1849
|
-
return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col bg-[#ffffff]", children: [
|
|
1850
|
-
/* @__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 }) }),
|
|
1851
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden", children: editable ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-full", children: [
|
|
1852
|
-
/* @__PURE__ */ jsx(
|
|
1853
|
-
"div",
|
|
1854
|
-
{
|
|
1855
|
-
ref: containerRef,
|
|
1856
|
-
className: "absolute top-0 left-0 right-0 pointer-events-none p-4",
|
|
1857
|
-
"aria-hidden": "true",
|
|
1858
|
-
children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1859
|
-
"div",
|
|
1860
|
-
{
|
|
1861
|
-
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",
|
|
1862
|
-
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1863
|
-
}
|
|
1864
|
-
) : /* @__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 }) })
|
|
1865
|
-
}
|
|
1866
|
-
),
|
|
1867
|
-
/* @__PURE__ */ jsx(
|
|
1868
|
-
"textarea",
|
|
1869
|
-
{
|
|
1870
|
-
ref: textareaRef,
|
|
1871
|
-
value: content,
|
|
1872
|
-
onChange: handleChange,
|
|
1873
|
-
onKeyDown: handleKeyDown,
|
|
1874
|
-
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",
|
|
1875
|
-
spellCheck: false,
|
|
1876
|
-
style: {
|
|
1877
|
-
tabSize: 2,
|
|
1878
|
-
caretColor: "#24292f",
|
|
1879
|
-
wordBreak: "break-word",
|
|
1880
|
-
overflowWrap: "break-word"
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
)
|
|
1884
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1885
|
-
"div",
|
|
1886
|
-
{
|
|
1887
|
-
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",
|
|
1888
|
-
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1889
|
-
}
|
|
1890
|
-
) : /* @__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 }) }) }) })
|
|
1891
|
-
] });
|
|
1892
|
-
}
|
|
1893
1920
|
function formatFileSize(bytes) {
|
|
1894
1921
|
if (bytes < 1024) return `${bytes} B`;
|
|
1895
1922
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -1980,6 +2007,55 @@ function MediaPreview({ content, mimeType, fileName }) {
|
|
|
1980
2007
|
] })
|
|
1981
2008
|
] });
|
|
1982
2009
|
}
|
|
2010
|
+
function SaveConfirmDialog({
|
|
2011
|
+
isOpen,
|
|
2012
|
+
isSaving,
|
|
2013
|
+
error,
|
|
2014
|
+
onSave,
|
|
2015
|
+
onDiscard,
|
|
2016
|
+
onCancel
|
|
2017
|
+
}) {
|
|
2018
|
+
if (!isOpen) return null;
|
|
2019
|
+
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: [
|
|
2020
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
2021
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: "Unsaved Changes" }),
|
|
2022
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "You have unsaved changes. Would you like to save them before closing?" }),
|
|
2023
|
+
error && /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-3", children: [
|
|
2024
|
+
"Save failed: ",
|
|
2025
|
+
error
|
|
2026
|
+
] })
|
|
2027
|
+
] }),
|
|
2028
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 p-6 pt-0", children: [
|
|
2029
|
+
/* @__PURE__ */ jsx(
|
|
2030
|
+
"button",
|
|
2031
|
+
{
|
|
2032
|
+
onClick: onCancel,
|
|
2033
|
+
disabled: isSaving,
|
|
2034
|
+
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",
|
|
2035
|
+
children: "Cancel"
|
|
2036
|
+
}
|
|
2037
|
+
),
|
|
2038
|
+
/* @__PURE__ */ jsx(
|
|
2039
|
+
"button",
|
|
2040
|
+
{
|
|
2041
|
+
onClick: onDiscard,
|
|
2042
|
+
disabled: isSaving,
|
|
2043
|
+
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",
|
|
2044
|
+
children: "Discard"
|
|
2045
|
+
}
|
|
2046
|
+
),
|
|
2047
|
+
/* @__PURE__ */ jsx(
|
|
2048
|
+
"button",
|
|
2049
|
+
{
|
|
2050
|
+
onClick: onSave,
|
|
2051
|
+
disabled: isSaving,
|
|
2052
|
+
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",
|
|
2053
|
+
children: isSaving ? "Saving..." : "Save"
|
|
2054
|
+
}
|
|
2055
|
+
)
|
|
2056
|
+
] })
|
|
2057
|
+
] }) });
|
|
2058
|
+
}
|
|
1983
2059
|
function hashCode(str) {
|
|
1984
2060
|
let hash = 0;
|
|
1985
2061
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -2416,69 +2492,6 @@ function WidgetPreview({
|
|
|
2416
2492
|
/* @__PURE__ */ jsx("div", { ref: containerRef, className: "w-full" })
|
|
2417
2493
|
] });
|
|
2418
2494
|
}
|
|
2419
|
-
var VFS_BASE_URL = "/vfs";
|
|
2420
|
-
var vfsConfigCache = null;
|
|
2421
|
-
async function getVFSConfig() {
|
|
2422
|
-
if (vfsConfigCache) return vfsConfigCache;
|
|
2423
|
-
try {
|
|
2424
|
-
const res = await fetch(`${VFS_BASE_URL}/config`);
|
|
2425
|
-
if (res.ok) {
|
|
2426
|
-
vfsConfigCache = await res.json();
|
|
2427
|
-
return vfsConfigCache;
|
|
2428
|
-
}
|
|
2429
|
-
} catch {
|
|
2430
|
-
}
|
|
2431
|
-
return { usePaths: false };
|
|
2432
|
-
}
|
|
2433
|
-
var storeInstance = null;
|
|
2434
|
-
function getVFSStore() {
|
|
2435
|
-
if (!storeInstance) {
|
|
2436
|
-
const provider = new HttpBackend({ baseUrl: VFS_BASE_URL });
|
|
2437
|
-
storeInstance = new VFSStore(provider, {
|
|
2438
|
-
sync: true,
|
|
2439
|
-
conflictStrategy: "local-wins"
|
|
2440
|
-
});
|
|
2441
|
-
}
|
|
2442
|
-
return storeInstance;
|
|
2443
|
-
}
|
|
2444
|
-
async function saveProject(project) {
|
|
2445
|
-
const store = getVFSStore();
|
|
2446
|
-
await store.saveProject(project);
|
|
2447
|
-
}
|
|
2448
|
-
async function loadProject(id) {
|
|
2449
|
-
const store = getVFSStore();
|
|
2450
|
-
return store.loadProject(id);
|
|
2451
|
-
}
|
|
2452
|
-
async function listProjects() {
|
|
2453
|
-
const store = getVFSStore();
|
|
2454
|
-
const files = await store.listFiles();
|
|
2455
|
-
const projectIds = /* @__PURE__ */ new Set();
|
|
2456
|
-
for (const file of files) {
|
|
2457
|
-
const id = file.split("/")[0];
|
|
2458
|
-
if (id) projectIds.add(id);
|
|
2459
|
-
}
|
|
2460
|
-
return Array.from(projectIds);
|
|
2461
|
-
}
|
|
2462
|
-
async function saveFile(path, content) {
|
|
2463
|
-
const store = getVFSStore();
|
|
2464
|
-
await store.writeFile(path, content);
|
|
2465
|
-
}
|
|
2466
|
-
async function loadFile(path, encoding) {
|
|
2467
|
-
const store = getVFSStore();
|
|
2468
|
-
return store.readFile(path, encoding);
|
|
2469
|
-
}
|
|
2470
|
-
function subscribeToChanges(callback) {
|
|
2471
|
-
const store = getVFSStore();
|
|
2472
|
-
return store.on("change", callback);
|
|
2473
|
-
}
|
|
2474
|
-
async function isVFSAvailable() {
|
|
2475
|
-
try {
|
|
2476
|
-
const res = await fetch(VFS_BASE_URL, { method: "HEAD" });
|
|
2477
|
-
return res.ok || res.status === 404;
|
|
2478
|
-
} catch {
|
|
2479
|
-
return false;
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
2495
|
function createManifest2(services) {
|
|
2483
2496
|
return {
|
|
2484
2497
|
name: "preview",
|
|
@@ -2591,8 +2604,7 @@ function CodePreview({
|
|
|
2591
2604
|
await saveProject(project);
|
|
2592
2605
|
setLastSavedCode(currentCode);
|
|
2593
2606
|
setSaveStatus("saved");
|
|
2594
|
-
} catch
|
|
2595
|
-
console.warn("[VFS] Failed to save project:", err);
|
|
2607
|
+
} catch {
|
|
2596
2608
|
setSaveStatus("error");
|
|
2597
2609
|
}
|
|
2598
2610
|
}, [currentCode, getProjectId, getEntryFile]);
|
|
@@ -2763,8 +2775,7 @@ ${errors.join("\n")}` : "";
|
|
|
2763
2775
|
await saveProject(project);
|
|
2764
2776
|
setLastSavedCode(finalCode);
|
|
2765
2777
|
setSaveStatus("saved");
|
|
2766
|
-
} catch
|
|
2767
|
-
console.warn("[VFS] Failed to save project:", err);
|
|
2778
|
+
} catch {
|
|
2768
2779
|
setSaveStatus("error");
|
|
2769
2780
|
}
|
|
2770
2781
|
})();
|
|
@@ -3012,4 +3023,4 @@ function cn(...inputs) {
|
|
|
3012
3023
|
return twMerge(clsx(inputs));
|
|
3013
3024
|
}
|
|
3014
3025
|
|
|
3015
|
-
export { CodeBlockExtension, CodeBlockView, CodePreview, EditHistory, EditModal, FileTree, MarkdownEditor, MarkdownPreview, MediaPreview, SaveConfirmDialog, ServicesInspector, WidgetPreview, applyDiffs, cn, extractCodeBlocks, extractProject, extractSummary, extractTextWithoutDiffs, findDiffMarkers, findFirstCodeBlock, getActiveContent, getCodeBlockLanguages, getFileType, getFiles, getLanguageFromExt, getMimeType, getVFSConfig, getVFSStore, hasCodeBlock, hasDiffBlocks, isCompilable, isImageFile, isMarkdownFile, isMediaFile, isPreviewable, isTextFile, isVFSAvailable, isVideoFile, listProjects, loadProject, parseCodeBlockAttributes, parseCodeBlocks, parseDiffs, parseEditResponse, sanitizeDiffMarkers, saveFile, saveProject, sendEditRequest, useEditSession };
|
|
3026
|
+
export { CodeBlockExtension, CodeBlockView, CodePreview, EditHistory, EditModal, FileTree, MarkdownEditor, MarkdownPreview, MediaPreview, SaveConfirmDialog, ServicesInspector, WidgetPreview, applyDiffs, cn, extractCodeBlocks, extractProject, extractSummary, extractTextWithoutDiffs, findDiffMarkers, findFirstCodeBlock, getActiveContent, getCodeBlockLanguages, getFileType, getFiles, getLanguageFromExt, getMimeType, getVFSConfig, getVFSStore, hasCodeBlock, hasDiffBlocks, isCompilable, isImageFile, isMarkdownFile, isMediaFile, isPreviewable, isTextFile, isVFSAvailable, isVideoFile, listProjects, loadProject, parseCodeBlockAttributes, parseCodeBlocks, parseDiffs, parseEditResponse, sanitizeDiffMarkers, saveFile, saveProject, sendEditRequest, useEditSession, useProjectState };
|