mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-07 08:24:27 +00:00
Add git api calls on frontend
This commit is contained in:
@@ -9,6 +9,22 @@ import './App.scss';
|
|||||||
function App() {
|
function App() {
|
||||||
const [themeType, setThemeType] = useState('light');
|
const [themeType, setThemeType] = useState('light');
|
||||||
const [userId, setUserId] = useState(1);
|
const [userId, setUserId] = useState(1);
|
||||||
|
const [settings, setSettings] = useState({ gitEnabled: false });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadUserSettings = async () => {
|
||||||
|
try {
|
||||||
|
const fetchedSettings = await fetchUserSettings(userId);
|
||||||
|
setSettings(fetchedSettings.settings);
|
||||||
|
setThemeType(fetchedSettings.settings.theme);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load user settings:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadUserSettings();
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
content,
|
content,
|
||||||
files,
|
files,
|
||||||
@@ -19,20 +35,8 @@ function App() {
|
|||||||
handleFileSelect,
|
handleFileSelect,
|
||||||
handleContentChange,
|
handleContentChange,
|
||||||
handleSave,
|
handleSave,
|
||||||
} = useFileManagement();
|
pullLatestChanges,
|
||||||
|
} = useFileManagement(settings.gitEnabled);
|
||||||
useEffect(() => {
|
|
||||||
const loadUserSettings = async () => {
|
|
||||||
try {
|
|
||||||
const settings = await fetchUserSettings(userId);
|
|
||||||
setThemeType(settings.settings.theme);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load user settings:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadUserSettings();
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
const setTheme = (newTheme) => {
|
const setTheme = (newTheme) => {
|
||||||
setThemeType(newTheme);
|
setThemeType(newTheme);
|
||||||
@@ -43,7 +47,7 @@ function App() {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Page>
|
<Page>
|
||||||
<Header currentTheme={themeType} onThemeChange={setTheme} />
|
<Header currentTheme={themeType} onThemeChange={setTheme} />
|
||||||
<Page.Content>
|
<Page.Content className='page-content'>
|
||||||
<MainContent
|
<MainContent
|
||||||
content={content}
|
content={content}
|
||||||
files={files}
|
files={files}
|
||||||
@@ -54,6 +58,8 @@ function App() {
|
|||||||
onFileSelect={handleFileSelect}
|
onFileSelect={handleFileSelect}
|
||||||
onContentChange={handleContentChange}
|
onContentChange={handleContentChange}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
|
settings={settings}
|
||||||
|
pullLatestChanges={pullLatestChanges}
|
||||||
/>
|
/>
|
||||||
</Page.Content>
|
</Page.Content>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -6,13 +6,25 @@ $navbar-height: 64px;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 $padding;
|
|
||||||
height: $navbar-height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
overflow-y: auto;
|
overflow: auto;
|
||||||
padding: $padding;
|
padding: $padding;
|
||||||
|
|
||||||
|
.file-tree-container {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding: 0 $padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
@@ -25,7 +37,7 @@ $navbar-height: 64px;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $padding;
|
padding: 0 $padding;
|
||||||
|
|
||||||
.breadcrumbs-container {
|
.breadcrumbs-container {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@@ -50,6 +62,7 @@ $navbar-height: 64px;
|
|||||||
.setting-group {
|
.setting-group {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-item {
|
.setting-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -64,7 +77,8 @@ $navbar-height: 64px;
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container, .markdown-preview {
|
.editor-container,
|
||||||
|
.markdown-preview {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: $padding;
|
padding: $padding;
|
||||||
@@ -72,7 +86,7 @@ $navbar-height: 64px;
|
|||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -82,6 +96,10 @@ $navbar-height: 64px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
padding-top: $padding;
|
||||||
|
}
|
||||||
|
|
||||||
// Geist UI Tree component customization
|
// Geist UI Tree component customization
|
||||||
:global {
|
:global {
|
||||||
.file-tree {
|
.file-tree {
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tree } from '@geist-ui/core';
|
import { Tree, Button, Tooltip, Spacer, ButtonGroup } from '@geist-ui/core';
|
||||||
import { File, Folder } from '@geist-ui/icons';
|
import { File, Folder, GitPullRequest, GitCommit, Plus, Trash } from '@geist-ui/icons';
|
||||||
|
|
||||||
const FileTree = ({
|
const FileTree = ({
|
||||||
files = [],
|
files = [],
|
||||||
onFileSelect = () => {},
|
onFileSelect = () => {},
|
||||||
selectedFile = null
|
selectedFile = null,
|
||||||
|
gitEnabled = false,
|
||||||
|
gitAutoCommit = false,
|
||||||
|
onPull = () => {},
|
||||||
|
onCommitAndPush = () => {},
|
||||||
|
onCreateFile = () => {},
|
||||||
|
onDeleteFile = () => {}
|
||||||
}) => {
|
}) => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
return <div>No files to display</div>;
|
return <div>No files to display</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSelect = (filePath) => {
|
const handleSelect = (filePath) => {
|
||||||
onFileSelect(filePath);
|
onFileSelect(filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderLabel = (node) => {
|
const renderLabel = (node) => {
|
||||||
const path = getFilePath(node);
|
const path = node.extra;
|
||||||
return (
|
return (
|
||||||
<span style={{ color: path === selectedFile ? '#0070f3' : 'inherit' }}>
|
<span style={{ color: path === selectedFile ? '#0070f3' : 'inherit' }}>
|
||||||
{node.name}
|
{node.name}
|
||||||
@@ -27,12 +33,63 @@ const FileTree = ({
|
|||||||
const renderIcon = ({ type }) => type === 'directory' ? <Folder /> : <File />;
|
const renderIcon = ({ type }) => type === 'directory' ? <Folder /> : <File />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tree
|
<div>
|
||||||
value={files}
|
<ButtonGroup className='file-tree-buttons'>
|
||||||
onClick={handleSelect}
|
<Tooltip text="Create new file" type="dark">
|
||||||
renderIcon={renderIcon}
|
<Button
|
||||||
renderLabel={renderLabel}
|
icon={<Plus />}
|
||||||
/>
|
auto
|
||||||
|
scale={2/3}
|
||||||
|
onClick={onCreateFile}
|
||||||
|
px={0.6}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Spacer w={0.5} />
|
||||||
|
<Tooltip text={selectedFile ? "Delete current file" : "No file selected"} type="dark">
|
||||||
|
<Button
|
||||||
|
icon={<Trash />}
|
||||||
|
auto
|
||||||
|
scale={2/3}
|
||||||
|
onClick={onDeleteFile}
|
||||||
|
disabled={!selectedFile}
|
||||||
|
type="error"
|
||||||
|
px={0.6}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Spacer w={0.5} />
|
||||||
|
<Tooltip text={gitEnabled ? "Pull changes from remote" : "Git is not enabled"} type="dark">
|
||||||
|
<Button
|
||||||
|
icon={<GitPullRequest />}
|
||||||
|
auto
|
||||||
|
scale={2/3}
|
||||||
|
onClick={onPull}
|
||||||
|
disabled={!gitEnabled}
|
||||||
|
px={0.6}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Spacer w={0.5} />
|
||||||
|
<Tooltip text={
|
||||||
|
!gitEnabled ? "Git is not enabled" :
|
||||||
|
gitAutoCommit ? "Auto-commit is enabled" :
|
||||||
|
"Commit and push changes"
|
||||||
|
} type="dark">
|
||||||
|
<Button
|
||||||
|
icon={<GitCommit />}
|
||||||
|
auto
|
||||||
|
scale={2/3}
|
||||||
|
onClick={onCommitAndPush}
|
||||||
|
disabled={!gitEnabled || gitAutoCommit}
|
||||||
|
px={0.6}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</ButtonGroup>
|
||||||
|
<Tree
|
||||||
|
value={files}
|
||||||
|
onClick={handleSelect}
|
||||||
|
renderIcon={renderIcon}
|
||||||
|
renderLabel={renderLabel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Grid, Breadcrumbs, Tabs, Dot, useTheme } from '@geist-ui/core';
|
import { Grid, Breadcrumbs, Tabs, Dot, useTheme, useToasts, Modal, Input, Button } from '@geist-ui/core';
|
||||||
import { Code, Eye } from '@geist-ui/icons';
|
import { Code, Eye } from '@geist-ui/icons';
|
||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import FileTree from './FileTree';
|
import FileTree from './FileTree';
|
||||||
import MarkdownPreview from './MarkdownPreview';
|
import MarkdownPreview from './MarkdownPreview';
|
||||||
|
import { commitAndPush, saveFileContent, deleteFile } from '../services/api';
|
||||||
|
|
||||||
const MainContent = ({
|
const MainContent = ({
|
||||||
content,
|
content,
|
||||||
@@ -14,9 +15,71 @@ const MainContent = ({
|
|||||||
onFileSelect,
|
onFileSelect,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
onSave,
|
onSave,
|
||||||
|
settings,
|
||||||
|
pullLatestChanges,
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
const { type: themeType } = useTheme();
|
const { type: themeType } = useTheme();
|
||||||
|
const { setToast } = useToasts();
|
||||||
|
const [newFileModalVisible, setNewFileModalVisible] = useState(false);
|
||||||
|
const [newFileName, setNewFileName] = useState('');
|
||||||
|
|
||||||
|
const handlePull = async () => {
|
||||||
|
try {
|
||||||
|
await pullLatestChanges();
|
||||||
|
setToast({ text: 'Successfully pulled latest changes', type: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
setToast({ text: 'Failed to pull changes: ' + error.message, type: 'error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCommitAndPush = async () => {
|
||||||
|
try {
|
||||||
|
const message = prompt('Enter commit message:');
|
||||||
|
if (message) {
|
||||||
|
await commitAndPush(message);
|
||||||
|
setToast({ text: 'Changes committed and pushed successfully', type: 'success' });
|
||||||
|
await pullLatestChanges(); // Pull changes after successful push
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setToast({ text: 'Failed to commit and push changes: ' + error.message, type: 'error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateFile = () => {
|
||||||
|
setNewFileModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewFileSubmit = async () => {
|
||||||
|
if (newFileName) {
|
||||||
|
try {
|
||||||
|
await saveFileContent(newFileName, '');
|
||||||
|
setToast({ text: 'New file created successfully', type: 'success' });
|
||||||
|
await pullLatestChanges(); // Refresh file list
|
||||||
|
onFileSelect(newFileName); // Select the new file
|
||||||
|
} catch (error) {
|
||||||
|
setToast({ text: 'Failed to create new file: ' + error.message, type: 'error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNewFileModalVisible(false);
|
||||||
|
setNewFileName('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFile = async () => {
|
||||||
|
if (selectedFile) {
|
||||||
|
const confirmDelete = window.confirm(`Are you sure you want to delete "${selectedFile}"?`);
|
||||||
|
if (confirmDelete) {
|
||||||
|
try {
|
||||||
|
await deleteFile(selectedFile);
|
||||||
|
setToast({ text: 'File deleted successfully', type: 'success' });
|
||||||
|
await pullLatestChanges(); // Refresh file list
|
||||||
|
onFileSelect(null); // Deselect the file
|
||||||
|
} catch (error) {
|
||||||
|
setToast({ text: 'Failed to delete file: ' + error.message, type: 'error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderBreadcrumbs = () => {
|
const renderBreadcrumbs = () => {
|
||||||
if (!selectedFile) return null;
|
if (!selectedFile) return null;
|
||||||
@@ -34,41 +97,60 @@ const MainContent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid.Container gap={1} height="calc(100vh - 64px)">
|
<>
|
||||||
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
<Grid.Container gap={1} height="calc(100vh - 64px)">
|
||||||
{error ? (
|
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
||||||
<div className="error">{error}</div>
|
<div className="file-tree-container">
|
||||||
) : (
|
<FileTree
|
||||||
<FileTree
|
files={files}
|
||||||
files={files}
|
onFileSelect={onFileSelect}
|
||||||
onFileSelect={onFileSelect}
|
selectedFile={selectedFile}
|
||||||
selectedFile={selectedFile}
|
gitEnabled={settings.gitEnabled}
|
||||||
/>
|
gitAutoCommit={settings.gitAutoCommit}
|
||||||
)}
|
onPull={handlePull}
|
||||||
</Grid>
|
onCommitAndPush={handleCommitAndPush}
|
||||||
<Grid xs={24} sm={18} md={19} lg={20} height="100%" className="main-content">
|
onCreateFile={handleCreateFile}
|
||||||
<div className="content-header">
|
onDeleteFile={handleDeleteFile}
|
||||||
{renderBreadcrumbs()}
|
|
||||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
|
||||||
<Tabs.Item label={<Code />} value="source" />
|
|
||||||
<Tabs.Item label={<Eye />} value="preview" />
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
<div className="content-body">
|
|
||||||
{activeTab === 'source' ? (
|
|
||||||
<Editor
|
|
||||||
content={content}
|
|
||||||
onChange={onContentChange}
|
|
||||||
onSave={onSave}
|
|
||||||
filePath={selectedFile}
|
|
||||||
themeType={themeType}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
</div>
|
||||||
<MarkdownPreview content={content} />
|
</Grid>
|
||||||
)}
|
<Grid xs={24} sm={18} md={19} lg={20} height="100%" className="main-content">
|
||||||
</div>
|
<div className="content-header">
|
||||||
</Grid>
|
{renderBreadcrumbs()}
|
||||||
</Grid.Container>
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||||
|
<Tabs.Item label={<Code />} value="source" />
|
||||||
|
<Tabs.Item label={<Eye />} value="preview" />
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
{activeTab === 'source' ? (
|
||||||
|
<Editor
|
||||||
|
content={content}
|
||||||
|
onChange={onContentChange}
|
||||||
|
onSave={onSave}
|
||||||
|
filePath={selectedFile}
|
||||||
|
themeType={themeType}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MarkdownPreview content={content} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Grid.Container>
|
||||||
|
<Modal visible={newFileModalVisible} onClose={() => setNewFileModalVisible(false)}>
|
||||||
|
<Modal.Title>Create New File</Modal.Title>
|
||||||
|
<Modal.Content>
|
||||||
|
<Input
|
||||||
|
width="100%"
|
||||||
|
placeholder="Enter file name"
|
||||||
|
value={newFileName}
|
||||||
|
onChange={(e) => setNewFileName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Action passive onClick={() => setNewFileModalVisible(false)}>Cancel</Modal.Action>
|
||||||
|
<Modal.Action onClick={handleNewFileSubmit}>Create</Modal.Action>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useToasts } from '@geist-ui/core';
|
import { useToasts } from '@geist-ui/core';
|
||||||
import { fetchFileList, fetchFileContent, saveFileContent } from '../services/api';
|
import { fetchFileList, fetchFileContent, saveFileContent, pullChanges } from '../services/api';
|
||||||
|
|
||||||
const DEFAULT_FILE = {
|
const DEFAULT_FILE = {
|
||||||
name: 'New File.md',
|
name: 'New File.md',
|
||||||
@@ -8,7 +8,7 @@ const DEFAULT_FILE = {
|
|||||||
content: '# Welcome to NovaMD\n\nStart editing here!'
|
content: '# Welcome to NovaMD\n\nStart editing here!'
|
||||||
};
|
};
|
||||||
|
|
||||||
const useFileManagement = () => {
|
const useFileManagement = (gitEnabled = false) => {
|
||||||
const [content, setContent] = useState(DEFAULT_FILE.content);
|
const [content, setContent] = useState(DEFAULT_FILE.content);
|
||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path);
|
const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path);
|
||||||
@@ -17,23 +17,41 @@ const useFileManagement = () => {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const { setToast } = useToasts();
|
const { setToast } = useToasts();
|
||||||
|
|
||||||
useEffect(() => {
|
const loadFileList = useCallback(async () => {
|
||||||
const loadFileList = async () => {
|
try {
|
||||||
try {
|
const fileList = await fetchFileList();
|
||||||
const fileList = await fetchFileList();
|
if (Array.isArray(fileList)) {
|
||||||
if (Array.isArray(fileList)) {
|
setFiles(fileList);
|
||||||
setFiles(fileList);
|
} else {
|
||||||
} else {
|
throw new Error('File list is not an array');
|
||||||
throw new Error('File list is not an array');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load file list:', error);
|
|
||||||
setError('Failed to load file list. Please try again later.');
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load file list:', error);
|
||||||
|
setError('Failed to load file list. Please try again later.');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const pullLatestChanges = useCallback(async () => {
|
||||||
|
if (gitEnabled) {
|
||||||
|
try {
|
||||||
|
await pullChanges();
|
||||||
|
setToast({ text: 'Latest changes pulled successfully', type: 'success' });
|
||||||
|
await loadFileList(); // Reload file list after pulling changes
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to pull latest changes:', error);
|
||||||
|
setToast({ text: 'Failed to pull latest changes: ' + error.message, type: 'error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [gitEnabled, loadFileList, setToast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initializeFileSystem = async () => {
|
||||||
|
await pullLatestChanges();
|
||||||
|
await loadFileList();
|
||||||
};
|
};
|
||||||
|
|
||||||
loadFileList();
|
initializeFileSystem();
|
||||||
}, []);
|
}, [pullLatestChanges, loadFileList]);
|
||||||
|
|
||||||
const handleFileSelect = async (filePath) => {
|
const handleFileSelect = async (filePath) => {
|
||||||
if (hasUnsavedChanges) {
|
if (hasUnsavedChanges) {
|
||||||
@@ -66,14 +84,13 @@ const useFileManagement = () => {
|
|||||||
setIsNewFile(false);
|
setIsNewFile(false);
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
if (isNewFile) {
|
if (isNewFile) {
|
||||||
const updatedFileList = await fetchFileList();
|
await loadFileList();
|
||||||
setFiles(updatedFileList);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving file:', error);
|
console.error('Error saving file:', error);
|
||||||
setToast({ text: 'Failed to save file. Please try again.', type: 'error' });
|
setToast({ text: 'Failed to save file. Please try again.', type: 'error' });
|
||||||
}
|
}
|
||||||
}, [setToast, isNewFile]);
|
}, [setToast, isNewFile, loadFileList]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
@@ -85,6 +102,7 @@ const useFileManagement = () => {
|
|||||||
handleFileSelect,
|
handleFileSelect,
|
||||||
handleContentChange,
|
handleContentChange,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
pullLatestChanges,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,21 @@ export const saveFileContent = async (filePath, content) => {
|
|||||||
return await response.text();
|
return await response.text();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteFile = async (filePath) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/files/${filePath}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete file');
|
||||||
|
}
|
||||||
|
return await response.text();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting file:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchUserSettings = async (userId) => {
|
export const fetchUserSettings = async (userId) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/settings?userId=${userId}`);
|
const response = await fetch(`${API_BASE_URL}/settings?userId=${userId}`);
|
||||||
@@ -75,4 +90,38 @@ export const saveUserSettings = async (settings) => {
|
|||||||
console.error('Error saving user settings:', error);
|
console.error('Error saving user settings:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pullChanges = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/git/pull`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to pull changes');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error pulling changes:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const commitAndPush = async (message) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/git/commit`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to commit and push changes');
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error committing and pushing changes:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user