feat: implement file editor with syntax highlighting and editing
- Install react-simple-code-editor and prismjs dependencies - Create language detection utility with 50+ file extensions - Create SyntaxHighlighter component with Prism.js highlighting - Create CodeEditor component with syntax-highlighted editing - Create CommitDialog with diff preview and commit message - Create FileEditor component integrating view/edit/commit flow - Replace FileViewer with FileEditor in RepoWorkspace - Add comprehensive CSS styles for editor, highlighter, and dialog - Support keyboard shortcuts: Ctrl+E (toggle edit), Ctrl+S (save) - Quality gates: typecheck ✓ lint ✓ build ✓
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
type GitStatus,
|
||||
} from "../api/git_repositories";
|
||||
import { CommitPanel } from "../components/commit-panel";
|
||||
import { FileEditor } from "../components/file-editor";
|
||||
import { GitToolbar } from "../components/git-toolbar";
|
||||
import { WorkspaceHeader } from "../components/workspace-header";
|
||||
|
||||
@@ -237,7 +238,7 @@ export const RepoWorkspace = () => {
|
||||
|
||||
<main className="workspace-main">
|
||||
{selectedRepoId && (
|
||||
<FileViewer projectId={projectId!} repoId={selectedRepoId} />
|
||||
<FileEditor projectId={projectId!} repoId={selectedRepoId} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
@@ -365,92 +366,3 @@ const FileBrowser = ({
|
||||
);
|
||||
};
|
||||
|
||||
// File Viewer Component
|
||||
const FileViewer = ({
|
||||
projectId,
|
||||
repoId,
|
||||
}: {
|
||||
projectId: string;
|
||||
repoId: string;
|
||||
}) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [content, setContent] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isBinary, setIsBinary] = useState(false);
|
||||
|
||||
const branch = searchParams.get("branch") || "main";
|
||||
const filePath = searchParams.get("file");
|
||||
|
||||
const loadFile = useCallback(async () => {
|
||||
if (!filePath) {
|
||||
setContent(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await apiClient.get(
|
||||
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
||||
{
|
||||
params: {
|
||||
branch,
|
||||
path: filePath,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data;
|
||||
if (data.is_binary) {
|
||||
setIsBinary(true);
|
||||
setContent("Binary file - cannot display");
|
||||
} else {
|
||||
setIsBinary(false);
|
||||
setContent(data.content);
|
||||
}
|
||||
} catch {
|
||||
setError("Failed to load file");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [projectId, repoId, branch, filePath]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadFile();
|
||||
}, [loadFile]);
|
||||
|
||||
if (!filePath) {
|
||||
return (
|
||||
<div className="file-viewer-empty">
|
||||
<p className="muted">Select a file to view its contents</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) return <p className="muted">Loading file...</p>;
|
||||
if (error) return <p className="error-text">{error}</p>;
|
||||
|
||||
return (
|
||||
<div className="file-viewer">
|
||||
<div className="file-viewer-header">
|
||||
<div className="file-breadcrumbs">
|
||||
{filePath.split("/").map((part, i, arr) => (
|
||||
<span key={i}>
|
||||
{part}
|
||||
{i < arr.length - 1 && <span className="breadcrumb-sep">/</span>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="file-content">
|
||||
{isBinary ? (
|
||||
<p className="muted">{content}</p>
|
||||
) : (
|
||||
<pre>
|
||||
<code>{content}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user