Browse Source

refactor: dev optimizations + improvements

NGPixel 7 years ago
parent
commit
392cbe9388

+ 1 - 0
.eslintignore

@@ -1,6 +1,7 @@
 **/node_modules/**
 **/*.min.js
 assets/**
+client/libs/**
 coverage/**
 repo/**
 data/**

+ 0 - 1
client/app.js

@@ -80,7 +80,6 @@ Vue.component('login', () => import(/* webpackMode: "eager" */ './components/log
 Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/nav-header.vue'))
 Vue.component('navigator', () => import(/* webpackMode: "eager" */ './components/navigator.vue'))
 Vue.component('setup', () => import(/* webpackChunkName: "setup" */ './components/setup.vue'))
-Vue.component('toggle', () => import(/* webpackMode: "eager" */ './components/toggle.vue'))
 
 let bootstrap = () => {
   // ====================================

+ 59 - 104
client/components/admin-dev.vue

@@ -4,49 +4,71 @@
       .pa-3.pt-4
         .headline.primary--text Developer Tools
         .subheading.grey--text ¯\_(ツ)_/¯
-      v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
-        v-tab Graph API Playground
-        v-tab Graph API Map
-
-        v-tab-item(:transition='false', :reverse-transition='false')
+      v-tabs(v-model='selectedTab', color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows, @input='tabChanged')
+        v-tab(key='0') Graph API Playground
+        v-tab(key='1') Graph API Map
+      v-tabs-items(v-model='selectedTab')
+        v-tab-item(key='0', :transition='false', :reverse-transition='false')
           #graphiql
+
+        v-tab-item(key='1', :transition='false', :reverse-transition='false')
+          #voyager
+
 </template>
 
 <script>
 import React from 'react'
 import ReactDOM from 'react-dom'
 import GraphiQL from 'graphiql'
+import { Voyager } from 'graphql-voyager'
+import 'graphiql/graphiql.css'
+import 'graphql-voyager/dist/voyager.css'
+
+const fetcher = (qry, respType) => {
+  return fetch('/graphql', {
+    method: 'post',
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(qry),
+    credentials: 'include'
+  }).then(response => {
+    if (respType === 'json') {
+      return response.json()
+    } else {
+      return response.text()
+    }
+  }).then(responseBody => {
+    try {
+      return JSON.parse(responseBody)
+    } catch (error) {
+      return responseBody
+    }
+  })
+}
 
 export default {
   data() {
-    return {}
+    return {
+      selectedTab: '0'
+    }
   },
   mounted() {
     this.renderGraphiQL()
   },
   methods: {
+    tabChanged (tabId) {
+      switch (tabId) {
+        case '1':
+          this.renderVoyager()
+          break
+      }
+    },
     renderGraphiQL() {
       ReactDOM.render(
         React.createElement(GraphiQL, {
-          fetcher: graphQLParams => {
-            return fetch('/graphql', {
-              method: 'post',
-              headers: {
-                'Accept': 'application/json',
-                'Content-Type': 'application/json'
-              },
-              body: JSON.stringify(graphQLParams),
-              credentials: 'include'
-            }).then(function (response) {
-              return response.text()
-            }).then(function (responseBody) {
-              try {
-                return JSON.parse(responseBody)
-              } catch (error) {
-                return responseBody
-              }
-            })
-          },
+          fetcher: qry => fetcher(qry, 'text'),
           query: null,
           response: null,
           variables: null,
@@ -55,6 +77,15 @@ export default {
         }),
         document.getElementById('graphiql')
       )
+    },
+    renderVoyager() {
+      ReactDOM.render(
+        React.createElement(Voyager, {
+          introspection: qry => fetcher({ query: qry }, 'json'),
+          workerURI: '/js/voyager.worker.js'
+        }),
+        document.getElementById('voyager')
+      )
     }
   }
 }
@@ -79,85 +110,9 @@ export default {
     background-color: initial;
     box-shadow: initial;
   }
+}
 
-  .cm-s-wikijs-dark.CodeMirror {
-    background: darken(mc('grey','900'), 3%);
-    color: #e0e0e0;
-  }
-  .cm-s-wikijs-dark div.CodeMirror-selected {
-    background: mc('blue','800');
-  }
-  .cm-s-wikijs-dark .cm-matchhighlight {
-    background: mc('blue','800');
-  }
-  .cm-s-wikijs-dark .CodeMirror-line::selection, .cm-s-wikijs-dark .CodeMirror-line > span::selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::selection {
-    background: mc('red', '500');
-  }
-  .cm-s-wikijs-dark .CodeMirror-line::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::-moz-selection {
-    background: mc('red', '500');
-  }
-  .cm-s-wikijs-dark .CodeMirror-gutters {
-    background: darken(mc('grey','900'), 6%);
-    border-right: 1px solid mc('grey','900');
-  }
-  .cm-s-wikijs-dark .CodeMirror-guttermarker {
-    color: #ac4142;
-  }
-  .cm-s-wikijs-dark .CodeMirror-guttermarker-subtle {
-    color: #505050;
-  }
-  .cm-s-wikijs-dark .CodeMirror-linenumber {
-    color: mc('grey','800');
-  }
-  .cm-s-wikijs-dark .CodeMirror-cursor {
-    border-left: 1px solid #b0b0b0;
-  }
-  .cm-s-wikijs-dark span.cm-comment {
-    color: mc('orange','800');
-  }
-  .cm-s-wikijs-dark span.cm-atom {
-    color: #aa759f;
-  }
-  .cm-s-wikijs-dark span.cm-number {
-    color: #aa759f;
-  }
-  .cm-s-wikijs-dark span.cm-property, .cm-s-wikijs-dark span.cm-attribute {
-    color: #90a959;
-  }
-  .cm-s-wikijs-dark span.cm-keyword {
-    color: #ac4142;
-  }
-  .cm-s-wikijs-dark span.cm-string {
-    color: #f4bf75;
-  }
-  .cm-s-wikijs-dark span.cm-variable {
-    color: #90a959;
-  }
-  .cm-s-wikijs-dark span.cm-variable-2 {
-    color: #6a9fb5;
-  }
-  .cm-s-wikijs-dark span.cm-def {
-    color: #d28445;
-  }
-  .cm-s-wikijs-dark span.cm-bracket {
-    color: #e0e0e0;
-  }
-  .cm-s-wikijs-dark span.cm-tag {
-    color: #ac4142;
-  }
-  .cm-s-wikijs-dark span.cm-link {
-    color: #aa759f;
-  }
-  .cm-s-wikijs-dark span.cm-error {
-    background: #ac4142;
-    color: #b0b0b0;
-  }
-  .cm-s-wikijs-dark .CodeMirror-activeline-background {
-    background: mc('grey','900');
-  }
-  .cm-s-wikijs-dark .CodeMirror-matchingbracket {
-    text-decoration: underline;
-    color: white !important;
-  }
+#voyager {
+  height: calc(100vh - 250px);
 }
 </style>

+ 45 - 0
client/components/admin-editor.vue

@@ -0,0 +1,45 @@
+<template lang='pug'>
+  v-card(flat)
+    v-card(color='grey lighten-5')
+      .pa-3.pt-4
+        .headline.primary--text Editor
+        .subheading.grey--text Configure the content editor
+      v-tabs(color='grey lighten-4', fixed-tabs, slider-color='primary', show-arrows)
+        v-tab(key='settings'): v-icon settings
+        v-tab(key='code') Markdown
+
+        v-tab-item(key='settings', :transition='false', :reverse-transition='false')
+          v-card.pa-3
+            v-form
+              v-radio-group(v-model='selectedEditor')
+                v-radio(v-for='(editor, n) in editors', :key='n', :label='editor.text', :value='editor.value', color='primary')
+              v-divider
+              v-btn(color='primary')
+                v-icon(left) chevron_right
+                | Set Editor
+              v-btn(icon)
+                v-icon.grey--text refresh
+        v-tab-item(key='code', :transition='false', :reverse-transition='false')
+          v-card.pa-3
+            v-form
+              v-subheader Editor Configuration
+              .body-1 This editor has no configuration options you can modify.
+
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      editors: [
+        { text: 'Markdown (default)', value: 'code' }
+      ],
+      selectedEditor: 'code'
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 108 - 0
client/components/admin-groups.vue

@@ -0,0 +1,108 @@
+<template lang='pug'>
+  v-card(flat)
+    v-card(flat, color='grey lighten-5').pa-3.pt-4
+      .headline.blue--text.text--darken-2 Groups
+      .subheading.grey--text Manage groups
+    v-card
+      v-card-title
+        v-btn(color='primary', dark)
+          v-icon(left) add
+          | New Group
+        v-btn(icon)
+          v-icon.grey--text refresh
+        v-spacer
+        v-text-field(append-icon='search', label='Search', single-line, hide-details, v-model='search')
+      v-data-table(
+        v-model='selected'
+        :items='items',
+        :headers='headers',
+        :search='search',
+        :pagination.sync='pagination',
+        :rows-per-page-items='[15]'
+        select-all,
+        hide-actions,
+        disable-initial-sort
+      )
+        template(slot='headers', slot-scope='props')
+          tr
+            th(width='50')
+            th.text-xs-right(
+              width='80'
+              :class='[`column sortable`, pagination.descending ? `desc` : `asc`, pagination.sortBy === `id` ? `active` : ``]'
+              @click='changeSort(`id`)'
+            )
+              v-icon(small) arrow_upward
+              | ID
+            th.text-xs-left(
+              v-for='header in props.headers'
+              :key='header.text'
+              :width='header.width'
+              :class='[`column sortable`, pagination.descending ? `desc` : `asc`, header.value === pagination.sortBy ? `active` : ``]'
+              @click='changeSort(header.value)'
+            )
+              | {{ header.text }}
+              v-icon(small) arrow_upward
+        template(slot='items', slot-scope='props')
+          tr(:active='props.selected')
+            td
+              v-checkbox(hide-details, :input-value='props.selected', color='blue darken-2', @click='props.selected = !props.selected')
+            td.text-xs-right {{ props.item.id }}
+            td {{ props.item.name }}
+            td {{ props.item.userCount }}
+            td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz
+        template(slot='no-data')
+          v-alert(icon='warning', :value='true') No users to display!
+      .text-xs-center.py-2(v-if='items.length > 15')
+        v-pagination(v-model='pagination.page', :length='pages')
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      selected: [],
+      pagination: {},
+      items: [
+        { id: 1, name: 'Administrators', userCount: 1 },
+        { id: 2, name: 'Users', userCount: 23 }
+      ],
+      headers: [
+        { text: 'Name', value: 'name' },
+        { text: 'Users', value: 'userCount', width: 200 },
+        { text: '', value: 'actions', sortable: false, width: 50 }
+      ],
+      search: ''
+    }
+  },
+  computed: {
+    pages () {
+      if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
+        return 0
+      }
+
+      return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
+    }
+  },
+  methods: {
+    changeSort (column) {
+      if (this.pagination.sortBy === column) {
+        this.pagination.descending = !this.pagination.descending
+      } else {
+        this.pagination.sortBy = column
+        this.pagination.descending = false
+      }
+    },
+    toggleAll () {
+      if (this.selected.length) {
+        this.selected = []
+      } else {
+        this.selected = this.items.slice()
+      }
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 19 - 0
client/components/admin-rendering.vue

@@ -0,0 +1,19 @@
+<template lang='pug'>
+  v-container(fluid, fill-height)
+    v-layout(row wrap)
+      v-flex(xs12)
+        .headline.primary--text Content Rendering
+        .subheading.grey--text Configure how content is rendered
+</template>
+
+<script>
+export default {
+  data() {
+    return {}
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 19 - 0
client/components/admin-stats.vue

@@ -0,0 +1,19 @@
+<template lang='pug'>
+  v-container(fluid, fill-height)
+    v-layout(row wrap)
+      v-flex(xs12)
+        .headline.primary--text Statistics
+        .subheading.grey--text Useful information about your wiki
+</template>
+
+<script>
+export default {
+  data() {
+    return {}
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 4 - 0
client/components/admin.vue

@@ -82,9 +82,13 @@ const router = new VueRouter({
     { path: '/dashboard', component: () => import(/* webpackChunkName: "admin" */ './admin-dashboard.vue') },
     { path: '/general', component: () => import(/* webpackChunkName: "admin" */ './admin-general.vue') },
     { path: '/locale', component: () => import(/* webpackChunkName: "admin" */ './admin-locale.vue') },
+    { path: '/stats', component: () => import(/* webpackChunkName: "admin" */ './admin-stats.vue') },
     { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin-theme.vue') },
+    { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin-groups.vue') },
     { path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin-users.vue') },
     { path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin-auth.vue') },
+    { path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin-rendering.vue') },
+    { path: '/editor', component: () => import(/* webpackChunkName: "admin" */ './admin-editor.vue') },
     { path: '/logging', component: () => import(/* webpackChunkName: "admin" */ './admin-logging.vue') },
     { path: '/search', component: () => import(/* webpackChunkName: "admin" */ './admin-search.vue') },
     { path: '/storage', component: () => import(/* webpackChunkName: "admin" */ './admin-storage.vue') },

+ 38 - 72
client/components/login.vue

@@ -1,30 +1,39 @@
 <template lang="pug">
-  .login(:class='{ "is-error": error }')
-    .login-container(:class='{ "is-expanded": strategies.length > 1, "is-loading": isLoading }')
-      .login-providers(v-show='strategies.length > 1')
-        button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
-          em(v-html='strategy.icon')
-          span {{ strategy.title }}
-        .login-providers-fill
-      .login-frame(v-show='screen === "login"')
-        h1 {{ siteTitle }}
-        h2 {{ $t('auth:loginRequired') }}
-        input(type='text', ref='iptEmail', v-model='username', :placeholder='$t("auth:fields.emailUser")')
-        input(type='password', ref='iptPassword', v-model='password', :placeholder='$t("auth:fields.password")', @keyup.enter='login')
-        button.button.is-blue.is-fullwidth(@click='login')
-          span {{ $t('auth:actions.login') }}
-      .login-frame(v-show='screen === "tfa"')
-        .login-frame-icon
-          svg.icons.is-48(role='img')
-            title {{ $t('auth:tfa.title') }}
-            use(xlink:href='#nc-key')
-        h2 {{ $t('auth:tfa.subtitle') }}
-        input(type='text', ref='iptTFA', v-model='securityCode', :placeholder='$t("auth:tfa.placeholder")', @keyup.enter='verifySecurityCode')
-        button.button.is-blue.is-fullwidth(@click='verifySecurityCode')
-          span {{ $t('auth:tfa.verifyToken') }}
-    .login-copyright
-      span {{ $t('footer.poweredBy') }}
-      a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
+  v-app
+    .login(:class='{ "is-error": error }')
+      .login-container(:class='{ "is-expanded": strategies.length > 1, "is-loading": isLoading }')
+        .login-providers(v-show='strategies.length > 1')
+          button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
+            em(v-html='strategy.icon')
+            span {{ strategy.title }}
+          .login-providers-fill
+        .login-frame(v-show='screen === "login"')
+          h1.text-xs-center.display-1 {{ siteTitle }}
+          h2.text-xs-center.subheading {{ $t('auth:loginRequired') }}
+          v-text-field(solo, ref='iptEmail', v-model='username', :placeholder='$t("auth:fields.emailUser")')
+          v-text-field.mt-2(
+            solo,
+            ref='iptPassword',
+            v-model='password',
+            :append-icon='hidePassword ? "visibility" : "visibility_off"',
+            :append-icon-cb='() => (hidePassword = !hidePassword)',
+            :type='hidePassword ? "password" : "text"',
+            :placeholder='$t("auth:fields.password")',
+            @keyup.enter='login'
+          )
+          v-btn.mt-3(block, large, color='primary', @click='login') {{ $t('auth:actions.login') }}
+        .login-frame(v-show='screen === "tfa"')
+          .login-frame-icon
+            svg.icons.is-48(role='img')
+              title {{ $t('auth:tfa.title') }}
+              use(xlink:href='#nc-key')
+          h2 {{ $t('auth:tfa.subtitle') }}
+          input(type='text', ref='iptTFA', v-model='securityCode', :placeholder='$t("auth:tfa.placeholder")', @keyup.enter='verifySecurityCode')
+          button.button.is-blue.is-fullwidth(@click='verifySecurityCode')
+            span {{ $t('auth:tfa.verifyToken') }}
+      .login-copyright
+        span {{ $t('footer.poweredBy') }}
+        a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
 </template>
 
 <script>
@@ -41,6 +50,7 @@ export default {
       screen: 'login',
       username: '',
       password: '',
+      hidePassword: true,
       securityCode: '',
       loginToken: '',
       isLoading: false
@@ -408,10 +418,7 @@ export default {
       width: 400px;
       padding: 1rem;
       color: mc('grey', '700');
-      display: flex;
-      justify-content: center;
-      flex-direction: column;
-      text-align: center;
+      display: block;
 
       @include until($tablet) {
         width: 100%;
@@ -421,7 +428,7 @@ export default {
 
       h1 {
         font-size: 2rem;
-        font-weight: 600;
+        font-weight: 400;
         color: mc('light-blue', '700');
         text-shadow: 1px 1px 0 #FFF;
         padding: 0;
@@ -436,47 +443,6 @@ export default {
         padding: 0;
         margin: 0 0 25px 0;
       }
-
-      form {
-        display: flex;
-        flex-direction: column;
-      }
-
-      input[type=text], input[type=password] {
-        width: 100%;
-        border: 1px solid rgba(mc('blue-grey','500'), .5);
-        border-radius: 3px;
-        background-color: rgba(255,255,255,.9);
-        box-shadow: inset 0 0 0 3px rgba(255,255,255, .25);
-        padding: 0 15px;
-        height: 40px;
-        margin: 0 0 10px 0;
-        color: mc('grey', '700');
-        font-weight: 600;
-        font-size: .8rem;
-        transition: all 0.4s ease;
-        text-align: center;
-
-        &:focus {
-          outline: none;
-          border-color: mc('light-blue','500');
-          background-color: rgba(255,255,255,1);
-          box-shadow: inset 0 0 8px rgba(mc('light-blue','500'), .5);
-          color: mc('light-blue', '800');
-        }
-
-      }
-
-      .button {
-        background-image: linear-gradient(to bottom, mc('blue', '400') 0%, mc('blue', '600') 50%, mc('blue', '700') 100%);
-        background-repeat: no-repeat;
-        background-size: 100% 200%;
-
-        &:hover {
-          background-position-y: 100%;
-        }
-      }
-
     }
 
     &-tfa {

+ 0 - 21
client/components/toggle.vue

@@ -1,21 +0,0 @@
-<template lang="pug">
-  .toggle(:class='{ "is-active": value }', @click='changeToggle')
-    .toggle-container
-      .toggle-pin
-    .toggle-text {{ desc }}
-</template>
-
-<script>
-export default {
-  name: 'toggle',
-  props: ['value', 'desc'],
-  data () {
-    return { }
-  },
-  methods: {
-    changeToggle() {
-      this.$emit('input', !this.value)
-    }
-  }
-}
-</script>

+ 1 - 6
client/scss/app.scss

@@ -4,20 +4,15 @@
 @import "base/base";
 @import 'base/icons';
 
-@import "../libs/animate/animate";
+// @import "../libs/animate/animate";
 
-@import 'components/button';
 @import 'components/markdown-content';
 @import 'components/navigator';
-@import 'components/toggle';
-@import 'components/typography';
 
 // @import '../libs/twemoji/twemoji-awesome';
 @import '../libs/prism/prism.css';
 // @import 'node_modules/diff2html/dist/diff2html.min';
 
-@import 'node_modules/graphiql/graphiql';
-
 @import 'pages/welcome';
 
 @import 'layout/_rtl';

+ 0 - 9
client/scss/base/base.scss

@@ -42,15 +42,6 @@ html {
   display: none !important;
 }
 
-.is-hidden-until-scroll {
-  max-height: 0;
-  overflow: hidden;
-  transition: all .6s ease;
-}
-.is-sticky .is-hidden-until-scroll {
-  max-height: 30px;
-}
-
 .is-hidden-mobile {
   @include mobile {
     display: none !important;

+ 0 - 24
client/scss/base/material.scss

@@ -325,27 +325,3 @@ $material-colors: (
 @function mc($color-name, $color-variant: '500') {
 	@return material-color($color-name, $color-variant);
 }
-
-@mixin md-elevation-0 {
-  box-shadow: none !important;
-}
-
-@mixin md-elevation-1 {
-  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
-}
-
-@mixin md-elevation-2 {
-  box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
-}
-
-@mixin md-elevation-3 {
-  box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3);
-}
-
-@mixin md-elevation-4 {
-  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.3);
-}
-
-@mixin md-elevation-5 {
-  box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.3);
-}

+ 0 - 136
client/scss/components/button.scss

@@ -1,136 +0,0 @@
-.button {
-	background-color: mc('orange','600');
-	color: #FFF;
-	border: 1px solid mc('orange','700');
-	border-radius: 3px;
-	display: inline-flex;
-	height: 40px;
-	align-items: center;
-	padding: 0 15px;
-	font-size: 13px;
-	font-weight: 600;
-	font-family: $core-font-standard;
-	margin: 0;
-	transition: all .4s ease;
-	cursor: pointer;
-	text-decoration: none;
-	text-transform: uppercase;
-
-	span {
-		font-weight: 600;
-		display: inline-flex;
-		align-items: center;
-		line-height: 14px;
-		height: 14px;
-	}
-
-	i {
-		margin-right: 8px;
-		font-size: 14px;
-		line-height: 14px;
-		height: 14px;
-	}
-
-	&:focus {
-		outline: none;
-		border-color: #FFF;
-	}
-
-	&:hover {
-		background-color: mc('orange','800');
-		text-decoration: none;
-	}
-
-	@each $color, $colorvalue in $material-colors {
-		&.is-#{$color} {
-			background-color: mc($color, '600');
-			border-color: mc($color,'700');
-			color: #FFF;
-
-			&.is-outlined {
-				background-color: #FFF;
-				color: mc($color,'700');
-			}
-
-			&.is-inverted {
-				background-color: rgba(mc($color, '800'), 0);
-				border-color: mc($color, '500');
-			}
-
-			&:hover {
-				background-color: mc($color,'800');
-				color: #FFF;
-				animation: none;
-      }
-
-      &:focus {
-        box-shadow: inset 0 0 0 3px rgba(255,255,255, .4);
-      }
-		}
-
-	}
-
-	&.is-icon-only {
-		i {
-			margin-right: 0;
-		}
-	}
-
-	&.is-featured {
-		animation: btnInvertedPulse .6s ease alternate infinite;
-  }
-
-  &.is-fullwidth {
-    width: 100%;
-    text-align: center;
-    justify-content: center;
-  }
-
-	&.is-disabled, &:disabled {
-		background-color: mc('grey', '300');
-		border: 1px solid mc('grey','400');
-		color: mc('grey', '500');
-		cursor: default;
-    transition: none;
-
-    &:hover {
-      background-color: mc('grey', '300') !important;
-      color: mc('grey', '500') !important;
-    }
-  }
-
-  &.is-small {
-    height: 30px;
-  }
-
-}
-
-.button-group {
-
-	.button {
-		border-radius: 0;
-		margin-left: 1px;
-
-		&:first-child {
-			margin-left: 0;
-			border-top-left-radius: 4px;
-			border-bottom-left-radius: 4px;
-		}
-
-		&:last-child {
-			border-top-right-radius: 4px;
-			border-bottom-right-radius: 4px;
-		}
-
-	}
-
-}
-
-@include keyframes(btnInvertedPulse) {
-	0% {
-		background-color: rgba(mc('grey', '500'), 0);
-	}
-	100% {
-		background-color: rgba(mc('grey', '500'), 0.25);
-	}
-}

+ 0 - 118
client/scss/components/panel.scss

@@ -1,118 +0,0 @@
-
-.panel-aside {
-	background-color: mc('blue-grey', '800');
-	border: 1px solid mc('blue-grey', '800');
-	border-bottom-left-radius: 8px;
-	padding: 20px;
-	color: mc('blue-grey','100');
-
-	label {
-		color: #FFF;
-	}
-
-}
-
-.panel {
-  background-color: #FFF;
-  box-shadow: 0 0 12px 0 rgba(mc('grey','800'), .10), 1px 6px 8px 0 rgba(mc('grey','800'), .10);
-  padding: 0 0 1px 0;
-  border-radius: 4px;
-
-  .panel-title {
-    border-bottom: 1px solid darken($color-bg, 5%);
-    padding: 0 15px;
-    color: $color-text;
-    font-size: 16px;
-    font-weight: 500;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    height: 40px;
-
-    &.is-featured {
-      border-top-left-radius: 4px;
-      border-top-right-radius: 4px;
-      background-color: mc('blue', '700');
-      background-image: linear-gradient(to bottom, mc('blue', '700') 0%, mc('blue', '800') 100%);
-      border-bottom-color: mc('blue', '900');
-      box-shadow: inset 0 0 0 1px mc('blue', '600'), inset 0 0 0px 2px rgba(mc('blue', '800'), .5);
-      color: #FFF;
-
-      > i::before {
-        @include spinner(#FFF, 0.4s, 18px);
-      }
-
-    }
-
-    & + .panel-content {
-      box-shadow: inset 0 0 0 1px #FFF, inset 0 30px 80px -25px mc('blue', '100');
-    }
-
-    > span {
-      font-weight: 500;
-    }
-
-    > i {
-      display: flex;
-      width: 18px;
-      align-items: center;
-
-      &::before {
-        content: " ";
-        @include spinner(mc($primary,'500'), 0.4s, 18px);
-      }
-    }
-
-  }
-
-  .panel-content {
-    padding: 0 15px;
-
-    &.is-text {
-      padding: 25px;
-
-      p + p, p + h3 {
-        margin-top: 25px;
-      }
-
-      h3 {
-        margin-bottom: 15px;
-        font-weight: 500;
-      }
-
-      ul li {
-        color: mc('grey', '700');
-      }
-
-      strong {
-        font-weight: 500;
-        color: mc($primary,'800');
-      }
-
-    }
-
-  }
-
-  .panel-footer {
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
-    height: 50px;
-    background-color: $color-bg;
-    padding: 0 15px;
-    margin: 0 1px;
-    border-bottom-left-radius: 4px;
-    border-bottom-right-radius: 4px;
-    position: relative;
-
-    .button + .button {
-      margin-left: 10px;
-    }
-
-  }
-
-  + .panel {
-    margin-top: 25px;
-  }
-
-}

+ 0 - 67
client/scss/components/toggle.scss

@@ -1,67 +0,0 @@
-.toggle {
-  display: inline-flex;
-  align-items: center;
-  height: 24px;
-  cursor: pointer;
-  margin: 5px 5px 5px 0;
-
-  & + & {
-    margin-left: 15px;
-  }
-
-  &-container {
-    display: inline-flex;
-    align-items: center;
-    height: 24px;
-    width: 50px;
-    background-color: mc('blue-grey', '200');
-    border-radius: 12px;
-    padding: 2px;
-    transition: background-color .5s ease;
-  }
-
-  &-pin {
-    display: flex;
-    background-color: #FFF;
-    border-radius:10px;
-    height: 20px;
-    width: 20px;
-    transition: all .5s ease;
-  }
-
-  &-text {
-    padding-left: 10px;
-    color: mc('grey', '700');
-    font-size: 12px;
-  }
-
-  &:hover {
-    .toggle-container {
-      background-color: mc('grey', '400');
-    }
-  }
-
-  &.is-active {
-    .toggle-container {
-      background-color: mc('indigo', '500');
-    }
-    .toggle-pin {
-      margin-left: 28px;
-    }
-  }
-
-}
-
-/* THEME OVERRIDE - START */
-
-@each $color, $colorvalue in $material-colors {
-  .is-primary-#{$color} .toggle {
-    &.is-active {
-      .toggle-container {
-        background-color: mc($color, '500');
-      }
-    }
-  }
-}
-
-/* THEME OVERRIDE - END */

+ 0 - 16
client/scss/components/typography.scss

@@ -1,16 +0,0 @@
-
-h1 {
-	font-size: 28px;
-}
-h2 {
-	font-size: 18px;
-}
-h3 {
-	font-size: 16px;
-}
-
-@each $color, $colorvalue in $material-colors {
-	i.is-#{$color} {
-		color: mc($color, '600');
-	}
-}

+ 0 - 18
client/scss/error.scss

@@ -1,18 +0,0 @@
-@charset "utf-8";
-
-$primary: 'indigo';
-
-@import "base/variables";
-@import "base/colors";
-@import "base/reset";
-@import "base/mixins";
-@import "base/fonts";
-@import "base/base";
-
-@import "libs/animate";
-
-@import 'components/button';
-@import 'components/grid';
-@import 'components/typography';
-
-@import 'pages/error';

+ 0 - 14
dev/config/postcss.config.js

@@ -1,14 +0,0 @@
-module.exports = {
-  plugins: {
-    'autoprefixer': {},
-    'cssnano': {
-      preset: ['default', {
-        discardComments: {
-          removeAll: true
-        }
-      }]
-    },
-    'postcss-flexbugs-fixes': {},
-    'postcss-flexibility': {}
-  }
-}

+ 0 - 16
dev/config/pug-lintrc.json

@@ -1,16 +0,0 @@
-{
-  "disallowDuplicateAttributes": true,
-  "disallowIdAttributeWithStaticValue": true,
-  "disallowMultipleLineBreaks": true,
-  "requireClassLiteralsBeforeAttributes": true,
-  "requireIdLiteralsBeforeAttributes": true,
-  "requireLineFeedAtFileEnd": true,
-  "requireLowerCaseAttributes": true,
-  "requireLowerCaseTags": true,
-  "requireSpaceAfterCodeOperator": true,
-  "requireStrictEqualityOperators": true,
-  "validateAttributeQuoteMarks": "'",
-  "validateAttributeSeparator": ", ",
-  "validateDivTags": true,
-  "validateIndentation": 2
-}

+ 0 - 262
dev/webpack/webpack.common.js

@@ -1,262 +0,0 @@
-const path = require('path')
-const fs = require('fs-extra')
-const webpack = require('webpack')
-
-const CopyWebpackPlugin = require('copy-webpack-plugin')
-const MiniCssExtractPlugin = require('mini-css-extract-plugin')
-const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin')
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
-
-const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
-const postCSSConfig = {
-  config: {
-    path: path.join(process.cwd(), 'dev/config/postcss.config.js')
-  }
-}
-const cacheDir = '.webpack-cache/cache'
-const babelDir = path.join(process.cwd(), '.webpack-cache/babel')
-
-process.noDeprecation = true
-
-module.exports = {
-  entry: {
-    client: './client/index.js'
-  },
-  output: {
-    path: path.join(process.cwd(), 'assets'),
-    publicPath: '/',
-    filename: 'js/[name].js',
-    chunkFilename: 'js/[name].js',
-    globalObject: 'this'
-  },
-  module: {
-    rules: [
-      {
-        test: /\.js$/,
-        exclude: /node_modules/,
-        use: [
-          {
-            loader: 'cache-loader',
-            options: {
-              cacheDirectory: cacheDir
-            }
-          },
-          {
-            loader: 'babel-loader',
-            options: {
-              ...babelConfig,
-              cacheDirectory: babelDir
-            }
-          }
-        ]
-      },
-      {
-        test: /\.css$/,
-        use: [
-          MiniCssExtractPlugin.loader,
-          'css-loader',
-          {
-            loader: 'postcss-loader',
-            options: postCSSConfig
-          }
-        ]
-      },
-      {
-        test: /\.scss$/,
-        use: [
-          'style-loader',
-          MiniCssExtractPlugin.loader,
-          'css-loader',
-          {
-            loader: 'postcss-loader',
-            options: postCSSConfig
-          },
-          {
-            loader: 'sass-loader',
-            options: {
-              sourceMap: false
-            }
-          }
-        ]
-      },
-      {
-        test: /\.styl$/,
-        use: [
-          'style-loader',
-          MiniCssExtractPlugin.loader,
-          'css-loader',
-          {
-            loader: 'postcss-loader',
-            options: postCSSConfig
-          },
-          'stylus-loader'
-        ]
-      },
-      {
-        test: /\.vue$/,
-        loader: 'vue-loader',
-        options: {
-          loaders: {
-            css: [
-              'vue-style-loader',
-              MiniCssExtractPlugin.loader,
-              'css-loader',
-              {
-                loader: 'postcss-loader',
-                options: postCSSConfig
-              }
-            ],
-            scss: [
-              'vue-style-loader',
-              MiniCssExtractPlugin.loader,
-              'css-loader',
-              {
-                loader: 'postcss-loader',
-                options: postCSSConfig
-              },
-              {
-                loader: 'sass-loader',
-                options: {
-                  sourceMap: false
-                }
-              },
-              {
-                loader: 'sass-resources-loader',
-                options: {
-                  resources: path.join(process.cwd(), '/client/scss/global.scss')
-                }
-              }
-            ],
-            js: [
-              {
-                loader: 'cache-loader',
-                options: {
-                  cacheDirectory: cacheDir
-                }
-              },
-              {
-                loader: 'babel-loader',
-                options: {
-                  babelrc: path.join(process.cwd(), '.babelrc'),
-                  cacheDirectory: babelDir
-                }
-              }
-            ]
-          }
-        }
-      },
-      {
-        test: /\.(png|jpg|gif)$/,
-        use: [
-          {
-            loader: 'url-loader',
-            options: {
-              limit: 8192
-            }
-          }
-        ]
-      },
-      {
-        test: /\.svg$/,
-        exclude: [
-          path.join(process.cwd(), 'client/svg')
-        ],
-        use: [
-          {
-            loader: 'file-loader',
-            options: {
-              name: '[name].[ext]',
-              outputPath: 'svg/'
-            }
-          }
-        ]
-      },
-      {
-        test: /\.svg$/,
-        include: [
-          path.join(process.cwd(), 'client/svg')
-        ],
-        use: [
-          {
-            loader: 'raw-loader'
-          }
-        ]
-      },
-      {
-        test: /\.flow$/,
-        loader: 'ignore-loader'
-      }
-    ]
-  },
-  plugins: [
-    new webpack.BannerPlugin('Wiki.js - wiki.js.org - Licensed under AGPL'),
-    new CopyWebpackPlugin([
-      { from: 'client/static' }
-    ], {
-
-    }),
-    new MiniCssExtractPlugin({
-      filename: 'css/bundle.css',
-      chunkFilename: 'css/[name].css'
-    }),
-    new HtmlWebpackPlugin({
-      template: 'dev/templates/master.pug',
-      filename: '../server/views/master.pug',
-      hash: true,
-      inject: 'head'
-    }),
-    new HtmlWebpackPugPlugin(),
-    new ScriptExtHtmlWebpackPlugin({
-      sync: 'runtime.js',
-      defaultAttribute: 'async'
-    })
-  ],
-  optimization: {
-    namedModules: true,
-    namedChunks: true,
-    splitChunks: {
-      cacheGroups: {
-        default: {
-          minChunks: 2,
-          priority: -20,
-          reuseExistingChunk: true
-        },
-        vendor: {
-          test: /[\\/]node_modules[\\/]/,
-          priority: -10
-        }
-      }
-    },
-    runtimeChunk: 'single'
-  },
-  resolve: {
-    mainFields: ['browser', 'main', 'module'],
-    symlinks: true,
-    alias: {
-      '@': path.join(process.cwd(), 'client'),
-      'vue$': 'vue/dist/vue.esm.js',
-      'mdi': path.resolve(process.cwd(), 'node_modules/vue-material-design-icons'),
-      // Duplicates fixes:
-      'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'),
-      'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'),
-      'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro')
-    },
-    extensions: [
-      '.js',
-      '.json',
-      '.vue'
-    ],
-    modules: [
-      'node_modules'
-    ]
-  },
-  node: {
-    fs: 'empty'
-  },
-  stats: {
-    children: false,
-    entrypoints: false
-  },
-  target: 'web'
-}

+ 228 - 5
dev/webpack/webpack.dev.js

@@ -1,24 +1,198 @@
 const webpack = require('webpack')
-const merge = require('webpack-merge')
+const path = require('path')
+const fs = require('fs-extra')
 
-const WriteFilePlugin = require('write-file-webpack-plugin')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin')
 const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')
+const WriteFilePlugin = require('write-file-webpack-plugin')
+
+const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
+const cacheDir = '.webpack-cache/cache'
+const babelDir = path.join(process.cwd(), '.webpack-cache/babel')
 
-const common = require('./webpack.common.js')
+process.noDeprecation = true
 
-module.exports = merge(common, {
+module.exports = {
   mode: 'development',
   entry: {
     client: ['./client/index.js', 'webpack-hot-middleware/client']
   },
   output: {
+    path: path.join(process.cwd(), 'assets'),
+    publicPath: '/',
+    filename: 'js/[name].js',
+    chunkFilename: 'js/[name].js',
+    globalObject: 'this',
     pathinfo: true
   },
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: 'cache-loader',
+            options: {
+              cacheDirectory: cacheDir
+            }
+          },
+          {
+            loader: 'babel-loader',
+            options: {
+              ...babelConfig,
+              cacheDirectory: babelDir
+            }
+          }
+        ]
+      },
+      {
+        test: /\.css$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'postcss-loader'
+        ]
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          {
+            loader: 'cache-loader',
+            options: {
+              cacheDirectory: cacheDir
+            }
+          },
+          'style-loader',
+          'css-loader',
+          'postcss-loader',
+          {
+            loader: 'sass-loader',
+            options: {
+              sourceMap: false
+            }
+          }
+        ]
+      },
+      {
+        test: /\.styl$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          'postcss-loader',
+          'stylus-loader'
+        ]
+      },
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            scss: [
+              'vue-style-loader',
+              'css-loader',
+              'postcss-loader',
+              {
+                loader: 'sass-loader',
+                options: {
+                  sourceMap: false
+                }
+              },
+              {
+                loader: 'sass-resources-loader',
+                options: {
+                  resources: path.join(process.cwd(), '/client/scss/global.scss')
+                }
+              }
+            ],
+            js: [
+              {
+                loader: 'cache-loader',
+                options: {
+                  cacheDirectory: cacheDir
+                }
+              },
+              {
+                loader: 'babel-loader',
+                options: {
+                  babelrc: path.join(process.cwd(), '.babelrc'),
+                  cacheDirectory: babelDir
+                }
+              }
+            ]
+          }
+        }
+      },
+      {
+        test: /\.(png|jpg|gif)$/,
+        use: [
+          {
+            loader: 'url-loader',
+            options: {
+              limit: 8192
+            }
+          }
+        ]
+      },
+      {
+        test: /\.svg$/,
+        exclude: [
+          path.join(process.cwd(), 'client/svg')
+        ],
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: '[name].[ext]',
+              outputPath: 'svg/'
+            }
+          }
+        ]
+      },
+      {
+        test: /\.svg$/,
+        include: [
+          path.join(process.cwd(), 'client/svg')
+        ],
+        use: [
+          {
+            loader: 'raw-loader'
+          }
+        ]
+      },
+      {
+        test: /.jsx$/,
+        loader: 'babel-loader',
+        exclude: /node_modules/,
+        options: {
+          presets: ['es2015', 'react']
+        }
+      },
+      {
+        test: /\.flow$/,
+        loader: 'ignore-loader'
+      }
+    ]
+  },
   plugins: [
+    new CopyWebpackPlugin([
+      { from: 'client/static' },
+      { from: './node_modules/graphql-voyager/dist/voyager.worker.js', to: 'js/' }
+    ], {}),
+    new HtmlWebpackPlugin({
+      template: 'dev/templates/master.pug',
+      filename: '../server/views/master.pug',
+      hash: false,
+      inject: 'head'
+    }),
+    new HtmlWebpackPugPlugin(),
     new SimpleProgressWebpackPlugin({
       format: 'compact'
     }),
     new webpack.DefinePlugin({
+      'process.env': { NODE_ENV: '"development"' },
       '__REACT_DEVTOOLS_GLOBAL_HOOK__': '({ isDisabled: true })'
     }),
     new WriteFilePlugin(),
@@ -27,5 +201,54 @@ module.exports = merge(common, {
       /node_modules/
     ])
   ],
+  optimization: {
+    namedModules: true,
+    namedChunks: true,
+    splitChunks: {
+      cacheGroups: {
+        default: {
+          minChunks: 2,
+          priority: -20,
+          reuseExistingChunk: true
+        },
+        vendor: {
+          test: /[\\/]node_modules[\\/]/,
+          minChunks: 2,
+          priority: -10
+        }
+      }
+    },
+    runtimeChunk: 'single'
+  },
+  resolve: {
+    mainFields: ['browser', 'main', 'module'],
+    symlinks: true,
+    alias: {
+      '@': path.join(process.cwd(), 'client'),
+      'vue$': 'vue/dist/vue.esm.js',
+      'mdi': path.resolve(process.cwd(), 'node_modules/vue-material-design-icons'),
+      // Duplicates fixes:
+      'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'),
+      'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'),
+      'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro')
+    },
+    extensions: [
+      '.js',
+      '.json',
+      '.jsx',
+      '.vue'
+    ],
+    modules: [
+      'node_modules'
+    ]
+  },
+  node: {
+    fs: 'empty'
+  },
+  stats: {
+    children: false,
+    entrypoints: false
+  },
+  target: 'web',
   watch: true
-})
+}

+ 237 - 7
dev/webpack/webpack.prod.js

@@ -1,18 +1,209 @@
-const merge = require('webpack-merge')
+const webpack = require('webpack')
+const path = require('path')
+const fs = require('fs-extra')
 
 const CleanWebpackPlugin = require('clean-webpack-plugin')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 const OfflinePlugin = require('offline-plugin')
-const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')
 const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
+const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
+const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')
 
-const common = require('./webpack.common.js')
+const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
+const cacheDir = '.webpack-cache/cache'
+const babelDir = path.join(process.cwd(), '.webpack-cache/babel')
 
-module.exports = merge(common, {
+process.noDeprecation = true
+
+module.exports = {
   mode: 'production',
+  entry: {
+    client: './client/index.js'
+  },
+  output: {
+    path: path.join(process.cwd(), 'assets'),
+    publicPath: '/',
+    filename: 'js/[name].js',
+    chunkFilename: 'js/[name].js',
+    globalObject: 'this'
+  },
   module: {
-    rules: []
+    rules: [
+      {
+        test: /\.js$/,
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: 'cache-loader',
+            options: {
+              cacheDirectory: cacheDir
+            }
+          },
+          {
+            loader: 'babel-loader',
+            options: {
+              ...babelConfig,
+              cacheDirectory: babelDir
+            }
+          }
+        ]
+      },
+      {
+        test: /\.css$/,
+        use: [
+          'style-loader',
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          'postcss-loader'
+        ]
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          {
+            loader: 'cache-loader',
+            options: {
+              cacheDirectory: cacheDir
+            }
+          },
+          'style-loader',
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          'postcss-loader',
+          {
+            loader: 'sass-loader',
+            options: {
+              sourceMap: false
+            }
+          }
+        ]
+      },
+      {
+        test: /\.styl$/,
+        use: [
+          'style-loader',
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          'postcss-loader',
+          'stylus-loader'
+        ]
+      },
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            scss: [
+              'vue-style-loader',
+              MiniCssExtractPlugin.loader,
+              'css-loader',
+              'postcss-loader',
+              {
+                loader: 'sass-loader',
+                options: {
+                  sourceMap: false
+                }
+              },
+              {
+                loader: 'sass-resources-loader',
+                options: {
+                  resources: path.join(process.cwd(), '/client/scss/global.scss')
+                }
+              }
+            ],
+            js: [
+              {
+                loader: 'cache-loader',
+                options: {
+                  cacheDirectory: cacheDir
+                }
+              },
+              {
+                loader: 'babel-loader',
+                options: {
+                  babelrc: path.join(process.cwd(), '.babelrc'),
+                  cacheDirectory: babelDir
+                }
+              }
+            ]
+          }
+        }
+      },
+      {
+        test: /\.(png|jpg|gif)$/,
+        use: [
+          {
+            loader: 'url-loader',
+            options: {
+              limit: 8192
+            }
+          }
+        ]
+      },
+      {
+        test: /\.svg$/,
+        exclude: [
+          path.join(process.cwd(), 'client/svg')
+        ],
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: '[name].[ext]',
+              outputPath: 'svg/'
+            }
+          }
+        ]
+      },
+      {
+        test: /\.svg$/,
+        include: [
+          path.join(process.cwd(), 'client/svg')
+        ],
+        use: [
+          {
+            loader: 'raw-loader'
+          }
+        ]
+      },
+      {
+        test: /.jsx$/,
+        loader: 'babel-loader',
+        exclude: /node_modules/,
+        options: {
+          presets: ['es2015', 'react']
+        }
+      },
+      {
+        test: /\.flow$/,
+        loader: 'ignore-loader'
+      }
+    ]
   },
   plugins: [
+    new webpack.BannerPlugin('Wiki.js - wiki.js.org - Licensed under AGPL'),
+    new CopyWebpackPlugin([
+      { from: 'client/static' },
+      { from: './node_modules/graphql-voyager/dist/voyager.worker.js', to: 'js/' }
+    ], {}),
+    new MiniCssExtractPlugin({
+      filename: 'css/bundle.css',
+      chunkFilename: 'css/[name].css'
+    }),
+    new HtmlWebpackPlugin({
+      template: 'dev/templates/master.pug',
+      filename: '../server/views/master.pug',
+      hash: true,
+      inject: 'head'
+    }),
+    new HtmlWebpackPugPlugin(),
+    new ScriptExtHtmlWebpackPlugin({
+      sync: 'runtime.js',
+      defaultAttribute: 'async'
+    }),
     new SimpleProgressWebpackPlugin({
       format: 'expanded'
     }),
@@ -48,5 +239,44 @@ module.exports = merge(common, {
       },
       safeToUseOptionalCaches: true
     })
-  ]
-})
+  ],
+  optimization: {
+    namedModules: true,
+    namedChunks: true,
+    splitChunks: {
+      name: 'vendor',
+      minChunks: 2
+    },
+    runtimeChunk: 'single'
+  },
+  resolve: {
+    mainFields: ['browser', 'main', 'module'],
+    symlinks: true,
+    alias: {
+      '@': path.join(process.cwd(), 'client'),
+      'vue$': 'vue/dist/vue.esm.js',
+      'mdi': path.resolve(process.cwd(), 'node_modules/vue-material-design-icons'),
+      // Duplicates fixes:
+      'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'),
+      'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'),
+      'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro')
+    },
+    extensions: [
+      '.js',
+      '.json',
+      'jsx',
+      '.vue'
+    ],
+    modules: [
+      'node_modules'
+    ]
+  },
+  node: {
+    fs: 'empty'
+  },
+  stats: {
+    children: false,
+    entrypoints: false
+  },
+  target: 'web'
+}

+ 38 - 2
package.json

@@ -11,7 +11,7 @@
     "build": "webpack --profile --config dev/webpack/webpack.prod.js",
     "build:locales": "node dev/tasks/localization",
     "watch": "webpack --config dev/webpack/webpack.dev.js",
-    "test": "eslint --ext .js,.vue . && jest"
+    "test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest"
   },
   "bin": {
     "wiki": "wiki.js"
@@ -174,6 +174,7 @@
     "file-loader": "1.1.11",
     "graphiql": "0.11.11",
     "graphql-tag": "^2.8.0",
+    "graphql-voyager": "1.0.0-rc.15",
     "hammerjs": "2.0.8",
     "html-webpack-plugin": "3.0.6",
     "html-webpack-pug-plugin": "0.3.0",
@@ -187,9 +188,11 @@
     "node-sass": "4.7.2",
     "offline-plugin": "4.9.0",
     "optimize-css-assets-webpack-plugin": "4.0.0",
+    "postcss-cssnext": "3.1.0",
     "postcss-flexbugs-fixes": "3.3.0",
     "postcss-flexibility": "2.0.0",
-    "postcss-loader": "2.1.1",
+    "postcss-import": "11.1.0",
+    "postcss-loader": "2.1.2",
     "postcss-selector-parser": "3.1.1",
     "pug-lint": "2.5.0",
     "pug-loader": "2.3.0",
@@ -249,6 +252,39 @@
     "classNameTemplate": "{classname}-{title}",
     "titleTemplate": "{classname}-{title}"
   },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {},
+      "cssnano": {
+        "preset": [
+          "default",
+          {
+            "discardComments": {
+              "removeAll": true
+            }
+          }
+        ]
+      },
+      "postcss-flexbugs-fixes": {},
+      "postcss-flexibility": {}
+    }
+  },
+  "pugLintConfig": {
+    "disallowDuplicateAttributes": true,
+    "disallowIdAttributeWithStaticValue": true,
+    "disallowMultipleLineBreaks": true,
+    "requireClassLiteralsBeforeAttributes": true,
+    "requireIdLiteralsBeforeAttributes": true,
+    "requireLineFeedAtFileEnd": true,
+    "requireLowerCaseAttributes": true,
+    "requireLowerCaseTags": true,
+    "requireSpaceAfterCodeOperator": true,
+    "requireStrictEqualityOperators": true,
+    "validateAttributeQuoteMarks": "'",
+    "validateAttributeSeparator": ", ",
+    "validateDivTags": true,
+    "validateIndentation": 2
+  },
   "nodemonConfig": {
     "exec": "node server --dev",
     "ignore": [

+ 45 - 0
server/graph/schemas/system.graphql

@@ -0,0 +1,45 @@
+# ===============================================
+# SYSTEM
+# ===============================================
+
+extend type Query {
+  system: SystemQuery
+}
+
+extend type Mutation {
+  system: SystemMutation
+}
+
+# -----------------------------------------------
+# QUERIES
+# -----------------------------------------------
+
+type SystemQuery {
+  info: SystemInfo
+}
+
+# -----------------------------------------------
+# MUTATIONS
+# -----------------------------------------------
+
+type SystemMutation {
+  todo: String
+}
+
+# -----------------------------------------------
+# TYPES
+# -----------------------------------------------
+
+type SystemInfo {
+  currentVersion: String
+  latestVersion: String
+  latestVersionReleaseDate: Date
+  operatingSystem: String
+  hostname: String
+  cpuCores: Int
+  ramTotal: Int
+  workingDirectory: String
+  nodeVersion: String
+  redisVersion: String
+  postgreVersion: String
+}

+ 2 - 4
server/views/main/login.pug

@@ -1,7 +1,5 @@
 extends ../master.pug
 
 block body
-  body
-    #app.is-fullscreen
-      login
-      navigator
+  #app.is-fullscreen
+    login

+ 7 - 8
server/views/main/welcome.pug

@@ -1,11 +1,10 @@
 extends ../master.pug
 
 block body
-  body
-    #app.is-fullscreen
-      v-app
-        .onboarding
-          img(src='/svg/logo-wikijs.svg', alt='Wiki.js')
-          h1= t('welcome.title')
-          h2= t('welcome.subtitle')
-          v-btn(color='primary', href='/e/home')= t('welcome.createhome')
+  #app.is-fullscreen
+    v-app
+      .onboarding
+        img(src='/svg/logo-wikijs.svg', alt='Wiki.js')
+        h1= t('welcome.title')
+        h2= t('welcome.subtitle')
+        v-btn(color='primary', href='/e/home')= t('welcome.createhome')

+ 3 - 3
server/views/master.pug

@@ -27,8 +27,8 @@ html
 
     block head
 
-    link(href="/css/client.css?89183d15bb0e30aac49f" rel="stylesheet")
-    script(type="text/javascript" src="/js/runtime.js?89183d15bb0e30aac49f")
-    script(type="text/javascript" src="/js/client.js?89183d15bb0e30aac49f" async)
+    link(href="/css/client.css?c473342a20546c545ebc" rel="stylesheet")
+    script(type="text/javascript" src="/js/runtime.js?c473342a20546c545ebc")
+    script(type="text/javascript" src="/js/client.js?c473342a20546c545ebc" async)
   body
     block body

+ 0 - 77
test/lint.js

@@ -1,77 +0,0 @@
-'use strict'
-
-const fs = require('fs-extra')
-const colors = require('colors')
-
-expect.extend({
-  /**
-   * Expect ESLint results to have no errors
-   * @param {*} received ESLint results
-   * @param {*} argument Arguments
-   * @returns {object} Matcher result
-   */
-  toESLint (received, argument) {
-    if (received && received.errorCount > 0) {
-      let errorMsgBuf = []
-      for (let i = 0; i < received.results.length; i++) {
-        const result = received.results[i]
-        if (result.errorCount <= 0) {
-          continue
-        }
-
-        for (let x = 0; x < result.messages.length; x++) {
-          const m = result.messages[x]
-          errorMsgBuf.push(colors.grey(`└── ${result.filePath}\t${m.line}:${m.column}\t${m.message}`))
-        }
-      }
-      if (errorMsgBuf.length > 0) {
-        return {
-          message: () => (errorMsgBuf.join(`\n`)),
-          pass: false
-        }
-      }
-    }
-    return {
-      pass: true
-    }
-  },
-  /**
-   * Expect PugLint results to have no errors
-   * @param {*} received PugLint results
-   * @param {*} argument Arguments
-   * @returns {object} Matcher result
-   */
-  toPugLint (received, argument) {
-    if (received && received.length > 0) {
-      let errorMsgBuf = []
-      for (let i = 0; i < received.length; i++) {
-        errorMsgBuf.push(colors.grey(`└── ${received[i].message}`))
-      }
-      return {
-        message: () => (errorMsgBuf.join(`\n`)),
-        pass: false
-      }
-    }
-    return {
-      pass: true
-    }
-  }
-})
-
-describe('Code Linting', () => {
-  // it('should pass ESLint validation', () => {
-  //   const CLIEngine = require('eslint').CLIEngine
-  //   const cli = new CLIEngine()
-  //   let report = cli.executeOnFiles(['**/*.js', '**/*.vue'])
-  //   expect(report).toESLint()
-  // })
-
-  it('should pass PugLint validation', () => {
-    const PugLint = require('pug-lint')
-    const lint = new PugLint()
-    const pugConfig = fs.readJsonSync('tools/pug-lintrc.json')
-    lint.configure(pugConfig)
-    let report = lint.checkPath('./server/views')
-    expect(report).toPugLint()
-  })
-})

+ 4 - 1
wiki.js

@@ -84,7 +84,10 @@ const init = {
         console.info('>>> Starting Wiki.js in DEVELOPER mode...')
         require('./server')
 
-        const devWatcher = chokidar.watch('./server')
+        const devWatcher = chokidar.watch([
+          './server',
+          '!./server/views/master.pug'
+        ])
         devWatcher.on('ready', () => {
           devWatcher.on('all', () => {
             console.warn('--- >>>>>>>>>>>>>>>>>>>>>>>>>>>> ---')

+ 530 - 17
yarn.lock

@@ -79,6 +79,38 @@
     lodash "^4.2.0"
     to-fast-properties "^2.0.0"
 
+"@f/animate@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@f/animate/-/animate-1.0.1.tgz#a031398adadf9a0bd3a56398cec931f877d88485"
+  dependencies:
+    "@f/elapsed-time" "^1.0.0"
+    "@f/raf" "^1.0.0"
+    "@f/tween" "^1.0.0"
+
+"@f/elapsed-time@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@f/elapsed-time/-/elapsed-time-1.0.0.tgz#6a079a6104a87278bf5b4080444ef02d1b595449"
+  dependencies:
+    "@f/timestamp" "^1.0.0"
+
+"@f/map-obj@^1.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@f/map-obj/-/map-obj-1.2.2.tgz#d9a9f8bd76caa2ae11b633dda24d9c6cccc1e60d"
+
+"@f/raf@^1.0.0":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@f/raf/-/raf-1.0.3.tgz#32ddca37de345b20c8c3843018cc51b8f897914d"
+
+"@f/timestamp@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@f/timestamp/-/timestamp-1.0.0.tgz#32a9166e2516e5ccb9b0fcfdc89223819710e88c"
+
+"@f/tween@^1.0.0":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@f/tween/-/tween-1.0.1.tgz#18aef79c4975e54415adf326e4b5e0d053d207f0"
+  dependencies:
+    "@f/map-obj" "^1.2.2"
+
 "@mrmlnc/readdir-enhanced@^2.2.1":
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -779,6 +811,17 @@ autoprefixer@^6.3.1:
     postcss "^5.2.16"
     postcss-value-parser "^3.2.3"
 
+autoprefixer@^7.1.1:
+  version "7.2.6"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.6.tgz#256672f86f7c735da849c4f07d008abb056067dc"
+  dependencies:
+    browserslist "^2.11.3"
+    caniuse-lite "^1.0.30000805"
+    normalize-range "^0.1.2"
+    num2fraction "^1.2.2"
+    postcss "^6.0.17"
+    postcss-value-parser "^3.2.3"
+
 aws-sign2@~0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@@ -1599,7 +1642,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0:
     core-js "^2.4.0"
     regenerator-runtime "^0.10.0"
 
-babel-runtime@^6.26.0:
+babel-runtime@^6.23.0, babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
@@ -1706,6 +1749,10 @@ backoff@^2.5.0:
   dependencies:
     precond "0.2"
 
+balanced-match@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a"
+
 balanced-match@^0.4.2:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
@@ -1973,7 +2020,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
     caniuse-db "^1.0.30000639"
     electron-to-chromium "^1.2.7"
 
-browserslist@^2.0.0:
+browserslist@^2.0.0, browserslist@^2.11.3:
   version "2.11.3"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2"
   dependencies:
@@ -2218,6 +2265,10 @@ caniuse-lite@^1.0.30000744:
   version "1.0.30000744"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000744.tgz#860fa5c83ba34fe619397d607f30bb474821671b"
 
+caniuse-lite@^1.0.30000805:
+  version "1.0.30000815"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000815.tgz#3a4258e6850362185adb11b0d754a48402d35bf6"
+
 caniuse-lite@^1.0.30000809, caniuse-lite@^1.0.30000810:
   version "1.0.30000812"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000812.tgz#d173b686b49bc941fa18ff2e7e533048e20ed92c"
@@ -2250,6 +2301,10 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
+chain-function@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
+
 chalk@2.3.x, chalk@^2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
@@ -2420,6 +2475,10 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
+classnames@^2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
+
 clean-css@4.1.x:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a"
@@ -2621,7 +2680,7 @@ color-convert@^1.0.0, color-convert@^1.9.0:
   dependencies:
     color-name "^1.1.1"
 
-color-convert@^1.3.0, color-convert@^1.8.2:
+color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
   dependencies:
@@ -2637,7 +2696,7 @@ color-string@^0.3.0:
   dependencies:
     color-name "^1.0.0"
 
-color-string@^1.4.0:
+color-string@^1.4.0, color-string@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9"
   dependencies:
@@ -2659,13 +2718,20 @@ color@^0.11.0:
     color-convert "^1.3.0"
     color-string "^0.3.0"
 
-color@^1.0.0:
+color@^1.0.0, color@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/color/-/color-1.0.3.tgz#e48e832d85f14ef694fb468811c2d5cfe729b55d"
   dependencies:
     color-convert "^1.8.2"
     color-string "^1.4.0"
 
+color@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839"
+  dependencies:
+    color-convert "^1.9.1"
+    color-string "^1.5.2"
+
 colormin@^1.0.5:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133"
@@ -2737,6 +2803,15 @@ commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
 
+commonmark@^0.28.1:
+  version "0.28.1"
+  resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.28.1.tgz#06eab8d52338b839fa1a2d75af0085eed1b1beae"
+  dependencies:
+    entities "~ 1.1.1"
+    mdurl "~ 1.0.1"
+    minimist "~ 1.2.0"
+    string.prototype.repeat "^0.2.0"
+
 component-emitter@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
@@ -3060,6 +3135,15 @@ crypto-browserify@^3.11.0:
     randombytes "^2.0.0"
     randomfill "^1.0.3"
 
+css-color-function@~1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e"
+  dependencies:
+    balanced-match "0.1.0"
+    color "^0.11.0"
+    debug "^3.1.0"
+    rgb "~0.1.0"
+
 css-color-names@0.0.4, css-color-names@^0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@@ -3589,6 +3673,10 @@ dom-converter@~0.1:
   dependencies:
     utila "~0.3"
 
+dom-helpers@^3.2.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
+
 dom-serializer@0, dom-serializer@~0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -3817,7 +3905,7 @@ enhanced-resolve@^4.0.0:
     memory-fs "^0.4.0"
     tapable "^1.0.0"
 
-entities@^1.1.1, entities@~1.1.1:
+entities@^1.1.1, "entities@~ 1.1.1", entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
@@ -4138,6 +4226,10 @@ execa@^0.9.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
+exenv@^1.2.0:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+
 exit-hook@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -5130,7 +5222,7 @@ graphql-list-fields@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/graphql-list-fields/-/graphql-list-fields-2.0.1.tgz#06d921eddfbadafa66970e21ff0acdab3cf039db"
 
-graphql-request@^1.4.0:
+graphql-request@^1.3.6, graphql-request@^1.4.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.5.1.tgz#cccdf5cce6432ca062b90f7b63793c77c821ff9a"
   dependencies:
@@ -5154,6 +5246,25 @@ graphql-tools@2.23.1:
     iterall "^1.1.3"
     uuid "^3.1.0"
 
+graphql-voyager@1.0.0-rc.15:
+  version "1.0.0-rc.15"
+  resolved "https://registry.yarnpkg.com/graphql-voyager/-/graphql-voyager-1.0.0-rc.15.tgz#1ece08f2ec0606dbf7ecec234fb4bd1466c05f0c"
+  dependencies:
+    "@f/animate" "^1.0.1"
+    classnames "^2.2.5"
+    clipboard "^1.7.1"
+    commonmark "^0.28.1"
+    graphql-request "^1.3.6"
+    lodash "^4.17.2"
+    prop-types "^15.6.0"
+    react-modal "^2.3.3"
+    react-redux "^5.0.6"
+    react-toolbox "^2.0.0-beta.11"
+    redux "^3.7.1"
+    redux-thunk "^2.1.0"
+    reselect "^3.0.1"
+    svg-pan-zoom "^3.5.2"
+
 graphql@0.13.2:
   version "0.13.2"
   resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270"
@@ -5377,6 +5488,14 @@ hogan.js@^3.0.2:
     mkdirp "0.3.0"
     nopt "1.0.10"
 
+hoist-non-react-statics@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
+
+hoist-non-react-statics@^2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
+
 home-or-tmp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -5725,6 +5844,12 @@ into-stream@^3.1.0:
     from2 "^2.1.1"
     p-is-promise "^1.1.0"
 
+invariant@^2.0.0, invariant@^2.2.1:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+  dependencies:
+    loose-envify "^1.0.0"
+
 invariant@^2.2.0, invariant@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
@@ -6181,6 +6306,10 @@ isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
 
+isnumeric@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64"
+
 isobject@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
@@ -7100,12 +7229,20 @@ locate-path@^2.0.0:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
+lodash-es@^4.17.5, lodash-es@^4.2.1:
+  version "4.17.7"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.7.tgz#db240a3252c3dd8360201ac9feef91ac977ea856"
+
 lodash-webpack-plugin@0.11.4:
   version "0.11.4"
   resolved "https://registry.yarnpkg.com/lodash-webpack-plugin/-/lodash-webpack-plugin-0.11.4.tgz#6c3ecba3d4b8d24b53940b63542715c5ed3c4ac5"
   dependencies:
     lodash "^4.17.4"
 
+lodash._reinterpolate@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+
 lodash.assign@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@@ -7262,6 +7399,19 @@ lodash.tail@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
 
+lodash.template@^4.2.4:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
+  dependencies:
+    lodash._reinterpolate "~3.0.0"
+    lodash.templatesettings "^4.0.0"
+
+lodash.templatesettings@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
+  dependencies:
+    lodash._reinterpolate "~3.0.0"
+
 lodash.throttle@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
@@ -7274,7 +7424,7 @@ lodash.values@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347"
 
-lodash@4.17.5, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.5:
+lodash@4.17.5, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1:
   version "4.17.5"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
 
@@ -7493,7 +7643,7 @@ md5@^2.2.1:
     crypt "~0.0.1"
     is-buffer "~1.1.1"
 
-mdurl@^1.0.1:
+mdurl@^1.0.1, "mdurl@~ 1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
 
@@ -7727,7 +7877,7 @@ minimist@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de"
 
-minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, "minimist@~ 1.2.0":
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
@@ -8285,6 +8435,10 @@ one-time@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
 
+onecolor@^3.0.4:
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.0.5.tgz#36eff32201379efdf1180fb445e51a8e2425f9f6"
+
 onetime@^1.0.0:
   version "1.1.0"
   resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
@@ -8873,6 +9027,14 @@ pinkie@^2.0.0:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
 
+pixrem@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/pixrem/-/pixrem-4.0.1.tgz#2da4a1de6ec4423c5fc3794e930b81d4490ec686"
+  dependencies:
+    browserslist "^2.0.0"
+    postcss "^6.0.0"
+    reduce-css-calc "^1.2.7"
+
 pkg-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@@ -8889,6 +9051,13 @@ pkginfo@0.2.x, pkginfo@^0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.2.3.tgz#7239c42a5ef6c30b8f328439d9b9ff71042490f8"
 
+pleeease-filters@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/pleeease-filters/-/pleeease-filters-4.0.0.tgz#6632b2fb05648d2758d865384fbced79e1ccaec7"
+  dependencies:
+    onecolor "^3.0.4"
+    postcss "^6.0.1"
+
 pluralize@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
@@ -8975,6 +9144,21 @@ posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
 
+postcss-apply@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/postcss-apply/-/postcss-apply-0.8.0.tgz#14e544bbb5cb6f1c1e048857965d79ae066b1343"
+  dependencies:
+    babel-runtime "^6.23.0"
+    balanced-match "^0.4.2"
+    postcss "^6.0.0"
+
+postcss-attribute-case-insensitive@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz#94dc422c8f90997f16bd33a3654bbbec084963b4"
+  dependencies:
+    postcss "^6.0.0"
+    postcss-selector-parser "^2.2.3"
+
 postcss-calc@^5.2.0:
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
@@ -8992,6 +9176,71 @@ postcss-calc@^6.0.0:
     postcss-selector-parser "^2.2.2"
     reduce-css-calc "^2.0.0"
 
+postcss-color-function@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-color-function/-/postcss-color-function-4.0.1.tgz#402b3f2cebc3f6947e618fb6be3654fbecef6444"
+  dependencies:
+    css-color-function "~1.3.3"
+    postcss "^6.0.1"
+    postcss-message-helpers "^2.0.0"
+    postcss-value-parser "^3.3.0"
+
+postcss-color-gray@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz#e5581ed57eaa826fb652ca11b1e2b7b136a9f9df"
+  dependencies:
+    color "^2.0.1"
+    postcss "^6.0.14"
+    postcss-message-helpers "^2.0.0"
+    reduce-function-call "^1.0.2"
+
+postcss-color-hex-alpha@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz#1e53e6c8acb237955e8fd08b7ecdb1b8b8309f95"
+  dependencies:
+    color "^1.0.3"
+    postcss "^6.0.1"
+    postcss-message-helpers "^2.0.0"
+
+postcss-color-hsl@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz#12703666fa310430e3f30a454dac1386317d5844"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-value-parser "^3.3.0"
+    units-css "^0.4.0"
+
+postcss-color-hwb@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz#3402b19ef4d8497540c1fb5072be9863ca95571e"
+  dependencies:
+    color "^1.0.3"
+    postcss "^6.0.1"
+    postcss-message-helpers "^2.0.0"
+    reduce-function-call "^1.0.2"
+
+postcss-color-rebeccapurple@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.0.0.tgz#eebaf03d363b4300b96792bd3081c19ed66513d3"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-value-parser "^3.3.0"
+
+postcss-color-rgb@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz#14539c8a7131494b482e0dd1cc265ff6514b5263"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-value-parser "^3.3.0"
+
+postcss-color-rgba-fallback@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz#37d5c9353a07a09270912a82606bb42a0d702c04"
+  dependencies:
+    postcss "^6.0.6"
+    postcss-value-parser "^3.3.0"
+    rgb-hex "^2.1.0"
+
 postcss-colormin@^2.1.8:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
@@ -9024,6 +9273,62 @@ postcss-convert-values@^4.0.0-rc.2:
     postcss "^6.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-cssnext@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz#927dc29341a938254cde38ea60a923b9dfedead9"
+  dependencies:
+    autoprefixer "^7.1.1"
+    caniuse-api "^2.0.0"
+    chalk "^2.0.1"
+    pixrem "^4.0.0"
+    pleeease-filters "^4.0.0"
+    postcss "^6.0.5"
+    postcss-apply "^0.8.0"
+    postcss-attribute-case-insensitive "^2.0.0"
+    postcss-calc "^6.0.0"
+    postcss-color-function "^4.0.0"
+    postcss-color-gray "^4.0.0"
+    postcss-color-hex-alpha "^3.0.0"
+    postcss-color-hsl "^2.0.0"
+    postcss-color-hwb "^3.0.0"
+    postcss-color-rebeccapurple "^3.0.0"
+    postcss-color-rgb "^2.0.0"
+    postcss-color-rgba-fallback "^3.0.0"
+    postcss-custom-media "^6.0.0"
+    postcss-custom-properties "^6.1.0"
+    postcss-custom-selectors "^4.0.1"
+    postcss-font-family-system-ui "^3.0.0"
+    postcss-font-variant "^3.0.0"
+    postcss-image-set-polyfill "^0.3.5"
+    postcss-initial "^2.0.0"
+    postcss-media-minmax "^3.0.0"
+    postcss-nesting "^4.0.1"
+    postcss-pseudo-class-any-link "^4.0.0"
+    postcss-pseudoelements "^5.0.0"
+    postcss-replace-overflow-wrap "^2.0.0"
+    postcss-selector-matches "^3.0.1"
+    postcss-selector-not "^3.0.1"
+
+postcss-custom-media@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz#be532784110ecb295044fb5395a18006eb21a737"
+  dependencies:
+    postcss "^6.0.1"
+
+postcss-custom-properties@^6.1.0:
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz#5c52abde313d7ec9368c4abf67d27a656cba8b39"
+  dependencies:
+    balanced-match "^1.0.0"
+    postcss "^6.0.18"
+
+postcss-custom-selectors@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz#781382f94c52e727ef5ca4776ea2adf49a611382"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-selector-matches "^3.0.0"
+
 postcss-discard-comments@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d"
@@ -9098,6 +9403,41 @@ postcss-flexibility@2.0.0:
   dependencies:
     postcss "^6.0.1"
 
+postcss-font-family-system-ui@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz#675fe7a9e029669f05f8dba2e44c2225ede80623"
+  dependencies:
+    postcss "^6.0"
+
+postcss-font-variant@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz#08ccc88f6050ba82ed8ef2cc76c0c6a6b41f183e"
+  dependencies:
+    postcss "^6.0.1"
+
+postcss-image-set-polyfill@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz#0f193413700cf1f82bd39066ef016d65a4a18181"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-media-query-parser "^0.2.3"
+
+postcss-import@11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-11.1.0.tgz#55c9362c9192994ec68865d224419df1db2981f0"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-value-parser "^3.2.3"
+    read-cache "^1.0.0"
+    resolve "^1.1.7"
+
+postcss-initial@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-2.0.0.tgz#72715f7336e0bb79351d99ee65c4a253a8441ba4"
+  dependencies:
+    lodash.template "^4.2.4"
+    postcss "^6.0.1"
+
 postcss-load-config@^1.1.0, postcss-load-config@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
@@ -9121,15 +9461,25 @@ postcss-load-plugins@^2.3.0:
     cosmiconfig "^2.1.1"
     object-assign "^4.1.0"
 
-postcss-loader@2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.1.tgz#208935af3b1d65e1abb1a870a912dd12e7b36895"
+postcss-loader@2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.2.tgz#03f54e23be736c9ea4f610e17d2a299254c05130"
   dependencies:
     loader-utils "^1.1.0"
     postcss "^6.0.0"
     postcss-load-config "^1.2.0"
     schema-utils "^0.4.0"
 
+postcss-media-minmax@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz#675256037a43ef40bc4f0760bfd06d4dc69d48d2"
+  dependencies:
+    postcss "^6.0.1"
+
+postcss-media-query-parser@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
+
 postcss-merge-idents@^2.1.5:
   version "2.1.7"
   resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
@@ -9272,6 +9622,12 @@ postcss-modules-values@^1.3.0:
     icss-replace-symbols "^1.1.0"
     postcss "^6.0.1"
 
+postcss-nesting@^4.0.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d"
+  dependencies:
+    postcss "^6.0.11"
+
 postcss-normalize-charset@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1"
@@ -9373,6 +9729,19 @@ postcss-ordered-values@^4.0.0-rc.2:
     postcss "^6.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-pseudo-class-any-link@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz#9152a0613d3450720513e8892854bae42d0ee68e"
+  dependencies:
+    postcss "^6.0.1"
+    postcss-selector-parser "^2.2.3"
+
+postcss-pseudoelements@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz#eef194e8d524645ca520a949e95e518e812402cb"
+  dependencies:
+    postcss "^6.0.0"
+
 postcss-reduce-idents@^2.2.2:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3"
@@ -9412,6 +9781,26 @@ postcss-reduce-transforms@^4.0.0-rc.2:
     postcss "^6.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-replace-overflow-wrap@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz#794db6faa54f8db100854392a93af45768b4e25b"
+  dependencies:
+    postcss "^6.0.1"
+
+postcss-selector-matches@^3.0.0, postcss-selector-matches@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz#e5634011e13950881861bbdd58c2d0111ffc96ab"
+  dependencies:
+    balanced-match "^0.4.2"
+    postcss "^6.0.1"
+
+postcss-selector-not@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz#2e4db2f0965336c01e7cec7db6c60dff767335d9"
+  dependencies:
+    balanced-match "^0.4.2"
+    postcss "^6.0.1"
+
 postcss-selector-parser@3.1.1, postcss-selector-parser@^3.0.0-rc.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865"
@@ -9420,7 +9809,7 @@ postcss-selector-parser@3.1.1, postcss-selector-parser@^3.0.0-rc.0:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
+postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
   dependencies:
@@ -9483,6 +9872,14 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
     source-map "^0.5.6"
     supports-color "^3.2.3"
 
+postcss@^6.0, postcss@^6.0.11, postcss@^6.0.14, postcss@^6.0.17, postcss@^6.0.18, postcss@^6.0.5, postcss@^6.0.6:
+  version "6.0.20"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.20.tgz#686107e743a12d5530cb68438c590d5b2bf72c3c"
+  dependencies:
+    chalk "^2.3.2"
+    source-map "^0.6.1"
+    supports-color "^5.3.0"
+
 postcss@^6.0.0, postcss@^6.0.8:
   version "6.0.16"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.16.tgz#112e2fe2a6d2109be0957687243170ea5589e146"
@@ -9615,7 +10012,7 @@ promptly@2.2.0:
   dependencies:
     read "^1.0.4"
 
-prop-types@^15.6.0:
+prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.6.0:
   version "15.6.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
   dependencies:
@@ -9875,6 +10272,10 @@ querystring@0.2.0, querystring@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
 
+ramda@^0.23.0:
+  version "0.23.0"
+  resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.23.0.tgz#ccd13fff73497a93974e3e86327bfd87bd6e8e2b"
+
 random-bytes@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@@ -9935,6 +10336,13 @@ rc@^1.1.7:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
+react-css-themr@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/react-css-themr/-/react-css-themr-2.1.2.tgz#e017514e471c232f43a754a55b49d81faf5dafb8"
+  dependencies:
+    hoist-non-react-statics "^1.2.0"
+    invariant "^2.2.1"
+
 react-dom@16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
@@ -9944,6 +10352,51 @@ react-dom@16.2.0:
     object-assign "^4.1.1"
     prop-types "^15.6.0"
 
+react-modal@^2.3.3:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-2.4.1.tgz#cb09b26711b148eb9f59cb180e1b7d82980ded05"
+  dependencies:
+    exenv "^1.2.0"
+    prop-types "^15.5.10"
+
+react-redux@^5.0.6:
+  version "5.0.7"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
+  dependencies:
+    hoist-non-react-statics "^2.5.0"
+    invariant "^2.0.0"
+    lodash "^4.17.5"
+    lodash-es "^4.17.5"
+    loose-envify "^1.1.0"
+    prop-types "^15.6.0"
+
+react-style-proptype@^3.0.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.2.1.tgz#7cfeb9b87ec7ab9dcbde9715170ed10c11fb86aa"
+  dependencies:
+    prop-types "^15.5.4"
+
+react-toolbox@^2.0.0-beta.11:
+  version "2.0.0-beta.12"
+  resolved "https://registry.yarnpkg.com/react-toolbox/-/react-toolbox-2.0.0-beta.12.tgz#1d9dd7cc41e3b35dabdce5eb100a8068eee88c45"
+  dependencies:
+    classnames "^2.2.5"
+    core-js "^2.4.0"
+    ramda "^0.23.0"
+    react-css-themr "^2.1.2"
+    react-style-proptype "^3.0.0"
+    react-transition-group "^1.1.3"
+
+react-transition-group@^1.1.3:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
+  dependencies:
+    chain-function "^1.0.0"
+    dom-helpers "^3.2.0"
+    loose-envify "^1.3.1"
+    prop-types "^15.5.6"
+    warning "^3.0.0"
+
 react@16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
@@ -9953,6 +10406,12 @@ react@16.2.0:
     object-assign "^4.1.1"
     prop-types "^15.6.0"
 
+read-cache@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+  dependencies:
+    pify "^2.3.0"
+
 read-chunk@2.1.0, read-chunk@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655"
@@ -10135,7 +10594,7 @@ redis@~0.10.0:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/redis/-/redis-0.10.3.tgz#8927fe2110ee39617bcf3fd37b89d8e123911bb6"
 
-reduce-css-calc@^1.2.6:
+reduce-css-calc@^1.2.6, reduce-css-calc@^1.2.7:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
   dependencies:
@@ -10150,12 +10609,25 @@ reduce-css-calc@^2.0.0:
     css-unit-converter "^1.1.1"
     postcss-value-parser "^3.3.0"
 
-reduce-function-call@^1.0.1:
+reduce-function-call@^1.0.1, reduce-function-call@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
   dependencies:
     balanced-match "^0.4.2"
 
+redux-thunk@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
+
+redux@^3.7.1:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
+  dependencies:
+    lodash "^4.2.1"
+    lodash-es "^4.2.1"
+    loose-envify "^1.1.0"
+    symbol-observable "^1.0.3"
+
 regenerate@^1.2.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
@@ -10430,6 +10902,10 @@ require_optional@^1.0.1:
     resolve-from "^2.0.0"
     semver "^5.1.0"
 
+reselect@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -10506,10 +10982,18 @@ retry@0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/retry/-/retry-0.6.0.tgz#1c010713279a6fd1e8def28af0c3ff1871caa537"
 
+rgb-hex@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-2.1.0.tgz#c773c5fe2268a25578d92539a82a7a5ce53beda6"
+
 rgb-regex@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
 
+rgb@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5"
+
 rgba-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
@@ -11213,6 +11697,10 @@ string.prototype.padstart@^3.0.0:
     es-abstract "^1.4.3"
     function-bind "^1.0.2"
 
+string.prototype.repeat@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz#aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf"
+
 string@^3.3.3:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/string/-/string-3.3.3.tgz#5ea211cd92d228e184294990a6cc97b366a77cb0"
@@ -11358,6 +11846,10 @@ supports-color@^5.3.0:
   dependencies:
     has-flag "^3.0.0"
 
+svg-pan-zoom@^3.5.2:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/svg-pan-zoom/-/svg-pan-zoom-3.5.3.tgz#e6b52a1b349716c78e7b5f92d0ed1cc1b860f5d8"
+
 svgo@^0.7.0:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
@@ -11382,6 +11874,10 @@ symbol-observable@^1.0.2:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
 
+symbol-observable@^1.0.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
+
 symbol-tree@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
@@ -11804,6 +12300,13 @@ unique-slug@^2.0.0:
   dependencies:
     imurmurhash "^0.1.4"
 
+units-css@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07"
+  dependencies:
+    isnumeric "^0.2.0"
+    viewport-dimensions "^0.2.0"
+
 universalify@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
@@ -12010,6 +12513,10 @@ verror@^1.8.1:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+viewport-dimensions@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"
+
 vinyl-file@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a"
@@ -12187,6 +12694,12 @@ walker@~1.0.5:
   dependencies:
     makeerror "1.0.x"
 
+warning@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
+  dependencies:
+    loose-envify "^1.0.0"
+
 watch@~0.10.0:
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"