mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 23:44:22 +00:00
Initial frontend implementation
This commit is contained in:
3
frontend/.babelrc
Normal file
3
frontend/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||||
|
}
|
||||||
7305
frontend/package-lock.json
generated
Normal file
7305
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
frontend/package.json
Normal file
64
frontend/package.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "novamd-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Yet another markdown editor",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack serve --mode development --open",
|
||||||
|
"build": "webpack --mode production"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/LordMathis/NovaMD.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"markdown",
|
||||||
|
"hypermd",
|
||||||
|
"editor"
|
||||||
|
],
|
||||||
|
"author": "Matúš Námešný",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/LordMathis/NovaMD/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/LordMathis/NovaMD#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.6.2",
|
||||||
|
"@codemirror/lang-markdown": "^6.2.5",
|
||||||
|
"@codemirror/state": "^6.4.1",
|
||||||
|
"@codemirror/view": "^6.34.0",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.25.2",
|
||||||
|
"@babel/preset-env": "^7.25.4",
|
||||||
|
"@babel/preset-react": "^7.24.7",
|
||||||
|
"babel-loader": "^9.2.1",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"html-webpack-plugin": "^5.6.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"webpack": "^5.94.0",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-dev-server": "^5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
frontend/public/index.html
Normal file
12
frontend/public/index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>NovaMD</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
frontend/src/App.js
Normal file
23
frontend/src/App.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import Editor from './components/Editor';
|
||||||
|
import FileTree from './components/FileTree';
|
||||||
|
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']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<div className="sidebar">
|
||||||
|
<FileTree files={files} />
|
||||||
|
</div>
|
||||||
|
<div className="main-content">
|
||||||
|
<h1>NovaMD</h1>
|
||||||
|
<Editor content={content} onChange={setContent} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
33
frontend/src/App.scss
Normal file
33
frontend/src/App.scss
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
.App {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-tree {
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
frontend/src/components/Editor.js
Normal file
50
frontend/src/components/Editor.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
const Editor = ({ content, onChange }) => {
|
||||||
|
const editorRef = useRef();
|
||||||
|
const viewRef = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const state = EditorState.create({
|
||||||
|
doc: content,
|
||||||
|
extensions: [
|
||||||
|
basicSetup,
|
||||||
|
markdown(),
|
||||||
|
keymap.of(defaultKeymap),
|
||||||
|
EditorView.updateListener.of((update) => {
|
||||||
|
if (update.docChanged) {
|
||||||
|
onChange(update.state.doc.toString());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const view = new EditorView({
|
||||||
|
state,
|
||||||
|
parent: editorRef.current,
|
||||||
|
});
|
||||||
|
|
||||||
|
viewRef.current = view;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
view.destroy();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (viewRef.current && content !== viewRef.current.state.doc.toString()) {
|
||||||
|
viewRef.current.dispatch({
|
||||||
|
changes: { from: 0, to: viewRef.current.state.doc.length, insert: content }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
return <div ref={editorRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Editor;
|
||||||
16
frontend/src/components/FileTree.js
Normal file
16
frontend/src/components/FileTree.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const FileTree = ({ files }) => {
|
||||||
|
return (
|
||||||
|
<div className="file-tree">
|
||||||
|
<h3>Files</h3>
|
||||||
|
<ul>
|
||||||
|
{files.map((file, index) => (
|
||||||
|
<li key={index}>{file}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileTree;
|
||||||
10
frontend/src/index.js
Normal file
10
frontend/src/index.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
35
frontend/webpack.config.js
Normal file
35
frontend/webpack.config.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(js|jsx)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: ['babel-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './public/index.html',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
devServer: {
|
||||||
|
static: {
|
||||||
|
directory: path.join(__dirname, 'public'),
|
||||||
|
},
|
||||||
|
port: 3000,
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
126
package-lock.json
generated
Normal file
126
package-lock.json
generated
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"name": "NovaMD",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {
|
||||||
|
"sass": "^1.79.3",
|
||||||
|
"sass-loader": "^16.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/neo-async": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass": {
|
||||||
|
"version": "1.79.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.79.3.tgz",
|
||||||
|
"integrity": "sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^4.0.0",
|
||||||
|
"immutable": "^4.0.0",
|
||||||
|
"source-map-js": ">=0.6.2 <2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sass": "sass.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sass-loader": {
|
||||||
|
"version": "16.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ll6iXZ1EYwYT19SqW4mSBb76vSSi8JgzElmzIerhEGgzB5hRjDQIWsPmuk1UrAXkR16KJHqVY0eH+5/uw9Tmfw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"neo-async": "^2.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rspack/core": "0.x || 1.x",
|
||||||
|
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
|
"sass": "^1.3.0",
|
||||||
|
"sass-embedded": "*",
|
||||||
|
"webpack": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@rspack/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node-sass": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"webpack": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"sass": "^1.79.3",
|
||||||
|
"sass-loader": "^16.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user