@aprovan/patchwork-editor 0.1.2-dev.ba8f277 → 0.1.2-dev.c1ac1dc
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 +2 -2
- 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/api.d.ts +1 -1
- package/dist/components/edit/fileTypes.d.ts +1 -1
- package/dist/components/edit/useEditSession.d.ts +1 -1
- package/dist/index.js +287 -267
- package/package.json +3 -3
- package/src/components/CodePreview.tsx +5 -6
- package/src/components/MarkdownEditor.tsx +3 -3
- package/src/components/MarkdownPreview.tsx +3 -3
- package/src/components/ServicesInspector.tsx +2 -2
- package/src/components/WidgetPreview.tsx +1 -1
- 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/api.ts +8 -8
- package/src/components/edit/fileTypes.ts +75 -55
- package/src/components/edit/useEditSession.ts +2 -2
- package/src/lib/code-extractor.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[32m104.
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m104.93 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 450ms
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Compiler } from '@aprovan/patchwork-compiler';
|
|
2
|
-
import type { VirtualProject } from '@aprovan/patchwork-compiler';
|
|
1
|
+
import type { Compiler, VirtualProject } from '@aprovan/patchwork-compiler';
|
|
3
2
|
interface CodePreviewProps {
|
|
4
3
|
code: string;
|
|
5
4
|
compiler: Compiler | null;
|
|
@@ -18,5 +17,5 @@ interface CodePreviewProps {
|
|
|
18
17
|
initialProject: VirtualProject;
|
|
19
18
|
}) => void;
|
|
20
19
|
}
|
|
21
|
-
export declare function CodePreview({ code: originalCode, compiler, services, filePath, entrypoint, onOpenEditSession, }: CodePreviewProps): import("react
|
|
20
|
+
export declare function CodePreview({ code: originalCode, compiler, services, filePath, entrypoint, onOpenEditSession, }: CodePreviewProps): import("react").JSX.Element;
|
|
22
21
|
export {};
|
|
@@ -6,5 +6,5 @@ interface MarkdownEditorProps {
|
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
className?: string;
|
|
8
8
|
}
|
|
9
|
-
export declare function MarkdownEditor({ value, onChange, onSubmit, placeholder, disabled, className, }: MarkdownEditorProps): import("react
|
|
9
|
+
export declare function MarkdownEditor({ value, onChange, onSubmit, placeholder, disabled, className, }: MarkdownEditorProps): import("react").JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -4,5 +4,5 @@ interface MarkdownPreviewProps {
|
|
|
4
4
|
editable?: boolean;
|
|
5
5
|
className?: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function MarkdownPreview({ value, onChange, editable, className, }: MarkdownPreviewProps): import("react
|
|
7
|
+
export declare function MarkdownPreview({ value, onChange, editable, className, }: MarkdownPreviewProps): import("react").JSX.Element;
|
|
8
8
|
export {};
|
|
@@ -5,5 +5,5 @@ interface SaveStatusButtonProps {
|
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
tone: 'muted' | 'primary';
|
|
7
7
|
}
|
|
8
|
-
export declare function SaveStatusButton({ status, onClick, disabled, tone, }: SaveStatusButtonProps): import("react
|
|
8
|
+
export declare function SaveStatusButton({ status, onClick, disabled, tone, }: SaveStatusButtonProps): import("react").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -44,5 +44,5 @@ interface ServicesInspectorProps {
|
|
|
44
44
|
onClose?: () => void;
|
|
45
45
|
}>;
|
|
46
46
|
}
|
|
47
|
-
export declare function ServicesInspector({ namespaces, services, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogContentComponent, DialogCloseComponent, }: ServicesInspectorProps): import("react
|
|
47
|
+
export declare function ServicesInspector({ namespaces, services, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogContentComponent, DialogCloseComponent, }: ServicesInspectorProps): import("react").JSX.Element;
|
|
48
48
|
export {};
|
|
@@ -5,4 +5,4 @@ export interface WidgetPreviewProps {
|
|
|
5
5
|
services?: string[];
|
|
6
6
|
enabled?: boolean;
|
|
7
7
|
}
|
|
8
|
-
export declare function WidgetPreview({ code, compiler, services, enabled, }: WidgetPreviewProps): import("react
|
|
8
|
+
export declare function WidgetPreview({ code, compiler, services, enabled, }: WidgetPreviewProps): import("react").JSX.Element;
|
|
@@ -4,4 +4,4 @@ export interface CodeBlockViewProps {
|
|
|
4
4
|
editable?: boolean;
|
|
5
5
|
onChange?: (content: string) => void;
|
|
6
6
|
}
|
|
7
|
-
export declare function CodeBlockView({ content, language, editable, onChange }: CodeBlockViewProps): import("react
|
|
7
|
+
export declare function CodeBlockView({ content, language, editable, onChange }: CodeBlockViewProps): import("react").JSX.Element;
|
|
@@ -6,5 +6,5 @@ interface EditHistoryProps {
|
|
|
6
6
|
pendingPrompt?: string | null;
|
|
7
7
|
className?: string;
|
|
8
8
|
}
|
|
9
|
-
export declare function EditHistory({ entries, streamingNotes, isStreaming, pendingPrompt, className, }: EditHistoryProps): import("react
|
|
9
|
+
export declare function EditHistory({ entries, streamingNotes, isStreaming, pendingPrompt, className, }: EditHistoryProps): import("react").JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -18,4 +18,4 @@ export interface EditModalProps extends UseEditSessionOptions {
|
|
|
18
18
|
previewError?: string | null;
|
|
19
19
|
previewLoading?: boolean;
|
|
20
20
|
}
|
|
21
|
-
export declare function EditModal({ isOpen, onClose, onSave, onSaveProject, renderPreview, renderLoading, renderError, previewError, previewLoading, initialTreePath, initialState, hideFileTree, ...sessionOptions }: EditModalProps): import("react
|
|
21
|
+
export declare function EditModal({ isOpen, onClose, onSave, onSaveProject, renderPreview, renderLoading, renderError, previewError, previewLoading, initialTreePath, initialState, hideFileTree, ...sessionOptions }: EditModalProps): import("react").JSX.Element;
|
|
@@ -24,4 +24,4 @@ export interface FileTreeProps {
|
|
|
24
24
|
pageSize?: number;
|
|
25
25
|
reloadToken?: number;
|
|
26
26
|
}
|
|
27
|
-
export declare function FileTree({ files, activeFile, activePath, title, onSelectFile, onSelectDirectory, onReplaceFile, onOpenInEditor, openInEditorMode, openInEditorIcon, openInEditorTitle, pinnedPaths, onTogglePin, directoryLoader, pageSize, reloadToken, }: FileTreeProps): import("react
|
|
27
|
+
export declare function FileTree({ files, activeFile, activePath, title, onSelectFile, onSelectDirectory, onReplaceFile, onOpenInEditor, openInEditorMode, openInEditorIcon, openInEditorTitle, pinnedPaths, onTogglePin, directoryLoader, pageSize, reloadToken, }: FileTreeProps): import("react").JSX.Element;
|
|
@@ -3,4 +3,4 @@ export interface MediaPreviewProps {
|
|
|
3
3
|
mimeType: string;
|
|
4
4
|
fileName: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function MediaPreview({ content, mimeType, fileName }: MediaPreviewProps): import("react
|
|
6
|
+
export declare function MediaPreview({ content, mimeType, fileName }: MediaPreviewProps): import("react").JSX.Element;
|
|
@@ -6,4 +6,4 @@ export interface SaveConfirmDialogProps {
|
|
|
6
6
|
onDiscard: () => void;
|
|
7
7
|
onCancel: () => void;
|
|
8
8
|
}
|
|
9
|
-
export declare function SaveConfirmDialog({ isOpen, isSaving, error, onSave, onDiscard, onCancel, }: SaveConfirmDialogProps): import("react
|
|
9
|
+
export declare function SaveConfirmDialog({ isOpen, isSaving, error, onSave, onDiscard, onCancel, }: SaveConfirmDialogProps): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VirtualProject } from "@aprovan/patchwork-compiler";
|
|
2
1
|
import type { EditSessionState, EditSessionActions, CompileFn } from "./types";
|
|
2
|
+
import type { VirtualProject } from "@aprovan/patchwork-compiler";
|
|
3
3
|
export interface UseEditSessionOptions {
|
|
4
4
|
originalCode?: string;
|
|
5
5
|
originalProject?: VirtualProject;
|
package/dist/index.js
CHANGED
|
@@ -2,17 +2,17 @@ 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';
|
|
14
15
|
import { createHighlighter } from 'shiki';
|
|
15
|
-
import { Bobbin, serializeChangesToYAML } from '@aprovan/bobbin';
|
|
16
16
|
import { clsx } from 'clsx';
|
|
17
17
|
import { twMerge } from 'tailwind-merge';
|
|
18
18
|
|
|
@@ -180,6 +180,69 @@ var CodeBlockExtension = Node.create({
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
});
|
|
183
|
+
var VFS_BASE_URL = "/vfs";
|
|
184
|
+
var vfsConfigCache = null;
|
|
185
|
+
async function getVFSConfig() {
|
|
186
|
+
if (vfsConfigCache) return vfsConfigCache;
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetch(`${VFS_BASE_URL}/config`);
|
|
189
|
+
if (res.ok) {
|
|
190
|
+
vfsConfigCache = await res.json();
|
|
191
|
+
return vfsConfigCache;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
return { usePaths: false };
|
|
196
|
+
}
|
|
197
|
+
var storeInstance = null;
|
|
198
|
+
function getVFSStore() {
|
|
199
|
+
if (!storeInstance) {
|
|
200
|
+
const provider = new HttpBackend({ baseUrl: VFS_BASE_URL });
|
|
201
|
+
storeInstance = new VFSStore(provider, {
|
|
202
|
+
sync: true,
|
|
203
|
+
conflictStrategy: "local-wins"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return storeInstance;
|
|
207
|
+
}
|
|
208
|
+
async function saveProject(project) {
|
|
209
|
+
const store = getVFSStore();
|
|
210
|
+
await store.saveProject(project);
|
|
211
|
+
}
|
|
212
|
+
async function loadProject(id) {
|
|
213
|
+
const store = getVFSStore();
|
|
214
|
+
return store.loadProject(id);
|
|
215
|
+
}
|
|
216
|
+
async function listProjects() {
|
|
217
|
+
const store = getVFSStore();
|
|
218
|
+
const files = await store.listFiles();
|
|
219
|
+
const projectIds = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const file of files) {
|
|
221
|
+
const id = file.split("/")[0];
|
|
222
|
+
if (id) projectIds.add(id);
|
|
223
|
+
}
|
|
224
|
+
return Array.from(projectIds);
|
|
225
|
+
}
|
|
226
|
+
async function saveFile(path, content) {
|
|
227
|
+
const store = getVFSStore();
|
|
228
|
+
await store.writeFile(path, content);
|
|
229
|
+
}
|
|
230
|
+
async function loadFile(path, encoding) {
|
|
231
|
+
const store = getVFSStore();
|
|
232
|
+
return store.readFile(path, encoding);
|
|
233
|
+
}
|
|
234
|
+
function subscribeToChanges(callback) {
|
|
235
|
+
const store = getVFSStore();
|
|
236
|
+
return store.on("change", callback);
|
|
237
|
+
}
|
|
238
|
+
async function isVFSAvailable() {
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch(VFS_BASE_URL, { method: "HEAD" });
|
|
241
|
+
return res.ok || res.status === 404;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
183
246
|
|
|
184
247
|
// src/components/edit/types.ts
|
|
185
248
|
function getActiveContent(state) {
|
|
@@ -1010,11 +1073,179 @@ function SaveStatusButton({
|
|
|
1010
1073
|
}
|
|
1011
1074
|
);
|
|
1012
1075
|
}
|
|
1076
|
+
var highlighterPromise = null;
|
|
1077
|
+
var COMMON_LANGUAGES = [
|
|
1078
|
+
"typescript",
|
|
1079
|
+
"javascript",
|
|
1080
|
+
"tsx",
|
|
1081
|
+
"jsx",
|
|
1082
|
+
"json",
|
|
1083
|
+
"html",
|
|
1084
|
+
"css",
|
|
1085
|
+
"markdown",
|
|
1086
|
+
"yaml",
|
|
1087
|
+
"python",
|
|
1088
|
+
"bash",
|
|
1089
|
+
"sql"
|
|
1090
|
+
];
|
|
1091
|
+
function getHighlighter() {
|
|
1092
|
+
if (!highlighterPromise) {
|
|
1093
|
+
highlighterPromise = createHighlighter({
|
|
1094
|
+
themes: ["github-light"],
|
|
1095
|
+
langs: COMMON_LANGUAGES
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
return highlighterPromise;
|
|
1099
|
+
}
|
|
1100
|
+
function normalizeLanguage(lang) {
|
|
1101
|
+
if (!lang) return "typescript";
|
|
1102
|
+
const normalized = lang.toLowerCase();
|
|
1103
|
+
const mapping = {
|
|
1104
|
+
ts: "typescript",
|
|
1105
|
+
tsx: "tsx",
|
|
1106
|
+
js: "javascript",
|
|
1107
|
+
jsx: "jsx",
|
|
1108
|
+
json: "json",
|
|
1109
|
+
html: "html",
|
|
1110
|
+
css: "css",
|
|
1111
|
+
md: "markdown",
|
|
1112
|
+
markdown: "markdown",
|
|
1113
|
+
yml: "yaml",
|
|
1114
|
+
yaml: "yaml",
|
|
1115
|
+
py: "python",
|
|
1116
|
+
python: "python",
|
|
1117
|
+
sh: "bash",
|
|
1118
|
+
bash: "bash",
|
|
1119
|
+
sql: "sql",
|
|
1120
|
+
typescript: "typescript",
|
|
1121
|
+
javascript: "javascript"
|
|
1122
|
+
};
|
|
1123
|
+
return mapping[normalized] || "typescript";
|
|
1124
|
+
}
|
|
1125
|
+
function CodeBlockView({ content, language, editable = false, onChange }) {
|
|
1126
|
+
const textareaRef = useRef(null);
|
|
1127
|
+
const containerRef = useRef(null);
|
|
1128
|
+
const [highlighter, setHighlighter] = useState(null);
|
|
1129
|
+
useEffect(() => {
|
|
1130
|
+
let mounted = true;
|
|
1131
|
+
getHighlighter().then((h) => {
|
|
1132
|
+
if (mounted) setHighlighter(h);
|
|
1133
|
+
});
|
|
1134
|
+
return () => {
|
|
1135
|
+
mounted = false;
|
|
1136
|
+
};
|
|
1137
|
+
}, []);
|
|
1138
|
+
useEffect(() => {
|
|
1139
|
+
if (textareaRef.current) {
|
|
1140
|
+
textareaRef.current.style.height = "auto";
|
|
1141
|
+
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
|
1142
|
+
}
|
|
1143
|
+
}, [content]);
|
|
1144
|
+
const handleChange = useCallback(
|
|
1145
|
+
(e) => {
|
|
1146
|
+
onChange?.(e.target.value);
|
|
1147
|
+
},
|
|
1148
|
+
[onChange]
|
|
1149
|
+
);
|
|
1150
|
+
const handleKeyDown = useCallback(
|
|
1151
|
+
(e) => {
|
|
1152
|
+
if (e.key === "Tab") {
|
|
1153
|
+
e.preventDefault();
|
|
1154
|
+
const target = e.target;
|
|
1155
|
+
const start = target.selectionStart;
|
|
1156
|
+
const end = target.selectionEnd;
|
|
1157
|
+
const value = target.value;
|
|
1158
|
+
const newValue = value.substring(0, start) + " " + value.substring(end);
|
|
1159
|
+
onChange?.(newValue);
|
|
1160
|
+
requestAnimationFrame(() => {
|
|
1161
|
+
target.selectionStart = target.selectionEnd = start + 2;
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
[onChange]
|
|
1166
|
+
);
|
|
1167
|
+
const langLabel = language || "text";
|
|
1168
|
+
const shikiLang = useMemo(() => normalizeLanguage(language), [language]);
|
|
1169
|
+
const highlightedHtml = useMemo(() => {
|
|
1170
|
+
if (!highlighter) return null;
|
|
1171
|
+
try {
|
|
1172
|
+
return highlighter.codeToHtml(content, {
|
|
1173
|
+
lang: shikiLang,
|
|
1174
|
+
theme: "github-light"
|
|
1175
|
+
});
|
|
1176
|
+
} catch {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
}, [highlighter, content, shikiLang]);
|
|
1180
|
+
return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col bg-[#ffffff]", children: [
|
|
1181
|
+
/* @__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 }) }),
|
|
1182
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden", children: editable ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-full", children: [
|
|
1183
|
+
/* @__PURE__ */ jsx(
|
|
1184
|
+
"div",
|
|
1185
|
+
{
|
|
1186
|
+
ref: containerRef,
|
|
1187
|
+
className: "absolute top-0 left-0 right-0 pointer-events-none p-4",
|
|
1188
|
+
"aria-hidden": "true",
|
|
1189
|
+
children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1190
|
+
"div",
|
|
1191
|
+
{
|
|
1192
|
+
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",
|
|
1193
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1194
|
+
}
|
|
1195
|
+
) : /* @__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 }) })
|
|
1196
|
+
}
|
|
1197
|
+
),
|
|
1198
|
+
/* @__PURE__ */ jsx(
|
|
1199
|
+
"textarea",
|
|
1200
|
+
{
|
|
1201
|
+
ref: textareaRef,
|
|
1202
|
+
value: content,
|
|
1203
|
+
onChange: handleChange,
|
|
1204
|
+
onKeyDown: handleKeyDown,
|
|
1205
|
+
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",
|
|
1206
|
+
spellCheck: false,
|
|
1207
|
+
style: {
|
|
1208
|
+
tabSize: 2,
|
|
1209
|
+
caretColor: "#24292f",
|
|
1210
|
+
wordBreak: "break-word",
|
|
1211
|
+
overflowWrap: "break-word"
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1216
|
+
"div",
|
|
1217
|
+
{
|
|
1218
|
+
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",
|
|
1219
|
+
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1220
|
+
}
|
|
1221
|
+
) : /* @__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 }) }) }) })
|
|
1222
|
+
] });
|
|
1223
|
+
}
|
|
1013
1224
|
|
|
1014
1225
|
// src/components/edit/fileTypes.ts
|
|
1015
1226
|
var COMPILABLE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
|
|
1016
|
-
var MEDIA_EXTENSIONS = [
|
|
1017
|
-
|
|
1227
|
+
var MEDIA_EXTENSIONS = [
|
|
1228
|
+
".svg",
|
|
1229
|
+
".png",
|
|
1230
|
+
".jpg",
|
|
1231
|
+
".jpeg",
|
|
1232
|
+
".gif",
|
|
1233
|
+
".webp",
|
|
1234
|
+
".mp4",
|
|
1235
|
+
".mov",
|
|
1236
|
+
".webm"
|
|
1237
|
+
];
|
|
1238
|
+
var TEXT_EXTENSIONS = [
|
|
1239
|
+
".json",
|
|
1240
|
+
".yaml",
|
|
1241
|
+
".yml",
|
|
1242
|
+
".md",
|
|
1243
|
+
".txt",
|
|
1244
|
+
".css",
|
|
1245
|
+
".html",
|
|
1246
|
+
".xml",
|
|
1247
|
+
".toml"
|
|
1248
|
+
];
|
|
1018
1249
|
var EXTENSION_TO_LANGUAGE = {
|
|
1019
1250
|
".tsx": "tsx",
|
|
1020
1251
|
".jsx": "jsx",
|
|
@@ -1673,203 +1904,6 @@ function FileTree({
|
|
|
1673
1904
|
) })
|
|
1674
1905
|
] });
|
|
1675
1906
|
}
|
|
1676
|
-
function SaveConfirmDialog({
|
|
1677
|
-
isOpen,
|
|
1678
|
-
isSaving,
|
|
1679
|
-
error,
|
|
1680
|
-
onSave,
|
|
1681
|
-
onDiscard,
|
|
1682
|
-
onCancel
|
|
1683
|
-
}) {
|
|
1684
|
-
if (!isOpen) return null;
|
|
1685
|
-
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: [
|
|
1686
|
-
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
1687
|
-
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: "Unsaved Changes" }),
|
|
1688
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "You have unsaved changes. Would you like to save them before closing?" }),
|
|
1689
|
-
error && /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-3", children: [
|
|
1690
|
-
"Save failed: ",
|
|
1691
|
-
error
|
|
1692
|
-
] })
|
|
1693
|
-
] }),
|
|
1694
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 p-6 pt-0", children: [
|
|
1695
|
-
/* @__PURE__ */ jsx(
|
|
1696
|
-
"button",
|
|
1697
|
-
{
|
|
1698
|
-
onClick: onCancel,
|
|
1699
|
-
disabled: isSaving,
|
|
1700
|
-
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",
|
|
1701
|
-
children: "Cancel"
|
|
1702
|
-
}
|
|
1703
|
-
),
|
|
1704
|
-
/* @__PURE__ */ jsx(
|
|
1705
|
-
"button",
|
|
1706
|
-
{
|
|
1707
|
-
onClick: onDiscard,
|
|
1708
|
-
disabled: isSaving,
|
|
1709
|
-
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",
|
|
1710
|
-
children: "Discard"
|
|
1711
|
-
}
|
|
1712
|
-
),
|
|
1713
|
-
/* @__PURE__ */ jsx(
|
|
1714
|
-
"button",
|
|
1715
|
-
{
|
|
1716
|
-
onClick: onSave,
|
|
1717
|
-
disabled: isSaving,
|
|
1718
|
-
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",
|
|
1719
|
-
children: isSaving ? "Saving..." : "Save"
|
|
1720
|
-
}
|
|
1721
|
-
)
|
|
1722
|
-
] })
|
|
1723
|
-
] }) });
|
|
1724
|
-
}
|
|
1725
|
-
var highlighterPromise = null;
|
|
1726
|
-
var COMMON_LANGUAGES = [
|
|
1727
|
-
"typescript",
|
|
1728
|
-
"javascript",
|
|
1729
|
-
"tsx",
|
|
1730
|
-
"jsx",
|
|
1731
|
-
"json",
|
|
1732
|
-
"html",
|
|
1733
|
-
"css",
|
|
1734
|
-
"markdown",
|
|
1735
|
-
"yaml",
|
|
1736
|
-
"python",
|
|
1737
|
-
"bash",
|
|
1738
|
-
"sql"
|
|
1739
|
-
];
|
|
1740
|
-
function getHighlighter() {
|
|
1741
|
-
if (!highlighterPromise) {
|
|
1742
|
-
highlighterPromise = createHighlighter({
|
|
1743
|
-
themes: ["github-light"],
|
|
1744
|
-
langs: COMMON_LANGUAGES
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
return highlighterPromise;
|
|
1748
|
-
}
|
|
1749
|
-
function normalizeLanguage(lang) {
|
|
1750
|
-
if (!lang) return "typescript";
|
|
1751
|
-
const normalized = lang.toLowerCase();
|
|
1752
|
-
const mapping = {
|
|
1753
|
-
ts: "typescript",
|
|
1754
|
-
tsx: "tsx",
|
|
1755
|
-
js: "javascript",
|
|
1756
|
-
jsx: "jsx",
|
|
1757
|
-
json: "json",
|
|
1758
|
-
html: "html",
|
|
1759
|
-
css: "css",
|
|
1760
|
-
md: "markdown",
|
|
1761
|
-
markdown: "markdown",
|
|
1762
|
-
yml: "yaml",
|
|
1763
|
-
yaml: "yaml",
|
|
1764
|
-
py: "python",
|
|
1765
|
-
python: "python",
|
|
1766
|
-
sh: "bash",
|
|
1767
|
-
bash: "bash",
|
|
1768
|
-
sql: "sql",
|
|
1769
|
-
typescript: "typescript",
|
|
1770
|
-
javascript: "javascript"
|
|
1771
|
-
};
|
|
1772
|
-
return mapping[normalized] || "typescript";
|
|
1773
|
-
}
|
|
1774
|
-
function CodeBlockView({ content, language, editable = false, onChange }) {
|
|
1775
|
-
const textareaRef = useRef(null);
|
|
1776
|
-
const containerRef = useRef(null);
|
|
1777
|
-
const [highlighter, setHighlighter] = useState(null);
|
|
1778
|
-
useEffect(() => {
|
|
1779
|
-
let mounted = true;
|
|
1780
|
-
getHighlighter().then((h) => {
|
|
1781
|
-
if (mounted) setHighlighter(h);
|
|
1782
|
-
});
|
|
1783
|
-
return () => {
|
|
1784
|
-
mounted = false;
|
|
1785
|
-
};
|
|
1786
|
-
}, []);
|
|
1787
|
-
useEffect(() => {
|
|
1788
|
-
if (textareaRef.current) {
|
|
1789
|
-
textareaRef.current.style.height = "auto";
|
|
1790
|
-
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
|
1791
|
-
}
|
|
1792
|
-
}, [content]);
|
|
1793
|
-
const handleChange = useCallback(
|
|
1794
|
-
(e) => {
|
|
1795
|
-
onChange?.(e.target.value);
|
|
1796
|
-
},
|
|
1797
|
-
[onChange]
|
|
1798
|
-
);
|
|
1799
|
-
const handleKeyDown = useCallback(
|
|
1800
|
-
(e) => {
|
|
1801
|
-
if (e.key === "Tab") {
|
|
1802
|
-
e.preventDefault();
|
|
1803
|
-
const target = e.target;
|
|
1804
|
-
const start = target.selectionStart;
|
|
1805
|
-
const end = target.selectionEnd;
|
|
1806
|
-
const value = target.value;
|
|
1807
|
-
const newValue = value.substring(0, start) + " " + value.substring(end);
|
|
1808
|
-
onChange?.(newValue);
|
|
1809
|
-
requestAnimationFrame(() => {
|
|
1810
|
-
target.selectionStart = target.selectionEnd = start + 2;
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1814
|
-
[onChange]
|
|
1815
|
-
);
|
|
1816
|
-
const langLabel = language || "text";
|
|
1817
|
-
const shikiLang = useMemo(() => normalizeLanguage(language), [language]);
|
|
1818
|
-
const highlightedHtml = useMemo(() => {
|
|
1819
|
-
if (!highlighter) return null;
|
|
1820
|
-
try {
|
|
1821
|
-
return highlighter.codeToHtml(content, {
|
|
1822
|
-
lang: shikiLang,
|
|
1823
|
-
theme: "github-light"
|
|
1824
|
-
});
|
|
1825
|
-
} catch {
|
|
1826
|
-
return null;
|
|
1827
|
-
}
|
|
1828
|
-
}, [highlighter, content, shikiLang]);
|
|
1829
|
-
return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col bg-[#ffffff]", children: [
|
|
1830
|
-
/* @__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 }) }),
|
|
1831
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden", children: editable ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-full", children: [
|
|
1832
|
-
/* @__PURE__ */ jsx(
|
|
1833
|
-
"div",
|
|
1834
|
-
{
|
|
1835
|
-
ref: containerRef,
|
|
1836
|
-
className: "absolute top-0 left-0 right-0 pointer-events-none p-4",
|
|
1837
|
-
"aria-hidden": "true",
|
|
1838
|
-
children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1839
|
-
"div",
|
|
1840
|
-
{
|
|
1841
|
-
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",
|
|
1842
|
-
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1843
|
-
}
|
|
1844
|
-
) : /* @__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 }) })
|
|
1845
|
-
}
|
|
1846
|
-
),
|
|
1847
|
-
/* @__PURE__ */ jsx(
|
|
1848
|
-
"textarea",
|
|
1849
|
-
{
|
|
1850
|
-
ref: textareaRef,
|
|
1851
|
-
value: content,
|
|
1852
|
-
onChange: handleChange,
|
|
1853
|
-
onKeyDown: handleKeyDown,
|
|
1854
|
-
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",
|
|
1855
|
-
spellCheck: false,
|
|
1856
|
-
style: {
|
|
1857
|
-
tabSize: 2,
|
|
1858
|
-
caretColor: "#24292f",
|
|
1859
|
-
wordBreak: "break-word",
|
|
1860
|
-
overflowWrap: "break-word"
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
)
|
|
1864
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: highlightedHtml ? /* @__PURE__ */ jsx(
|
|
1865
|
-
"div",
|
|
1866
|
-
{
|
|
1867
|
-
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",
|
|
1868
|
-
dangerouslySetInnerHTML: { __html: highlightedHtml }
|
|
1869
|
-
}
|
|
1870
|
-
) : /* @__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 }) }) }) })
|
|
1871
|
-
] });
|
|
1872
|
-
}
|
|
1873
1907
|
function formatFileSize(bytes) {
|
|
1874
1908
|
if (bytes < 1024) return `${bytes} B`;
|
|
1875
1909
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -1960,6 +1994,55 @@ function MediaPreview({ content, mimeType, fileName }) {
|
|
|
1960
1994
|
] })
|
|
1961
1995
|
] });
|
|
1962
1996
|
}
|
|
1997
|
+
function SaveConfirmDialog({
|
|
1998
|
+
isOpen,
|
|
1999
|
+
isSaving,
|
|
2000
|
+
error,
|
|
2001
|
+
onSave,
|
|
2002
|
+
onDiscard,
|
|
2003
|
+
onCancel
|
|
2004
|
+
}) {
|
|
2005
|
+
if (!isOpen) return null;
|
|
2006
|
+
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: [
|
|
2007
|
+
/* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
2008
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold leading-none tracking-tight", children: "Unsaved Changes" }),
|
|
2009
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "You have unsaved changes. Would you like to save them before closing?" }),
|
|
2010
|
+
error && /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive mt-3", children: [
|
|
2011
|
+
"Save failed: ",
|
|
2012
|
+
error
|
|
2013
|
+
] })
|
|
2014
|
+
] }),
|
|
2015
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 p-6 pt-0", children: [
|
|
2016
|
+
/* @__PURE__ */ jsx(
|
|
2017
|
+
"button",
|
|
2018
|
+
{
|
|
2019
|
+
onClick: onCancel,
|
|
2020
|
+
disabled: isSaving,
|
|
2021
|
+
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",
|
|
2022
|
+
children: "Cancel"
|
|
2023
|
+
}
|
|
2024
|
+
),
|
|
2025
|
+
/* @__PURE__ */ jsx(
|
|
2026
|
+
"button",
|
|
2027
|
+
{
|
|
2028
|
+
onClick: onDiscard,
|
|
2029
|
+
disabled: isSaving,
|
|
2030
|
+
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",
|
|
2031
|
+
children: "Discard"
|
|
2032
|
+
}
|
|
2033
|
+
),
|
|
2034
|
+
/* @__PURE__ */ jsx(
|
|
2035
|
+
"button",
|
|
2036
|
+
{
|
|
2037
|
+
onClick: onSave,
|
|
2038
|
+
disabled: isSaving,
|
|
2039
|
+
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",
|
|
2040
|
+
children: isSaving ? "Saving..." : "Save"
|
|
2041
|
+
}
|
|
2042
|
+
)
|
|
2043
|
+
] })
|
|
2044
|
+
] }) });
|
|
2045
|
+
}
|
|
1963
2046
|
function hashCode(str) {
|
|
1964
2047
|
let hash = 0;
|
|
1965
2048
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -2396,69 +2479,6 @@ function WidgetPreview({
|
|
|
2396
2479
|
/* @__PURE__ */ jsx("div", { ref: containerRef, className: "w-full" })
|
|
2397
2480
|
] });
|
|
2398
2481
|
}
|
|
2399
|
-
var VFS_BASE_URL = "/vfs";
|
|
2400
|
-
var vfsConfigCache = null;
|
|
2401
|
-
async function getVFSConfig() {
|
|
2402
|
-
if (vfsConfigCache) return vfsConfigCache;
|
|
2403
|
-
try {
|
|
2404
|
-
const res = await fetch(`${VFS_BASE_URL}/config`);
|
|
2405
|
-
if (res.ok) {
|
|
2406
|
-
vfsConfigCache = await res.json();
|
|
2407
|
-
return vfsConfigCache;
|
|
2408
|
-
}
|
|
2409
|
-
} catch {
|
|
2410
|
-
}
|
|
2411
|
-
return { usePaths: false };
|
|
2412
|
-
}
|
|
2413
|
-
var storeInstance = null;
|
|
2414
|
-
function getVFSStore() {
|
|
2415
|
-
if (!storeInstance) {
|
|
2416
|
-
const provider = new HttpBackend({ baseUrl: VFS_BASE_URL });
|
|
2417
|
-
storeInstance = new VFSStore(provider, {
|
|
2418
|
-
sync: true,
|
|
2419
|
-
conflictStrategy: "local-wins"
|
|
2420
|
-
});
|
|
2421
|
-
}
|
|
2422
|
-
return storeInstance;
|
|
2423
|
-
}
|
|
2424
|
-
async function saveProject(project) {
|
|
2425
|
-
const store = getVFSStore();
|
|
2426
|
-
await store.saveProject(project);
|
|
2427
|
-
}
|
|
2428
|
-
async function loadProject(id) {
|
|
2429
|
-
const store = getVFSStore();
|
|
2430
|
-
return store.loadProject(id);
|
|
2431
|
-
}
|
|
2432
|
-
async function listProjects() {
|
|
2433
|
-
const store = getVFSStore();
|
|
2434
|
-
const files = await store.listFiles();
|
|
2435
|
-
const projectIds = /* @__PURE__ */ new Set();
|
|
2436
|
-
for (const file of files) {
|
|
2437
|
-
const id = file.split("/")[0];
|
|
2438
|
-
if (id) projectIds.add(id);
|
|
2439
|
-
}
|
|
2440
|
-
return Array.from(projectIds);
|
|
2441
|
-
}
|
|
2442
|
-
async function saveFile(path, content) {
|
|
2443
|
-
const store = getVFSStore();
|
|
2444
|
-
await store.writeFile(path, content);
|
|
2445
|
-
}
|
|
2446
|
-
async function loadFile(path, encoding) {
|
|
2447
|
-
const store = getVFSStore();
|
|
2448
|
-
return store.readFile(path, encoding);
|
|
2449
|
-
}
|
|
2450
|
-
function subscribeToChanges(callback) {
|
|
2451
|
-
const store = getVFSStore();
|
|
2452
|
-
return store.on("change", callback);
|
|
2453
|
-
}
|
|
2454
|
-
async function isVFSAvailable() {
|
|
2455
|
-
try {
|
|
2456
|
-
const res = await fetch(VFS_BASE_URL, { method: "HEAD" });
|
|
2457
|
-
return res.ok || res.status === 404;
|
|
2458
|
-
} catch {
|
|
2459
|
-
return false;
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
2482
|
function createManifest2(services) {
|
|
2463
2483
|
return {
|
|
2464
2484
|
name: "preview",
|
|
@@ -2841,7 +2861,7 @@ function ServicesInspector({
|
|
|
2841
2861
|
),
|
|
2842
2862
|
/* @__PURE__ */ jsxs(DialogComponent, { open, onOpenChange: setOpen, children: [
|
|
2843
2863
|
/* @__PURE__ */ jsxs(DialogHeaderComponent, { children: [
|
|
2844
|
-
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "
|
|
2864
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Services" }),
|
|
2845
2865
|
/* @__PURE__ */ jsx(DialogCloseComponent, { onClose: () => setOpen(false) })
|
|
2846
2866
|
] }),
|
|
2847
2867
|
/* @__PURE__ */ jsx(DialogContentComponent, { className: "space-y-3 max-h-96 overflow-auto", children: namespaces.map((ns) => /* @__PURE__ */ jsxs("details", { open: namespaces.length === 1, children: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aprovan/patchwork-editor",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.c1ac1dc",
|
|
4
4
|
"description": "Components for facilitating widget generation and editing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"shiki": "^3.22.0",
|
|
26
26
|
"tailwind-merge": "^3.4.0",
|
|
27
27
|
"tiptap-markdown": "^0.9.0",
|
|
28
|
-
"@aprovan/
|
|
29
|
-
"@aprovan/
|
|
28
|
+
"@aprovan/bobbin": "0.1.0-dev.c1ac1dc",
|
|
29
|
+
"@aprovan/patchwork-compiler": "0.1.2-dev.c1ac1dc"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
2
|
-
import { Code, Eye, Pencil, RotateCcw, MessageSquare } from 'lucide-react';
|
|
3
|
-
import type { Compiler, Manifest } from '@aprovan/patchwork-compiler';
|
|
4
1
|
import { createSingleFileProject } from '@aprovan/patchwork-compiler';
|
|
2
|
+
import { Code, Eye, Pencil, RotateCcw, MessageSquare } from 'lucide-react';
|
|
3
|
+
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
|
+
import { saveProject, getVFSConfig, loadFile, subscribeToChanges } from '../lib/vfs';
|
|
5
5
|
import { EditModal, type CompileFn, CodeBlockView, MediaPreview, getFileType } from './edit';
|
|
6
|
+
import { MarkdownPreview } from './MarkdownPreview';
|
|
6
7
|
import { SaveStatusButton, type SaveStatus } from './SaveStatusButton';
|
|
7
8
|
import { WidgetPreview } from './WidgetPreview';
|
|
8
|
-
import {
|
|
9
|
-
import { saveProject, getVFSConfig, loadFile, subscribeToChanges } from '../lib/vfs';
|
|
10
|
-
import type { VirtualProject } from '@aprovan/patchwork-compiler';
|
|
9
|
+
import type { Compiler, Manifest , VirtualProject } from '@aprovan/patchwork-compiler';
|
|
11
10
|
|
|
12
11
|
interface CodePreviewProps {
|
|
13
12
|
code: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useEditor, EditorContent } from '@tiptap/react';
|
|
2
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
3
1
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
4
2
|
import Typography from '@tiptap/extension-typography';
|
|
5
|
-
import { Markdown } from 'tiptap-markdown';
|
|
6
3
|
import { TextSelection } from '@tiptap/pm/state';
|
|
4
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
5
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
7
6
|
import { useEffect, useCallback, useRef } from 'react';
|
|
7
|
+
import { Markdown } from 'tiptap-markdown';
|
|
8
8
|
import { CodeBlockExtension } from './CodeBlockExtension';
|
|
9
9
|
|
|
10
10
|
interface MarkdownEditorProps {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import Typography from '@tiptap/extension-typography';
|
|
1
2
|
import { useEditor, EditorContent } from '@tiptap/react';
|
|
2
3
|
import StarterKit from '@tiptap/starter-kit';
|
|
3
|
-
import Typography from '@tiptap/extension-typography';
|
|
4
|
-
import { Markdown } from 'tiptap-markdown';
|
|
5
4
|
import { useEffect, useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { Markdown } from 'tiptap-markdown';
|
|
6
6
|
import { CodeBlockExtension } from './CodeBlockExtension';
|
|
7
7
|
|
|
8
8
|
function parseFrontmatter(content: string): { frontmatter: string; body: string } {
|
|
9
9
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
10
10
|
if (!match) return { frontmatter: '', body: content };
|
|
11
|
-
return { frontmatter: match[1]
|
|
11
|
+
return { frontmatter: match[1]!, body: match[2]! };
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function assembleFrontmatter(frontmatter: string, body: string): string {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
1
|
import { ChevronDown, Server } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
export interface ServiceInfo {
|
|
5
5
|
name: string;
|
|
@@ -133,7 +133,7 @@ export function ServicesInspector({
|
|
|
133
133
|
|
|
134
134
|
<DialogComponent open={open} onOpenChange={setOpen}>
|
|
135
135
|
<DialogHeaderComponent>
|
|
136
|
-
<h2 className="text-lg font-semibold">
|
|
136
|
+
<h2 className="text-lg font-semibold">Services</h2>
|
|
137
137
|
<DialogCloseComponent onClose={() => setOpen(false)} />
|
|
138
138
|
</DialogHeaderComponent>
|
|
139
139
|
<DialogContentComponent className="space-y-3 max-h-96 overflow-auto">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
1
|
import { AlertCircle, Loader2 } from 'lucide-react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import type { Compiler, Manifest, MountedWidget } from '@aprovan/patchwork-compiler';
|
|
4
4
|
|
|
5
5
|
export interface WidgetPreviewProps {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bobbin, serializeChangesToYAML, type Change } from '@aprovan/bobbin';
|
|
2
2
|
import {
|
|
3
3
|
Code,
|
|
4
4
|
Eye,
|
|
@@ -11,18 +11,18 @@ import {
|
|
|
11
11
|
FolderTree,
|
|
12
12
|
FileCode,
|
|
13
13
|
} from 'lucide-react';
|
|
14
|
+
import { useState, useCallback, useMemo, useRef, useEffect, type ReactNode } from 'react';
|
|
14
15
|
import { MarkdownEditor } from '../MarkdownEditor';
|
|
15
16
|
import { MarkdownPreview } from '../MarkdownPreview';
|
|
16
17
|
import { SaveStatusButton, type SaveStatus } from '../SaveStatusButton';
|
|
18
|
+
import { CodeBlockView } from './CodeBlockView';
|
|
17
19
|
import { EditHistory } from './EditHistory';
|
|
18
20
|
import { FileTree } from './FileTree';
|
|
19
|
-
import {
|
|
20
|
-
import { CodeBlockView } from './CodeBlockView';
|
|
21
|
+
import { getFileType, isCompilable, isMarkdownFile, getMimeType } from './fileTypes';
|
|
21
22
|
import { MediaPreview } from './MediaPreview';
|
|
22
|
-
import {
|
|
23
|
+
import { SaveConfirmDialog } from './SaveConfirmDialog';
|
|
23
24
|
import { getActiveContent, getFiles } from './types';
|
|
24
|
-
import {
|
|
25
|
-
import { Bobbin, serializeChangesToYAML, type Change } from '@aprovan/bobbin';
|
|
25
|
+
import { useEditSession, type UseEditSessionOptions } from './useEditSession';
|
|
26
26
|
import type { VirtualProject } from '@aprovan/patchwork-compiler';
|
|
27
27
|
|
|
28
28
|
// Simple hash for React key to force re-render on code changes
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useMemo, useState, useRef, useCallback, useEffect, type ReactNode } from 'react';
|
|
2
1
|
import {
|
|
3
2
|
ChevronRight,
|
|
4
3
|
ChevronDown,
|
|
@@ -12,8 +11,9 @@ import {
|
|
|
12
11
|
PinOff,
|
|
13
12
|
X,
|
|
14
13
|
} from 'lucide-react';
|
|
15
|
-
import type
|
|
14
|
+
import { useMemo, useState, useRef, useCallback, useEffect, type ReactNode } from 'react';
|
|
16
15
|
import { isMediaFile } from './fileTypes';
|
|
16
|
+
import type { VirtualFile } from '@aprovan/patchwork-compiler';
|
|
17
17
|
|
|
18
18
|
interface TreeNode {
|
|
19
19
|
name: string;
|
|
@@ -384,7 +384,6 @@ function LazyTreeNode({
|
|
|
384
384
|
.catch((err) => setLoadError(err instanceof Error ? err.message : 'Failed to load directory'))
|
|
385
385
|
.finally(() => setLoading(false));
|
|
386
386
|
}
|
|
387
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
388
387
|
}, [reloadToken]);
|
|
389
388
|
|
|
390
389
|
const isActive = entry.path === activePath;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { applyDiffs, hasDiffBlocks, parseEditResponse } from
|
|
2
|
-
import type { EditRequest, EditResponse } from
|
|
1
|
+
import { applyDiffs, hasDiffBlocks, parseEditResponse } from "../../lib/diff";
|
|
2
|
+
import type { EditRequest, EditResponse } from "./types";
|
|
3
3
|
|
|
4
4
|
export interface EditApiOptions {
|
|
5
5
|
endpoint?: string;
|
|
@@ -12,16 +12,16 @@ export async function sendEditRequest(
|
|
|
12
12
|
request: EditRequest,
|
|
13
13
|
options: EditApiOptions = {},
|
|
14
14
|
): Promise<EditResponse> {
|
|
15
|
-
const { endpoint =
|
|
15
|
+
const { endpoint = "/api/edit", onProgress, sanitize = true } = options;
|
|
16
16
|
|
|
17
17
|
const response = await fetch(endpoint, {
|
|
18
|
-
method:
|
|
19
|
-
headers: {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
20
|
body: JSON.stringify(request),
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
if (!response.ok) {
|
|
24
|
-
throw new Error(
|
|
24
|
+
throw new Error("Edit request failed");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const text = await streamResponse(response, onProgress);
|
|
@@ -42,7 +42,7 @@ export async function sendEditRequest(
|
|
|
42
42
|
// Provide detailed context about failed diffs for better error feedback
|
|
43
43
|
const failedDetails = result.failed
|
|
44
44
|
.map((f, i) => `[${i + 1}] "${f}"`)
|
|
45
|
-
.join(
|
|
45
|
+
.join("\n");
|
|
46
46
|
throw new Error(
|
|
47
47
|
`Failed to apply ${parsed.diffs.length} diff(s). None of the SEARCH blocks matched the code.\n\nFailed searches:\n${failedDetails}\n\nThis usually means the code has changed or the SEARCH text doesn't match exactly.`,
|
|
48
48
|
);
|
|
@@ -71,7 +71,7 @@ async function streamResponse(
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const decoder = new TextDecoder();
|
|
74
|
-
let fullText =
|
|
74
|
+
let fullText = "";
|
|
75
75
|
const emittedNotes = new Set<string>();
|
|
76
76
|
|
|
77
77
|
let done = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FileCategory =
|
|
1
|
+
export type FileCategory = "compilable" | "text" | "media" | "binary";
|
|
2
2
|
|
|
3
3
|
export interface FileTypeInfo {
|
|
4
4
|
category: FileCategory;
|
|
@@ -6,55 +6,75 @@ export interface FileTypeInfo {
|
|
|
6
6
|
mimeType: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const COMPILABLE_EXTENSIONS = [
|
|
10
|
-
const MEDIA_EXTENSIONS = [
|
|
11
|
-
|
|
9
|
+
const COMPILABLE_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
|
|
10
|
+
const MEDIA_EXTENSIONS = [
|
|
11
|
+
".svg",
|
|
12
|
+
".png",
|
|
13
|
+
".jpg",
|
|
14
|
+
".jpeg",
|
|
15
|
+
".gif",
|
|
16
|
+
".webp",
|
|
17
|
+
".mp4",
|
|
18
|
+
".mov",
|
|
19
|
+
".webm",
|
|
20
|
+
];
|
|
21
|
+
const TEXT_EXTENSIONS = [
|
|
22
|
+
".json",
|
|
23
|
+
".yaml",
|
|
24
|
+
".yml",
|
|
25
|
+
".md",
|
|
26
|
+
".txt",
|
|
27
|
+
".css",
|
|
28
|
+
".html",
|
|
29
|
+
".xml",
|
|
30
|
+
".toml",
|
|
31
|
+
];
|
|
12
32
|
|
|
13
33
|
const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
".tsx": "tsx",
|
|
35
|
+
".jsx": "jsx",
|
|
36
|
+
".ts": "typescript",
|
|
37
|
+
".js": "javascript",
|
|
38
|
+
".json": "json",
|
|
39
|
+
".yaml": "yaml",
|
|
40
|
+
".yml": "yaml",
|
|
41
|
+
".md": "markdown",
|
|
42
|
+
".txt": "text",
|
|
43
|
+
".css": "css",
|
|
44
|
+
".html": "html",
|
|
45
|
+
".xml": "xml",
|
|
46
|
+
".toml": "toml",
|
|
47
|
+
".svg": "xml",
|
|
28
48
|
};
|
|
29
49
|
|
|
30
50
|
const EXTENSION_TO_MIME: Record<string, string> = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
".tsx": "text/typescript-jsx",
|
|
52
|
+
".jsx": "text/javascript-jsx",
|
|
53
|
+
".ts": "text/typescript",
|
|
54
|
+
".js": "text/javascript",
|
|
55
|
+
".json": "application/json",
|
|
56
|
+
".yaml": "text/yaml",
|
|
57
|
+
".yml": "text/yaml",
|
|
58
|
+
".md": "text/markdown",
|
|
59
|
+
".txt": "text/plain",
|
|
60
|
+
".css": "text/css",
|
|
61
|
+
".html": "text/html",
|
|
62
|
+
".xml": "application/xml",
|
|
63
|
+
".toml": "text/toml",
|
|
64
|
+
".svg": "image/svg+xml",
|
|
65
|
+
".png": "image/png",
|
|
66
|
+
".jpg": "image/jpeg",
|
|
67
|
+
".jpeg": "image/jpeg",
|
|
68
|
+
".gif": "image/gif",
|
|
69
|
+
".webp": "image/webp",
|
|
70
|
+
".mp4": "video/mp4",
|
|
71
|
+
".mov": "video/quicktime",
|
|
72
|
+
".webm": "video/webm",
|
|
53
73
|
};
|
|
54
74
|
|
|
55
75
|
function getExtension(path: string): string {
|
|
56
|
-
const lastDot = path.lastIndexOf(
|
|
57
|
-
if (lastDot === -1) return
|
|
76
|
+
const lastDot = path.lastIndexOf(".");
|
|
77
|
+
if (lastDot === -1) return "";
|
|
58
78
|
return path.slice(lastDot).toLowerCase();
|
|
59
79
|
}
|
|
60
80
|
|
|
@@ -63,32 +83,32 @@ export function getFileType(path: string): FileTypeInfo {
|
|
|
63
83
|
|
|
64
84
|
if (COMPILABLE_EXTENSIONS.includes(ext)) {
|
|
65
85
|
return {
|
|
66
|
-
category:
|
|
86
|
+
category: "compilable",
|
|
67
87
|
language: EXTENSION_TO_LANGUAGE[ext] ?? null,
|
|
68
|
-
mimeType: EXTENSION_TO_MIME[ext] ??
|
|
88
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain",
|
|
69
89
|
};
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
if (TEXT_EXTENSIONS.includes(ext)) {
|
|
73
93
|
return {
|
|
74
|
-
category:
|
|
94
|
+
category: "text",
|
|
75
95
|
language: EXTENSION_TO_LANGUAGE[ext] ?? null,
|
|
76
|
-
mimeType: EXTENSION_TO_MIME[ext] ??
|
|
96
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "text/plain",
|
|
77
97
|
};
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
if (MEDIA_EXTENSIONS.includes(ext)) {
|
|
81
101
|
return {
|
|
82
|
-
category:
|
|
83
|
-
language: ext ===
|
|
84
|
-
mimeType: EXTENSION_TO_MIME[ext] ??
|
|
102
|
+
category: "media",
|
|
103
|
+
language: ext === ".svg" ? "xml" : null,
|
|
104
|
+
mimeType: EXTENSION_TO_MIME[ext] ?? "application/octet-stream",
|
|
85
105
|
};
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
return {
|
|
89
|
-
category:
|
|
109
|
+
category: "binary",
|
|
90
110
|
language: null,
|
|
91
|
-
mimeType:
|
|
111
|
+
mimeType: "application/octet-stream",
|
|
92
112
|
};
|
|
93
113
|
}
|
|
94
114
|
|
|
@@ -105,7 +125,7 @@ export function isTextFile(path: string): boolean {
|
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
export function isMarkdownFile(path: string): boolean {
|
|
108
|
-
return getExtension(path) ===
|
|
128
|
+
return getExtension(path) === ".md";
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
export function isPreviewable(path: string): boolean {
|
|
@@ -119,15 +139,15 @@ export function getLanguageFromExt(path: string): string | null {
|
|
|
119
139
|
|
|
120
140
|
export function getMimeType(path: string): string {
|
|
121
141
|
const ext = getExtension(path);
|
|
122
|
-
return EXTENSION_TO_MIME[ext] ??
|
|
142
|
+
return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
export function isImageFile(path: string): boolean {
|
|
126
146
|
const ext = getExtension(path);
|
|
127
|
-
return [
|
|
147
|
+
return [".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext);
|
|
128
148
|
}
|
|
129
149
|
|
|
130
150
|
export function isVideoFile(path: string): boolean {
|
|
131
151
|
const ext = getExtension(path);
|
|
132
|
-
return [
|
|
152
|
+
return [".mp4", ".mov", ".webm"].includes(ext);
|
|
133
153
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
2
|
-
import type { VirtualProject } from "@aprovan/patchwork-compiler";
|
|
3
1
|
import { createSingleFileProject } from "@aprovan/patchwork-compiler";
|
|
2
|
+
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
4
3
|
import { sendEditRequest } from "./api";
|
|
5
4
|
import type {
|
|
6
5
|
EditHistoryEntry,
|
|
@@ -8,6 +7,7 @@ import type {
|
|
|
8
7
|
EditSessionActions,
|
|
9
8
|
CompileFn,
|
|
10
9
|
} from "./types";
|
|
10
|
+
import type { VirtualProject } from "@aprovan/patchwork-compiler";
|
|
11
11
|
|
|
12
12
|
export interface UseEditSessionOptions {
|
|
13
13
|
originalCode?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VirtualFile, VirtualProject } from '@aprovan/patchwork-compiler';
|
|
2
1
|
import { createProjectFromFiles, detectMainFile } from '@aprovan/patchwork-compiler';
|
|
2
|
+
import type { VirtualFile, VirtualProject } from '@aprovan/patchwork-compiler';
|
|
3
3
|
|
|
4
4
|
// Matches fenced code blocks with optional attributes: ```language attr="value"\n...content...```
|
|
5
5
|
// Captures: [1] = language (optional), [2] = attributes (optional), [3] = content
|