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
|
167
agent.js
167
agent.js
@ -4,75 +4,72 @@
|
|||||||
// Licensed under AGPLv3
|
// Licensed under AGPLv3
|
||||||
// ===========================================
|
// ===========================================
|
||||||
|
|
||||||
global.PROCNAME = 'AGENT';
|
global.PROCNAME = 'AGENT'
|
||||||
global.ROOTPATH = __dirname;
|
global.ROOTPATH = __dirname
|
||||||
global.IS_DEBUG = process.env.NODE_ENV === 'development';
|
global.IS_DEBUG = process.env.NODE_ENV === 'development'
|
||||||
if (IS_DEBUG) {
|
if (IS_DEBUG) {
|
||||||
global.CORE_PATH = ROOTPATH + '/../core/';
|
global.CORE_PATH = ROOTPATH + '/../core/'
|
||||||
} else {
|
} else {
|
||||||
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/';
|
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load Winston
|
// Load Winston
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG);
|
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load global modules
|
// Load global modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
winston.info('[AGENT] Background Agent is initializing...');
|
winston.info('[AGENT] Background Agent is initializing...')
|
||||||
|
|
||||||
let appconf = require(CORE_PATH + 'core-libs/config')();
|
let appconf = require(CORE_PATH + 'core-libs/config')()
|
||||||
global.appconfig = appconf.config;
|
global.appconfig = appconf.config
|
||||||
global.appdata = appconf.data;
|
global.appdata = appconf.data
|
||||||
global.db = require(CORE_PATH + 'core-libs/mongodb').init();
|
global.db = require(CORE_PATH + 'core-libs/mongodb').init()
|
||||||
global.upl = require('./libs/uploads-agent').init();
|
global.upl = require('./libs/uploads-agent').init()
|
||||||
global.git = require('./libs/git').init();
|
global.git = require('./libs/git').init()
|
||||||
global.entries = require('./libs/entries').init();
|
global.entries = require('./libs/entries').init()
|
||||||
global.mark = require('./libs/markdown');
|
global.mark = require('./libs/markdown')
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Load modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var _ = require('lodash');
|
var moment = require('moment')
|
||||||
var moment = require('moment');
|
var Promise = require('bluebird')
|
||||||
var Promise = require('bluebird');
|
var fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
var fs = Promise.promisifyAll(require("fs-extra"));
|
var klaw = require('klaw')
|
||||||
var klaw = require('klaw');
|
var path = require('path')
|
||||||
var path = require('path');
|
var Cron = require('cron').CronJob
|
||||||
var cron = require('cron').CronJob;
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start Cron
|
// Start Cron
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var jobIsBusy = false;
|
var jobIsBusy = false
|
||||||
var jobUplWatchStarted = false;
|
var jobUplWatchStarted = false
|
||||||
|
|
||||||
var job = new cron({
|
var job = new Cron({
|
||||||
cronTime: '0 */5 * * * *',
|
cronTime: '0 */5 * * * *',
|
||||||
onTick: () => {
|
onTick: () => {
|
||||||
|
|
||||||
// Make sure we don't start two concurrent jobs
|
// Make sure we don't start two concurrent jobs
|
||||||
|
|
||||||
if (jobIsBusy) {
|
if (jobIsBusy) {
|
||||||
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)');
|
winston.warn('[AGENT] Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
winston.info('[AGENT] Running all jobs...');
|
winston.info('[AGENT] Running all jobs...')
|
||||||
jobIsBusy = true;
|
jobIsBusy = true
|
||||||
|
|
||||||
// Prepare async job collector
|
// Prepare async job collector
|
||||||
|
|
||||||
let jobs = [];
|
let jobs = []
|
||||||
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
||||||
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data);
|
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
|
||||||
let uploadsPath = path.join(repoPath, 'uploads');
|
let uploadsTempPath = path.join(dataPath, 'temp-upload')
|
||||||
let uploadsTempPath = path.join(dataPath, 'temp-upload');
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// REGULAR JOBS
|
// REGULAR JOBS
|
||||||
@ -84,61 +81,52 @@ var job = new cron({
|
|||||||
|
|
||||||
jobs.push(git.onReady.then(() => {
|
jobs.push(git.onReady.then(() => {
|
||||||
return git.resync().then(() => {
|
return git.resync().then(() => {
|
||||||
|
|
||||||
// -> Stream all documents
|
// -> Stream all documents
|
||||||
|
|
||||||
let cacheJobs = [];
|
let cacheJobs = []
|
||||||
let jobCbStreamDocs_resolve = null,
|
let jobCbStreamDocsResolve = null
|
||||||
jobCbStreamDocs = new Promise((resolve, reject) => {
|
let jobCbStreamDocs = new Promise((resolve, reject) => {
|
||||||
jobCbStreamDocs_resolve = resolve;
|
jobCbStreamDocsResolve = resolve
|
||||||
});
|
})
|
||||||
|
|
||||||
klaw(repoPath).on('data', function (item) {
|
klaw(repoPath).on('data', function (item) {
|
||||||
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
|
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
|
||||||
|
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path))
|
||||||
let entryPath = entries.parsePath(entries.getEntryPathFromFullPath(item.path));
|
let cachePath = entries.getCachePath(entryPath)
|
||||||
let cachePath = entries.getCachePath(entryPath);
|
|
||||||
|
|
||||||
// -> Purge outdated cache
|
// -> Purge outdated cache
|
||||||
|
|
||||||
cacheJobs.push(
|
cacheJobs.push(
|
||||||
fs.statAsync(cachePath).then((st) => {
|
fs.statAsync(cachePath).then((st) => {
|
||||||
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active';
|
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return (err.code !== 'EEXIST') ? err : 'new';
|
return (err.code !== 'EEXIST') ? err : 'new'
|
||||||
}).then((fileStatus) => {
|
}).then((fileStatus) => {
|
||||||
|
|
||||||
// -> Delete expired cache file
|
// -> Delete expired cache file
|
||||||
|
|
||||||
if (fileStatus === 'expired') {
|
if (fileStatus === 'expired') {
|
||||||
return fs.unlinkAsync(cachePath).return(fileStatus);
|
return fs.unlinkAsync(cachePath).return(fileStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileStatus;
|
return fileStatus
|
||||||
|
|
||||||
}).then((fileStatus) => {
|
}).then((fileStatus) => {
|
||||||
|
|
||||||
// -> Update cache and search index
|
// -> Update cache and search index
|
||||||
|
|
||||||
if (fileStatus !== 'active') {
|
if (fileStatus !== 'active') {
|
||||||
return entries.updateCache(entryPath);
|
return entries.updateCache(entryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
|
|
||||||
})
|
})
|
||||||
|
)
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}).on('end', () => {
|
}).on('end', () => {
|
||||||
jobCbStreamDocs_resolve(Promise.all(cacheJobs));
|
jobCbStreamDocsResolve(Promise.all(cacheJobs))
|
||||||
});
|
})
|
||||||
|
|
||||||
return jobCbStreamDocs;
|
return jobCbStreamDocs
|
||||||
|
})
|
||||||
});
|
}))
|
||||||
}));
|
|
||||||
|
|
||||||
//* ****************************************
|
//* ****************************************
|
||||||
// -> Clear failed temporary upload files
|
// -> Clear failed temporary upload files
|
||||||
@ -146,65 +134,58 @@ var job = new cron({
|
|||||||
|
|
||||||
jobs.push(
|
jobs.push(
|
||||||
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
||||||
|
let fifteenAgo = moment().subtract(15, 'minutes')
|
||||||
let fifteenAgo = moment().subtract(15, 'minutes');
|
|
||||||
|
|
||||||
return Promise.map(ls, (f) => {
|
return Promise.map(ls, (f) => {
|
||||||
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s }; });
|
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
|
||||||
}).filter((s) => { return s.stat.isFile(); }).then((arrFiles) => {
|
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
|
||||||
return Promise.map(arrFiles, (f) => {
|
return Promise.map(arrFiles, (f) => {
|
||||||
|
|
||||||
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
||||||
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename));
|
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
})
|
||||||
);
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Run
|
// Run
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
Promise.all(jobs).then(() => {
|
Promise.all(jobs).then(() => {
|
||||||
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.');
|
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now.')
|
||||||
|
|
||||||
if (!jobUplWatchStarted) {
|
if (!jobUplWatchStarted) {
|
||||||
jobUplWatchStarted = true;
|
jobUplWatchStarted = true
|
||||||
upl.initialScan().then(() => {
|
upl.initialScan().then(() => {
|
||||||
job.start();
|
job.start()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error('[AGENT] One or more jobs have failed: ', err);
|
winston.error('[AGENT] One or more jobs have failed: ', err)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
jobIsBusy = false;
|
jobIsBusy = false
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
start: false,
|
start: false,
|
||||||
timeZone: 'UTC',
|
timeZone: 'UTC',
|
||||||
runOnInit: true
|
runOnInit: true
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Shutdown gracefully
|
// Shutdown gracefully
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
process.on('disconnect', () => {
|
process.on('disconnect', () => {
|
||||||
winston.warn('[AGENT] Lost connection to main server. Exiting...');
|
winston.warn('[AGENT] Lost connection to main server. Exiting...')
|
||||||
job.stop();
|
job.stop()
|
||||||
process.exit();
|
process.exit()
|
||||||
});
|
})
|
||||||
|
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
job.stop();
|
job.stop()
|
||||||
});
|
})
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
jQuery(document).ready(function ($) {
|
jQuery(document).ready(function ($) {
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Scroll
|
// Scroll
|
||||||
// ====================================
|
// ====================================
|
||||||
@ -9,35 +8,35 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
$('a').smoothScroll({
|
$('a').smoothScroll({
|
||||||
speed: 400,
|
speed: 400,
|
||||||
offset: -70
|
offset: -70
|
||||||
});
|
})
|
||||||
|
|
||||||
var sticky = new Sticky('.stickyscroll');
|
var sticky = new Sticky('.stickyscroll')
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Notifications
|
// Notifications
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
$(window).bind('beforeunload', () => {
|
$(window).bind('beforeunload', () => {
|
||||||
$('#notifload').addClass('active');
|
$('#notifload').addClass('active')
|
||||||
});
|
})
|
||||||
$(document).ajaxSend(() => {
|
$(document).ajaxSend(() => {
|
||||||
$('#notifload').addClass('active');
|
$('#notifload').addClass('active')
|
||||||
}).ajaxComplete(() => {
|
}).ajaxComplete(() => {
|
||||||
$('#notifload').removeClass('active');
|
$('#notifload').removeClass('active')
|
||||||
});
|
})
|
||||||
|
|
||||||
var alerts = new Alerts();
|
var alerts = new Alerts()
|
||||||
if (alertsData) {
|
if (alertsData) {
|
||||||
_.forEach(alertsData, (alertRow) => {
|
_.forEach(alertsData, (alertRow) => {
|
||||||
alerts.push(alertRow);
|
alerts.push(alertRow)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Establish WebSocket connection
|
// Establish WebSocket connection
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
var socket = io(window.location.origin);
|
var socket = io(window.location.origin)
|
||||||
|
|
||||||
// =include components/search.js
|
// =include components/search.js
|
||||||
|
|
||||||
@ -50,8 +49,7 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
// =include pages/edit.js
|
// =include pages/edit.js
|
||||||
// =include pages/source.js
|
// =include pages/source.js
|
||||||
// =include pages/admin.js
|
// =include pages/admin.js
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
// =include helpers/form.js
|
// =include helpers/form.js
|
||||||
// =include helpers/pages.js
|
// =include helpers/pages.js
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alerts
|
* Alerts
|
||||||
@ -11,8 +11,7 @@ class Alerts {
|
|||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
constructor () {
|
constructor () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
self.mdl = new Vue({
|
self.mdl = new Vue({
|
||||||
el: '#alerts',
|
el: '#alerts',
|
||||||
@ -21,13 +20,12 @@ class Alerts {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
acknowledge: (uid) => {
|
acknowledge: (uid) => {
|
||||||
self.close(uid);
|
self.close(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
self.uidNext = 1;
|
|
||||||
|
|
||||||
|
self.uidNext = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,8 +35,7 @@ class Alerts {
|
|||||||
* @return {null} Void
|
* @return {null} Void
|
||||||
*/
|
*/
|
||||||
push (options) {
|
push (options) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
let nAlert = _.defaults(options, {
|
let nAlert = _.defaults(options, {
|
||||||
_uid: self.uidNext,
|
_uid: self.uidNext,
|
||||||
@ -46,18 +43,17 @@ class Alerts {
|
|||||||
message: '---',
|
message: '---',
|
||||||
sticky: false,
|
sticky: false,
|
||||||
title: '---'
|
title: '---'
|
||||||
});
|
})
|
||||||
|
|
||||||
self.mdl.children.push(nAlert);
|
self.mdl.children.push(nAlert)
|
||||||
|
|
||||||
if (!nAlert.sticky) {
|
if (!nAlert.sticky) {
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
self.close(nAlert._uid);
|
self.close(nAlert._uid)
|
||||||
}, 5000);
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.uidNext++;
|
self.uidNext++
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +68,7 @@ class Alerts {
|
|||||||
message,
|
message,
|
||||||
sticky: false,
|
sticky: false,
|
||||||
title
|
title
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +83,7 @@ class Alerts {
|
|||||||
message,
|
message,
|
||||||
sticky: false,
|
sticky: false,
|
||||||
title
|
title
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,20 +92,18 @@ class Alerts {
|
|||||||
* @param {Integer} uid The unique ID of the alert
|
* @param {Integer} uid The unique ID of the alert
|
||||||
*/
|
*/
|
||||||
close (uid) {
|
close (uid) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid])
|
||||||
|
let nAlert = _.nth(self.mdl.children, nAlertIdx)
|
||||||
let nAlertIdx = _.findIndex(self.mdl.children, ['_uid', uid]);
|
|
||||||
let nAlert = _.nth(self.mdl.children, nAlertIdx);
|
|
||||||
|
|
||||||
if (nAlertIdx >= 0 && nAlert) {
|
if (nAlertIdx >= 0 && nAlert) {
|
||||||
nAlert.class += ' exit';
|
nAlert.class += ' exit'
|
||||||
Vue.set(self.mdl.children, nAlertIdx, nAlert);
|
Vue.set(self.mdl.children, nAlertIdx, nAlert)
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
self.mdl.children.splice(nAlertIdx, 1);
|
self.mdl.children.splice(nAlertIdx, 1)
|
||||||
}, 500);
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,25 +1,25 @@
|
|||||||
|
|
||||||
let modelist = ace.require("ace/ext/modelist");
|
let modelist = ace.require('ace/ext/modelist')
|
||||||
let codeEditor = null;
|
let codeEditor = null
|
||||||
|
|
||||||
// ACE - Mode Loader
|
// ACE - Mode Loader
|
||||||
|
|
||||||
let modelistLoaded = [];
|
let modelistLoaded = []
|
||||||
let loadAceMode = (m) => {
|
let loadAceMode = (m) => {
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: '/js/ace/mode-' + m + '.js',
|
url: '/js/ace/mode-' + m + '.js',
|
||||||
dataType: "script",
|
dataType: 'script',
|
||||||
cache: true,
|
cache: true,
|
||||||
beforeSend: () => {
|
beforeSend: () => {
|
||||||
if (_.includes(modelistLoaded, m)) {
|
if (_.includes(modelistLoaded, m)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success: () => {
|
success: () => {
|
||||||
modelistLoaded.push(m);
|
modelistLoaded.push(m)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vue Code Block instance
|
// Vue Code Block instance
|
||||||
|
|
||||||
@ -33,46 +33,42 @@ let vueCodeBlock = new Vue({
|
|||||||
watch: {
|
watch: {
|
||||||
modeSelected: (val, oldVal) => {
|
modeSelected: (val, oldVal) => {
|
||||||
loadAceMode(val).done(() => {
|
loadAceMode(val).done(() => {
|
||||||
ace.require("ace/mode/" + val);
|
ace.require('ace/mode/' + val)
|
||||||
codeEditor.getSession().setMode("ace/mode/" + val);
|
codeEditor.getSession().setMode('ace/mode/' + val)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
|
$('#modal-editor-codeblock').addClass('is-active')
|
||||||
$('#modal-editor-codeblock').addClass('is-active');
|
|
||||||
|
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
codeEditor = ace.edit("codeblock-editor");
|
codeEditor = ace.edit('codeblock-editor')
|
||||||
codeEditor.setTheme("ace/theme/tomorrow_night");
|
codeEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
codeEditor.getSession().setMode("ace/mode/" + vueCodeBlock.modeSelected);
|
codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
|
||||||
codeEditor.setOption('fontSize', '14px');
|
codeEditor.setOption('fontSize', '14px')
|
||||||
codeEditor.setOption('hScrollBarAlwaysVisible', false);
|
codeEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
codeEditor.setOption('wrap', true);
|
codeEditor.setOption('wrap', true)
|
||||||
|
|
||||||
codeEditor.setValue(vueCodeBlock.initContent);
|
codeEditor.setValue(vueCodeBlock.initContent)
|
||||||
|
|
||||||
codeEditor.focus();
|
|
||||||
codeEditor.renderer.updateFull();
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
|
codeEditor.focus()
|
||||||
|
codeEditor.renderer.updateFull()
|
||||||
|
}, 300)
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-codeblock').removeClass('is-active');
|
$('#modal-editor-codeblock').removeClass('is-active')
|
||||||
vueCodeBlock.initContent = '';
|
vueCodeBlock.initContent = ''
|
||||||
},
|
},
|
||||||
insertCode: (ev) => {
|
insertCode: (ev) => {
|
||||||
|
|
||||||
if (mde.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
mde.codemirror.execCommand('singleSelection');
|
mde.codemirror.execCommand('singleSelection')
|
||||||
}
|
}
|
||||||
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n';
|
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(codeBlockText);
|
|
||||||
vueCodeBlock.cancel();
|
|
||||||
|
|
||||||
|
mde.codemirror.doc.replaceSelection(codeBlockText)
|
||||||
|
vueCodeBlock.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
@ -23,13 +23,13 @@ let vueFile = new Vue({
|
|||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
open: () => {
|
open: () => {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true
|
||||||
$('#modal-editor-file').addClass('is-active');
|
$('#modal-editor-file').addClass('is-active')
|
||||||
vueFile.refreshFolders();
|
vueFile.refreshFolders()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-file').removeClass('is-active');
|
$('#modal-editor-file').removeClass('is-active')
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -37,23 +37,21 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFile: (fileId) => {
|
selectFile: (fileId) => {
|
||||||
vueFile.currentFile = fileId;
|
vueFile.currentFile = fileId
|
||||||
},
|
},
|
||||||
insertFileLink: (ev) => {
|
insertFileLink: (ev) => {
|
||||||
|
|
||||||
if (mde.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
mde.codemirror.execCommand('singleSelection');
|
mde.codemirror.execCommand('singleSelection')
|
||||||
}
|
}
|
||||||
|
|
||||||
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile]);
|
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
|
||||||
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename;
|
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
|
||||||
selFile.titleGuess = _.startCase(selFile.basename);
|
selFile.titleGuess = _.startCase(selFile.basename)
|
||||||
|
|
||||||
let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")';
|
let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(fileText);
|
|
||||||
vueFile.cancel();
|
|
||||||
|
|
||||||
|
mde.codemirror.doc.replaceSelection(fileText)
|
||||||
|
vueFile.cancel()
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -61,37 +59,35 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
newFolder: (ev) => {
|
newFolder: (ev) => {
|
||||||
vueFile.newFolderName = '';
|
vueFile.newFolderName = ''
|
||||||
vueFile.newFolderError = false;
|
vueFile.newFolderError = false
|
||||||
vueFile.newFolderShow = true;
|
vueFile.newFolderShow = true
|
||||||
_.delay(() => { $('#txt-editor-file-newfoldername').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
|
||||||
},
|
},
|
||||||
newFolderDiscard: (ev) => {
|
newFolderDiscard: (ev) => {
|
||||||
vueFile.newFolderShow = false;
|
vueFile.newFolderShow = false
|
||||||
},
|
},
|
||||||
newFolderCreate: (ev) => {
|
newFolderCreate: (ev) => {
|
||||||
|
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
|
||||||
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
|
|
||||||
|
|
||||||
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
||||||
vueFile.newFolderError = true;
|
vueFile.newFolderError = true
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vueFile.newFolderDiscard();
|
vueFile.newFolderDiscard()
|
||||||
vueFile.isLoadingText = 'Creating new folder...';
|
vueFile.isLoadingText = 'Creating new folder...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
||||||
vueFile.folders = data;
|
vueFile.folders = data
|
||||||
vueFile.currentFolder = vueFile.newFolderName;
|
vueFile.currentFolder = vueFile.newFolderName
|
||||||
vueFile.files = [];
|
vueFile.files = []
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -99,35 +95,32 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameFile: () => {
|
renameFile: () => {
|
||||||
|
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ])
|
||||||
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]);
|
vueFile.renameFileFilename = c.basename || ''
|
||||||
vueFile.renameFileFilename = c.basename || '';
|
vueFile.renameFileShow = true
|
||||||
vueFile.renameFileShow = true;
|
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
$('#txt-editor-renamefile').focus();
|
$('#txt-editor-renamefile').focus()
|
||||||
_.defer(() => { $('#txt-editor-file-rename').select(); });
|
_.defer(() => { $('#txt-editor-file-rename').select() })
|
||||||
}, 400);
|
}, 400)
|
||||||
},
|
},
|
||||||
renameFileDiscard: () => {
|
renameFileDiscard: () => {
|
||||||
vueFile.renameFileShow = false;
|
vueFile.renameFileShow = false
|
||||||
},
|
},
|
||||||
renameFileGo: () => {
|
renameFileGo: () => {
|
||||||
|
vueFile.renameFileDiscard()
|
||||||
vueFile.renameFileDiscard();
|
vueFile.isLoadingText = 'Renaming file...'
|
||||||
vueFile.isLoadingText = 'Renaming file...';
|
vueFile.isLoading = true
|
||||||
vueFile.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
vueFile.waitChangeComplete(vueFile.files.length, false);
|
vueFile.waitChangeComplete(vueFile.files.length, false)
|
||||||
} else {
|
} else {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -135,18 +128,18 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
moveFile: (uid, fld) => {
|
moveFile: (uid, fld) => {
|
||||||
vueFile.isLoadingText = 'Moving file...';
|
vueFile.isLoadingText = 'Moving file...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
} else {
|
} else {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -155,20 +148,20 @@ let vueFile = new Vue({
|
|||||||
|
|
||||||
deleteFileWarn: (show) => {
|
deleteFileWarn: (show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ]);
|
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ])
|
||||||
vueFile.deleteFileFilename = c.filename || 'this file';
|
vueFile.deleteFileFilename = c.filename || 'this file'
|
||||||
}
|
}
|
||||||
vueFile.deleteFileShow = show;
|
vueFile.deleteFileShow = show
|
||||||
},
|
},
|
||||||
deleteFileGo: () => {
|
deleteFileGo: () => {
|
||||||
vueFile.deleteFileWarn(false);
|
vueFile.deleteFileWarn(false)
|
||||||
vueFile.isLoadingText = 'Deleting file...';
|
vueFile.isLoadingText = 'Deleting file...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
|
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -176,66 +169,64 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFolder: (fldName) => {
|
selectFolder: (fldName) => {
|
||||||
vueFile.currentFolder = fldName;
|
vueFile.currentFolder = fldName
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders: () => {
|
refreshFolders: () => {
|
||||||
vueFile.isLoadingText = 'Fetching folders list...';
|
vueFile.isLoadingText = 'Fetching folders list...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
vueFile.currentFolder = '';
|
vueFile.currentFolder = ''
|
||||||
vueFile.currentImage = '';
|
vueFile.currentImage = ''
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFolders', { }, (data) => {
|
socket.emit('uploadsGetFolders', { }, (data) => {
|
||||||
vueFile.folders = data;
|
vueFile.folders = data
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
loadFiles: (silent) => {
|
loadFiles: (silent) => {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
vueFile.isLoadingText = 'Fetching files...';
|
vueFile.isLoadingText = 'Fetching files...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
|
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
|
||||||
vueFile.files = data;
|
vueFile.files = data
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
}
|
}
|
||||||
vueFile.attachContextMenus();
|
vueFile.attachContextMenus()
|
||||||
resolve(true);
|
resolve(true)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
waitChangeComplete: (oldAmount, expectChange) => {
|
waitChangeComplete: (oldAmount, expectChange) => {
|
||||||
|
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
|
||||||
|
|
||||||
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
|
vueFile.postUploadChecks++
|
||||||
|
vueFile.isLoadingText = 'Processing...'
|
||||||
vueFile.postUploadChecks++;
|
|
||||||
vueFile.isLoadingText = 'Processing...';
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
vueFile.loadFiles(true).then(() => {
|
vueFile.loadFiles(true).then(() => {
|
||||||
if ((vueFile.files.length !== oldAmount) === expectChange) {
|
if ((vueFile.files.length !== oldAmount) === expectChange) {
|
||||||
vueFile.postUploadChecks = 0;
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
} else if (vueFile.postUploadChecks > 5) {
|
} else if (vueFile.postUploadChecks > 5) {
|
||||||
vueFile.postUploadChecks = 0;
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
} else {
|
} else {
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
vueFile.waitChangeComplete(oldAmount, expectChange);
|
vueFile.waitChangeComplete(oldAmount, expectChange)
|
||||||
}, 1500);
|
}, 1500)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -243,68 +234,66 @@ let vueFile = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
attachContextMenus: () => {
|
||||||
|
|
||||||
let moveFolders = _.map(vueFile.folders, (f) => {
|
let moveFolders = _.map(vueFile.folders, (f) => {
|
||||||
return {
|
return {
|
||||||
name: (f !== '') ? f : '/ (root)',
|
name: (f !== '') ? f : '/ (root)',
|
||||||
icon: 'fa-folder',
|
icon: 'fa-folder',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
let moveFileId = _.toString($(opt.$trigger).data('uid'));
|
let moveFileId = _.toString($(opt.$trigger).data('uid'))
|
||||||
let moveFileDestFolder = _.nth(vueFile.folders, key);
|
let moveFileDestFolder = _.nth(vueFile.folders, key)
|
||||||
vueFile.moveFile(moveFileId, moveFileDestFolder);
|
vueFile.moveFile(moveFileId, moveFileDestFolder)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$.contextMenu('destroy', '.editor-modal-file-choices > figure');
|
$.contextMenu('destroy', '.editor-modal-file-choices > figure')
|
||||||
$.contextMenu({
|
$.contextMenu({
|
||||||
selector: '.editor-modal-file-choices > figure',
|
selector: '.editor-modal-file-choices > figure',
|
||||||
appendTo: '.editor-modal-file-choices',
|
appendTo: '.editor-modal-file-choices',
|
||||||
position: (opt, x, y) => {
|
position: (opt, x, y) => {
|
||||||
$(opt.$trigger).addClass('is-contextopen');
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let trigPos = $(opt.$trigger).position();
|
let trigPos = $(opt.$trigger).position()
|
||||||
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 };
|
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 }
|
||||||
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
|
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
hide: (opt) => {
|
hide: (opt) => {
|
||||||
$(opt.$trigger).removeClass('is-contextopen');
|
$(opt.$trigger).removeClass('is-contextopen')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
rename: {
|
rename: {
|
||||||
name: "Rename",
|
name: 'Rename',
|
||||||
icon: "fa-edit",
|
icon: 'fa-edit',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid);
|
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueFile.renameFile();
|
vueFile.renameFile()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: {
|
move: {
|
||||||
name: "Move to...",
|
name: 'Move to...',
|
||||||
icon: "fa-folder-open-o",
|
icon: 'fa-folder-open-o',
|
||||||
items: moveFolders
|
items: moveFolders
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
name: "Delete",
|
name: 'Delete',
|
||||||
icon: "fa-trash",
|
icon: 'fa-trash',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid);
|
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueFile.deleteFileWarn(true);
|
vueFile.deleteFileWarn(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#btn-editor-file-upload input').on('change', (ev) => {
|
$('#btn-editor-file-upload input').on('change', (ev) => {
|
||||||
|
let curFileAmount = vueFile.files.length
|
||||||
|
|
||||||
let curFileAmount = vueFile.files.length;
|
$(ev.currentTarget).simpleUpload('/uploads/file', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/file", {
|
|
||||||
|
|
||||||
name: 'binfile',
|
name: 'binfile',
|
||||||
data: {
|
data: {
|
||||||
@ -315,51 +304,48 @@ $('#btn-editor-file-upload input').on('change', (ev) => {
|
|||||||
maxFileSize: 0,
|
maxFileSize: 0,
|
||||||
|
|
||||||
init: (totalUploads) => {
|
init: (totalUploads) => {
|
||||||
vueFile.uploadSucceeded = false;
|
vueFile.uploadSucceeded = false
|
||||||
vueFile.isLoadingText = 'Preparing to upload...';
|
vueFile.isLoadingText = 'Preparing to upload...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
},
|
},
|
||||||
|
|
||||||
progress: (progress) => {
|
progress: (progress) => {
|
||||||
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
|
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
|
||||||
},
|
},
|
||||||
|
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
|
let failedUpls = _.filter(data.results, ['ok', false])
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
|
||||||
if (failedUpls.length) {
|
if (failedUpls.length) {
|
||||||
_.forEach(failedUpls, (u) => {
|
_.forEach(failedUpls, (u) => {
|
||||||
alerts.pushError('Upload error', u.msg);
|
alerts.pushError('Upload error', u.msg)
|
||||||
});
|
})
|
||||||
if (failedUpls.length < data.results.length) {
|
if (failedUpls.length < data.results.length) {
|
||||||
alerts.push({
|
alerts.push({
|
||||||
title: 'Some uploads succeeded',
|
title: 'Some uploads succeeded',
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
});
|
})
|
||||||
vueFile.uploadSucceeded = true;
|
vueFile.uploadSucceeded = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vueFile.uploadSucceeded = true;
|
vueFile.uploadSucceeded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alerts.pushError('Upload error', data.msg);
|
alerts.pushError('Upload error', data.msg)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
},
|
},
|
||||||
|
|
||||||
finish: () => {
|
finish: () => {
|
||||||
if (vueFile.uploadSucceeded) {
|
if (vueFile.uploadSucceeded) {
|
||||||
vueFile.waitChangeComplete(curFileAmount, true);
|
vueFile.waitChangeComplete(curFileAmount, true)
|
||||||
} else {
|
} else {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
@ -26,13 +26,13 @@ let vueImage = new Vue({
|
|||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
open: () => {
|
open: () => {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true
|
||||||
$('#modal-editor-image').addClass('is-active');
|
$('#modal-editor-image').addClass('is-active')
|
||||||
vueImage.refreshFolders();
|
vueImage.refreshFolders()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-image').removeClass('is-active');
|
$('#modal-editor-image').removeClass('is-active')
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -40,34 +40,32 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectImage: (imageId) => {
|
selectImage: (imageId) => {
|
||||||
vueImage.currentImage = imageId;
|
vueImage.currentImage = imageId
|
||||||
},
|
},
|
||||||
insertImage: (ev) => {
|
insertImage: (ev) => {
|
||||||
|
|
||||||
if (mde.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
mde.codemirror.execCommand('singleSelection');
|
mde.codemirror.execCommand('singleSelection')
|
||||||
}
|
}
|
||||||
|
|
||||||
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage]);
|
let selImage = _.find(vueImage.images, ['_id', vueImage.currentImage])
|
||||||
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename;
|
selImage.normalizedPath = (selImage.folder === 'f:') ? selImage.filename : selImage.folder.slice(2) + '/' + selImage.filename
|
||||||
selImage.titleGuess = _.startCase(selImage.basename);
|
selImage.titleGuess = _.startCase(selImage.basename)
|
||||||
|
|
||||||
let imageText = '';
|
let imageText = ''
|
||||||
switch (vueImage.currentAlign) {
|
switch (vueImage.currentAlign) {
|
||||||
case 'center':
|
case 'center':
|
||||||
imageText += '{.align-center}';
|
imageText += '{.align-center}'
|
||||||
break;
|
break
|
||||||
case 'right':
|
case 'right':
|
||||||
imageText += '{.align-right}';
|
imageText += '{.align-right}'
|
||||||
break;
|
break
|
||||||
case 'logo':
|
case 'logo':
|
||||||
imageText += '{.pagelogo}';
|
imageText += '{.pagelogo}'
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(imageText);
|
mde.codemirror.doc.replaceSelection(imageText)
|
||||||
vueImage.cancel();
|
vueImage.cancel()
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -75,37 +73,35 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
newFolder: (ev) => {
|
newFolder: (ev) => {
|
||||||
vueImage.newFolderName = '';
|
vueImage.newFolderName = ''
|
||||||
vueImage.newFolderError = false;
|
vueImage.newFolderError = false
|
||||||
vueImage.newFolderShow = true;
|
vueImage.newFolderShow = true
|
||||||
_.delay(() => { $('#txt-editor-image-newfoldername').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
|
||||||
},
|
},
|
||||||
newFolderDiscard: (ev) => {
|
newFolderDiscard: (ev) => {
|
||||||
vueImage.newFolderShow = false;
|
vueImage.newFolderShow = false
|
||||||
},
|
},
|
||||||
newFolderCreate: (ev) => {
|
newFolderCreate: (ev) => {
|
||||||
|
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
|
||||||
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
|
|
||||||
|
|
||||||
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
||||||
vueImage.newFolderError = true;
|
vueImage.newFolderError = true
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vueImage.newFolderDiscard();
|
vueImage.newFolderDiscard()
|
||||||
vueImage.isLoadingText = 'Creating new folder...';
|
vueImage.isLoadingText = 'Creating new folder...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
||||||
vueImage.folders = data;
|
vueImage.folders = data
|
||||||
vueImage.currentFolder = vueImage.newFolderName;
|
vueImage.currentFolder = vueImage.newFolderName
|
||||||
vueImage.images = [];
|
vueImage.images = []
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -113,30 +109,28 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
fetchFromUrl: (ev) => {
|
fetchFromUrl: (ev) => {
|
||||||
vueImage.fetchFromUrlURL = '';
|
vueImage.fetchFromUrlURL = ''
|
||||||
vueImage.fetchFromUrlShow = true;
|
vueImage.fetchFromUrlShow = true
|
||||||
_.delay(() => { $('#txt-editor-image-fetchurl').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
|
||||||
},
|
},
|
||||||
fetchFromUrlDiscard: (ev) => {
|
fetchFromUrlDiscard: (ev) => {
|
||||||
vueImage.fetchFromUrlShow = false;
|
vueImage.fetchFromUrlShow = false
|
||||||
},
|
},
|
||||||
fetchFromUrlGo: (ev) => {
|
fetchFromUrlGo: (ev) => {
|
||||||
|
vueImage.fetchFromUrlDiscard()
|
||||||
vueImage.fetchFromUrlDiscard();
|
vueImage.isLoadingText = 'Fetching image...'
|
||||||
vueImage.isLoadingText = 'Fetching image...';
|
vueImage.isLoading = true
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, true);
|
vueImage.waitChangeComplete(vueImage.images.length, true)
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Upload error', data.msg);
|
alerts.pushError('Upload error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -144,35 +138,32 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameImage: () => {
|
renameImage: () => {
|
||||||
|
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
|
||||||
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]);
|
vueImage.renameImageFilename = c.basename || ''
|
||||||
vueImage.renameImageFilename = c.basename || '';
|
vueImage.renameImageShow = true
|
||||||
vueImage.renameImageShow = true;
|
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
$('#txt-editor-image-rename').focus();
|
$('#txt-editor-image-rename').focus()
|
||||||
_.defer(() => { $('#txt-editor-image-rename').select(); });
|
_.defer(() => { $('#txt-editor-image-rename').select() })
|
||||||
}, 400);
|
}, 400)
|
||||||
},
|
},
|
||||||
renameImageDiscard: () => {
|
renameImageDiscard: () => {
|
||||||
vueImage.renameImageShow = false;
|
vueImage.renameImageShow = false
|
||||||
},
|
},
|
||||||
renameImageGo: () => {
|
renameImageGo: () => {
|
||||||
|
vueImage.renameImageDiscard()
|
||||||
vueImage.renameImageDiscard();
|
vueImage.isLoadingText = 'Renaming image...'
|
||||||
vueImage.isLoadingText = 'Renaming image...';
|
vueImage.isLoading = true
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, false);
|
vueImage.waitChangeComplete(vueImage.images.length, false)
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -180,18 +171,18 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
moveImage: (uid, fld) => {
|
moveImage: (uid, fld) => {
|
||||||
vueImage.isLoadingText = 'Moving image...';
|
vueImage.isLoadingText = 'Moving image...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -200,20 +191,20 @@ let vueImage = new Vue({
|
|||||||
|
|
||||||
deleteImageWarn: (show) => {
|
deleteImageWarn: (show) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ]);
|
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ])
|
||||||
vueImage.deleteImageFilename = c.filename || 'this image';
|
vueImage.deleteImageFilename = c.filename || 'this image'
|
||||||
}
|
}
|
||||||
vueImage.deleteImageShow = show;
|
vueImage.deleteImageShow = show
|
||||||
},
|
},
|
||||||
deleteImageGo: () => {
|
deleteImageGo: () => {
|
||||||
vueImage.deleteImageWarn(false);
|
vueImage.deleteImageWarn(false)
|
||||||
vueImage.isLoadingText = 'Deleting image...';
|
vueImage.isLoadingText = 'Deleting image...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
|
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -221,66 +212,64 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFolder: (fldName) => {
|
selectFolder: (fldName) => {
|
||||||
vueImage.currentFolder = fldName;
|
vueImage.currentFolder = fldName
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders: () => {
|
refreshFolders: () => {
|
||||||
vueImage.isLoadingText = 'Fetching folders list...';
|
vueImage.isLoadingText = 'Fetching folders list...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
vueImage.currentFolder = '';
|
vueImage.currentFolder = ''
|
||||||
vueImage.currentImage = '';
|
vueImage.currentImage = ''
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFolders', { }, (data) => {
|
socket.emit('uploadsGetFolders', { }, (data) => {
|
||||||
vueImage.folders = data;
|
vueImage.folders = data
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
loadImages: (silent) => {
|
loadImages: (silent) => {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
vueImage.isLoadingText = 'Fetching images...';
|
vueImage.isLoadingText = 'Fetching images...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
|
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
|
||||||
vueImage.images = data;
|
vueImage.images = data
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
}
|
}
|
||||||
vueImage.attachContextMenus();
|
vueImage.attachContextMenus()
|
||||||
resolve(true);
|
resolve(true)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
waitChangeComplete: (oldAmount, expectChange) => {
|
waitChangeComplete: (oldAmount, expectChange) => {
|
||||||
|
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
|
||||||
|
|
||||||
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
|
vueImage.postUploadChecks++
|
||||||
|
vueImage.isLoadingText = 'Processing...'
|
||||||
vueImage.postUploadChecks++;
|
|
||||||
vueImage.isLoadingText = 'Processing...';
|
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
vueImage.loadImages(true).then(() => {
|
vueImage.loadImages(true).then(() => {
|
||||||
if ((vueImage.images.length !== oldAmount) === expectChange) {
|
if ((vueImage.images.length !== oldAmount) === expectChange) {
|
||||||
vueImage.postUploadChecks = 0;
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
} else if (vueImage.postUploadChecks > 5) {
|
} else if (vueImage.postUploadChecks > 5) {
|
||||||
vueImage.postUploadChecks = 0;
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
} else {
|
} else {
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
vueImage.waitChangeComplete(oldAmount, expectChange);
|
vueImage.waitChangeComplete(oldAmount, expectChange)
|
||||||
}, 1500);
|
}, 1500)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
@ -288,68 +277,66 @@ let vueImage = new Vue({
|
|||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
attachContextMenus: () => {
|
||||||
|
|
||||||
let moveFolders = _.map(vueImage.folders, (f) => {
|
let moveFolders = _.map(vueImage.folders, (f) => {
|
||||||
return {
|
return {
|
||||||
name: (f !== '') ? f : '/ (root)',
|
name: (f !== '') ? f : '/ (root)',
|
||||||
icon: 'fa-folder',
|
icon: 'fa-folder',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
let moveImageId = _.toString($(opt.$trigger).data('uid'));
|
let moveImageId = _.toString($(opt.$trigger).data('uid'))
|
||||||
let moveImageDestFolder = _.nth(vueImage.folders, key);
|
let moveImageDestFolder = _.nth(vueImage.folders, key)
|
||||||
vueImage.moveImage(moveImageId, moveImageDestFolder);
|
vueImage.moveImage(moveImageId, moveImageDestFolder)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$.contextMenu('destroy', '.editor-modal-image-choices > figure');
|
$.contextMenu('destroy', '.editor-modal-image-choices > figure')
|
||||||
$.contextMenu({
|
$.contextMenu({
|
||||||
selector: '.editor-modal-image-choices > figure',
|
selector: '.editor-modal-image-choices > figure',
|
||||||
appendTo: '.editor-modal-image-choices',
|
appendTo: '.editor-modal-image-choices',
|
||||||
position: (opt, x, y) => {
|
position: (opt, x, y) => {
|
||||||
$(opt.$trigger).addClass('is-contextopen');
|
$(opt.$trigger).addClass('is-contextopen')
|
||||||
let trigPos = $(opt.$trigger).position();
|
let trigPos = $(opt.$trigger).position()
|
||||||
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 };
|
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
|
||||||
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
|
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w })
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
hide: (opt) => {
|
hide: (opt) => {
|
||||||
$(opt.$trigger).removeClass('is-contextopen');
|
$(opt.$trigger).removeClass('is-contextopen')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
rename: {
|
rename: {
|
||||||
name: "Rename",
|
name: 'Rename',
|
||||||
icon: "fa-edit",
|
icon: 'fa-edit',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid);
|
vueImage.renameImageId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueImage.renameImage();
|
vueImage.renameImage()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
move: {
|
move: {
|
||||||
name: "Move to...",
|
name: 'Move to...',
|
||||||
icon: "fa-folder-open-o",
|
icon: 'fa-folder-open-o',
|
||||||
items: moveFolders
|
items: moveFolders
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
name: "Delete",
|
name: 'Delete',
|
||||||
icon: "fa-trash",
|
icon: 'fa-trash',
|
||||||
callback: (key, opt) => {
|
callback: (key, opt) => {
|
||||||
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid);
|
vueImage.deleteImageId = _.toString(opt.$trigger[0].dataset.uid)
|
||||||
vueImage.deleteImageWarn(true);
|
vueImage.deleteImageWarn(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#btn-editor-image-upload input').on('change', (ev) => {
|
$('#btn-editor-image-upload input').on('change', (ev) => {
|
||||||
|
let curImageAmount = vueImage.images.length
|
||||||
|
|
||||||
let curImageAmount = vueImage.images.length;
|
$(ev.currentTarget).simpleUpload('/uploads/img', {
|
||||||
|
|
||||||
$(ev.currentTarget).simpleUpload("/uploads/img", {
|
|
||||||
|
|
||||||
name: 'imgfile',
|
name: 'imgfile',
|
||||||
data: {
|
data: {
|
||||||
@ -357,56 +344,53 @@ $('#btn-editor-image-upload input').on('change', (ev) => {
|
|||||||
},
|
},
|
||||||
limit: 20,
|
limit: 20,
|
||||||
expect: 'json',
|
expect: 'json',
|
||||||
allowedExts: ["jpg", "jpeg", "gif", "png", "webp"],
|
allowedExts: ['jpg', 'jpeg', 'gif', 'png', 'webp'],
|
||||||
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
||||||
maxFileSize: 3145728, // max 3 MB
|
maxFileSize: 3145728, // max 3 MB
|
||||||
|
|
||||||
init: (totalUploads) => {
|
init: (totalUploads) => {
|
||||||
vueImage.uploadSucceeded = false;
|
vueImage.uploadSucceeded = false
|
||||||
vueImage.isLoadingText = 'Preparing to upload...';
|
vueImage.isLoadingText = 'Preparing to upload...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
},
|
},
|
||||||
|
|
||||||
progress: (progress) => {
|
progress: (progress) => {
|
||||||
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
|
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%'
|
||||||
},
|
},
|
||||||
|
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
|
let failedUpls = _.filter(data.results, ['ok', false])
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
|
||||||
if (failedUpls.length) {
|
if (failedUpls.length) {
|
||||||
_.forEach(failedUpls, (u) => {
|
_.forEach(failedUpls, (u) => {
|
||||||
alerts.pushError('Upload error', u.msg);
|
alerts.pushError('Upload error', u.msg)
|
||||||
});
|
})
|
||||||
if (failedUpls.length < data.results.length) {
|
if (failedUpls.length < data.results.length) {
|
||||||
alerts.push({
|
alerts.push({
|
||||||
title: 'Some uploads succeeded',
|
title: 'Some uploads succeeded',
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
});
|
})
|
||||||
vueImage.uploadSucceeded = true;
|
vueImage.uploadSucceeded = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vueImage.uploadSucceeded = true;
|
vueImage.uploadSucceeded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alerts.pushError('Upload error', data.msg);
|
alerts.pushError('Upload error', data.msg)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
},
|
},
|
||||||
|
|
||||||
finish: () => {
|
finish: () => {
|
||||||
if (vueImage.uploadSucceeded) {
|
if (vueImage.uploadSucceeded) {
|
||||||
vueImage.waitChangeComplete(curImageAmount, true);
|
vueImage.waitChangeComplete(curImageAmount, true)
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
@ -3,7 +3,7 @@ const videoRules = {
|
|||||||
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
|
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
|
||||||
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
||||||
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
|
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
|
||||||
};
|
}
|
||||||
|
|
||||||
// Vue Video instance
|
// Vue Video instance
|
||||||
|
|
||||||
@ -14,36 +14,34 @@ let vueVideo = new Vue({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
$('#modal-editor-video').addClass('is-active');
|
$('#modal-editor-video').addClass('is-active')
|
||||||
$('#modal-editor-video input').focus();
|
$('#modal-editor-video input').focus()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-video').removeClass('is-active');
|
$('#modal-editor-video').removeClass('is-active')
|
||||||
vueVideo.link = '';
|
vueVideo.link = ''
|
||||||
},
|
},
|
||||||
insertVideo: (ev) => {
|
insertVideo: (ev) => {
|
||||||
|
|
||||||
if (mde.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
mde.codemirror.execCommand('singleSelection');
|
mde.codemirror.execCommand('singleSelection')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guess video type
|
// Guess video type
|
||||||
|
|
||||||
let videoType = _.findKey(videoRules, (vr) => {
|
let videoType = _.findKey(videoRules, (vr) => {
|
||||||
return vr.test(vueVideo.link);
|
return vr.test(vueVideo.link)
|
||||||
});
|
})
|
||||||
if (_.isNil(videoType)) {
|
if (_.isNil(videoType)) {
|
||||||
videoType = 'video';
|
videoType = 'video'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert video tag
|
// Insert video tag
|
||||||
|
|
||||||
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n';
|
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(videoText);
|
|
||||||
vueVideo.cancel();
|
|
||||||
|
|
||||||
|
mde.codemirror.doc.replaceSelection(videoText)
|
||||||
|
vueVideo.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
@ -4,13 +4,12 @@
|
|||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
if ($('#mk-editor').length === 1) {
|
if ($('#mk-editor').length === 1) {
|
||||||
|
let mdeModalOpenState = false
|
||||||
let mdeModalOpenState = false;
|
let mdeCurrentEditor = null
|
||||||
let mdeCurrentEditor = null;
|
|
||||||
|
|
||||||
Vue.filter('filesize', (v) => {
|
Vue.filter('filesize', (v) => {
|
||||||
return _.toUpper(filesize(v));
|
return _.toUpper(filesize(v))
|
||||||
});
|
})
|
||||||
|
|
||||||
// =include editor-image.js
|
// =include editor-image.js
|
||||||
// =include editor-file.js
|
// =include editor-file.js
|
||||||
@ -20,164 +19,161 @@ if($('#mk-editor').length === 1) {
|
|||||||
var mde = new SimpleMDE({
|
var mde = new SimpleMDE({
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autoDownloadFontAwesome: false,
|
autoDownloadFontAwesome: false,
|
||||||
element: $("#mk-editor").get(0),
|
element: $('#mk-editor').get(0),
|
||||||
placeholder: 'Enter Markdown formatted content here...',
|
placeholder: 'Enter Markdown formatted content here...',
|
||||||
spellChecker: false,
|
spellChecker: false,
|
||||||
status: false,
|
status: false,
|
||||||
toolbar: [{
|
toolbar: [{
|
||||||
name: "bold",
|
name: 'bold',
|
||||||
action: SimpleMDE.toggleBold,
|
action: SimpleMDE.toggleBold,
|
||||||
className: "icon-bold",
|
className: 'icon-bold',
|
||||||
title: "Bold",
|
title: 'Bold'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "italic",
|
name: 'italic',
|
||||||
action: SimpleMDE.toggleItalic,
|
action: SimpleMDE.toggleItalic,
|
||||||
className: "icon-italic",
|
className: 'icon-italic',
|
||||||
title: "Italic",
|
title: 'Italic'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "strikethrough",
|
name: 'strikethrough',
|
||||||
action: SimpleMDE.toggleStrikethrough,
|
action: SimpleMDE.toggleStrikethrough,
|
||||||
className: "icon-strikethrough",
|
className: 'icon-strikethrough',
|
||||||
title: "Strikethrough",
|
title: 'Strikethrough'
|
||||||
},
|
},
|
||||||
'|',
|
'|',
|
||||||
{
|
{
|
||||||
name: "heading-1",
|
name: 'heading-1',
|
||||||
action: SimpleMDE.toggleHeading1,
|
action: SimpleMDE.toggleHeading1,
|
||||||
className: "icon-header fa-header-x fa-header-1",
|
className: 'icon-header fa-header-x fa-header-1',
|
||||||
title: "Big Heading",
|
title: 'Big Heading'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "heading-2",
|
name: 'heading-2',
|
||||||
action: SimpleMDE.toggleHeading2,
|
action: SimpleMDE.toggleHeading2,
|
||||||
className: "icon-header fa-header-x fa-header-2",
|
className: 'icon-header fa-header-x fa-header-2',
|
||||||
title: "Medium Heading",
|
title: 'Medium Heading'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "heading-3",
|
name: 'heading-3',
|
||||||
action: SimpleMDE.toggleHeading3,
|
action: SimpleMDE.toggleHeading3,
|
||||||
className: "icon-header fa-header-x fa-header-3",
|
className: 'icon-header fa-header-x fa-header-3',
|
||||||
title: "Small Heading",
|
title: 'Small Heading'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "quote",
|
name: 'quote',
|
||||||
action: SimpleMDE.toggleBlockquote,
|
action: SimpleMDE.toggleBlockquote,
|
||||||
className: "icon-quote-left",
|
className: 'icon-quote-left',
|
||||||
title: "Quote",
|
title: 'Quote'
|
||||||
},
|
},
|
||||||
'|',
|
'|',
|
||||||
{
|
{
|
||||||
name: "unordered-list",
|
name: 'unordered-list',
|
||||||
action: SimpleMDE.toggleUnorderedList,
|
action: SimpleMDE.toggleUnorderedList,
|
||||||
className: "icon-th-list",
|
className: 'icon-th-list',
|
||||||
title: "Bullet List",
|
title: 'Bullet List'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ordered-list",
|
name: 'ordered-list',
|
||||||
action: SimpleMDE.toggleOrderedList,
|
action: SimpleMDE.toggleOrderedList,
|
||||||
className: "icon-list-ol",
|
className: 'icon-list-ol',
|
||||||
title: "Numbered List",
|
title: 'Numbered List'
|
||||||
},
|
},
|
||||||
'|',
|
'|',
|
||||||
{
|
{
|
||||||
name: "link",
|
name: 'link',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
/* if(!mdeModalOpenState) {
|
/* if(!mdeModalOpenState) {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true;
|
||||||
$('#modal-editor-link').slideToggle();
|
$('#modal-editor-link').slideToggle();
|
||||||
} */
|
} */
|
||||||
},
|
},
|
||||||
className: "icon-link2",
|
className: 'icon-link2',
|
||||||
title: "Insert Link",
|
title: 'Insert Link'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "image",
|
name: 'image',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
if (!mdeModalOpenState) {
|
if (!mdeModalOpenState) {
|
||||||
vueImage.open();
|
vueImage.open()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: "icon-image",
|
className: 'icon-image',
|
||||||
title: "Insert Image",
|
title: 'Insert Image'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "file",
|
name: 'file',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
if (!mdeModalOpenState) {
|
if (!mdeModalOpenState) {
|
||||||
vueFile.open();
|
vueFile.open()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: "icon-paper",
|
className: 'icon-paper',
|
||||||
title: "Insert File",
|
title: 'Insert File'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "video",
|
name: 'video',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
if (!mdeModalOpenState) {
|
if (!mdeModalOpenState) {
|
||||||
vueVideo.open();
|
vueVideo.open()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: "icon-video-camera2",
|
className: 'icon-video-camera2',
|
||||||
title: "Insert Video Player",
|
title: 'Insert Video Player'
|
||||||
},
|
},
|
||||||
'|',
|
'|',
|
||||||
{
|
{
|
||||||
name: "inline-code",
|
name: 'inline-code',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
|
|
||||||
if (!editor.codemirror.doc.somethingSelected()) {
|
if (!editor.codemirror.doc.somethingSelected()) {
|
||||||
return alerts.pushError('Invalid selection','You must select at least 1 character first.');
|
return alerts.pushError('Invalid selection', 'You must select at least 1 character first.')
|
||||||
}
|
}
|
||||||
let curSel = editor.codemirror.doc.getSelections();
|
let curSel = editor.codemirror.doc.getSelections()
|
||||||
curSel = _.map(curSel, (s) => {
|
curSel = _.map(curSel, (s) => {
|
||||||
return '`' + s + '`';
|
return '`' + s + '`'
|
||||||
});
|
})
|
||||||
editor.codemirror.doc.replaceSelections(curSel);
|
editor.codemirror.doc.replaceSelections(curSel)
|
||||||
|
|
||||||
},
|
},
|
||||||
className: "icon-terminal",
|
className: 'icon-terminal',
|
||||||
title: "Inline Code",
|
title: 'Inline Code'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code-block",
|
name: 'code-block',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
if (!mdeModalOpenState) {
|
if (!mdeModalOpenState) {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true
|
||||||
|
|
||||||
if (mde.codemirror.doc.somethingSelected()) {
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
vueCodeBlock.initContent = mde.codemirror.doc.getSelection();
|
vueCodeBlock.initContent = mde.codemirror.doc.getSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
vueCodeBlock.open();
|
vueCodeBlock.open()
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
className: "icon-code",
|
className: 'icon-code',
|
||||||
title: "Code Block",
|
title: 'Code Block'
|
||||||
},
|
},
|
||||||
'|',
|
'|',
|
||||||
{
|
{
|
||||||
name: "table",
|
name: 'table',
|
||||||
action: (editor) => {
|
action: (editor) => {
|
||||||
// todo
|
// todo
|
||||||
},
|
},
|
||||||
className: "icon-table",
|
className: 'icon-table',
|
||||||
title: "Insert Table",
|
title: 'Insert Table'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "horizontal-rule",
|
name: 'horizontal-rule',
|
||||||
action: SimpleMDE.drawHorizontalRule,
|
action: SimpleMDE.drawHorizontalRule,
|
||||||
className: "icon-minus2",
|
className: 'icon-minus2',
|
||||||
title: "Horizontal Rule",
|
title: 'Horizontal Rule'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
"toggleBlockquote": null,
|
'toggleBlockquote': null,
|
||||||
"toggleFullScreen": null
|
'toggleFullScreen': null
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// -> Save
|
// -> Save
|
||||||
|
|
||||||
@ -190,28 +186,27 @@ if($('#mk-editor').length === 1) {
|
|||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
}).then((rData, rStatus, rXHR) => {
|
}).then((rData, rStatus, rXHR) => {
|
||||||
if (rData.ok) {
|
if (rData.ok) {
|
||||||
window.location.assign('/' + pageEntryPath);
|
window.location.assign('/' + pageEntryPath)
|
||||||
} else {
|
} else {
|
||||||
alerts.pushError('Something went wrong', rData.error);
|
alerts.pushError('Something went wrong', rData.error)
|
||||||
}
|
}
|
||||||
}, (rXHR, rStatus, err) => {
|
}, (rXHR, rStatus, err) => {
|
||||||
alerts.pushError('Something went wrong', 'Save operation failed.');
|
alerts.pushError('Something went wrong', 'Save operation failed.')
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
|
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
|
||||||
saveCurrentDocument(ev);
|
saveCurrentDocument(ev)
|
||||||
});
|
})
|
||||||
|
|
||||||
$(window).bind('keydown', (ev) => {
|
$(window).bind('keydown', (ev) => {
|
||||||
if (ev.ctrlKey || ev.metaKey) {
|
if (ev.ctrlKey || ev.metaKey) {
|
||||||
switch (String.fromCharCode(ev.which).toLowerCase()) {
|
switch (String.fromCharCode(ev.which).toLowerCase()) {
|
||||||
case 's':
|
case 's':
|
||||||
ev.preventDefault();
|
ev.preventDefault()
|
||||||
saveCurrentDocument(ev);
|
saveCurrentDocument(ev)
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
@ -1,10 +1,9 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
if ($('#search-input').length) {
|
if ($('#search-input').length) {
|
||||||
|
$('#search-input').focus()
|
||||||
|
|
||||||
$('#search-input').focus();
|
$('.searchresults').css('display', 'block')
|
||||||
|
|
||||||
$('.searchresults').css('display', 'block');
|
|
||||||
|
|
||||||
var vueHeader = new Vue({
|
var vueHeader = new Vue({
|
||||||
el: '#header-container',
|
el: '#header-container',
|
||||||
@ -20,65 +19,63 @@ if($('#search-input').length) {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchq: (val, oldVal) => {
|
searchq: (val, oldVal) => {
|
||||||
vueHeader.searchmoveidx = 0;
|
vueHeader.searchmoveidx = 0
|
||||||
if (val.length >= 3) {
|
if (val.length >= 3) {
|
||||||
vueHeader.searchactive = true;
|
vueHeader.searchactive = true
|
||||||
vueHeader.searchload++;
|
vueHeader.searchload++
|
||||||
socket.emit('search', { terms: val }, (data) => {
|
socket.emit('search', { terms: val }, (data) => {
|
||||||
vueHeader.searchres = data.match;
|
vueHeader.searchres = data.match
|
||||||
vueHeader.searchsuggest = data.suggest;
|
vueHeader.searchsuggest = data.suggest
|
||||||
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest);
|
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
|
||||||
if(vueHeader.searchload > 0) { vueHeader.searchload--; }
|
if (vueHeader.searchload > 0) { vueHeader.searchload-- }
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchactive = false;
|
vueHeader.searchactive = false
|
||||||
vueHeader.searchres = [];
|
vueHeader.searchres = []
|
||||||
vueHeader.searchsuggest = [];
|
vueHeader.searchsuggest = []
|
||||||
vueHeader.searchmovearr = [];
|
vueHeader.searchmovearr = []
|
||||||
vueHeader.searchload = 0;
|
vueHeader.searchload = 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchmoveidx: (val, oldVal) => {
|
searchmoveidx: (val, oldVal) => {
|
||||||
if (val > 0) {
|
if (val > 0) {
|
||||||
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
|
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
|
||||||
'res.' + vueHeader.searchmovearr[val - 1]._id :
|
'res.' + vueHeader.searchmovearr[val - 1]._id :
|
||||||
'sug.' + vueHeader.searchmovearr[val - 1];
|
'sug.' + vueHeader.searchmovearr[val - 1]
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchmovekey = '';
|
vueHeader.searchmovekey = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
useSuggestion: (sug) => {
|
useSuggestion: (sug) => {
|
||||||
vueHeader.searchq = sug;
|
vueHeader.searchq = sug
|
||||||
},
|
},
|
||||||
closeSearch: () => {
|
closeSearch: () => {
|
||||||
vueHeader.searchq = '';
|
vueHeader.searchq = ''
|
||||||
},
|
},
|
||||||
moveSelectSearch: () => {
|
moveSelectSearch: () => {
|
||||||
if(vueHeader.searchmoveidx < 1) { return; }
|
if (vueHeader.searchmoveidx < 1) { return }
|
||||||
let i = vueHeader.searchmoveidx - 1;
|
let i = vueHeader.searchmoveidx - 1
|
||||||
|
|
||||||
if (vueHeader.searchmovearr[i]) {
|
if (vueHeader.searchmovearr[i]) {
|
||||||
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
|
window.location.assign('/' + vueHeader.searchmovearr[i]._id)
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchq = vueHeader.searchmovearr[i];
|
vueHeader.searchq = vueHeader.searchmovearr[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
moveDownSearch: () => {
|
moveDownSearch: () => {
|
||||||
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
||||||
vueHeader.searchmoveidx++;
|
vueHeader.searchmoveidx++
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
moveUpSearch: () => {
|
moveUpSearch: () => {
|
||||||
if (vueHeader.searchmoveidx > 0) {
|
if (vueHeader.searchmoveidx > 0) {
|
||||||
vueHeader.searchmoveidx--;
|
vueHeader.searchmoveidx--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('main').on('click', vueHeader.closeSearch);
|
|
||||||
|
|
||||||
|
$('main').on('click', vueHeader.closeSearch)
|
||||||
}
|
}
|
@ -1,16 +1,16 @@
|
|||||||
|
|
||||||
function setInputSelection (input, startPos, endPos) {
|
function setInputSelection (input, startPos, endPos) {
|
||||||
input.focus();
|
input.focus()
|
||||||
if (typeof input.selectionStart != "undefined") {
|
if (typeof input.selectionStart !== 'undefined') {
|
||||||
input.selectionStart = startPos;
|
input.selectionStart = startPos
|
||||||
input.selectionEnd = endPos;
|
input.selectionEnd = endPos
|
||||||
} else if (document.selection && document.selection.createRange) {
|
} else if (document.selection && document.selection.createRange) {
|
||||||
// IE branch
|
// IE branch
|
||||||
input.select();
|
input.select()
|
||||||
var range = document.selection.createRange();
|
var range = document.selection.createRange()
|
||||||
range.collapse(true);
|
range.collapse(true)
|
||||||
range.moveEnd("character", endPos);
|
range.moveEnd('character', endPos)
|
||||||
range.moveStart("character", startPos);
|
range.moveStart('character', startPos)
|
||||||
range.select();
|
range.select()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
function makeSafePath (rawPath) {
|
function makeSafePath (rawPath) {
|
||||||
|
let rawParts = _.split(_.trim(rawPath), '/')
|
||||||
let rawParts = _.split(_.trim(rawPath), '/');
|
|
||||||
rawParts = _.map(rawParts, (r) => {
|
rawParts = _.map(rawParts, (r) => {
|
||||||
return _.kebabCase(_.deburr(_.trim(r)));
|
return _.kebabCase(_.deburr(_.trim(r)))
|
||||||
});
|
})
|
||||||
|
|
||||||
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r); }), '/');
|
|
||||||
|
|
||||||
|
return _.join(_.filter(rawParts, (r) => { return !_.isEmpty(r) }), '/')
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
jQuery(document).ready(function ($) {
|
jQuery(document).ready(function ($) {
|
||||||
|
$('#login-user').focus()
|
||||||
$('#login-user').focus();
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -11,20 +11,18 @@ let vueCreateUser = new Vue({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
$('#modal-admin-users-create').addClass('is-active');
|
$('#modal-admin-users-create').addClass('is-active')
|
||||||
$('#modal-admin-users-create input').first().focus();
|
$('#modal-admin-users-create input').first().focus()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
$('#modal-admin-users-create').removeClass('is-active');
|
$('#modal-admin-users-create').removeClass('is-active')
|
||||||
vueCreateUser.email = '';
|
vueCreateUser.email = ''
|
||||||
vueCreateUser.provider = 'local';
|
vueCreateUser.provider = 'local'
|
||||||
},
|
},
|
||||||
create: (ev) => {
|
create: (ev) => {
|
||||||
|
vueCreateUser.cancel()
|
||||||
vueCreateUser.cancel();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-create-prompt').on('click', vueCreateUser.open);
|
$('.btn-create-prompt').on('click', vueCreateUser.open)
|
||||||
|
@ -8,15 +8,15 @@ let vueDeleteUser = new Vue({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
$('#modal-admin-users-delete').addClass('is-active');
|
$('#modal-admin-users-delete').addClass('is-active')
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
$('#modal-admin-users-delete').removeClass('is-active');
|
$('#modal-admin-users-delete').removeClass('is-active')
|
||||||
},
|
},
|
||||||
deleteUser: (ev) => {
|
deleteUser: (ev) => {
|
||||||
vueDeleteUser.cancel();
|
vueDeleteUser.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-deluser-prompt').on('click', vueDeleteUser.open);
|
$('.btn-deluser-prompt').on('click', vueDeleteUser.open)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
});
|
|
||||||
|
@ -2,31 +2,30 @@
|
|||||||
// -> Move Existing Document
|
// -> Move Existing Document
|
||||||
|
|
||||||
if (currentBasePath !== '') {
|
if (currentBasePath !== '') {
|
||||||
$('.btn-move-prompt').removeClass('is-hidden');
|
$('.btn-move-prompt').removeClass('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1;
|
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
|
||||||
|
|
||||||
$('.btn-move-prompt').on('click', (ev) => {
|
$('.btn-move-prompt').on('click', (ev) => {
|
||||||
$('#txt-move-prompt').val(currentBasePath);
|
$('#txt-move-prompt').val(currentBasePath)
|
||||||
$('#modal-move-prompt').toggleClass('is-active');
|
$('#modal-move-prompt').toggleClass('is-active')
|
||||||
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length);
|
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
|
||||||
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden');
|
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#txt-move-prompt').on('keypress', (ev) => {
|
$('#txt-move-prompt').on('keypress', (ev) => {
|
||||||
if (ev.which === 13) {
|
if (ev.which === 13) {
|
||||||
$('.btn-move-go').trigger('click');
|
$('.btn-move-go').trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-move-go').on('click', (ev) => {
|
$('.btn-move-go').on('click', (ev) => {
|
||||||
|
let newDocPath = makeSafePath($('#txt-move-prompt').val())
|
||||||
let newDocPath = makeSafePath($('#txt-move-prompt').val());
|
|
||||||
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
||||||
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden');
|
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
|
||||||
} else {
|
} else {
|
||||||
$('#txt-move-prompt').parent().addClass('is-loading');
|
$('#txt-move-prompt').parent().addClass('is-loading')
|
||||||
|
|
||||||
$.ajax(window.location.href, {
|
$.ajax(window.location.href, {
|
||||||
data: {
|
data: {
|
||||||
@ -36,14 +35,12 @@ $('.btn-move-go').on('click', (ev) => {
|
|||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
}).then((rData, rStatus, rXHR) => {
|
}).then((rData, rStatus, rXHR) => {
|
||||||
if (rData.ok) {
|
if (rData.ok) {
|
||||||
window.location.assign('/' + newDocPath);
|
window.location.assign('/' + newDocPath)
|
||||||
} else {
|
} else {
|
||||||
alerts.pushError('Something went wrong', rData.error);
|
alerts.pushError('Something went wrong', rData.error)
|
||||||
}
|
}
|
||||||
}, (rXHR, rStatus, err) => {
|
}, (rXHR, rStatus, err) => {
|
||||||
alerts.pushError('Something went wrong', 'Save operation failed.');
|
alerts.pushError('Something went wrong', 'Save operation failed.')
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
});
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
if ($('#page-type-admin-profile').length) {
|
if ($('#page-type-admin-profile').length) {
|
||||||
|
|
||||||
let vueProfile = new Vue({
|
let vueProfile = new Vue({
|
||||||
el: '#page-type-admin-profile',
|
el: '#page-type-admin-profile',
|
||||||
data: {
|
data: {
|
||||||
@ -11,30 +10,28 @@ if($('#page-type-admin-profile').length) {
|
|||||||
methods: {
|
methods: {
|
||||||
saveUser: (ev) => {
|
saveUser: (ev) => {
|
||||||
if (vueProfile.password !== vueProfile.passwordVerify) {
|
if (vueProfile.password !== vueProfile.passwordVerify) {
|
||||||
alerts.pushError('Error', "Passwords don't match!");
|
alerts.pushError('Error', "Passwords don't match!")
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
$.post(window.location.href, {
|
$.post(window.location.href, {
|
||||||
password: vueProfile.password,
|
password: vueProfile.password,
|
||||||
name: vueProfile.name
|
name: vueProfile.name
|
||||||
}).done((resp) => {
|
}).done((resp) => {
|
||||||
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
|
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
|
||||||
}).fail((jqXHR, txtStatus, resp) => {
|
}).fail((jqXHR, txtStatus, resp) => {
|
||||||
alerts.pushError('Error', resp);
|
alerts.pushError('Error', resp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
this.name = usrDataName;
|
this.name = usrDataName
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
} else if ($('#page-type-admin-users').length) {
|
} else if ($('#page-type-admin-users').length) {
|
||||||
|
|
||||||
// =include ../modals/admin-users-create.js
|
// =include ../modals/admin-users-create.js
|
||||||
|
|
||||||
} else if ($('#page-type-admin-users-edit').length) {
|
} else if ($('#page-type-admin-users-edit').length) {
|
||||||
|
|
||||||
let vueEditUser = new Vue({
|
let vueEditUser = new Vue({
|
||||||
el: '#page-type-admin-users-edit',
|
el: '#page-type-admin-users-edit',
|
||||||
data: {
|
data: {
|
||||||
@ -52,7 +49,7 @@ if($('#page-type-admin-profile').length) {
|
|||||||
path: '/',
|
path: '/',
|
||||||
exact: false,
|
exact: false,
|
||||||
deny: false
|
deny: false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
removeRightsRow: (idx) => {
|
removeRightsRow: (idx) => {
|
||||||
_.pullAt(vueEditUser.rights, idx)
|
_.pullAt(vueEditUser.rights, idx)
|
||||||
@ -68,35 +65,32 @@ if($('#page-type-admin-profile').length) {
|
|||||||
exact: false,
|
exact: false,
|
||||||
deny: false
|
deny: false
|
||||||
})
|
})
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
$.post(window.location.href, {
|
$.post(window.location.href, {
|
||||||
password: vueEditUser.password,
|
password: vueEditUser.password,
|
||||||
name: vueEditUser.name,
|
name: vueEditUser.name,
|
||||||
rights: JSON.stringify(formattedRights)
|
rights: JSON.stringify(formattedRights)
|
||||||
}).done((resp) => {
|
}).done((resp) => {
|
||||||
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
|
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
|
||||||
}).fail((jqXHR, txtStatus, resp) => {
|
}).fail((jqXHR, txtStatus, resp) => {
|
||||||
alerts.pushError('Error', resp);
|
alerts.pushError('Error', resp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
|
this.id = usrData._id
|
||||||
this.id = usrData._id;
|
this.email = usrData.email
|
||||||
this.email = usrData.email;
|
this.name = usrData.name
|
||||||
this.name = usrData.name;
|
|
||||||
|
|
||||||
if (_.find(usrData.rights, { role: 'admin' })) {
|
if (_.find(usrData.rights, { role: 'admin' })) {
|
||||||
this.rights = _.reject(usrData.rights, ['role', 'admin']);
|
this.rights = _.reject(usrData.rights, ['role', 'admin'])
|
||||||
this.roleoverride = 'admin';
|
this.roleoverride = 'admin'
|
||||||
} else {
|
} else {
|
||||||
this.rights = usrData.rights;
|
this.rights = usrData.rights
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// =include ../modals/admin-users-delete.js
|
// =include ../modals/admin-users-delete.js
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if ($('#page-type-create').length) {
|
if ($('#page-type-create').length) {
|
||||||
|
let pageEntryPath = $('#page-type-create').data('entrypath')
|
||||||
let pageEntryPath = $('#page-type-create').data('entrypath');
|
|
||||||
|
|
||||||
// -> Discard
|
// -> Discard
|
||||||
|
|
||||||
$('.btn-create-discard').on('click', (ev) => {
|
$('.btn-create-discard').on('click', (ev) => {
|
||||||
$('#modal-create-discard').toggleClass('is-active');
|
$('#modal-create-discard').toggleClass('is-active')
|
||||||
});
|
})
|
||||||
|
|
||||||
// =include ../components/editor.js
|
// =include ../components/editor.js
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if ($('#page-type-edit').length) {
|
if ($('#page-type-edit').length) {
|
||||||
|
let pageEntryPath = $('#page-type-edit').data('entrypath')
|
||||||
let pageEntryPath = $('#page-type-edit').data('entrypath');
|
|
||||||
|
|
||||||
// -> Discard
|
// -> Discard
|
||||||
|
|
||||||
$('.btn-edit-discard').on('click', (ev) => {
|
$('.btn-edit-discard').on('click', (ev) => {
|
||||||
$('#modal-edit-discard').toggleClass('is-active');
|
$('#modal-edit-discard').toggleClass('is-active')
|
||||||
});
|
})
|
||||||
|
|
||||||
// =include ../components/editor.js
|
// =include ../components/editor.js
|
||||||
|
|
||||||
}
|
}
|
@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
if ($('#page-type-source').length) {
|
if ($('#page-type-source').length) {
|
||||||
|
var scEditor = ace.edit('source-display')
|
||||||
|
scEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
|
scEditor.getSession().setMode('ace/mode/markdown')
|
||||||
|
scEditor.setOption('fontSize', '14px')
|
||||||
|
scEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
|
scEditor.setOption('wrap', true)
|
||||||
|
scEditor.setReadOnly(true)
|
||||||
|
scEditor.renderer.updateFull()
|
||||||
|
|
||||||
var scEditor = ace.edit("source-display");
|
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
|
||||||
scEditor.setTheme("ace/theme/tomorrow_night");
|
|
||||||
scEditor.getSession().setMode("ace/mode/markdown");
|
|
||||||
scEditor.setOption('fontSize', '14px');
|
|
||||||
scEditor.setOption('hScrollBarAlwaysVisible', false);
|
|
||||||
scEditor.setOption('wrap', true);
|
|
||||||
scEditor.setReadOnly(true);
|
|
||||||
scEditor.renderer.updateFull();
|
|
||||||
|
|
||||||
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '';
|
|
||||||
|
|
||||||
// =include ../modals/create.js
|
// =include ../modals/create.js
|
||||||
// =include ../modals/move.js
|
// =include ../modals/move.js
|
||||||
|
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
|
|
||||||
if ($('#page-type-view').length) {
|
if ($('#page-type-view').length) {
|
||||||
|
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
|
||||||
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '';
|
|
||||||
|
|
||||||
// =include ../modals/create.js
|
// =include ../modals/create.js
|
||||||
// =include ../modals/move.js
|
// =include ../modals/move.js
|
||||||
|
|
||||||
}
|
}
|
@ -1,61 +1,56 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express')
|
||||||
var router = express.Router();
|
var router = express.Router()
|
||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird')
|
||||||
const validator = require('validator');
|
const validator = require('validator')
|
||||||
const _ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin
|
* Admin
|
||||||
*/
|
*/
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.redirect('/admin/profile');
|
res.redirect('/admin/profile')
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/profile', (req, res) => {
|
router.get('/profile', (req, res) => {
|
||||||
|
|
||||||
if (res.locals.isGuest) {
|
if (res.locals.isGuest) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('pages/admin/profile', { adminTab: 'profile' });
|
res.render('pages/admin/profile', { adminTab: 'profile' })
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/profile', (req, res) => {
|
router.post('/profile', (req, res) => {
|
||||||
|
|
||||||
if (res.locals.isGuest) {
|
if (res.locals.isGuest) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findById(req.user.id).then((usr) => {
|
return db.User.findById(req.user.id).then((usr) => {
|
||||||
usr.name = _.trim(req.body.name);
|
usr.name = _.trim(req.body.name)
|
||||||
if (usr.provider === 'local' && req.body.password !== '********') {
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
let nPwd = _.trim(req.body.password);
|
let nPwd = _.trim(req.body.password)
|
||||||
if (nPwd.length < 6) {
|
if (nPwd.length < 6) {
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
} else {
|
} else {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
usr.password = pwd;
|
usr.password = pwd
|
||||||
return usr.save();
|
return usr.save()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return usr.save();
|
return usr.save()
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return res.json({ msg: 'OK' });
|
return res.json({ msg: 'OK' })
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.status(400).json({ msg: err.message });
|
res.status(400).json({ msg: err.message })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/stats', (req, res) => {
|
router.get('/stats', (req, res) => {
|
||||||
|
|
||||||
if (res.locals.isGuest) {
|
if (res.locals.isGuest) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
@ -64,99 +59,88 @@ router.get('/stats', (req, res) => {
|
|||||||
db.User.count()
|
db.User.count()
|
||||||
]).spread((totalEntries, totalUploads, totalUsers) => {
|
]).spread((totalEntries, totalUploads, totalUsers) => {
|
||||||
return res.render('pages/admin/stats', {
|
return res.render('pages/admin/stats', {
|
||||||
totalEntries, totalUploads, totalUsers,
|
totalEntries, totalUploads, totalUsers, adminTab: 'stats'
|
||||||
adminTab: 'stats'
|
}) || true
|
||||||
}) || true;
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
throw err;
|
throw err
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users', (req, res) => {
|
router.get('/users', (req, res) => {
|
||||||
|
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
db.User.find({})
|
db.User.find({})
|
||||||
.select('-password -rights')
|
.select('-password -rights')
|
||||||
.sort('name email')
|
.sort('name email')
|
||||||
.exec().then((usrs) => {
|
.exec().then((usrs) => {
|
||||||
res.render('pages/admin/users', { adminTab: 'users', usrs });
|
res.render('pages/admin/users', { adminTab: 'users', usrs })
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users/:id', (req, res) => {
|
router.get('/users/:id', (req, res) => {
|
||||||
|
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isMongoId(req.params.id)) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
db.User.findById(req.params.id)
|
db.User.findById(req.params.id)
|
||||||
.select('-password -providerId')
|
.select('-password -providerId')
|
||||||
.exec().then((usr) => {
|
.exec().then((usr) => {
|
||||||
|
|
||||||
let usrOpts = {
|
let usrOpts = {
|
||||||
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
||||||
canChangeName: (usr.email !== 'guest'),
|
canChangeName: (usr.email !== 'guest'),
|
||||||
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
||||||
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
||||||
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
||||||
};
|
}
|
||||||
|
|
||||||
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts });
|
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/users/:id', (req, res) => {
|
router.post('/users/:id', (req, res) => {
|
||||||
|
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.status(401).json({ msg: 'Unauthorized' });
|
return res.status(401).json({ msg: 'Unauthorized' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isMongoId(req.params.id)) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.status(400).json({ msg: 'Invalid User ID' });
|
return res.status(400).json({ msg: 'Invalid User ID' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findById(req.params.id).then((usr) => {
|
return db.User.findById(req.params.id).then((usr) => {
|
||||||
usr.name = _.trim(req.body.name);
|
usr.name = _.trim(req.body.name)
|
||||||
usr.rights = JSON.parse(req.body.rights);
|
usr.rights = JSON.parse(req.body.rights)
|
||||||
if (usr.provider === 'local' && req.body.password !== '********') {
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
let nPwd = _.trim(req.body.password);
|
let nPwd = _.trim(req.body.password)
|
||||||
if (nPwd.length < 6) {
|
if (nPwd.length < 6) {
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
} else {
|
} else {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
usr.password = pwd;
|
usr.password = pwd
|
||||||
return usr.save();
|
return usr.save()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return usr.save();
|
return usr.save()
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return res.json({ msg: 'OK' });
|
return res.json({ msg: 'OK' })
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.status(400).json({ msg: err.message });
|
res.status(400).json({ msg: err.message })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/settings', (req, res) => {
|
router.get('/settings', (req, res) => {
|
||||||
|
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('pages/admin/settings', { adminTab: 'settings' });
|
res.render('pages/admin/settings', { adminTab: 'settings' })
|
||||||
|
})
|
||||||
|
|
||||||
});
|
module.exports = router
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
var express = require('express');
|
'use strict'
|
||||||
var router = express.Router();
|
|
||||||
var passport = require('passport');
|
const express = require('express')
|
||||||
var ExpressBrute = require('express-brute');
|
const router = express.Router()
|
||||||
var ExpressBruteMongooseStore = require('express-brute-mongoose');
|
const passport = require('passport')
|
||||||
var moment = require('moment');
|
const ExpressBrute = require('express-brute')
|
||||||
|
const ExpressBruteMongooseStore = require('express-brute-mongoose')
|
||||||
|
const moment = require('moment')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Express-Brute
|
* Setup Express-Brute
|
||||||
*/
|
*/
|
||||||
var EBstore = new ExpressBruteMongooseStore(db.Bruteforce);
|
const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
|
||||||
var bruteforce = new ExpressBrute(EBstore, {
|
const bruteforce = new ExpressBrute(EBstore, {
|
||||||
freeRetries: 5,
|
freeRetries: 5,
|
||||||
minWait: 60 * 1000,
|
minWait: 60 * 1000,
|
||||||
maxWait: 5 * 60 * 1000,
|
maxWait: 5 * 60 * 1000,
|
||||||
@ -20,10 +22,10 @@ var bruteforce = new ExpressBrute(EBstore, {
|
|||||||
title: 'Too many attempts!',
|
title: 'Too many attempts!',
|
||||||
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
|
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
|
||||||
iconClass: 'fa-times'
|
iconClass: 'fa-times'
|
||||||
});
|
})
|
||||||
res.redirect('/login');
|
res.redirect('/login')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login form
|
* Login form
|
||||||
@ -31,50 +33,48 @@ var bruteforce = new ExpressBrute(EBstore, {
|
|||||||
router.get('/login', function (req, res, next) {
|
router.get('/login', function (req, res, next) {
|
||||||
res.render('auth/login', {
|
res.render('auth/login', {
|
||||||
usr: res.locals.usr
|
usr: res.locals.usr
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||||
passport.authenticate('local', function (err, user, info) {
|
passport.authenticate('local', function (err, user, info) {
|
||||||
|
if (err) { return next(err) }
|
||||||
if (err) { return next(err); }
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
title: 'Invalid login',
|
title: 'Invalid login',
|
||||||
message: "The email or password is invalid."
|
message: 'The email or password is invalid.'
|
||||||
});
|
})
|
||||||
return res.redirect('/login');
|
return res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
req.logIn(user, function (err) {
|
req.logIn(user, function (err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err) }
|
||||||
req.brute.reset(function () {
|
req.brute.reset(function () {
|
||||||
return res.redirect('/');
|
return res.redirect('/')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})(req, res, next)
|
||||||
})(req, res, next);
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Social Login
|
* Social Login
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }));
|
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
|
||||||
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
|
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
|
||||||
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }));
|
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
|
||||||
|
|
||||||
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout
|
* Logout
|
||||||
*/
|
*/
|
||||||
router.get('/logout', function (req, res) {
|
router.get('/logout', function (req, res) {
|
||||||
req.logout();
|
req.logout()
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
});
|
})
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
const express = require('express')
|
||||||
var router = express.Router();
|
const router = express.Router()
|
||||||
var _ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// EDIT MODE
|
// EDIT MODE
|
||||||
@ -12,12 +12,11 @@ var _ = require('lodash');
|
|||||||
* Edit document in Markdown
|
* Edit document in Markdown
|
||||||
*/
|
*/
|
||||||
router.get('/edit/*', (req, res, next) => {
|
router.get('/edit/*', (req, res, next) => {
|
||||||
|
|
||||||
if (!res.locals.rights.write) {
|
if (!res.locals.rights.write) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
|
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
|
||||||
|
|
||||||
entries.fetchOriginal(safePath, {
|
entries.fetchOriginal(safePath, {
|
||||||
parseMarkdown: false,
|
parseMarkdown: false,
|
||||||
@ -28,116 +27,108 @@ router.get('/edit/*', (req, res, next) => {
|
|||||||
cache: false
|
cache: false
|
||||||
}).then((pageData) => {
|
}).then((pageData) => {
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
res.render('pages/edit', { pageData });
|
res.render('pages/edit', { pageData })
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid page path.');
|
throw new Error('Invalid page path.')
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: {}
|
error: {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/edit/*', (req, res, next) => {
|
router.put('/edit/*', (req, res, next) => {
|
||||||
|
|
||||||
if (!res.locals.rights.write) {
|
if (!res.locals.rights.write) {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: 'Forbidden'
|
error: 'Forbidden'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''));
|
let safePath = entries.parsePath(_.replace(req.path, '/edit', ''))
|
||||||
|
|
||||||
entries.update(safePath, req.body.markdown).then(() => {
|
entries.update(safePath, req.body.markdown).then(() => {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: true
|
ok: true
|
||||||
}) || true;
|
}) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.json({
|
res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: err.message
|
error: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// CREATE MODE
|
// CREATE MODE
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
router.get('/create/*', (req, res, next) => {
|
router.get('/create/*', (req, res, next) => {
|
||||||
|
|
||||||
if (!res.locals.rights.write) {
|
if (!res.locals.rights.write) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.some(['create','edit','account','source','history','mk'], (e) => { return _.startsWith(req.path, '/create/' + e); })) {
|
if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk'], (e) => { return _.startsWith(req.path, '/create/' + e) })) {
|
||||||
return res.render('error', {
|
return res.render('error', {
|
||||||
message: 'You cannot create a document with this name as it is reserved by the system.',
|
message: 'You cannot create a document with this name as it is reserved by the system.',
|
||||||
error: {}
|
error: {}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
|
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
|
||||||
|
|
||||||
entries.exists(safePath).then((docExists) => {
|
entries.exists(safePath).then((docExists) => {
|
||||||
if (!docExists) {
|
if (!docExists) {
|
||||||
return entries.getStarter(safePath).then((contents) => {
|
return entries.getStarter(safePath).then((contents) => {
|
||||||
|
|
||||||
let pageData = {
|
let pageData = {
|
||||||
markdown: contents,
|
markdown: contents,
|
||||||
meta: {
|
meta: {
|
||||||
title: _.startCase(safePath),
|
title: _.startCase(safePath),
|
||||||
path: safePath
|
path: safePath
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
res.render('pages/create', { pageData });
|
res.render('pages/create', { pageData })
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
throw new Error('Could not load starter content!');
|
winston.warn(err)
|
||||||
});
|
throw new Error('Could not load starter content!')
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('This entry already exists!');
|
throw new Error('This entry already exists!')
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: {}
|
error: {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/create/*', (req, res, next) => {
|
router.put('/create/*', (req, res, next) => {
|
||||||
|
|
||||||
if (!res.locals.rights.write) {
|
if (!res.locals.rights.write) {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: 'Forbidden'
|
error: 'Forbidden'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/create', ''));
|
let safePath = entries.parsePath(_.replace(req.path, '/create', ''))
|
||||||
|
|
||||||
entries.create(safePath, req.body.markdown).then(() => {
|
entries.create(safePath, req.body.markdown).then(() => {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: true
|
ok: true
|
||||||
}) || true;
|
}) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: err.message
|
error: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// VIEW MODE
|
// VIEW MODE
|
||||||
@ -147,8 +138,7 @@ router.put('/create/*', (req, res, next) => {
|
|||||||
* View source of a document
|
* View source of a document
|
||||||
*/
|
*/
|
||||||
router.get('/source/*', (req, res, next) => {
|
router.get('/source/*', (req, res, next) => {
|
||||||
|
let safePath = entries.parsePath(_.replace(req.path, '/source', ''))
|
||||||
let safePath = entries.parsePath(_.replace(req.path, '/source', ''));
|
|
||||||
|
|
||||||
entries.fetchOriginal(safePath, {
|
entries.fetchOriginal(safePath, {
|
||||||
parseMarkdown: false,
|
parseMarkdown: false,
|
||||||
@ -159,90 +149,83 @@ router.get('/source/*', (req, res, next) => {
|
|||||||
cache: false
|
cache: false
|
||||||
}).then((pageData) => {
|
}).then((pageData) => {
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
res.render('pages/source', { pageData });
|
res.render('pages/source', { pageData })
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid page path.');
|
throw new Error('Invalid page path.')
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: {}
|
error: {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View document
|
* View document
|
||||||
*/
|
*/
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
|
let safePath = entries.parsePath(req.path)
|
||||||
let safePath = entries.parsePath(req.path);
|
|
||||||
|
|
||||||
entries.fetch(safePath).then((pageData) => {
|
entries.fetch(safePath).then((pageData) => {
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
res.render('pages/view', { pageData });
|
res.render('pages/view', { pageData })
|
||||||
} else {
|
} else {
|
||||||
res.render('error-notexist', {
|
res.render('error-notexist', {
|
||||||
newpath: safePath
|
newpath: safePath
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
}).error((err) => {
|
}).error((err) => {
|
||||||
|
|
||||||
if (safePath === 'home') {
|
if (safePath === 'home') {
|
||||||
res.render('pages/welcome');
|
res.render('pages/welcome')
|
||||||
} else {
|
} else {
|
||||||
res.render('error-notexist', {
|
res.render('error-notexist', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
newpath: safePath
|
newpath: safePath
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: {}
|
error: {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move document
|
* Move document
|
||||||
*/
|
*/
|
||||||
router.put('/*', (req, res, next) => {
|
router.put('/*', (req, res, next) => {
|
||||||
|
|
||||||
if (!res.locals.rights.write) {
|
if (!res.locals.rights.write) {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: 'Forbidden'
|
error: 'Forbidden'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let safePath = entries.parsePath(req.path);
|
let safePath = entries.parsePath(req.path)
|
||||||
|
|
||||||
if (_.isEmpty(req.body.move)) {
|
if (_.isEmpty(req.body.move)) {
|
||||||
return res.json({
|
return res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: 'Invalid document action call.'
|
error: 'Invalid document action call.'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let safeNewPath = entries.parsePath(req.body.move);
|
let safeNewPath = entries.parsePath(req.body.move)
|
||||||
|
|
||||||
entries.move(safePath, safeNewPath).then(() => {
|
entries.move(safePath, safeNewPath).then(() => {
|
||||||
res.json({
|
res.json({
|
||||||
ok: true
|
ok: true
|
||||||
});
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.json({
|
res.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
error: err.message
|
error: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
|
||||||
});
|
module.exports = router
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
const express = require('express')
|
||||||
var router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
var readChunk = require('read-chunk'),
|
const readChunk = require('read-chunk')
|
||||||
fileType = require('file-type'),
|
const fileType = require('file-type')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$");
|
const validPathRe = new RegExp('^([a-z0-9\\/-]+\\.[a-z0-9]+)$')
|
||||||
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$");
|
const validPathThumbsRe = new RegExp('^([0-9]+\\.png)$')
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// SERVE UPLOADS FILES
|
// SERVE UPLOADS FILES
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
router.get('/t/*', (req, res, next) => {
|
router.get('/t/*', (req, res, next) => {
|
||||||
|
let fileName = req.params[0]
|
||||||
let fileName = req.params[0];
|
|
||||||
if (!validPathThumbsRe.test(fileName)) {
|
if (!validPathThumbsRe.test(fileName)) {
|
||||||
return res.sendStatus(404).end();
|
return res.sendStatus(404).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Authentication-based access
|
// todo: Authentication-based access
|
||||||
@ -31,141 +30,119 @@ router.get('/t/*', (req, res, next) => {
|
|||||||
dotfiles: 'deny'
|
dotfiles: 'deny'
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.status(err.status).end();
|
res.status(err.status).end()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
||||||
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
|
|
||||||
if (!destFolderPath) {
|
if (!destFolderPath) {
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
let destFilename = '';
|
let destFilePath = ''
|
||||||
let destFilePath = '';
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
||||||
|
destFilename = fname
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
destFilename = fname;
|
return readChunk(f.path, 0, 262)
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
|
||||||
|
|
||||||
return readChunk(f.path, 0, 262);
|
|
||||||
|
|
||||||
}).then((buf) => {
|
}).then((buf) => {
|
||||||
|
|
||||||
// -> Check MIME type by magic number
|
// -> Check MIME type by magic number
|
||||||
|
|
||||||
let mimeInfo = fileType(buf);
|
let mimeInfo = fileType(buf)
|
||||||
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
return Promise.reject(new Error('Invalid file type.'));
|
return Promise.reject(new Error('Invalid file type.'))
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
||||||
// -> Move file to final destination
|
// -> Move file to final destination
|
||||||
|
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
filename: destFilename,
|
filename: destFilename,
|
||||||
filesize: f.size
|
filesize: f.size
|
||||||
};
|
}
|
||||||
}).reflect();
|
}).reflect()
|
||||||
|
|
||||||
}, {concurrency: 3}).then((results) => {
|
}, {concurrency: 3}).then((results) => {
|
||||||
let uplResults = _.map(results, (r) => {
|
let uplResults = _.map(results, (r) => {
|
||||||
if (r.isFulfilled()) {
|
if (r.isFulfilled()) {
|
||||||
return r.value();
|
return r.value()
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: r.reason().message
|
msg: r.reason().message
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
res.json({ ok: true, results: uplResults });
|
})
|
||||||
return true;
|
res.json({ ok: true, results: uplResults })
|
||||||
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.json({ ok: false, msg: err.message });
|
res.json({ ok: false, msg: err.message })
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
||||||
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
|
|
||||||
if (!destFolderPath) {
|
if (!destFolderPath) {
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
let destFilename = '';
|
let destFilePath = ''
|
||||||
let destFilePath = '';
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
||||||
|
destFilename = fname
|
||||||
destFilename = fname;
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
|
||||||
|
|
||||||
// -> Move file to final destination
|
// -> Move file to final destination
|
||||||
|
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
filename: destFilename,
|
filename: destFilename,
|
||||||
filesize: f.size
|
filesize: f.size
|
||||||
};
|
}
|
||||||
}).reflect();
|
}).reflect()
|
||||||
|
|
||||||
}, {concurrency: 3}).then((results) => {
|
}, {concurrency: 3}).then((results) => {
|
||||||
let uplResults = _.map(results, (r) => {
|
let uplResults = _.map(results, (r) => {
|
||||||
if (r.isFulfilled()) {
|
if (r.isFulfilled()) {
|
||||||
return r.value();
|
return r.value()
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: r.reason().message
|
msg: r.reason().message
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
res.json({ ok: true, results: uplResults });
|
})
|
||||||
return true;
|
res.json({ ok: true, results: uplResults })
|
||||||
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.json({ ok: false, msg: err.message });
|
res.json({ ok: false, msg: err.message })
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
|
let fileName = req.params[0]
|
||||||
let fileName = req.params[0];
|
|
||||||
if (!validPathRe.test(fileName)) {
|
if (!validPathRe.test(fileName)) {
|
||||||
return res.sendStatus(404).end();
|
return res.sendStatus(404).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Authentication-based access
|
// todo: Authentication-based access
|
||||||
@ -175,10 +152,9 @@ router.get('/*', (req, res, next) => {
|
|||||||
dotfiles: 'deny'
|
dotfiles: 'deny'
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.status(err.status).end();
|
res.status(err.status).end()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
|
||||||
});
|
module.exports = router
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
module.exports = (socket) => {
|
module.exports = (socket) => {
|
||||||
|
|
||||||
if (!socket.request.user.logged_in) {
|
if (!socket.request.user.logged_in) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
@ -11,85 +12,84 @@ module.exports = (socket) => {
|
|||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
socket.on('search', (data, cb) => {
|
socket.on('search', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
entries.search(data.terms).then((results) => {
|
entries.search(data.terms).then((results) => {
|
||||||
return cb(results) || true;
|
return cb(results) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// UPLOADS
|
// UPLOADS
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
socket.on('uploadsGetFolders', (data, cb) => {
|
socket.on('uploadsGetFolders', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFolders().then((f) => {
|
upl.getUploadsFolders().then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsCreateFolder', (data, cb) => {
|
socket.on('uploadsCreateFolder', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.createUploadsFolder(data.foldername).then((f) => {
|
upl.createUploadsFolder(data.foldername).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsGetImages', (data, cb) => {
|
socket.on('uploadsGetImages', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFiles('image', data.folder).then((f) => {
|
upl.getUploadsFiles('image', data.folder).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsGetFiles', (data, cb) => {
|
socket.on('uploadsGetFiles', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFiles('binary', data.folder).then((f) => {
|
upl.getUploadsFiles('binary', data.folder).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsDeleteFile', (data, cb) => {
|
socket.on('uploadsDeleteFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.deleteUploadsFile(data.uid).then((f) => {
|
upl.deleteUploadsFile(data.uid).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsFetchFileFromURL', (data, cb) => {
|
socket.on('uploadsFetchFileFromURL', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
|
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsRenameFile', (data, cb) => {
|
socket.on('uploadsRenameFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
|
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsMoveFile', (data, cb) => {
|
socket.on('uploadsMoveFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.moveUploadsFile(data.uid, data.folder).then((f) => {
|
upl.moveUploadsFile(data.uid, data.folder).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
}
|
||||||
};
|
|
||||||
|
117
gulpfile.js
117
gulpfile.js
@ -1,26 +1,27 @@
|
|||||||
var gulp = require("gulp");
|
'use strict'
|
||||||
var watch = require('gulp-watch');
|
|
||||||
var merge = require('merge-stream');
|
const gulp = require('gulp')
|
||||||
var babel = require("gulp-babel");
|
const watch = require('gulp-watch')
|
||||||
var uglify = require('gulp-uglify');
|
const merge = require('merge-stream')
|
||||||
var concat = require('gulp-concat');
|
const babel = require('gulp-babel')
|
||||||
var nodemon = require('gulp-nodemon');
|
const uglify = require('gulp-uglify')
|
||||||
var plumber = require('gulp-plumber');
|
const concat = require('gulp-concat')
|
||||||
var zip = require('gulp-zip');
|
const nodemon = require('gulp-nodemon')
|
||||||
var tar = require('gulp-tar');
|
const plumber = require('gulp-plumber')
|
||||||
var gzip = require('gulp-gzip');
|
const zip = require('gulp-zip')
|
||||||
var sass = require('gulp-sass');
|
const tar = require('gulp-tar')
|
||||||
var cleanCSS = require('gulp-clean-css');
|
const gzip = require('gulp-gzip')
|
||||||
var include = require("gulp-include");
|
const sass = require('gulp-sass')
|
||||||
var run = require('run-sequence');
|
const cleanCSS = require('gulp-clean-css')
|
||||||
var _ = require('lodash');
|
const include = require('gulp-include')
|
||||||
|
const run = require('run-sequence')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths
|
* Paths
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
var paths = {
|
const paths = {
|
||||||
scripts: {
|
scripts: {
|
||||||
combine: [
|
combine: [
|
||||||
'./node_modules/socket.io-client/dist/socket.io.min.js',
|
'./node_modules/socket.io-client/dist/socket.io.min.js',
|
||||||
@ -81,7 +82,7 @@ var paths = {
|
|||||||
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
|
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
|
||||||
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
|
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Starts server in development mode
|
* TASK - Starts server in development mode
|
||||||
@ -92,111 +93,105 @@ gulp.task('server', ['scripts', 'css', 'fonts'], function() {
|
|||||||
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
||||||
ext: 'js json',
|
ext: 'js json',
|
||||||
env: { 'NODE_ENV': 'development' }
|
env: { 'NODE_ENV': 'development' }
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Process all scripts processes
|
* TASK - Process all scripts processes
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts", ['scripts-libs', 'scripts-app']);
|
gulp.task('scripts', ['scripts-libs', 'scripts-app'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine js libraries
|
* TASK - Combine js libraries
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts-libs", function () {
|
gulp.task('scripts-libs', function () {
|
||||||
|
|
||||||
return merge(
|
return merge(
|
||||||
|
|
||||||
gulp.src(paths.scripts.combine)
|
gulp.src(paths.scripts.combine)
|
||||||
.pipe(concat('libs.js', {newLine: ';\n'}))
|
.pipe(concat('libs.js', {newLine: ';\n'}))
|
||||||
.pipe(uglify({ mangle: false }))
|
.pipe(uglify({ mangle: false }))
|
||||||
.pipe(gulp.dest("./assets/js")),
|
.pipe(gulp.dest('./assets/js')),
|
||||||
|
|
||||||
gulp.src(paths.scripts.ace)
|
gulp.src(paths.scripts.ace)
|
||||||
.pipe(gulp.dest("./assets/js/ace"))
|
.pipe(gulp.dest('./assets/js/ace'))
|
||||||
|
|
||||||
);
|
)
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine, make compatible and compress js app scripts
|
* TASK - Combine, make compatible and compress js app scripts
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts-app", function () {
|
gulp.task('scripts-app', function () {
|
||||||
|
|
||||||
return gulp.src(paths.scripts.compile)
|
return gulp.src(paths.scripts.compile)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(include({ extensions: "js" }))
|
.pipe(include({ extensions: 'js' }))
|
||||||
.pipe(babel())
|
.pipe(babel())
|
||||||
.pipe(uglify())
|
.pipe(uglify())
|
||||||
.pipe(plumber.stop())
|
.pipe(plumber.stop())
|
||||||
.pipe(gulp.dest("./assets/js"));
|
.pipe(gulp.dest('./assets/js'))
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Process all css processes
|
* TASK - Process all css processes
|
||||||
*/
|
*/
|
||||||
gulp.task("css", ['css-libs', 'css-app']);
|
gulp.task('css', ['css-libs', 'css-app'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine css libraries
|
* TASK - Combine css libraries
|
||||||
*/
|
*/
|
||||||
gulp.task("css-libs", function () {
|
gulp.task('css-libs', function () {
|
||||||
return gulp.src(paths.css.combine)
|
return gulp.src(paths.css.combine)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(concat('libs.css'))
|
.pipe(concat('libs.css'))
|
||||||
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
||||||
.pipe(plumber.stop())
|
.pipe(plumber.stop())
|
||||||
.pipe(gulp.dest("./assets/css"));
|
.pipe(gulp.dest('./assets/css'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine app css
|
* TASK - Combine app css
|
||||||
*/
|
*/
|
||||||
gulp.task("css-app", function () {
|
gulp.task('css-app', function () {
|
||||||
return gulp.src(paths.css.compile)
|
return gulp.src(paths.css.compile)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(sass.sync({ includePaths: paths.css.includes }))
|
.pipe(sass.sync({ includePaths: paths.css.includes }))
|
||||||
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
||||||
.pipe(plumber.stop())
|
.pipe(plumber.stop())
|
||||||
.pipe(gulp.dest("./assets/css"));
|
.pipe(gulp.dest('./assets/css'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Copy web fonts
|
* TASK - Copy web fonts
|
||||||
*/
|
*/
|
||||||
gulp.task("fonts", function () {
|
gulp.task('fonts', function () {
|
||||||
return gulp.src(paths.fonts)
|
return gulp.src(paths.fonts)
|
||||||
.pipe(gulp.dest("./assets/fonts"));
|
.pipe(gulp.dest('./assets/fonts'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Start dev watchers
|
* TASK - Start dev watchers
|
||||||
*/
|
*/
|
||||||
gulp.task('watch', function () {
|
gulp.task('watch', function () {
|
||||||
return merge(
|
return merge(
|
||||||
watch(paths.scripts.watch, {base: './'}, function() { return gulp.start('scripts-app'); }),
|
watch(paths.scripts.watch, {base: './'}, function () { return gulp.start('scripts-app') }),
|
||||||
watch(paths.css.watch, {base: './'}, function() { return gulp.start('css-app'); })
|
watch(paths.css.watch, {base: './'}, function () { return gulp.start('css-app') })
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Starts development server with watchers
|
* TASK - Starts development server with watchers
|
||||||
*/
|
*/
|
||||||
gulp.task('default', ['watch', 'server']);
|
gulp.task('default', ['watch', 'server'])
|
||||||
|
|
||||||
gulp.task('dev', function () {
|
gulp.task('dev', function () {
|
||||||
|
paths.css.includes.pop()
|
||||||
|
paths.css.includes.push('../core')
|
||||||
|
|
||||||
paths.css.includes.pop();
|
paths.fonts.pop()
|
||||||
paths.css.includes.push('../core');
|
paths.fonts.push('../core/core-client/fonts/**/*')
|
||||||
|
|
||||||
paths.fonts.pop();
|
return run('default')
|
||||||
paths.fonts.push('../core/core-client/fonts/**/*');
|
})
|
||||||
|
|
||||||
return run('default');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Creates deployment packages
|
* TASK - Creates deployment packages
|
||||||
@ -204,12 +199,12 @@ gulp.task('dev', function() {
|
|||||||
gulp.task('deploy', ['scripts', 'css', 'fonts'], function () {
|
gulp.task('deploy', ['scripts', 'css', 'fonts'], function () {
|
||||||
var zipStream = gulp.src(paths.deploy)
|
var zipStream = gulp.src(paths.deploy)
|
||||||
.pipe(zip('wiki-js.zip'))
|
.pipe(zip('wiki-js.zip'))
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'))
|
||||||
|
|
||||||
var targzStream = gulp.src(paths.deploy)
|
var targzStream = gulp.src(paths.deploy)
|
||||||
.pipe(tar('wiki-js.tar'))
|
.pipe(tar('wiki-js.tar'))
|
||||||
.pipe(gzip())
|
.pipe(gzip())
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'))
|
||||||
|
|
||||||
return merge(zipStream, targzStream);
|
return merge(zipStream, targzStream)
|
||||||
});
|
})
|
||||||
|
290
libs/entries.js
290
libs/entries.js
@ -1,11 +1,10 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
fs = Promise.promisifyAll(require("fs-extra")),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
_ = require('lodash'),
|
const _ = require('lodash')
|
||||||
farmhash = require('farmhash'),
|
const farmhash = require('farmhash')
|
||||||
moment = require('moment');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entries Model
|
* Entries Model
|
||||||
@ -21,14 +20,12 @@ module.exports = {
|
|||||||
* @return {Object} Entries model instance
|
* @return {Object} Entries model instance
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
||||||
|
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache')
|
||||||
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
|
||||||
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
|
|
||||||
|
|
||||||
return self;
|
|
||||||
|
|
||||||
|
return self
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,8 +35,7 @@ module.exports = {
|
|||||||
* @return {Promise<Boolean>} True if exists, false otherwise
|
* @return {Promise<Boolean>} True if exists, false otherwise
|
||||||
*/
|
*/
|
||||||
exists (entryPath) {
|
exists (entryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self.fetchOriginal(entryPath, {
|
return self.fetchOriginal(entryPath, {
|
||||||
parseMarkdown: false,
|
parseMarkdown: false,
|
||||||
@ -49,11 +45,10 @@ module.exports = {
|
|||||||
includeParentInfo: false,
|
includeParentInfo: false,
|
||||||
cache: false
|
cache: false
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return true;
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return false;
|
return false
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,39 +58,31 @@ module.exports = {
|
|||||||
* @return {Promise<Object>} Page Data
|
* @return {Promise<Object>} Page Data
|
||||||
*/
|
*/
|
||||||
fetch (entryPath) {
|
fetch (entryPath) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
let cpath = self.getCachePath(entryPath)
|
||||||
|
|
||||||
let cpath = self.getCachePath(entryPath);
|
|
||||||
|
|
||||||
return fs.statAsync(cpath).then((st) => {
|
return fs.statAsync(cpath).then((st) => {
|
||||||
return st.isFile();
|
return st.isFile()
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return false;
|
return false
|
||||||
}).then((isCache) => {
|
}).then((isCache) => {
|
||||||
|
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
|
|
||||||
// Load from cache
|
// Load from cache
|
||||||
|
|
||||||
return fs.readFileAsync(cpath).then((contents) => {
|
return fs.readFileAsync(cpath).then((contents) => {
|
||||||
return JSON.parse(contents);
|
return JSON.parse(contents)
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
winston.error('Corrupted cache file. Deleting it...');
|
winston.error('Corrupted cache file. Deleting it...')
|
||||||
fs.unlinkSync(cpath);
|
fs.unlinkSync(cpath)
|
||||||
return false;
|
return false
|
||||||
});
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Load original
|
// Load original
|
||||||
|
|
||||||
return self.fetchOriginal(entryPath);
|
return self.fetchOriginal(entryPath)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,11 +93,10 @@ module.exports = {
|
|||||||
* @return {Promise<Object>} Page data
|
* @return {Promise<Object>} Page data
|
||||||
*/
|
*/
|
||||||
fetchOriginal (entryPath, options) {
|
fetchOriginal (entryPath, options) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
let fpath = self.getFullPath(entryPath)
|
||||||
|
let cpath = self.getCachePath(entryPath)
|
||||||
let fpath = self.getFullPath(entryPath);
|
|
||||||
let cpath = self.getCachePath(entryPath);
|
|
||||||
|
|
||||||
options = _.defaults(options, {
|
options = _.defaults(options, {
|
||||||
parseMarkdown: true,
|
parseMarkdown: true,
|
||||||
@ -119,12 +105,11 @@ module.exports = {
|
|||||||
includeMarkdown: false,
|
includeMarkdown: false,
|
||||||
includeParentInfo: true,
|
includeParentInfo: true,
|
||||||
cache: true
|
cache: true
|
||||||
});
|
})
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((st) => {
|
return fs.statAsync(fpath).then((st) => {
|
||||||
if (st.isFile()) {
|
if (st.isFile()) {
|
||||||
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
||||||
|
|
||||||
// Parse contents
|
// Parse contents
|
||||||
|
|
||||||
let pageData = {
|
let pageData = {
|
||||||
@ -132,46 +117,43 @@ module.exports = {
|
|||||||
html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
|
html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
|
||||||
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
||||||
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
||||||
};
|
|
||||||
|
|
||||||
if(!pageData.meta.title) {
|
|
||||||
pageData.meta.title = _.startCase(entryPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pageData.meta.path = entryPath;
|
if (!pageData.meta.title) {
|
||||||
|
pageData.meta.title = _.startCase(entryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageData.meta.path = entryPath
|
||||||
|
|
||||||
// Get parent
|
// Get parent
|
||||||
|
|
||||||
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
||||||
return (pageData.parent = parentData);
|
return (pageData.parent = parentData)
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return (pageData.parent = false);
|
return (pageData.parent = false)
|
||||||
}) : Promise.resolve(true);
|
}) : Promise.resolve(true)
|
||||||
|
|
||||||
return parentPromise.then(() => {
|
return parentPromise.then(() => {
|
||||||
|
|
||||||
// Cache to disk
|
// Cache to disk
|
||||||
|
|
||||||
if (options.cache) {
|
if (options.cache) {
|
||||||
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false);
|
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
|
||||||
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
||||||
winston.error('Unable to write to cache! Performance may be affected.');
|
winston.error('Unable to write to cache! Performance may be affected.')
|
||||||
return true;
|
winston.error(err)
|
||||||
});
|
return true
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
}).return(pageData)
|
||||||
}).return(pageData);
|
})
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'));
|
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'))
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -181,19 +163,17 @@ module.exports = {
|
|||||||
* @return {String} Safe entry path
|
* @return {String} Safe entry path
|
||||||
*/
|
*/
|
||||||
parsePath (urlPath) {
|
parsePath (urlPath) {
|
||||||
|
let wlist = new RegExp('[^a-z0-9/-]', 'g')
|
||||||
|
|
||||||
let wlist = new RegExp('[^a-z0-9/\-]','g');
|
urlPath = _.toLower(urlPath).replace(wlist, '')
|
||||||
|
|
||||||
urlPath = _.toLower(urlPath).replace(wlist, '');
|
|
||||||
|
|
||||||
if (urlPath === '/') {
|
if (urlPath === '/') {
|
||||||
urlPath = 'home';
|
urlPath = 'home'
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p); });
|
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p) })
|
||||||
|
|
||||||
return _.join(urlParts, '/');
|
|
||||||
|
|
||||||
|
return _.join(urlParts, '/')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,38 +183,32 @@ module.exports = {
|
|||||||
* @return {Promise<Object|False>} The parent information.
|
* @return {Promise<Object|False>} The parent information.
|
||||||
*/
|
*/
|
||||||
getParentInfo (entryPath) {
|
getParentInfo (entryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
if (_.includes(entryPath, '/')) {
|
if (_.includes(entryPath, '/')) {
|
||||||
|
let parentParts = _.initial(_.split(entryPath, '/'))
|
||||||
let parentParts = _.initial(_.split(entryPath, '/'));
|
let parentPath = _.join(parentParts, '/')
|
||||||
let parentPath = _.join(parentParts,'/');
|
let parentFile = _.last(parentParts)
|
||||||
let parentFile = _.last(parentParts);
|
let fpath = self.getFullPath(parentPath)
|
||||||
let fpath = self.getFullPath(parentPath);
|
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((st) => {
|
return fs.statAsync(fpath).then((st) => {
|
||||||
if (st.isFile()) {
|
if (st.isFile()) {
|
||||||
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
||||||
|
let pageMeta = mark.parseMeta(contents)
|
||||||
let pageMeta = mark.parseMeta(contents);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: parentPath,
|
path: parentPath,
|
||||||
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
|
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
|
||||||
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
|
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error('Parent entry is not a valid file.'));
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Parent entry is root.'));
|
return Promise.reject(new Error('Parent entry is not a valid file.'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('Parent entry is root.'))
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -244,7 +218,7 @@ module.exports = {
|
|||||||
* @return {String} The full path.
|
* @return {String} The full path.
|
||||||
*/
|
*/
|
||||||
getFullPath (entryPath) {
|
getFullPath (entryPath) {
|
||||||
return path.join(this._repoPath, entryPath + '.md');
|
return path.join(this._repoPath, entryPath + '.md')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,7 +228,7 @@ module.exports = {
|
|||||||
* @return {String} The full cache path.
|
* @return {String} The full cache path.
|
||||||
*/
|
*/
|
||||||
getCachePath (entryPath) {
|
getCachePath (entryPath) {
|
||||||
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json');
|
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,8 +238,8 @@ module.exports = {
|
|||||||
* @return {String} The entry path
|
* @return {String} The entry path
|
||||||
*/
|
*/
|
||||||
getEntryPathFromFullPath (fullPath) {
|
getEntryPathFromFullPath (fullPath) {
|
||||||
let absRepoPath = path.resolve(ROOTPATH, this._repoPath);
|
let absRepoPath = path.resolve(ROOTPATH, this._repoPath)
|
||||||
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'),'/').value();
|
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'), '/').value()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -276,23 +250,21 @@ module.exports = {
|
|||||||
* @return {Promise<Boolean>} True on success, false on failure
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
*/
|
*/
|
||||||
update (entryPath, contents) {
|
update (entryPath, contents) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
let fpath = self.getFullPath(entryPath)
|
||||||
let fpath = self.getFullPath(entryPath);
|
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((st) => {
|
return fs.statAsync(fpath).then((st) => {
|
||||||
if (st.isFile()) {
|
if (st.isFile()) {
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
return self.updateCache(entryPath);
|
return self.updateCache(entryPath)
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Entry does not exist!'));
|
return Promise.reject(new Error('Entry does not exist!'))
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error(err);
|
winston.error(err)
|
||||||
return Promise.reject(new Error('Failed to save document.'));
|
return Promise.reject(new Error('Failed to save document.'))
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -302,8 +274,7 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
updateCache (entryPath) {
|
updateCache (entryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self.fetchOriginal(entryPath, {
|
return self.fetchOriginal(entryPath, {
|
||||||
parseMarkdown: true,
|
parseMarkdown: true,
|
||||||
@ -318,7 +289,7 @@ module.exports = {
|
|||||||
meta: pageData.meta,
|
meta: pageData.meta,
|
||||||
parent: pageData.parent || {},
|
parent: pageData.parent || {},
|
||||||
text: mark.removeMarkdown(pageData.markdown)
|
text: mark.removeMarkdown(pageData.markdown)
|
||||||
};
|
}
|
||||||
}).then((content) => {
|
}).then((content) => {
|
||||||
return db.Entry.findOneAndUpdate({
|
return db.Entry.findOneAndUpdate({
|
||||||
_id: content.entryPath
|
_id: content.entryPath
|
||||||
@ -331,9 +302,8 @@ module.exports = {
|
|||||||
}, {
|
}, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -344,22 +314,20 @@ module.exports = {
|
|||||||
* @return {Promise<Boolean>} True on success, false on failure
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
*/
|
*/
|
||||||
create (entryPath, contents) {
|
create (entryPath, contents) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self.exists(entryPath).then((docExists) => {
|
return self.exists(entryPath).then((docExists) => {
|
||||||
if (!docExists) {
|
if (!docExists) {
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
return self.updateCache(entryPath);
|
return self.updateCache(entryPath)
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Entry already exists!'));
|
return Promise.reject(new Error('Entry already exists!'))
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error(err);
|
winston.error(err)
|
||||||
return Promise.reject(new Error('Something went wrong.'));
|
return Promise.reject(new Error('Something went wrong.'))
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -370,14 +338,12 @@ module.exports = {
|
|||||||
* @return {Promise<Boolean>} True on success, false on failure
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
*/
|
*/
|
||||||
makePersistent (entryPath, contents) {
|
makePersistent (entryPath, contents) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
let fpath = self.getFullPath(entryPath)
|
||||||
let fpath = self.getFullPath(entryPath);
|
|
||||||
|
|
||||||
return fs.outputFileAsync(fpath, contents).then(() => {
|
return fs.outputFileAsync(fpath, contents).then(() => {
|
||||||
return git.commitDocument(entryPath);
|
return git.commitDocument(entryPath)
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -388,35 +354,31 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
move (entryPath, newEntryPath) {
|
move (entryPath, newEntryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
if (_.isEmpty(entryPath) || entryPath === 'home') {
|
if (_.isEmpty(entryPath) || entryPath === 'home') {
|
||||||
return Promise.reject(new Error('Invalid path!'));
|
return Promise.reject(new Error('Invalid path!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return git.moveDocument(entryPath, newEntryPath).then(() => {
|
return git.moveDocument(entryPath, newEntryPath).then(() => {
|
||||||
return git.commitDocument(newEntryPath).then(() => {
|
return git.commitDocument(newEntryPath).then(() => {
|
||||||
|
|
||||||
// Delete old cache version
|
// Delete old cache version
|
||||||
|
|
||||||
let oldEntryCachePath = self.getCachePath(entryPath);
|
let oldEntryCachePath = self.getCachePath(entryPath)
|
||||||
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true; });
|
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
|
||||||
|
|
||||||
// Delete old index entry
|
// Delete old index entry
|
||||||
|
|
||||||
ws.emit('searchDel', {
|
ws.emit('searchDel', {
|
||||||
auth: WSInternalKey,
|
auth: WSInternalKey,
|
||||||
entryPath
|
entryPath
|
||||||
});
|
})
|
||||||
|
|
||||||
// Create cache for new entry
|
// Create cache for new entry
|
||||||
|
|
||||||
return self.updateCache(newEntryPath);
|
return self.updateCache(newEntryPath)
|
||||||
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -426,14 +388,11 @@ module.exports = {
|
|||||||
* @return {Promise<String>} Starter content
|
* @return {Promise<String>} Starter content
|
||||||
*/
|
*/
|
||||||
getStarter (entryPath) {
|
getStarter (entryPath) {
|
||||||
|
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
|
||||||
let self = this;
|
|
||||||
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')));
|
|
||||||
|
|
||||||
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
|
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
|
||||||
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle);
|
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -443,29 +402,26 @@ module.exports = {
|
|||||||
* @return {Promise<Object>} Promise of the search results
|
* @return {Promise<Object>} Promise of the search results
|
||||||
*/
|
*/
|
||||||
search (terms) {
|
search (terms) {
|
||||||
|
|
||||||
let self = this;
|
|
||||||
terms = _.chain(terms)
|
terms = _.chain(terms)
|
||||||
.deburr()
|
.deburr()
|
||||||
.toLower()
|
.toLower()
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/[^a-z0-9\- ]/g, '')
|
.replace(/[^a-z0-9\- ]/g, '')
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.filter((f) => { return !_.isEmpty(f); })
|
.filter((f) => { return !_.isEmpty(f) })
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.value();
|
.value()
|
||||||
|
|
||||||
return db.Entry.find(
|
return db.Entry.find(
|
||||||
{ $text: { $search: terms } },
|
{ $text: { $search: terms } },
|
||||||
{ score: { $meta: "textScore" }, title: 1 }
|
{ score: { $meta: 'textScore' }, title: 1 }
|
||||||
)
|
)
|
||||||
.sort({ score: { $meta: "textScore" } })
|
.sort({ score: { $meta: 'textScore' } })
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.exec()
|
.exec()
|
||||||
.then((hits) => {
|
.then((hits) => {
|
||||||
|
|
||||||
if (hits.length < 5) {
|
if (hits.length < 5) {
|
||||||
let regMatch = new RegExp('^' + _.split(terms, ' ')[0]);
|
let regMatch = new RegExp('^' + _.split(terms, ' ')[0])
|
||||||
return db.Entry.find({
|
return db.Entry.find({
|
||||||
_id: { $regex: regMatch }
|
_id: { $regex: regMatch }
|
||||||
}, '_id')
|
}, '_id')
|
||||||
@ -476,25 +432,21 @@ module.exports = {
|
|||||||
return {
|
return {
|
||||||
match: hits,
|
match: hits,
|
||||||
suggest: (matches) ? _.map(matches, '_id') : []
|
suggest: (matches) ? _.map(matches, '_id') : []
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
match: _.filter(hits, (h) => { return h._doc.score >= 1; }),
|
match: _.filter(hits, (h) => { return h._doc.score >= 1 }),
|
||||||
suggest: []
|
suggest: []
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
winston.error(err)
|
||||||
winston.error(err);
|
|
||||||
return {
|
return {
|
||||||
match: [],
|
match: [],
|
||||||
suggest: []
|
suggest: []
|
||||||
};
|
}
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
179
libs/git.js
179
libs/git.js
@ -1,13 +1,11 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Git = require("git-wrapper2-promise"),
|
const Git = require('git-wrapper2-promise')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
os = require('os'),
|
const fs = Promise.promisifyAll(require('fs'))
|
||||||
fs = Promise.promisifyAll(require("fs")),
|
const _ = require('lodash')
|
||||||
moment = require('moment'),
|
const URL = require('url')
|
||||||
_ = require('lodash'),
|
|
||||||
URL = require('url');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git Model
|
* Git Model
|
||||||
@ -37,28 +35,26 @@ module.exports = {
|
|||||||
* @return {Object} Git model instance
|
* @return {Object} Git model instance
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
// -> Build repository path
|
// -> Build repository path
|
||||||
|
|
||||||
if (_.isEmpty(appconfig.paths.repo)) {
|
if (_.isEmpty(appconfig.paths.repo)) {
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo');
|
self._repo.path = path.join(ROOTPATH, 'repo')
|
||||||
} else {
|
} else {
|
||||||
self._repo.path = appconfig.paths.repo;
|
self._repo.path = appconfig.paths.repo
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Initialize repository
|
// -> Initialize repository
|
||||||
|
|
||||||
self.onReady = self._initRepo(appconfig);
|
self.onReady = self._initRepo(appconfig)
|
||||||
|
|
||||||
// Define signature
|
// Define signature
|
||||||
|
|
||||||
self._signature.name = appconfig.git.signature.name || 'Wiki';
|
self._signature.name = appconfig.git.signature.name || 'Wiki'
|
||||||
self._signature.email = appconfig.git.signature.email || 'user@example.com';
|
self._signature.email = appconfig.git.signature.email || 'user@example.com'
|
||||||
|
|
||||||
return self;
|
|
||||||
|
|
||||||
|
return self
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,60 +64,54 @@ module.exports = {
|
|||||||
* @return {Object} Promise
|
* @return {Object} Promise
|
||||||
*/
|
*/
|
||||||
_initRepo (appconfig) {
|
_initRepo (appconfig) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
|
|
||||||
|
|
||||||
// -> Check if path is accessible
|
// -> Check if path is accessible
|
||||||
|
|
||||||
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
||||||
if (err.code !== 'EEXIST') {
|
if (err.code !== 'EEXIST') {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.');
|
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.')
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
self._git = new Git({ 'git-dir': self._repo.path })
|
||||||
self._git = new Git({ 'git-dir': self._repo.path });
|
|
||||||
|
|
||||||
// -> Check if path already contains a git working folder
|
// -> Check if path already contains a git working folder
|
||||||
|
|
||||||
return self._git.isRepo().then((isRepo) => {
|
return self._git.isRepo().then((isRepo) => {
|
||||||
self._repo.exists = isRepo;
|
self._repo.exists = isRepo
|
||||||
return (!isRepo) ? self._git.exec('init') : true;
|
return (!isRepo) ? self._git.exec('init') : true
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
self._repo.exists = false;
|
self._repo.exists = false
|
||||||
});
|
})
|
||||||
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
||||||
// Initialize remote
|
// Initialize remote
|
||||||
|
|
||||||
let urlObj = URL.parse(appconfig.git.url);
|
let urlObj = URL.parse(appconfig.git.url)
|
||||||
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '');
|
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
|
||||||
self._url = URL.format(urlObj);
|
self._url = URL.format(urlObj)
|
||||||
|
|
||||||
return self._git.exec('remote', 'show').then((cProc) => {
|
return self._git.exec('remote', 'show').then((cProc) => {
|
||||||
let out = cProc.stdout.toString();
|
let out = cProc.stdout.toString()
|
||||||
if (_.includes(out, 'origin')) {
|
if (_.includes(out, 'origin')) {
|
||||||
return true;
|
return true
|
||||||
} else {
|
} else {
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
||||||
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
||||||
).then(() => {
|
).then(() => {
|
||||||
return self._git.exec('remote', ['add', 'origin', self._url]);
|
return self._git.exec('remote', ['add', 'origin', self._url])
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Git remote error!');
|
winston.error('[' + PROCNAME + '][GIT] Git remote error!')
|
||||||
throw err;
|
throw err
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.');
|
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,9 +120,7 @@ module.exports = {
|
|||||||
* @return {String} The repo path.
|
* @return {String} The repo path.
|
||||||
*/
|
*/
|
||||||
getRepoPath () {
|
getRepoPath () {
|
||||||
|
return this._repo.path || path.join(ROOTPATH, 'repo')
|
||||||
return this._repo.path || path.join(ROOTPATH, 'repo');
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,49 +129,40 @@ module.exports = {
|
|||||||
* @return {Promise} Resolve on sync success
|
* @return {Promise} Resolve on sync success
|
||||||
*/
|
*/
|
||||||
resync () {
|
resync () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...');
|
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
|
||||||
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
||||||
winston.info('[' + PROCNAME + '][GIT] Pull completed.');
|
winston.info('[' + PROCNAME + '][GIT] Pull completed.')
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!');
|
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
|
||||||
throw err;
|
throw err
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
||||||
// Check for changes
|
// Check for changes
|
||||||
|
|
||||||
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
||||||
let out = cProc.stdout.toString();
|
let out = cProc.stdout.toString()
|
||||||
|
|
||||||
if (_.includes(out, 'commit')) {
|
if (_.includes(out, 'commit')) {
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...');
|
|
||||||
return self._git.push('origin', self._repo.branch).then(() => {
|
return self._git.push('origin', self._repo.branch).then(() => {
|
||||||
return winston.info('[' + PROCNAME + '][GIT] Push completed.');
|
return winston.info('[' + PROCNAME + '][GIT] Push completed.')
|
||||||
});
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
|
||||||
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!');
|
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
|
||||||
throw err;
|
throw err
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,23 +172,21 @@ module.exports = {
|
|||||||
* @return {Promise} Resolve on commit success
|
* @return {Promise} Resolve on commit success
|
||||||
*/
|
*/
|
||||||
commitDocument (entryPath) {
|
commitDocument (entryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
let gitFilePath = entryPath + '.md'
|
||||||
let gitFilePath = entryPath + '.md';
|
let commitMsg = ''
|
||||||
let commitMsg = '';
|
|
||||||
|
|
||||||
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
||||||
let out = cProc.stdout.toString();
|
let out = cProc.stdout.toString()
|
||||||
return _.includes(out, gitFilePath);
|
return _.includes(out, gitFilePath)
|
||||||
}).then((isTracked) => {
|
}).then((isTracked) => {
|
||||||
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
|
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
|
||||||
return self._git.add(gitFilePath);
|
return self._git.add(gitFilePath)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return self._git.commit(commitMsg).catch((err) => {
|
return self._git.commit(commitMsg).catch((err) => {
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,20 +197,18 @@ module.exports = {
|
|||||||
* @return {Promise<Boolean>} Resolve on success
|
* @return {Promise<Boolean>} Resolve on success
|
||||||
*/
|
*/
|
||||||
moveDocument (entryPath, newEntryPath) {
|
moveDocument (entryPath, newEntryPath) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
let gitFilePath = entryPath + '.md'
|
||||||
let gitFilePath = entryPath + '.md';
|
let gitNewFilePath = newEntryPath + '.md'
|
||||||
let gitNewFilePath = newEntryPath + '.md';
|
|
||||||
|
|
||||||
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
||||||
let out = cProc.stdout.toString();
|
let out = cProc.stdout.toString()
|
||||||
if (_.includes(out, 'fatal')) {
|
if (_.includes(out, 'fatal')) {
|
||||||
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')));
|
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg)
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -243,16 +218,14 @@ module.exports = {
|
|||||||
* @return {Promise} Resolve on commit success
|
* @return {Promise} Resolve on commit success
|
||||||
*/
|
*/
|
||||||
commitUploads (msg) {
|
commitUploads (msg) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
msg = msg || 'Uploads repository sync'
|
||||||
msg = msg || "Uploads repository sync";
|
|
||||||
|
|
||||||
return self._git.add('uploads').then(() => {
|
return self._git.add('uploads').then(() => {
|
||||||
return self._git.commit(msg).catch((err) => {
|
return self._git.commit(msg).catch((err) => {
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal Authentication
|
* Internal Authentication
|
||||||
@ -10,23 +10,17 @@ module.exports = {
|
|||||||
_curKey: false,
|
_curKey: false,
|
||||||
|
|
||||||
init (inKey) {
|
init (inKey) {
|
||||||
|
this._curKey = inKey
|
||||||
|
|
||||||
this._curKey = inKey;
|
return this
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
generateKey () {
|
generateKey () {
|
||||||
|
return crypto.randomBytes(20).toString('hex')
|
||||||
return crypto.randomBytes(20).toString('hex');
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
validateKey (inKey) {
|
validateKey (inKey) {
|
||||||
|
return inKey === this._curKey
|
||||||
return inKey === this._curKey;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
109
libs/local.js
109
libs/local.js
@ -1,11 +1,11 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
multer = require('multer'),
|
const multer = require('multer')
|
||||||
os = require('os'),
|
const os = require('os')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local Data Storage
|
* Local Data Storage
|
||||||
@ -23,15 +23,13 @@ module.exports = {
|
|||||||
* @return {Object} Local Data Storage model instance
|
* @return {Object} Local Data Storage model instance
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
|
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
this.createBaseDirectories(appconfig)
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
this.initMulter(appconfig)
|
||||||
|
|
||||||
this.createBaseDirectories(appconfig);
|
|
||||||
this.initMulter(appconfig);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,60 +39,56 @@ module.exports = {
|
|||||||
* @return {boolean} Void
|
* @return {boolean} Void
|
||||||
*/
|
*/
|
||||||
initMulter (appconfig) {
|
initMulter (appconfig) {
|
||||||
|
|
||||||
let maxFileSizes = {
|
let maxFileSizes = {
|
||||||
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
||||||
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
||||||
};
|
}
|
||||||
|
|
||||||
// -> IMAGES
|
// -> IMAGES
|
||||||
|
|
||||||
this.uploadImgHandler = multer({
|
this.uploadImgHandler = multer({
|
||||||
storage: multer.diskStorage({
|
storage: multer.diskStorage({
|
||||||
destination: (req, f, cb) => {
|
destination: (req, f, cb) => {
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
fileFilter: (req, f, cb) => {
|
fileFilter: (req, f, cb) => {
|
||||||
|
|
||||||
// -> Check filesize
|
// -> Check filesize
|
||||||
|
|
||||||
if (f.size > maxFileSizes.img) {
|
if (f.size > maxFileSizes.img) {
|
||||||
return cb(null, false);
|
return cb(null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Check MIME type (quick check only)
|
// -> Check MIME type (quick check only)
|
||||||
|
|
||||||
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
||||||
return cb(null, false);
|
return cb(null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, true);
|
cb(null, true)
|
||||||
}
|
}
|
||||||
}).array('imgfile', 20);
|
}).array('imgfile', 20)
|
||||||
|
|
||||||
// -> FILES
|
// -> FILES
|
||||||
|
|
||||||
this.uploadFileHandler = multer({
|
this.uploadFileHandler = multer({
|
||||||
storage: multer.diskStorage({
|
storage: multer.diskStorage({
|
||||||
destination: (req, f, cb) => {
|
destination: (req, f, cb) => {
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
fileFilter: (req, f, cb) => {
|
fileFilter: (req, f, cb) => {
|
||||||
|
|
||||||
// -> Check filesize
|
// -> Check filesize
|
||||||
|
|
||||||
if (f.size > maxFileSizes.file) {
|
if (f.size > maxFileSizes.file) {
|
||||||
return cb(null, false);
|
return cb(null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, true);
|
cb(null, true)
|
||||||
}
|
}
|
||||||
}).array('binfile', 20);
|
}).array('binfile', 20)
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,34 +98,31 @@ module.exports = {
|
|||||||
* @return {Void} Void
|
* @return {Void} Void
|
||||||
*/
|
*/
|
||||||
createBaseDirectories (appconfig) {
|
createBaseDirectories (appconfig) {
|
||||||
|
winston.info('[SERVER] Checking data directories...')
|
||||||
winston.info('[SERVER] Checking data directories...');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
|
||||||
|
|
||||||
if (os.type() !== 'Windows_NT') {
|
if (os.type() !== 'Windows_NT') {
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644');
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
|
||||||
|
|
||||||
if (os.type() !== 'Windows_NT') {
|
if (os.type() !== 'Windows_NT') {
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
winston.error(err);
|
winston.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
winston.info('[SERVER] Data and Repository directories are OK.');
|
winston.info('[SERVER] Data and Repository directories are OK.')
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
return
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +131,7 @@ module.exports = {
|
|||||||
* @return {String} The uploads path.
|
* @return {String} The uploads path.
|
||||||
*/
|
*/
|
||||||
getUploadsPath () {
|
getUploadsPath () {
|
||||||
return this._uploadsPath;
|
return this._uploadsPath
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +140,7 @@ module.exports = {
|
|||||||
* @return {String} The thumbs path.
|
* @return {String} The thumbs path.
|
||||||
*/
|
*/
|
||||||
getThumbsPath () {
|
getThumbsPath () {
|
||||||
return this._uploadsThumbsPath;
|
return this._uploadsThumbsPath
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,27 +152,25 @@ module.exports = {
|
|||||||
* @return {Promise<String>} Promise of the accepted filename
|
* @return {Promise<String>} Promise of the accepted filename
|
||||||
*/
|
*/
|
||||||
validateUploadsFilename (f, fld, isImage) {
|
validateUploadsFilename (f, fld, isImage) {
|
||||||
|
let fObj = path.parse(f)
|
||||||
let fObj = path.parse(f);
|
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
|
||||||
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
|
let fext = _.toLower(fObj.ext)
|
||||||
let fext = _.toLower(fObj.ext);
|
|
||||||
|
|
||||||
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
||||||
fext = '.png';
|
fext = '.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
f = fname + fext;
|
f = fname + fext
|
||||||
let fpath = path.resolve(this._uploadsPath, fld, f);
|
let fpath = path.resolve(this._uploadsPath, fld, f)
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((s) => {
|
return fs.statAsync(fpath).then((s) => {
|
||||||
throw new Error('File ' + f + ' already exists.');
|
throw new Error('File ' + f + ' already exists.')
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return f;
|
return f
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
}
|
||||||
|
|
||||||
};
|
|
||||||
|
212
libs/markdown.js
212
libs/markdown.js
@ -1,19 +1,18 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
const md = require('markdown-it')
|
||||||
md = require('markdown-it'),
|
const mdEmoji = require('markdown-it-emoji')
|
||||||
mdEmoji = require('markdown-it-emoji'),
|
const mdTaskLists = require('markdown-it-task-lists')
|
||||||
mdTaskLists = require('markdown-it-task-lists'),
|
const mdAbbr = require('markdown-it-abbr')
|
||||||
mdAbbr = require('markdown-it-abbr'),
|
const mdAnchor = require('markdown-it-anchor')
|
||||||
mdAnchor = require('markdown-it-anchor'),
|
const mdFootnote = require('markdown-it-footnote')
|
||||||
mdFootnote = require('markdown-it-footnote'),
|
const mdExternalLinks = require('markdown-it-external-links')
|
||||||
mdExternalLinks = require('markdown-it-external-links'),
|
const mdExpandTabs = require('markdown-it-expand-tabs')
|
||||||
mdExpandTabs = require('markdown-it-expand-tabs'),
|
const mdAttrs = require('markdown-it-attrs')
|
||||||
mdAttrs = require('markdown-it-attrs'),
|
const hljs = require('highlight.js')
|
||||||
hljs = require('highlight.js'),
|
const cheerio = require('cheerio')
|
||||||
cheerio = require('cheerio'),
|
const _ = require('lodash')
|
||||||
_ = require('lodash'),
|
const mdRemove = require('remove-markdown')
|
||||||
mdRemove = require('remove-markdown');
|
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
|
|
||||||
@ -24,12 +23,12 @@ var mkdown = md({
|
|||||||
highlight (str, lang) {
|
highlight (str, lang) {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>';
|
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return '<pre><code>' + str + '</code></pre>';
|
return '<pre><code>' + str + '</code></pre>'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '<pre><code>' + str + '</code></pre>';
|
return '<pre><code>' + str + '</code></pre>'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.use(mdEmoji)
|
.use(mdEmoji)
|
||||||
@ -50,30 +49,30 @@ var mkdown = md({
|
|||||||
.use(mdExpandTabs, {
|
.use(mdExpandTabs, {
|
||||||
tabWidth: 4
|
tabWidth: 4
|
||||||
})
|
})
|
||||||
.use(mdAttrs);
|
.use(mdAttrs)
|
||||||
|
|
||||||
// Rendering rules
|
// Rendering rules
|
||||||
|
|
||||||
mkdown.renderer.rules.emoji = function (token, idx) {
|
mkdown.renderer.rules.emoji = function (token, idx) {
|
||||||
return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>';
|
return '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'
|
||||||
};
|
}
|
||||||
|
|
||||||
// Video rules
|
// Video rules
|
||||||
|
|
||||||
const videoRules = [
|
const videoRules = [
|
||||||
{
|
{
|
||||||
selector: 'a.youtube',
|
selector: 'a.youtube',
|
||||||
regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
|
regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'),
|
||||||
output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
|
output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: 'a.vimeo',
|
selector: 'a.vimeo',
|
||||||
regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
||||||
output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
|
output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: 'a.dailymotion',
|
selector: 'a.dailymotion',
|
||||||
regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i'),
|
regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i'),
|
||||||
output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
|
output: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -90,81 +89,79 @@ const videoRules = [
|
|||||||
* @return {Array} TOC tree
|
* @return {Array} TOC tree
|
||||||
*/
|
*/
|
||||||
const parseTree = (content) => {
|
const parseTree = (content) => {
|
||||||
|
let tokens = md().parse(content, {})
|
||||||
let tokens = md().parse(content, {});
|
let tocArray = []
|
||||||
let tocArray = [];
|
|
||||||
|
|
||||||
// -> Extract headings and their respective levels
|
// -> Extract headings and their respective levels
|
||||||
|
|
||||||
for (let i = 0; i < tokens.length; i++) {
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
if (tokens[i].type !== "heading_close") {
|
if (tokens[i].type !== 'heading_close') {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const heading = tokens[i - 1];
|
const heading = tokens[i - 1]
|
||||||
const heading_close = tokens[i];
|
const headingclose = tokens[i]
|
||||||
|
|
||||||
if (heading.type === "inline") {
|
if (heading.type === 'inline') {
|
||||||
let content = "";
|
let content = ''
|
||||||
let anchor = "";
|
let anchor = ''
|
||||||
if (heading.children && heading.children[0].type === "link_open") {
|
if (heading.children && heading.children[0].type === 'link_open') {
|
||||||
content = heading.children[1].content;
|
content = heading.children[1].content
|
||||||
anchor = _.kebabCase(content);
|
anchor = _.kebabCase(content)
|
||||||
} else {
|
} else {
|
||||||
content = heading.content;
|
content = heading.content
|
||||||
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ""));
|
anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
tocArray.push({
|
tocArray.push({
|
||||||
content,
|
content,
|
||||||
anchor,
|
anchor,
|
||||||
level: +heading_close.tag.substr(1, 1)
|
level: +headingclose.tag.substr(1, 1)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Exclude levels deeper than 2
|
// -> Exclude levels deeper than 2
|
||||||
|
|
||||||
_.remove(tocArray, (n) => { return n.level > 2; });
|
_.remove(tocArray, (n) => { return n.level > 2 })
|
||||||
|
|
||||||
// -> Build tree from flat array
|
// -> Build tree from flat array
|
||||||
|
|
||||||
return _.reduce(tocArray, (tree, v) => {
|
return _.reduce(tocArray, (tree, v) => {
|
||||||
let treeLength = tree.length - 1;
|
let treeLength = tree.length - 1
|
||||||
if (v.level < 2) {
|
if (v.level < 2) {
|
||||||
tree.push({
|
tree.push({
|
||||||
content: v.content,
|
content: v.content,
|
||||||
anchor: v.anchor,
|
anchor: v.anchor,
|
||||||
nodes: []
|
nodes: []
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
let lastNodeLevel = 1;
|
let lastNodeLevel = 1
|
||||||
let GetNodePath = (startPos) => {
|
let GetNodePath = (startPos) => {
|
||||||
lastNodeLevel++;
|
lastNodeLevel++
|
||||||
if (_.isEmpty(startPos)) {
|
if (_.isEmpty(startPos)) {
|
||||||
startPos = 'nodes';
|
startPos = 'nodes'
|
||||||
}
|
}
|
||||||
if (lastNodeLevel === v.level) {
|
if (lastNodeLevel === v.level) {
|
||||||
return startPos;
|
return startPos
|
||||||
} else {
|
} else {
|
||||||
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes');
|
return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes')
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let lastNodePath = GetNodePath();
|
let lastNodePath = GetNodePath()
|
||||||
let lastNode = _.get(tree[treeLength], lastNodePath);
|
let lastNode = _.get(tree[treeLength], lastNodePath)
|
||||||
if (lastNode) {
|
if (lastNode) {
|
||||||
lastNode.push({
|
lastNode.push({
|
||||||
content: v.content,
|
content: v.content,
|
||||||
anchor: v.anchor,
|
anchor: v.anchor,
|
||||||
nodes: []
|
nodes: []
|
||||||
});
|
})
|
||||||
_.set(tree[treeLength], lastNodePath, lastNode);
|
_.set(tree[treeLength], lastNodePath, lastNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tree;
|
return tree
|
||||||
}, []);
|
}, [])
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse markdown content to HTML
|
* Parse markdown content to HTML
|
||||||
@ -173,86 +170,84 @@ const parseTree = (content) => {
|
|||||||
* @return {String} HTML formatted content
|
* @return {String} HTML formatted content
|
||||||
*/
|
*/
|
||||||
const parseContent = (content) => {
|
const parseContent = (content) => {
|
||||||
|
let output = mkdown.render(content)
|
||||||
let output = mkdown.render(content);
|
let cr = cheerio.load(output)
|
||||||
let cr = cheerio.load(output);
|
|
||||||
|
|
||||||
// -> Check for empty first element
|
// -> Check for empty first element
|
||||||
|
|
||||||
let firstElm = cr.root().children().first()[0];
|
let firstElm = cr.root().children().first()[0]
|
||||||
if (firstElm.type === 'tag' && firstElm.name === 'p') {
|
if (firstElm.type === 'tag' && firstElm.name === 'p') {
|
||||||
let firstElmChildren = firstElm.children;
|
let firstElmChildren = firstElm.children
|
||||||
if (firstElmChildren.length < 1) {
|
if (firstElmChildren.length < 1) {
|
||||||
firstElm.remove();
|
firstElm.remove()
|
||||||
} else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
|
} else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') {
|
||||||
cr(firstElm).addClass('is-gapless');
|
cr(firstElm).addClass('is-gapless')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Remove links in headers
|
// -> Remove links in headers
|
||||||
|
|
||||||
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
|
cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => {
|
||||||
let txtLink = cr(elm).text();
|
let txtLink = cr(elm).text()
|
||||||
cr(elm).replaceWith(txtLink);
|
cr(elm).replaceWith(txtLink)
|
||||||
});
|
})
|
||||||
|
|
||||||
// -> Re-attach blockquote styling classes to their parents
|
// -> Re-attach blockquote styling classes to their parents
|
||||||
|
|
||||||
cr.root().children('blockquote').each((i, elm) => {
|
cr.root().children('blockquote').each((i, elm) => {
|
||||||
if (cr(elm).children().length > 0) {
|
if (cr(elm).children().length > 0) {
|
||||||
let bqLastChild = cr(elm).children().last()[0];
|
let bqLastChild = cr(elm).children().last()[0]
|
||||||
let bqLastChildClasses = cr(bqLastChild).attr('class');
|
let bqLastChildClasses = cr(bqLastChild).attr('class')
|
||||||
if (bqLastChildClasses && bqLastChildClasses.length > 0) {
|
if (bqLastChildClasses && bqLastChildClasses.length > 0) {
|
||||||
cr(bqLastChild).removeAttr('class');
|
cr(bqLastChild).removeAttr('class')
|
||||||
cr(elm).addClass(bqLastChildClasses);
|
cr(elm).addClass(bqLastChildClasses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// -> Enclose content below headers
|
// -> Enclose content below headers
|
||||||
|
|
||||||
cr('h2').each((i, elm) => {
|
cr('h2').each((i, elm) => {
|
||||||
let subH2Content = cr(elm).nextUntil('h1, h2');
|
let subH2Content = cr(elm).nextUntil('h1, h2')
|
||||||
cr(elm).after('<div class="indent-h2"></div>');
|
cr(elm).after('<div class="indent-h2"></div>')
|
||||||
let subH2Container = cr(elm).next('.indent-h2');
|
let subH2Container = cr(elm).next('.indent-h2')
|
||||||
_.forEach(subH2Content, (ch) => {
|
_.forEach(subH2Content, (ch) => {
|
||||||
cr(subH2Container).append(ch);
|
cr(subH2Container).append(ch)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
cr('h3').each((i, elm) => {
|
cr('h3').each((i, elm) => {
|
||||||
let subH3Content = cr(elm).nextUntil('h1, h2, h3');
|
let subH3Content = cr(elm).nextUntil('h1, h2, h3')
|
||||||
cr(elm).after('<div class="indent-h3"></div>');
|
cr(elm).after('<div class="indent-h3"></div>')
|
||||||
let subH3Container = cr(elm).next('.indent-h3');
|
let subH3Container = cr(elm).next('.indent-h3')
|
||||||
_.forEach(subH3Content, (ch) => {
|
_.forEach(subH3Content, (ch) => {
|
||||||
cr(subH3Container).append(ch);
|
cr(subH3Container).append(ch)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Replace video links with embeds
|
// Replace video links with embeds
|
||||||
|
|
||||||
_.forEach(videoRules, (vrule) => {
|
_.forEach(videoRules, (vrule) => {
|
||||||
cr(vrule.selector).each((i, elm) => {
|
cr(vrule.selector).each((i, elm) => {
|
||||||
let originLink = cr(elm).attr('href');
|
let originLink = cr(elm).attr('href')
|
||||||
if (vrule.regexp) {
|
if (vrule.regexp) {
|
||||||
let vidMatches = originLink.match(vrule.regexp);
|
let vidMatches = originLink.match(vrule.regexp)
|
||||||
if ((vidMatches && _.isArray(vidMatches))) {
|
if ((vidMatches && _.isArray(vidMatches))) {
|
||||||
vidMatches = _.filter(vidMatches, (f) => {
|
vidMatches = _.filter(vidMatches, (f) => {
|
||||||
return f && _.isString(f);
|
return f && _.isString(f)
|
||||||
});
|
})
|
||||||
originLink = _.last(vidMatches);
|
originLink = _.last(vidMatches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let processedLink = _.replace(vrule.output, '{0}', originLink);
|
let processedLink = _.replace(vrule.output, '{0}', originLink)
|
||||||
cr(elm).replaceWith(processedLink);
|
cr(elm).replaceWith(processedLink)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
output = cr.html();
|
output = cr.html()
|
||||||
|
|
||||||
return output;
|
return output
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse meta-data tags from content
|
* Parse meta-data tags from content
|
||||||
@ -261,16 +256,15 @@ const parseContent = (content) => {
|
|||||||
* @return {Object} Properties found in the content and their values
|
* @return {Object} Properties found in the content and their values
|
||||||
*/
|
*/
|
||||||
const parseMeta = (content) => {
|
const parseMeta = (content) => {
|
||||||
|
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->', 'g')
|
||||||
let commentMeta = new RegExp('<!-- ?([a-zA-Z]+):(.*)-->','g');
|
let results = {}
|
||||||
let results = {}, match;
|
let match
|
||||||
while(match = commentMeta.exec(content)) {
|
while ((match = commentMeta.exec(content)) !== null) {
|
||||||
results[_.toLower(match[1])] = _.trim(match[2]);
|
results[_.toLower(match[1])] = _.trim(match[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@ -285,7 +279,7 @@ module.exports = {
|
|||||||
meta: parseMeta(content),
|
meta: parseMeta(content),
|
||||||
html: parseContent(content),
|
html: parseContent(content),
|
||||||
tree: parseTree(content)
|
tree: parseTree(content)
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseContent,
|
parseContent,
|
||||||
@ -312,7 +306,7 @@ module.exports = {
|
|||||||
.replace(/(\b(\w{1,2})\b(\W|$))/g, '')
|
.replace(/(\b(\w{1,2})\b(\W|$))/g, '')
|
||||||
.replace(/\s\s+/g, ' ')
|
.replace(/\s\s+/g, ' ')
|
||||||
.value()
|
.value()
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
readChunk = require('read-chunk'),
|
const readChunk = require('read-chunk')
|
||||||
fileType = require('file-type'),
|
const fileType = require('file-type')
|
||||||
mime = require('mime-types'),
|
const mime = require('mime-types')
|
||||||
farmhash = require('farmhash'),
|
const farmhash = require('farmhash')
|
||||||
moment = require('moment'),
|
const chokidar = require('chokidar')
|
||||||
chokidar = require('chokidar'),
|
const sharp = require('sharp')
|
||||||
sharp = require('sharp'),
|
const _ = require('lodash')
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads - Agent
|
* Uploads - Agent
|
||||||
@ -28,17 +27,15 @@ module.exports = {
|
|||||||
* @return {Object} Uploads model instance
|
* @return {Object} Uploads model instance
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
|
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
|
||||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
|
||||||
|
|
||||||
// Disable Sharp cache, as it cause file locks issues when deleting uploads.
|
// Disable Sharp cache, as it cause file locks issues when deleting uploads.
|
||||||
sharp.cache(false);
|
sharp.cache(false)
|
||||||
|
|
||||||
return self;
|
|
||||||
|
|
||||||
|
return self
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,8 +44,7 @@ module.exports = {
|
|||||||
* @return {Void} Void
|
* @return {Void} Void
|
||||||
*/
|
*/
|
||||||
watch () {
|
watch () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
self._watcher = chokidar.watch(self._uploadsPath, {
|
self._watcher = chokidar.watch(self._uploadsPath, {
|
||||||
persistent: true,
|
persistent: true,
|
||||||
@ -56,30 +52,24 @@ module.exports = {
|
|||||||
cwd: self._uploadsPath,
|
cwd: self._uploadsPath,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
awaitWriteFinish: true
|
awaitWriteFinish: true
|
||||||
});
|
})
|
||||||
|
|
||||||
// -> Add new upload file
|
// -> Add new upload file
|
||||||
|
|
||||||
self._watcher.on('add', (p) => {
|
self._watcher.on('add', (p) => {
|
||||||
|
let pInfo = self.parseUploadsRelPath(p)
|
||||||
let pInfo = self.parseUploadsRelPath(p);
|
|
||||||
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
||||||
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true });
|
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return git.commitUploads('Uploaded ' + p);
|
return git.commitUploads('Uploaded ' + p)
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
// -> Remove upload file
|
// -> Remove upload file
|
||||||
|
|
||||||
self._watcher.on('unlink', (p) => {
|
self._watcher.on('unlink', (p) => {
|
||||||
|
return git.commitUploads('Deleted/Renamed ' + p)
|
||||||
let pInfo = self.parseUploadsRelPath(p);
|
})
|
||||||
return git.commitUploads('Deleted/Renamed ' + p);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,19 +78,16 @@ module.exports = {
|
|||||||
* @return {Promise<Void>} Promise of the scan operation
|
* @return {Promise<Void>} Promise of the scan operation
|
||||||
*/
|
*/
|
||||||
initialScan () {
|
initialScan () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return fs.readdirAsync(self._uploadsPath).then((ls) => {
|
return fs.readdirAsync(self._uploadsPath).then((ls) => {
|
||||||
|
|
||||||
// Get all folders
|
// Get all folders
|
||||||
|
|
||||||
return Promise.map(ls, (f) => {
|
return Promise.map(ls, (f) => {
|
||||||
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
|
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })
|
||||||
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
|
}).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {
|
||||||
|
let folderNames = _.map(arrDirs, 'filename')
|
||||||
let folderNames = _.map(arrDirs, 'filename');
|
folderNames.unshift('')
|
||||||
folderNames.unshift('');
|
|
||||||
|
|
||||||
// Add folders to DB
|
// Add folders to DB
|
||||||
|
|
||||||
@ -109,53 +96,43 @@ module.exports = {
|
|||||||
return {
|
return {
|
||||||
_id: 'f:' + f,
|
_id: 'f:' + f,
|
||||||
name: f
|
name: f
|
||||||
};
|
}
|
||||||
}));
|
}))
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
||||||
// Travel each directory and scan files
|
// Travel each directory and scan files
|
||||||
|
|
||||||
let allFiles = [];
|
let allFiles = []
|
||||||
|
|
||||||
return Promise.map(folderNames, (fldName) => {
|
return Promise.map(folderNames, (fldName) => {
|
||||||
|
let fldPath = path.join(self._uploadsPath, fldName)
|
||||||
let fldPath = path.join(self._uploadsPath, fldName);
|
|
||||||
return fs.readdirAsync(fldPath).then((fList) => {
|
return fs.readdirAsync(fldPath).then((fList) => {
|
||||||
return Promise.map(fList, (f) => {
|
return Promise.map(fList, (f) => {
|
||||||
return upl.processFile(fldName, f).then((mData) => {
|
return upl.processFile(fldName, f).then((mData) => {
|
||||||
if (mData) {
|
if (mData) {
|
||||||
allFiles.push(mData);
|
allFiles.push(mData)
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
}, {concurrency: 3});
|
}, {concurrency: 3})
|
||||||
});
|
})
|
||||||
}, {concurrency: 1}).finally(() => {
|
}, {concurrency: 1}).finally(() => {
|
||||||
|
|
||||||
// Add files to DB
|
// Add files to DB
|
||||||
|
|
||||||
return db.UplFile.remove({}).then(() => {
|
return db.UplFile.remove({}).then(() => {
|
||||||
if (_.isArray(allFiles) && allFiles.length > 0) {
|
if (_.isArray(allFiles) && allFiles.length > 0) {
|
||||||
return db.UplFile.insertMany(allFiles);
|
return db.UplFile.insertMany(allFiles)
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
||||||
// Watch for new changes
|
// Watch for new changes
|
||||||
|
|
||||||
return upl.watch();
|
return upl.watch()
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,13 +142,11 @@ module.exports = {
|
|||||||
* @return {Object} Parsed path (folder and filename)
|
* @return {Object} Parsed path (folder and filename)
|
||||||
*/
|
*/
|
||||||
parseUploadsRelPath (f) {
|
parseUploadsRelPath (f) {
|
||||||
|
let fObj = path.parse(f)
|
||||||
let fObj = path.parse(f);
|
|
||||||
return {
|
return {
|
||||||
folder: fObj.dir,
|
folder: fObj.dir,
|
||||||
filename: fObj.base
|
filename: fObj.base
|
||||||
};
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,25 +157,23 @@ module.exports = {
|
|||||||
* @return {Promise<Object>} Promise of the file metadata
|
* @return {Promise<Object>} Promise of the file metadata
|
||||||
*/
|
*/
|
||||||
processFile (fldName, f) {
|
processFile (fldName, f) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
let fldPath = path.join(self._uploadsPath, fldName)
|
||||||
|
let fPath = path.join(fldPath, f)
|
||||||
let fldPath = path.join(self._uploadsPath, fldName);
|
let fPathObj = path.parse(fPath)
|
||||||
let fPath = path.join(fldPath, f);
|
let fUid = farmhash.fingerprint32(fldName + '/' + f)
|
||||||
let fPathObj = path.parse(fPath);
|
|
||||||
let fUid = farmhash.fingerprint32(fldName + '/' + f);
|
|
||||||
|
|
||||||
return fs.statAsync(fPath).then((s) => {
|
return fs.statAsync(fPath).then((s) => {
|
||||||
|
if (!s.isFile()) { return false }
|
||||||
if(!s.isFile()) { return false; }
|
|
||||||
|
|
||||||
// Get MIME info
|
// Get MIME info
|
||||||
|
|
||||||
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
|
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))
|
||||||
if (_.isNil(mimeInfo)) {
|
if (_.isNil(mimeInfo)) {
|
||||||
mimeInfo = {
|
mimeInfo = {
|
||||||
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
|
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
@ -208,9 +181,8 @@ module.exports = {
|
|||||||
if (s.size < 3145728) { // ignore files larger than 3MB
|
if (s.size < 3145728) { // ignore files larger than 3MB
|
||||||
if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
return self.getImageMetadata(fPath).then((mImgData) => {
|
return self.getImageMetadata(fPath).then((mImgData) => {
|
||||||
|
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))
|
||||||
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
|
let cacheThumbnailPathStr = path.format(cacheThumbnailPath)
|
||||||
let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
|
|
||||||
|
|
||||||
let mData = {
|
let mData = {
|
||||||
_id: fUid,
|
_id: fUid,
|
||||||
@ -221,23 +193,20 @@ module.exports = {
|
|||||||
filename: f,
|
filename: f,
|
||||||
basename: fPathObj.name,
|
basename: fPathObj.name,
|
||||||
filesize: s.size
|
filesize: s.size
|
||||||
};
|
}
|
||||||
|
|
||||||
// Generate thumbnail
|
// Generate thumbnail
|
||||||
|
|
||||||
return fs.statAsync(cacheThumbnailPathStr).then((st) => {
|
return fs.statAsync(cacheThumbnailPathStr).then((st) => {
|
||||||
return st.isFile();
|
return st.isFile()
|
||||||
}).catch((err) => {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return false;
|
return false
|
||||||
}).then((thumbExists) => {
|
}).then((thumbExists) => {
|
||||||
|
|
||||||
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
|
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
|
||||||
return self.generateThumbnail(fPath, cacheThumbnailPathStr);
|
return self.generateThumbnail(fPath, cacheThumbnailPathStr)
|
||||||
}).return(mData);
|
}).return(mData)
|
||||||
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,10 +220,8 @@ module.exports = {
|
|||||||
filename: f,
|
filename: f,
|
||||||
basename: fPathObj.name,
|
basename: fPathObj.name,
|
||||||
filesize: s.size
|
filesize: s.size
|
||||||
};
|
}
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,7 +232,6 @@ module.exports = {
|
|||||||
* @return {Promise<Object>} Promise returning the resized image info
|
* @return {Promise<Object>} Promise returning the resized image info
|
||||||
*/
|
*/
|
||||||
generateThumbnail (sourcePath, destPath) {
|
generateThumbnail (sourcePath, destPath) {
|
||||||
|
|
||||||
return sharp(sourcePath)
|
return sharp(sourcePath)
|
||||||
.withoutEnlargement()
|
.withoutEnlargement()
|
||||||
.resize(150, 150)
|
.resize(150, 150)
|
||||||
@ -273,8 +239,7 @@ module.exports = {
|
|||||||
.embed()
|
.embed()
|
||||||
.flatten()
|
.flatten()
|
||||||
.toFormat('png')
|
.toFormat('png')
|
||||||
.toFile(destPath);
|
.toFile(destPath)
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,9 +249,7 @@ module.exports = {
|
|||||||
* @return {Object} The image metadata.
|
* @return {Object} The image metadata.
|
||||||
*/
|
*/
|
||||||
getImageMetadata (sourcePath) {
|
getImageMetadata (sourcePath) {
|
||||||
|
return sharp(sourcePath).metadata()
|
||||||
return sharp(sourcePath).metadata();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
192
libs/uploads.js
192
libs/uploads.js
@ -1,16 +1,15 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
multer = require('multer'),
|
const request = require('request')
|
||||||
request = require('request'),
|
const url = require('url')
|
||||||
url = require('url'),
|
const farmhash = require('farmhash')
|
||||||
farmhash = require('farmhash'),
|
const _ = require('lodash')
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
|
||||||
const maxDownloadFileSize = 3145728; // 3 MB
|
const maxDownloadFileSize = 3145728 // 3 MB
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads
|
* Uploads
|
||||||
@ -26,12 +25,10 @@ module.exports = {
|
|||||||
* @return {Object} Uploads model instance
|
* @return {Object} Uploads model instance
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
|
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
return this
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
|
||||||
|
|
||||||
return this;
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,7 +37,7 @@ module.exports = {
|
|||||||
* @return {String} The thumbs path.
|
* @return {String} The thumbs path.
|
||||||
*/
|
*/
|
||||||
getThumbsPath () {
|
getThumbsPath () {
|
||||||
return this._uploadsThumbsPath;
|
return this._uploadsThumbsPath
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,8 +47,8 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
getUploadsFolders () {
|
getUploadsFolders () {
|
||||||
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
||||||
return (results) ? _.map(results, 'name') : [{ name: '' }];
|
return (results) ? _.map(results, 'name') : [{ name: '' }]
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,13 +58,12 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
createUploadsFolder (folderName) {
|
createUploadsFolder (folderName) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
folderName = _.kebabCase(_.trim(folderName))
|
||||||
|
|
||||||
folderName = _.kebabCase(_.trim(folderName));
|
|
||||||
|
|
||||||
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
||||||
return Promise.resolve(self.getUploadsFolders());
|
return Promise.resolve(self.getUploadsFolders())
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
||||||
@ -77,11 +73,10 @@ module.exports = {
|
|||||||
name: folderName
|
name: folderName
|
||||||
}, {
|
}, {
|
||||||
upsert: true
|
upsert: true
|
||||||
});
|
})
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return self.getUploadsFolders();
|
return self.getUploadsFolders()
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,11 +86,9 @@ module.exports = {
|
|||||||
* @return {Boolean} True if valid
|
* @return {Boolean} True if valid
|
||||||
*/
|
*/
|
||||||
validateUploadsFolder (folderName) {
|
validateUploadsFolder (folderName) {
|
||||||
|
|
||||||
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
||||||
return (f) ? path.resolve(this._uploadsPath, folderName) : false;
|
return (f) ? path.resolve(this._uploadsPath, folderName) : false
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,7 +101,7 @@ module.exports = {
|
|||||||
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
||||||
// this._uploadsDb.Files.insert(arrFiles);
|
// this._uploadsDb.Files.insert(arrFiles);
|
||||||
}
|
}
|
||||||
return;
|
return
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,12 +112,10 @@ module.exports = {
|
|||||||
* @return {Array<Object>} The files matching the query
|
* @return {Array<Object>} The files matching the query
|
||||||
*/
|
*/
|
||||||
getUploadsFiles (cat, fld) {
|
getUploadsFiles (cat, fld) {
|
||||||
|
|
||||||
return db.UplFile.find({
|
return db.UplFile.find({
|
||||||
category: cat,
|
category: cat,
|
||||||
folder: 'f:' + fld
|
folder: 'f:' + fld
|
||||||
}).sort('filename').exec();
|
}).sort('filename').exec()
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,22 +125,20 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
deleteUploadsFile (uid) {
|
deleteUploadsFile (uid) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
||||||
if (f) {
|
if (f) {
|
||||||
return self.deleteUploadsFileTry(f, 0);
|
return self.deleteUploadsFileTry(f, 0)
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteUploadsFileTry (f, attempt) {
|
deleteUploadsFileTry (f, attempt) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
|
||||||
|
|
||||||
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './';
|
|
||||||
|
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
||||||
@ -157,14 +146,13 @@ module.exports = {
|
|||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
if (err.code === 'EBUSY' && attempt < 5) {
|
if (err.code === 'EBUSY' && attempt < 5) {
|
||||||
return Promise.delay(100).then(() => {
|
return Promise.delay(100).then(() => {
|
||||||
return self.deleteUploadsFileTry(f, attempt + 1);
|
return self.deleteUploadsFileTry(f, attempt + 1)
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.');
|
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,62 +163,52 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
downloadFromUrl (fFolder, fUrl) {
|
downloadFromUrl (fFolder, fUrl) {
|
||||||
|
let fUrlObj = url.parse(fUrl)
|
||||||
let self = this;
|
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
|
||||||
|
let destFolder = _.chain(fFolder).trim().toLower().value()
|
||||||
let fUrlObj = url.parse(fUrl);
|
|
||||||
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'));
|
|
||||||
let destFolder = _.chain(fFolder).trim().toLower().value();
|
|
||||||
|
|
||||||
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
|
|
||||||
if (!destFolderPath) {
|
if (!destFolderPath) {
|
||||||
return Promise.reject(new Error('Invalid Folder'));
|
return Promise.reject(new Error('Invalid Folder'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
||||||
|
let destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
let destFilePath = path.resolve(destFolderPath, destFilename);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let rq = request({
|
let rq = request({
|
||||||
url: fUrl,
|
url: fUrl,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
followRedirect: true,
|
followRedirect: true,
|
||||||
maxRedirects: 5,
|
maxRedirects: 5,
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
});
|
})
|
||||||
|
|
||||||
let destFileStream = fs.createWriteStream(destFilePath);
|
let destFileStream = fs.createWriteStream(destFilePath)
|
||||||
let curFileSize = 0;
|
let curFileSize = 0
|
||||||
|
|
||||||
rq.on('data', (data) => {
|
rq.on('data', (data) => {
|
||||||
curFileSize += data.length;
|
curFileSize += data.length
|
||||||
if (curFileSize > maxDownloadFileSize) {
|
if (curFileSize > maxDownloadFileSize) {
|
||||||
rq.abort();
|
rq.abort()
|
||||||
destFileStream.destroy();
|
destFileStream.destroy()
|
||||||
fs.remove(destFilePath);
|
fs.remove(destFilePath)
|
||||||
reject(new Error('Remote file is too large!'));
|
reject(new Error('Remote file is too large!'))
|
||||||
}
|
}
|
||||||
}).on('error', (err) => {
|
}).on('error', (err) => {
|
||||||
destFileStream.destroy();
|
destFileStream.destroy()
|
||||||
fs.remove(destFilePath);
|
fs.remove(destFilePath)
|
||||||
reject(err);
|
reject(err)
|
||||||
});
|
})
|
||||||
|
|
||||||
destFileStream.on('finish', () => {
|
destFileStream.on('finish', () => {
|
||||||
resolve(true);
|
resolve(true)
|
||||||
});
|
})
|
||||||
|
|
||||||
rq.pipe(destFileStream);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
rq.pipe(destFileStream)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,49 +220,46 @@ module.exports = {
|
|||||||
* @return {Promise} Promise of the operation
|
* @return {Promise} Promise of the operation
|
||||||
*/
|
*/
|
||||||
moveUploadsFile (uid, fld, nFilename) {
|
moveUploadsFile (uid, fld, nFilename) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return db.UplFolder.findById('f:' + fld).then((folder) => {
|
return db.UplFolder.findById('f:' + fld).then((folder) => {
|
||||||
if (folder) {
|
if (folder) {
|
||||||
return db.UplFile.findById(uid).then((originFile) => {
|
return db.UplFile.findById(uid).then((originFile) => {
|
||||||
|
|
||||||
// -> Check if rename is valid
|
// -> Check if rename is valid
|
||||||
|
|
||||||
let nameCheck = null;
|
let nameCheck = null
|
||||||
if (nFilename) {
|
if (nFilename) {
|
||||||
let originFileObj = path.parse(originFile.filename);
|
let originFileObj = path.parse(originFile.filename)
|
||||||
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name);
|
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name)
|
||||||
} else {
|
} else {
|
||||||
nameCheck = Promise.resolve(originFile.filename);
|
nameCheck = Promise.resolve(originFile.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameCheck.then((destFilename) => {
|
return nameCheck.then((destFilename) => {
|
||||||
|
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
|
||||||
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './';
|
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
|
||||||
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename);
|
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
|
||||||
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename);
|
let preMoveOps = []
|
||||||
let preMoveOps = [];
|
|
||||||
|
|
||||||
// -> Check for invalid operations
|
// -> Check for invalid operations
|
||||||
|
|
||||||
if (sourceFilePath === destFilePath) {
|
if (sourceFilePath === destFilePath) {
|
||||||
return Promise.reject(new Error('Invalid Operation!'));
|
return Promise.reject(new Error('Invalid Operation!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Delete DB entry
|
// -> Delete DB entry
|
||||||
|
|
||||||
preMoveOps.push(db.UplFile.findByIdAndRemove(uid));
|
preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
|
||||||
|
|
||||||
// -> Move thumbnail ahead to avoid re-generation
|
// -> Move thumbnail ahead to avoid re-generation
|
||||||
|
|
||||||
if (originFile.category === 'image') {
|
if (originFile.category === 'image') {
|
||||||
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename);
|
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename)
|
||||||
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png');
|
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
|
||||||
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png');
|
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
|
||||||
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath));
|
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
|
||||||
} else {
|
} else {
|
||||||
preMoveOps.push(Promise.resolve(true));
|
preMoveOps.push(Promise.resolve(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Proceed to move actual file
|
// -> Proceed to move actual file
|
||||||
@ -292,17 +267,14 @@ module.exports = {
|
|||||||
return Promise.all(preMoveOps).then(() => {
|
return Promise.all(preMoveOps).then(() => {
|
||||||
return fs.moveAsync(sourceFilePath, destFilePath, {
|
return fs.moveAsync(sourceFilePath, destFilePath, {
|
||||||
clobber: false
|
clobber: false
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Invalid Destination Folder'));
|
return Promise.reject(new Error('Invalid Destination Folder'))
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
const moment = require('moment-timezone')
|
||||||
moment = require('moment-timezone');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication middleware
|
* Authentication middleware
|
||||||
@ -12,29 +11,27 @@ var Promise = require('bluebird'),
|
|||||||
* @return {any} void
|
* @return {any} void
|
||||||
*/
|
*/
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
|
|
||||||
// Is user authenticated ?
|
// Is user authenticated ?
|
||||||
|
|
||||||
if (!req.isAuthenticated()) {
|
if (!req.isAuthenticated()) {
|
||||||
return res.redirect('/login');
|
return res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
|
|
||||||
if (!rights.check(req, 'read')) {
|
if (!rights.check(req, 'read')) {
|
||||||
return res.render('error-forbidden');
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set i18n locale
|
// Set i18n locale
|
||||||
|
|
||||||
req.i18n.changeLanguage(req.user.lang);
|
req.i18n.changeLanguage(req.user.lang)
|
||||||
res.locals.userMoment = moment;
|
res.locals.userMoment = moment
|
||||||
res.locals.userMoment.locale(req.user.lang);
|
res.locals.userMoment.locale(req.user.lang)
|
||||||
|
|
||||||
// Expose user data
|
// Expose user data
|
||||||
|
|
||||||
res.locals.user = req.user;
|
res.locals.user = req.user
|
||||||
|
|
||||||
return next();
|
return next()
|
||||||
|
}
|
||||||
};
|
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
@ -7,22 +9,20 @@
|
|||||||
* @return {any} void
|
* @return {any} void
|
||||||
*/
|
*/
|
||||||
module.exports = function (req, res, next) {
|
module.exports = function (req, res, next) {
|
||||||
|
|
||||||
// -> Disable X-Powered-By
|
// -> Disable X-Powered-By
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by')
|
||||||
|
|
||||||
// -> Disable Frame Embedding
|
// -> Disable Frame Embedding
|
||||||
res.set('X-Frame-Options', 'deny');
|
res.set('X-Frame-Options', 'deny')
|
||||||
|
|
||||||
// -> Re-enable XSS Fitler if disabled
|
// -> Re-enable XSS Fitler if disabled
|
||||||
res.set('X-XSS-Protection', '1; mode=block');
|
res.set('X-XSS-Protection', '1; mode=block')
|
||||||
|
|
||||||
// -> Disable MIME-sniffing
|
// -> Disable MIME-sniffing
|
||||||
res.set('X-Content-Type-Options', 'nosniff');
|
res.set('X-Content-Type-Options', 'nosniff')
|
||||||
|
|
||||||
// -> Disable IE Compatibility Mode
|
// -> Disable IE Compatibility Mode
|
||||||
res.set('X-UA-Compatible', 'IE=edge');
|
res.set('X-UA-Compatible', 'IE=edge')
|
||||||
|
|
||||||
return next();
|
return next()
|
||||||
|
}
|
||||||
};
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BruteForce schema
|
* BruteForce schema
|
||||||
@ -13,6 +13,6 @@ var bruteForceSchema = Mongoose.Schema({
|
|||||||
firstRequest: Date
|
firstRequest: Date
|
||||||
},
|
},
|
||||||
expires: { type: Date, index: { expires: '1d' } }
|
expires: { type: Date, index: { expires: '1d' } }
|
||||||
});
|
})
|
||||||
|
|
||||||
module.exports = Mongoose.model('Bruteforce', bruteForceSchema);
|
module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry schema
|
* Entry schema
|
||||||
@ -33,7 +30,7 @@ var entrySchema = Mongoose.Schema({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: {}
|
timestamps: {}
|
||||||
});
|
})
|
||||||
|
|
||||||
entrySchema.index({
|
entrySchema.index({
|
||||||
_id: 'text',
|
_id: 'text',
|
||||||
@ -48,6 +45,6 @@ entrySchema.index({
|
|||||||
content: 1
|
content: 1
|
||||||
},
|
},
|
||||||
name: 'EntriesTextIndex'
|
name: 'EntriesTextIndex'
|
||||||
});
|
})
|
||||||
|
|
||||||
module.exports = Mongoose.model('Entry', entrySchema);
|
module.exports = Mongoose.model('Entry', entrySchema)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload File schema
|
* Upload File schema
|
||||||
@ -42,9 +39,6 @@ var uplFileSchema = Mongoose.Schema({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
}, { timestamps: {} })
|
||||||
{
|
|
||||||
timestamps: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('UplFile', uplFileSchema);
|
module.exports = Mongoose.model('UplFile', uplFileSchema)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload Folder schema
|
* Upload Folder schema
|
||||||
@ -17,9 +14,6 @@ var uplFolderSchema = Mongoose.Schema({
|
|||||||
index: true
|
index: true
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
}, { timestamps: {} })
|
||||||
{
|
|
||||||
timestamps: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('UplFolder', uplFolderSchema);
|
module.exports = Mongoose.model('UplFolder', uplFolderSchema)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
bcrypt = require('bcryptjs-then'),
|
const bcrypt = require('bcryptjs-then')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Region schema
|
* Region schema
|
||||||
@ -41,21 +41,17 @@ var userSchema = Mongoose.Schema({
|
|||||||
deny: Boolean
|
deny: Boolean
|
||||||
}]
|
}]
|
||||||
|
|
||||||
},
|
}, { timestamps: {} })
|
||||||
{
|
|
||||||
timestamps: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
userSchema.statics.processProfile = (profile) => {
|
userSchema.statics.processProfile = (profile) => {
|
||||||
|
let primaryEmail = ''
|
||||||
let primaryEmail = '';
|
|
||||||
if (_.isArray(profile.emails)) {
|
if (_.isArray(profile.emails)) {
|
||||||
let e = _.find(profile.emails, ['primary', true]);
|
let e = _.find(profile.emails, ['primary', true])
|
||||||
primaryEmail = (e) ? e.value : _.first(profile.emails).value;
|
primaryEmail = (e) ? e.value : _.first(profile.emails).value
|
||||||
} else if (_.isString(profile.email) && profile.email.length > 5) {
|
} else if (_.isString(profile.email) && profile.email.length > 5) {
|
||||||
primaryEmail = profile.email;
|
primaryEmail = profile.email
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error('Invalid User Email'));
|
return Promise.reject(new Error('Invalid User Email'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findOneAndUpdate({
|
return db.User.findOneAndUpdate({
|
||||||
@ -70,19 +66,18 @@ userSchema.statics.processProfile = (profile) => {
|
|||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
}).then((user) => {
|
}).then((user) => {
|
||||||
return (user) ? user : Promise.reject(new Error('User Upsert failed.'));
|
return user || Promise.reject(new Error('User Upsert failed.'))
|
||||||
});
|
})
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
userSchema.statics.hashPassword = (rawPwd) => {
|
userSchema.statics.hashPassword = (rawPwd) => {
|
||||||
return bcrypt.hash(rawPwd);
|
return bcrypt.hash(rawPwd)
|
||||||
};
|
}
|
||||||
|
|
||||||
userSchema.methods.validatePassword = function (rawPwd) {
|
userSchema.methods.validatePassword = function (rawPwd) {
|
||||||
return bcrypt.compare(rawPwd, this.password).then((isValid) => {
|
return bcrypt.compare(rawPwd, this.password).then((isValid) => {
|
||||||
return (isValid) ? true : Promise.reject(new Error('Invalid Login'));
|
return (isValid) ? true : Promise.reject(new Error('Invalid Login'))
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = Mongoose.model('User', userSchema);
|
module.exports = Mongoose.model('User', userSchema)
|
||||||
|
30
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
|
||||||
}
|
}
|
||||||
|
204
server.js
204
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'],
|
||||||
@ -128,90 +130,90 @@ lang
|
|||||||
backend: {
|
backend: {
|
||||||
loadPath: './locales/{{lng}}/{{ns}}.json'
|
loadPath: './locales/{{lng}}/{{ns}}.json'
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// View Engine Setup
|
// View Engine Setup
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use(i18next_mw.handle(lang));
|
app.use(i18nextMw.handle(lang))
|
||||||
app.set('views', path.join(ROOTPATH, 'views'));
|
app.set('views', path.join(ROOTPATH, 'views'))
|
||||||
app.set('view engine', 'pug');
|
app.set('view engine', 'pug')
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json())
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// View accessible data
|
// View accessible data
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.locals._ = require('lodash');
|
app.locals._ = require('lodash')
|
||||||
app.locals.moment = require('moment');
|
app.locals.moment = require('moment')
|
||||||
app.locals.appconfig = appconfig;
|
app.locals.appconfig = appconfig
|
||||||
app.use(mw.flash);
|
app.use(mw.flash)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Controllers
|
// Controllers
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use('/', ctrl.auth);
|
app.use('/', ctrl.auth)
|
||||||
|
|
||||||
app.use('/uploads', mw.auth, ctrl.uploads);
|
app.use('/uploads', mw.auth, ctrl.uploads)
|
||||||
app.use('/admin', mw.auth, ctrl.admin);
|
app.use('/admin', mw.auth, ctrl.admin)
|
||||||
app.use('/', mw.auth, ctrl.pages);
|
app.use('/', mw.auth, ctrl.pages)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Error handling
|
// Error handling
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
var err = new Error('Not Found');
|
var err = new Error('Not Found')
|
||||||
err.status = 404;
|
err.status = 404
|
||||||
next(err);
|
next(err)
|
||||||
});
|
})
|
||||||
|
|
||||||
app.use(function (err, req, res, next) {
|
app.use(function (err, req, res, next) {
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500)
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: IS_DEBUG ? err : {}
|
error: IS_DEBUG ? err : {}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start HTTP server
|
// Start HTTP server
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...');
|
winston.info('[SERVER] Starting HTTP/WS server on port ' + appconfig.port + '...')
|
||||||
|
|
||||||
app.set('port', appconfig.port);
|
app.set('port', appconfig.port)
|
||||||
var server = http.createServer(app);
|
var server = http.createServer(app)
|
||||||
var io = socketio(server);
|
var io = socketio(server)
|
||||||
|
|
||||||
server.listen(appconfig.port);
|
server.listen(appconfig.port)
|
||||||
server.on('error', (error) => {
|
server.on('error', (error) => {
|
||||||
if (error.syscall !== 'listen') {
|
if (error.syscall !== 'listen') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle specific listen errors with friendly messages
|
// handle specific listen errors with friendly messages
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!');
|
console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!')
|
||||||
process.exit(1);
|
process.exit(1)
|
||||||
break;
|
break
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
console.error('Port ' + appconfig.port + ' is already in use!');
|
console.error('Port ' + appconfig.port + ' is already in use!')
|
||||||
process.exit(1);
|
process.exit(1)
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
server.on('listening', () => {
|
server.on('listening', () => {
|
||||||
winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]');
|
winston.info('[SERVER] HTTP/WS server started successfully! [RUNNING]')
|
||||||
});
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// WebSocket
|
// WebSocket
|
||||||
@ -224,21 +226,21 @@ io.use(passportSocketIo.authorize({
|
|||||||
passport,
|
passport,
|
||||||
cookieParser,
|
cookieParser,
|
||||||
success: (data, accept) => {
|
success: (data, accept) => {
|
||||||
accept();
|
accept()
|
||||||
},
|
},
|
||||||
fail: (data, message, error, accept) => {
|
fail: (data, message, error, accept) => {
|
||||||
return accept(new Error(message));
|
return accept(new Error(message))
|
||||||
}
|
}
|
||||||
}));
|
}))
|
||||||
|
|
||||||
io.on('connection', ctrl.ws);
|
io.on('connection', ctrl.ws)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start child processes
|
// Start child processes
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.bgAgent = fork('agent.js');
|
global.bgAgent = fork('agent.js')
|
||||||
|
|
||||||
process.on('exit', (code) => {
|
process.on('exit', (code) => {
|
||||||
bgAgent.disconnect();
|
bgAgent.disconnect()
|
||||||
});
|
})
|
||||||
|
3
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
|
|
||||||
.hero-body
|
|
||||||
.container
|
.container
|
||||||
a(href='/'): img(src='/favicons/android-icon-96x96.png')
|
a(href='/'): img(src='/favicons/android-icon-96x96.png')
|
||||||
h1.title(style={ 'margin-top': '30px'})= message
|
h1= message
|
||||||
h2.subtitle(style={ 'margin-bottom': '50px'}) Oops, something went wrong
|
h2 Oops, something went wrong
|
||||||
a.button.is-warning.is-inverted(href='/') Go Home
|
a.button.is-amber.is-inverted.is-featured(href='/') Go Home
|
||||||
|
|
||||||
if error.stack
|
if error.stack
|
||||||
section.section
|
|
||||||
.container.is-fluid
|
|
||||||
.content
|
|
||||||
h3 Detailed debug trail:
|
h3 Detailed debug trail:
|
||||||
pre: code #{error.stack}
|
pre: code #{error.stack}
|
@ -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…
x
Reference in New Issue
Block a user