mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 23:44:22 +00:00
Set up and run eslint and prettier
This commit is contained in:
31
.eslintrc.json
Normal file
31
.eslintrc.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/prop-types": "off",
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
},
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"hypermd",
|
||||
"editor"
|
||||
],
|
||||
"author": "Matúš Námešný",
|
||||
|
||||
@@ -47,7 +47,7 @@ function App() {
|
||||
<CssBaseline />
|
||||
<Page>
|
||||
<Header currentTheme={themeType} onThemeChange={setTheme} />
|
||||
<Page.Content className='page-content'>
|
||||
<Page.Content className="page-content">
|
||||
<MainContent
|
||||
content={content}
|
||||
files={files}
|
||||
@@ -67,4 +67,4 @@ function App() {
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
@@ -15,12 +15,11 @@ $navbar-height: 64px;
|
||||
.file-tree-container {
|
||||
width: 100%;
|
||||
|
||||
&>div {
|
||||
& > div {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.page-content {
|
||||
@@ -121,4 +120,4 @@ $navbar-height: 64px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <div ref={editorRef} className="editor-container" />;
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
export default Editor;
|
||||
|
||||
@@ -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 <div>No files to display</div>;
|
||||
@@ -30,26 +37,30 @@ const FileTree = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderIcon = ({ type }) => type === 'directory' ? <Folder /> : <File />;
|
||||
const renderIcon = ({ type }) =>
|
||||
type === 'directory' ? <Folder /> : <File />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ButtonGroup className='file-tree-buttons'>
|
||||
<ButtonGroup className="file-tree-buttons">
|
||||
<Tooltip text="Create new file" type="dark">
|
||||
<Button
|
||||
icon={<Plus />}
|
||||
auto
|
||||
scale={2/3}
|
||||
scale={2 / 3}
|
||||
onClick={onCreateFile}
|
||||
px={0.6}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Spacer w={0.5} />
|
||||
<Tooltip text={selectedFile ? "Delete current file" : "No file selected"} type="dark">
|
||||
<Tooltip
|
||||
text={selectedFile ? 'Delete current file' : 'No file selected'}
|
||||
type="dark"
|
||||
>
|
||||
<Button
|
||||
icon={<Trash />}
|
||||
auto
|
||||
scale={2/3}
|
||||
scale={2 / 3}
|
||||
onClick={onDeleteFile}
|
||||
disabled={!selectedFile}
|
||||
type="error"
|
||||
@@ -57,26 +68,34 @@ const FileTree = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
<Spacer w={0.5} />
|
||||
<Tooltip text={gitEnabled ? "Pull changes from remote" : "Git is not enabled"} type="dark">
|
||||
<Tooltip
|
||||
text={gitEnabled ? 'Pull changes from remote' : 'Git is not enabled'}
|
||||
type="dark"
|
||||
>
|
||||
<Button
|
||||
icon={<GitPullRequest />}
|
||||
auto
|
||||
scale={2/3}
|
||||
scale={2 / 3}
|
||||
onClick={onPull}
|
||||
disabled={!gitEnabled}
|
||||
px={0.6}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Spacer w={0.5} />
|
||||
<Tooltip text={
|
||||
!gitEnabled ? "Git is not enabled" :
|
||||
gitAutoCommit ? "Auto-commit is enabled" :
|
||||
"Commit and push changes"
|
||||
} type="dark">
|
||||
<Tooltip
|
||||
text={
|
||||
!gitEnabled
|
||||
? 'Git is not enabled'
|
||||
: gitAutoCommit
|
||||
? 'Auto-commit is enabled'
|
||||
: 'Commit and push changes'
|
||||
}
|
||||
type="dark"
|
||||
>
|
||||
<Button
|
||||
icon={<GitCommit />}
|
||||
auto
|
||||
scale={2/3}
|
||||
scale={2 / 3}
|
||||
onClick={onCommitAndPush}
|
||||
disabled={!gitEnabled || gitAutoCommit}
|
||||
px={0.6}
|
||||
@@ -93,4 +112,4 @@ const FileTree = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileTree;
|
||||
export default FileTree;
|
||||
|
||||
@@ -16,9 +16,9 @@ const Header = ({ currentTheme, onThemeChange }) => {
|
||||
<User src="https://via.placeholder.com/40" name="User" />
|
||||
<Spacer w={0.5} />
|
||||
<Button auto icon={<SettingsIcon />} onClick={openSettings} />
|
||||
<Settings
|
||||
visible={settingsVisible}
|
||||
onClose={closeSettings}
|
||||
<Settings
|
||||
visible={settingsVisible}
|
||||
onClose={closeSettings}
|
||||
currentTheme={currentTheme}
|
||||
onThemeChange={onThemeChange}
|
||||
/>
|
||||
@@ -26,4 +26,4 @@ const Header = ({ currentTheme, onThemeChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
||||
@@ -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 = ({
|
||||
<Breadcrumbs.Item key={index}>{part}</Breadcrumbs.Item>
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
{hasUnsavedChanges && <Dot type="warning" className="unsaved-indicator" />}
|
||||
{hasUnsavedChanges && (
|
||||
<Dot type="warning" className="unsaved-indicator" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -101,9 +130,9 @@ const MainContent = ({
|
||||
<Grid.Container gap={1} height="calc(100vh - 64px)">
|
||||
<Grid xs={24} sm={6} md={5} lg={4} height="100%" className="sidebar">
|
||||
<div className="file-tree-container">
|
||||
<FileTree
|
||||
files={files}
|
||||
onFileSelect={onFileSelect}
|
||||
<FileTree
|
||||
files={files}
|
||||
onFileSelect={onFileSelect}
|
||||
selectedFile={selectedFile}
|
||||
gitEnabled={settings.gitEnabled}
|
||||
gitAutoCommit={settings.gitAutoCommit}
|
||||
@@ -114,7 +143,14 @@ const MainContent = ({
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid xs={24} sm={18} md={19} lg={20} height="100%" className="main-content">
|
||||
<Grid
|
||||
xs={24}
|
||||
sm={18}
|
||||
md={19}
|
||||
lg={20}
|
||||
height="100%"
|
||||
className="main-content"
|
||||
>
|
||||
<div className="content-header">
|
||||
{renderBreadcrumbs()}
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
@@ -124,9 +160,9 @@ const MainContent = ({
|
||||
</div>
|
||||
<div className="content-body">
|
||||
{activeTab === 'source' ? (
|
||||
<Editor
|
||||
content={content}
|
||||
onChange={onContentChange}
|
||||
<Editor
|
||||
content={content}
|
||||
onChange={onContentChange}
|
||||
onSave={onSave}
|
||||
filePath={selectedFile}
|
||||
themeType={themeType}
|
||||
@@ -137,21 +173,26 @@ const MainContent = ({
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid.Container>
|
||||
<Modal visible={newFileModalVisible} onClose={() => setNewFileModalVisible(false)}>
|
||||
<Modal
|
||||
visible={newFileModalVisible}
|
||||
onClose={() => setNewFileModalVisible(false)}
|
||||
>
|
||||
<Modal.Title>Create New File</Modal.Title>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
width="100%"
|
||||
<Input
|
||||
width="100%"
|
||||
placeholder="Enter file name"
|
||||
value={newFileName}
|
||||
onChange={(e) => setNewFileName(e.target.value)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Action passive onClick={() => setNewFileModalVisible(false)}>Cancel</Modal.Action>
|
||||
<Modal.Action passive onClick={() => setNewFileModalVisible(false)}>
|
||||
Cancel
|
||||
</Modal.Action>
|
||||
<Modal.Action onClick={handleNewFileSubmit}>Create</Modal.Action>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainContent;
|
||||
export default MainContent;
|
||||
|
||||
@@ -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 ? (
|
||||
<SyntaxHighlighter
|
||||
style={vscDarkPlus}
|
||||
@@ -28,8 +28,8 @@ const MarkdownPreview = ({ content }) => {
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
@@ -38,4 +38,4 @@ const MarkdownPreview = ({ content }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPreview;
|
||||
export default MarkdownPreview;
|
||||
|
||||
@@ -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 }) => {
|
||||
<Text h4>Appearance</Text>
|
||||
<div className="setting-item">
|
||||
<Text>Dark Mode</Text>
|
||||
<Toggle
|
||||
<Toggle
|
||||
checked={themeSettings === 'dark'}
|
||||
onChange={handleThemeChange}
|
||||
/>
|
||||
@@ -111,7 +125,9 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
|
||||
<Text>Enable Git</Text>
|
||||
<Toggle
|
||||
checked={settings.gitEnabled}
|
||||
onChange={(e) => handleInputChange('gitEnabled', e.target.checked)}
|
||||
onChange={(e) =>
|
||||
handleInputChange('gitEnabled', e.target.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={settings.gitEnabled ? '' : 'disabled'}>
|
||||
@@ -143,7 +159,9 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
|
||||
<Text>Auto Commit</Text>
|
||||
<Toggle
|
||||
checked={settings.gitAutoCommit}
|
||||
onChange={(e) => handleInputChange('gitAutoCommit', e.target.checked)}
|
||||
onChange={(e) =>
|
||||
handleInputChange('gitAutoCommit', e.target.checked)
|
||||
}
|
||||
disabled={!settings.gitEnabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Action passive onClick={onClose}>Cancel</Modal.Action>
|
||||
<Modal.Action passive onClick={onClose}>
|
||||
Cancel
|
||||
</Modal.Action>
|
||||
<Modal.Action onClick={handleSubmit}>Save Changes</Modal.Action>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
export default Settings;
|
||||
|
||||
@@ -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;
|
||||
export default useFileManagement;
|
||||
|
||||
@@ -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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error committing and pushing changes:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,4 +46,4 @@ module.exports = (env, argv) => {
|
||||
open: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user