Procházet zdrojové kódy

First mithril-based version

Benoît Hubert před 6 roky
rodič
revize
5f70d01ff9

+ 2 - 0
back/.gitignore

@@ -0,0 +1,2 @@
+public/dist/md-json/*.json
+

+ 14 - 0
back/index.js

@@ -1,6 +1,20 @@
 import express from 'express';
 import bodyParser from 'body-parser';
+import cors from 'cors';
 
 const app = express();
 
+const whitelist = ['http://localhost:8000'];
+const corsOptions = {
+  origin: (origin, callback) => {
+    if (whitelist.indexOf(origin) !== -1) {
+      callback(null, true)
+    } else {
+      callback(new Error('Not allowed by CORS'))
+    }
+  }
+};
+app.use(cors(corsOptions));
+app.use(express.static('public'));
+
 app.listen(5000);

+ 1 - 0
back/package.json

@@ -12,6 +12,7 @@
   "license": "ISC",
   "dependencies": {
     "body-parser": "^1.19.0",
+    "cors": "^2.8.5",
     "express": "^4.17.1"
   },
   "devDependencies": {

+ 0 - 0
back/public/dist/md-json/.gitkeep


+ 10 - 2
back/yarn.lock

@@ -1089,6 +1089,14 @@ core-util-is@~1.0.0:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
+cors@^2.8.5:
+  version "2.8.5"
+  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+  dependencies:
+    object-assign "^4"
+    vary "^1"
+
 create-error-class@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
@@ -2263,7 +2271,7 @@ number-is-nan@^1.0.0:
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
   integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
 
-object-assign@^4.1.0:
+object-assign@^4, object-assign@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -3122,7 +3130,7 @@ v8flags@^3.1.1:
   dependencies:
     homedir-polyfill "^1.0.1"
 
-vary@~1.1.2:
+vary@^1, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
front/src/resources/markdown.json


+ 8 - 0
mithril-front/.babelrc

@@ -0,0 +1,8 @@
+{
+  "presets": ["@babel/env"],
+  "plugins": [
+    ["@babel/transform-react-jsx", {
+      "pragma": "m"
+    }]
+  ]
+}

+ 1 - 0
mithril-front/.gitignore

@@ -0,0 +1 @@
+dist/bin

+ 65 - 0
mithril-front/dist/css/styles.css

@@ -0,0 +1,65 @@
+body,
+.input,
+.button {
+  font: normal 16px Helvetica, Arial, Verdana, Tahoma, sans-serif;
+  margin: 0;
+}
+
+.layout {
+  margin: 10px auto;
+  max-width: 1000px;
+}
+
+.menu {
+  margin: 0 0 30px;
+}
+
+.user-list {
+  list-style: none;
+  margin: 0 0 10px;
+  padding: 0;
+}
+
+.user-list-item {
+  background: #fafafa;
+  border: 1px solid #ddd;
+  color: #333;
+  display: block;
+  margin: 0 0 1px;
+  padding: 8px 15px;
+  text-decoration: none;
+}
+
+.user-list-item:hover {
+  text-decoration: underline;
+}
+
+.label {
+  display: block;
+  margin: 0 0 5px;
+}
+
+.input {
+  border: 1px solid #ddd;
+  border-radius: 3px;
+  box-sizing: border-box;
+  display: block;
+  margin: 0 0 10px;
+  padding: 10px 15px;
+  width: 100%;
+}
+
+.button {
+  background: #eee;
+  border: 1px solid #ddd;
+  border-radius: 3px;
+  color: #333;
+  display: inline-block;
+  margin: 0 0 10px;
+  padding: 10px 15px;
+  text-decoration: none;
+}
+
+.button:hover {
+  background: #e8e8e8;
+}

+ 12 - 0
mithril-front/dist/index.html

@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <title>Let's Build A Compiler</title>
+        <link href="css/styles.css" rel="stylesheet" />
+    </head>
+    <body>
+        <script src="dist/bin/app.js"></script>
+    </body>
+</html>

+ 38 - 0
mithril-front/package.json

@@ -0,0 +1,38 @@
+{
+  "name": "mithril-tutorial",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "npx webpack-dev-server --mode development",
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "lint": "./node_modules/eslint/bin/eslint.js src/",
+    "lint-fix": "./node_modules/eslint/bin/eslint.js --fix src/",
+    "lint-css": "./node_modules/stylelint/bin/stylelint.js --fix css/*.css"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "express": "^4.16.4",
+    "marked": "^0.7.0",
+    "mithril": "^2.0.3"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.4.4",
+    "@babel/plugin-transform-react-jsx": "^7.3.0",
+    "@babel/preset-env": "^7.4.4",
+    "babel-loader": "^8.0.5",
+    "eslint": "^5.16.0",
+    "eslint-config-airbnb-base": "^13.1.0",
+    "eslint-plugin-import": "^2.17.2",
+    "eslint-plugin-mithril": "^0.1.1",
+    "eslint-plugin-prefer-import": "0.0.1",
+    "stylelint": "^10.0.1",
+    "stylelint-config-standard": "^18.3.0",
+    "webpack": "^4.30.0",
+    "webpack-cli": "^3.3.2",
+    "webpack-dev-server": "^3.8.0",
+    "webpack-node-externals": "^1.7.2"
+  }
+}

+ 21 - 0
mithril-front/src/index.js

@@ -0,0 +1,21 @@
+import m from 'mithril';
+import UserList from './views/UserList';
+import UserForm from './views/UserForm';
+import Layout from './views/Layout';
+
+// remove hashbang (later)
+// m.route.prefix('#/');
+
+m.route(document.body, '/', {
+  '/': {
+    render() {
+      return m(Layout, m(UserList));
+    }
+  },
+  '/view/:id': {
+    render(vnode) {
+      console.log(vnode.attrs);
+      return m(Layout, m(UserForm, vnode.attrs));
+    }
+  }
+});

+ 39 - 0
mithril-front/src/models/User.js

@@ -0,0 +1,39 @@
+// src/models/User.js
+import m from 'mithril';
+
+const User = {
+  list: [],
+  current: null,
+
+  loadList() {
+    return m.request({
+      method: 'GET',
+      url: 'http://localhost:5000/dist/md-json/index.json',
+    })
+      .then((list) => {
+        User.list = list;
+      });
+  },
+
+  load(id) {
+    return m.request({
+      method: 'GET',
+      url: `http://localhost:5000/dist/md-json/${id}.json`,
+    })
+      .then((result) => {
+        console.log(id, result);
+        User.current = result;
+      });
+  },
+
+  save() {
+    return m.request({
+      method: 'PUT',
+      url: `https://rem-rest-api.herokuapp.com/api/users/${User.current.id}`,
+      data: User.current,
+      withCredentials: true,
+    });
+  }
+};
+
+export default User;

+ 15 - 0
mithril-front/src/views/Layout.js

@@ -0,0 +1,15 @@
+// src/views/Layout.js
+import m from 'mithril';
+
+export default {
+  view(vnode) {
+    return (
+      <main class="layout">
+        <nav class="menu">
+          <a href="/list" oncreate={m.route.link}>Users</a>
+        </nav>
+        <section>{vnode.children}</section>
+      </main>
+    );
+  }
+};

+ 50 - 0
mithril-front/src/views/UserForm.js

@@ -0,0 +1,50 @@
+// eslint-disable-next-line no-unused-vars
+import m from 'mithril';
+import marked from 'marked';
+import User from '../models/User';
+
+export default {
+  oninit(vnode) { User.load(vnode.attrs.id); },
+  handleSubmit(e) {
+    e.preventDefault();
+    User.save();
+  },
+  handleChange(e) {
+    const { target: { name: key, value } } = e;
+    User.current[key] = value;
+  },
+  view() {
+    console.log('view', User.current);
+    if (!User.current) {
+      return (
+        <div />
+      );
+    }
+    return m("div", m.trust(marked(User.current[0].content)));
+    // return (
+    //   <form onsubmit={this.handleSubmit}>
+    //     <label class="label">First name</label>
+    //     <input
+    //       class="input"
+    //       type="text"
+    //       name="firstName"
+    //       placeholder="First name"
+    //       oninput={this.handleChange}
+    //       value={User.current.firstName}
+    //     />
+    //     <label class="label">Last name</label>
+    //     <input
+    //       class="input"
+    //       type="text"
+    //       name="lastName"
+    //       placeholder="Last name"
+    //       oninput={this.handleChange}
+    //       value={User.current.lastName}
+    //     />
+
+    //     <button class="button" type="submit">Save</button>
+
+    //   </form>
+    // );
+  }
+};

+ 27 - 0
mithril-front/src/views/UserList.js

@@ -0,0 +1,27 @@
+// src/views/UserList.js
+import m from 'mithril';
+import User from '../models/User';
+
+export default {
+  oninit: User.loadList,
+  view() {
+    return (
+      <div class="user-list">
+      {
+        User.list.map(
+          user => (
+            <a
+              key={user.id}
+              class="user-list-item"
+              href={`#/view/${user.slug}`}
+              oncreate={m.route.link}
+            >
+              {user.title}
+            </a>
+          )
+        )
+      }
+      </div>
+    );
+  },
+};

+ 27 - 0
mithril-front/webpack.config.js

@@ -0,0 +1,27 @@
+// eslint-disable-next-line prefer-import/prefer-import-over-require
+const path = require('path');
+
+module.exports = {
+  mode: 'development',
+  entry: './src/index.js',
+  output: {
+    path: path.resolve(__dirname, './dist/bin'),
+    publicPath: '/dist/bin/',
+    filename: 'app.js',
+  },
+  devServer: {
+    contentBase: path.join(__dirname, './dist/'),
+    compress: true,
+    port: 8000,
+    historyApiFallback: {
+      index: 'index.html'
+    }
+  },
+  module: {
+    rules: [{
+      test: /\.js$/,
+      exclude: /node_modules/,
+      loader: 'babel-loader'
+    }]
+  }
+};

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 6575 - 0
mithril-front/yarn.lock


+ 5 - 5
tutorial/md-sections-tool/extract.js

@@ -12,8 +12,8 @@ function extractTitleAndPath(markdown) {
   } catch(e) {
     throw new Error('Could not find h1 title');
   }
-  const path = `/${makeSlug(title)}`;
-  return { title, path };
+  const slug = makeSlug(title);
+  return { title, slug };
 }
 
 function findNextSubtitleIndex(lines) {
@@ -53,15 +53,15 @@ function extractSections(markdown) {
 
   return titles.map((title, i) => ({
     title,
-    path: `/${makeSlug(title)}`,
+    slug: makeSlug(title),
     content: contents[i]
   }));
 }
 
 function extract(markdown) {
-  const { title, path } = extractTitleAndPath(markdown);
+  const { title, slug } = extractTitleAndPath(markdown);
   const items = extractSections(markdown);
-  return { title, path, items };
+  return { title, slug, items };
 }
 
 module.exports = extract;

+ 18 - 3
tutorial/md-sections-tool/extractAll.js

@@ -10,7 +10,21 @@ const writeFileAsync = promisify(fs.writeFile);
 
 // Source and destination paths
 const markdownPath = path.resolve(__dirname, '../markdown');
-const jsonPath = path.resolve(__dirname, '../../front/src/resources/markdown.json');
+const jsonPath = path.resolve(__dirname, '../../back/public/dist/md-json');
+
+async function writeJson(tuto) {
+  // console.log(tuto.title, tuto.slug, tuto.items.map(({ title }) => title));
+  const filePath = path.join(jsonPath, `${tuto.slug}.json`);
+  const json = JSON.stringify(tuto.items);
+  return writeFileAsync(filePath, json);
+}
+
+async function writeJsonIndex(tutos) {
+  const filePath = path.join(jsonPath, 'index.json');
+  const index = tutos.map(({ title, slug }) => ({ title, slug }));
+  const json = JSON.stringify(index);
+  return writeFileAsync(filePath, json);
+}
 
 async function run(filterFunc) {
   const allFiles = await readdirAsync(markdownPath);
@@ -27,8 +41,9 @@ async function run(filterFunc) {
     }
   });
   const tutos = await Promise.all(promises);
-  const json = JSON.stringify(tutos);
-  await writeFileAsync(jsonPath, json);
+  const writePromises = tutos.map(writeJson);
+  writePromises.push(writeJsonIndex(tutos));
+  await Promise.all(writePromises);
   process.exit();
 }