Bläddra i källkod

Setup wizard - all UI steps

NGPixel 8 år sedan
förälder
incheckning
bb45618447

+ 1 - 0
CHANGELOG.md

@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 
 ### Fixed
 ### Fixed
 - Auth: Authentication would fail if email has uppercase chars and provider callback is in lowercase
 - Auth: Authentication would fail if email has uppercase chars and provider callback is in lowercase
+- Markdown: Fixed potential crash on markdown processing of video links
 - Search: Search index should now update upon article creation
 - Search: Search index should now update upon article creation
 - Search: Search results are no longer duplicated upon article update
 - Search: Search results are no longer duplicated upon article update
 - UI: Missing icons on login page
 - UI: Missing icons on login page

+ 71 - 67
agent.js

@@ -49,38 +49,41 @@ var Cron = require('cron').CronJob
 // Start Cron
 // Start Cron
 // ----------------------------------------
 // ----------------------------------------
 
 
+var job
 var jobIsBusy = false
 var jobIsBusy = false
 var jobUplWatchStarted = false
 var jobUplWatchStarted = false
 
 
-var job = new Cron({
-  cronTime: '0 */5 * * * *',
-  onTick: () => {
-    // Make sure we don't start two concurrent jobs
-
-    if (jobIsBusy) {
-      winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
-      return
-    }
-    winston.info('[AGENT] Running all jobs...')
-    jobIsBusy = true
+db.onReady.then(() => {
+  return db.Entry.remove({})
+}).then(() => {
+  job = new Cron({
+    cronTime: '0 */5 * * * *',
+    onTick: () => {
+      // Make sure we don't start two concurrent jobs
+
+      if (jobIsBusy) {
+        winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
+        return
+      }
+      winston.info('[AGENT] Running all jobs...')
+      jobIsBusy = true
 
 
-    // Prepare async job collector
+      // Prepare async job collector
 
 
-    let jobs = []
-    let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
-    let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
-    let uploadsTempPath = path.join(dataPath, 'temp-upload')
+      let jobs = []
+      let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
+      let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
+      let uploadsTempPath = path.join(dataPath, 'temp-upload')
 
 
-    // ----------------------------------------
-    // REGULAR JOBS
-    // ----------------------------------------
+      // ----------------------------------------
+      // REGULAR JOBS
+      // ----------------------------------------
 
 
-    //* ****************************************
-    // -> Sync with Git remote
-    //* ****************************************
+      //* ****************************************
+      // -> Sync with Git remote
+      //* ****************************************
 
 
-    jobs.push(git.onReady.then(() => {
-      return git.resync().then(() => {
+      jobs.push(git.resync().then(() => {
         // -> Stream all documents
         // -> Stream all documents
 
 
         let cacheJobs = []
         let cacheJobs = []
@@ -131,55 +134,56 @@ var job = new Cron({
         })
         })
 
 
         return jobCbStreamDocs
         return jobCbStreamDocs
-      })
-    }))
-
-    //* ****************************************
-    // -> Clear failed temporary upload files
-    //* ****************************************
-
-    jobs.push(
-      fs.readdirAsync(uploadsTempPath).then((ls) => {
-        let fifteenAgo = moment().subtract(15, 'minutes')
-
-        return Promise.map(ls, (f) => {
-          return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
-        }).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
-          return Promise.map(arrFiles, (f) => {
-            if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
-              return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
-            } else {
-              return true
-            }
+      }))
+
+      //* ****************************************
+      // -> Clear failed temporary upload files
+      //* ****************************************
+
+      jobs.push(
+        fs.readdirAsync(uploadsTempPath).then((ls) => {
+          let fifteenAgo = moment().subtract(15, 'minutes')
+
+          return Promise.map(ls, (f) => {
+            return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
+          }).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
+            return Promise.map(arrFiles, (f) => {
+              if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
+                return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
+              } else {
+                return true
+              }
+            })
           })
           })
         })
         })
-      })
-    )
+      )
 
 
-    // ----------------------------------------
-    // Run
-    // ----------------------------------------
+      // ----------------------------------------
+      // Run
+      // ----------------------------------------
 
 
-    Promise.all(jobs).then(() => {
-      winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
+      Promise.all(jobs).then(() => {
+        winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
 
 
-      if (!jobUplWatchStarted) {
-        jobUplWatchStarted = true
-        upl.initialScan().then(() => {
-          job.start()
-        })
-      }
+        if (!jobUplWatchStarted) {
+          jobUplWatchStarted = true
+          upl.initialScan().then(() => {
+            job.start()
+          })
+        }
+
+        return true
+      }).catch((err) => {
+        winston.error('[AGENT] One or more jobs have failed: ', err)
+      }).finally(() => {
+        jobIsBusy = false
+      })
+    },
+    start: false,
+    timeZone: 'UTC',
+    runOnInit: true
+  })
 
 
-      return true
-    }).catch((err) => {
-      winston.error('[AGENT] One or more jobs have failed: ', err)
-    }).finally(() => {
-      jobIsBusy = false
-    })
-  },
-  start: false,
-  timeZone: 'UTC',
-  runOnInit: true
 })
 })
 
 
 // ----------------------------------------
 // ----------------------------------------

+ 22 - 0
app/data.yml

@@ -50,4 +50,26 @@ defaults:
       signature:
       signature:
         name: Wiki
         name: Wiki
         email: wiki@example.com
         email: wiki@example.com
+langs:
+  -
+    id: en
+    name: English
+  -
+    id: fr
+    name: French - Français
+  -
+    id: de
+    name: German - Deutsch
+  -
+    id: ko
+    name: Korean - 한국어
+  -
+    id: pt
+    name: Portuguese - Português
+  -
+    id: ru
+    name: Russian - Русский
+  -
+    id: es
+    name: Spanish - Español
 # ---------------------------------
 # ---------------------------------

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
assets/css/app.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
assets/css/configure.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
assets/js/app.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 1
assets/js/configure.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
assets/js/libs.js


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

@@ -93,6 +93,7 @@ if ($('#mk-editor').length === 1) {
             mdeModalOpenState = true;
             mdeModalOpenState = true;
             $('#modal-editor-link').slideToggle();
             $('#modal-editor-link').slideToggle();
           } */
           } */
+          window.alert('Coming soon!')
         },
         },
         className: 'icon-link2',
         className: 'icon-link2',
         title: 'Insert Link'
         title: 'Insert Link'
@@ -163,6 +164,7 @@ if ($('#mk-editor').length === 1) {
       {
       {
         name: 'table',
         name: 'table',
         action: (editor) => {
         action: (editor) => {
+          window.alert('Coming soon!')
           // todo
           // todo
         },
         },
         className: 'icon-table',
         className: 'icon-table',

+ 196 - 8
client/js/configure.js

@@ -1,24 +1,63 @@
 'use strict'
 'use strict'
 
 
-/* global jQuery, _, Vue, axios */
+/* global jQuery, _, Vue, VeeValidate, axios */
+
+Vue.use(VeeValidate, {
+  enableAutoClasses: true,
+  classNames: {
+    touched: 'is-touched', // the control has been blurred
+    untouched: 'is-untouched', // the control hasn't been blurred
+    valid: 'is-valid', // model is valid
+    invalid: 'is-invalid', // model is invalid
+    pristine: 'is-pristine', // control has not been interacted with
+    dirty: 'is-dirty' // control has been interacted with
+  }
+})
 
 
 jQuery(document).ready(function ($) {
 jQuery(document).ready(function ($) {
   new Vue({ // eslint-disable-line no-new
   new Vue({ // eslint-disable-line no-new
     el: 'main',
     el: 'main',
     data: {
     data: {
       loading: false,
       loading: false,
-      state: 'considerations',
+      state: 'welcome',
       syscheck: {
       syscheck: {
         ok: false,
         ok: false,
         error: '',
         error: '',
         results: []
         results: []
       },
       },
+      dbcheck: {
+        ok: false,
+        error: ''
+      },
+      gitcheck: {
+        ok: false,
+        error: ''
+      },
+      final: {
+        ok: false,
+        error: '',
+        results: []
+      },
       conf: {
       conf: {
         title: 'Wiki',
         title: 'Wiki',
-        host: '',
+        host: 'http://',
         port: 80,
         port: 80,
         lang: 'en',
         lang: 'en',
-        db: 'mongodb://localhost:27017/wiki'
+        db: 'mongodb://localhost:27017/wiki',
+        pathData: './data',
+        pathRepo: './repo',
+        gitUrl: '',
+        gitBranch: 'master',
+        gitAuthType: 'ssh',
+        gitAuthSSHKey: '',
+        gitAuthUser: '',
+        gitAuthPass: '',
+        gitAuthSSL: true,
+        gitSignatureName: '',
+        gitSignatureEmail: '',
+        adminEmail: '',
+        adminPassword: '',
+        adminPasswordConfirm: ''
       },
       },
       considerations: {
       considerations: {
         https: false,
         https: false,
@@ -26,6 +65,44 @@ jQuery(document).ready(function ($) {
         localhost: false
         localhost: false
       }
       }
     },
     },
+    computed: {
+      currentProgress: function () {
+        let perc = '0%'
+        switch (this.state) {
+          case 'welcome':
+            perc = '0%'
+            break
+          case 'syscheck':
+            perc = (this.syscheck.ok) ? '15%' : '5%'
+            break
+          case 'general':
+            perc = '20%'
+            break
+          case 'considerations':
+            perc = '30%'
+            break
+          case 'db':
+            perc = '35%'
+            break
+          case 'dbcheck':
+            perc = (this.dbcheck.ok) ? '50%' : '40%'
+            break
+          case 'paths':
+            perc = '55%'
+            break
+          case 'git':
+            perc = '60%'
+            break
+          case 'gitcheck':
+            perc = (this.gitcheck.ok) ? '75%' : '65%'
+            break
+          case 'admin':
+            perc = '80%'
+            break
+        }
+        return perc
+      }
+    },
     methods: {
     methods: {
       proceedToWelcome: function (ev) {
       proceedToWelcome: function (ev) {
         this.state = 'welcome'
         this.state = 'welcome'
@@ -58,8 +135,12 @@ jQuery(document).ready(function ($) {
         }, 1000)
         }, 1000)
       },
       },
       proceedToGeneral: function (ev) {
       proceedToGeneral: function (ev) {
-        this.state = 'general'
-        this.loading = false
+        let self = this
+        self.state = 'general'
+        self.loading = false
+        self.$nextTick(() => {
+          self.$validator.validateAll('general')
+        })
       },
       },
       proceedToConsiderations: function (ev) {
       proceedToConsiderations: function (ev) {
         this.considerations = {
         this.considerations = {
@@ -71,8 +152,115 @@ jQuery(document).ready(function ($) {
         this.loading = false
         this.loading = false
       },
       },
       proceedToDb: function (ev) {
       proceedToDb: function (ev) {
-        this.state = 'db'
-        this.loading = false
+        let self = this
+        self.state = 'db'
+        self.loading = false
+        self.$nextTick(() => {
+          self.$validator.validateAll('db')
+        })
+      },
+      proceedToDbcheck: function (ev) {
+        let self = this
+        this.state = 'dbcheck'
+        this.loading = true
+        self.dbcheck = {
+          ok: false,
+          error: ''
+        }
+
+        _.delay(() => {
+          axios.post('/dbcheck', {
+            db: self.conf.db
+          }).then(resp => {
+            if (resp.data.ok === true) {
+              self.dbcheck.ok = true
+            } else {
+              self.dbcheck.ok = false
+              self.dbcheck.error = resp.data.error
+            }
+            self.loading = false
+            self.$nextTick()
+          }).catch(err => {
+            window.alert(err.message)
+          })
+        }, 1000)
+      },
+      proceedToPaths: function (ev) {
+        let self = this
+        self.state = 'paths'
+        self.loading = false
+        self.$nextTick(() => {
+          self.$validator.validateAll('paths')
+        })
+      },
+      proceedToGit: function (ev) {
+        let self = this
+        self.state = 'git'
+        self.loading = false
+        self.$nextTick(() => {
+          self.$validator.validateAll('git')
+        })
+      },
+      proceedToGitCheck: function (ev) {
+        let self = this
+        this.state = 'gitcheck'
+        this.loading = true
+        self.dbcheck = {
+          ok: false,
+          error: ''
+        }
+
+        _.delay(() => {
+          axios.post('/gitcheck', self.conf).then(resp => {
+            if (resp.data.ok === true) {
+              self.gitcheck.ok = true
+            } else {
+              self.gitcheck.ok = false
+              self.gitcheck.error = resp.data.error
+            }
+            self.loading = false
+            self.$nextTick()
+          }).catch(err => {
+            window.alert(err.message)
+          })
+        }, 1000)
+      },
+      proceedToAdmin: function (ev) {
+        let self = this
+        self.state = 'admin'
+        self.loading = false
+        self.$nextTick(() => {
+          self.$validator.validateAll('admin')
+        })
+      },
+      proceedToFinal: function (ev) {
+        let self = this
+        self.state = 'final'
+        self.loading = true
+        self.final = {
+          ok: false,
+          error: '',
+          results: []
+        }
+
+        _.delay(() => {
+          axios.post('/finalize', self.conf).then(resp => {
+            if (resp.data.ok === true) {
+              self.final.ok = true
+              self.final.results = resp.data.results
+            } else {
+              self.final.ok = false
+              self.final.error = resp.data.error
+            }
+            self.loading = false
+            self.$nextTick()
+          }).catch(err => {
+            window.alert(err.message)
+          })
+        }, 1000)
+      },
+      finish: function (ev) {
+
       }
       }
     }
     }
   })
   })

+ 5 - 0
client/js/pages/edit.js

@@ -2,6 +2,7 @@
 
 
 if ($('#page-type-edit').length) {
 if ($('#page-type-edit').length) {
   let pageEntryPath = $('#page-type-edit').data('entrypath') // eslint-disable-line no-unused-vars
   let pageEntryPath = $('#page-type-edit').data('entrypath') // eslint-disable-line no-unused-vars
+  // let pageCleanExit = false
 
 
   // -> Discard
   // -> Discard
 
 
@@ -9,6 +10,10 @@ if ($('#page-type-edit').length) {
     $('#modal-edit-discard').toggleClass('is-active')
     $('#modal-edit-discard').toggleClass('is-active')
   })
   })
 
 
+  // window.onbeforeunload = function () {
+  //   return (pageCleanExit) ? true : 'Unsaved modifications will be lost. Are you sure you want to navigate away from this page?'
+  // }
+
   /* eslint-disable spaced-comment */
   /* eslint-disable spaced-comment */
   //=include ../components/editor.js
   //=include ../components/editor.js
   /* eslint-enable spaced-comment */
   /* eslint-enable spaced-comment */

+ 6 - 6
client/scss/components/_editor.scss

@@ -1,13 +1,13 @@
 
 
 .editor-toolbar {
 .editor-toolbar {
 	z-index: 2;
 	z-index: 2;
-	background-color: rgba(0,0,0,0.75);
+	background-color: mc('indigo', '900');
 	border: none;
 	border: none;
 	border-top-left-radius: 0;
 	border-top-left-radius: 0;
 	border-top-right-radius: 0;
 	border-top-right-radius: 0;
 	opacity: 1;
 	opacity: 1;
 	position: fixed;
 	position: fixed;
-	top: 51px;
+	top: 50px;
 	left: 0;
 	left: 0;
 	width: 100%;
 	width: 100%;
 
 
@@ -90,7 +90,7 @@
 
 
 	}
 	}
 
 
-	
+
 
 
 }
 }
 
 
@@ -98,7 +98,7 @@
 	display: flex;
 	display: flex;
 	flex-wrap: wrap;
 	flex-wrap: wrap;
 	align-items: flex-start;
 	align-items: flex-start;
-	
+
 	overflow: auto;
 	overflow: auto;
 	overflow-x: hidden;
 	overflow-x: hidden;
 
 
@@ -140,7 +140,7 @@
 
 
 		> span {
 		> span {
 			font-size: 12px;
 			font-size: 12px;
-			
+
 			> strong {
 			> strong {
 				text-overflow: ellipsis;
 				text-overflow: ellipsis;
 				white-space: nowrap;
 				white-space: nowrap;
@@ -358,4 +358,4 @@
 	left: 0;
 	left: 0;
 	bottom: 0;
 	bottom: 0;
 	right: 0;
 	right: 0;
-}
+}

+ 21 - 0
client/scss/configure.scss

@@ -53,3 +53,24 @@ i.icon-warning-outline {
   max-height: 0;
   max-height: 0;
   padding-top: 0;
   padding-top: 0;
 }
 }
+
+.progress-bar {
+  width: 150px;
+  height: 10px;
+  background-color: mc('indigo', '50');
+  border:1px solid mc('indigo', '100');
+  border-radius: 3px;
+  position: absolute;
+  left: 15px;
+  top: 21px;
+  padding: 1px;
+
+  > div {
+    width: 5px;
+    height: 6px;
+    background-color: mc('indigo', '200');
+    border-radius: 2px;
+    transition: all 1s ease;
+  }
+
+}

+ 115 - 1
configure.js

@@ -15,8 +15,11 @@ module.exports = (port, spinner) => {
   const http = require('http')
   const http = require('http')
   const path = require('path')
   const path = require('path')
   const Promise = require('bluebird')
   const Promise = require('bluebird')
+  const fs = require('fs-extra')
+  const yaml = require('js-yaml')
   const _ = require('lodash')
   const _ = require('lodash')
 
 
+
   // ----------------------------------------
   // ----------------------------------------
   // Define Express App
   // Define Express App
   // ----------------------------------------
   // ----------------------------------------
@@ -48,9 +51,20 @@ module.exports = (port, spinner) => {
   // ----------------------------------------
   // ----------------------------------------
 
 
   app.get('*', (req, res) => {
   app.get('*', (req, res) => {
-    res.render('configure/index')
+    let langs = []
+    try {
+      langs = yaml.safeLoad(fs.readFileSync('./app/data.yml', 'utf8')).langs
+    } catch (err) {
+      console.error(err)
+    }
+    res.render('configure/index', {
+      langs
+    })
   })
   })
 
 
+  /**
+   * Perform basic system checks
+   */
   app.post('/syscheck', (req, res) => {
   app.post('/syscheck', (req, res) => {
     Promise.mapSeries([
     Promise.mapSeries([
       () => {
       () => {
@@ -105,6 +119,106 @@ module.exports = (port, spinner) => {
     })
     })
   })
   })
 
 
+  /**
+   * Check the DB connection
+   */
+  app.post('/dbcheck', (req, res) => {
+    let mongo = require('mongodb').MongoClient
+    mongo.connect(req.body.db, {
+      autoReconnect: false,
+      reconnectTries: 2,
+      reconnectInterval: 1000,
+      connectTimeoutMS: 5000,
+      socketTimeoutMS: 5000
+    }, (err, db) => {
+      if (err === null) {
+        // Try to create a test collection
+        db.createCollection('test', (err, results) => {
+          if (err === null) {
+            // Try to drop test collection
+            db.dropCollection('test', (err, results) => {
+              if (err === null) {
+                res.json({ ok: true })
+              } else {
+                res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
+              }
+              db.close()
+            })
+          } else {
+            res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
+            db.close()
+          }
+        })
+      } else {
+        res.json({ ok: false, error: err.message })
+      }
+    })
+  })
+
+  /**
+   * Check the Git connection
+   */
+  app.post('/gitcheck', (req, res) => {
+    const exec = require('execa')
+
+    const dataDir = path.resolve(ROOTPATH, req.body.pathData)
+    const gitDir = path.resolve(ROOTPATH, req.body.pathRepo)
+    let results = []
+
+    fs.ensureDir(dataDir).then(() => {
+      results.push('Data directory path is valid.')
+      return fs.ensureDir(gitDir).then(() => {
+        results.push('Git directory path is valid.')
+        return true
+      })
+    }).then(() => {
+      return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {
+        results.push('Git local repository initialized.')
+        return true
+      })
+    }).then(() => {
+      return res.json({ ok: true, results })
+    }).catch(err => {
+      res.json({ ok: false, error: err.message })
+    })
+  })
+
+  /**
+   * Check the DB connection
+   */
+  app.post('/finalize', (req, res) => {
+    let mongo = require('mongodb').MongoClient
+    mongo.connect(req.body.db, {
+      autoReconnect: false,
+      reconnectTries: 2,
+      reconnectInterval: 1000,
+      connectTimeoutMS: 5000,
+      socketTimeoutMS: 5000
+    }, (err, db) => {
+      if (err === null) {
+        // Try to create a test collection
+        db.createCollection('test', (err, results) => {
+          if (err === null) {
+            // Try to drop test collection
+            db.dropCollection('test', (err, results) => {
+              if (err === null) {
+                res.json({ ok: true })
+              } else {
+                res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
+              }
+              db.close()
+            })
+          } else {
+            res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
+            db.close()
+          }
+        })
+      } else {
+        res.json({ ok: false, error: err.message })
+      }
+    })
+  })
+
   // ----------------------------------------
   // ----------------------------------------
   // Error handling
   // Error handling
   // ----------------------------------------
   // ----------------------------------------

+ 1 - 0
gulpfile.js

@@ -27,6 +27,7 @@ const paths = {
       './node_modules/socket.io-client/dist/socket.io.min.js',
       './node_modules/socket.io-client/dist/socket.io.min.js',
       './node_modules/jquery/dist/jquery.min.js',
       './node_modules/jquery/dist/jquery.min.js',
       './node_modules/vue/dist/vue.min.js',
       './node_modules/vue/dist/vue.min.js',
+      './node_modules/vee-validate/dist/vee-validate.min.js',
       './node_modules/axios/dist/axios.min.js',
       './node_modules/axios/dist/axios.min.js',
       './node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
       './node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
       './node_modules/jquery-simple-upload/simpleUpload.min.js',
       './node_modules/jquery-simple-upload/simpleUpload.min.js',

+ 2 - 58
libs/entries.js

@@ -152,7 +152,7 @@ module.exports = {
         return false
         return false
       }
       }
     }).catch((err) => { // eslint-disable-line handle-callback-err
     }).catch((err) => { // eslint-disable-line handle-callback-err
-      return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'))
+      throw new Promise.OperationalError('Entry ' + entryPath + ' does not exist!')
     })
     })
   },
   },
 
 
@@ -299,8 +299,7 @@ module.exports = {
         _id: content.entryPath,
         _id: content.entryPath,
         title: content.meta.title || content.entryPath,
         title: content.meta.title || content.entryPath,
         subtitle: content.meta.subtitle || '',
         subtitle: content.meta.subtitle || '',
-        parent: content.parent.title || '',
-        content: content.text || ''
+        parent: content.parent.title || ''
       }, {
       }, {
         new: true,
         new: true,
         upsert: true
         upsert: true
@@ -396,60 +395,5 @@ module.exports = {
     return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
     return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
       return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
       return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
     })
     })
-  },
-
-  /**
-   * Searches entries based on terms.
-   *
-   * @param      {String}  terms   The terms to search for
-   * @return     {Promise<Object>}  Promise of the search results
-   */
-  search (terms) {
-    terms = _.chain(terms)
-      .deburr()
-      .toLower()
-      .trim()
-      .replace(/[^a-z0-9\- ]/g, '')
-      .split(' ')
-      .filter((f) => { return !_.isEmpty(f) })
-      .join(' ')
-      .value()
-
-    return db.Entry.find(
-      { $text: { $search: terms } },
-      { score: { $meta: 'textScore' }, title: 1 }
-    )
-    .sort({ score: { $meta: 'textScore' } })
-    .limit(10)
-    .exec()
-    .then((hits) => {
-      if (hits.length < 5) {
-        let regMatch = new RegExp('^' + _.split(terms, ' ')[0])
-        return db.Entry.find({
-          _id: { $regex: regMatch }
-        }, '_id')
-            .sort('_id')
-            .limit(5)
-            .exec()
-            .then((matches) => {
-              return {
-                match: hits,
-                suggest: (matches) ? _.map(matches, '_id') : []
-              }
-            })
-      } else {
-        return {
-          match: _.filter(hits, (h) => { return h._doc.score >= 1 }),
-          suggest: []
-        }
-      }
-    }).catch((err) => {
-      winston.error(err)
-      return {
-        match: [],
-        suggest: []
-      }
-    })
   }
   }
-
 }
 }

+ 2 - 4
libs/uploads-agent.js

@@ -231,10 +231,8 @@ module.exports = {
    */
    */
   generateThumbnail (sourcePath, destPath) {
   generateThumbnail (sourcePath, destPath) {
     return jimp.read(sourcePath).then(img => {
     return jimp.read(sourcePath).then(img => {
-      return img.cover(150, 150)
-              .background(0xFFFFFFFF)
-              .opaque()
-              .rgba(false)
+      return img
+              .contain(150, 150)
               .write(destPath)
               .write(destPath)
     })
     })
   },
   },

+ 0 - 19
models/entry.js

@@ -21,10 +21,6 @@ var entrySchema = Mongoose.Schema({
   parent: {
   parent: {
     type: String,
     type: String,
     default: ''
     default: ''
-  },
-  content: {
-    type: String,
-    default: ''
   }
   }
 
 
 },
 },
@@ -32,19 +28,4 @@ var entrySchema = Mongoose.Schema({
     timestamps: {}
     timestamps: {}
   })
   })
 
 
-entrySchema.index({
-  _id: 'text',
-  title: 'text',
-  subtitle: 'text',
-  content: 'text'
-}, {
-  weights: {
-    _id: 3,
-    title: 10,
-    subtitle: 5,
-    content: 1
-  },
-  name: 'EntriesTextIndex'
-})
-
 module.exports = Mongoose.model('Entry', entrySchema)
 module.exports = Mongoose.model('Entry', entrySchema)

+ 25 - 22
package.json

@@ -41,7 +41,7 @@
     "bcryptjs-then": "^1.0.1",
     "bcryptjs-then": "^1.0.1",
     "bluebird": "^3.4.7",
     "bluebird": "^3.4.7",
     "body-parser": "^1.17.1",
     "body-parser": "^1.17.1",
-    "bunyan": "^1.8.8",
+    "bunyan": "^1.8.9",
     "cheerio": "^0.22.0",
     "cheerio": "^0.22.0",
     "child-process-promise": "^2.2.0",
     "child-process-promise": "^2.2.0",
     "chokidar": "^1.6.0",
     "chokidar": "^1.6.0",
@@ -51,21 +51,22 @@
     "connect-mongo": "^1.3.2",
     "connect-mongo": "^1.3.2",
     "cookie-parser": "^1.4.3",
     "cookie-parser": "^1.4.3",
     "cron": "^1.2.1",
     "cron": "^1.2.1",
