Commit 414dc386 authored by NGPixel's avatar NGPixel

Standard JS code conversion + fixes

parent a508b2a7
# 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
\ No newline at end of file
...@@ -4,207 +4,188 @@ ...@@ -4,207 +4,188 @@
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
global.PROCNAME = 'AGENT'; global.PROCNAME = 'AGENT'
global.ROOTPATH = __dirname; global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'; global.IS_DEBUG = process.env.NODE_ENV === 'development'
if(IS_DEBUG) { if (IS_DEBUG) {
global.CORE_PATH = ROOTPATH + '/../core/'; global.CORE_PATH = ROOTPATH + '/../core/'
} else { } else {
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'; global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
} }
// ---------------------------------------- // ----------------------------------------
// Load Winston // Load Winston
// ---------------------------------------- // ----------------------------------------
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG); global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
winston.info('[AGENT] Background Agent is initializing...'); winston.info('[AGENT] Background Agent is initializing...')
let appconf = require(CORE_PATH + 'core-libs/config')(); let appconf = require(CORE_PATH + 'core-libs/config')()
global.appconfig = appconf.config; global.appconfig = appconf.config
global.appdata = appconf.data; global.appdata = appconf.data
global.db = require(CORE_PATH + 'core-libs/mongodb').init(); global.db = require(CORE_PATH + 'core-libs/mongodb').init()
global.upl = require('./libs/uploads-agent').init(); global.upl = require('./libs/uploads-agent').init()
global.git = require('./libs/git').init(); global.git = require('./libs/git').init()
global.entries = require('./libs/entries').init(); global.entries = require('./libs/entries').init()
global.mark = require('./libs/markdown'); global.mark = require('./libs/markdown')
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
// ---------------------------------------- // ----------------------------------------
var _ = require('lodash'); var moment = require('moment')
var moment = require('moment'); var Promise = require('bluebird')
var Promise = require('bluebird'); var fs = Promise.promisifyAll(require('fs-extra'))
var fs = Promise.promisifyAll(require("fs-extra")); var klaw = require('klaw')
var klaw = require('klaw'); var path = require('path')
var path = require('path'); var Cron = require('cron').CronJob
var cron = require('cron').CronJob;
// ---------------------------------------- // ----------------------------------------
// Start Cron // Start Cron
// ---------------------------------------- // ----------------------------------------
var jobIsBusy = false; var jobIsBusy = false
var jobUplWatchStarted = false; var jobUplWatchStarted = false
var job = new cron({ var job = new Cron({
cronTime: '0 */5 * * * *', cronTime: '0 */5 * * * *',
onTick: () => { onTick: () => {
// Make sure we don't start two concurrent jobs
// Make sure we don't start two concurrent jobs
if (jobIsBusy) {
if(jobIsBusy) { winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)'); return
return; }
} winston.info('[AGENT] Running all jobs...')
winston.info('[AGENT] Running all jobs...'); jobIsBusy = true
jobIsBusy = true;
// Prepare async job collector
// Prepare async job collector
let jobs = []
let jobs = []; let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo); let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data); let uploadsTempPath = path.join(dataPath, 'temp-upload')
let uploadsPath = path.join(repoPath, 'uploads');
let uploadsTempPath = path.join(dataPath, 'temp-upload'); // ----------------------------------------
// REGULAR JOBS
// ---------------------------------------- // ----------------------------------------
// REGULAR JOBS
// ---------------------------------------- //* ****************************************
// -> Sync with Git remote
//***************************************** //* ****************************************
//-> Sync with Git remote
//***************************************** jobs.push(git.onReady.then(() => {
return git.resync().then(() => {
jobs.push(git.onReady.then(() => { // -> Stream all documents
return git.resync().then(() => {
let cacheJobs = []
//-> Stream all documents let jobCbStreamDocsResolve = null
let jobCbStreamDocs = new Promise((resolve, reject) => {
let cacheJobs = []; jobCbStreamDocsResolve = resolve
let jobCbStreamDocs_resolve = null, })
jobCbStreamDocs = new Promise((resolve, reject) => {
jobCbStreamDocs_resolve = resolve; klaw(repoPath).on('data', function (item) {
}); if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path))
klaw(repoPath).on('data', function (item) { let cachePath = entries.getCachePath(entryPath)
if(path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
// -> Purge outdated cache
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path));
let cachePath = entries.getCachePath(entryPath); cacheJobs.push(
fs.statAsync(cachePath).then((st) => {
//-> Purge outdated cache return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
}).catch((err) => {
cacheJobs.push( return (err.code !== 'EEXIST') ? err : 'new'
fs.statAsync(cachePath).then((st) => { }).then((fileStatus) => {
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'; // -> Delete expired cache file
}).catch((err) => {
return (err.code !== 'EEXIST') ? err : 'new'; if (fileStatus === 'expired') {
}).then((fileStatus) => { return fs.unlinkAsync(cachePath).return(fileStatus)
}
//-> Delete expired cache file
return fileStatus
if(fileStatus === 'expired') { }).then((fileStatus) => {
return fs.unlinkAsync(cachePath).return(fileStatus); // -> Update cache and search index
}
if (fileStatus !== 'active') {
return fileStatus; return entries.updateCache(entryPath)
}
}).then((fileStatus) => {
return true
//-> Update cache and search index })
)
if(fileStatus !== 'active') { }
return entries.updateCache(entryPath); }).on('end', () => {
} jobCbStreamDocsResolve(Promise.all(cacheJobs))
})
return true;
return jobCbStreamDocs
}) })
}))
);
//* ****************************************
} // -> Clear failed temporary upload files
}).on('end', () => { //* ****************************************
jobCbStreamDocs_resolve(Promise.all(cacheJobs));
}); jobs.push(
fs.readdirAsync(uploadsTempPath).then((ls) => {
return jobCbStreamDocs; let fifteenAgo = moment().subtract(15, 'minutes')
}); return Promise.map(ls, (f) => {
})); return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
//***************************************** return Promise.map(arrFiles, (f) => {
//-> Clear failed temporary upload files if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
//***************************************** return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
} else {
jobs.push( return true
fs.readdirAsync(uploadsTempPath).then((ls) => { }
})
let fifteenAgo = moment().subtract(15, 'minutes'); })
})
return Promise.map(ls, (f) => { )
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s }; });
}).filter((s) => { return s.stat.isFile(); }).then((arrFiles) => { // ----------------------------------------
return Promise.map(arrFiles, (f) => { // Run
// ----------------------------------------
if(moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename)); Promise.all(jobs).then(() => {
} else { winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
return true;
} if (!jobUplWatchStarted) {
jobUplWatchStarted = true
}); upl.initialScan().then(() => {
}); job.start()
})
}) }
);
return true
// ---------------------------------------- }).catch((err) => {
// Run winston.error('[AGENT] One or more jobs have failed: ', err)
// ---------------------------------------- }).finally(() => {
jobIsBusy = false
Promise.all(jobs).then(() => { })
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.'); },
start: false,
if(!jobUplWatchStarted) { timeZone: 'UTC',
jobUplWatchStarted = true; runOnInit: true
upl.initialScan().then(() => { })
job.start();
});
}
return true;
}).catch((err) => {
winston.error('[AGENT] One or more jobs have failed: ', err);
}).finally(() => {
jobIsBusy = false;
});
},
start: false,
timeZone: 'UTC',
runOnInit: true
});
// ---------------------------------------- // ----------------------------------------
// Shutdown gracefully // Shutdown gracefully
// ---------------------------------------- // ----------------------------------------
process.on('disconnect', () => { process.on('disconnect', () => {
winston.warn('[AGENT] Lost connection to main server. Exiting...'); winston.warn('[AGENT] Lost connection to main server. Exiting...')
job.stop(); job.stop()
process.exit(); process.exit()
}); })
process.on('exit', () => { process.on('exit', () => {
job.stop(); job.stop()
}); })
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
.cm-header,.cm-strong,.hljs-strong{font-weight:700}.cm-em,.hljs-emphasis{font-style:italic}.hljs-comment,.hljs-quote{color:#8e908c}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#c82829}.hljs-built_in,.hljs-builtin-name,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5871f}.hljs-attribute{color:#eab700}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#718c00}.hljs-section,.hljs-title{color:#4271ae}.hljs-keyword,.hljs-selector-tag{color:#8959a8}.hljs{display:block;overflow-x:auto;background:#fff;color:#4d4d4c;padding:.5em}.CodeMirror{color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected,.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.CodeMirror{height:auto;min-height:300px;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:1}.CodeMirror-scroll{min-height:300px}.CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:9}.CodeMirror-sided{width:50%!important}.editor-toolbar{position:relative;opacity:.6;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:0 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar:after,.editor-toolbar:before{display:block;content:' ';height:1px}.editor-toolbar:before{margin-bottom:8px}.editor-toolbar:after{margin-top:8px}.editor-toolbar:hover,.editor-wrapper input.title:focus,.editor-wrapper input.title:hover{opacity:.8}.editor-toolbar.fullscreen{width:100%;height:50px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,1)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar a{display:inline-block;text-align:center;text-decoration:none!important;color:#2c3e50!important;width:30px;height:30px;margin:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar a.active,.editor-toolbar a:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar a:before{line-height:30px}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar a.fa-header-x:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar a.fa-header-1:after{content:"1"}.editor-toolbar a.fa-header-2:after{content:"2"}.editor-toolbar a.fa-header-3:after{content:"3"}.editor-toolbar a.fa-header-bigger:after{content:"▲"}.editor-toolbar a.fa-header-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview a:not(.no-disable){pointer-events:none;background:#fff;border-color:transparent;text-shadow:inherit}@media only screen and (max-width:700px){.editor-toolbar a.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-preview,.editor-preview-side{padding:10px;background:#fafafa;overflow:auto;display:none;box-sizing:border-box}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;border:1px solid #ddd}.editor-preview-active,.editor-preview-active-side{display:block}.editor-preview-side>p,.editor-preview>p{margin-top:0}.editor-preview pre,.editor-preview-side pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th,.editor-preview-side table td,.editor-preview-side table th{border:1px solid #ddd;padding:5px}.CodeMirror .CodeMirror-code .cm-tag{color:#63a35c}.CodeMirror .CodeMirror-code .cm-attribute{color:#795da3}.CodeMirror .CodeMirror-code .cm-string{color:#183691}.CodeMirror .CodeMirror-selected{background:#d9d9d9}.CodeMirror .CodeMirror-code .cm-header-1{font-size:200%;line-height:200%}.CodeMirror .CodeMirror-code .cm-header-2{font-size:160%;line-height:160%}.CodeMirror .CodeMirror-code .cm-header-3{font-size:125%;line-height:125%}.CodeMirror .CodeMirror-code .cm-header-4{font-size:110%;line-height:110%}.CodeMirror .CodeMirror-code .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.CodeMirror .CodeMirror-code .cm-link{color:#7f8c8d}.CodeMirror .CodeMirror-code .cm-url{color:#aab2b3}.CodeMirror .CodeMirror-code .cm-strikethrough{text-decoration:line-through}.CodeMirror .CodeMirror-placeholder{opacity:.5}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)} .hljs-comment,.hljs-quote{color:#8e908c}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#c82829}.hljs-built_in,.hljs-builtin-name,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5871f}.hljs-attribute{color:#eab700}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#718c00}.hljs-section,.hljs-title{color:#4271ae}.hljs-keyword,.hljs-selector-tag{color:#8959a8}.hljs{display:block;overflow-x:auto;background:#fff;color:#4d4d4c;padding:.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.CodeMirror{color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected,.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.CodeMirror{height:auto;min-height:300px;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:1}.CodeMirror-scroll{min-height:300px}.CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:9}.CodeMirror-sided{width:50%!important}.editor-toolbar{position:relative;opacity:.6;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:0 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar:after,.editor-toolbar:before{display:block;content:' ';height:1px}.editor-toolbar:before{margin-bottom:8px}.editor-toolbar:after{margin-top:8px}.editor-toolbar:hover,.editor-wrapper input.title:focus,.editor-wrapper input.title:hover{opacity:.8}.editor-toolbar.fullscreen{width:100%;height:50px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,1)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar a{display:inline-block;text-align:center;text-decoration:none!important;color:#2c3e50!important;width:30px;height:30px;margin:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar a.active,.editor-toolbar a:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar a:before{line-height:30px}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar a.fa-header-x:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar a.fa-header-1:after{content:"1"}.editor-toolbar a.fa-header-2:after{content:"2"}.editor-toolbar a.fa-header-3:after{content:"3"}.editor-toolbar a.fa-header-bigger:after{content:"▲"}.editor-toolbar a.fa-header-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview a:not(.no-disable){pointer-events:none;background:#fff;border-color:transparent;text-shadow:inherit}@media only screen and (max-width:700px){.editor-toolbar a.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-preview,.editor-preview-side{padding:10px;background:#fafafa;overflow:auto;display:none;box-sizing:border-box}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;border:1px solid #ddd}.editor-preview-active,.editor-preview-active-side{display:block}.editor-preview-side>p,.editor-preview>p{margin-top:0}.editor-preview pre,.editor-preview-side pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th,.editor-preview-side table td,.editor-preview-side table th{border:1px solid #ddd;padding:5px}.CodeMirror .CodeMirror-code .cm-tag{color:#63a35c}.CodeMirror .CodeMirror-code .cm-attribute{color:#795da3}.CodeMirror .CodeMirror-code .cm-string{color:#183691}.CodeMirror .CodeMirror-selected{background:#d9d9d9}.CodeMirror .CodeMirror-code .cm-header-1{font-size:200%;line-height:200%}.CodeMirror .CodeMirror-code .cm-header-2{font-size:160%;line-height:160%}.CodeMirror .CodeMirror-code .cm-header-3{font-size:125%;line-height:125%}.CodeMirror .CodeMirror-code .cm-header-4{font-size:110%;line-height:110%}.CodeMirror .CodeMirror-code .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.CodeMirror .CodeMirror-code .cm-link{color:#7f8c8d}.CodeMirror .CodeMirror-code .cm-url{color:#aab2b3}.CodeMirror .CodeMirror-code .cm-strikethrough{text-decoration:line-through}.CodeMirror .CodeMirror-placeholder{opacity:.5}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
"use strict"; 'use strict'
jQuery( document ).ready(function( $ ) {
jQuery(document).ready(function ($) {
// ==================================== // ====================================
// Scroll // Scroll
// ==================================== // ====================================
$('a').smoothScroll({ $('a').smoothScroll({
speed: 400, speed: 400,
offset: -70 offset: -70
}); })
var sticky = new Sticky('.stickyscroll'); var sticky = new Sticky('.stickyscroll')
// ==================================== // ====================================
// Notifications // Notifications
// ==================================== // ====================================
$(window).bind('beforeunload', () => { $(window).bind('beforeunload', () => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}); })
$(document).ajaxSend(() => { $(document).ajaxSend(() => {
$('#notifload').addClass('active'); $('#notifload').addClass('active')
}).ajaxComplete(() => { }).ajaxComplete(() => {
$('#notifload').removeClass('active'); $('#notifload').removeClass('active')
}); })
var alerts = new Alerts(); var alerts = new Alerts()
if(alertsData) { if (alertsData) {
_.forEach(alertsData, (alertRow) => { _.forEach(alertsData, (alertRow) => {
alerts.push(alertRow); alerts.push(alertRow)
}); })
} }
// ==================================== // ====================================
// Establish WebSocket connection // Establish WebSocket connection
// ==================================== // ====================================
var socket = io(window.location.origin); var socket = io(window.location.origin)
//=include components/search.js // =include components/search.js
// ==================================== // ====================================
// Pages logic // Pages logic
// ==================================== // ====================================
//=include pages/view.js // =include pages/view.js
//=include pages/create.js // =include pages/create.js
//=include pages/edit.js // =include pages/edit.js
//=include pages/source.js // =include pages/source.js
//=include pages/admin.js // =include pages/admin.js
})
});
//=include helpers/form.js // =include helpers/form.js
//=include helpers/pages.js // =include helpers/pages.js
//=include components/alerts.js // =include components/alerts.js
\ No newline at end of file
"use strict"; 'use strict'
/** /**
* Alerts * Alerts
...@@ -10,25 +10,23 @@ class Alerts { ...@@ -10,25 +10,23 @@ class Alerts {
* *
* @class * @class
*/ */
constructor() { constructor () {
let self = this
let self = this;
self.mdl = new Vue({
self.mdl = new Vue({ el: '#alerts',
el: '#alerts', data: {
data: { children: []
children: [] },
}, methods: {
methods: { acknowledge: (uid) => {
acknowledge: (uid) => { self.close(uid)
self.close(uid); }
} }
} })
});
self.uidNext = 1
self.uidNext = 1; }
}
/** /**
* Show a new Alert * Show a new Alert
...@@ -36,29 +34,27 @@ class Alerts { ...@@ -36,29 +34,27 @@ class Alerts {
* @param {Object} options Alert properties * @param {Object} options Alert properties
* @return {null} Void * @return {null} Void
*/ */
push(options) { push (options) {
let self = this
let self = this;
let nAlert = _.defaults(options, {
_uid: self.uidNext,
class: 'info',
message: '---',
sticky: false,
title: '---'
});
self.mdl.children.push(nAlert); let nAlert = _.defaults(options, {
_uid: self.uidNext,
class: 'info',
message: '---',
sticky: false,
title: '---'
})
if(!nAlert.sticky) { self.mdl.children.push(nAlert)
_.delay(() => {
self.close(nAlert._uid);
}, 5000);
}
self.uidNext++; if (!nAlert.sticky) {
_.delay(() => {
self.close(nAlert._uid)
}, 5000)
}
} self.uidNext++
}
/** /**
* Shorthand method for pushing errors * Shorthand method for pushing errors
...@@ -66,14 +62,14 @@ class Alerts { ...@@ -66,14 +62,14 @@ class Alerts {
* @param {String} title The title * @param {String} title The title
* @param {String} message The message * @param {String} message The message
*/ */
pushError(title, message) { pushError (title, message) {
this.push({ this.push({
class: 'error', class: 'error',
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
* Shorthand method for pushing success messages * Shorthand method for pushing success messages
...@@ -81,35 +77,33 @@ class Alerts { ...@@ -81,35 +77,33 @@ class Alerts {
* @param {String} title The title * @param {String} title The title
* @param {String} message The message * @param {String} message The message
*/ */
pushSuccess(title, message) { pushSuccess (title, message) {
this.push({ this.push({
class: 'success', class: 'success',
message, message,
sticky: false, sticky: false,
title title
}); })
} }
/** /**
* Close an alert * Close an alert
* *
* @param {Integer} uid The unique ID of the alert * @param {Integer} uid The unique ID of the alert
*/ */
close(uid) { close (uid) {
let self = this
let self = this;
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid])
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid]); let nAlert = _.nth(self.mdl.children, nAlertIdx)
let nAlert = _.nth(self.mdl.children, nAlertIdx);
if (nAlertIdx >= 0 && nAlert) {
if(nAlertIdx >= 0 && nAlert) { nAlert.class += ' exit'
nAlert.class += ' exit'; Vue.set(self.mdl.children, nAlertIdx, nAlert)
Vue.set(self.mdl.children, nAlertIdx, nAlert); _.delay(() => {
_.delay(() => { self.mdl.children.splice(nAlertIdx, 1)
self.mdl.children.splice(nAlertIdx, 1); }, 500)
}, 500); }
} }
} }
}
\ No newline at end of file
let modelist = ace.require("ace/ext/modelist"); let modelist = ace.require('ace/ext/modelist')
let codeEditor = null; let codeEditor = null
// ACE - Mode Loader // ACE - Mode Loader
let modelistLoaded = []; let modelistLoaded = []
let loadAceMode = (m) => { let loadAceMode = (m) => {
return $.ajax({ return $.ajax({
url: '/js/ace/mode-' + m + '.js', url: '/js/ace/mode-' + m + '.js',
dataType: "script", dataType: 'script',
cache: true, cache: true,
beforeSend: () => { beforeSend: () => {
if(_.includes(modelistLoaded, m)) { if (_.includes(modelistLoaded, m)) {
return false; return false
} }
}, },
success: () => { success: () => {
modelistLoaded.push(m); modelistLoaded.push(m)
} }
}); })
}; }
// Vue Code Block instance // Vue Code Block instance
let vueCodeBlock = new Vue({ let vueCodeBlock = new Vue({
el: '#modal-editor-codeblock', el: '#modal-editor-codeblock',
data: { data: {
modes: modelist.modesByName, modes: modelist.modesByName,
modeSelected: 'text', modeSelected: 'text',
initContent: '' initContent: ''
}, },
watch: { watch: {
modeSelected: (val, oldVal) => { modeSelected: (val, oldVal) => {
loadAceMode(val).done(() => { loadAceMode(val).done(() => {
ace.require("ace/mode/" + val); ace.require('ace/mode/' + val)
codeEditor.getSession().setMode("ace/mode/" + val); codeEditor.getSession().setMode('ace/mode/' + val)
}); })
} }
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active')
$('#modal-editor-codeblock').addClass('is-active'); _.delay(() => {
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)
_.delay(() => { codeEditor.setValue(vueCodeBlock.initContent)
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)
},
cancel: (ev) => {
mdeModalOpenState = false
$('#modal-editor-codeblock').removeClass('is-active')
vueCodeBlock.initContent = ''
},
insertCode: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection')
}
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
codeEditor.focus(); mde.codemirror.doc.replaceSelection(codeBlockText)
codeEditor.renderer.updateFull(); vueCodeBlock.cancel()
}, 300); }
}
}, })
cancel: (ev) => {
mdeModalOpenState = false;
$('#modal-editor-codeblock').removeClass('is-active');
vueCodeBlock.initContent = '';
},
insertCode: (ev) => {
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();
}
}
});
\ No newline at end of file
let vueFile = new Vue({ let vueFile = new Vue({
el: '#modal-editor-file', el: '#modal-editor-file',
data: { data: {
isLoading: false, isLoading: false,
isLoadingText: '', isLoadingText: '',
newFolderName: '', newFolderName: '',
newFolderShow: false, newFolderShow: false,
newFolderError: false, newFolderError: false,
folders: [], folders: [],
currentFolder: '', currentFolder: '',
currentFile: '', currentFile: '',
files: [], files: [],
uploadSucceeded: false, uploadSucceeded: false,
postUploadChecks: 0, postUploadChecks: 0,
renameFileShow: false, renameFileShow: false,
renameFileId: '', renameFileId: '',
renameFileFilename: '', renameFileFilename: '',
deleteFileShow: false, deleteFileShow: false,
deleteFileId: '', deleteFileId: '',
deleteFileFilename: '' deleteFileFilename: ''
}, },
methods: { methods: {
open: () => { open: () => {
mdeModalOpenState = true; mdeModalOpenState = true
$('#modal-editor-file').addClass('is-active'); $('#modal-editor-file').addClass('is-active')
vueFile.refreshFolders(); vueFile.refreshFolders()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-file').removeClass('is-active'); $('#modal-editor-file').removeClass('is-active')
}, },
// ------------------------------------------- // -------------------------------------------
// INSERT LINK TO FILE // INSERT LINK TO FILE
// ------------------------------------------- // -------------------------------------------
selectFile: (fileId) => { selectFile: (fileId) => {
vueFile.currentFile = fileId; vueFile.currentFile = fileId
}, },
insertFileLink: (ev) => { insertFileLink: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection')
}
if(mde.codemirror.doc.somethingSelected()) { let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
mde.codemirror.execCommand('singleSelection'); 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]); let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
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()
mde.codemirror.doc.replaceSelection(fileText); },
vueFile.cancel();
},
// ------------------------------------------- // -------------------------------------------
// NEW FOLDER // NEW FOLDER
// ------------------------------------------- // -------------------------------------------
newFolder: (ev) => { newFolder: (ev) => {
vueFile.newFolderName = ''; vueFile.newFolderName = ''
vueFile.newFolderError = false; vueFile.newFolderError = false
vueFile.newFolderShow = true; vueFile.newFolderShow = true
_.delay(() => { $('#txt-editor-file-newfoldername').focus(); }, 400); _.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
}, },
newFolderDiscard: (ev) => { newFolderDiscard: (ev) => {
vueFile.newFolderShow = false; vueFile.newFolderShow = false
}, },
newFolderCreate: (ev) => { newFolderCreate: (ev) => {
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
if(_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) { vueFile.newFolderError = true
vueFile.newFolderError = true; return
return; }
}
vueFile.newFolderDiscard()
vueFile.newFolderDiscard(); vueFile.isLoadingText = 'Creating new folder...'
vueFile.isLoadingText = 'Creating new folder...'; vueFile.isLoading = true
vueFile.isLoading = true;
Vue.nextTick(() => {
Vue.nextTick(() => { socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => { vueFile.folders = data
vueFile.folders = data; vueFile.currentFolder = vueFile.newFolderName
vueFile.currentFolder = vueFile.newFolderName; vueFile.files = []
vueFile.files = []; vueFile.isLoading = false
vueFile.isLoading = false; })
}); })
}); },
},
// ------------------------------------------- // -------------------------------------------
// RENAME FILE // RENAME FILE
// ------------------------------------------- // -------------------------------------------
renameFile: () => { renameFile: () => {
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ])
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]); vueFile.renameFileFilename = c.basename || ''
vueFile.renameFileFilename = c.basename || ''; vueFile.renameFileShow = true
vueFile.renameFileShow = true; _.delay(() => {
_.delay(() => { $('#txt-editor-renamefile').focus()
$('#txt-editor-renamefile').focus(); _.defer(() => { $('#txt-editor-file-rename').select() })
_.defer(() => { $('#txt-editor-file-rename').select(); }); }, 400)
}, 400); },
}, renameFileDiscard: () => {
renameFileDiscard: () => { vueFile.renameFileShow = false
vueFile.renameFileShow = false; },
}, renameFileGo: () => {
renameFileGo: () => { vueFile.renameFileDiscard()
vueFile.isLoadingText = 'Renaming file...'
vueFile.renameFileDiscard(); vueFile.isLoading = true
vueFile.isLoadingText = 'Renaming file...';
vueFile.isLoading = true; Vue.nextTick(() => {
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
Vue.nextTick(() => { if (data.ok) {
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => { vueFile.waitChangeComplete(vueFile.files.length, false)
if(data.ok) { } else {
vueFile.waitChangeComplete(vueFile.files.length, false); vueFile.isLoading = false
} else { alerts.pushError('Rename error', data.msg)
vueFile.isLoading = false; }
alerts.pushError('Rename error', data.msg); })
} })
}); },
});
},
// ------------------------------------------- // -------------------------------------------
// MOVE FILE // MOVE FILE
// ------------------------------------------- // -------------------------------------------
moveFile: (uid, fld) => { moveFile: (uid, fld) => {
vueFile.isLoadingText = 'Moving file...'; vueFile.isLoadingText = 'Moving file...'
vueFile.isLoading = true; vueFile.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
if(data.ok) { if (data.ok) {
vueFile.loadFiles(); vueFile.loadFiles()
} else { } else {
vueFile.isLoading = false; vueFile.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
// DELETE FILE // DELETE FILE
// ------------------------------------------- // -------------------------------------------
deleteFileWarn: (show) => { deleteFileWarn: (show) => {
if(show) { if (show) {
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ]); let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ])
vueFile.deleteFileFilename = c.filename || 'this file'; vueFile.deleteFileFilename = c.filename || 'this file'
} }
vueFile.deleteFileShow = show; vueFile.deleteFileShow = show
}, },
deleteFileGo: () => { deleteFileGo: () => {
vueFile.deleteFileWarn(false); vueFile.deleteFileWarn(false)
vueFile.isLoadingText = 'Deleting file...'; vueFile.isLoadingText = 'Deleting file...'
vueFile.isLoading = true; vueFile.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => { socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
vueFile.loadFiles(); vueFile.loadFiles()
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
// LOAD FROM REMOTE // LOAD FROM REMOTE
// ------------------------------------------- // -------------------------------------------
selectFolder: (fldName) => { selectFolder: (fldName) => {
vueFile.currentFolder = fldName; vueFile.currentFolder = fldName
vueFile.loadFiles(); vueFile.loadFiles()
}, },
refreshFolders: () => { refreshFolders: () => {
vueFile.isLoadingText = 'Fetching folders list...'; vueFile.isLoadingText = 'Fetching folders list...'
vueFile.isLoading = true; vueFile.isLoading = true
vueFile.currentFolder = ''; vueFile.currentFolder = ''
vueFile.currentImage = ''; vueFile.currentImage = ''
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => { socket.emit('uploadsGetFolders', { }, (data) => {
vueFile.folders = data; vueFile.folders = data
vueFile.loadFiles(); vueFile.loadFiles()
}); })
}); })
}, },
loadFiles: (silent) => { loadFiles: (silent) => {
if(!silent) { if (!silent) {
vueFile.isLoadingText = 'Fetching files...'; vueFile.isLoadingText = 'Fetching files...'
vueFile.isLoading = true; vueFile.isLoading = true
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => { socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
vueFile.files = data; vueFile.files = data
if(!silent) { if (!silent) {
vueFile.isLoading = false; vueFile.isLoading = false
} }
vueFile.attachContextMenus(); vueFile.attachContextMenus()
resolve(true); resolve(true)
}); })
}); })
}); })
}, },
waitChangeComplete: (oldAmount, expectChange) => { waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
vueFile.postUploadChecks++
vueFile.postUploadChecks++; vueFile.isLoadingText = 'Processing...'
vueFile.isLoadingText = 'Processing...';
Vue.nextTick(() => {
Vue.nextTick(() => { vueFile.loadFiles(true).then(() => {
vueFile.loadFiles(true).then(() => { if ((vueFile.files.length !== oldAmount) === expectChange) {
if((vueFile.files.length !== oldAmount) === expectChange) { vueFile.postUploadChecks = 0
vueFile.postUploadChecks = 0; vueFile.isLoading = false
vueFile.isLoading = false; } else if (vueFile.postUploadChecks > 5) {
} else if(vueFile.postUploadChecks > 5) { vueFile.postUploadChecks = 0
vueFile.postUploadChecks = 0; vueFile.isLoading = false
vueFile.isLoading = false; alerts.pushError('Unable to fetch updated listing', 'Try again later')
alerts.pushError('Unable to fetch updated listing', 'Try again later'); } else {
} else { _.delay(() => {
_.delay(() => { vueFile.waitChangeComplete(oldAmount, expectChange)
vueFile.waitChangeComplete(oldAmount, expectChange); }, 1500)
}, 1500); }
} })
}); })
}); },
},
// ------------------------------------------- // -------------------------------------------
// IMAGE CONTEXT MENU // IMAGE CONTEXT MENU
// ------------------------------------------- // -------------------------------------------
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);
}
};
});
$.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 });
},
events: {
hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen');
}
},
items: {
rename: {
name: "Rename",
icon: "fa-edit",
callback: (key, opt) => {
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid);
vueFile.renameFile();
}
},
move: {
name: "Move to...",
icon: "fa-folder-open-o",
items: moveFolders
},
delete: {
name: "Delete",
icon: "fa-trash",
callback: (key, opt) => {
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid);
vueFile.deleteFileWarn(true);
}
}
}
});
}
}
});
$('#btn-editor-file-upload input').on('change', (ev) => { 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)
}
}
})
$.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 })
},
events: {
hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen')
}
},
items: {
rename: {
name: 'Rename',
icon: 'fa-edit',
callback: (key, opt) => {
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
vueFile.renameFile()
}
},
move: {
name: 'Move to...',
icon: 'fa-folder-open-o',
items: moveFolders
},
delete: {
name: 'Delete',
icon: 'fa-trash',
callback: (key, opt) => {
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
vueFile.deleteFileWarn(true)
}
}
}
})
}
}
})
let curFileAmount = vueFile.files.length; $('#btn-editor-file-upload input').on('change', (ev) => {
let curFileAmount = vueFile.files.length
$(ev.currentTarget).simpleUpload("/uploads/file", {
$(ev.currentTarget).simpleUpload('/uploads/file', {
name: 'binfile',
data: { name: 'binfile',
folder: vueFile.currentFolder data: {
}, folder: vueFile.currentFolder
limit: 20, },
expect: 'json', limit: 20,
maxFileSize: 0, expect: 'json',
maxFileSize: 0,
init: (totalUploads) => {
vueFile.uploadSucceeded = false; init: (totalUploads) => {
vueFile.isLoadingText = 'Preparing to upload...'; vueFile.uploadSucceeded = false
vueFile.isLoading = true; vueFile.isLoadingText = 'Preparing to upload...'
}, vueFile.isLoading = true
},
progress: (progress) => {
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'; progress: (progress) => {
}, vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
},
success: (data) => {
if(data.ok) { success: (data) => {
if (data.ok) {
let failedUpls = _.filter(data.results, ['ok', false]); let failedUpls = _.filter(data.results, ['ok', false])
if(failedUpls.length) { if (failedUpls.length) {
_.forEach(failedUpls, (u) => { _.forEach(failedUpls, (u) => {
alerts.pushError('Upload error', u.msg); alerts.pushError('Upload error', u.msg)
}); })
if(failedUpls.length < data.results.length) { if (failedUpls.length < data.results.length) {
alerts.push({ alerts.push({
title: 'Some uploads succeeded', title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' message: 'Files that are not mentionned in the errors above were uploaded successfully.'
}); })
vueFile.uploadSucceeded = true; vueFile.uploadSucceeded = true
} }
} else { } else {
vueFile.uploadSucceeded = true; vueFile.uploadSucceeded = true
} }
} else {
} else { alerts.pushError('Upload error', data.msg)
alerts.pushError('Upload error', data.msg); }
} },
},
error: (error) => {
error: (error) => { alerts.pushError(error.message, this.upload.file.name)
alerts.pushError(error.message, this.upload.file.name); },
},
finish: () => {
finish: () => { if (vueFile.uploadSucceeded) {
if(vueFile.uploadSucceeded) { vueFile.waitChangeComplete(curFileAmount, true)
vueFile.waitChangeComplete(curFileAmount, true); } else {
} else { vueFile.isLoading = false
vueFile.isLoading = false; }
} }
}
})
}); })
});
\ No newline at end of file
let vueImage = new Vue({ let vueImage = new Vue({
el: '#modal-editor-image', el: '#modal-editor-image',
data: { data: {
isLoading: false, isLoading: false,
isLoadingText: '', isLoadingText: '',
newFolderName: '', newFolderName: '',
newFolderShow: false, newFolderShow: false,
newFolderError: false, newFolderError: false,
fetchFromUrlURL: '', fetchFromUrlURL: '',
fetchFromUrlShow: false, fetchFromUrlShow: false,
folders: [], folders: [],
currentFolder: '', currentFolder: '',
currentImage: '', currentImage: '',
currentAlign: 'left', currentAlign: 'left',
images: [], images: [],
uploadSucceeded: false, uploadSucceeded: false,
postUploadChecks: 0, postUploadChecks: 0,
renameImageShow: false, renameImageShow: false,
renameImageId: '', renameImageId: '',
renameImageFilename: '', renameImageFilename: '',
deleteImageShow: false, deleteImageShow: false,
deleteImageId: '', deleteImageId: '',
deleteImageFilename: '' deleteImageFilename: ''
}, },
methods: { methods: {
open: () => { open: () => {
mdeModalOpenState = true; mdeModalOpenState = true
$('#modal-editor-image').addClass('is-active'); $('#modal-editor-image').addClass('is-active')
vueImage.refreshFolders(); vueImage.refreshFolders()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-image').removeClass('is-active'); $('#modal-editor-image').removeClass('is-active')
}, },
// ------------------------------------------- // -------------------------------------------
// INSERT IMAGE // INSERT IMAGE
// ------------------------------------------- // -------------------------------------------
selectImage: (imageId) => { selectImage: (imageId) => {
vueImage.currentImage = imageId; vueImage.currentImage = imageId
}, },
insertImage: (ev) => { insertImage: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
if(mde.codemirror.doc.somethingSelected()) { mde.codemirror.execCommand('singleSelection')
mde.codemirror.execCommand('singleSelection'); }
}
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]); selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename; selImage.titleGuess = _.startCase(selImage.basename)
selImage.titleGuess = _.startCase(selImage.basename);
let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
let imageText = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'; switch (vueImage.currentAlign) {
switch(vueImage.currentAlign) { case 'center':
case 'center': imageText += '{.align-center}'
imageText += '{.align-center}'; break
break; case 'right':
case 'right': imageText += '{.align-right}'
imageText += '{.align-right}'; break
break; case 'logo':
case 'logo': imageText += '{.pagelogo}'
imageText += '{.pagelogo}'; break
break; }
}
mde.codemirror.doc.replaceSelection(imageText)
mde.codemirror.doc.replaceSelection(imageText); vueImage.cancel()
vueImage.cancel(); },
},
// ------------------------------------------- // -------------------------------------------
// NEW FOLDER // NEW FOLDER
// ------------------------------------------- // -------------------------------------------
newFolder: (ev) => { newFolder: (ev) => {
vueImage.newFolderName = ''; vueImage.newFolderName = ''
vueImage.newFolderError = false; vueImage.newFolderError = false
vueImage.newFolderShow = true; vueImage.newFolderShow = true
_.delay(() => { $('#txt-editor-image-newfoldername').focus(); }, 400); _.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
}, },
newFolderDiscard: (ev) => { newFolderDiscard: (ev) => {
vueImage.newFolderShow = false; vueImage.newFolderShow = false
}, },
newFolderCreate: (ev) => { newFolderCreate: (ev) => {
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
if(_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) { vueImage.newFolderError = true
vueImage.newFolderError = true; return
return; }
}
vueImage.newFolderDiscard()
vueImage.newFolderDiscard(); vueImage.isLoadingText = 'Creating new folder...'
vueImage.isLoadingText = 'Creating new folder...'; vueImage.isLoading = true
vueImage.isLoading = true;
Vue.nextTick(() => {
Vue.nextTick(() => { socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => { vueImage.folders = data
vueImage.folders = data; vueImage.currentFolder = vueImage.newFolderName
vueImage.currentFolder = vueImage.newFolderName; vueImage.images = []
vueImage.images = []; vueImage.isLoading = false
vueImage.isLoading = false; })
}); })
}); },
},
// ------------------------------------------- // -------------------------------------------
// FETCH FROM URL // FETCH FROM URL
// ------------------------------------------- // -------------------------------------------
fetchFromUrl: (ev) => { fetchFromUrl: (ev) => {
vueImage.fetchFromUrlURL = ''; vueImage.fetchFromUrlURL = ''
vueImage.fetchFromUrlShow = true; vueImage.fetchFromUrlShow = true
_.delay(() => { $('#txt-editor-image-fetchurl').focus(); }, 400); _.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
}, },
fetchFromUrlDiscard: (ev) => { fetchFromUrlDiscard: (ev) => {
vueImage.fetchFromUrlShow = false; vueImage.fetchFromUrlShow = false
}, },
fetchFromUrlGo: (ev) => { fetchFromUrlGo: (ev) => {
vueImage.fetchFromUrlDiscard()
vueImage.fetchFromUrlDiscard(); vueImage.isLoadingText = 'Fetching image...'
vueImage.isLoadingText = 'Fetching image...'; vueImage.isLoading = true
vueImage.isLoading = true;
Vue.nextTick(() => {
Vue.nextTick(() => { socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => { if (data.ok) {
if(data.ok) { vueImage.waitChangeComplete(vueImage.images.length, true)
vueImage.waitChangeComplete(vueImage.images.length, true); } else {
} else { vueImage.isLoading = false
vueImage.isLoading = false; alerts.pushError('Upload error', data.msg)
alerts.pushError('Upload error', data.msg); }
} })
}); })
}); },
},
// ------------------------------------------- // -------------------------------------------
// RENAME IMAGE // RENAME IMAGE
// ------------------------------------------- // -------------------------------------------
renameImage: () => { renameImage: () => {
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]); vueImage.renameImageFilename = c.basename || ''
vueImage.renameImageFilename = c.basename || ''; vueImage.renameImageShow = true
vueImage.renameImageShow = true; _.delay(() => {
_.delay(() => { $('#txt-editor-image-rename').focus()
$('#txt-editor-image-rename').focus(); _.defer(() => { $('#txt-editor-image-rename').select() })
_.defer(() => { $('#txt-editor-image-rename').select(); }); }, 400)
}, 400); },
}, renameImageDiscard: () => {
renameImageDiscard: () => { vueImage.renameImageShow = false
vueImage.renameImageShow = false; },
}, renameImageGo: () => {
renameImageGo: () => { vueImage.renameImageDiscard()
vueImage.isLoadingText = 'Renaming image...'
vueImage.renameImageDiscard(); vueImage.isLoading = true
vueImage.isLoadingText = 'Renaming image...';
vueImage.isLoading = true; Vue.nextTick(() => {
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
Vue.nextTick(() => { if (data.ok) {
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => { vueImage.waitChangeComplete(vueImage.images.length, false)
if(data.ok) { } else {
vueImage.waitChangeComplete(vueImage.images.length, false); vueImage.isLoading = false
} else { alerts.pushError('Rename error', data.msg)
vueImage.isLoading = false; }
alerts.pushError('Rename error', data.msg); })
} })
}); },
});
},
// ------------------------------------------- // -------------------------------------------
// MOVE IMAGE // MOVE IMAGE
// ------------------------------------------- // -------------------------------------------
moveImage: (uid, fld) => { moveImage: (uid, fld) => {
vueImage.isLoadingText = 'Moving image...'; vueImage.isLoadingText = 'Moving image...'
vueImage.isLoading = true; vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => { socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
if(data.ok) { if (data.ok) {
vueImage.loadImages(); vueImage.loadImages()
} else { } else {
vueImage.isLoading = false; vueImage.isLoading = false
alerts.pushError('Rename error', data.msg); alerts.pushError('Rename error', data.msg)
} }
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
// DELETE IMAGE // DELETE IMAGE
// ------------------------------------------- // -------------------------------------------
deleteImageWarn: (show) => { deleteImageWarn: (show) => {
if(show) { if (show) {
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ]); let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ])
vueImage.deleteImageFilename = c.filename || 'this image'; vueImage.deleteImageFilename = c.filename || 'this image'
} }
vueImage.deleteImageShow = show; vueImage.deleteImageShow = show
}, },
deleteImageGo: () => { deleteImageGo: () => {
vueImage.deleteImageWarn(false); vueImage.deleteImageWarn(false)
vueImage.isLoadingText = 'Deleting image...'; vueImage.isLoadingText = 'Deleting image...'
vueImage.isLoading = true; vueImage.isLoading = true
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => { socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
vueImage.loadImages(); vueImage.loadImages()
}); })
}); })
}, },
// ------------------------------------------- // -------------------------------------------
// LOAD FROM REMOTE // LOAD FROM REMOTE
// ------------------------------------------- // -------------------------------------------
selectFolder: (fldName) => { selectFolder: (fldName) => {
vueImage.currentFolder = fldName; vueImage.currentFolder = fldName
vueImage.loadImages(); vueImage.loadImages()
}, },
refreshFolders: () => { refreshFolders: () => {
vueImage.isLoadingText = 'Fetching folders list...'; vueImage.isLoadingText = 'Fetching folders list...'
vueImage.isLoading = true; vueImage.isLoading = true
vueImage.currentFolder = ''; vueImage.currentFolder = ''
vueImage.currentImage = ''; vueImage.currentImage = ''
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => { socket.emit('uploadsGetFolders', { }, (data) => {
vueImage.folders = data; vueImage.folders = data
vueImage.loadImages(); vueImage.loadImages()
}); })
}); })
}, },
loadImages: (silent) => { loadImages: (silent) => {
if(!silent) { if (!silent) {
vueImage.isLoadingText = 'Fetching images...'; vueImage.isLoadingText = 'Fetching images...'
vueImage.isLoading = true; vueImage.isLoading = true
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Vue.nextTick(() => { Vue.nextTick(() => {
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => { socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
vueImage.images = data; vueImage.images = data
if(!silent) { if (!silent) {
vueImage.isLoading = false; vueImage.isLoading = false
} }
vueImage.attachContextMenus(); vueImage.attachContextMenus()
resolve(true); resolve(true)
}); })
}); })
}); })
}, },
waitChangeComplete: (oldAmount, expectChange) => { waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
vueImage.postUploadChecks++
vueImage.postUploadChecks++; vueImage.isLoadingText = 'Processing...'
vueImage.isLoadingText = 'Processing...';
Vue.nextTick(() => {
Vue.nextTick(() => { vueImage.loadImages(true).then(() => {
vueImage.loadImages(true).then(() => { if ((vueImage.images.length !== oldAmount) === expectChange) {
if((vueImage.images.length !== oldAmount) === expectChange) { vueImage.postUploadChecks = 0
vueImage.postUploadChecks = 0; vueImage.isLoading = false
vueImage.isLoading = false; } else if (vueImage.postUploadChecks > 5) {
} else if(vueImage.postUploadChecks > 5) { vueImage.postUploadChecks = 0
vueImage.postUploadChecks = 0; vueImage.isLoading = false
vueImage.isLoading = false; alerts.pushError('Unable to fetch updated listing', 'Try again later')
alerts.pushError('Unable to fetch updated listing', 'Try again later'); } else {
} else { _.delay(() => {
_.delay(() => { vueImage.waitChangeComplete(oldAmount, expectChange)
vueImage.waitChangeComplete(oldAmount, expectChange); }, 1500)
}, 1500); }
} })
}); })
}); },
},
// ------------------------------------------- // -------------------------------------------
// IMAGE CONTEXT MENU // IMAGE CONTEXT MENU
// ------------------------------------------- // -------------------------------------------
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);
}
};
});
$.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 });
},
events: {
hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen');
}
},
items: {
rename: {
name: "Rename",
icon: "fa-edit",
callback: (key, opt) => {
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid);
vueImage.renameImage();
}
},
move: {
name: "Move to...",
icon: "fa-folder-open-o",
items: moveFolders
},
delete: {
name: "Delete",
icon: "fa-trash",
callback: (key, opt) => {
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid);
vueImage.deleteImageWarn(true);
}
}
}
});
}
}
});
$('#btn-editor-image-upload input').on('change', (ev) => { 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)
}
}
})
$.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 })
},
events: {
hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen')
}
},
items: {
rename: {
name: 'Rename',
icon: 'fa-edit',
callback: (key, opt) => {
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
vueImage.renameImage()
}
},
move: {
name: 'Move to...',
icon: 'fa-folder-open-o',
items: moveFolders
},
delete: {
name: 'Delete',
icon: 'fa-trash',
callback: (key, opt) => {
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
vueImage.deleteImageWarn(true)
}
}
}
})
}
}
})
let curImageAmount = vueImage.images.length; $('#btn-editor-image-upload input').on('change', (ev) => {
let curImageAmount = vueImage.images.length
$(ev.currentTarget).simpleUpload("/uploads/img", {
$(ev.currentTarget).simpleUpload('/uploads/img', {
name: 'imgfile',
data: { name: 'imgfile',
folder: vueImage.currentFolder data: {
}, folder: vueImage.currentFolder
limit: 20, },
expect: 'json', limit: 20,
allowedExts: ["jpg", "jpeg", "gif", "png", "webp"], expect: 'json',
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'], allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
maxFileSize: 3145728, // max 3 MB allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
maxFileSize: 3145728, // max 3 MB
init: (totalUploads) => {
vueImage.uploadSucceeded = false; init: (totalUploads) => {
vueImage.isLoadingText = 'Preparing to upload...'; vueImage.uploadSucceeded = false
vueImage.isLoading = true; vueImage.isLoadingText = 'Preparing to upload...'
}, vueImage.isLoading = true
},
progress: (progress) => {
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'; progress: (progress) => {
}, vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
},
success: (data) => {
if(data.ok) { success: (data) => {
if (data.ok) {
let failedUpls = _.filter(data.results, ['ok', false]); let failedUpls = _.filter(data.results, ['ok', false])
if(failedUpls.length) { if (failedUpls.length) {
_.forEach(failedUpls, (u) => { _.forEach(failedUpls, (u) => {
alerts.pushError('Upload error', u.msg); alerts.pushError('Upload error', u.msg)
}); })
if(failedUpls.length < data.results.length) { if (failedUpls.length < data.results.length) {
alerts.push({ alerts.push({
title: 'Some uploads succeeded', title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.' message: 'Files that are not mentionned in the errors above were uploaded successfully.'
}); })
vueImage.uploadSucceeded = true; vueImage.uploadSucceeded = true
} }
} else { } else {
vueImage.uploadSucceeded = true; vueImage.uploadSucceeded = true
} }
} else {
} else { alerts.pushError('Upload error', data.msg)
alerts.pushError('Upload error', data.msg); }
} },
},
error: (error) => {
error: (error) => { alerts.pushError(error.message, this.upload.file.name)
alerts.pushError(error.message, this.upload.file.name); },
},
finish: () => {
finish: () => { if (vueImage.uploadSucceeded) {
if(vueImage.uploadSucceeded) { vueImage.waitChangeComplete(curImageAmount, true)
vueImage.waitChangeComplete(curImageAmount, true); } else {
} else { vueImage.isLoading = false
vueImage.isLoading = false; }
} }
}
})
}); })
});
\ No newline at end of file
const videoRules = { const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'), 'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), 'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i') 'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
}; }
// Vue Video instance // Vue Video instance
let vueVideo = new Vue({ let vueVideo = new Vue({
el: '#modal-editor-video', el: '#modal-editor-video',
data: { data: {
link: '' link: ''
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-editor-video').addClass('is-active'); $('#modal-editor-video').addClass('is-active')
$('#modal-editor-video input').focus(); $('#modal-editor-video input').focus()
}, },
cancel: (ev) => { cancel: (ev) => {
mdeModalOpenState = false; mdeModalOpenState = false
$('#modal-editor-video').removeClass('is-active'); $('#modal-editor-video').removeClass('is-active')
vueVideo.link = ''; vueVideo.link = ''
}, },
insertVideo: (ev) => { insertVideo: (ev) => {
if (mde.codemirror.doc.somethingSelected()) {
if(mde.codemirror.doc.somethingSelected()) { mde.codemirror.execCommand('singleSelection')
mde.codemirror.execCommand('singleSelection'); }
}
// Guess video type // Guess video type
let videoType = _.findKey(videoRules, (vr) => { let videoType = _.findKey(videoRules, (vr) => {
return vr.test(vueVideo.link); return vr.test(vueVideo.link)
}); })
if(_.isNil(videoType)) { if (_.isNil(videoType)) {
videoType = 'video'; videoType = 'video'
} }
// Insert video tag // Insert video tag
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'; let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
mde.codemirror.doc.replaceSelection(videoText); mde.codemirror.doc.replaceSelection(videoText)
vueVideo.cancel(); vueVideo.cancel()
}
} }
} })
});
\ No newline at end of file
...@@ -3,215 +3,210 @@ ...@@ -3,215 +3,210 @@
// Markdown Editor // Markdown Editor
// ==================================== // ====================================
if($('#mk-editor').length === 1) { if ($('#mk-editor').length === 1) {
let mdeModalOpenState = false
let mdeModalOpenState = false; let mdeCurrentEditor = null
let mdeCurrentEditor = null;
Vue.filter('filesize', (v) => {
Vue.filter('filesize', (v) => { return _.toUpper(filesize(v))
return _.toUpper(filesize(v)); })
});
// =include editor-image.js
//=include editor-image.js // =include editor-file.js
//=include editor-file.js // =include editor-video.js
//=include editor-video.js // =include editor-codeblock.js
//=include editor-codeblock.js
var mde = new SimpleMDE({
var mde = new SimpleMDE({ autofocus: true,
autofocus: true, autoDownloadFontAwesome: false,
autoDownloadFontAwesome: false, element: $('#mk-editor').get(0),
element: $("#mk-editor").get(0), placeholder: 'Enter Markdown formatted content here...',
placeholder: 'Enter Markdown formatted content here...', spellChecker: false,
spellChecker: false, status: false,
status: false, toolbar: [{
toolbar: [{ name: 'bold',
name: "bold", action: SimpleMDE.toggleBold,
action: SimpleMDE.toggleBold, className: 'icon-bold',
className: "icon-bold", title: 'Bold'
title: "Bold", },
}, {
{ name: 'italic',
name: "italic", action: SimpleMDE.toggleItalic,
action: SimpleMDE.toggleItalic, className: 'icon-italic',
className: "icon-italic", title: 'Italic'
title: "Italic", },
}, {
{ name: 'strikethrough',
name: "strikethrough", action: SimpleMDE.toggleStrikethrough,
action: SimpleMDE.toggleStrikethrough, className: 'icon-strikethrough',
className: "icon-strikethrough", title: 'Strikethrough'
title: "Strikethrough", },
}, '|',
'|', {
{ name: 'heading-1',
name: "heading-1", action: SimpleMDE.toggleHeading1,
action: SimpleMDE.toggleHeading1, className: 'icon-header fa-header-x fa-header-1',
className: "icon-header fa-header-x fa-header-1", title: 'Big Heading'
title: "Big Heading", },
}, {
{ name: 'heading-2',
name: "heading-2", action: SimpleMDE.toggleHeading2,
action: SimpleMDE.toggleHeading2, className: 'icon-header fa-header-x fa-header-2',
className: "icon-header fa-header-x fa-header-2", title: 'Medium Heading'
title: "Medium Heading", },
}, {
{ name: 'heading-3',
name: "heading-3", action: SimpleMDE.toggleHeading3,
action: SimpleMDE.toggleHeading3, className: 'icon-header fa-header-x fa-header-3',
className: "icon-header fa-header-x fa-header-3", title: 'Small Heading'
title: "Small Heading", },
}, {
{ name: 'quote',
name: "quote", action: SimpleMDE.toggleBlockquote,
action: SimpleMDE.toggleBlockquote, className: 'icon-quote-left',
className: "icon-quote-left", title: 'Quote'
title: "Quote", },
}, '|',
'|', {
{ name: 'unordered-list',
name: "unordered-list", action: SimpleMDE.toggleUnorderedList,
action: SimpleMDE.toggleUnorderedList, className: 'icon-th-list',
className: "icon-th-list", title: 'Bullet List'
title: "Bullet List", },
}, {
{ name: 'ordered-list',
name: "ordered-list", action: SimpleMDE.toggleOrderedList,
action: SimpleMDE.toggleOrderedList, className: 'icon-list-ol',
className: "icon-list-ol", title: 'Numbered List'
title: "Numbered List", },
}, '|',
'|', {
{ name: 'link',
name: "link", action: (editor) => {
action: (editor) => { /* if(!mdeModalOpenState) {
/*if(!mdeModalOpenState) {
mdeModalOpenState = true; mdeModalOpenState = true;
$('#modal-editor-link').slideToggle(); $('#modal-editor-link').slideToggle();
}*/ } */
}, },
className: "icon-link2", className: 'icon-link2',
title: "Insert Link", title: 'Insert Link'
}, },
{ {
name: "image", name: 'image',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueImage.open(); vueImage.open()
} }
}, },
className: "icon-image", className: 'icon-image',
title: "Insert Image", title: 'Insert Image'
}, },
{ {
name: "file", name: 'file',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueFile.open(); vueFile.open()
} }
}, },
className: "icon-paper", className: 'icon-paper',
title: "Insert File", title: 'Insert File'
}, },
{ {
name: "video", name: 'video',
action: (editor) => { action: (editor) => {
if(!mdeModalOpenState) { if (!mdeModalOpenState) {
vueVideo.open(); vueVideo.open()
} }
}, },
className: "icon-video-camera2", className: 'icon-video-camera2',
title: "Insert Video Player", title: 'Insert Video Player'
}, },
'|', '|',
{ {
name: "inline-code", name: 'inline-code',
action: (editor) => { action: (editor) => {
if (!editor.codemirror.doc.somethingSelected()) {
if(!editor.codemirror.doc.somethingSelected()) { return alerts.pushError('Invalid selection', 'You must select at least 1 character first.')
return alerts.pushError('Invalid selection','You must select at least 1 character first.'); }
} let curSel = editor.codemirror.doc.getSelections()
let curSel = editor.codemirror.doc.getSelections(); curSel = _.map(curSel, (s) => {
curSel = _.map(curSel, (s) => { return '`' + s + '`'
return '`' + s + '`'; })
}); editor.codemirror.doc.replaceSelections(curSel)
editor.codemirror.doc.replaceSelections(curSel); },
className: 'icon-terminal',
}, title: 'Inline Code'
className: "icon-terminal", },
title: "Inline Code", {
}, name: 'code-block',
{ action: (editor) => {
name: "code-block", if (!mdeModalOpenState) {
action: (editor) => { 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',
'|', action: (editor) => {
{ // todo
name: "table", },
action: (editor) => { className: 'icon-table',
//todo title: 'Insert Table'
}, },
className: "icon-table", {
title: "Insert Table", name: 'horizontal-rule',
}, action: SimpleMDE.drawHorizontalRule,
{ className: 'icon-minus2',
name: "horizontal-rule", title: 'Horizontal Rule'
action: SimpleMDE.drawHorizontalRule, }
className: "icon-minus2", ],
title: "Horizontal Rule", shortcuts: {
} 'toggleBlockquote': null,
], 'toggleFullScreen': null
shortcuts: { }
"toggleBlockquote": null, })
"toggleFullScreen": null
} // -> Save
});
let saveCurrentDocument = (ev) => {
//-> Save $.ajax(window.location.href, {
data: {
let saveCurrentDocument = (ev) => { markdown: mde.value()
$.ajax(window.location.href, { },
data: { dataType: 'json',
markdown: mde.value() method: 'PUT'
}, }).then((rData, rStatus, rXHR) => {
dataType: 'json', if (rData.ok) {
method: 'PUT' window.location.assign('/' + pageEntryPath)
}).then((rData, rStatus, rXHR) => { } else {
if(rData.ok) { alerts.pushError('Something went wrong', rData.error)
window.location.assign('/' + pageEntryPath); }
} else { }, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', rData.error); alerts.pushError('Something went wrong', 'Save operation failed.')
} })
}, (rXHR, rStatus, err) => { }
alerts.pushError('Something went wrong', 'Save operation failed.');
}); $('.btn-edit-save, .btn-create-save').on('click', (ev) => {
}; saveCurrentDocument(ev)
})
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
saveCurrentDocument(ev); $(window).bind('keydown', (ev) => {
}); if (ev.ctrlKey || ev.metaKey) {
switch (String.fromCharCode(ev.which).toLowerCase()) {
$(window).bind('keydown', (ev) => { case 's':
if (ev.ctrlKey || ev.metaKey) { ev.preventDefault()
switch (String.fromCharCode(ev.which).toLowerCase()) { saveCurrentDocument(ev)
case 's': break
ev.preventDefault(); }
saveCurrentDocument(ev); }
break; })
} }
}
});
}
\ No newline at end of file
"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',
var vueHeader = new Vue({ data: {
el: '#header-container', searchq: '',
data: { searchres: [],
searchq: '', searchsuggest: [],
searchres: [], searchload: 0,
searchsuggest: [], searchactive: false,
searchload: 0, searchmoveidx: 0,
searchactive: false, searchmovekey: '',
searchmoveidx: 0, searchmovearr: []
searchmovekey: '', },
searchmovearr: [] watch: {
}, searchq: (val, oldVal) => {
watch: { vueHeader.searchmoveidx = 0
searchq: (val, oldVal) => { if (val.length >= 3) {
vueHeader.searchmoveidx = 0; vueHeader.searchactive = true
if(val.length >= 3) { vueHeader.searchload++
vueHeader.searchactive = true; socket.emit('search', { terms: val }, (data) => {
vueHeader.searchload++; vueHeader.searchres = data.match
socket.emit('search', { terms: val }, (data) => { vueHeader.searchsuggest = data.suggest
vueHeader.searchres = data.match; vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
vueHeader.searchsuggest = data.suggest; if (vueHeader.searchload > 0) { vueHeader.searchload-- }
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest); })
if(vueHeader.searchload > 0) { vueHeader.searchload--; } } else {
}); vueHeader.searchactive = false
} else { vueHeader.searchres = []
vueHeader.searchactive = false; vueHeader.searchsuggest = []
vueHeader.searchres = []; vueHeader.searchmovearr = []
vueHeader.searchsuggest = []; vueHeader.searchload = 0
vueHeader.searchmovearr = []; }
vueHeader.searchload = 0; },
} searchmoveidx: (val, oldVal) => {
}, if (val > 0) {
searchmoveidx: (val, oldVal) => { vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
if(val > 0) {
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
'res.' + vueHeader.searchmovearr[val - 1]._id : 'res.' + vueHeader.searchmovearr[val - 1]._id :
'sug.' + vueHeader.searchmovearr[val - 1]; 'sug.' + vueHeader.searchmovearr[val - 1]
} else { } else {
vueHeader.searchmovekey = ''; vueHeader.searchmovekey = ''
} }
} }
}, },
methods: { methods: {
useSuggestion: (sug) => { useSuggestion: (sug) => {
vueHeader.searchq = sug; vueHeader.searchq = sug
}, },
closeSearch: () => { closeSearch: () => {
vueHeader.searchq = ''; vueHeader.searchq = ''
}, },
moveSelectSearch: () => { moveSelectSearch: () => {
if(vueHeader.searchmoveidx < 1) { return; } if (vueHeader.searchmoveidx < 1) { return }
let i = vueHeader.searchmoveidx - 1; let i = vueHeader.searchmoveidx - 1
if(vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
} else {
vueHeader.searchq = vueHeader.searchmovearr[i];
}
},
moveDownSearch: () => {
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++;
}
},
moveUpSearch: () => {
if(vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--;
}
}
}
});
$('main').on('click', vueHeader.closeSearch); if (vueHeader.searchmovearr[i]) {
window.location.assign('/' + vueHeader.searchmovearr[i]._id)
} else {
vueHeader.searchq = vueHeader.searchmovearr[i]
}
},
moveDownSearch: () => {
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
vueHeader.searchmoveidx++
}
},
moveUpSearch: () => {
if (vueHeader.searchmoveidx > 0) {
vueHeader.searchmoveidx--
}
}
}
})
} $('main').on('click', vueHeader.closeSearch)
\ No newline at end of file }
function setInputSelection(input, startPos, endPos) { function setInputSelection (input, startPos, endPos) {
input.focus(); input.focus()
if (typeof input.selectionStart != "undefined") { if (typeof input.selectionStart !== 'undefined') {
input.selectionStart = startPos; input.selectionStart = startPos
input.selectionEnd = endPos; input.selectionEnd = endPos
} else if (document.selection && document.selection.createRange) { } else if (document.selection && document.selection.createRange) {
// IE branch // IE branch
input.select(); input.select()
var range = document.selection.createRange(); var range = document.selection.createRange()
range.collapse(true); range.collapse(true)
range.moveEnd("character", endPos); range.moveEnd('character', endPos)
range.moveStart("character", startPos); range.moveStart('character', startPos)
range.select(); range.select()
} }
} }
\ No newline at end of file
function makeSafePath(rawPath) { function makeSafePath (rawPath) {
let rawParts = _.split(_.trim(rawPath), '/')
let rawParts = _.split(_.trim(rawPath), '/'); rawParts = _.map(rawParts, (r) => {
rawParts = _.map(rawParts, (r) => { return _.kebabCase(_.deburr(_.trim(r)))
return _.kebabCase(_.deburr(_.trim(r))); })
});
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/'); }
}
\ No newline at end of file
"use strict"; 'use strict'
jQuery( document ).ready(function( $ ) { jQuery(document).ready(function ($) {
$('#login-user').focus()
$('#login-user').focus(); })
});
\ No newline at end of file
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
// Vue Create User instance // Vue Create User instance
let vueCreateUser = new Vue({ let vueCreateUser = new Vue({
el: '#modal-admin-users-create', el: '#modal-admin-users-create',
data: { data: {
email: '', email: '',
provider: 'local', provider: 'local',
password: '', password: '',
name: '' name: ''
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-create').addClass('is-active'); $('#modal-admin-users-create').addClass('is-active')
$('#modal-admin-users-create input').first().focus(); $('#modal-admin-users-create input').first().focus()
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-create').removeClass('is-active'); $('#modal-admin-users-create').removeClass('is-active')
vueCreateUser.email = ''; vueCreateUser.email = ''
vueCreateUser.provider = 'local'; vueCreateUser.provider = 'local'
}, },
create: (ev) => { create: (ev) => {
vueCreateUser.cancel()
}
}
})
vueCreateUser.cancel(); $('.btn-create-prompt').on('click', vueCreateUser.open)
}
}
});
$('.btn-create-prompt').on('click', vueCreateUser.open);
\ No newline at end of file
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
// Vue Delete User instance // Vue Delete User instance
let vueDeleteUser = new Vue({ let vueDeleteUser = new Vue({
el: '#modal-admin-users-delete', el: '#modal-admin-users-delete',
data: { data: {
}, },
methods: { methods: {
open: (ev) => { open: (ev) => {
$('#modal-admin-users-delete').addClass('is-active'); $('#modal-admin-users-delete').addClass('is-active')
}, },
cancel: (ev) => { cancel: (ev) => {
$('#modal-admin-users-delete').removeClass('is-active'); $('#modal-admin-users-delete').removeClass('is-active')
}, },
deleteUser: (ev) => { deleteUser: (ev) => {
vueDeleteUser.cancel(); vueDeleteUser.cancel()
} }
} }
}); })
$('.btn-deluser-prompt').on('click', vueDeleteUser.open); $('.btn-deluser-prompt').on('click', vueDeleteUser.open)
\ No newline at end of file
//-> Create New Document // -> Create New Document
let suggestedCreatePath = currentBasePath + '/new-page'; let suggestedCreatePath = currentBasePath + '/new-page'
$('.btn-create-prompt').on('click', (ev) => { $('.btn-create-prompt').on('click', (ev) => {
$('#txt-create-prompt').val(suggestedCreatePath); $('#txt-create-prompt').val(suggestedCreatePath)
$('#modal-create-prompt').toggleClass('is-active'); $('#modal-create-prompt').toggleClass('is-active')
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length); setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-create-prompt').on('keypress', (ev) => { $('#txt-create-prompt').on('keypress', (ev) => {
if(ev.which === 13) { if (ev.which === 13) {
$('.btn-create-go').trigger('click'); $('.btn-create-go').trigger('click')
} }
}); })
$('.btn-create-go').on('click', (ev) => { $('.btn-create-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-create-prompt').val())
let newDocPath = makeSafePath($('#txt-create-prompt').val()); if (_.isEmpty(newDocPath)) {
if(_.isEmpty(newDocPath)) { $('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden'); } else {
} else { $('#txt-create-prompt').parent().addClass('is-loading')
$('#txt-create-prompt').parent().addClass('is-loading'); window.location.assign('/create/' + newDocPath)
window.location.assign('/create/' + newDocPath); }
} })
});
\ No newline at end of file
//-> Move Existing Document // -> Move Existing Document
if(currentBasePath !== '') { if (currentBasePath !== '') {
$('.btn-move-prompt').removeClass('is-hidden'); $('.btn-move-prompt').removeClass('is-hidden')
} }
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1; let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
$('.btn-move-prompt').on('click', (ev) => { $('.btn-move-prompt').on('click', (ev) => {
$('#txt-move-prompt').val(currentBasePath); $('#txt-move-prompt').val(currentBasePath)
$('#modal-move-prompt').toggleClass('is-active'); $('#modal-move-prompt').toggleClass('is-active')
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length); setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden'); $('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
}); })
$('#txt-move-prompt').on('keypress', (ev) => { $('#txt-move-prompt').on('keypress', (ev) => {
if(ev.which === 13) { if (ev.which === 13) {
$('.btn-move-go').trigger('click'); $('.btn-move-go').trigger('click')
} }
}); })
$('.btn-move-go').on('click', (ev) => { $('.btn-move-go').on('click', (ev) => {
let newDocPath = makeSafePath($('#txt-move-prompt').val())
let newDocPath = makeSafePath($('#txt-move-prompt').val()); if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
if(_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') { $('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden'); } else {
} else { $('#txt-move-prompt').parent().addClass('is-loading')
$('#txt-move-prompt').parent().addClass('is-loading');
$.ajax(window.location.href, {
$.ajax(window.location.href, { data: {
data: { move: newDocPath
move: newDocPath },
}, dataType: 'json',
dataType: 'json', method: 'PUT'
method: 'PUT' }).then((rData, rStatus, rXHR) => {
}).then((rData, rStatus, rXHR) => { if (rData.ok) {
if(rData.ok) { window.location.assign('/' + newDocPath)
window.location.assign('/' + newDocPath); } else {
} else { alerts.pushError('Something went wrong', rData.error)
alerts.pushError('Something went wrong', rData.error); }
} }, (rXHR, rStatus, err) => {
}, (rXHR, rStatus, err) => { alerts.pushError('Something went wrong', 'Save operation failed.')
alerts.pushError('Something went wrong', 'Save operation failed.'); })
}); }
})
}
});
\ No newline at end of file
if($('#page-type-admin-profile').length) { if ($('#page-type-admin-profile').length) {
let vueProfile = new Vue({
el: '#page-type-admin-profile',
data: {
password: '********',
passwordVerify: '********',
name: ''
},
methods: {
saveUser: (ev) => {
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.')
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp)
})
}
},
created: function () {
this.name = usrDataName
}
})
} else if ($('#page-type-admin-users').length) {
let vueProfile = new Vue({ // =include ../modals/admin-users-create.js
el: '#page-type-admin-profile',
data: {
password: '********',
passwordVerify: '********',
name: ''
},
methods: {
saveUser: (ev) => {
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.');
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp);
})
}
},
created: function() {
this.name = usrDataName;
}
});
} else if($('#page-type-admin-users').length) { } else if ($('#page-type-admin-users-edit').length) {
let vueEditUser = new Vue({
el: '#page-type-admin-users-edit',
data: {
id: '',
email: '',
password: '********',
name: '',
rights: [],
roleoverride: 'none'
},
methods: {
addRightsRow: (ev) => {
vueEditUser.rights.push({
role: 'write',
path: '/',
exact: false,
deny: false
})
},
removeRightsRow: (idx) => {
_.pullAt(vueEditUser.rights, idx)
vueEditUser.$forceUpdate()
},
saveUser: (ev) => {
let formattedRights = _.cloneDeep(vueEditUser.rights)
switch (vueEditUser.roleoverride) {
case 'admin':
formattedRights.push({
role: 'admin',
path: '/',
exact: false,
deny: false
})
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.')
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp)
})
}
},
created: function () {
this.id = usrData._id
this.email = usrData.email
this.name = usrData.name
//=include ../modals/admin-users-create.js if (_.find(usrData.rights, { role: 'admin' })) {
this.rights = _.reject(usrData.rights, ['role', 'admin'])
this.roleoverride = 'admin'
} else {
this.rights = usrData.rights
}
}
})
} else if($('#page-type-admin-users-edit').length) { // =include ../modals/admin-users-delete.js
}
let vueEditUser = new Vue({
el: '#page-type-admin-users-edit',
data: {
id: '',
email: '',
password: '********',
name: '',
rights: [],
roleoverride: 'none'
},
methods: {
addRightsRow: (ev) => {
vueEditUser.rights.push({
role: 'write',
path: '/',
exact: false,
deny: false
});
},
removeRightsRow: (idx) => {
_.pullAt(vueEditUser.rights, idx)
vueEditUser.$forceUpdate()
},
saveUser: (ev) => {
let formattedRights = _.cloneDeep(vueEditUser.rights)
switch(vueEditUser.roleoverride) {
case 'admin':
formattedRights.push({
role: 'admin',
path: '/',
exact: false,
deny: false
})
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.');
}).fail((jqXHR, txtStatus, resp) => {
alerts.pushError('Error', resp);
})
}
},
created: function() {
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';
} else {
this.rights = usrData.rights;
}
}
});
//=include ../modals/admin-users-delete.js
}
\ No newline at end of file
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')
})
$('.btn-create-discard').on('click', (ev) => { // =include ../components/editor.js
$('#modal-create-discard').toggleClass('is-active'); }
});
//=include ../components/editor.js
}
\ No newline at end of file
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')
})
$('.btn-edit-discard').on('click', (ev) => { // =include ../components/editor.js
$('#modal-edit-discard').toggleClass('is-active'); }
});
//=include ../components/editor.js
}
\ No newline at end of file
if($('#page-type-source').length) { if ($('#page-type-source').length) {
var scEditor = ace.edit('source-display')
scEditor.setTheme('ace/theme/tomorrow_night')
scEditor.getSession().setMode('ace/mode/markdown')
scEditor.setOption('fontSize', '14px')
scEditor.setOption('hScrollBarAlwaysVisible', false)
scEditor.setOption('wrap', true)
scEditor.setReadOnly(true)
scEditor.renderer.updateFull()
var scEditor = ace.edit("source-display"); let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
scEditor.setTheme("ace/theme/tomorrow_night");
scEditor.getSession().setMode("ace/mode/markdown");
scEditor.setOption('fontSize', '14px');
scEditor.setOption('hScrollBarAlwaysVisible', false);
scEditor.setOption('wrap', true);
scEditor.setReadOnly(true);
scEditor.renderer.updateFull();
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''; // =include ../modals/create.js
// =include ../modals/move.js
//=include ../modals/create.js }
//=include ../modals/move.js
}
\ No newline at end of file
if($('#page-type-view').length) { if ($('#page-type-view').length) {
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''; // =include ../modals/create.js
// =include ../modals/move.js
//=include ../modals/create.js }
//=include ../modals/move.js
}
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); var express = require('express')
var router = express.Router(); var router = express.Router()
const Promise = require('bluebird'); const Promise = require('bluebird')
const validator = require('validator'); const validator = require('validator')
const _ = require('lodash'); const _ = require('lodash')
/** /**
* Admin * Admin
*/ */
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.redirect('/admin/profile'); res.redirect('/admin/profile')
}); })
router.get('/profile', (req, res) => { router.get('/profile', (req, res) => {
if (res.locals.isGuest) {
return res.render('error-forbidden')
}
if(res.locals.isGuest) { res.render('pages/admin/profile', { adminTab: 'profile' })
return res.render('error-forbidden'); })
}
res.render('pages/admin/profile', { adminTab: 'profile' });
});
router.post('/profile', (req, res) => { router.post('/profile', (req, res) => {
if (res.locals.isGuest) {
if(res.locals.isGuest) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
return db.User.findById(req.user.id).then((usr) => {
return db.User.findById(req.user.id).then((usr) => { usr.name = _.trim(req.body.name)
usr.name = _.trim(req.body.name); if (usr.provider === 'local' && req.body.password !== '********') {
if(usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password)
let nPwd = _.trim(req.body.password); if (nPwd.length < 6) {
if(nPwd.length < 6) { return Promise.reject(new Error('New Password too short!'))
return Promise.reject(new Error('New Password too short!')) } else {
} else { return db.User.hashPassword(nPwd).then((pwd) => {
return db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd
usr.password = pwd; return usr.save()
return usr.save(); })
}); }
} } else {
} else { return usr.save()
return usr.save(); }
} }).then(() => {
}).then(() => { return res.json({ msg: 'OK' })
return res.json({ msg: 'OK' }); }).catch((err) => {
}).catch((err) => { res.status(400).json({ msg: err.message })
res.status(400).json({ msg: err.message }); })
}) })
});
router.get('/stats', (req, res) => { router.get('/stats', (req, res) => {
if (res.locals.isGuest) {
if(res.locals.isGuest) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
Promise.all([
Promise.all([ db.Entry.count(),
db.Entry.count(), db.UplFile.count(),
db.UplFile.count(), db.User.count()
db.User.count() ]).spread((totalEntries, totalUploads, totalUsers) => {
]).spread((totalEntries, totalUploads, totalUsers) => { return res.render('pages/admin/stats', {
return res.render('pages/admin/stats', { totalEntries, totalUploads, totalUsers, adminTab: 'stats'
totalEntries, totalUploads, totalUsers, }) || true
adminTab: 'stats' }).catch((err) => {
}) || true; throw err
}).catch((err) => { })
throw err; })
});
});
router.get('/users', (req, res) => { router.get('/users', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
db.User.find({})
db.User.find({}) .select('-password -rights')
.select('-password -rights') .sort('name email')
.sort('name email') .exec().then((usrs) => {
.exec().then((usrs) => { res.render('pages/admin/users', { adminTab: 'users', usrs })
res.render('pages/admin/users', { adminTab: 'users', usrs }); })
}); })
});
router.get('/users/:id', (req, res) => { router.get('/users/:id', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
if (!validator.isMongoId(req.params.id)) {
if(!validator.isMongoId(req.params.id)) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
db.User.findById(req.params.id)
db.User.findById(req.params.id) .select('-password -providerId')
.select('-password -providerId') .exec().then((usr) => {
.exec().then((usr) => { let usrOpts = {
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
let usrOpts = { canChangeName: (usr.email !== 'guest'),
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin), canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
canChangeName: (usr.email !== 'guest'), canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'), canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)), }
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
}; res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
})
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts }); })
});
});
router.post('/users/:id', (req, res) => { router.post('/users/:id', (req, res) => {
if (!res.locals.rights.manage) {
if(!res.locals.rights.manage) { return res.status(401).json({ msg: 'Unauthorized' })
return res.status(401).json({ msg: 'Unauthorized' }); }
}
if (!validator.isMongoId(req.params.id)) {
if(!validator.isMongoId(req.params.id)) { return res.status(400).json({ msg: 'Invalid User ID' })
return res.status(400).json({ msg: 'Invalid User ID' }); }
}
return db.User.findById(req.params.id).then((usr) => {
return db.User.findById(req.params.id).then((usr) => { usr.name = _.trim(req.body.name)
usr.name = _.trim(req.body.name); usr.rights = JSON.parse(req.body.rights)
usr.rights = JSON.parse(req.body.rights); if (usr.provider === 'local' && req.body.password !== '********') {
if(usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password)
let nPwd = _.trim(req.body.password); if (nPwd.length < 6) {
if(nPwd.length < 6) { return Promise.reject(new Error('New Password too short!'))
return Promise.reject(new Error('New Password too short!')) } else {
} else { return db.User.hashPassword(nPwd).then((pwd) => {
return db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd
usr.password = pwd; return usr.save()
return usr.save(); })
}); }
} } else {
} else { return usr.save()
return usr.save(); }
} }).then(() => {
}).then(() => { return res.json({ msg: 'OK' })
return res.json({ msg: 'OK' }); }).catch((err) => {
}).catch((err) => { res.status(400).json({ msg: err.message })
res.status(400).json({ msg: err.message }); })
}) })
});
router.get('/settings', (req, res) => { router.get('/settings', (req, res) => {
if (!res.locals.rights.manage) {
return res.render('error-forbidden')
}
if(!res.locals.rights.manage) { res.render('pages/admin/settings', { adminTab: 'settings' })
return res.render('error-forbidden'); })
}
res.render('pages/admin/settings', { adminTab: 'settings' });
});
module.exports = router; module.exports = router
\ No newline at end of file
var express = require('express'); 'use strict'
var router = express.Router();
var passport = require('passport'); const express = require('express')
var ExpressBrute = require('express-brute'); const router = express.Router()
var ExpressBruteMongooseStore = require('express-brute-mongoose'); const passport = require('passport')
var moment = require('moment'); const ExpressBrute = require('express-brute')
const ExpressBruteMongooseStore = require('express-brute-mongoose')
const moment = require('moment')
/** /**
* Setup Express-Brute * Setup Express-Brute
*/ */
var EBstore = new ExpressBruteMongooseStore(db.Bruteforce); const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
var bruteforce = new ExpressBrute(EBstore, { const bruteforce = new ExpressBrute(EBstore, {
freeRetries: 5, freeRetries: 5,
minWait: 60 * 1000, minWait: 60 * 1000,
maxWait: 5 * 60 * 1000, maxWait: 5 * 60 * 1000,
refreshTimeoutOnRequest: false, refreshTimeoutOnRequest: false,
failCallback(req, res, next, nextValidRequestDate) { failCallback (req, res, next, nextValidRequestDate) {
req.flash('alert', { req.flash('alert', {
class: 'error', class: 'error',
title: 'Too many attempts!', title: 'Too many attempts!',
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.', message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
iconClass: 'fa-times' iconClass: 'fa-times'
}); })
res.redirect('/login'); res.redirect('/login')
} }
}); })
/** /**
* Login form * Login form
*/ */
router.get('/login', function(req, res, next) { router.get('/login', function (req, res, next) {
res.render('auth/login', { res.render('auth/login', {
usr: res.locals.usr usr: res.locals.usr
}); })
}); })
router.post('/login', bruteforce.prevent, function(req, res, next) {
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) { if (!user) {
req.flash('alert', { req.flash('alert', {
title: 'Invalid login', title: 'Invalid login',
message: "The email or password is invalid." message: 'The email or password is invalid.'
}); })
return res.redirect('/login'); return res.redirect('/login')
} }
req.logIn(user, function(err) { req.logIn(user, function (err) {
if (err) { return next(err); } if (err) { return next(err) }
req.brute.reset(function () { req.brute.reset(function () {
return res.redirect('/'); return res.redirect('/')
}); })
}); })
})(req, res, next)
})(req, res, next); })
});
/** /**
* Social Login * Social Login
*/ */
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] })); router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] })); router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] })); router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' })); router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
/** /**
* Logout * Logout
*/ */
router.get('/logout', function(req, res) { router.get('/logout', function (req, res) {
req.logout(); req.logout()
res.redirect('/'); res.redirect('/')
}); })
module.exports = router; module.exports = router
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var _ = require('lodash'); const _ = require('lodash')
// ========================================== // ==========================================
// EDIT MODE // EDIT MODE
...@@ -12,132 +12,123 @@ var _ = require('lodash'); ...@@ -12,132 +12,123 @@ var _ = require('lodash');
* Edit document in Markdown * Edit document in Markdown
*/ */
router.get('/edit/*', (req, res, next) => { router.get('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
entries.fetchOriginal(safePath, {
entries.fetchOriginal(safePath, { parseMarkdown: false,
parseMarkdown: false, parseMeta: true,
parseMeta: true, parseTree: false,
parseTree: false, includeMarkdown: true,
includeMarkdown: true, includeParentInfo: false,
includeParentInfo: false, cache: false
cache: false }).then((pageData) => {
}).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/edit', { pageData })
res.render('pages/edit', { pageData }); } else {
} else { throw new Error('Invalid page path.')
throw new Error('Invalid page path.'); }
} return true
return true; }).catch((err) => {
}).catch((err) => { res.render('error', {
res.render('error', { message: err.message,
message: err.message, error: {}
error: {} })
}); })
}); })
});
router.put('/edit/*', (req, res, next) => { router.put('/edit/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
entries.update(safePath, req.body.markdown).then(() => {
entries.update(safePath, req.body.markdown).then(() => { return res.json({
return res.json({ ok: true
ok: true }) || true
}) || true; }).catch((err) => {
}).catch((err) => { res.json({
res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
});
// ========================================== // ==========================================
// CREATE MODE // CREATE MODE
// ========================================== // ==========================================
router.get('/create/*', (req, res, next) => { router.get('/create/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.render('error-forbidden')
return res.render('error-forbidden'); }
}
if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
if(_.some(['create','edit','account','source','history','mk'], (e) => { return _.startsWith(req.path, '/create/' + e); })) { return res.render('error', {
return res.render('error', { message: 'You cannot create a document with this name as it is reserved by the system.',
message: 'You cannot create a document with this name as it is reserved by the system.', error: {}
error: {} })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
entries.exists(safePath).then((docExists) => {
entries.exists(safePath).then((docExists) => { if (!docExists) {
if(!docExists) { return entries.getStarter(safePath).then((contents) => {
return entries.getStarter(safePath).then((contents) => { let pageData = {
markdown: contents,
let pageData = { meta: {
markdown: contents, title: _.startCase(safePath),
meta: { path: safePath
title: _.startCase(safePath), }
path: safePath }
} res.render('pages/create', { pageData })
};
res.render('pages/create', { pageData }); return true
}).catch((err) => {
return true; winston.warn(err)
throw new Error('Could not load starter content!')
}).catch((err) => { })
throw new Error('Could not load starter content!'); } else {
}); throw new Error('This entry already exists!')
} else { }
throw new Error('This entry already exists!'); }).catch((err) => {
} res.render('error', {
}).catch((err) => { message: err.message,
res.render('error', { error: {}
message: err.message, })
error: {} })
}); })
});
});
router.put('/create/*', (req, res, next) => { router.put('/create/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
entries.create(safePath, req.body.markdown).then(() => {
entries.create(safePath, req.body.markdown).then(() => { return res.json({
return res.json({ ok: true
ok: true }) || true
}) || true; }).catch((err) => {
}).catch((err) => { return res.json({
return res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
});
// ========================================== // ==========================================
// VIEW MODE // VIEW MODE
...@@ -147,102 +138,94 @@ router.put('/create/*', (req, res, next) => { ...@@ -147,102 +138,94 @@ router.put('/create/*', (req, res, next) => {
* View source of a document * View source of a document
*/ */
router.get('/source/*', (req, res, next) => { router.get('/source/*', (req, res, next) => {
let safePath = entries.parsePath(_.replace(req.path, '/source', ''))
let safePath = entries.parsePath(_.replace(req.path, '/source', ''));
entries.fetchOriginal(safePath, {
entries.fetchOriginal(safePath, { parseMarkdown: false,
parseMarkdown: false, parseMeta: true,
parseMeta: true, parseTree: false,
parseTree: false, includeMarkdown: true,
includeMarkdown: true, includeParentInfo: false,
includeParentInfo: false, cache: false
cache: false }).then((pageData) => {
}).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/source', { pageData })
res.render('pages/source', { pageData }); } else {
} else { throw new Error('Invalid page path.')
throw new Error('Invalid page path.'); }
} return true
return true; }).catch((err) => {
}).catch((err) => { res.render('error', {
res.render('error', { message: err.message,
message: err.message, error: {}
error: {} })
}); })
}); })
});
/** /**
* View document * View document
*/ */
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let safePath = entries.parsePath(req.path)
let safePath = entries.parsePath(req.path);
entries.fetch(safePath).then((pageData) => {
entries.fetch(safePath).then((pageData) => { if (pageData) {
if(pageData) { res.render('pages/view', { pageData })
res.render('pages/view', { pageData }); } else {
} else { res.render('error-notexist', {
res.render('error-notexist', { newpath: safePath
newpath: safePath })
}); }
} return true
return true; }).error((err) => {
}).error((err) => { if (safePath === 'home') {
res.render('pages/welcome')
if(safePath === 'home') { } else {
res.render('pages/welcome'); res.render('error-notexist', {
} else { message: err.message,
res.render('error-notexist', { newpath: safePath
message: err.message, })
newpath: safePath }
}); }).catch((err) => {
} res.render('error', {
message: err.message,
}).catch((err) => { error: {}
res.render('error', { })
message: err.message, })
error: {} })
});
});
});
/** /**
* Move document * Move document
*/ */
router.put('/*', (req, res, next) => { router.put('/*', (req, res, next) => {
if (!res.locals.rights.write) {
if(!res.locals.rights.write) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Forbidden'
error: 'Forbidden' })
}); }
}
let safePath = entries.parsePath(req.path)
let safePath = entries.parsePath(req.path);
if (_.isEmpty(req.body.move)) {
if(_.isEmpty(req.body.move)) { return res.json({
return res.json({ ok: false,
ok: false, error: 'Invalid document action call.'
error: 'Invalid document action call.' })
}); }
}
let safeNewPath = entries.parsePath(req.body.move)
let safeNewPath = entries.parsePath(req.body.move);
entries.move(safePath, safeNewPath).then(() => {
entries.move(safePath, safeNewPath).then(() => { res.json({
res.json({ ok: true
ok: true })
}); }).catch((err) => {
}).catch((err) => { res.json({
res.json({ ok: false,
ok: false, error: err.message
error: err.message })
}); })
}); })
}); module.exports = router
module.exports = router;
\ No newline at end of file
"use strict"; 'use strict'
var express = require('express'); const express = require('express')
var router = express.Router(); const router = express.Router()
var readChunk = require('read-chunk'), const readChunk = require('read-chunk')
fileType = require('file-type'), const fileType = require('file-type')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
path = require('path'), const path = require('path')
_ = require('lodash'); const _ = require('lodash')
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$"); const validPathRe = new RegExp('^([a-z0-9\\/-]+\\.[a-z0-9]+)$')
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$"); const validPathThumbsRe = new RegExp('^([0-9]+\\.png)$')
// ========================================== // ==========================================
// SERVE UPLOADS FILES // SERVE UPLOADS FILES
// ========================================== // ==========================================
router.get('/t/*', (req, res, next) => { router.get('/t/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0]; if (!validPathThumbsRe.test(fileName)) {
if(!validPathThumbsRe.test(fileName)) { return res.sendStatus(404).end()
return res.sendStatus(404).end(); }
}
// todo: Authentication-based access
//todo: Authentication-based access
res.sendFile(fileName, {
res.sendFile(fileName, { root: lcdata.getThumbsPath(),
root: lcdata.getThumbsPath(), dotfiles: 'deny'
dotfiles: 'deny' }, (err) => {
}, (err) => { if (err) {
if (err) { res.status(err.status).end()
res.status(err.status).end(); }
} })
}); })
});
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' })
if(!destFolderPath) { return true
res.json({ ok: false, msg: 'Invalid Folder' }); }
return true;
} Promise.map(req.files, (f) => {
let destFilename = ''
Promise.map(req.files, (f) => { let destFilePath = ''
let destFilename = ''; return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
let destFilePath = ''; destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
return readChunk(f.path, 0, 262)
destFilename = fname; }).then((buf) => {
destFilePath = path.resolve(destFolderPath, destFilename); // -> Check MIME type by magic number
return readChunk(f.path, 0, 262); let mimeInfo = fileType(buf)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
}).then((buf) => { return Promise.reject(new Error('Invalid file type.'))
}
//-> Check MIME type by magic number return true
}).then(() => {
let mimeInfo = fileType(buf); // -> Move file to final destination
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return Promise.reject(new Error('Invalid file type.')); return fs.moveAsync(f.path, destFilePath, { clobber: false })
} }).then(() => {
return true; return {
ok: true,
}).then(() => { filename: destFilename,
filesize: f.size
//-> Move file to final destination }
}).reflect()
return fs.moveAsync(f.path, destFilePath, { clobber: false }); }, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => {
}).then(() => { if (r.isFulfilled()) {
return { return r.value()
ok: true, } else {
filename: destFilename, return {
filesize: f.size ok: false,
}; msg: r.reason().message
}).reflect(); }
}
}, {concurrency: 3}).then((results) => { })
let uplResults = _.map(results, (r) => { res.json({ ok: true, results: uplResults })
if(r.isFulfilled()) { return true
return r.value(); }).catch((err) => {
} else { res.json({ ok: false, msg: err.message })
return { return true
ok: false, })
msg: r.reason().message })
}; })
}
});
res.json({ ok: true, results: uplResults });
return true;
}).catch((err) => {
res.json({ ok: false, msg: err.message });
return true;
});
});
});
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => { router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { if (!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' })
if(!destFolderPath) { return true
res.json({ ok: false, msg: 'Invalid Folder' }); }
return true;
} Promise.map(req.files, (f) => {
let destFilename = ''
Promise.map(req.files, (f) => { let destFilePath = ''
let destFilename = ''; return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
let destFilePath = ''; destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
// -> Move file to final destination
destFilename = fname;
destFilePath = path.resolve(destFolderPath, destFilename); return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => {
//-> Move file to final destination return {
ok: true,
return fs.moveAsync(f.path, destFilePath, { clobber: false }); filename: destFilename,
filesize: f.size
}).then(() => { }
return { }).reflect()
ok: true, }, {concurrency: 3}).then((results) => {
filename: destFilename, let uplResults = _.map(results, (r) => {
filesize: f.size if (r.isFulfilled()) {
}; return r.value()
}).reflect(); } else {
return {
}, {concurrency: 3}).then((results) => { ok: false,
let uplResults = _.map(results, (r) => { msg: r.reason().message
if(r.isFulfilled()) { }
return r.value(); }
} else { })
return { res.json({ ok: true, results: uplResults })
ok: false, return true
msg: r.reason().message }).catch((err) => {
}; res.json({ ok: false, msg: err.message })
} return true
}); })
res.json({ ok: true, results: uplResults }); })
return true; })
}).catch((err) => {
res.json({ ok: false, msg: err.message });
return true;
});
});
});
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let fileName = req.params[0]
let fileName = req.params[0]; if (!validPathRe.test(fileName)) {
if(!validPathRe.test(fileName)) { return res.sendStatus(404).end()
return res.sendStatus(404).end(); }
}
// todo: Authentication-based access
//todo: Authentication-based access
res.sendFile(fileName, {
res.sendFile(fileName, { root: git.getRepoPath() + '/uploads/',
root: git.getRepoPath() + '/uploads/', dotfiles: 'deny'
dotfiles: 'deny' }, (err) => {
}, (err) => { if (err) {
if (err) { res.status(err.status).end()
res.status(err.status).end(); }
} })
}); })
}); module.exports = router
module.exports = router;
\ No newline at end of file
"use strict"; 'use strict'
const _ = require('lodash')
module.exports = (socket) => { module.exports = (socket) => {
if (!socket.request.user.logged_in) {
if(!socket.request.user.logged_in) { return
return;
} }
//----------------------------------------- // -----------------------------------------
// SEARCH // SEARCH
//----------------------------------------- // -----------------------------------------
socket.on('search', (data, cb) => { socket.on('search', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
entries.search(data.terms).then((results) => { entries.search(data.terms).then((results) => {
return cb(results) || true; return cb(results) || true
}); })
}); })
//----------------------------------------- // -----------------------------------------
// UPLOADS // UPLOADS
//----------------------------------------- // -----------------------------------------
socket.on('uploadsGetFolders', (data, cb) => { socket.on('uploadsGetFolders', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFolders().then((f) => { upl.getUploadsFolders().then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsCreateFolder', (data, cb) => { socket.on('uploadsCreateFolder', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.createUploadsFolder(data.foldername).then((f) => { upl.createUploadsFolder(data.foldername).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetImages', (data, cb) => { socket.on('uploadsGetImages', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('image', data.folder).then((f) => { upl.getUploadsFiles('image', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsGetFiles', (data, cb) => { socket.on('uploadsGetFiles', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.getUploadsFiles('binary', data.folder).then((f) => { upl.getUploadsFiles('binary', data.folder).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsDeleteFile', (data, cb) => { socket.on('uploadsDeleteFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.deleteUploadsFile(data.uid).then((f) => { upl.deleteUploadsFile(data.uid).then((f) => {
return cb(f) || true; return cb(f) || true
}); })
}); })
socket.on('uploadsFetchFileFromURL', (data, cb) => { socket.on('uploadsFetchFileFromURL', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => { upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsRenameFile', (data, cb) => { socket.on('uploadsRenameFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => { upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
socket.on('uploadsMoveFile', (data, cb) => { socket.on('uploadsMoveFile', (data, cb) => {
cb = cb || _.noop; cb = cb || _.noop
upl.moveUploadsFile(data.uid, data.folder).then((f) => { upl.moveUploadsFile(data.uid, data.folder).then((f) => {
return cb({ ok: true }) || true; return cb({ ok: true }) || true
}).catch((err) => { }).catch((err) => {
return cb({ return cb({
ok: false, ok: false,
msg: err.message msg: err.message
}) || true; }) || true
}); })
}); })
}
};
\ No newline at end of file
var gulp = require("gulp"); 'use strict'
var watch = require('gulp-watch');
var merge = require('merge-stream'); const gulp = require('gulp')
var babel = require("gulp-babel"); const watch = require('gulp-watch')
var uglify = require('gulp-uglify'); const merge = require('merge-stream')
var concat = require('gulp-concat'); const babel = require('gulp-babel')
var nodemon = require('gulp-nodemon'); const uglify = require('gulp-uglify')
var plumber = require('gulp-plumber'); const concat = require('gulp-concat')
var zip = require('gulp-zip'); const nodemon = require('gulp-nodemon')
var tar = require('gulp-tar'); const plumber = require('gulp-plumber')
var gzip = require('gulp-gzip'); const zip = require('gulp-zip')
var sass = require('gulp-sass'); const tar = require('gulp-tar')
var cleanCSS = require('gulp-clean-css'); const gzip = require('gulp-gzip')
var include = require("gulp-include"); const sass = require('gulp-sass')
var run = require('run-sequence'); const cleanCSS = require('gulp-clean-css')
var _ = require('lodash'); const include = require('gulp-include')
const run = require('run-sequence')
/** /**
* Paths * Paths
* *
* @type {Object} * @type {Object}
*/ */
var paths = { const paths = {
scripts: { scripts: {
combine: [ combine: [
'./node_modules/socket.io-client/dist/socket.io.min.js', './node_modules/socket.io-client/dist/socket.io.min.js',
'./node_modules/jquery/dist/jquery.min.js', './node_modules/jquery/dist/jquery.min.js',
'./node_modules/vue/dist/vue.min.js', './node_modules/vue/dist/vue.min.js',
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js', './node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
'./node_modules/jquery-simple-upload/simpleUpload.min.js', './node_modules/jquery-simple-upload/simpleUpload.min.js',
'./node_modules/jquery-contextmenu/dist/jquery.contextMenu.min.js', './node_modules/jquery-contextmenu/dist/jquery.contextMenu.min.js',
'./node_modules/sticky-js/dist/sticky.min.js', './node_modules/sticky-js/dist/sticky.min.js',
'./node_modules/simplemde/dist/simplemde.min.js', './node_modules/simplemde/dist/simplemde.min.js',
'./node_modules/ace-builds/src-min-noconflict/ace.js', './node_modules/ace-builds/src-min-noconflict/ace.js',
'./node_modules/ace-builds/src-min-noconflict/ext-modelist.js', './node_modules/ace-builds/src-min-noconflict/ext-modelist.js',
'./node_modules/ace-builds/src-min-noconflict/mode-markdown.js', './node_modules/ace-builds/src-min-noconflict/mode-markdown.js',
'./node_modules/ace-builds/src-min-noconflict/theme-tomorrow_night.js', './node_modules/ace-builds/src-min-noconflict/theme-tomorrow_night.js',
'./node_modules/filesize.js/dist/filesize.min.js', './node_modules/filesize.js/dist/filesize.min.js',
'./node_modules/lodash/lodash.min.js' './node_modules/lodash/lodash.min.js'
], ],
ace: [ ace: [
'./node_modules/ace-builds/src-min-noconflict/mode-*.js', './node_modules/ace-builds/src-min-noconflict/mode-*.js',
'!./node_modules/ace-builds/src-min-noconflict/mode-markdown.js' '!./node_modules/ace-builds/src-min-noconflict/mode-markdown.js'
], ],
compile: [ compile: [
'./client/js/*.js' './client/js/*.js'
], ],
watch: [ watch: [
'./client/js/**/*.js' './client/js/**/*.js'
] ]
}, },
css: { css: {
combine: [ combine: [
'./node_modules/highlight.js/styles/tomorrow.css', './node_modules/highlight.js/styles/tomorrow.css',
'./node_modules/simplemde/dist/simplemde.min.css' './node_modules/simplemde/dist/simplemde.min.css'
], ],
compile: [ compile: [
'./client/scss/*.scss' './client/scss/*.scss'
], ],
includes: [ includes: [
'./node_modules/requarks-core' //! MUST BE LAST './node_modules/requarks-core' //! MUST BE LAST
], ],
watch: [ watch: [
'./client/scss/**/*.scss', './client/scss/**/*.scss',
'../core/core-client/scss/**/*.scss' '../core/core-client/scss/**/*.scss'
] ]
}, },
fonts: [ fonts: [
'../node_modules/requarks-core/core-client/fonts/**/*' //! MUST BE LAST '../node_modules/requarks-core/core-client/fonts/**/*' //! MUST BE LAST
], ],
deploy: [ deploy: [
'./**/*', './**/*',
'!node_modules', '!node_modules/**', '!node_modules', '!node_modules/**',
'!coverage', '!coverage/**', '!coverage', '!coverage/**',
'!client/js', '!client/js/**', '!client/js', '!client/js/**',
'!client/scss', '!client/scss/**', '!client/scss', '!client/scss/**',
'!dist', '!dist/**', '!dist', '!dist/**',
'!tests', '!tests/**', '!tests', '!tests/**',
'!data', '!data/**', '!data', '!data/**',
'!repo', '!repo/**', '!repo', '!repo/**',
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml', '!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project' '!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
] ]
}; }
/** /**
* TASK - Starts server in development mode * TASK - Starts server in development mode
*/ */
gulp.task('server', ['scripts', 'css', 'fonts'], function() { gulp.task('server', ['scripts', 'css', 'fonts'], function () {
nodemon({ nodemon({
script: './server', script: './server',
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'], ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
ext: 'js json', ext: 'js json',
env: { 'NODE_ENV': 'development' } env: { 'NODE_ENV': 'development' }
}); })
}); })
/** /**
* TASK - Process all scripts processes * TASK - Process all scripts processes
*/ */
gulp.task("scripts", ['scripts-libs', 'scripts-app']); gulp.task('scripts', ['scripts-libs', 'scripts-app'])
/** /**
* TASK - Combine js libraries * TASK - Combine js libraries
*/ */
gulp.task("scripts-libs", function () { gulp.task('scripts-libs', function () {
return merge(
return merge( gulp.src(paths.scripts.combine)
.pipe(concat('libs.js', {newLine: ';\n'}))
.pipe(uglify({ mangle: false }))
.pipe(gulp.dest('./assets/js')),
gulp.src(paths.scripts.combine) gulp.src(paths.scripts.ace)
.pipe(concat('libs.js', {newLine: ';\n'})) .pipe(gulp.dest('./assets/js/ace'))
.pipe(uglify({ mangle: false }))
.pipe(gulp.dest("./assets/js")),
gulp.src(paths.scripts.ace) )
.pipe(gulp.dest("./assets/js/ace")) })
);
});
/** /**
* TASK - Combine, make compatible and compress js app scripts * TASK - Combine, make compatible and compress js app scripts
*/ */
gulp.task("scripts-app", function () { gulp.task('scripts-app', function () {
return gulp.src(paths.scripts.compile)
return gulp.src(paths.scripts.compile) .pipe(plumber())
.pipe(plumber()) .pipe(include({ extensions: 'js' }))
.pipe(include({ extensions: "js" })) .pipe(babel())
.pipe(babel()) .pipe(uglify())
.pipe(uglify()) .pipe(plumber.stop())
.pipe(plumber.stop()) .pipe(gulp.dest('./assets/js'))
.pipe(gulp.dest("./assets/js")); })
});
/** /**
* TASK - Process all css processes * TASK - Process all css processes
*/ */
gulp.task("css", ['css-libs', 'css-app']); gulp.task('css', ['css-libs', 'css-app'])
/** /**
* TASK - Combine css libraries * TASK - Combine css libraries
*/ */
gulp.task("css-libs", function () { gulp.task('css-libs', function () {
return gulp.src(paths.css.combine) return gulp.src(paths.css.combine)
.pipe(plumber()) .pipe(plumber())
.pipe(concat('libs.css')) .pipe(concat('libs.css'))
.pipe(cleanCSS({ keepSpecialComments: 0 })) .pipe(cleanCSS({ keepSpecialComments: 0 }))
.pipe(plumber.stop()) .pipe(plumber.stop())
.pipe(gulp.dest("./assets/css")); .pipe(gulp.dest('./assets/css'))
}); })
/** /**
* TASK - Combine app css * TASK - Combine app css
*/ */
gulp.task("css-app", function () { gulp.task('css-app', function () {
return gulp.src(paths.css.compile) return gulp.src(paths.css.compile)
.pipe(plumber()) .pipe(plumber())
.pipe(sass.sync({ includePaths: paths.css.includes })) .pipe(sass.sync({ includePaths: paths.css.includes }))
.pipe(cleanCSS({ keepSpecialComments: 0 })) .pipe(cleanCSS({ keepSpecialComments: 0 }))
.pipe(plumber.stop()) .pipe(plumber.stop())
.pipe(gulp.dest("./assets/css")); .pipe(gulp.dest('./assets/css'))
}); })
/** /**
* TASK - Copy web fonts * TASK - Copy web fonts
*/ */
gulp.task("fonts", function () { gulp.task('fonts', function () {
return gulp.src(paths.fonts) return gulp.src(paths.fonts)
.pipe(gulp.dest("./assets/fonts")); .pipe(gulp.dest('./assets/fonts'))
}); })
/** /**
* TASK - Start dev watchers * TASK - Start dev watchers
*/ */
gulp.task('watch', function() { gulp.task('watch', function () {
return merge( return merge(
watch(paths.scripts.watch, {base: './'}, function() { return gulp.start('scripts-app'); }), watch(paths.scripts.watch, {base: './'}, function () { return gulp.start('scripts-app') }),
watch(paths.css.watch, {base: './'}, function() { return gulp.start('css-app'); }) watch(paths.css.watch, {base: './'}, function () { return gulp.start('css-app') })
); )
}); })
/** /**
* TASK - Starts development server with watchers * TASK - Starts development server with watchers
*/ */
gulp.task('default', ['watch', 'server']); gulp.task('default', ['watch', 'server'])
gulp.task('dev', function() {
paths.css.includes.pop();
paths.css.includes.push('../core');
paths.fonts.pop(); gulp.task('dev', function () {
paths.fonts.push('../core/core-client/fonts/**/*'); paths.css.includes.pop()
paths.css.includes.push('../core')
return run('default'); paths.fonts.pop()
paths.fonts.push('../core/core-client/fonts/**/*')
}); return run('default')
})
/** /**
* TASK - Creates deployment packages * TASK - Creates deployment packages
*/ */
gulp.task('deploy', ['scripts', 'css', 'fonts'], function() { gulp.task('deploy', ['scripts', 'css', 'fonts'], function () {
var zipStream = gulp.src(paths.deploy) var zipStream = gulp.src(paths.deploy)
.pipe(zip('wiki-js.zip')) .pipe(zip('wiki-js.zip'))
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'))
var targzStream = gulp.src(paths.deploy) var targzStream = gulp.src(paths.deploy)
.pipe(tar('wiki-js.tar')) .pipe(tar('wiki-js.tar'))
.pipe(gzip()) .pipe(gzip())
.pipe(gulp.dest('dist')); .pipe(gulp.dest('dist'))
return merge(zipStream, targzStream); return merge(zipStream, targzStream)
}); })
"use strict"; 'use strict'
var Promise = require('bluebird'), const Promise = require('bluebird')
path = require('path'), const path = require('path')
fs = Promise.promisifyAll(require("fs-extra")), const fs = Promise.promisifyAll(require('fs-extra'))
_ = require('lodash'), const _ = require('lodash')
farmhash = require('farmhash'), const farmhash = require('farmhash')
moment = require('moment');
/** /**
* Entries Model * Entries Model
*/ */
module.exports = { module.exports = {
_repoPath: 'repo', _repoPath: 'repo',
_cachePath: 'data/cache', _cachePath: 'data/cache',
/** /**
* Initialize Entries model * Initialize Entries model
* *
* @return {Object} Entries model instance * @return {Object} Entries model instance
*/ */
init() { init () {
let self = this
let self = this;
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo); self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache')
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
return self
return self; },
}, /**
* Check if a document already exists
/** *
* Check if a document already exists * @param {String} entryPath The entry path
* * @return {Promise<Boolean>} True if exists, false otherwise
* @param {String} entryPath The entry path */
* @return {Promise<Boolean>} True if exists, false otherwise exists (entryPath) {
*/ let self = this
exists(entryPath) {
return self.fetchOriginal(entryPath, {
let self = this; parseMarkdown: false,
parseMeta: false,
return self.fetchOriginal(entryPath, { parseTree: false,
parseMarkdown: false, includeMarkdown: false,
parseMeta: false, includeParentInfo: false,
parseTree: false, cache: false
includeMarkdown: false, }).then(() => {
includeParentInfo: false, return true
cache: false }).catch((err) => { // eslint-disable-line handle-callback-err
}).then(() => { return false
return true; })
}).catch((err) => { },
return false;
}); /**
* Fetch a document from cache, otherwise the original
}, *
* @param {String} entryPath The entry path
/** * @return {Promise<Object>} Page Data
* Fetch a document from cache, otherwise the original */
* fetch (entryPath) {
* @param {String} entryPath The entry path let self = this
* @return {Promise<Object>} Page Data
*/ let cpath = self.getCachePath(entryPath)
fetch(entryPath) {
return fs.statAsync(cpath).then((st) => {
let self = this; return st.isFile()
}).catch((err) => { // eslint-disable-line handle-callback-err
let cpath = self.getCachePath(entryPath); return false
}).then((isCache) => {
return fs.statAsync(cpath).then((st) => { if (isCache) {
return st.isFile(); // Load from cache
}).catch((err) => {
return false; return fs.readFileAsync(cpath).then((contents) => {
}).then((isCache) => { return JSON.parse(contents)
}).catch((err) => { // eslint-disable-line handle-callback-err
if(isCache) { winston.error('Corrupted cache file. Deleting it...')
fs.unlinkSync(cpath)
// Load from cache return false
})
return fs.readFileAsync(cpath).then((contents) => { } else {
return JSON.parse(contents); // Load original
}).catch((err) => {
winston.error('Corrupted cache file. Deleting it...'); return self.fetchOriginal(entryPath)
fs.unlinkSync(cpath); }
return false; })
}); },
} else { /**
* Fetches the original document entry
// Load original *
* @param {String} entryPath The entry path
return self.fetchOriginal(entryPath); * @param {Object} options The options
* @return {Promise<Object>} Page data
} */
fetchOriginal (entryPath, options) {
}); let self = this
}, let fpath = self.getFullPath(entryPath)
let cpath = self.getCachePath(entryPath)
/**
* Fetches the original document entry options = _.defaults(options, {
* parseMarkdown: true,
* @param {String} entryPath The entry path parseMeta: true,
* @param {Object} options The options parseTree: true,
* @return {Promise<Object>} Page data includeMarkdown: false,
*/ includeParentInfo: true,
fetchOriginal(entryPath, options) { cache: true
})
let self = this;
return fs.statAsync(fpath).then((st) => {
let fpath = self.getFullPath(entryPath); if (st.isFile()) {
let cpath = self.getCachePath(entryPath); return fs.readFileAsync(fpath, 'utf8').then((contents) => {
// Parse contents
options = _.defaults(options, {
parseMarkdown: true, let pageData = {
parseMeta: true, markdown: (options.includeMarkdown) ? contents : '',
parseTree: true, html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
includeMarkdown: false, meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
includeParentInfo: true, tree: (options.parseTree) ? mark.parseTree(contents) : []
cache: true }
});
if (!pageData.meta.title) {
return fs.statAsync(fpath).then((st) => { pageData.meta.title = _.startCase(entryPath)
if(st.isFile()) { }
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
pageData.meta.path = entryPath
// Parse contents
// Get parent
let pageData = {
markdown: (options.includeMarkdown) ? contents : '', let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
html: (options.parseMarkdown) ? mark.parseContent(contents) : '', return (pageData.parent = parentData)
meta: (options.parseMeta) ? mark.parseMeta(contents) : {}, }).catch((err) => { // eslint-disable-line handle-callback-err
tree: (options.parseTree) ? mark.parseTree(contents) : [] return (pageData.parent = false)
}; }) : Promise.resolve(true)
if(!pageData.meta.title) { return parentPromise.then(() => {
pageData.meta.title = _.startCase(entryPath); // Cache to disk
}
if (options.cache) {
pageData.meta.path = entryPath; let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
// Get parent winston.error('Unable to write to cache! Performance may be affected.')
winston.error(err)
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => { return true
return (pageData.parent = parentData); })
}).catch((err) => { } else {
return (pageData.parent = false); return true
}) : Promise.resolve(true); }
}).return(pageData)
return parentPromise.then(() => { })
} else {
// Cache to disk return false
}
if(options.cache) { }).catch((err) => { // eslint-disable-line handle-callback-err
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false); return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'))
return fs.writeFileAsync(cpath, cacheData).catch((err) => { })
winston.error('Unable to write to cache! Performance may be affected.'); },
return true;
}); /**
} else { * Parse raw url path and make it safe
return true; *
} * @param {String} urlPath The url path
* @return {String} Safe entry path
}).return(pageData); */
parsePath (urlPath) {
}); let wlist = new RegExp('[^a-z0-9/-]', 'g')
} else {
return false; urlPath = _.toLower(urlPath).replace(wlist, '')
}
}).catch((err) => { if (urlPath === '/') {
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!')); urlPath = 'home'
}); }
}, let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p) })
/** return _.join(urlParts, '/')
* Parse raw url path and make it safe },
*
* @param {String} urlPath The url path /**
* @return {String} Safe entry path * Gets the parent information.
*/ *
parsePath(urlPath) { * @param {String} entryPath The entry path
* @return {Promise<Object|False>} The parent information.
let wlist = new RegExp('[^a-z0-9/\-]','g'); */
getParentInfo (entryPath) {
urlPath = _.toLower(urlPath).replace(wlist, ''); let self = this
if(urlPath === '/') { if (_.includes(entryPath, '/')) {
urlPath = 'home'; let parentParts = _.initial(_.split(entryPath, '/'))
} let parentPath = _.join(parentParts, '/')
let parentFile = _.last(parentParts)
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p); }); let fpath = self.getFullPath(parentPath)
return _.join(urlParts, '/'); return fs.statAsync(fpath).then((st) => {
if (st.isFile()) {
}, return fs.readFileAsync(fpath, 'utf8').then((contents) => {
let pageMeta = mark.parseMeta(contents)
/**
* Gets the parent information. return {
* path: parentPath,
* @param {String} entryPath The entry path title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
* @return {Promise<Object|False>} The parent information. subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
*/ }
getParentInfo(entryPath) { })
} else {
let self = this; return Promise.reject(new Error('Parent entry is not a valid file.'))
}
if(_.includes(entryPath, '/')) { })
} else {
let parentParts = _.initial(_.split(entryPath, '/')); return Promise.reject(new Error('Parent entry is root.'))
let parentPath = _.join(parentParts,'/'); }
let parentFile = _.last(parentParts); },
let fpath = self.getFullPath(parentPath);
/**
return fs.statAsync(fpath).then((st) => { * Gets the full original path of a document.
if(st.isFile()) { *
return fs.readFileAsync(fpath, 'utf8').then((contents) => { * @param {String} entryPath The entry path
* @return {String} The full path.
let pageMeta = mark.parseMeta(contents); */
getFullPath (entryPath) {
return { return path.join(this._repoPath, entryPath + '.md')
path: parentPath, },
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false /**
}; * Gets the full cache path of a document.
*
}); * @param {String} entryPath The entry path
} else { * @return {String} The full cache path.
return Promise.reject(new Error('Parent entry is not a valid file.')); */
} getCachePath (entryPath) {
}); return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json')
},
} else {
return Promise.reject(new Error('Parent entry is root.')); /**
} * Gets the entry path from full path.
*
}, * @param {String} fullPath The full path
* @return {String} The entry path
/** */
* Gets the full original path of a document. getEntryPathFromFullPath (fullPath) {
* let absRepoPath = path.resolve(ROOTPATH, this._repoPath)
* @param {String} entryPath The entry path return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'), '/').value()
* @return {String} The full path. },
*/
getFullPath(entryPath) { /**
return path.join(this._repoPath, entryPath + '.md'); * Update an existing document
}, *
* @param {String} entryPath The entry path
/** * @param {String} contents The markdown-formatted contents
* Gets the full cache path of a document. * @return {Promise<Boolean>} True on success, false on failure
* */
* @param {String} entryPath The entry path update (entryPath, contents) {
* @return {String} The full cache path. let self = this
*/ let fpath = self.getFullPath(entryPath)
getCachePath(entryPath) {
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json'); return fs.statAsync(fpath).then((st) => {
}, if (st.isFile()) {
return self.makePersistent(entryPath, contents).then(() => {
/** return self.updateCache(entryPath)
* Gets the entry path from full path. })
* } else {
* @param {String} fullPath The full path return Promise.reject(new Error('Entry does not exist!'))
* @return {String} The entry path }
*/ }).catch((err) => {
getEntryPathFromFullPath(fullPath) { winston.error(err)
let absRepoPath = path.resolve(ROOTPATH, this._repoPath); return Promise.reject(new Error('Failed to save document.'))
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'),'/').value(); })
}, },
/** /**
* Update an existing document * Update local cache and search index
* *
* @param {String} entryPath The entry path * @param {String} entryPath The entry path
* @param {String} contents The markdown-formatted contents * @return {Promise} Promise of the operation
* @return {Promise<Boolean>} True on success, false on failure */
*/ updateCache (entryPath) {
update(entryPath, contents) { let self = this
let self = this; return self.fetchOriginal(entryPath, {
let fpath = self.getFullPath(entryPath); parseMarkdown: true,
parseMeta: true,
return fs.statAsync(fpath).then((st) => { parseTree: true,
if(st.isFile()) { includeMarkdown: true,
return self.makePersistent(entryPath, contents).then(() => { includeParentInfo: true,
return self.updateCache(entryPath); cache: true
}); }).then((pageData) => {
} else { return {
return Promise.reject(new Error('Entry does not exist!')); entryPath,
} meta: pageData.meta,
}).catch((err) => { parent: pageData.parent || {},
winston.error(err); text: mark.removeMarkdown(pageData.markdown)
return Promise.reject(new Error('Failed to save document.')); }
}); }).then((content) => {
return db.Entry.findOneAndUpdate({
}, _id: content.entryPath
}, {
/** _id: content.entryPath,
* Update local cache and search index title: content.meta.title || content.entryPath,
* subtitle: content.meta.subtitle || '',
* @param {String} entryPath The entry path parent: content.parent.title || '',
* @return {Promise} Promise of the operation content: content.text || ''
*/ }, {
updateCache(entryPath) { new: true,
upsert: true
let self = this; })
})
return self.fetchOriginal(entryPath, { },
parseMarkdown: true,
parseMeta: true, /**
parseTree: true, * Create a new document
includeMarkdown: true, *
includeParentInfo: true, * @param {String} entryPath The entry path
cache: true * @param {String} contents The markdown-formatted contents
}).then((pageData) => { * @return {Promise<Boolean>} True on success, false on failure
return { */
entryPath, create (entryPath, contents) {
meta: pageData.meta, let self = this
parent: pageData.parent || {},
text: mark.removeMarkdown(pageData.markdown) return self.exists(entryPath).then((docExists) => {
}; if (!docExists) {
}).then((content) => { return self.makePersistent(entryPath, contents).then(() => {
return db.Entry.findOneAndUpdate({ return self.updateCache(entryPath)
_id: content.entryPath })
}, { } else {
_id: content.entryPath, return Promise.reject(new Error('Entry already exists!'))
title: content.meta.title || content.entryPath, }
subtitle: content.meta.subtitle || '', }).catch((err) => {
parent: content.parent.title || '', winston.error(err)
content: content.text || '' return Promise.reject(new Error('Something went wrong.'))
}, { })
new: true, },
upsert: true
}); /**
}); * Makes a document persistent to disk and git repository
*
}, * @param {String} entryPath The entry path
* @param {String} contents The markdown-formatted contents
/** * @return {Promise<Boolean>} True on success, false on failure
* Create a new document */
* makePersistent (entryPath, contents) {
* @param {String} entryPath The entry path let self = this
* @param {String} contents The markdown-formatted contents let fpath = self.getFullPath(entryPath)
* @return {Promise<Boolean>} True on success, false on failure
*/ return fs.outputFileAsync(fpath, contents).then(() => {
create(entryPath, contents) { return git.commitDocument(entryPath)
})
let self = this; },
return self.exists(entryPath).then((docExists) => { /**
if(!docExists) { * Move a document
return self.makePersistent(entryPath, contents).then(() => { *
return self.updateCache(entryPath); * @param {String} entryPath The current entry path
}); * @param {String} newEntryPath The new entry path
} else { * @return {Promise} Promise of the operation
return Promise.reject(new Error('Entry already exists!')); */
} move (entryPath, newEntryPath) {
}).catch((err) => { let self = this
winston.error(err);
return Promise.reject(new Error('Something went wrong.')); if (_.isEmpty(entryPath) || entryPath === 'home') {
}); return Promise.reject(new Error('Invalid path!'))
}
},
return git.moveDocument(entryPath, newEntryPath).then(() => {
/** return git.commitDocument(newEntryPath).then(() => {
* Makes a document persistent to disk and git repository // Delete old cache version
*
* @param {String} entryPath The entry path let oldEntryCachePath = self.getCachePath(entryPath)
* @param {String} contents The markdown-formatted contents fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
* @return {Promise<Boolean>} True on success, false on failure
*/ // Delete old index entry
makePersistent(entryPath, contents) {
ws.emit('searchDel', {
let self = this; auth: WSInternalKey,
let fpath = self.getFullPath(entryPath); entryPath
})
return fs.outputFileAsync(fpath, contents).then(() => {
return git.commitDocument(entryPath); // Create cache for new entry
});
return self.updateCache(newEntryPath)
}, })
})
/** },
* Move a document
* /**
* @param {String} entryPath The current entry path * Generate a starter page content based on the entry path
* @param {String} newEntryPath The new entry path *
* @return {Promise} Promise of the operation * @param {String} entryPath The entry path
*/ * @return {Promise<String>} Starter content
move(entryPath, newEntryPath) { */
getStarter (entryPath) {
let self = this; let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
if(_.isEmpty(entryPath) || entryPath === 'home') { return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
return Promise.reject(new Error('Invalid path!')); return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
} })
},
return git.moveDocument(entryPath, newEntryPath).then(() => {
return git.commitDocument(newEntryPath).then(() => { /**
* Searches entries based on terms.
// Delete old cache version *
* @param {String} terms The terms to search for
let oldEntryCachePath = self.getCachePath(entryPath); * @return {Promise<Object>} Promise of the search results
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true; }); */
search (terms) {
// Delete old index entry terms = _.chain(terms)
.deburr()
ws.emit('searchDel', { .toLower()
auth: WSInternalKey, .trim()
entryPath .replace(/[^a-z0-9\- ]/g, '')
}); .split(' ')
.filter((f) => { return !_.isEmpty(f) })
// Create cache for new entry .join(' ')
.value()
return self.updateCache(newEntryPath);
return db.Entry.find(
}); { $text: { $search: terms } },
}); { score: { $meta: 'textScore' }, title: 1 }
)
}, .sort({ score: { $meta: 'textScore' } })
.limit(10)
/** .exec()
* Generate a starter page content based on the entry path .then((hits) => {
* if (hits.length < 5) {
* @param {String} entryPath The entry path let regMatch = new RegExp('^' + _.split(terms, ' ')[0])
* @return {Promise<String>} Starter content return db.Entry.find({
*/ _id: { $regex: regMatch }
getStarter(entryPath) { }, '_id')
.sort('_id')
let self = this; .limit(5)
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/'))); .exec()
.then((matches) => {
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => { return {
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle); match: hits,
}); suggest: (matches) ? _.map(matches, '_id') : []
}
}, })
} else {
/** return {
* Searches entries based on terms. match: _.filter(hits, (h) => { return h._doc.score >= 1 }),
* suggest: []
* @param {String} terms The terms to search for }
* @return {Promise<Object>} Promise of the search results }
*/ }).catch((err) => {
search(terms) { winston.error(err)
return {
let self = this; match: [],
terms = _.chain(terms) suggest: []
.deburr() }
.toLower() })
.trim() }
.replace(/[^a-z0-9\- ]/g, '')
.split(' ') }
.filter((f) => { return !_.isEmpty(f); })
.join(' ')
.value();
return db.Entry.find(
{ $text: { $search: terms } },
{ score: { $meta: "textScore" }, title: 1 }
)
.sort({ score: { $meta: "textScore" } })
.limit(10)
.exec()
.then((hits) => {
if(hits.length < 5) {
let regMatch = new RegExp('^' + _.split(terms, ' ')[0]);
return db.Entry.find({
_id: { $regex: regMatch }
}, '_id')
.sort('_id')
.limit(5)
.exec()
.then((matches) => {
return {
match: hits,
suggest: (matches) ? _.map(matches, '_id') : []
};
});
} else {
return {
match: _.filter(hits, (h) => { return h._doc.score >= 1; }),
suggest: []
};
}
}).catch((err) => {
winston.error(err);
return {
match: [],
suggest: []
};
});
}
};
\ No newline at end of file
"use strict"; 'use strict'
var Git = require("git-wrapper2-promise"), const Git = require('git-wrapper2-promise')
Promise = require('bluebird'), const Promise = require('bluebird')
path = require('path'), const path = require('path')
os = require('os'), const fs = Promise.promisifyAll(require('fs'))
fs = Promise.promisifyAll(require("fs")), const _ = require('lodash')
moment = require('moment'), const URL = require('url')
_ = require('lodash'),
URL = require('url');
/** /**
* Git Model * Git Model
*/ */
module.exports = { module.exports = {
_git: null, _git: null,
_url: '', _url: '',
_repo: { _repo: {
path: '', path: '',
branch: 'master', branch: 'master',
exists: false exists: false
}, },
_signature: { _signature: {
name: 'Wiki', name: 'Wiki',
email: 'user@example.com' email: 'user@example.com'
}, },
_opts: { _opts: {
clone: {}, clone: {},
push: {} push: {}
}, },
onReady: null, onReady: null,
/** /**
* Initialize Git model * Initialize Git model
* *
* @return {Object} Git model instance * @return {Object} Git model instance
*/ */
init() { init () {
let self = this
let self = this;
// -> Build repository path
//-> Build repository path
if (_.isEmpty(appconfig.paths.repo)) {
if(_.isEmpty(appconfig.paths.repo)) { self._repo.path = path.join(ROOTPATH, 'repo')
self._repo.path = path.join(ROOTPATH, 'repo'); } else {
} else { self._repo.path = appconfig.paths.repo
self._repo.path = appconfig.paths.repo; }
}
// -> Initialize repository
//-> Initialize repository
self.onReady = self._initRepo(appconfig)
self.onReady = self._initRepo(appconfig);
// Define signature
// Define signature
self._signature.name = appconfig.git.signature.name || 'Wiki'
self._signature.name = appconfig.git.signature.name || 'Wiki'; self._signature.email = appconfig.git.signature.email || 'user@example.com'
self._signature.email = appconfig.git.signature.email || 'user@example.com';
return self
return self; },
}, /**
* Initialize Git repository
/** *
* Initialize Git repository * @param {Object} appconfig The application config
* * @return {Object} Promise
* @param {Object} appconfig The application config */
* @return {Object} Promise _initRepo (appconfig) {
*/ let self = this
_initRepo(appconfig) {
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
let self = this;
// -> Check if path is accessible
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
return fs.mkdirAsync(self._repo.path).catch((err) => {
//-> Check if path is accessible if (err.code !== 'EEXIST') {
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.')
return fs.mkdirAsync(self._repo.path).catch((err) => { }
if(err.code !== 'EEXIST') { }).then(() => {
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.'); self._git = new Git({ 'git-dir': self._repo.path })
}
}).then(() => { // -> Check if path already contains a git working folder
self._git = new Git({ 'git-dir': self._repo.path }); return self._git.isRepo().then((isRepo) => {
self._repo.exists = isRepo
//-> Check if path already contains a git working folder return (!isRepo) ? self._git.exec('init') : true
}).catch((err) => { // eslint-disable-line handle-callback-err
return self._git.isRepo().then((isRepo) => { self._repo.exists = false
self._repo.exists = isRepo; })
return (!isRepo) ? self._git.exec('init') : true; }).then(() => {
}).catch((err) => { // Initialize remote
self._repo.exists = false;
}); let urlObj = URL.parse(appconfig.git.url)
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
}).then(() => { self._url = URL.format(urlObj)
// Initialize remote return self._git.exec('remote', 'show').then((cProc) => {
let out = cProc.stdout.toString()
let urlObj = URL.parse(appconfig.git.url); if (_.includes(out, 'origin')) {
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : ''); return true
self._url = URL.format(urlObj); } else {
return Promise.join(
return self._git.exec('remote', 'show').then((cProc) => { self._git.exec('config', ['--local', 'user.name', self._signature.name]),
let out = cProc.stdout.toString(); self._git.exec('config', ['--local', 'user.email', self._signature.email])
if(_.includes(out, 'origin')) { ).then(() => {
return true; return self._git.exec('remote', ['add', 'origin', self._url])
} else { })
return Promise.join( }
self._git.exec('config', ['--local', 'user.name', self._signature.name]), })
self._git.exec('config', ['--local', 'user.email', self._signature.email]) }).catch((err) => {
).then(() => { winston.error('[' + PROCNAME + '][GIT] Git remote error!')
return self._git.exec('remote', ['add', 'origin', self._url]); throw err
}); }).then(() => {
} winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
}); return true
})
}).catch((err) => { },
winston.error('[' + PROCNAME + '][GIT] Git remote error!');
throw err; /**
}).then(() => { * Gets the repo path.
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.'); *
return true; * @return {String} The repo path.
}); */
getRepoPath () {
}, return this._repo.path || path.join(ROOTPATH, 'repo')
},
/**
* Gets the repo path. /**
* * Sync with the remote repository
* @return {String} The repo path. *
*/ * @return {Promise} Resolve on sync success
getRepoPath() { */
resync () {
return this._repo.path || path.join(ROOTPATH, 'repo'); let self = this
}, // Fetch
/** winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
* Sync with the remote repository return self._git.pull('origin', self._repo.branch).then((cProc) => {
* winston.info('[' + PROCNAME + '][GIT] Pull completed.')
* @return {Promise} Resolve on sync success })
*/ .catch((err) => {
resync() { winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
throw err
let self = this; })
.then(() => {
// Fetch // Check for changes
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...'); return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
return self._git.pull('origin', self._repo.branch).then((cProc) => { let out = cProc.stdout.toString()
winston.info('[' + PROCNAME + '][GIT] Pull completed.');
}) if (_.includes(out, 'commit')) {
.catch((err) => { winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!'); return self._git.push('origin', self._repo.branch).then(() => {
throw err; return winston.info('[' + PROCNAME + '][GIT] Push completed.')
}) })
.then(() => { } else {
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
// Check for changes }
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => { return true
let out = cProc.stdout.toString(); })
})
if(_.includes(out, 'commit')) { .catch((err) => {
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...'); throw err
return self._git.push('origin', self._repo.branch).then(() => { })
return winston.info('[' + PROCNAME + '][GIT] Push completed.'); },
});
/**
} else { * Commits a document.
*
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.'); * @param {String} entryPath The entry path
* @return {Promise} Resolve on commit success
} */
commitDocument (entryPath) {
return true; let self = this
let gitFilePath = entryPath + '.md'
}); let commitMsg = ''
}) return self._git.exec('ls-files', gitFilePath).then((cProc) => {
.catch((err) => { let out = cProc.stdout.toString()
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!'); return _.includes(out, gitFilePath)
throw err; }).then((isTracked) => {
}); 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 }
* Commits a document. })
* })
* @param {String} entryPath The entry path },
* @return {Promise} Resolve on commit success
*/ /**
commitDocument(entryPath) { * Move a document.
*
let self = this; * @param {String} entryPath The current entry path
let gitFilePath = entryPath + '.md'; * @param {String} newEntryPath The new entry path
let commitMsg = ''; * @return {Promise<Boolean>} Resolve on success
*/
return self._git.exec('ls-files', gitFilePath).then((cProc) => { moveDocument (entryPath, newEntryPath) {
let out = cProc.stdout.toString(); let self = this
return _.includes(out, gitFilePath); let gitFilePath = entryPath + '.md'
}).then((isTracked) => { let gitNewFilePath = newEntryPath + '.md'
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
return self._git.add(gitFilePath); return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
}).then(() => { let out = cProc.stdout.toString()
return self._git.commit(commitMsg).catch((err) => { if (_.includes(out, 'fatal')) {
if(_.includes(err.stdout, 'nothing to commit')) { return true; } let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
}); throw new Error(errorMsg)
}); }
return true
}, })
},
/**
* Move a document. /**
* * Commits uploads changes.
* @param {String} entryPath The current entry path *
* @param {String} newEntryPath The new entry path * @param {String} msg The commit message
* @return {Promise<Boolean>} Resolve on success * @return {Promise} Resolve on commit success
*/ */
moveDocument(entryPath, newEntryPath) { commitUploads (msg) {
let self = this
let self = this; msg = msg || 'Uploads repository sync'
let gitFilePath = entryPath + '.md';
let gitNewFilePath = newEntryPath + '.md'; return self._git.add('uploads').then(() => {
return self._git.commit(msg).catch((err) => {
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => { if (_.includes(err.stdout, 'nothing to commit')) { return true }
let out = cProc.stdout.toString(); })
if(_.includes(out, 'fatal')) { })
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ','))); }
throw new Error(errorMsg);
} }
return true;
});
},
/**
* Commits uploads changes.
*
* @param {String} msg The commit message
* @return {Promise} Resolve on commit success
*/
commitUploads(msg) {
let self = this;
msg = msg || "Uploads repository sync";
return self._git.add('uploads').then(() => {
return self._git.commit(msg).catch((err) => {
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
});
});
}
};
\ No newline at end of file
"use strict"; 'use strict'
const crypto = require('crypto'); const crypto = require('crypto')
/** /**
* Internal Authentication * Internal Authentication
*/ */
module.exports = { module.exports = {
_curKey: false, _curKey: false,
init(inKey) { init (inKey) {
this._curKey = inKey
this._curKey = inKey; return this
},
return this; generateKey () {
return crypto.randomBytes(20).toString('hex')
},
}, validateKey (inKey) {
return inKey === this._curKey
}
generateKey() { }
return crypto.randomBytes(20).toString('hex');
},
validateKey(inKey) {
return inKey === this._curKey;
}
};
\ No newline at end of file
"use strict"; 'use strict'
var path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
multer = require('multer'), const multer = require('multer')
os = require('os'), const os = require('os')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Local Data Storage * Local Data Storage
*/ */
module.exports = { module.exports = {
_uploadsPath: './repo/uploads', _uploadsPath: './repo/uploads',
_uploadsThumbsPath: './data/thumbs', _uploadsThumbsPath: './data/thumbs',
uploadImgHandler: null, uploadImgHandler: null,
/** /**
* Initialize Local Data Storage model * Initialize Local Data Storage model
* *
* @return {Object} Local Data Storage model instance * @return {Object} Local Data Storage model instance
*/ */
init() { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
this.createBaseDirectories(appconfig)
this.createBaseDirectories(appconfig); this.initMulter(appconfig)
this.initMulter(appconfig);
return this
return this; },
}, /**
* Init Multer upload handlers
/** *
* Init Multer upload handlers * @param {Object} appconfig The application config
* * @return {boolean} Void
* @param {Object} appconfig The application config */
* @return {boolean} Void initMulter (appconfig) {
*/ let maxFileSizes = {
initMulter(appconfig) { img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
let maxFileSizes = { }
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024 // -> IMAGES
};
this.uploadImgHandler = multer({
//-> IMAGES storage: multer.diskStorage({
destination: (req, f, cb) => {
this.uploadImgHandler = multer({ cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
storage: multer.diskStorage({ }
destination: (req, f, cb) => { }),
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); fileFilter: (req, f, cb) => {
} // -> Check filesize
}),
fileFilter: (req, f, cb) => { if (f.size > maxFileSizes.img) {
return cb(null, false)
//-> Check filesize }
if(f.size > maxFileSizes.img) { // -> Check MIME type (quick check only)
return cb(null, false);
} if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
return cb(null, false)
//-> Check MIME type (quick check only) }
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) { cb(null, true)
return cb(null, false); }
} }).array('imgfile', 20)
cb(null, true); // -> FILES
}
}).array('imgfile', 20); this.uploadFileHandler = multer({
storage: multer.diskStorage({
//-> FILES destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
this.uploadFileHandler = multer({ }
storage: multer.diskStorage({ }),
destination: (req, f, cb) => { fileFilter: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')); // -> Check filesize
}
}), if (f.size > maxFileSizes.file) {
fileFilter: (req, f, cb) => { return cb(null, false)
}
//-> Check filesize
cb(null, true)
if(f.size > maxFileSizes.file) { }
return cb(null, false); }).array('binfile', 20)
}
return true
cb(null, true); },
}
}).array('binfile', 20); /**
* Creates a base directories (Synchronous).
return true; *
* @param {Object} appconfig The application config
}, * @return {Void} Void
*/
/** createBaseDirectories (appconfig) {
* Creates a base directories (Synchronous). winston.info('[SERVER] Checking data directories...')
*
* @param {Object} appconfig The application config try {
* @return {Void} Void fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
*/ fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
createBaseDirectories(appconfig) { fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
winston.info('[SERVER] Checking data directories...');
if (os.type() !== 'Windows_NT') {
try { fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
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.repo))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload')); fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
if(os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644'); fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
} }
} catch (err) {
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo)); winston.error(err)
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads')); }
if(os.type() !== 'Windows_NT') { winston.info('[SERVER] Data and Repository directories are OK.')
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
} return
},
} catch (err) {
winston.error(err); /**
} * Gets the uploads path.
*
winston.info('[SERVER] Data and Repository directories are OK.'); * @return {String} The uploads path.
*/
return; getUploadsPath () {
return this._uploadsPath
}, },
/** /**
* Gets the uploads path. * Gets the thumbnails folder path.
* *
* @return {String} The uploads path. * @return {String} The thumbs path.
*/ */
getUploadsPath() { getThumbsPath () {
return this._uploadsPath; return this._uploadsThumbsPath
}, },
/** /**
* Gets the thumbnails folder path. * Check if filename is valid and unique
* *
* @return {String} The thumbs path. * @param {String} f The filename
*/ * @param {String} fld The containing folder
getThumbsPath() { * @param {boolean} isImage Indicates if image
return this._uploadsThumbsPath; * @return {Promise<String>} Promise of the accepted filename
}, */
validateUploadsFilename (f, fld, isImage) {
/** let fObj = path.parse(f)
* Check if filename is valid and unique let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
* let fext = _.toLower(fObj.ext)
* @param {String} f The filename
* @param {String} fld The containing folder if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
* @param {boolean} isImage Indicates if image fext = '.png'
* @return {Promise<String>} Promise of the accepted filename }
*/
validateUploadsFilename(f, fld, isImage) { f = fname + fext
let fpath = path.resolve(this._uploadsPath, fld, f)
let fObj = path.parse(f);
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, ''); return fs.statAsync(fpath).then((s) => {
let fext = _.toLower(fObj.ext); throw new Error('File ' + f + ' already exists.')
}).catch((err) => {
if(isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) { if (err.code === 'ENOENT') {
fext = '.png'; return f
} }
throw err
f = fname + fext; })
let fpath = path.resolve(this._uploadsPath, fld, f); }
return fs.statAsync(fpath).then((s) => { }
throw new Error('File ' + f + ' already exists.');
}).catch((err) => {
if(err.code === 'ENOENT') {
return f;
}
throw err;
});
},
};
\ No newline at end of file
"use strict"; 'use strict'
var Promise = require('bluebird'), const md = require('markdown-it')
md = require('markdown-it'), const mdEmoji = require('markdown-it-emoji')
mdEmoji = require('markdown-it-emoji'), const mdTaskLists = require('markdown-it-task-lists')
mdTaskLists = require('markdown-it-task-lists'), const mdAbbr = require('markdown-it-abbr')
mdAbbr = require('markdown-it-abbr'), const mdAnchor = require('markdown-it-anchor')
mdAnchor = require('markdown-it-anchor'), const mdFootnote = require('markdown-it-footnote')
mdFootnote = require('markdown-it-footnote'), const mdExternalLinks = require('markdown-it-external-links')
mdExternalLinks = require('markdown-it-external-links'), const mdExpandTabs = require('markdown-it-expand-tabs')
mdExpandTabs = require('markdown-it-expand-tabs'), const mdAttrs = require('markdown-it-attrs')
mdAttrs = require('markdown-it-attrs'), const hljs = require('highlight.js')
hljs = require('highlight.js'), const cheerio = require('cheerio')
cheerio = require('cheerio'), const _ = require('lodash')
_ = require('lodash'), const mdRemove = require('remove-markdown')
mdRemove = require('remove-markdown');
// Load plugins // Load plugins
var mkdown = md({ var mkdown = md({
html: true, html: true,
linkify: true, linkify: true,
typography: true, typography: true,
highlight(str, lang) { highlight (str, lang) {
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'; return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
} catch (err) { } catch (err) {
return '<pre><code>' + str + '</code></pre>'; return '<pre><code>' + str + '</code></pre>'
} }
} }
return '<pre><code>' + str + '</code></pre>'; return '<pre><code>' + str + '</code></pre>'
} }
}) })
.use(mdEmoji) .use(mdEmoji)
.use(mdTaskLists) .use(mdTaskLists)
.use(mdAbbr) .use(mdAbbr)
.use(mdAnchor, { .use(mdAnchor, {
slugify: _.kebabCase, slugify: _.kebabCase,
permalink: true, permalink: true,
permalinkClass: 'toc-anchor', permalinkClass: 'toc-anchor',
permalinkSymbol: '#', permalinkSymbol: '#',
permalinkBefore: true permalinkBefore: true
}) })
.use(mdFootnote) .use(mdFootnote)
.use(mdExternalLinks, { .use(mdExternalLinks, {
externalClassName: 'external-link', externalClassName: 'external-link',
internalClassName: 'internal-link' internalClassName: 'internal-link'
}) })
.use(mdExpandTabs, { .use(mdExpandTabs, {
tabWidth: 4 tabWidth: 4
}) })
.use(mdAttrs); .use(mdAttrs)
// Rendering rules // Rendering rules
mkdown.renderer.rules.emoji = function(token, idx) { mkdown.renderer.rules.emoji = function (token, idx) {
return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'; return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'
}; }
// Video rules // Video rules
const videoRules = [ const videoRules = [
{ {
selector: 'a.youtube', selector: 'a.youtube',
regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'), regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'),
output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>' output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
}, },
{ {
selector: 'a.vimeo', selector: 'a.vimeo',
regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>' output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
}, },
{ {
selector: 'a.dailymotion', selector: 'a.dailymotion',
regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i'), regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i'),
output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>' output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
}, },
{ {
selector: 'a.video', selector: 'a.video',
regexp: false, regexp: false,
output: '<video width="640" height="360" controls preload="metadata"><source src="{0}" type="video/mp4"></video>' output: '<video width="640" height="360" controls preload="metadata"><source src="{0}" type="video/mp4"></video>'
} }
] ]
/** /**
...@@ -90,81 +89,79 @@ const videoRules = [ ...@@ -90,81 +89,79 @@ const videoRules = [
* @return {Array} TOC tree * @return {Array} TOC tree
*/ */
const parseTree = (content) => { const parseTree = (content) => {
let tokens = md().parse(content, {})
let tokens = md().parse(content, {}); let tocArray = []
let tocArray = [];
// -> Extract headings and their respective levels
//-> Extract headings and their respective levels
for (let i = 0; i < tokens.length; i++) {
for (let i = 0; i < tokens.length; i++) { if (tokens[i].type !== 'heading_close') {
if (tokens[i].type !== "heading_close") { continue
continue; }
}
const heading = tokens[i - 1]
const heading = tokens[i - 1]; const headingclose = tokens[i]
const heading_close = tokens[i];
if (heading.type === 'inline') {
if (heading.type === "inline") { let content = ''
let content = ""; let anchor = ''
let anchor = ""; if (heading.children && heading.children[0].type === 'link_open') {
if (heading.children && heading.children[0].type === "link_open") { content = heading.children[1].content
content = heading.children[1].content; anchor = _.kebabCase(content)
anchor = _.kebabCase(content); } else {
} else { content = heading.content
content = heading.content; anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ''))
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, "")); }
}
tocArray.push({
tocArray.push({ content,
content, anchor,
anchor, level: +headingclose.tag.substr(1, 1)
level: +heading_close.tag.substr(1, 1) })
}); }
} }
}
// -> Exclude levels deeper than 2
//-> Exclude levels deeper than 2
_.remove(tocArray, (n) => { return n.level > 2 })
_.remove(tocArray, (n) => { return n.level > 2; });
// -> Build tree from flat array
//-> Build tree from flat array
return _.reduce(tocArray, (tree, v) => {
return _.reduce(tocArray, (tree, v) => { let treeLength = tree.length - 1
let treeLength = tree.length - 1; if (v.level < 2) {
if(v.level < 2) { tree.push({
tree.push({ content: v.content,
content: v.content, anchor: v.anchor,
anchor: v.anchor, nodes: []
nodes: [] })
}); } else {
} else { let lastNodeLevel = 1
let lastNodeLevel = 1; let GetNodePath = (startPos) => {
let GetNodePath = (startPos) => { lastNodeLevel++
lastNodeLevel++; if (_.isEmpty(startPos)) {
if(_.isEmpty(startPos)) { startPos = 'nodes'
startPos = 'nodes'; }
} if (lastNodeLevel === v.level) {
if(lastNodeLevel === v.level) { return startPos
return startPos; } else {
} else { return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes')
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes'); }
} }
}; let lastNodePath = GetNodePath()
let lastNodePath = GetNodePath(); let lastNode = _.get(tree[treeLength], lastNodePath)
let lastNode = _.get(tree[treeLength], lastNodePath); if (lastNode) {
if(lastNode) { lastNode.push({
lastNode.push({ content: v.content,
content: v.content, anchor: v.anchor,
anchor: v.anchor, nodes: []
nodes: [] })
}); _.set(tree[treeLength], lastNodePath, lastNode)
_.set(tree[treeLength], lastNodePath, lastNode); }
} }
} return tree
return tree; }, [])
}, []); }
};
/** /**
* Parse markdown content to HTML * Parse markdown content to HTML
...@@ -172,87 +169,85 @@ const parseTree = (content) => { ...@@ -172,87 +169,85 @@ const parseTree = (content) => {
* @param {String} content Markdown content * @param {String} content Markdown content
* @return {String} HTML formatted content * @return {String} HTML formatted content
*/ */
const parseContent = (content) => { const parseContent = (content) => {
let output = mkdown.render(content)
let output = mkdown.render(content); let cr = cheerio.load(output)
let cr = cheerio.load(output);
// -> Check for empty first element
//-> Check for empty first element
let firstElm = cr.root().children().first()[0]
let firstElm = cr.root().children().first()[0]; if (firstElm.type === 'tag' && firstElm.name === 'p') {
if(firstElm.type === 'tag' && firstElm.name === 'p') { let firstElmChildren = firstElm.children
let firstElmChildren = firstElm.children; if (firstElmChildren.length < 1) {
if(firstElmChildren.length < 1) { firstElm.remove()
firstElm.remove(); } else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
} else if(firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') { cr(firstElm).addClass('is-gapless')
cr(firstElm).addClass('is-gapless'); }
} }
}
// -> Remove links in headers
//-> Remove links in headers
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => { let txtLink = cr(elm).text()
let txtLink = cr(elm).text(); cr(elm).replaceWith(txtLink)
cr(elm).replaceWith(txtLink); })
});
// -> Re-attach blockquote styling classes to their parents
//-> Re-attach blockquote styling classes to their parents
cr.root().children('blockquote').each((i, elm) => {
cr.root().children('blockquote').each((i, elm) => { if (cr(elm).children().length > 0) {
if(cr(elm).children().length > 0) { let bqLastChild = cr(elm).children().last()[0]
let bqLastChild = cr(elm).children().last()[0]; let bqLastChildClasses = cr(bqLastChild).attr('class')
let bqLastChildClasses = cr(bqLastChild).attr('class'); if (bqLastChildClasses && bqLastChildClasses.length > 0) {
if(bqLastChildClasses && bqLastChildClasses.length > 0) { cr(bqLastChild).removeAttr('class')
cr(bqLastChild).removeAttr('class'); cr(elm).addClass(bqLastChildClasses)
cr(elm).addClass(bqLastChildClasses); }
} }
} })
});
// -> Enclose content below headers
//-> Enclose content below headers
cr('h2').each((i, elm) => {
cr('h2').each((i, elm) => { let subH2Content = cr(elm).nextUntil('h1, h2')
let subH2Content = cr(elm).nextUntil('h1, h2'); cr(elm).after('<div class="indent-h2"></div>')
cr(elm).after('<div class="indent-h2"></div>'); let subH2Container = cr(elm).next('.indent-h2')
let subH2Container = cr(elm).next('.indent-h2'); _.forEach(subH2Content, (ch) => {
_.forEach(subH2Content, (ch) => { cr(subH2Container).append(ch)
cr(subH2Container).append(ch); })
}); })
});
cr('h3').each((i, elm) => {
cr('h3').each((i, elm) => { let subH3Content = cr(elm).nextUntil('h1, h2, h3')
let subH3Content = cr(elm).nextUntil('h1, h2, h3'); cr(elm).after('<div class="indent-h3"></div>')
cr(elm).after('<div class="indent-h3"></div>'); let subH3Container = cr(elm).next('.indent-h3')
let subH3Container = cr(elm).next('.indent-h3'); _.forEach(subH3Content, (ch) => {
_.forEach(subH3Content, (ch) => { cr(subH3Container).append(ch)
cr(subH3Container).append(ch); })
}); })
});
// Replace video links with embeds
// Replace video links with embeds
_.forEach(videoRules, (vrule) => {
_.forEach(videoRules, (vrule) => { cr(vrule.selector).each((i, elm) => {
cr(vrule.selector).each((i, elm) => { let originLink = cr(elm).attr('href')
let originLink = cr(elm).attr('href'); if (vrule.regexp) {
if(vrule.regexp) { let vidMatches = originLink.match(vrule.regexp)
let vidMatches = originLink.match(vrule.regexp); if ((vidMatches && _.isArray(vidMatches))) {
if((vidMatches && _.isArray(vidMatches))) { vidMatches = _.filter(vidMatches, (f) => {
vidMatches = _.filter(vidMatches, (f) => { return f && _.isString(f)
return f && _.isString(f); })
}); originLink = _.last(vidMatches)
originLink = _.last(vidMatches); }
} }
} let processedLink = _.replace(vrule.output, '{0}', originLink)
let processedLink = _.replace(vrule.output, '{0}', originLink); cr(elm).replaceWith(processedLink)
cr(elm).replaceWith(processedLink); })
}); })
});
output = cr.html()
output = cr.html();
return output
return output; }
};
/** /**
* Parse meta-data tags from content * Parse meta-data tags from content
...@@ -261,58 +256,57 @@ const parseContent = (content) => { ...@@ -261,58 +256,57 @@ const parseContent = (content) => {
* @return {Object} Properties found in the content and their values * @return {Object} Properties found in the content and their values
*/ */
const parseMeta = (content) => { const parseMeta = (content) => {
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->', 'g')
let results = {}
let match
while ((match = commentMeta.exec(content)) !== null) {
results[_.toLower(match[1])] = _.trim(match[2])
}
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->','g'); return results
let results = {}, match; }
while(match = commentMeta.exec(content)) {
results[_.toLower(match[1])] = _.trim(match[2]);
}
return results;
};
module.exports = { module.exports = {
/** /**
* Parse content and return all data * Parse content and return all data
* *
* @param {String} content Markdown-formatted content * @param {String} content Markdown-formatted content
* @return {Object} Object containing meta, html and tree data * @return {Object} Object containing meta, html and tree data
*/ */
parse(content) { parse (content) {
return { return {
meta: parseMeta(content), meta: parseMeta(content),
html: parseContent(content), html: parseContent(content),
tree: parseTree(content) tree: parseTree(content)
}; }
}, },
parseContent, parseContent,
parseMeta, parseMeta,
parseTree, parseTree,
/** /**
* Strips non-text elements from Markdown content * Strips non-text elements from Markdown content
* *
* @param {String} content Markdown-formatted content * @param {String} content Markdown-formatted content
* @return {String} Text-only version * @return {String} Text-only version
*/ */
removeMarkdown(content) { removeMarkdown (content) {
return mdRemove(_.chain(content) return mdRemove(_.chain(content)
.replace(/<!-- ?([a-zA-Z]+):(.*)-->/g, '') .replace(/<!-- ?([a-zA-Z]+):(.*)-->/g, '')
.replace(/```[^`]+```/g, '') .replace(/```[^`]+```/g, '')
.replace(/`[^`]+`/g, '') .replace(/`[^`]+`/g, '')
.replace(new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'g'), '') .replace(new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'g'), '')
.replace(/\r?\n|\r/g, ' ') .replace(/\r?\n|\r/g, ' ')
.deburr() .deburr()
.toLower() .toLower()
.replace(/(\b([^a-z]+)\b)/g, ' ') .replace(/(\b([^a-z]+)\b)/g, ' ')
.replace(/[^a-z]+/g, ' ') .replace(/[^a-z]+/g, ' ')
.replace(/(\b(\w{1,2})\b(\W|$))/g, '') .replace(/(\b(\w{1,2})\b(\W|$))/g, '')
.replace(/\s\s+/g, ' ') .replace(/\s\s+/g, ' ')
.value() .value()
); )
} }
}; }
\ No newline at end of file
"use strict"; 'use strict'
var path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
readChunk = require('read-chunk'), const readChunk = require('read-chunk')
fileType = require('file-type'), const fileType = require('file-type')
mime = require('mime-types'), const mime = require('mime-types')
farmhash = require('farmhash'), const farmhash = require('farmhash')
moment = require('moment'), const chokidar = require('chokidar')
chokidar = require('chokidar'), const sharp = require('sharp')
sharp = require('sharp'), const _ = require('lodash')
_ = require('lodash');
/** /**
* Uploads - Agent * Uploads - Agent
*/ */
module.exports = { module.exports = {
_uploadsPath: './repo/uploads', _uploadsPath: './repo/uploads',
_uploadsThumbsPath: './data/thumbs', _uploadsThumbsPath: './data/thumbs',
_watcher: null, _watcher: null,
/** /**
* Initialize Uploads model * Initialize Uploads model
* *
* @return {Object} Uploads model instance * @return {Object} Uploads model instance
*/ */
init() { init () {
let self = this
let self = this;
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
// Disable Sharp cache, as it cause file locks issues when deleting uploads.
// Disable Sharp cache, as it cause file locks issues when deleting uploads. sharp.cache(false)
sharp.cache(false);
return self
return self; },
}, /**
* Watch the uploads folder for changes
/** *
* Watch the uploads folder for changes * @return {Void} Void
* */
* @return {Void} Void watch () {
*/ let self = this
watch() {
self._watcher = chokidar.watch(self._uploadsPath, {
let self = this; persistent: true,
ignoreInitial: true,
self._watcher = chokidar.watch(self._uploadsPath, { cwd: self._uploadsPath,
persistent: true, depth: 1,
ignoreInitial: true, awaitWriteFinish: true
cwd: self._uploadsPath, })
depth: 1,
awaitWriteFinish: true // -> Add new upload file
});
self._watcher.on('add', (p) => {
//-> Add new upload file let pInfo = self.parseUploadsRelPath(p)
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
self._watcher.on('add', (p) => { return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
}).then(() => {
let pInfo = self.parseUploadsRelPath(p); return git.commitUploads('Uploaded ' + p)
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => { })
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true }); })
}).then(() => {
return git.commitUploads('Uploaded ' + p); // -> Remove upload file
});
self._watcher.on('unlink', (p) => {
}); return git.commitUploads('Deleted/Renamed ' + p)
})
//-> Remove upload file },
self._watcher.on('unlink', (p) => { /**
* Initial Uploads scan
let pInfo = self.parseUploadsRelPath(p); *
return git.commitUploads('Deleted/Renamed ' + p); * @return {Promise<Void>} Promise of the scan operation
*/
}); initialScan () {
let self = this
},
return fs.readdirAsync(self._uploadsPath).then((ls) => {
/** // Get all folders
* Initial Uploads scan
* return Promise.map(ls, (f) => {
* @return {Promise<Void>} Promise of the scan operation return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })
*/ }).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {
initialScan() { let folderNames = _.map(arrDirs, 'filename')
folderNames.unshift('')
let self = this;
// Add folders to DB
return fs.readdirAsync(self._uploadsPath).then((ls) => {
return db.UplFolder.remove({}).then(() => {
// Get all folders return db.UplFolder.insertMany(_.map(folderNames, (f) => {
return {
return Promise.map(ls, (f) => { _id: 'f:' + f,
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; }); name: f
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => { }
}))
let folderNames = _.map(arrDirs, 'filename'); }).then(() => {
folderNames.unshift(''); // Travel each directory and scan files
// Add folders to DB let allFiles = []
return db.UplFolder.remove({}).then(() => { return Promise.map(folderNames, (fldName) => {
return db.UplFolder.insertMany(_.map(folderNames, (f) => { let fldPath = path.join(self._uploadsPath, fldName)
return { return fs.readdirAsync(fldPath).then((fList) => {
_id: 'f:' + f, return Promise.map(fList, (f) => {
name: f return upl.processFile(fldName, f).then((mData) => {
}; if (mData) {
})); allFiles.push(mData)
}).then(() => { }
return true
// Travel each directory and scan files })
}, {concurrency: 3})
let allFiles = []; })
}, {concurrency: 1}).finally(() => {
return Promise.map(folderNames, (fldName) => { // Add files to DB
let fldPath = path.join(self._uploadsPath, fldName); return db.UplFile.remove({}).then(() => {
return fs.readdirAsync(fldPath).then((fList) => { if (_.isArray(allFiles) && allFiles.length > 0) {
return Promise.map(fList, (f) => { return db.UplFile.insertMany(allFiles)
return upl.processFile(fldName, f).then((mData) => { } else {
if(mData) { return true
allFiles.push(mData); }
} })
return true; })
}); })
}, {concurrency: 3}); })
}); }).then(() => {
}, {concurrency: 1}).finally(() => { // Watch for new changes
// Add files to DB return upl.watch()
})
return db.UplFile.remove({}).then(() => { },
if(_.isArray(allFiles) && allFiles.length > 0) {
return db.UplFile.insertMany(allFiles); /**
} else { * Parse relative Uploads path
return true; *
} * @param {String} f Relative Uploads path
}); * @return {Object} Parsed path (folder and filename)
*/
}); parseUploadsRelPath (f) {
let fObj = path.parse(f)
}); return {
folder: fObj.dir,
}); filename: fObj.base
}
}).then(() => { },
// Watch for new changes /**
* Get metadata from file and generate thumbnails if necessary
return upl.watch(); *
* @param {String} fldName The folder name
}); * @param {String} f The filename
* @return {Promise<Object>} Promise of the file metadata
}, */
processFile (fldName, f) {
/** let self = this
* Parse relative Uploads path
* let fldPath = path.join(self._uploadsPath, fldName)
* @param {String} f Relative Uploads path let fPath = path.join(fldPath, f)
* @return {Object} Parsed path (folder and filename) let fPathObj = path.parse(fPath)
*/ let fUid = farmhash.fingerprint32(fldName + '/' + f)
parseUploadsRelPath(f) {
return fs.statAsync(fPath).then((s) => {
let fObj = path.parse(f); if (!s.isFile()) { return false }
return {
folder: fObj.dir, // Get MIME info
filename: fObj.base
}; let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))
if (_.isNil(mimeInfo)) {
}, mimeInfo = {
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
/** }
* Get metadata from file and generate thumbnails if necessary }
*
* @param {String} fldName The folder name // Images
* @param {String} f The filename
* @return {Promise<Object>} Promise of the file metadata if (s.size < 3145728) { // ignore files larger than 3MB
*/ if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
processFile(fldName, f) { return self.getImageMetadata(fPath).then((mImgData) => {
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))
let self = this; let cacheThumbnailPathStr = path.format(cacheThumbnailPath)
let fldPath = path.join(self._uploadsPath, fldName); let mData = {
let fPath = path.join(fldPath, f); _id: fUid,
let fPathObj = path.parse(fPath); category: 'image',
let fUid = farmhash.fingerprint32(fldName + '/' + f); mime: mimeInfo.mime,
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
return fs.statAsync(fPath).then((s) => { folder: 'f:' + fldName,
filename: f,
if(!s.isFile()) { return false; } basename: fPathObj.name,
filesize: s.size
// Get MIME info }
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262)); // Generate thumbnail
if(_.isNil(mimeInfo)) {
mimeInfo = { return fs.statAsync(cacheThumbnailPathStr).then((st) => {
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream' return st.isFile()
}; }).catch((err) => { // eslint-disable-line handle-callback-err
} return false
}).then((thumbExists) => {
// Images return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
return self.generateThumbnail(fPath, cacheThumbnailPathStr)
if(s.size < 3145728) { // ignore files larger than 3MB }).return(mData)
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);
// Other Files
let mData = {
_id: fUid, return {
category: 'image', _id: fUid,
mime: mimeInfo.mime, category: 'binary',
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']), mime: mimeInfo.mime,
folder: 'f:' + fldName, folder: 'f:' + fldName,
filename: f, filename: f,
basename: fPathObj.name, basename: fPathObj.name,
filesize: s.size filesize: s.size
}; }
})
// Generate thumbnail },
return fs.statAsync(cacheThumbnailPathStr).then((st) => { /**
return st.isFile(); * Generate thumbnail of image
}).catch((err) => { *
return false; * @param {String} sourcePath The source path
}).then((thumbExists) => { * @param {String} destPath The destination path
* @return {Promise<Object>} Promise returning the resized image info
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => { */
return self.generateThumbnail(fPath, cacheThumbnailPathStr); generateThumbnail (sourcePath, destPath) {
}).return(mData); return sharp(sourcePath)
.withoutEnlargement()
}); .resize(150, 150)
.background('white')
}); .embed()
} .flatten()
} .toFormat('png')
.toFile(destPath)
// Other Files },
return { /**
_id: fUid, * Gets the image metadata.
category: 'binary', *
mime: mimeInfo.mime, * @param {String} sourcePath The source path
folder: 'f:' + fldName, * @return {Object} The image metadata.
filename: f, */
basename: fPathObj.name, getImageMetadata (sourcePath) {
filesize: s.size return sharp(sourcePath).metadata()
}; }
}); }
},
/**
* Generate thumbnail of image
*
* @param {String} sourcePath The source path
* @param {String} destPath The destination path
* @return {Promise<Object>} Promise returning the resized image info
*/
generateThumbnail(sourcePath, destPath) {
return sharp(sourcePath)
.withoutEnlargement()
.resize(150,150)
.background('white')
.embed()
.flatten()
.toFormat('png')
.toFile(destPath);
},
/**
* Gets the image metadata.
*
* @param {String} sourcePath The source path
* @return {Object} The image metadata.
*/
getImageMetadata(sourcePath) {
return sharp(sourcePath).metadata();
}
};
\ No newline at end of file
"use strict"; 'use strict'
const path = require('path'), const path = require('path')
Promise = require('bluebird'), const Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs-extra')), const fs = Promise.promisifyAll(require('fs-extra'))
multer = require('multer'), const request = require('request')
request = require('request'), const url = require('url')
url = require('url'), const farmhash = require('farmhash')
farmhash = require('farmhash'), const _ = require('lodash')
_ = require('lodash');
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$"); var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
const maxDownloadFileSize = 3145728; // 3 MB const maxDownloadFileSize = 3145728 // 3 MB
/** /**
* Uploads * Uploads
*/ */
module.exports = { module.exports = {
_uploadsPath: './repo/uploads', _uploadsPath: './repo/uploads',
_uploadsThumbsPath: './data/thumbs', _uploadsThumbsPath: './data/thumbs',
/** /**
* Initialize Local Data Storage model * Initialize Local Data Storage model
* *
* @return {Object} Uploads model instance * @return {Object} Uploads model instance
*/ */
init() { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads'); this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
return this
return this; },
}, /**
* Gets the thumbnails folder path.
/** *
* Gets the thumbnails folder path. * @return {String} The thumbs path.
* */
* @return {String} The thumbs path. getThumbsPath () {
*/ return this._uploadsThumbsPath
getThumbsPath() { },
return this._uploadsThumbsPath;
}, /**
* Gets the uploads folders.
/** *
* Gets the uploads folders. * @return {Array<String>} The uploads folders.
* */
* @return {Array<String>} The uploads folders. getUploadsFolders () {
*/ return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
getUploadsFolders() { return (results) ? _.map(results, 'name') : [{ name: '' }]
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => { })
return (results) ? _.map(results, 'name') : [{ name: '' }]; },
});
}, /**
* Creates an uploads folder.
/** *
* Creates an uploads folder. * @param {String} folderName The folder name
* * @return {Promise} Promise of the operation
* @param {String} folderName The folder name */
* @return {Promise} Promise of the operation createUploadsFolder (folderName) {
*/ let self = this
createUploadsFolder(folderName) {
folderName = _.kebabCase(_.trim(folderName))
let self = this;
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
folderName = _.kebabCase(_.trim(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(() => {
} return db.UplFolder.findOneAndUpdate({
_id: 'f:' + folderName
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => { }, {
return db.UplFolder.findOneAndUpdate({ name: folderName
_id: 'f:' + folderName }, {
}, { upsert: true
name: folderName })
}, { }).then(() => {
upsert: true return self.getUploadsFolders()
}); })
}).then(() => { },
return self.getUploadsFolders();
}); /**
* Check if folder is valid and exists
}, *
* @param {String} folderName The folder name
/** * @return {Boolean} True if valid
* Check if folder is valid and exists */
* validateUploadsFolder (folderName) {
* @param {String} folderName The folder name return db.UplFolder.findOne({ name: folderName }).then((f) => {
* @return {Boolean} True if valid return (f) ? path.resolve(this._uploadsPath, folderName) : false
*/ })
validateUploadsFolder(folderName) { },
return db.UplFolder.findOne({ name: folderName }).then((f) => { /**
return (f) ? path.resolve(this._uploadsPath, folderName) : false; * Adds one or more uploads files.
}); *
* @param {Array<Object>} arrFiles The uploads files
}, * @return {Void} Void
*/
/** addUploadsFiles (arrFiles) {
* Adds one or more uploads files. if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
* // this._uploadsDb.Files.insert(arrFiles);
* @param {Array<Object>} arrFiles The uploads files }
* @return {Void} Void return
*/ },
addUploadsFiles(arrFiles) {
if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) { /**
//this._uploadsDb.Files.insert(arrFiles); * Gets the uploads files.
} *
return; * @param {String} cat Category type
}, * @param {String} fld Folder
* @return {Array<Object>} The files matching the query
/** */
* Gets the uploads files. getUploadsFiles (cat, fld) {
* return db.UplFile.find({
* @param {String} cat Category type category: cat,
* @param {String} fld Folder folder: 'f:' + fld
* @return {Array<Object>} The files matching the query }).sort('filename').exec()
*/ },
getUploadsFiles(cat, fld) {
/**
return db.UplFile.find({ * Deletes an uploads file.
category: cat, *
folder: 'f:' + fld * @param {string} uid The file unique ID
}).sort('filename').exec(); * @return {Promise} Promise of the operation
*/
}, deleteUploadsFile (uid) {
let self = this
/**
* Deletes an uploads file. return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
* if (f) {
* @param {string} uid The file unique ID return self.deleteUploadsFileTry(f, 0)
* @return {Promise} Promise of the operation }
*/ return true
deleteUploadsFile(uid) { })
},
let self = this;
deleteUploadsFileTry (f, attempt) {
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => { let self = this
if(f) {
return self.deleteUploadsFileTry(f, 0); let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
}
return true; return Promise.join(
}); fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
}, fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
).catch((err) => {
deleteUploadsFileTry(f, attempt) { if (err.code === 'EBUSY' && attempt < 5) {
return Promise.delay(100).then(() => {
let self = this; return self.deleteUploadsFileTry(f, attempt + 1)
})
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'; } else {
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
return Promise.join( return true
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')), }
fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename)) })
).catch((err) => { },
if(err.code === 'EBUSY' && attempt < 5) {
return Promise.delay(100).then(() => { /**
return self.deleteUploadsFileTry(f, attempt + 1); * Downloads a file from url.
}); *
} else { * @param {String} fFolder The folder
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.'); * @param {String} fUrl The full URL
return true; * @return {Promise} Promise of the operation
} */
}); downloadFromUrl (fFolder, fUrl) {
let fUrlObj = url.parse(fUrl)
}, let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
let destFolder = _.chain(fFolder).trim().toLower().value()
/**
* Downloads a file from url. return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
* if (!destFolderPath) {
* @param {String} fFolder The folder return Promise.reject(new Error('Invalid Folder'))
* @param {String} fUrl The full URL }
* @return {Promise} Promise of the operation
*/ return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
downloadFromUrl(fFolder, fUrl) { let destFilePath = path.resolve(destFolderPath, destFilename)
let self = this; return new Promise((resolve, reject) => {
let rq = request({
let fUrlObj = url.parse(fUrl); url: fUrl,
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/')); method: 'GET',
let destFolder = _.chain(fFolder).trim().toLower().value(); followRedirect: true,
maxRedirects: 5,
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => { timeout: 10000
})
if(!destFolderPath) {
return Promise.reject(new Error('Invalid Folder')); let destFileStream = fs.createWriteStream(destFilePath)
} let curFileSize = 0
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => { rq.on('data', (data) => {
curFileSize += data.length
let destFilePath = path.resolve(destFolderPath, destFilename); if (curFileSize > maxDownloadFileSize) {
rq.abort()
return new Promise((resolve, reject) => { destFileStream.destroy()
fs.remove(destFilePath)
let rq = request({ reject(new Error('Remote file is too large!'))
url: fUrl, }
method: 'GET', }).on('error', (err) => {
followRedirect: true, destFileStream.destroy()
maxRedirects: 5, fs.remove(destFilePath)
timeout: 10000 reject(err)
}); })
let destFileStream = fs.createWriteStream(destFilePath); destFileStream.on('finish', () => {
let curFileSize = 0; resolve(true)
})
rq.on('data', (data) => {
curFileSize += data.length; rq.pipe(destFileStream)
if(curFileSize > maxDownloadFileSize) { })
rq.abort(); })
destFileStream.destroy(); })
fs.remove(destFilePath); },
reject(new Error('Remote file is too large!'));
} /**
}).on('error', (err) => { * Move/Rename a file
destFileStream.destroy(); *
fs.remove(destFilePath); * @param {String} uid The file ID
reject(err); * @param {String} fld The destination folder
}); * @param {String} nFilename The new filename (optional)
* @return {Promise} Promise of the operation
destFileStream.on('finish', () => { */
resolve(true); moveUploadsFile (uid, fld, nFilename) {
}); let self = this
rq.pipe(destFileStream); return db.UplFolder.findById('f:' + fld).then((folder) => {
if (folder) {
}); return db.UplFile.findById(uid).then((originFile) => {
// -> Check if rename is valid
});
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)
* Move/Rename a file }
*
* @param {String} uid The file ID return nameCheck.then((destFilename) => {
* @param {String} fld The destination folder let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
* @param {String} nFilename The new filename (optional) let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
* @return {Promise} Promise of the operation let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
*/ let preMoveOps = []
moveUploadsFile(uid, fld, nFilename) {
// -> Check for invalid operations
let self = this;
if (sourceFilePath === destFilePath) {
return db.UplFolder.findById('f:' + fld).then((folder) => { return Promise.reject(new Error('Invalid Operation!'))
if(folder) { }
return db.UplFile.findById(uid).then((originFile) => {
// -> Delete DB entry
//-> Check if rename is valid
preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
let nameCheck = null;
if(nFilename) { // -> Move thumbnail ahead to avoid re-generation
let originFileObj = path.parse(originFile.filename);
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name); if (originFile.category === 'image') {
} else { let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename)
nameCheck = Promise.resolve(originFile.filename); let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
} let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
return nameCheck.then((destFilename) => { } else {
preMoveOps.push(Promise.resolve(true))
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); // -> Proceed to move actual file
let preMoveOps = [];
return Promise.all(preMoveOps).then(() => {
//-> Check for invalid operations return fs.moveAsync(sourceFilePath, destFilePath, {
clobber: false
if(sourceFilePath === destFilePath) { })
return Promise.reject(new Error('Invalid Operation!')); })
} })
})
//-> Delete DB entry } else {
return Promise.reject(new Error('Invalid Destination Folder'))
preMoveOps.push(db.UplFile.findByIdAndRemove(uid)); }
})
//-> 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));
} else {
preMoveOps.push(Promise.resolve(true));
}
//-> Proceed to move actual file
return Promise.all(preMoveOps).then(() => {
return fs.moveAsync(sourceFilePath, destFilePath, {
clobber: false
});
});
})
});
} else {
return Promise.reject(new Error('Invalid Destination Folder'));
}
});
}
};
\ No newline at end of file
"use strict"; 'use strict'
var Promise = require('bluebird'), const moment = require('moment-timezone')
moment = require('moment-timezone');
/** /**
* Authentication middleware * Authentication middleware
...@@ -12,29 +11,27 @@ var Promise = require('bluebird'), ...@@ -12,29 +11,27 @@ var Promise = require('bluebird'),
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
// Is user authenticated ?
// Is user authenticated ? if (!req.isAuthenticated()) {
return res.redirect('/login')
}
if (!req.isAuthenticated()) { // Check permissions
return res.redirect('/login');
}
// Check permissions if (!rights.check(req, 'read')) {
return res.render('error-forbidden')
}
if(!rights.check(req, 'read')) { // Set i18n locale
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); // Expose user data
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();
};
\ No newline at end of file
"use strict"; 'use strict'
/** /**
* Flash middleware * Flash middleware
...@@ -9,9 +9,7 @@ ...@@ -9,9 +9,7 @@
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
res.locals.appflash = req.flash('alert')
res.locals.appflash = req.flash('alert'); next()
}
next();
};
\ No newline at end of file
'use strict'
/** /**
* Security Middleware * Security Middleware
* *
...@@ -6,23 +8,21 @@ ...@@ -6,23 +8,21 @@
* @param {Function} next next callback function * @param {Function} next next callback function
* @return {any} void * @return {any} void
*/ */
module.exports = function(req, res, next) { module.exports = function (req, res, next) {
// -> Disable X-Powered-By
//-> Disable X-Powered-By app.disable('x-powered-by')
app.disable('x-powered-by');
//-> Disable Frame Embedding
res.set('X-Frame-Options', 'deny');
//-> Re-enable XSS Fitler if disabled // -> Disable Frame Embedding
res.set('X-XSS-Protection', '1; mode=block'); res.set('X-Frame-Options', 'deny')
//-> Disable MIME-sniffing // -> Re-enable XSS Fitler if disabled
res.set('X-Content-Type-Options', 'nosniff'); res.set('X-XSS-Protection', '1; mode=block')
//-> Disable IE Compatibility Mode // -> Disable MIME-sniffing
res.set('X-UA-Compatible', 'IE=edge'); res.set('X-Content-Type-Options', 'nosniff')
return next(); // -> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge')
}; return next()
\ No newline at end of file }
"use strict"; 'use strict'
/** /**
* BruteForce schema * BruteForce schema
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
* @type {<Mongoose.Schema>} * @type {<Mongoose.Schema>}
*/ */
var bruteForceSchema = Mongoose.Schema({ var bruteForceSchema = Mongoose.Schema({
_id: { type: String, index: 1 }, _id: { type: String, index: 1 },
data: { data: {
count: Number, count: Number,
lastRequest: Date, lastRequest: Date,
firstRequest: Date firstRequest: Date
}, },
expires: { type: Date, index: { expires: '1d' } } expires: { type: Date, index: { expires: '1d' } }
}); })
module.exports = Mongoose.model('Bruteforce', bruteForceSchema); module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Entry schema * Entry schema
...@@ -10,7 +7,7 @@ const Promise = require('bluebird'), ...@@ -10,7 +7,7 @@ const Promise = require('bluebird'),
*/ */
var entrySchema = Mongoose.Schema({ var entrySchema = Mongoose.Schema({
_id: String, _id: String,
title: { title: {
type: String, type: String,
...@@ -31,9 +28,9 @@ var entrySchema = Mongoose.Schema({ ...@@ -31,9 +28,9 @@ var entrySchema = Mongoose.Schema({
} }
}, },
{ {
timestamps: {} timestamps: {}
}); })
entrySchema.index({ entrySchema.index({
_id: 'text', _id: 'text',
...@@ -48,6 +45,6 @@ entrySchema.index({ ...@@ -48,6 +45,6 @@ entrySchema.index({
content: 1 content: 1
}, },
name: 'EntriesTextIndex' name: 'EntriesTextIndex'
}); })
module.exports = Mongoose.model('Entry', entrySchema); module.exports = Mongoose.model('Entry', entrySchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload File schema * Upload File schema
...@@ -10,7 +7,7 @@ const Promise = require('bluebird'), ...@@ -10,7 +7,7 @@ const Promise = require('bluebird'),
*/ */
var uplFileSchema = Mongoose.Schema({ var uplFileSchema = Mongoose.Schema({
_id: String, _id: String,
category: { category: {
type: String, type: String,
...@@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({ ...@@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({
required: true required: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFile', uplFileSchema); module.exports = Mongoose.model('UplFile', uplFileSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'),
_ = require('lodash');
/** /**
* Upload Folder schema * Upload Folder schema
...@@ -10,16 +7,13 @@ const Promise = require('bluebird'), ...@@ -10,16 +7,13 @@ const Promise = require('bluebird'),
*/ */
var uplFolderSchema = Mongoose.Schema({ var uplFolderSchema = Mongoose.Schema({
_id: String, _id: String,
name: { name: {
type: String, type: String,
index: true index: true
} }
}, }, { timestamps: {} })
{
timestamps: {}
});
module.exports = Mongoose.model('UplFolder', uplFolderSchema); module.exports = Mongoose.model('UplFolder', uplFolderSchema)
\ No newline at end of file
"use strict"; 'use strict'
const Promise = require('bluebird'), const Promise = require('bluebird')
bcrypt = require('bcryptjs-then'), const bcrypt = require('bcryptjs-then')
_ = require('lodash'); const _ = require('lodash')
/** /**
* Region schema * Region schema
...@@ -11,78 +11,73 @@ const Promise = require('bluebird'), ...@@ -11,78 +11,73 @@ const Promise = require('bluebird'),
*/ */
var userSchema = Mongoose.Schema({ var userSchema = Mongoose.Schema({
email: { email: {
type: String, type: String,
required: true, required: true,
index: true index: true
}, },
provider: { provider: {
type: String, type: String,
required: true required: true
}, },
providerId: { providerId: {
type: String type: String
}, },
password: { password: {
type: String type: String
}, },
name: { name: {
type: String type: String
}, },
rights: [{ rights: [{
role: String, role: String,
path: String, path: String,
exact: Boolean, exact: Boolean,
deny: Boolean deny: Boolean
}] }]
}, }, { timestamps: {} })
{
timestamps: {}
});
userSchema.statics.processProfile = (profile) => { 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
} else {
return Promise.reject(new Error('Invalid User Email'))
}
let primaryEmail = ''; return db.User.findOneAndUpdate({
if(_.isArray(profile.emails)) { email: primaryEmail,
let e = _.find(profile.emails, ['primary', true]); provider: profile.provider
primaryEmail = (e) ? e.value : _.first(profile.emails).value; }, {
} else if(_.isString(profile.email) && profile.email.length > 5) { email: primaryEmail,
primaryEmail = profile.email; provider: profile.provider,
} else { providerId: profile.id,
return Promise.reject(new Error('Invalid User Email')); name: profile.displayName || _.split(primaryEmail, '@')[0]
} }, {
new: true,
return db.User.findOneAndUpdate({ upsert: true
email: primaryEmail, }).then((user) => {
provider: profile.provider return user || Promise.reject(new Error('User Upsert failed.'))
}, { })
email: primaryEmail, }
provider: profile.provider,
providerId: profile.id,
name: profile.displayName || _.split(primaryEmail, '@')[0]
}, {
new: true,
upsert: true
}).then((user) => {
return (user) ? user : Promise.reject(new Error('User Upsert failed.'));
});
};
userSchema.statics.hashPassword = (rawPwd) => { userSchema.statics.hashPassword = (rawPwd) => {
return bcrypt.hash(rawPwd); return bcrypt.hash(rawPwd)
}; }
userSchema.methods.validatePassword = function(rawPwd) { userSchema.methods.validatePassword = function (rawPwd) {
return bcrypt.compare(rawPwd, this.password).then((isValid) => { return bcrypt.compare(rawPwd, this.password).then((isValid) => {
return (isValid) ? true : Promise.reject(new Error('Invalid Login')); return (isValid) ? true : Promise.reject(new Error('Invalid Login'))
}); })
}; }
module.exports = Mongoose.model('User', userSchema); module.exports = Mongoose.model('User', userSchema)
\ No newline at end of file
...@@ -129,5 +129,35 @@ ...@@ -129,5 +129,35 @@
"twemoji-awesome": "^1.0.4", "twemoji-awesome": "^1.0.4",
"vue": "^2.1.10" "vue": "^2.1.10"
}, },
"standard": {
"globals": [
"app",
"appconfig",
"appdata",
"bgAgent",
"db",
"entries",
"git",
"mark",
"lang",
"lcdata",
"rights",
"upl",
"winston",
"ws",
"Mongoose",
"CORE_PATH",
"ROOTPATH",
"IS_DEBUG",
"PROCNAME",
"WSInternalKey"
],
"ignore": [
"assets/**/*",
"data/**/*",
"node_modules/**/*",
"repo/**/*"
]
},
"snyk": true "snyk": true
} }
"use strict"; 'use strict'
// =========================================== // ===========================================
// Wiki.js // Wiki.js
// 1.0.0 // 1.0.0
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
global.PROCNAME = 'SERVER'; global.PROCNAME = 'SERVER'
global.ROOTPATH = __dirname; global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'; global.IS_DEBUG = process.env.NODE_ENV === 'development'
if(IS_DEBUG) { if (IS_DEBUG) {
global.CORE_PATH = ROOTPATH + '/../core/'; global.CORE_PATH = ROOTPATH + '/../core/'
} else { } else {
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'; global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
} }
process.env.VIPS_WARNING = false
// ---------------------------------------- // ----------------------------------------
// Load Winston // Load Winston
// ---------------------------------------- // ----------------------------------------
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG); global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
winston.info('[SERVER] Wiki.js is initializing...'); winston.info('[SERVER] Wiki.js is initializing...')
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
let appconf = require(CORE_PATH + 'core-libs/config')(); let appconf = require(CORE_PATH + 'core-libs/config')()
global.appconfig = appconf.config; global.appconfig = appconf.config
global.appdata = appconf.data; global.appdata = appconf.data
global.lcdata = require('./libs/local').init(); global.lcdata = require('./libs/local').init()
global.db = require(CORE_PATH + 'core-libs/mongodb').init(); global.db = require(CORE_PATH + 'core-libs/mongodb').init()
global.entries = require('./libs/entries').init(); global.entries = require('./libs/entries').init()
global.git = require('./libs/git').init(false); global.git = require('./libs/git').init(false)
global.lang = require('i18next'); global.lang = require('i18next')
global.mark = require('./libs/markdown'); global.mark = require('./libs/markdown')
global.upl = require('./libs/uploads').init(); global.upl = require('./libs/uploads').init()
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
// ---------------------------------------- // ----------------------------------------
const _ = require('lodash'); const autoload = require('auto-load')
const autoload = require('auto-load'); const bodyParser = require('body-parser')
const bodyParser = require('body-parser'); const compression = require('compression')
const compression = require('compression'); const cookieParser = require('cookie-parser')
const cookieParser = require('cookie-parser'); const express = require('express')
const express = require('express'); const favicon = require('serve-favicon')
const favicon = require('serve-favicon'); const flash = require('connect-flash')
const flash = require('connect-flash'); const fork = require('child_process').fork
const fork = require('child_process').fork; const http = require('http')
const http = require('http'); const i18nextBackend = require('i18next-node-fs-backend')
const i18next_backend = require('i18next-node-fs-backend'); const i18nextMw = require('i18next-express-middleware')
const i18next_mw = require('i18next-express-middleware'); const passport = require('passport')
const passport = require('passport'); const passportSocketIo = require('passport.socketio')
const passportSocketIo = require('passport.socketio'); const path = require('path')
const path = require('path'); const session = require('express-session')
const session = require('express-session'); const SessionMongoStore = require('connect-mongo')(session)
const sessionMongoStore = require('connect-mongo')(session); const socketio = require('socket.io')
const socketio = require('socket.io');
var mw = autoload(CORE_PATH + '/core-middlewares')
var mw = autoload(CORE_PATH + '/core-middlewares'); var ctrl = autoload(path.join(ROOTPATH, '/controllers'))
var ctrl = autoload(path.join(ROOTPATH, '/controllers')); var libInternalAuth = require('./libs/internalAuth')
var libInternalAuth = require('./libs/internalAuth');
global.WSInternalKey = libInternalAuth.generateKey()
global.WSInternalKey = libInternalAuth.generateKey();
// ---------------------------------------- // ----------------------------------------
// Define Express App // Define Express App
// ---------------------------------------- // ----------------------------------------
global.app = express(); global.app = express()
app.use(compression()); app.use(compression())
// ---------------------------------------- // ----------------------------------------
// Security // Security
// ---------------------------------------- // ----------------------------------------
app.use(mw.security); app.use(mw.security)
// ---------------------------------------- // ----------------------------------------
// Public Assets // Public Assets
// ---------------------------------------- // ----------------------------------------
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico'))); app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(ROOTPATH, 'assets'))); app.use(express.static(path.join(ROOTPATH, 'assets')))
// ---------------------------------------- // ----------------------------------------
// Passport Authentication // Passport Authentication
// ---------------------------------------- // ----------------------------------------
var strategy = require(CORE_PATH + 'core-libs/auth')(passport); require(CORE_PATH + 'core-libs/auth')(passport)
global.rights = require(CORE_PATH + 'core-libs/rights'); global.rights = require(CORE_PATH + 'core-libs/rights')
rights.init(); rights.init()
var sessionStore = new sessionMongoStore({ var sessionStore = new SessionMongoStore({
mongooseConnection: db.connection, mongooseConnection: db.connection,
touchAfter: 15 touchAfter: 15
}); })
app.use(cookieParser()); app.use(cookieParser())
app.use(session({ app.use(session({
name: 'requarkswiki.sid', name: 'requarkswiki.sid',
store: sessionStore, store: sessionStore,
secret: appconfig.sessionSecret, secret: appconfig.sessionSecret,
resave: false, resave: false,
saveUninitialized: false saveUninitialized: false
})); }))
app.use(flash()); app.use(flash())
app.use(passport.initialize()); app.use(passport.initialize())
app.use(passport.session()); app.use(passport.session())
// ---------------------------------------- // ----------------------------------------
// Localization Engine // Localization Engine
// ---------------------------------------- // ----------------------------------------
lang lang
.use(i18next_backend) .use(i18nextBackend)
.use(i18next_mw.LanguageDetector) .use(i18nextMw.LanguageDetector)
.init({ .init({
load: 'languageOnly', load: 'languageOnly',
ns: ['common', 'auth'], ns: ['common', 'auth'],
...@@ -124,94 +126,94 @@ lang ...@@ -124,94 +126,94 @@ lang
saveMissing: false, saveMissing: false,
supportedLngs: ['en', 'fr'], supportedLngs: ['en', 'fr'],
preload: ['en', 'fr'], preload: ['en', 'fr'],
fallbackLng : 'en', fallbackLng: 'en',
backend: { backend: {
loadPath: './locales/{{lng}}/{{ns}}.json' loadPath: './locales/{{lng}}/{{ns}}.json'
} }
}); })
// ---------------------------------------- // ----------------------------------------
// View Engine Setup // View Engine Setup
// ---------------------------------------- // ----------------------------------------
app.use(i18next_mw.handle(lang)); app.use(i18nextMw.handle(lang))
app.set('views', path.join(ROOTPATH, 'views')); app.set('views', path.join(ROOTPATH, 'views'))
app.set('view engine', 'pug'); app.set('view engine', 'pug')
app.use(bodyParser.json()); app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }))
// ---------------------------------------- // ----------------------------------------
// View accessible data // View accessible data
// ---------------------------------------- // ----------------------------------------
app.locals._ = require('lodash'); app.locals._ = require('lodash')
app.locals.moment = require('moment'); app.locals.moment = require('moment')
app.locals.appconfig = appconfig; app.locals.appconfig = appconfig
app.use(mw.flash); app.use(mw.flash)
// ---------------------------------------- // ----------------------------------------
// Controllers // Controllers
// ---------------------------------------- // ----------------------------------------
app.use('/', ctrl.auth); app.use('/', ctrl.auth)
app.use('/uploads', mw.auth, ctrl.uploads); app.use('/uploads', mw.auth, ctrl.uploads)
app.use('/admin', mw.auth, ctrl.admin); app.use('/admin', mw.auth, ctrl.admin)
app.use('/', mw.auth, ctrl.pages); app.use('/', mw.auth, ctrl.pages)
// ---------------------------------------- // ----------------------------------------
// Error handling // Error handling
// ---------------------------------------- // ----------------------------------------
app.use(function(req, res, next) { app.use(function (req, res, next) {
var err = new Error('Not Found'); var err = new Error('Not Found')
err.status = 404; err.status = 404
next(err); next(err)
}); })
app.use(function(err, req, res, next) { app.use(function (err, req, res, next) {
res.status(err.status || 500); res.status(err.status || 500)
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: IS_DEBUG ? err : {} error: IS_DEBUG ? err : {}
}); })
}); })
// ---------------------------------------- // ----------------------------------------
// Start HTTP server // Start HTTP server
// ---------------------------------------- // ----------------------------------------
winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...'); winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...')
app.set('port', appconfig.port); app.set('port', appconfig.port)
var server = http.createServer(app); var server = http.createServer(app)
var io = socketio(server); var io = socketio(server)
server.listen(appconfig.port); server.listen(appconfig.port)
server.on('error', (error) => { server.on('error', (error) => {
if (error.syscall !== 'listen') { if (error.syscall !== 'listen') {
throw error; throw error
} }
// handle specific listen errors with friendly messages // handle specific listen errors with friendly messages
switch (error.code) { switch (error.code) {
case 'EACCES': case 'EACCES':
console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!'); console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!')
process.exit(1); process.exit(1)
break; break
case 'EADDRINUSE': case 'EADDRINUSE':
console.error('Port ' + appconfig.port + ' is already in use!'); console.error('Port ' + appconfig.port + ' is already in use!')
process.exit(1); process.exit(1)
break; break
default: default:
throw error; throw error
} }
}); })
server.on('listening', () => { server.on('listening', () => {
winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]'); winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]')
}); })
// ---------------------------------------- // ----------------------------------------
// WebSocket // WebSocket
...@@ -224,21 +226,21 @@ io.use(passportSocketIo.authorize({ ...@@ -224,21 +226,21 @@ io.use(passportSocketIo.authorize({
passport, passport,
cookieParser, cookieParser,
success: (data, accept) => { success: (data, accept) => {
accept(); accept()
}, },
fail: (data, message, error, accept) => { fail: (data, message, error, accept) => {
return accept(new Error(message)); return accept(new Error(message))
} }
})); }))
io.on('connection', ctrl.ws); io.on('connection', ctrl.ws)
// ---------------------------------------- // ----------------------------------------
// Start child processes // Start child processes
// ---------------------------------------- // ----------------------------------------
global.bgAgent = fork('agent.js'); global.bgAgent = fork('agent.js')
process.on('exit', (code) => { process.on('exit', (code) => {
bgAgent.disconnect(); bgAgent.disconnect()
}); })
\ No newline at end of file
'use strict'
// TODO
"use strict";
let path = require('path'),
fs = require('fs');
// ========================================
// Load global modules
// ========================================
global._ = require('lodash');
global.winston = require('winston');
\ No newline at end of file
...@@ -19,20 +19,15 @@ html ...@@ -19,20 +19,15 @@ html
// CSS // CSS
link(type='text/css', rel='stylesheet', href='/css/libs.css') link(type='text/css', rel='stylesheet', href='/css/libs.css')
link(type='text/css', rel='stylesheet', href='/css/app.css') link(type='text/css', rel='stylesheet', href='/css/error.css')
body(class='server-error') body(class='is-error')
section.hero.is-warning.is-fullheight .container
.hero-body a(href='/'): img(src='/favicons/android-icon-96x96.png')
.container h1= message
a(href='/'): img(src='/favicons/android-icon-96x96.png') h2 Oops, something went wrong
h1.title(style={ 'margin-top': '30px'})= message a.button.is-amber.is-inverted.is-featured(href='/') Go Home
h2.subtitle(style={ 'margin-bottom': '50px'}) Oops, something went wrong
a.button.is-warning.is-inverted(href='/') Go Home
if error.stack if error.stack
section.section h3 Detailed debug trail:
.container.is-fluid pre: code #{error.stack}
.content \ No newline at end of file
h3 Detailed debug trail:
pre: code #{error.stack}
\ No newline at end of file
...@@ -53,6 +53,11 @@ block content ...@@ -53,6 +53,11 @@ block content
a(href='/admin') a(href='/admin')
i.icon-head i.icon-head
span Account span Account
else
li
a(href='/login')
i.icon-unlock
span Login
aside.stickyscroll(data-margin-top=40) aside.stickyscroll(data-margin-top=40)
.sidebar-label .sidebar-label
i.icon-th-list i.icon-th-list
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment