Explorar o código

refactor: renderers + auth providers + fixes

NGPixel %!s(int64=7) %!d(string=hai) anos
pai
achega
74bd722168
Modificáronse 41 ficheiros con 475 adicións e 770 borrados
  1. 10 1
      assets/browserconfig.xml
  2. 1 1
      assets/manifest.json
  3. 0 2
      client/js/components/editor.component.js
  4. 2 1
      client/js/components/login.vue
  5. 44 8
      client/js/components/navigator.vue
  6. 19 17
      client/js/components/setup.component.js
  7. 0 15
      client/js/helpers/common.js
  8. 0 25
      client/js/helpers/form.js
  9. 54 5
      client/js/helpers/index.js
  10. 0 2
      client/js/helpers/lodash.js
  11. 0 26
      client/js/helpers/pages.js
  12. 1 1
      client/js/modules/localization.js
  13. 0 2
      client/js/pages/admin-edit-user.component.js
  14. 0 2
      client/js/pages/admin-profile.component.js
  15. 0 2
      client/js/pages/admin-settings.component.js
  16. 0 2
      client/js/pages/admin-theme.component.js
  17. 0 2
      client/js/pages/content-view.component.js
  18. 0 2
      client/js/pages/source-view.component.js
  19. 0 3
      client/js/store/modules/anchor.js
  20. 57 0
      client/scss/components/navigator.scss
  21. 10 2
      client/svg/icons.svg
  22. 2 0
      package.json
  23. 0 307
      server/controllers/admin.js
  24. 1 4
      server/controllers/auth.js
  25. 0 0
      server/controllers/common.js
  26. 0 162
      server/controllers/uploads.js
  27. 0 116
      server/controllers/ws.js
  28. 30 0
      server/extensions/authentication/dropbox.js
  29. 31 0
      server/extensions/authentication/oauth2.js
  30. 86 0
      server/extensions/renderer/common/mathjax.js
  31. 15 0
      server/extensions/renderer/markdown/abbreviations.js
  32. 15 0
      server/extensions/renderer/markdown/emoji.js
  33. 18 0
      server/extensions/renderer/markdown/expand-tabs.js
  34. 15 0
      server/extensions/renderer/markdown/footnotes.js
  35. 15 0
      server/extensions/renderer/markdown/mathjax.js
  36. 15 0
      server/extensions/renderer/markdown/tasks-lists.js
  37. 4 19
      server/master.js
  38. 16 18
      server/setup.js
  39. 2 2
      server/views/master.pug
  40. 3 19
      server/views/setup.pug
  41. 9 2
      yarn.lock

+ 10 - 1
assets/browserconfig.xml

@@ -1,2 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<browserconfig><msapplication><tile><square70x70logo src="/favicons/ms-icon-70x70.png"/><square150x150logo src="/favicons/ms-icon-150x150.png"/><square310x310logo src="/favicons/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
+<browserconfig>
+  <msapplication>
+    <tile>
+      <square70x70logo src="/favicons/ms-icon-70x70.png"/>
+      <square150x150logo src="/favicons/ms-icon-150x150.png"/>
+      <square310x310logo src="/favicons/ms-icon-310x310.png"/>
+      <TileColor>#ffffff</TileColor>
+    </tile>
+  </msapplication>
+</browserconfig>

+ 1 - 1
assets/manifest.json

@@ -42,5 +42,5 @@
   "name": "Wiki",
   "short_name": "Wiki",
   "start_url": "/",
-  "theme_color": "#3f51b5"
+  "theme_color": "#0288d1"
 }

+ 0 - 2
client/js/components/editor.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 /* global $, siteRoot */
 
 let mde

+ 2 - 1
client/js/components/login.vue

@@ -55,7 +55,8 @@ export default {
       this.selectedStrategy = key
       this.screen = 'login'
       if (!useForm) {
-        window.location.assign(siteConfig.path + 'login/' + key)
+        this.isLoading = true
+        window.location.assign(this.$helpers.resolvePath('login/' + key))
       } else {
         this.$refs.iptEmail.focus()
       }

+ 44 - 8
client/js/components/navigator.vue

@@ -15,12 +15,24 @@
             use(:xlink:href='subtitleIconClass')
         h2 {{subtitleText}}
       .navigator-action
-        .navigator-action-item
+        .navigator-action-item(:class='{"is-active": userMenuShown}', @click='toggleUserMenu')
           svg.icons.is-32(role='img')
             title User
             use(xlink:href='#nc-user-circle')
+          transition(name='navigator-action-item-dropdown')
+            ul.navigator-action-item-dropdown(v-show='userMenuShown', v-cloak)
+              li
+                label Account
+                svg.icons.is-24(role='img')
+                  title Account
+                  use(xlink:href='#nc-man-green')
+              li(@click='logout')
+                label Sign out
+                svg.icons.is-24(role='img')
+                  title Sign Out
+                  use(xlink:href='#nc-exit')
     transition(name='navigator-sd')
-      .navigator-sd(v-show='sdShown')
+      .navigator-sd(v-show='sdShown', v-cloak)
         .navigator-sd-actions
           a.is-active(href='', title='Search')
             svg.icons.is-24(role='img')
@@ -77,7 +89,8 @@ export default {
   name: 'navigator',
   data() {
     return {
-      sdShown: false
+      sdShown: false,
+      userMenuShown: false
     }
   },
   computed: {
@@ -104,16 +117,39 @@ export default {
   methods: {
     toggleMainMenu() {
       this.sdShown = !this.sdShown
+      this.userMenuShown = false
       if (this.sdShown) {
         this.$nextTick(() => {
+          this.bindOutsideClick()
           this.$refs.iptSearch.focus()
         })
+      } else {
+        this.unbindOutsideClick()
       }
-      // this.$store.dispatch('navigator/alert', {
-      //   style: 'success',
-      //   icon: 'gg-check',
-      //   msg: 'Changes were saved successfully!'
-      // })
+    },
+    toggleUserMenu() {
+      this.userMenuShown = !this.userMenuShown
+      this.sdShown = false
+      if (this.userMenuShown) {
+        this.bindOutsideClick()
+      } else {
+        this.unbindOutsideClick()
+      }
+    },
+    bindOutsideClick() {
+      document.addEventListener('mousedown', this.handleOutsideClick, false)
+    },
+    unbindOutsideClick() {
+      document.removeEventListener('mousedown', this.handleOutsideClick, false)
+    },
+    handleOutsideClick(ev) {
+      if (!this.$el.contains(ev.target)) {
+        this.sdShown = false
+        this.userMenuShown = false
+      }
+    },
+    logout() {
+      window.location.assign(this.$helpers.resolvePath('logout'))
     }
   },
   mounted() {

+ 19 - 17
client/js/components/setup.component.js

@@ -24,7 +24,7 @@ export default {
       final: {
         ok: false,
         error: '',
-        results: []
+        redirectUrl: ''
       },
       conf: {
         adminEmail: '',
@@ -219,19 +219,32 @@ export default {
       self.final = {
         ok: false,
         error: '',
-        results: []
+        redirectUrl: ''
       }
 
       this.$helpers._.delay(() => {
         axios.post('/finalize', self.conf).then(resp => {
           if (resp.data.ok === true) {
-            self.final.ok = true
-            self.final.results = resp.data.results
+            self.$helpers._.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 {
             self.final.ok = false
             self.final.error = resp.data.error
+            self.loading = false
           }
-          self.loading = false
           self.$nextTick()
         }).catch(err => {
           window.alert(err.message)
@@ -239,18 +252,7 @@ export default {
       }, 1000)
     },
     finish: function (ev) {
-      let self = this
-      self.state = 'restart'
-
-      this.$helpers._.delay(() => {
-        axios.post('/restart', {}).then(resp => {
-          this.$helpers._.delay(() => {
-            window.location.assign(self.conf.host)
-          }, 30000)
-        }).catch(err => {
-          window.alert(err.message)
-        })
-      }, 1000)
+      window.location.assign(this.final.redirectUrl)
     }
   }
 }

+ 0 - 15
client/js/helpers/common.js

@@ -1,15 +0,0 @@
-'use strict'
-
-import filesize from 'filesize.js'
-import toUpper from 'lodash/toUpper'
-
-module.exports = {
-  /**
-   * Convert bytes to humanized form
-   * @param {number} rawSize Size in bytes
-   * @returns {string} Humanized file size
-   */
-  filesize(rawSize) {
-    return toUpper(filesize(rawSize))
-  }
-}

+ 0 - 25
client/js/helpers/form.js

@@ -1,25 +0,0 @@
-'use strict'
-
-module.exports = {
-  /**
-   * Set Input Selection
-   * @param {DOMElement} input The input element
-   * @param {number} startPos The starting position
-   * @param {nunber} endPos The ending position
-   */
-  setInputSelection: (input, startPos, endPos) => {
-    input.focus()
-    if (typeof input.selectionStart !== 'undefined') {
-      input.selectionStart = startPos
-      input.selectionEnd = endPos
-    } else if (document.selection && document.selection.createRange) {
-      // IE branch
-      input.select()
-      var range = document.selection.createRange()
-      range.collapse(true)
-      range.moveEnd('character', endPos)
-      range.moveStart('character', startPos)
-      range.select()
-    }
-  }
-}

+ 54 - 5
client/js/helpers/index.js

@@ -1,10 +1,59 @@
-'use strict'
+import filesize from 'filesize.js'
 
+/* global siteConfig */
+
+const _ = require('./lodash')
 const helpers = {
-  _: require('./lodash'),
-  common: require('./common'),
-  form: require('./form'),
-  pages: require('./pages')
+  /**
+   * Minimal set of lodash functions
+   */
+  _,
+  /**
+   * Convert bytes to humanized form
+   * @param {number} rawSize Size in bytes
+   * @returns {string} Humanized file size
+   */
+  filesize (rawSize) {
+    return _.toUpper(filesize(rawSize))
+  },
+  /**
+   * Convert raw path to safe path
+   * @param {string} rawPath Raw path
+   * @returns {string} Safe path
+   */
+  makeSafePath (rawPath) {
+    let rawParts = _.split(_.trim(rawPath), '/')
+    rawParts = _.map(rawParts, (r) => {
+      return _.kebabCase(_.deburr(_.trim(r)))
+    })
+
+    return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
+  },
+  resolvePath (path) {
+    if (_.startsWith(path, '/')) { path = path.substring(1) }
+    return `${siteConfig.path}${path}`
+  },
+  /**
+   * Set Input Selection
+   * @param {DOMElement} input The input element
+   * @param {number} startPos The starting position
+   * @param {nunber} endPos The ending position
+   */
+  setInputSelection (input, startPos, endPos) {
+    input.focus()
+    if (typeof input.selectionStart !== 'undefined') {
+      input.selectionStart = startPos
+      input.selectionEnd = endPos
+    } else if (document.selection && document.selection.createRange) {
+      // IE branch
+      input.select()
+      var range = document.selection.createRange()
+      range.collapse(true)
+      range.moveEnd('character', endPos)
+      range.moveStart('character', startPos)
+      range.select()
+    }
+  }
 }
 
 export default {

+ 0 - 2
client/js/helpers/lodash.js

@@ -1,5 +1,3 @@
-'use strict'
-
 // ====================================
 // Load minimal lodash
 // ====================================

+ 0 - 26
client/js/helpers/pages.js

@@ -1,26 +0,0 @@
-'use strict'
-
-import deburr from 'lodash/deburr'
-import filter from 'lodash/filter'
-import isEmpty from 'lodash/isEmpty'
-import join from 'lodash/join'
-import kebabCase from 'lodash/kebabCase'
-import map from 'lodash/map'
-import split from 'lodash/split'
-import trim from 'lodash/trim'
-
-module.exports = {
-  /**
-   * Convert raw path to safe path
-   * @param {string} rawPath Raw path
-   * @returns {string} Safe path
-   */
-  makeSafePath: (rawPath) => {
-    let rawParts = split(trim(rawPath), '/')
-    rawParts = map(rawParts, (r) => {
-      return kebabCase(deburr(trim(r)))
-    })
-
-    return join(filter(rawParts, (r) => { return !isEmpty(r) }), '/')
-  }
-}

+ 1 - 1
client/js/modules/localization.js

@@ -45,7 +45,7 @@ module.exports = {
         defaultNS: 'common',
         lng: siteConfig.lang,
         fallbackLng: siteConfig.lang,
-        ns: ['common', 'admin', 'auth']
+        ns: ['common', 'auth']
       })
     return new VueI18Next(i18next)
   }

+ 0 - 2
client/js/pages/admin-edit-user.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   name: 'admin-edit-user',
   props: ['usrdata'],

+ 0 - 2
client/js/pages/admin-profile.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   name: 'admin-profile',
   props: ['email', 'name', 'provider', 'tfaIsActive'],

+ 0 - 2
client/js/pages/admin-settings.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   name: 'admin-settings',
   data() {

+ 0 - 2
client/js/pages/admin-theme.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   name: 'admin-theme',
   props: ['themedata'],

+ 0 - 2
client/js/pages/content-view.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 /* global $ */
 
 export default {

+ 0 - 2
client/js/pages/source-view.component.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   name: 'source-view',
   data() {

+ 0 - 3
client/js/store/modules/anchor.js

@@ -1,5 +1,3 @@
-'use strict'
-
 export default {
   namespaced: true,
   state: {
@@ -15,7 +13,6 @@ export default {
   },
   actions: {
     open({ commit }, hash) {
-      console.info('MIGUEL!')
       commit('anchorChange', { shown: true, hash })
     },
     close({ commit }) {

+ 57 - 0
client/scss/components/navigator.scss

@@ -3,6 +3,7 @@
   top: 0;
   left: 0;
   width: 100%;
+  z-index: 100;
 
   &-bar {
     display: flex;
@@ -123,6 +124,7 @@
     display: flex;
     justify-content: flex-end;
     align-items: stretch;
+    position: relative;
 
     &-item {
       display: flex;
@@ -130,6 +132,7 @@
       align-items: center;
       width: 50px;
       cursor: pointer;
+      transition: all .4s ease;
 
       svg use {
         color: #FFF;
@@ -143,6 +146,60 @@
           fill: mc('blue', '500');
         }
       }
+
+      &.is-active {
+        background-color: #FFF;
+
+        svg use {
+          color: mc('grey', '500');
+          fill: mc('grey', '500');
+        }
+      }
+
+      &-dropdown {
+        position: absolute;
+        right: 0;
+        top: 100%;
+        width: 250px;
+        border-radius: 0 0 0 5px;
+        transition: all .4s ease;
+        transform-origin: top right;
+
+        &-enter-active, &-leave-active {
+          transform: scale(1);
+        }
+        &-enter, &-leave-to {
+          transform: scale(.1, 0);
+        }
+
+        li {
+          background-color: #FFF;
+          height: 50px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 0 .8rem 0 1rem;
+          
+          & + li {
+            border-top: 1px solid mc('grey', '100');
+          }
+
+          &:hover {
+            background-color: mc('grey', '100');
+          }
+          
+          label {
+            font-size: .8rem;
+            font-weight: 600;
+            color: mc('blue', '800');
+            text-transform: uppercase;
+          }
+
+          &:last-child {
+            border-radius: 0 0 0 5px;
+          }
+        }
+      }
     }
   }
 

+ 10 - 2
client/svg/icons.svg

@@ -166,7 +166,7 @@
             </g>
         </g>
     </symbol>
-    <symbol id="nc-man" viewBox="0 0 48 48">
+    <symbol id="nc-man-green" viewBox="0 0 48 48">
         <g>
             <g class="nc-icon-wrapper">
                 <rect x="18" y="24" fill-rule="evenodd" clip-rule="evenodd" fill="#EAC3A2" width="12" height="15"/>
@@ -288,7 +288,7 @@
             </g>
         </g>
     </symbol>
-    <symbol id="nc-man" viewBox="0 0 48 48">
+    <symbol id="nc-man-black" viewBox="0 0 48 48">
         <g>
             <g class="nc-icon-wrapper">
                 <rect x="17.9983292" y="24" fill-rule="evenodd" clip-rule="evenodd" fill="#EAC3A2" width="12" height="17"/>
@@ -440,4 +440,12 @@
             <path fill="#335262" d="M24,13c-6.07513,0-11,4.92487-11,11s4.92487,11,11,11s11-4.92487,11-11S30.07513,13,24,13z M24,31 c-3.86597,0-7-3.13403-7-7c0-3.86603,3.13403-7,7-7c3.86603,0,7,3.13397,7,7C31,27.86597,27.86603,31,24,31z"></path>
         </g>
     </symbol>
+    <symbol id='nc-exit' viewBox="0 0 48 48">
+        <g class="nc-icon-wrapper">
+            <rect x="2" y="2" fill="#59391F" width="24" height="30"></rect>
+            <path fill="#A67C52" d="M26,33H2c-0.55225,0-1-0.44727-1-1V2c0-0.55273,0.44775-1,1-1h24c0.55225,0,1,0.44727,1,1v30 C27,32.55273,26.55225,33,26,33z M3,31h22V3H3V31z"></path>
+            <path fill="#A67C52" d="M16.58252,11.21875l-14-10.03125C2.27686,0.96875,1.87549,0.93848,1.54297,1.11035 C1.20947,1.28223,1,1.625,1,2v30c0,0.32324,0.15576,0.62598,0.41895,0.81348l14,10C15.5918,42.9375,15.79541,43,16,43 c0.15625,0,0.31348-0.03711,0.45752-0.11035C16.79053,42.71777,17,42.375,17,42V12.03125 C17,11.70898,16.84473,11.40625,16.58252,11.21875z"></path>
+            <path fill="#EFD358" d="M45.61377,17.21094l-9-7c-0.30127-0.23438-0.70996-0.27734-1.05322-0.10938 C35.21777,10.26953,35,10.61816,35,11v5H22c-0.55228,0-1,0.44772-1,1v2c0,0.55228,0.44772,1,1,1h13v5 c0,0.38184,0.21777,0.73047,0.56055,0.89844C35.7002,25.9668,35.8501,26,36,26c0.21826,0,0.43506-0.07129,0.61377-0.21094l9-7 C45.85742,18.59961,46,18.30859,46,18S45.85742,17.40039,45.61377,17.21094z"></path>
+        </g>
+    </symbol>
 </svg>

+ 2 - 0
package.json

@@ -103,11 +103,13 @@
     "passport-auth0": "0.6.1",
     "passport-azure-ad-oauth2": "0.0.4",
     "passport-discord": "0.1.3",
+    "passport-dropbox-oauth2": "1.1.0",
     "passport-facebook": "2.1.1",
     "passport-github2": "0.1.11",
     "passport-google-oauth20": "1.0.0",
     "passport-ldapauth": "2.0.0",
     "passport-local": "1.0.0",
+    "passport-oauth2": "1.4.0",
     "passport-slack": "0.0.7",
     "passport-twitch": "1.0.3",
     "passport-windowslive": "1.0.2",

+ 0 - 307
server/controllers/admin.js

@@ -1,307 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-var express = require('express')
-var router = express.Router()
-const Promise = require('bluebird')
-const validator = require('validator')
-const _ = require('lodash')
-const axios = require('axios')
-const path = require('path')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const os = require('os')
-const filesize = require('filesize.js')
-
-/**
- * Admin
- */
-router.get('/', (req, res) => {
-  res.redirect('/admin/profile')
-})
-
-router.get('/profile', (req, res) => {
-  if (res.locals.isGuest) {
-    return res.render('error-forbidden')
-  }
-
-  res.render('pages/admin/profile', { adminTab: 'profile' })
-})
-
-router.post('/profile', (req, res) => {
-  if (res.locals.isGuest) {
-    return res.render('error-forbidden')
-  }
-
-  return wiki.db.User.findById(req.user.id).then((usr) => {
-    usr.name = _.trim(req.body.name)
-    if (usr.provider === 'local' && req.body.password !== '********') {
-      let nPwd = _.trim(req.body.password)
-      if (nPwd.length < 6) {
-        return Promise.reject(new Error('New Password too short!'))
-      } else {
-        return wiki.db.User.hashPassword(nPwd).then((pwd) => {
-          usr.password = pwd
-          return usr.save()
-        })
-      }
-    } else {
-      return usr.save()
-    }
-  }).then(() => {
-    return res.json({ msg: 'OK' })
-  }).catch((err) => {
-    res.status(400).json({ msg: err.message })
-  })
-})
-
-router.get('/stats', (req, res) => {
-  if (res.locals.isGuest) {
-    return res.render('error-forbidden')
-  }
-
-  Promise.all([
-    wiki.db.Entry.count(),
-    wiki.db.UplFile.count(),
-    wiki.db.User.count()
-  ]).spread((totalEntries, totalUploads, totalUsers) => {
-    return res.render('pages/admin/stats', {
-      totalEntries, totalUploads, totalUsers, adminTab: 'stats'
-    }) || true
-  }).catch((err) => {
-    throw err
-  })
-})
-
-router.get('/users', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-
-  wiki.db.User.find({})
-    .select('-password -rights')
-    .sort('name email')
-    .exec().then((usrs) => {
-      res.render('pages/admin/users', { adminTab: 'users', usrs })
-    })
-})
-
-router.get('/users/:id', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-
-  if (!validator.isMongoId(req.params.id)) {
-    return res.render('error-forbidden')
-  }
-
-  wiki.db.User.findById(req.params.id)
-    .select('-password -providerId')
-    .exec().then((usr) => {
-      let usrOpts = {
-        canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
-        canChangeName: (usr.email !== 'guest'),
-        canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
-        canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
-        canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
-      }
-
-      res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
-    }).catch(err => { // eslint-disable-line handle-callback-err
-      return res.status(404).end() || true
-    })
-})
-
-/**
- * Create / Authorize a new user
- */
-router.post('/users/create', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.status(401).json({ msg: 'Unauthorized' })
-  }
-
-  let nUsr = {
-    email: _.toLower(_.trim(req.body.email)),
-    provider: _.trim(req.body.provider),
-    password: req.body.password,
-    name: _.trim(req.body.name)
-  }
-
-  if (!validator.isEmail(nUsr.email)) {
-    return res.status(400).json({ msg: 'Invalid email address' })
-  } else if (!validator.isIn(nUsr.provider, ['local', 'google', 'windowslive', 'facebook', 'github', 'slack'])) {
-    return res.status(400).json({ msg: 'Invalid provider' })
-  } else if (nUsr.provider === 'local' && !validator.isLength(nUsr.password, { min: 6 })) {
-    return res.status(400).json({ msg: 'Password too short or missing' })
-  } else if (nUsr.provider === 'local' && !validator.isLength(nUsr.name, { min: 2 })) {
-    return res.status(400).json({ msg: 'Name is missing' })
-  }
-
-  wiki.db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => {
-    if (exUsr) {
-      return res.status(400).json({ msg: 'User already exists!' }) || true
-    }
-
-    let pwdGen = (nUsr.provider === 'local') ? wiki.db.User.hashPassword(nUsr.password) : Promise.resolve(true)
-    return pwdGen.then(nPwd => {
-      if (nUsr.provider !== 'local') {
-        nUsr.password = ''
-        nUsr.name = '-- pending --'
-      } else {
-        nUsr.password = nPwd
-      }
-
-      nUsr.rights = [{
-        role: 'read',
-        path: '/',
-        exact: false,
-        deny: false
-      }]
-
-      return wiki.db.User.create(nUsr).then(() => {
-        return res.json({ ok: true })
-      })
-    }).catch(err => {
-      wiki.logger.warn(err)
-      return res.status(500).json({ msg: err })
-    })
-  }).catch(err => {
-    wiki.logger.warn(err)
-    return res.status(500).json({ msg: err })
-  })
-})
-
-router.post('/users/:id', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
-  }
-
-  if (!validator.isMongoId(req.params.id)) {
-    return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
-  }
-
-  return wiki.db.User.findById(req.params.id).then((usr) => {
-    usr.name = _.trim(req.body.name)
-    usr.rights = JSON.parse(req.body.rights)
-    if (usr.provider === 'local' && req.body.password !== '********') {
-      let nPwd = _.trim(req.body.password)
-      if (nPwd.length < 6) {
-        return Promise.reject(new Error(wiki.lang.t('errors:newpasswordtooshort')))
-      } else {
-        return wiki.db.User.hashPassword(nPwd).then((pwd) => {
-          usr.password = pwd
-          return usr.save()
-        })
-      }
-    } else {
-      return usr.save()
-    }
-  }).then((usr) => {
-    // Update guest rights for future requests
-    if (usr.provider === 'local' && usr.email === 'guest') {
-      wiki.rights.guest = usr
-    }
-    return usr
-  }).then(() => {
-    return res.json({ msg: 'OK' })
-  }).catch((err) => {
-    res.status(400).json({ msg: err.message })
-  })
-})
-
-/**
- * Delete / Deauthorize a user
- */
-router.delete('/users/:id', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
-  }
-
-  if (!validator.isMongoId(req.params.id)) {
-    return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
-  }
-
-  return wiki.db.User.findByIdAndRemove(req.params.id).then(() => {
-    return res.json({ ok: true })
-  }).catch((err) => {
-    res.status(500).json({ ok: false, msg: err.message })
-  })
-})
-
-router.get('/settings', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-  res.render('pages/admin/settings', { adminTab: 'settings' })
-})
-
-router.get('/system', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-
-  let hostInfo = {
-    cpus: os.cpus(),
-    hostname: os.hostname(),
-    nodeversion: process.version,
-    os: `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}`,
-    totalmem: filesize(os.totalmem()),
-    cwd: process.cwd()
-  }
-
-  fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
-    axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => {
-      let sysversion = {
-        current: 'v' + packageObj.version,
-        latest: resp.data.tag_name,
-        latestPublishedAt: resp.data.published_at
-      }
-
-      res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion })
-    }).catch(err => {
-      wiki.logger.warn(err)
-      res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } })
-    })
-  })
-})
-
-router.post('/system/install', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-
-  // let sysLib = require(path.join(ROOTPATH, 'libs/system.js'))
-  // sysLib.install('v1.0-beta.7')
-  res.status(400).send('Sorry, Upgrade/Re-Install via the web UI is not yet ready. You must use the npm upgrade method in the meantime.').end()
-})
-
-router.get('/theme', (req, res) => {
-  if (!res.locals.rights.manage) {
-    return res.render('error-forbidden')
-  }
-  res.render('pages/admin/theme', { adminTab: 'theme' })
-})
-
-router.post('/theme', (req, res) => {
-  if (res.locals.isGuest) {
-    return res.render('error-forbidden')
-  }
-
-  if (!validator.isIn(req.body.primary, wiki.data.colors)) {
-    return res.status(406).json({ msg: 'Primary color is invalid.' })
-  } else if (!validator.isIn(req.body.alt, wiki.data.colors)) {
-    return res.status(406).json({ msg: 'Alternate color is invalid.' })
-  } else if (!validator.isIn(req.body.footer, wiki.data.colors)) {
-    return res.status(406).json({ msg: 'Footer color is invalid.' })
-  }
-
-  wiki.config.theme.primary = req.body.primary
-  wiki.config.theme.alt = req.body.alt
-  wiki.config.theme.footer = req.body.footer
-  wiki.config.theme.code.dark = req.body.codedark === 'true'
-  wiki.config.theme.code.colorize = req.body.codecolorize === 'true'
-
-  return res.json({ msg: 'OK' })
-})
-
-module.exports = router

+ 1 - 4
server/controllers/auth.js

@@ -34,10 +34,7 @@ const bruteforce = new ExpressBrute(EBstore, {
  * Login form
  */
 router.get('/login', function (req, res, next) {
-  res.render('auth/login', {
-    authStrategies: _.reject(wiki.auth.strategies, { key: 'local' }),
-    hasMultipleStrategies: Object.keys(wiki.config.auth.strategies).length > 1
-  })
+  res.render('auth/login')
 })
 
 router.post('/login', bruteforce.prevent, function (req, res, next) {

+ 0 - 0
server/controllers/pages.js → server/controllers/common.js


+ 0 - 162
server/controllers/uploads.js

@@ -1,162 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const express = require('express')
-const router = express.Router()
-
-const readChunk = require('read-chunk')
-const fileType = require('file-type')
-const Promise = require('bluebird')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const path = require('path')
-const _ = require('lodash')
-
-const validPathRe = new RegExp('^([a-z0-9/-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\.[a-z0-9]+)$')
-const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$')
-
-// ==========================================
-// SERVE UPLOADS FILES
-// ==========================================
-
-router.get('/t/*', (req, res, next) => {
-  let fileName = req.params[0]
-  if (!validPathThumbsRe.test(fileName)) {
-    return res.sendStatus(404).end()
-  }
-
-  // todo: Authentication-based access
-
-  res.sendFile(fileName, {
-    root: wiki.disk.getThumbsPath(),
-    dotfiles: 'deny'
-  }, (err) => {
-    if (err) {
-      res.status(err.status).end()
-    }
-  })
-})
-
-// router.post('/img', wiki.disk.uploadImgHandler, (req, res, next) => {
-//   let destFolder = _.chain(req.body.folder).trim().toLower().value()
-
-//   wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
-//     if (!destFolderPath) {
-//       res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
-//       return true
-//     }
-
-//     Promise.map(req.files, (f) => {
-//       let destFilename = ''
-//       let destFilePath = ''
-
-//       return wiki.disk.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
-//         destFilename = fname
-//         destFilePath = path.resolve(destFolderPath, destFilename)
-
-//         return readChunk(f.path, 0, 262)
-//       }).then((buf) => {
-//         // -> Check MIME type by magic number
-
-//         let mimeInfo = fileType(buf)
-//         if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
-//           return Promise.reject(new Error(wiki.lang.t('errors:invalidfiletype')))
-//         }
-//         return true
-//       }).then(() => {
-//         // -> Move file to final destination
-
-//         return fs.moveAsync(f.path, destFilePath, { clobber: false })
-//       }).then(() => {
-//         return {
-//           ok: true,
-//           filename: destFilename,
-//           filesize: f.size
-//         }
-//       }).reflect()
-//     }, {concurrency: 3}).then((results) => {
-//       let uplResults = _.map(results, (r) => {
-//         if (r.isFulfilled()) {
-//           return r.value()
-//         } else {
-//           return {
-//             ok: false,
-//             msg: r.reason().message
-//           }
-//         }
-//       })
-//       res.json({ ok: true, results: uplResults })
-//       return true
-//     }).catch((err) => {
-//       res.json({ ok: false, msg: err.message })
-//       return true
-//     })
-//   })
-// })
-
-// router.post('/file', wiki.disk.uploadFileHandler, (req, res, next) => {
-//   let destFolder = _.chain(req.body.folder).trim().toLower().value()
-
-//   wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
-//     if (!destFolderPath) {
-//       res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
-//       return true
-//     }
-
-//     Promise.map(req.files, (f) => {
-//       let destFilename = ''
-//       let destFilePath = ''
-
-//       return wiki.disk.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
-//         destFilename = fname
-//         destFilePath = path.resolve(destFolderPath, destFilename)
-
-//         // -> Move file to final destination
-
-//         return fs.moveAsync(f.path, destFilePath, { clobber: false })
-//       }).then(() => {
-//         return {
-//           ok: true,
-//           filename: destFilename,
-//           filesize: f.size
-//         }
-//       }).reflect()
-//     }, {concurrency: 3}).then((results) => {
-//       let uplResults = _.map(results, (r) => {
-//         if (r.isFulfilled()) {
-//           return r.value()
-//         } else {
-//           return {
-//             ok: false,
-//             msg: r.reason().message
-//           }
-//         }
-//       })
-//       res.json({ ok: true, results: uplResults })
-//       return true
-//     }).catch((err) => {
-//       res.json({ ok: false, msg: err.message })
-//       return true
-//     })
-//   })
-// })
-
-// router.get('/*', (req, res, next) => {
-//   let fileName = req.params[0]
-//   if (!validPathRe.test(fileName)) {
-//     return res.sendStatus(404).end()
-//   }
-
-//   // todo: Authentication-based access
-
-//   res.sendFile(fileName, {
-//     root: wiki.git.getRepoPath() + '/uploads/',
-//     dotfiles: 'deny'
-//   }, (err) => {
-//     if (err) {
-//       res.status(err.status).end()
-//     }
-//   })
-// })
-
-module.exports = router

+ 0 - 116
server/controllers/ws.js

@@ -1,116 +0,0 @@
-'use strict'
-
-/* global appconfig, entries, rights, search, upl */
-/* eslint-disable standard/no-callback-literal */
-
-const _ = require('lodash')
-
-module.exports = (socket) => {
-  // Check if Guest
-  if (!socket.request.user.logged_in) {
-    socket.request.user = _.assign(rights.guest, socket.request.user)
-  }
-
-  // -----------------------------------------
-  // SEARCH
-  // -----------------------------------------
-
-  if (appconfig.public || socket.request.user.logged_in) {
-    socket.on('search', (data, cb) => {
-      cb = cb || _.noop
-      search.find(data.terms).then((results) => {
-        return cb(results) || true
-      })
-    })
-  }
-
-  // -----------------------------------------
-  // TREE VIEW (LIST ALL PAGES)
-  // -----------------------------------------
-
-  if (appconfig.public || socket.request.user.logged_in) {
-    socket.on('treeFetch', (data, cb) => {
-      cb = cb || _.noop
-      entries.getFromTree(data.basePath, socket.request.user).then((f) => {
-        return cb(f) || true
-      })
-    })
-  }
-
-  // -----------------------------------------
-  // UPLOADS
-  // -----------------------------------------
-
-  if (socket.request.user.logged_in) {
-    socket.on('uploadsGetFolders', (data, cb) => {
-      cb = cb || _.noop
-      upl.getUploadsFolders().then((f) => {
-        return cb(f) || true
-      })
-    })
-
-    socket.on('uploadsCreateFolder', (data, cb) => {
-      cb = cb || _.noop
-      upl.createUploadsFolder(data.foldername).then((f) => {
-        return cb(f) || true
-      })
-    })
-
-    socket.on('uploadsGetImages', (data, cb) => {
-      cb = cb || _.noop
-      upl.getUploadsFiles('image', data.folder).then((f) => {
-        return cb(f) || true
-      })
-    })
-
-    socket.on('uploadsGetFiles', (data, cb) => {
-      cb = cb || _.noop
-      upl.getUploadsFiles('binary', data.folder).then((f) => {
-        return cb(f) || true
-      })
-    })
-
-    socket.on('uploadsDeleteFile', (data, cb) => {
-      cb = cb || _.noop
-      upl.deleteUploadsFile(data.uid).then((f) => {
-        return cb(f) || true
-      })
-    })
-
-    socket.on('uploadsFetchFileFromURL', (data, cb) => {
-      cb = cb || _.noop
-      upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
-        return cb({ ok: true }) || true
-      }).catch((err) => {
-        return cb({
-          ok: false,
-          msg: err.message
-        }) || true
-      })
-    })
-
-    socket.on('uploadsRenameFile', (data, cb) => {
-      cb = cb || _.noop
-      upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
-        return cb({ ok: true }) || true
-      }).catch((err) => {
-        return cb({
-          ok: false,
-          msg: err.message
-        }) || true
-      })
-    })
-
-    socket.on('uploadsMoveFile', (data, cb) => {
-      cb = cb || _.noop
-      upl.moveUploadsFile(data.uid, data.folder).then((f) => {
-        return cb({ ok: true }) || true
-      }).catch((err) => {
-        return cb({
-          ok: false,
-          msg: err.message
-        }) || true
-      })
-    })
-  }
-}

+ 30 - 0
server/extensions/authentication/dropbox.js

@@ -0,0 +1,30 @@
+/* global wiki */
+
+// ------------------------------------
+// Dropbox Account
+// ------------------------------------
+
+const DropboxStrategy = require('passport-dropbox-oauth2').Strategy
+
+module.exports = {
+  key: 'dropbox',
+  title: 'Dropbox',
+  useForm: false,
+  props: ['clientId', 'clientSecret'],
+  init (passport, conf) {
+    passport.use('dropbox',
+      new DropboxStrategy({
+        apiVersion: '2',
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL
+      }, (accessToken, refreshToken, profile, cb) => {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      })
+    )
+  }
+}

+ 31 - 0
server/extensions/authentication/oauth2.js

@@ -0,0 +1,31 @@
+/* global wiki */
+
+// ------------------------------------
+// OAuth2 Account
+// ------------------------------------
+
+const OAuth2Strategy = require('passport-oauth2').Strategy
+
+module.exports = {
+  key: 'oauth2',
+  title: 'OAuth2',
+  useForm: false,
+  props: ['clientId', 'clientSecret', 'authorizationURL', 'tokenURL'],
+  init (passport, conf) {
+    passport.use('oauth2',
+      new OAuth2Strategy({
+        authorizationURL: conf.authorizationURL,
+        tokenURL: conf.tokenURL,
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        callbackURL: conf.callbackURL
+      }, (accessToken, refreshToken, profile, cb) => {
+        wiki.db.User.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      })
+    )
+  }
+}

+ 86 - 0
server/extensions/renderer/common/mathjax.js

@@ -0,0 +1,86 @@
+const mathjax = require('mathjax-node')
+const _ = require('lodash')
+
+// ------------------------------------
+// Mathjax
+// ------------------------------------
+
+/* global wiki */
+
+const mathRegex = [
+  {
+    format: 'TeX',
+    regex: /\\\[([\s\S]*?)\\\]/g
+  },
+  {
+    format: 'inline-TeX',
+    regex: /\\\((.*?)\\\)/g
+  },
+  {
+    format: 'MathML',
+    regex: /<math([\s\S]*?)<\/math>/g
+  }
+]
+
+module.exports = {
+  key: 'common/mathjax',
+  title: 'Mathjax',
+  dependsOn: [],
+  props: [],
+  init (conf) {
+    mathjax.config({
+      MathJax: {
+        jax: ['input/TeX', 'input/MathML', 'output/SVG'],
+        extensions: ['tex2jax.js', 'mml2jax.js'],
+        TeX: {
+          extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
+        },
+        SVG: {
+          scale: 120,
+          font: 'STIX-Web'
+        }
+      }
+    })
+  },
+  async render (content) {
+    let matchStack = []
+    let replaceStack = []
+    let currentMatch
+    let mathjaxState = {}
+
+    _.forEach(mathRegex, mode => {
+      do {
+        currentMatch = mode.regex.exec(content)
+        if (currentMatch) {
+          matchStack.push(currentMatch[0])
+          replaceStack.push(
+            new Promise((resolve, reject) => {
+              mathjax.typeset({
+                math: (mode.format === 'MathML') ? currentMatch[0] : currentMatch[1],
+                format: mode.format,
+                speakText: false,
+                svg: true,
+                state: mathjaxState,
+                timeout: 30 * 1000
+              }, result => {
+                if (!result.errors) {
+                  resolve(result.svg)
+                } else {
+                  resolve(currentMatch[0])
+                  wiki.logger.warn(result.errors.join(', '))
+                }
+              })
+            })
+          )
+        }
+      } while (currentMatch)
+    })
+
+    return (matchStack.length > 0) ? Promise.all(replaceStack).then(results => {
+      _.forEach(matchStack, (repMatch, idx) => {
+        content = content.replace(repMatch, results[idx])
+      })
+      return content
+    }) : Promise.resolve(content)
+  }
+}

+ 15 - 0
server/extensions/renderer/markdown/abbreviations.js

@@ -0,0 +1,15 @@
+const mdAbbr = require('markdown-it-abbr')
+
+// ------------------------------------
+// Markdown - Abbreviations
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/abbreviations',
+  title: 'Abbreviations',
+  dependsOn: [],
+  props: [],
+  init (md, conf) {
+    md.use(mdAbbr)
+  }
+}

+ 15 - 0
server/extensions/renderer/markdown/emoji.js

@@ -0,0 +1,15 @@
+const mdEmoji = require('markdown-it-emoji')
+
+// ------------------------------------
+// Markdown - Emoji
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/emoji',
+  title: 'Emoji',
+  dependsOn: [],
+  props: [],
+  init (md, conf) {
+    md.use(mdEmoji)
+  }
+}

+ 18 - 0
server/extensions/renderer/markdown/expand-tabs.js

@@ -0,0 +1,18 @@
+const mdExpandTabs = require('markdown-it-expand-tabs')
+const _ = require('lodash')
+
+// ------------------------------------
+// Markdown - Expand Tabs
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/expand-tabs',
+  title: 'Expand Tabs',
+  dependsOn: [],
+  props: ['tabWidth'],
+  init (md, conf) {
+    md.use(mdExpandTabs, {
+      tabWidth: _.toInteger(conf.tabWidth || 4)
+    })
+  }
+}

+ 15 - 0
server/extensions/renderer/markdown/footnotes.js

@@ -0,0 +1,15 @@
+const mdFootnote = require('markdown-it-footnote')
+
+// ------------------------------------
+// Markdown - Footnotes
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/footnotes',
+  title: 'Footnotes',
+  dependsOn: [],
+  props: [],
+  init (md, conf) {
+    md.use(mdFootnote)
+  }
+}

+ 15 - 0
server/extensions/renderer/markdown/mathjax.js

@@ -0,0 +1,15 @@
+const mdMathjax = require('markdown-it-mathjax')()
+
+// ------------------------------------
+// Markdown - Mathjax Preprocessor
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/mathjax',
+  title: 'Mathjax Preprocessor',
+  dependsOn: [],
+  props: [],
+  init (md, conf) {
+    md.use(mdMathjax)
+  }
+}

+ 15 - 0
server/extensions/renderer/markdown/tasks-lists.js

@@ -0,0 +1,15 @@
+const mdTaskLists = require('markdown-it-task-lists')
+
+// ------------------------------------
+// Markdown - Task Lists
+// ------------------------------------
+
+module.exports = {
+  key: 'markdown/task-lists',
+  title: 'Task Lists',
+  dependsOn: [],
+  props: [],
+  init (md, conf) {
+    md.use(mdTaskLists)
+  }
+}

+ 4 - 19
server/master.js

@@ -29,7 +29,6 @@ module.exports = async () => {
   const path = require('path')
   const session = require('express-session')
   const SessionRedisStore = require('connect-redis')(session)
-  // const graceful = require('node-graceful')
   const graphqlApollo = require('apollo-server-express')
   const graphqlSchema = require('./modules/graphql')
 
@@ -106,7 +105,6 @@ module.exports = async () => {
   app.locals.moment = require('moment')
   app.locals.moment.locale(wiki.config.site.lang)
   app.locals.config = wiki.config
-  app.use(mw.flash)
 
   // ----------------------------------------
   // Controllers
@@ -126,21 +124,20 @@ module.exports = async () => {
     })(req, res, next)
   })
   app.use('/graphiql', graphqlApollo.graphiqlExpress({ endpointURL: '/graphql' }))
-  // app.use('/uploads', mw.auth, ctrl.uploads)
-  app.use('/admin', mw.auth, ctrl.admin)
-  app.use('/', mw.auth, ctrl.pages)
+
+  app.use('/', mw.auth, ctrl.common)
 
   // ----------------------------------------
   // Error handling
   // ----------------------------------------
 
-  app.use(function (req, res, next) {
+  app.use((req, res, next) => {
     var err = new Error('Not Found')
     err.status = 404
     next(err)
   })
 
-  app.use(function (err, req, res, next) {
+  app.use((err, req, res, next) => {
     res.status(err.status || 500)
     res.render('error', {
       message: err.message,
@@ -180,17 +177,5 @@ module.exports = async () => {
     wiki.logger.info('HTTP Server: [ RUNNING ]')
   })
 
-  // ----------------------------------------
-  // Graceful shutdown
-  // ----------------------------------------
-
-  // graceful.on('exit', () => {
-  //   wiki.logger.info('- SHUTTING DOWN - Performing git sync...')
-  //   return global.git.resync().then(() => {
-  //     wiki.logger.info('- SHUTTING DOWN - Git sync successful. Now safe to exit.')
-  //     process.exit()
-  //   })
-  // })
-
   return true
 }

+ 16 - 18
server/setup.js

@@ -247,6 +247,9 @@ module.exports = () => {
       confRaw = yaml.safeDump(conf)
       await fs.writeFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
 
+      _.set(wiki.config, 'port', req.body.port)
+      _.set(wiki.config, 'paths.repo', req.body.pathRepo)
+
       // Populate config namespaces
       wiki.config.auth = wiki.config.auth || {}
       wiki.config.features = wiki.config.features || {}
@@ -313,29 +316,24 @@ module.exports = () => {
       })
 
       wiki.logger.info('Setup is complete!')
-      res.json({ ok: true })
+      res.json({
+        ok: true,
+        redirectPath: wiki.config.site.path,
+        redirectPort: wiki.config.port
+      }).end()
+
+      wiki.logger.info('Stopping Setup...')
+      server.destroy(() => {
+        wiki.logger.info('Setup stopped. Starting Wiki.js...')
+        _.delay(() => {
+          wiki.kernel.bootMaster()
+        }, 1000)
+      })
     } catch (err) {
       res.json({ ok: false, error: err.message })
     }
   })
 
-  /**
-   * Restart in normal mode
-   */
-  app.post('/restart', (req, res) => {
-    res.status(204).end()
-    /* server.destroy(() => {
-      spinner.text = 'Setup wizard terminated. Restarting in normal mode...'
-      _.delay(() => {
-        const exec = require('execa')
-        exec.stdout('node', ['wiki', 'start']).then(result => {
-          spinner.succeed('Wiki.js is now running in normal mode!')
-          process.exit(0)
-        })
-      }, 1000)
-    }) */
-  })
-
   // ----------------------------------------
   // Error handling
   // ----------------------------------------

+ 2 - 2
server/views/master.pug

@@ -4,8 +4,8 @@ html
     meta(http-equiv='X-UA-Compatible', content='IE=edge')
     meta(charset='UTF-8')
     meta(name='viewport', content='width=device-width, initial-scale=1')
-    meta(name='theme-color', content='#009688')
-    meta(name='msapplication-TileColor', content='#009688')
+    meta(name='theme-color', content='#0288d1')
+    meta(name='msapplication-TileColor', content='#0288d1')
     meta(name='msapplication-TileImage', content=config.site.path + 'favicons/ms-icon-144x144.png')
     title= config.site.title
 

+ 3 - 19
server/views/setup.pug

@@ -339,31 +339,15 @@ block body
                       h4 Finalizing your installation...
                     .is-logo(v-if='!loading && final.ok')
                       svg.icons.is-64: use(xlink:href='#nc-check-bold')
-                      h4 All done!
+                      h4 Installation complete!
                     p(v-if='!loading && final.ok'): strong Wiki.js was configured successfully and is now ready for use.
-                    p(v-if='!loading && final.ok')
-                      | Click the <strong>Start</strong> button below to launch your newly configured wiki.
+                    p(v-if='!loading && final.ok') Click the #[strong Start] button below to access your newly configured wiki.
                     p(v-if='!loading && !final.ok') #[svg.icons.is-18.is-text: use(xlink:href='#nc-square-remove')] Error: {{ final.error }}
                   .panel-footer
                     .progress-bar: div(v-bind:style='{width: currentProgress}')
-                    button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Back
+                    button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToAdmin', v-if='!loading && !final.ok', v-bind:disabled='loading') Back
                     button.button.is-small.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again
                     button.button.is-small.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start
-
-              //- ==============================================
-              //- RESTART
-              //- ==============================================
-
-              template(v-else-if='state === "restart"')
-                .panel
-                  h2.panel-title.is-featured
-                    span Restarting...
-                    i
-                  .panel-content.is-text
-                    p #[i.icon-loader.animated.rotateIn.infinite] Restarting Wiki.js in normal mode...
-                    p You'll automatically be redirected to the homepage when ready. This usually takes about 30 seconds.
-                  .panel-footer
-                    button.button.is-small.is-green(disabled='disabled') Start
           
           footer
             small Wiki.js Installation Wizard

+ 9 - 2
yarn.lock

@@ -6184,6 +6184,13 @@ passport-discord@0.1.3:
   dependencies:
     passport-oauth2 "^1.2.0"
 
+passport-dropbox-oauth2@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/passport-dropbox-oauth2/-/passport-dropbox-oauth2-1.1.0.tgz#77c737636e4841944dfb82dfc42c3d8ab782c10e"
+  dependencies:
+    passport-oauth "^1.0.0"
+    pkginfo "^0.2.3"
+
 passport-facebook@2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/passport-facebook/-/passport-facebook-2.1.1.tgz#c39d0b52ae4d59163245a4e21a7b9b6321303311"
@@ -6225,7 +6232,7 @@ passport-oauth1@1.x.x:
     passport-strategy "1.x.x"
     utils-merge "1.x.x"
 
-passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.2.0:
+passport-oauth2@1.4.0, passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.2.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad"
   dependencies:
@@ -6472,7 +6479,7 @@ pkg-dir@^1.0.0:
   dependencies:
     find-up "^1.0.0"
 
-pkginfo@0.2.x:
+pkginfo@0.2.x, pkginfo@^0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.2.3.tgz#7239c42a5ef6c30b8f328439d9b9ff71042490f8"