Standard JS code conversion + fixes

This commit is contained in:
NGPixel
2017-02-08 20:52:37 -05:00
parent a508b2a7f4
commit 414dc386d6
54 changed files with 4022 additions and 4288 deletions
+24
View File
@@ -0,0 +1,24 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Change log
### Fixed
- Fixed issue with social accounts with empty name
### Changed
- Updated dependencies + snyk policy
- Conversion to Standard JS compliant code
## [v1.0-beta.2] - 2017-01-30
### Added
- Save own profile under My Account
### Changed
- Updated dependencies + snyk policy
[Unreleased]: https://github.com/Requarks/wiki/compare/v1.0-beta.2...HEAD
[v1.0-beta.2]: https://github.com/Requarks/wiki/releases/tag/v1.0-beta.2
+74 -93
View File
@@ -4,75 +4,72 @@
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
global.PROCNAME = 'AGENT'; global.PROCNAME = 'AGENT'
global.ROOTPATH = __dirname; global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'; global.IS_DEBUG = process.env.NODE_ENV === 'development'
if (IS_DEBUG) { if (IS_DEBUG) {
global.CORE_PATH = ROOTPATH + '/../core/'; global.CORE_PATH = ROOTPATH + '/../core/'
} else { } else {
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'; global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
} }
// ---------------------------------------- // ----------------------------------------
// Load Winston // Load Winston
// ---------------------------------------- // ----------------------------------------
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG); global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
winston.info('[AGENT] Background Agent is initializing...'); winston.info('[AGENT] Background Agent is initializing...')
let appconf = require(CORE_PATH + 'core-libs/config')(); let appconf = require(CORE_PATH + 'core-libs/config')()
global.appconfig = appconf.config; global.appconfig = appconf.config
global.appdata = appconf.data; global.appdata = appconf.data
global.db = require(CORE_PATH + 'core-libs/mongodb').init(); global.db = require(CORE_PATH + 'core-libs/mongodb').init()
global.upl = require('./libs/uploads-agent').init(); global.upl = require('./libs/uploads-agent').init()
global.git = require('./libs/git').init(); global.git = require('./libs/git').init()
global.entries = require('./libs/entries').init(); global.entries = require('./libs/entries').init()
global.mark = require('./libs/markdown'); global.mark = require('./libs/markdown')
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
// ---------------------------------------- // ----------------------------------------
var _ = require('lodash'); var moment = require('moment')
var moment = require('moment'); var Promise = require('bluebird')
var Promise = require('bluebird'); var fs = Promise.promisifyAll(require('fs-extra'))
var fs = Promise.promisifyAll(require("fs-extra")); var klaw = require('klaw')
var klaw = require('klaw'); var path = require('path')
var path = require('path'); var Cron = require('cron').CronJob
var cron = require('cron').CronJob;
// ---------------------------------------- // ----------------------------------------
// Start Cron // Start Cron
// ---------------------------------------- // ----------------------------------------
var jobIsBusy = false; var jobIsBusy = false
var jobUplWatchStarted = false; var jobUplWatchStarted = false
var job = new cron({ var job = new Cron({
cronTime: '0 */5 * * * *', cronTime: '0 */5 * * * *',
onTick: () => { onTick: () => {
// Make sure we don't start two concurrent jobs // Make sure we don't start two concurrent jobs
if (jobIsBusy) { if (jobIsBusy) {
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)'); winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
return; return
} }
winston.info('[AGENT] Running all jobs...'); winston.info('[AGENT] Running all jobs...')
jobIsBusy = true; jobIsBusy = true
// Prepare async job collector // Prepare async job collector
let jobs = []; let jobs = []
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo); let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data); let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
let uploadsPath = path.join(repoPath, 'uploads'); let uploadsTempPath = path.join(dataPath, 'temp-upload')
let uploadsTempPath = path.join(dataPath, 'temp-upload');
// ---------------------------------------- // ----------------------------------------
// REGULAR JOBS // REGULAR JOBS
@@ -84,61 +81,52 @@ var job = new cron({
jobs.push(git.onReady.then(() => { jobs.push(git.onReady.then(() => {
return git.resync().then(() => { return git.resync().then(() => {
// -> Stream all documents // -> Stream all documents
let cacheJobs = []; let cacheJobs = []
let jobCbStreamDocs_resolve = null, let jobCbStreamDocsResolve = null
jobCbStreamDocs = new Promise((resolve, reject) => { let jobCbStreamDocs = new Promise((resolve, reject) => {
jobCbStreamDocs_resolve = resolve; jobCbStreamDocsResolve = resolve
}); })
klaw(repoPath).on('data', function (item) { klaw(repoPath).on('data', function (item) {
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') { if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path))
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path)); let cachePath = entries.getCachePath(entryPath)
let cachePath = entries.getCachePath(entryPath);
// -> Purge outdated cache // -> Purge outdated cache
cacheJobs.push( cacheJobs.push(
fs.statAsync(cachePath).then((st) => { fs.statAsync(cachePath).then((st) => {
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'; return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
}).catch((err) => { }).catch((err) => {
return (err.code !== 'EEXIST') ? err : 'new'; return (err.code !== 'EEXIST') ? err : 'new'
}).then((fileStatus) => { }).then((fileStatus) => {
// -> Delete expired cache file // -> Delete expired cache file
if (fileStatus === 'expired') { if (fileStatus === 'expired') {
return fs.unlinkAsync(cachePath).return(fileStatus); return fs.unlinkAsync(cachePath).return(fileStatus)
} }
return fileStatus; return fileStatus
}).then((fileStatus) => { }).then((fileStatus) => {
// -> Update cache and search index // -> Update cache and search index
if (fileStatus !== 'active') { if (fileStatus !== 'active') {
return entries.updateCache(entryPath); return entries.updateCache(entryPath)
} }
return true; return true
}) })
)
);
} }
}).on('end', () => { }).on('end', () => {
jobCbStreamDocs_resolve(Promise.all(cacheJobs)); jobCbStreamDocsResolve(Promise.all(cacheJobs))
}); })
return jobCbStreamDocs; return jobCbStreamDocs
})
}); }))
}));
//* **************************************** //* ****************************************
// -> Clear failed temporary upload files // -> Clear failed temporary upload files
@@ -146,65 +134,58 @@ var job = new cron({
jobs.push( jobs.push(
fs.readdirAsync(uploadsTempPath).then((ls) => { fs.readdirAsync(uploadsTempPath).then((ls) => {
let fifteenAgo = moment().subtract(15, 'minutes')
let fifteenAgo = moment().subtract(15, 'minutes');
return Promise.map(ls, (f) => { return Promise.map(ls, (f) => {
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s }; }); return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isFile(); }).then((arrFiles) => { }).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
return Promise.map(arrFiles, (f) => { return Promise.map(arrFiles, (f) => {
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) { if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename)); return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
} else { } else {
return true; return true
} }
});
});
}) })
); })
})
)
// ---------------------------------------- // ----------------------------------------
// Run // Run
// ---------------------------------------- // ----------------------------------------
Promise.all(jobs).then(() => { Promise.all(jobs).then(() => {
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.'); winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
if (!jobUplWatchStarted) { if (!jobUplWatchStarted) {
jobUplWatchStarted = true; jobUplWatchStarted = true
upl.initialScan().then(() => { upl.initialScan().then(() => {
job.start(); job.start()
}); })
} }
return true; return true
}).catch((err) => { }).catch((err) => {
winston.error('[AGENT] One or more jobs have failed: ', err); winston.error('[AGENT] One or more jobs have failed: ', err)
}).finally(() => { }).finally(() => {
jobIsBusy = false; jobIsBusy = false
}); })
}, },
start: false, start: false,
timeZone: 'UTC', timeZone: 'UTC',
runOnInit: true runOnInit: true
}); })
// ---------------------------------------- // ----------------------------------------
// Shutdown gracefully // Shutdown gracefully
// ---------------------------------------- // ----------------------------------------
process.on('disconnect', () => { process.on('disconnect', () => {
winston.warn('[AGENT] Lost connection to main server. Exiting...'); winston.warn('[AGENT] Lost connection to main server. Exiting...')
job.stop(); job.stop()
process.exit(); process.exit()
}); })
process.on('exit', () => { process.on('exit', () => {
job.stop(); job.stop()
}); })
+21 -1
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
File diff suppressed because one or more lines are too long
+25 -25
View File
File diff suppressed because one or more lines are too long
+13 -15
View File
@@ -1,7 +1,6 @@
"use strict"; 'use strict'
jQuery(document).ready(function ($) { jQuery(document).ready(function ($) {
// ==================================== // ====================================
// Scroll // Scroll
// ==================================== // ====================================
@@ -9,35 +8,35 @@ jQuery( document ).ready(function( $ ) {
$('a').smoothScroll({ $('a').smoothScroll({
speed: 400, speed: 400,
offset: -70 offset: -70
}); })
var sticky = new Sticky('.stickyscroll'); var sticky = new Sticky('.stickyscroll')
// ==================================== // ====================================
// Notifications // Notifications
// ==================================== // ====================================
$(window).bind('beforeunload', () => { $(window).bind('beforeunload', () => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}); })
$(document).ajaxSend(() => { $(document).ajaxSend(() => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}).ajaxComplete(() => { }).ajaxComplete(() => {
$('#notifload').removeClass('active'); $('#notifload').removeClass('active')
}); })
var alerts = new Alerts(); var alerts = new Alerts()
if (alertsData) { if (alertsData) {
_.forEach(alertsData, (alertRow) => { _.forEach(alertsData, (alertRow) => {
alerts.push(alertRow); alerts.push(alertRow)
}); })
} }
// ==================================== // ====================================
// Establish WebSocket connection // Establish WebSocket connection
// ==================================== // ====================================
var socket = io(window.location.origin); var socket = io(window.location.origin)
// =include components/search.js // =include components/search.js
@@ -50,8 +49,7 @@ jQuery( document ).ready(function( $ ) {
// =include pages/edit.js // =include pages/edit.js
// =include pages/source.js // =include pages/source.js
// =include pages/admin.js // =include pages/admin.js
})
});
// =include helpers/form.js // =include helpers/form.js
// =include helpers/pages.js // =include helpers/pages.js
+21 -27
View File
@@ -1,4 +1,4 @@
"use strict"; 'use strict'
/** /**
* Alerts * Alerts
@@ -11,8 +11,7 @@ class Alerts {
* @class * @class
*/ */
constructor () { constructor () {
let self = this
let self = this;
self.mdl = new Vue({ self.mdl = new Vue({
el: '#alerts', el: '#alerts',
@@ -21,13 +20,12 @@ class Alerts {
}, },
methods: { methods: {
acknowledge: (uid) => { acknowledge: (uid) => {
self.close(uid); self.close(uid)
} }
} }
}); })
self.uidNext = 1;
self.uidNext = 1
} }
/** /**
@@ -37,8 +35,7 @@ class Alerts {
* @return {null} Void * @return {null} Void
*/ */
push (options) { push (options) {
let self = this
let self = this;
let nAlert = _.defaults(options, { let nAlert = _.defaults(options, {
_uid: self.uidNext, _uid: self.uidNext,
@@ -46,18 +43,17 @@ class Alerts {
message: '---', message: '---',
sticky: false, sticky: false,
title: '---' title: '---'
}); })
self.mdl.children.push(nAlert); self.mdl.children.push(nAlert)
if (!nAlert.sticky) { if (!nAlert.sticky) {
_.delay(() => { _.delay(() => {
self.close(nAlert._uid); self.close(nAlert._uid)
}, 5000); }, 5000)
} }
self.uidNext++; self.uidNext++
} }
/** /**
@@ -72,7 +68,7 @@ class Alerts {
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
@@ -87,7 +83,7 @@ class Alerts {
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
@@ -96,20 +92,18 @@ class Alerts {
* @param {Integer} uid The unique ID of the alert * @param {Integer} uid The unique ID of the alert
*/ */
close (uid) { close (uid) {
let self = this
let self = this; let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid])
let nAlert = _.nth(self.mdl.children, nAlertIdx)
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid]);
let nAlert = _.nth(self.mdl.children, nAlertIdx);
if (nAlertIdx >= 0 && nAlert) { if (nAlertIdx >= 0 && nAlert) {
nAlert.class += ' exit'; nAlert.class += ' exit'
Vue.set(self.mdl.children, nAlertIdx, nAlert); Vue.set(self.mdl.children, nAlertIdx, nAlert)
_.delay(() => { _.delay(() => {
self.mdl.children.splice(nAlertIdx, 1); self.mdl.children.splice(nAlertIdx, 1)
}, 500); }, 500)
} }
} }
} }
+30 -34
View File
@@ -1,25 +1,25 @@
let modelist = ace.require("ace/ext/modelist"); let modelist = ace.require('ace/ext/modelist')
let codeEditor = null; let codeEditor = null
// ACE - Mode Loader // ACE - Mode Loader
let modelistLoaded = []; let modelistLoaded = []
let loadAceMode = (m) => { let loadAceMode = (m) => {
return $.ajax({ return $.ajax({
url: '/js/ace/mode-' + m + '.js', url: '/js/ace/mode-' + m + '.js',
dataType: "script", dataType: 'script',
cache: true, cache: true,
beforeSend: () => { beforeSend: () => {
if (_.includes(modelistLoaded, m)) { if (_.includes(modelistLoaded, m)) {
return false; return false
} }
}, },
success: () => { success: () => {
modelistLoaded.push(m); modelistLoaded.push(m)
}
})
} }
});
};
// Vue Code Block instance // Vue Code Block instance
@@ -33,46 +33,42 @@ let vueCodeBlock = new Vue({
watch: { watch: {
modeSelected: (val, oldVal) => { modeSelected: (val, oldVal) => {
loadAceMode(val).done(() => { loadAceMode(val).done(() => {
ace.require("ace/mode/" + val); ace.require('ace/mode/' + val)
codeEditor.getSession().setMode("ace/mode/" + val); codeEditor.getSession().setMode('ace/mode/' + val)
}); })
} }
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active')
$('#modal-editor-codeblock').addClass('is-active');
_.delay(() => { _.delay(() => {
codeEditor = ace.edit("codeblock-editor"); codeEditor = ace.edit('codeblock-editor')
codeEditor.setTheme("ace/theme/tomorrow_night"); codeEditor.setTheme('ace/theme/tomorrow_night')
codeEditor.getSession().setMode("ace/mode/" + vueCodeBlock.modeSelected); codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
codeEditor.setOption('fontSize', '14px'); codeEditor.setOption('fontSize', '14px')
codeEditor.setOption('hScrollBarAlwaysVisible', false); codeEditor.setOption('hScrollBarAlwaysVisible', false)
codeEditor.setOption('wrap', true); codeEditor.setOption('wrap', true)
codeEditor.setValue(vueCodeBlock.initContent); codeEditor.setValue(vueCodeBlock.initContent)
codeEditor.focus();
codeEditor.renderer.updateFull();
}, 300);
codeEditor.focus()
codeEditor.renderer.updateFull()
}, 300)
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-codeblock').removeClass('is-active'); $('#modal-editor-codeblock').removeClass('is-active')
vueCodeBlock.initContent = ''; vueCodeBlock.initContent = ''
}, },
insertCode: (ev) => { insertCode: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection'); mde.codemirror.execCommand('singleSelection')
} }
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'; let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
mde.codemirror.doc.replaceSelection(codeBlockText);
vueCodeBlock.cancel();
mde.codemirror.doc.replaceSelection(codeBlockText)
vueCodeBlock.cancel()
} }
} }
}); })
+134 -148
View File
@@ -23,13 +23,13 @@ let vueFile = new Vue({
methods: { methods: {
open: () => { open: () => {
mdeModalOpenState = true; mdeModalOpenState = true
$('#modal-editor-file').addClass('is-active'); $('#modal-editor-file').addClass('is-active')
vueFile.refreshFolders(); vueFile.refreshFolders()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-file').removeClass('is-active'); $('#modal-editor-file').removeClass('is-active')
}, },
// ------------------------------------------- // -------------------------------------------
@@ -37,23 +37,21 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
selectFile: (fileId) => { selectFile: (fileId) => {
vueFile.currentFile = fileId; vueFile.currentFile = fileId
}, },
insertFileLink: (ev) => { insertFileLink: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection'); mde.codemirror.execCommand('singleSelection')
} }
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile]); let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename; selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
selFile.titleGuess = _.startCase(selFile.basename); selFile.titleGuess = _.startCase(selFile.basename)
let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'; let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
mde.codemirror.doc.replaceSelection(fileText);
vueFile.cancel();
mde.codemirror.doc.replaceSelection(fileText)
vueFile.cancel()
}, },
// ------------------------------------------- // -------------------------------------------
@@ -61,37 +59,35 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
newFolder: (ev) => { newFolder: (ev) => {
vueFile.newFolderName = ''; vueFile.newFolderName = ''
vueFile.newFolderError = false; vueFile.newFolderError = false
vueFile.newFolderShow = true; vueFile.newFolderShow = true
_.delay(() => { $('#txt-editor-file-newfoldername').focus(); }, 400); _.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
}, },
newFolderDiscard: (ev) => { newFolderDiscard: (ev) => {
vueFile.newFolderShow = false; vueFile.newFolderShow = false
}, },
newFolderCreate: (ev) => { newFolderCreate: (ev) => {
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) { if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
vueFile.newFolderError = true; vueFile.newFolderError = true
return; return
} }
vueFile.newFolderDiscard(); vueFile.newFolderDiscard()
vueFile.isLoadingText = 'Creating new folder...'; vueFile.isLoadingText = 'Creating new folder...'
vueFile.isLoading = true; vueFile.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => { socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
vueFile.folders = data; vueFile.folders = data
vueFile.currentFolder = vueFile.newFolderName; vueFile.currentFolder = vueFile.newFolderName
vueFile.files = []; vueFile.files = []
vueFile.isLoading = false; vueFile.isLoading = false
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -99,35 +95,32 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
renameFile: () => { renameFile: () => {
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ])
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]); vueFile.renameFileFilename = c.basename || ''
vueFile.renameFileFilename = c.basename || ''; vueFile.renameFileShow = true
vueFile.renameFileShow = true;
_.delay(() => { _.delay(() => {
$('#txt-editor-renamefile').focus(); $('#txt-editor-renamefile').focus()
_.defer(() => { $('#txt-editor-file-rename').select(); }); _.defer(() => { $('#txt-editor-file-rename').select() })
}, 400); }, 400)
}, },
renameFileDiscard: () => { renameFileDiscard: () => {
vueFile.renameFileShow = false; vueFile.renameFileShow = false
}, },
renameFileGo: () => { renameFileGo: () => {
vueFile.renameFileDiscard()
vueFile.renameFileDiscard(); vueFile.isLoadingText = 'Renaming file...'
vueFile.isLoadingText = 'Renaming file...'; vueFile.isLoading = true
vueFile.isLoading = true;
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => { socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
if (data.ok) { if (data.ok) {
vueFile.waitChangeComplete(vueFile.files.length, false); vueFile.waitChangeComplete(vueFile.files.length, false)
} else { } else {
vueFile.isLoading = false; vueFile.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -135,18 +128,18 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
moveFile: (uid, fld) => { moveFile: (uid, fld) => {
vueFile.isLoadingText = 'Moving file...'; vueFile.isLoadingText = 'Moving file...'
vueFile.isLoading = true; vueFile.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
if (data.ok) { if (data.ok) {
vueFile.loadFiles(); vueFile.loadFiles()
} else { } else {
vueFile.isLoading = false; vueFile.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -155,20 +148,20 @@ let vueFile = new Vue({
deleteFileWarn: (show) => { deleteFileWarn: (show) => {
if (show) { if (show) {
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ]); let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ])
vueFile.deleteFileFilename = c.filename || 'this file'; vueFile.deleteFileFilename = c.filename || 'this file'
} }
vueFile.deleteFileShow = show; vueFile.deleteFileShow = show
}, },
deleteFileGo: () => { deleteFileGo: () => {
vueFile.deleteFileWarn(false); vueFile.deleteFileWarn(false)
vueFile.isLoadingText = 'Deleting file...'; vueFile.isLoadingText = 'Deleting file...'
vueFile.isLoading = true; vueFile.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => { socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
vueFile.loadFiles(); vueFile.loadFiles()
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -176,66 +169,64 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
selectFolder: (fldName) => { selectFolder: (fldName) => {
vueFile.currentFolder = fldName; vueFile.currentFolder = fldName
vueFile.loadFiles(); vueFile.loadFiles()
}, },
refreshFolders: () => { refreshFolders: () => {
vueFile.isLoadingText = 'Fetching folders list...'; vueFile.isLoadingText = 'Fetching folders list...'
vueFile.isLoading = true; vueFile.isLoading = true
vueFile.currentFolder = ''; vueFile.currentFolder = ''
vueFile.currentImage = ''; vueFile.currentImage = ''
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => { socket.emit('uploadsGetFolders', { }, (data) => {
vueFile.folders = data; vueFile.folders = data
vueFile.loadFiles(); vueFile.loadFiles()
}); })
}); })
}, },
loadFiles: (silent) => { loadFiles: (silent) => {
if (!silent) { if (!silent) {
vueFile.isLoadingText = 'Fetching files...'; vueFile.isLoadingText = 'Fetching files...'
vueFile.isLoading = true; vueFile.isLoading = true
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => { socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
vueFile.files = data; vueFile.files = data
if (!silent) { if (!silent) {
vueFile.isLoading = false; vueFile.isLoading = false
} }
vueFile.attachContextMenus(); vueFile.attachContextMenus()
resolve(true); resolve(true)
}); })
}); })
}); })
}, },
waitChangeComplete: (oldAmount, expectChange) => { waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
expectChange = (_.isBoolean(expectChange)) ? expectChange : true; vueFile.postUploadChecks++
vueFile.isLoadingText = 'Processing...'
vueFile.postUploadChecks++;
vueFile.isLoadingText = 'Processing...';
Vue.nextTick(() => { Vue.nextTick(() => {
vueFile.loadFiles(true).then(() => { vueFile.loadFiles(true).then(() => {
if ((vueFile.files.length !== oldAmount) === expectChange) { if ((vueFile.files.length !== oldAmount) === expectChange) {
vueFile.postUploadChecks = 0; vueFile.postUploadChecks = 0
vueFile.isLoading = false; vueFile.isLoading = false
} else if (vueFile.postUploadChecks > 5) { } else if (vueFile.postUploadChecks > 5) {
vueFile.postUploadChecks = 0; vueFile.postUploadChecks = 0
vueFile.isLoading = false; vueFile.isLoading = false
alerts.pushError('Unable to fetch updated listing', 'Try again later'); alerts.pushError('Unable to fetch updated listing', 'Try again later')
} else { } else {
_.delay(() => { _.delay(() => {
vueFile.waitChangeComplete(oldAmount, expectChange); vueFile.waitChangeComplete(oldAmount, expectChange)
}, 1500); }, 1500)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -243,68 +234,66 @@ let vueFile = new Vue({
// ------------------------------------------- // -------------------------------------------
attachContextMenus: () => { attachContextMenus: () => {
let moveFolders = _.map(vueFile.folders, (f) => { let moveFolders = _.map(vueFile.folders, (f) => {
return { return {
name: (f !== '') ? f : '/ (root)', name: (f !== '') ? f : '/ (root)',
icon: 'fa-folder', icon: 'fa-folder',
callback: (key, opt) => { callback: (key, opt) => {
let moveFileId = _.toString($(opt.$trigger).data('uid')); let moveFileId = _.toString($(opt.$trigger).data('uid'))
let moveFileDestFolder = _.nth(vueFile.folders, key); let moveFileDestFolder = _.nth(vueFile.folders, key)
vueFile.moveFile(moveFileId, moveFileDestFolder); vueFile.moveFile(moveFileId, moveFileDestFolder)
} }
}; }
}); })
$.contextMenu('destroy', '.editor-modal-file-choices > figure'); $.contextMenu('destroy', '.editor-modal-file-choices > figure')
$.contextMenu({ $.contextMenu({
selector: '.editor-modal-file-choices > figure', selector: '.editor-modal-file-choices > figure',
appendTo: '.editor-modal-file-choices', appendTo: '.editor-modal-file-choices',
position: (opt, x, y) => { position: (opt, x, y) => {
$(opt.$trigger).addClass('is-contextopen'); $(opt.$trigger).addClass('is-contextopen')
let trigPos = $(opt.$trigger).position(); let trigPos = $(opt.$trigger).position()
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }; let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w }); opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
}, },
events: { events: {
hide: (opt) => { hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen'); $(opt.$trigger).removeClass('is-contextopen')
} }
}, },
items: { items: {
rename: { rename: {
name: "Rename", name: 'Rename',
icon: "fa-edit", icon: 'fa-edit',
callback: (key, opt) => { callback: (key, opt) => {
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid); vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
vueFile.renameFile(); vueFile.renameFile()
} }
}, },
move: { move: {
name: "Move to...", name: 'Move to...',
icon: "fa-folder-open-o", icon: 'fa-folder-open-o',
items: moveFolders items: moveFolders
}, },
delete: { delete: {
name: "Delete", name: 'Delete',
icon: "fa-trash", icon: 'fa-trash',
callback: (key, opt) => { callback: (key, opt) => {
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid); vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
vueFile.deleteFileWarn(true); vueFile.deleteFileWarn(true)
} }
} }
} }
}); })
} }
} }
}); })
$('#btn-editor-file-upload input').on('change', (ev) => { $('#btn-editor-file-upload input').on('change', (ev) => {
let curFileAmount = vueFile.files.length
let curFileAmount = vueFile.files.length; $(ev.currentTarget).simpleUpload('/uploads/file', {
$(ev.currentTarget).simpleUpload("/uploads/file", {
name: 'binfile', name: 'binfile',
data: { data: {
@@ -315,51 +304,48 @@ $('#btn-editor-file-upload input').on('change', (ev) => {
maxFileSize: 0, maxFileSize: 0,
init: (totalUploads) => { init: (totalUploads) => {
vueFile.uploadSucceeded = false; vueFile.uploadSucceeded = false
vueFile.isLoadingText = 'Preparing to upload...'; vueFile.isLoadingText = 'Preparing to upload...'
vueFile.isLoading = true; vueFile.isLoading = true
}, },
progress: (progress) => { progress: (progress) => {
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'; vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
}, },
success: (data) => { success: (data) => {
if (data.ok) { if (data.ok) {
let failedUpls = _.filter(data.results, ['ok', false])
let failedUpls = _.filter(data.results, ['ok', false]);
if (failedUpls.length) { if (failedUpls.length) {
_.forEach(failedUpls, (u) => { _.forEach(failedUpls, (u) => {
alerts.pushError('Upload error', u.msg); alerts.pushError('Upload error', u.msg)
}); })
if (failedUpls.length < data.results.length) { if (failedUpls.length < data.results.length) {
alerts.push({ alerts.push({
title: 'Some uploads succeeded', title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' message: 'Files that are not mentionned in the errors above were uploaded successfully.'
}); })
vueFile.uploadSucceeded = true; vueFile.uploadSucceeded = true
} }
} else { } else {
vueFile.uploadSucceeded = true; vueFile.uploadSucceeded = true
} }
} else { } else {
alerts.pushError('Upload error', data.msg); alerts.pushError('Upload error', data.msg)
} }
}, },
error: (error) => { error: (error) => {
alerts.pushError(error.message, this.upload.file.name); alerts.pushError(error.message, this.upload.file.name)
}, },
finish: () => { finish: () => {
if (vueFile.uploadSucceeded) { if (vueFile.uploadSucceeded) {
vueFile.waitChangeComplete(curFileAmount, true); vueFile.waitChangeComplete(curFileAmount, true)
} else { } else {
vueFile.isLoading = false; vueFile.isLoading = false
} }
} }
}); })
})
});
+153 -169
View File
@@ -26,13 +26,13 @@ let vueImage = new Vue({
methods: { methods: {
open: () => { open: () => {
mdeModalOpenState = true; mdeModalOpenState = true
$('#modal-editor-image').addClass('is-active'); $('#modal-editor-image').addClass('is-active')
vueImage.refreshFolders(); vueImage.refreshFolders()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-image').removeClass('is-active'); $('#modal-editor-image').removeClass('is-active')
}, },
// ------------------------------------------- // -------------------------------------------
@@ -40,34 +40,32 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
selectImage: (imageId) => { selectImage: (imageId) => {
vueImage.currentImage = imageId; vueImage.currentImage = imageId
}, },
insertImage: (ev) => { insertImage: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection'); mde.codemirror.execCommand('singleSelection')
} }
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]); let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename; selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
selImage.titleGuess = _.startCase(selImage.basename); selImage.titleGuess = _.startCase(selImage.basename)
let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'; let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
switch (vueImage.currentAlign) { switch (vueImage.currentAlign) {
case 'center': case 'center':
imageText += '{.align-center}'; imageText += '{.align-center}'
break; break
case 'right': case 'right':
imageText += '{.align-right}'; imageText += '{.align-right}'
break; break
case 'logo': case 'logo':
imageText += '{.pagelogo}'; imageText += '{.pagelogo}'
break; break
} }
mde.codemirror.doc.replaceSelection(imageText); mde.codemirror.doc.replaceSelection(imageText)
vueImage.cancel(); vueImage.cancel()
}, },
// ------------------------------------------- // -------------------------------------------
@@ -75,37 +73,35 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
newFolder: (ev) => { newFolder: (ev) => {
vueImage.newFolderName = ''; vueImage.newFolderName = ''
vueImage.newFolderError = false; vueImage.newFolderError = false
vueImage.newFolderShow = true; vueImage.newFolderShow = true
_.delay(() => { $('#txt-editor-image-newfoldername').focus(); }, 400); _.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
}, },
newFolderDiscard: (ev) => { newFolderDiscard: (ev) => {
vueImage.newFolderShow = false; vueImage.newFolderShow = false
}, },
newFolderCreate: (ev) => { newFolderCreate: (ev) => {
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) { if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
vueImage.newFolderError = true; vueImage.newFolderError = true
return; return
} }
vueImage.newFolderDiscard(); vueImage.newFolderDiscard()
vueImage.isLoadingText = 'Creating new folder...'; vueImage.isLoadingText = 'Creating new folder...'
vueImage.isLoading = true; vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => { socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
vueImage.folders = data; vueImage.folders = data
vueImage.currentFolder = vueImage.newFolderName; vueImage.currentFolder = vueImage.newFolderName
vueImage.images = []; vueImage.images = []
vueImage.isLoading = false; vueImage.isLoading = false
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -113,30 +109,28 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
fetchFromUrl: (ev) => { fetchFromUrl: (ev) => {
vueImage.fetchFromUrlURL = ''; vueImage.fetchFromUrlURL = ''
vueImage.fetchFromUrlShow = true; vueImage.fetchFromUrlShow = true
_.delay(() => { $('#txt-editor-image-fetchurl').focus(); }, 400); _.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
}, },
fetchFromUrlDiscard: (ev) => { fetchFromUrlDiscard: (ev) => {
vueImage.fetchFromUrlShow = false; vueImage.fetchFromUrlShow = false
}, },
fetchFromUrlGo: (ev) => { fetchFromUrlGo: (ev) => {
vueImage.fetchFromUrlDiscard()
vueImage.fetchFromUrlDiscard(); vueImage.isLoadingText = 'Fetching image...'
vueImage.isLoadingText = 'Fetching image...'; vueImage.isLoading = true
vueImage.isLoading = true;
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => { socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
if (data.ok) { if (data.ok) {
vueImage.waitChangeComplete(vueImage.images.length, true); vueImage.waitChangeComplete(vueImage.images.length, true)
} else { } else {
vueImage.isLoading = false; vueImage.isLoading = false
alerts.pushError('Upload error', data.msg); alerts.pushError('Upload error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -144,35 +138,32 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
renameImage: () => { renameImage: () => {
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]); vueImage.renameImageFilename = c.basename || ''
vueImage.renameImageFilename = c.basename || ''; vueImage.renameImageShow = true
vueImage.renameImageShow = true;
_.delay(() => { _.delay(() => {
$('#txt-editor-image-rename').focus(); $('#txt-editor-image-rename').focus()
_.defer(() => { $('#txt-editor-image-rename').select(); }); _.defer(() => { $('#txt-editor-image-rename').select() })
}, 400); }, 400)
}, },
renameImageDiscard: () => { renameImageDiscard: () => {
vueImage.renameImageShow = false; vueImage.renameImageShow = false
}, },
renameImageGo: () => { renameImageGo: () => {
vueImage.renameImageDiscard()
vueImage.renameImageDiscard(); vueImage.isLoadingText = 'Renaming image...'
vueImage.isLoadingText = 'Renaming image...'; vueImage.isLoading = true
vueImage.isLoading = true;
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => { socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
if (data.ok) { if (data.ok) {
vueImage.waitChangeComplete(vueImage.images.length, false); vueImage.waitChangeComplete(vueImage.images.length, false)
} else { } else {
vueImage.isLoading = false; vueImage.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -180,18 +171,18 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
moveImage: (uid, fld) => { moveImage: (uid, fld) => {
vueImage.isLoadingText = 'Moving image...'; vueImage.isLoadingText = 'Moving image...'
vueImage.isLoading = true; vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
if (data.ok) { if (data.ok) {
vueImage.loadImages(); vueImage.loadImages()
} else { } else {
vueImage.isLoading = false; vueImage.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -200,20 +191,20 @@ let vueImage = new Vue({
deleteImageWarn: (show) => { deleteImageWarn: (show) => {
if (show) { if (show) {
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ]); let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ])
vueImage.deleteImageFilename = c.filename || 'this image'; vueImage.deleteImageFilename = c.filename || 'this image'
} }
vueImage.deleteImageShow = show; vueImage.deleteImageShow = show
}, },
deleteImageGo: () => { deleteImageGo: () => {
vueImage.deleteImageWarn(false); vueImage.deleteImageWarn(false)
vueImage.isLoadingText = 'Deleting image...'; vueImage.isLoadingText = 'Deleting image...'
vueImage.isLoading = true; vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => { socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
vueImage.loadImages(); vueImage.loadImages()
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -221,66 +212,64 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
selectFolder: (fldName) => { selectFolder: (fldName) => {
vueImage.currentFolder = fldName; vueImage.currentFolder = fldName
vueImage.loadImages(); vueImage.loadImages()
}, },
refreshFolders: () => { refreshFolders: () => {
vueImage.isLoadingText = 'Fetching folders list...'; vueImage.isLoadingText = 'Fetching folders list...'
vueImage.isLoading = true; vueImage.isLoading = true
vueImage.currentFolder = ''; vueImage.currentFolder = ''
vueImage.currentImage = ''; vueImage.currentImage = ''
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => { socket.emit('uploadsGetFolders', { }, (data) => {
vueImage.folders = data; vueImage.folders = data
vueImage.loadImages(); vueImage.loadImages()
}); })
}); })
}, },
loadImages: (silent) => { loadImages: (silent) => {
if (!silent) { if (!silent) {
vueImage.isLoadingText = 'Fetching images...'; vueImage.isLoadingText = 'Fetching images...'
vueImage.isLoading = true; vueImage.isLoading = true
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => { socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
vueImage.images = data; vueImage.images = data
if (!silent) { if (!silent) {
vueImage.isLoading = false; vueImage.isLoading = false
} }
vueImage.attachContextMenus(); vueImage.attachContextMenus()
resolve(true); resolve(true)
}); })
}); })
}); })
}, },
waitChangeComplete: (oldAmount, expectChange) => { waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
expectChange = (_.isBoolean(expectChange)) ? expectChange : true; vueImage.postUploadChecks++
vueImage.isLoadingText = 'Processing...'
vueImage.postUploadChecks++;
vueImage.isLoadingText = 'Processing...';
Vue.nextTick(() => { Vue.nextTick(() => {
vueImage.loadImages(true).then(() => { vueImage.loadImages(true).then(() => {
if ((vueImage.images.length !== oldAmount) === expectChange) { if ((vueImage.images.length !== oldAmount) === expectChange) {
vueImage.postUploadChecks = 0; vueImage.postUploadChecks = 0
vueImage.isLoading = false; vueImage.isLoading = false
} else if (vueImage.postUploadChecks > 5) { } else if (vueImage.postUploadChecks > 5) {
vueImage.postUploadChecks = 0; vueImage.postUploadChecks = 0
vueImage.isLoading = false; vueImage.isLoading = false
alerts.pushError('Unable to fetch updated listing', 'Try again later'); alerts.pushError('Unable to fetch updated listing', 'Try again later')
} else { } else {
_.delay(() => { _.delay(() => {
vueImage.waitChangeComplete(oldAmount, expectChange); vueImage.waitChangeComplete(oldAmount, expectChange)
}, 1500); }, 1500)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
@@ -288,68 +277,66 @@ let vueImage = new Vue({
// ------------------------------------------- // -------------------------------------------
attachContextMenus: () => { attachContextMenus: () => {
let moveFolders = _.map(vueImage.folders, (f) => { let moveFolders = _.map(vueImage.folders, (f) => {
return { return {
name: (f !== '') ? f : '/ (root)', name: (f !== '') ? f : '/ (root)',
icon: 'fa-folder', icon: 'fa-folder',
callback: (key, opt) => { callback: (key, opt) => {
let moveImageId = _.toString($(opt.$trigger).data('uid')); let moveImageId = _.toString($(opt.$trigger).data('uid'))
let moveImageDestFolder = _.nth(vueImage.folders, key); let moveImageDestFolder = _.nth(vueImage.folders, key)
vueImage.moveImage(moveImageId, moveImageDestFolder); vueImage.moveImage(moveImageId, moveImageDestFolder)
} }
}; }
}); })
$.contextMenu('destroy', '.editor-modal-image-choices > figure'); $.contextMenu('destroy', '.editor-modal-image-choices > figure')
$.contextMenu({ $.contextMenu({
selector: '.editor-modal-image-choices > figure', selector: '.editor-modal-image-choices > figure',
appendTo: '.editor-modal-image-choices', appendTo: '.editor-modal-image-choices',
position: (opt, x, y) => { position: (opt, x, y) => {
$(opt.$trigger).addClass('is-contextopen'); $(opt.$trigger).addClass('is-contextopen')
let trigPos = $(opt.$trigger).position(); let trigPos = $(opt.$trigger).position()
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }; let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w }); opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
}, },
events: { events: {
hide: (opt) => { hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen'); $(opt.$trigger).removeClass('is-contextopen')
} }
}, },
items: { items: {
rename: { rename: {
name: "Rename", name: 'Rename',
icon: "fa-edit", icon: 'fa-edit',
callback: (key, opt) => { callback: (key, opt) => {
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid); vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
vueImage.renameImage(); vueImage.renameImage()
} }
}, },
move: { move: {
name: "Move to...", name: 'Move to...',
icon: "fa-folder-open-o", icon: 'fa-folder-open-o',
items: moveFolders items: moveFolders
}, },
delete: { delete: {
name: "Delete", name: 'Delete',
icon: "fa-trash", icon: 'fa-trash',
callback: (key, opt) => { callback: (key, opt) => {
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid); vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
vueImage.deleteImageWarn(true); vueImage.deleteImageWarn(true)
} }
} }
} }
}); })
} }
} }
}); })
$('#btn-editor-image-upload input').on('change', (ev) => { $('#btn-editor-image-upload input').on('change', (ev) => {
let curImageAmount = vueImage.images.length
let curImageAmount = vueImage.images.length; $(ev.currentTarget).simpleUpload('/uploads/img', {
$(ev.currentTarget).simpleUpload("/uploads/img", {
name: 'imgfile', name: 'imgfile',
data: { data: {
@@ -357,56 +344,53 @@ $('#btn-editor-image-upload input').on('change', (ev) => {
}, },
limit: 20, limit: 20,
expect: 'json', expect: 'json',
allowedExts: ["jpg", "jpeg", "gif", "png", "webp"], allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'], allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
maxFileSize: 3145728, // max 3 MB maxFileSize: 3145728, // max 3 MB
init: (totalUploads) => { init: (totalUploads) => {
vueImage.uploadSucceeded = false; vueImage.uploadSucceeded = false
vueImage.isLoadingText = 'Preparing to upload...'; vueImage.isLoadingText = 'Preparing to upload...'
vueImage.isLoading = true; vueImage.isLoading = true
}, },
progress: (progress) => { progress: (progress) => {
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'; vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
}, },
success: (data) => { success: (data) => {
if (data.ok) { if (data.ok) {
let failedUpls = _.filter(data.results, ['ok', false])
let failedUpls = _.filter(data.results, ['ok', false]);
if (failedUpls.length) { if (failedUpls.length) {
_.forEach(failedUpls, (u) => { _.forEach(failedUpls, (u) => {
alerts.pushError('Upload error', u.msg); alerts.pushError('Upload error', u.msg)
}); })
if (failedUpls.length < data.results.length) { if (failedUpls.length < data.results.length) {
alerts.push({ alerts.push({
title: 'Some uploads succeeded', title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' message: 'Files that are not mentionned in the errors above were uploaded successfully.'
}); })
vueImage.uploadSucceeded = true; vueImage.uploadSucceeded = true
} }
} else { } else {
vueImage.uploadSucceeded = true; vueImage.uploadSucceeded = true
} }
} else { } else {
alerts.pushError('Upload error', data.msg); alerts.pushError('Upload error', data.msg)
} }
}, },
error: (error) => { error: (error) => {
alerts.pushError(error.message, this.upload.file.name); alerts.pushError(error.message, this.upload.file.name)
}, },
finish: () => { finish: () => {
if (vueImage.uploadSucceeded) { if (vueImage.uploadSucceeded) {
vueImage.waitChangeComplete(curImageAmount, true); vueImage.waitChangeComplete(curImageAmount, true)
} else { } else {
vueImage.isLoading = false; vueImage.isLoading = false
} }
} }
}); })
})
});
+14 -16
View File
@@ -3,7 +3,7 @@ const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'), 'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), 'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i') 'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
}; }
// Vue Video instance // Vue Video instance
@@ -14,36 +14,34 @@ let vueVideo = new Vue({
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-video').addClass('is-active'); $('#modal-editor-video').addClass('is-active')
$('#modal-editor-video input').focus(); $('#modal-editor-video input').focus()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-video').removeClass('is-active'); $('#modal-editor-video').removeClass('is-active')
vueVideo.link = ''; vueVideo.link = ''
}, },
insertVideo: (ev) => { insertVideo: (ev) => {
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection'); mde.codemirror.execCommand('singleSelection')
} }
// Guess video type // Guess video type
let videoType = _.findKey(videoRules, (vr) => { let videoType = _.findKey(videoRules, (vr) => {
return vr.test(vueVideo.link); return vr.test(vueVideo.link)
}); })
if (_.isNil(videoType)) { if (_.isNil(videoType)) {
videoType = 'video'; videoType = 'video'
} }
// Insert video tag // Insert video tag
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'; let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
mde.codemirror.doc.replaceSelection(videoText);
vueVideo.cancel();
mde.codemirror.doc.replaceSelection(videoText)
vueVideo.cancel()
} }
} }
}); })
+81 -86
View File
@@ -4,13 +4,12 @@
// ==================================== // ====================================
if ($('#mk-editor').length === 1) { if ($('#mk-editor').length === 1) {
let mdeModalOpenState = false
let mdeModalOpenState = false; let mdeCurrentEditor = null
let mdeCurrentEditor = null;
Vue.filter('filesize', (v) => { Vue.filter('filesize', (v) => {
return _.toUpper(filesize(v)); return _.toUpper(filesize(v))
}); })
// =include editor-image.js // =include editor-image.js
// =include editor-file.js // =include editor-file.js
@@ -20,164 +19,161 @@ if($('#mk-editor').length === 1) {
var mde = new SimpleMDE({ var mde = new SimpleMDE({
autofocus: true, autofocus: true,
autoDownloadFontAwesome: false, autoDownloadFontAwesome: false,
element: $("#mk-editor").get(0), element: $('#mk-editor').get(0),
placeholder: 'Enter Markdown formatted content here...', placeholder: 'Enter Markdown formatted content here...',
spellChecker: false, spellChecker: false,
status: false, status: false,
toolbar: [{ toolbar: [{
name: "bold", name: 'bold',
action: SimpleMDE.toggleBold, action: SimpleMDE.toggleBold,
className: "icon-bold", className: 'icon-bold',
title: "Bold", title: 'Bold'
}, },
{ {
name: "italic", name: 'italic',
action: SimpleMDE.toggleItalic, action: SimpleMDE.toggleItalic,
className: "icon-italic", className: 'icon-italic',
title: "Italic", title: 'Italic'
}, },
{ {
name: "strikethrough", name: 'strikethrough',
action: SimpleMDE.toggleStrikethrough, action: SimpleMDE.toggleStrikethrough,
className: "icon-strikethrough", className: 'icon-strikethrough',
title: "Strikethrough", title: 'Strikethrough'
}, },
'|', '|',
{ {
name: "heading-1", name: 'heading-1',
action: SimpleMDE.toggleHeading1, action: SimpleMDE.toggleHeading1,
className: "icon-header fa-header-x fa-header-1", className: 'icon-header fa-header-x fa-header-1',
title: "Big Heading", title: 'Big Heading'
}, },
{ {
name: "heading-2", name: 'heading-2',
action: SimpleMDE.toggleHeading2, action: SimpleMDE.toggleHeading2,
className: "icon-header fa-header-x fa-header-2", className: 'icon-header fa-header-x fa-header-2',
title: "Medium Heading", title: 'Medium Heading'
}, },
{ {
name: "heading-3", name: 'heading-3',
action: SimpleMDE.toggleHeading3, action: SimpleMDE.toggleHeading3,
className: "icon-header fa-header-x fa-header-3", className: 'icon-header fa-header-x fa-header-3',
title: "Small Heading", title: 'Small Heading'
}, },
{ {
name: "quote", name: 'quote',
action: SimpleMDE.toggleBlockquote, action: SimpleMDE.toggleBlockquote,
className: "icon-quote-left", className: 'icon-quote-left',
title: "Quote", title: 'Quote'
}, },
'|', '|',
{ {
name: "unordered-list", name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList, action: SimpleMDE.toggleUnorderedList,
className: "icon-th-list", className: 'icon-th-list',
title: "Bullet List", title: 'Bullet List'
}, },
{ {
name: "ordered-list", name: 'ordered-list',
action: SimpleMDE.toggleOrderedList, action: SimpleMDE.toggleOrderedList,
className: "icon-list-ol", className: 'icon-list-ol',
title: "Numbered List", title: 'Numbered List'
}, },
'|', '|',
{ {
name: "link", name: 'link',
action: (editor) => { action: (editor) => {
/* if(!mdeModalOpenState) { /* if(!mdeModalOpenState) {
mdeModalOpenState = true; mdeModalOpenState = true;
$('#modal-editor-link').slideToggle(); $('#modal-editor-link').slideToggle();
} */ } */
}, },
className: "icon-link2", className: 'icon-link2',
title: "Insert Link", title: 'Insert Link'
}, },
{ {
name: "image", name: 'image',
action: (editor) => { action: (editor) => {
if (!mdeModalOpenState) { if (!mdeModalOpenState) {
vueImage.open(); vueImage.open()
} }
}, },
className: "icon-image", className: 'icon-image',
title: "Insert Image", title: 'Insert Image'
}, },
{ {
name: "file", name: 'file',
action: (editor) => { action: (editor) => {
if (!mdeModalOpenState) { if (!mdeModalOpenState) {
vueFile.open(); vueFile.open()
} }
}, },
className: "icon-paper", className: 'icon-paper',
title: "Insert File", title: 'Insert File'
}, },
{ {
name: "video", name: 'video',
action: (editor) => { action: (editor) => {
if (!mdeModalOpenState) { if (!mdeModalOpenState) {
vueVideo.open(); vueVideo.open()
} }
}, },
className: "icon-video-camera2", className: 'icon-video-camera2',
title: "Insert Video Player", title: 'Insert Video Player'
}, },
'|', '|',
{ {
name: "inline-code", name: 'inline-code',
action: (editor) => { action: (editor) => {
if (!editor.codemirror.doc.somethingSelected()) { if (!editor.codemirror.doc.somethingSelected()) {
return alerts.pushError('Invalid selection','You must select at least 1 character first.'); return alerts.pushError('Invalid selection', 'You must select at least 1 character first.')
} }
let curSel = editor.codemirror.doc.getSelections(); let curSel = editor.codemirror.doc.getSelections()
curSel = _.map(curSel, (s) => { curSel = _.map(curSel, (s) => {
return '`' + s + '`'; return '`' + s + '`'
}); })
editor.codemirror.doc.replaceSelections(curSel); editor.codemirror.doc.replaceSelections(curSel)
}, },
className: "icon-terminal", className: 'icon-terminal',
title: "Inline Code", title: 'Inline Code'
}, },
{ {
name: "code-block", name: 'code-block',
action: (editor) => { action: (editor) => {
if (!mdeModalOpenState) { if (!mdeModalOpenState) {
mdeModalOpenState = true; mdeModalOpenState = true
if (mde.codemirror.doc.somethingSelected()) { if (mde.codemirror.doc.somethingSelected()) {
vueCodeBlock.initContent = mde.codemirror.doc.getSelection(); vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
} }
vueCodeBlock.open(); vueCodeBlock.open()
} }
}, },
className: "icon-code", className: 'icon-code',
title: "Code Block", title: 'Code Block'
}, },
'|', '|',
{ {
name: "table", name: 'table',
action: (editor) => { action: (editor) => {
// todo // todo
}, },
className: "icon-table", className: 'icon-table',
title: "Insert Table", title: 'Insert Table'
}, },
{ {
name: "horizontal-rule", name: 'horizontal-rule',
action: SimpleMDE.drawHorizontalRule, action: SimpleMDE.drawHorizontalRule,
className: "icon-minus2", className: 'icon-minus2',
title: "Horizontal Rule", title: 'Horizontal Rule'
} }
], ],
shortcuts: { shortcuts: {
"toggleBlockquote": null, 'toggleBlockquote': null,
"toggleFullScreen": null 'toggleFullScreen': null
} }
}); })
// -> Save // -> Save
@@ -190,28 +186,27 @@ if($('#mk-editor').length === 1) {
method: 'PUT' method: 'PUT'
}).then((rData, rStatus, rXHR) => { }).then((rData, rStatus, rXHR) => {
if (rData.ok) { if (rData.ok) {
window.location.assign('/' + pageEntryPath); window.location.assign('/' + pageEntryPath)
} else { } else {
alerts.pushError('Something went wrong', rData.error); alerts.pushError('Something went wrong', rData.error)
} }
}, (rXHR, rStatus, err) => { }, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', 'Save operation failed.'); alerts.pushError('Something went wrong', 'Save operation failed.')
}); })
}; }
$('.btn-edit-save, .btn-create-save').on('click', (ev) => { $('.btn-edit-save, .btn-create-save').on('click', (ev) => {
saveCurrentDocument(ev); saveCurrentDocument(ev)
}); })
$(window).bind('keydown', (ev) => { $(window).bind('keydown', (ev) => {
if (ev.ctrlKey || ev.metaKey) { if (ev.ctrlKey || ev.metaKey) {
switch (String.fromCharCode(ev.which).toLowerCase()) { switch (String.fromCharCode(ev.which).toLowerCase()) {
case 's': case 's':
ev.preventDefault(); ev.preventDefault()
saveCurrentDocument(ev); saveCurrentDocument(ev)
break; break
} }
} }
}); })
} }
+28 -31
View File
@@ -1,10 +1,9 @@
"use strict"; 'use strict'
if ($('#search-input').length) { if ($('#search-input').length) {
$('#search-input').focus()
$('#search-input').focus(); $('.searchresults').css('display', 'block')
$('.searchresults').css('display', 'block');
var vueHeader = new Vue({ var vueHeader = new Vue({
el: '#header-container', el: '#header-container',
@@ -20,65 +19,63 @@ if($('#search-input').length) {
}, },
watch: { watch: {
searchq: (val, oldVal) => { searchq: (val, oldVal) => {
vueHeader.searchmoveidx = 0; vueHeader.searchmoveidx = 0
if (val.length >= 3) { if (val.length >= 3) {
vueHeader.searchactive = true; vueHeader.searchactive = true
vueHeader.searchload++; vueHeader.searchload++
socket.emit('search', { terms: val }, (data) => { socket.emit('search', { terms: val }, (data) => {
vueHeader.searchres = data.match; vueHeader.searchres = data.match
vueHeader.searchsuggest = data.suggest; vueHeader.searchsuggest = data.suggest
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest); vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
if(vueHeader.searchload > 0) { vueHeader.searchload--; } if (vueHeader.searchload > 0) { vueHeader.searchload-- }
}); })
} else { } else {
vueHeader.searchactive = false; vueHeader.searchactive = false
vueHeader.searchres = []; vueHeader.searchres = []
vueHeader.searchsuggest = []; vueHeader.searchsuggest = []
vueHeader.searchmovearr = []; vueHeader.searchmovearr = []
vueHeader.searchload = 0; vueHeader.searchload = 0
} }
}, },
searchmoveidx: (val, oldVal) => { searchmoveidx: (val, oldVal) => {
if (val > 0) { if (val > 0) {
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ? vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
'res.' + vueHeader.searchmovearr[val - 1]._id : 'res.' + vueHeader.searchmovearr[val - 1]._id :
'sug.' + vueHeader.searchmovearr[val - 1]; 'sug.' + vueHeader.searchmovearr[val - 1]
} else { } else {
vueHeader.searchmovekey = ''; vueHeader.searchmovekey = ''
} }
} }
}, },
methods: { methods: {
useSuggestion: (sug) => { useSuggestion: (sug) => {
vueHeader.searchq = sug; vueHeader.searchq = sug
}, },
closeSearch: () => { closeSearch: () => {
vueHeader.searchq = ''; vueHeader.searchq = ''
}, },
moveSelectSearch: () => { moveSelectSearch: () => {
if(vueHeader.searchmoveidx < 1) { return; } if (vueHeader.searchmoveidx < 1) { return }
let i = vueHeader.searchmoveidx - 1; let i = vueHeader.searchmoveidx - 1
if (vueHeader.searchmovearr[i]) { if (vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i]._id); window.location.assign('/' + vueHeader.searchmovearr[i]._id)
} else { } else {
vueHeader.searchq = vueHeader.searchmovearr[i]; vueHeader.searchq = vueHeader.searchmovearr[i]
} }
}, },
moveDownSearch: () => { moveDownSearch: () => {
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) { if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++; vueHeader.searchmoveidx++
} }
}, },
moveUpSearch: () => { moveUpSearch: () => {
if (vueHeader.searchmoveidx > 0) { if (vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--; vueHeader.searchmoveidx--
} }
} }
} }
}); })
$('main').on('click', vueHeader.closeSearch);
$('main').on('click', vueHeader.closeSearch)
} }
+10 -10
View File
@@ -1,16 +1,16 @@
function setInputSelection (input, startPos, endPos) { function setInputSelection (input, startPos, endPos) {
input.focus(); input.focus()
if (typeof input.selectionStart != "undefined") { if (typeof input.selectionStart !== 'undefined') {
input.selectionStart = startPos; input.selectionStart = startPos
input.selectionEnd = endPos; input.selectionEnd = endPos
} else if (document.selection && document.selection.createRange) { } else if (document.selection && document.selection.createRange) {
// IE branch // IE branch
input.select(); input.select()
var range = document.selection.createRange(); var range = document.selection.createRange()
range.collapse(true); range.collapse(true)
range.moveEnd("character", endPos); range.moveEnd('character', endPos)
range.moveStart("character", startPos); range.moveStart('character', startPos)
range.select(); range.select()
} }
} }
+4 -6
View File
@@ -1,11 +1,9 @@
function makeSafePath (rawPath) { function makeSafePath (rawPath) {
let rawParts = _.split(_.trim(rawPath), '/')
let rawParts = _.split(_.trim(rawPath), '/');
rawParts = _.map(rawParts, (r) => { rawParts = _.map(rawParts, (r) => {
return _.kebabCase(_.deburr(_.trim(r))); return _.kebabCase(_.deburr(_.trim(r)))
}); })
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/');
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
} }
+3 -5
View File
@@ -1,7 +1,5 @@
"use strict"; 'use strict'
jQuery(document).ready(function ($) { jQuery(document).ready(function ($) {
$('#login-user').focus()
$('#login-user').focus(); })
});
+8 -10
View File
@@ -11,20 +11,18 @@ let vueCreateUser = new Vue({
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-create').addClass('is-active'); $('#modal-admin-users-create').addClass('is-active')
$('#modal-admin-users-create input').first().focus(); $('#modal-admin-users-create input').first().focus()
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-create').removeClass('is-active'); $('#modal-admin-users-create').removeClass('is-active')
vueCreateUser.email = ''; vueCreateUser.email = ''
vueCreateUser.provider = 'local'; vueCreateUser.provider = 'local'
}, },
create: (ev) => { create: (ev) => {
vueCreateUser.cancel()
vueCreateUser.cancel();
} }
} }
}); })
$('.btn-create-prompt').on('click', vueCreateUser.open); $('.btn-create-prompt').on('click', vueCreateUser.open)
+5 -5
View File
@@ -8,15 +8,15 @@ let vueDeleteUser = new Vue({
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-delete').addClass('is-active'); $('#modal-admin-users-delete').addClass('is-active')
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-delete').removeClass('is-active'); $('#modal-admin-users-delete').removeClass('is-active')
}, },
deleteUser: (ev) => { deleteUser: (ev) => {
vueDeleteUser.cancel(); vueDeleteUser.cancel()
} }
} }
}); })
$('.btn-deluser-prompt').on('click', vueDeleteUser.open); $('.btn-deluser-prompt').on('click', vueDeleteUser.open)
+13 -15
View File
@@ -1,29 +1,27 @@
// -> Create New Document // -> Create New Document
let suggestedCreatePath = currentBasePath + '/new-page'; let suggestedCreatePath = currentBasePath + '/new-page'
$('.btn-create-prompt').on('click', (ev) => { $('.btn-create-prompt').on('click', (ev) => {
$('#txt-create-prompt').val(suggestedCreatePath); $('#txt-create-prompt').val(suggestedCreatePath)
$('#modal-create-prompt').toggleClass('is-active'); $('#modal-create-prompt').toggleClass('is-active')
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length); setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-create-prompt').on('keypress', (ev) => { $('#txt-create-prompt').on('keypress', (ev) => {
if (ev.which === 13) { if (ev.which === 13) {
$('.btn-create-go').trigger('click'); $('.btn-create-go').trigger('click')
} }
}); })
$('.btn-create-go').on('click', (ev) => { $('.btn-create-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-create-prompt').val())
let newDocPath = makeSafePath($('#txt-create-prompt').val());
if (_.isEmpty(newDocPath)) { if (_.isEmpty(newDocPath)) {
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden'); $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
} else { } else {
$('#txt-create-prompt').parent().addClass('is-loading'); $('#txt-create-prompt').parent().addClass('is-loading')
window.location.assign('/create/' + newDocPath); window.location.assign('/create/' + newDocPath)
} }
})
});
+17 -20
View File
@@ -2,31 +2,30 @@
// -> Move Existing Document // -> Move Existing Document
if (currentBasePath !== '') { if (currentBasePath !== '') {
$('.btn-move-prompt').removeClass('is-hidden'); $('.btn-move-prompt').removeClass('is-hidden')
} }
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1; let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
$('.btn-move-prompt').on('click', (ev) => { $('.btn-move-prompt').on('click', (ev) => {
$('#txt-move-prompt').val(currentBasePath); $('#txt-move-prompt').val(currentBasePath)
$('#modal-move-prompt').toggleClass('is-active'); $('#modal-move-prompt').toggleClass('is-active')
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length); setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-move-prompt').on('keypress', (ev) => { $('#txt-move-prompt').on('keypress', (ev) => {
if (ev.which === 13) { if (ev.which === 13) {
$('.btn-move-go').trigger('click'); $('.btn-move-go').trigger('click')
} }
}); })
$('.btn-move-go').on('click', (ev) => { $('.btn-move-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-move-prompt').val())
let newDocPath = makeSafePath($('#txt-move-prompt').val());
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') { if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden'); $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
} else { } else {
$('#txt-move-prompt').parent().addClass('is-loading'); $('#txt-move-prompt').parent().addClass('is-loading')
$.ajax(window.location.href, { $.ajax(window.location.href, {
data: { data: {
@@ -36,14 +35,12 @@ $('.btn-move-go').on('click', (ev) => {
method: 'PUT' method: 'PUT'
}).then((rData, rStatus, rXHR) => { }).then((rData, rStatus, rXHR) => {
if (rData.ok) { if (rData.ok) {
window.location.assign('/' + newDocPath); window.location.assign('/' + newDocPath)
} else { } else {
alerts.pushError('Something went wrong', rData.error); alerts.pushError('Something went wrong', rData.error)
} }
}, (rXHR, rStatus, err) => { }, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', 'Save operation failed.'); alerts.pushError('Something went wrong', 'Save operation failed.')
}); })
} }
})
});
+17 -23
View File
@@ -1,6 +1,5 @@
if ($('#page-type-admin-profile').length) { if ($('#page-type-admin-profile').length) {
let vueProfile = new Vue({ let vueProfile = new Vue({
el: '#page-type-admin-profile', el: '#page-type-admin-profile',
data: { data: {
@@ -11,30 +10,28 @@ if($('#page-type-admin-profile').length) {
methods: { methods: {
saveUser: (ev) => { saveUser: (ev) => {
if (vueProfile.password !== vueProfile.passwordVerify) { if (vueProfile.password !== vueProfile.passwordVerify) {
alerts.pushError('Error', "Passwords don't match!"); alerts.pushError('Error', "Passwords don't match!")
return; return
} }
$.post(window.location.href, { $.post(window.location.href, {
password: vueProfile.password, password: vueProfile.password,
name: vueProfile.name name: vueProfile.name
}).done((resp) => { }).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.'); alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => { }).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp); alerts.pushError('Error', resp)
}) })
} }
}, },
created: function () { created: function () {
this.name = usrDataName; this.name = usrDataName
} }
}); })
} else if ($('#page-type-admin-users').length) { } else if ($('#page-type-admin-users').length) {
// =include ../modals/admin-users-create.js // =include ../modals/admin-users-create.js
} else if ($('#page-type-admin-users-edit').length) { } else if ($('#page-type-admin-users-edit').length) {
let vueEditUser = new Vue({ let vueEditUser = new Vue({
el: '#page-type-admin-users-edit', el: '#page-type-admin-users-edit',
data: { data: {
@@ -52,7 +49,7 @@ if($('#page-type-admin-profile').length) {
path: '/', path: '/',
exact: false, exact: false,
deny: false deny: false
}); })
}, },
removeRightsRow: (idx) => { removeRightsRow: (idx) => {
_.pullAt(vueEditUser.rights, idx) _.pullAt(vueEditUser.rights, idx)
@@ -68,35 +65,32 @@ if($('#page-type-admin-profile').length) {
exact: false, exact: false,
deny: false deny: false
}) })
break; break
} }
$.post(window.location.href, { $.post(window.location.href, {
password: vueEditUser.password, password: vueEditUser.password,
name: vueEditUser.name, name: vueEditUser.name,
rights: JSON.stringify(formattedRights) rights: JSON.stringify(formattedRights)
}).done((resp) => { }).done((resp) => {
alerts.pushSuccess('Saved successfully', 'Changes have been applied.'); alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
}).fail((jqXHR, txtStatus, resp) => { }).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp); alerts.pushError('Error', resp)
}) })
} }
}, },
created: function () { created: function () {
this.id = usrData._id
this.id = usrData._id; this.email = usrData.email
this.email = usrData.email; this.name = usrData.name
this.name = usrData.name;
if (_.find(usrData.rights, { role: 'admin' })) { if (_.find(usrData.rights, { role: 'admin' })) {
this.rights = _.reject(usrData.rights, ['role', 'admin']); this.rights = _.reject(usrData.rights, ['role', 'admin'])
this.roleoverride = 'admin'; this.roleoverride = 'admin'
} else { } else {
this.rights = usrData.rights; this.rights = usrData.rights
} }
} }
}); })
// =include ../modals/admin-users-delete.js // =include ../modals/admin-users-delete.js
} }
+3 -5
View File
@@ -1,14 +1,12 @@
if ($('#page-type-create').length) { if ($('#page-type-create').length) {
let pageEntryPath = $('#page-type-create').data('entrypath')
let pageEntryPath = $('#page-type-create').data('entrypath');
// -> Discard // -> Discard
$('.btn-create-discard').on('click', (ev) => { $('.btn-create-discard').on('click', (ev) => {
$('#modal-create-discard').toggleClass('is-active'); $('#modal-create-discard').toggleClass('is-active')
}); })
// =include ../components/editor.js // =include ../components/editor.js
} }
+3 -5
View File
@@ -1,14 +1,12 @@
if ($('#page-type-edit').length) { if ($('#page-type-edit').length) {
let pageEntryPath = $('#page-type-edit').data('entrypath')
let pageEntryPath = $('#page-type-edit').data('entrypath');
// -> Discard // -> Discard
$('.btn-edit-discard').on('click', (ev) => { $('.btn-edit-discard').on('click', (ev) => {
$('#modal-edit-discard').toggleClass('is-active'); $('#modal-edit-discard').toggleClass('is-active')
}); })
// =include ../components/editor.js // =include ../components/editor.js
} }
+9 -11
View File
@@ -1,18 +1,16 @@
if ($('#page-type-source').length) { if ($('#page-type-source').length) {
var scEditor = ace.edit('source-display')
scEditor.setTheme('ace/theme/tomorrow_night')
scEditor.getSession().setMode('ace/mode/markdown')
scEditor.setOption('fontSize', '14px')
scEditor.setOption('hScrollBarAlwaysVisible', false)
scEditor.setOption('wrap', true)
scEditor.setReadOnly(true)
scEditor.renderer.updateFull()
var scEditor = ace.edit("source-display"); let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
scEditor.setTheme("ace/theme/tomorrow_night");
scEditor.getSession().setMode("ace/mode/markdown");
scEditor.setOption('fontSize', '14px');
scEditor.setOption('hScrollBarAlwaysVisible', false);
scEditor.setOption('wrap', true);
scEditor.setReadOnly(true);
scEditor.renderer.updateFull();
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '';
// =include ../modals/create.js // =include ../modals/create.js
// =include ../modals/move.js // =include ../modals/move.js
} }
+1 -3
View File
@@ -1,9 +1,7 @@
if ($('#page-type-view').length) { if ($('#page-type-view').length) {
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '';
// =include ../modals/create.js // =include ../modals/create.js
// =include ../modals/move.js // =include ../modals/move.js
} }
+53 -69
View File
@@ -1,61 +1,56 @@
"use strict"; 'use strict'
var express = require('express'); var express = require('express')
var router = express.Router(); var router = express.Router()
const Promise = require('bluebird'); const Promise = require('bluebird')
const validator = require('validator'); const validator = require('validator')
const _ = require('lodash'); const _ = require('lodash')
/** /**
* Admin * Admin
*/ */
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.redirect('/admin/profile'); res.redirect('/admin/profile')
}); })
router.get('/profile', (req, res) => { router.get('/profile', (req, res) => {
if (res.locals.isGuest) { if (res.locals.isGuest) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
res.render('pages/admin/profile', { adminTab: 'profile' }); res.render('pages/admin/profile', { adminTab: 'profile' })
})
});
router.post('/profile', (req, res) => { router.post('/profile', (req, res) => {
if (res.locals.isGuest) { if (res.locals.isGuest) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
return db.User.findById(req.user.id).then((usr) => { return db.User.findById(req.user.id).then((usr) => {
usr.name = _.trim(req.body.name); usr.name = _.trim(req.body.name)
if (usr.provider === 'local' && req.body.password !== '********') { if (usr.provider === 'local' && req.body.password !== '********') {
let nPwd = _.trim(req.body.password); let nPwd = _.trim(req.body.password)
if (nPwd.length < 6) { if (nPwd.length < 6) {
return Promise.reject(new Error('New Password too short!')) return Promise.reject(new Error('New Password too short!'))
} else { } else {
return db.User.hashPassword(nPwd).then((pwd) => { return db.User.hashPassword(nPwd).then((pwd) => {
usr.password = pwd; usr.password = pwd
return usr.save(); return usr.save()
}); })
} }
} else { } else {
return usr.save(); return usr.save()
} }
}).then(() => { }).then(() => {
return res.json({ msg: 'OK' }); return res.json({ msg: 'OK' })
}).catch((err) => { }).catch((err) => {
res.status(400).json({ msg: err.message }); res.status(400).json({ msg: err.message })
})
}) })
});
router.get('/stats', (req, res) => { router.get('/stats', (req, res) => {
if (res.locals.isGuest) { if (res.locals.isGuest) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
Promise.all([ Promise.all([
@@ -64,99 +59,88 @@ router.get('/stats', (req, res) => {
db.User.count() db.User.count()
]).spread((totalEntries, totalUploads, totalUsers) => { ]).spread((totalEntries, totalUploads, totalUsers) => {
return res.render('pages/admin/stats', { return res.render('pages/admin/stats', {
totalEntries, totalUploads, totalUsers, totalEntries, totalUploads, totalUsers, adminTab: 'stats'
adminTab: 'stats' }) || true
}) || true;
}).catch((err) => { }).catch((err) => {
throw err; throw err
}); })
})
});
router.get('/users', (req, res) => { router.get('/users', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
db.User.find({}) db.User.find({})
.select('-password -rights') .select('-password -rights')
.sort('name email') .sort('name email')
.exec().then((usrs) => { .exec().then((usrs) => {
res.render('pages/admin/users', { adminTab: 'users', usrs }); res.render('pages/admin/users', { adminTab: 'users', usrs })
}); })
})
});
router.get('/users/:id', (req, res) => { router.get('/users/:id', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
if (!validator.isMongoId(req.params.id)) { if (!validator.isMongoId(req.params.id)) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
db.User.findById(req.params.id) db.User.findById(req.params.id)
.select('-password -providerId') .select('-password -providerId')
.exec().then((usr) => { .exec().then((usr) => {
let usrOpts = { let usrOpts = {
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin), canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
canChangeName: (usr.email !== 'guest'), canChangeName: (usr.email !== 'guest'),
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'), canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)), canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)) canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
}; }
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts }); res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
}); })
})
});
router.post('/users/:id', (req, res) => { router.post('/users/:id', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.status(401).json({ msg: 'Unauthorized' }); return res.status(401).json({ msg: 'Unauthorized' })
} }
if (!validator.isMongoId(req.params.id)) { if (!validator.isMongoId(req.params.id)) {
return res.status(400).json({ msg: 'Invalid User ID' }); return res.status(400).json({ msg: 'Invalid User ID' })
} }
return db.User.findById(req.params.id).then((usr) => { return db.User.findById(req.params.id).then((usr) => {
usr.name = _.trim(req.body.name); usr.name = _.trim(req.body.name)
usr.rights = JSON.parse(req.body.rights); usr.rights = JSON.parse(req.body.rights)
if (usr.provider === 'local' && req.body.password !== '********') { if (usr.provider === 'local' && req.body.password !== '********') {
let nPwd = _.trim(req.body.password); let nPwd = _.trim(req.body.password)
if (nPwd.length < 6) { if (nPwd.length < 6) {
return Promise.reject(new Error('New Password too short!')) return Promise.reject(new Error('New Password too short!'))
} else { } else {
return db.User.hashPassword(nPwd).then((pwd) => { return db.User.hashPassword(nPwd).then((pwd) => {
usr.password = pwd; usr.password = pwd
return usr.save(); return usr.save()
}); })
} }
} else { } else {
return usr.save(); return usr.save()
} }
}).then(() => { }).then(() => {
return res.json({ msg: 'OK' }); return res.json({ msg: 'OK' })
}).catch((err) => { }).catch((err) => {
res.status(400).json({ msg: err.message }); res.status(400).json({ msg: err.message })
})
}) })
});
router.get('/settings', (req, res) => { router.get('/settings', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
res.render('pages/admin/settings', { adminTab: 'settings' }); res.render('pages/admin/settings', { adminTab: 'settings' })
})
}); module.exports = router
module.exports = router;
+35 -35
View File
@@ -1,15 +1,17 @@
var express = require('express'); 'use strict'
var router = express.Router();
var passport = require('passport'); const express = require('express')
var ExpressBrute = require('express-brute'); const router = express.Router()
var ExpressBruteMongooseStore = require('express-brute-mongoose'); const passport = require('passport')
var moment = require('moment'); const ExpressBrute = require('express-brute')
const ExpressBruteMongooseStore = require('express-brute-mongoose')
const moment = require('moment')
/** /**
* Setup Express-Brute * Setup Express-Brute
*/ */
var EBstore = new ExpressBruteMongooseStore(db.Bruteforce); const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
var bruteforce = new ExpressBrute(EBstore, { const bruteforce = new ExpressBrute(EBstore, {
freeRetries: 5, freeRetries: 5,
minWait: 60 * 1000, minWait: 60 * 1000,
maxWait: 5 * 60 * 1000, maxWait: 5 * 60 * 1000,
@@ -20,10 +22,10 @@ var bruteforce = new ExpressBrute(EBstore, {
title: 'Too many attempts!', title: 'Too many attempts!',
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.', message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
iconClass: 'fa-times' iconClass: 'fa-times'
}); })
res.redirect('/login'); res.redirect('/login')
} }
}); })
/** /**
* Login form * Login form
@@ -31,50 +33,48 @@ var bruteforce = new ExpressBrute(EBstore, {
router.get('/login', function (req, res, next) { router.get('/login', function (req, res, next) {
res.render('auth/login', { res.render('auth/login', {
usr: res.locals.usr usr: res.locals.usr
}); })
}); })
router.post('/login', bruteforce.prevent, function (req, res, next) { router.post('/login', bruteforce.prevent, function (req, res, next) {
passport.authenticate('local', function (err, user, info) { passport.authenticate('local', function (err, user, info) {
if (err) { return next(err) }
if (err) { return next(err); }
if (!user) { if (!user) {
req.flash('alert', { req.flash('alert', {
title: 'Invalid login', title: 'Invalid login',
message: "The email or password is invalid." message: 'The email or password is invalid.'
}); })
return res.redirect('/login'); return res.redirect('/login')
} }
req.logIn(user, function (err) { req.logIn(user, function (err) {
if (err) { return next(err); } if (err) { return next(err) }
req.brute.reset(function () { req.brute.reset(function () {
return res.redirect('/'); return res.redirect('/')
}); })
}); })
})(req, res, next)
})(req, res, next); })
});
/** /**
* Social Login * Social Login
*/ */
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] })); router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] })); router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] })); router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
/** /**
* Logout * Logout
*/ */
router.get('/logout', function (req, res) { router.get('/logout', function (req, res) {
req.logout(); req.logout()
res.redirect('/'); res.redirect('/')
}); })
module.exports = router; module.exports = router
+63 -80
View File
@@ -1,8 +1,8 @@
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var _ = require('lodash'); const _ = require('lodash')
// ========================================== // ==========================================
// EDIT MODE // EDIT MODE
@@ -12,12 +12,11 @@ var _ = require('lodash');
* Edit document in Markdown * Edit document in Markdown
*/ */
router.get('/edit/*', (req, res, next) => { router.get('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) { if (!res.locals.rights.write) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
let safePath = entries.parsePath(_.replace(req.path, '/edit', '')); let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
entries.fetchOriginal(safePath, { entries.fetchOriginal(safePath, {
parseMarkdown: false, parseMarkdown: false,
@@ -28,116 +27,108 @@ router.get('/edit/*', (req, res, next) => {
cache: false cache: false
}).then((pageData) => { }).then((pageData) => {
if (pageData) { if (pageData) {
res.render('pages/edit', { pageData }); res.render('pages/edit', { pageData })
} else { } else {
throw new Error('Invalid page path.'); throw new Error('Invalid page path.')
} }
return true; return true
}).catch((err) => { }).catch((err) => {
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: {} error: {}
}); })
}); })
})
});
router.put('/edit/*', (req, res, next) => { router.put('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) { if (!res.locals.rights.write) {
return res.json({ return res.json({
ok: false, ok: false,
error: 'Forbidden' error: 'Forbidden'
}); })
} }
let safePath = entries.parsePath(_.replace(req.path, '/edit', '')); let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
entries.update(safePath, req.body.markdown).then(() => { entries.update(safePath, req.body.markdown).then(() => {
return res.json({ return res.json({
ok: true ok: true
}) || true; }) || true
}).catch((err) => { }).catch((err) => {
res.json({ res.json({
ok: false, ok: false,
error: err.message error: err.message
}); })
}); })
})
});
// ========================================== // ==========================================
// CREATE MODE // CREATE MODE
// ========================================== // ==========================================
router.get('/create/*', (req, res, next) => { router.get('/create/*', (req, res, next) => {
if (!res.locals.rights.write) { if (!res.locals.rights.write) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
if(_.some(['create','edit','account','source','history','mk'], (e) => { return _.startsWith(req.path, '/create/' + e); })) { if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
return res.render('error', { return res.render('error', {
message: 'You cannot create a document with this name as it is reserved by the system.', message: 'You cannot create a document with this name as it is reserved by the system.',
error: {} error: {}
}); })
} }
let safePath = entries.parsePath(_.replace(req.path, '/create', '')); let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
entries.exists(safePath).then((docExists) => { entries.exists(safePath).then((docExists) => {
if (!docExists) { if (!docExists) {
return entries.getStarter(safePath).then((contents) => { return entries.getStarter(safePath).then((contents) => {
let pageData = { let pageData = {
markdown: contents, markdown: contents,
meta: { meta: {
title: _.startCase(safePath), title: _.startCase(safePath),
path: safePath path: safePath
} }
}; }
res.render('pages/create', { pageData }); res.render('pages/create', { pageData })
return true;
return true
}).catch((err) => { }).catch((err) => {
throw new Error('Could not load starter content!'); winston.warn(err)
}); throw new Error('Could not load starter content!')
})
} else { } else {
throw new Error('This entry already exists!'); throw new Error('This entry already exists!')
} }
}).catch((err) => { }).catch((err) => {
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: {} error: {}
}); })
}); })
})
});
router.put('/create/*', (req, res, next) => { router.put('/create/*', (req, res, next) => {
if (!res.locals.rights.write) { if (!res.locals.rights.write) {
return res.json({ return res.json({
ok: false, ok: false,
error: 'Forbidden' error: 'Forbidden'
}); })
} }
let safePath = entries.parsePath(_.replace(req.path, '/create', '')); let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
entries.create(safePath, req.body.markdown).then(() => { entries.create(safePath, req.body.markdown).then(() => {
return res.json({ return res.json({
ok: true ok: true
}) || true; }) || true
}).catch((err) => { }).catch((err) => {
return res.json({ return res.json({
ok: false, ok: false,
error: err.message error: err.message
}); })
}); })
})
});
// ========================================== // ==========================================
// VIEW MODE // VIEW MODE
@@ -147,8 +138,7 @@ router.put('/create/*', (req, res, next) => {
* View source of a document * View source of a document
*/ */
router.get('/source/*', (req, res, next) => { router.get('/source/*', (req, res, next) => {
let safePath = entries.parsePath(_.replace(req.path, '/source', ''))
let safePath = entries.parsePath(_.replace(req.path, '/source', ''));
entries.fetchOriginal(safePath, { entries.fetchOriginal(safePath, {
parseMarkdown: false, parseMarkdown: false,
@@ -159,90 +149,83 @@ router.get('/source/*', (req, res, next) => {
cache: false cache: false
}).then((pageData) => { }).then((pageData) => {
if (pageData) { if (pageData) {
res.render('pages/source', { pageData }); res.render('pages/source', { pageData })
} else { } else {
throw new Error('Invalid page path.'); throw new Error('Invalid page path.')
} }
return true; return true
}).catch((err) => { }).catch((err) => {
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: {} error: {}
}); })
}); })
})
});
/** /**
* View document * View document
*/ */
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let safePath = entries.parsePath(req.path)
let safePath = entries.parsePath(req.path);
entries.fetch(safePath).then((pageData) => { entries.fetch(safePath).then((pageData) => {
if (pageData) { if (pageData) {
res.render('pages/view', { pageData }); res.render('pages/view', { pageData })
} else { } else {
res.render('error-notexist', { res.render('error-notexist', {
newpath: safePath newpath: safePath
}); })
} }
return true; return true
}).error((err) => { }).error((err) => {
if (safePath === 'home') { if (safePath === 'home') {
res.render('pages/welcome'); res.render('pages/welcome')
} else { } else {
res.render('error-notexist', { res.render('error-notexist', {
message: err.message, message: err.message,
newpath: safePath newpath: safePath
}); })
} }
}).catch((err) => { }).catch((err) => {
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: {} error: {}
}); })
}); })
})
});
/** /**
* Move document * Move document
*/ */
router.put('/*', (req, res, next) => { router.put('/*', (req, res, next) => {
if (!res.locals.rights.write) { if (!res.locals.rights.write) {
return res.json({ return res.json({
ok: false, ok: false,
error: 'Forbidden' error: 'Forbidden'
}); })
} }
let safePath = entries.parsePath(req.path); let safePath = entries.parsePath(req.path)
if (_.isEmpty(req.body.move)) { if (_.isEmpty(req.body.move)) {
return res.json({ return res.json({
ok: false, ok: false,
error: 'Invalid document action call.' error: 'Invalid document action call.'
}); })
} }
let safeNewPath = entries.parsePath(req.body.move); let safeNewPath = entries.parsePath(req.body.move)
entries.move(safePath, safeNewPath).then(() => { entries.move(safePath, safeNewPath).then(() => {
res.json({ res.json({
ok: true ok: true
}); })
}).catch((err) => { }).catch((err) => {
res.json({ res.json({
ok: false, ok: false,
error: err.message error: err.message
}); })
}); })
})
}); module.exports = router
module.exports = router;
+66 -90
View File
@@ -1,27 +1,26 @@
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var readChunk = require('read-chunk'), const readChunk = require('read-chunk')
fileType = require('file-type'), const fileType = require('file-type')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
path = require('path'), const path = require('path')
_ = require('lodash'); const _ = require('lodash')
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$"); const validPathRe = new RegExp('^([a-z0-9\\/-]+\\.[a-z0-9]+)$')
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$"); const validPathThumbsRe = new RegExp('^([0-9]+\\.png)$')
// ========================================== // ==========================================
// SERVE UPLOADS FILES // SERVE UPLOADS FILES
// ========================================== // ==========================================
router.get('/t/*', (req, res, next) => { router.get('/t/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0];
if (!validPathThumbsRe.test(fileName)) { if (!validPathThumbsRe.test(fileName)) {
return res.sendStatus(404).end(); return res.sendStatus(404).end()
} }
// todo: Authentication-based access // todo: Authentication-based access
@@ -31,141 +30,119 @@ router.get('/t/*', (req, res, next) => {
dotfiles: 'deny' dotfiles: 'deny'
}, (err) => { }, (err) => {
if (err) { if (err) {
res.status(err.status).end(); res.status(err.status).end()
} }
}); })
})
});
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' }); res.json({ ok: false, msg: 'Invalid Folder' })
return true; return true
} }
Promise.map(req.files, (f) => { Promise.map(req.files, (f) => {
let destFilename = ''
let destFilename = ''; let destFilePath = ''
let destFilePath = '';
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => { return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
destFilename = fname; return readChunk(f.path, 0, 262)
destFilePath = path.resolve(destFolderPath, destFilename);
return readChunk(f.path, 0, 262);
}).then((buf) => { }).then((buf) => {
// -> Check MIME type by magic number // -> Check MIME type by magic number
let mimeInfo = fileType(buf); let mimeInfo = fileType(buf)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) { if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return Promise.reject(new Error('Invalid file type.')); return Promise.reject(new Error('Invalid file type.'))
} }
return true; return true
}).then(() => { }).then(() => {
// -> Move file to final destination // -> Move file to final destination
return fs.moveAsync(f.path, destFilePath, { clobber: false }); return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => { }).then(() => {
return { return {
ok: true, ok: true,
filename: destFilename, filename: destFilename,
filesize: f.size filesize: f.size
}; }
}).reflect(); }).reflect()
}, {concurrency: 3}).then((results) => { }, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => { let uplResults = _.map(results, (r) => {
if (r.isFulfilled()) { if (r.isFulfilled()) {
return r.value(); return r.value()
} else { } else {
return { return {
ok: false, ok: false,
msg: r.reason().message msg: r.reason().message
};
} }
}); }
res.json({ ok: true, results: uplResults }); })
return true; res.json({ ok: true, results: uplResults })
return true
}).catch((err) => { }).catch((err) => {
res.json({ ok: false, msg: err.message }); res.json({ ok: false, msg: err.message })
return true; return true
}); })
})
}); })
});
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => { router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' }); res.json({ ok: false, msg: 'Invalid Folder' })
return true; return true
} }
Promise.map(req.files, (f) => { Promise.map(req.files, (f) => {
let destFilename = ''
let destFilename = ''; let destFilePath = ''
let destFilePath = '';
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => { return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
destFilename = fname
destFilename = fname; destFilePath = path.resolve(destFolderPath, destFilename)
destFilePath = path.resolve(destFolderPath, destFilename);
// -> Move file to final destination // -> Move file to final destination
return fs.moveAsync(f.path, destFilePath, { clobber: false }); return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => { }).then(() => {
return { return {
ok: true, ok: true,
filename: destFilename, filename: destFilename,
filesize: f.size filesize: f.size
}; }
}).reflect(); }).reflect()
}, {concurrency: 3}).then((results) => { }, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => { let uplResults = _.map(results, (r) => {
if (r.isFulfilled()) { if (r.isFulfilled()) {
return r.value(); return r.value()
} else { } else {
return { return {
ok: false, ok: false,
msg: r.reason().message msg: r.reason().message
};
} }
}); }
res.json({ ok: true, results: uplResults }); })
return true; res.json({ ok: true, results: uplResults })
return true
}).catch((err) => { }).catch((err) => {
res.json({ ok: false, msg: err.message }); res.json({ ok: false, msg: err.message })
return true; return true
}); })
})
}); })
});
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0];
if (!validPathRe.test(fileName)) { if (!validPathRe.test(fileName)) {
return res.sendStatus(404).end(); return res.sendStatus(404).end()
} }
// todo: Authentication-based access // todo: Authentication-based access
@@ -175,10 +152,9 @@ router.get('/*', (req, res, next) => {
dotfiles: 'deny' dotfiles: 'deny'
}, (err) => { }, (err) => {
if (err) { if (err) {
res.status(err.status).end(); res.status(err.status).end()
} }
}); })
})
}); module.exports = router
module.exports = router;
+44 -44
View File
@@ -1,9 +1,10 @@
"use strict"; 'use strict'
const _ = require('lodash')
module.exports = (socket) => { module.exports = (socket) => {
if (!socket.request.user.logged_in) { if (!socket.request.user.logged_in) {
return; return
} }
// ----------------------------------------- // -----------------------------------------
@@ -11,85 +12,84 @@ module.exports = (socket) => {
// ----------------------------------------- // -----------------------------------------
socket.on('search', (data, cb) => { socket.on('search', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
entries.search(data.terms).then((results) => { entries.search(data.terms).then((results) => {
return cb(results) || true; return cb(results) || true
}); })
}); })
// ----------------------------------------- // -----------------------------------------
// UPLOADS // UPLOADS
// ----------------------------------------- // -----------------------------------------
socket.on('uploadsGetFolders', (data, cb) => { socket.on('uploadsGetFolders', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFolders().then((f) => { upl.getUploadsFolders().then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsCreateFolder', (data, cb) => { socket.on('uploadsCreateFolder', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.createUploadsFolder(data.foldername).then((f) => { upl.createUploadsFolder(data.foldername).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetImages', (data, cb) => { socket.on('uploadsGetImages', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('image', data.folder).then((f) => { upl.getUploadsFiles('image', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetFiles', (data, cb) => { socket.on('uploadsGetFiles', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('binary', data.folder).then((f) => { upl.getUploadsFiles('binary', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsDeleteFile', (data, cb) => { socket.on('uploadsDeleteFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.deleteUploadsFile(data.uid).then((f) => { upl.deleteUploadsFile(data.uid).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsFetchFileFromURL', (data, cb) => { socket.on('uploadsFetchFileFromURL', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => { upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsRenameFile', (data, cb) => { socket.on('uploadsRenameFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => { upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsMoveFile', (data, cb) => { socket.on('uploadsMoveFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder).then((f) => { upl.moveUploadsFile(data.uid, data.folder).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
}
};
+56 -61
View File
@@ -1,26 +1,27 @@
var gulp = require("gulp"); 'use strict'
var watch = require('gulp-watch');
var merge = require('merge-stream'); const gulp = require('gulp')
var babel = require("gulp-babel"); const watch = require('gulp-watch')
var uglify = require('gulp-uglify'); const merge = require('merge-stream')
var concat = require('gulp-concat'); const babel = require('gulp-babel')
var nodemon = require('gulp-nodemon'); const uglify = require('gulp-uglify')
var plumber = require('gulp-plumber'); const concat = require('gulp-concat')
var zip = require('gulp-zip'); const nodemon = require('gulp-nodemon')
var tar = require('gulp-tar'); const plumber = require('gulp-plumber')
var gzip = require('gulp-gzip'); const zip = require('gulp-zip')
var sass = require('gulp-sass'); const tar = require('gulp-tar')
var cleanCSS = require('gulp-clean-css'); const gzip = require('gulp-gzip')
var include = require("gulp-include"); const sass = require('gulp-sass')
var run = require('run-sequence'); const cleanCSS = require('gulp-clean-css')
var _ = require('lodash'); const include = require('gulp-include')
const run = require('run-sequence')
/** /**
* Paths * Paths
* *
* @type {Object} * @type {Object}
*/ */
var paths = { const paths = {
scripts: { scripts: {
combine: [ combine: [
'./node_modules/socket.io-client/dist/socket.io.min.js', './node_modules/socket.io-client/dist/socket.io.min.js',
@@ -81,7 +82,7 @@ var paths = {
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml', '!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project' '!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
] ]
}; }
/** /**
* TASK - Starts server in development mode * TASK - Starts server in development mode
@@ -92,111 +93,105 @@ gulp.task('server', ['scripts', 'css', 'fonts'], function() {
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'], ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
ext: 'js json', ext: 'js json',
env: { 'NODE_ENV': 'development' } env: { 'NODE_ENV': 'development' }
}); })
}); })
/** /**
* TASK - Process all scripts processes * TASK - Process all scripts processes
*/ */
gulp.task("scripts", ['scripts-libs', 'scripts-app']); gulp.task('scripts', ['scripts-libs', 'scripts-app'])
/** /**
* TASK - Combine js libraries * TASK - Combine js libraries
*/ */
gulp.task("scripts-libs", function () { gulp.task('scripts-libs', function () {
return merge( return merge(
gulp.src(paths.scripts.combine) gulp.src(paths.scripts.combine)
.pipe(concat('libs.js', {newLine: ';\n'})) .pipe(concat('libs.js', {newLine: ';\n'}))
.pipe(uglify({ mangle: false })) .pipe(uglify({ mangle: false }))
.pipe(gulp.dest("./assets/js")), .pipe(gulp.dest('./assets/js')),
gulp.src(paths.scripts.ace) gulp.src(paths.scripts.ace)
.pipe(gulp.dest("./assets/js/ace")) .pipe(gulp.dest('./assets/js/ace'))
); )
})
});
/** /**
* TASK - Combine, make compatible and compress js app scripts * TASK - Combine, make compatible and compress js app scripts
*/ */
gulp.task("scripts-app", function () { gulp.task('scripts-app', function () {
return gulp.src(paths.scripts.compile) return gulp.src(paths.scripts.compile)
.pipe(plumber()) .pipe(plumber())
.pipe(include({ extensions: "js" })) .pipe(include({ extensions: 'js' }))
.pipe(babel()) .pipe(babel())
.pipe(uglify()) .pipe(uglify())
.pipe(plumber.stop()) .pipe(plumber.stop())
.pipe(gulp.dest("./assets/js")); .pipe(gulp.dest('./assets/js'))
})
});
/** /**
* TASK - Process all css processes * TASK - Process all css processes
*/ */
gulp.task("css", ['css-libs', 'css-app']); gulp.task('css', ['css-libs', 'css-app'])
/** /**
* TASK - Combine css libraries * TASK - Combine css libraries
*/ */
gulp.task("css-libs", function () { gulp.task('css-libs', function () {
return gulp.src(paths.css.combine) return gulp.src(paths.css.combine)
.pipe(plumber()) .pipe(plumber())
.pipe(concat('libs.css')) .pipe(concat('libs.css'))
.pipe(cleanCSS({ keepSpecialComments: 0 })) .pipe(cleanCSS({ keepSpecialComments: 0 }))
.pipe(plumber.stop()) .pipe(plumber.stop())
.pipe(gulp.dest("./assets/css")); .pipe(gulp.dest('./assets/css'))
}); })
/** /**
* TASK - Combine app css * TASK - Combine app css
*/ */
gulp.task("css-app", function () { gulp.task('css-app', function () {
return gulp.src(paths.css.compile) return gulp.src(paths.css.compile)
.pipe(plumber()) .pipe(plumber())
.pipe(sass.sync({ includePaths: paths.css.includes })) .pipe(sass.sync({ includePaths: paths.css.includes }))
.pipe(cleanCSS({ keepSpecialComments: 0 })) .pipe(cleanCSS({ keepSpecialComments: 0 }))
.pipe(plumber.stop()) .pipe(plumber.stop())
.pipe(gulp.dest("./assets/css")); .pipe(gulp.dest('./assets/css'))
}); })
/** /**
* TASK - Copy web fonts * TASK - Copy web fonts
*/ */
gulp.task("fonts", function () { gulp.task('fonts', function () {
return gulp.src(paths.fonts) return gulp.src(paths.fonts)
.pipe(gulp.dest("./assets/fonts")); .pipe(gulp.dest('./assets/fonts'))
}); })
/** /**
* TASK - Start dev watchers * TASK - Start dev watchers
*/ */
gulp.task('watch', function () { gulp.task('watch', function () {
return merge( return merge(
watch(paths.scripts.watch, {base: './'}, function() { return gulp.start('scripts-app'); }), watch(paths.scripts.watch, {base: './'}, function () { return gulp.start('scripts-app') }),
watch(paths.css.watch, {base: './'}, function() { return gulp.start('css-app'); }) watch(paths.css.watch, {base: './'}, function () { return gulp.start('css-app') })
); )
}); })
/** /**
* TASK - Starts development server with watchers * TASK - Starts development server with watchers
*/ */
gulp.task('default', ['watch', 'server']); gulp.task('default', ['watch', 'server'])
gulp.task('dev', function () { gulp.task('dev', function () {
paths.css.includes.pop()
paths.css.includes.push('../core')
paths.css.includes.pop(); paths.fonts.pop()
paths.css.includes.push('../core'); paths.fonts.push('../core/core-client/fonts/**/*')
paths.fonts.pop(); return run('default')
paths.fonts.push('../core/core-client/fonts/**/*'); })
return run('default');
});
/** /**
* TASK - Creates deployment packages * TASK - Creates deployment packages
@@ -204,12 +199,12 @@ gulp.task('dev', function() {
gulp.task('deploy', ['scripts', 'css', 'fonts'], function () { gulp.task('deploy', ['scripts', 'css', 'fonts'], function () {
var zipStream = gulp.src(paths.deploy) var zipStream = gulp.src(paths.deploy)
.pipe(zip('wiki-js.zip')) .pipe(zip('wiki-js.zip'))
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'))
var targzStream = gulp.src(paths.deploy) var targzStream = gulp.src(paths.deploy)
.pipe(tar('wiki-js.tar')) .pipe(tar('wiki-js.tar'))
.pipe(gzip()) .pipe(gzip())
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'))
return merge(zipStream, targzStream); return merge(zipStream, targzStream)
}); })
+121 -169
View File
@@ -1,11 +1,10 @@
"use strict"; 'use strict'
var Promise = require('bluebird'), const Promise = require('bluebird')
path = require('path'), const path = require('path')
fs = Promise.promisifyAll(require("fs-extra")), const fs = Promise.promisifyAll(require('fs-extra'))
_ = require('lodash'), const _ = require('lodash')
farmhash = require('farmhash'), const farmhash = require('farmhash')
moment = require('moment');
/** /**
* Entries Model * Entries Model
@@ -21,14 +20,12 @@ module.exports = {
* @return {Object} Entries model instance * @return {Object} Entries model instance
*/ */
init () { init () {
let self = this
let self = this; self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache')
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
return self;
return self
}, },
/** /**
@@ -38,8 +35,7 @@ module.exports = {
* @return {Promise<Boolean>} True if exists, false otherwise * @return {Promise<Boolean>} True if exists, false otherwise
*/ */
exists (entryPath) { exists (entryPath) {
let self = this
let self = this;
return self.fetchOriginal(entryPath, { return self.fetchOriginal(entryPath, {
parseMarkdown: false, parseMarkdown: false,
@@ -49,11 +45,10 @@ module.exports = {
includeParentInfo: false, includeParentInfo: false,
cache: false cache: false
}).then(() => { }).then(() => {
return true; return true
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
return false; return false
}); })
}, },
/** /**
@@ -63,39 +58,31 @@ module.exports = {
* @return {Promise<Object>} Page Data * @return {Promise<Object>} Page Data
*/ */
fetch (entryPath) { fetch (entryPath) {
let self = this
let self = this; let cpath = self.getCachePath(entryPath)
let cpath = self.getCachePath(entryPath);
return fs.statAsync(cpath).then((st) => { return fs.statAsync(cpath).then((st) => {
return st.isFile(); return st.isFile()
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
return false; return false
}).then((isCache) => { }).then((isCache) => {
if (isCache) { if (isCache) {
// Load from cache // Load from cache
return fs.readFileAsync(cpath).then((contents) => { return fs.readFileAsync(cpath).then((contents) => {
return JSON.parse(contents); return JSON.parse(contents)
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
winston.error('Corrupted cache file. Deleting it...'); winston.error('Corrupted cache file. Deleting it...')
fs.unlinkSync(cpath); fs.unlinkSync(cpath)
return false; return false
}); })
} else { } else {
// Load original // Load original
return self.fetchOriginal(entryPath); return self.fetchOriginal(entryPath)
} }
})
});
}, },
/** /**
@@ -106,11 +93,10 @@ module.exports = {
* @return {Promise<Object>} Page data * @return {Promise<Object>} Page data
*/ */
fetchOriginal (entryPath, options) { fetchOriginal (entryPath, options) {
let self = this
let self = this; let fpath = self.getFullPath(entryPath)
let cpath = self.getCachePath(entryPath)
let fpath = self.getFullPath(entryPath);
let cpath = self.getCachePath(entryPath);
options = _.defaults(options, { options = _.defaults(options, {
parseMarkdown: true, parseMarkdown: true,
@@ -119,12 +105,11 @@ module.exports = {
includeMarkdown: false, includeMarkdown: false,
includeParentInfo: true, includeParentInfo: true,
cache: true cache: true
}); })
return fs.statAsync(fpath).then((st) => { return fs.statAsync(fpath).then((st) => {
if (st.isFile()) { if (st.isFile()) {
return fs.readFileAsync(fpath, 'utf8').then((contents) => { return fs.readFileAsync(fpath, 'utf8').then((contents) => {
// Parse contents // Parse contents
let pageData = { let pageData = {
@@ -132,46 +117,43 @@ module.exports = {
html: (options.parseMarkdown) ? mark.parseContent(contents) : '', html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
meta: (options.parseMeta) ? mark.parseMeta(contents) : {}, meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
tree: (options.parseTree) ? mark.parseTree(contents) : [] tree: (options.parseTree) ? mark.parseTree(contents) : []
};
if(!pageData.meta.title) {
pageData.meta.title = _.startCase(entryPath);
} }
pageData.meta.path = entryPath; if (!pageData.meta.title) {
pageData.meta.title = _.startCase(entryPath)
}
pageData.meta.path = entryPath
// Get parent // Get parent
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => { let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
return (pageData.parent = parentData); return (pageData.parent = parentData)
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
return (pageData.parent = false); return (pageData.parent = false)
}) : Promise.resolve(true); }) : Promise.resolve(true)
return parentPromise.then(() => { return parentPromise.then(() => {
// Cache to disk // Cache to disk
if (options.cache) { if (options.cache) {
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false); let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
return fs.writeFileAsync(cpath, cacheData).catch((err) => { return fs.writeFileAsync(cpath, cacheData).catch((err) => {
winston.error('Unable to write to cache! Performance may be affected.'); winston.error('Unable to write to cache! Performance may be affected.')
return true; winston.error(err)
}); return true
})
} else { } else {
return true; return true
} }
}).return(pageData)
}).return(pageData); })
});
} else { } else {
return false; return false
} }
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!')); return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'))
}); })
}, },
/** /**
@@ -181,19 +163,17 @@ module.exports = {
* @return {String} Safe entry path * @return {String} Safe entry path
*/ */
parsePath (urlPath) { parsePath (urlPath) {
let wlist = new RegExp('[^a-z0-9/-]', 'g')
let wlist = new RegExp('[^a-z0-9/\-]','g'); urlPath = _.toLower(urlPath).replace(wlist, '')
urlPath = _.toLower(urlPath).replace(wlist, '');
if (urlPath === '/') { if (urlPath === '/') {
urlPath = 'home'; urlPath = 'home'
} }
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p); }); let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p) })
return _.join(urlParts, '/');
return _.join(urlParts, '/')
}, },
/** /**
@@ -203,38 +183,32 @@ module.exports = {
* @return {Promise<Object|False>} The parent information. * @return {Promise<Object|False>} The parent information.
*/ */
getParentInfo (entryPath) { getParentInfo (entryPath) {
let self = this
let self = this;
if (_.includes(entryPath, '/')) { if (_.includes(entryPath, '/')) {
let parentParts = _.initial(_.split(entryPath, '/'))
let parentParts = _.initial(_.split(entryPath, '/')); let parentPath = _.join(parentParts, '/')
let parentPath = _.join(parentParts,'/'); let parentFile = _.last(parentParts)
let parentFile = _.last(parentParts); let fpath = self.getFullPath(parentPath)
let fpath = self.getFullPath(parentPath);
return fs.statAsync(fpath).then((st) => { return fs.statAsync(fpath).then((st) => {
if (st.isFile()) { if (st.isFile()) {
return fs.readFileAsync(fpath, 'utf8').then((contents) => { return fs.readFileAsync(fpath, 'utf8').then((contents) => {
let pageMeta = mark.parseMeta(contents)
let pageMeta = mark.parseMeta(contents);
return { return {
path: parentPath, path: parentPath,
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile), title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
};
});
} else {
return Promise.reject(new Error('Parent entry is not a valid file.'));
} }
}); })
} else { } else {
return Promise.reject(new Error('Parent entry is root.')); return Promise.reject(new Error('Parent entry is not a valid file.'))
}
})
} else {
return Promise.reject(new Error('Parent entry is root.'))
} }
}, },
/** /**
@@ -244,7 +218,7 @@ module.exports = {
* @return {String} The full path. * @return {String} The full path.
*/ */
getFullPath (entryPath) { getFullPath (entryPath) {
return path.join(this._repoPath, entryPath + '.md'); return path.join(this._repoPath, entryPath + '.md')
}, },
/** /**
@@ -254,7 +228,7 @@ module.exports = {
* @return {String} The full cache path. * @return {String} The full cache path.
*/ */
getCachePath (entryPath) { getCachePath (entryPath) {
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json'); return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json')
}, },
/** /**
@@ -264,8 +238,8 @@ module.exports = {
* @return {String} The entry path * @return {String} The entry path
*/ */
getEntryPathFromFullPath (fullPath) { getEntryPathFromFullPath (fullPath) {
let absRepoPath = path.resolve(ROOTPATH, this._repoPath); let absRepoPath = path.resolve(ROOTPATH, this._repoPath)
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'),'/').value(); return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'), '/').value()
}, },
/** /**
@@ -276,23 +250,21 @@ module.exports = {
* @return {Promise<Boolean>} True on success, false on failure * @return {Promise<Boolean>} True on success, false on failure
*/ */
update (entryPath, contents) { update (entryPath, contents) {
let self = this
let self = this; let fpath = self.getFullPath(entryPath)
let fpath = self.getFullPath(entryPath);
return fs.statAsync(fpath).then((st) => { return fs.statAsync(fpath).then((st) => {
if (st.isFile()) { if (st.isFile()) {
return self.makePersistent(entryPath, contents).then(() => { return self.makePersistent(entryPath, contents).then(() => {
return self.updateCache(entryPath); return self.updateCache(entryPath)
}); })
} else { } else {
return Promise.reject(new Error('Entry does not exist!')); return Promise.reject(new Error('Entry does not exist!'))
} }
}).catch((err) => { }).catch((err) => {
winston.error(err); winston.error(err)
return Promise.reject(new Error('Failed to save document.')); return Promise.reject(new Error('Failed to save document.'))
}); })
}, },
/** /**
@@ -302,8 +274,7 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
updateCache (entryPath) { updateCache (entryPath) {
let self = this
let self = this;
return self.fetchOriginal(entryPath, { return self.fetchOriginal(entryPath, {
parseMarkdown: true, parseMarkdown: true,
@@ -318,7 +289,7 @@ module.exports = {
meta: pageData.meta, meta: pageData.meta,
parent: pageData.parent || {}, parent: pageData.parent || {},
text: mark.removeMarkdown(pageData.markdown) text: mark.removeMarkdown(pageData.markdown)
}; }
}).then((content) => { }).then((content) => {
return db.Entry.findOneAndUpdate({ return db.Entry.findOneAndUpdate({
_id: content.entryPath _id: content.entryPath
@@ -331,9 +302,8 @@ module.exports = {
}, { }, {
new: true, new: true,
upsert: true upsert: true
}); })
}); })
}, },
/** /**
@@ -344,22 +314,20 @@ module.exports = {
* @return {Promise<Boolean>} True on success, false on failure * @return {Promise<Boolean>} True on success, false on failure
*/ */
create (entryPath, contents) { create (entryPath, contents) {
let self = this
let self = this;
return self.exists(entryPath).then((docExists) => { return self.exists(entryPath).then((docExists) => {
if (!docExists) { if (!docExists) {
return self.makePersistent(entryPath, contents).then(() => { return self.makePersistent(entryPath, contents).then(() => {
return self.updateCache(entryPath); return self.updateCache(entryPath)
}); })
} else { } else {
return Promise.reject(new Error('Entry already exists!')); return Promise.reject(new Error('Entry already exists!'))
} }
}).catch((err) => { }).catch((err) => {
winston.error(err); winston.error(err)
return Promise.reject(new Error('Something went wrong.')); return Promise.reject(new Error('Something went wrong.'))
}); })
}, },
/** /**
@@ -370,14 +338,12 @@ module.exports = {
* @return {Promise<Boolean>} True on success, false on failure * @return {Promise<Boolean>} True on success, false on failure
*/ */
makePersistent (entryPath, contents) { makePersistent (entryPath, contents) {
let self = this
let self = this; let fpath = self.getFullPath(entryPath)
let fpath = self.getFullPath(entryPath);
return fs.outputFileAsync(fpath, contents).then(() => { return fs.outputFileAsync(fpath, contents).then(() => {
return git.commitDocument(entryPath); return git.commitDocument(entryPath)
}); })
}, },
/** /**
@@ -388,35 +354,31 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
move (entryPath, newEntryPath) { move (entryPath, newEntryPath) {
let self = this
let self = this;
if (_.isEmpty(entryPath) || entryPath === 'home') { if (_.isEmpty(entryPath) || entryPath === 'home') {
return Promise.reject(new Error('Invalid path!')); return Promise.reject(new Error('Invalid path!'))
} }
return git.moveDocument(entryPath, newEntryPath).then(() => { return git.moveDocument(entryPath, newEntryPath).then(() => {
return git.commitDocument(newEntryPath).then(() => { return git.commitDocument(newEntryPath).then(() => {
// Delete old cache version // Delete old cache version
let oldEntryCachePath = self.getCachePath(entryPath); let oldEntryCachePath = self.getCachePath(entryPath)
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true; }); fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
// Delete old index entry // Delete old index entry
ws.emit('searchDel', { ws.emit('searchDel', {
auth: WSInternalKey, auth: WSInternalKey,
entryPath entryPath
}); })
// Create cache for new entry // Create cache for new entry
return self.updateCache(newEntryPath); return self.updateCache(newEntryPath)
})
}); })
});
}, },
/** /**
@@ -426,14 +388,11 @@ module.exports = {
* @return {Promise<String>} Starter content * @return {Promise<String>} Starter content
*/ */
getStarter (entryPath) { getStarter (entryPath) {
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
let self = this;
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')));
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => { return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle); return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
}); })
}, },
/** /**
@@ -443,29 +402,26 @@ module.exports = {
* @return {Promise<Object>} Promise of the search results * @return {Promise<Object>} Promise of the search results
*/ */
search (terms) { search (terms) {
let self = this;
terms = _.chain(terms) terms = _.chain(terms)
.deburr() .deburr()
.toLower() .toLower()
.trim() .trim()
.replace(/[^a-z0-9\- ]/g, '') .replace(/[^a-z0-9\- ]/g, '')
.split(' ') .split(' ')
.filter((f) => { return !_.isEmpty(f); }) .filter((f) => { return !_.isEmpty(f) })
.join(' ') .join(' ')
.value(); .value()
return db.Entry.find( return db.Entry.find(
{ $text: { $search: terms } }, { $text: { $search: terms } },
{ score: { $meta: "textScore" }, title: 1 } { score: { $meta: 'textScore' }, title: 1 }
) )
.sort({ score: { $meta: "textScore" } }) .sort({ score: { $meta: 'textScore' } })
.limit(10) .limit(10)
.exec() .exec()
.then((hits) => { .then((hits) => {
if (hits.length < 5) { if (hits.length < 5) {
let regMatch = new RegExp('^' + _.split(terms, ' ')[0]); let regMatch = new RegExp('^' + _.split(terms, ' ')[0])
return db.Entry.find({ return db.Entry.find({
_id: { $regex: regMatch } _id: { $regex: regMatch }
}, '_id') }, '_id')
@@ -476,25 +432,21 @@ module.exports = {
return { return {
match: hits, match: hits,
suggest: (matches) ? _.map(matches, '_id') : [] suggest: (matches) ? _.map(matches, '_id') : []
}; }
}); })
} else { } else {
return { return {
match: _.filter(hits, (h) => { return h._doc.score >= 1; }), match: _.filter(hits, (h) => { return h._doc.score >= 1 }),
suggest: [] suggest: []
};
} }
}
}).catch((err) => { }).catch((err) => {
winston.error(err)
winston.error(err);
return { return {
match: [], match: [],
suggest: [] suggest: []
}; }
})
});
} }
}; }
+76 -103
View File
@@ -1,13 +1,11 @@
"use strict"; 'use strict'
var Git = require("git-wrapper2-promise"), const Git = require('git-wrapper2-promise')
Promise = require('bluebird'), const Promise = require('bluebird')
path = require('path'), const path = require('path')
os = require('os'), const fs = Promise.promisifyAll(require('fs'))
fs = Promise.promisifyAll(require("fs")), const _ = require('lodash')
moment = require('moment'), const URL = require('url')
_ = require('lodash'),
URL = require('url');
/** /**
* Git Model * Git Model
@@ -37,28 +35,26 @@ module.exports = {
* @return {Object} Git model instance * @return {Object} Git model instance
*/ */
init () { init () {
let self = this
let self = this;
// -> Build repository path // -> Build repository path
if (_.isEmpty(appconfig.paths.repo)) { if (_.isEmpty(appconfig.paths.repo)) {
self._repo.path = path.join(ROOTPATH, 'repo'); self._repo.path = path.join(ROOTPATH, 'repo')
} else { } else {
self._repo.path = appconfig.paths.repo; self._repo.path = appconfig.paths.repo
} }
// -> Initialize repository // -> Initialize repository
self.onReady = self._initRepo(appconfig); self.onReady = self._initRepo(appconfig)
// Define signature // Define signature
self._signature.name = appconfig.git.signature.name || 'Wiki'; self._signature.name = appconfig.git.signature.name || 'Wiki'
self._signature.email = appconfig.git.signature.email || 'user@example.com'; self._signature.email = appconfig.git.signature.email || 'user@example.com'
return self;
return self
}, },
/** /**
@@ -68,60 +64,54 @@ module.exports = {
* @return {Object} Promise * @return {Object} Promise
*/ */
_initRepo (appconfig) { _initRepo (appconfig) {
let self = this
let self = this; winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
// -> Check if path is accessible // -> Check if path is accessible
return fs.mkdirAsync(self._repo.path).catch((err) => { return fs.mkdirAsync(self._repo.path).catch((err) => {
if (err.code !== 'EEXIST') { if (err.code !== 'EEXIST') {
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.'); winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.')
} }
}).then(() => { }).then(() => {
self._git = new Git({ 'git-dir': self._repo.path })
self._git = new Git({ 'git-dir': self._repo.path });
// -> Check if path already contains a git working folder // -> Check if path already contains a git working folder
return self._git.isRepo().then((isRepo) => { return self._git.isRepo().then((isRepo) => {
self._repo.exists = isRepo; self._repo.exists = isRepo
return (!isRepo) ? self._git.exec('init') : true; return (!isRepo) ? self._git.exec('init') : true
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
self._repo.exists = false; self._repo.exists = false
}); })
}).then(() => { }).then(() => {
// Initialize remote // Initialize remote
let urlObj = URL.parse(appconfig.git.url); let urlObj = URL.parse(appconfig.git.url)
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : ''); urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
self._url = URL.format(urlObj); self._url = URL.format(urlObj)
return self._git.exec('remote', 'show').then((cProc) => { return self._git.exec('remote', 'show').then((cProc) => {
let out = cProc.stdout.toString(); let out = cProc.stdout.toString()
if (_.includes(out, 'origin')) { if (_.includes(out, 'origin')) {
return true; return true
} else { } else {
return Promise.join( return Promise.join(
self._git.exec('config', ['--local', 'user.name', self._signature.name]), self._git.exec('config', ['--local', 'user.name', self._signature.name]),
self._git.exec('config', ['--local', 'user.email', self._signature.email]) self._git.exec('config', ['--local', 'user.email', self._signature.email])
).then(() => { ).then(() => {
return self._git.exec('remote', ['add', 'origin', self._url]); return self._git.exec('remote', ['add', 'origin', self._url])
}); })
} }
}); })
}).catch((err) => { }).catch((err) => {
winston.error('[' + PROCNAME + '][GIT] Git remote error!'); winston.error('[' + PROCNAME + '][GIT] Git remote error!')
throw err; throw err
}).then(() => { }).then(() => {
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.'); winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
return true; return true
}); })
}, },
/** /**
@@ -130,9 +120,7 @@ module.exports = {
* @return {String} The repo path. * @return {String} The repo path.
*/ */
getRepoPath () { getRepoPath () {
return this._repo.path || path.join(ROOTPATH, 'repo')
return this._repo.path || path.join(ROOTPATH, 'repo');
}, },
/** /**
@@ -141,49 +129,40 @@ module.exports = {
* @return {Promise} Resolve on sync success * @return {Promise} Resolve on sync success
*/ */
resync () { resync () {
let self = this
let self = this;
// Fetch // Fetch
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...'); winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
return self._git.pull('origin', self._repo.branch).then((cProc) => { return self._git.pull('origin', self._repo.branch).then((cProc) => {
winston.info('[' + PROCNAME + '][GIT] Pull completed.'); winston.info('[' + PROCNAME + '][GIT] Pull completed.')
}) })
.catch((err) => { .catch((err) => {
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!'); winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
throw err; throw err
}) })
.then(() => { .then(() => {
// Check for changes // Check for changes
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => { return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
let out = cProc.stdout.toString(); let out = cProc.stdout.toString()
if (_.includes(out, 'commit')) { if (_.includes(out, 'commit')) {
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...');
return self._git.push('origin', self._repo.branch).then(() => { return self._git.push('origin', self._repo.branch).then(() => {
return winston.info('[' + PROCNAME + '][GIT] Push completed.'); return winston.info('[' + PROCNAME + '][GIT] Push completed.')
}); })
} else { } else {
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.');
} }
return true; return true
})
});
}) })
.catch((err) => { .catch((err) => {
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!'); winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
throw err; throw err
}); })
}, },
/** /**
@@ -193,23 +172,21 @@ module.exports = {
* @return {Promise} Resolve on commit success * @return {Promise} Resolve on commit success
*/ */
commitDocument (entryPath) { commitDocument (entryPath) {
let self = this
let self = this; let gitFilePath = entryPath + '.md'
let gitFilePath = entryPath + '.md'; let commitMsg = ''
let commitMsg = '';
return self._git.exec('ls-files', gitFilePath).then((cProc) => { return self._git.exec('ls-files', gitFilePath).then((cProc) => {
let out = cProc.stdout.toString(); let out = cProc.stdout.toString()
return _.includes(out, gitFilePath); return _.includes(out, gitFilePath)
}).then((isTracked) => { }).then((isTracked) => {
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath; commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
return self._git.add(gitFilePath); return self._git.add(gitFilePath)
}).then(() => { }).then(() => {
return self._git.commit(commitMsg).catch((err) => { return self._git.commit(commitMsg).catch((err) => {
if(_.includes(err.stdout, 'nothing to commit')) { return true; } if (_.includes(err.stdout, 'nothing to commit')) { return true }
}); })
}); })
}, },
/** /**
@@ -220,20 +197,18 @@ module.exports = {
* @return {Promise<Boolean>} Resolve on success * @return {Promise<Boolean>} Resolve on success
*/ */
moveDocument (entryPath, newEntryPath) { moveDocument (entryPath, newEntryPath) {
let self = this
let self = this; let gitFilePath = entryPath + '.md'
let gitFilePath = entryPath + '.md'; let gitNewFilePath = newEntryPath + '.md'
let gitNewFilePath = newEntryPath + '.md';
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => { return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
let out = cProc.stdout.toString(); let out = cProc.stdout.toString()
if (_.includes(out, 'fatal')) { if (_.includes(out, 'fatal')) {
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ','))); let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
throw new Error(errorMsg); throw new Error(errorMsg)
} }
return true; return true
}); })
}, },
/** /**
@@ -243,16 +218,14 @@ module.exports = {
* @return {Promise} Resolve on commit success * @return {Promise} Resolve on commit success
*/ */
commitUploads (msg) { commitUploads (msg) {
let self = this
let self = this; msg = msg || 'Uploads repository sync'
msg = msg || "Uploads repository sync";
return self._git.add('uploads').then(() => { return self._git.add('uploads').then(() => {
return self._git.commit(msg).catch((err) => { return self._git.commit(msg).catch((err) => {
if(_.includes(err.stdout, 'nothing to commit')) { return true; } if (_.includes(err.stdout, 'nothing to commit')) { return true }
}); })
}); })
} }
}; }
+7 -13
View File
@@ -1,6 +1,6 @@
"use strict"; 'use strict'
const crypto = require('crypto'); const crypto = require('crypto')
/** /**
* Internal Authentication * Internal Authentication
@@ -10,23 +10,17 @@ module.exports = {
_curKey: false, _curKey: false,
init (inKey) { init (inKey) {
this._curKey = inKey
this._curKey = inKey; return this
return this;
}, },
generateKey () { generateKey () {
return crypto.randomBytes(20).toString('hex')
return crypto.randomBytes(20).toString('hex');
}, },
validateKey (inKey) { validateKey (inKey) {
return inKey === this._curKey
return inKey === this._curKey;
} }
}; }
+49 -60
View File
@@ -1,11 +1,11 @@
"use strict"; 'use strict'
var path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
multer = require('multer'), const multer = require('multer')
os = require('os'), const os = require('os')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Local Data Storage * Local Data Storage
@@ -23,15 +23,13 @@ module.exports = {
* @return {Object} Local Data Storage model instance * @return {Object} Local Data Storage model instance
*/ */
init () { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); this.createBaseDirectories(appconfig)
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs'); this.initMulter(appconfig)
this.createBaseDirectories(appconfig);
this.initMulter(appconfig);
return this;
return this
}, },
/** /**
@@ -41,60 +39,56 @@ module.exports = {
* @return {boolean} Void * @return {boolean} Void
*/ */
initMulter (appconfig) { initMulter (appconfig) {
let maxFileSizes = { let maxFileSizes = {
img: appconfig.uploads.maxImageFileSize * 1024 * 1024, img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024 file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
}; }
// -> IMAGES // -> IMAGES
this.uploadImgHandler = multer({ this.uploadImgHandler = multer({
storage: multer.diskStorage({ storage: multer.diskStorage({
destination: (req, f, cb) => { destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
} }
}), }),
fileFilter: (req, f, cb) => { fileFilter: (req, f, cb) => {
// -> Check filesize // -> Check filesize
if (f.size > maxFileSizes.img) { if (f.size > maxFileSizes.img) {
return cb(null, false); return cb(null, false)
} }
// -> Check MIME type (quick check only) // -> Check MIME type (quick check only)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) { if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
return cb(null, false); return cb(null, false)
} }
cb(null, true); cb(null, true)
} }
}).array('imgfile', 20); }).array('imgfile', 20)
// -> FILES // -> FILES
this.uploadFileHandler = multer({ this.uploadFileHandler = multer({
storage: multer.diskStorage({ storage: multer.diskStorage({
destination: (req, f, cb) => { destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
} }
}), }),
fileFilter: (req, f, cb) => { fileFilter: (req, f, cb) => {
// -> Check filesize // -> Check filesize
if (f.size > maxFileSizes.file) { if (f.size > maxFileSizes.file) {
return cb(null, false); return cb(null, false)
} }
cb(null, true); cb(null, true)
} }
}).array('binfile', 20); }).array('binfile', 20)
return true;
return true
}, },
/** /**
@@ -104,34 +98,31 @@ module.exports = {
* @return {Void} Void * @return {Void} Void
*/ */
createBaseDirectories (appconfig) { createBaseDirectories (appconfig) {
winston.info('[SERVER] Checking data directories...')
winston.info('[SERVER] Checking data directories...');
try { try {
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data)); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
if (os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644'); fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
} }
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo)); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
if (os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644'); fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
} }
} catch (err) { } catch (err) {
winston.error(err); winston.error(err)
} }
winston.info('[SERVER] Data and Repository directories are OK.'); winston.info('[SERVER] Data and Repository directories are OK.')
return;
return
}, },
/** /**
@@ -140,7 +131,7 @@ module.exports = {
* @return {String} The uploads path. * @return {String} The uploads path.
*/ */
getUploadsPath () { getUploadsPath () {
return this._uploadsPath; return this._uploadsPath
}, },
/** /**
@@ -149,7 +140,7 @@ module.exports = {
* @return {String} The thumbs path. * @return {String} The thumbs path.
*/ */
getThumbsPath () { getThumbsPath () {
return this._uploadsThumbsPath; return this._uploadsThumbsPath
}, },
/** /**
@@ -161,27 +152,25 @@ module.exports = {
* @return {Promise<String>} Promise of the accepted filename * @return {Promise<String>} Promise of the accepted filename
*/ */
validateUploadsFilename (f, fld, isImage) { validateUploadsFilename (f, fld, isImage) {
let fObj = path.parse(f)
let fObj = path.parse(f); let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, ''); let fext = _.toLower(fObj.ext)
let fext = _.toLower(fObj.ext);
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) { if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
fext = '.png'; fext = '.png'
} }
f = fname + fext; f = fname + fext
let fpath = path.resolve(this._uploadsPath, fld, f); let fpath = path.resolve(this._uploadsPath, fld, f)
return fs.statAsync(fpath).then((s) => { return fs.statAsync(fpath).then((s) => {
throw new Error('File ' + f + ' already exists.'); throw new Error('File ' + f + ' already exists.')
}).catch((err) => { }).catch((err) => {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
return f; return f
}
throw err
})
} }
throw err;
});
}, }
};
+103 -109
View File
@@ -1,19 +1,18 @@
"use strict"; 'use strict'
var Promise = require('bluebird'), const md = require('markdown-it')
md = require('markdown-it'), const mdEmoji = require('markdown-it-emoji')
mdEmoji = require('markdown-it-emoji'), const mdTaskLists = require('markdown-it-task-lists')
mdTaskLists = require('markdown-it-task-lists'), const mdAbbr = require('markdown-it-abbr')
mdAbbr = require('markdown-it-abbr'), const mdAnchor = require('markdown-it-anchor')
mdAnchor = require('markdown-it-anchor'), const mdFootnote = require('markdown-it-footnote')
mdFootnote = require('markdown-it-footnote'), const mdExternalLinks = require('markdown-it-external-links')
mdExternalLinks = require('markdown-it-external-links'), const mdExpandTabs = require('markdown-it-expand-tabs')
mdExpandTabs = require('markdown-it-expand-tabs'), const mdAttrs = require('markdown-it-attrs')
mdAttrs = require('markdown-it-attrs'), const hljs = require('highlight.js')
hljs = require('highlight.js'), const cheerio = require('cheerio')
cheerio = require('cheerio'), const _ = require('lodash')
_ = require('lodash'), const mdRemove = require('remove-markdown')
mdRemove = require('remove-markdown');
// Load plugins // Load plugins
@@ -24,12 +23,12 @@ var mkdown = md({
highlight (str, lang) { highlight (str, lang) {
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'; return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
} catch (err) { } catch (err) {
return '<pre><code>' + str + '</code></pre>'; return '<pre><code>' + str + '</code></pre>'
} }
} }
return '<pre><code>' + str + '</code></pre>'; return '<pre><code>' + str + '</code></pre>'
} }
}) })
.use(mdEmoji) .use(mdEmoji)
@@ -50,30 +49,30 @@ var mkdown = md({
.use(mdExpandTabs, { .use(mdExpandTabs, {
tabWidth: 4 tabWidth: 4
}) })
.use(mdAttrs); .use(mdAttrs)
// Rendering rules // Rendering rules
mkdown.renderer.rules.emoji = function (token, idx) { mkdown.renderer.rules.emoji = function (token, idx) {
return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'; return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'
}; }
// Video rules // Video rules
const videoRules = [ const videoRules = [
{ {
selector: 'a.youtube', selector: 'a.youtube',
regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'), 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>' output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
}, },
{ {
selector: 'a.vimeo', selector: 'a.vimeo',
regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), 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>' output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
}, },
{ {
selector: 'a.dailymotion', 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'), 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>' output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
}, },
{ {
@@ -90,81 +89,79 @@ const videoRules = [
* @return {Array} TOC tree * @return {Array} TOC tree
*/ */
const parseTree = (content) => { const parseTree = (content) => {
let tokens = md().parse(content, {})
let tokens = md().parse(content, {}); let tocArray = []
let tocArray = [];
// -> Extract headings and their respective levels // -> Extract headings and their respective levels
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type !== "heading_close") { if (tokens[i].type !== 'heading_close') {
continue; continue
} }
const heading = tokens[i - 1]; const heading = tokens[i - 1]
const heading_close = tokens[i]; const headingclose = tokens[i]
if (heading.type === "inline") { if (heading.type === 'inline') {
let content = ""; let content = ''
let anchor = ""; let anchor = ''
if (heading.children && heading.children[0].type === "link_open") { if (heading.children && heading.children[0].type === 'link_open') {
content = heading.children[1].content; content = heading.children[1].content
anchor = _.kebabCase(content); anchor = _.kebabCase(content)
} else { } else {
content = heading.content; content = heading.content
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, "")); anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ''))
} }
tocArray.push({ tocArray.push({
content, content,
anchor, anchor,
level: +heading_close.tag.substr(1, 1) level: +headingclose.tag.substr(1, 1)
}); })
} }
} }
// -> Exclude levels deeper than 2 // -> Exclude levels deeper than 2
_.remove(tocArray, (n) => { return n.level > 2; }); _.remove(tocArray, (n) => { return n.level > 2 })
// -> Build tree from flat array // -> Build tree from flat array
return _.reduce(tocArray, (tree, v) => { return _.reduce(tocArray, (tree, v) => {
let treeLength = tree.length - 1; let treeLength = tree.length - 1
if (v.level < 2) { if (v.level < 2) {
tree.push({ tree.push({
content: v.content, content: v.content,
anchor: v.anchor, anchor: v.anchor,
nodes: [] nodes: []
}); })
} else { } else {
let lastNodeLevel = 1; let lastNodeLevel = 1
let GetNodePath = (startPos) => { let GetNodePath = (startPos) => {
lastNodeLevel++; lastNodeLevel++
if (_.isEmpty(startPos)) { if (_.isEmpty(startPos)) {
startPos = 'nodes'; startPos = 'nodes'
} }
if (lastNodeLevel === v.level) { if (lastNodeLevel === v.level) {
return startPos; return startPos
} else { } else {
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes'); return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes')
} }
}; }
let lastNodePath = GetNodePath(); let lastNodePath = GetNodePath()
let lastNode = _.get(tree[treeLength], lastNodePath); let lastNode = _.get(tree[treeLength], lastNodePath)
if (lastNode) { if (lastNode) {
lastNode.push({ lastNode.push({
content: v.content, content: v.content,
anchor: v.anchor, anchor: v.anchor,
nodes: [] nodes: []
}); })
_.set(tree[treeLength], lastNodePath, lastNode); _.set(tree[treeLength], lastNodePath, lastNode)
} }
} }
return tree; return tree
}, []); }, [])
}
};
/** /**
* Parse markdown content to HTML * Parse markdown content to HTML
@@ -173,86 +170,84 @@ const parseTree = (content) => {
* @return {String} HTML formatted content * @return {String} HTML formatted content
*/ */
const parseContent = (content) => { const parseContent = (content) => {
let output = mkdown.render(content)
let output = mkdown.render(content); let cr = cheerio.load(output)
let cr = cheerio.load(output);
// -> Check for empty first element // -> Check for empty first element
let firstElm = cr.root().children().first()[0]; let firstElm = cr.root().children().first()[0]
if (firstElm.type === 'tag' && firstElm.name === 'p') { if (firstElm.type === 'tag' && firstElm.name === 'p') {
let firstElmChildren = firstElm.children; let firstElmChildren = firstElm.children
if (firstElmChildren.length < 1) { if (firstElmChildren.length < 1) {
firstElm.remove(); firstElm.remove()
} else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') { } else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
cr(firstElm).addClass('is-gapless'); cr(firstElm).addClass('is-gapless')
} }
} }
// -> Remove links in headers // -> Remove links in headers
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => { cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
let txtLink = cr(elm).text(); let txtLink = cr(elm).text()
cr(elm).replaceWith(txtLink); cr(elm).replaceWith(txtLink)
}); })
// -> Re-attach blockquote styling classes to their parents // -> Re-attach blockquote styling classes to their parents
cr.root().children('blockquote').each((i, elm) => { cr.root().children('blockquote').each((i, elm) => {
if (cr(elm).children().length > 0) { if (cr(elm).children().length > 0) {
let bqLastChild = cr(elm).children().last()[0]; let bqLastChild = cr(elm).children().last()[0]
let bqLastChildClasses = cr(bqLastChild).attr('class'); let bqLastChildClasses = cr(bqLastChild).attr('class')
if (bqLastChildClasses && bqLastChildClasses.length > 0) { if (bqLastChildClasses && bqLastChildClasses.length > 0) {
cr(bqLastChild).removeAttr('class'); cr(bqLastChild).removeAttr('class')
cr(elm).addClass(bqLastChildClasses); cr(elm).addClass(bqLastChildClasses)
} }
} }
}); })
// -> Enclose content below headers // -> Enclose content below headers
cr('h2').each((i, elm) => { cr('h2').each((i, elm) => {
let subH2Content = cr(elm).nextUntil('h1, h2'); let subH2Content = cr(elm).nextUntil('h1, h2')
cr(elm).after('<div class="indent-h2"></div>'); cr(elm).after('<div class="indent-h2"></div>')
let subH2Container = cr(elm).next('.indent-h2'); let subH2Container = cr(elm).next('.indent-h2')
_.forEach(subH2Content, (ch) => { _.forEach(subH2Content, (ch) => {
cr(subH2Container).append(ch); cr(subH2Container).append(ch)
}); })
}); })
cr('h3').each((i, elm) => { cr('h3').each((i, elm) => {
let subH3Content = cr(elm).nextUntil('h1, h2, h3'); let subH3Content = cr(elm).nextUntil('h1, h2, h3')
cr(elm).after('<div class="indent-h3"></div>'); cr(elm).after('<div class="indent-h3"></div>')
let subH3Container = cr(elm).next('.indent-h3'); let subH3Container = cr(elm).next('.indent-h3')
_.forEach(subH3Content, (ch) => { _.forEach(subH3Content, (ch) => {
cr(subH3Container).append(ch); cr(subH3Container).append(ch)
}); })
}); })
// Replace video links with embeds // Replace video links with embeds
_.forEach(videoRules, (vrule) => { _.forEach(videoRules, (vrule) => {
cr(vrule.selector).each((i, elm) => { cr(vrule.selector).each((i, elm) => {
let originLink = cr(elm).attr('href'); let originLink = cr(elm).attr('href')
if (vrule.regexp) { if (vrule.regexp) {
let vidMatches = originLink.match(vrule.regexp); let vidMatches = originLink.match(vrule.regexp)
if ((vidMatches && _.isArray(vidMatches))) { if ((vidMatches && _.isArray(vidMatches))) {
vidMatches = _.filter(vidMatches, (f) => { vidMatches = _.filter(vidMatches, (f) => {
return f && _.isString(f); return f && _.isString(f)
}); })
originLink = _.last(vidMatches); originLink = _.last(vidMatches)
} }
} }
let processedLink = _.replace(vrule.output, '{0}', originLink); let processedLink = _.replace(vrule.output, '{0}', originLink)
cr(elm).replaceWith(processedLink); cr(elm).replaceWith(processedLink)
}); })
}); })
output = cr.html(); output = cr.html()
return output; return output
}
};
/** /**
* Parse meta-data tags from content * Parse meta-data tags from content
@@ -261,16 +256,15 @@ const parseContent = (content) => {
* @return {Object} Properties found in the content and their values * @return {Object} Properties found in the content and their values
*/ */
const parseMeta = (content) => { const parseMeta = (content) => {
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->', 'g')
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->','g'); let results = {}
let results = {}, match; let match
while(match = commentMeta.exec(content)) { while ((match = commentMeta.exec(content)) !== null) {
results[_.toLower(match[1])] = _.trim(match[2]); results[_.toLower(match[1])] = _.trim(match[2])
} }
return results; return results
}
};
module.exports = { module.exports = {
@@ -285,7 +279,7 @@ module.exports = {
meta: parseMeta(content), meta: parseMeta(content),
html: parseContent(content), html: parseContent(content),
tree: parseTree(content) tree: parseTree(content)
}; }
}, },
parseContent, parseContent,
@@ -312,7 +306,7 @@ module.exports = {
.replace(/(\b(\w{1,2})\b(\W|$))/g, '') .replace(/(\b(\w{1,2})\b(\W|$))/g, '')
.replace(/\s\s+/g, ' ') .replace(/\s\s+/g, ' ')
.value() .value()
); )
} }
}; }
+72 -109
View File
@@ -1,16 +1,15 @@
"use strict"; 'use strict'
var path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
readChunk = require('read-chunk'), const readChunk = require('read-chunk')
fileType = require('file-type'), const fileType = require('file-type')
mime = require('mime-types'), const mime = require('mime-types')
farmhash = require('farmhash'), const farmhash = require('farmhash')
moment = require('moment'), const chokidar = require('chokidar')
chokidar = require('chokidar'), const sharp = require('sharp')
sharp = require('sharp'), const _ = require('lodash')
_ = require('lodash');
/** /**
* Uploads - Agent * Uploads - Agent
@@ -28,17 +27,15 @@ module.exports = {
* @return {Object} Uploads model instance * @return {Object} Uploads model instance
*/ */
init () { init () {
let self = this
let self = this; self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
// Disable Sharp cache, as it cause file locks issues when deleting uploads. // Disable Sharp cache, as it cause file locks issues when deleting uploads.
sharp.cache(false); sharp.cache(false)
return self;
return self
}, },
/** /**
@@ -47,8 +44,7 @@ module.exports = {
* @return {Void} Void * @return {Void} Void
*/ */
watch () { watch () {
let self = this
let self = this;
self._watcher = chokidar.watch(self._uploadsPath, { self._watcher = chokidar.watch(self._uploadsPath, {
persistent: true, persistent: true,
@@ -56,30 +52,24 @@ module.exports = {
cwd: self._uploadsPath, cwd: self._uploadsPath,
depth: 1, depth: 1,
awaitWriteFinish: true awaitWriteFinish: true
}); })
// -> Add new upload file // -> Add new upload file
self._watcher.on('add', (p) => { self._watcher.on('add', (p) => {
let pInfo = self.parseUploadsRelPath(p)
let pInfo = self.parseUploadsRelPath(p);
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => { return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true }); return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
}).then(() => { }).then(() => {
return git.commitUploads('Uploaded ' + p); return git.commitUploads('Uploaded ' + p)
}); })
})
});
// -> Remove upload file // -> Remove upload file
self._watcher.on('unlink', (p) => { self._watcher.on('unlink', (p) => {
return git.commitUploads('Deleted/Renamed ' + p)
let pInfo = self.parseUploadsRelPath(p); })
return git.commitUploads('Deleted/Renamed ' + p);
});
}, },
/** /**
@@ -88,19 +78,16 @@ module.exports = {
* @return {Promise<Void>} Promise of the scan operation * @return {Promise<Void>} Promise of the scan operation
*/ */
initialScan () { initialScan () {
let self = this
let self = this;
return fs.readdirAsync(self._uploadsPath).then((ls) => { return fs.readdirAsync(self._uploadsPath).then((ls) => {
// Get all folders // Get all folders
return Promise.map(ls, (f) => { return Promise.map(ls, (f) => {
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; }); return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => { }).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {
let folderNames = _.map(arrDirs, 'filename')
let folderNames = _.map(arrDirs, 'filename'); folderNames.unshift('')
folderNames.unshift('');
// Add folders to DB // Add folders to DB
@@ -109,53 +96,43 @@ module.exports = {
return { return {
_id: 'f:' + f, _id: 'f:' + f,
name: f name: f
}; }
})); }))
}).then(() => { }).then(() => {
// Travel each directory and scan files // Travel each directory and scan files
let allFiles = []; let allFiles = []
return Promise.map(folderNames, (fldName) => { return Promise.map(folderNames, (fldName) => {
let fldPath = path.join(self._uploadsPath, fldName)
let fldPath = path.join(self._uploadsPath, fldName);
return fs.readdirAsync(fldPath).then((fList) => { return fs.readdirAsync(fldPath).then((fList) => {
return Promise.map(fList, (f) => { return Promise.map(fList, (f) => {
return upl.processFile(fldName, f).then((mData) => { return upl.processFile(fldName, f).then((mData) => {
if (mData) { if (mData) {
allFiles.push(mData); allFiles.push(mData)
} }
return true; return true
}); })
}, {concurrency: 3}); }, {concurrency: 3})
}); })
}, {concurrency: 1}).finally(() => { }, {concurrency: 1}).finally(() => {
// Add files to DB // Add files to DB
return db.UplFile.remove({}).then(() => { return db.UplFile.remove({}).then(() => {
if (_.isArray(allFiles) && allFiles.length > 0) { if (_.isArray(allFiles) && allFiles.length > 0) {
return db.UplFile.insertMany(allFiles); return db.UplFile.insertMany(allFiles)
} else { } else {
return true; return true
} }
}); })
})
}); })
})
});
});
}).then(() => { }).then(() => {
// Watch for new changes // Watch for new changes
return upl.watch(); return upl.watch()
})
});
}, },
/** /**
@@ -165,13 +142,11 @@ module.exports = {
* @return {Object} Parsed path (folder and filename) * @return {Object} Parsed path (folder and filename)
*/ */
parseUploadsRelPath (f) { parseUploadsRelPath (f) {
let fObj = path.parse(f)
let fObj = path.parse(f);
return { return {
folder: fObj.dir, folder: fObj.dir,
filename: fObj.base filename: fObj.base
}; }
}, },
/** /**
@@ -182,25 +157,23 @@ module.exports = {
* @return {Promise<Object>} Promise of the file metadata * @return {Promise<Object>} Promise of the file metadata
*/ */
processFile (fldName, f) { processFile (fldName, f) {
let self = this
let self = this; let fldPath = path.join(self._uploadsPath, fldName)
let fPath = path.join(fldPath, f)
let fldPath = path.join(self._uploadsPath, fldName); let fPathObj = path.parse(fPath)
let fPath = path.join(fldPath, f); let fUid = farmhash.fingerprint32(fldName + '/' + f)
let fPathObj = path.parse(fPath);
let fUid = farmhash.fingerprint32(fldName + '/' + f);
return fs.statAsync(fPath).then((s) => { return fs.statAsync(fPath).then((s) => {
if (!s.isFile()) { return false }
if(!s.isFile()) { return false; }
// Get MIME info // Get MIME info
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262)); let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))
if (_.isNil(mimeInfo)) { if (_.isNil(mimeInfo)) {
mimeInfo = { mimeInfo = {
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream' mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
}; }
} }
// Images // Images
@@ -208,9 +181,8 @@ module.exports = {
if (s.size < 3145728) { // ignore files larger than 3MB if (s.size < 3145728) { // ignore files larger than 3MB
if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) { if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return self.getImageMetadata(fPath).then((mImgData) => { return self.getImageMetadata(fPath).then((mImgData) => {
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png')); let cacheThumbnailPathStr = path.format(cacheThumbnailPath)
let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
let mData = { let mData = {
_id: fUid, _id: fUid,
@@ -221,23 +193,20 @@ module.exports = {
filename: f, filename: f,
basename: fPathObj.name, basename: fPathObj.name,
filesize: s.size filesize: s.size
}; }
// Generate thumbnail // Generate thumbnail
return fs.statAsync(cacheThumbnailPathStr).then((st) => { return fs.statAsync(cacheThumbnailPathStr).then((st) => {
return st.isFile(); return st.isFile()
}).catch((err) => { }).catch((err) => { // eslint-disable-line handle-callback-err
return false; return false
}).then((thumbExists) => { }).then((thumbExists) => {
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => { return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
return self.generateThumbnail(fPath, cacheThumbnailPathStr); return self.generateThumbnail(fPath, cacheThumbnailPathStr)
}).return(mData); }).return(mData)
})
}); })
});
} }
} }
@@ -251,10 +220,8 @@ module.exports = {
filename: f, filename: f,
basename: fPathObj.name, basename: fPathObj.name,
filesize: s.size filesize: s.size
}; }
})
});
}, },
/** /**
@@ -265,7 +232,6 @@ module.exports = {
* @return {Promise<Object>} Promise returning the resized image info * @return {Promise<Object>} Promise returning the resized image info
*/ */
generateThumbnail (sourcePath, destPath) { generateThumbnail (sourcePath, destPath) {
return sharp(sourcePath) return sharp(sourcePath)
.withoutEnlargement() .withoutEnlargement()
.resize(150, 150) .resize(150, 150)
@@ -273,8 +239,7 @@ module.exports = {
.embed() .embed()
.flatten() .flatten()
.toFormat('png') .toFormat('png')
.toFile(destPath); .toFile(destPath)
}, },
/** /**
@@ -284,9 +249,7 @@ module.exports = {
* @return {Object} The image metadata. * @return {Object} The image metadata.
*/ */
getImageMetadata (sourcePath) { getImageMetadata (sourcePath) {
return sharp(sourcePath).metadata()
return sharp(sourcePath).metadata();
} }
}; }
+82 -110
View File
@@ -1,16 +1,15 @@
"use strict"; 'use strict'
const path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
multer = require('multer'), const request = require('request')
request = require('request'), const url = require('url')
url = require('url'), const farmhash = require('farmhash')
farmhash = require('farmhash'), const _ = require('lodash')
_ = require('lodash');
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
const maxDownloadFileSize = 3145728; // 3 MB const maxDownloadFileSize = 3145728 // 3 MB
/** /**
* Uploads * Uploads
@@ -26,12 +25,10 @@ module.exports = {
* @return {Object} Uploads model instance * @return {Object} Uploads model instance
*/ */
init () { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); return this
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
return this;
}, },
/** /**
@@ -40,7 +37,7 @@ module.exports = {
* @return {String} The thumbs path. * @return {String} The thumbs path.
*/ */
getThumbsPath () { getThumbsPath () {
return this._uploadsThumbsPath; return this._uploadsThumbsPath
}, },
/** /**
@@ -50,8 +47,8 @@ module.exports = {
*/ */
getUploadsFolders () { getUploadsFolders () {
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => { return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
return (results) ? _.map(results, 'name') : [{ name: '' }]; return (results) ? _.map(results, 'name') : [{ name: '' }]
}); })
}, },
/** /**
@@ -61,13 +58,12 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
createUploadsFolder (folderName) { createUploadsFolder (folderName) {
let self = this
let self = this; folderName = _.kebabCase(_.trim(folderName))
folderName = _.kebabCase(_.trim(folderName));
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) { if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
return Promise.resolve(self.getUploadsFolders()); return Promise.resolve(self.getUploadsFolders())
} }
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => { return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
@@ -77,11 +73,10 @@ module.exports = {
name: folderName name: folderName
}, { }, {
upsert: true upsert: true
}); })
}).then(() => { }).then(() => {
return self.getUploadsFolders(); return self.getUploadsFolders()
}); })
}, },
/** /**
@@ -91,11 +86,9 @@ module.exports = {
* @return {Boolean} True if valid * @return {Boolean} True if valid
*/ */
validateUploadsFolder (folderName) { validateUploadsFolder (folderName) {
return db.UplFolder.findOne({ name: folderName }).then((f) => { return db.UplFolder.findOne({ name: folderName }).then((f) => {
return (f) ? path.resolve(this._uploadsPath, folderName) : false; return (f) ? path.resolve(this._uploadsPath, folderName) : false
}); })
}, },
/** /**
@@ -108,7 +101,7 @@ module.exports = {
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) { if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
// this._uploadsDb.Files.insert(arrFiles); // this._uploadsDb.Files.insert(arrFiles);
} }
return; return
}, },
/** /**
@@ -119,12 +112,10 @@ module.exports = {
* @return {Array<Object>} The files matching the query * @return {Array<Object>} The files matching the query
*/ */
getUploadsFiles (cat, fld) { getUploadsFiles (cat, fld) {
return db.UplFile.find({ return db.UplFile.find({
category: cat, category: cat,
folder: 'f:' + fld folder: 'f:' + fld
}).sort('filename').exec(); }).sort('filename').exec()
}, },
/** /**
@@ -134,22 +125,20 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
deleteUploadsFile (uid) { deleteUploadsFile (uid) {
let self = this
let self = this;
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => { return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
if (f) { if (f) {
return self.deleteUploadsFileTry(f, 0); return self.deleteUploadsFileTry(f, 0)
} }
return true; return true
}); })
}, },
deleteUploadsFileTry (f, attempt) { deleteUploadsFileTry (f, attempt) {
let self = this
let self = this; let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './';
return Promise.join( return Promise.join(
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')), fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
@@ -157,14 +146,13 @@ module.exports = {
).catch((err) => { ).catch((err) => {
if (err.code === 'EBUSY' && attempt < 5) { if (err.code === 'EBUSY' && attempt < 5) {
return Promise.delay(100).then(() => { return Promise.delay(100).then(() => {
return self.deleteUploadsFileTry(f, attempt + 1); return self.deleteUploadsFileTry(f, attempt + 1)
}); })
} else { } else {
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.'); winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
return true; return true
} }
}); })
}, },
/** /**
@@ -175,62 +163,52 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
downloadFromUrl (fFolder, fUrl) { downloadFromUrl (fFolder, fUrl) {
let fUrlObj = url.parse(fUrl)
let self = this; let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
let destFolder = _.chain(fFolder).trim().toLower().value()
let fUrlObj = url.parse(fUrl);
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'));
let destFolder = _.chain(fFolder).trim().toLower().value();
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => { return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
return Promise.reject(new Error('Invalid Folder')); return Promise.reject(new Error('Invalid Folder'))
} }
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => { return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
let destFilePath = path.resolve(destFolderPath, destFilename)
let destFilePath = path.resolve(destFolderPath, destFilename);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let rq = request({ let rq = request({
url: fUrl, url: fUrl,
method: 'GET', method: 'GET',
followRedirect: true, followRedirect: true,
maxRedirects: 5, maxRedirects: 5,
timeout: 10000 timeout: 10000
}); })
let destFileStream = fs.createWriteStream(destFilePath); let destFileStream = fs.createWriteStream(destFilePath)
let curFileSize = 0; let curFileSize = 0
rq.on('data', (data) => { rq.on('data', (data) => {
curFileSize += data.length; curFileSize += data.length
if (curFileSize > maxDownloadFileSize) { if (curFileSize > maxDownloadFileSize) {
rq.abort(); rq.abort()
destFileStream.destroy(); destFileStream.destroy()
fs.remove(destFilePath); fs.remove(destFilePath)
reject(new Error('Remote file is too large!')); reject(new Error('Remote file is too large!'))
} }
}).on('error', (err) => { }).on('error', (err) => {
destFileStream.destroy(); destFileStream.destroy()
fs.remove(destFilePath); fs.remove(destFilePath)
reject(err); reject(err)
}); })
destFileStream.on('finish', () => { destFileStream.on('finish', () => {
resolve(true); resolve(true)
}); })
rq.pipe(destFileStream);
});
});
});
rq.pipe(destFileStream)
})
})
})
}, },
/** /**
@@ -242,49 +220,46 @@ module.exports = {
* @return {Promise} Promise of the operation * @return {Promise} Promise of the operation
*/ */
moveUploadsFile (uid, fld, nFilename) { moveUploadsFile (uid, fld, nFilename) {
let self = this
let self = this;
return db.UplFolder.findById('f:' + fld).then((folder) => { return db.UplFolder.findById('f:' + fld).then((folder) => {
if (folder) { if (folder) {
return db.UplFile.findById(uid).then((originFile) => { return db.UplFile.findById(uid).then((originFile) => {
// -> Check if rename is valid // -> Check if rename is valid
let nameCheck = null; let nameCheck = null
if (nFilename) { if (nFilename) {
let originFileObj = path.parse(originFile.filename); let originFileObj = path.parse(originFile.filename)
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name); nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name)
} else { } else {
nameCheck = Promise.resolve(originFile.filename); nameCheck = Promise.resolve(originFile.filename)
} }
return nameCheck.then((destFilename) => { return nameCheck.then((destFilename) => {
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'; let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename); let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename); let preMoveOps = []
let preMoveOps = [];
// -> Check for invalid operations // -> Check for invalid operations
if (sourceFilePath === destFilePath) { if (sourceFilePath === destFilePath) {
return Promise.reject(new Error('Invalid Operation!')); return Promise.reject(new Error('Invalid Operation!'))
} }
// -> Delete DB entry // -> Delete DB entry
preMoveOps.push(db.UplFile.findByIdAndRemove(uid)); preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
// -> Move thumbnail ahead to avoid re-generation // -> Move thumbnail ahead to avoid re-generation
if (originFile.category === 'image') { if (originFile.category === 'image') {
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename); let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename)
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png'); let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png'); let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath)); preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
} else { } else {
preMoveOps.push(Promise.resolve(true)); preMoveOps.push(Promise.resolve(true))
} }
// -> Proceed to move actual file // -> Proceed to move actual file
@@ -292,17 +267,14 @@ module.exports = {
return Promise.all(preMoveOps).then(() => { return Promise.all(preMoveOps).then(() => {
return fs.moveAsync(sourceFilePath, destFilePath, { return fs.moveAsync(sourceFilePath, destFilePath, {
clobber: false clobber: false
});
});
}) })
})
}); })
})
} else { } else {
return Promise.reject(new Error('Invalid Destination Folder')); return Promise.reject(new Error('Invalid Destination Folder'))
} }
}); })
} }
}; }
+10 -13
View File
@@ -1,7 +1,6 @@
"use strict"; 'use strict'
var Promise = require('bluebird'), const moment = require('moment-timezone')
moment = require('moment-timezone');
/** /**
* Authentication middleware * Authentication middleware
@@ -12,29 +11,27 @@ var Promise = require('bluebird'),
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
// Is user authenticated ? // Is user authenticated ?
if (!req.isAuthenticated()) { if (!req.isAuthenticated()) {
return res.redirect('/login'); return res.redirect('/login')
} }
// Check permissions // Check permissions
if (!rights.check(req, 'read')) { if (!rights.check(req, 'read')) {
return res.render('error-forbidden'); return res.render('error-forbidden')
} }
// Set i18n locale // Set i18n locale
req.i18n.changeLanguage(req.user.lang); req.i18n.changeLanguage(req.user.lang)
res.locals.userMoment = moment; res.locals.userMoment = moment
res.locals.userMoment.locale(req.user.lang); res.locals.userMoment.locale(req.user.lang)
// Expose user data // Expose user data
res.locals.user = req.user; res.locals.user = req.user
return next(); return next()
}
};
+4 -6
View File
@@ -1,4 +1,4 @@
"use strict"; 'use strict'
/** /**
* Flash middleware * Flash middleware
@@ -9,9 +9,7 @@
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
res.locals.appflash = req.flash('alert')
res.locals.appflash = req.flash('alert'); next()
}
next();
};
+9 -9
View File
@@ -1,3 +1,5 @@
'use strict'
/** /**
* Security Middleware * Security Middleware
* *
@@ -7,22 +9,20 @@
* @return {any} void * @return {any} void
*/ */
module.exports = function (req, res, next) { module.exports = function (req, res, next) {
// -> Disable X-Powered-By // -> Disable X-Powered-By
app.disable('x-powered-by'); app.disable('x-powered-by')
// -> Disable Frame Embedding // -> Disable Frame Embedding
res.set('X-Frame-Options', 'deny'); res.set('X-Frame-Options', 'deny')
// -> Re-enable XSS Fitler if disabled // -> Re-enable XSS Fitler if disabled
res.set('X-XSS-Protection', '1; mode=block'); res.set('X-XSS-Protection', '1; mode=block')
// -> Disable MIME-sniffing // -> Disable MIME-sniffing
res.set('X-Content-Type-Options', 'nosniff'); res.set('X-Content-Type-Options', 'nosniff')
// -> Disable IE Compatibility Mode // -> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge'); res.set('X-UA-Compatible', 'IE=edge')
return next(); return next()
}
};
+3 -3
View File
@@ -1,4 +1,4 @@
"use strict"; 'use strict'
/** /**
* BruteForce schema * BruteForce schema
@@ -13,6 +13,6 @@ var bruteForceSchema = Mongoose.Schema({
firstRequest: Date firstRequest: Date
}, },
expires: { type: Date, index: { expires: '1d' } } expires: { type: Date, index: { expires: '1d' } }
}); })
module.exports = Mongoose.model('Bruteforce', bruteForceSchema); module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
+4 -7
View File
@@ -1,7 +1,4 @@
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Entry schema * Entry schema
@@ -33,7 +30,7 @@ var entrySchema = Mongoose.Schema({
}, },
{ {
timestamps: {} timestamps: {}
}); })
entrySchema.index({ entrySchema.index({
_id: 'text', _id: 'text',
@@ -48,6 +45,6 @@ entrySchema.index({
content: 1 content: 1
}, },
name: 'EntriesTextIndex' name: 'EntriesTextIndex'
}); })
module.exports = Mongoose.model('Entry', entrySchema); module.exports = Mongoose.model('Entry', entrySchema)
+3 -9
View File
@@ -1,7 +1,4 @@
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload File schema * Upload File schema
@@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({
required: true required: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFile', uplFileSchema); module.exports = Mongoose.model('UplFile', uplFileSchema)
+3 -9
View File
@@ -1,7 +1,4 @@
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload Folder schema * Upload Folder schema
@@ -17,9 +14,6 @@ var uplFolderSchema = Mongoose.Schema({
index: true index: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFolder', uplFolderSchema); module.exports = Mongoose.model('UplFolder', uplFolderSchema)
+19 -24
View File
@@ -1,8 +1,8 @@
"use strict"; 'use strict'
const Promise = require('bluebird'), const Promise = require('bluebird')
bcrypt = require('bcryptjs-then'), const bcrypt = require('bcryptjs-then')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Region schema * Region schema
@@ -41,21 +41,17 @@ var userSchema = Mongoose.Schema({
deny: Boolean deny: Boolean
}] }]
}, }, { timestamps: {} })
{
timestamps: {}
});
userSchema.statics.processProfile = (profile) => { userSchema.statics.processProfile = (profile) => {
let primaryEmail = ''
let primaryEmail = '';
if (_.isArray(profile.emails)) { if (_.isArray(profile.emails)) {
let e = _.find(profile.emails, ['primary', true]); let e = _.find(profile.emails, ['primary', true])
primaryEmail = (e) ? e.value : _.first(profile.emails).value; primaryEmail = (e) ? e.value : _.first(profile.emails).value
} else if (_.isString(profile.email) && profile.email.length > 5) { } else if (_.isString(profile.email) && profile.email.length > 5) {
primaryEmail = profile.email; primaryEmail = profile.email
} else { } else {
return Promise.reject(new Error('Invalid User Email')); return Promise.reject(new Error('Invalid User Email'))
} }
return db.User.findOneAndUpdate({ return db.User.findOneAndUpdate({
@@ -70,19 +66,18 @@ userSchema.statics.processProfile = (profile) => {
new: true, new: true,
upsert: true upsert: true
}).then((user) => { }).then((user) => {
return (user) ? user : Promise.reject(new Error('User Upsert failed.')); return user || Promise.reject(new Error('User Upsert failed.'))
}); })
}
};
userSchema.statics.hashPassword = (rawPwd) => { userSchema.statics.hashPassword = (rawPwd) => {
return bcrypt.hash(rawPwd); return bcrypt.hash(rawPwd)
}; }
userSchema.methods.validatePassword = function (rawPwd) { userSchema.methods.validatePassword = function (rawPwd) {
return bcrypt.compare(rawPwd, this.password).then((isValid) => { return bcrypt.compare(rawPwd, this.password).then((isValid) => {
return (isValid) ? true : Promise.reject(new Error('Invalid Login')); return (isValid) ? true : Promise.reject(new Error('Invalid Login'))
}); })
}; }
module.exports = Mongoose.model('User', userSchema); module.exports = Mongoose.model('User', userSchema)
+30
View File
@@ -129,5 +129,35 @@
"twemoji-awesome": "^1.0.4", "twemoji-awesome": "^1.0.4",
"vue": "^2.1.10" "vue": "^2.1.10"
}, },
"standard": {
"globals": [
"app",
"appconfig",
"appdata",
"bgAgent",
"db",
"entries",
"git",
"mark",
"lang",
"lcdata",
"rights",
"upl",
"winston",
"ws",
"Mongoose",
"CORE_PATH",
"ROOTPATH",
"IS_DEBUG",
"PROCNAME",
"WSInternalKey"
],
"ignore": [
"assets/**/*",
"data/**/*",
"node_modules/**/*",
"repo/**/*"
]
},
"snyk": true "snyk": true
} }
+103 -101
View File
@@ -1,122 +1,124 @@
"use strict"; 'use strict'
// =========================================== // ===========================================
// Wiki.js // Wiki.js
// 1.0.0 // 1.0.0
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
global.PROCNAME = 'SERVER'; global.PROCNAME = 'SERVER'
global.ROOTPATH = __dirname; global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'; global.IS_DEBUG = process.env.NODE_ENV === 'development'
if (IS_DEBUG) { if (IS_DEBUG) {
global.CORE_PATH = ROOTPATH + '/../core/'; global.CORE_PATH = ROOTPATH + '/../core/'
} else { } else {
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'; global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
} }
process.env.VIPS_WARNING = false
// ---------------------------------------- // ----------------------------------------
// Load Winston // Load Winston
// ---------------------------------------- // ----------------------------------------
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG); global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
winston.info('[SERVER] Wiki.js is initializing...'); winston.info('[SERVER] Wiki.js is initializing...')
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
let appconf = require(CORE_PATH + 'core-libs/config')(); let appconf = require(CORE_PATH + 'core-libs/config')()
global.appconfig = appconf.config; global.appconfig = appconf.config
global.appdata = appconf.data; global.appdata = appconf.data
global.lcdata = require('./libs/local').init(); global.lcdata = require('./libs/local').init()
global.db = require(CORE_PATH + 'core-libs/mongodb').init(); global.db = require(CORE_PATH + 'core-libs/mongodb').init()
global.entries = require('./libs/entries').init(); global.entries = require('./libs/entries').init()
global.git = require('./libs/git').init(false); global.git = require('./libs/git').init(false)
global.lang = require('i18next'); global.lang = require('i18next')
global.mark = require('./libs/markdown'); global.mark = require('./libs/markdown')
global.upl = require('./libs/uploads').init(); global.upl = require('./libs/uploads').init()
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
// ---------------------------------------- // ----------------------------------------
const _ = require('lodash'); const autoload = require('auto-load')
const autoload = require('auto-load'); const bodyParser = require('body-parser')
const bodyParser = require('body-parser'); const compression = require('compression')
const compression = require('compression'); const cookieParser = require('cookie-parser')
const cookieParser = require('cookie-parser'); const express = require('express')
const express = require('express'); const favicon = require('serve-favicon')
const favicon = require('serve-favicon'); const flash = require('connect-flash')
const flash = require('connect-flash'); const fork = require('child_process').fork
const fork = require('child_process').fork; const http = require('http')
const http = require('http'); const i18nextBackend = require('i18next-node-fs-backend')
const i18next_backend = require('i18next-node-fs-backend'); const i18nextMw = require('i18next-express-middleware')
const i18next_mw = require('i18next-express-middleware'); const passport = require('passport')
const passport = require('passport'); const passportSocketIo = require('passport.socketio')
const passportSocketIo = require('passport.socketio'); const path = require('path')
const path = require('path'); const session = require('express-session')
const session = require('express-session'); const SessionMongoStore = require('connect-mongo')(session)
const sessionMongoStore = require('connect-mongo')(session); const socketio = require('socket.io')
const socketio = require('socket.io');
var mw = autoload(CORE_PATH + '/core-middlewares'); var mw = autoload(CORE_PATH + '/core-middlewares')
var ctrl = autoload(path.join(ROOTPATH, '/controllers')); var ctrl = autoload(path.join(ROOTPATH, '/controllers'))
var libInternalAuth = require('./libs/internalAuth'); var libInternalAuth = require('./libs/internalAuth')
global.WSInternalKey = libInternalAuth.generateKey(); global.WSInternalKey = libInternalAuth.generateKey()
// ---------------------------------------- // ----------------------------------------
// Define Express App // Define Express App
// ---------------------------------------- // ----------------------------------------
global.app = express(); global.app = express()
app.use(compression()); app.use(compression())
// ---------------------------------------- // ----------------------------------------
// Security // Security
// ---------------------------------------- // ----------------------------------------
app.use(mw.security); app.use(mw.security)
// ---------------------------------------- // ----------------------------------------
// Public Assets // Public Assets
// ---------------------------------------- // ----------------------------------------
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico'))); app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(ROOTPATH, 'assets'))); app.use(express.static(path.join(ROOTPATH, 'assets')))
// ---------------------------------------- // ----------------------------------------
// Passport Authentication // Passport Authentication
// ---------------------------------------- // ----------------------------------------
var strategy = require(CORE_PATH + 'core-libs/auth')(passport); require(CORE_PATH + 'core-libs/auth')(passport)
global.rights = require(CORE_PATH + 'core-libs/rights'); global.rights = require(CORE_PATH + 'core-libs/rights')
rights.init(); rights.init()
var sessionStore = new sessionMongoStore({ var sessionStore = new SessionMongoStore({
mongooseConnection: db.connection, mongooseConnection: db.connection,
touchAfter: 15 touchAfter: 15
}); })
app.use(cookieParser()); app.use(cookieParser())
app.use(session({ app.use(session({
name: 'requarkswiki.sid', name: 'requarkswiki.sid',
store: sessionStore, store: sessionStore,
secret: appconfig.sessionSecret, secret: appconfig.sessionSecret,
resave: false, resave: false,
saveUninitialized: false saveUninitialized: false
})); }))
app.use(flash()); app.use(flash())
app.use(passport.initialize()); app.use(passport.initialize())
app.use(passport.session()); app.use(passport.session())
// ---------------------------------------- // ----------------------------------------
// Localization Engine // Localization Engine
// ---------------------------------------- // ----------------------------------------
lang lang
.use(i18next_backend) .use(i18nextBackend)
.use(i18next_mw.LanguageDetector) .use(i18nextMw.LanguageDetector)
.init({ .init({
load: 'languageOnly', load: 'languageOnly',
ns: ['common', 'auth'], ns: ['common', 'auth'],
@@ -128,90 +130,90 @@ lang
backend: { backend: {
loadPath: './locales/{{lng}}/{{ns}}.json' loadPath: './locales/{{lng}}/{{ns}}.json'
} }
}); })
// ---------------------------------------- // ----------------------------------------
// View Engine Setup // View Engine Setup
// ---------------------------------------- // ----------------------------------------
app.use(i18next_mw.handle(lang)); app.use(i18nextMw.handle(lang))
app.set('views', path.join(ROOTPATH, 'views')); app.set('views', path.join(ROOTPATH, 'views'))
app.set('view engine', 'pug'); app.set('view engine', 'pug')
app.use(bodyParser.json()); app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }))
// ---------------------------------------- // ----------------------------------------
// View accessible data // View accessible data
// ---------------------------------------- // ----------------------------------------
app.locals._ = require('lodash'); app.locals._ = require('lodash')
app.locals.moment = require('moment'); app.locals.moment = require('moment')
app.locals.appconfig = appconfig; app.locals.appconfig = appconfig
app.use(mw.flash); app.use(mw.flash)
// ---------------------------------------- // ----------------------------------------
// Controllers // Controllers
// ---------------------------------------- // ----------------------------------------
app.use('/', ctrl.auth); app.use('/', ctrl.auth)
app.use('/uploads', mw.auth, ctrl.uploads); app.use('/uploads', mw.auth, ctrl.uploads)
app.use('/admin', mw.auth, ctrl.admin); app.use('/admin', mw.auth, ctrl.admin)
app.use('/', mw.auth, ctrl.pages); app.use('/', mw.auth, ctrl.pages)
// ---------------------------------------- // ----------------------------------------
// Error handling // Error handling
// ---------------------------------------- // ----------------------------------------
app.use(function (req, res, next) { app.use(function (req, res, next) {
var err = new Error('Not Found'); var err = new Error('Not Found')
err.status = 404; err.status = 404
next(err); next(err)
}); })
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
res.status(err.status || 500); res.status(err.status || 500)
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: IS_DEBUG ? err : {} error: IS_DEBUG ? err : {}
}); })
}); })
// ---------------------------------------- // ----------------------------------------
// Start HTTP server // Start HTTP server
// ---------------------------------------- // ----------------------------------------
winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...'); winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...')
app.set('port', appconfig.port); app.set('port', appconfig.port)
var server = http.createServer(app); var server = http.createServer(app)
var io = socketio(server); var io = socketio(server)
server.listen(appconfig.port); server.listen(appconfig.port)
server.on('error', (error) => { server.on('error', (error) => {
if (error.syscall !== 'listen') { if (error.syscall !== 'listen') {
throw error; throw error
} }
// handle specific listen errors with friendly messages // handle specific listen errors with friendly messages
switch (error.code) { switch (error.code) {
case 'EACCES': case 'EACCES':
console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!'); console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!')
process.exit(1); process.exit(1)
break; break
case 'EADDRINUSE': case 'EADDRINUSE':
console.error('Port ' + appconfig.port + ' is already in use!'); console.error('Port ' + appconfig.port + ' is already in use!')
process.exit(1); process.exit(1)
break; break
default: default:
throw error; throw error
} }
}); })
server.on('listening', () => { server.on('listening', () => {
winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]'); winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]')
}); })
// ---------------------------------------- // ----------------------------------------
// WebSocket // WebSocket
@@ -224,21 +226,21 @@ io.use(passportSocketIo.authorize({
passport, passport,
cookieParser, cookieParser,
success: (data, accept) => { success: (data, accept) => {
accept(); accept()
}, },
fail: (data, message, error, accept) => { fail: (data, message, error, accept) => {
return accept(new Error(message)); return accept(new Error(message))
} }
})); }))
io.on('connection', ctrl.ws); io.on('connection', ctrl.ws)
// ---------------------------------------- // ----------------------------------------
// Start child processes // Start child processes
// ---------------------------------------- // ----------------------------------------
global.bgAgent = fork('agent.js'); global.bgAgent = fork('agent.js')
process.on('exit', (code) => { process.on('exit', (code) => {
bgAgent.disconnect(); bgAgent.disconnect()
}); })
+3
View File
@@ -0,0 +1,3 @@
'use strict'
// TODO
-11
View File
@@ -1,11 +0,0 @@
"use strict";
let path = require('path'),
fs = require('fs');
// ========================================
// Load global modules
// ========================================
global._ = require('lodash');
global.winston = require('winston');
+5 -10
View File
@@ -19,20 +19,15 @@ html
// CSS // CSS
link(type='text/css', rel='stylesheet', href='/css/libs.css') link(type='text/css', rel='stylesheet', href='/css/libs.css')
link(type='text/css', rel='stylesheet', href='/css/app.css') link(type='text/css', rel='stylesheet', href='/css/error.css')
body(class='server-error') body(class='is-error')
section.hero.is-warning.is-fullheight
.hero-body
.container .container
a(href='/'): img(src='/favicons/android-icon-96x96.png') a(href='/'): img(src='/favicons/android-icon-96x96.png')
h1.title(style={ 'margin-top': '30px'})= message h1= message
h2.subtitle(style={ 'margin-bottom': '50px'}) Oops, something went wrong h2 Oops, something went wrong
a.button.is-warning.is-inverted(href='/') Go Home a.button.is-amber.is-inverted.is-featured(href='/') Go Home
if error.stack if error.stack
section.section
.container.is-fluid
.content
h3 Detailed debug trail: h3 Detailed debug trail:
pre: code #{error.stack} pre: code #{error.stack}
+5
View File
@@ -53,6 +53,11 @@ block content
a(href='/admin') a(href='/admin')
i.icon-head i.icon-head
span Account span Account
else
li
a(href='/login')
i.icon-unlock
span Login
aside.stickyscroll(data-margin-top=40) aside.stickyscroll(data-margin-top=40)
.sidebar-label .sidebar-label
i.icon-th-list i.icon-th-list