diff --git a/backend/internal/filesystem/filesystem.go b/backend/internal/filesystem/filesystem.go index ce02617..3f2b1b1 100644 --- a/backend/internal/filesystem/filesystem.go +++ b/backend/internal/filesystem/filesystem.go @@ -1,7 +1,6 @@ package filesystem import ( - "io/ioutil" "os" "path/filepath" ) @@ -10,28 +9,52 @@ type FileSystem struct { RootDir string } +type FileNode struct { + Type string `json:"type"` + Name string `json:"name"` + Files []FileNode `json:"files,omitempty"` +} + func New(rootDir string) *FileSystem { return &FileSystem{RootDir: rootDir} } -func (fs *FileSystem) ListFilesRecursively() ([]string, error) { - var files []string +func (fs *FileSystem) ListFilesRecursively() ([]FileNode, error) { + return fs.walkDirectory(fs.RootDir) +} - err := filepath.Walk(fs.RootDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() { - relPath, _ := filepath.Rel(fs.RootDir, path) - files = append(files, relPath) - } - return nil - }) +func (fs *FileSystem) walkDirectory(dir string) ([]FileNode, error) { + var nodes []FileNode - return files, err + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.IsDir() { + subdir := filepath.Join(dir, entry.Name()) + subFiles, err := fs.walkDirectory(subdir) + if err != nil { + return nil, err + } + nodes = append(nodes, FileNode{ + Type: "directory", + Name: entry.Name(), + Files: subFiles, + }) + } else { + nodes = append(nodes, FileNode{ + Type: "file", + Name: entry.Name(), + }) + } + } + + return nodes, nil } func (fs *FileSystem) GetFileContent(filePath string) ([]byte, error) { fullPath := filepath.Join(fs.RootDir, filePath) - return ioutil.ReadFile(fullPath) + return os.ReadFile(fullPath) } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 603294b..29371d6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,8 @@ "@codemirror/lang-markdown": "^6.2.5", "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.34.0", + "@geist-ui/core": "^2.3.8", + "@geist-ui/icons": "^1.0.2", "codemirror": "^6.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -1857,7 +1859,6 @@ "version": "7.25.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", - "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2111,6 +2112,29 @@ "node": ">=10.0.0" } }, + "node_modules/@geist-ui/core": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@geist-ui/core/-/core-2.3.8.tgz", + "integrity": "sha512-OKwGgTA4+fBM41eQbqDoUj4XBycZbYH7Ynrn6LPO5yKX7zeWPu/R7HN3vB4/oHt34VTDQI5sDNb1SirHvNyB5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@geist-ui/icons": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@geist-ui/icons/-/icons-1.0.2.tgz", + "integrity": "sha512-Npfa0NW6fQ31qw/+iMPWbs1hAcJ/3FqBjSLYgEfITDqy/3TJFpFKeVyK04AC/hTmYTsdNruVYczqPNcham5FOQ==", + "license": "MIT", + "peerDependencies": { + "@geist-ui/core": ">=1.0.0", + "react": ">=16.13.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -5941,7 +5965,6 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, "license": "MIT" }, "node_modules/regenerator-transform": { diff --git a/frontend/package.json b/frontend/package.json index 0521433..aa21624 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,8 @@ "@codemirror/lang-markdown": "^6.2.5", "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.34.0", + "@geist-ui/core": "^2.3.8", + "@geist-ui/icons": "^1.0.2", "codemirror": "^6.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/frontend/src/App.js b/frontend/src/App.js index 817a10e..9cb3f91 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,23 +1,67 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { GeistProvider, CssBaseline } from '@geist-ui/core'; import Editor from './components/Editor'; import FileTree from './components/FileTree'; +import { fetchFileList, fetchFileContent } from './services/api'; import './App.scss'; function App() { const [content, setContent] = useState('# Welcome to NovaMD\n\nStart editing here!'); - const [files, setFiles] = useState(['README.md', 'chapter1.md', 'chapter2.md']); + const [files, setFiles] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const loadFileList = 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.'); + } + }; + + loadFileList(); + }, []); + + const handleFileSelect = async (filePath) => { + try { + const fileContent = await fetchFileContent(filePath); + setContent(fileContent); + setSelectedFile(filePath); + } catch (error) { + console.error('Failed to load file content:', error); + setError('Failed to load file content. Please try again.'); + } + }; return ( -