Browse Source

refactor: project cleanup + onboarding page

NGPixel 7 years ago
parent
commit
3471a7a6f9
80 changed files with 177 additions and 2782 deletions
  1. 6 5
      client/js/components/setup.vue
  2. 2 0
      client/scss/app.scss
  3. 4 22
      client/scss/base/base.scss
  4. 42 0
      client/scss/pages/_welcome.scss
  5. 0 2
      package.json
  6. 1 1
      server/controllers/auth.js
  7. 1 306
      server/controllers/common.js
  8. 1 1
      server/core/auth.js
  9. 5 5
      server/core/config.js
  10. 41 45
      server/core/db.js
  11. 4 6
      server/core/graphql.js
  12. 1 1
      server/core/kernel.js
  13. 4 0
      server/core/localization.js
  14. 4 4
      server/core/logger.js
  15. 2 4
      server/core/queue.js
  16. 2 16
      server/core/redis.js
  17. 3 4
      server/core/system.js
  18. 1 1
      server/core/telemetry.js
  19. 5 5
      server/index.js
  20. 25 26
      server/master.js
  21. 0 0
      server/modules/authentication/auth0.js
  22. 0 0
      server/modules/authentication/azure.js
  23. 0 0
      server/modules/authentication/discord.js
  24. 0 0
      server/modules/authentication/dropbox.js
  25. 0 0
      server/modules/authentication/facebook.js
  26. 0 0
      server/modules/authentication/github.js
  27. 0 0
      server/modules/authentication/google.js
  28. 0 0
      server/modules/authentication/ldap.js
  29. 0 0
      server/modules/authentication/local.js
  30. 0 0
      server/modules/authentication/microsoft.js
  31. 0 0
      server/modules/authentication/oauth2.js
  32. 0 0
      server/modules/authentication/slack.js
  33. 0 0
      server/modules/authentication/twitch.js
  34. 0 169
      server/modules/disk.js
  35. 0 455
      server/modules/documents.js
  36. 0 306
      server/modules/git.js
  37. 0 0
      server/modules/logging/bugsnag.js
  38. 0 0
      server/modules/logging/console.js
  39. 0 0
      server/modules/logging/loggly.js
  40. 0 0
      server/modules/logging/papertrail.js
  41. 0 0
      server/modules/logging/rollbar.js
  42. 0 0
      server/modules/logging/sentry.js
  43. 0 417
      server/modules/markdown.js
  44. 0 0
      server/modules/renderer/common/mathjax.js
  45. 0 0
      server/modules/renderer/markdown/abbreviations.js
  46. 0 0
      server/modules/renderer/markdown/emoji.js
  47. 0 0
      server/modules/renderer/markdown/expand-tabs.js
  48. 0 0
      server/modules/renderer/markdown/footnotes.js
  49. 0 0
      server/modules/renderer/markdown/mathjax.js
  50. 0 0
      server/modules/renderer/markdown/tasks-lists.js
  51. 0 122
      server/modules/rights.js
  52. 0 211
      server/modules/search.js
  53. 0 0
      server/modules/storage/disk.js
  54. 0 0
      server/modules/storage/git.js
  55. 0 252
      server/modules/uploads-agent.js
  56. 0 281
      server/modules/uploads.js
  57. 4 9
      server/setup.js
  58. 0 0
      server/views/admin/_layout.pug
  59. 0 0
      server/views/admin/profile.pug
  60. 0 0
      server/views/admin/settings.pug
  61. 0 0
      server/views/admin/stats.pug
  62. 0 0
      server/views/admin/system.pug
  63. 0 0
      server/views/admin/theme.pug
  64. 0 0
      server/views/admin/users-edit.pug
  65. 0 0
      server/views/admin/users.pug
  66. 0 0
      server/views/errors/error-forbidden.pug
  67. 0 0
      server/views/errors/error-notexist.pug
  68. 3 4
      server/views/layout.pug
  69. 0 0
      server/views/main/all.pug
  70. 0 0
      server/views/main/create.pug
  71. 0 0
      server/views/main/edit.pug
  72. 0 0
      server/views/main/history.pug
  73. 0 0
      server/views/main/login.pug
  74. 0 0
      server/views/main/setup.pug
  75. 0 0
      server/views/main/source.pug
  76. 0 0
      server/views/main/view.pug
  77. 10 0
      server/views/main/welcome.pug
  78. 0 11
      server/views/pages/welcome.pug
  79. 3 7
      server/worker.js
  80. 3 84
      yarn.lock

+ 6 - 5
client/js/components/setup.vue

@@ -353,6 +353,7 @@
 /* global siteConfig */
 
 import axios from 'axios'
