diff --git a/frontend/src/App.js b/frontend/src/App.js index c34bdbe..99177c3 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,8 +2,8 @@ import React, { useState, useEffect } from 'react'; import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core'; import Header from './components/Header'; import MainContent from './components/MainContent'; -import useFileManagement from './hooks/useFileManagement'; -import { fetchUserSettings } from './services/api'; +import { useFileManagement } from './hooks/useFileManagement'; +import { fetchUserSettings, lookupFileByName } from './services/api'; import './App.scss'; function App() { @@ -37,7 +37,6 @@ function App() { handleContentChange, handleSave, pullLatestChanges, - lookupFileByName, } = useFileManagement(settings.gitEnabled); const handleThemeChange = (newTheme) => { diff --git a/frontend/src/hooks/useFileContent.js b/frontend/src/hooks/useFileContent.js new file mode 100644 index 0000000..d538595 --- /dev/null +++ b/frontend/src/hooks/useFileContent.js @@ -0,0 +1,90 @@ +import { useState, useCallback } from 'react'; +import { fetchFileContent, saveFileContent } from '../services/api'; +import { isImageFile } from '../utils/fileHelpers'; + +const DEFAULT_FILE = { + name: 'New File.md', + path: 'New File.md', + content: '# Welcome to NovaMD\n\nStart editing here!', +}; + +export const useFileContent = () => { + const [content, setContent] = useState(DEFAULT_FILE.content); + const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path); + const [isNewFile, setIsNewFile] = useState(true); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [error, setError] = useState(null); + + 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; + } + + try { + if (filePath === DEFAULT_FILE.path) { + setContent(DEFAULT_FILE.content); + setSelectedFile(DEFAULT_FILE.path); + setIsNewFile(true); + } else if (!isImageFile(filePath)) { + const fileContent = await fetchFileContent(filePath); + setContent(fileContent); + setSelectedFile(filePath); + setIsNewFile(false); + } else { + setContent(''); // Set empty content for image files + setSelectedFile(filePath); + setIsNewFile(false); + } + setHasUnsavedChanges(false); + setError(null); + } catch (error) { + console.error('Failed to load file content:', error); + setError('Failed to load file content. Please try again.'); + } + }, + [hasUnsavedChanges] + ); + + const handleContentChange = useCallback((newContent) => { + setContent(newContent); + setHasUnsavedChanges(true); + }, []); + + const handleSave = useCallback(async (filePath, fileContent) => { + try { + await saveFileContent(filePath, fileContent); + setIsNewFile(false); + setHasUnsavedChanges(false); + return true; + } catch (error) { + console.error('Error saving file:', error); + setError('Failed to save file. Please try again.'); + return false; + } + }, []); + + const createNewFile = useCallback(() => { + setContent(DEFAULT_FILE.content); + setSelectedFile(DEFAULT_FILE.path); + setIsNewFile(true); + setHasUnsavedChanges(false); + setError(null); + }, []); + + return { + content, + selectedFile, + isNewFile, + hasUnsavedChanges, + error, + handleFileSelect, + handleContentChange, + handleSave, + createNewFile, + DEFAULT_FILE, + }; +}; diff --git a/frontend/src/hooks/useFileList.js b/frontend/src/hooks/useFileList.js new file mode 100644 index 0000000..4737cf9 --- /dev/null +++ b/frontend/src/hooks/useFileList.js @@ -0,0 +1,27 @@ +import { useState, useEffect, useCallback } from 'react'; +import { fetchFileList } from '../services/api'; + +export const useFileList = (gitEnabled) => { + const [files, setFiles] = useState([]); + const [error, setError] = useState(null); + + const loadFileList = useCallback(async () => { + try { + const fileList = await fetchFileList(); + if (Array.isArray(fileList)) { + setFiles(fileList); + } else { + 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.'); + } + }, []); + + useEffect(() => { + loadFileList(); + }, [loadFileList, gitEnabled]); + + return { files, error, loadFileList }; +}; diff --git a/frontend/src/hooks/useFileManagement.js b/frontend/src/hooks/useFileManagement.js index 6d7ccaf..8620be7 100644 --- a/frontend/src/hooks/useFileManagement.js +++ b/frontend/src/hooks/useFileManagement.js @@ -1,138 +1,34 @@ -import { useState, useEffect, useCallback } from 'react'; -import { useToasts } from '@geist-ui/core'; -import { - fetchFileList, - fetchFileContent, - saveFileContent, - pullChanges, - lookupFileByName, -} from '../services/api'; -import { isImageFile } from '../utils/fileHelpers'; +import { useFileList } from './useFileList'; +import { useFileContent } from './useFileContent'; +import { useGitOperations } from './useGitOperations'; -const DEFAULT_FILE = { - name: 'New File.md', - path: 'New File.md', - content: '# Welcome to NovaMD\n\nStart editing here!', -}; - -const useFileManagement = (gitEnabled = false) => { - const [content, setContent] = useState(DEFAULT_FILE.content); - const [files, setFiles] = useState([]); - const [selectedFile, setSelectedFile] = useState(DEFAULT_FILE.path); - const [isNewFile, setIsNewFile] = useState(true); - const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); - const [error, setError] = useState(null); - const { setToast } = useToasts(); - - const loadFileList = useCallback(async () => { - try { - const fileList = await fetchFileList(); - if (Array.isArray(fileList)) { - setFiles(fileList); - } else { - 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.'); - } - }, []); - - 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 () => { - if (gitEnabled) { - await pullLatestChanges(); - } else { - await loadFileList(); - } - }; - - initializeFileSystem(); - }, [gitEnabled]); - - const handleFileSelect = async (filePath) => { - if (hasUnsavedChanges) { - const confirmSwitch = window.confirm( - 'You have unsaved changes. Are you sure you want to switch files?' - ); - if (!confirmSwitch) return; - } - - try { - if (!isImageFile(filePath)) { - const fileContent = await fetchFileContent(filePath); - setContent(fileContent); - } else { - setContent(''); // Set empty content for image files - } - setSelectedFile(filePath); - setIsNewFile(false); - setHasUnsavedChanges(false); - setError(null); - } catch (error) { - console.error('Failed to load file content:', error); - setError('Failed to load file content. Please try again.'); - } - }; - - const handleContentChange = (newContent) => { - setContent(newContent); - setHasUnsavedChanges(true); - }; - - const handleSave = useCallback( - async (filePath, fileContent) => { - try { - await saveFileContent(filePath, fileContent); - setToast({ text: 'File saved successfully', type: 'success' }); - setIsNewFile(false); - setHasUnsavedChanges(false); - if (isNewFile) { - await loadFileList(); - } - } catch (error) { - console.error('Error saving file:', error); - setToast({ - text: 'Failed to save file. Please try again.', - type: 'error', - }); - } - }, - [setToast, isNewFile, loadFileList] - ); - - return { +export const useFileManagement = (gitEnabled) => { + const { files, error: fileListError, loadFileList } = useFileList(gitEnabled); + const { content, - files, selectedFile, isNewFile, hasUnsavedChanges, - error, + error: fileContentError, + handleFileSelect, + handleContentChange, + handleSave, + } = useFileContent(); + const { pullLatestChanges, handleCommitAndPush } = + useGitOperations(gitEnabled); + + return { + files, + content, + selectedFile, + isNewFile, + hasUnsavedChanges, + error: fileListError || fileContentError, handleFileSelect, handleContentChange, handleSave, pullLatestChanges, - lookupFileByName, + handleCommitAndPush, + loadFileList, }; }; - -export default useFileManagement; diff --git a/frontend/src/hooks/useGitOperations.js b/frontend/src/hooks/useGitOperations.js new file mode 100644 index 0000000..7bf8013 --- /dev/null +++ b/frontend/src/hooks/useGitOperations.js @@ -0,0 +1,31 @@ +import { useCallback } from 'react'; +import { pullChanges, commitAndPush } from '../services/api'; + +export const useGitOperations = (gitEnabled) => { + const pullLatestChanges = useCallback(async () => { + if (!gitEnabled) return; + try { + await pullChanges(); + return true; + } catch (error) { + console.error('Failed to pull latest changes:', error); + return false; + } + }, [gitEnabled]); + + const handleCommitAndPush = useCallback( + async (message) => { + if (!gitEnabled) return; + try { + await commitAndPush(message); + return true; + } catch (error) { + console.error('Failed to commit and push changes:', error); + return false; + } + }, + [gitEnabled] + ); + + return { pullLatestChanges, handleCommitAndPush }; +};