Standard JS code conversion + fixes
This commit is contained in:
parent
a508b2a7f4
commit
414dc386d6
24
CHANGELOG.md
Normal file
24
CHANGELOG.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Change Log
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Change log
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed issue with social accounts with empty name
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies + snyk policy
|
||||||
|
- Conversion to Standard JS compliant code
|
||||||
|
|
||||||
|
## [v1.0-beta.2] - 2017-01-30
|
||||||
|
### Added
|
||||||
|
- Save own profile under My Account
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies + snyk policy
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/Requarks/wiki/compare/v1.0-beta.2...HEAD
|
||||||
|
[v1.0-beta.2]: https://github.com/Requarks/wiki/releases/tag/v1.0-beta.2
|
281
agent.js
281
agent.js
@ -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
|
|
||||||
|
|
||||||
cacheJobs.push(
|
return fileStatus
|
||||||
fs.statAsync(cachePath).then((st) => {
|
}).then((fileStatus) => {
|
||||||
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active';
|
// -> Update cache and search index
|
||||||
}).catch((err) => {
|
|
||||||
return (err.code !== 'EEXIST') ? err : 'new';
|
|
||||||
}).then((fileStatus) => {
|
|
||||||
|
|
||||||
//-> Delete expired cache file
|
if (fileStatus !== 'active') {
|
||||||
|
return entries.updateCache(entryPath)
|
||||||
|
}
|
||||||
|
|
||||||
if(fileStatus === 'expired') {
|
return true
|
||||||
return fs.unlinkAsync(cachePath).return(fileStatus);
|
})
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}).on('end', () => {
|
||||||
|
jobCbStreamDocsResolve(Promise.all(cacheJobs))
|
||||||
|
})
|
||||||
|
|
||||||
return fileStatus;
|
return jobCbStreamDocs
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
}).then((fileStatus) => {
|
//* ****************************************
|
||||||
|
// -> Clear failed temporary upload files
|
||||||
|
//* ****************************************
|
||||||
|
|
||||||
//-> Update cache and search index
|
jobs.push(
|
||||||
|
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
||||||
|
let fifteenAgo = moment().subtract(15, 'minutes')
|
||||||
|
|
||||||
if(fileStatus !== 'active') {
|
return Promise.map(ls, (f) => {
|
||||||
return entries.updateCache(entryPath);
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
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(() => {
|
||||||
}).on('end', () => {
|
job.start()
|
||||||
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()
|
||||||
});
|
})
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,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)
|
||||||
|
_.delay(() => {
|
||||||
|
self.mdl.children.splice(nAlertIdx, 1)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(nAlertIdx >= 0 && nAlert) {
|
}
|
||||||
nAlert.class += ' exit';
|
|
||||||
Vue.set(self.mdl.children, nAlertIdx, nAlert);
|
|
||||||
_.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();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
@ -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: () => {
|
|
||||||
|
|
||||||
let moveFolders = _.map(vueFile.folders, (f) => {
|
attachContextMenus: () => {
|
||||||
return {
|
let moveFolders = _.map(vueFile.folders, (f) => {
|
||||||
name: (f !== '') ? f : '/ (root)',
|
return {
|
||||||
icon: 'fa-folder',
|
name: (f !== '') ? f : '/ (root)',
|
||||||
callback: (key, opt) => {
|
icon: 'fa-folder',
|
||||||
let moveFileId = _.toString($(opt.$trigger).data('uid'));
|
callback: (key, opt) => {
|
||||||
let moveFileDestFolder = _.nth(vueFile.folders, key);
|
let moveFileId = _.toString($(opt.$trigger).data('uid'))
|
||||||
vueFile.moveFile(moveFileId, moveFileDestFolder);
|
let moveFileDestFolder = _.nth(vueFile.folders, key)
|
||||||
}
|
vueFile.moveFile(moveFileId, moveFileDestFolder)
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$.contextMenu('destroy', '.editor-modal-file-choices > figure');
|
$.contextMenu('destroy', '.editor-modal-file-choices > figure')
|
||||||
$.contextMenu({
|
$.contextMenu({
|
||||||
selector: '.editor-modal-file-choices > figure',
|
selector: '.editor-modal-file-choices > figure',
|
||||||
appendTo: '.editor-modal-file-choices',
|
appendTo: '.editor-modal-file-choices',
|
||||||
position: (opt, x, y) => {
|
position: (opt, x, y) => {
|
||||||
$(opt.$trigger).addClass('is-contextopen');
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let trigPos = $(opt.$trigger).position();
|
let trigPos = $(opt.$trigger).position()
|
||||||
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 };
|
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
|
||||||
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
|
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
hide: (opt) => {
|
hide: (opt) => {
|
||||||
$(opt.$trigger).removeClass('is-contextopen');
|
$(opt.$trigger).removeClass('is-contextopen')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
rename: {
|
rename: {
|
||||||
name: "Rename",
|
name: 'Rename',
|
||||||
icon: "fa-edit",
|
icon: 'fa-edit',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid);
|
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueFile.renameFile();
|
vueFile.renameFile()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: {
|
move: {
|
||||||
name: "Move to...",
|
name: 'Move to...',
|
||||||
icon: "fa-folder-open-o",
|
icon: 'fa-folder-open-o',
|
||||||
items: moveFolders
|
items: moveFolders
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
name: "Delete",
|
name: 'Delete',
|
||||||
icon: "fa-trash",
|
icon: 'fa-trash',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid);
|
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueFile.deleteFileWarn(true);
|
vueFile.deleteFileWarn(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#btn-editor-file-upload input').on('change', (ev) => {
|
$('#btn-editor-file-upload input').on('change', (ev) => {
|
||||||
|
let curFileAmount = vueFile.files.length
|
||||||
|
|
||||||
let curFileAmount = vueFile.files.length;
|
$(ev.currentTarget).simpleUpload('/uploads/file', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/file", {
|
name: 'binfile',
|
||||||
|
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.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")'
|
||||||
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 = '![' + selImage.titleGuess + '](/uploads/' + selImage.normalizedPath + ' "' + selImage.titleGuess + '")';
|
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.fetchFromUrlDiscard();
|
vueImage.isLoadingText = 'Fetching image...'
|
||||||
vueImage.isLoadingText = 'Fetching image...';
|
vueImage.isLoading = true
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
||||||
if(data.ok) {
|
if (data.ok) {
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, true);
|
vueImage.waitChangeComplete(vueImage.images.length, true)
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Upload error', data.msg);
|
alerts.pushError('Upload error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
},
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// RENAME IMAGE
|
// RENAME IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameImage: () => {
|
renameImage: () => {
|
||||||
|
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
|
||||||
|
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: () => {
|
|
||||||
|
|
||||||
let moveFolders = _.map(vueImage.folders, (f) => {
|
attachContextMenus: () => {
|
||||||
return {
|
let moveFolders = _.map(vueImage.folders, (f) => {
|
||||||
name: (f !== '') ? f : '/ (root)',
|
return {
|
||||||
icon: 'fa-folder',
|
name: (f !== '') ? f : '/ (root)',
|
||||||
callback: (key, opt) => {
|
icon: 'fa-folder',
|
||||||
let moveImageId = _.toString($(opt.$trigger).data('uid'));
|
callback: (key, opt) => {
|
||||||
let moveImageDestFolder = _.nth(vueImage.folders, key);
|
let moveImageId = _.toString($(opt.$trigger).data('uid'))
|
||||||
vueImage.moveImage(moveImageId, moveImageDestFolder);
|
let moveImageDestFolder = _.nth(vueImage.folders, key)
|
||||||
}
|
vueImage.moveImage(moveImageId, moveImageDestFolder)
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$.contextMenu('destroy', '.editor-modal-image-choices > figure');
|
$.contextMenu('destroy', '.editor-modal-image-choices > figure')
|
||||||
$.contextMenu({
|
$.contextMenu({
|
||||||
selector: '.editor-modal-image-choices > figure',
|
selector: '.editor-modal-image-choices > figure',
|
||||||
appendTo: '.editor-modal-image-choices',
|
appendTo: '.editor-modal-image-choices',
|
||||||
position: (opt, x, y) => {
|
position: (opt, x, y) => {
|
||||||
$(opt.$trigger).addClass('is-contextopen');
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let trigPos = $(opt.$trigger).position();
|
let trigPos = $(opt.$trigger).position()
|
||||||
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 };
|
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
|
||||||
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
|
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
hide: (opt) => {
|
hide: (opt) => {
|
||||||
$(opt.$trigger).removeClass('is-contextopen');
|
$(opt.$trigger).removeClass('is-contextopen')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
rename: {
|
rename: {
|
||||||
name: "Rename",
|
name: 'Rename',
|
||||||
icon: "fa-edit",
|
icon: 'fa-edit',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid);
|
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueImage.renameImage();
|
vueImage.renameImage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: {
|
move: {
|
||||||
name: "Move to...",
|
name: 'Move to...',
|
||||||
icon: "fa-folder-open-o",
|
icon: 'fa-folder-open-o',
|
||||||
items: moveFolders
|
items: moveFolders
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
name: "Delete",
|
name: 'Delete',
|
||||||
icon: "fa-trash",
|
icon: 'fa-trash',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid);
|
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueImage.deleteImageWarn(true);
|
vueImage.deleteImageWarn(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#btn-editor-image-upload input').on('change', (ev) => {
|
$('#btn-editor-image-upload input').on('change', (ev) => {
|
||||||
|
let curImageAmount = vueImage.images.length
|
||||||
|
|
||||||
let curImageAmount = vueImage.images.length;
|
$(ev.currentTarget).simpleUpload('/uploads/img', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/img", {
|
name: 'imgfile',
|
||||||
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
|
||||||
|
@ -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
|
$(window).bind('keydown', (ev) => {
|
||||||
|
if (ev.ctrlKey || ev.metaKey) {
|
||||||
let saveCurrentDocument = (ev) => {
|
switch (String.fromCharCode(ev.which).toLowerCase()) {
|
||||||
$.ajax(window.location.href, {
|
case 's':
|
||||||
data: {
|
ev.preventDefault()
|
||||||
markdown: mde.value()
|
saveCurrentDocument(ev)
|
||||||
},
|
break
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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: () => {
|
||||||
|
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
||||||
|
vueHeader.searchmoveidx++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveUpSearch: () => {
|
||||||
|
if (vueHeader.searchmoveidx > 0) {
|
||||||
|
vueHeader.searchmoveidx--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
},
|
$('main').on('click', vueHeader.closeSearch)
|
||||||
moveDownSearch: () => {
|
}
|
||||||
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
|
||||||
vueHeader.searchmoveidx++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
moveUpSearch: () => {
|
|
||||||
if(vueHeader.searchmoveidx > 0) {
|
|
||||||
vueHeader.searchmoveidx--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('main').on('click', vueHeader.closeSearch);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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), '/')
|
||||||
|
rawParts = _.map(rawParts, (r) => {
|
||||||
|
return _.kebabCase(_.deburr(_.trim(r)))
|
||||||
|
})
|
||||||
|
|
||||||
let rawParts = _.split(_.trim(rawPath), '/');
|
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
|
||||||
rawParts = _.map(rawParts, (r) => {
|
}
|
||||||
return _.kebabCase(_.deburr(_.trim(r)));
|
|
||||||
});
|
|
||||||
|
|
||||||
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
jQuery( document ).ready(function( $ ) {
|
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)
|
||||||
|
@ -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);
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -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.');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -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'])
|
||||||
|
this.roleoverride = 'admin'
|
||||||
|
} else {
|
||||||
|
this.rights = usrData.rights
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
} else if($('#page-type-admin-users-edit').length) {
|
// =include ../modals/admin-users-delete.js
|
||||||
|
}
|
||||||
let vueEditUser = new Vue({
|
|
||||||
el: '#page-type-admin-users-edit',
|
|
||||||
data: {
|
|
||||||
id: '',
|
|
||||||
email: '',
|
|
||||||
password: '********',
|
|
||||||
name: '',
|
|
||||||
rights: [],
|
|
||||||
roleoverride: 'none'
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addRightsRow: (ev) => {
|
|
||||||
vueEditUser.rights.push({
|
|
||||||
role: 'write',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeRightsRow: (idx) => {
|
|
||||||
_.pullAt(vueEditUser.rights, idx)
|
|
||||||
vueEditUser.$forceUpdate()
|
|
||||||
},
|
|
||||||
saveUser: (ev) => {
|
|
||||||
let formattedRights = _.cloneDeep(vueEditUser.rights)
|
|
||||||
switch(vueEditUser.roleoverride) {
|
|
||||||
case 'admin':
|
|
||||||
formattedRights.push({
|
|
||||||
role: 'admin',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: false
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$.post(window.location.href, {
|
|
||||||
password: vueEditUser.password,
|
|
||||||
name: vueEditUser.name,
|
|
||||||
rights: JSON.stringify(formattedRights)
|
|
||||||
}).done((resp) => {
|
|
||||||
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
|
|
||||||
}).fail((jqXHR, txtStatus, resp) => {
|
|
||||||
alerts.pushError('Error', resp);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function() {
|
|
||||||
|
|
||||||
this.id = usrData._id;
|
|
||||||
this.email = usrData.email;
|
|
||||||
this.name = usrData.name;
|
|
||||||
|
|
||||||
if(_.find(usrData.rights, { role: 'admin' })) {
|
|
||||||
this.rights = _.reject(usrData.rights, ['role', 'admin']);
|
|
||||||
this.roleoverride = 'admin';
|
|
||||||
} else {
|
|
||||||
this.rights = usrData.rights;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../modals/admin-users-delete.js
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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) => {
|
// =include ../components/editor.js
|
||||||
$('#modal-create-discard').toggleClass('is-active');
|
}
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if($('#page-type-edit').length) {
|
if ($('#page-type-edit').length) {
|
||||||
|
let pageEntryPath = $('#page-type-edit').data('entrypath')
|
||||||
|
|
||||||
let pageEntryPath = $('#page-type-edit').data('entrypath');
|
// -> Discard
|
||||||
|
|
||||||
//-> Discard
|
$('.btn-edit-discard').on('click', (ev) => {
|
||||||
|
$('#modal-edit-discard').toggleClass('is-active')
|
||||||
|
})
|
||||||
|
|
||||||
$('.btn-edit-discard').on('click', (ev) => {
|
// =include ../components/editor.js
|
||||||
$('#modal-edit-discard').toggleClass('is-active');
|
}
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
if($('#page-type-source').length) {
|
if ($('#page-type-source').length) {
|
||||||
|
var scEditor = ace.edit('source-display')
|
||||||
|
scEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
|
scEditor.getSession().setMode('ace/mode/markdown')
|
||||||
|
scEditor.setOption('fontSize', '14px')
|
||||||
|
scEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
|
scEditor.setOption('wrap', true)
|
||||||
|
scEditor.setReadOnly(true)
|
||||||
|
scEditor.renderer.updateFull()
|
||||||
|
|
||||||
var scEditor = ace.edit("source-display");
|
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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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;
|
|
||||||
|
@ -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
|
||||||
|
@ -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) => {
|
entries.exists(safePath).then((docExists) => {
|
||||||
if(!docExists) {
|
if (!docExists) {
|
||||||
return entries.getStarter(safePath).then((contents) => {
|
return entries.getStarter(safePath).then((contents) => {
|
||||||
|
let pageData = {
|
||||||
|
markdown: contents,
|
||||||
|
meta: {
|
||||||
|
title: _.startCase(safePath),
|
||||||
|
path: safePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.render('pages/create', { pageData })
|
||||||
|
|
||||||
let pageData = {
|
return true
|
||||||
markdown: contents,
|
}).catch((err) => {
|
||||||
meta: {
|
winston.warn(err)
|
||||||
title: _.startCase(safePath),
|
throw new Error('Could not load starter content!')
|
||||||
path: safePath
|
})
|
||||||
}
|
} else {
|
||||||
};
|
throw new Error('This entry already exists!')
|
||||||
res.render('pages/create', { pageData });
|
}
|
||||||
|
}).catch((err) => {
|
||||||
return true;
|
res.render('error', {
|
||||||
|
message: err.message,
|
||||||
}).catch((err) => {
|
error: {}
|
||||||
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;
|
|
||||||
|
@ -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 = ''
|
||||||
if(!destFolderPath) {
|
let destFilePath = ''
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
||||||
|
destFilename = fname
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
let destFilename = '';
|
return readChunk(f.path, 0, 262)
|
||||||
let destFilePath = '';
|
}).then((buf) => {
|
||||||
|
// -> Check MIME type by magic number
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
let mimeInfo = fileType(buf)
|
||||||
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
destFilename = fname;
|
return Promise.reject(new Error('Invalid file type.'))
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
}
|
||||||
|
return true
|
||||||
|
}).then(() => {
|
||||||
|
// -> Move file to final destination
|
||||||
|
|
||||||
return readChunk(f.path, 0, 262);
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
}).then(() => {
|
||||||
}).then((buf) => {
|
return {
|
||||||
|
ok: true,
|
||||||
//-> Check MIME type by magic number
|
filename: destFilename,
|
||||||
|
filesize: f.size
|
||||||
let mimeInfo = fileType(buf);
|
}
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
}).reflect()
|
||||||
return Promise.reject(new Error('Invalid file type.'));
|
}, {concurrency: 3}).then((results) => {
|
||||||
}
|
let uplResults = _.map(results, (r) => {
|
||||||
return true;
|
if (r.isFulfilled()) {
|
||||||
|
return r.value()
|
||||||
}).then(() => {
|
} else {
|
||||||
|
return {
|
||||||
//-> Move file to final destination
|
ok: false,
|
||||||
|
msg: r.reason().message
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
}
|
||||||
|
}
|
||||||
}).then(() => {
|
})
|
||||||
return {
|
res.json({ ok: true, results: uplResults })
|
||||||
ok: true,
|
return true
|
||||||
filename: destFilename,
|
}).catch((err) => {
|
||||||
filesize: f.size
|
res.json({ ok: false, msg: err.message })
|
||||||
};
|
return true
|
||||||
}).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 = ''
|
||||||
if(!destFolderPath) {
|
let destFilePath = ''
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
||||||
|
destFilename = fname
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
let destFilename = '';
|
// -> Move file to final destination
|
||||||
let destFilePath = '';
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
}).then(() => {
|
||||||
destFilename = fname;
|
return {
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
ok: true,
|
||||||
|
filename: destFilename,
|
||||||
//-> Move file to final destination
|
filesize: f.size
|
||||||
|
}
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
}).reflect()
|
||||||
|
}, {concurrency: 3}).then((results) => {
|
||||||
}).then(() => {
|
let uplResults = _.map(results, (r) => {
|
||||||
return {
|
if (r.isFulfilled()) {
|
||||||
ok: true,
|
return r.value()
|
||||||
filename: destFilename,
|
} else {
|
||||||
filesize: f.size
|
return {
|
||||||
};
|
ok: false,
|
||||||
}).reflect();
|
msg: r.reason().message
|
||||||
|
}
|
||||||
}, {concurrency: 3}).then((results) => {
|
}
|
||||||
let uplResults = _.map(results, (r) => {
|
})
|
||||||
if(r.isFulfilled()) {
|
res.json({ ok: true, results: uplResults })
|
||||||
return r.value();
|
return true
|
||||||
} else {
|
}).catch((err) => {
|
||||||
return {
|
res.json({ ok: false, msg: err.message })
|
||||||
ok: false,
|
return true
|
||||||
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;
|
|
||||||
|
@ -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
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
}
|
||||||
};
|
|
||||||
|
309
gulpfile.js
309
gulpfile.js
@ -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)
|
||||||
});
|
})
|
||||||
|
938
libs/entries.js
938
libs/entries.js
@ -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: []
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
415
libs/git.js
415
libs/git.js
@ -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')
|
||||||
if(_.isEmpty(appconfig.paths.repo)) {
|
} else {
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo');
|
self._repo.path = appconfig.paths.repo
|
||||||
} else {
|
}
|
||||||
self._repo.path = appconfig.paths.repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Initialize repository
|
// -> Initialize repository
|
||||||
|
|
||||||
self.onReady = self._initRepo(appconfig);
|
self.onReady = self._initRepo(appconfig)
|
||||||
|
|
||||||
// Define signature
|
// Define signature
|
||||||
|
|
||||||
self._signature.name = appconfig.git.signature.name || 'Wiki';
|
self._signature.name = appconfig.git.signature.name || 'Wiki'
|
||||||
self._signature.email = appconfig.git.signature.email || 'user@example.com';
|
self._signature.email = appconfig.git.signature.email || 'user@example.com'
|
||||||
|
|
||||||
return self;
|
return self
|
||||||
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Initialize Git repository
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} Promise
|
||||||
|
*/
|
||||||
|
_initRepo (appconfig) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
/**
|
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
|
||||||
* Initialize Git repository
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Object} Promise
|
|
||||||
*/
|
|
||||||
_initRepo(appconfig) {
|
|
||||||
|
|
||||||
let self = this;
|
// -> Check if path is accessible
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
|
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 })
|
||||||
|
|
||||||
//-> Check if path is accessible
|
// -> Check if path already contains a git working folder
|
||||||
|
|
||||||
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
return self._git.isRepo().then((isRepo) => {
|
||||||
if(err.code !== 'EEXIST') {
|
self._repo.exists = isRepo
|
||||||
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.');
|
return (!isRepo) ? self._git.exec('init') : true
|
||||||
}
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
}).then(() => {
|
self._repo.exists = false
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
// Initialize remote
|
||||||
|
|
||||||
self._git = new Git({ 'git-dir': self._repo.path });
|
let urlObj = URL.parse(appconfig.git.url)
|
||||||
|
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
|
||||||
|
self._url = URL.format(urlObj)
|
||||||
|
|
||||||
//-> Check if path already contains a git working folder
|
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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
return self._git.isRepo().then((isRepo) => {
|
/**
|
||||||
self._repo.exists = isRepo;
|
* Gets the repo path.
|
||||||
return (!isRepo) ? self._git.exec('init') : true;
|
*
|
||||||
}).catch((err) => {
|
* @return {String} The repo path.
|
||||||
self._repo.exists = false;
|
*/
|
||||||
});
|
getRepoPath () {
|
||||||
|
return this._repo.path || path.join(ROOTPATH, 'repo')
|
||||||
|
},
|
||||||
|
|
||||||
}).then(() => {
|
/**
|
||||||
|
* Sync with the remote repository
|
||||||
|
*
|
||||||
|
* @return {Promise} Resolve on sync success
|
||||||
|
*/
|
||||||
|
resync () {
|
||||||
|
let self = this
|
||||||
|
|
||||||
// Initialize remote
|
// Fetch
|
||||||
|
|
||||||
let urlObj = URL.parse(appconfig.git.url);
|
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
|
||||||
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '');
|
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
||||||
self._url = URL.format(urlObj);
|
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('remote', 'show').then((cProc) => {
|
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
||||||
let out = cProc.stdout.toString();
|
let out = cProc.stdout.toString()
|
||||||
if(_.includes(out, '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) => {
|
if (_.includes(out, 'commit')) {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Git remote error!');
|
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
|
||||||
throw err;
|
return self._git.push('origin', self._repo.branch).then(() => {
|
||||||
}).then(() => {
|
return winston.info('[' + PROCNAME + '][GIT] Push completed.')
|
||||||
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.');
|
})
|
||||||
return true;
|
} 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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the repo path.
|
* Commits a document.
|
||||||
*
|
*
|
||||||
* @return {String} The repo path.
|
* @param {String} entryPath The entry path
|
||||||
*/
|
* @return {Promise} Resolve on commit success
|
||||||
getRepoPath() {
|
*/
|
||||||
|
commitDocument (entryPath) {
|
||||||
|
let self = this
|
||||||
|
let gitFilePath = entryPath + '.md'
|
||||||
|
let commitMsg = ''
|
||||||
|
|
||||||
return this._repo.path || path.join(ROOTPATH, 'repo');
|
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) => {
|
||||||
* Sync with the remote repository
|
let out = cProc.stdout.toString()
|
||||||
*
|
if (_.includes(out, 'fatal')) {
|
||||||
* @return {Promise} Resolve on sync success
|
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
|
||||||
*/
|
throw new Error(errorMsg)
|
||||||
resync() {
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
let self = this;
|
/**
|
||||||
|
* 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'
|
||||||
|
|
||||||
// Fetch
|
return self._git.add('uploads').then(() => {
|
||||||
|
return self._git.commit(msg).catch((err) => {
|
||||||
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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; }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
289
libs/local.js
289
libs/local.js
@ -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;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
500
libs/markdown.js
500
libs/markdown.js
@ -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) {
|
||||||
cr.root().children('blockquote').each((i, elm) => {
|
let bqLastChild = cr(elm).children().last()[0]
|
||||||
if(cr(elm).children().length > 0) {
|
let bqLastChildClasses = cr(bqLastChild).attr('class')
|
||||||
let bqLastChild = cr(elm).children().last()[0];
|
if (bqLastChildClasses && bqLastChildClasses.length > 0) {
|
||||||
let bqLastChildClasses = cr(bqLastChild).attr('class');
|
cr(bqLastChild).removeAttr('class')
|
||||||
if(bqLastChildClasses && bqLastChildClasses.length > 0) {
|
cr(elm).addClass(bqLastChildClasses)
|
||||||
cr(bqLastChild).removeAttr('class');
|
}
|
||||||
cr(elm).addClass(bqLastChildClasses);
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//-> Enclose content below headers
|
// -> Enclose content below headers
|
||||||
|
|
||||||
cr('h2').each((i, elm) => {
|
cr('h2').each((i, elm) => {
|
||||||
let subH2Content = cr(elm).nextUntil('h1, h2');
|
let subH2Content = cr(elm).nextUntil('h1, h2')
|
||||||
cr(elm).after('<div class="indent-h2"></div>');
|
cr(elm).after('<div class="indent-h2"></div>')
|
||||||
let subH2Container = cr(elm).next('.indent-h2');
|
let subH2Container = cr(elm).next('.indent-h2')
|
||||||
_.forEach(subH2Content, (ch) => {
|
_.forEach(subH2Content, (ch) => {
|
||||||
cr(subH2Container).append(ch);
|
cr(subH2Container).append(ch)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
cr('h3').each((i, elm) => {
|
cr('h3').each((i, elm) => {
|
||||||
let subH3Content = cr(elm).nextUntil('h1, h2, h3');
|
let subH3Content = cr(elm).nextUntil('h1, h2, h3')
|
||||||
cr(elm).after('<div class="indent-h3"></div>');
|
cr(elm).after('<div class="indent-h3"></div>')
|
||||||
let subH3Container = cr(elm).next('.indent-h3');
|
let subH3Container = cr(elm).next('.indent-h3')
|
||||||
_.forEach(subH3Content, (ch) => {
|
_.forEach(subH3Content, (ch) => {
|
||||||
cr(subH3Container).append(ch);
|
cr(subH3Container).append(ch)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Replace video links with embeds
|
// Replace video links with embeds
|
||||||
|
|
||||||
_.forEach(videoRules, (vrule) => {
|
_.forEach(videoRules, (vrule) => {
|
||||||
cr(vrule.selector).each((i, elm) => {
|
cr(vrule.selector).each((i, elm) => {
|
||||||
let originLink = cr(elm).attr('href');
|
let originLink = cr(elm).attr('href')
|
||||||
if(vrule.regexp) {
|
if (vrule.regexp) {
|
||||||
let vidMatches = originLink.match(vrule.regexp);
|
let vidMatches = originLink.match(vrule.regexp)
|
||||||
if((vidMatches && _.isArray(vidMatches))) {
|
if ((vidMatches && _.isArray(vidMatches))) {
|
||||||
vidMatches = _.filter(vidMatches, (f) => {
|
vidMatches = _.filter(vidMatches, (f) => {
|
||||||
return f && _.isString(f);
|
return f && _.isString(f)
|
||||||
});
|
})
|
||||||
originLink = _.last(vidMatches);
|
originLink = _.last(vidMatches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let processedLink = _.replace(vrule.output, '{0}', originLink);
|
let processedLink = _.replace(vrule.output, '{0}', originLink)
|
||||||
cr(elm).replaceWith(processedLink);
|
cr(elm).replaceWith(processedLink)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
output = cr.html();
|
output = cr.html()
|
||||||
|
|
||||||
return output;
|
return output
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse meta-data tags from content
|
* Parse meta-data tags from content
|
||||||
@ -261,58 +256,57 @@ const parseContent = (content) => {
|
|||||||
* @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()
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
@ -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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
498
libs/uploads.js
498
libs/uploads.js
@ -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) {
|
|
||||||
return Promise.reject(new Error('Invalid Folder'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
|
||||||
|
|
||||||
let destFilePath = path.resolve(destFolderPath, destFilename);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
// -> Move thumbnail ahead to avoid re-generation
|
||||||
|
|
||||||
let rq = request({
|
if (originFile.category === 'image') {
|
||||||
url: fUrl,
|
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename)
|
||||||
method: 'GET',
|
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
|
||||||
followRedirect: true,
|
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
|
||||||
maxRedirects: 5,
|
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
|
||||||
timeout: 10000
|
} else {
|
||||||
});
|
preMoveOps.push(Promise.resolve(true))
|
||||||
|
}
|
||||||
|
|
||||||
let destFileStream = fs.createWriteStream(destFilePath);
|
// -> Proceed to move actual file
|
||||||
let curFileSize = 0;
|
|
||||||
|
|
||||||
rq.on('data', (data) => {
|
return Promise.all(preMoveOps).then(() => {
|
||||||
curFileSize += data.length;
|
return fs.moveAsync(sourceFilePath, destFilePath, {
|
||||||
if(curFileSize > maxDownloadFileSize) {
|
clobber: false
|
||||||
rq.abort();
|
})
|
||||||
destFileStream.destroy();
|
})
|
||||||
fs.remove(destFilePath);
|
})
|
||||||
reject(new Error('Remote file is too large!'));
|
})
|
||||||
}
|
} else {
|
||||||
}).on('error', (err) => {
|
return Promise.reject(new Error('Invalid Destination Folder'))
|
||||||
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'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -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();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -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();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
127
models/user.js
127
models/user.js
@ -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)
|
||||||
|
30
package.json
30
package.json
@ -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
|
||||||
}
|
}
|
||||||
|
212
server.js
212
server.js
@ -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()
|
||||||
});
|
})
|
||||||
|
3
test/index.js
Normal file
3
test/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
// TODO
|
@ -1,11 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
let path = require('path'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// Load global modules
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
global._ = require('lodash');
|
|
||||||
global.winston = require('winston');
|
|
@ -19,20 +19,15 @@ html
|
|||||||
|
|
||||||
// CSS
|
// 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
|
||||||
|
Loading…
Reference in New Issue
Block a user