Standard JS code conversion + fixes
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
# Change Log
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Change log
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue with social accounts with empty name
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies + snyk policy
|
||||||
|
- Conversion to Standard JS compliant code
|
||||||
|
|
||||||
|
## [v1.0-beta.2] - 2017-01-30
|
||||||
|
### Added
|
||||||
|
- Save own profile under My Account
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies + snyk policy
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/Requarks/wiki/compare/v1.0-beta.2...HEAD
|
||||||
|
[v1.0-beta.2]: https://github.com/Requarks/wiki/releases/tag/v1.0-beta.2
|
||||||
@@ -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) {
|
||||||
|
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
winston.info('[AGENT] Running all jobs...')
|
||||||
|
jobIsBusy = true
|
||||||
|
|
||||||
if(jobIsBusy) {
|
// Prepare async job collector
|
||||||
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
winston.info('[AGENT] Running all jobs...');
|
|
||||||
jobIsBusy = true;
|
|
||||||
|
|
||||||
// Prepare async job collector
|
let jobs = []
|
||||||
|
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
||||||
|
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
|
||||||
|
let uploadsTempPath = path.join(dataPath, 'temp-upload')
|
||||||
|
|
||||||
let jobs = [];
|
// ----------------------------------------
|
||||||
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
// REGULAR JOBS
|
||||||
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data);
|
// ----------------------------------------
|
||||||
let uploadsPath = path.join(repoPath, 'uploads');
|
|
||||||
let uploadsTempPath = path.join(dataPath, 'temp-upload');
|
|
||||||
|
|
||||||
// ----------------------------------------
|
//* ****************************************
|
||||||
// REGULAR JOBS
|
// -> Sync with Git remote
|
||||||
// ----------------------------------------
|
//* ****************************************
|
||||||
|
|
||||||
//*****************************************
|
jobs.push(git.onReady.then(() => {
|
||||||
//-> Sync with Git remote
|
return git.resync().then(() => {
|
||||||
//*****************************************
|
// -> Stream all documents
|
||||||
|
|
||||||
jobs.push(git.onReady.then(() => {
|
let cacheJobs = []
|
||||||
return git.resync().then(() => {
|
let jobCbStreamDocsResolve = null
|
||||||
|
let jobCbStreamDocs = new Promise((resolve, reject) => {
|
||||||
|
jobCbStreamDocsResolve = resolve
|
||||||
|
})
|
||||||
|
|
||||||
//-> Stream all documents
|
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))
|
||||||
|
let cachePath = entries.getCachePath(entryPath)
|
||||||
|
|
||||||
let cacheJobs = [];
|
// -> Purge outdated cache
|
||||||
let jobCbStreamDocs_resolve = null,
|
|
||||||
jobCbStreamDocs = new Promise((resolve, reject) => {
|
|
||||||
jobCbStreamDocs_resolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
klaw(repoPath).on('data', function (item) {
|
cacheJobs.push(
|
||||||
if(path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
|
fs.statAsync(cachePath).then((st) => {
|
||||||
|
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
|
||||||
|
}).catch((err) => {
|
||||||
|
return (err.code !== 'EEXIST') ? err : 'new'
|
||||||
|
}).then((fileStatus) => {
|
||||||
|
// -> Delete expired cache file
|
||||||
|
|
||||||
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path));
|
if (fileStatus === 'expired') {
|
||||||
let cachePath = entries.getCachePath(entryPath);
|
return fs.unlinkAsync(cachePath).return(fileStatus)
|
||||||
|
}
|
||||||
|
|
||||||
//-> Purge outdated cache
|
return fileStatus
|
||||||
|
}).then((fileStatus) => {
|
||||||
|
// -> Update cache and search index
|
||||||
|
|
||||||
cacheJobs.push(
|
if (fileStatus !== 'active') {
|
||||||
fs.statAsync(cachePath).then((st) => {
|
return entries.updateCache(entryPath)
|
||||||
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active';
|
}
|
||||||
}).catch((err) => {
|
|
||||||
return (err.code !== 'EEXIST') ? err : 'new';
|
|
||||||
}).then((fileStatus) => {
|
|
||||||
|
|
||||||
//-> Delete expired cache file
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).on('end', () => {
|
||||||
|
jobCbStreamDocsResolve(Promise.all(cacheJobs))
|
||||||
|
})
|
||||||
|
|
||||||
if(fileStatus === 'expired') {
|
return jobCbStreamDocs
|
||||||
return fs.unlinkAsync(cachePath).return(fileStatus);
|
})
|
||||||
}
|
}))
|
||||||
|
|
||||||
return fileStatus;
|
//* ****************************************
|
||||||
|
// -> Clear failed temporary upload files
|
||||||
|
//* ****************************************
|
||||||
|
|
||||||
}).then((fileStatus) => {
|
jobs.push(
|
||||||
|
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
||||||
|
let fifteenAgo = moment().subtract(15, 'minutes')
|
||||||
|
|
||||||
//-> Update cache and search index
|
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) => {
|
||||||
|
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
||||||
|
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
if(fileStatus !== 'active') {
|
// ----------------------------------------
|
||||||
return entries.updateCache(entryPath);
|
// Run
|
||||||
}
|
// ----------------------------------------
|
||||||
|
|
||||||
return true;
|
Promise.all(jobs).then(() => {
|
||||||
|
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
|
||||||
|
|
||||||
})
|
if (!jobUplWatchStarted) {
|
||||||
|
jobUplWatchStarted = true
|
||||||
);
|
upl.initialScan().then(() => {
|
||||||
|
job.start()
|
||||||
}
|
})
|
||||||
}).on('end', () => {
|
}
|
||||||
jobCbStreamDocs_resolve(Promise.all(cacheJobs));
|
|
||||||
});
|
|
||||||
|
|
||||||
return jobCbStreamDocs;
|
|
||||||
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
//*****************************************
|
|
||||||
//-> Clear failed temporary upload files
|
|
||||||
//*****************************************
|
|
||||||
|
|
||||||
jobs.push(
|
|
||||||
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) => {
|
|
||||||
|
|
||||||
if(moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
|
||||||
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename));
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Run
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
Promise.all(jobs).then(() => {
|
|
||||||
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.');
|
|
||||||
|
|
||||||
if(!jobUplWatchStarted) {
|
|
||||||
jobUplWatchStarted = 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
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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()
|
||||||
});
|
})
|
||||||
|
|||||||
+21
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+25
-25
File diff suppressed because one or more lines are too long
+32
-34
@@ -1,59 +1,57 @@
|
|||||||
"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/pages.js
|
||||||
|
|
||||||
//=include helpers/form.js
|
// =include components/alerts.js
|
||||||
//=include helpers/pages.js
|
|
||||||
|
|
||||||
//=include components/alerts.js
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alerts
|
* Alerts
|
||||||
@@ -10,25 +10,23 @@ class Alerts {
|
|||||||
*
|
*
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor () {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
self.mdl = new Vue({
|
||||||
|
el: '#alerts',
|
||||||
|
data: {
|
||||||
|
children: []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
acknowledge: (uid) => {
|
||||||
|
self.close(uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
self.mdl = new Vue({
|
self.uidNext = 1
|
||||||
el: '#alerts',
|
}
|
||||||
data: {
|
|
||||||
children: []
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
acknowledge: (uid) => {
|
|
||||||
self.close(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.uidNext = 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a new Alert
|
* Show a new Alert
|
||||||
@@ -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: '---'
|
||||||
|
})
|
||||||
|
|
||||||
let nAlert = _.defaults(options, {
|
self.mdl.children.push(nAlert)
|
||||||
_uid: self.uidNext,
|
|
||||||
class: 'info',
|
|
||||||
message: '---',
|
|
||||||
sticky: false,
|
|
||||||
title: '---'
|
|
||||||
});
|
|
||||||
|
|
||||||
self.mdl.children.push(nAlert);
|
if (!nAlert.sticky) {
|
||||||
|
_.delay(() => {
|
||||||
|
self.close(nAlert._uid)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
if(!nAlert.sticky) {
|
self.uidNext++
|
||||||
_.delay(() => {
|
}
|
||||||
self.close(nAlert._uid);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.uidNext++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand method for pushing errors
|
* Shorthand method for pushing errors
|
||||||
@@ -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 {
|
|||||||
* @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 nAlert = _.nth(self.mdl.children, nAlertIdx)
|
||||||
|
|
||||||
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid]);
|
if (nAlertIdx >= 0 && nAlert) {
|
||||||
let nAlert = _.nth(self.mdl.children, nAlertIdx);
|
nAlert.class += ' exit'
|
||||||
|
Vue.set(self.mdl.children, nAlertIdx, nAlert)
|
||||||
if(nAlertIdx >= 0 && nAlert) {
|
_.delay(() => {
|
||||||
nAlert.class += ' exit';
|
self.mdl.children.splice(nAlertIdx, 1)
|
||||||
Vue.set(self.mdl.children, nAlertIdx, nAlert);
|
}, 500)
|
||||||
_.delay(() => {
|
}
|
||||||
self.mdl.children.splice(nAlertIdx, 1);
|
}
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,78 +1,74 @@
|
|||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
+289
-303
@@ -1,365 +1,351 @@
|
|||||||
|
|
||||||
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]$')
|
||||||
|
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
|
||||||
|
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
||||||
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
|
vueFile.newFolderError = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if(_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
vueFile.newFolderDiscard()
|
||||||
vueFile.newFolderError = true;
|
vueFile.isLoadingText = 'Creating new folder...'
|
||||||
return;
|
vueFile.isLoading = true
|
||||||
}
|
|
||||||
|
|
||||||
vueFile.newFolderDiscard();
|
Vue.nextTick(() => {
|
||||||
vueFile.isLoadingText = 'Creating new folder...';
|
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
||||||
vueFile.isLoading = true;
|
vueFile.folders = data
|
||||||
|
vueFile.currentFolder = vueFile.newFolderName
|
||||||
Vue.nextTick(() => {
|
vueFile.files = []
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
vueFile.isLoading = false
|
||||||
vueFile.folders = data;
|
})
|
||||||
vueFile.currentFolder = vueFile.newFolderName;
|
})
|
||||||
vueFile.files = [];
|
},
|
||||||
vueFile.isLoading = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// RENAME FILE
|
// RENAME FILE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameFile: () => {
|
renameFile: () => {
|
||||||
|
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ])
|
||||||
|
vueFile.renameFileFilename = c.basename || ''
|
||||||
|
vueFile.renameFileShow = true
|
||||||
|
_.delay(() => {
|
||||||
|
$('#txt-editor-renamefile').focus()
|
||||||
|
_.defer(() => { $('#txt-editor-file-rename').select() })
|
||||||
|
}, 400)
|
||||||
|
},
|
||||||
|
renameFileDiscard: () => {
|
||||||
|
vueFile.renameFileShow = false
|
||||||
|
},
|
||||||
|
renameFileGo: () => {
|
||||||
|
vueFile.renameFileDiscard()
|
||||||
|
vueFile.isLoadingText = 'Renaming file...'
|
||||||
|
vueFile.isLoading = true
|
||||||
|
|
||||||
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]);
|
Vue.nextTick(() => {
|
||||||
vueFile.renameFileFilename = c.basename || '';
|
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
||||||
vueFile.renameFileShow = true;
|
if (data.ok) {
|
||||||
_.delay(() => {
|
vueFile.waitChangeComplete(vueFile.files.length, false)
|
||||||
$('#txt-editor-renamefile').focus();
|
} else {
|
||||||
_.defer(() => { $('#txt-editor-file-rename').select(); });
|
vueFile.isLoading = false
|
||||||
}, 400);
|
alerts.pushError('Rename error', data.msg)
|
||||||
},
|
}
|
||||||
renameFileDiscard: () => {
|
})
|
||||||
vueFile.renameFileShow = false;
|
})
|
||||||
},
|
},
|
||||||
renameFileGo: () => {
|
|
||||||
|
|
||||||
vueFile.renameFileDiscard();
|
|
||||||
vueFile.isLoadingText = 'Renaming file...';
|
|
||||||
vueFile.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
|
||||||
if(data.ok) {
|
|
||||||
vueFile.waitChangeComplete(vueFile.files.length, false);
|
|
||||||
} else {
|
|
||||||
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.isLoadingText = 'Processing...'
|
||||||
|
|
||||||
vueFile.postUploadChecks++;
|
Vue.nextTick(() => {
|
||||||
vueFile.isLoadingText = 'Processing...';
|
vueFile.loadFiles(true).then(() => {
|
||||||
|
if ((vueFile.files.length !== oldAmount) === expectChange) {
|
||||||
Vue.nextTick(() => {
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.loadFiles(true).then(() => {
|
vueFile.isLoading = false
|
||||||
if((vueFile.files.length !== oldAmount) === expectChange) {
|
} else if (vueFile.postUploadChecks > 5) {
|
||||||
vueFile.postUploadChecks = 0;
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
} else if(vueFile.postUploadChecks > 5) {
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
vueFile.postUploadChecks = 0;
|
} else {
|
||||||
vueFile.isLoading = false;
|
_.delay(() => {
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
vueFile.waitChangeComplete(oldAmount, expectChange)
|
||||||
} else {
|
}, 1500)
|
||||||
_.delay(() => {
|
}
|
||||||
vueFile.waitChangeComplete(oldAmount, expectChange);
|
})
|
||||||
}, 1500);
|
})
|
||||||
}
|
},
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// IMAGE CONTEXT MENU
|
// IMAGE CONTEXT MENU
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
attachContextMenus: () => {
|
||||||
|
let moveFolders = _.map(vueFile.folders, (f) => {
|
||||||
|
return {
|
||||||
|
name: (f !== '') ? f : '/ (root)',
|
||||||
|
icon: 'fa-folder',
|
||||||
|
callback: (key, opt) => {
|
||||||
|
let moveFileId = _.toString($(opt.$trigger).data('uid'))
|
||||||
|
let moveFileDestFolder = _.nth(vueFile.folders, key)
|
||||||
|
vueFile.moveFile(moveFileId, moveFileDestFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let moveFolders = _.map(vueFile.folders, (f) => {
|
$.contextMenu('destroy', '.editor-modal-file-choices > figure')
|
||||||
return {
|
$.contextMenu({
|
||||||
name: (f !== '') ? f : '/ (root)',
|
selector: '.editor-modal-file-choices > figure',
|
||||||
icon: 'fa-folder',
|
appendTo: '.editor-modal-file-choices',
|
||||||
callback: (key, opt) => {
|
position: (opt, x, y) => {
|
||||||
let moveFileId = _.toString($(opt.$trigger).data('uid'));
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let moveFileDestFolder = _.nth(vueFile.folders, key);
|
let trigPos = $(opt.$trigger).position()
|
||||||
vueFile.moveFile(moveFileId, moveFileDestFolder);
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$.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) => {
|
$('#btn-editor-file-upload input').on('change', (ev) => {
|
||||||
|
let curFileAmount = vueFile.files.length
|
||||||
|
|
||||||
let curFileAmount = vueFile.files.length;
|
$(ev.currentTarget).simpleUpload('/uploads/file', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/file", {
|
name: 'binfile',
|
||||||
|
data: {
|
||||||
|
folder: vueFile.currentFolder
|
||||||
|
},
|
||||||
|
limit: 20,
|
||||||
|
expect: 'json',
|
||||||
|
maxFileSize: 0,
|
||||||
|
|
||||||
name: 'binfile',
|
init: (totalUploads) => {
|
||||||
data: {
|
vueFile.uploadSucceeded = false
|
||||||
folder: vueFile.currentFolder
|
vueFile.isLoadingText = 'Preparing to upload...'
|
||||||
},
|
vueFile.isLoading = true
|
||||||
limit: 20,
|
},
|
||||||
expect: 'json',
|
|
||||||
maxFileSize: 0,
|
|
||||||
|
|
||||||
init: (totalUploads) => {
|
progress: (progress) => {
|
||||||
vueFile.uploadSucceeded = false;
|
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
|
||||||
vueFile.isLoadingText = 'Preparing to upload...';
|
},
|
||||||
vueFile.isLoading = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
progress: (progress) => {
|
success: (data) => {
|
||||||
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
|
if (data.ok) {
|
||||||
},
|
let failedUpls = _.filter(data.results, ['ok', false])
|
||||||
|
if (failedUpls.length) {
|
||||||
|
_.forEach(failedUpls, (u) => {
|
||||||
|
alerts.pushError('Upload error', u.msg)
|
||||||
|
})
|
||||||
|
if (failedUpls.length < data.results.length) {
|
||||||
|
alerts.push({
|
||||||
|
title: 'Some uploads succeeded',
|
||||||
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
|
})
|
||||||
|
vueFile.uploadSucceeded = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vueFile.uploadSucceeded = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alerts.pushError('Upload error', data.msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
success: (data) => {
|
error: (error) => {
|
||||||
if(data.ok) {
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
|
},
|
||||||
|
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
finish: () => {
|
||||||
if(failedUpls.length) {
|
if (vueFile.uploadSucceeded) {
|
||||||
_.forEach(failedUpls, (u) => {
|
vueFile.waitChangeComplete(curFileAmount, true)
|
||||||
alerts.pushError('Upload error', u.msg);
|
} else {
|
||||||
});
|
vueFile.isLoading = false
|
||||||
if(failedUpls.length < data.results.length) {
|
}
|
||||||
alerts.push({
|
}
|
||||||
title: 'Some uploads succeeded',
|
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
|
||||||
});
|
|
||||||
vueFile.uploadSucceeded = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vueFile.uploadSucceeded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
})
|
||||||
alerts.pushError('Upload error', data.msg);
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
error: (error) => {
|
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
|
||||||
},
|
|
||||||
|
|
||||||
finish: () => {
|
|
||||||
if(vueFile.uploadSucceeded) {
|
|
||||||
vueFile.waitChangeComplete(curFileAmount, true);
|
|
||||||
} else {
|
|
||||||
vueFile.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,412 +1,396 @@
|
|||||||
|
|
||||||
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()) {
|
||||||
|
mde.codemirror.execCommand('singleSelection')
|
||||||
|
}
|
||||||
|
|
||||||
if(mde.codemirror.doc.somethingSelected()) {
|
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
|
||||||
mde.codemirror.execCommand('singleSelection');
|
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
|
||||||
}
|
selImage.titleGuess = _.startCase(selImage.basename)
|
||||||
|
|
||||||
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]);
|
let imageText = ''
|
||||||
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename;
|
switch (vueImage.currentAlign) {
|
||||||
selImage.titleGuess = _.startCase(selImage.basename);
|
case 'center':
|
||||||
|
imageText += '{.align-center}'
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
imageText += '{.align-right}'
|
||||||
|
break
|
||||||
|
case 'logo':
|
||||||
|
imageText += '{.pagelogo}'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
let imageText = '';
|
mde.codemirror.doc.replaceSelection(imageText)
|
||||||
switch(vueImage.currentAlign) {
|
vueImage.cancel()
|
||||||
case 'center':
|
},
|
||||||
imageText += '{.align-center}';
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
imageText += '{.align-right}';
|
|
||||||
break;
|
|
||||||
case 'logo':
|
|
||||||
imageText += '{.pagelogo}';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(imageText);
|
|
||||||
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]$')
|
||||||
|
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
|
||||||
|
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
||||||
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
|
vueImage.newFolderError = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if(_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
vueImage.newFolderDiscard()
|
||||||
vueImage.newFolderError = true;
|
vueImage.isLoadingText = 'Creating new folder...'
|
||||||
return;
|
vueImage.isLoading = true
|
||||||
}
|
|
||||||
|
|
||||||
vueImage.newFolderDiscard();
|
Vue.nextTick(() => {
|
||||||
vueImage.isLoadingText = 'Creating new folder...';
|
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
||||||
vueImage.isLoading = true;
|
vueImage.folders = data
|
||||||
|
vueImage.currentFolder = vueImage.newFolderName
|
||||||
Vue.nextTick(() => {
|
vueImage.images = []
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
vueImage.isLoading = false
|
||||||
vueImage.folders = data;
|
})
|
||||||
vueImage.currentFolder = vueImage.newFolderName;
|
})
|
||||||
vueImage.images = [];
|
},
|
||||||
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.isLoadingText = 'Fetching image...'
|
||||||
|
vueImage.isLoading = true
|
||||||
|
|
||||||
vueImage.fetchFromUrlDiscard();
|
Vue.nextTick(() => {
|
||||||
vueImage.isLoadingText = 'Fetching image...';
|
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
||||||
vueImage.isLoading = true;
|
if (data.ok) {
|
||||||
|
vueImage.waitChangeComplete(vueImage.images.length, true)
|
||||||
Vue.nextTick(() => {
|
} else {
|
||||||
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
vueImage.isLoading = false
|
||||||
if(data.ok) {
|
alerts.pushError('Upload error', data.msg)
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, true);
|
}
|
||||||
} else {
|
})
|
||||||
vueImage.isLoading = false;
|
})
|
||||||
alerts.pushError('Upload error', data.msg);
|
},
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// RENAME IMAGE
|
// RENAME IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameImage: () => {
|
renameImage: () => {
|
||||||
|
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
|
||||||
|
vueImage.renameImageFilename = c.basename || ''
|
||||||
|
vueImage.renameImageShow = true
|
||||||
|
_.delay(() => {
|
||||||
|
$('#txt-editor-image-rename').focus()
|
||||||
|
_.defer(() => { $('#txt-editor-image-rename').select() })
|
||||||
|
}, 400)
|
||||||
|
},
|
||||||
|
renameImageDiscard: () => {
|
||||||
|
vueImage.renameImageShow = false
|
||||||
|
},
|
||||||
|
renameImageGo: () => {
|
||||||
|
vueImage.renameImageDiscard()
|
||||||
|
vueImage.isLoadingText = 'Renaming image...'
|
||||||
|
vueImage.isLoading = true
|
||||||
|
|
||||||
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]);
|
Vue.nextTick(() => {
|
||||||
vueImage.renameImageFilename = c.basename || '';
|
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
||||||
vueImage.renameImageShow = true;
|
if (data.ok) {
|
||||||
_.delay(() => {
|
vueImage.waitChangeComplete(vueImage.images.length, false)
|
||||||
$('#txt-editor-image-rename').focus();
|
} else {
|
||||||
_.defer(() => { $('#txt-editor-image-rename').select(); });
|
vueImage.isLoading = false
|
||||||
}, 400);
|
alerts.pushError('Rename error', data.msg)
|
||||||
},
|
}
|
||||||
renameImageDiscard: () => {
|
})
|
||||||
vueImage.renameImageShow = false;
|
})
|
||||||
},
|
},
|
||||||
renameImageGo: () => {
|
|
||||||
|
|
||||||
vueImage.renameImageDiscard();
|
|
||||||
vueImage.isLoadingText = 'Renaming image...';
|
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
|
||||||
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
|
||||||
if(data.ok) {
|
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, false);
|
|
||||||
} else {
|
|
||||||
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.isLoadingText = 'Processing...'
|
||||||
|
|
||||||
vueImage.postUploadChecks++;
|
Vue.nextTick(() => {
|
||||||
vueImage.isLoadingText = 'Processing...';
|
vueImage.loadImages(true).then(() => {
|
||||||
|
if ((vueImage.images.length !== oldAmount) === expectChange) {
|
||||||
Vue.nextTick(() => {
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.loadImages(true).then(() => {
|
vueImage.isLoading = false
|
||||||
if((vueImage.images.length !== oldAmount) === expectChange) {
|
} else if (vueImage.postUploadChecks > 5) {
|
||||||
vueImage.postUploadChecks = 0;
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
} else if(vueImage.postUploadChecks > 5) {
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
vueImage.postUploadChecks = 0;
|
} else {
|
||||||
vueImage.isLoading = false;
|
_.delay(() => {
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
vueImage.waitChangeComplete(oldAmount, expectChange)
|
||||||
} else {
|
}, 1500)
|
||||||
_.delay(() => {
|
}
|
||||||
vueImage.waitChangeComplete(oldAmount, expectChange);
|
})
|
||||||
}, 1500);
|
})
|
||||||
}
|
},
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// IMAGE CONTEXT MENU
|
// IMAGE CONTEXT MENU
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
attachContextMenus: () => {
|
||||||
|
let moveFolders = _.map(vueImage.folders, (f) => {
|
||||||
|
return {
|
||||||
|
name: (f !== '') ? f : '/ (root)',
|
||||||
|
icon: 'fa-folder',
|
||||||
|
callback: (key, opt) => {
|
||||||
|
let moveImageId = _.toString($(opt.$trigger).data('uid'))
|
||||||
|
let moveImageDestFolder = _.nth(vueImage.folders, key)
|
||||||
|
vueImage.moveImage(moveImageId, moveImageDestFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let moveFolders = _.map(vueImage.folders, (f) => {
|
$.contextMenu('destroy', '.editor-modal-image-choices > figure')
|
||||||
return {
|
$.contextMenu({
|
||||||
name: (f !== '') ? f : '/ (root)',
|
selector: '.editor-modal-image-choices > figure',
|
||||||
icon: 'fa-folder',
|
appendTo: '.editor-modal-image-choices',
|
||||||
callback: (key, opt) => {
|
position: (opt, x, y) => {
|
||||||
let moveImageId = _.toString($(opt.$trigger).data('uid'));
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let moveImageDestFolder = _.nth(vueImage.folders, key);
|
let trigPos = $(opt.$trigger).position()
|
||||||
vueImage.moveImage(moveImageId, moveImageDestFolder);
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$.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) => {
|
$('#btn-editor-image-upload input').on('change', (ev) => {
|
||||||
|
let curImageAmount = vueImage.images.length
|
||||||
|
|
||||||
let curImageAmount = vueImage.images.length;
|
$(ev.currentTarget).simpleUpload('/uploads/img', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/img", {
|
name: 'imgfile',
|
||||||
|
data: {
|
||||||
|
folder: vueImage.currentFolder
|
||||||
|
},
|
||||||
|
limit: 20,
|
||||||
|
expect: 'json',
|
||||||
|
allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
|
||||||
|
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
||||||
|
maxFileSize: 3145728, // max 3 MB
|
||||||
|
|
||||||
name: 'imgfile',
|
init: (totalUploads) => {
|
||||||
data: {
|
vueImage.uploadSucceeded = false
|
||||||
folder: vueImage.currentFolder
|
vueImage.isLoadingText = 'Preparing to upload...'
|
||||||
},
|
vueImage.isLoading = true
|
||||||
limit: 20,
|
},
|
||||||
expect: 'json',
|
|
||||||
allowedExts: ["jpg", "jpeg", "gif", "png", "webp"],
|
|
||||||
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
|
||||||
maxFileSize: 3145728, // max 3 MB
|
|
||||||
|
|
||||||
init: (totalUploads) => {
|
progress: (progress) => {
|
||||||
vueImage.uploadSucceeded = false;
|
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
|
||||||
vueImage.isLoadingText = 'Preparing to upload...';
|
},
|
||||||
vueImage.isLoading = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
progress: (progress) => {
|
success: (data) => {
|
||||||
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
|
if (data.ok) {
|
||||||
},
|
let failedUpls = _.filter(data.results, ['ok', false])
|
||||||
|
if (failedUpls.length) {
|
||||||
|
_.forEach(failedUpls, (u) => {
|
||||||
|
alerts.pushError('Upload error', u.msg)
|
||||||
|
})
|
||||||
|
if (failedUpls.length < data.results.length) {
|
||||||
|
alerts.push({
|
||||||
|
title: 'Some uploads succeeded',
|
||||||
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
|
})
|
||||||
|
vueImage.uploadSucceeded = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vueImage.uploadSucceeded = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alerts.pushError('Upload error', data.msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
success: (data) => {
|
error: (error) => {
|
||||||
if(data.ok) {
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
|
},
|
||||||
|
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
finish: () => {
|
||||||
if(failedUpls.length) {
|
if (vueImage.uploadSucceeded) {
|
||||||
_.forEach(failedUpls, (u) => {
|
vueImage.waitChangeComplete(curImageAmount, true)
|
||||||
alerts.pushError('Upload error', u.msg);
|
} else {
|
||||||
});
|
vueImage.isLoading = false
|
||||||
if(failedUpls.length < data.results.length) {
|
}
|
||||||
alerts.push({
|
}
|
||||||
title: 'Some uploads succeeded',
|
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
|
||||||
});
|
|
||||||
vueImage.uploadSucceeded = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vueImage.uploadSucceeded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
})
|
||||||
alerts.pushError('Upload error', data.msg);
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
error: (error) => {
|
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
|
||||||
},
|
|
||||||
|
|
||||||
finish: () => {
|
|
||||||
if(vueImage.uploadSucceeded) {
|
|
||||||
vueImage.waitChangeComplete(curImageAmount, true);
|
|
||||||
} else {
|
|
||||||
vueImage.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,49 +1,47 @@
|
|||||||
|
|
||||||
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
|
||||||
|
|||||||
+195
-200
@@ -3,215 +3,210 @@
|
|||||||
// Markdown Editor
|
// Markdown Editor
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
if($('#mk-editor').length === 1) {
|
if ($('#mk-editor').length === 1) {
|
||||||
|
let mdeModalOpenState = false
|
||||||
|
let mdeCurrentEditor = null
|
||||||
|
|
||||||
let mdeModalOpenState = false;
|
Vue.filter('filesize', (v) => {
|
||||||
let mdeCurrentEditor = null;
|
return _.toUpper(filesize(v))
|
||||||
|
})
|
||||||
|
|
||||||
Vue.filter('filesize', (v) => {
|
// =include editor-image.js
|
||||||
return _.toUpper(filesize(v));
|
// =include editor-file.js
|
||||||
});
|
// =include editor-video.js
|
||||||
|
// =include editor-codeblock.js
|
||||||
|
|
||||||
//=include editor-image.js
|
var mde = new SimpleMDE({
|
||||||
//=include editor-file.js
|
autofocus: true,
|
||||||
//=include editor-video.js
|
autoDownloadFontAwesome: false,
|
||||||
//=include editor-codeblock.js
|
element: $('#mk-editor').get(0),
|
||||||
|
placeholder: 'Enter Markdown formatted content here...',
|
||||||
var mde = new SimpleMDE({
|
spellChecker: false,
|
||||||
autofocus: true,
|
status: false,
|
||||||
autoDownloadFontAwesome: false,
|
toolbar: [{
|
||||||
element: $("#mk-editor").get(0),
|
name: 'bold',
|
||||||
placeholder: 'Enter Markdown formatted content here...',
|
action: SimpleMDE.toggleBold,
|
||||||
spellChecker: false,
|
className: 'icon-bold',
|
||||||
status: false,
|
title: 'Bold'
|
||||||
toolbar: [{
|
},
|
||||||
name: "bold",
|
{
|
||||||
action: SimpleMDE.toggleBold,
|
name: 'italic',
|
||||||
className: "icon-bold",
|
action: SimpleMDE.toggleItalic,
|
||||||
title: "Bold",
|
className: 'icon-italic',
|
||||||
},
|
title: 'Italic'
|
||||||
{
|
},
|
||||||
name: "italic",
|
{
|
||||||
action: SimpleMDE.toggleItalic,
|
name: 'strikethrough',
|
||||||
className: "icon-italic",
|
action: SimpleMDE.toggleStrikethrough,
|
||||||
title: "Italic",
|
className: 'icon-strikethrough',
|
||||||
},
|
title: 'Strikethrough'
|
||||||
{
|
},
|
||||||
name: "strikethrough",
|
'|',
|
||||||
action: SimpleMDE.toggleStrikethrough,
|
{
|
||||||
className: "icon-strikethrough",
|
name: 'heading-1',
|
||||||
title: "Strikethrough",
|
action: SimpleMDE.toggleHeading1,
|
||||||
},
|
className: 'icon-header fa-header-x fa-header-1',
|
||||||
'|',
|
title: 'Big Heading'
|
||||||
{
|
},
|
||||||
name: "heading-1",
|
{
|
||||||
action: SimpleMDE.toggleHeading1,
|
name: 'heading-2',
|
||||||
className: "icon-header fa-header-x fa-header-1",
|
action: SimpleMDE.toggleHeading2,
|
||||||
title: "Big Heading",
|
className: 'icon-header fa-header-x fa-header-2',
|
||||||
},
|
title: 'Medium Heading'
|
||||||
{
|
},
|
||||||
name: "heading-2",
|
{
|
||||||
action: SimpleMDE.toggleHeading2,
|
name: 'heading-3',
|
||||||
className: "icon-header fa-header-x fa-header-2",
|
action: SimpleMDE.toggleHeading3,
|
||||||
title: "Medium Heading",
|
className: 'icon-header fa-header-x fa-header-3',
|
||||||
},
|
title: 'Small Heading'
|
||||||
{
|
},
|
||||||
name: "heading-3",
|
{
|
||||||
action: SimpleMDE.toggleHeading3,
|
name: 'quote',
|
||||||
className: "icon-header fa-header-x fa-header-3",
|
action: SimpleMDE.toggleBlockquote,
|
||||||
title: "Small Heading",
|
className: 'icon-quote-left',
|
||||||
},
|
title: 'Quote'
|
||||||
{
|
},
|
||||||
name: "quote",
|
'|',
|
||||||
action: SimpleMDE.toggleBlockquote,
|
{
|
||||||
className: "icon-quote-left",
|
name: 'unordered-list',
|
||||||
title: "Quote",
|
action: SimpleMDE.toggleUnorderedList,
|
||||||
},
|
className: 'icon-th-list',
|
||||||
'|',
|
title: 'Bullet List'
|
||||||
{
|
},
|
||||||
name: "unordered-list",
|
{
|
||||||
action: SimpleMDE.toggleUnorderedList,
|
name: 'ordered-list',
|
||||||
className: "icon-th-list",
|
action: SimpleMDE.toggleOrderedList,
|
||||||
title: "Bullet List",
|
className: 'icon-list-ol',
|
||||||
},
|
title: 'Numbered List'
|
||||||
{
|
},
|
||||||
name: "ordered-list",
|
'|',
|
||||||
action: SimpleMDE.toggleOrderedList,
|
{
|
||||||
className: "icon-list-ol",
|
name: 'link',
|
||||||
title: "Numbered List",
|
action: (editor) => {
|
||||||
},
|
/* if(!mdeModalOpenState) {
|
||||||
'|',
|
|
||||||
{
|
|
||||||
name: "link",
|
|
||||||
action: (editor) => {
|
|
||||||
/*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()) {
|
||||||
|
return alerts.pushError('Invalid selection', 'You must select at least 1 character first.')
|
||||||
|
}
|
||||||
|
let curSel = editor.codemirror.doc.getSelections()
|
||||||
|
curSel = _.map(curSel, (s) => {
|
||||||
|
return '`' + s + '`'
|
||||||
|
})
|
||||||
|
editor.codemirror.doc.replaceSelections(curSel)
|
||||||
|
},
|
||||||
|
className: 'icon-terminal',
|
||||||
|
title: 'Inline Code'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'code-block',
|
||||||
|
action: (editor) => {
|
||||||
|
if (!mdeModalOpenState) {
|
||||||
|
mdeModalOpenState = true
|
||||||
|
|
||||||
if(!editor.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
return alerts.pushError('Invalid selection','You must select at least 1 character first.');
|
vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
|
||||||
}
|
}
|
||||||
let curSel = editor.codemirror.doc.getSelections();
|
|
||||||
curSel = _.map(curSel, (s) => {
|
|
||||||
return '`' + s + '`';
|
|
||||||
});
|
|
||||||
editor.codemirror.doc.replaceSelections(curSel);
|
|
||||||
|
|
||||||
},
|
vueCodeBlock.open()
|
||||||
className: "icon-terminal",
|
}
|
||||||
title: "Inline Code",
|
},
|
||||||
},
|
className: 'icon-code',
|
||||||
{
|
title: 'Code Block'
|
||||||
name: "code-block",
|
},
|
||||||
action: (editor) => {
|
'|',
|
||||||
if(!mdeModalOpenState) {
|
{
|
||||||
mdeModalOpenState = true;
|
name: 'table',
|
||||||
|
action: (editor) => {
|
||||||
|
// todo
|
||||||
|
},
|
||||||
|
className: 'icon-table',
|
||||||
|
title: 'Insert Table'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'horizontal-rule',
|
||||||
|
action: SimpleMDE.drawHorizontalRule,
|
||||||
|
className: 'icon-minus2',
|
||||||
|
title: 'Horizontal Rule'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
shortcuts: {
|
||||||
|
'toggleBlockquote': null,
|
||||||
|
'toggleFullScreen': null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if(mde.codemirror.doc.somethingSelected()) {
|
// -> Save
|
||||||
vueCodeBlock.initContent = mde.codemirror.doc.getSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
vueCodeBlock.open();
|
let saveCurrentDocument = (ev) => {
|
||||||
|
$.ajax(window.location.href, {
|
||||||
|
data: {
|
||||||
|
markdown: mde.value()
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'PUT'
|
||||||
|
}).then((rData, rStatus, rXHR) => {
|
||||||
|
if (rData.ok) {
|
||||||
|
window.location.assign('/' + pageEntryPath)
|
||||||
|
} else {
|
||||||
|
alerts.pushError('Something went wrong', rData.error)
|
||||||
|
}
|
||||||
|
}, (rXHR, rStatus, err) => {
|
||||||
|
alerts.pushError('Something went wrong', 'Save operation failed.')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
|
||||||
},
|
saveCurrentDocument(ev)
|
||||||
className: "icon-code",
|
})
|
||||||
title: "Code Block",
|
|
||||||
},
|
|
||||||
'|',
|
|
||||||
{
|
|
||||||
name: "table",
|
|
||||||
action: (editor) => {
|
|
||||||
//todo
|
|
||||||
},
|
|
||||||
className: "icon-table",
|
|
||||||
title: "Insert Table",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "horizontal-rule",
|
|
||||||
action: SimpleMDE.drawHorizontalRule,
|
|
||||||
className: "icon-minus2",
|
|
||||||
title: "Horizontal Rule",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
shortcuts: {
|
|
||||||
"toggleBlockquote": null,
|
|
||||||
"toggleFullScreen": null
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//-> Save
|
|
||||||
|
|
||||||
let saveCurrentDocument = (ev) => {
|
|
||||||
$.ajax(window.location.href, {
|
|
||||||
data: {
|
|
||||||
markdown: mde.value()
|
|
||||||
},
|
|
||||||
dataType: 'json',
|
|
||||||
method: 'PUT'
|
|
||||||
}).then((rData, rStatus, rXHR) => {
|
|
||||||
if(rData.ok) {
|
|
||||||
window.location.assign('/' + pageEntryPath);
|
|
||||||
} else {
|
|
||||||
alerts.pushError('Something went wrong', rData.error);
|
|
||||||
}
|
|
||||||
}, (rXHR, rStatus, err) => {
|
|
||||||
alerts.pushError('Something went wrong', 'Save operation failed.');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.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()) {
|
|
||||||
case 's':
|
|
||||||
ev.preventDefault();
|
|
||||||
saveCurrentDocument(ev);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
$(window).bind('keydown', (ev) => {
|
||||||
|
if (ev.ctrlKey || ev.metaKey) {
|
||||||
|
switch (String.fromCharCode(ev.which).toLowerCase()) {
|
||||||
|
case 's':
|
||||||
|
ev.preventDefault()
|
||||||
|
saveCurrentDocument(ev)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,84 +1,81 @@
|
|||||||
"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]) {
|
if (vueHeader.searchmovearr[i]) {
|
||||||
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
|
window.location.assign('/' + vueHeader.searchmovearr[i]._id)
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchq = vueHeader.searchmovearr[i];
|
vueHeader.searchq = vueHeader.searchmovearr[i]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
},
|
moveDownSearch: () => {
|
||||||
moveDownSearch: () => {
|
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
||||||
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
vueHeader.searchmoveidx++
|
||||||
vueHeader.searchmoveidx++;
|
}
|
||||||
}
|
},
|
||||||
},
|
moveUpSearch: () => {
|
||||||
moveUpSearch: () => {
|
if (vueHeader.searchmoveidx > 0) {
|
||||||
if(vueHeader.searchmoveidx > 0) {
|
vueHeader.searchmoveidx--
|
||||||
vueHeader.searchmoveidx--;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
|
||||||
|
|
||||||
$('main').on('click', vueHeader.closeSearch);
|
|
||||||
|
|
||||||
|
$('main').on('click', vueHeader.closeSearch)
|
||||||
}
|
}
|
||||||
+13
-13
@@ -1,16 +1,16 @@
|
|||||||
|
|
||||||
function setInputSelection(input, startPos, endPos) {
|
function setInputSelection (input, startPos, endPos) {
|
||||||
input.focus();
|
input.focus()
|
||||||
if (typeof input.selectionStart != "undefined") {
|
if (typeof input.selectionStart !== 'undefined') {
|
||||||
input.selectionStart = startPos;
|
input.selectionStart = startPos
|
||||||
input.selectionEnd = endPos;
|
input.selectionEnd = endPos
|
||||||
} else if (document.selection && document.selection.createRange) {
|
} else if (document.selection && document.selection.createRange) {
|
||||||
// IE branch
|
// IE branch
|
||||||
input.select();
|
input.select()
|
||||||
var range = document.selection.createRange();
|
var range = document.selection.createRange()
|
||||||
range.collapse(true);
|
range.collapse(true)
|
||||||
range.moveEnd("character", endPos);
|
range.moveEnd('character', endPos)
|
||||||
range.moveStart("character", startPos);
|
range.moveStart('character', startPos)
|
||||||
range.select();
|
range.select()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
function makeSafePath(rawPath) {
|
function makeSafePath (rawPath) {
|
||||||
|
let rawParts = _.split(_.trim(rawPath), '/')
|
||||||
let rawParts = _.split(_.trim(rawPath), '/');
|
rawParts = _.map(rawParts, (r) => {
|
||||||
rawParts = _.map(rawParts, (r) => {
|
return _.kebabCase(_.deburr(_.trim(r)))
|
||||||
return _.kebabCase(_.deburr(_.trim(r)));
|
})
|
||||||
});
|
|
||||||
|
|
||||||
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/');
|
|
||||||
|
|
||||||
|
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
|
||||||
}
|
}
|
||||||
+4
-6
@@ -1,7 +1,5 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
jQuery( document ).ready(function( $ ) {
|
jQuery(document).ready(function ($) {
|
||||||
|
$('#login-user').focus()
|
||||||
$('#login-user').focus();
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+19
-21
@@ -1,29 +1,27 @@
|
|||||||
|
|
||||||
//-> Create New Document
|
// -> Create New Document
|
||||||
|
|
||||||
let suggestedCreatePath = currentBasePath + '/new-page';
|
let suggestedCreatePath = currentBasePath + '/new-page'
|
||||||
|
|
||||||
$('.btn-create-prompt').on('click', (ev) => {
|
$('.btn-create-prompt').on('click', (ev) => {
|
||||||
$('#txt-create-prompt').val(suggestedCreatePath);
|
$('#txt-create-prompt').val(suggestedCreatePath)
|
||||||
$('#modal-create-prompt').toggleClass('is-active');
|
$('#modal-create-prompt').toggleClass('is-active')
|
||||||
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length);
|
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
|
||||||
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden');
|
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#txt-create-prompt').on('keypress', (ev) => {
|
$('#txt-create-prompt').on('keypress', (ev) => {
|
||||||
if(ev.which === 13) {
|
if (ev.which === 13) {
|
||||||
$('.btn-create-go').trigger('click');
|
$('.btn-create-go').trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-create-go').on('click', (ev) => {
|
$('.btn-create-go').on('click', (ev) => {
|
||||||
|
let newDocPath = makeSafePath($('#txt-create-prompt').val())
|
||||||
let newDocPath = makeSafePath($('#txt-create-prompt').val());
|
if (_.isEmpty(newDocPath)) {
|
||||||
if(_.isEmpty(newDocPath)) {
|
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
|
||||||
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden');
|
} else {
|
||||||
} else {
|
$('#txt-create-prompt').parent().addClass('is-loading')
|
||||||
$('#txt-create-prompt').parent().addClass('is-loading');
|
window.location.assign('/create/' + newDocPath)
|
||||||
window.location.assign('/create/' + newDocPath);
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
+35
-38
@@ -1,49 +1,46 @@
|
|||||||
|
|
||||||
//-> 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())
|
||||||
|
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
||||||
|
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
|
||||||
|
} else {
|
||||||
|
$('#txt-move-prompt').parent().addClass('is-loading')
|
||||||
|
|
||||||
let newDocPath = makeSafePath($('#txt-move-prompt').val());
|
$.ajax(window.location.href, {
|
||||||
if(_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
data: {
|
||||||
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden');
|
move: newDocPath
|
||||||
} else {
|
},
|
||||||
$('#txt-move-prompt').parent().addClass('is-loading');
|
dataType: 'json',
|
||||||
|
method: 'PUT'
|
||||||
$.ajax(window.location.href, {
|
}).then((rData, rStatus, rXHR) => {
|
||||||
data: {
|
if (rData.ok) {
|
||||||
move: newDocPath
|
window.location.assign('/' + newDocPath)
|
||||||
},
|
} else {
|
||||||
dataType: 'json',
|
alerts.pushError('Something went wrong', rData.error)
|
||||||
method: 'PUT'
|
}
|
||||||
}).then((rData, rStatus, rXHR) => {
|
}, (rXHR, rStatus, err) => {
|
||||||
if(rData.ok) {
|
alerts.pushError('Something went wrong', 'Save operation failed.')
|
||||||
window.location.assign('/' + newDocPath);
|
})
|
||||||
} else {
|
}
|
||||||
alerts.pushError('Something went wrong', rData.error);
|
})
|
||||||
}
|
|
||||||
}, (rXHR, rStatus, err) => {
|
|
||||||
alerts.pushError('Something went wrong', 'Save operation failed.');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
+90
-96
@@ -1,102 +1,96 @@
|
|||||||
|
|
||||||
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'])
|
||||||
} else if($('#page-type-admin-users-edit').length) {
|
this.roleoverride = 'admin'
|
||||||
|
} else {
|
||||||
let vueEditUser = new Vue({
|
this.rights = usrData.rights
|
||||||
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
|
|
||||||
|
|
||||||
|
// =include ../modals/admin-users-delete.js
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if($('#page-type-create').length) {
|
if ($('#page-type-create').length) {
|
||||||
|
let pageEntryPath = $('#page-type-create').data('entrypath')
|
||||||
|
|
||||||
let pageEntryPath = $('#page-type-create').data('entrypath');
|
// -> Discard
|
||||||
|
|
||||||
//-> Discard
|
$('.btn-create-discard').on('click', (ev) => {
|
||||||
|
$('#modal-create-discard').toggleClass('is-active')
|
||||||
$('.btn-create-discard').on('click', (ev) => {
|
})
|
||||||
$('#modal-create-discard').toggleClass('is-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
|
// =include ../components/editor.js
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if($('#page-type-edit').length) {
|
if ($('#page-type-edit').length) {
|
||||||
|
let pageEntryPath = $('#page-type-edit').data('entrypath')
|
||||||
|
|
||||||
let pageEntryPath = $('#page-type-edit').data('entrypath');
|
// -> Discard
|
||||||
|
|
||||||
//-> Discard
|
$('.btn-edit-discard').on('click', (ev) => {
|
||||||
|
$('#modal-edit-discard').toggleClass('is-active')
|
||||||
$('.btn-edit-discard').on('click', (ev) => {
|
})
|
||||||
$('#modal-edit-discard').toggleClass('is-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
|
// =include ../components/editor.js
|
||||||
}
|
}
|
||||||
+12
-14
@@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
if($('#page-type-source').length) {
|
if ($('#page-type-source').length) {
|
||||||
|
var scEditor = ace.edit('source-display')
|
||||||
|
scEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
|
scEditor.getSession().setMode('ace/mode/markdown')
|
||||||
|
scEditor.setOption('fontSize', '14px')
|
||||||
|
scEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
|
scEditor.setOption('wrap', true)
|
||||||
|
scEditor.setReadOnly(true)
|
||||||
|
scEditor.renderer.updateFull()
|
||||||
|
|
||||||
var scEditor = ace.edit("source-display");
|
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
|
||||||
scEditor.setTheme("ace/theme/tomorrow_night");
|
|
||||||
scEditor.getSession().setMode("ace/mode/markdown");
|
|
||||||
scEditor.setOption('fontSize', '14px');
|
|
||||||
scEditor.setOption('hScrollBarAlwaysVisible', false);
|
|
||||||
scEditor.setOption('wrap', true);
|
|
||||||
scEditor.setReadOnly(true);
|
|
||||||
scEditor.renderer.updateFull();
|
|
||||||
|
|
||||||
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '';
|
|
||||||
|
|
||||||
//=include ../modals/create.js
|
|
||||||
//=include ../modals/move.js
|
|
||||||
|
|
||||||
|
// =include ../modals/create.js
|
||||||
|
// =include ../modals/move.js
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
|
|
||||||
if($('#page-type-view').length) {
|
if ($('#page-type-view').length) {
|
||||||
|
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
|
||||||
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '';
|
|
||||||
|
|
||||||
//=include ../modals/create.js
|
|
||||||
//=include ../modals/move.js
|
|
||||||
|
|
||||||
|
// =include ../modals/create.js
|
||||||
|
// =include ../modals/move.js
|
||||||
}
|
}
|
||||||
+115
-131
@@ -1,162 +1,146 @@
|
|||||||
"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) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(res.locals.isGuest) {
|
return db.User.findById(req.user.id).then((usr) => {
|
||||||
return res.render('error-forbidden');
|
usr.name = _.trim(req.body.name)
|
||||||
}
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
|
let nPwd = _.trim(req.body.password)
|
||||||
return db.User.findById(req.user.id).then((usr) => {
|
if (nPwd.length < 6) {
|
||||||
usr.name = _.trim(req.body.name);
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
if(usr.provider === 'local' && req.body.password !== '********') {
|
} else {
|
||||||
let nPwd = _.trim(req.body.password);
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
if(nPwd.length < 6) {
|
usr.password = pwd
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
return usr.save()
|
||||||
} else {
|
})
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
}
|
||||||
usr.password = pwd;
|
} else {
|
||||||
return usr.save();
|
return usr.save()
|
||||||
});
|
}
|
||||||
}
|
}).then(() => {
|
||||||
} else {
|
return res.json({ msg: 'OK' })
|
||||||
return usr.save();
|
}).catch((err) => {
|
||||||
}
|
res.status(400).json({ msg: err.message })
|
||||||
}).then(() => {
|
})
|
||||||
return res.json({ msg: 'OK' });
|
})
|
||||||
}).catch((err) => {
|
|
||||||
res.status(400).json({ msg: err.message });
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/stats', (req, res) => {
|
router.get('/stats', (req, res) => {
|
||||||
|
if (res.locals.isGuest) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(res.locals.isGuest) {
|
Promise.all([
|
||||||
return res.render('error-forbidden');
|
db.Entry.count(),
|
||||||
}
|
db.UplFile.count(),
|
||||||
|
db.User.count()
|
||||||
Promise.all([
|
]).spread((totalEntries, totalUploads, totalUsers) => {
|
||||||
db.Entry.count(),
|
return res.render('pages/admin/stats', {
|
||||||
db.UplFile.count(),
|
totalEntries, totalUploads, totalUsers, adminTab: 'stats'
|
||||||
db.User.count()
|
}) || true
|
||||||
]).spread((totalEntries, totalUploads, totalUsers) => {
|
}).catch((err) => {
|
||||||
return res.render('pages/admin/stats', {
|
throw err
|
||||||
totalEntries, totalUploads, totalUsers,
|
})
|
||||||
adminTab: 'stats'
|
})
|
||||||
}) || true;
|
|
||||||
}).catch((err) => {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users', (req, res) => {
|
router.get('/users', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.manage) {
|
db.User.find({})
|
||||||
return res.render('error-forbidden');
|
.select('-password -rights')
|
||||||
}
|
.sort('name email')
|
||||||
|
.exec().then((usrs) => {
|
||||||
db.User.find({})
|
res.render('pages/admin/users', { adminTab: 'users', usrs })
|
||||||
.select('-password -rights')
|
})
|
||||||
.sort('name email')
|
})
|
||||||
.exec().then((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) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.manage) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!validator.isMongoId(req.params.id)) {
|
db.User.findById(req.params.id)
|
||||||
return res.render('error-forbidden');
|
.select('-password -providerId')
|
||||||
}
|
.exec().then((usr) => {
|
||||||
|
let usrOpts = {
|
||||||
|
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
||||||
|
canChangeName: (usr.email !== 'guest'),
|
||||||
|
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
||||||
|
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
||||||
|
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
||||||
|
}
|
||||||
|
|
||||||
db.User.findById(req.params.id)
|
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
|
||||||
.select('-password -providerId')
|
})
|
||||||
.exec().then((usr) => {
|
})
|
||||||
|
|
||||||
let usrOpts = {
|
|
||||||
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
|
||||||
canChangeName: (usr.email !== 'guest'),
|
|
||||||
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
|
||||||
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
|
||||||
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
|
||||||
};
|
|
||||||
|
|
||||||
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/users/:id', (req, res) => {
|
router.post('/users/:id', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
|
return res.status(401).json({ msg: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.manage) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.status(401).json({ msg: 'Unauthorized' });
|
return res.status(400).json({ msg: 'Invalid User ID' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!validator.isMongoId(req.params.id)) {
|
return db.User.findById(req.params.id).then((usr) => {
|
||||||
return res.status(400).json({ msg: 'Invalid User ID' });
|
usr.name = _.trim(req.body.name)
|
||||||
}
|
usr.rights = JSON.parse(req.body.rights)
|
||||||
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
return db.User.findById(req.params.id).then((usr) => {
|
let nPwd = _.trim(req.body.password)
|
||||||
usr.name = _.trim(req.body.name);
|
if (nPwd.length < 6) {
|
||||||
usr.rights = JSON.parse(req.body.rights);
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
if(usr.provider === 'local' && req.body.password !== '********') {
|
} else {
|
||||||
let nPwd = _.trim(req.body.password);
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
if(nPwd.length < 6) {
|
usr.password = pwd
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
return usr.save()
|
||||||
} else {
|
})
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
}
|
||||||
usr.password = pwd;
|
} else {
|
||||||
return usr.save();
|
return usr.save()
|
||||||
});
|
}
|
||||||
}
|
}).then(() => {
|
||||||
} else {
|
return res.json({ msg: 'OK' })
|
||||||
return usr.save();
|
}).catch((err) => {
|
||||||
}
|
res.status(400).json({ msg: err.message })
|
||||||
}).then(() => {
|
})
|
||||||
return res.json({ msg: 'OK' });
|
})
|
||||||
}).catch((err) => {
|
|
||||||
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;
|
|
||||||
|
|||||||
+57
-57
@@ -1,80 +1,80 @@
|
|||||||
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) {
|
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||||
passport.authenticate('local', function(err, user, info) {
|
passport.authenticate('local', function (err, user, info) {
|
||||||
|
if (err) { return next(err) }
|
||||||
|
|
||||||
if (err) { return next(err); }
|
if (!user) {
|
||||||
|
req.flash('alert', {
|
||||||
|
title: 'Invalid login',
|
||||||
|
message: 'The email or password is invalid.'
|
||||||
|
})
|
||||||
|
return res.redirect('/login')
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
req.logIn(user, function (err) {
|
||||||
req.flash('alert', {
|
if (err) { return next(err) }
|
||||||
title: 'Invalid login',
|
|
||||||
message: "The email or password is invalid."
|
|
||||||
});
|
|
||||||
return res.redirect('/login');
|
|
||||||
}
|
|
||||||
|
|
||||||
req.logIn(user, function(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
|
||||||
|
|||||||
+175
-192
@@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
const express = require('express')
|
||||||
var router = express.Router();
|
const router = express.Router()
|
||||||
var _ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// EDIT MODE
|
// EDIT MODE
|
||||||
@@ -12,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) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.write) {
|
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
|
||||||
return res.render('error-forbidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
|
entries.fetchOriginal(safePath, {
|
||||||
|
parseMarkdown: false,
|
||||||
entries.fetchOriginal(safePath, {
|
parseMeta: true,
|
||||||
parseMarkdown: false,
|
parseTree: false,
|
||||||
parseMeta: true,
|
includeMarkdown: true,
|
||||||
parseTree: false,
|
includeParentInfo: false,
|
||||||
includeMarkdown: true,
|
cache: false
|
||||||
includeParentInfo: false,
|
}).then((pageData) => {
|
||||||
cache: false
|
if (pageData) {
|
||||||
}).then((pageData) => {
|
res.render('pages/edit', { pageData })
|
||||||
if(pageData) {
|
} else {
|
||||||
res.render('pages/edit', { pageData });
|
throw new Error('Invalid page path.')
|
||||||
} else {
|
}
|
||||||
throw new Error('Invalid page path.');
|
return true
|
||||||
}
|
}).catch((err) => {
|
||||||
return true;
|
res.render('error', {
|
||||||
}).catch((err) => {
|
message: err.message,
|
||||||
res.render('error', {
|
error: {}
|
||||||
message: err.message,
|
})
|
||||||
error: {}
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/edit/*', (req, res, next) => {
|
router.put('/edit/*', (req, res, next) => {
|
||||||
|
if (!res.locals.rights.write) {
|
||||||
|
return res.json({
|
||||||
|
ok: false,
|
||||||
|
error: 'Forbidden'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.write) {
|
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
|
||||||
return res.json({
|
|
||||||
ok: false,
|
|
||||||
error: 'Forbidden'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
|
entries.update(safePath, req.body.markdown).then(() => {
|
||||||
|
return res.json({
|
||||||
entries.update(safePath, req.body.markdown).then(() => {
|
ok: true
|
||||||
return res.json({
|
}) || true
|
||||||
ok: true
|
}).catch((err) => {
|
||||||
}) || true;
|
res.json({
|
||||||
}).catch((err) => {
|
ok: false,
|
||||||
res.json({
|
error: err.message
|
||||||
ok: false,
|
})
|
||||||
error: err.message
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// CREATE MODE
|
// CREATE MODE
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
router.get('/create/*', (req, res, next) => {
|
router.get('/create/*', (req, res, next) => {
|
||||||
|
if (!res.locals.rights.write) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.write) {
|
if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error', {
|
||||||
}
|
message: 'You cannot create a document with this name as it is reserved by the system.',
|
||||||
|
error: {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(_.some(['create','edit','account','source','history','mk'], (e) => { return _.startsWith(req.path, '/create/' + e); })) {
|
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
|
||||||
return res.render('error', {
|
|
||||||
message: 'You cannot create a document with this name as it is reserved by the system.',
|
|
||||||
error: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
|
entries.exists(safePath).then((docExists) => {
|
||||||
|
if (!docExists) {
|
||||||
|
return entries.getStarter(safePath).then((contents) => {
|
||||||
|
let pageData = {
|
||||||
|
markdown: contents,
|
||||||
|
meta: {
|
||||||
|
title: _.startCase(safePath),
|
||||||
|
path: safePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.render('pages/create', { pageData })
|
||||||
|
|
||||||
entries.exists(safePath).then((docExists) => {
|
return true
|
||||||
if(!docExists) {
|
}).catch((err) => {
|
||||||
return entries.getStarter(safePath).then((contents) => {
|
winston.warn(err)
|
||||||
|
throw new Error('Could not load starter content!')
|
||||||
let pageData = {
|
})
|
||||||
markdown: contents,
|
} else {
|
||||||
meta: {
|
throw new Error('This entry already exists!')
|
||||||
title: _.startCase(safePath),
|
}
|
||||||
path: safePath
|
}).catch((err) => {
|
||||||
}
|
res.render('error', {
|
||||||
};
|
message: err.message,
|
||||||
res.render('pages/create', { pageData });
|
error: {}
|
||||||
|
})
|
||||||
return true;
|
})
|
||||||
|
})
|
||||||
}).catch((err) => {
|
|
||||||
throw new Error('Could not load starter content!');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error('This entry already exists!');
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
res.render('error', {
|
|
||||||
message: err.message,
|
|
||||||
error: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/create/*', (req, res, next) => {
|
router.put('/create/*', (req, res, next) => {
|
||||||
|
if (!res.locals.rights.write) {
|
||||||
|
return res.json({
|
||||||
|
ok: false,
|
||||||
|
error: 'Forbidden'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.write) {
|
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
|
||||||
return res.json({
|
|
||||||
ok: false,
|
|
||||||
error: 'Forbidden'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
|
entries.create(safePath, req.body.markdown).then(() => {
|
||||||
|
return res.json({
|
||||||
entries.create(safePath, req.body.markdown).then(() => {
|
ok: true
|
||||||
return res.json({
|
}) || true
|
||||||
ok: true
|
}).catch((err) => {
|
||||||
}) || true;
|
return res.json({
|
||||||
}).catch((err) => {
|
ok: false,
|
||||||
return res.json({
|
error: err.message
|
||||||
ok: false,
|
})
|
||||||
error: err.message
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// VIEW MODE
|
// VIEW MODE
|
||||||
@@ -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, {
|
||||||
|
parseMarkdown: false,
|
||||||
entries.fetchOriginal(safePath, {
|
parseMeta: true,
|
||||||
parseMarkdown: false,
|
parseTree: false,
|
||||||
parseMeta: true,
|
includeMarkdown: true,
|
||||||
parseTree: false,
|
includeParentInfo: false,
|
||||||
includeMarkdown: true,
|
cache: false
|
||||||
includeParentInfo: false,
|
}).then((pageData) => {
|
||||||
cache: false
|
if (pageData) {
|
||||||
}).then((pageData) => {
|
res.render('pages/source', { pageData })
|
||||||
if(pageData) {
|
} else {
|
||||||
res.render('pages/source', { pageData });
|
throw new Error('Invalid page path.')
|
||||||
} else {
|
}
|
||||||
throw new Error('Invalid page path.');
|
return true
|
||||||
}
|
}).catch((err) => {
|
||||||
return true;
|
res.render('error', {
|
||||||
}).catch((err) => {
|
message: err.message,
|
||||||
res.render('error', {
|
error: {}
|
||||||
message: err.message,
|
})
|
||||||
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) => {
|
||||||
|
if (pageData) {
|
||||||
entries.fetch(safePath).then((pageData) => {
|
res.render('pages/view', { pageData })
|
||||||
if(pageData) {
|
} else {
|
||||||
res.render('pages/view', { pageData });
|
res.render('error-notexist', {
|
||||||
} else {
|
newpath: safePath
|
||||||
res.render('error-notexist', {
|
})
|
||||||
newpath: safePath
|
}
|
||||||
});
|
return true
|
||||||
}
|
}).error((err) => {
|
||||||
return true;
|
if (safePath === 'home') {
|
||||||
}).error((err) => {
|
res.render('pages/welcome')
|
||||||
|
} else {
|
||||||
if(safePath === 'home') {
|
res.render('error-notexist', {
|
||||||
res.render('pages/welcome');
|
message: err.message,
|
||||||
} else {
|
newpath: safePath
|
||||||
res.render('error-notexist', {
|
})
|
||||||
message: err.message,
|
}
|
||||||
newpath: safePath
|
}).catch((err) => {
|
||||||
});
|
res.render('error', {
|
||||||
}
|
message: err.message,
|
||||||
|
error: {}
|
||||||
}).catch((err) => {
|
})
|
||||||
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) {
|
||||||
|
return res.json({
|
||||||
|
ok: false,
|
||||||
|
error: 'Forbidden'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.write) {
|
let safePath = entries.parsePath(req.path)
|
||||||
return res.json({
|
|
||||||
ok: false,
|
|
||||||
error: 'Forbidden'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let safePath = entries.parsePath(req.path);
|
if (_.isEmpty(req.body.move)) {
|
||||||
|
return res.json({
|
||||||
|
ok: false,
|
||||||
|
error: 'Invalid document action call.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(_.isEmpty(req.body.move)) {
|
let safeNewPath = entries.parsePath(req.body.move)
|
||||||
return res.json({
|
|
||||||
ok: false,
|
|
||||||
error: 'Invalid document action call.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let safeNewPath = entries.parsePath(req.body.move);
|
entries.move(safePath, safeNewPath).then(() => {
|
||||||
|
res.json({
|
||||||
|
ok: true
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
res.json({
|
||||||
|
ok: false,
|
||||||
|
error: err.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
entries.move(safePath, safeNewPath).then(() => {
|
module.exports = router
|
||||||
res.json({
|
|
||||||
ok: true
|
|
||||||
});
|
|
||||||
}).catch((err) => {
|
|
||||||
res.json({
|
|
||||||
ok: false,
|
|
||||||
error: err.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|||||||
+129
-153
@@ -1,184 +1,160 @@
|
|||||||
"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]
|
||||||
|
if (!validPathThumbsRe.test(fileName)) {
|
||||||
|
return res.sendStatus(404).end()
|
||||||
|
}
|
||||||
|
|
||||||
let fileName = req.params[0];
|
// todo: Authentication-based access
|
||||||
if(!validPathThumbsRe.test(fileName)) {
|
|
||||||
return res.sendStatus(404).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: Authentication-based access
|
res.sendFile(fileName, {
|
||||||
|
root: lcdata.getThumbsPath(),
|
||||||
res.sendFile(fileName, {
|
dotfiles: 'deny'
|
||||||
root: lcdata.getThumbsPath(),
|
}, (err) => {
|
||||||
dotfiles: 'deny'
|
if (err) {
|
||||||
}, (err) => {
|
res.status(err.status).end()
|
||||||
if (err) {
|
}
|
||||||
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) => {
|
||||||
|
if (!destFolderPath) {
|
||||||
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
|
let destFilePath = ''
|
||||||
|
|
||||||
if(!destFolderPath) {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
destFilename = fname
|
||||||
return true;
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
}
|
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
return readChunk(f.path, 0, 262)
|
||||||
|
}).then((buf) => {
|
||||||
|
// -> Check MIME type by magic number
|
||||||
|
|
||||||
let destFilename = '';
|
let mimeInfo = fileType(buf)
|
||||||
let destFilePath = '';
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
|
return Promise.reject(new Error('Invalid file type.'))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}).then(() => {
|
||||||
|
// -> Move file to final destination
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
}).then(() => {
|
||||||
destFilename = fname;
|
return {
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
ok: true,
|
||||||
|
filename: destFilename,
|
||||||
return readChunk(f.path, 0, 262);
|
filesize: f.size
|
||||||
|
}
|
||||||
}).then((buf) => {
|
}).reflect()
|
||||||
|
}, {concurrency: 3}).then((results) => {
|
||||||
//-> Check MIME type by magic number
|
let uplResults = _.map(results, (r) => {
|
||||||
|
if (r.isFulfilled()) {
|
||||||
let mimeInfo = fileType(buf);
|
return r.value()
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
} else {
|
||||||
return Promise.reject(new Error('Invalid file type.'));
|
return {
|
||||||
}
|
ok: false,
|
||||||
return true;
|
msg: r.reason().message
|
||||||
|
}
|
||||||
}).then(() => {
|
}
|
||||||
|
})
|
||||||
//-> Move file to final destination
|
res.json({ ok: true, results: uplResults })
|
||||||
|
return true
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
}).catch((err) => {
|
||||||
|
res.json({ ok: false, msg: err.message })
|
||||||
}).then(() => {
|
return true
|
||||||
return {
|
})
|
||||||
ok: true,
|
})
|
||||||
filename: destFilename,
|
})
|
||||||
filesize: f.size
|
|
||||||
};
|
|
||||||
}).reflect();
|
|
||||||
|
|
||||||
}, {concurrency: 3}).then((results) => {
|
|
||||||
let uplResults = _.map(results, (r) => {
|
|
||||||
if(r.isFulfilled()) {
|
|
||||||
return r.value();
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
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) => {
|
||||||
|
if (!destFolderPath) {
|
||||||
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
|
let destFilePath = ''
|
||||||
|
|
||||||
if(!destFolderPath) {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
destFilename = fname
|
||||||
return true;
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
}
|
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
// -> Move file to final destination
|
||||||
|
|
||||||
let destFilename = '';
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
let destFilePath = '';
|
}).then(() => {
|
||||||
|
return {
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
ok: true,
|
||||||
|
filename: destFilename,
|
||||||
destFilename = fname;
|
filesize: f.size
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
}
|
||||||
|
}).reflect()
|
||||||
//-> Move file to final destination
|
}, {concurrency: 3}).then((results) => {
|
||||||
|
let uplResults = _.map(results, (r) => {
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
if (r.isFulfilled()) {
|
||||||
|
return r.value()
|
||||||
}).then(() => {
|
} else {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: false,
|
||||||
filename: destFilename,
|
msg: r.reason().message
|
||||||
filesize: f.size
|
}
|
||||||
};
|
}
|
||||||
}).reflect();
|
})
|
||||||
|
res.json({ ok: true, results: uplResults })
|
||||||
}, {concurrency: 3}).then((results) => {
|
return true
|
||||||
let uplResults = _.map(results, (r) => {
|
}).catch((err) => {
|
||||||
if(r.isFulfilled()) {
|
res.json({ ok: false, msg: err.message })
|
||||||
return r.value();
|
return true
|
||||||
} else {
|
})
|
||||||
return {
|
})
|
||||||
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.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
|
let fileName = req.params[0]
|
||||||
|
if (!validPathRe.test(fileName)) {
|
||||||
|
return res.sendStatus(404).end()
|
||||||
|
}
|
||||||
|
|
||||||
let fileName = req.params[0];
|
// todo: Authentication-based access
|
||||||
if(!validPathRe.test(fileName)) {
|
|
||||||
return res.sendStatus(404).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: Authentication-based access
|
res.sendFile(fileName, {
|
||||||
|
root: git.getRepoPath() + '/uploads/',
|
||||||
|
dotfiles: 'deny'
|
||||||
|
}, (err) => {
|
||||||
|
if (err) {
|
||||||
|
res.status(err.status).end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
res.sendFile(fileName, {
|
module.exports = router
|
||||||
root: git.getRepoPath() + '/uploads/',
|
|
||||||
dotfiles: 'deny'
|
|
||||||
}, (err) => {
|
|
||||||
if (err) {
|
|
||||||
res.status(err.status).end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|||||||
+58
-58
@@ -1,95 +1,95 @@
|
|||||||
"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
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|||||||
+152
-157
@@ -1,215 +1,210 @@
|
|||||||
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() {
|
gulp.task('dev', function () {
|
||||||
|
paths.css.includes.pop()
|
||||||
|
paths.css.includes.push('../core')
|
||||||
|
|
||||||
paths.css.includes.pop();
|
paths.fonts.pop()
|
||||||
paths.css.includes.push('../core');
|
paths.fonts.push('../core/core-client/fonts/**/*')
|
||||||
|
|
||||||
paths.fonts.pop();
|
return run('default')
|
||||||
paths.fonts.push('../core/core-client/fonts/**/*');
|
})
|
||||||
|
|
||||||
return run('default');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Creates deployment packages
|
* TASK - Creates deployment packages
|
||||||
*/
|
*/
|
||||||
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)
|
||||||
});
|
})
|
||||||
|
|||||||
+445
-493
@@ -1,500 +1,452 @@
|
|||||||
"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: []
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+194
-221
@@ -1,258 +1,231 @@
|
|||||||
"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)) {
|
||||||
|
self._repo.path = path.join(ROOTPATH, 'repo')
|
||||||
|
} else {
|
||||||
|
self._repo.path = appconfig.paths.repo
|
||||||
|
}
|
||||||
|
|
||||||
if(_.isEmpty(appconfig.paths.repo)) {
|
// -> Initialize repository
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo');
|
|
||||||
} else {
|
|
||||||
self._repo.path = appconfig.paths.repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> 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.email = appconfig.git.signature.email || 'user@example.com'
|
||||||
|
|
||||||
self._signature.name = appconfig.git.signature.name || 'Wiki';
|
return self
|
||||||
self._signature.email = appconfig.git.signature.email || 'user@example.com';
|
},
|
||||||
|
|
||||||
return self;
|
/**
|
||||||
|
* Initialize Git repository
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} Promise
|
||||||
|
*/
|
||||||
|
_initRepo (appconfig) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
},
|
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
|
||||||
|
|
||||||
/**
|
// -> Check if path is accessible
|
||||||
* Initialize Git repository
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Object} Promise
|
|
||||||
*/
|
|
||||||
_initRepo(appconfig) {
|
|
||||||
|
|
||||||
let self = this;
|
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
||||||
|
if (err.code !== 'EEXIST') {
|
||||||
|
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.')
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
self._git = new Git({ 'git-dir': self._repo.path })
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
|
// -> Check if path already contains a git working folder
|
||||||
|
|
||||||
//-> Check if path is accessible
|
return self._git.isRepo().then((isRepo) => {
|
||||||
|
self._repo.exists = isRepo
|
||||||
|
return (!isRepo) ? self._git.exec('init') : true
|
||||||
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
|
self._repo.exists = false
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
// Initialize remote
|
||||||
|
|
||||||
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
let urlObj = URL.parse(appconfig.git.url)
|
||||||
if(err.code !== 'EEXIST') {
|
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
|
||||||
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.');
|
self._url = URL.format(urlObj)
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
|
|
||||||
self._git = new Git({ 'git-dir': self._repo.path });
|
return self._git.exec('remote', 'show').then((cProc) => {
|
||||||
|
let out = cProc.stdout.toString()
|
||||||
|
if (_.includes(out, 'origin')) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return Promise.join(
|
||||||
|
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
||||||
|
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
||||||
|
).then(() => {
|
||||||
|
return self._git.exec('remote', ['add', 'origin', self._url])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
winston.error('[' + PROCNAME + '][GIT] Git remote error!')
|
||||||
|
throw err
|
||||||
|
}).then(() => {
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
//-> Check if path already contains a git working folder
|
/**
|
||||||
|
* Gets the repo path.
|
||||||
|
*
|
||||||
|
* @return {String} The repo path.
|
||||||
|
*/
|
||||||
|
getRepoPath () {
|
||||||
|
return this._repo.path || path.join(ROOTPATH, 'repo')
|
||||||
|
},
|
||||||
|
|
||||||
return self._git.isRepo().then((isRepo) => {
|
/**
|
||||||
self._repo.exists = isRepo;
|
* Sync with the remote repository
|
||||||
return (!isRepo) ? self._git.exec('init') : true;
|
*
|
||||||
}).catch((err) => {
|
* @return {Promise} Resolve on sync success
|
||||||
self._repo.exists = false;
|
*/
|
||||||
});
|
resync () {
|
||||||
|
let self = this
|
||||||
|
|
||||||
}).then(() => {
|
// Fetch
|
||||||
|
|
||||||
// Initialize remote
|
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
|
||||||
|
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Pull completed.')
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Check for changes
|
||||||
|
|
||||||
let urlObj = URL.parse(appconfig.git.url);
|
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
||||||
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '');
|
let out = cProc.stdout.toString()
|
||||||
self._url = URL.format(urlObj);
|
|
||||||
|
|
||||||
return self._git.exec('remote', 'show').then((cProc) => {
|
if (_.includes(out, 'commit')) {
|
||||||
let out = cProc.stdout.toString();
|
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
|
||||||
if(_.includes(out, 'origin')) {
|
return self._git.push('origin', self._repo.branch).then(() => {
|
||||||
return true;
|
return winston.info('[' + PROCNAME + '][GIT] Push completed.')
|
||||||
} else {
|
})
|
||||||
return Promise.join(
|
} else {
|
||||||
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
|
||||||
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
}
|
||||||
).then(() => {
|
|
||||||
return self._git.exec('remote', ['add', 'origin', self._url]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}).catch((err) => {
|
return true
|
||||||
winston.error('[' + PROCNAME + '][GIT] Git remote error!');
|
})
|
||||||
throw err;
|
})
|
||||||
}).then(() => {
|
.catch((err) => {
|
||||||
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.');
|
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
|
||||||
return true;
|
throw err
|
||||||
});
|
})
|
||||||
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Commits a document.
|
||||||
|
*
|
||||||
|
* @param {String} entryPath The entry path
|
||||||
|
* @return {Promise} Resolve on commit success
|
||||||
|
*/
|
||||||
|
commitDocument (entryPath) {
|
||||||
|
let self = this
|
||||||
|
let gitFilePath = entryPath + '.md'
|
||||||
|
let commitMsg = ''
|
||||||
|
|
||||||
/**
|
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
||||||
* Gets the repo path.
|
let out = cProc.stdout.toString()
|
||||||
*
|
return _.includes(out, gitFilePath)
|
||||||
* @return {String} The repo path.
|
}).then((isTracked) => {
|
||||||
*/
|
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
|
||||||
getRepoPath() {
|
return self._git.add(gitFilePath)
|
||||||
|
}).then(() => {
|
||||||
|
return self._git.commit(commitMsg).catch((err) => {
|
||||||
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
return this._repo.path || path.join(ROOTPATH, 'repo');
|
/**
|
||||||
|
* Move a document.
|
||||||
|
*
|
||||||
|
* @param {String} entryPath The current entry path
|
||||||
|
* @param {String} newEntryPath The new entry path
|
||||||
|
* @return {Promise<Boolean>} Resolve on success
|
||||||
|
*/
|
||||||
|
moveDocument (entryPath, newEntryPath) {
|
||||||
|
let self = this
|
||||||
|
let gitFilePath = entryPath + '.md'
|
||||||
|
let gitNewFilePath = newEntryPath + '.md'
|
||||||
|
|
||||||
},
|
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
||||||
|
let out = cProc.stdout.toString()
|
||||||
|
if (_.includes(out, 'fatal')) {
|
||||||
|
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
|
||||||
|
throw new Error(errorMsg)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync with the remote repository
|
* Commits uploads changes.
|
||||||
*
|
*
|
||||||
* @return {Promise} Resolve on sync success
|
* @param {String} msg The commit message
|
||||||
*/
|
* @return {Promise} Resolve on commit success
|
||||||
resync() {
|
*/
|
||||||
|
commitUploads (msg) {
|
||||||
|
let self = this
|
||||||
|
msg = msg || 'Uploads repository sync'
|
||||||
|
|
||||||
let self = this;
|
return self._git.add('uploads').then(() => {
|
||||||
|
return self._git.commit(msg).catch((err) => {
|
||||||
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch
|
}
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...');
|
|
||||||
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Pull completed.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!');
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
|
|
||||||
// Check for changes
|
|
||||||
|
|
||||||
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
|
||||||
let out = cProc.stdout.toString();
|
|
||||||
|
|
||||||
if(_.includes(out, 'commit')) {
|
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...');
|
|
||||||
return self._git.push('origin', self._repo.branch).then(() => {
|
|
||||||
return winston.info('[' + PROCNAME + '][GIT] Push completed.');
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!');
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits a document.
|
|
||||||
*
|
|
||||||
* @param {String} entryPath The entry path
|
|
||||||
* @return {Promise} Resolve on commit success
|
|
||||||
*/
|
|
||||||
commitDocument(entryPath) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
let gitFilePath = entryPath + '.md';
|
|
||||||
let commitMsg = '';
|
|
||||||
|
|
||||||
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
|
||||||
let out = cProc.stdout.toString();
|
|
||||||
return _.includes(out, gitFilePath);
|
|
||||||
}).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; }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move a document.
|
|
||||||
*
|
|
||||||
* @param {String} entryPath The current entry path
|
|
||||||
* @param {String} newEntryPath The new entry path
|
|
||||||
* @return {Promise<Boolean>} Resolve on success
|
|
||||||
*/
|
|
||||||
moveDocument(entryPath, newEntryPath) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
let gitFilePath = entryPath + '.md';
|
|
||||||
let gitNewFilePath = newEntryPath + '.md';
|
|
||||||
|
|
||||||
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
|
||||||
let out = cProc.stdout.toString();
|
|
||||||
if(_.includes(out, 'fatal')) {
|
|
||||||
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')));
|
|
||||||
throw new Error(errorMsg);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits uploads changes.
|
|
||||||
*
|
|
||||||
* @param {String} msg The commit message
|
|
||||||
* @return {Promise} Resolve on commit success
|
|
||||||
*/
|
|
||||||
commitUploads(msg) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
msg = msg || "Uploads repository sync";
|
|
||||||
|
|
||||||
return self._git.add('uploads').then(() => {
|
|
||||||
return self._git.commit(msg).catch((err) => {
|
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+14
-20
@@ -1,32 +1,26 @@
|
|||||||
"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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+139
-150
@@ -1,187 +1,176 @@
|
|||||||
"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._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
|
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
this.createBaseDirectories(appconfig)
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
this.initMulter(appconfig)
|
||||||
|
|
||||||
this.createBaseDirectories(appconfig);
|
return this
|
||||||
this.initMulter(appconfig);
|
},
|
||||||
|
|
||||||
return this;
|
/**
|
||||||
|
* Init Multer upload handlers
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {boolean} Void
|
||||||
|
*/
|
||||||
|
initMulter (appconfig) {
|
||||||
|
let maxFileSizes = {
|
||||||
|
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
||||||
|
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
||||||
|
}
|
||||||
|
|
||||||
},
|
// -> IMAGES
|
||||||
|
|
||||||
/**
|
this.uploadImgHandler = multer({
|
||||||
* Init Multer upload handlers
|
storage: multer.diskStorage({
|
||||||
*
|
destination: (req, f, cb) => {
|
||||||
* @param {Object} appconfig The application config
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
* @return {boolean} Void
|
}
|
||||||
*/
|
}),
|
||||||
initMulter(appconfig) {
|
fileFilter: (req, f, cb) => {
|
||||||
|
// -> Check filesize
|
||||||
|
|
||||||
let maxFileSizes = {
|
if (f.size > maxFileSizes.img) {
|
||||||
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
return cb(null, false)
|
||||||
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
}
|
||||||
};
|
|
||||||
|
|
||||||
//-> IMAGES
|
// -> Check MIME type (quick check only)
|
||||||
|
|
||||||
this.uploadImgHandler = multer({
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
||||||
storage: multer.diskStorage({
|
return cb(null, false)
|
||||||
destination: (req, f, cb) => {
|
}
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
fileFilter: (req, f, cb) => {
|
|
||||||
|
|
||||||
//-> Check filesize
|
cb(null, true)
|
||||||
|
}
|
||||||
|
}).array('imgfile', 20)
|
||||||
|
|
||||||
if(f.size > maxFileSizes.img) {
|
// -> FILES
|
||||||
return cb(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Check MIME type (quick check only)
|
this.uploadFileHandler = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, f, cb) => {
|
||||||
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fileFilter: (req, f, cb) => {
|
||||||
|
// -> Check filesize
|
||||||
|
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
if (f.size > maxFileSizes.file) {
|
||||||
return cb(null, false);
|
return cb(null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, true);
|
cb(null, true)
|
||||||
}
|
}
|
||||||
}).array('imgfile', 20);
|
}).array('binfile', 20)
|
||||||
|
|
||||||
//-> FILES
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
this.uploadFileHandler = multer({
|
/**
|
||||||
storage: multer.diskStorage({
|
* Creates a base directories (Synchronous).
|
||||||
destination: (req, f, cb) => {
|
*
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
* @param {Object} appconfig The application config
|
||||||
}
|
* @return {Void} Void
|
||||||
}),
|
*/
|
||||||
fileFilter: (req, f, cb) => {
|
createBaseDirectories (appconfig) {
|
||||||
|
winston.info('[SERVER] Checking data directories...')
|
||||||
|
|
||||||
//-> Check filesize
|
try {
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
|
||||||
|
|
||||||
if(f.size > maxFileSizes.file) {
|
if (os.type() !== 'Windows_NT') {
|
||||||
return cb(null, false);
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, true);
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
|
||||||
}
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
|
||||||
}).array('binfile', 20);
|
|
||||||
|
|
||||||
return true;
|
if (os.type() !== 'Windows_NT') {
|
||||||
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
winston.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
},
|
winston.info('[SERVER] Data and Repository directories are OK.')
|
||||||
|
|
||||||
/**
|
return
|
||||||
* Creates a base directories (Synchronous).
|
},
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
|
||||||
createBaseDirectories(appconfig) {
|
|
||||||
|
|
||||||
winston.info('[SERVER] Checking data directories...');
|
/**
|
||||||
|
* Gets the uploads path.
|
||||||
|
*
|
||||||
|
* @return {String} The uploads path.
|
||||||
|
*/
|
||||||
|
getUploadsPath () {
|
||||||
|
return this._uploadsPath
|
||||||
|
},
|
||||||
|
|
||||||
try {
|
/**
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data));
|
* Gets the thumbnails folder path.
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'));
|
*
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'));
|
* @return {String} The thumbs path.
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'));
|
*/
|
||||||
|
getThumbsPath () {
|
||||||
|
return this._uploadsThumbsPath
|
||||||
|
},
|
||||||
|
|
||||||
if(os.type() !== 'Windows_NT') {
|
/**
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644');
|
* Check if filename is valid and unique
|
||||||
}
|
*
|
||||||
|
* @param {String} f The filename
|
||||||
|
* @param {String} fld The containing folder
|
||||||
|
* @param {boolean} isImage Indicates if image
|
||||||
|
* @return {Promise<String>} Promise of the accepted filename
|
||||||
|
*/
|
||||||
|
validateUploadsFilename (f, fld, isImage) {
|
||||||
|
let fObj = path.parse(f)
|
||||||
|
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
|
||||||
|
let fext = _.toLower(fObj.ext)
|
||||||
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo));
|
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'));
|
fext = '.png'
|
||||||
|
}
|
||||||
|
|
||||||
if(os.type() !== 'Windows_NT') {
|
f = fname + fext
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
|
let fpath = path.resolve(this._uploadsPath, fld, f)
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
return fs.statAsync(fpath).then((s) => {
|
||||||
winston.error(err);
|
throw new Error('File ' + f + ' already exists.')
|
||||||
}
|
}).catch((err) => {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
winston.info('[SERVER] Data and Repository directories are OK.');
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the uploads path.
|
|
||||||
*
|
|
||||||
* @return {String} The uploads path.
|
|
||||||
*/
|
|
||||||
getUploadsPath() {
|
|
||||||
return this._uploadsPath;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the thumbnails folder path.
|
|
||||||
*
|
|
||||||
* @return {String} The thumbs path.
|
|
||||||
*/
|
|
||||||
getThumbsPath() {
|
|
||||||
return this._uploadsThumbsPath;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if filename is valid and unique
|
|
||||||
*
|
|
||||||
* @param {String} f The filename
|
|
||||||
* @param {String} fld The containing folder
|
|
||||||
* @param {boolean} isImage Indicates if image
|
|
||||||
* @return {Promise<String>} Promise of the accepted filename
|
|
||||||
*/
|
|
||||||
validateUploadsFilename(f, fld, isImage) {
|
|
||||||
|
|
||||||
let fObj = path.parse(f);
|
|
||||||
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
|
|
||||||
let fext = _.toLower(fObj.ext);
|
|
||||||
|
|
||||||
if(isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
|
||||||
fext = '.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
f = fname + fext;
|
|
||||||
let fpath = path.resolve(this._uploadsPath, fld, f);
|
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((s) => {
|
|
||||||
throw new Error('File ' + f + ' already exists.');
|
|
||||||
}).catch((err) => {
|
|
||||||
if(err.code === 'ENOENT') {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+247
-253
@@ -1,86 +1,85 @@
|
|||||||
"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 = [
|
|||||||
* @return {Array} TOC tree
|
* @return {Array} TOC tree
|
||||||
*/
|
*/
|
||||||
const parseTree = (content) => {
|
const parseTree = (content) => {
|
||||||
|
let tokens = md().parse(content, {})
|
||||||
|
let tocArray = []
|
||||||
|
|
||||||
let tokens = md().parse(content, {});
|
// -> Extract headings and their respective levels
|
||||||
let tocArray = [];
|
|
||||||
|
|
||||||
//-> Extract headings and their respective levels
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
|
if (tokens[i].type !== 'heading_close') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < tokens.length; i++) {
|
const heading = tokens[i - 1]
|
||||||
if (tokens[i].type !== "heading_close") {
|
const headingclose = tokens[i]
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = tokens[i - 1];
|
if (heading.type === 'inline') {
|
||||||
const heading_close = tokens[i];
|
let content = ''
|
||||||
|
let anchor = ''
|
||||||
|
if (heading.children && heading.children[0].type === 'link_open') {
|
||||||
|
content = heading.children[1].content
|
||||||
|
anchor = _.kebabCase(content)
|
||||||
|
} else {
|
||||||
|
content = heading.content
|
||||||
|
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ''))
|
||||||
|
}
|
||||||
|
|
||||||
if (heading.type === "inline") {
|
tocArray.push({
|
||||||
let content = "";
|
content,
|
||||||
let anchor = "";
|
anchor,
|
||||||
if (heading.children && heading.children[0].type === "link_open") {
|
level: +headingclose.tag.substr(1, 1)
|
||||||
content = heading.children[1].content;
|
})
|
||||||
anchor = _.kebabCase(content);
|
}
|
||||||
} else {
|
}
|
||||||
content = heading.content;
|
|
||||||
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
tocArray.push({
|
// -> Exclude levels deeper than 2
|
||||||
content,
|
|
||||||
anchor,
|
|
||||||
level: +heading_close.tag.substr(1, 1)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Exclude levels deeper than 2
|
_.remove(tocArray, (n) => { return n.level > 2 })
|
||||||
|
|
||||||
_.remove(tocArray, (n) => { return n.level > 2; });
|
// -> Build tree from flat array
|
||||||
|
|
||||||
//-> Build tree from flat array
|
return _.reduce(tocArray, (tree, v) => {
|
||||||
|
let treeLength = tree.length - 1
|
||||||
return _.reduce(tocArray, (tree, v) => {
|
if (v.level < 2) {
|
||||||
let treeLength = tree.length - 1;
|
tree.push({
|
||||||
if(v.level < 2) {
|
content: v.content,
|
||||||
tree.push({
|
anchor: v.anchor,
|
||||||
content: v.content,
|
nodes: []
|
||||||
anchor: v.anchor,
|
})
|
||||||
nodes: []
|
} else {
|
||||||
});
|
let lastNodeLevel = 1
|
||||||
} else {
|
let GetNodePath = (startPos) => {
|
||||||
let lastNodeLevel = 1;
|
lastNodeLevel++
|
||||||
let GetNodePath = (startPos) => {
|
if (_.isEmpty(startPos)) {
|
||||||
lastNodeLevel++;
|
startPos = 'nodes'
|
||||||
if(_.isEmpty(startPos)) {
|
}
|
||||||
startPos = 'nodes';
|
if (lastNodeLevel === v.level) {
|
||||||
}
|
return startPos
|
||||||
if(lastNodeLevel === v.level) {
|
} else {
|
||||||
return startPos;
|
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes')
|
||||||
} else {
|
}
|
||||||
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes');
|
}
|
||||||
}
|
let lastNodePath = GetNodePath()
|
||||||
};
|
let lastNode = _.get(tree[treeLength], lastNodePath)
|
||||||
let lastNodePath = GetNodePath();
|
if (lastNode) {
|
||||||
let lastNode = _.get(tree[treeLength], lastNodePath);
|
lastNode.push({
|
||||||
if(lastNode) {
|
content: v.content,
|
||||||
lastNode.push({
|
anchor: v.anchor,
|
||||||
content: v.content,
|
nodes: []
|
||||||
anchor: v.anchor,
|
})
|
||||||
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) => {
|
|||||||
* @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 cr = cheerio.load(output)
|
||||||
|
|
||||||
let output = mkdown.render(content);
|
// -> Check for empty first element
|
||||||
let cr = cheerio.load(output);
|
|
||||||
|
|
||||||
//-> Check for empty first element
|
let firstElm = cr.root().children().first()[0]
|
||||||
|
if (firstElm.type === 'tag' && firstElm.name === 'p') {
|
||||||
|
let firstElmChildren = firstElm.children
|
||||||
|
if (firstElmChildren.length < 1) {
|
||||||
|
firstElm.remove()
|
||||||
|
} else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
|
||||||
|
cr(firstElm).addClass('is-gapless')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let firstElm = cr.root().children().first()[0];
|
// -> Remove links in headers
|
||||||
if(firstElm.type === 'tag' && firstElm.name === 'p') {
|
|
||||||
let firstElmChildren = firstElm.children;
|
|
||||||
if(firstElmChildren.length < 1) {
|
|
||||||
firstElm.remove();
|
|
||||||
} else if(firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
|
|
||||||
cr(firstElm).addClass('is-gapless');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Remove links in headers
|
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
|
||||||
|
let txtLink = cr(elm).text()
|
||||||
|
cr(elm).replaceWith(txtLink)
|
||||||
|
})
|
||||||
|
|
||||||
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
|
// -> Re-attach blockquote styling classes to their parents
|
||||||
let txtLink = cr(elm).text();
|
|
||||||
cr(elm).replaceWith(txtLink);
|
|
||||||
});
|
|
||||||
|
|
||||||
//-> Re-attach blockquote styling classes to their parents
|
cr.root().children('blockquote').each((i, elm) => {
|
||||||
|
if (cr(elm).children().length > 0) {
|
||||||
|
let bqLastChild = cr(elm).children().last()[0]
|
||||||
|
let bqLastChildClasses = cr(bqLastChild).attr('class')
|
||||||
|
if (bqLastChildClasses && bqLastChildClasses.length > 0) {
|
||||||
|
cr(bqLastChild).removeAttr('class')
|
||||||
|
cr(elm).addClass(bqLastChildClasses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
cr.root().children('blockquote').each((i, elm) => {
|
// -> Enclose content below headers
|
||||||
if(cr(elm).children().length > 0) {
|
|
||||||
let bqLastChild = cr(elm).children().last()[0];
|
|
||||||
let bqLastChildClasses = cr(bqLastChild).attr('class');
|
|
||||||
if(bqLastChildClasses && bqLastChildClasses.length > 0) {
|
|
||||||
cr(bqLastChild).removeAttr('class');
|
|
||||||
cr(elm).addClass(bqLastChildClasses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//-> Enclose content below headers
|
cr('h2').each((i, elm) => {
|
||||||
|
let subH2Content = cr(elm).nextUntil('h1, h2')
|
||||||
|
cr(elm).after('<div class="indent-h2"></div>')
|
||||||
|
let subH2Container = cr(elm).next('.indent-h2')
|
||||||
|
_.forEach(subH2Content, (ch) => {
|
||||||
|
cr(subH2Container).append(ch)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
cr('h2').each((i, elm) => {
|
cr('h3').each((i, elm) => {
|
||||||
let subH2Content = cr(elm).nextUntil('h1, h2');
|
let subH3Content = cr(elm).nextUntil('h1, h2, h3')
|
||||||
cr(elm).after('<div class="indent-h2"></div>');
|
cr(elm).after('<div class="indent-h3"></div>')
|
||||||
let subH2Container = cr(elm).next('.indent-h2');
|
let subH3Container = cr(elm).next('.indent-h3')
|
||||||
_.forEach(subH2Content, (ch) => {
|
_.forEach(subH3Content, (ch) => {
|
||||||
cr(subH2Container).append(ch);
|
cr(subH3Container).append(ch)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
cr('h3').each((i, elm) => {
|
// Replace video links with embeds
|
||||||
let subH3Content = cr(elm).nextUntil('h1, h2, h3');
|
|
||||||
cr(elm).after('<div class="indent-h3"></div>');
|
|
||||||
let subH3Container = cr(elm).next('.indent-h3');
|
|
||||||
_.forEach(subH3Content, (ch) => {
|
|
||||||
cr(subH3Container).append(ch);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace video links with embeds
|
_.forEach(videoRules, (vrule) => {
|
||||||
|
cr(vrule.selector).each((i, elm) => {
|
||||||
|
let originLink = cr(elm).attr('href')
|
||||||
|
if (vrule.regexp) {
|
||||||
|
let vidMatches = originLink.match(vrule.regexp)
|
||||||
|
if ((vidMatches && _.isArray(vidMatches))) {
|
||||||
|
vidMatches = _.filter(vidMatches, (f) => {
|
||||||
|
return f && _.isString(f)
|
||||||
|
})
|
||||||
|
originLink = _.last(vidMatches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let processedLink = _.replace(vrule.output, '{0}', originLink)
|
||||||
|
cr(elm).replaceWith(processedLink)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
_.forEach(videoRules, (vrule) => {
|
output = cr.html()
|
||||||
cr(vrule.selector).each((i, elm) => {
|
|
||||||
let originLink = cr(elm).attr('href');
|
|
||||||
if(vrule.regexp) {
|
|
||||||
let vidMatches = originLink.match(vrule.regexp);
|
|
||||||
if((vidMatches && _.isArray(vidMatches))) {
|
|
||||||
vidMatches = _.filter(vidMatches, (f) => {
|
|
||||||
return f && _.isString(f);
|
|
||||||
});
|
|
||||||
originLink = _.last(vidMatches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let processedLink = _.replace(vrule.output, '{0}', originLink);
|
|
||||||
cr(elm).replaceWith(processedLink);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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) => {
|
|||||||
* @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()
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|||||||
+248
-285
@@ -1,292 +1,255 @@
|
|||||||
"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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+235
-263
@@ -1,308 +1,280 @@
|
|||||||
"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._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
|
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
return this
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
},
|
||||||
|
|
||||||
return this;
|
/**
|
||||||
|
* Gets the thumbnails folder path.
|
||||||
|
*
|
||||||
|
* @return {String} The thumbs path.
|
||||||
|
*/
|
||||||
|
getThumbsPath () {
|
||||||
|
return this._uploadsThumbsPath
|
||||||
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Gets the uploads folders.
|
||||||
|
*
|
||||||
|
* @return {Array<String>} The uploads folders.
|
||||||
|
*/
|
||||||
|
getUploadsFolders () {
|
||||||
|
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
||||||
|
return (results) ? _.map(results, 'name') : [{ name: '' }]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the thumbnails folder path.
|
* Creates an uploads folder.
|
||||||
*
|
*
|
||||||
* @return {String} The thumbs path.
|
* @param {String} folderName The folder name
|
||||||
*/
|
* @return {Promise} Promise of the operation
|
||||||
getThumbsPath() {
|
*/
|
||||||
return this._uploadsThumbsPath;
|
createUploadsFolder (folderName) {
|
||||||
},
|
let self = this
|
||||||
|
|
||||||
/**
|
folderName = _.kebabCase(_.trim(folderName))
|
||||||
* Gets the uploads folders.
|
|
||||||
*
|
|
||||||
* @return {Array<String>} The uploads folders.
|
|
||||||
*/
|
|
||||||
getUploadsFolders() {
|
|
||||||
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
|
||||||
return (results) ? _.map(results, 'name') : [{ name: '' }];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
||||||
* Creates an uploads folder.
|
return Promise.resolve(self.getUploadsFolders())
|
||||||
*
|
}
|
||||||
* @param {String} folderName The folder name
|
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
|
||||||
createUploadsFolder(folderName) {
|
|
||||||
|
|
||||||
let self = this;
|
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
||||||
|
return db.UplFolder.findOneAndUpdate({
|
||||||
|
_id: 'f:' + folderName
|
||||||
|
}, {
|
||||||
|
name: folderName
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
return self.getUploadsFolders()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
folderName = _.kebabCase(_.trim(folderName));
|
/**
|
||||||
|
* Check if folder is valid and exists
|
||||||
|
*
|
||||||
|
* @param {String} folderName The folder name
|
||||||
|
* @return {Boolean} True if valid
|
||||||
|
*/
|
||||||
|
validateUploadsFolder (folderName) {
|
||||||
|
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
||||||
|
return (f) ? path.resolve(this._uploadsPath, folderName) : false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
if(_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
/**
|
||||||
return Promise.resolve(self.getUploadsFolders());
|
* Adds one or more uploads files.
|
||||||
}
|
*
|
||||||
|
* @param {Array<Object>} arrFiles The uploads files
|
||||||
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
|
addUploadsFiles (arrFiles) {
|
||||||
|
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
||||||
|
// this._uploadsDb.Files.insert(arrFiles);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
|
||||||
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
/**
|
||||||
return db.UplFolder.findOneAndUpdate({
|
* Gets the uploads files.
|
||||||
_id: 'f:' + folderName
|
*
|
||||||
}, {
|
* @param {String} cat Category type
|
||||||
name: folderName
|
* @param {String} fld Folder
|
||||||
}, {
|
* @return {Array<Object>} The files matching the query
|
||||||
upsert: true
|
*/
|
||||||
});
|
getUploadsFiles (cat, fld) {
|
||||||
}).then(() => {
|
return db.UplFile.find({
|
||||||
return self.getUploadsFolders();
|
category: cat,
|
||||||
});
|
folder: 'f:' + fld
|
||||||
|
}).sort('filename').exec()
|
||||||
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Deletes an uploads file.
|
||||||
|
*
|
||||||
|
* @param {string} uid The file unique ID
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
|
deleteUploadsFile (uid) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
/**
|
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
||||||
* Check if folder is valid and exists
|
if (f) {
|
||||||
*
|
return self.deleteUploadsFileTry(f, 0)
|
||||||
* @param {String} folderName The folder name
|
}
|
||||||
* @return {Boolean} True if valid
|
return true
|
||||||
*/
|
})
|
||||||
validateUploadsFolder(folderName) {
|
},
|
||||||
|
|
||||||
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
deleteUploadsFileTry (f, attempt) {
|
||||||
return (f) ? path.resolve(this._uploadsPath, folderName) : false;
|
let self = this
|
||||||
});
|
|
||||||
|
|
||||||
},
|
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
|
||||||
|
|
||||||
/**
|
return Promise.join(
|
||||||
* Adds one or more uploads files.
|
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
||||||
*
|
fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
|
||||||
* @param {Array<Object>} arrFiles The uploads files
|
).catch((err) => {
|
||||||
* @return {Void} Void
|
if (err.code === 'EBUSY' && attempt < 5) {
|
||||||
*/
|
return Promise.delay(100).then(() => {
|
||||||
addUploadsFiles(arrFiles) {
|
return self.deleteUploadsFileTry(f, attempt + 1)
|
||||||
if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
})
|
||||||
//this._uploadsDb.Files.insert(arrFiles);
|
} else {
|
||||||
}
|
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
|
||||||
return;
|
return true
|
||||||
},
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the uploads files.
|
* Downloads a file from url.
|
||||||
*
|
*
|
||||||
* @param {String} cat Category type
|
* @param {String} fFolder The folder
|
||||||
* @param {String} fld Folder
|
* @param {String} fUrl The full URL
|
||||||
* @return {Array<Object>} The files matching the query
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
getUploadsFiles(cat, fld) {
|
downloadFromUrl (fFolder, fUrl) {
|
||||||
|
let fUrlObj = url.parse(fUrl)
|
||||||
|
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
|
||||||
|
let destFolder = _.chain(fFolder).trim().toLower().value()
|
||||||
|
|
||||||
return db.UplFile.find({
|
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
category: cat,
|
if (!destFolderPath) {
|
||||||
folder: 'f:' + fld
|
return Promise.reject(new Error('Invalid Folder'))
|
||||||
}).sort('filename').exec();
|
}
|
||||||
|
|
||||||
},
|
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
||||||
|
let destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
/**
|
return new Promise((resolve, reject) => {
|
||||||
* Deletes an uploads file.
|
let rq = request({
|
||||||
*
|
url: fUrl,
|
||||||
* @param {string} uid The file unique ID
|
method: 'GET',
|
||||||
* @return {Promise} Promise of the operation
|
followRedirect: true,
|
||||||
*/
|
maxRedirects: 5,
|
||||||
deleteUploadsFile(uid) {
|
timeout: 10000
|
||||||
|
})
|
||||||
|
|
||||||
let self = this;
|
let destFileStream = fs.createWriteStream(destFilePath)
|
||||||
|
let curFileSize = 0
|
||||||
|
|
||||||
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
rq.on('data', (data) => {
|
||||||
if(f) {
|
curFileSize += data.length
|
||||||
return self.deleteUploadsFileTry(f, 0);
|
if (curFileSize > maxDownloadFileSize) {
|
||||||
}
|
rq.abort()
|
||||||
return true;
|
destFileStream.destroy()
|
||||||
});
|
fs.remove(destFilePath)
|
||||||
},
|
reject(new Error('Remote file is too large!'))
|
||||||
|
}
|
||||||
|
}).on('error', (err) => {
|
||||||
|
destFileStream.destroy()
|
||||||
|
fs.remove(destFilePath)
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
|
||||||
deleteUploadsFileTry(f, attempt) {
|
destFileStream.on('finish', () => {
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
|
||||||
let self = this;
|
rq.pipe(destFileStream)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './';
|
/**
|
||||||
|
* Move/Rename a file
|
||||||
|
*
|
||||||
|
* @param {String} uid The file ID
|
||||||
|
* @param {String} fld The destination folder
|
||||||
|
* @param {String} nFilename The new filename (optional)
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
|
moveUploadsFile (uid, fld, nFilename) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
return Promise.join(
|
return db.UplFolder.findById('f:' + fld).then((folder) => {
|
||||||
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
if (folder) {
|
||||||
fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
|
return db.UplFile.findById(uid).then((originFile) => {
|
||||||
).catch((err) => {
|
// -> Check if rename is valid
|
||||||
if(err.code === 'EBUSY' && attempt < 5) {
|
|
||||||
return Promise.delay(100).then(() => {
|
|
||||||
return self.deleteUploadsFileTry(f, attempt + 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return nameCheck.then((destFilename) => {
|
||||||
* Downloads a file from url.
|
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
|
||||||
*
|
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
|
||||||
* @param {String} fFolder The folder
|
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
|
||||||
* @param {String} fUrl The full URL
|
let preMoveOps = []
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
|
||||||
downloadFromUrl(fFolder, fUrl) {
|
|
||||||
|
|
||||||
let self = this;
|
// -> Check for invalid operations
|
||||||
|
|
||||||
let fUrlObj = url.parse(fUrl);
|
if (sourceFilePath === destFilePath) {
|
||||||
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'));
|
return Promise.reject(new Error('Invalid Operation!'))
|
||||||
let destFolder = _.chain(fFolder).trim().toLower().value();
|
}
|
||||||
|
|
||||||
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
// -> Delete DB entry
|
||||||
|
|
||||||
if(!destFolderPath) {
|
preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
|
||||||
return Promise.reject(new Error('Invalid Folder'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
// -> Move thumbnail ahead to avoid re-generation
|
||||||
|
|
||||||
let destFilePath = path.resolve(destFolderPath, destFilename);
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
// -> Proceed to move actual file
|
||||||
|
|
||||||
let rq = request({
|
return Promise.all(preMoveOps).then(() => {
|
||||||
url: fUrl,
|
return fs.moveAsync(sourceFilePath, destFilePath, {
|
||||||
method: 'GET',
|
clobber: false
|
||||||
followRedirect: true,
|
})
|
||||||
maxRedirects: 5,
|
})
|
||||||
timeout: 10000
|
})
|
||||||
});
|
})
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('Invalid Destination Folder'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let destFileStream = fs.createWriteStream(destFilePath);
|
}
|
||||||
let curFileSize = 0;
|
|
||||||
|
|
||||||
rq.on('data', (data) => {
|
|
||||||
curFileSize += data.length;
|
|
||||||
if(curFileSize > maxDownloadFileSize) {
|
|
||||||
rq.abort();
|
|
||||||
destFileStream.destroy();
|
|
||||||
fs.remove(destFilePath);
|
|
||||||
reject(new Error('Remote file is too large!'));
|
|
||||||
}
|
|
||||||
}).on('error', (err) => {
|
|
||||||
destFileStream.destroy();
|
|
||||||
fs.remove(destFilePath);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
destFileStream.on('finish', () => {
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
rq.pipe(destFileStream);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move/Rename a file
|
|
||||||
*
|
|
||||||
* @param {String} uid The file ID
|
|
||||||
* @param {String} fld The destination folder
|
|
||||||
* @param {String} nFilename The new filename (optional)
|
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
|
||||||
moveUploadsFile(uid, fld, nFilename) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nameCheck.then((destFilename) => {
|
|
||||||
|
|
||||||
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './';
|
|
||||||
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename);
|
|
||||||
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename);
|
|
||||||
let preMoveOps = [];
|
|
||||||
|
|
||||||
//-> Check for invalid operations
|
|
||||||
|
|
||||||
if(sourceFilePath === destFilePath) {
|
|
||||||
return Promise.reject(new Error('Invalid Operation!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Delete DB entry
|
|
||||||
|
|
||||||
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'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+18
-21
@@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
const moment = require('moment-timezone')
|
||||||
moment = require('moment-timezone');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication middleware
|
* Authentication middleware
|
||||||
@@ -12,29 +11,27 @@ var Promise = require('bluebird'),
|
|||||||
* @return {any} void
|
* @return {any} void
|
||||||
*/
|
*/
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
|
// Is user authenticated ?
|
||||||
|
|
||||||
// Is user authenticated ?
|
if (!req.isAuthenticated()) {
|
||||||
|
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();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flash middleware
|
* Flash middleware
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
* @return {any} void
|
* @return {any} void
|
||||||
*/
|
*/
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
|
res.locals.appflash = req.flash('alert')
|
||||||
|
|
||||||
res.locals.appflash = req.flash('alert');
|
next()
|
||||||
|
}
|
||||||
next();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+15
-15
@@ -1,3 +1,5 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security Middleware
|
* Security Middleware
|
||||||
*
|
*
|
||||||
@@ -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
|
||||||
|
app.disable('x-powered-by')
|
||||||
|
|
||||||
//-> Disable X-Powered-By
|
// -> Disable Frame Embedding
|
||||||
app.disable('x-powered-by');
|
res.set('X-Frame-Options', 'deny')
|
||||||
|
|
||||||
//-> Disable Frame Embedding
|
// -> Re-enable XSS Fitler if disabled
|
||||||
res.set('X-Frame-Options', 'deny');
|
res.set('X-XSS-Protection', '1; mode=block')
|
||||||
|
|
||||||
//-> Re-enable XSS Fitler if disabled
|
// -> Disable MIME-sniffing
|
||||||
res.set('X-XSS-Protection', '1; mode=block');
|
res.set('X-Content-Type-Options', 'nosniff')
|
||||||
|
|
||||||
//-> Disable MIME-sniffing
|
// -> Disable IE Compatibility Mode
|
||||||
res.set('X-Content-Type-Options', 'nosniff');
|
res.set('X-UA-Compatible', 'IE=edge')
|
||||||
|
|
||||||
//-> Disable IE Compatibility Mode
|
return next()
|
||||||
res.set('X-UA-Compatible', 'IE=edge');
|
}
|
||||||
|
|
||||||
return next();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
+10
-10
@@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BruteForce schema
|
* BruteForce schema
|
||||||
@@ -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)
|
||||||
|
|||||||
+7
-10
@@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry schema
|
* Entry schema
|
||||||
@@ -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({
|
|||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: {}
|
timestamps: {}
|
||||||
});
|
})
|
||||||
|
|
||||||
entrySchema.index({
|
entrySchema.index({
|
||||||
_id: 'text',
|
_id: 'text',
|
||||||
@@ -48,6 +45,6 @@ entrySchema.index({
|
|||||||
content: 1
|
content: 1
|
||||||
},
|
},
|
||||||
name: 'EntriesTextIndex'
|
name: 'EntriesTextIndex'
|
||||||
});
|
})
|
||||||
|
|
||||||
module.exports = Mongoose.model('Entry', entrySchema);
|
module.exports = Mongoose.model('Entry', entrySchema)
|
||||||
|
|||||||
+4
-10
@@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload File schema
|
* Upload File schema
|
||||||
@@ -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({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
}, { timestamps: {} })
|
||||||
{
|
|
||||||
timestamps: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('UplFile', uplFileSchema);
|
module.exports = Mongoose.model('UplFile', uplFileSchema)
|
||||||
|
|||||||
+4
-10
@@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload Folder schema
|
* Upload Folder schema
|
||||||
@@ -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)
|
||||||
|
|||||||
+61
-66
@@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
bcrypt = require('bcryptjs-then'),
|
const bcrypt = require('bcryptjs-then')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Region schema
|
* Region schema
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,122 +1,124 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
// ===========================================
|
// ===========================================
|
||||||
// Wiki.js
|
// Wiki.js
|
||||||
// 1.0.0
|
// 1.0.0
|
||||||
// Licensed under AGPLv3
|
// Licensed under AGPLv3
|
||||||
// ===========================================
|
// ===========================================
|
||||||
|
|
||||||
global.PROCNAME = 'SERVER';
|
global.PROCNAME = 'SERVER'
|
||||||
global.ROOTPATH = __dirname;
|
global.ROOTPATH = __dirname
|
||||||
global.IS_DEBUG = process.env.NODE_ENV === 'development';
|
global.IS_DEBUG = process.env.NODE_ENV === 'development'
|
||||||
if(IS_DEBUG) {
|
if (IS_DEBUG) {
|
||||||
global.CORE_PATH = ROOTPATH + '/../core/';
|
global.CORE_PATH = ROOTPATH + '/../core/'
|
||||||
} else {
|
} else {
|
||||||
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/';
|
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.env.VIPS_WARNING = false
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load Winston
|
// Load Winston
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG);
|
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
|
||||||
winston.info('[SERVER] Wiki.js is initializing...');
|
winston.info('[SERVER] Wiki.js is initializing...')
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load global modules
|
// Load global modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
let appconf = require(CORE_PATH + 'core-libs/config')();
|
let appconf = require(CORE_PATH + 'core-libs/config')()
|
||||||
global.appconfig = appconf.config;
|
global.appconfig = appconf.config
|
||||||
global.appdata = appconf.data;
|
global.appdata = appconf.data
|
||||||
global.lcdata = require('./libs/local').init();
|
global.lcdata = require('./libs/local').init()
|
||||||
global.db = require(CORE_PATH + 'core-libs/mongodb').init();
|
global.db = require(CORE_PATH + 'core-libs/mongodb').init()
|
||||||
global.entries = require('./libs/entries').init();
|
global.entries = require('./libs/entries').init()
|
||||||
global.git = require('./libs/git').init(false);
|
global.git = require('./libs/git').init(false)
|
||||||
global.lang = require('i18next');
|
global.lang = require('i18next')
|
||||||
global.mark = require('./libs/markdown');
|
global.mark = require('./libs/markdown')
|
||||||
global.upl = require('./libs/uploads').init();
|
global.upl = require('./libs/uploads').init()
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Load modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
const _ = require('lodash');
|
const autoload = require('auto-load')
|
||||||
const autoload = require('auto-load');
|
const bodyParser = require('body-parser')
|
||||||
const bodyParser = require('body-parser');
|
const compression = require('compression')
|
||||||
const compression = require('compression');
|
const cookieParser = require('cookie-parser')
|
||||||
const cookieParser = require('cookie-parser');
|
const express = require('express')
|
||||||
const express = require('express');
|
const favicon = require('serve-favicon')
|
||||||
const favicon = require('serve-favicon');
|
const flash = require('connect-flash')
|
||||||
const flash = require('connect-flash');
|
const fork = require('child_process').fork
|
||||||
const fork = require('child_process').fork;
|
const http = require('http')
|
||||||
const http = require('http');
|
const i18nextBackend = require('i18next-node-fs-backend')
|
||||||
const i18next_backend = require('i18next-node-fs-backend');
|
const i18nextMw = require('i18next-express-middleware')
|
||||||
const i18next_mw = require('i18next-express-middleware');
|
const passport = require('passport')
|
||||||
const passport = require('passport');
|
const passportSocketIo = require('passport.socketio')
|
||||||
const passportSocketIo = require('passport.socketio');
|
const path = require('path')
|
||||||
const path = require('path');
|
const session = require('express-session')
|
||||||
const session = require('express-session');
|
const SessionMongoStore = require('connect-mongo')(session)
|
||||||
const sessionMongoStore = require('connect-mongo')(session);
|
const socketio = require('socket.io')
|
||||||
const socketio = require('socket.io');
|
|
||||||
|
|
||||||
var mw = autoload(CORE_PATH + '/core-middlewares');
|
var mw = autoload(CORE_PATH + '/core-middlewares')
|
||||||
var ctrl = autoload(path.join(ROOTPATH, '/controllers'));
|
var ctrl = autoload(path.join(ROOTPATH, '/controllers'))
|
||||||
var libInternalAuth = require('./libs/internalAuth');
|
var libInternalAuth = require('./libs/internalAuth')
|
||||||
|
|
||||||
global.WSInternalKey = libInternalAuth.generateKey();
|
global.WSInternalKey = libInternalAuth.generateKey()
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Define Express App
|
// Define Express App
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.app = express();
|
global.app = express()
|
||||||
app.use(compression());
|
app.use(compression())
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Security
|
// Security
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use(mw.security);
|
app.use(mw.security)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Public Assets
|
// Public Assets
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')));
|
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
|
||||||
app.use(express.static(path.join(ROOTPATH, 'assets')));
|
app.use(express.static(path.join(ROOTPATH, 'assets')))
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Passport Authentication
|
// Passport Authentication
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var strategy = require(CORE_PATH + 'core-libs/auth')(passport);
|
require(CORE_PATH + 'core-libs/auth')(passport)
|
||||||
global.rights = require(CORE_PATH + 'core-libs/rights');
|
global.rights = require(CORE_PATH + 'core-libs/rights')
|
||||||
rights.init();
|
rights.init()
|
||||||
|
|
||||||
var sessionStore = new sessionMongoStore({
|
var sessionStore = new SessionMongoStore({
|
||||||
mongooseConnection: db.connection,
|
mongooseConnection: db.connection,
|
||||||
touchAfter: 15
|
touchAfter: 15
|
||||||
});
|
})
|
||||||
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser())
|
||||||
app.use(session({
|
app.use(session({
|
||||||
name: 'requarkswiki.sid',
|
name: 'requarkswiki.sid',
|
||||||
store: sessionStore,
|
store: sessionStore,
|
||||||
secret: appconfig.sessionSecret,
|
secret: appconfig.sessionSecret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false
|
saveUninitialized: false
|
||||||
}));
|
}))
|
||||||
app.use(flash());
|
app.use(flash())
|
||||||
app.use(passport.initialize());
|
app.use(passport.initialize())
|
||||||
app.use(passport.session());
|
app.use(passport.session())
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Localization Engine
|
// Localization Engine
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
lang
|
lang
|
||||||
.use(i18next_backend)
|
.use(i18nextBackend)
|
||||||
.use(i18next_mw.LanguageDetector)
|
.use(i18nextMw.LanguageDetector)
|
||||||
.init({
|
.init({
|
||||||
load: 'languageOnly',
|
load: 'languageOnly',
|
||||||
ns: ['common', 'auth'],
|
ns: ['common', 'auth'],
|
||||||
@@ -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({
|
|||||||
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()
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// TODO
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
let path = require('path'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// Load global modules
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
global._ = require('lodash');
|
|
||||||
global.winston = require('winston');
|
|
||||||
+10
-15
@@ -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
|
|
||||||
h3 Detailed debug trail:
|
|
||||||
pre: code #{error.stack}
|
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user