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