diff --git a/frontend/src/App.js b/frontend/src/App.js
index 204bf1e..6d8e561 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -4,10 +4,13 @@ import { GeistProvider, CssBaseline, Page } from '@geist-ui/core';
import Header from './components/Header';
import MainContent from './components/MainContent';
import { SettingsProvider, useSettings } from './contexts/SettingsContext';
-import { FileContentProvider } from './contexts/FileContentContext';
-import { FileListProvider } from './contexts/FileListContext';
+import { ModalProvider } from './contexts/ModalContext';
+import { TabProvider } from './contexts/TabContext';
import { GitOperationsProvider } from './contexts/GitOperationsContext';
-import { UIStateProvider } from './contexts/UIStateContext';
+import { FileListProvider } from './contexts/FileListContext';
+import { FileSelectionProvider } from './contexts/FileSelectionContext';
+import { FileOperationsProvider } from './contexts/FileOperationsContext';
+import { EditorContentProvider } from './contexts/EditorContentContext';
import './App.scss';
function AppContent() {
@@ -33,15 +36,21 @@ function AppContent() {
function App() {
return (
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
);
}
diff --git a/frontend/src/components/ContentView.js b/frontend/src/components/ContentView.js
index a6c04ce..cb88264 100644
--- a/frontend/src/components/ContentView.js
+++ b/frontend/src/components/ContentView.js
@@ -4,12 +4,12 @@ import MarkdownPreview from './MarkdownPreview';
import { Text } from '@geist-ui/core';
import { getFileUrl } from '../services/api';
import { isImageFile } from '../utils/fileHelpers';
-import { useFileContentContext } from '../contexts/FileContentContext';
-import { useUIStateContext } from '../contexts/UIStateContext';
+import { useFileSelection } from '../contexts/FileSelectionContext';
+import { useTabContext } from '../contexts/TabContext';
const ContentView = () => {
- const { selectedFile } = useFileContentContext();
- const { activeTab } = useUIStateContext();
+ const { selectedFile } = useFileSelection();
+ const { activeTab } = useTabContext();
if (!selectedFile) {
return (
diff --git a/frontend/src/components/Editor.js b/frontend/src/components/Editor.js
index 77b316c..d9554ca 100644
--- a/frontend/src/components/Editor.js
+++ b/frontend/src/components/Editor.js
@@ -5,12 +5,13 @@ import { EditorView, keymap } from '@codemirror/view';
import { markdown } from '@codemirror/lang-markdown';
import { defaultKeymap } from '@codemirror/commands';
import { oneDark } from '@codemirror/theme-one-dark';
-import { useFileContentContext } from '../contexts/FileContentContext';
import { useSettings } from '../contexts/SettingsContext';
+import { useFileSelection } from '../contexts/FileSelectionContext';
+import { useEditorContent } from '../contexts/EditorContentContext';
const Editor = () => {
- const { content, selectedFile, handleContentChange, handleSave } =
- useFileContentContext();
+ const { content, handleContentChange, handleSave } = useEditorContent();
+ const { selectedFile } = useFileSelection();
const { settings } = useSettings();
const editorRef = useRef();
const viewRef = useRef();
diff --git a/frontend/src/components/FileActions.js b/frontend/src/components/FileActions.js
index e62b58e..24e106d 100644
--- a/frontend/src/components/FileActions.js
+++ b/frontend/src/components/FileActions.js
@@ -1,20 +1,20 @@
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';
+import { useFileSelection } from '../contexts/FileSelectionContext';
+import { useModalContext } from '../contexts/ModalContext';
const FileActions = () => {
- const { selectedFile } = useFileContentContext();
+ const { selectedFile } = useFileSelection();
const { pullLatestChanges } = useGitOperationsContext();
const { settings } = useSettings();
const {
setNewFileModalVisible,
setDeleteFileModalVisible,
setCommitMessageModalVisible,
- } = useUIStateContext();
+ } = useModalContext();
const handleCreateFile = () => setNewFileModalVisible(true);
const handleDeleteFile = () => setDeleteFileModalVisible(true);
diff --git a/frontend/src/components/FileTree.js b/frontend/src/components/FileTree.js
index 4ff01a4..844b560 100644
--- a/frontend/src/components/FileTree.js
+++ b/frontend/src/components/FileTree.js
@@ -1,13 +1,13 @@
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';
+import { useFileSelection } from '../contexts/FileSelectionContext';
const FileTree = () => {
const { files } = useFileListContext();
- const { selectedFile, handleFileSelect } = useFileContentContext();
+ const { selectedFile, handleFileSelect } = useFileSelection();
if (files.length === 0) {
return
No files to display
;
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 2b6b69d..1406805 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -2,10 +2,10 @@ import React from 'react';
import { Page, Text, User, Button, Spacer } from '@geist-ui/core';
import { Settings as SettingsIcon } from '@geist-ui/icons';
import Settings from './Settings';
-import { useUIStateContext } from '../contexts/UIStateContext';
+import { useModalContext } from '../contexts/ModalContext';
const Header = () => {
- const { setSettingsModalVisible } = useUIStateContext();
+ const { setSettingsModalVisible } = useModalContext();
const openSettings = () => setSettingsModalVisible(true);
diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js
index 4f51212..e837b3f 100644
--- a/frontend/src/components/MainContent.js
+++ b/frontend/src/components/MainContent.js
@@ -7,12 +7,14 @@ import ContentView from './ContentView';
import CreateFileModal from './modals/CreateFileModal';
import DeleteFileModal from './modals/DeleteFileModal';
import CommitMessageModal from './modals/CommitMessageModal';
-import { useFileContentContext } from '../contexts/FileContentContext';
-import { useUIStateContext } from '../contexts/UIStateContext';
+import { useTabContext } from '../contexts/TabContext';
+import { useEditorContent } from '../contexts/EditorContentContext';
+import { useFileSelection } from '../contexts/FileSelectionContext';
const MainContent = () => {
- const { selectedFile, hasUnsavedChanges } = useFileContentContext();
- const { activeTab, setActiveTab } = useUIStateContext();
+ const { hasUnsavedChanges } = useEditorContent();
+ const { selectedFile } = useFileSelection();
+ const { activeTab, setActiveTab } = useTabContext();
const handleTabChange = (value) => {
setActiveTab(value);
diff --git a/frontend/src/components/MarkdownPreview.js b/frontend/src/components/MarkdownPreview.js
index 43c89d5..2834476 100644
--- a/frontend/src/components/MarkdownPreview.js
+++ b/frontend/src/components/MarkdownPreview.js
@@ -5,12 +5,12 @@ import rehypeKatex from 'rehype-katex';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import 'katex/dist/katex.min.css';
-import { useFileContentContext } from '../contexts/FileContentContext';
import { useFileNavigation } from '../hooks/useFileNavigation';
import { lookupFileByName } from '../services/api';
+import { useEditorContent } from '../contexts/EditorContentContext';
const MarkdownPreview = () => {
- const { content } = useFileContentContext();
+ const { content } = useEditorContent();
const { handleLinkClick } = useFileNavigation();
const [processedContent, setProcessedContent] = useState(content);
const baseUrl = window.API_BASE_URL;
diff --git a/frontend/src/components/Settings.js b/frontend/src/components/Settings.js
index b79b3f1..811cc69 100644
--- a/frontend/src/components/Settings.js
+++ b/frontend/src/components/Settings.js
@@ -1,10 +1,10 @@
import React, { useReducer, useEffect, useCallback, useRef } from 'react';
-import { Modal, Spacer, useTheme, Dot, useToasts } from '@geist-ui/core';
+import { Modal, Spacer, Dot, useToasts } from '@geist-ui/core';
import { useSettings } from '../contexts/SettingsContext';
-import { useUIStateContext } from '../contexts/UIStateContext';
import AppearanceSettings from './settings/AppearanceSettings';
import EditorSettings from './settings/EditorSettings';
import GitSettings from './settings/GitSettings';
+import { useModalContext } from '../contexts/ModalContext';
const initialState = {
localSettings: {},
@@ -50,7 +50,7 @@ function settingsReducer(state, action) {
const Settings = () => {
const { settings, updateSettings, updateTheme } = useSettings();
- const { settingsModalVisible, setSettingsModalVisible } = useUIStateContext();
+ const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
const { setToast } = useToasts();
const [state, dispatch] = useReducer(settingsReducer, initialState);
const isInitialMount = useRef(true);
diff --git a/frontend/src/components/modals/CommitMessageModal.js b/frontend/src/components/modals/CommitMessageModal.js
index fb66b33..faf3cb8 100644
--- a/frontend/src/components/modals/CommitMessageModal.js
+++ b/frontend/src/components/modals/CommitMessageModal.js
@@ -1,13 +1,13 @@
import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core';
import { useGitOperationsContext } from '../../contexts/GitOperationsContext';
-import { useUIStateContext } from '../../contexts/UIStateContext';
+import { useModalContext } from '../../contexts/ModalContext';
const CommitMessageModal = () => {
const [message, setMessage] = useState('');
const { handleCommitAndPush } = useGitOperationsContext();
const { commitMessageModalVisible, setCommitMessageModalVisible } =
- useUIStateContext();
+ useModalContext();
const handleSubmit = async () => {
if (message) {
diff --git a/frontend/src/components/modals/CreateFileModal.js b/frontend/src/components/modals/CreateFileModal.js
index 06b6173..ea4b57a 100644
--- a/frontend/src/components/modals/CreateFileModal.js
+++ b/frontend/src/components/modals/CreateFileModal.js
@@ -1,12 +1,12 @@
import React, { useState } from 'react';
import { Modal, Input } from '@geist-ui/core';
-import { useFileContentContext } from '../../contexts/FileContentContext';
-import { useUIStateContext } from '../../contexts/UIStateContext';
+import { useFileOperations } from '../../contexts/FileOperationsContext';
+import { useModalContext } from '../../contexts/ModalContext';
const CreateFileModal = () => {
const [fileName, setFileName] = useState('');
- const { newFileModalVisible, setNewFileModalVisible } = useUIStateContext();
- const { handleCreateNewFile } = useFileContentContext();
+ const { newFileModalVisible, setNewFileModalVisible } = useModalContext();
+ const { handleCreateNewFile } = useFileOperations();
const handleSubmit = async () => {
if (fileName) {
diff --git a/frontend/src/components/modals/DeleteFileModal.js b/frontend/src/components/modals/DeleteFileModal.js
index 600e9be..9abf3dd 100644
--- a/frontend/src/components/modals/DeleteFileModal.js
+++ b/frontend/src/components/modals/DeleteFileModal.js
@@ -1,12 +1,12 @@
import React from 'react';
import { Modal, Text } from '@geist-ui/core';
-import { useFileContentContext } from '../../contexts/FileContentContext';
-import { useUIStateContext } from '../../contexts/UIStateContext';
+import { useModalContext } from '../../contexts/ModalContext';
+import { useFileSelection } from '../../contexts/FileSelectionContext';
const DeleteFileModal = () => {
- const { selectedFile, handleDeleteFile } = useFileContentContext();
+ const { selectedFile, handleDeleteFile } = useFileSelection();
const { deleteFileModalVisible, setDeleteFileModalVisible } =
- useUIStateContext();
+ useModalContext();
const handleConfirm = async () => {
await handleDeleteFile();
diff --git a/frontend/src/contexts/EditorContentContext.js b/frontend/src/contexts/EditorContentContext.js
new file mode 100644
index 0000000..4bfdd00
--- /dev/null
+++ b/frontend/src/contexts/EditorContentContext.js
@@ -0,0 +1,33 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import { useFileContent } from '../hooks/useFileContent';
+
+const EditorContentContext = createContext();
+
+export const EditorContentProvider = ({ children }) => {
+ const { content, handleContentChange, handleSave } = useFileContent();
+
+ const value = useMemo(
+ () => ({
+ content,
+ handleContentChange,
+ handleSave,
+ }),
+ [content, handleContentChange, handleSave]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useEditorContent = () => {
+ const context = useContext(EditorContentContext);
+ if (context === undefined) {
+ throw new Error(
+ 'useEditorContent must be used within an EditorContentProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/FileContentContext.js b/frontend/src/contexts/FileContentContext.js
deleted file mode 100644
index b97a7f6..0000000
--- a/frontend/src/contexts/FileContentContext.js
+++ /dev/null
@@ -1,24 +0,0 @@
-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
index 60c37f7..9192b01 100644
--- a/frontend/src/contexts/FileListContext.js
+++ b/frontend/src/contexts/FileListContext.js
@@ -1,13 +1,15 @@
-import React, { createContext, useContext } from 'react';
+import React, { createContext, useContext, useMemo } from 'react';
import { useFileList } from '../hooks/useFileList';
const FileListContext = createContext();
export const FileListProvider = ({ children }) => {
- const fileListHook = useFileList();
+ const { files, loadFileList } = useFileList();
+
+ const value = useMemo(() => ({ files, loadFileList }), [files, loadFileList]);
return (
-
+
{children}
);
@@ -15,7 +17,7 @@ export const FileListProvider = ({ children }) => {
export const useFileListContext = () => {
const context = useContext(FileListContext);
- if (!context) {
+ if (context === undefined) {
throw new Error(
'useFileListContext must be used within a FileListProvider'
);
diff --git a/frontend/src/contexts/FileOperationsContext.js b/frontend/src/contexts/FileOperationsContext.js
new file mode 100644
index 0000000..41b646b
--- /dev/null
+++ b/frontend/src/contexts/FileOperationsContext.js
@@ -0,0 +1,32 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import { useFileContent } from '../hooks/useFileContent';
+
+const FileOperationsContext = createContext();
+
+export const FileOperationsProvider = ({ children }) => {
+ const { handleCreateNewFile, handleDeleteFile } = useFileContent();
+
+ const value = useMemo(
+ () => ({
+ handleCreateNewFile,
+ handleDeleteFile,
+ }),
+ [handleCreateNewFile, handleDeleteFile]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFileOperations = () => {
+ const context = useContext(FileOperationsContext);
+ if (context === undefined) {
+ throw new Error(
+ 'useFileOperations must be used within a FileOperationsProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/FileSelectionContext.js b/frontend/src/contexts/FileSelectionContext.js
new file mode 100644
index 0000000..d89adeb
--- /dev/null
+++ b/frontend/src/contexts/FileSelectionContext.js
@@ -0,0 +1,35 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import { useFileContent } from '../hooks/useFileContent';
+
+const FileSelectionContext = createContext();
+
+export const FileSelectionProvider = ({ children }) => {
+ const { selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect } =
+ useFileContent();
+
+ const value = useMemo(
+ () => ({
+ selectedFile,
+ isNewFile,
+ hasUnsavedChanges,
+ handleFileSelect,
+ }),
+ [selectedFile, isNewFile, hasUnsavedChanges, handleFileSelect]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFileSelection = () => {
+ const context = useContext(FileSelectionContext);
+ if (context === undefined) {
+ throw new Error(
+ 'useFileSelection must be used within a FileSelectionProvider'
+ );
+ }
+ return context;
+};
diff --git a/frontend/src/contexts/UIStateContext.js b/frontend/src/contexts/ModalContext.js
similarity index 58%
rename from frontend/src/contexts/UIStateContext.js
rename to frontend/src/contexts/ModalContext.js
index e36d999..697bdc7 100644
--- a/frontend/src/contexts/UIStateContext.js
+++ b/frontend/src/contexts/ModalContext.js
@@ -1,9 +1,8 @@
import React, { createContext, useContext, useState } from 'react';
-const UIStateContext = createContext();
+const ModalContext = createContext();
-export const UIStateProvider = ({ children }) => {
- const [activeTab, setActiveTab] = useState('source');
+export const ModalProvider = ({ children }) => {
const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
const [commitMessageModalVisible, setCommitMessageModalVisible] =
@@ -11,8 +10,6 @@ export const UIStateProvider = ({ children }) => {
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
const value = {
- activeTab,
- setActiveTab,
newFileModalVisible,
setNewFileModalVisible,
deleteFileModalVisible,
@@ -24,14 +21,8 @@ export const UIStateProvider = ({ children }) => {
};
return (
- {children}
+ {children}
);
};
-export const useUIStateContext = () => {
- const context = useContext(UIStateContext);
- if (!context) {
- throw new Error('useUIStateContext must be used within a UIStateProvider');
- }
- return context;
-};
+export const useModalContext = () => useContext(ModalContext);
diff --git a/frontend/src/contexts/TabContext.js b/frontend/src/contexts/TabContext.js
new file mode 100644
index 0000000..4123479
--- /dev/null
+++ b/frontend/src/contexts/TabContext.js
@@ -0,0 +1,15 @@
+import React, { createContext, useContext, useState } from 'react';
+
+const TabContext = createContext();
+
+export const TabProvider = ({ children }) => {
+ const [activeTab, setActiveTab] = useState('source');
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTabContext = () => useContext(TabContext);
diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js
index eb8c46b..7d5c239 100644
--- a/frontend/src/hooks/useFileNavigation.js
+++ b/frontend/src/hooks/useFileNavigation.js
@@ -2,11 +2,11 @@
import { useCallback } from 'react';
import { useToasts } from '@geist-ui/core';
import { lookupFileByName } from '../services/api';
-import { useFileContentContext } from '../contexts/FileContentContext';
+import { useFileSelection } from '../contexts/FileSelectionContext';
export const useFileNavigation = () => {
const { setToast } = useToasts();
- const { handleFileSelect } = useFileContentContext();
+ const { handleFileSelect } = useFileSelection();
const handleLinkClick = useCallback(
async (filename) => {