+    "execa": "^0.6.3",
     "express": "^4.15.2",
     "express": "^4.15.2",
     "express-brute": "^1.0.0",
     "express-brute": "^1.0.0",
     "express-brute-mongoose": "0.0.7",
     "express-brute-mongoose": "0.0.7",
     "express-session": "^1.15.1",
     "express-session": "^1.15.1",
     "file-type": "^4.0.0",
     "file-type": "^4.0.0",
     "filesize.js": "^1.0.2",
     "filesize.js": "^1.0.2",
-    "follow-redirects": "^1.2.1",
-    "fs-extra": "^2.0.0",
+    "follow-redirects": "^1.2.3",
+    "fs-extra": "^2.1.2",
     "git-wrapper2-promise": "^0.2.9",
     "git-wrapper2-promise": "^0.2.9",
     "highlight.js": "^9.9.0",
     "highlight.js": "^9.9.0",
-    "i18next": "^7.1.1",
-    "i18next-express-middleware": "^1.0.2",
+    "i18next": "^7.1.3",
+    "i18next-express-middleware": "^1.0.3",
     "i18next-node-fs-backend": "^0.1.3",
     "i18next-node-fs-backend": "^0.1.3",
     "image-size": "^0.5.1",
     "image-size": "^0.5.1",
-    "jimp": "github:NGPixel/jimp",
+    "jimp": "github:ngpixel/jimp",
     "js-yaml": "^3.8.1",
     "js-yaml": "^3.8.1",
     "klaw": "^1.3.1",
     "klaw": "^1.3.1",
     "levelup": "^1.3.5",
     "levelup": "^1.3.5",
@@ -80,12 +81,13 @@
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-task-lists": "^1.4.1",
     "markdown-it-task-lists": "^1.4.1",
     "memdown": "^1.2.4",
     "memdown": "^1.2.4",
-    "mime-types": "^2.1.13",
-    "moment": "^2.17.1",
+    "mime-types": "^2.1.15",
+    "moment": "^2.18.1",
     "moment-timezone": "^0.5.11",
     "moment-timezone": "^0.5.11",
-    "mongoose": "^4.8.5",
+    "mongodb": "^2.2.25",
+    "mongoose": "^4.9.1",
     "multer": "^1.2.1",
     "multer": "^1.2.1",
-    "ora": "^1.1.0",
+    "ora": "^1.2.0",
     "passport": "^0.3.2",
     "passport": "^0.3.2",
     "passport-local": "^1.0.0",
     "passport-local": "^1.0.0",
     "passport.socketio": "^3.7.0",
     "passport.socketio": "^3.7.0",
@@ -94,11 +96,11 @@
     "read-chunk": "^2.0.0",
     "read-chunk": "^2.0.0",
     "remove-markdown": "^0.1.0",
     "remove-markdown": "^0.1.0",
     "requarks-core": "^0.2.2",
     "requarks-core": "^0.2.2",
-    "request": "^2.80.0",
-    "search-index-adder": "github:NGPixel/search-index-adder",
-    "search-index-searcher": "github:NGPixel/search-index-searcher",
+    "request": "^2.81.0",
+    "search-index-adder": "github:ngpixel/search-index-adder",
+    "search-index-searcher": "github:ngpixel/search-index-searcher",
     "semver": "^5.3.0",
     "semver": "^5.3.0",
-    "serve-favicon": "^2.4.1",
+    "serve-favicon": "^2.4.2",
     "simplemde": "^1.11.2",
     "simplemde": "^1.11.2",
     "socket.io": "^1.7.3",
     "socket.io": "^1.7.3",
     "sticky-js": "^1.0.7",
     "sticky-js": "^1.0.7",
@@ -112,16 +114,16 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "ace-builds": "^1.2.6",
     "ace-builds": "^1.2.6",
-    "babel-preset-es2015": "^6.16.0",
+    "babel-preset-es2015": "^6.24.0",
     "chai": "^3.5.0",
     "chai": "^3.5.0",
     "chai-as-promised": "^6.0.0",
     "chai-as-promised": "^6.0.0",
     "codacy-coverage": "^2.0.0",
     "codacy-coverage": "^2.0.0",
-    "eslint": "^3.16.1",
+    "eslint": "^3.18.0",
     "eslint-plugin-promise": "^3.5.0",
     "eslint-plugin-promise": "^3.5.0",
     "eslint-plugin-standard": "^2.1.1",
     "eslint-plugin-standard": "^2.1.1",
     "gulp": "^3.9.1",
     "gulp": "^3.9.1",
     "gulp-babel": "^6.1.2",
     "gulp-babel": "^6.1.2",
-    "gulp-clean-css": "^3.0.3",
+    "gulp-clean-css": "^3.0.4",
     "gulp-concat": "^2.6.1",
     "gulp-concat": "^2.6.1",
     "gulp-gzip": "^1.4.0",
     "gulp-gzip": "^1.4.0",
     "gulp-include": "^2.3.1",
     "gulp-include": "^2.3.1",
@@ -129,12 +131,12 @@
     "gulp-plumber": "^1.1.0",
     "gulp-plumber": "^1.1.0",
     "gulp-sass": "^3.0.0",
     "gulp-sass": "^3.0.0",
     "gulp-tar": "^1.9.0",
     "gulp-tar": "^1.9.0",
-    "gulp-uglify": "^2.0.0",
+    "gulp-uglify": "^2.1.2",
     "gulp-watch": "^4.3.11",
     "gulp-watch": "^4.3.11",
     "gulp-zip": "^4.0.0",
     "gulp-zip": "^4.0.0",
     "istanbul": "^0.4.5",
     "istanbul": "^0.4.5",
-    "jquery": "^3.1.1",
-    "jquery-contextmenu": "^2.4.3",
+    "jquery": "^3.2.1",
+    "jquery-contextmenu": "^2.4.4",
     "jquery-simple-upload": "^1.0.0",
     "jquery-simple-upload": "^1.0.0",
     "jquery-smooth-scroll": "^2.0.0",
     "jquery-smooth-scroll": "^2.0.0",
     "merge-stream": "^1.0.1",
     "merge-stream": "^1.0.1",
@@ -144,10 +146,11 @@
     "pug-lint": "^2.4.0",
     "pug-lint": "^2.4.0",
     "run-sequence": "^1.2.2",
     "run-sequence": "^1.2.2",
     "snyk": "^1.25.1",
     "snyk": "^1.25.1",
-    "standard": "^9.0.0",
+    "standard": "^9.0.2",
     "sticky-js": "^1.1.9",
     "sticky-js": "^1.1.9",
     "twemoji-awesome": "^1.0.4",
     "twemoji-awesome": "^1.0.4",
-    "vue": "^2.2.1"
+    "vee-validate": "^2.0.0-beta.25",
+    "vue": "^2.2.5"
   },
   },
   "standard": {
   "standard": {
     "globals": [
     "globals": [

+ 238 - 12
views/configure/index.pug

@@ -36,6 +36,11 @@ html
               h1 Welcome to Wiki.js!
               h1 Welcome to Wiki.js!
               h2(style={'margin-bottom': 0}) A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown
               h2(style={'margin-bottom': 0}) A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown
           .content(v-cloak)
           .content(v-cloak)
+
+            //- ==============================================
+            //- WELCOME
+            //- ==============================================
+
             template(v-if='state === "welcome"')
             template(v-if='state === "welcome"')
               .panel
               .panel
                 h2.panel-title.is-featured
                 h2.panel-title.is-featured
@@ -45,8 +50,13 @@ html
                   p This installation wizard will guide you through the steps needed to get your wiki up and running in no time!
                   p This installation wizard will guide you through the steps needed to get your wiki up and running in no time!
                   p Detailed information about installation and usage can be found on the #[a(href='https://docs.wiki.requarks.io/') 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].
                   p Detailed information about installation and usage can be found on the #[a(href='https://docs.wiki.requarks.io/') 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].
                 .panel-footer
                 .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
                   button.button.is-indigo(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Start
                   button.button.is-indigo(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Start
 
 
+            //- ==============================================
+            //- SYSTEM CHECK
+            //- ==============================================
+
             template(v-else-if='state === "syscheck"')
             template(v-else-if='state === "syscheck"')
               .panel
               .panel
                 h2.panel-title.is-featured
                 h2.panel-title.is-featured
@@ -62,10 +72,15 @@ html
                     strong  Looks good! No issues so far.
                     strong  Looks good! No issues so far.
                   p(v-if='!loading && !syscheck.ok') #[i.icon-square-cross] Error: {{ syscheck.error }}
                   p(v-if='!loading && !syscheck.ok') #[i.icon-square-cross] Error: {{ syscheck.error }}
                 .panel-footer
                 .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
                   button.button.is-indigo.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back
                   button.button.is-indigo.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back
                   button.button.is-teal(v-on:click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again
                   button.button.is-teal(v-on:click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again
                   button.button.is-indigo(v-on:click='proceedToGeneral', v-if='loading || syscheck.ok', v-bind:disabled='loading') Continue
                   button.button.is-indigo(v-on:click='proceedToGeneral', v-if='loading || syscheck.ok', v-bind:disabled='loading') Continue
 
 
+            //- ==============================================
+            //- GENERAL
+            //- ==============================================
+
             template(v-else-if='state === "general"')
             template(v-else-if='state === "general"')
               .panel
               .panel
                 h2.panel-title.is-featured
                 h2.panel-title.is-featured
@@ -75,27 +90,33 @@ html
                   section
                   section
                     p.control.is-fullwidth
                     p.control.is-fullwidth
                       label.label Site Title
                       label.label Site Title
-                      input(type='text', placeholder='e.g. Wiki', v-model='conf.title')
-                      p.desc The site title will appear in the top left corner on every page and within the window title bar.
+                      input(type='text', placeholder='e.g. Wiki', v-model='conf.title', data-vv-scope='general', name='ipt-title', v-validate='{ required: true, min: 2 }')
+                      span.desc The site title will appear in the top left corner on every page and within the window title bar.
                   section
                   section
                     p.control.is-fullwidth
                     p.control.is-fullwidth
                       label.label Host
                       label.label Host
-                      input(type='text', placeholder='http://', v-model='conf.host')
-                      p.desc The full URL to your wiki, without the trailing slash. E.g.: http://wiki.domain.com. Note that sub-folders are not supported.
+                      input(type='text', placeholder='http://', v-model='conf.host', data-vv-scope='general', name='ipt-host', v-validate='{ required: true, url: true }')
+                      span.desc The full URL to your wiki, without the trailing slash. E.g.: http://wiki.domain.com. Note that sub-folders are not supported.
                   section
                   section
                     p.control
                     p.control
                       label.label Port
                       label.label Port
-                      input(type='text', placeholder='e.g. 80', v-model='conf.port')
-                      p.desc The port on which Wiki.js will listen to. Usually port 80 if connecting directly, or a random port (e.g. 3000) if using a web server in front of it.
+                      input(type='text', placeholder='e.g. 80', v-model.number='conf.port', data-vv-scope='general', name='ipt-port', v-validate='{ required: true, numeric: true, min_value: 1, max_value: 65535 }')
+                      span.desc The port on which Wiki.js will listen to. Usually port 80 if connecting directly, or a random port (e.g. 3000) if using a web server in front of it.
                   section
                   section
                     p.control
                     p.control
                       label.label Site UI Language
                       label.label Site UI Language
                       select(v-model='conf.lang')
                       select(v-model='conf.lang')
-                        option(value='en') English
-                      p.desc The language in which navigation, help and other UI elements will be displayed.
+                        each lg in langs
+                          option(value=lg.id)= lg.name
+                      span.desc The language in which navigation, help and other UI elements will be displayed.
                 .panel-footer
                 .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
                   button.button.is-indigo.is-outlined(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Back
                   button.button.is-indigo.is-outlined(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Back
-                  button.button.is-indigo(v-on:click='proceedToConsiderations', v-bind:disabled='loading') Continue
+                  button.button.is-indigo(v-on:click='proceedToConsiderations', v-bind:disabled='loading || errors.any("general")') Continue
+
+            //- ==============================================
+            //- CONSIDERATIONS
+            //- ==============================================
 
 
             template(v-else-if='state === "considerations"')
             template(v-else-if='state === "considerations"')
               .panel
               .panel
@@ -120,23 +141,228 @@ html
                     h3 Are you sure you want to use localhost as the host base URL? #[i.icon-warning-outline.animated.fadeOut.infinite]
                     h3 Are you sure you want to use localhost as the host base URL? #[i.icon-warning-outline.animated.fadeOut.infinite]
                     p The host URL you specified is localhost. Unless you are a developer running Wiki.js locally on your machine, this is not recommended!
                     p The host URL you specified is localhost. Unless you are a developer running Wiki.js locally on your machine, this is not recommended!
                 .panel-footer
                 .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
                   button.button.is-indigo.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back
                   button.button.is-indigo.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back
                   button.button.is-indigo(v-on:click='proceedToDb', v-bind:disabled='loading') Continue
                   button.button.is-indigo(v-on:click='proceedToDb', v-bind:disabled='loading') Continue
 
 
+            //- ==============================================
+            //- DATABASE
+            //- ==============================================
+
             template(v-else-if='state === "db"')
             template(v-else-if='state === "db"')
               .panel
               .panel
                 h2.panel-title.is-featured
                 h2.panel-title.is-featured
                   span Database
                   span Database
                   i(v-if='loading')
                   i(v-if='loading')
+                .panel-content.is-text
+                  p Wiki.js stores administrative data such as users, permissions and assets metadata in a MongoDB database. Article contents and uploads are <u>not</u> stored in the DB. Instead, they are stored on-disk and synced automatically with a remote git repository of your choice.
                 .panel-content.form-sections
                 .panel-content.form-sections
                   section
                   section
                     p.control.is-fullwidth
                     p.control.is-fullwidth
                       label.label MongoDB Connection String
                       label.label MongoDB Connection String
-                      input(type='text', placeholder='e.g. mongodb://localhost:27017/wiki', v-model='conf.db')
-                      p.desc The connection string to your MongoDB server. Leave the default localhost value if MongoDB is installed on the same server.
+                      input(type='text', placeholder='e.g. mongodb://localhost:27017/wiki', v-model='conf.db', data-vv-scope='db', name='ipt-db', v-validate='{ required: true, min: 14 }')
+                      span.desc The connection string to your MongoDB server. Leave the default localhost value if MongoDB is installed on the same server.
                 .panel-footer
                 .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
                   button.button.is-indigo.is-outlined(v-on:click='proceedToConsiderations', v-bind:disabled='loading') Back
                   button.button.is-indigo.is-outlined(v-on:click='proceedToConsiderations', v-bind:disabled='loading') Back
-                  button.button.is-indigo(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Connect
+                  button.button.is-indigo(v-on:click='proceedToDbcheck', v-bind:disabled='loading || errors.any("db")') Connect
+
+            //- ==============================================
+            //- DATABASE CHECK
+            //- ==============================================
+
+            template(v-else-if='state === "dbcheck"')
+              .panel
+                h2.panel-title.is-featured
+                  span Database Check
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Testing the connection to MongoDB...
+                  p(v-if='!loading && dbcheck.ok')
+                    i.icon-check
+                    strong  Connected successfully!
+                  p(v-if='!loading && !dbcheck.ok') #[i.icon-square-cross] Error: {{ dbcheck.error }}
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back
+                  button.button.is-teal(v-on:click='proceedToDbcheck', v-if='!loading && !dbcheck.ok') Try Again
+                  button.button.is-indigo(v-on:click='proceedToPaths', v-if='loading || dbcheck.ok', v-bind:disabled='loading') Continue
+
+            //- ==============================================
+            //- PATHS
+            //- ==============================================
+
+            template(v-else-if='state === "paths"')
+              .panel
+                h2.panel-title.is-featured
+                  span Paths
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p It is recommended to leave the default values.
+                .panel-content.form-sections
+                  section
+                    p.control.is-fullwidth
+                      label.label Local Data Path
+                      input(type='text', placeholder='e.g. ./data', v-model='conf.pathData', data-vv-scope='paths', name='ipt-datapath', v-validate='{ required: true, min: 2 }')
+                      span.desc The path where cache (processed content, thumbnails, search index, etc.) will be stored on disk.
+                  section
+                    p.control.is-fullwidth
+                      label.label Local Repository Path
+                      input(type='text', placeholder='e.g. ./repo', v-model='conf.pathRepo', data-vv-scope='paths', name='ipt-repopath', v-validate='{ required: true, min: 2 }')
+                      span.desc The path where the local git repository will be created, used to store content in markdown files and uploads.
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back
+                  button.button.is-indigo(v-on:click='proceedToGit', v-bind:disabled='loading || errors.any("paths")') Continue
+
+            //- ==============================================
+            //- GIT
+            //- ==============================================
+
+            template(v-else-if='state === "git"')
+              .panel
+                h2.panel-title.is-featured
+                  span Git Repository
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p Wiki.js stores article content and uploads locally on disk. All content is then regularly kept in sync with a remote git repository. This acts a backup protection and provides history / revert features. While optional, it is <strong>HIGHLY</strong> recommended to setup the remote git repository connection.
+                .panel-content.form-sections
+                  section.columns
+                    .column.is-two-thirds
+                      p.control.is-fullwidth
+                        label.label Repository URL
+                        input(type='text', placeholder='e.g. git@github.com/org/repo.git or https://github.com/org/repo', v-model='conf.gitUrl', data-vv-scope='git', name='ipt-giturl', v-validate='{ required: true, min: 5 }')
+                        span.desc The full git repository URL to connect to.
+                    .column
+                      p.control.is-fullwidth
+                        label.label Branch
+                        input(type='text', placeholder='e.g. master', v-model='conf.gitBranch', data-vv-scope='git', name='ipt-gitbranch', v-validate='{ required: true, min: 2 }')
+                        span.desc The git branch to use when synchronizing changes.
+                  section.columns
+                    .column.is-one-third
+                      p.control.is-fullwidth
+                        label.label Authentication
+                        select(v-model='conf.gitAuthType')
+                          option(value='ssh') SSH (recommended)
+                          option(value='basic') Basic
+                        span.desc The authentication method used to connect to your remote Git repository.
+                      p.control.is-fullwidth
+                        input#ipt-git-verify-ssl(type='checkbox', v-model='conf.gitAuthSSL')
+                        label.label(for='ipt-git-verify-ssl') Verify SSL
+                    .column(v-show='conf.gitAuthType === "basic"')
+                      p.control.is-fullwidth
+                        label.label Username
+                        input(type='text', v-model='conf.gitUrl')
+                        span.desc The username for the remote git connection.
+                    .column(v-show='conf.gitAuthType === "basic"')
+                      p.control.is-fullwidth
+                        label.label Password
+                        input(type='password', v-model='conf.gitUrl')
+                        span.desc The password for the remote git connection.
+                    .column(v-show='conf.gitAuthType === "ssh"')
+                      p.control.is-fullwidth
+                        label.label Private Key location
+                        input(type='text', placeholder='e.g. /etc/wiki/keys/git.pem')
+                        span.desc The full path to the private key on disk.
+                  section.columns
+                    .column
+                      p.control.is-fullwidth
+                        label.label Sync User Name
+                        input(type='text', placeholder='e.g. John Smith', v-model.number='conf.gitSignatureName', data-vv-scope='git', name='ipt-gitsigname', v-validate='{ required: true }')
+                        span.desc The name to use when pushing commits to the git repository.
+                    .column
+                      p.control.is-fullwidth
+                        label.label Sync User Email
+                        input(type='text', placeholder='e.g. user@example.com', v-model.number='conf.gitSignatureEmail', data-vv-scope='git', name='ipt-gitsigemail', v-validate='{ required: true, email: true }')
+                        span.desc The email to use when pushing commits to the git repository.
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Skip this step
+                  button.button.is-indigo(v-on:click='proceedToGitCheck', v-bind:disabled='loading || errors.any("git")') Continue
+            
+            //- ==============================================
+            //- GIT CHECK
+            //- ==============================================
+
+            template(v-else-if='state === "gitcheck"')
+              .panel
+                h2.panel-title.is-featured
+                  span Git Repository Check
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Testing the connection to Git repository...
+                  p(v-if='!loading && gitcheck.ok')
+                    ul
+                      li(v-for='rs in gitcheck.results') #[i.icon-check] {{rs}}
+                  p(v-if='!loading && gitcheck.ok')
+                    i.icon-check
+                    strong  Git settings are correct!
+                  p(v-if='!loading && !gitcheck.ok') #[i.icon-square-cross] Error: {{ gitcheck.error }}
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back
+                  button.button.is-teal(v-on:click='proceedToGitCheck', v-if='!loading && !gitcheck.ok') Try Again
+                  button.button.is-indigo(v-on:click='proceedToAdmin', v-if='loading || gitcheck.ok', v-bind:disabled='loading') Continue
+
+            //- ==============================================
+            //- ADMINISTRATOR ACCOUNT
+            //- ==============================================
+
+            template(v-else-if='state === "admin"')
+              .panel
+                h2.panel-title.is-featured
+                  span Administrator Account
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p An administrator account will be created for local authentication. From this account, you can create or authorize more users.
+                .panel-content.form-sections
+                  section
+                    p.control.is-fullwidth
+                      label.label Administrator Email
+                      input(type='text', placeholder='e.g. admin@example.com', v-model='conf.adminEmail', data-vv-scope='admin', name='ipt-adminemail', v-validate='{ required: true, email: true }')
+                      span.desc The full git repository URL to connect to.
+                  section.columns
+                    .column
+                      p.control.is-fullwidth
+                        label.label Password
+                        input(type='password', v-model='conf.adminPassword', data-vv-scope='admin', name='ipt-adminpwd', v-validate='{ required: true, min: 8 }')
+                        span.desc The full git repository URL to connect to.
+                    .column
+                      p.control.is-fullwidth
+                        label.label Confirm Password
+                        input(type='password', v-model='conf.adminPasswordConfirm', data-vv-scope='admin', name='ipt-adminpwd2', v-validate='{ required: true, confirmed: "ipt-adminpwd" }')
+                        span.desc The git branch to use when synchronizing changes.
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back
+                  button.button.is-indigo(v-on:click='proceedToFinal', v-bind:disabled='loading || errors.any("admin")') Continue
+
+            //- ==============================================
+            //- FINAL
+            //- ==============================================
+
+            template(v-else-if='state === "final"')
+              .panel
+                h2.panel-title.is-featured
+                  span Finalizing
+                  i(v-if='loading')
+                .panel-content.is-text
+                  p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Finalizing your installation...
+                  p(v-if='!loading && final.ok')
+                    ul
+                      li(v-for='rs in final.results') #[i.icon-check] {{rs}}
+                  p(v-if='!loading && final.ok')
+                    i.icon-check
+                    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 start the Wiki.js server.
+                  p(v-if='!loading && !final.ok') #[i.icon-square-cross] Error: {{ final.error }}
+                .panel-footer
+                  .progress-bar: div(v-bind:style='{width: currentProgress}')
+                  button.button.is-indigo.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back
+                  button.button.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again
+                  button.button.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start
 
 
       footer.footer
       footer.footer
         span
         span

Vissa filer visades inte eftersom för många filer har ändrats