Ver código fonte

fix: setup issues + webpack improvements

Nicolas Giard 6 anos atrás
pai
commit
ed7d3ab540

+ 3 - 1
.babelrc

@@ -20,7 +20,9 @@
   ],
   "presets": [
     [
-      "@babel/preset-env"
+      "@babel/preset-env", {
+        "useBuiltIns": "entry"
+      }
     ]
   ]
 }

+ 1 - 4
client/app.js → client/client-app.js

@@ -1,5 +1,3 @@
-'use strict'
-
 /* global siteConfig */
 
 import Vue from 'vue'
@@ -116,7 +114,6 @@ Vue.component('nav-footer', () => import(/* webpackMode: "eager" */ './component
 Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue'))
 Vue.component('nav-sidebar', () => import(/* webpackMode: "eager" */ './components/common/nav-sidebar.vue'))
 Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue'))
-Vue.component('setup', () => import(/* webpackChunkName: "setup" */ './components/setup.vue'))
 Vue.component('v-card-chin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue'))
 Vue.component('page', () => import(/* webpackChunkName: "theme-page" */ './themes/' + process.env.CURRENT_THEME + '/components/app.vue'))
 
@@ -142,7 +139,7 @@ let bootstrap = () => {
     el: '#root',
     components: {},
     mixins: [helpers],
-    provide: apolloProvider.provide(),
+    apolloProvider,
     store,
     i18n
   })

+ 30 - 0
client/client-setup.js

@@ -0,0 +1,30 @@
+/* eslint-disable import/first */
+import Vue from 'vue'
+import Vuetify from 'vuetify'
+import VeeValidate from 'vee-validate'
+import boot from './modules/boot'
+/* eslint-enable import/first */
+
+window.WIKI = null
+window.boot = boot
+
+Vue.use(Vuetify)
+Vue.use(VeeValidate, { events: '' })
+
+Vue.component('setup', () => import(/* webpackMode: "eager" */ './components/setup.vue'))
+
+let bootstrap = () => {
+  window.WIKI = new Vue({
+    el: '#root'
+  })
+
+  // ====================================
+  // Load Icons
+  // ====================================
+
+  import(/* webpackChunkName: "icons" */ './svg/icons.svg').then(icons => {
+    document.body.insertAdjacentHTML('beforeend', icons.default)
+  })
+}
+
+window.boot.onDOMReady(bootstrap)

+ 95 - 46
client/components/admin/admin-navigation.vue

@@ -3,63 +3,77 @@
     v-layout(row wrap)
       v-flex(xs12)
         .admin-header-icon: v-icon(size='80', color='grey lighten-2') near_me
-        .headline.primary--text {{$t('admin:navigation.title')}}
-        .subheading.grey--text {{$t('admin:navigation.subtitle')}}
+        .headline.primary--text {{$t('navigation.title')}}
+        .subheading.grey--text {{$t('navigation.subtitle')}}
         v-container.pa-0.mt-3(fluid, grid-list-lg)
           v-layout(row)
             v-flex(style='flex: 0 0 350px;')
               v-card
                 v-list.primary.py-2(dense, dark)
-                  draggable
+                  draggable(v-model='navTree')
                     template(v-for='navItem in navTree')
-                      v-list-tile(v-if='navItem.kind === "link"', :class='(navItem === current) ? "blue" : ""', @click='selectItem(navItem)')
+                      v-list-tile(
+                        v-if='navItem.kind === "link"'
+                        :key='navItem.id'
+                        :class='(navItem === current) ? "blue" : ""'
+                        @click='selectItem(navItem)'
+                        )
                         v-list-tile-avatar: v-icon {{navItem.icon}}
                         v-list-tile-title {{navItem.label}}
-                      .py-2.clickable(v-else-if='navItem.kind === "divider"', :class='(navItem === current) ? "blue" : ""', @click='selectItem(navItem)')
+                      .py-2.clickable(
+                        v-else-if='navItem.kind === "divider"'
+                        :key='navItem.id'
+                        :class='(navItem === current) ? "blue" : ""'
+                        @click='selectItem(navItem)'
+                        )
                         v-divider
-                      v-subheader.pl-4.clickable(v-else-if='navItem.kind === "header"', :class='(navItem === current) ? "blue" : ""', @click='selectItem(navItem)') {{navItem.label}}
+                      v-subheader.pl-4.clickable(
+                        v-else-if='navItem.kind === "header"'
+                        :key='navItem.id'
+                        :class='(navItem === current) ? "blue" : ""'
+                        @click='selectItem(navItem)'
+                        ) {{navItem.label}}
                 v-card-chin
-                  v-spacer
-                  v-menu(offset-y, bottom, min-width='200px')
-                    v-btn(slot='activator', color='primary', depressed)
+                  v-menu(offset-y, bottom, min-width='200px', style='flex: 1 1;')
+                    v-btn(slot='activator', color='primary', depressed, block)
                       v-icon(left) add
-                      span Add
+                      span {{$t('common:actions.add')}}
                     v-list
                       v-list-tile(@click='addItem("link")')
                         v-list-tile-avatar: v-icon link
-                        v-list-tile-title Link
+                        v-list-tile-title {{$t('navigation.link')}}
                       v-list-tile(@click='addItem("header")')
                         v-list-tile-avatar: v-icon title
-                        v-list-tile-title Header
+                        v-list-tile-title {{$t('navigation.header')}}
                       v-list-tile(@click='addItem("divider")')
                         v-list-tile-avatar: v-icon power_input