+import _ from 'lodash'
 
 export default {
   props: {
@@ -491,7 +492,7 @@ export default {
         results: []
       }
 
-      this.$helpers._.delay(() => {
+      _.delay(() => {
         axios.post('/syscheck', self.conf).then(resp => {
           if (resp.data.ok === true) {
             self.syscheck.ok = true
@@ -517,9 +518,9 @@ export default {
     },
     proceedToConsiderations: function (ev) {
       this.considerations = {
-        https: !this.$helpers._.startsWith(this.conf.host, 'https'),
+        https: false,
         port: false, // TODO
-        localhost: this.$helpers._.includes(this.conf.host, 'localhost')
+        localhost: false
       }
       this.state = 'considerations'
       this.loading = false
@@ -542,7 +543,7 @@ export default {
         error: ''
       }
 
-      this.$helpers._.delay(() => {
+      _.delay(() => {
         axios.post('/gitcheck', self.conf).then(resp => {
           if (resp.data.ok === true) {
             self.gitcheck.ok = true
@@ -587,7 +588,7 @@ export default {
         redirectUrl: ''
       }
 
-      this.$helpers._.delay(() => {
+      _.delay(() => {
         axios.post('/finalize', self.conf).then(resp => {
           if (resp.data.ok === true) {
             self.$helpers._.delay(() => {

+ 2 - 0
client/scss/app.scss

@@ -30,6 +30,8 @@
 
 @import 'components/editor';
 
+@import 'pages/welcome';
+
 @import 'layout/_header';
 @import 'layout/_loader';
 @import 'layout/_rtl';

+ 4 - 22
client/scss/base/base.scss

@@ -16,7 +16,6 @@ html {
   min-height: 100%;
 
   &.is-fullscreen {
-    //width: 100vw;
     height: 100vh;
   }
 }
@@ -27,7 +26,10 @@ body {
 }
 
 main {
-	background-color: lighten(mc('blue-grey','50'), 5%);
+  background-color: mc('blue','500');
+  background-image: linear-gradient(to bottom, mc('blue', '700') 0%, mc('blue', '500') 100%);
+  padding: 50px;
+  min-height: 100vh;
 }
 
 a {
@@ -42,28 +44,8 @@ a {
 
 // Container
 
-.has-stickynav {
-	padding-top: 50px;
-}
-
 .container {
   position: relative;
-
-  @include desktop {
-    margin: 0 auto;
-    max-width: 960px;
-
-    // Modifiers
-    &.is-fluid {
-      margin: 0;
-      max-width: none;
-    }
-  }
-
-
-  @include widescreen {
-    max-width: 1200px;
-  }
 }
 
 .content {

+ 42 - 0
client/scss/pages/_welcome.scss

@@ -0,0 +1,42 @@
+.onboarding {
+  background: linear-gradient(to bottom, mc('grey', '900') 0%, mc('grey', '800') 100%);
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: mc('grey', '50');
+
+  &::before {
+    content: '';
+    display:block;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background-image: url('../static/svg/login-bg-motif.svg');
+    background-repeat: repeat;
+    background-size: 500px;
+    z-index: 0;
+    opacity: .75;
+  }
+
+  img {
+    width: 500px;
+    filter: grayscale(100%) brightness(160%);
+    margin-bottom: 3rem;
+    z-index: 2;
+  }
+  h1 {
+    margin-bottom: 1rem;
+    z-index: 2;
+  }
+  h2 {
+    margin-bottom: 3rem;
+    z-index: 2;
+  }
+  .button {
+    z-index: 2;
+  }
+}

+ 0 - 2
package.json

@@ -65,7 +65,6 @@
     "filesize.js": "1.0.2",
     "follow-redirects": "1.4.1",
     "fs-extra": "5.0.0",
-    "git-wrapper2-promise": "0.2.9",
     "graphql": "0.12.3",
     "graphql-tools": "2.19.0",
     "highlight.js": "9.12.0",
@@ -96,7 +95,6 @@
     "mongodb": "3.0.1",
     "multer": "1.3.0",
     "node-2fa": "1.1.2",
-    "nodegit": "0.20.3",
     "ora": "1.3.0",
     "passport": "0.4.0",
     "passport-auth0": "0.6.1",

+ 1 - 1
server/controllers/auth.js

@@ -34,7 +34,7 @@ const bruteforce = new ExpressBrute(EBstore, {
  * Login form
  */
 router.get('/login', function (req, res, next) {
-  res.render('pages/login')
+  res.render('main/login')
 })
 
 router.post('/login', bruteforce.prevent, function (req, res, next) {

+ 1 - 306
server/controllers/common.js

@@ -1,316 +1,11 @@
-'use strict'
-
-/* global entries, git, lang, winston */
-
 const express = require('express')
 const router = express.Router()
-const _ = require('lodash')
-
-const entryHelper = require('../helpers/entry')
-
-// ==========================================
-// EDIT MODE
-// ==========================================
-
-/**
- * Edit document in Markdown
- */
-router.get('/edit/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.render('error-forbidden')
-  }
-
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/edit', ''))
-
-  entries.fetchOriginal(safePath, {
-    parseMarkdown: false,
-    parseMeta: true,
-    parseTree: false,
-    includeMarkdown: true,
-    includeParentInfo: false,
-    cache: false
-  }).then((pageData) => {
-    if (pageData) {
-      res.render('pages/edit', { pageData })
-    } else {
-      throw new Error(lang.t('errors:invalidpath'))
-    }
-    return true
-  }).catch((err) => {
-    res.render('error', {
-      message: err.message,
-      error: {}
-    })
-  })
-})
-
-router.put('/edit/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.json({
-      ok: false,
-      error: lang.t('errors:forbidden')
-    })
-  }
-
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/edit', ''))
-
-  entries.update(safePath, req.body.markdown, req.user).then(() => {
-    return res.json({
-      ok: true
-    }) || true
-  }).catch((err) => {
-    res.json({
-      ok: false,
-      error: err.message
-    })
-  })
-})
-
-// ==========================================
-// CREATE MODE
-// ==========================================
-
-router.get('/create/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.render('error-forbidden')
-  }
-
-  if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk', 'all'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
-    return res.render('error', {
-      message: lang.t('errors:reservedname'),
-      error: {}
-    })
-  }
-
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/create', ''))
-
-  entries.exists(safePath).then((docExists) => {
-    if (!docExists) {
-      return entries.getStarter(safePath).then((contents) => {
-        let pageData = {
-          markdown: contents,
-          meta: {
-            title: _.startCase(safePath),
-            path: safePath
-          }
-        }
-        res.render('pages/create', { pageData })
-
-        return true
-      }).catch((err) => {
-        winston.warn(err)
-        throw new Error(lang.t('errors:starterfailed'))
-      })
-    } else {
-      throw new Error(lang.t('errors:alreadyexists'))
-    }
-  }).catch((err) => {
-    res.render('error', {
-      message: err.message,
-      error: {}
-    })
-  })
-})
-
-router.put('/create/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.json({
-      ok: false,
-      error: lang.t('errors:forbidden')
-    })
-  }
-
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/create', ''))
-
-  entries.create(safePath, req.body.markdown, req.user).then(() => {
-    return res.json({
-      ok: true
-    }) || true
-  }).catch((err) => {
-    return res.json({
-      ok: false,
-      error: err.message
-    })
-  })
-})
-
-// ==========================================
-// LIST ALL PAGES
-// ==========================================
-
-/**
- * View tree view of all pages
- */
-router.use((req, res, next) => {
-  if (_.endsWith(req.url, '/all')) {
-    res.render('pages/all')
-  } else {
-    next()
-  }
-})
-
-// ==========================================
-// VIEW MODE
-// ==========================================
-
-/**
- * View source of a document
- */
-router.get('/source/*', (req, res, next) => {
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/source', ''))
-
-  entries.fetchOriginal(safePath, {
-    parseMarkdown: false,
-    parseMeta: true,
-    parseTree: false,
-    includeMarkdown: true,
-    includeParentInfo: false,
-    cache: false
-  }).then((pageData) => {
-    if (pageData) {
-      res.render('pages/source', { pageData })
-    } else {
-      throw new Error(lang.t('errors:invalidpath'))
-    }
-    return true
-  }).catch((err) => {
-    res.render('error', {
-      message: err.message,
-      error: {}
-    })
-  })
-})
-
-/**
- * View history of a document
- */
-router.get('/hist/*', (req, res, next) => {
-  let safePath = entryHelper.parsePath(_.replace(req.path, '/hist', ''))
-
-  entries.getHistory(safePath).then((pageData) => {
-    if (pageData) {
-      res.render('pages/history', { pageData })
-    } else {
-      throw new Error(lang.t('errors:invalidpath'))
-    }
-    return true
-  }).catch((err) => {
-    res.render('error', {
-      message: err.message,
-      error: {}
-    })
-  })
-})
-
-/**
- * View history of a document
- */
-router.post('/hist', (req, res, next) => {
-  let commit = req.body.commit
-  let safePath = entryHelper.parsePath(req.body.path)
-
-  if (!/^[a-f0-9]{40}$/.test(commit)) {
-    return res.status(400).json({ ok: false, error: 'Invalid commit' })
-  }
-
-  git.getHistoryDiff(safePath, commit).then((diff) => {
-    res.json({ ok: true, diff })
-    return true
-  }).catch((err) => {
-    res.status(500).json({ ok: false, error: err.message })
-  })
-})
 
 /**
  * View document
  */
 router.get('/*', (req, res, next) => {
-  let safePath = entryHelper.parsePath(req.path)
-
-  entries.fetch(safePath).then((pageData) => {
-    if (pageData) {
-      res.render('pages/view', { pageData })
-    } else {
-      res.render('error-notexist', {
-        newpath: safePath
-      })
-    }
-    return true
-  }).error((err) => {
-    if (safePath === 'home') {
-      res.render('pages/welcome')
-    } else {
-      res.render('error-notexist', {
-        message: err.message,
-        newpath: safePath
-      })
-    }
-    return true
-  }).catch((err) => {
-    res.render('error', {
-      message: err.message,
-      error: {}
-    })
-  })
-})
-
-/**
- * Move document
- */
-router.put('/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.json({
-      ok: false,
-      error: lang.t('errors:forbidden')
-    })
-  }
-
-  let safePath = entryHelper.parsePath(req.path)
-
-  if (_.isEmpty(req.body.move)) {
-    return res.json({
-      ok: false,
-      error: lang.t('errors:invalidaction')
-    })
-  }
-
-  let safeNewPath = entryHelper.parsePath(req.body.move)
-
-  entries.move(safePath, safeNewPath, req.user).then(() => {
-    res.json({
-      ok: true
-    })
-  }).catch((err) => {
-    res.json({
-      ok: false,
-      error: err.message
-    })
-  })
-})
-
-/**
- * Delete document
- */
-router.delete('/*', (req, res, next) => {
-  if (!res.locals.rights.write) {
-    return res.json({
-      ok: false,
-      error: lang.t('errors:forbidden')
-    })
-  }
-
-  let safePath = entryHelper.parsePath(req.path)
-
-  entries.remove(safePath, req.user).then(() => {
-    res.json({
-      ok: true
-    })
-  }).catch((err) => {
-    res.json({
-      ok: false,
-      error: err.message
-    })
-  })
+  res.render('main/welcome')
 })
 
 module.exports = router

+ 1 - 1
server/modules/auth.js → server/core/auth.js

@@ -33,7 +33,7 @@ module.exports = {
 
     _.forOwn(_.omitBy(wiki.config.auth.strategies, s => s.enabled === false), (strategyConfig, strategyKey) => {
       strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}login/${strategyKey}/callback`
-      let strategy = require(`../extensions/authentication/${strategyKey}`)
+      let strategy = require(`../modules/authentication/${strategyKey}`)
       try {
         strategy.init(passport, strategyConfig)
       } catch (err) {

+ 5 - 5
server/modules/config.js → server/core/config.js

@@ -1,10 +1,10 @@
-/* global wiki */
-
-const fs = require('fs')
-const yaml = require('js-yaml')
 const _ = require('lodash')
-const path = require('path')
 const cfgHelper = require('../helpers/config')
+const fs = require('fs')
+const path = require('path')
+const yaml = require('js-yaml')
+
+/* global wiki */
 
 module.exports = {
   /**

+ 41 - 45
server/modules/db.js → server/core/db.js

@@ -1,54 +1,52 @@
-/* global wiki */
-
+const _ = require('lodash')
 const fs = require('fs')
 const path = require('path')
-const _ = require('lodash')
 const Promise = require('bluebird')
 const Sequelize = require('sequelize')
-const Op = Sequelize.Op
+
+/* global wiki */
 
 const operatorsAliases = {
-  $eq: Op.eq,
-  $ne: Op.ne,
-  $gte: Op.gte,
-  $gt: Op.gt,
-  $lte: Op.lte,
-  $lt: Op.lt,
-  $not: Op.not,
-  $in: Op.in,
-  $notIn: Op.notIn,
-  $is: Op.is,
-  $like: Op.like,
-  $notLike: Op.notLike,
-  $iLike: Op.iLike,
-  $notILike: Op.notILike,
-  $regexp: Op.regexp,
-  $notRegexp: Op.notRegexp,
-  $iRegexp: Op.iRegexp,
-  $notIRegexp: Op.notIRegexp,
-  $between: Op.between,
-  $notBetween: Op.notBetween,
-  $overlap: Op.overlap,
-  $contains: Op.contains,
-  $contained: Op.contained,
-  $adjacent: Op.adjacent,
-  $strictLeft: Op.strictLeft,
-  $strictRight: Op.strictRight,
-  $noExtendRight: Op.noExtendRight,
-  $noExtendLeft: Op.noExtendLeft,
-  $and: Op.and,
-  $or: Op.or,
-  $any: Op.any,
-  $all: Op.all,
-  $values: Op.values,
-  $col: Op.col
+  $eq: Sequelize.Op.eq,
+  $ne: Sequelize.Op.ne,
+  $gte: Sequelize.Op.gte,
+  $gt: Sequelize.Op.gt,
+  $lte: Sequelize.Op.lte,
+  $lt: Sequelize.Op.lt,
+  $not: Sequelize.Op.not,
+  $in: Sequelize.Op.in,
+  $notIn: Sequelize.Op.notIn,
+  $is: Sequelize.Op.is,
+  $like: Sequelize.Op.like,
+  $notLike: Sequelize.Op.notLike,
+  $iLike: Sequelize.Op.iLike,
+  $notILike: Sequelize.Op.notILike,
+  $regexp: Sequelize.Op.regexp,
+  $notRegexp: Sequelize.Op.notRegexp,
+  $iRegexp: Sequelize.Op.iRegexp,
+  $notIRegexp: Sequelize.Op.notIRegexp,
+  $between: Sequelize.Op.between,
+  $notBetween: Sequelize.Op.notBetween,
+  $overlap: Sequelize.Op.overlap,
+  $contains: Sequelize.Op.contains,
+  $contained: Sequelize.Op.contained,
+  $adjacent: Sequelize.Op.adjacent,
+  $strictLeft: Sequelize.Op.strictLeft,
+  $strictRight: Sequelize.Op.strictRight,
+  $noExtendRight: Sequelize.Op.noExtendRight,
+  $noExtendLeft: Sequelize.Op.noExtendLeft,
+  $and: Sequelize.Op.and,
+  $or: Sequelize.Op.or,
+  $any: Sequelize.Op.any,
+  $all: Sequelize.Op.all,
+  $values: Sequelize.Op.values,
+  $col: Sequelize.Op.col
 }
 
 /**
  * PostgreSQL DB module
  */
 module.exports = {
-
   Sequelize,
   Op: Sequelize.Op,
 
@@ -59,12 +57,11 @@ module.exports = {
    */
   init() {
     let self = this
-
     let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
 
     // Define Sequelize instance
 
-    self.inst = new self.Sequelize(wiki.config.db.db, wiki.config.db.user, wiki.config.db.pass, {
+    this.inst = new this.Sequelize(wiki.config.db.db, wiki.config.db.user, wiki.config.db.pass, {
       host: wiki.config.db.host,
       port: wiki.config.db.port,
       dialect: 'postgres',
@@ -79,7 +76,7 @@ module.exports = {
 
     // Attempt to connect and authenticate to DB
 
-    self.inst.authenticate().then(() => {
+    this.inst.authenticate().then(() => {
       wiki.logger.info('Database (PostgreSQL) connection: [ OK ]')
     }).catch(err => {
       wiki.logger.error('Failed to connect to PostgreSQL instance.')
@@ -128,9 +125,8 @@ module.exports = {
 
     // Perform init tasks
 
-    self.onReady = Promise.each(initTasksQueue, t => t()).return(true)
+    this.onReady = Promise.each(initTasksQueue, t => t()).return(true)
 
-    return self
+    return this
   }
-
 }

+ 4 - 6
server/modules/graphql.js → server/core/graphql.js

@@ -1,11 +1,9 @@
-'use strict'
-
-/* global wiki */
-
-const gqlTools = require('graphql-tools')
+const _ = require('lodash')
 const fs = require('fs')
+const gqlTools = require('graphql-tools')
 const path = require('path')
-const _ = require('lodash')
+
+/* global wiki */
 
 const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
 

+ 1 - 1
server/modules/kernel.js → server/core/kernel.js

@@ -1,6 +1,6 @@
+const _ = require('lodash')
 const cluster = require('cluster')
 const Promise = require('bluebird')
-const _ = require('lodash')
 
 /* global wiki */
 

+ 4 - 0
server/modules/localization.js → server/core/localization.js

@@ -1,6 +1,7 @@
 const _ = require('lodash')
 const dotize = require('dotize')
 const i18nBackend = require('i18next-node-fs-backend')
+const i18nMW = require('i18next-express-middleware')
 const i18next = require('i18next')
 const path = require('path')
 const Promise = require('bluebird')
@@ -27,6 +28,9 @@ module.exports = {
     })
     return this
   },
+  attachMiddleware (app) {
+    app.use(i18nMW.handle(this.engine))
+  },
   async getByNamespace(locale, namespace) {
     if (this.engine.hasResourceBundle(locale, namespace)) {
       let data = this.engine.getResourceBundle(locale, namespace)

+ 4 - 4
server/modules/logger.js → server/core/logger.js

@@ -1,10 +1,10 @@
-/* global wiki */
-
-const cluster = require('cluster')
 const _ = require('lodash')
+const cluster = require('cluster')
 const fs = require('fs-extra')
 const path = require('path')
 
+/* global wiki */
+
 module.exports = {
   loggers: {},
   init() {
@@ -21,7 +21,7 @@ module.exports = {
     })
 
     _.forOwn(_.omitBy(wiki.config.logging.loggers, s => s.enabled === false), (loggerConfig, loggerKey) => {
-      let loggerModule = require(`../extensions/logging/${loggerKey}`)
+      let loggerModule = require(`../modules/logging/${loggerKey}`)
       loggerModule.init(logger, loggerConfig)
       fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => {
         logger.icon = iconData

+ 2 - 4
server/modules/queue.js → server/core/queue.js

@@ -1,10 +1,8 @@
-'use strict'
-
-/* global wiki */
-
 const Bull = require('bull')
 const Promise = require('bluebird')
 
+/* global wiki */
+
 module.exports = {
   init() {
     wiki.data.queues.forEach(queueName => {

+ 2 - 16
server/modules/redis.js → server/core/redis.js

@@ -1,22 +1,9 @@
-'use strict'
-
-/* global wiki */
-
 const Redis = require('ioredis')
 const { isPlainObject } = require('lodash')
 
-/**
- * Redis module
- *
- * @return     {Object}  Redis client wrapper instance
- */
-module.exports = {
+/* global wiki */
 
-  /**
-   * Initialize Redis client
-   *
-   * @return     {Object}  Redis client instance
-   */
+module.exports = {
   init() {
     if (isPlainObject(wiki.config.redis)) {
       let red = new Redis(wiki.config.redis)
@@ -33,5 +20,4 @@ module.exports = {
       process.exit(1)
     }
   }
-
 }

+ 3 - 4
server/modules/system.js → server/core/system.js

@@ -1,9 +1,8 @@
-/* global wiki */
-
-const Promise = require('bluebird')
-// const pm2 = Promise.promisifyAll(require('pm2'))
 const _ = require('lodash')
 const cfgHelper = require('../helpers/config')
+const Promise = require('bluebird')
+
+/* global wiki */
 
 module.exports = {
   /**

+ 1 - 1
server/modules/telemetry.js → server/core/telemetry.js

@@ -1,8 +1,8 @@
+const _ = require('lodash')
 const axios = require('axios')
 const bugsnag = require('bugsnag')
 const path = require('path')
 const uuid = require('uuid/v4')
-const _ = require('lodash')
 
 /* global wiki */
 

+ 5 - 5
server/index.js

@@ -12,8 +12,8 @@ let wiki = {
   ROOTPATH: process.cwd(),
   SERVERPATH: path.join(process.cwd(), 'server'),
   Error: require('./helpers/error'),
-  configSvc: require('./modules/config'),
-  kernel: require('./modules/kernel')
+  configSvc: require('./core/config'),
+  kernel: require('./core/kernel')
 }
 global.wiki = wiki
 
@@ -27,13 +27,13 @@ wiki.configSvc.init()
 // Init Logger
 // ----------------------------------------
 
-wiki.logger = require('./modules/logger').init()
+wiki.logger = require('./core/logger').init()
 
 // ----------------------------------------
 // Init Telemetry
 // ----------------------------------------
 
-wiki.telemetry = require('./modules/telemetry').init()
+wiki.telemetry = require('./core/telemetry').init()
 
 process.on('unhandledRejection', (err) => {
   wiki.telemetry.sendError(err)
@@ -46,7 +46,7 @@ process.on('uncaughtException', (err) => {
 // Init DB
 // ----------------------------------------
 
-wiki.db = require('./modules/db').init()
+wiki.db = require('./core/db').init()
 
 // ----------------------------------------
 // Start Kernel

+ 25 - 26
server/master.js

@@ -1,37 +1,31 @@
+const autoload = require('auto-load')
+const bodyParser = require('body-parser')
+const compression = require('compression')
+const cookieParser = require('cookie-parser')
+const cors = require('cors')
+const express = require('express')
+const favicon = require('serve-favicon')
+const http = require('http')
+const path = require('path')
+const session = require('express-session')
+const SessionRedisStore = require('connect-redis')(session)
+const graphqlApollo = require('apollo-server-express')
+const graphqlSchema = require('./core/graphql')
+
 /* global wiki */
 
 module.exports = async () => {
   // ----------------------------------------
-  // Load global modules
+  // Load core modules
   // ----------------------------------------
 
-  wiki.auth = require('./modules/auth').init()
-  wiki.disk = require('./modules/disk').init()
-  wiki.docs = require('./modules/documents').init()
-  wiki.git = require('./modules/git').init(false)
-  wiki.lang = require('./modules/localization').init()
-  // wiki.mark = require('./modules/markdown')
-  // wiki.search = require('./modules/search').init()
-  // wiki.upl = require('./modules/uploads').init()
+  wiki.auth = require('./core/auth').init()
+  wiki.lang = require('./core/localization').init()
 
   // ----------------------------------------
-  // Load modules
+  // Load middlewares
   // ----------------------------------------
 
-  const autoload = require('auto-load')
-  const bodyParser = require('body-parser')
-  const compression = require('compression')
-  const cookieParser = require('cookie-parser')
-  const cors = require('cors')
-  const express = require('express')
-  const favicon = require('serve-favicon')
-  const http = require('http')
-  const path = require('path')
-  const session = require('express-session')
-  const SessionRedisStore = require('connect-redis')(session)
-  const graphqlApollo = require('apollo-server-express')
-  const graphqlSchema = require('./modules/graphql')
-
   var mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
   var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
 
@@ -97,13 +91,18 @@ module.exports = async () => {
   app.use(bodyParser.json({ limit: '1mb' }))
   app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
 
+  // ----------------------------------------
+  // Localization
+  // ----------------------------------------
+
+  wiki.lang.attachMiddleware(app)
+
   // ----------------------------------------
   // View accessible data
   // ----------------------------------------
 
   app.locals.basedir = wiki.ROOTPATH
   app.locals._ = require('lodash')
-  app.locals.t = wiki.lang.engine.t.bind(wiki.lang)
   app.locals.moment = require('moment')
   app.locals.moment.locale(wiki.config.site.lang)
   app.locals.config = wiki.config
@@ -157,7 +156,7 @@ module.exports = async () => {
   })
 
   // ----------------------------------------
-  // Start HTTP server
+  // HTTP server
   // ----------------------------------------
 
   let srvConnections = {}

+ 0 - 0
server/extensions/authentication/auth0.js → server/modules/authentication/auth0.js


+ 0 - 0
server/extensions/authentication/azure.js → server/modules/authentication/azure.js


+ 0 - 0
server/extensions/authentication/discord.js → server/modules/authentication/discord.js


+ 0 - 0
server/extensions/authentication/dropbox.js → server/modules/authentication/dropbox.js


+ 0 - 0
server/extensions/authentication/facebook.js → server/modules/authentication/facebook.js


+ 0 - 0
server/extensions/authentication/github.js → server/modules/authentication/github.js


+ 0 - 0
server/extensions/authentication/google.js → server/modules/authentication/google.js


+ 0 - 0
server/extensions/authentication/ldap.js → server/modules/authentication/ldap.js


+ 0 - 0
server/extensions/authentication/local.js → server/modules/authentication/local.js


+ 0 - 0
server/extensions/authentication/microsoft.js → server/modules/authentication/microsoft.js


+ 0 - 0
server/extensions/authentication/oauth2.js → server/modules/authentication/oauth2.js


+ 0 - 0
server/extensions/authentication/slack.js → server/modules/authentication/slack.js


+ 0 - 0
server/extensions/authentication/twitch.js → server/modules/authentication/twitch.js


+ 0 - 169
server/modules/disk.js

@@ -1,169 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const path = require('path')
-const Promise = require('bluebird')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const multer = require('multer')
-const os = require('os')
-const _ = require('lodash')
-
-/**
- * Local Disk Storage
- */
-module.exports = {
-
-  _uploadsPath: './repo/uploads',
-  _uploadsThumbsPath: './data/thumbs',
-
-  uploadImgHandler: null,
-
-  /**
-   * Initialize Local Data Storage model
-   */
-  init () {
-    this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
-    this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
-
-    this.createBaseDirectories()
-    // this.initMulter()
-
-    return this
-  },
-
-  /**
-   * Init Multer upload handlers
-   */
-  initMulter () {
-    let maxFileSizes = {
-      // img: wiki.config.uploads.maxImageFileSize * 1024 * 1024,
-      // file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024
-      img: 3 * 1024 * 1024,
-      file: 10 * 1024 * 1024
-    }
-
-    // -> IMAGES
-
-    this.uploadImgHandler = multer({
-      storage: multer.diskStorage({
-        destination: (req, f, cb) => {
-          cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
-        }
-      }),
-      fileFilter: (req, f, cb) => {
-        // -> Check filesize
-
-        if (f.size > maxFileSizes.img) {
-          return cb(null, false)
-        }
-
-        // -> Check MIME type (quick check only)
-
-        if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
-          return cb(null, false)
-        }
-
-        cb(null, true)
-      }
-    }).array('imgfile', 20)
-
-    // -> FILES
-
-    this.uploadFileHandler = multer({
-      storage: multer.diskStorage({
-        destination: (req, f, cb) => {
-          cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
-        }
-      }),
-      fileFilter: (req, f, cb) => {
-        // -> Check filesize
-
-        if (f.size > maxFileSizes.file) {
-          return cb(null, false)
-        }
-
-        cb(null, true)
-      }
-    }).array('binfile', 20)
-
-    return true
-  },
-
-  /**
-   * Creates a base directories (Synchronous).
-   */
-  createBaseDirectories () {
-    try {
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
-      fs.emptyDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './cache'))
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './thumbs'))
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'))
-
-      if (os.type() !== 'Windows_NT') {
-        fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'), '755')
-      }
-
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo))
-      fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'))
-
-      if (os.type() !== 'Windows_NT') {
-        fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'), '755')
-      }
-    } catch (err) {
-      wiki.logger.error(err)
-    }
-
-    wiki.logger.info('Disk Data Paths: [ OK ]')
-  },
-
-  /**
-   * Gets the uploads path.
-   *
-   * @return     {String}  The uploads path.
-   */
-  getUploadsPath () {
-    return this._uploadsPath
-  },
-
-  /**
-   * Gets the thumbnails folder path.
-   *
-   * @return     {String}  The thumbs path.
-   */
-  getThumbsPath () {
-    return this._uploadsThumbsPath
-  },
-
-  /**
-   * Check if filename is valid and unique
-   *
-   * @param      {String}           f        The filename
-   * @param      {String}           fld      The containing folder
-   * @param      {boolean}          isImage  Indicates if image
-   * @return     {Promise<String>}  Promise of the accepted filename
-   */
-  validateUploadsFilename (f, fld, isImage) {
-    let fObj = path.parse(f)
-    let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']', 'g'), '')
-    let fext = _.toLower(fObj.ext)
-
-    if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
-      fext = '.png'
-    }
-
-    f = fname + fext
-    let fpath = path.resolve(this._uploadsPath, fld, f)
-
-    return fs.statAsync(fpath).then((s) => {
-      throw new Error(wiki.lang.t('errors:fileexists', { path: f }))
-    }).catch((err) => {
-      if (err.code === 'ENOENT') {
-        return f
-      }
-      throw err
-    })
-  }
-
-}

+ 0 - 455
server/modules/documents.js

@@ -1,455 +0,0 @@
-/* global wiki */
-
-const Promise = require('bluebird')
-const path = require('path')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const _ = require('lodash')
-
-const entryHelper = require('../helpers/entry')
-
-/**
- * Documents Model
- */
-module.exports = {
-
-  _repoPath: 'repo',
-  _cachePath: 'data/cache',
-
-  /**
-   * Initialize Entries model
-   *
-   * @return     {Object}  Entries model instance
-   */
-  init() {
-    let self = this
-
-    self._repoPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
-    self._cachePath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'cache')
-    wiki.data.repoPath = self._repoPath
-    wiki.data.cachePath = self._cachePath
-
-    return self
-  },
-
-  /**
-   * Check if a document already exists
-   *
-   * @param      {String}  entryPath  The entry path
-   * @return     {Promise<Boolean>}  True if exists, false otherwise
-   */
-  exists(entryPath) {
-    let self = this
-
-    return self.fetchOriginal(entryPath, {
-      parseMarkdown: false,
-      parseMeta: false,
-      parseTree: false,
-      includeMarkdown: false,
-      includeParentInfo: false,
-      cache: false
-    }).then(() => {
-      return true
-    }).catch((err) => { // eslint-disable-line handle-callback-err
-      return false
-    })
-  },
-
-  /**
-   * Fetch a document from cache, otherwise the original
-   *
-   * @param      {String}           entryPath  The entry path
-   * @return     {Promise<Object>}  Page Data
-   */
-  fetch(entryPath) {
-    let self = this
-
-    let cpath = entryHelper.getCachePath(entryPath)
-
-    return fs.statAsync(cpath).then((st) => {
-      return st.isFile()
-    }).catch((err) => { // eslint-disable-line handle-callback-err
-      return false
-    }).then((isCache) => {
-      if (isCache) {
-        // Load from cache
-
-        return fs.readFileAsync(cpath).then((contents) => {
-          return JSON.parse(contents)
-        }).catch((err) => { // eslint-disable-line handle-callback-err
-          wiki.logger.error('Corrupted cache file. Deleting it...')
-          fs.unlinkSync(cpath)
-          return false
-        })
-      } else {
-        // Load original
-
-        return self.fetchOriginal(entryPath)
-      }
-    })
-  },
-
-  /**
-   * Fetches the original document entry
-   *
-   * @param      {String}           entryPath  The entry path
-   * @param      {Object}           options    The options
-   * @return     {Promise<Object>}  Page data
-   */
-  fetchOriginal(entryPath, options) {
-    let self = this
-
-    let fpath = entryHelper.getFullPath(entryPath)
-    let cpath = entryHelper.getCachePath(entryPath)
-
-    options = _.defaults(options, {
-      parseMarkdown: true,
-      parseMeta: true,
-      parseTree: true,
-      includeMarkdown: false,
-      includeParentInfo: true,
-      cache: true
-    })
-
-    return fs.statAsync(fpath).then((st) => {
-      if (st.isFile()) {
-        return fs.readFileAsync(fpath, 'utf8').then((contents) => {
-          let htmlProcessor = (options.parseMarkdown) ? wiki.mark.parseContent(contents) : Promise.resolve('')
-
-          // Parse contents
-
-          return htmlProcessor.then(html => {
-            let pageData = {
-              markdown: (options.includeMarkdown) ? contents : '',
-              html,
-              meta: (options.parseMeta) ? wiki.mark.parseMeta(contents) : {},
-              tree: (options.parseTree) ? wiki.mark.parseTree(contents) : []
-            }
-
-            if (!pageData.meta.title) {
-              pageData.meta.title = _.startCase(entryPath)
-            }
-
-            pageData.meta.path = entryPath
-
-            // Get parent
-
-            let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
-              return (pageData.parent = parentData)
-            }).catch((err) => { // eslint-disable-line handle-callback-err
-              return (pageData.parent = false)
-            }) : Promise.resolve(true)
-
-            return parentPromise.then(() => {
-              // Cache to disk
-
-              if (options.cache) {
-                let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
-                return fs.writeFileAsync(cpath, cacheData).catch((err) => {
-                  wiki.logger.error('Unable to write to cache! Performance may be affected.')
-                  wiki.logger.error(err)
-                  return true
-                })
-              } else {
-                return true
-              }
-            }).return(pageData)
-          })
-        })
-      } else {
-        return false
-      }
-    }).catch((err) => { // eslint-disable-line handle-callback-err
-      throw new Promise.OperationalError(wiki.lang.t('errors:notexist', { path: entryPath }))
-    })
-  },
-
-  /**
-   * Gets the parent information.
-   *
-   * @param      {String}                 entryPath  The entry path
-   * @return     {Promise<Object|False>}  The parent information.
-   */
-  getParentInfo(entryPath) {
-    if (_.includes(entryPath, '/')) {
-      let parentParts = _.initial(_.split(entryPath, '/'))
-      let parentPath = _.join(parentParts, '/')
-      let parentFile = _.last(parentParts)
-      let fpath = entryHelper.getFullPath(parentPath)
-
-      return fs.statAsync(fpath).then((st) => {
-        if (st.isFile()) {
-          return fs.readFileAsync(fpath, 'utf8').then((contents) => {
-            let pageMeta = wiki.mark.parseMeta(contents)
-
-            return {
-              path: parentPath,
-              title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
-              subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
-            }
-          })
-        } else {
-          return Promise.reject(new Error(wiki.lang.t('errors:parentinvalid')))
-        }
-      })
-    } else {
-      return Promise.reject(new Error(wiki.lang.t('errors:parentisroot')))
-    }
-  },
-
-  /**
-   * Update an existing document
-   *
-   * @param      {String}            entryPath  The entry path
-   * @param      {String}            contents   The markdown-formatted contents
-   * @param {Object} author The author user object
-   * @return     {Promise<Boolean>}  True on success, false on failure
-   */
-  update(entryPath, contents, author) {
-    let self = this
-    let fpath = entryHelper.getFullPath(entryPath)
-
-    return fs.statAsync(fpath).then((st) => {
-      if (st.isFile()) {
-        return self.makePersistent(entryPath, contents, author).then(() => {
-          return self.updateCache(entryPath).then(entry => {
-            return wiki.search.add(entry)
-          })
-        })
-      } else {
-        return Promise.reject(new Error(wiki.lang.t('errors:notexist', { path: entryPath })))
-      }
-    }).catch((err) => {
-      wiki.logger.error(err)
-      return Promise.reject(new Error(wiki.lang.t('errors:savefailed')))
-    })
-  },
-
-  /**
-   * Update local cache
-   *
-   * @param      {String}   entryPath  The entry path
-   * @return     {Promise}  Promise of the operation
-   */
-  updateCache(entryPath) {
-    let self = this
-
-    return self.fetchOriginal(entryPath, {
-      parseMarkdown: true,
-      parseMeta: true,
-      parseTree: true,
-      includeMarkdown: true,
-      includeParentInfo: true,
-      cache: true
-    }).catch(err => {
-      wiki.logger.error(err)
-      return err
-    }).then((pageData) => {
-      return {
-        entryPath,
-        meta: pageData.meta,
-        parent: pageData.parent || {},
-        text: wiki.mark.removeMarkdown(pageData.markdown)
-      }
-    }).catch(err => {
-      wiki.logger.error(err)
-      return err
-    }).then((content) => {
-      let parentPath = _.chain(content.entryPath).split('/').initial().join('/').value()
-      return wiki.db.Entry.findOneAndUpdate({
-        _id: content.entryPath
-      }, {
-        _id: content.entryPath,
-        title: content.meta.title || content.entryPath,
-        subtitle: content.meta.subtitle || '',
-        parentTitle: content.parent.title || '',
-        parentPath: parentPath,
-        isDirectory: false,
-        isEntry: true
-      }, {
-        new: true,
-        upsert: true
-      }).then(result => {
-        let plainResult = result.toObject()
-        plainResult.text = content.text
-        return plainResult
-      })
-    }).then(result => {
-      return self.updateTreeInfo().then(() => {
-        return result
-      })
-    }).catch(err => {
-      wiki.logger.error(err)
-      return err
-    })
-  },
-
-  /**
-   * Update tree info for all directory and parent entries
-   *
-   * @returns {Promise<Boolean>} Promise of the operation
-   */
-  updateTreeInfo() {
-    return wiki.db.Entry.distinct('parentPath', { parentPath: { $ne: '' } }).then(allPaths => {
-      if (allPaths.length > 0) {
-        return Promise.map(allPaths, pathItem => {
-          let parentPath = _.chain(pathItem).split('/').initial().join('/').value()
-          let guessedTitle = _.chain(pathItem).split('/').last().startCase().value()
-          return wiki.db.Entry.update({ _id: pathItem }, {
-            $set: { isDirectory: true },
-            $setOnInsert: { isEntry: false, title: guessedTitle, parentPath }
-          }, { upsert: true })
-        })
-      } else {
-        return true
-      }
-    })
-  },
-
-  /**
-   * Create a new document
-   *
-   * @param {String} entryPath The entry path
-   * @param {String}  contents The markdown-formatted contents
-   * @param {Object} author The author user object
-   * @return {Promise<Boolean>} True on success, false on failure
-   */
-  create(entryPath, contents, author) {
-    let self = this
-
-    return self.exists(entryPath).then((docExists) => {
-      if (!docExists) {
-        return self.makePersistent(entryPath, contents, author).then(() => {
-          return self.updateCache(entryPath).then(entry => {
-            return wiki.search.add(entry)
-          })
-        })
-      } else {
-        return Promise.reject(new Error(wiki.lang.t('errors:alreadyexists')))
-      }
-    }).catch((err) => {
-      wiki.logger.error(err)
-      return Promise.reject(new Error(wiki.lang.t('errors:generic')))
-    })
-  },
-
-  /**
-   * Makes a document persistent to disk and git repository
-   *
-   * @param {String} entryPath The entry path
-   * @param {String} contents The markdown-formatted contents
-   * @param {Object} author The author user object
-   * @return {Promise<Boolean>} True on success, false on failure
-   */
-  makePersistent(entryPath, contents, author) {
-    let fpath = entryHelper.getFullPath(entryPath)
-
-    return fs.outputFileAsync(fpath, contents).then(() => {
-      return wiki.git.commitDocument(entryPath, author)
-    })
-  },
-
-  /**
-   * Move a document
-   *
-   * @param {String} entryPath The current entry path
-   * @param {String} newEntryPath  The new entry path
-   * @param {Object} author The author user object
-   * @return {Promise} Promise of the operation
-   */
-  move(entryPath, newEntryPath, author) {
-    let self = this
-
-    if (_.isEmpty(entryPath) || entryPath === 'home') {
-      return Promise.reject(new Error(wiki.lang.t('errors:invalidpath')))
-    }
-
-    return wiki.git.moveDocument(entryPath, newEntryPath).then(() => {
-      return wiki.git.commitDocument(newEntryPath, author).then(() => {
-        // Delete old cache version
-
-        let oldEntryCachePath = entryHelper.getCachePath(entryPath)
-        fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
-
-        // Delete old index entry
-
-        wiki.search.delete(entryPath)
-
-        // Create cache for new entry
-
-        return Promise.join(
-          wiki.db.Entry.deleteOne({ _id: entryPath }),
-          self.updateCache(newEntryPath).then(entry => {
-            return wiki.search.add(entry)
-          })
-        )
-      })
-    })
-  },
-
-  /**
-   * Delete a document
-   *
-   * @param {String} entryPath The current entry path
-   * @param {Object} author The author user object
-   * @return {Promise} Promise of the operation
-   */
-  remove(entryPath, author) {
-    if (_.isEmpty(entryPath) || entryPath === 'home') {
-      return Promise.reject(new Error(wiki.lang.t('errors:invalidpath')))
-    }
-
-    return wiki.git.deleteDocument(entryPath, author).then(() => {
-      // Delete old cache version
-
-      let oldEntryCachePath = entryHelper.getCachePath(entryPath)
-      fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
-
-      // Delete old index entry
-      wiki.search.delete(entryPath)
-
-      // Delete entry
-      return wiki.db.Entry.deleteOne({ _id: entryPath })
-    })
-  },
-
-  /**
-   * Generate a starter page content based on the entry path
-   *
-   * @param      {String}           entryPath  The entry path
-   * @return     {Promise<String>}  Starter content
-   */
-  getStarter(entryPath) {
-    let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
-
-    return fs.readFileAsync(path.join(wiki.SERVERPATH, 'app/content/create.md'), 'utf8').then((contents) => {
-      return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
-    })
-  },
-
-  /**
-   * Get all entries from base path
-   *
-   * @param {String} basePath Path to list from
-   * @param {Object} usr Current user
-   * @return {Promise<Array>} List of entries
-   */
-  getFromTree(basePath, usr) {
-    return wiki.db.Entry.find({ parentPath: basePath }, 'title parentPath isDirectory isEntry').sort({ title: 'asc' }).then(results => {
-      return _.filter(results, r => {
-        return wiki.rights.checkRole('/' + r._id, usr.rights, 'read')
-      })
-    })
-  },
-
-  getHistory(entryPath) {
-    return wiki.db.Entry.findOne({ _id: entryPath, isEntry: true }).then(entry => {
-      if (!entry) { return false }
-      return wiki.git.getHistory(entryPath).then(history => {
-        return {
-          meta: entry,
-          history
-        }
-      })
-    })
-  }
-}

+ 0 - 306
server/modules/git.js

@@ -1,306 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const Git = require('git-wrapper2-promise')
-const Promise = require('bluebird')
-const path = require('path')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const _ = require('lodash')
-const URL = require('url')
-const moment = require('moment')
-
-const securityHelper = require('../helpers/security')
-
-/**
- * Git Model
- */
-module.exports = {
-
-  _git: null,
-  _url: '',
-  _repo: {
-    path: '',
-    branch: 'master',
-    exists: false
-  },
-  _signature: {
-    email: 'wiki@example.com'
-  },
-  _opts: {
-    clone: {},
-    push: {}
-  },
-  onReady: null,
-
-  /**
-   * Initialize Git model
-   *
-   * @return     {Object}  Git model instance
-   */
-  init() {
-    let self = this
-
-    // -> Build repository path
-
-    if (_.isEmpty(wiki.config.paths.repo)) {
-      self._repo.path = path.join(wiki.ROOTPATH, 'repo')
-    } else {
-      self._repo.path = wiki.config.paths.repo
-    }
-
-    // -> Initialize repository
-
-    self.onReady = (wiki.IS_MASTER) ? self._initRepo() : Promise.resolve()
-
-    if (wiki.config.git) {
-      self._repo.branch = wiki.config.git.branch || 'master'
-      self._signature.email = wiki.config.git.serverEmail || 'wiki@example.com'
-    }
-
-    return self
-  },
-
-  /**
-   * Initialize Git repository
-   *
-   * @param      {Object}  wiki.config  The application config
-   * @return     {Object}  Promise
-   */
-  _initRepo() {
-    let self = this
-
-    // -> Check if path is accessible
-
-    return fs.mkdirAsync(self._repo.path).catch((err) => {
-      if (err.code !== 'EEXIST') {
-        wiki.logger.error('Invalid Git repository path or missing permissions.')
-      }
-    }).then(() => {
-      self._git = new Git({ 'git-dir': self._repo.path })
-
-      // -> Check if path already contains a git working folder
-
-      return self._git.isRepo().then((isRepo) => {
-        self._repo.exists = isRepo
-        return (!isRepo) ? self._git.exec('init') : true
-      }).catch((err) => { // eslint-disable-line handle-callback-err
-        self._repo.exists = false
-      })
-    }).then(() => {
-      if (wiki.config.git.enabled === false) {
-        wiki.logger.warn('Git Remote Sync: [ DISABLED ]')
-        return Promise.resolve(true)
-      }
-
-      // Initialize remote
-
-      let urlObj = URL.parse(wiki.config.git.url)
-      if (wiki.config.git.auth.type !== 'ssh') {
-        urlObj.auth = wiki.config.git.auth.username + ':' + wiki.config.git.auth.password
-      }
-      self._url = URL.format(urlObj)
-
-      let gitConfigs = [
-        () => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) },
-        () => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) },
-        () => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(wiki.config.git.auth.sslVerify)]) }
-      ]
-
-      if (wiki.config.git.auth.type === 'ssh') {
-        gitConfigs.push(() => {
-          return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + wiki.config.git.auth.privateKey + '" -o StrictHostKeyChecking=no'])
-        })
-      }
-
-      return self._git.exec('remote', 'show').then((cProc) => {
-        let out = cProc.stdout.toString()
-        return Promise.each(gitConfigs, fn => { return fn() }).then(() => {
-          if (!_.includes(out, 'origin')) {
-            return self._git.exec('remote', ['add', 'origin', self._url])
-          } else {
-            return self._git.exec('remote', ['set-url', 'origin', self._url])
-          }
-        }).catch(err => {
-          wiki.logger.error(err)
-        })
-      })
-    }).catch((err) => {
-      wiki.logger.error('Git remote error!')
-      throw err
-    }).then(() => {
-      wiki.logger.info('Git Repository: [ OK ]')
-      return true
-    })
-  },
-
-  /**
-   * Gets the repo path.
-   *
-   * @return     {String}  The repo path.
-   */
-  getRepoPath() {
-    return this._repo.path || path.join(wiki.ROOTPATH, 'repo')
-  },
-
-  /**
-   * Sync with the remote repository
-   *
-   * @return     {Promise}  Resolve on sync success
-   */
-  resync() {
-    let self = this
-
-    // Is git remote disabled?
-
-    if (wiki.config.git === false) {
-      return Promise.resolve(true)
-    }
-
-    // Fetch
-
-    wiki.logger.info('Performing pull from remote Git repository...')
-    return self._git.pull('origin', self._repo.branch).then((cProc) => {
-      wiki.logger.info('Git Pull completed.')
-    })
-      .catch((err) => {
-        wiki.logger.error('Unable to fetch from git origin!')
-        throw err
-      })
-      .then(() => {
-        // Check for changes
-
-        return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
-          let out = cProc.stdout.toString()
-
-          if (_.includes(out, 'commit')) {
-            wiki.logger.info('Performing push to remote Git repository...')
-            return self._git.push('origin', self._repo.branch).then(() => {
-              return wiki.logger.info('Git Push completed.')
-            })
-          } else {
-            wiki.logger.info('Git Push skipped. Repository is already in sync.')
-          }
-
-          return true
-        })
-      })
-      .catch((err) => {
-        wiki.logger.error('Unable to push changes to remote Git repository!')
-        throw err
-      })
-  },
-
-  /**
-   * Commits a document.
-   *
-   * @param      {String}   entryPath  The entry path
-   * @return     {Promise}  Resolve on commit success
-   */
-  commitDocument(entryPath, author) {
-    let self = this
-    let gitFilePath = entryPath + '.md'
-    let commitMsg = ''
-
-    return self._git.exec('ls-files', gitFilePath).then((cProc) => {
-      let out = cProc.stdout.toString()
-      return _.includes(out, gitFilePath)
-    }).then((isTracked) => {
-      commitMsg = (isTracked) ? wiki.lang.t('git:updated', { path: gitFilePath }) : wiki.lang.t('git:added', { path: gitFilePath })
-      return self._git.add(gitFilePath)
-    }).then(() => {
-      let commitUsr = securityHelper.sanitizeCommitUser(author)
-      return self._git.exec('commit', ['-m', commitMsg, '--author="' + commitUsr.name + ' <' + commitUsr.email + '>"']).catch((err) => {
-        if (_.includes(err.stdout, 'nothing to commit')) { return true }
-      })
-    })
-  },
-
-  /**
-   * Move a document.
-   *
-   * @param      {String}            entryPath     The current entry path
-   * @param      {String}            newEntryPath  The new entry path
-   * @return     {Promise<Boolean>}  Resolve on success
-   */
-  moveDocument(entryPath, newEntryPath) {
-    let self = this
-    let gitFilePath = entryPath + '.md'
-    let gitNewFilePath = newEntryPath + '.md'
-    let destPathObj = path.parse(this.getRepoPath() + '/' + gitNewFilePath)
-
-    return fs.ensureDir(destPathObj.dir).then(() => {
-      return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
-        let out = cProc.stdout.toString()
-        if (_.includes(out, 'fatal')) {
-          let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
-          throw new Error(errorMsg)
-        }
-        return true
-      })
-    })
-  },
-
-  /**
-   * Commits uploads changes.
-   *
-   * @param      {String}   msg     The commit message
-   * @return     {Promise}  Resolve on commit success
-   */
-  commitUploads(msg) {
-    let self = this
-    msg = msg || 'Uploads repository sync'
-
-    return self._git.add('uploads').then(() => {
-      return self._git.commit(msg).catch((err) => {
-        if (_.includes(err.stdout, 'nothing to commit')) { return true }
-      })
-    })
-  },
-
-  getHistory(entryPath) {
-    let self = this
-    let gitFilePath = entryPath + '.md'
-
-    return self._git.exec('log', ['--max-count=25', '--skip=1', '--format=format:%H %h %cI %cE %cN', '--', gitFilePath]).then((cProc) => {
-      let out = cProc.stdout.toString()
-      if (_.includes(out, 'fatal')) {
-        let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
-        throw new Error(errorMsg)
-      }
-      let hist = _.chain(out).split('\n').map(h => {
-        let hParts = h.split(' ', 4)
-        let hDate = moment(hParts[2])
-        return {
-          commit: hParts[0],
-          commitAbbr: hParts[1],
-          date: hParts[2],
-          dateFull: hDate.format('LLLL'),
-          dateCalendar: hDate.calendar(null, { sameElse: 'llll' }),
-          email: hParts[3],
-          name: hParts[4]
-        }
-      }).value()
-      return hist
-    })
-  },
-
-  getHistoryDiff(path, commit, comparewith) {
-    let self = this
-    if (!comparewith) {
-      comparewith = 'HEAD'
-    }
-
-    return self._git.exec('diff', ['--no-color', `${commit}:${path}.md`, `${comparewith}:${path}.md`]).then((cProc) => {
-      let out = cProc.stdout.toString()
-      if (_.startsWith(out, 'fatal: ')) {
-        throw new Error(out)
-      } else if (!_.includes(out, 'diff')) {
-        throw new Error('Unable to query diff data.')
-      } else {
-        return out
-      }
-    })
-  }
-
-}

+ 0 - 0
server/extensions/logging/bugsnag.js → server/modules/logging/bugsnag.js


+ 0 - 0
server/extensions/logging/console.js → server/modules/logging/console.js


+ 0 - 0
server/extensions/logging/loggly.js → server/modules/logging/loggly.js


+ 0 - 0
server/extensions/logging/papertrail.js → server/modules/logging/papertrail.js


+ 0 - 0
server/extensions/logging/rollbar.js → server/modules/logging/rollbar.js


+ 0 - 0
server/extensions/logging/sentry.js → server/modules/logging/sentry.js


+ 0 - 417
server/modules/markdown.js

@@ -1,417 +0,0 @@
-/* global wiki */
-
-const Promise = require('bluebird')
-const md = require('markdown-it')
-const mdEmoji = require('markdown-it-emoji')
-const mdTaskLists = require('markdown-it-task-lists')
-const mdAbbr = require('markdown-it-abbr')
-const mdAnchor = require('markdown-it-anchor')
-const mdFootnote = require('markdown-it-footnote')
-const mdExternalLinks = require('markdown-it-external-links')
-const mdExpandTabs = require('markdown-it-expand-tabs')
-const mdAttrs = require('markdown-it-attrs')
-const mdMathjax = require('markdown-it-mathjax')()
-const mathjax = require('mathjax-node')
-const hljs = require('highlight.js')
-const cheerio = require('cheerio')
-const _ = require('lodash')
-const mdRemove = require('remove-markdown')
-
-// Load plugins
-
-var mkdown = md({
-  html: true,
-  // breaks: wiki.config.features.linebreaks,
-  breaks: true,
-  linkify: true,
-  typography: true,
-  highlight(str, lang) {
-    if (wiki.config.theme.code.colorize && lang && hljs.getLanguage(lang)) {
-      try {
-        return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
-      } catch (err) {
-        return '<pre><code>' + _.escape(str) + '</code></pre>'
-      }
-    }
-    return '<pre><code>' + _.escape(str) + '</code></pre>'
-  }
-})
-  .use(mdEmoji)
-  .use(mdTaskLists)
-  .use(mdAbbr)
-  .use(mdAnchor, {
-    slugify: _.kebabCase,
-    permalink: true,
-    permalinkClass: 'toc-anchor nc-icon-outline location_bookmark-add',
-    permalinkSymbol: '',
-    permalinkBefore: true
-  })
-  .use(mdFootnote)
-  .use(mdExternalLinks, {
-    externalClassName: 'external-link',
-    internalClassName: 'internal-link'
-  })
-  .use(mdExpandTabs, {
-    tabWidth: 4
-  })
-  .use(mdAttrs)
-
-if (wiki.config.features.mathjax) {
-  mkdown.use(mdMathjax)
-}
-
-// Rendering rules
-
-mkdown.renderer.rules.emoji = function (token, idx) {
-  return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'
-}
-
-// Video rules
-
-const videoRules = [
-  {
-    selector: 'a.youtube',
-    regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/i),
-    output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
-  },
-  {
-    selector: 'a.vimeo',
-    regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/i),
-    output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
-  },
-  {
-    selector: 'a.dailymotion',
-    regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/i),
-    output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
-  },
-  {
-    selector: 'a.video',
-    regexp: false,
-    output: '<video width="640" height="360" controls preload="metadata"><source src="{0}" type="video/mp4"></video>'
-  }
-]
-
-// Regex
-
-const textRegex = new RegExp('\\b[a-z0-9-.,' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\b', 'g')
-const mathRegex = [
-  {
-    format: 'TeX',
-    regex: /\\\[([\s\S]*?)\\\]/g
-  },
-  {
-    format: 'inline-TeX',
-    regex: /\\\((.*?)\\\)/g
-  },
-  {
-    format: 'MathML',
-    regex: /<math([\s\S]*?)<\/math>/g
-  }
-]
-
-// MathJax
-
-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'
-    }
-  }
-})
-
-/**
- * Parse markdown content and build TOC tree
- *
- * @param      {(Function|string)}  content  Markdown content
- * @return     {Array}             TOC tree
- */
-const parseTree = (content) => {
-  content = content.replace(/<!--(.|\t|\n|\r)*?-->/g, '')
-  let tokens = md().parse(content, {})
-  let tocArray = []
-
-  // -> Extract headings and their respective levels
-
-  for (let i = 0; i < tokens.length; i++) {
-    if (tokens[i].type !== 'heading_close') {
-      continue
-    }
-
-    const heading = tokens[i - 1]
-    const headingclose = tokens[i]
-
-    if (heading.type === 'inline') {
-      let content = ''
-      let anchor = ''
-      if (heading.children && heading.children.length > 0 && heading.children[0].type === 'link_open') {
-        content = mdRemove(heading.children[1].content)
-        anchor = _.kebabCase(content)
-      } else {
-        content = mdRemove(heading.content)
-        anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ''))
-      }
-
-      tocArray.push({
-        content,
-        anchor,
-        level: +headingclose.tag.substr(1, 1)
-      })
-    }
-  }
-
-  // -> Exclude levels deeper than 2
-
-  _.remove(tocArray, (n) => { return n.level > 2 })
-
-  // -> Build tree from flat array
-
-  return _.reduce(tocArray, (tree, v) => {
-    let treeLength = tree.length - 1
-    if (v.level < 2) {
-      tree.push({
-        content: v.content,
-        anchor: v.anchor,
-        nodes: []
-      })
-    } else {
-      let lastNodeLevel = 1
-      let GetNodePath = (startPos) => {
-        lastNodeLevel++
-        if (_.isEmpty(startPos)) {
-          startPos = 'nodes'
-        }
-        if (lastNodeLevel === v.level) {
-          return startPos
-        } else {
-          return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes')
-        }
-      }
-      let lastNodePath = GetNodePath()
-      let lastNode = _.get(tree[treeLength], lastNodePath)
-      if (lastNode) {
-        lastNode.push({
-          content: v.content,
-          anchor: v.anchor,
-          nodes: []
-        })
-        _.set(tree[treeLength], lastNodePath, lastNode)
-      }
-    }
-    return tree
-  }, [])
-}
-
-/**
- * Parse markdown content to HTML
- *
- * @param      {String}    content  Markdown content
- * @return     {Promise<String>} Promise
- */
-const parseContent = (content) => {
-  let cr = cheerio.load(mkdown.render(content))
-
-  if (cr.root().children().length < 1) {
-    return ''
-  }
-
-  // -> Check for empty first element
-
-  let firstElm = cr.root().children().first()[0]
-  if (firstElm.type === 'tag' && firstElm.name === 'p') {
-    let firstElmChildren = firstElm.children
-    if (firstElmChildren.length < 1) {
-      firstElm.remove()
-    } else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
-      cr(firstElm).addClass('is-gapless')
-    }
-  }
-
-  // -> Remove links in headers
-
-  cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
-    let txtLink = cr(elm).text()
-    cr(elm).replaceWith(txtLink)
-  })
-
-  // -> Re-attach blockquote styling classes to their parents
-
-  cr('blockquote').each((i, elm) => {
-    if (cr(elm).children().length > 0) {
-      let bqLastChild = cr(elm).children().last()[0]
-      let bqLastChildClasses = cr(bqLastChild).attr('class')
-      if (bqLastChildClasses && bqLastChildClasses.length > 0) {
-        cr(bqLastChild).removeAttr('class')
-        cr(elm).addClass(bqLastChildClasses)
-      }
-    }
-  })
-
-  // -> Enclose content below headers
-
-  cr('h2').each((i, elm) => {
-    let subH2Content = cr(elm).nextUntil('h1, h2')
-    cr(elm).after('<div class="indent-h2"></div>')
-    let subH2Container = cr(elm).next('.indent-h2')
-    _.forEach(subH2Content, (ch) => {
-      cr(subH2Container).append(ch)
-    })
-  })
-
-  cr('h3').each((i, elm) => {
-    let subH3Content = cr(elm).nextUntil('h1, h2, h3')
-    cr(elm).after('<div class="indent-h3"></div>')
-    let subH3Container = cr(elm).next('.indent-h3')
-    _.forEach(subH3Content, (ch) => {
-      cr(subH3Container).append(ch)
-    })
-  })
-
-  // Replace video links with embeds
-
-  _.forEach(videoRules, (vrule) => {
-    cr(vrule.selector).each((i, elm) => {
-      let originLink = cr(elm).attr('href')
-      if (vrule.regexp) {
-        let vidMatches = originLink.match(vrule.regexp)
-        if ((vidMatches && _.isArray(vidMatches))) {
-          vidMatches = _.filter(vidMatches, (f) => {
-            return f && _.isString(f)
-          })
-          originLink = _.last(vidMatches)
-        }
-      }
-      let processedLink = _.replace(vrule.output, '{0}', originLink)
-      cr(elm).replaceWith(processedLink)
-    })
-  })
-
-  // Apply align-center to parent
-
-  cr('img.align-center').each((i, elm) => {
-    cr(elm).parent().addClass('align-center')
-    cr(elm).removeClass('align-center')
-  })
-
-  // Mathjax Post-processor
-
-  if (wiki.config.features.mathjax) {
-    return processMathjax(cr.html())
-  } else {
-    return Promise.resolve(cr.html())
-  }
-}
-
-/**
- * Process MathJax expressions
- *
- * @param {String} content HTML content
- * @returns {Promise<String>} Promise
- */
-const processMathjax = (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)
-}
-
-/**
- * Parse meta-data tags from content
- *
- * @param      {String}  content  Markdown content
- * @return     {Object}  Properties found in the content and their values
- */
-const parseMeta = (content) => {
-  let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->', 'g')
-  let results = {}
-  let match
-  while ((match = commentMeta.exec(content)) !== null) {
-    results[_.toLower(match[1])] = _.trim(match[2])
-  }
-
-  return results
-}
-
-/**
- * Strips non-text elements from Markdown content
- *
- * @param      {String}  content  Markdown-formatted content
- * @return     {String}  Text-only version
- */
-const removeMarkdown = (content) => {
-  return _.join(mdRemove(_.chain(content)
-    .replace(/<!-- ?([a-zA-Z]+):(.*)-->/g, '')
-    .replace(/```([^`]|`)+?```/g, '')
-    .replace(/`[^`]+`/g, '')
-    .replace(new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'g'), '')
-    .deburr()
-    .toLower()
-    .value()
-  ).replace(/\r?\n|\r/g, ' ').match(textRegex), ' ')
-}
-
-module.exports = {
-
-  /**
-   * Parse content and return all data
-   *
-   * @param      {String}  content  Markdown-formatted content
-   * @return     {Object}  Object containing meta, html and tree data
-   */
-  parse(content) {
-    return parseContent(content).then(html => {
-      return {
-        meta: parseMeta(content),
-        html,
-        tree: parseTree(content)
-      }
-    })
-  },
-
-  parseContent,
-  parseMeta,
-  parseTree,
-
-  removeMarkdown
-
-}

+ 0 - 0
server/extensions/renderer/common/mathjax.js → server/modules/renderer/common/mathjax.js


+ 0 - 0
server/extensions/renderer/markdown/abbreviations.js → server/modules/renderer/markdown/abbreviations.js


+ 0 - 0
server/extensions/renderer/markdown/emoji.js → server/modules/renderer/markdown/emoji.js


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


+ 0 - 0
server/extensions/renderer/markdown/footnotes.js → server/modules/renderer/markdown/footnotes.js


+ 0 - 0
server/extensions/renderer/markdown/mathjax.js → server/modules/renderer/markdown/mathjax.js


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


+ 0 - 122
server/modules/rights.js

@@ -1,122 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const _ = require('lodash')
-
-/**
- * Rights
- */
-module.exports = {
-
-  guest: {
-    provider: 'local',
-    email: 'guest',
-    name: 'Guest',
-    password: '',
-    rights: [
-      {
-        role: 'read',
-        path: '/',
-        deny: false,
-        exact: false
-      }
-    ]
-  },
-
-  /**
-   * Initialize Rights module
-   *
-   * @return     {void}  Void
-   */
-  init () {
-    let self = this
-
-    wiki.db.onReady.then(() => {
-      wiki.db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => {
-        if (u) {
-          self.guest = u
-        }
-      })
-    })
-  },
-
-  /**
-   * Check user permissions for this request
-   *
-   * @param      {object}  req     The request object
-   * @return     {object}  List of permissions for this request
-   */
-  check (req) {
-    let self = this
-
-    let perm = {
-      read: false,
-      write: false,
-      manage: false
-    }
-    let rt = []
-    let p = _.chain(req.originalUrl).toLower().trim().value()
-
-    // Load user rights
-
-    if (_.isArray(req.user.rights)) {
-      rt = req.user.rights
-    }
-
-    // Check rights
-
-    if (self.checkRole(p, rt, 'admin')) {
-      perm.read = true
-      perm.write = true
-      perm.manage = true
-    } else if (self.checkRole(p, rt, 'write')) {
-      perm.read = true
-      perm.write = true
-    } else if (self.checkRole(p, rt, 'read')) {
-      perm.read = true
-    }
-
-    return perm
-  },
-
-  /**
-   * Check for a specific role based on list of user rights
-   *
-   * @param      {String}         p       Base path
-   * @param      {array<object>}  rt      The user rights
-   * @param      {string}         role    The minimum role required
-   * @return     {boolean}        True if authorized
-   */
-  checkRole (p, rt, role) {
-    if (_.find(rt, { role: 'admin' })) { return true }
-
-    // Check specific role on path
-
-    let filteredRights = _.filter(rt, (r) => {
-      if (r.role === role || (r.role === 'write' && role === 'read')) {
-        if ((!r.exact && _.startsWith(p, r.path)) || (r.exact && p === r.path)) {
-          return true
-        }
-      }
-      return false
-    })
-
-    // Check for deny scenario
-
-    let isValid = false
-
-    if (filteredRights.length > 1) {
-      isValid = !_.chain(filteredRights).sortBy((r) => {
-        return r.path.length + ((r.deny) ? 0.5 : 0)
-      }).last().get('deny').value()
-    } else if (filteredRights.length === 1 && filteredRights[0].deny === false) {
-      isValid = true
-    }
-
-    // Deny by default
-
-    return isValid
-  }
-
-}

+ 0 - 211
server/modules/search.js

@@ -1,211 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const Promise = require('bluebird')
-const _ = require('lodash')
-// const searchIndex = require('./search-index')
-// const stopWord = require('stopword')
-const streamToPromise = require('stream-to-promise')
-const searchAllowedChars = new RegExp('[^a-z0-9' + wiki.data.regex.cjk + wiki.data.regex.arabic + ' ]', 'g')
-
-module.exports = {
-
-  _si: null,
-  _isReady: false,
-
-  /**
-   * Initialize search index
-   *
-   * @return {undefined} Void
-   */
-  init () {
-    let self = this
-    self._isReady = new Promise((resolve, reject) => {
-      /* searchIndex({
-        deletable: true,
-        fieldedSearch: true,
-        indexPath: 'wiki',
-        logLevel: 'error',
-        stopwords: _.get(stopWord, wiki.config.lang, [])
-      }, (err, si) => {
-        if (err) {
-          wiki.logger.error('Failed to initialize search index.', err)
-          reject(err)
-        } else {
-          self._si = Promise.promisifyAll(si)
-          self._si.flushAsync().then(() => {
-            wiki.logger.info('Search index flushed and ready.')
-            resolve(true)
-          })
-        }
-      }) */
-    })
-
-    return self
-  },
-
-  /**
-   * Add a document to the index
-   *
-   * @param      {Object}   content  Document content
-   * @return     {Promise}  Promise of the add operation
-   */
-  add (content) {
-    let self = this
-
-    if (!content.isEntry) {
-      return Promise.resolve(true)
-    }
-
-    return self._isReady.then(() => {
-      return self.delete(content._id).then(() => {
-        return self._si.concurrentAddAsync({
-          fieldOptions: [{
-            fieldName: 'entryPath',
-            searchable: true,
-            weight: 2
-          },
-          {
-            fieldName: 'title',
-            nGramLength: [1, 2],
-            searchable: true,
-            weight: 3
-          },
-          {
-            fieldName: 'subtitle',
-            searchable: true,
-            weight: 1,
-            storeable: false
-          },
-          {
-            fieldName: 'parent',
-            searchable: false
-          },
-          {
-            fieldName: 'content',
-            searchable: true,
-            weight: 0,
-            storeable: false
-          }]
-        }, [{
-          entryPath: content._id,
-          title: content.title,
-          subtitle: content.subtitle || '',
-          parent: content.parent || '',
-          content: content.text || ''
-        }]).then(() => {
-          wiki.logger.log('verbose', 'Entry ' + content._id + ' added/updated to search index.')
-          return true
-        }).catch((err) => {
-          wiki.logger.error(err)
-        })
-      }).catch((err) => {
-        wiki.logger.error(err)
-      })
-    })
-  },
-
-  /**
-   * Delete an entry from the index
-   *
-   * @param      {String}   The     entry path
-   * @return     {Promise}  Promise of the operation
-   */
-  delete (entryPath) {
-    let self = this
-
-    return self._isReady.then(() => {
-      return streamToPromise(self._si.search({
-        query: [{
-          AND: { 'entryPath': [entryPath] }
-        }]
-      })).then((results) => {
-        if (results && results.length > 0) {
-          let delIds = _.map(results, 'id')
-          return self._si.delAsync(delIds)
-        } else {
-          return true
-        }
-      }).catch((err) => {
-        if (err.type === 'NotFoundError') {
-          return true
-        } else {
-          wiki.logger.error(err)
-        }
-      })
-    })
-  },
-
-  /**
-   * Flush the index
-   *
-   * @returns {Promise} Promise of the flush operation
-   */
-  flush () {
-    let self = this
-    return self._isReady.then(() => {
-      return self._si.flushAsync()
-    })
-  },
-
-  /**
-   * Search the index
-   *
-   * @param {Array<String>} terms
-   * @returns {Promise<Object>} Hits and suggestions
-   */
-  find (terms) {
-    let self = this
-    terms = _.chain(terms)
-      .deburr()
-      .toLower()
-      .trim()
-      .replace(searchAllowedChars, ' ')
-      .value()
-    let arrTerms = _.chain(terms)
-      .split(' ')
-      .filter((f) => { return !_.isEmpty(f) })
-      .value()
-
-    return streamToPromise(self._si.search({
-      query: [{
-        AND: { '*': arrTerms }
-      }],
-      pageSize: 10
-    })).then((hits) => {
-      if (hits.length > 0) {
-        hits = _.map(_.sortBy(hits, ['score']), h => {
-          return h.document
-        })
-      }
-      if (hits.length < 5) {
-        return streamToPromise(self._si.match({
-          beginsWith: terms,
-          threshold: 3,
-          limit: 5,
-          type: 'simple'
-        })).then((matches) => {
-          return {
-            match: hits,
-            suggest: matches
-          }
-        })
-      } else {
-        return {
-          match: hits,
-          suggest: []
-        }
-      }
-    }).catch((err) => {
-      if (err.type === 'NotFoundError') {
-        return {
-          match: [],
-          suggest: []
-        }
-      } else {
-        wiki.logger.error(err)
-      }
-    })
-  }
-}

+ 0 - 0
server/extensions/storage/disk.js → server/modules/storage/disk.js


+ 0 - 0
server/extensions/storage/git.js → server/modules/storage/git.js


+ 0 - 252
server/modules/uploads-agent.js

@@ -1,252 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const path = require('path')
-const Promise = require('bluebird')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const readChunk = require('read-chunk')
-const fileType = require('file-type')
-const mime = require('mime-types')
-const crypto = require('crypto')
-const chokidar = require('chokidar')
-const jimp = require('jimp')
-const imageSize = Promise.promisify(require('image-size'))
-const _ = require('lodash')
-
-/**
- * Uploads - Agent
- */
-module.exports = {
-
-  _uploadsPath: './repo/uploads',
-  _uploadsThumbsPath: './data/thumbs',
-
-  _watcher: null,
-
-  /**
-   * Initialize Uploads model
-   *
-   * @return     {Object}  Uploads model instance
-   */
-  init () {
-    let self = this
-
-    self._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
-    self._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
-
-    return self
-  },
-
-  /**
-   * Watch the uploads folder for changes
-   *
-   * @return     {Void}  Void
-   */
-  watch () {
-    let self = this
-
-    self._watcher = chokidar.watch(self._uploadsPath, {
-      persistent: true,
-      ignoreInitial: true,
-      cwd: self._uploadsPath,
-      depth: 1,
-      awaitWriteFinish: true
-    })
-
-    // -> Add new upload file
-
-    self._watcher.on('add', (p) => {
-      let pInfo = self.parseUploadsRelPath(p)
-      return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
-        return wiki.db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
-      }).then(() => {
-        return wiki.git.commitUploads(wiki.lang.t('git:uploaded', { path: p }))
-      })
-    })
-
-    // -> Remove upload file
-
-    self._watcher.on('unlink', (p) => {
-      return wiki.git.commitUploads(wiki.lang.t('git:deleted', { path: p }))
-    })
-  },
-
-  /**
-   * Initial Uploads scan
-   *
-   * @return     {Promise<Void>}  Promise of the scan operation
-   */
-  initialScan () {
-    let self = this
-
-    return fs.readdirAsync(self._uploadsPath).then((ls) => {
-      // Get all folders
-
-      return Promise.map(ls, (f) => {
-        return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })
-      }).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {
-        let folderNames = _.map(arrDirs, 'filename')
-        folderNames.unshift('')
-
-        // Add folders to DB
-
-        return wiki.db.UplFolder.remove({}).then(() => {
-          return wiki.db.UplFolder.insertMany(_.map(folderNames, (f) => {
-            return {
-              _id: 'f:' + f,
-              name: f
-            }
-          }))
-        }).then(() => {
-          // Travel each directory and scan files
-
-          let allFiles = []
-
-          return Promise.map(folderNames, (fldName) => {
-            let fldPath = path.join(self._uploadsPath, fldName)
-            return fs.readdirAsync(fldPath).then((fList) => {
-              return Promise.map(fList, (f) => {
-                return wiki.upl.processFile(fldName, f).then((mData) => {
-                  if (mData) {
-                    allFiles.push(mData)
-                  }
-                  return true
-                })
-              }, {concurrency: 3})
-            })
-          }, {concurrency: 1}).finally(() => {
-            // Add files to DB
-
-            return wiki.db.UplFile.remove({}).then(() => {
-              if (_.isArray(allFiles) && allFiles.length > 0) {
-                return wiki.db.UplFile.insertMany(allFiles)
-              } else {
-                return true
-              }
-            })
-          })
-        })
-      })
-    }).then(() => {
-      // Watch for new changes
-
-      return wiki.upl.watch()
-    })
-  },
-
-  /**
-   * Parse relative Uploads path
-   *
-   * @param      {String}  f       Relative Uploads path
-   * @return     {Object}  Parsed path (folder and filename)
-   */
-  parseUploadsRelPath (f) {
-    let fObj = path.parse(f)
-    return {
-      folder: fObj.dir,
-      filename: fObj.base
-    }
-  },
-
-  /**
-   * Get metadata from file and generate thumbnails if necessary
-   *
-   * @param      {String}  fldName  The folder name
-   * @param      {String}  f        The filename
-   * @return     {Promise<Object>}  Promise of the file metadata
-   */
-  processFile (fldName, f) {
-    let self = this
-
-    let fldPath = path.join(self._uploadsPath, fldName)
-    let fPath = path.join(fldPath, f)
-    let fPathObj = path.parse(fPath)
-    let fUid = crypto.createHash('md5').update(fldName + '/' + f).digest('hex')
-
-    return fs.statAsync(fPath).then((s) => {
-      if (!s.isFile()) { return false }
-
-      // Get MIME info
-
-      let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))
-      if (_.isNil(mimeInfo)) {
-        mimeInfo = {
-          mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
-        }
-      }
-
-      // Images
-
-      if (s.size < 3145728) { // ignore files larger than 3MB
-        if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/bmp'], mimeInfo.mime)) {
-          return self.getImageSize(fPath).then((mImgSize) => {
-            let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))
-            let cacheThumbnailPathStr = path.format(cacheThumbnailPath)
-
-            let mData = {
-              _id: fUid,
-              category: 'image',
-              mime: mimeInfo.mime,
-              extra: mImgSize,
-              folder: 'f:' + fldName,
-              filename: f,
-              basename: fPathObj.name,
-              filesize: s.size
-            }
-
-            // Generate thumbnail
-
-            return fs.statAsync(cacheThumbnailPathStr).then((st) => {
-              return st.isFile()
-            }).catch((err) => { // eslint-disable-line handle-callback-err
-              return false
-            }).then((thumbExists) => {
-              return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
-                return self.generateThumbnail(fPath, cacheThumbnailPathStr)
-              }).return(mData)
-            })
-          })
-        }
-      }
-
-      // Other Files
-
-      return {
-        _id: fUid,
-        category: 'binary',
-        mime: mimeInfo.mime,
-        folder: 'f:' + fldName,
-        filename: f,
-        basename: fPathObj.name,
-        filesize: s.size
-      }
-    })
-  },
-
-  /**
-   * Generate thumbnail of image
-   *
-   * @param      {String}           sourcePath  The source path
-   * @param      {String}           destPath    The destination path
-   * @return     {Promise<Object>}  Promise returning the resized image info
-   */
-  generateThumbnail (sourcePath, destPath) {
-    return jimp.read(sourcePath).then(img => {
-      return img
-        .contain(150, 150)
-        .write(destPath)
-    })
-  },
-
-  /**
-   * Gets the image dimensions.
-   *
-   * @param      {String}  sourcePath  The source path
-   * @return     {Object}  The image dimensions.
-   */
-  getImageSize (sourcePath) {
-    return imageSize(sourcePath)
-  }
-
-}

+ 0 - 281
server/modules/uploads.js

@@ -1,281 +0,0 @@
-'use strict'
-
-/* global wiki */
-
-const path = require('path')
-const Promise = require('bluebird')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const request = require('request')
-const url = require('url')
-const crypto = require('crypto')
-const _ = require('lodash')
-
-var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
-const maxDownloadFileSize = 3145728 // 3 MB
-
-/**
- * Uploads
- */
-module.exports = {
-
-  _uploadsPath: './repo/uploads',
-  _uploadsThumbsPath: './data/thumbs',
-
-  /**
-   * Initialize Local Data Storage model
-   *
-   * @return     {Object}  Uploads model instance
-   */
-  init () {
-    this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
-    this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
-
-    return this
-  },
-
-  /**
-   * Gets the thumbnails folder path.
-   *
-   * @return     {String}  The thumbs path.
-   */
-  getThumbsPath () {
-    return this._uploadsThumbsPath
-  },
-
-  /**
-   * Gets the uploads folders.
-   *
-   * @return     {Array<String>}  The uploads folders.
-   */
-  getUploadsFolders () {
-    return wiki.db.Folder.find({}, 'name').sort('name').exec().then((results) => {
-      return (results) ? _.map(results, 'name') : [{ name: '' }]
-    })
-  },
-
-  /**
-   * Creates an uploads folder.
-   *
-   * @param      {String}  folderName  The folder name
-   * @return     {Promise}  Promise of the operation
-   */
-  createUploadsFolder (folderName) {
-    let self = this
-
-    folderName = _.kebabCase(_.trim(folderName))
-
-    if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
-      return Promise.resolve(self.getUploadsFolders())
-    }
-
-    return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
-      return wiki.db.UplFolder.findOneAndUpdate({
-        _id: 'f:' + folderName
-      }, {
-        name: folderName
-      }, {
-        upsert: true
-      })
-    }).then(() => {
-      return self.getUploadsFolders()
-    })
-  },
-
-  /**
-   * Check if folder is valid and exists
-   *
-   * @param      {String}  folderName  The folder name
-   * @return     {Boolean}   True if valid
-   */
-  validateUploadsFolder (folderName) {
-    return wiki.db.UplFolder.findOne({ name: folderName }).then((f) => {
-      return (f) ? path.resolve(this._uploadsPath, folderName) : false
-    })
-  },
-
-  /**
-   * Adds one or more uploads files.
-   *
-   * @param      {Array<Object>}  arrFiles  The uploads files
-   * @return     {Void}  Void
-   */
-  addUploadsFiles (arrFiles) {
-    if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
-      // this._uploadswiki.Db.Files.insert(arrFiles);
-    }
-  },
-
-  /**
-   * Gets the uploads files.
-   *
-   * @param      {String}  cat     Category type
-   * @param      {String}  fld     Folder
-   * @return     {Array<Object>}  The files matching the query
-   */
-  getUploadsFiles (cat, fld) {
-    return wiki.db.UplFile.find({
-      category: cat,
-      folder: 'f:' + fld
-    }).sort('filename').exec()
-  },
-
-  /**
-   * Deletes an uploads file.
-   *
-   * @param      {string}   uid     The file unique ID
-   * @return     {Promise}  Promise of the operation
-   */
-  deleteUploadsFile (uid) {
-    let self = this
-
-    return wiki.db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
-      if (f) {
-        return self.deleteUploadsFileTry(f, 0)
-      }
-      return true
-    })
-  },
-
-  deleteUploadsFileTry (f, attempt) {
-    let self = this
-
-    let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
-
-    return Promise.join(
-      fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
-      fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
-    ).catch((err) => {
-      if (err.code === 'EBUSY' && attempt < 5) {
-        return Promise.delay(100).then(() => {
-          return self.deleteUploadsFileTry(f, attempt + 1)
-        })
-      } else {
-        wiki.logger.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
-        return true
-      }
-    })
-  },
-
-  /**
-   * Downloads a file from url.
-   *
-   * @param      {String}   fFolder  The folder
-   * @param      {String}   fUrl     The full URL
-   * @return     {Promise}  Promise of the operation
-   */
-  downloadFromUrl (fFolder, fUrl) {
-    let fUrlObj = url.parse(fUrl)
-    let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
-    let destFolder = _.chain(fFolder).trim().toLower().value()
-
-    return wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
-      if (!destFolderPath) {
-        return Promise.reject(new Error(wiki.lang.t('errors:invalidfolder')))
-      }
-
-      return wiki.disk.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
-        let destFilePath = path.resolve(destFolderPath, destFilename)
-
-        return new Promise((resolve, reject) => {
-          let rq = request({
-            url: fUrl,
-            method: 'GET',
-            followRedirect: true,
-            maxRedirects: 5,
-            timeout: 10000
-          })
-
-          let destFileStream = fs.createWriteStream(destFilePath)
-          let curFileSize = 0
-
-          rq.on('data', (data) => {
-            curFileSize += data.length
-            if (curFileSize > maxDownloadFileSize) {
-              rq.abort()
-              destFileStream.destroy()
-              fs.remove(destFilePath)
-              reject(new Error(wiki.lang.t('errors:remotetoolarge')))
-            }
-          }).on('error', (err) => {
-            destFileStream.destroy()
-            fs.remove(destFilePath)
-            reject(err)
-          })
-
-          destFileStream.on('finish', () => {
-            resolve(true)
-          })
-
-          rq.pipe(destFileStream)
-        })
-      })
-    })
-  },
-
-  /**
-   * Move/Rename a file
-   *
-   * @param      {String}   uid        The file ID
-   * @param      {String}   fld        The destination folder
-   * @param      {String}   nFilename  The new filename (optional)
-   * @return     {Promise}  Promise of the operation
-   */
-  moveUploadsFile (uid, fld, nFilename) {
-    let self = this
-
-    return wiki.db.UplFolder.finwiki.dById('f:' + fld).then((folder) => {
-      if (folder) {
-        return wiki.db.UplFile.finwiki.dById(uid).then((originFile) => {
-          // -> Check if rename is valid
-
-          let nameCheck = null
-          if (nFilename) {
-            let originFileObj = path.parse(originFile.filename)
-            nameCheck = wiki.disk.validateUploadsFilename(nFilename + originFileObj.ext, folder.name)
-          } else {
-            nameCheck = Promise.resolve(originFile.filename)
-          }
-
-          return nameCheck.then((destFilename) => {
-            let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
-            let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
-            let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
-            let preMoveOps = []
-
-            // -> Check for invalid operations
-
-            if (sourceFilePath === destFilePath) {
-              return Promise.reject(new Error(wiki.lang.t('errors:invalidoperation')))
-            }
-
-            // -> Delete wiki.DB entry
-
-            preMoveOps.push(wiki.db.UplFile.finwiki.dByIdAndRemove(uid))
-
-            // -> Move thumbnail ahead to avoid re-generation
-
-            if (originFile.category === 'image') {
-              let fUid = crypto.createHash('md5').update(folder.name + '/' + destFilename).digest('hex')
-              let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
-              let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
-              preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
-            } else {
-              preMoveOps.push(Promise.resolve(true))
-            }
-
-            // -> Proceed to move actual file
-
-            return Promise.all(preMoveOps).then(() => {
-              return fs.moveAsync(sourceFilePath, destFilePath, {
-                clobber: false
-              })
-            })
-          })
-        })
-      } else {
-        return Promise.reject(new Error(wiki.lang.t('errors:invaliddestfolder')))
-      }
-    })
-  }
-
-}

+ 4 - 9
server/setup.js

@@ -8,7 +8,7 @@ module.exports = () => {
     title: 'Wiki.js'
   }
 
-  wiki.system = require('./modules/system')
+  wiki.system = require('./core/system')
 
   // ----------------------------------------
   // Load modules
@@ -62,13 +62,8 @@ module.exports = () => {
   // ----------------------------------------
 
   if (global.DEV) {
-    const webpackDevMiddleware = require('webpack-dev-middleware')
-    const webpackHotMiddleware = require('webpack-hot-middleware')
-    app.use(webpackDevMiddleware(global.WP, {
-      publicPath: global.WPCONFIG.output.publicPath,
-      logger: wiki.logger
-    }))
-    app.use(webpackHotMiddleware(global.WP))
+    app.use(global.WP_DEV.devMiddleware)
+    app.use(global.WP_DEV.hotMiddleware)
   }
 
   // ----------------------------------------
@@ -77,7 +72,7 @@ module.exports = () => {
 
   app.get('*', async (req, res) => {
     let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json'))
-    res.render('pages/setup', {
+    res.render('main/setup', {
       packageObj,
       telemetryClientID: wiki.telemetry.cid
     })

+ 0 - 0
server/views/pages/admin/_layout.pug → server/views/admin/_layout.pug


+ 0 - 0
server/views/pages/admin/profile.pug → server/views/admin/profile.pug


+ 0 - 0
server/views/pages/admin/settings.pug → server/views/admin/settings.pug


+ 0 - 0
server/views/pages/admin/stats.pug → server/views/admin/stats.pug


+ 0 - 0
server/views/pages/admin/system.pug → server/views/admin/system.pug


+ 0 - 0
server/views/pages/admin/theme.pug → server/views/admin/theme.pug


+ 0 - 0
server/views/pages/admin/users-edit.pug → server/views/admin/users-edit.pug


+ 0 - 0
server/views/pages/admin/users.pug → server/views/admin/users.pug


+ 0 - 0
server/views/error-forbidden.pug → server/views/errors/error-forbidden.pug


+ 0 - 0
server/views/error-notexist.pug → server/views/errors/error-notexist.pug


+ 3 - 4
server/views/layout.pug

@@ -2,11 +2,10 @@ extends ./master.pug
 
 block body
   body
-    #app.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt])
-      include ./common/header.pug
-      alert
+    #app
+      navigator
       main
         block content
-      include ./common/footer.pug
+      footer
 
     block outside

+ 0 - 0
server/views/pages/all.pug → server/views/main/all.pug


+ 0 - 0
server/views/pages/create.pug → server/views/main/create.pug


+ 0 - 0
server/views/pages/edit.pug → server/views/main/edit.pug


+ 0 - 0
server/views/pages/history.pug → server/views/main/history.pug


+ 0 - 0
server/views/pages/login.pug → server/views/main/login.pug


+ 0 - 0
server/views/pages/setup.pug → server/views/main/setup.pug


+ 0 - 0
server/views/pages/source.pug → server/views/main/source.pug


+ 0 - 0
server/views/pages/view.pug → server/views/main/view.pug


+ 10 - 0
server/views/main/welcome.pug

@@ -0,0 +1,10 @@
+extends ../master.pug
+
+block body
+  body
+    #app.is-fullscreen
+      .onboarding
+        img(src='/svg/logo-wikijs.svg', alt='Wiki.js')
+        h1= t('welcome.title')
+        h2= t('welcome.subtitle')
+        a.button.is-blue(href='/create/home')= t('welcome.createhome')

+ 0 - 11
server/views/pages/welcome.pug

@@ -1,11 +0,0 @@
-extends ../layout.pug
-
-block rootNavCenter
-
-block content
-  .container
-    .welcome
-      img(src='/images/logo.png', alt='Wiki.js')
-      h1= t('welcome.title')
-      h2= t('welcome.subtitle')
-      a.button.is-indigo(href='/create/home')= t('welcome.createhome')

+ 3 - 7
server/worker.js

@@ -1,7 +1,7 @@
-/* global wiki */
-
 const Promise = require('bluebird')
 
+/* global wiki */
+
 module.exports = Promise.join(
   wiki.db.onReady,
   wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads'])
@@ -16,11 +16,7 @@ module.exports = Promise.join(
   // Load global modules
   // ----------------------------------------
 
-  // wiki.upl = require('./modules/uploads-agent').init()
-  // wiki.git = require('./modules/git').init()
-  // wiki.entries = require('./modules/entries').init()
   wiki.lang = require('i18next')
-  wiki.mark = require('./modules/markdown')
 
   // ----------------------------------------
   // Localization Engine
@@ -36,7 +32,7 @@ module.exports = Promise.join(
     lng: wiki.config.lang,
     fallbackLng: 'en',
     backend: {
-      loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
+      loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.yml')
     }
   })
 

+ 3 - 84
yarn.lock

@@ -2101,12 +2101,6 @@ child-process-promise@2.2.1:
     node-version "^1.0.0"
     promise-polyfill "^6.0.1"
 
-child-process-promise@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/child-process-promise/-/child-process-promise-1.1.0.tgz#131e01a705f15ed4a05d554dd5e032e52612cf30"
-  dependencies:
-    q "^1.1.2"
-
 chokidar@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.0.tgz#6686313c541d3274b2a5c01233342037948c911b"
@@ -3086,10 +3080,6 @@ detect-indent@^4.0.0:
   dependencies:
     repeating "^2.0.0"
 
-detect-libc@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-
 detect-newline@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
@@ -4171,16 +4161,6 @@ fs-extra@5.0.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@~0.26.2:
-  version "0.26.7"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
-  dependencies:
-    graceful-fs "^4.1.2"
-    jsonfile "^2.1.0"
-    klaw "^1.0.0"
-    path-is-absolute "^1.0.0"
-    rimraf "^2.2.8"
-
 fs-readdir-recursive@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560"
@@ -4310,12 +4290,6 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
-git-wrapper2-promise@0.2.9:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/git-wrapper2-promise/-/git-wrapper2-promise-0.2.9.tgz#2c781e26a16246b05eba45fa17df687403bbfd7d"
-  dependencies:
-    child-process-promise "^1.1.0"
-
 "gkt@https://tgz.pm2.io/gkt-1.0.0.tgz":
   version "1.0.0"
   resolved "https://tgz.pm2.io/gkt-1.0.0.tgz#405502b007f319c3f47175c4474527300f2ab5ad"
@@ -4594,7 +4568,7 @@ hashring@0.0.x:
     bisection ""
     simple-lru-cache "0.0.x"
 
-hawk@3.1.3, hawk@~3.1.3:
+hawk@~3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
   dependencies:
@@ -5791,12 +5765,6 @@ json5@^0.5.0, json5@^0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
 
-jsonfile@^2.1.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
-  optionalDependencies:
-    graceful-fs "^4.1.6"
-
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -5895,12 +5863,6 @@ klaw@2.1.1:
   dependencies:
     graceful-fs "^4.1.9"
 
-klaw@^1.0.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
-  optionalDependencies:
-    graceful-fs "^4.1.9"
-
 lazy-cache@^0.2.3:
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
@@ -6646,10 +6608,6 @@ name-all-modules-plugin@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/name-all-modules-plugin/-/name-all-modules-plugin-1.0.1.tgz#0abfb6ad835718b9fb4def0674e06657a954375c"
 
-nan@^2.2.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
-
 nan@^2.3.0, nan@^2.3.2, nan@^2.3.3:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
@@ -6711,7 +6669,7 @@ node-fingerprint@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/node-fingerprint/-/node-fingerprint-0.0.2.tgz#31cbabeb71a67ae7dd5a7dc042e51c3c75868501"
 
-node-gyp@^3.3.1, node-gyp@^3.5.0:
+node-gyp@^3.3.1:
   version "3.6.2"
   resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
   dependencies:
@@ -6784,22 +6742,6 @@ node-pre-gyp@^0.6.36:
     tar "^2.2.1"
     tar-pack "^3.4.0"
 
-node-pre-gyp@~0.6.32:
-  version "0.6.39"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
-  dependencies:
-    detect-libc "^1.0.2"
-    hawk "3.1.3"
-    mkdirp "^0.5.1"
-    nopt "^4.0.1"
-    npmlog "^4.0.2"
-    rc "^1.1.7"
-    request "2.81.0"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar "^2.2.1"
-    tar-pack "^3.4.0"
-
 node-sass@4.7.2:
   version "4.7.2"
   resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
@@ -6828,23 +6770,6 @@ node-version@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/node-version/-/node-version-1.0.0.tgz#1b9b9584a9a7f7a6123f215cd14a652bf21ab19e"
 
-nodegit-promise@~4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/nodegit-promise/-/nodegit-promise-4.0.0.tgz#5722b184f2df7327161064a791d2e842c9167b34"
-  dependencies:
-    asap "~2.0.3"
-
-nodegit@0.20.3:
-  version "0.20.3"
-  resolved "https://registry.yarnpkg.com/nodegit/-/nodegit-0.20.3.tgz#a44172db08c833cdcb1ab9bbc2c832ce05898815"
-  dependencies:
-    fs-extra "~0.26.2"
-    lodash "^4.13.1"
-    nan "^2.2.0"
-    node-gyp "^3.5.0"
-    node-pre-gyp "~0.6.32"
-    promisify-node "~0.3.0"
-
 nopt@1.0.10:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
@@ -8215,12 +8140,6 @@ promise@7.x, promise@^7.0.1:
   dependencies:
     asap "~2.0.3"
 
-promisify-node@~0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf"
-  dependencies:
-    nodegit-promise "~4.0.0"
-
 promptly@2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/promptly/-/promptly-2.2.0.tgz#2a13fa063688a2a5983b161fff0108a07d26fc74"
@@ -8772,7 +8691,7 @@ request-promise@4.2.2:
     stealthy-require "^1.1.0"
     tough-cookie ">=2.3.3"
 
-request@2, request@2.81.0, request@^2.67.0, request@^2.79.0, request@^2.81.0:
+request@2, request@^2.67.0, request@^2.79.0, request@^2.81.0:
   version "2.81.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
   dependencies: