diff --git a/frontend/src/App.js b/frontend/src/App.js
index 2b5cd4d..204bf1e 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,48 +1,17 @@
+// App.js
import React from 'react';
-import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core';
+import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
import Header from './components/Header';
import MainContent from './components/MainContent';
-import { useFileManagement } from './hooks/useFileManagement';
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
-import { lookupFileByName } from './services/api';
+import { FileContentProvider } from './contexts/FileContentContext';
+import { FileListProvider } from './contexts/FileListContext';
+import { GitOperationsProvider } from './contexts/GitOperationsContext';
+import { UIStateProvider } from './contexts/UIStateContext';
import './App.scss';
function AppContent() {
const { settings, loading } = useSettings();
- const { setToast } = useToasts();
-
- const {
- content,
- files,
- selectedFile,
- isNewFile,
- hasUnsavedChanges,
- error,
- handleFileSelect,
- handleContentChange,
- handleSave,
- pullLatestChanges,
- } = useFileManagement(settings.gitEnabled);
-
- const handleLinkClick = async (filename) => {
- try {
- const filePaths = await lookupFileByName(filename);
- if (filePaths.length === 1) {
- handleFileSelect(filePaths[0]);
- } else if (filePaths.length > 1) {
- // Handle multiple file options (you may want to show a modal or dropdown)
- console.log('Multiple files found:', filePaths);
- } else {
- setToast({ text: `File "${filename}" not found`, type: 'error' });
- }
- } catch (error) {
- console.error('Error looking up file:', error);
- setToast({
- text: 'Failed to lookup file. Please try again.',
- type: 'error',
- });
- }
- };
if (loading) {
return
Loading...
;
@@ -54,21 +23,7 @@ function AppContent() {
-
+
@@ -78,7 +33,15 @@ function AppContent() {
function App() {
return (
-
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/ContentView.js b/frontend/src/components/ContentView.js
index 29aa61e..952869d 100644
--- a/frontend/src/components/ContentView.js
+++ b/frontend/src/components/ContentView.js
@@ -3,17 +3,17 @@ import Editor from './Editor';
import MarkdownPreview from './MarkdownPreview';
import { getFileUrl } from '../services/api';
import { isImageFile } from '../utils/fileHelpers';
+import { useFileContentContext } from '../contexts/FileContentContext';
const ContentView = ({
activeTab,
- content,
- selectedFile,
- onContentChange,
- onSave,
themeType,
onLinkClick,
lookupFileByName,
}) => {
+ const { content, selectedFile, handleContentChange, handleSave } =
+ useFileContentContext();
+
if (isImageFile(selectedFile)) {
return (
@@ -33,8 +33,8 @@ const ContentView = ({
return activeTab === 'source' ? (
diff --git a/frontend/src/components/FileActions.js b/frontend/src/components/FileActions.js
index 6a54f46..e62b58e 100644
--- a/frontend/src/components/FileActions.js
+++ b/frontend/src/components/FileActions.js
@@ -1,16 +1,25 @@
import React from 'react';
import { Button, Tooltip, ButtonGroup, Spacer } from '@geist-ui/core';
import { Plus, Trash, GitPullRequest, GitCommit } from '@geist-ui/icons';
+import { useFileContentContext } from '../contexts/FileContentContext';
+import { useGitOperationsContext } from '../contexts/GitOperationsContext';
+import { useSettings } from '../contexts/SettingsContext';
+import { useUIStateContext } from '../contexts/UIStateContext';
+
+const FileActions = () => {
+ const { selectedFile } = useFileContentContext();
+ const { pullLatestChanges } = useGitOperationsContext();
+ const { settings } = useSettings();
+ const {
+ setNewFileModalVisible,
+ setDeleteFileModalVisible,
+ setCommitMessageModalVisible,
+ } = useUIStateContext();
+
+ const handleCreateFile = () => setNewFileModalVisible(true);
+ const handleDeleteFile = () => setDeleteFileModalVisible(true);
+ const handleCommitAndPush = () => setCommitMessageModalVisible(true);
-const FileActions = ({
- selectedFile,
- gitEnabled,
- gitAutoCommit,
- onPull,
- onCommitAndPush,
- onCreateFile,
- onDeleteFile,
-}) => {
return (
@@ -18,7 +27,7 @@ const FileActions = ({
icon={}
auto
scale={2 / 3}
- onClick={onCreateFile}
+ onClick={handleCreateFile}
px={0.6}
/>
@@ -31,7 +40,7 @@ const FileActions = ({
icon={}
auto
scale={2 / 3}
- onClick={onDeleteFile}
+ onClick={handleDeleteFile}
disabled={!selectedFile}
type="error"
px={0.6}
@@ -39,24 +48,28 @@ const FileActions = ({
}
auto
scale={2 / 3}
- onClick={onPull}
- disabled={!gitEnabled}
+ onClick={pullLatestChanges}
+ disabled={!settings.gitEnabled}
px={0.6}
/>
}
auto
scale={2 / 3}
- onClick={onCommitAndPush}
- disabled={!gitEnabled || gitAutoCommit}
+ onClick={handleCommitAndPush}
+ disabled={!settings.gitEnabled || settings.gitAutoCommit}
px={0.6}
/>
diff --git a/frontend/src/components/FileTree.js b/frontend/src/components/FileTree.js
index 3eb5bcb..4ff01a4 100644
--- a/frontend/src/components/FileTree.js
+++ b/frontend/src/components/FileTree.js
@@ -1,20 +1,18 @@
import React from 'react';
import { Tree } from '@geist-ui/core';
import { File, Folder, Image } from '@geist-ui/icons';
+import { useFileContentContext } from '../contexts/FileContentContext';
+import { useFileListContext } from '../contexts/FileListContext';
+import { isImageFile } from '../utils/fileHelpers';
+
+const FileTree = () => {
+ const { files } = useFileListContext();
+ const { selectedFile, handleFileSelect } = useFileContentContext();
-const FileTree = ({
- files = [],
- onFileSelect = () => {},
- selectedFile = null,
-}) => {
if (files.length === 0) {
return No files to display
;
}
- const handleSelect = (filePath) => {
- onFileSelect(filePath);
- };
-
const renderLabel = (node) => {
const path = node.extra;
return (
@@ -24,11 +22,6 @@ const FileTree = ({
);
};
- const isImageFile = (fileName) => {
- const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
- return imageExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
- };
-
const renderIcon = ({ type, name }) => {
if (type === 'directory') return ;
return isImageFile(name) ? : ;
@@ -37,7 +30,7 @@ const FileTree = ({
return (
handleFileSelect(filePath)}
renderIcon={renderIcon}
renderLabel={renderLabel}
/>
diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js
index f193bee..e13adb5 100644
--- a/frontend/src/components/MainContent.js
+++ b/frontend/src/components/MainContent.js
@@ -1,4 +1,5 @@
-import React, { useState, useEffect } from 'react';
+// components/MainContent.js
+import React from 'react';
import {
Grid,
Breadcrumbs,
@@ -10,46 +11,36 @@ import {
import { Code, Eye } from '@geist-ui/icons';
import FileTree from './FileTree';
import FileActions from './FileActions';
+import ContentView from './ContentView';
import CreateFileModal from './modals/CreateFileModal';
import DeleteFileModal from './modals/DeleteFileModal';
import CommitMessageModal from './modals/CommitMessageModal';
-import ContentView from './ContentView';
-import { commitAndPush, saveFileContent, deleteFile } from '../services/api';
-import { isImageFile } from '../utils/fileHelpers';
-import { useSettings } from '../contexts/SettingsContext';
+import { useFileListContext } from '../contexts/FileListContext';
+import { useFileContentContext } from '../contexts/FileContentContext';
+import { useGitOperationsContext } from '../contexts/GitOperationsContext';
+import { useUIStateContext } from '../contexts/UIStateContext';
+import { useFileNavigation } from '../hooks/useFileNavigation';
-const MainContent = ({
- content,
- files,
- selectedFile,
- hasUnsavedChanges,
- onFileSelect,
- onContentChange,
- onSave,
- pullLatestChanges,
- onLinkClick,
- lookupFileByName,
-}) => {
- const [activeTab, setActiveTab] = useState('source');
+const MainContent = () => {
+ const { files } = useFileListContext();
+ const { selectedFile, hasUnsavedChanges } = useFileContentContext();
+ const { pullLatestChanges } = useGitOperationsContext();
+ const {
+ activeTab,
+ setActiveTab,
+ newFileModalVisible,
+ setNewFileModalVisible,
+ deleteFileModalVisible,
+ setDeleteFileModalVisible,
+ commitMessageModalVisible,
+ setCommitMessageModalVisible,
+ } = useUIStateContext();
+ const { handleLinkClick } = useFileNavigation();
const { type: themeType } = useTheme();
const { setToast } = useToasts();
- const { settings } = useSettings();
- const [newFileModalVisible, setNewFileModalVisible] = useState(false);
- const [newFileName, setNewFileName] = useState('');
- const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
- const [commitMessageModalVisible, setCommitMessageModalVisible] =
- useState(false);
-
- useEffect(() => {
- if (isImageFile(selectedFile)) {
- setActiveTab('preview');
- }
- }, [selectedFile]);
const handleTabChange = (value) => {
- if (!isImageFile(selectedFile) || value === 'preview') {
- setActiveTab(value);
- }
+ setActiveTab(value);
};
const handlePull = async () => {
@@ -68,64 +59,14 @@ const MainContent = ({
setNewFileModalVisible(true);
};
- const handleNewFileSubmit = async () => {
- if (newFileName) {
- try {
- await saveFileContent(newFileName, '');
- setToast({ text: 'New file created successfully', type: 'success' });
- await pullLatestChanges();
- onFileSelect(newFileName);
- } catch (error) {
- setToast({
- text: 'Failed to create new file: ' + error.message,
- type: 'error',
- });
- }
- }
- setNewFileModalVisible(false);
- setNewFileName('');
- };
-
const handleDeleteFile = () => {
setDeleteFileModalVisible(true);
};
- const confirmDeleteFile = async () => {
- try {
- await deleteFile(selectedFile);
- setToast({ text: 'File deleted successfully', type: 'success' });
- await pullLatestChanges();
- onFileSelect(null);
- } catch (error) {
- setToast({
- text: 'Failed to delete file: ' + error.message,
- type: 'error',
- });
- }
- setDeleteFileModalVisible(false);
- };
-
const handleCommitAndPush = () => {
setCommitMessageModalVisible(true);
};
- const confirmCommitAndPush = async (message) => {
- try {
- await commitAndPush(message);
- setToast({
- text: 'Changes committed and pushed successfully',
- type: 'success',
- });
- await pullLatestChanges();
- } catch (error) {
- setToast({
- text: 'Failed to commit and push changes: ' + error.message,
- type: 'error',
- });
- }
- setCommitMessageModalVisible(false);
- };
-
const renderBreadcrumbs = () => {
if (!selectedFile) return null;
const pathParts = selectedFile.split('/');
@@ -149,19 +90,12 @@ const MainContent = ({
-
+
{renderBreadcrumbs()}
- }
- value="source"
- disabled={isImageFile(selectedFile)}
- />
+ } value="source" />
} value="preview" />
- setNewFileModalVisible(false)}
- onSubmit={handleNewFileSubmit}
- fileName={newFileName}
- setFileName={setNewFileName}
- />
- setDeleteFileModalVisible(false)}
- onConfirm={confirmDeleteFile}
- fileName={selectedFile}
- />
- setCommitMessageModalVisible(false)}
- onSubmit={confirmCommitAndPush}
- />
+
+
+
>
);
};
diff --git a/frontend/src/components/modals/CommitMessageModal.js b/frontend/src/components/modals/CommitMessageModal.js
index db0ecf5..fb66b33 100644
--- a/frontend/src/components/modals/CommitMessageModal.js
+++ b/frontend/src/components/modals/CommitMessageModal.js
@@ -1,16 +1,27 @@
import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core';
+import { useGitOperationsContext } from '../../contexts/GitOperationsContext';
+import { useUIStateContext } from '../../contexts/UIStateContext';
-const CommitMessageModal = ({ visible, onClose, onSubmit }) => {
+const CommitMessageModal = () => {
const [message, setMessage] = useState('');
+ const { handleCommitAndPush } = useGitOperationsContext();
+ const { commitMessageModalVisible, setCommitMessageModalVisible } =
+ useUIStateContext();
- const handleSubmit = () => {
- onSubmit(message);
- setMessage('');
+ const handleSubmit = async () => {
+ if (message) {
+ await handleCommitAndPush(message);
+ setMessage('');
+ setCommitMessageModalVisible(false);
+ }
};
return (
-
+ setCommitMessageModalVisible(false)}
+ >
Enter Commit Message
{
onChange={(e) => setMessage(e.target.value)}
/>
-
+ setCommitMessageModalVisible(false)}>
Cancel
Commit
diff --git a/frontend/src/components/modals/CreateFileModal.js b/frontend/src/components/modals/CreateFileModal.js
index 09dbaa1..06b6173 100644
--- a/frontend/src/components/modals/CreateFileModal.js
+++ b/frontend/src/components/modals/CreateFileModal.js
@@ -1,15 +1,26 @@
-import React from 'react';
+import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core';
+import { useFileContentContext } from '../../contexts/FileContentContext';
+import { useUIStateContext } from '../../contexts/UIStateContext';
+
+const CreateFileModal = () => {
+ const [fileName, setFileName] = useState('');
+ const { newFileModalVisible, setNewFileModalVisible } = useUIStateContext();
+ const { handleCreateNewFile } = useFileContentContext();
+
+ const handleSubmit = async () => {
+ if (fileName) {
+ await handleCreateNewFile(fileName);
+ setFileName('');
+ setNewFileModalVisible(false);
+ }
+ };
-const CreateFileModal = ({
- visible,
- onClose,
- onSubmit,
- fileName,
- setFileName,
-}) => {
return (
-
+ setNewFileModalVisible(false)}
+ >
Create New File
setFileName(e.target.value)}
/>
-
+ setNewFileModalVisible(false)}>
Cancel
- Create
+ Create
);
};
diff --git a/frontend/src/components/modals/DeleteFileModal.js b/frontend/src/components/modals/DeleteFileModal.js
index 7221669..600e9be 100644
--- a/frontend/src/components/modals/DeleteFileModal.js
+++ b/frontend/src/components/modals/DeleteFileModal.js
@@ -1,17 +1,31 @@
import React from 'react';
import { Modal, Text } from '@geist-ui/core';
+import { useFileContentContext } from '../../contexts/FileContentContext';
+import { useUIStateContext } from '../../contexts/UIStateContext';
+
+const DeleteFileModal = () => {
+ const { selectedFile, handleDeleteFile } = useFileContentContext();
+ const { deleteFileModalVisible, setDeleteFileModalVisible } =
+ useUIStateContext();
+
+ const handleConfirm = async () => {
+ await handleDeleteFile();
+ setDeleteFileModalVisible(false);
+ };
-const DeleteFileModal = ({ visible, onClose, onConfirm, fileName }) => {
return (
-
+ setDeleteFileModalVisible(false)}
+ >
Delete File
- Are you sure you want to delete "{fileName}"?
+ Are you sure you want to delete "{selectedFile}"?
-
+ setDeleteFileModalVisible(false)}>
Cancel
- Delete
+ Delete
);
};
diff --git a/frontend/src/contexts/FileContentContext.js b/frontend/src/contexts/FileContentContext.js
new file mode 100644
index 0000000..b97a7f6
--- /dev/null
+++ b/frontend/src/contexts/FileContentContext.js
@@ -0,0 +1,24 @@
+import React, { createContext, useContext } from 'react';
+import { useFileContent } from '../hooks/useFileContent';
+
+const FileContentContext = createContext();
+
+export const FileContentProvider = ({ children }) => {
+ const fileContentHook = useFileContent();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFileContentContext = () => {
+ const context = useContext(FileContentContext);
+ if (!context) {
+ throw new Error(
+ 'useFileContentContext must be used within a FileContentProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/FileListContext.js b/frontend/src/contexts/FileListContext.js
new file mode 100644
index 0000000..60c37f7
--- /dev/null
+++ b/frontend/src/contexts/FileListContext.js
@@ -0,0 +1,24 @@
+import React, { createContext, useContext } from 'react';
+import { useFileList } from '../hooks/useFileList';
+
+const FileListContext = createContext();
+
+export const FileListProvider = ({ children }) => {
+ const fileListHook = useFileList();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFileListContext = () => {
+ const context = useContext(FileListContext);
+ if (!context) {
+ throw new Error(
+ 'useFileListContext must be used within a FileListProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/GitOperationsContext.js b/frontend/src/contexts/GitOperationsContext.js
new file mode 100644
index 0000000..41589c3
--- /dev/null
+++ b/frontend/src/contexts/GitOperationsContext.js
@@ -0,0 +1,24 @@
+import React, { createContext, useContext } from 'react';
+import { useGitOperations } from '../hooks/useGitOperations';
+
+const GitOperationsContext = createContext();
+
+export const GitOperationsProvider = ({ children }) => {
+ const gitOperationsHook = useGitOperations();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useGitOperationsContext = () => {
+ const context = useContext(GitOperationsContext);
+ if (!context) {
+ throw new Error(
+ 'useGitOperationsContext must be used within a GitOperationsProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/UIStateContext.js b/frontend/src/contexts/UIStateContext.js
new file mode 100644
index 0000000..30c05da
--- /dev/null
+++ b/frontend/src/contexts/UIStateContext.js
@@ -0,0 +1,34 @@
+import React, { createContext, useContext, useState } from 'react';
+
+const UIStateContext = createContext();
+
+export const UIStateProvider = ({ children }) => {
+ const [activeTab, setActiveTab] = useState('source');
+ const [newFileModalVisible, setNewFileModalVisible] = useState(false);
+ const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
+ const [commitMessageModalVisible, setCommitMessageModalVisible] =
+ useState(false);
+
+ const value = {
+ activeTab,
+ setActiveTab,
+ newFileModalVisible,
+ setNewFileModalVisible,
+ deleteFileModalVisible,
+ setDeleteFileModalVisible,
+ commitMessageModalVisible,
+ setCommitMessageModalVisible,
+ };
+
+ return (
+ {children}
+ );
+};
+
+export const useUIStateContext = () => {
+ const context = useContext(UIStateContext);
+ if (!context) {
+ throw new Error('useUIStateContext must be used within a UIStateProvider');
+ }
+ return context;
+};
diff --git a/frontend/src/hooks/useFileContent.js b/frontend/src/hooks/useFileContent.js
index d538595..8d49de0 100644
--- a/frontend/src/hooks/useFileContent.js
+++ b/frontend/src/hooks/useFileContent.js
@@ -1,6 +1,8 @@
import { useState, useCallback } from 'react';
-import { fetchFileContent, saveFileContent } from '../services/api';
+import { fetchFileContent, saveFileContent, deleteFile } from '../services/api';
import { isImageFile } from '../utils/fileHelpers';
+import { useToasts } from '@geist-ui/core';
+import { useFileListContext } from '../contexts/FileListContext';
const DEFAULT_FILE = {
name: 'New File.md',
@@ -14,16 +16,12 @@ export const useFileContent = () => {
const [isNewFile, setIsNewFile] = useState(true);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [error, setError] = useState(null);
+ const { setToast } = useToasts();
+ const { loadFileList } = useFileListContext();
const handleFileSelect = useCallback(
async (filePath) => {
- if (hasUnsavedChanges) {
- const confirmSwitch = window.confirm(
- 'You have unsaved changes. Are you sure you want to switch files?'
- );
- if (!confirmSwitch) return;
- }
-
+ console.log('handleFileSelect', filePath);
try {
if (filePath === DEFAULT_FILE.path) {
setContent(DEFAULT_FILE.content);
@@ -67,13 +65,38 @@ export const useFileContent = () => {
}
}, []);
- const createNewFile = useCallback(() => {
- setContent(DEFAULT_FILE.content);
- setSelectedFile(DEFAULT_FILE.path);
- setIsNewFile(true);
- setHasUnsavedChanges(false);
- setError(null);
- }, []);
+ const handleCreateNewFile = useCallback(
+ async (fileName, initialContent = '') => {
+ try {
+ await saveFileContent(fileName, initialContent);
+ setToast({ text: 'New file created successfully', type: 'success' });
+ await loadFileList(); // Refresh the file list
+ handleFileSelect(fileName);
+ } catch (error) {
+ setToast({
+ text: 'Failed to create new file: ' + error.message,
+ type: 'error',
+ });
+ }
+ },
+ [setToast, loadFileList, handleFileSelect]
+ );
+
+ const handleDeleteFile = useCallback(async () => {
+ if (!selectedFile) return;
+ try {
+ await deleteFile(selectedFile);
+ setToast({ text: 'File deleted successfully', type: 'success' });
+ await loadFileList(); // Refresh the file list
+ setSelectedFile(null);
+ setContent('');
+ } catch (error) {
+ setToast({
+ text: 'Failed to delete file: ' + error.message,
+ type: 'error',
+ });
+ }
+ }, [selectedFile, setToast, loadFileList]);
return {
content,
@@ -84,7 +107,8 @@ export const useFileContent = () => {
handleFileSelect,
handleContentChange,
handleSave,
- createNewFile,
+ handleCreateNewFile,
+ handleDeleteFile,
DEFAULT_FILE,
};
};
diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js
new file mode 100644
index 0000000..eb8c46b
--- /dev/null
+++ b/frontend/src/hooks/useFileNavigation.js
@@ -0,0 +1,39 @@
+// hooks/useFileNavigation.js
+import { useCallback } from 'react';
+import { useToasts } from '@geist-ui/core';
+import { lookupFileByName } from '../services/api';
+import { useFileContentContext } from '../contexts/FileContentContext';
+
+export const useFileNavigation = () => {
+ const { setToast } = useToasts();
+ const { handleFileSelect } = useFileContentContext();
+
+ const handleLinkClick = useCallback(
+ async (filename) => {
+ try {
+ const filePaths = await lookupFileByName(filename);
+ if (filePaths.length === 1) {
+ handleFileSelect(filePaths[0]);
+ } else if (filePaths.length > 1) {
+ // Handle multiple file options (you may want to show a modal or dropdown)
+ console.log('Multiple files found:', filePaths);
+ setToast({
+ text: 'Multiple files found with the same name. Please specify the full path.',
+ type: 'warning',
+ });
+ } else {
+ setToast({ text: `File "${filename}" not found`, type: 'error' });
+ }
+ } catch (error) {
+ console.error('Error looking up file:', error);
+ setToast({
+ text: 'Failed to lookup file. Please try again.',
+ type: 'error',
+ });
+ }
+ },
+ [handleFileSelect, setToast]
+ );
+
+ return { handleLinkClick };
+};