div {
+ & > div {
padding: 0;
margin: 0;
}
}
-
}
.page-content {
@@ -121,4 +120,4 @@ $navbar-height: 64px;
font-weight: bold;
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/Editor.js b/frontend/src/components/Editor.js
index d6f4237..4b7ee76 100644
--- a/frontend/src/components/Editor.js
+++ b/frontend/src/components/Editor.js
@@ -1,10 +1,10 @@
-import React, { useEffect, useRef } from "react";
-import { basicSetup } from "codemirror";
-import { EditorState } from "@codemirror/state";
-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 React, { useEffect, useRef } from 'react';
+import { basicSetup } from 'codemirror';
+import { EditorState } from '@codemirror/state';
+import { EditorView, keymap } from '@codemirror/view';
+import { markdown } from '@codemirror/lang-markdown';
+import { defaultKeymap } from '@codemirror/commands';
+import { oneDark } from '@codemirror/theme-one-dark';
const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
const editorRef = useRef();
@@ -17,20 +17,20 @@ const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
};
const theme = EditorView.theme({
- "&": {
- height: "100%",
- fontSize: "14px",
+ '&': {
+ height: '100%',
+ fontSize: '14px',
},
- ".cm-scroller": {
- overflow: "auto",
+ '.cm-scroller': {
+ overflow: 'auto',
},
- ".cm-gutters": {
- backgroundColor: themeType === "dark" ? "#1e1e1e" : "#f5f5f5",
- color: themeType === "dark" ? "#858585" : "#999",
- border: "none",
+ '.cm-gutters': {
+ backgroundColor: themeType === 'dark' ? '#1e1e1e' : '#f5f5f5',
+ color: themeType === 'dark' ? '#858585' : '#999',
+ border: 'none',
},
- ".cm-activeLineGutter": {
- backgroundColor: themeType === "dark" ? "#2c313a" : "#e8e8e8",
+ '.cm-activeLineGutter': {
+ backgroundColor: themeType === 'dark' ? '#2c313a' : '#e8e8e8',
},
});
@@ -41,18 +41,20 @@ const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
markdown(),
EditorView.lineWrapping,
keymap.of(defaultKeymap),
- keymap.of([{
- key: "Ctrl-s",
- run: handleSave,
- preventDefault: true
- }]),
+ keymap.of([
+ {
+ key: 'Ctrl-s',
+ run: handleSave,
+ preventDefault: true,
+ },
+ ]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
onChange(update.state.doc.toString());
}
}),
theme,
- themeType === "dark" ? oneDark : [],
+ themeType === 'dark' ? oneDark : [],
],
});
@@ -71,7 +73,11 @@ const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
useEffect(() => {
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
viewRef.current.dispatch({
- changes: { from: 0, to: viewRef.current.state.doc.length, insert: content }
+ changes: {
+ from: 0,
+ to: viewRef.current.state.doc.length,
+ insert: content,
+ },
});
}
}, [content]);
@@ -79,4 +85,4 @@ const Editor = ({ content, onChange, onSave, filePath, themeType }) => {
return ;
};
-export default Editor;
\ No newline at end of file
+export default Editor;
diff --git a/frontend/src/components/FileTree.js b/frontend/src/components/FileTree.js
index 91e4a84..b440757 100644
--- a/frontend/src/components/FileTree.js
+++ b/frontend/src/components/FileTree.js
@@ -1,17 +1,24 @@
import React from 'react';
import { Tree, Button, Tooltip, Spacer, ButtonGroup } from '@geist-ui/core';
-import { File, Folder, GitPullRequest, GitCommit, Plus, Trash } from '@geist-ui/icons';
+import {
+ File,
+ Folder,
+ GitPullRequest,
+ GitCommit,
+ Plus,
+ Trash,
+} from '@geist-ui/icons';
-const FileTree = ({
- files = [],
- onFileSelect = () => {},
+const FileTree = ({
+ files = [],
+ onFileSelect = () => {},
selectedFile = null,
gitEnabled = false,
gitAutoCommit = false,
onPull = () => {},
onCommitAndPush = () => {},
onCreateFile = () => {},
- onDeleteFile = () => {}
+ onDeleteFile = () => {},
}) => {
if (files.length === 0) {
return No files to display
;
@@ -30,26 +37,30 @@ const FileTree = ({
);
};
- const renderIcon = ({ type }) => type === 'directory' ? : ;
+ const renderIcon = ({ type }) =>
+ type === 'directory' ? : ;
return (
-
+
}
auto
- scale={2/3}
+ scale={2 / 3}
onClick={onCreateFile}
px={0.6}
/>
-
+
}
auto
- scale={2/3}
+ scale={2 / 3}
onClick={onDeleteFile}
disabled={!selectedFile}
type="error"
@@ -57,26 +68,34 @@ const FileTree = ({
/>
-
+
}
auto
- scale={2/3}
+ scale={2 / 3}
onClick={onPull}
disabled={!gitEnabled}
px={0.6}
/>
-
+
}
auto
- scale={2/3}
+ scale={2 / 3}
onClick={onCommitAndPush}
disabled={!gitEnabled || gitAutoCommit}
px={0.6}
@@ -93,4 +112,4 @@ const FileTree = ({
);
};
-export default FileTree;
\ No newline at end of file
+export default FileTree;
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 585d188..cbac3aa 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -16,9 +16,9 @@ const Header = ({ currentTheme, onThemeChange }) => {
} onClick={openSettings} />
-
@@ -26,4 +26,4 @@ const Header = ({ currentTheme, onThemeChange }) => {
);
};
-export default Header;
\ No newline at end of file
+export default Header;
diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js
index f0417fe..8e6fdee 100644
--- a/frontend/src/components/MainContent.js
+++ b/frontend/src/components/MainContent.js
@@ -1,5 +1,15 @@
import React, { useState } from 'react';
-import { Grid, Breadcrumbs, Tabs, Dot, useTheme, useToasts, Modal, Input, Button } from '@geist-ui/core';
+import {
+ Grid,
+ Breadcrumbs,
+ Tabs,
+ Dot,
+ useTheme,
+ useToasts,
+ Modal,
+ Input,
+ Button,
+} from '@geist-ui/core';
import { Code, Eye } from '@geist-ui/icons';
import Editor from './Editor';
import FileTree from './FileTree';
@@ -29,7 +39,10 @@ const MainContent = ({
await pullLatestChanges();
setToast({ text: 'Successfully pulled latest changes', type: 'success' });
} catch (error) {
- setToast({ text: 'Failed to pull changes: ' + error.message, type: 'error' });
+ setToast({
+ text: 'Failed to pull changes: ' + error.message,
+ type: 'error',
+ });
}
};
@@ -38,11 +51,17 @@ const MainContent = ({
const message = prompt('Enter commit message:');
if (message) {
await commitAndPush(message);
- setToast({ text: 'Changes committed and pushed successfully', type: 'success' });
+ setToast({
+ text: 'Changes committed and pushed successfully',
+ type: 'success',
+ });
await pullLatestChanges(); // Pull changes after successful push
}
} catch (error) {
- setToast({ text: 'Failed to commit and push changes: ' + error.message, type: 'error' });
+ setToast({
+ text: 'Failed to commit and push changes: ' + error.message,
+ type: 'error',
+ });
}
};
@@ -58,7 +77,10 @@ const MainContent = ({
await pullLatestChanges(); // Refresh file list
onFileSelect(newFileName); // Select the new file
} catch (error) {
- setToast({ text: 'Failed to create new file: ' + error.message, type: 'error' });
+ setToast({
+ text: 'Failed to create new file: ' + error.message,
+ type: 'error',
+ });
}
}
setNewFileModalVisible(false);
@@ -67,7 +89,9 @@ const MainContent = ({
const handleDeleteFile = async () => {
if (selectedFile) {
- const confirmDelete = window.confirm(`Are you sure you want to delete "${selectedFile}"?`);
+ const confirmDelete = window.confirm(
+ `Are you sure you want to delete "${selectedFile}"?`
+ );
if (confirmDelete) {
try {
await deleteFile(selectedFile);
@@ -75,7 +99,10 @@ const MainContent = ({
await pullLatestChanges(); // Refresh file list
onFileSelect(null); // Deselect the file
} catch (error) {
- setToast({ text: 'Failed to delete file: ' + error.message, type: 'error' });
+ setToast({
+ text: 'Failed to delete file: ' + error.message,
+ type: 'error',
+ });
}
}
}
@@ -91,7 +118,9 @@ const MainContent = ({
{part}
))}
- {hasUnsavedChanges && }
+ {hasUnsavedChanges && (
+
+ )}
);
};
@@ -101,9 +130,9 @@ const MainContent = ({
-
-
+
{renderBreadcrumbs()}
@@ -124,9 +160,9 @@ const MainContent = ({
{activeTab === 'source' ? (
-
- setNewFileModalVisible(false)}>
+ setNewFileModalVisible(false)}
+ >
Create New File
- setNewFileName(e.target.value)}
/>
- setNewFileModalVisible(false)}>Cancel
+ setNewFileModalVisible(false)}>
+ Cancel
+
Create
>
);
};
-export default MainContent;
\ No newline at end of file
+export default MainContent;
diff --git a/frontend/src/components/MarkdownPreview.js b/frontend/src/components/MarkdownPreview.js
index 3fc4d79..8f7af2d 100644
--- a/frontend/src/components/MarkdownPreview.js
+++ b/frontend/src/components/MarkdownPreview.js
@@ -13,8 +13,8 @@ const MarkdownPreview = ({ content }) => {
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
components={{
- code({node, inline, className, children, ...props}) {
- const match = /language-(\w+)/.exec(className || '')
+ code({ node, inline, className, children, ...props }) {
+ const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
{
{children}
- )
- }
+ );
+ },
}}
>
{content}
@@ -38,4 +38,4 @@ const MarkdownPreview = ({ content }) => {
);
};
-export default MarkdownPreview;
\ No newline at end of file
+export default MarkdownPreview;
diff --git a/frontend/src/components/Settings.js b/frontend/src/components/Settings.js
index 67205c7..03123e2 100644
--- a/frontend/src/components/Settings.js
+++ b/frontend/src/components/Settings.js
@@ -1,5 +1,15 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { Modal, Text, Toggle, Input, Spacer, useTheme, Button, Dot, useToasts } from '@geist-ui/core';
+import {
+ Modal,
+ Text,
+ Toggle,
+ Input,
+ Spacer,
+ useTheme,
+ Button,
+ Dot,
+ useToasts,
+} from '@geist-ui/core';
import { saveUserSettings, fetchUserSettings } from '../services/api';
const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
@@ -42,13 +52,14 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
}, [isInitialized, loadSettings]);
useEffect(() => {
- const settingsChanged = JSON.stringify(settings) !== JSON.stringify(originalSettings);
+ const settingsChanged =
+ JSON.stringify(settings) !== JSON.stringify(originalSettings);
const themeChanged = themeSettings !== originalTheme;
setHasUnsavedChanges(settingsChanged || themeChanged);
}, [settings, themeSettings, originalSettings, originalTheme]);
const handleInputChange = (key, value) => {
- setSettings(prev => ({ ...prev, [key]: value }));
+ setSettings((prev) => ({ ...prev, [key]: value }));
};
const handleThemeChange = () => {
@@ -70,7 +81,10 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
onClose();
} catch (error) {
console.error('Failed to save settings:', error);
- setToast({ text: 'Failed to save settings: ' + error.message, type: 'error' });
+ setToast({
+ text: 'Failed to save settings: ' + error.message,
+ type: 'error',
+ });
}
};
@@ -87,7 +101,7 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
Appearance
Dark Mode
-
@@ -111,7 +125,9 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
Enable Git
handleInputChange('gitEnabled', e.target.checked)}
+ onChange={(e) =>
+ handleInputChange('gitEnabled', e.target.checked)
+ }
/>
@@ -143,7 +159,9 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
Auto Commit
handleInputChange('gitAutoCommit', e.target.checked)}
+ onChange={(e) =>
+ handleInputChange('gitAutoCommit', e.target.checked)
+ }
disabled={!settings.gitEnabled}
/>
@@ -152,16 +170,20 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
width="100%"
label="Commit Message Template"
value={settings.gitCommitMsgTemplate}
- onChange={(e) => handleInputChange('gitCommitMsgTemplate', e.target.value)}
+ onChange={(e) =>
+ handleInputChange('gitCommitMsgTemplate', e.target.value)
+ }
disabled={!settings.gitEnabled}
/>
- Cancel
+
+ Cancel
+
Save Changes
);
};
-export default Settings;
\ No newline at end of file
+export default Settings;
diff --git a/frontend/src/hooks/useFileManagement.js b/frontend/src/hooks/useFileManagement.js
index c94eebe..2105816 100644
--- a/frontend/src/hooks/useFileManagement.js
+++ b/frontend/src/hooks/useFileManagement.js
@@ -1,11 +1,16 @@
import { useState, useEffect, useCallback } from 'react';
import { useToasts } from '@geist-ui/core';
-import { fetchFileList, fetchFileContent, saveFileContent, pullChanges } from '../services/api';
+import {
+ fetchFileList,
+ fetchFileContent,
+ saveFileContent,
+ pullChanges,
+} from '../services/api';
const DEFAULT_FILE = {
name: 'New File.md',
path: 'New File.md',
- content: '# Welcome to NovaMD\n\nStart editing here!'
+ content: '# Welcome to NovaMD\n\nStart editing here!',
};
const useFileManagement = (gitEnabled = false) => {
@@ -35,11 +40,17 @@ const useFileManagement = (gitEnabled = false) => {
if (gitEnabled) {
try {
await pullChanges();
- setToast({ text: 'Latest changes pulled successfully', type: 'success' });
+ 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' });
+ setToast({
+ text: 'Failed to pull latest changes: ' + error.message,
+ type: 'error',
+ });
}
}
}, [gitEnabled, loadFileList, setToast]);
@@ -58,7 +69,9 @@ const useFileManagement = (gitEnabled = false) => {
const handleFileSelect = async (filePath) => {
if (hasUnsavedChanges) {
- const confirmSwitch = window.confirm('You have unsaved changes. Are you sure you want to switch files?');
+ const confirmSwitch = window.confirm(
+ 'You have unsaved changes. Are you sure you want to switch files?'
+ );
if (!confirmSwitch) return;
}
@@ -80,20 +93,26 @@ const useFileManagement = (gitEnabled = false) => {
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();
+ 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',
+ });
}
- } catch (error) {
- console.error('Error saving file:', error);
- setToast({ text: 'Failed to save file. Please try again.', type: 'error' });
- }
- }, [setToast, isNewFile, loadFileList]);
+ },
+ [setToast, isNewFile, loadFileList]
+ );
return {
content,
@@ -109,4 +128,4 @@ const useFileManagement = (gitEnabled = false) => {
};
};
-export default useFileManagement;
\ No newline at end of file
+export default useFileManagement;
diff --git a/frontend/src/index.js b/frontend/src/index.js
index bd365f0..593edf1 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -1,10 +1,10 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import App from "./App";
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
-const root = ReactDOM.createRoot(document.getElementById("root"));
+const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-);
\ No newline at end of file
+);
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index 8a5d13d..151cc1c 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -1,127 +1,129 @@
const API_BASE_URL = window.API_BASE_URL;
export const fetchFileList = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/files`);
- if (!response.ok) {
- throw new Error('Failed to fetch file list');
- }
- return await response.json();
- } catch (error) {
- console.error('Error fetching file list:', error);
- throw error;
+ try {
+ const response = await fetch(`${API_BASE_URL}/files`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch file list');
}
+ return await response.json();
+ } catch (error) {
+ console.error('Error fetching file list:', error);
+ throw error;
+ }
};
export const fetchFileContent = async (filePath) => {
- try {
- const response = await fetch(`${API_BASE_URL}/files/${filePath}`);
- if (!response.ok) {
- throw new Error('Failed to fetch file content');
- }
- return await response.text();
- } catch (error) {
- console.error('Error fetching file content:', error);
- throw error;
+ try {
+ const response = await fetch(`${API_BASE_URL}/files/${filePath}`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch file content');
}
+ return await response.text();
+ } catch (error) {
+ console.error('Error fetching file content:', error);
+ throw error;
+ }
};
export const saveFileContent = async (filePath, content) => {
+ const response = await fetch(`${API_BASE_URL}/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();
+};
+
+export const deleteFile = async (filePath) => {
+ try {
const response = await fetch(`${API_BASE_URL}/files/${filePath}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'text/plain',
- },
- body: content,
+ method: 'DELETE',
});
-
if (!response.ok) {
- throw new Error('Failed to save file');
+ throw new Error('Failed to delete file');
}
-
return await response.text();
- };
+ } catch (error) {
+ console.error('Error deleting file:', error);
+ throw error;
+ }
+};
- export const deleteFile = async (filePath) => {
- try {
- const response = await fetch(`${API_BASE_URL}/files/${filePath}`, {
- method: 'DELETE',
- });
- if (!response.ok) {
- throw new Error('Failed to delete file');
- }
- return await response.text();
- } catch (error) {
- console.error('Error deleting file:', error);
- throw error;
- }
- };
-
- export const fetchUserSettings = async (userId) => {
- try {
- const response = await fetch(`${API_BASE_URL}/settings?userId=${userId}`);
- if (!response.ok) {
- throw new Error('Failed to fetch user settings');
- }
- return await response.json();
- } catch (error) {
- console.error('Error fetching user settings:', error);
- throw error;
+export const fetchUserSettings = async (userId) => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/settings?userId=${userId}`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch user settings');
}
+ return await response.json();
+ } catch (error) {
+ console.error('Error fetching user settings:', error);
+ throw error;
+ }
};
export const saveUserSettings = async (settings) => {
- try {
- const response = await fetch(`${API_BASE_URL}/settings`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(settings),
- });
+ try {
+ const response = await fetch(`${API_BASE_URL}/settings`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(settings),
+ });
- if (!response.ok) {
- const errorData = await response.json().catch(() => null);
- throw new Error(errorData?.message || `HTTP error! status: ${response.status}`);
- }
-
- return await response.json();
- } catch (error) {
- console.error('Error saving user settings:', error);
- throw error;
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => null);
+ throw new Error(
+ errorData?.message || `HTTP error! status: ${response.status}`
+ );
}
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error saving user settings:', error);
+ throw error;
+ }
};
export const pullChanges = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/git/pull`, {
- method: 'POST',
- });
- if (!response.ok) {
- throw new Error('Failed to pull changes');
- }
- return await response.json();
- } catch (error) {
- console.error('Error pulling changes:', error);
- throw error;
+ try {
+ const response = await fetch(`${API_BASE_URL}/git/pull`, {
+ method: 'POST',
+ });
+ if (!response.ok) {
+ throw new Error('Failed to pull changes');
}
- };
-
- export const commitAndPush = async (message) => {
- try {
- const response = await fetch(`${API_BASE_URL}/git/commit`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ message }),
- });
- if (!response.ok) {
- throw new Error('Failed to commit and push changes');
- }
- return await response.json();
- } catch (error) {
- console.error('Error committing and pushing changes:', error);
- throw error;
+ return await response.json();
+ } catch (error) {
+ console.error('Error pulling changes:', error);
+ throw error;
+ }
+};
+
+export const commitAndPush = async (message) => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/git/commit`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ message }),
+ });
+ if (!response.ok) {
+ throw new Error('Failed to commit and push changes');
}
- };
\ No newline at end of file
+ return await response.json();
+ } catch (error) {
+ console.error('Error committing and pushing changes:', error);
+ throw error;
+ }
+};
diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js
index 4448a94..e2625ba 100644
--- a/frontend/webpack.config.js
+++ b/frontend/webpack.config.js
@@ -46,4 +46,4 @@ module.exports = (env, argv) => {
open: true,
},
};
-};
\ No newline at end of file
+};