Benoit Sida 3 년 전
부모
커밋
609689be9a

+ 6
- 5
package.json 파일 보기

@@ -4,11 +4,9 @@
4 4
   "description": "",
5 5
   "main": "index.js",
6 6
   "scripts": {
7
-    "test": "echo \"Error: no test specified\" && exit 1",
8 7
     "dev": "node_modules/.bin/webpack-dev-server --progress --colors --hot --config ./webpack.config.js",
9
-    "server": "NODE_ENV=production node_modules/babel-cli/bin/babel-node.js --presets env server.js",
10
-    "build": "node_modules/.bin/webpack",
11
-    "start": "npm run build && npm run server"
8
+    "build": "NODE_ENV=production node_modules/.bin/webpack",
9
+    "start": "npm run dev"
12 10
   },
13 11
   "repository": {
14 12
     "type": "git",
@@ -36,10 +34,13 @@
36 34
   },
37 35
   "dependencies": {
38 36
     "@blueprintjs/core": "^1.16.0",
37
+    "axios": "^0.16.1",
39 38
     "babel-cli": "^6.24.1",
40 39
     "classnames": "^2.2.5",
41
-    "express": "^4.15.2",
40
+    "firebase": "^3.9.0",
42 41
     "font-awesome": "^4.7.0",
42
+    "lodash": "^4.17.4",
43
+    "markdown-it": "^8.3.1",
43 44
     "react": "^15.5.4",
44 45
     "react-addons-css-transition-group": "^15.5.2",
45 46
     "react-dom": "^15.5.4",

+ 0
- 22
server.js 파일 보기

@@ -1,22 +0,0 @@
1
-import path from 'path';
2
-import Express from 'express';
3
-
4
-// initialize the server and configure support for ejs templates
5
-const app = new Express();
6
-app.set('views', path.join(__dirname, 'views'));
7
-
8
-// define the folder that will be used for static assets
9
-app.use(Express.static(path.join(__dirname, 'dist')));
10
-
11
-// universal routing and rendering
12
-app.get('/user', () => console.log('api call'));
13
-
14
-// start the server
15
-const port = process.env.PORT || 3000;
16
-const env = process.env.NODE_ENV || 'production';
17
-app.listen(port, err => {
18
-  if (err) {
19
-    return console.error(err);
20
-  }
21
-  console.info(`Server running on http://localhost:${port} [${env}]`);
22
-});

+ 0
- 8
src/App.jsx 파일 보기

@@ -8,14 +8,6 @@ import { LOGIN } from './Store/Actions'
8 8
 import Login from './Pages/Login/Login'
9 9
 import './assets'
10 10
 
11
-const USER = {
12
-    username: 'Coldiary',
13
-    email: 'coldiary@orange.fr',
14
-    links: {
15
-        twitter: 'coldiary'
16
-    }
17
-}
18
-
19 11
 const App = ({ user, login }) => {
20 12
     if (!user)
21 13
         return <Login login={() => login(USER)} />

+ 12
- 2
src/App.scss 파일 보기

@@ -1,13 +1,23 @@
1 1
 @import '~@blueprintjs/core/dist/variables.scss';
2 2
 
3
+html, body {
4
+    margin: 0;
5
+}
6
+
7
+.pt-toast-container.pt-toast-container-top.app-toaster {
8
+    top: $pt-navbar-height;
9
+}
10
+
11
+.pt-non-ideal-state {
12
+    height: inherit;
13
+}
14
+
3 15
 .flex-frame {
4 16
     display: flex;
5 17
     flex-direction: column;
6 18
     min-height: 100vh;
7 19
     background: $pt-app-background-color;
8 20
 
9
-    
10
-
11 21
     main {
12 22
         flex: 1;
13 23
         padding: 50px;

+ 1
- 1
src/Header/NavLinkBP.jsx 파일 보기

@@ -1,7 +1,7 @@
1 1
 import React from 'react'
2 2
 import { NavLink } from 'react-router-dom'
3 3
 
4
-const NavLinkBP = ({path, label, icon, exact}) =>
4
+const NavLinkBP = ({ path, label, icon, exact, location }) =>
5 5
     <NavLink to={path} {...{exact}}
6 6
         activeClassName="pt-active"
7 7
         className={"pt-button pt-minimal pt-icon-" + icon}>

+ 8
- 8
src/Header/Topbar.jsx 파일 보기

@@ -1,5 +1,6 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import { withRouter } from 'react-router'
3 4
 import { Popover, PopoverInteractionKind, Position } from '@blueprintjs/core'
4 5
 import { Navbar, NavbarGroup, NavbarDivider, NavbarHeading } from './Navbar'
5 6
 import NavLinkBP from './NavLinkBP'
@@ -7,13 +8,14 @@ import UserMenu from './UserMenu'
7 8
 import { LOGOUT } from '../Store/Actions'
8 9
 import Notifications from './Notifications'
9 10
 
10
-const TopbarTemplate = ({ logout }) =>
11
-    <div>
11
+const TopbarTemplate = ({ username, logout }) =>
12 12
         <Navbar dark>
13 13
             <NavbarGroup align="left">
14 14
                 <NavbarHeading>Lighthouse</NavbarHeading>
15 15
                 <NavLinkBP path="/" label="Home" icon="home" exact/>
16
-                <NavLinkBP path="/projects" label="Projects" icon="document"/>
16
+                <NavLinkBP path="/projects" label="Projects" icon="projects"/>
17
+                <NavLinkBP path="/notes" label="Notes" icon="clipboard"/>
18
+                <NavLinkBP path="/todos" label="Todos" icon="property"/>
17 19
             </NavbarGroup>
18 20
             <NavbarGroup align="right">
19 21
                 <input className="pt-input" placeholder="Search files..." type="text" />
@@ -23,15 +25,13 @@ const TopbarTemplate = ({ logout }) =>
23 25
                         interactionKind={PopoverInteractionKind.HOVER}>
24 26
                     <button className="pt-button pt-minimal pt-icon-notifications"></button>
25 27
                 </Popover>
26
-                <Popover content={UserMenu({logout})} position={Position.BOTTOM_RIGHT}
28
+                <Popover content={<UserMenu />} position={Position.BOTTOM_RIGHT}
27 29
                         interactionKind={PopoverInteractionKind.CLICK}>
28
-                    <button className="pt-button pt-minimal pt-icon-user"></button>
30
+                    <button className="pt-button pt-minimal pt-icon-user">{username}</button>
29 31
                 </Popover>
30 32
             </NavbarGroup>
31 33
         </Navbar>
32
-    </div>
33 34
 
34
-const mapDispatchToProps = dispatch => ({ logout: () => { console.log('dispatching'); dispatch(LOGOUT()) } })
35
-const Topbar = connect(null, mapDispatchToProps)(TopbarTemplate)
35
+const Topbar = withRouter(connect(state => ({ username: state.user.displayName }))(TopbarTemplate))
36 36
 
37 37
 export default Topbar

+ 13
- 8
src/Header/UserMenu.jsx 파일 보기

@@ -1,14 +1,19 @@
1 1
 import React from 'react'
2 2
 import classNames from 'classnames'
3
+import firebase from 'firebase'
3 4
 import { Menu, MenuItem, MenuDivider } from "@blueprintjs/core"
4 5
 import { MenuLink } from '../Shared/Components'
5 6
 
6
-const userMenu = ({ logout }) => 
7
-    <Menu>
8
-        <MenuLink to="/user" iconName="user">Profile</MenuLink>
9
-        <MenuLink to="/settings" iconName="cog">Settings</MenuLink>
10
-        <MenuDivider />
11
-        <MenuItem text="Log out" iconName="power" onClick={() => { console.log('loginout'); logout() }} />
12
-    </Menu>
7
+const UserMenu = () => {
8
+    const logout = () => firebase.auth().signOut()
9
+    return (
10
+        <Menu>
11
+            <MenuLink to="/user" iconName="user">Profile</MenuLink>
12
+            <MenuLink to="/settings" iconName="cog">Settings</MenuLink>
13
+            <MenuDivider />
14
+            <MenuItem text="Log out" iconName="power" onClick={logout} />
15
+        </Menu>
16
+    )
17
+}
13 18
 
14
-export default userMenu
19
+export default UserMenu

+ 15
- 8
src/Pages/Login/Login.jsx 파일 보기

@@ -1,9 +1,12 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import axios from 'axios'
4
+import firebase from 'firebase'
3 5
 import { Card } from '../../Shared/Components'
4 6
 import { InputGroup, Tooltip, Button, Classes, Intent } from '@blueprintjs/core'
5 7
 import { LOGIN } from '../../Store/Actions'
6 8
 import './Login.scss'
9
+import { Map } from '../../utils'
7 10
 
8 11
 const PasswordToggler = ({show, toggle}) => 
9 12
     <Tooltip content={`${show ? "Hide" : "Show"} Password`}>
@@ -18,7 +21,7 @@ const LoginTemplate = ({ togglePassword, showPassword, connect }) =>
18 21
         <Logo />
19 22
         <Card>
20 23
             <form onSubmit={e => connect('test', e)}>
21
-                <InputGroup name="username" placeholder="Username" leftIconName="user"/>
24
+                <InputGroup name="email" placeholder="Email" leftIconName="user"/>
22 25
                 <InputGroup name="password" placeholder="Enter your password..." leftIconName="lock" 
23 26
                     type={showPassword ? "text" : "password"}
24 27
                     rightElement={<PasswordToggler show={showPassword} toggle={togglePassword} />}
@@ -28,16 +31,16 @@ const LoginTemplate = ({ togglePassword, showPassword, connect }) =>
28 31
         </Card>
29 32
     </div>
30 33
 
31
-export default class Login extends React.Component {
34
+class LoginContainer extends React.Component {
32 35
     state = { showPassword: false }
33 36
 
34 37
     togglePassword = () => this.setState({ showPassword: !this.state.showPassword })
35 38
 
36
-    connect(test, e) {
37
-        e.preventDefault();
38
-        let data = new Map(new FormData(e.target));
39
-        console.log(data);
40
-        return false;
39
+    connect = (test, e) => {
40
+        e.preventDefault()
41
+        let data = (new Map(new FormData(e.target))).toObj()
42
+        firebase.auth().signInWithEmailAndPassword(data.email, data.password)
43
+                .catch(e => { console.error(`Firebase auth error: [${e.code}] ${e.message}`) })
41 44
     }
42 45
 
43 46
     render() {
@@ -47,4 +50,8 @@ export default class Login extends React.Component {
47 50
             connect={this.connect}
48 51
         />
49 52
     }
50
-}
53
+}
54
+
55
+const Login = connect(null, dispatch => ({ login: user => dispatch(LOGIN(user)) }))(LoginContainer)
56
+
57
+export default Login

+ 4
- 0
src/Pages/Login/Login.scss 파일 보기

@@ -24,6 +24,10 @@
24 24
             .pt-input-group {
25 25
                 margin-bottom: $pt-grid-size;
26 26
             }
27
+
28
+            form {
29
+                margin: 0;
30
+            }
27 31
         }
28 32
 
29 33
 

+ 88
- 0
src/Pages/Notes/Notes.jsx 파일 보기

@@ -0,0 +1,88 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import MarkdownIt from 'markdown-it'
4
+import { Card, Placeholder, Icon } from '../../Shared/Components'
5
+import { Menu, MenuDivider, MenuItem, EditableText, Intent, Button, Alert } from '@blueprintjs/core'
6
+import { SELECT_NOTE, UPDATE_NOTE, UPDATE_SELECTED_NOTE, ADD_NOTE, REMOVE_NOTE, UNSELECT_NOTE } from '../../Store/Actions'
7
+import './Notes.scss'
8
+
9
+const md = new MarkdownIt()
10
+
11
+const NotesList = ({ notes, select }) =>
12
+    <div>
13
+        { notes.map(e => <MenuItem key={e.id} text={e.title} onClick={() => select(e)}></MenuItem>) }
14
+    </div>
15
+
16
+class AlertButton extends React.Component {
17
+    state = { open: false }
18
+
19
+    close = () => this.setState({ open: false })
20
+
21
+    show = () => this.setState({ open: true })
22
+
23
+    confirm = () => {
24
+        this.props.onConfirm()
25
+        this.close();
26
+    }
27
+
28
+    render() {
29
+        return (
30
+            <Button iconName={this.props.iconName} className={this.props.className} onClick={this.show}>
31
+                <Alert intent={Intent.DANGER} isOpen={this.state.open}
32
+                    confirmButtonText={this.props.confirmButtonText} cancelButtonText={this.props.cancelButtonText}
33
+                    onConfirm={this.confirm} onCancel={this.close} >
34
+                    {this.props.children}
35
+                </Alert>
36
+            </Button>
37
+        );
38
+    }
39
+}
40
+
41
+const Note = ({ note, update, remove }) =>
42
+    <div className="Note">
43
+        <AlertButton iconName="trash" className="trash-btn"
44
+            confirmButtonText="delete" cancelButtonText={"Cancel"}
45
+            onConfirm={() => remove(note)}>
46
+            Do you really want to delete this note ? You won't be able to recover it.
47
+        </AlertButton>
48
+        <h2>{note.title}</h2>
49
+        <div className="editor">
50
+            <EditableText className="source" multiline value={note.content} onChange={value => update({...note, content: value})}></EditableText>
51
+            <div className="preview" dangerouslySetInnerHTML={{__html: md.render(note.content)}}></div>
52
+        </div>
53
+    </div>
54
+
55
+
56
+const NotesTemplate = ({ notes, selectedNote, selectNote, updateNote, addNote, removeNote }) =>
57
+    <div className="Notes">
58
+        <Card className="List">
59
+            <Menu>
60
+                <MenuDivider title="Notes"/>
61
+                <NotesList notes={notes} select={selectNote}/>
62
+                <MenuDivider />
63
+                <MenuItem iconName="plus" text="New note" onClick={addNote}/>
64
+            </Menu>
65
+        </Card>
66
+        <Card className="content">
67
+            { selectedNote ? <Note note={selectedNote} update={updateNote} remove={removeNote} /> :
68
+                <Placeholder iconName="clipboard" title="No note selected" description="Choose one in the list on the left"/> }
69
+        </Card>
70
+    </div>
71
+
72
+const Notes = connect(
73
+    state => ({ notes: state.note.notes, selectedNote: state.note.selectedNote }),
74
+    dispatch => ({
75
+        addNote: () => dispatch(ADD_NOTE()),
76
+        selectNote: note => dispatch(SELECT_NOTE(note)),
77
+        removeNote: note => {
78
+            dispatch(REMOVE_NOTE(note))
79
+            dispatch(UNSELECT_NOTE())
80
+        },
81
+        updateNote: note => {
82
+            dispatch(UPDATE_NOTE(note))
83
+            dispatch(UPDATE_SELECTED_NOTE(note))
84
+        }
85
+    })
86
+)(NotesTemplate)
87
+
88
+export default Notes

+ 36
- 0
src/Pages/Notes/Notes.scss 파일 보기

@@ -0,0 +1,36 @@
1
+@import '~@blueprintjs/core/dist/variables.scss';
2
+
3
+.Notes {
4
+    display: flex;
5
+    align-items: flex-start;
6
+
7
+    .list {
8
+        width: 200px;
9
+        margin-right: $pt-grid-size;
10
+        box-sizing: content-box;
11
+    }
12
+
13
+    .content {
14
+        margin-left: $pt-grid-size;
15
+        flex: 1;
16
+        max-height: 300px;
17
+
18
+        .Note {
19
+            .trash-btn {
20
+                float: right;
21
+            }
22
+            
23
+            .editor {
24
+                display: flex;
25
+                flex-direction: row;
26
+                margin-top: $pt-grid-size * 4;
27
+
28
+                .source, .preview {
29
+                    flex: 1;
30
+                    margin: 30px;
31
+                }
32
+            }
33
+        }
34
+    }
35
+
36
+}

+ 11
- 12
src/Pages/User/User.scss 파일 보기

@@ -1,31 +1,26 @@
1 1
 .User {
2
-    display: flex;
3
-    flex-direction: row;
4
-    align-content: flex-start;
5
-    justify-content: flex-start;
6
-
7 2
     .profile-pic {
8 3
         width: 200px;
9 4
         height: 200px;
5
+        float: left;
6
+        margin-right: 50px;
7
+
8
+        img { width: 100%; }
10 9
     }
11 10
 
12 11
     .profile-info {
13 12
         display: flex;
14
-        margin-left: 50px;
13
+        
15 14
         flex-direction: column;
16 15
         justify-content: space-around;
17 16
 
18
-        h3 {
19
-            margin-bottom: 30px;
20
-        }
17
+        h3 { margin-bottom: 30px; }
21 18
 
22 19
         .social-columns {
23 20
             display: flex;
24 21
             flex-wrap: wrap;
25 22
 
26
-            & > h4 {
27
-                margin: 20px;
28
-            }
23
+            & > h4 { margin: 20px; }
29 24
 
30 25
             .fa {
31 26
                 margin-right: 20px;
@@ -33,6 +28,10 @@
33 28
             }
34 29
         }
35 30
 
31
+        .actions {
32
+            text-align: right;
33
+        }
34
+
36 35
 
37 36
         .pt-label > input.pt-input {
38 37
             margin-left: 20px;

+ 39
- 6
src/Pages/User/UserInfo.jsx 파일 보기

@@ -1,13 +1,19 @@
1 1
 import React from 'react'
2
+import { connect } from 'react-redux'
2 3
 import FontAwesome from 'react-fontawesome'
3
-import { EditableText } from "@blueprintjs/core"
4
+import firebase from 'firebase'
5
+import { EditableText, Button, Intent } from "@blueprintjs/core"
6
+import { Card } from '../../Shared/Components'
7
+import AppToaster from '../../Shared/Toaster'
4 8
 
5 9
 const SocialLink = ({ iconName, ...otherProps }) => <h4><FontAwesome name={iconName} /><EditableText {...otherProps}/></h4>
6 10
 
7
-const UserInfo = () => 
8
-    <div className="profile-info">
9
-        <h3><EditableText placeholder="Username"/></h3>
10
-        <h3><EditableText placeholder="Email"/></h3>
11
+const UserInfoTemplate = ({ user, changeProfile, changeEmail, save }) => 
12
+    <Card className="profile-info">
13
+        <h3><EditableText placeholder="Display Name" value={user.displayName}
14
+            onChange={value => changeProfile({ displayName: value})}/></h3>
15
+        <h3><EditableText placeholder="Email" value={user.email}
16
+            onChange={value => changeEmail(value)}/></h3>
11 17
         <div className="social-columns">
12 18
             <SocialLink iconName="twitter" placeholder="Twitter" />
13 19
             <SocialLink iconName="facebook" placeholder="Facebook"/>
@@ -16,7 +22,34 @@ const UserInfo = () =>
16 22
             <SocialLink iconName="github" placeholder="Github"/>
17 23
             <SocialLink iconName="globe" placeholder="Website"/>
18 24
         </div>
19
-    </div>
25
+        <div className="actions">
26
+            <Button onClick={save} intent={Intent.PRIMARY}>Save</Button>
27
+        </div>
28
+    </Card>
29
+
30
+class UserInfoContainer extends React.Component {
31
+    state = { user: this.props.user }
32
+
33
+    changeEmail = email => this.setState({ user: {...this.state.user, email } })
20 34
     
35
+    changeProfile = profile => this.setState({ user: {...this.state.user, ...profile } })
36
+
37
+    save = () => {
38
+        if (this.state.user.email != this.props.user.email)
39
+            firebase.auth().currentUser.updateEmail(this.state.user.email)
40
+                    .then(AppToaster.show({ message: 'Email updated'}))
41
+        if (this.state.user.displayName != this.props.user.displayName)
42
+            firebase.auth().currentUser.updateProfile({ displayName: this.state.user.displayName })
43
+                    .then(AppToaster.show({ message: 'Display name updated'}))
44
+    }
45
+
46
+    render() {
47
+        return <UserInfoTemplate user={this.state.user} save={this.save} 
48
+            changeEmail={this.changeEmail} changeProfile={this.changeProfile}
49
+        />
50
+    }
51
+}
52
+
53
+const UserInfo = connect(state => ({ user: state.user }))(UserInfoContainer)
21 54
 
22 55
 export default UserInfo

+ 31
- 4
src/Pages/User/UserPic.jsx 파일 보기

@@ -1,9 +1,36 @@
1 1
 import React from 'react'
2
-import { Card, Placeholder } from '../../Shared/Components'
2
+import firebase from 'firebase'
3
+import { connect } from 'react-redux'
4
+import { Card, ProfilePic } from '../../Shared/Components'
5
+import UserPicChange from './UserPicChange'
6
+import { CHANGE_PIC } from '../../Store/Actions'
7
+import AppToaster from '../../Shared/Toaster'
3 8
 
4
-const UserPic = () => 
5
-    <Card className="profile-pic" interactive>
6
-        <Placeholder iconName="mugshot" title="No picture" description="Click to change"/>
9
+const UserPicTemplate = ({ pic, dialogOpen, toggleDialog, changePic }) => 
10
+    <Card className="profile-pic" interactive onClick={toggleDialog}>
11
+        <ProfilePic src={pic} />
12
+        <UserPicChange save={changePic} cancel={toggleDialog} open={dialogOpen}/>
7 13
     </Card>
8 14
 
15
+class UserPicContainer extends React.Component {
16
+    state = { dialogOpen: false }
17
+
18
+    toggleDialog = () => this.setState({ dialogOpen: !this.state.dialogOpen })
19
+
20
+    changePic = url => firebase.auth().currentUser.updateProfile({ photoURL: url })
21
+                               .then(() => this.props.updatePic(url))
22
+                               .then(this.toggleDialog)
23
+                               .then(AppToaster.show({ message: 'Picture updated'}))
24
+
25
+    render() {
26
+        return <UserPicTemplate pic={this.props.pic} dialogOpen={this.state.dialogOpen}
27
+            toggleDialog={this.toggleDialog} changePic={this.changePic}/>
28
+    }
29
+}
30
+
31
+const UserPic = connect(
32
+    state => ({ pic: state.user.photoURL }),
33
+    dispatch => ({ updatePic: url => dispatch(CHANGE_PIC(url)) })
34
+)(UserPicContainer)
35
+
9 36
 export default UserPic;

+ 28
- 0
src/Pages/User/UserPicChange.jsx 파일 보기

@@ -0,0 +1,28 @@
1
+import React from 'react'
2
+import { InputGroup, Dialog, Intent, Button } from '@blueprintjs/core'
3
+
4
+const UserPicChangeTemplate = ({ value, change, save, cancel, open }) =>
5
+    <Dialog iconName="inbox" isOpen={open} onClose={cancel} title="Change profile picture">
6
+        <div className="pt-dialog-body">
7
+            <InputGroup name="picURL" placeholder="Picture URL" leftIconName="mugshot" value={value} onChange={e => change(e.target.value)} />
8
+        </div>
9
+        <div className="pt-dialog-footer">
10
+            <div className="pt-dialog-footer-actions">
11
+                <Button text="Cancel" onClick={cancel} />
12
+                <Button intent={Intent.PRIMARY} onClick={save} text="Save" />
13
+            </div>
14
+        </div>
15
+    </Dialog>
16
+
17
+class UserPicChange extends React.Component {
18
+    state = { value: '' }
19
+
20
+    render() {
21
+        return <UserPicChangeTemplate open={this.props.open}
22
+            value={this.state.value} change={value => this.setState({ value })}
23
+            save={() => this.props.save(this.state.value)} cancel={this.props.cancel} 
24
+        />
25
+    }
26
+}
27
+
28
+export default UserPicChange

+ 5
- 1
src/Routes.jsx 파일 보기

@@ -2,15 +2,19 @@ import React from 'react'
2 2
 import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
3 3
 
4 4
 import User from './Pages/User/User'
5
+import Notes from './Pages/Notes/Notes'
5 6
 
6 7
 const Home = () => <div>Home</div>
7 8
 const Projects = () => <div>Projects</div>
9
+const Todos = () => <div>Todos</div>
8 10
 const Settings = () => <div>Settings</div>
9 11
 
10 12
 const Routes = () => 
11 13
     <div>
12 14
         <Route path="/"         component={Home} exact />
13
-        <Route path="/projects"    component={Projects} />
15
+        <Route path="/projects" component={Projects} />
16
+        <Route path="/notes"    component={Notes} />
17
+        <Route path="/todos"    component={Todos} />
14 18
         <Route path="/user"     component={User} />
15 19
         <Route path="/settings" component={Settings} />
16 20
     </div>

+ 19
- 6
src/Shared/Components.jsx 파일 보기

@@ -2,11 +2,21 @@ import React from 'react'
2 2
 import cl from 'classnames'
3 3
 import { Link } from 'react-router-dom'
4 4
 
5
-export const Card = ({ className, children, interactive }) => 
6
-    <div className={cl(["pt-card", className, {'pt-interactive': interactive}])}>{children}</div>
5
+export const Icon = ({ name, size, intent, className, ...otherProps }) => {
6
+    const classes = cl({
7
+        [`pt-icon-${size}`]: size,
8
+        [`pt-icon-${name}`]: name,
9
+        [`pt-intent-${intent}`]: intent,
10
+        [className]: true
11
+    })
12
+    return <span className={classes} {...otherProps}></span>
13
+}
7 14
 
8
-export const Placeholder = ({ iconName, title, description }) => 
9
-    <div className="pt-non-ideal-state">
15
+export const Card = ({ className, children, interactive, ...otherProps }) => 
16
+    <div className={cl(["pt-card", className, {'pt-interactive': interactive}])} {...otherProps}>{children}</div>
17
+
18
+export const Placeholder = ({ iconName, title, description, ...otherProps }) => 
19
+    <div className="pt-non-ideal-state" {...otherProps} >
10 20
         <div className="pt-non-ideal-state-visual pt-non-ideal-state-icon">
11 21
             <span className={`pt-icon pt-icon-${iconName}`}></span>
12 22
         </div>
@@ -16,7 +26,10 @@ export const Placeholder = ({ iconName, title, description }) =>
16 26
         </div>
17 27
     </div>
18 28
 
19
-const MenuLink = ({ to, iconName, children, ...otherProps }) => {
29
+export const MenuLink = ({ to, iconName, children, ...otherProps }) => {
20 30
     let classes = cl(["pt-menu-item","pt-popover-dismiss", {[`pt-icon-${iconName}`]: iconName}]);
21 31
     return <li><Link to={to} className={classes} {...otherProps}>{children}</Link></li>
22
-}
32
+}
33
+
34
+export const ProfilePic = ({ src }) => src ? <img src={src} /> :
35
+    <Placeholder iconName="mugshot" title="No picture" description="Click to change"/>

+ 8
- 0
src/Shared/Toaster.jsx 파일 보기

@@ -0,0 +1,8 @@
1
+import { Position, Toaster } from "@blueprintjs/core";
2
+ 
3
+const AppToasts = Toaster.create({
4
+    className: "app-toaster",
5
+    position: Position.TOP_RIGHT,
6
+});
7
+
8
+export default AppToasts

+ 10
- 3
src/Store/Actions/index.js 파일 보기

@@ -1,3 +1,10 @@
1
-import { LOGIN, LOGOUT } from './user'
2
-import { TOGGLE_SHOW_PASSWORD } from './login'
3
-export { LOGIN, LOGOUT, TOGGLE_SHOW_PASSWORD }
1
+import { LOGIN, LOGOUT, CHANGE_PIC } from './user'
2
+import {
3
+    ADD_NOTE, REMOVE_NOTE, UPDATE_NOTE,
4
+    SELECT_NOTE, UNSELECT_NOTE, UPDATE_SELECTED_NOTE
5
+} from './notes'
6
+export {
7
+    LOGIN, LOGOUT, CHANGE_PIC,
8
+    ADD_NOTE, REMOVE_NOTE, UPDATE_NOTE,
9
+    SELECT_NOTE, UNSELECT_NOTE, UPDATE_SELECTED_NOTE
10
+}

+ 0
- 2
src/Store/Actions/login.js 파일 보기

@@ -1,2 +0,0 @@
1
-import { createAction } from 'redux-actions'
2
-export const TOGGLE_SHOW_PASSWORD = createAction('TOGGLE_SHOW_PASSWORD');

+ 7
- 0
src/Store/Actions/notes.js 파일 보기

@@ -0,0 +1,7 @@
1
+import { createAction } from 'redux-actions'
2
+export const ADD_NOTE = createAction('ADD_NOTE')
3
+export const REMOVE_NOTE = createAction('REMOVE_NOTE')
4
+export const UPDATE_NOTE = createAction('UPDATE_NOTE')
5
+export const SELECT_NOTE = createAction('SELECT_NOTE')
6
+export const UPDATE_SELECTED_NOTE = createAction('UPDATE_SELECTED_NOTE')
7
+export const UNSELECT_NOTE = createAction('UNSELECT_NOTE')

+ 3
- 2
src/Store/Actions/user.js 파일 보기

@@ -1,3 +1,4 @@
1 1
 import { createAction } from 'redux-actions'
2
-export const LOGIN = createAction('LOGIN');
3
-export const LOGOUT = createAction('LOGOUT');
2
+export const LOGIN = createAction('LOGIN')
3
+export const LOGOUT = createAction('LOGOUT')
4
+export const CHANGE_PIC = createAction('CHANGE_PIC')

+ 2
- 2
src/Store/Reducers/index.js 파일 보기

@@ -1,7 +1,7 @@
1 1
 import { combineReducers } from 'redux'
2 2
 import user from './user'
3
-import login from './login'
3
+import note from './note'
4 4
 
5
-const reducers = combineReducers({ user, login })
5
+const reducers = combineReducers({ user, note })
6 6
 
7 7
 export default reducers

+ 0
- 8
src/Store/Reducers/login.js 파일 보기

@@ -1,8 +0,0 @@
1
-import { handleActions } from 'redux-actions'
2
-import { TOGGLE_SHOW_PASSWORD } from '../Actions'
3
-
4
-const login = handleActions({
5
-  [TOGGLE_SHOW_PASSWORD]: (state, action) => ({ showPassword: !state.showPassword }),
6
-}, { showPassword: false });
7
-
8
-export default login

+ 28
- 0
src/Store/Reducers/note.js 파일 보기

@@ -0,0 +1,28 @@
1
+import { _ } from '../../utils'
2
+import { handleActions } from 'redux-actions'
3
+import { combineReducers } from 'redux'
4
+import {
5
+    ADD_NOTE, UPDATE_NOTE, UPDATE_SELECTED_NOTE,
6
+    REMOVE_NOTE, SELECT_NOTE, UNSELECT_NOTE
7
+} from '../Actions'
8
+
9
+const MOCK_NOTES = [
10
+    {id: _.uniqueId(), title: 'First note', content: 'This is a little note' },
11
+    {id: _.uniqueId(), title: 'Second note', content: '### This is a little md note' },
12
+]
13
+
14
+const selectedNote = handleActions({
15
+    [SELECT_NOTE]: (state, action) => state = action.payload,
16
+    [UPDATE_SELECTED_NOTE]: (state, action) => ({...state, ...action.payload }),
17
+    [UNSELECT_NOTE]: (state, action) => null
18
+}, null)
19
+
20
+const notes = handleActions({
21
+    [ADD_NOTE]: (state, action) => [...state, { id: _.uniqueId(), title: 'new_note', content: '' }],
22
+    [UPDATE_NOTE]: (state, action) => _.updateWhere(state, action.payload, _.idCheck(action.payload.id)),
23
+    [REMOVE_NOTE]: (state, action) =>  _.reject(state, _.idCheck(action.payload.id)),
24
+}, MOCK_NOTES)
25
+
26
+const note = combineReducers({ notes, selectedNote })
27
+
28
+export default note

+ 3
- 2
src/Store/Reducers/user.js 파일 보기

@@ -1,9 +1,10 @@
1 1
 import { handleActions } from 'redux-actions'
2
-import { LOGIN, LOGOUT } from '../Actions'
2
+import { LOGIN, LOGOUT, CHANGE_PIC } from '../Actions'
3 3
 
4 4
 const user = handleActions({
5 5
   [LOGIN]: (state, action) => action.payload,
6
-  [LOGOUT]: (state, action) => null
6
+  [LOGOUT]: (state, action) => null,
7
+  [CHANGE_PIC]: (state, action) => ({ ...state, photoURL: action.payload })
7 8
 }, null);
8 9
 
9 10
 export default user;

+ 10
- 0
src/Store/index.js 파일 보기

@@ -0,0 +1,10 @@
1
+import { createStore } from 'redux'
2
+import * as actions from './Actions'
3
+import reducers from './Reducers'
4
+
5
+const devToolsEnabled = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
6
+
7
+let Store = createStore(reducers, devToolsEnabled)
8
+
9
+export { actions }
10
+export default Store

+ 0
- 1
src/assets.js 파일 보기

@@ -1,4 +1,3 @@
1
-import "resetcss/reset.min.css"
2 1
 import "@blueprintjs/core/dist/blueprint.css"
3 2
 import 'font-awesome/css/font-awesome.min.css'
4 3
 import './App.scss'

+ 14
- 0
src/auth.js 파일 보기

@@ -0,0 +1,14 @@
1
+import firebase from 'firebase'
2
+import { dispatch } from 'redux'
3
+import Store, { actions } from './Store'
4
+import config from './config'
5
+
6
+firebase.initializeApp(config)
7
+
8
+firebase.auth().onAuthStateChanged(user => {
9
+  if (user) {
10
+    const { email, displayName, photoURL } = user
11
+    Store.dispatch(actions.LOGIN({ email, displayName, photoURL }))
12
+  } else if (Store.getState().user)
13
+    Store.dispatch(actions.LOGOUT())
14
+})

+ 8
- 0
src/config.js 파일 보기

@@ -0,0 +1,8 @@
1
+export default {
2
+    apiKey: "AIzaSyAMKhC-LLCylhigPv2TJgBV5Hl5BGBm8WQ",
3
+    authDomain: "lighthouse-aba3f.firebaseapp.com",
4
+    databaseURL: "https://lighthouse-aba3f.firebaseio.com",
5
+    projectId: "lighthouse-aba3f",
6
+    storageBucket: "lighthouse-aba3f.appspot.com",
7
+    messagingSenderId: "224395526411"
8
+}

+ 5
- 9
src/index.js 파일 보기

@@ -1,15 +1,11 @@
1 1
 import React from 'react'
2 2
 import ReactDOM from 'react-dom'
3
-import { createStore } from 'redux'
4 3
 import { Provider } from 'react-redux'
5
-import reducers from './Store/Reducers'
6 4
 import App from './App'
5
+import Store from './Store'
6
+import './auth'
7 7
 
8
-const devToolsEnabled = ENV != 'production' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
8
+ReactDOM.render(<Provider store={Store}><App /></Provider>, document.getElementById('root'));
9 9
 
10
-let store = createStore(reducers, devToolsEnabled)
11
-window.onload = () => ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
12
-
13
-if (ENV != 'production') {
14
-    module.hot.accept();
15
-}
10
+if (ENV != 'production')
11
+    module.hot.accept();

+ 17
- 0
src/utils.js 파일 보기

@@ -0,0 +1,17 @@
1
+import * as _ from 'lodash'
2
+
3
+Map.prototype.toObj = function() {
4
+    return [...this].reduce((o, [k, v]) => (o[k] = v, o), {});
5
+}
6
+
7
+_.updateIndex = (array, value, index) =>
8
+    [...array.slice(0, index), value, ...array.slice(index + 1)]
9
+
10
+_.updateWhere = (array, value, predicate) => {
11
+     const index = _.findIndex(array, predicate)
12
+     return _.updateIndex(array, value, index)
13
+}
14
+
15
+_.idCheck = id => e => e.id === id
16
+
17
+export { Map, _ }

+ 1
- 1
webpack.config.js 파일 보기

@@ -36,7 +36,7 @@ const config = {
36 36
     plugins: [
37 37
         new ExtractTextPlugin({ filename: 'style.css', allChunks: true }),
38 38
         new webpack.DefinePlugin({
39
-            ENV: "'test'"
39
+            ENV: JSON.stringify(process.env.NODE_ENV || 'developpement')
40 40
         }),
41 41
     ],
42 42
     devServer: {

+ 141
- 8
yarn.lock 파일 보기

@@ -208,6 +208,12 @@ aws4@^1.2.1:
208 208
   version "1.6.0"
209 209
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
210 210
 
211
+axios@^0.16.1:
212
+  version "0.16.1"
213
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.1.tgz#c0b6d26600842384b8f509e57111f0d2df8223ca"
214
+  dependencies:
215
+    follow-redirects "^1.2.3"
216
+
211 217
 babel-cli@^6.24.1:
212 218
   version "6.24.1"
213 219
   resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.1.tgz#207cd705bba61489b2ea41b5312341cf6aca2283"
@@ -802,6 +808,10 @@ base64-js@^1.0.2:
802 808
   version "1.2.0"
803 809
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
804 810
 
811
+base64url@2.0.0, base64url@^2.0.0:
812
+  version "2.0.0"
813
+  resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
814
+
805 815
 batch@0.5.3:
806 816
   version "0.5.3"
807 817
   resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464"
@@ -921,6 +931,10 @@ browserslist@^1.3.6, browserslist@^1.4.0, browserslist@^1.5.2, browserslist@^1.7
921 931
     caniuse-db "^1.0.30000639"
922 932
     electron-to-chromium "^1.2.7"
923 933
 
934
+buffer-equal-constant-time@1.0.1:
935
+  version "1.0.1"
936
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
937
+
924 938
 buffer-shims@~1.0.0:
925 939
   version "1.0.0"
926 940
   resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
@@ -1387,13 +1401,13 @@ date-now@^0.1.4:
1387 1401
   version "0.1.4"
1388 1402
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
1389 1403
 
1390
-debug@2.6.1, debug@^2.1.1:
1404
+debug@2.6.1, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5:
1391 1405
   version "2.6.1"
1392 1406
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
1393 1407
   dependencies:
1394 1408
     ms "0.7.2"
1395 1409
 
1396
-debug@2.6.4, debug@^2.2.0:
1410
+debug@2.6.4:
1397 1411
   version "2.6.4"
1398 1412
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.4.tgz#7586a9b3c39741c0282ae33445c4e8ac74734fe0"
1399 1413
   dependencies:
@@ -1467,6 +1481,10 @@ dom-serializer@0:
1467 1481
     domelementtype "~1.1.1"
1468 1482
     entities "~1.1.1"
1469 1483
 
1484
+dom-storage@^2.0.2:
1485
+  version "2.0.2"
1486
+  resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.0.2.tgz#ed17cbf68abd10e0aef8182713e297c5e4b500b0"
1487
+
1470 1488
 dom4@^1.8:
1471 1489
   version "1.8.3"
1472 1490
   resolved "https://registry.yarnpkg.com/dom4/-/dom4-1.8.3.tgz#2b0aa096b46368e33bbd2c2767f0e32dbb3394cd"
@@ -1508,6 +1526,13 @@ ecc-jsbn@~0.1.1:
1508 1526
   dependencies:
1509 1527
     jsbn "~0.1.0"
1510 1528
 
1529
+ecdsa-sig-formatter@1.0.9:
1530
+  version "1.0.9"
1531
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
1532
+  dependencies:
1533
+    base64url "^2.0.0"
1534
+    safe-buffer "^5.0.1"
1535
+
1511 1536
 ee-first@1.1.1:
1512 1537
   version "1.1.1"
1513 1538
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -1619,7 +1644,7 @@ expand-range@^1.8.1:
1619 1644
   dependencies:
1620 1645
     fill-range "^2.1.0"
1621 1646
 
1622
-express@^4.13.3, express@^4.15.2:
1647
+express@^4.13.3:
1623 1648
   version "4.15.2"
1624 1649
   resolved "https://registry.yarnpkg.com/express/-/express-4.15.2.tgz#af107fc148504457f2dca9a6f2571d7129b97b35"
1625 1650
   dependencies:
@@ -1679,6 +1704,12 @@ fastparse@^1.1.1:
1679 1704
   version "1.1.1"
1680 1705
   resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
1681 1706
 
1707
+faye-websocket@0.9.3:
1708
+  version "0.9.3"
1709
+  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.9.3.tgz#482a505b0df0ae626b969866d3bd740cdb962e83"
1710
+  dependencies:
1711
+    websocket-driver ">=0.5.1"
1712
+
1682 1713
 faye-websocket@^0.10.0:
1683 1714
   version "0.10.0"
1684 1715
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -1750,10 +1781,26 @@ find-up@^1.0.0:
1750 1781
     path-exists "^2.0.0"
1751 1782
     pinkie-promise "^2.0.0"
1752 1783
 
1784
+firebase@^3.9.0:
1785
+  version "3.9.0"
1786
+  resolved "https://registry.yarnpkg.com/firebase/-/firebase-3.9.0.tgz#c4237f50f58eeb25081b1839d6cbf175f8f7ed9b"
1787
+  dependencies:
1788
+    dom-storage "^2.0.2"
1789
+    faye-websocket "0.9.3"
1790
+    jsonwebtoken "^7.3.0"
1791
+    promise-polyfill "^6.0.2"
1792
+    xmlhttprequest "^1.8.0"
1793
+
1753 1794
 flatten@^1.0.2:
1754 1795
   version "1.0.2"
1755 1796
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
1756 1797
 
1798
+follow-redirects@^1.2.3:
1799
+  version "1.2.3"
1800
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.3.tgz#01abaeca85e3609837d9fcda3167a7e42fdaca21"
1801
+  dependencies:
1802
+    debug "^2.4.5"
1803
+
1757 1804
 font-awesome@^4.7.0:
1758 1805
   version "4.7.0"
1759 1806
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
@@ -2096,8 +2143,8 @@ https-browserify@0.0.1:
2096 2143
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
2097 2144
 
2098 2145
 iconv-lite@~0.4.13:
2099
-  version "0.4.17"
2100
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.17.tgz#4fdaa3b38acbc2c031b045d0edcdfe1ecab18c8d"
2146
+  version "0.4.15"
2147
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
2101 2148
 
2102 2149
 icss-replace-symbols@^1.0.2:
2103 2150
   version "1.0.2"
@@ -2282,6 +2329,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
2282 2329
   version "1.0.0"
2283 2330
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
2284 2331
 
2332
+isemail@1.x.x:
2333
+  version "1.2.0"
2334
+  resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
2335
+
2285 2336
 isexe@^2.0.0:
2286 2337
   version "2.0.0"
2287 2338
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -2313,6 +2364,15 @@ jodid25519@^1.0.0:
2313 2364
   dependencies:
2314 2365
     jsbn "~0.1.0"
2315 2366
 
2367
+joi@^6.10.1:
2368
+  version "6.10.1"
2369
+  resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
2370
+  dependencies:
2371
+    hoek "2.x.x"
2372
+    isemail "1.x.x"
2373
+    moment "2.x.x"
2374
+    topo "1.x.x"
2375
+
2316 2376
 js-base64@^2.1.8, js-base64@^2.1.9:
2317 2377
   version "2.1.9"
2318 2378
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
@@ -2370,6 +2430,16 @@ jsonify@~0.0.0:
2370 2430
   version "0.0.0"
2371 2431
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
2372 2432
 
2433
+jsonwebtoken@^7.3.0:
2434
+  version "7.4.0"
2435
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.0.tgz#515bf2bba070ec615bad97fd2e945027eb476946"
2436
+  dependencies:
2437
+    joi "^6.10.1"
2438
+    jws "^3.1.4"
2439
+    lodash.once "^4.0.0"
2440
+    ms "^0.7.1"
2441
+    xtend "^4.0.1"
2442
+
2373 2443
 jsprim@^1.2.2:
2374 2444
   version "1.4.0"
2375 2445
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
@@ -2379,6 +2449,23 @@ jsprim@^1.2.2:
2379 2449
     json-schema "0.2.3"
2380 2450
     verror "1.3.6"
2381 2451
 
2452
+jwa@^1.1.4:
2453
+  version "1.1.5"
2454
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
2455
+  dependencies:
2456
+    base64url "2.0.0"
2457
+    buffer-equal-constant-time "1.0.1"
2458
+    ecdsa-sig-formatter "1.0.9"
2459
+    safe-buffer "^5.0.1"
2460
+
2461
+jws@^3.1.4:
2462
+  version "3.1.4"
2463
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
2464
+  dependencies:
2465
+    base64url "^2.0.0"
2466
+    jwa "^1.1.4"
2467
+    safe-buffer "^5.0.1"
2468
+
2382 2469
 kind-of@^2.0.1:
2383 2470
   version "2.0.1"
2384 2471
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
@@ -2405,6 +2492,12 @@ lcid@^1.0.0:
2405 2492
   dependencies:
2406 2493
     invert-kv "^1.0.0"
2407 2494
 
2495
+linkify-it@^2.0.0:
2496
+  version "2.0.3"
2497
+  resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
2498
+  dependencies:
2499
+    uc.micro "^1.0.1"
2500
+
2408 2501
 load-json-file@^1.0.0:
2409 2502
   version "1.1.0"
2410 2503
   resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -2460,6 +2553,10 @@ lodash.mergewith@^4.6.0:
2460 2553
   version "4.6.0"
2461 2554
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
2462 2555
 
2556
+lodash.once@^4.0.0:
2557
+  version "4.1.1"
2558
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
2559
+
2463 2560
 lodash.tail@^4.1.1:
2464 2561
   version "4.1.1"
2465 2562
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
@@ -2468,7 +2565,7 @@ lodash.uniq@^4.5.0:
2468 2565
   version "4.5.0"
2469 2566
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
2470 2567
 
2471
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.2.0, lodash@^4.2.1:
2568
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
2472 2569
   version "4.17.4"
2473 2570
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
2474 2571
 
@@ -2512,10 +2609,24 @@ map-obj@^1.0.0, map-obj@^1.0.1:
2512 2609
   version "1.0.1"
2513 2610
   resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
2514 2611
 
2612
+markdown-it@^8.3.1:
2613
+  version "8.3.1"
2614
+  resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.3.1.tgz#2f4b622948ccdc193d66f3ca2d43125ac4ac7323"
2615
+  dependencies:
2616
+    argparse "^1.0.7"
2617
+    entities "~1.1.1"
2618
+    linkify-it "^2.0.0"
2619
+    mdurl "^1.0.1"
2620
+    uc.micro "^1.0.3"
2621
+
2515 2622
 math-expression-evaluator@^1.2.14:
2516 2623
   version "1.2.17"
2517 2624
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
2518 2625
 
2626
+mdurl@^1.0.1:
2627
+  version "1.0.1"
2628
+  resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
2629
+
2519 2630
 media-typer@0.3.0:
2520 2631
   version "0.3.0"
2521 2632
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -2624,6 +2735,10 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
2624 2735
   dependencies:
2625 2736
     minimist "0.0.8"
2626 2737
 
2738
+moment@2.x.x:
2739
+  version "2.18.1"
2740
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
2741
+
2627 2742
 ms@0.7.1:
2628 2743
   version "0.7.1"
2629 2744
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@@ -2632,7 +2747,7 @@ ms@0.7.2:
2632 2747
   version "0.7.2"
2633 2748
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
2634 2749
 
2635
-ms@0.7.3:
2750
+ms@0.7.3, ms@^0.7.1:
2636 2751
   version "0.7.3"
2637 2752
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
2638 2753
 
@@ -3269,6 +3384,10 @@ process@^0.11.0:
3269 3384
   version "0.11.10"
3270 3385
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
3271 3386
 
3387
+promise-polyfill@^6.0.2:
3388
+  version "6.0.2"
3389
+  resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.0.2.tgz#d9c86d3dc4dc2df9016e88946defd69b49b41162"
3390
+
3272 3391
 promise@^7.1.1:
3273 3392
   version "7.1.1"
3274 3393
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
@@ -4064,6 +4183,12 @@ to-fast-properties@^1.0.1:
4064 4183
   version "1.0.3"
4065 4184
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
4066 4185
 
4186
+topo@1.x.x:
4187
+  version "1.1.0"
4188
+  resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
4189
+  dependencies:
4190
+    hoek "2.x.x"
4191
+
4067 4192
 toposort@^1.0.0:
4068 4193
   version "1.0.3"
4069 4194
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.3.tgz#f02cd8a74bd8be2fc0e98611c3bacb95a171869c"
@@ -4111,6 +4236,10 @@ ua-parser-js@^0.7.9:
4111 4236
   version "0.7.12"
4112 4237
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
4113 4238
 
4239
+uc.micro@^1.0.1, uc.micro@^1.0.3:
4240
+  version "1.0.3"
4241
+  resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
4242
+
4114 4243
 uglify-js@^2.8.5, uglify-js@~2.8.22:
4115 4244
   version "2.8.23"
4116 4245
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.23.tgz#8230dd9783371232d62a7821e2cf9a817270a8a0"
@@ -4390,7 +4519,11 @@ xml-char-classes@^1.0.0:
4390 4519
   version "1.0.0"
4391 4520
   resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"
4392 4521
 
4393
-xtend@^4.0.0:
4522
+xmlhttprequest@^1.8.0:
4523
+  version "1.8.0"
4524
+  resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
4525
+
4526
+xtend@^4.0.0, xtend@^4.0.1:
4394 4527
   version "4.0.1"
4395 4528
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
4396 4529