-                        v-list-tile-title Divider
-                  v-btn.ml-2(color='success', depressed)
+                        v-list-tile-title {{$t('navigation.divider')}}
+                  v-btn.ml-2(color='success', depressed, block, @click='save')
                     v-icon(left) check
-                    span Save
+                    span {{$t('common:actions.save')}}
             v-flex
               v-card(v-if='current.kind === "link"')
                 v-toolbar(dense, color='blue', flat, dark)
-                  .subheading Edit Link
+                  .subheading {{$t('navigation.edit', { kind: $t('navigation.link') })}}
                 v-card-text
                   v-text-field(
                     outline
                     background-color='grey lighten-2'
-                    label='Label'
+                    :label='$t("navigation.label")'
                     prepend-icon='title'
                     v-model='current.label'
                   )
                   v-text-field(
                     outline
                     background-color='grey lighten-2'
-                    label='Icon'
+                    :label='$t("navigation.icon")'
                     prepend-icon='casino'
                     v-model='current.icon'
                   )
                   v-select(
                     outline
                     background-color='grey lighten-2'
-                    label='Target Type'
+                    :label='$t("navigation.targetType")'
                     prepend-icon='near_me'
                     :items='navTypes'
                     v-model='current.targetType'
@@ -68,41 +82,47 @@
                     v-if='current.targetType === "external"'
                     outline
                     background-color='grey lighten-2'
-                    label='Target'
+                    :label='$t("navigation.target")'
                     prepend-icon='near_me'
                     v-model='current.target'
                   )
                 v-card-chin
                   v-spacer
-                  v-btn(color='red', outline)
+                  v-btn(color='red', outline, @click='deleteItem(current)')
                     v-icon(left) delete
-                    span Delete Link
+                    span {{$t('navigation.delete', { kind: $t('navigation.link') })}}
               v-card(v-else-if='current.kind === "header"')
                 v-toolbar(dense, color='blue', flat, dark)
-                  .subheading Edit Header
+                  .subheading {{$t('navigation.edit', { kind: $t('navigation.header') })}}
                 v-card-text
                   v-text-field(
                     outline
                     background-color='grey lighten-2'
-                    label='Label'
+                    :label='$t("navigation.label")'
                     prepend-icon='title'
                     v-model='current.label'
                   )
                 v-card-chin
                   v-spacer
-                  v-btn(color='red', outline)
+                  v-btn(color='red', outline, @click='deleteItem(current)')
                     v-icon(left) delete
-                    span Delete Header
+                    span {{$t('navigation.delete', { kind: $t('navigation.header') })}}
               div(v-else-if='current.kind === "divider"')
-                v-btn.mt-0(color='red', outline)
+                v-btn.mt-0(color='red', outline, @click='deleteItem(current)')
                   v-icon(left) delete
-                  span Delete Divider
+                  span {{$t('navigation.delete', { kind: $t('navigation.divider') })}}
               v-card(v-else)
-                v-card-text.grey--text Select a navigation item on the left.
+                v-card-text.grey--text {{$t('navigation.noSelectionText')}}
 
 </template>
 
 <script>
+import _ from 'lodash'
+import uuid from 'uuid/v4'
+
+import treeSaveMutation from 'gql/admin/navigation/navigation-mutation-save-tree.gql'
+import treeQuery from 'gql/admin/navigation/navigation-query-tree.gql'
+
 import draggable from 'vuedraggable'
 
 export default {
@@ -111,48 +131,77 @@ export default {
   },
   data() {
     return {
-      navTypes: [
-        { text: 'External Link', value: 'external' },
-        { text: 'Home', value: 'home' },
-        { text: 'Page', value: 'page' },
-        { text: 'Search Query', value: 'search' }
-      ],
-      navTree: [
-        {
-          kind: 'link',
-          label: 'Home',
-          icon: 'home',
-          targetType: 'home',
-          target: '/'
-        }
-      ],
+      navTree: [],
       current: {}
     }
   },
+  computed: {
+    navTypes() {
+      return [
+        { text: this.$t('navigation.navType.external'), value: 'external' },
+        { text: this.$t('navigation.navType.home'), value: 'home' },
+        { text: this.$t('navigation.navType.page'), value: 'page' },
+        { text: this.$t('navigation.navType.searchQuery'), value: 'search' }
+      ]
+    }
+  },
   methods: {
     addItem(kind) {
       let newItem = {
+        id: uuid(),
         kind
       }
       switch (kind) {
         case 'link':
           newItem = {
             ...newItem,
-            label: 'Untitled Link',
+            label: this.$t('navigation.untitled', { kind: this.$t(`navigation.link`) }),
             icon: 'chevron_right',
             targetType: 'home',
             target: '/'
           }
           break
         case 'header':
-          newItem.label = 'Untitled Header'
+          newItem.label = this.$t('navigation.untitled', { kind: this.$t(`navigation.header`) })
           break
       }
       this.navTree.push(newItem)
       this.current = newItem
     },
+    deleteItem(item) {
+      this.navTree = _.pull(this.navTree, item)
+      this.current = {}
+    },
     selectItem(item) {
       this.current = item
+    },
+    async save() {
+      this.$store.commit(`loadingStart`, 'admin-navigation-save')
+      try {
+        await this.$apollo.mutate({
+          mutation: treeSaveMutation,
+          variables: {
+            tree: this.navTree
+          }
+        })
+      } catch (err) {
+        this.$store.commit('showNotification', {
+          message: this.$t('navigation.saveSuccess'),
+          style: 'success',
+          icon: 'check'
+        })
+      }
+      this.$store.commit(`loadingStop`, 'admin-navigation-save')
+    }
+  },
+  apollo: {
+    navTree: {
+      query: treeQuery,
+      fetchPolicy: 'network-only',
+      update: (data) => _.cloneDeep(data.navigation.tree),
+      watchLoading (isLoading) {
+        this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-navigation-tree')
+      }
     }
   }
 }

