├── .gitignore ├── README.md ├── client ├── assets │ └── mnemozine.png ├── index.js ├── components │ ├── Header.jsx │ ├── DisplayTexts.jsx │ ├── AddText.jsx │ ├── AddCard.jsx │ ├── RenderText.jsx │ ├── RenderEditableText.jsx │ └── GetUser.jsx ├── index.html ├── App.jsx └── stylesheets │ └── styles.css ├── server ├── models │ └── pgModel.js ├── controllers │ ├── markdownParser.js │ ├── userController.js │ └── textController.js └── server.js ├── LICENSE ├── webpack.config.js ├── package.json └── sample.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mnemozine -------------------------------------------------------------------------------- /client/assets/mnemozine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kruckenberg/mnemozine/HEAD/client/assets/mnemozine.png -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | render( 6 | , 7 | document.getElementById('app'), 8 | ); -------------------------------------------------------------------------------- /client/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const Header = props => { 4 | 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default Header; -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mnemozine - Make Memory a Choice 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /server/models/pgModel.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const PG_URI = 'postgres://wgpafsft:qclBQ1i-dHj_TfCMXjn6t3fY0FtyQMXj@ruby.db.elephantsql.com:5432/wgpafsft'; 3 | 4 | // Create new Postgres Pool instance 5 | const pool = new Pool({ connectionString: PG_URI }); 6 | 7 | module.exports = { 8 | query: (text, params, callback) => { 9 | return pool.query(text, params, callback); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | 4 | import Header from './components/Header.jsx'; 5 | import GetUser from './components/GetUser.jsx'; 6 | import AddText from './components/AddText.jsx'; 7 | import DisplayTexts from './components/DisplayTexts.jsx'; 8 | import RenderText from './components/RenderText.jsx'; 9 | import RenderEditableText from './components/RenderEditableText.jsx'; 10 | 11 | import './stylesheets/styles.css'; 12 | 13 | const App = props => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default App; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nick Kruckenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/controllers/markdownParser.js: -------------------------------------------------------------------------------- 1 | const markdownParser = (text) => { 2 | const splitText = text.split('\n').filter((ea) => ea !== ''); 3 | 4 | const parsedText = []; 5 | position = 0; 6 | 7 | for (let block of splitText) { 8 | const blockObj = { 9 | position: position 10 | }; 11 | 12 | block = block.trim(); 13 | if (block[0] === '#') { 14 | if (block[2] === '#') { 15 | blockObj.type = 'heading-3'; 16 | blockObj.content = block.slice(3).trim(); 17 | } else if (block[1] === '#') { 18 | blockObj.type = 'heading-2'; 19 | blockObj.content = block.slice(2).trim(); 20 | } else { 21 | blockObj.type = 'heading-1'; 22 | blockObj.content = block.slice(1).trim(); 23 | } 24 | } else { 25 | blockObj.type = 'paragraph'; 26 | let content = block.trim(); 27 | while(content.indexOf('**') >= 0) { 28 | content = content.replace(/\*{2}/, '').replace(/\*{2}/, ''); 29 | } 30 | while(content.indexOf('__') >= 0) { 31 | content = content.replace(/_{2}/, '').replace(/_{2}/, '') 32 | } 33 | blockObj.content = content; 34 | 35 | 36 | } 37 | 38 | parsedText.push(blockObj); 39 | position += 1; 40 | } 41 | return parsedText; 42 | } 43 | 44 | module.exports = markdownParser; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: './client/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | publicPath: '/', 9 | filename: 'bundle.js', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /.(js|jsx)$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['@babel/preset-env', '@babel/preset-react'], 20 | } 21 | } 22 | }, 23 | { 24 | test: /.(css|scss)$/, 25 | exclude: /node_modules/, 26 | use: ['style-loader', 'css-loader'], 27 | } 28 | ], 29 | }, 30 | devServer: { 31 | publicPath: '/', 32 | contentBase: path.resolve(__dirname, 'client'), 33 | hot: true, 34 | proxy: { 35 | '/newText': 'http://localhost:3000', 36 | '/login': 'http://localhost:3000', 37 | '/signup': 'http://localhost:3000', 38 | '/getTexts': 'http://localhost:3000', 39 | '/getTextContent': 'http://localhost:3000', 40 | '/newCard': 'http://localhost:3000', 41 | '/getCards': 'http://localhost:3000', 42 | }, 43 | historyApiFallback: true, 44 | }, 45 | resolve: { 46 | extensions: ['.js', '.jsx'], 47 | }, 48 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mnemozine", 3 | "version": "1.0.0", 4 | "description": "Create and read texts in a new mnemonic medium", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "concurrently \"webpack-dev-server --open --hot\" \"nodemon ./server/server.js\"" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/kruckenberg/mnemozine.git" 12 | }, 13 | "author": "Nicholas Kruckenberg", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/kruckenberg/mnemozine/issues" 17 | }, 18 | "homepage": "https://github.com/kruckenberg/mnemozine#readme", 19 | "dependencies": { 20 | "body-parser": "^1.19.0", 21 | "cookie-parser": "^1.4.5", 22 | "css-loader": "^4.2.1", 23 | "express": "^4.17.1", 24 | "html-react-parser": "^0.13.0", 25 | "pg": "^8.3.0", 26 | "react": "^16.13.1", 27 | "react-dom": "^16.13.1", 28 | "react-router-dom": "^5.2.0", 29 | "style-loader": "^1.2.1", 30 | "webpack": "^4.44.1" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.11.1", 34 | "@babel/preset-env": "^7.11.0", 35 | "@babel/preset-react": "^7.10.4", 36 | "babel-loader": "^8.1.0", 37 | "concurrently": "^5.3.0", 38 | "nodemon": "^2.0.4", 39 | "sass-loader": "^9.0.3", 40 | "webpack-cli": "^3.3.12", 41 | "webpack-dev-server": "^3.11.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/components/DisplayTexts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Redirect, useHistory } from 'react-router-dom'; 3 | 4 | const DisplayTexts = props => { 5 | const [texts, setTexts] = useState([]); 6 | const history = useHistory(); 7 | 8 | useEffect(() => { 9 | fetch('/getTexts') 10 | .then(queryResults => queryResults.json()) 11 | .then((queryResults) => { 12 | const textList = queryResults.map((text) => { 13 | return ( 14 | 22 | ) 23 | }); 24 | setTexts(textList); 25 | }); 26 | }, [setTexts]); 27 | 28 | const handleClick = (e) => { 29 | if (e.target.id === 'createNew') { 30 | history.push('/addText'); 31 | } else { 32 | history.push(`/mnemos/${e.target.id}`); 33 | } 34 | } 35 | 36 | return ( 37 | 38 | 39 |

Learn and Remember More

40 |
41 |

Choose a Mnemo

42 |
43 | 46 | { texts } 47 |
48 |
49 |
50 | ); 51 | } 52 | 53 | export default DisplayTexts; -------------------------------------------------------------------------------- /client/components/AddText.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | 4 | const AddText = props => { 5 | const [title, setTitle] = useState(''); 6 | const [body, setBody] = useState(''); 7 | const history = useHistory(); 8 | 9 | const onTitleChange = e => setTitle(e.target.value); 10 | const onBodyChange = e => setBody(e.target.value); 11 | 12 | const handleSubmit = e => { 13 | e.preventDefault(); 14 | 15 | const text = { title, body }; 16 | const postOptions = { 17 | method: 'POST', 18 | headers: { 'Content-Type': 'application/json' }, 19 | body: JSON.stringify(text), 20 | }; 21 | 22 | fetch('http://localhost:8080/newText', postOptions) 23 | .then(response => { 24 | return response.json() 25 | }) 26 | .then((response) => { 27 | setTitle(''); 28 | setBody(''); 29 | history.push(`/mnemos/edit/${response._id}`); 30 | }); 31 | } 32 | 33 | return ( 34 | 35 | 36 |

Learn and Remember More

37 |
38 |

Create a New Mnemo

39 | 44 |