mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Save file on FE
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { GeistProvider, CssBaseline } from '@geist-ui/core';
|
import { GeistProvider, CssBaseline, useToasts } from '@geist-ui/core';
|
||||||
import Editor from './components/Editor';
|
import Editor from './components/Editor';
|
||||||
import FileTree from './components/FileTree';
|
import FileTree from './components/FileTree';
|
||||||
import { fetchFileList, fetchFileContent } from './services/api';
|
import { fetchFileList, fetchFileContent, saveFileContent } from './services/api';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -10,6 +10,7 @@ function App() {
|
|||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
const [selectedFile, setSelectedFile] = useState(null);
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const { setToast } = useToasts();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadFileList = async () => {
|
const loadFileList = async () => {
|
||||||
@@ -34,12 +35,23 @@ function App() {
|
|||||||
const fileContent = await fetchFileContent(filePath);
|
const fileContent = await fetchFileContent(filePath);
|
||||||
setContent(fileContent);
|
setContent(fileContent);
|
||||||
setSelectedFile(filePath);
|
setSelectedFile(filePath);
|
||||||
|
setError(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load file content:', error);
|
console.error('Failed to load file content:', error);
|
||||||
setError('Failed to load file content. Please try again.');
|
setError('Failed to load file content. Please try again.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSave = useCallback(async (filePath, fileContent) => {
|
||||||
|
try {
|
||||||
|
await saveFileContent(filePath, fileContent);
|
||||||
|
setToast({ text: 'File saved successfully', type: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving file:', error);
|
||||||
|
setToast({ text: 'Failed to save file. Please try again.', type: 'error' });
|
||||||
|
}
|
||||||
|
}, [setToast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeistProvider>
|
<GeistProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
@@ -57,7 +69,12 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="main-content">
|
<div className="main-content">
|
||||||
<h1>NovaMD</h1>
|
<h1>NovaMD</h1>
|
||||||
<Editor content={content} onChange={setContent} />
|
<Editor
|
||||||
|
content={content}
|
||||||
|
onChange={setContent}
|
||||||
|
onSave={handleSave}
|
||||||
|
filePath={selectedFile}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GeistProvider>
|
</GeistProvider>
|
||||||
|
|||||||
@@ -5,17 +5,27 @@ import { EditorView, keymap } from "@codemirror/view";
|
|||||||
import { markdown } from "@codemirror/lang-markdown";
|
import { markdown } from "@codemirror/lang-markdown";
|
||||||
import { defaultKeymap } from "@codemirror/commands";
|
import { defaultKeymap } from "@codemirror/commands";
|
||||||
|
|
||||||
const Editor = ({ content, onChange }) => {
|
const Editor = ({ content, onChange, onSave, filePath }) => {
|
||||||
const editorRef = useRef();
|
const editorRef = useRef();
|
||||||
const viewRef = useRef();
|
const viewRef = useRef();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const handleSave = (view) => {
|
||||||
|
onSave(filePath, view.state.doc.toString());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: content,
|
doc: content,
|
||||||
extensions: [
|
extensions: [
|
||||||
basicSetup,
|
basicSetup,
|
||||||
markdown(),
|
markdown(),
|
||||||
keymap.of(defaultKeymap),
|
keymap.of(defaultKeymap),
|
||||||
|
keymap.of([{
|
||||||
|
key: "Ctrl-s",
|
||||||
|
run: handleSave,
|
||||||
|
preventDefault: true
|
||||||
|
}]),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
onChange(update.state.doc.toString());
|
onChange(update.state.doc.toString());
|
||||||
@@ -34,7 +44,7 @@ const Editor = ({ content, onChange }) => {
|
|||||||
return () => {
|
return () => {
|
||||||
view.destroy();
|
view.destroy();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [filePath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
|
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
|
||||||
|
|||||||
@@ -24,4 +24,20 @@ export const fetchFileContent = async (filePath) => {
|
|||||||
console.error('Error fetching file content:', error);
|
console.error('Error fetching file content:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveFileContent = async (filePath, content) => {
|
||||||
|
const response = await fetch(`/api/v1/files/${filePath}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: content,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save file');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user