+ 1 - 1
client/components/login.vue

@@ -19,7 +19,7 @@
             ref='iptPassword'
             v-model='password'
             :append-icon='hidePassword ? "visibility" : "visibility_off"'
-            :append-icon-cb='() => (hidePassword = !hidePassword)'
+            @click:append='() => (hidePassword = !hidePassword)'
             :type='hidePassword ? "password" : "text"'
             :placeholder='$t("auth:fields.password")'
             @keyup.enter='login'

+ 24 - 31
client/components/setup.vue

@@ -5,7 +5,7 @@
       v-toolbar-title
         span.subheading Wiki.js Setup
       v-spacer
-    v-content
+    v-content.white
       v-progress-linear.ma-0(indeterminate, height='4', :active='loading')
       v-stepper.elevation-0(v-model='state')
         v-stepper-header
@@ -33,17 +33,19 @@
           v-stepper-content(step='1')
             v-card.text-xs-center.pa-3(flat)
               img(src='/svg/logo-wikijs.svg', alt='Wiki.js Logo', style='width: 300px;')
-            v-container
+            .text-xs-center
               .body-2.py-2 This installation wizard will guide you through the steps needed to get your wiki up and running in no time!
-              .body-1
-                | Detailed information about installation and usage can be found on the #[a(href='https://wiki.requarks.io/docs') official documentation site].
-                br
-                | Should you have any question or would like to report something that doesn't look right, feel free to create a new issue on the #[a(href='https://github.com/Requarks/wiki/issues') GitHub project].
-              .body-1.pt-3
-                v-icon.mr-2 system_update
-                span You are about to install Wiki.js #[strong {{wikiVersion}}].
-              v-divider.mt-3
-              v-form
+              .body-1 Detailed information about installation and usage can be found on the #[a(href='https://wiki.requarks.io/docs') official documentation site].
+              .body-1 Should you have any question or would like to report something that doesn't look right, feel free to create a new issue on the #[a(href='https://github.com/Requarks/wiki/issues') GitHub project].
+              .body-1.py-3
+                v-icon.mr-2(color='indigo') open_in_browser
+                span.indigo--text You are about to install Wiki.js #[strong {{wikiVersion}}].
+              v-btn.mt-4(color='primary', @click='proceedToAdmin', :disabled='loading', large)
+                span Start
+                v-icon(right) arrow_forward
+              v-divider.my-5
+              .body-2 Additional Setup Options
+              div(style='display:inline-block;')
                 v-checkbox(
                   color='primary',
                   v-model='conf.telemetry',
@@ -58,9 +60,6 @@
                   persistent-hint,
                   hint='Check this box if you are upgrading from Wiki.js 1.x and wish to migrate your existing data.'
                 )
-            v-divider
-            .pt-3.text-xs-center
-              v-btn(color='primary', @click='proceedToAdmin', :disabled='loading') Start
 
           //- ==============================================
           //- ADMINISTRATOR ACCOUNT
@@ -81,11 +80,13 @@
                       v-model='conf.adminEmail',
                       label='Administrator Email',
                       hint='The email address of the administrator account',
+                      persistent-hint
                       v-validate='{ required: true, email: true }',
                       data-vv-name='adminEmail',
                       data-vv-as='Administrator Email',
                       data-vv-scope='admin',
                       :error-messages='errors.collect(`adminEmail`)'
+                      ref='adminEmailInput'
                     )
                   v-flex.pr-3(xs6)
                     v-text-field(
@@ -96,9 +97,10 @@
                       v-model='conf.adminPassword',
                       label='Password',
                       :append-icon="pwdMode ? 'visibility' : 'visibility_off'"
-                      :append-icon-cb="() => (pwdMode = !pwdMode)"
+                      @click:append="() => (pwdMode = !pwdMode)"
                       :type="pwdMode ? 'password' : 'text'"
                       hint='At least 8 characters long.',
+                      persistent-hint
                       v-validate='{ required: true, min: 8 }',
                       data-vv-name='adminPassword',
                       data-vv-as='Password',
@@ -114,14 +116,16 @@
                       v-model='conf.adminPasswordConfirm',
                       label='Confirm Password',
                       :append-icon="pwdConfirmMode ? 'visibility' : 'visibility_off'"
-                      :append-icon-cb="() => (pwdConfirmMode = !pwdConfirmMode)"
+                      @click:append="() => (pwdConfirmMode = !pwdConfirmMode)"
                       :type="pwdConfirmMode ? 'password' : 'text'"
                       hint='Verify your password again.',
+                      persistent-hint
                       v-validate='{ required: true, min: 8 }',
                       data-vv-name='adminPasswordConfirm',
                       data-vv-as='Confirm Password',
                       data-vv-scope='admin',
                       :error-messages='errors.collect(`adminPasswordConfirm`)'
+                      @keyup.enter='proceedToUpgrade'
                     )
               .pt-3.text-xs-center
                 v-btn(@click='proceedToWelcome', :disabled='loading') Back
@@ -194,9 +198,6 @@
 </template>
 
 <script>
-
-/* global siteConfig */
-
 import axios from 'axios'
 import _ from 'lodash'
 import { AtomSpinner } from 'epic-spinners'
@@ -251,6 +252,9 @@ export default {
       }
       this.state = 2
       this.loading = false
+      _.delay(() => {
+        this.$refs.adminEmailInput.focus()
+      }, 400)
     },
     async proceedToUpgrade () {
       if (this.state < 3) {
@@ -292,17 +296,6 @@ export default {
           if (resp.data.ok === true) {
             _.delay(() => {
               self.final.ok = true
-              switch (resp.data.redirectPort) {
-                case 80:
-                  self.final.redirectUrl = `http://${window.location.hostname}${resp.data.redirectPath}/login`
-                  break
-                case 443:
-                  self.final.redirectUrl = `https://${window.location.hostname}${resp.data.redirectPath}/login`
-                  break
-                default:
-                  self.final.redirectUrl = `http://${window.location.hostname}:${resp.data.redirectPort}${resp.data.redirectPath}/login`
-                  break
-              }
               self.loading = false
             }, 5000)
           } else {
@@ -317,7 +310,7 @@ export default {
       }, 1000)
     },
     finish () {
-      window.location.assign(this.final.redirectUrl)
+      window.location.assign('/login')
     }
   }
 }

+ 12 - 0
client/graph/admin/navigation/navigation-mutation-save-tree.gql

@@ -0,0 +1,12 @@
+mutation ($tree: [NavigationItemInput]!) {
+  navigation{
+    updateTree(tree: $tree) {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+    }
+  }
+}

+ 12 - 0
client/graph/admin/navigation/navigation-query-tree.gql

@@ -0,0 +1,12 @@
+{
+  navigation {
+    tree {
+      id
+      kind
+      label
+      icon
+      targetType
+      target
+    }
+  }
+}

+ 0 - 2
client/helpers/compatibility.js

@@ -1,5 +1,3 @@
-require('@babel/polyfill')
-
 // =======================================
 // Fetch polyfill
 // =======================================

+ 2 - 2
client/index.js → client/index-app.js

@@ -1,9 +1,9 @@
-'use strict'
+require('@babel/polyfill')
 
 require('vuetify/src/stylus/main.styl')
 require('./scss/app.scss')
 require('./themes/' + process.env.CURRENT_THEME + '/scss/app.scss')
 
 require('./helpers/compatibility.js')
-require('./app.js')
+require('./client-app.js')
 require('./themes/' + process.env.CURRENT_THEME + '/js/app.js')

+ 7 - 0
client/index-setup.js

@@ -0,0 +1,7 @@
+require('@babel/polyfill')
+
+require('vuetify/src/stylus/main.styl')
+require('./scss/app.scss')
+require('./helpers/compatibility.js')
+
+require('./client-setup.js')

+ 34 - 0
dev/templates/master.pug

@@ -24,6 +24,40 @@ html
     //- CSS
     link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons')
     link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
+    <% for (var index in htmlWebpackPlugin.files.css) { %>
+      <% if (htmlWebpackPlugin.files.cssIntegrity) { %>
+    link(
+      type='text/css'
+      rel='stylesheet'
+      href='<%= htmlWebpackPlugin.files.css[index] %>'
+      integrity='<%= htmlWebpackPlugin.files.cssIntegrity[index] %>'
+      crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
+    )
+      <% } else { %>
+    link(
+      type='text/css'
+      rel='stylesheet'
+      href='<%= htmlWebpackPlugin.files.css[index] %>'
+    )
+      <% } %>
+    <% } %>
+
+    //- JS
+    <% for (var index in htmlWebpackPlugin.files.js) { %>
+      <% if (htmlWebpackPlugin.files.cssIntegrity) { %>
+    script(
+      type='text/javascript'
+      src='<%= htmlWebpackPlugin.files.js[index] %>'
+      integrity='<%= htmlWebpackPlugin.files.jsIntegrity[index] %>'
+      crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
+      )
+      <% } else { %>
+    script(
+      type='text/javascript'
+      src='<%= htmlWebpackPlugin.files.js[index] %>'
+      )
+      <% } %>
+    <% } %>
 
     block head
 

+ 64 - 0
dev/templates/setup.pug

@@ -0,0 +1,64 @@
+doctype html
+html
+  head
+    meta(http-equiv='X-UA-Compatible', content='IE=edge')
+    meta(charset='UTF-8')
+    meta(name='viewport', content='user-scalable=yes, width=device-width, initial-scale=1, maximum-scale=5')
+    meta(name='theme-color', content='#333333')
+    meta(name='msapplication-TileColor', content='#333333')
+    meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
+    title Wiki.js Setup
+
+    //- Favicon
+    each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
+      link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
+    link(rel='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png')
+    each favsize in [32, 96, 16]
+      link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
+    link(rel='manifest', href='/manifest.json')
+
+    //- Site Lang
+    script.
+      var siteConfig = !{JSON.stringify({ title: config.title })}
+
+    //- CSS
+    link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons')
+    link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
+    <% for (var index in htmlWebpackPlugin.files.css) { %>
+      <% if (htmlWebpackPlugin.files.cssIntegrity) { %>
+    link(
+      type='text/css'
+      rel='stylesheet'
+      href='<%= htmlWebpackPlugin.files.css[index] %>'
+      integrity='<%= htmlWebpackPlugin.files.cssIntegrity[index] %>'
+      crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
+    )
+      <% } else { %>
+    link(
+      type='text/css'
+      rel='stylesheet'
+      href='<%= htmlWebpackPlugin.files.css[index] %>'
+    )
+      <% } %>
+    <% } %>
+
+    //- JS
+    <% for (var index in htmlWebpackPlugin.files.js) { %>
+      <% if (htmlWebpackPlugin.files.cssIntegrity) { %>
+    script(
+      type='text/javascript'
+      src='<%= htmlWebpackPlugin.files.js[index] %>'
+      integrity='<%= htmlWebpackPlugin.files.jsIntegrity[index] %>'
+      crossorigin='<%= webpackConfig.output.crossOriginLoading %>'
+      )
+      <% } else { %>
+    script(
+      type='text/javascript'
+      src='<%= htmlWebpackPlugin.files.js[index] %>'
+      )
+      <% } %>
+    <% } %>
+
+  body
+    #root
+      setup(telemetry-id=telemetryClientID, wiki-version=packageObj.version)

+ 18 - 3
dev/webpack/webpack.dev.js

@@ -9,6 +9,7 @@ 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 SriWebpackPlugin = require('webpack-subresource-integrity')
 const WriteFilePlugin = require('write-file-webpack-plugin')
 
 const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
@@ -22,7 +23,8 @@ fs.emptyDirSync(path.join(process.cwd(), 'assets'))
 module.exports = {
   mode: 'development',
   entry: {
-    client: ['./client/index.js', 'webpack-hot-middleware/client']
+    app: ['./client/index-app.js', 'webpack-hot-middleware/client'],
+    setup: ['./client/index-setup.js', 'webpack-hot-middleware/client']
   },
   output: {
     path: path.join(process.cwd(), 'assets'),
@@ -30,7 +32,8 @@ module.exports = {
     filename: 'js/[name].js',
     chunkFilename: 'js/[name].js',
     globalObject: 'this',
-    pathinfo: true
+    pathinfo: true,
+    crossOriginLoading: 'use-credentials'
   },
   module: {
     rules: [
@@ -190,9 +193,21 @@ module.exports = {
       template: 'dev/templates/master.pug',
       filename: '../server/views/master.pug',
       hash: false,
-      inject: 'head'
+      inject: false,
+      excludeChunks	: ['setup']
+    }),
+    new HtmlWebpackPlugin({
+      template: 'dev/templates/setup.pug',
+      filename: '../server/views/setup.pug',
+      hash: false,
+      inject: false,
+      excludeChunks: ['app']
     }),
     new HtmlWebpackPugPlugin(),
+    new SriWebpackPlugin({
+      hashFuncNames: ['sha256', 'sha512'],
+      enabled: false
+    }),
     new SimpleProgressWebpackPlugin({
       format: 'compact'
     }),

+ 18 - 3
dev/webpack/webpack.prod.js

@@ -12,6 +12,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-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 SriWebpackPlugin = require('webpack-subresource-integrity')
 const { VueLoaderPlugin } = require('vue-loader')
 
 const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
@@ -25,14 +26,16 @@ fs.emptyDirSync(path.join(process.cwd(), 'assets'))
 module.exports = {
   mode: 'production',
   entry: {
-    client: './client/index.js'
+    app: './client/index-app.js',
+    setup: './client/index-setup.js'
   },
   output: {
     path: path.join(process.cwd(), 'assets'),
     publicPath: '/',
     filename: 'js/[name].[hash].js',
     chunkFilename: 'js/[name].[chunkhash].js',
-    globalObject: 'this'
+    globalObject: 'this',
+    crossOriginLoading: 'use-credentials'
   },
   module: {
     rules: [
@@ -200,13 +203,25 @@ module.exports = {
       template: 'dev/templates/master.pug',
       filename: '../server/views/master.pug',
       hash: false,
-      inject: 'head'
+      inject: false,
+      excludeChunks	: ['setup']
+    }),
+    new HtmlWebpackPlugin({
+      template: 'dev/templates/setup.pug',
+      filename: '../server/views/setup.pug',
+      hash: false,
+      inject: false,
+      excludeChunks: ['app']
     }),
     new HtmlWebpackPugPlugin(),
     new ScriptExtHtmlWebpackPlugin({
       sync: 'runtime.js',
       defaultAttribute: 'async'
     }),
+    new SriWebpackPlugin({
+      hashFuncNames: ['sha256', 'sha512'],
+      enabled: true
+    }),
     new SimpleProgressWebpackPlugin({
       format: 'expanded'
     }),

+ 56 - 54
package.json

@@ -42,8 +42,8 @@
     "node": ">=10.10"
   },
   "dependencies": {
-    "apollo-server": "2.0.7",
-    "apollo-server-express": "2.0.6",
+    "apollo-server": "2.1.0",
+    "apollo-server-express": "2.1.0",
     "auto-load": "3.0.1",
     "axios": "0.18.0",
     "bcryptjs-then": "1.0.1",
@@ -56,7 +56,7 @@
     "child-process-promise": "2.2.1",
     "chokidar": "2.0.4",
     "compression": "1.7.3",
-    "connect-redis": "3.3.3",
+    "connect-redis": "3.4.0",
     "cookie-parser": "1.4.3",
     "cors": "2.8.4",
     "dependency-graph": "0.7.2",
@@ -69,15 +69,15 @@
     "express-session": "1.15.6",
     "file-type": "9.0.0",
     "filesize.js": "1.0.2",
-    "follow-redirects": "1.5.7",
+    "follow-redirects": "1.5.8",
     "fs-extra": "7.0.0",
     "getos": "3.1.0",
     "graphql": "14.0.2",
     "graphql-list-fields": "2.0.2",
-    "graphql-tools": "3.1.1",
+    "graphql-tools": "4.0.0",
     "highlight.js": "9.12.0",
-    "i18next": "11.7.0",
-    "i18next-express-middleware": "1.3.2",
+    "i18next": "11.9.0",
+    "i18next-express-middleware": "1.4.0",
     "i18next-localstorage-cache": "1.1.1",
     "i18next-node-fs-backend": "2.1.0",
     "image-size": "0.6.3",
@@ -87,11 +87,11 @@
     "jsonwebtoken": "8.3.0",
     "klaw": "3.0.0",
     "knex": "0.15.2",
-    "lodash": "4.17.10",
+    "lodash": "4.17.11",
     "markdown-it": "8.4.2",
     "markdown-it-abbr": "1.0.4",
     "markdown-it-anchor": "5.0.2",
-    "markdown-it-attrs": "2.3.1",
+    "markdown-it-attrs": "2.3.2",
     "markdown-it-emoji": "1.4.0",
     "markdown-it-expand-tabs": "1.0.13",
     "markdown-it-external-links": "0.0.6",
@@ -106,14 +106,14 @@
     "mime-types": "2.1.20",
     "moment": "2.22.2",
     "moment-timezone": "0.5.21",
-    "mongodb": "3.1.4",
-    "mssql": "4.1.0",
-    "multer": "1.3.1",
+    "mongodb": "3.1.6",
+    "mssql": "4.2.1",
+    "multer": "1.4.0",
     "mysql2": "1.6.1",
     "node-2fa": "1.1.2",
     "node-cache": "4.2.0",
     "oauth2orize": "1.11.0",
-    "objection": "1.2.6",
+    "objection": "1.3.0",
     "ora": "3.0.0",
     "passport": "0.4.0",
     "passport-auth0": "1.0.0",
@@ -124,7 +124,7 @@
     "passport-facebook": "2.1.1",
     "passport-github2": "0.1.11",
     "passport-google-oauth20": "1.0.0",
-    "passport-ldapauth": "2.0.0",
+    "passport-ldapauth": "2.1.0",
     "passport-local": "1.0.0",
     "passport-oauth2": "1.4.0",
     "passport-okta-oauth": "0.0.1",
@@ -135,7 +135,7 @@
     "passport-windowslive": "1.0.2",
     "pg": "7.4.3",
     "pg-hstore": "2.3.2",
-    "pm2": "3.0.4",
+    "pm2": "3.1.3",
     "pug": "2.0.3",
     "qr-image": "3.2.0",
     "raven": "2.6.4",
@@ -149,40 +149,40 @@
     "sqlite3": "4.0.2",
     "uslug": "1.0.4",
     "uuid": "3.3.2",
-    "validator": "10.7.1",
+    "validator": "10.8.0",
     "validator-as-promised": "1.0.2",
     "winston": "3.1.0",
     "yargs": "12.0.2"
   },
   "devDependencies": {
-    "@babel/cli": "^7.0.0",
-    "@babel/core": "^7.0.0",
-    "@babel/plugin-proposal-class-properties": "^7.0.0",
-    "@babel/plugin-proposal-decorators": "^7.0.0",
+    "@babel/cli": "^7.1.2",
+    "@babel/core": "^7.1.2",
+    "@babel/plugin-proposal-class-properties": "^7.1.0",
+    "@babel/plugin-proposal-decorators": "^7.1.2",
     "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
-    "@babel/plugin-proposal-function-sent": "^7.0.0",
+    "@babel/plugin-proposal-function-sent": "^7.1.0",
     "@babel/plugin-proposal-json-strings": "^7.0.0",
     "@babel/plugin-proposal-numeric-separator": "^7.0.0",
     "@babel/plugin-proposal-throw-expressions": "^7.0.0",
     "@babel/plugin-syntax-dynamic-import": "^7.0.0",
     "@babel/plugin-syntax-import-meta": "^7.0.0",
     "@babel/polyfill": "^7.0.0",
-    "@babel/preset-env": "^7.0.0",
-    "@panter/vue-i18next": "0.12.0",
-    "@vue/cli": "3.0.1",
+    "@babel/preset-env": "^7.1.0",
+    "@panter/vue-i18next": "0.13.0",
+    "@vue/cli": "3.0.4",
     "animated-number-vue": "0.1.3",
-    "apollo-cache-inmemory": "1.2.9",
-    "apollo-client": "2.4.1",
+    "apollo-cache-inmemory": "1.3.0",
+    "apollo-client": "2.4.2",
     "apollo-fetch": "0.7.0",
-    "apollo-link": "1.2.2",
-    "apollo-link-batch-http": "1.2.2",
-    "apollo-link-error": "1.1.0",
-    "apollo-link-http": "1.5.4",
+    "apollo-link": "1.2.3",
+    "apollo-link-batch-http": "1.2.3",
+    "apollo-link-error": "1.1.1",
+    "apollo-link-http": "1.5.5",
     "apollo-link-persisted-queries": "0.2.1",
     "autoprefixer": "9.1.5",
-    "babel-eslint": "9.0.0",
-    "babel-jest": "23.4.2",
-    "babel-loader": "^8.0.0",
+    "babel-eslint": "10.0.1",
+    "babel-jest": "23.6.0",
+    "babel-loader": "^8.0.4",
     "babel-plugin-graphql-tag": "1.6.0",
     "babel-plugin-lodash": "3.3.4",
     "babel-plugin-transform-imports": "1.5.1",
@@ -192,10 +192,10 @@
     "clean-webpack-plugin": "0.1.19",
     "copy-webpack-plugin": "4.5.2",
     "css-loader": "1.0.0",
-    "cssnano": "4.1.0",
+    "cssnano": "4.1.4",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.0.3",
-    "eslint": "5.5.0",
+    "eslint": "5.6.1",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "12.0.0",
     "eslint-plugin-import": "2.14.0",
@@ -204,18 +204,18 @@
     "eslint-plugin-standard": "4.0.0",
     "eslint-plugin-vue": "4.7.1",
     "file-loader": "2.0.0",
-    "grapesjs": "0.14.29",
-    "graphiql": "0.11.11",
+    "grapesjs": "0.14.33",
+    "graphiql": "0.12.0",
     "graphql-persisted-document-loader": "1.0.1",
     "graphql-tag": "^2.9.2",
-    "graphql-voyager": "1.0.0-rc.19",
+    "graphql-voyager": "1.0.0-rc.25",
     "hammerjs": "2.0.8",
     "html-webpack-plugin": "3.2.0",
     "html-webpack-pug-plugin": "0.3.0",
     "i18next-xhr-backend": "1.5.1",
     "ignore-loader": "0.1.2",
     "js-cookie": "2.2.0",
-    "mini-css-extract-plugin": "0.4.2",
+    "mini-css-extract-plugin": "0.4.3",
     "node-sass": "4.9.3",
     "offline-plugin": "5.0.5",
     "optimize-css-assets-webpack-plugin": "5.0.1",
@@ -224,14 +224,14 @@
     "postcss-flexibility": "2.0.0",
     "postcss-import": "12.0.0",
     "postcss-loader": "3.0.0",
-    "postcss-preset-env": "5.3.0",
+    "postcss-preset-env": "6.0.7",
     "postcss-selector-parser": "5.0.0-rc.3",
     "pug-lint": "2.5.0",
     "pug-loader": "2.4.0",
     "pug-plain-loader": "1.0.0",
     "raw-loader": "0.5.1",
-    "react": "16.5.0",
-    "react-dom": "16.5.0",
+    "react": "16.5.2",
+    "react-dom": "16.5.2",
     "resolve-url-loader": "3.0.0",
     "sass-loader": "7.1.0",
     "sass-resources-loader": "1.3.3",
@@ -242,15 +242,16 @@
     "stylus-loader": "3.0.2",
     "twemoji-awesome": "1.0.6",
     "url-loader": "1.1.1",
-    "vee-validate": "2.1.0-beta.8",
+    "vee-validate": "2.1.0-beta.9",
     "velocity-animate": "1.5.2",
+    "viz.js": "2.0.0",
     "vue": "2.5.17",
-    "vue-apollo": "3.0.0-beta.24",
+    "vue-apollo": "3.0.0-beta.25",
     "vue-chartjs": "3.4.0",
     "vue-clipboards": "1.2.4",
     "vue-codemirror": "4.0.5",
-    "vue-hot-reload-api": "2.3.0",
-    "vue-loader": "15.4.1",
+    "vue-hot-reload-api": "2.3.1",
+    "vue-loader": "15.4.2",
     "vue-material-design-icons": "2.1.1",
     "vue-moment": "4.0.0",
     "vue-router": "3.0.1",
@@ -260,18 +261,19 @@
     "vue-tour": "1.0.1",
     "vue-tree-navigation": "3.0.1",
     "vuedraggable": "2.16.0",
-    "vuetify": "1.2.3",
+    "vuetify": "1.2.5",
     "vuex": "3.0.1",
     "vuex-pathify": "1.1.3",
     "vuex-persistedstate": "2.5.4",
-    "webpack": "4.17.2",
-    "webpack-bundle-analyzer": "2.13.1",
-    "webpack-cli": "3.1.0",
-    "webpack-dev-middleware": "3.2.0",
-    "webpack-hot-middleware": "2.23.1",
+    "webpack": "4.20.2",
+    "webpack-bundle-analyzer": "3.0.2",
+    "webpack-cli": "3.1.2",
+    "webpack-dev-middleware": "3.4.0",
+    "webpack-hot-middleware": "2.24.2",
     "webpack-merge": "4.1.4",
-    "whatwg-fetch": "2.0.4",
-    "write-file-webpack-plugin": "4.4.0",
+    "webpack-subresource-integrity": "1.1.0-rc.6",
+    "whatwg-fetch": "3.0.0",
+    "write-file-webpack-plugin": "4.4.1",
     "xterm": "3.7.0"
   },
   "browserslist": [

+ 7 - 0
server/db/migrations/2.0.0.js

@@ -77,6 +77,12 @@ exports.up = knex => {
       table.string('level').notNullable().defaultTo('warn')
       table.json('config')
     })
+    // NAVIGATION ----------------------------
+    .createTable('navigation', table => {
+      table.charset('utf8mb4')
+      table.string('key').notNullable().primary()
+      table.json('config')
+    })
     // PAGE HISTORY ------------------------
     .createTable('pageHistory', table => {
       table.charset('utf8mb4')
@@ -236,6 +242,7 @@ exports.down = knex => {
     .dropTableIfExists('editors')
     .dropTableIfExists('groups')
     .dropTableIfExists('locales')
+    .dropTableIfExists('navigation')
     .dropTableIfExists('pages')
     .dropTableIfExists('renderers')
     .dropTableIfExists('settings')

+ 39 - 0
server/graph/resolvers/navigation.js

@@ -0,0 +1,39 @@
+const _ = require('lodash')
+const graphHelper = require('../../helpers/graph')
+
+/* global WIKI */
+
+module.exports = {
+  Query: {
+    async navigation() { return {} }
+  },
+  Mutation: {
+    async navigation() { return {} }
+  },
+  NavigationQuery: {
+    async tree(obj, args, context, info) {
+      // let renderers = await WIKI.models.renderers.getRenderers()
+      return []
+    }
+  },
+  NavigationMutation: {
+    async updateTree(obj, args, context) {
+      try {
+        // for (let rdr of args.renderers) {
+        //   await WIKI.models.storage.query().patch({
+        //     isEnabled: rdr.isEnabled,
+        //     config: _.reduce(rdr.config, (result, value, key) => {
+        //       _.set(result, `${value.key}`, value.value)
+        //       return result
+        //     }, {})
+        //   }).where('key', rdr.key)
+        // }
+        return {
+          responseResult: graphHelper.generateSuccess('Navigation updated successfully')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
+    }
+  }
+}

+ 51 - 0
server/graph/schemas/navigation.graphql

@@ -0,0 +1,51 @@
+# ===============================================
+# NAVIGATION
+# ===============================================
+
+extend type Query {
+  navigation: NavigationQuery
+}
+
+extend type Mutation {
+  navigation: NavigationMutation
+}
+
+# -----------------------------------------------
+# QUERIES
+# -----------------------------------------------
+
+type NavigationQuery {
+  tree: [NavigationItem]!
+}
+
+# -----------------------------------------------
+# MUTATIONS
+# -----------------------------------------------
+
+type NavigationMutation {
+  updateTree(
+    tree: [NavigationItemInput]!
+  ): DefaultResponse
+}
+
+# -----------------------------------------------
+# TYPES
+# -----------------------------------------------
+
+type NavigationItem {
+  id: String!
+  kind: String!
+  label: String
+  icon: String
+  targetType: String
+  target: String
+}
+
+input NavigationItemInput {
+  id: String!
+  kind: String!
+  label: String
+  icon: String
+  targetType: String
+  target: String
+}

+ 8 - 0
server/locales/default.json

@@ -1,4 +1,12 @@
 {
+  "common": {
+    "footer": {
+      "poweredBy": "Powered by"
+    },
+    "welcome": {
+      "title": "Welcome to your wiki!"
+    }
+  },
   "auth": {
     "actions": {
       "login": "Log In"

+ 6 - 1
server/models/editors.js

@@ -32,6 +32,7 @@ module.exports = class Editor extends Model {
   }
 
   static async refreshEditorsFromDisk() {
+    let trx
     try {
       const dbEditors = await WIKI.models.editors.query()
 
@@ -72,7 +73,11 @@ module.exports = class Editor extends Model {
         }
       }
       if (newEditors.length > 0) {
-        await WIKI.models.editors.query().insert(newEditors)
+        trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
+        for (let editor of newEditors) {
+          await WIKI.models.editors.query(trx).insert(editor)
+        }
+        await trx.commit()
         WIKI.logger.info(`Loaded ${newEditors.length} new editors: [ OK ]`)
       } else {
         WIKI.logger.info(`No new editors found: [ SKIPPED ]`)

+ 27 - 0
server/models/navigation.js

@@ -0,0 +1,27 @@
+const Model = require('objection').Model
+
+/* global WIKI */
+
+/**
+ * Navigation model
+ */
+module.exports = class Navigation extends Model {
+  static get tableName() { return 'navigation' }
+  static get idColumn() { return 'key' }
+
+  static get jsonSchema () {
+    return {
+      type: 'object',
+      required: ['key'],
+
+      properties: {
+        key: {type: 'string'},
+        config: {type: 'object'}
+      }
+    }
+  }
+
+  static async getTree() {
+    return WIKI.models.navigation.query()
+  }
+}

+ 3 - 0
server/setup.js

@@ -277,5 +277,8 @@ module.exports = () => {
 
   WIKI.server.on('listening', () => {
     WIKI.logger.info('HTTP Server: [ RUNNING ]')
+    WIKI.logger.info('========================================')
+    WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/`)
+    WIKI.logger.info('========================================')
   })
 }

+ 18 - 2
server/views/master.pug

@@ -24,10 +24,26 @@ html
     //- CSS
     link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons')
     link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
+    
+
+    //- JS
+    
+      
+    script(
+      type='text/javascript'
+      src='/js/runtime.js'
+      )
+      
+    
+      
+    script(
+      type='text/javascript'
+      src='/js/app.js'
+      )
+      
+    
 
     block head
 
-    script(type="text/javascript" src="/js/runtime.js")
-    script(type="text/javascript" src="/js/client.js")
   body
     block body

+ 47 - 4
server/views/setup.pug

@@ -1,5 +1,48 @@
-extends master.pug
+doctype html
+html
+  head
+    meta(http-equiv='X-UA-Compatible', content='IE=edge')
+    meta(charset='UTF-8')
+    meta(name='viewport', content='user-scalable=yes, width=device-width, initial-scale=1, maximum-scale=5')
+    meta(name='theme-color', content='#333333')
+    meta(name='msapplication-TileColor', content='#333333')
+    meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
+    title Wiki.js Setup
 
-block body
-  #root
-    setup(telemetry-id=telemetryClientID, wiki-version=packageObj.version)
+    //- Favicon
+    each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
+      link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
+    link(rel='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png')
+    each favsize in [32, 96, 16]
+      link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
+    link(rel='manifest', href='/manifest.json')
+
+    //- Site Lang
+    script.
+      var siteConfig = !{JSON.stringify({ title: config.title })}
+
+    //- CSS
+    link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons')
+    link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
+    
+
+    //- JS
+    
+      
+    script(
+      type='text/javascript'
+      src='/js/runtime.js'
+      )
+      
+    
+      
+    script(
+      type='text/javascript'
+      src='/js/setup.js'
+      )
+      
+    
+
+  body
+    #root
+      setup(telemetry-id=telemetryClientID, wiki-version=packageObj.version)

Diferenças do arquivo suprimidas por serem muito extensas
+ 413 - 307
yarn.lock


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff