refactor: project cleanup + onboarding page
This commit is contained in:
1 parent
1d9f057f38
commit
3471a7a6f9
80 files changed
+174
-2698
No files matched your search
@@ -353,6 +353,7 @@
|
|||||||
/* global siteConfig */
|
/* global siteConfig */
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@@ -491,7 +492,7 @@ export default {
|
|||||||
results: []
|
results: []
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$helpers._.delay(() => {
|
_.delay(() => {
|
||||||
axios.post('/syscheck', self.conf).then(resp => {
|
axios.post('/syscheck', self.conf).then(resp => {
|
||||||
if (resp.data.ok === true) {
|
if (resp.data.ok === true) {
|
||||||
self.syscheck.ok = true
|
self.syscheck.ok = true
|
||||||
@@ -517,9 +518,9 @@ export default {
|
|||||||
},
|
},
|
||||||
proceedToConsiderations: function (ev) {
|
proceedToConsiderations: function (ev) {
|
||||||
this.considerations = {
|
this.considerations = {
|
||||||
https: !this.$helpers._.startsWith(this.conf.host, 'https'),
|
https: false,
|
||||||
port: false, // TODO
|
port: false, // TODO
|
||||||
localhost: this.$helpers._.includes(this.conf.host, 'localhost')
|
localhost: false
|
||||||
}
|
}
|
||||||
this.state = 'considerations'
|
this.state = 'considerations'
|
||||||
this.loading = false
|
this.loading = false
|
||||||
@@ -542,7 +543,7 @@ export default {
|
|||||||
error: ''
|
error: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$helpers._.delay(() => {
|
_.delay(() => {
|
||||||
axios.post('/gitcheck', self.conf).then(resp => {
|
axios.post('/gitcheck', self.conf).then(resp => {
|
||||||
if (resp.data.ok === true) {
|
if (resp.data.ok === true) {
|
||||||
self.gitcheck.ok = true
|
self.gitcheck.ok = true
|
||||||
@@ -587,7 +588,7 @@ export default {
|
|||||||
redirectUrl: ''
|
redirectUrl: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$helpers._.delay(() => {
|
_.delay(() => {
|
||||||
axios.post('/finalize', self.conf).then(resp => {
|
axios.post('/finalize', self.conf).then(resp => {
|
||||||
if (resp.data.ok === true) {
|
if (resp.data.ok === true) {
|
||||||
self.$helpers._.delay(() => {
|
self.$helpers._.delay(() => {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
@import 'components/editor';
|
@import 'components/editor';
|
||||||
|
|
||||||
|
@import 'pages/welcome';
|
||||||
|
|
||||||
@import 'layout/_header';
|
@import 'layout/_header';
|
||||||
@import 'layout/_loader';
|
@import 'layout/_loader';
|
||||||
@import 'layout/_rtl';
|
@import 'layout/_rtl';
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ #app {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
&.is-fullscreen {
|
&.is-fullscreen {
|
||||||
//width: 100vw;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main {
|
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 {
|
a {
|
||||||
@@ -42,28 +44,8 @@ a {
|
|||||||
|
|
||||||
// Container
|
// Container
|
||||||
|
|
||||||
.has-stickynav {
|
|
||||||
padding-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@include desktop {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 960px;
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
&.is-fluid {
|
|
||||||
margin: 0;
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@include widescreen {
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,6 @@
|
|||||||
"filesize.js": "1.0.2",
|
"filesize.js": "1.0.2",
|
||||||
"follow-redirects": "1.4.1",
|
"follow-redirects": "1.4.1",
|
||||||
"fs-extra": "5.0.0",
|
"fs-extra": "5.0.0",
|
||||||
"git-wrapper2-promise": "0.2.9",
|
|
||||||
"graphql": "0.12.3",
|
"graphql": "0.12.3",
|
||||||
"graphql-tools": "2.19.0",
|
"graphql-tools": "2.19.0",
|
||||||
"highlight.js": "9.12.0",
|
"highlight.js": "9.12.0",
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
"mongodb": "3.0.1",
|
"mongodb": "3.0.1",
|
||||||
"multer": "1.3.0",
|
"multer": "1.3.0",
|
||||||
"node-2fa": "1.1.2",
|
"node-2fa": "1.1.2",
|
||||||
"nodegit": "0.20.3",
|
|
||||||
"ora": "1.3.0",
|
"ora": "1.3.0",
|
||||||
"passport": "0.4.0",
|
"passport": "0.4.0",
|
||||||
"passport-auth0": "0.6.1",
|
"passport-auth0": "0.6.1",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const bruteforce = new ExpressBrute(EBstore, {
|
|||||||
* Login form
|
* Login form
|
||||||
*/
|
*/
|
||||||
router.get('/login', function (req, res, next) {
|
router.get('/login', function (req, res, next) {
|
||||||
res.render('pages/login')
|
res.render('main/login')
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||||
|
|||||||
@@ -1,316 +1,11 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global entries, git, lang, winston */
|
|
||||||
|
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
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
|
* View document
|
||||||
*/
|
*/
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
let safePath = entryHelper.parsePath(req.path)
|
res.render('main/welcome')
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
@@ -33,7 +33,7 @@ module.exports = {
|
|||||||
|
|
||||||
_.forOwn(_.omitBy(wiki.config.auth.strategies, s => s.enabled === false), (strategyConfig, strategyKey) => {
|
_.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`
|
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 {
|
try {
|
||||||
strategy.init(passport, strategyConfig)
|
strategy.init(passport, strategyConfig)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/* global wiki */
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const yaml = require('js-yaml')
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const path = require('path')
|
|
||||||
const cfgHelper = require('../helpers/config')
|
const cfgHelper = require('../helpers/config')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const yaml = require('js-yaml')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@@ -1,54 +1,52 @@
|
|||||||
/* global wiki */
|
const _ = require('lodash')
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const _ = require('lodash')
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
const Op = Sequelize.Op
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
const operatorsAliases = {
|
const operatorsAliases = {
|
||||||
$eq: Op.eq,
|
$eq: Sequelize.Op.eq,
|
||||||
$ne: Op.ne,
|
$ne: Sequelize.Op.ne,
|
||||||
$gte: Op.gte,
|
$gte: Sequelize.Op.gte,
|
||||||
$gt: Op.gt,
|
$gt: Sequelize.Op.gt,
|
||||||
$lte: Op.lte,
|
$lte: Sequelize.Op.lte,
|
||||||
$lt: Op.lt,
|
$lt: Sequelize.Op.lt,
|
||||||
$not: Op.not,
|
$not: Sequelize.Op.not,
|
||||||
$in: Op.in,
|
$in: Sequelize.Op.in,
|
||||||
$notIn: Op.notIn,
|
$notIn: Sequelize.Op.notIn,
|
||||||
$is: Op.is,
|
$is: Sequelize.Op.is,
|
||||||
$like: Op.like,
|
$like: Sequelize.Op.like,
|
||||||
$notLike: Op.notLike,
|
$notLike: Sequelize.Op.notLike,
|
||||||
$iLike: Op.iLike,
|
$iLike: Sequelize.Op.iLike,
|
||||||
$notILike: Op.notILike,
|
$notILike: Sequelize.Op.notILike,
|
||||||
$regexp: Op.regexp,
|
$regexp: Sequelize.Op.regexp,
|
||||||
$notRegexp: Op.notRegexp,
|
$notRegexp: Sequelize.Op.notRegexp,
|
||||||
$iRegexp: Op.iRegexp,
|
$iRegexp: Sequelize.Op.iRegexp,
|
||||||
$notIRegexp: Op.notIRegexp,
|
$notIRegexp: Sequelize.Op.notIRegexp,
|
||||||
$between: Op.between,
|
$between: Sequelize.Op.between,
|
||||||
$notBetween: Op.notBetween,
|
$notBetween: Sequelize.Op.notBetween,
|
||||||
$overlap: Op.overlap,
|
$overlap: Sequelize.Op.overlap,
|
||||||
$contains: Op.contains,
|
$contains: Sequelize.Op.contains,
|
||||||
$contained: Op.contained,
|
$contained: Sequelize.Op.contained,
|
||||||
$adjacent: Op.adjacent,
|
$adjacent: Sequelize.Op.adjacent,
|
||||||
$strictLeft: Op.strictLeft,
|
$strictLeft: Sequelize.Op.strictLeft,
|
||||||
$strictRight: Op.strictRight,
|
$strictRight: Sequelize.Op.strictRight,
|
||||||
$noExtendRight: Op.noExtendRight,
|
$noExtendRight: Sequelize.Op.noExtendRight,
|
||||||
$noExtendLeft: Op.noExtendLeft,
|
$noExtendLeft: Sequelize.Op.noExtendLeft,
|
||||||
$and: Op.and,
|
$and: Sequelize.Op.and,
|
||||||
$or: Op.or,
|
$or: Sequelize.Op.or,
|
||||||
$any: Op.any,
|
$any: Sequelize.Op.any,
|
||||||
$all: Op.all,
|
$all: Sequelize.Op.all,
|
||||||
$values: Op.values,
|
$values: Sequelize.Op.values,
|
||||||
$col: Op.col
|
$col: Sequelize.Op.col
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PostgreSQL DB module
|
* PostgreSQL DB module
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
Sequelize,
|
Sequelize,
|
||||||
Op: Sequelize.Op,
|
Op: Sequelize.Op,
|
||||||
|
|
||||||
@@ -59,12 +57,11 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
init() {
|
init() {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
|
let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
|
||||||
|
|
||||||
// Define Sequelize instance
|
// 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,
|
host: wiki.config.db.host,
|
||||||
port: wiki.config.db.port,
|
port: wiki.config.db.port,
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
@@ -79,7 +76,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Attempt to connect and authenticate to DB
|
// Attempt to connect and authenticate to DB
|
||||||
|
|
||||||
self.inst.authenticate().then(() => {
|
this.inst.authenticate().then(() => {
|
||||||
wiki.logger.info('Database (PostgreSQL) connection: [ OK ]')
|
wiki.logger.info('Database (PostgreSQL) connection: [ OK ]')
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
wiki.logger.error('Failed to connect to PostgreSQL instance.')
|
wiki.logger.error('Failed to connect to PostgreSQL instance.')
|
||||||
@@ -128,9 +125,8 @@ module.exports = {
|
|||||||
|
|
||||||
// Perform init tasks
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
'use strict'
|
const _ = require('lodash')
|
||||||
|
const fs = require('fs')
|
||||||
|
const gqlTools = require('graphql-tools')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
/* global wiki */
|
/* global wiki */
|
||||||
|
|
||||||
const gqlTools = require('graphql-tools')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
|
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
|
||||||
|
|
||||||
const DateScalar = require('../schemas/scalar-date')
|
const DateScalar = require('../schemas/scalar-date')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
const cluster = require('cluster')
|
const cluster = require('cluster')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
/* global wiki */
|
/* global wiki */
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const dotize = require('dotize')
|
const dotize = require('dotize')
|
||||||
const i18nBackend = require('i18next-node-fs-backend')
|
const i18nBackend = require('i18next-node-fs-backend')
|
||||||
|
const i18nMW = require('i18next-express-middleware')
|
||||||
const i18next = require('i18next')
|
const i18next = require('i18next')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
@@ -27,6 +28,9 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
attachMiddleware (app) {
|
||||||
|
app.use(i18nMW.handle(this.engine))
|
||||||
|
},
|
||||||
async getByNamespace(locale, namespace) {
|
async getByNamespace(locale, namespace) {
|
||||||
if (this.engine.hasResourceBundle(locale, namespace)) {
|
if (this.engine.hasResourceBundle(locale, namespace)) {
|
||||||
let data = this.engine.getResourceBundle(locale, namespace)
|
let data = this.engine.getResourceBundle(locale, namespace)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/* global wiki */
|
|
||||||
|
|
||||||
const cluster = require('cluster')
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
const cluster = require('cluster')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loggers: {},
|
loggers: {},
|
||||||
init() {
|
init() {
|
||||||
@@ -21,7 +21,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
_.forOwn(_.omitBy(wiki.config.logging.loggers, s => s.enabled === false), (loggerConfig, loggerKey) => {
|
_.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)
|
loggerModule.init(logger, loggerConfig)
|
||||||
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => {
|
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => {
|
||||||
logger.icon = iconData
|
logger.icon = iconData
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global wiki */
|
|
||||||
|
|
||||||
const Bull = require('bull')
|
const Bull = require('bull')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init() {
|
init() {
|
||||||
wiki.data.queues.forEach(queueName => {
|
wiki.data.queues.forEach(queueName => {
|
||||||
@@ -1,22 +1,9 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global wiki */
|
|
||||||
|
|
||||||
const Redis = require('ioredis')
|
const Redis = require('ioredis')
|
||||||
const { isPlainObject } = require('lodash')
|
const { isPlainObject } = require('lodash')
|
||||||
|
|
||||||
/**
|
/* global wiki */
|
||||||
* Redis module
|
|
||||||
*
|
|
||||||
* @return {Object} Redis client wrapper instance
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
module.exports = {
|
||||||
* Initialize Redis client
|
|
||||||
*
|
|
||||||
* @return {Object} Redis client instance
|
|
||||||
*/
|
|
||||||
init() {
|
init() {
|
||||||
if (isPlainObject(wiki.config.redis)) {
|
if (isPlainObject(wiki.config.redis)) {
|
||||||
let red = new Redis(wiki.config.redis)
|
let red = new Redis(wiki.config.redis)
|
||||||
@@ -33,5 +20,4 @@ module.exports = {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
/* global wiki */
|
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
|
||||||
// const pm2 = Promise.promisifyAll(require('pm2'))
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const cfgHelper = require('../helpers/config')
|
const cfgHelper = require('../helpers/config')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const bugsnag = require('bugsnag')
|
const bugsnag = require('bugsnag')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const uuid = require('uuid/v4')
|
const uuid = require('uuid/v4')
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
/* global wiki */
|
/* global wiki */
|
||||||
|
|
||||||
+5
-5
@@ -12,8 +12,8 @@ let wiki = {
|
|||||||
ROOTPATH: process.cwd(),
|
ROOTPATH: process.cwd(),
|
||||||
SERVERPATH: path.join(process.cwd(), 'server'),
|
SERVERPATH: path.join(process.cwd(), 'server'),
|
||||||
Error: require('./helpers/error'),
|
Error: require('./helpers/error'),
|
||||||
configSvc: require('./modules/config'),
|
configSvc: require('./core/config'),
|
||||||
kernel: require('./modules/kernel')
|
kernel: require('./core/kernel')
|
||||||
}
|
}
|
||||||
global.wiki = wiki
|
global.wiki = wiki
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ wiki.configSvc.init()
|
|||||||
// Init Logger
|
// Init Logger
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
wiki.logger = require('./modules/logger').init()
|
wiki.logger = require('./core/logger').init()
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Init Telemetry
|
// Init Telemetry
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
wiki.telemetry = require('./modules/telemetry').init()
|
wiki.telemetry = require('./core/telemetry').init()
|
||||||
|
|
||||||
process.on('unhandledRejection', (err) => {
|
process.on('unhandledRejection', (err) => {
|
||||||
wiki.telemetry.sendError(err)
|
wiki.telemetry.sendError(err)
|
||||||
@@ -46,7 +46,7 @@ process.on('uncaughtException', (err) => {
|
|||||||
// Init DB
|
// Init DB
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
wiki.db = require('./modules/db').init()
|
wiki.db = require('./core/db').init()
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start Kernel
|
// Start Kernel
|
||||||
|
|||||||
+25
-26
@@ -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 */
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load global modules
|
// Load core modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
wiki.auth = require('./modules/auth').init()
|
wiki.auth = require('./core/auth').init()
|
||||||
wiki.disk = require('./modules/disk').init()
|
wiki.lang = require('./core/localization').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()
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// 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 mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
|
||||||
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
|
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
|
||||||
|
|
||||||
@@ -97,13 +91,18 @@ module.exports = async () => {
|
|||||||
app.use(bodyParser.json({ limit: '1mb' }))
|
app.use(bodyParser.json({ limit: '1mb' }))
|
||||||
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
|
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Localization
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
wiki.lang.attachMiddleware(app)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// View accessible data
|
// View accessible data
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.locals.basedir = wiki.ROOTPATH
|
app.locals.basedir = wiki.ROOTPATH
|
||||||
app.locals._ = require('lodash')
|
app.locals._ = require('lodash')
|
||||||
app.locals.t = wiki.lang.engine.t.bind(wiki.lang)
|
|
||||||
app.locals.moment = require('moment')
|
app.locals.moment = require('moment')
|
||||||
app.locals.moment.locale(wiki.config.site.lang)
|
app.locals.moment.locale(wiki.config.site.lang)
|
||||||
app.locals.config = wiki.config
|
app.locals.config = wiki.config
|
||||||
@@ -157,7 +156,7 @@ module.exports = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start HTTP server
|
// HTTP server
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
let srvConnections = {}
|
let srvConnections = {}
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File renamed without changes.
File renamed without changes.
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
@@ -8,7 +8,7 @@ module.exports = () => {
|
|||||||
title: 'Wiki.js'
|
title: 'Wiki.js'
|
||||||
}
|
}
|
||||||
|
|
||||||
wiki.system = require('./modules/system')
|
wiki.system = require('./core/system')
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Load modules
|
||||||
@@ -62,13 +62,8 @@ module.exports = () => {
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
if (global.DEV) {
|
if (global.DEV) {
|
||||||
const webpackDevMiddleware = require('webpack-dev-middleware')
|
app.use(global.WP_DEV.devMiddleware)
|
||||||
const webpackHotMiddleware = require('webpack-hot-middleware')
|
app.use(global.WP_DEV.hotMiddleware)
|
||||||
app.use(webpackDevMiddleware(global.WP, {
|
|
||||||
publicPath: global.WPCONFIG.output.publicPath,
|
|
||||||
logger: wiki.logger
|
|
||||||
}))
|
|
||||||
app.use(webpackHotMiddleware(global.WP))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@@ -77,7 +72,7 @@ module.exports = () => {
|
|||||||
|
|
||||||
app.get('*', async (req, res) => {
|
app.get('*', async (req, res) => {
|
||||||
let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json'))
|
let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json'))
|
||||||
res.render('pages/setup', {
|
res.render('main/setup', {
|
||||||
packageObj,
|
packageObj,
|
||||||
telemetryClientID: wiki.telemetry.cid
|
telemetryClientID: wiki.telemetry.cid
|
||||||
})
|
})
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,11 +2,10 @@ extends ./master.pug
|
|||||||
|
|
||||||
block body
|
block body
|
||||||
body
|
body
|
||||||
#app.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt])
|
#app
|
||||||
include ./common/header.pug
|
navigator
|
||||||
alert
|
|
||||||
main
|
main
|
||||||
block content
|
block content
|
||||||
include ./common/footer.pug
|
footer
|
||||||
|
|
||||||
block outside
|
block outside
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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')
|
||||||
@@ -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
@@ -1,7 +1,7 @@
|
|||||||
/* global wiki */
|
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
module.exports = Promise.join(
|
module.exports = Promise.join(
|
||||||
wiki.db.onReady,
|
wiki.db.onReady,
|
||||||
wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads'])
|
wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads'])
|
||||||
@@ -16,11 +16,7 @@ module.exports = Promise.join(
|
|||||||
// Load global modules
|
// 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.lang = require('i18next')
|
||||||
wiki.mark = require('./modules/markdown')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Localization Engine
|
// Localization Engine
|
||||||
@@ -36,7 +32,7 @@ module.exports = Promise.join(
|
|||||||
lng: wiki.config.lang,
|
lng: wiki.config.lang,
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
|
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.yml')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user