mirror of https://github.com/requarks/wiki
parent
a508b2a7f4
commit
414dc386d6
@ -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
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,59 +1,57 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
jQuery( document ).ready(function( $ ) {
|
|
||||||
|
|
||||||
|
jQuery(document).ready(function ($) {
|
||||||
// ====================================
|
// ====================================
|
||||||
// Scroll
|
// Scroll
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
$('a').smoothScroll({
|
$('a').smoothScroll({
|
||||||
speed: 400,
|
speed: 400,
|
||||||
offset: -70
|
offset: -70
|
||||||
});
|
})
|
||||||
|
|
||||||
var sticky = new Sticky('.stickyscroll');
|
var sticky = new Sticky('.stickyscroll')
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Notifications
|
// Notifications
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
$(window).bind('beforeunload', () => {
|
$(window).bind('beforeunload', () => {
|
||||||
$('#notifload').addClass('active');
|
$('#notifload').addClass('active')
|
||||||
});
|
})
|
||||||
$(document).ajaxSend(() => {
|
$(document).ajaxSend(() => {
|
||||||
$('#notifload').addClass('active');
|
$('#notifload').addClass('active')
|
||||||
}).ajaxComplete(() => {
|
}).ajaxComplete(() => {
|
||||||
$('#notifload').removeClass('active');
|
$('#notifload').removeClass('active')
|
||||||
});
|
})
|
||||||
|
|
||||||
var alerts = new Alerts();
|
var alerts = new Alerts()
|
||||||
if(alertsData) {
|
if (alertsData) {
|
||||||
_.forEach(alertsData, (alertRow) => {
|
_.forEach(alertsData, (alertRow) => {
|
||||||
alerts.push(alertRow);
|
alerts.push(alertRow)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Establish WebSocket connection
|
// Establish WebSocket connection
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
var socket = io(window.location.origin);
|
var socket = io(window.location.origin)
|
||||||
|
|
||||||
//=include components/search.js
|
// =include components/search.js
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Pages logic
|
// Pages logic
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
//=include pages/view.js
|
// =include pages/view.js
|
||||||
//=include pages/create.js
|
// =include pages/create.js
|
||||||
//=include pages/edit.js
|
// =include pages/edit.js
|
||||||
//=include pages/source.js
|
// =include pages/source.js
|
||||||
//=include pages/admin.js
|
// =include pages/admin.js
|
||||||
|
})
|
||||||
});
|
|
||||||
|
|
||||||
//=include helpers/form.js
|
// =include helpers/form.js
|
||||||
//=include helpers/pages.js
|
// =include helpers/pages.js
|
||||||
|
|
||||||
//=include components/alerts.js
|
// =include components/alerts.js
|
||||||
|
@ -1,78 +1,74 @@
|
|||||||
|
|
||||||
let modelist = ace.require("ace/ext/modelist");
|
let modelist = ace.require('ace/ext/modelist')
|
||||||
let codeEditor = null;
|
let codeEditor = null
|
||||||
|
|
||||||
// ACE - Mode Loader
|
// ACE - Mode Loader
|
||||||
|
|
||||||
let modelistLoaded = [];
|
let modelistLoaded = []
|
||||||
let loadAceMode = (m) => {
|
let loadAceMode = (m) => {
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: '/js/ace/mode-' + m + '.js',
|
url: '/js/ace/mode-' + m + '.js',
|
||||||
dataType: "script",
|
dataType: 'script',
|
||||||
cache: true,
|
cache: true,
|
||||||
beforeSend: () => {
|
beforeSend: () => {
|
||||||
if(_.includes(modelistLoaded, m)) {
|
if (_.includes(modelistLoaded, m)) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success: () => {
|
success: () => {
|
||||||
modelistLoaded.push(m);
|
modelistLoaded.push(m)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// Vue Code Block instance
|
// Vue Code Block instance
|
||||||
|
|
||||||
let vueCodeBlock = new Vue({
|
let vueCodeBlock = new Vue({
|
||||||
el: '#modal-editor-codeblock',
|
el: '#modal-editor-codeblock',
|
||||||
data: {
|
data: {
|
||||||
modes: modelist.modesByName,
|
modes: modelist.modesByName,
|
||||||
modeSelected: 'text',
|
modeSelected: 'text',
|
||||||
initContent: ''
|
initContent: ''
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modeSelected: (val, oldVal) => {
|
modeSelected: (val, oldVal) => {
|
||||||
loadAceMode(val).done(() => {
|
loadAceMode(val).done(() => {
|
||||||
ace.require("ace/mode/" + val);
|
ace.require('ace/mode/' + val)
|
||||||
codeEditor.getSession().setMode("ace/mode/" + val);
|
codeEditor.getSession().setMode('ace/mode/' + val)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
|
$('#modal-editor-codeblock').addClass('is-active')
|
||||||
|
|
||||||
$('#modal-editor-codeblock').addClass('is-active');
|
_.delay(() => {
|
||||||
|
codeEditor = ace.edit('codeblock-editor')
|
||||||
|
codeEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
|
codeEditor.getSession().setMode('ace/mode/' + vueCodeBlock.modeSelected)
|
||||||
|
codeEditor.setOption('fontSize', '14px')
|
||||||
|
codeEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
|
codeEditor.setOption('wrap', true)
|
||||||
|
|
||||||
_.delay(() => {
|
codeEditor.setValue(vueCodeBlock.initContent)
|
||||||
codeEditor = ace.edit("codeblock-editor");
|
|
||||||
codeEditor.setTheme("ace/theme/tomorrow_night");
|
|
||||||
codeEditor.getSession().setMode("ace/mode/" + vueCodeBlock.modeSelected);
|
|
||||||
codeEditor.setOption('fontSize', '14px');
|
|
||||||
codeEditor.setOption('hScrollBarAlwaysVisible', false);
|
|
||||||
codeEditor.setOption('wrap', true);
|
|
||||||
|
|
||||||
codeEditor.setValue(vueCodeBlock.initContent);
|
codeEditor.focus()
|
||||||
|
codeEditor.renderer.updateFull()
|
||||||
|
}, 300)
|
||||||
|
},
|
||||||
|
cancel: (ev) => {
|
||||||
|
mdeModalOpenState = false
|
||||||
|
$('#modal-editor-codeblock').removeClass('is-active')
|
||||||
|
vueCodeBlock.initContent = ''
|
||||||
|
},
|
||||||
|
insertCode: (ev) => {
|
||||||
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
|
mde.codemirror.execCommand('singleSelection')
|
||||||
|
}
|
||||||
|
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n'
|
||||||
|
|
||||||
codeEditor.focus();
|
mde.codemirror.doc.replaceSelection(codeBlockText)
|
||||||
codeEditor.renderer.updateFull();
|
vueCodeBlock.cancel()
|
||||||
}, 300);
|
}
|
||||||
|
}
|
||||||
},
|
})
|
||||||
cancel: (ev) => {
|
|
||||||
mdeModalOpenState = false;
|
|
||||||
$('#modal-editor-codeblock').removeClass('is-active');
|
|
||||||
vueCodeBlock.initContent = '';
|
|
||||||
},
|
|
||||||
insertCode: (ev) => {
|
|
||||||
|
|
||||||
if(mde.codemirror.doc.somethingSelected()) {
|
|
||||||
mde.codemirror.execCommand('singleSelection');
|
|
||||||
}
|
|
||||||
let codeBlockText = '\n```' + vueCodeBlock.modeSelected + '\n' + codeEditor.getValue() + '\n```\n';
|
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(codeBlockText);
|
|
||||||
vueCodeBlock.cancel();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
@ -1,365 +1,351 @@
|
|||||||
|
|
||||||
let vueFile = new Vue({
|
let vueFile = new Vue({
|
||||||
el: '#modal-editor-file',
|
el: '#modal-editor-file',
|
||||||
data: {
|
data: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoadingText: '',
|
isLoadingText: '',
|
||||||
newFolderName: '',
|
newFolderName: '',
|
||||||
newFolderShow: false,
|
newFolderShow: false,
|
||||||
newFolderError: false,
|
newFolderError: false,
|
||||||
folders: [],
|
folders: [],
|
||||||
currentFolder: '',
|
currentFolder: '',
|
||||||
currentFile: '',
|
currentFile: '',
|
||||||
files: [],
|
files: [],
|
||||||
uploadSucceeded: false,
|
uploadSucceeded: false,
|
||||||
postUploadChecks: 0,
|
postUploadChecks: 0,
|
||||||
renameFileShow: false,
|
renameFileShow: false,
|
||||||
renameFileId: '',
|
renameFileId: '',
|
||||||
renameFileFilename: '',
|
renameFileFilename: '',
|
||||||
deleteFileShow: false,
|
deleteFileShow: false,
|
||||||
deleteFileId: '',
|
deleteFileId: '',
|
||||||
deleteFileFilename: ''
|
deleteFileFilename: ''
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
open: () => {
|
open: () => {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true
|
||||||
$('#modal-editor-file').addClass('is-active');
|
$('#modal-editor-file').addClass('is-active')
|
||||||
vueFile.refreshFolders();
|
vueFile.refreshFolders()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-file').removeClass('is-active');
|
$('#modal-editor-file').removeClass('is-active')
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// INSERT LINK TO FILE
|
// INSERT LINK TO FILE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFile: (fileId) => {
|
selectFile: (fileId) => {
|
||||||
vueFile.currentFile = fileId;
|
vueFile.currentFile = fileId
|
||||||
},
|
},
|
||||||
insertFileLink: (ev) => {
|
insertFileLink: (ev) => {
|
||||||
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
|
mde.codemirror.execCommand('singleSelection')
|
||||||
|
}
|
||||||
|
|
||||||
if(mde.codemirror.doc.somethingSelected()) {
|
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile])
|
||||||
mde.codemirror.execCommand('singleSelection');
|
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename
|
||||||
}
|
selFile.titleGuess = _.startCase(selFile.basename)
|
||||||
|
|
||||||
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile]);
|
let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")'
|
||||||
selFile.normalizedPath = (selFile.folder === 'f:') ? selFile.filename : selFile.folder.slice(2) + '/' + selFile.filename;
|
|
||||||
selFile.titleGuess = _.startCase(selFile.basename);
|
|
||||||
|
|
||||||
let fileText = '[' + selFile.titleGuess + '](/uploads/' + selFile.normalizedPath + ' "' + selFile.titleGuess + '")';
|
mde.codemirror.doc.replaceSelection(fileText)
|
||||||
|
vueFile.cancel()
|
||||||
mde.codemirror.doc.replaceSelection(fileText);
|
},
|
||||||
vueFile.cancel();
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// NEW FOLDER
|
// NEW FOLDER
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
newFolder: (ev) => {
|
newFolder: (ev) => {
|
||||||
vueFile.newFolderName = '';
|
vueFile.newFolderName = ''
|
||||||
vueFile.newFolderError = false;
|
vueFile.newFolderError = false
|
||||||
vueFile.newFolderShow = true;
|
vueFile.newFolderShow = true
|
||||||
_.delay(() => { $('#txt-editor-file-newfoldername').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-file-newfoldername').focus() }, 400)
|
||||||
},
|
},
|
||||||
newFolderDiscard: (ev) => {
|
newFolderDiscard: (ev) => {
|
||||||
vueFile.newFolderShow = false;
|
vueFile.newFolderShow = false
|
||||||
},
|
},
|
||||||
newFolderCreate: (ev) => {
|
newFolderCreate: (ev) => {
|
||||||
|
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName))
|
||||||
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
|
|
||||||
|
if (_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
||||||
if(_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
|
vueFile.newFolderError = true
|
||||||
vueFile.newFolderError = true;
|
return
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
vueFile.newFolderDiscard()
|
||||||
vueFile.newFolderDiscard();
|
vueFile.isLoadingText = 'Creating new folder...'
|
||||||
vueFile.isLoadingText = 'Creating new folder...';
|
vueFile.isLoading = true
|
||||||
vueFile.isLoading = true;
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
Vue.nextTick(() => {
|
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
|
vueFile.folders = data
|
||||||
vueFile.folders = data;
|
vueFile.currentFolder = vueFile.newFolderName
|
||||||
vueFile.currentFolder = vueFile.newFolderName;
|
vueFile.files = []
|
||||||
vueFile.files = [];
|
vueFile.isLoading = false
|
||||||
vueFile.isLoading = false;
|
})
|
||||||
});
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// RENAME FILE
|
// RENAME FILE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameFile: () => {
|
renameFile: () => {
|
||||||
|
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ])
|
||||||
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]);
|
vueFile.renameFileFilename = c.basename || ''
|
||||||
vueFile.renameFileFilename = c.basename || '';
|
vueFile.renameFileShow = true
|
||||||
vueFile.renameFileShow = true;
|
_.delay(() => {
|
||||||
_.delay(() => {
|
$('#txt-editor-renamefile').focus()
|
||||||
$('#txt-editor-renamefile').focus();
|
_.defer(() => { $('#txt-editor-file-rename').select() })
|
||||||
_.defer(() => { $('#txt-editor-file-rename').select(); });
|
}, 400)
|
||||||
}, 400);
|
},
|
||||||
},
|
renameFileDiscard: () => {
|
||||||
renameFileDiscard: () => {
|
vueFile.renameFileShow = false
|
||||||
vueFile.renameFileShow = false;
|
},
|
||||||
},
|
renameFileGo: () => {
|
||||||
renameFileGo: () => {
|
vueFile.renameFileDiscard()
|
||||||
|
vueFile.isLoadingText = 'Renaming file...'
|
||||||
vueFile.renameFileDiscard();
|
vueFile.isLoading = true
|
||||||
vueFile.isLoadingText = 'Renaming file...';
|
|
||||||
vueFile.isLoading = true;
|
Vue.nextTick(() => {
|
||||||
|
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
||||||
Vue.nextTick(() => {
|
if (data.ok) {
|
||||||
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
|
vueFile.waitChangeComplete(vueFile.files.length, false)
|
||||||
if(data.ok) {
|
} else {
|
||||||
vueFile.waitChangeComplete(vueFile.files.length, false);
|
vueFile.isLoading = false
|
||||||
} else {
|
alerts.pushError('Rename error', data.msg)
|
||||||
vueFile.isLoading = false;
|
}
|
||||||
alerts.pushError('Rename error', data.msg);
|
})
|
||||||
}
|
})
|
||||||
});
|
},
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// MOVE FILE
|
// MOVE FILE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
moveFile: (uid, fld) => {
|
moveFile: (uid, fld) => {
|
||||||
vueFile.isLoadingText = 'Moving file...';
|
vueFile.isLoadingText = 'Moving file...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
||||||
if(data.ok) {
|
if (data.ok) {
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
} else {
|
} else {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// DELETE FILE
|
// DELETE FILE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
deleteFileWarn: (show) => {
|
deleteFileWarn: (show) => {
|
||||||
if(show) {
|
if (show) {
|
||||||
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ]);
|
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ])
|
||||||
vueFile.deleteFileFilename = c.filename || 'this file';
|
vueFile.deleteFileFilename = c.filename || 'this file'
|
||||||
}
|
}
|
||||||
vueFile.deleteFileShow = show;
|
vueFile.deleteFileShow = show
|
||||||
},
|
},
|
||||||
deleteFileGo: () => {
|
deleteFileGo: () => {
|
||||||
vueFile.deleteFileWarn(false);
|
vueFile.deleteFileWarn(false)
|
||||||
vueFile.isLoadingText = 'Deleting file...';
|
vueFile.isLoadingText = 'Deleting file...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
|
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// LOAD FROM REMOTE
|
// LOAD FROM REMOTE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFolder: (fldName) => {
|
selectFolder: (fldName) => {
|
||||||
vueFile.currentFolder = fldName;
|
vueFile.currentFolder = fldName
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders: () => {
|
refreshFolders: () => {
|
||||||
vueFile.isLoadingText = 'Fetching folders list...';
|
vueFile.isLoadingText = 'Fetching folders list...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
vueFile.currentFolder = '';
|
vueFile.currentFolder = ''
|
||||||
vueFile.currentImage = '';
|
vueFile.currentImage = ''
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFolders', { }, (data) => {
|
socket.emit('uploadsGetFolders', { }, (data) => {
|
||||||
vueFile.folders = data;
|
vueFile.folders = data
|
||||||
vueFile.loadFiles();
|
vueFile.loadFiles()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
loadFiles: (silent) => {
|
loadFiles: (silent) => {
|
||||||
if(!silent) {
|
if (!silent) {
|
||||||
vueFile.isLoadingText = 'Fetching files...';
|
vueFile.isLoadingText = 'Fetching files...'
|
||||||
vueFile.isLoading = true;
|
vueFile.isLoading = true
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
|
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
|
||||||
vueFile.files = data;
|
vueFile.files = data
|
||||||
if(!silent) {
|
if (!silent) {
|
||||||
vueFile.isLoading = false;
|
vueFile.isLoading = false
|
||||||
}
|
}
|
||||||
vueFile.attachContextMenus();
|
vueFile.attachContextMenus()
|
||||||
resolve(true);
|
resolve(true)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
waitChangeComplete: (oldAmount, expectChange) => {
|
waitChangeComplete: (oldAmount, expectChange) => {
|
||||||
|
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
|
||||||
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
|
|
||||||
|
vueFile.postUploadChecks++
|
||||||
vueFile.postUploadChecks++;
|
vueFile.isLoadingText = 'Processing...'
|
||||||
vueFile.isLoadingText = 'Processing...';
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
Vue.nextTick(() => {
|
vueFile.loadFiles(true).then(() => {
|
||||||
vueFile.loadFiles(true).then(() => {
|
if ((vueFile.files.length !== oldAmount) === expectChange) {
|
||||||
if((vueFile.files.length !== oldAmount) === expectChange) {
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.postUploadChecks = 0;
|
vueFile.isLoading = false
|
||||||
vueFile.isLoading = false;
|
} else if (vueFile.postUploadChecks > 5) {
|
||||||
} else if(vueFile.postUploadChecks > 5) {
|
vueFile.postUploadChecks = 0
|
||||||
vueFile.postUploadChecks = 0;
|
vueFile.isLoading = false
|
||||||
vueFile.isLoading = false;
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
} else {
|
||||||
} else {
|
_.delay(() => {
|
||||||
_.delay(() => {
|
vueFile.waitChangeComplete(oldAmount, expectChange)
|
||||||
vueFile.waitChangeComplete(oldAmount, expectChange);
|
}, 1500)
|
||||||
}, 1500);
|
}
|
||||||
}
|
})
|
||||||
});
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// IMAGE CONTEXT MENU
|
// IMAGE CONTEXT MENU
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
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: {
|
folder: vueFile.currentFolder
|
||||||
folder: vueFile.currentFolder
|
},
|
||||||
},
|
limit: 20,
|
||||||
limit: 20,
|
expect: 'json',
|
||||||
expect: 'json',
|
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])
|
||||||
|
if (failedUpls.length) {
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
_.forEach(failedUpls, (u) => {
|
||||||
if(failedUpls.length) {
|
alerts.pushError('Upload error', u.msg)
|
||||||
_.forEach(failedUpls, (u) => {
|
})
|
||||||
alerts.pushError('Upload error', u.msg);
|
if (failedUpls.length < data.results.length) {
|
||||||
});
|
alerts.push({
|
||||||
if(failedUpls.length < data.results.length) {
|
title: 'Some uploads succeeded',
|
||||||
alerts.push({
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
title: 'Some uploads succeeded',
|
})
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
vueFile.uploadSucceeded = true
|
||||||
});
|
}
|
||||||
vueFile.uploadSucceeded = true;
|
} else {
|
||||||
}
|
vueFile.uploadSucceeded = true
|
||||||
} else {
|
}
|
||||||
vueFile.uploadSucceeded = true;
|
} else {
|
||||||
}
|
alerts.pushError('Upload error', data.msg)
|
||||||
|
}
|
||||||
} else {
|
},
|
||||||
alerts.pushError('Upload error', data.msg);
|
|
||||||
}
|
error: (error) => {
|
||||||
},
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
|
},
|
||||||
error: (error) => {
|
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
finish: () => {
|
||||||
},
|
if (vueFile.uploadSucceeded) {
|
||||||
|
vueFile.waitChangeComplete(curFileAmount, true)
|
||||||
finish: () => {
|
} else {
|
||||||
if(vueFile.uploadSucceeded) {
|
vueFile.isLoading = false
|
||||||
vueFile.waitChangeComplete(curFileAmount, true);
|
}
|
||||||
} else {
|
}
|
||||||
vueFile.isLoading = false;
|
|
||||||
}
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,412 +1,396 @@
|
|||||||
|
|
||||||
let vueImage = new Vue({
|
let vueImage = new Vue({
|
||||||
el: '#modal-editor-image',
|
el: '#modal-editor-image',
|
||||||
data: {
|
data: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoadingText: '',
|
isLoadingText: '',
|
||||||
newFolderName: '',
|
newFolderName: '',
|
||||||
newFolderShow: false,
|
newFolderShow: false,
|
||||||
newFolderError: false,
|
newFolderError: false,
|
||||||
fetchFromUrlURL: '',
|
fetchFromUrlURL: '',
|
||||||
fetchFromUrlShow: false,
|
fetchFromUrlShow: false,
|
||||||
folders: [],
|
folders: [],
|
||||||
currentFolder: '',
|
currentFolder: '',
|
||||||
currentImage: '',
|
currentImage: '',
|
||||||
currentAlign: 'left',
|
currentAlign: 'left',
|
||||||
images: [],
|
images: [],
|
||||||
uploadSucceeded: false,
|
uploadSucceeded: false,
|
||||||
postUploadChecks: 0,
|
postUploadChecks: 0,
|
||||||
renameImageShow: false,
|
renameImageShow: false,
|
||||||
renameImageId: '',
|
renameImageId: '',
|
||||||
renameImageFilename: '',
|
renameImageFilename: '',
|
||||||
deleteImageShow: false,
|
deleteImageShow: false,
|
||||||
deleteImageId: '',
|
deleteImageId: '',
|
||||||
deleteImageFilename: ''
|
deleteImageFilename: ''
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
open: () => {
|
open: () => {
|
||||||
mdeModalOpenState = true;
|
mdeModalOpenState = true
|
||||||
$('#modal-editor-image').addClass('is-active');
|
$('#modal-editor-image').addClass('is-active')
|
||||||
vueImage.refreshFolders();
|
vueImage.refreshFolders()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-image').removeClass('is-active');
|
$('#modal-editor-image').removeClass('is-active')
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// INSERT IMAGE
|
// INSERT IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectImage: (imageId) => {
|
selectImage: (imageId) => {
|
||||||
vueImage.currentImage = imageId;
|
vueImage.currentImage = imageId
|
||||||
},
|
},
|
||||||
insertImage: (ev) => {
|
insertImage: (ev) => {
|
||||||
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
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();
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// NEW FOLDER
|
// NEW FOLDER
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
newFolder: (ev) => {
|
newFolder: (ev) => {
|
||||||
vueImage.newFolderName = '';
|
vueImage.newFolderName = ''
|
||||||
vueImage.newFolderError = false;
|
vueImage.newFolderError = false
|
||||||
vueImage.newFolderShow = true;
|
vueImage.newFolderShow = true
|
||||||
_.delay(() => { $('#txt-editor-image-newfoldername').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-image-newfoldername').focus() }, 400)
|
||||||
},
|
},
|
||||||
newFolderDiscard: (ev) => {
|
newFolderDiscard: (ev) => {
|
||||||
vueImage.newFolderShow = false;
|
vueImage.newFolderShow = false
|
||||||
},
|
},
|
||||||
newFolderCreate: (ev) => {
|
newFolderCreate: (ev) => {
|
||||||
|
let regFolderName = new RegExp('^[a-z0-9][a-z0-9\-]*[a-z0-9]$')
|
||||||
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName))
|
||||||
vueImage.newFolderName = _.kebabCase(_.trim(vueImage.newFolderName));
|
|
||||||
|
if (_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
||||||
if(_.isEmpty(vueImage.newFolderName) || !regFolderName.test(vueImage.newFolderName)) {
|
vueImage.newFolderError = true
|
||||||
vueImage.newFolderError = true;
|
return
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
vueImage.newFolderDiscard()
|
||||||
vueImage.newFolderDiscard();
|
vueImage.isLoadingText = 'Creating new folder...'
|
||||||
vueImage.isLoadingText = 'Creating new folder...';
|
vueImage.isLoading = true
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
Vue.nextTick(() => {
|
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
||||||
socket.emit('uploadsCreateFolder', { foldername: vueImage.newFolderName }, (data) => {
|
vueImage.folders = data
|
||||||
vueImage.folders = data;
|
vueImage.currentFolder = vueImage.newFolderName
|
||||||
vueImage.currentFolder = vueImage.newFolderName;
|
vueImage.images = []
|
||||||
vueImage.images = [];
|
vueImage.isLoading = false
|
||||||
vueImage.isLoading = false;
|
})
|
||||||
});
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// FETCH FROM URL
|
// FETCH FROM URL
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
fetchFromUrl: (ev) => {
|
fetchFromUrl: (ev) => {
|
||||||
vueImage.fetchFromUrlURL = '';
|
vueImage.fetchFromUrlURL = ''
|
||||||
vueImage.fetchFromUrlShow = true;
|
vueImage.fetchFromUrlShow = true
|
||||||
_.delay(() => { $('#txt-editor-image-fetchurl').focus(); }, 400);
|
_.delay(() => { $('#txt-editor-image-fetchurl').focus() }, 400)
|
||||||
},
|
},
|
||||||
fetchFromUrlDiscard: (ev) => {
|
fetchFromUrlDiscard: (ev) => {
|
||||||
vueImage.fetchFromUrlShow = false;
|
vueImage.fetchFromUrlShow = false
|
||||||
},
|
},
|
||||||
fetchFromUrlGo: (ev) => {
|
fetchFromUrlGo: (ev) => {
|
||||||
|
vueImage.fetchFromUrlDiscard()
|
||||||
vueImage.fetchFromUrlDiscard();
|
vueImage.isLoadingText = 'Fetching image...'
|
||||||
vueImage.isLoadingText = 'Fetching image...';
|
vueImage.isLoading = true
|
||||||
vueImage.isLoading = true;
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
Vue.nextTick(() => {
|
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
||||||
socket.emit('uploadsFetchFileFromURL', { folder: vueImage.currentFolder, fetchUrl: vueImage.fetchFromUrlURL }, (data) => {
|
if (data.ok) {
|
||||||
if(data.ok) {
|
vueImage.waitChangeComplete(vueImage.images.length, true)
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, true);
|
} else {
|
||||||
} else {
|
vueImage.isLoading = false
|
||||||
vueImage.isLoading = false;
|
alerts.pushError('Upload error', data.msg)
|
||||||
alerts.pushError('Upload error', data.msg);
|
}
|
||||||
}
|
})
|
||||||
});
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// RENAME IMAGE
|
// RENAME IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
renameImage: () => {
|
renameImage: () => {
|
||||||
|
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ])
|
||||||
let c = _.find(vueImage.images, ['_id', vueImage.renameImageId ]);
|
vueImage.renameImageFilename = c.basename || ''
|
||||||
vueImage.renameImageFilename = c.basename || '';
|
vueImage.renameImageShow = true
|
||||||
vueImage.renameImageShow = true;
|
_.delay(() => {
|
||||||
_.delay(() => {
|
$('#txt-editor-image-rename').focus()
|
||||||
$('#txt-editor-image-rename').focus();
|
_.defer(() => { $('#txt-editor-image-rename').select() })
|
||||||
_.defer(() => { $('#txt-editor-image-rename').select(); });
|
}, 400)
|
||||||
}, 400);
|
},
|
||||||
},
|
renameImageDiscard: () => {
|
||||||
renameImageDiscard: () => {
|
vueImage.renameImageShow = false
|
||||||
vueImage.renameImageShow = false;
|
},
|
||||||
},
|
renameImageGo: () => {
|
||||||
renameImageGo: () => {
|
vueImage.renameImageDiscard()
|
||||||
|
vueImage.isLoadingText = 'Renaming image...'
|
||||||
vueImage.renameImageDiscard();
|
vueImage.isLoading = true
|
||||||
vueImage.isLoadingText = 'Renaming image...';
|
|
||||||
vueImage.isLoading = true;
|
Vue.nextTick(() => {
|
||||||
|
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
||||||
Vue.nextTick(() => {
|
if (data.ok) {
|
||||||
socket.emit('uploadsRenameFile', { uid: vueImage.renameImageId, folder: vueImage.currentFolder, filename: vueImage.renameImageFilename }, (data) => {
|
vueImage.waitChangeComplete(vueImage.images.length, false)
|
||||||
if(data.ok) {
|
} else {
|
||||||
vueImage.waitChangeComplete(vueImage.images.length, false);
|
vueImage.isLoading = false
|
||||||
} else {
|
alerts.pushError('Rename error', data.msg)
|
||||||
vueImage.isLoading = false;
|
}
|
||||||
alerts.pushError('Rename error', data.msg);
|
})
|
||||||
}
|
})
|
||||||
});
|
},
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// MOVE IMAGE
|
// MOVE IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
moveImage: (uid, fld) => {
|
moveImage: (uid, fld) => {
|
||||||
vueImage.isLoadingText = 'Moving image...';
|
vueImage.isLoadingText = 'Moving image...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
|
||||||
if(data.ok) {
|
if (data.ok) {
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
} else {
|
} else {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
alerts.pushError('Rename error', data.msg);
|
alerts.pushError('Rename error', data.msg)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// DELETE IMAGE
|
// DELETE IMAGE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
deleteImageWarn: (show) => {
|
deleteImageWarn: (show) => {
|
||||||
if(show) {
|
if (show) {
|
||||||
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ]);
|
let c = _.find(vueImage.images, ['_id', vueImage.deleteImageId ])
|
||||||
vueImage.deleteImageFilename = c.filename || 'this image';
|
vueImage.deleteImageFilename = c.filename || 'this image'
|
||||||
}
|
}
|
||||||
vueImage.deleteImageShow = show;
|
vueImage.deleteImageShow = show
|
||||||
},
|
},
|
||||||
deleteImageGo: () => {
|
deleteImageGo: () => {
|
||||||
vueImage.deleteImageWarn(false);
|
vueImage.deleteImageWarn(false)
|
||||||
vueImage.isLoadingText = 'Deleting image...';
|
vueImage.isLoadingText = 'Deleting image...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
|
socket.emit('uploadsDeleteFile', { uid: vueImage.deleteImageId }, (data) => {
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// LOAD FROM REMOTE
|
// LOAD FROM REMOTE
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
selectFolder: (fldName) => {
|
selectFolder: (fldName) => {
|
||||||
vueImage.currentFolder = fldName;
|
vueImage.currentFolder = fldName
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshFolders: () => {
|
refreshFolders: () => {
|
||||||
vueImage.isLoadingText = 'Fetching folders list...';
|
vueImage.isLoadingText = 'Fetching folders list...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
vueImage.currentFolder = '';
|
vueImage.currentFolder = ''
|
||||||
vueImage.currentImage = '';
|
vueImage.currentImage = ''
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetFolders', { }, (data) => {
|
socket.emit('uploadsGetFolders', { }, (data) => {
|
||||||
vueImage.folders = data;
|
vueImage.folders = data
|
||||||
vueImage.loadImages();
|
vueImage.loadImages()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
loadImages: (silent) => {
|
loadImages: (silent) => {
|
||||||
if(!silent) {
|
if (!silent) {
|
||||||
vueImage.isLoadingText = 'Fetching images...';
|
vueImage.isLoadingText = 'Fetching images...'
|
||||||
vueImage.isLoading = true;
|
vueImage.isLoading = true
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
|
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
|
||||||
vueImage.images = data;
|
vueImage.images = data
|
||||||
if(!silent) {
|
if (!silent) {
|
||||||
vueImage.isLoading = false;
|
vueImage.isLoading = false
|
||||||
}
|
}
|
||||||
vueImage.attachContextMenus();
|
vueImage.attachContextMenus()
|
||||||
resolve(true);
|
resolve(true)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
waitChangeComplete: (oldAmount, expectChange) => {
|
waitChangeComplete: (oldAmount, expectChange) => {
|
||||||
|
expectChange = (_.isBoolean(expectChange)) ? expectChange : true
|
||||||
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
|
|
||||||
|
vueImage.postUploadChecks++
|
||||||
vueImage.postUploadChecks++;
|
vueImage.isLoadingText = 'Processing...'
|
||||||
vueImage.isLoadingText = 'Processing...';
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
Vue.nextTick(() => {
|
vueImage.loadImages(true).then(() => {
|
||||||
vueImage.loadImages(true).then(() => {
|
if ((vueImage.images.length !== oldAmount) === expectChange) {
|
||||||
if((vueImage.images.length !== oldAmount) === expectChange) {
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.postUploadChecks = 0;
|
vueImage.isLoading = false
|
||||||
vueImage.isLoading = false;
|
} else if (vueImage.postUploadChecks > 5) {
|
||||||
} else if(vueImage.postUploadChecks > 5) {
|
vueImage.postUploadChecks = 0
|
||||||
vueImage.postUploadChecks = 0;
|
vueImage.isLoading = false
|
||||||
vueImage.isLoading = false;
|
alerts.pushError('Unable to fetch updated listing', 'Try again later')
|
||||||
alerts.pushError('Unable to fetch updated listing', 'Try again later');
|
} else {
|
||||||
} else {
|
_.delay(() => {
|
||||||
_.delay(() => {
|
vueImage.waitChangeComplete(oldAmount, expectChange)
|
||||||
vueImage.waitChangeComplete(oldAmount, expectChange);
|
}, 1500)
|
||||||
}, 1500);
|
}
|
||||||
}
|
})
|
||||||
});
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// IMAGE CONTEXT MENU
|
// IMAGE CONTEXT MENU
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
|
|
||||||
attachContextMenus: () => {
|
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: {
|
folder: vueImage.currentFolder
|
||||||
folder: vueImage.currentFolder
|
},
|
||||||
},
|
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])
|
||||||
|
if (failedUpls.length) {
|
||||||
let failedUpls = _.filter(data.results, ['ok', false]);
|
_.forEach(failedUpls, (u) => {
|
||||||
if(failedUpls.length) {
|
alerts.pushError('Upload error', u.msg)
|
||||||
_.forEach(failedUpls, (u) => {
|
})
|
||||||
alerts.pushError('Upload error', u.msg);
|
if (failedUpls.length < data.results.length) {
|
||||||
});
|
alerts.push({
|
||||||
if(failedUpls.length < data.results.length) {
|
title: 'Some uploads succeeded',
|
||||||
alerts.push({
|
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
||||||
title: 'Some uploads succeeded',
|
})
|
||||||
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
|
vueImage.uploadSucceeded = true
|
||||||
});
|
}
|
||||||
vueImage.uploadSucceeded = true;
|
} else {
|
||||||
}
|
vueImage.uploadSucceeded = true
|
||||||
} else {
|
}
|
||||||
vueImage.uploadSucceeded = true;
|
} else {
|
||||||
}
|
alerts.pushError('Upload error', data.msg)
|
||||||
|
}
|
||||||
} else {
|
},
|
||||||
alerts.pushError('Upload error', data.msg);
|
|
||||||
}
|
error: (error) => {
|
||||||
},
|
alerts.pushError(error.message, this.upload.file.name)
|
||||||
|
},
|
||||||
error: (error) => {
|
|
||||||
alerts.pushError(error.message, this.upload.file.name);
|
finish: () => {
|
||||||
},
|
if (vueImage.uploadSucceeded) {
|
||||||
|
vueImage.waitChangeComplete(curImageAmount, true)
|
||||||
finish: () => {
|
} else {
|
||||||
if(vueImage.uploadSucceeded) {
|
vueImage.isLoading = false
|
||||||
vueImage.waitChangeComplete(curImageAmount, true);
|
}
|
||||||
} else {
|
}
|
||||||
vueImage.isLoading = false;
|
|
||||||
}
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,49 +1,47 @@
|
|||||||
|
|
||||||
const videoRules = {
|
const videoRules = {
|
||||||
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
|
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/, 'i'),
|
||||||
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
||||||
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
|
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[\-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
|
||||||
};
|
}
|
||||||
|
|
||||||
// Vue Video instance
|
// Vue Video instance
|
||||||
|
|
||||||
let vueVideo = new Vue({
|
let vueVideo = new Vue({
|
||||||
el: '#modal-editor-video',
|
el: '#modal-editor-video',
|
||||||
data: {
|
data: {
|
||||||
link: ''
|
link: ''
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open: (ev) => {
|
open: (ev) => {
|
||||||
$('#modal-editor-video').addClass('is-active');
|
$('#modal-editor-video').addClass('is-active')
|
||||||
$('#modal-editor-video input').focus();
|
$('#modal-editor-video input').focus()
|
||||||
},
|
},
|
||||||
cancel: (ev) => {
|
cancel: (ev) => {
|
||||||
mdeModalOpenState = false;
|
mdeModalOpenState = false
|
||||||
$('#modal-editor-video').removeClass('is-active');
|
$('#modal-editor-video').removeClass('is-active')
|
||||||
vueVideo.link = '';
|
vueVideo.link = ''
|
||||||
},
|
},
|
||||||
insertVideo: (ev) => {
|
insertVideo: (ev) => {
|
||||||
|
if (mde.codemirror.doc.somethingSelected()) {
|
||||||
if(mde.codemirror.doc.somethingSelected()) {
|
mde.codemirror.execCommand('singleSelection')
|
||||||
mde.codemirror.execCommand('singleSelection');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Guess video type
|
// Guess video type
|
||||||
|
|
||||||
let videoType = _.findKey(videoRules, (vr) => {
|
let videoType = _.findKey(videoRules, (vr) => {
|
||||||
return vr.test(vueVideo.link);
|
return vr.test(vueVideo.link)
|
||||||
});
|
})
|
||||||
if(_.isNil(videoType)) {
|
if (_.isNil(videoType)) {
|
||||||
videoType = 'video';
|
videoType = 'video'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert video tag
|
// Insert video tag
|
||||||
|
|
||||||
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n';
|
let videoText = '[video](' + vueVideo.link + '){.' + videoType + '}\n'
|
||||||
|
|
||||||
mde.codemirror.doc.replaceSelection(videoText);
|
mde.codemirror.doc.replaceSelection(videoText)
|
||||||
vueVideo.cancel();
|
vueVideo.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
|
||||||
|
@ -1,84 +1,81 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
if($('#search-input').length) {
|
if ($('#search-input').length) {
|
||||||
|
$('#search-input').focus()
|
||||||
|
|
||||||
$('#search-input').focus();
|
$('.searchresults').css('display', 'block')
|
||||||
|
|
||||||
$('.searchresults').css('display', 'block');
|
var vueHeader = new Vue({
|
||||||
|
el: '#header-container',
|
||||||
var vueHeader = new Vue({
|
data: {
|
||||||
el: '#header-container',
|
searchq: '',
|
||||||
data: {
|
searchres: [],
|
||||||
searchq: '',
|
searchsuggest: [],
|
||||||
searchres: [],
|
searchload: 0,
|
||||||
searchsuggest: [],
|
searchactive: false,
|
||||||
searchload: 0,
|
searchmoveidx: 0,
|
||||||
searchactive: false,
|
searchmovekey: '',
|
||||||
searchmoveidx: 0,
|
searchmovearr: []
|
||||||
searchmovekey: '',
|
},
|
||||||
searchmovearr: []
|
watch: {
|
||||||
},
|
searchq: (val, oldVal) => {
|
||||||
watch: {
|
vueHeader.searchmoveidx = 0
|
||||||
searchq: (val, oldVal) => {
|
if (val.length >= 3) {
|
||||||
vueHeader.searchmoveidx = 0;
|
vueHeader.searchactive = true
|
||||||
if(val.length >= 3) {
|
vueHeader.searchload++
|
||||||
vueHeader.searchactive = true;
|
socket.emit('search', { terms: val }, (data) => {
|
||||||
vueHeader.searchload++;
|
vueHeader.searchres = data.match
|
||||||
socket.emit('search', { terms: val }, (data) => {
|
vueHeader.searchsuggest = data.suggest
|
||||||
vueHeader.searchres = data.match;
|
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest)
|
||||||
vueHeader.searchsuggest = data.suggest;
|
if (vueHeader.searchload > 0) { vueHeader.searchload-- }
|
||||||
vueHeader.searchmovearr = _.concat([], vueHeader.searchres, vueHeader.searchsuggest);
|
})
|
||||||
if(vueHeader.searchload > 0) { vueHeader.searchload--; }
|
} else {
|
||||||
});
|
vueHeader.searchactive = false
|
||||||
} else {
|
vueHeader.searchres = []
|
||||||
vueHeader.searchactive = false;
|
vueHeader.searchsuggest = []
|
||||||
vueHeader.searchres = [];
|
vueHeader.searchmovearr = []
|
||||||
vueHeader.searchsuggest = [];
|
vueHeader.searchload = 0
|
||||||
vueHeader.searchmovearr = [];
|
}
|
||||||
vueHeader.searchload = 0;
|
},
|
||||||
}
|
searchmoveidx: (val, oldVal) => {
|
||||||
},
|
if (val > 0) {
|
||||||
searchmoveidx: (val, oldVal) => {
|
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
|
||||||
if(val > 0) {
|
|
||||||
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
|
|
||||||
'res.' + vueHeader.searchmovearr[val - 1]._id :
|
'res.' + vueHeader.searchmovearr[val - 1]._id :
|
||||||
'sug.' + vueHeader.searchmovearr[val - 1];
|
'sug.' + vueHeader.searchmovearr[val - 1]
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchmovekey = '';
|
vueHeader.searchmovekey = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
useSuggestion: (sug) => {
|
useSuggestion: (sug) => {
|
||||||
vueHeader.searchq = sug;
|
vueHeader.searchq = sug
|
||||||
},
|
},
|
||||||
closeSearch: () => {
|
closeSearch: () => {
|
||||||
vueHeader.searchq = '';
|
vueHeader.searchq = ''
|
||||||
},
|
},
|
||||||
moveSelectSearch: () => {
|
moveSelectSearch: () => {
|
||||||
if(vueHeader.searchmoveidx < 1) { return; }
|
if (vueHeader.searchmoveidx < 1) { return }
|
||||||
let i = vueHeader.searchmoveidx - 1;
|
let i = vueHeader.searchmoveidx - 1
|
||||||
|
|
||||||
if(vueHeader.searchmovearr[i]) {
|
|
||||||
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
|
|
||||||
} else {
|
|
||||||
vueHeader.searchq = vueHeader.searchmovearr[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
moveDownSearch: () => {
|
|
||||||
if(vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
|
||||||
vueHeader.searchmoveidx++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
moveUpSearch: () => {
|
|
||||||
if(vueHeader.searchmoveidx > 0) {
|
|
||||||
vueHeader.searchmoveidx--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('main').on('click', vueHeader.closeSearch);
|
if (vueHeader.searchmovearr[i]) {
|
||||||
|
window.location.assign('/' + vueHeader.searchmovearr[i]._id)
|
||||||
|
} else {
|
||||||
|
vueHeader.searchq = vueHeader.searchmovearr[i]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveDownSearch: () => {
|
||||||
|
if (vueHeader.searchmoveidx < vueHeader.searchmovearr.length) {
|
||||||
|
vueHeader.searchmoveidx++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveUpSearch: () => {
|
||||||
|
if (vueHeader.searchmoveidx > 0) {
|
||||||
|
vueHeader.searchmoveidx--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$('main').on('click', vueHeader.closeSearch)
|
||||||
}
|
}
|
@ -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();
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
|
|
||||||
//-> Create New Document
|
// -> Create New Document
|
||||||
|
|
||||||
let suggestedCreatePath = currentBasePath + '/new-page';
|
let suggestedCreatePath = currentBasePath + '/new-page'
|
||||||
|
|
||||||
$('.btn-create-prompt').on('click', (ev) => {
|
$('.btn-create-prompt').on('click', (ev) => {
|
||||||
$('#txt-create-prompt').val(suggestedCreatePath);
|
$('#txt-create-prompt').val(suggestedCreatePath)
|
||||||
$('#modal-create-prompt').toggleClass('is-active');
|
$('#modal-create-prompt').toggleClass('is-active')
|
||||||
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length);
|
setInputSelection($('#txt-create-prompt').get(0), currentBasePath.length + 1, suggestedCreatePath.length)
|
||||||
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden');
|
$('#txt-create-prompt').removeClass('is-danger').next().addClass('is-hidden')
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#txt-create-prompt').on('keypress', (ev) => {
|
$('#txt-create-prompt').on('keypress', (ev) => {
|
||||||
if(ev.which === 13) {
|
if (ev.which === 13) {
|
||||||
$('.btn-create-go').trigger('click');
|
$('.btn-create-go').trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-create-go').on('click', (ev) => {
|
$('.btn-create-go').on('click', (ev) => {
|
||||||
|
let newDocPath = makeSafePath($('#txt-create-prompt').val())
|
||||||
let newDocPath = makeSafePath($('#txt-create-prompt').val());
|
if (_.isEmpty(newDocPath)) {
|
||||||
if(_.isEmpty(newDocPath)) {
|
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden')
|
||||||
$('#txt-create-prompt').addClass('is-danger').next().removeClass('is-hidden');
|
} else {
|
||||||
} else {
|
$('#txt-create-prompt').parent().addClass('is-loading')
|
||||||
$('#txt-create-prompt').parent().addClass('is-loading');
|
window.location.assign('/create/' + newDocPath)
|
||||||
window.location.assign('/create/' + newDocPath);
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,49 +1,46 @@
|
|||||||
|
|
||||||
//-> Move Existing Document
|
// -> Move Existing Document
|
||||||
|
|
||||||
if(currentBasePath !== '') {
|
if (currentBasePath !== '') {
|
||||||
$('.btn-move-prompt').removeClass('is-hidden');
|
$('.btn-move-prompt').removeClass('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1;
|
let moveInitialDocument = _.lastIndexOf(currentBasePath, '/') + 1
|
||||||
|
|
||||||
$('.btn-move-prompt').on('click', (ev) => {
|
$('.btn-move-prompt').on('click', (ev) => {
|
||||||
$('#txt-move-prompt').val(currentBasePath);
|
$('#txt-move-prompt').val(currentBasePath)
|
||||||
$('#modal-move-prompt').toggleClass('is-active');
|
$('#modal-move-prompt').toggleClass('is-active')
|
||||||
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length);
|
setInputSelection($('#txt-move-prompt').get(0), moveInitialDocument, currentBasePath.length)
|
||||||
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden');
|
$('#txt-move-prompt').removeClass('is-danger').next().addClass('is-hidden')
|
||||||
});
|
})
|
||||||
|
|
||||||
$('#txt-move-prompt').on('keypress', (ev) => {
|
$('#txt-move-prompt').on('keypress', (ev) => {
|
||||||
if(ev.which === 13) {
|
if (ev.which === 13) {
|
||||||
$('.btn-move-go').trigger('click');
|
$('.btn-move-go').trigger('click')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.btn-move-go').on('click', (ev) => {
|
$('.btn-move-go').on('click', (ev) => {
|
||||||
|
let newDocPath = makeSafePath($('#txt-move-prompt').val())
|
||||||
let newDocPath = makeSafePath($('#txt-move-prompt').val());
|
if (_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
||||||
if(_.isEmpty(newDocPath) || newDocPath === currentBasePath || newDocPath === 'home') {
|
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden')
|
||||||
$('#txt-move-prompt').addClass('is-danger').next().removeClass('is-hidden');
|
} else {
|
||||||
} else {
|
$('#txt-move-prompt').parent().addClass('is-loading')
|
||||||
$('#txt-move-prompt').parent().addClass('is-loading');
|
|
||||||
|
$.ajax(window.location.href, {
|
||||||
$.ajax(window.location.href, {
|
data: {
|
||||||
data: {
|
move: newDocPath
|
||||||
move: newDocPath
|
},
|
||||||
},
|
dataType: 'json',
|
||||||
dataType: 'json',
|
method: 'PUT'
|
||||||
method: 'PUT'
|
}).then((rData, rStatus, rXHR) => {
|
||||||
}).then((rData, rStatus, rXHR) => {
|
if (rData.ok) {
|
||||||
if(rData.ok) {
|
window.location.assign('/' + newDocPath)
|
||||||
window.location.assign('/' + newDocPath);
|
} else {
|
||||||
} else {
|
alerts.pushError('Something went wrong', rData.error)
|
||||||
alerts.pushError('Something went wrong', rData.error);
|
}
|
||||||
}
|
}, (rXHR, rStatus, err) => {
|
||||||
}, (rXHR, rStatus, err) => {
|
alerts.pushError('Something went wrong', 'Save operation failed.')
|
||||||
alerts.pushError('Something went wrong', 'Save operation failed.');
|
})
|
||||||
});
|
}
|
||||||
|
})
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -1,102 +1,96 @@
|
|||||||
|
|
||||||
if($('#page-type-admin-profile').length) {
|
if ($('#page-type-admin-profile').length) {
|
||||||
|
let vueProfile = new Vue({
|
||||||
|
el: '#page-type-admin-profile',
|
||||||
|
data: {
|
||||||
|
password: '********',
|
||||||
|
passwordVerify: '********',
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveUser: (ev) => {
|
||||||
|
if (vueProfile.password !== vueProfile.passwordVerify) {
|
||||||
|
alerts.pushError('Error', "Passwords don't match!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$.post(window.location.href, {
|
||||||
|
password: vueProfile.password,
|
||||||
|
name: vueProfile.name
|
||||||
|
}).done((resp) => {
|
||||||
|
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
|
||||||
|
}).fail((jqXHR, txtStatus, resp) => {
|
||||||
|
alerts.pushError('Error', resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.name = usrDataName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if ($('#page-type-admin-users').length) {
|
||||||
|
|
||||||
let vueProfile = new Vue({
|
// =include ../modals/admin-users-create.js
|
||||||
el: '#page-type-admin-profile',
|
|
||||||
data: {
|
|
||||||
password: '********',
|
|
||||||
passwordVerify: '********',
|
|
||||||
name: ''
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
saveUser: (ev) => {
|
|
||||||
if(vueProfile.password !== vueProfile.passwordVerify) {
|
|
||||||
alerts.pushError('Error', "Passwords don't match!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.post(window.location.href, {
|
|
||||||
password: vueProfile.password,
|
|
||||||
name: vueProfile.name
|
|
||||||
}).done((resp) => {
|
|
||||||
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
|
|
||||||
}).fail((jqXHR, txtStatus, resp) => {
|
|
||||||
alerts.pushError('Error', resp);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function() {
|
|
||||||
this.name = usrDataName;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if($('#page-type-admin-users').length) {
|
} else if ($('#page-type-admin-users-edit').length) {
|
||||||
|
let vueEditUser = new Vue({
|
||||||
|
el: '#page-type-admin-users-edit',
|
||||||
|
data: {
|
||||||
|
id: '',
|
||||||
|
email: '',
|
||||||
|
password: '********',
|
||||||
|
name: '',
|
||||||
|
rights: [],
|
||||||
|
roleoverride: 'none'
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addRightsRow: (ev) => {
|
||||||
|
vueEditUser.rights.push({
|
||||||
|
role: 'write',
|
||||||
|
path: '/',
|
||||||
|
exact: false,
|
||||||
|
deny: false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeRightsRow: (idx) => {
|
||||||
|
_.pullAt(vueEditUser.rights, idx)
|
||||||
|
vueEditUser.$forceUpdate()
|
||||||
|
},
|
||||||
|
saveUser: (ev) => {
|
||||||
|
let formattedRights = _.cloneDeep(vueEditUser.rights)
|
||||||
|
switch (vueEditUser.roleoverride) {
|
||||||
|
case 'admin':
|
||||||
|
formattedRights.push({
|
||||||
|
role: 'admin',
|
||||||
|
path: '/',
|
||||||
|
exact: false,
|
||||||
|
deny: false
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
$.post(window.location.href, {
|
||||||
|
password: vueEditUser.password,
|
||||||
|
name: vueEditUser.name,
|
||||||
|
rights: JSON.stringify(formattedRights)
|
||||||
|
}).done((resp) => {
|
||||||
|
alerts.pushSuccess('Saved successfully', 'Changes have been applied.')
|
||||||
|
}).fail((jqXHR, txtStatus, resp) => {
|
||||||
|
alerts.pushError('Error', resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.id = usrData._id
|
||||||
|
this.email = usrData.email
|
||||||
|
this.name = usrData.name
|
||||||
|
|
||||||
//=include ../modals/admin-users-create.js
|
if (_.find(usrData.rights, { role: 'admin' })) {
|
||||||
|
this.rights = _.reject(usrData.rights, ['role', 'admin'])
|
||||||
} else if($('#page-type-admin-users-edit').length) {
|
this.roleoverride = 'admin'
|
||||||
|
} else {
|
||||||
let vueEditUser = new Vue({
|
this.rights = usrData.rights
|
||||||
el: '#page-type-admin-users-edit',
|
}
|
||||||
data: {
|
}
|
||||||
id: '',
|
})
|
||||||
email: '',
|
|
||||||
password: '********',
|
|
||||||
name: '',
|
|
||||||
rights: [],
|
|
||||||
roleoverride: 'none'
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addRightsRow: (ev) => {
|
|
||||||
vueEditUser.rights.push({
|
|
||||||
role: 'write',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeRightsRow: (idx) => {
|
|
||||||
_.pullAt(vueEditUser.rights, idx)
|
|
||||||
vueEditUser.$forceUpdate()
|
|
||||||
},
|
|
||||||
saveUser: (ev) => {
|
|
||||||
let formattedRights = _.cloneDeep(vueEditUser.rights)
|
|
||||||
switch(vueEditUser.roleoverride) {
|
|
||||||
case 'admin':
|
|
||||||
formattedRights.push({
|
|
||||||
role: 'admin',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: false
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$.post(window.location.href, {
|
|
||||||
password: vueEditUser.password,
|
|
||||||
name: vueEditUser.name,
|
|
||||||
rights: JSON.stringify(formattedRights)
|
|
||||||
}).done((resp) => {
|
|
||||||
alerts.pushSuccess('Saved successfully', 'Changes have been applied.');
|
|
||||||
}).fail((jqXHR, txtStatus, resp) => {
|
|
||||||
alerts.pushError('Error', resp);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function() {
|
|
||||||
|
|
||||||
this.id = usrData._id;
|
|
||||||
this.email = usrData.email;
|
|
||||||
this.name = usrData.name;
|
|
||||||
|
|
||||||
if(_.find(usrData.rights, { role: 'admin' })) {
|
|
||||||
this.rights = _.reject(usrData.rights, ['role', 'admin']);
|
|
||||||
this.roleoverride = 'admin';
|
|
||||||
} else {
|
|
||||||
this.rights = usrData.rights;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../modals/admin-users-delete.js
|
|
||||||
|
|
||||||
|
// =include ../modals/admin-users-delete.js
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if($('#page-type-create').length) {
|
if ($('#page-type-create').length) {
|
||||||
|
let pageEntryPath = $('#page-type-create').data('entrypath')
|
||||||
|
|
||||||
let pageEntryPath = $('#page-type-create').data('entrypath');
|
// -> Discard
|
||||||
|
|
||||||
//-> Discard
|
$('.btn-create-discard').on('click', (ev) => {
|
||||||
|
$('#modal-create-discard').toggleClass('is-active')
|
||||||
$('.btn-create-discard').on('click', (ev) => {
|
})
|
||||||
$('#modal-create-discard').toggleClass('is-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
|
// =include ../components/editor.js
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
if($('#page-type-edit').length) {
|
if ($('#page-type-edit').length) {
|
||||||
|
let pageEntryPath = $('#page-type-edit').data('entrypath')
|
||||||
|
|
||||||
let pageEntryPath = $('#page-type-edit').data('entrypath');
|
// -> Discard
|
||||||
|
|
||||||
//-> Discard
|
$('.btn-edit-discard').on('click', (ev) => {
|
||||||
|
$('#modal-edit-discard').toggleClass('is-active')
|
||||||
$('.btn-edit-discard').on('click', (ev) => {
|
})
|
||||||
$('#modal-edit-discard').toggleClass('is-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
//=include ../components/editor.js
|
|
||||||
|
|
||||||
|
// =include ../components/editor.js
|
||||||
}
|
}
|
@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
if($('#page-type-source').length) {
|
if ($('#page-type-source').length) {
|
||||||
|
var scEditor = ace.edit('source-display')
|
||||||
|
scEditor.setTheme('ace/theme/tomorrow_night')
|
||||||
|
scEditor.getSession().setMode('ace/mode/markdown')
|
||||||
|
scEditor.setOption('fontSize', '14px')
|
||||||
|
scEditor.setOption('hScrollBarAlwaysVisible', false)
|
||||||
|
scEditor.setOption('wrap', true)
|
||||||
|
scEditor.setReadOnly(true)
|
||||||
|
scEditor.renderer.updateFull()
|
||||||
|
|
||||||
var scEditor = ace.edit("source-display");
|
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : ''
|
||||||
scEditor.setTheme("ace/theme/tomorrow_night");
|
|
||||||
scEditor.getSession().setMode("ace/mode/markdown");
|
|
||||||
scEditor.setOption('fontSize', '14px');
|
|
||||||
scEditor.setOption('hScrollBarAlwaysVisible', false);
|
|
||||||
scEditor.setOption('wrap', true);
|
|
||||||
scEditor.setReadOnly(true);
|
|
||||||
scEditor.renderer.updateFull();
|
|
||||||
|
|
||||||
let currentBasePath = ($('#page-type-source').data('entrypath') !== 'home') ? $('#page-type-source').data('entrypath') : '';
|
|
||||||
|
|
||||||
//=include ../modals/create.js
|
|
||||||
//=include ../modals/move.js
|
|
||||||
|
|
||||||
|
// =include ../modals/create.js
|
||||||
|
// =include ../modals/move.js
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
|
|
||||||
if($('#page-type-view').length) {
|
if ($('#page-type-view').length) {
|
||||||
|
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : ''
|
||||||
let currentBasePath = ($('#page-type-view').data('entrypath') !== 'home') ? $('#page-type-view').data('entrypath') : '';
|
|
||||||
|
|
||||||
//=include ../modals/create.js
|
|
||||||
//=include ../modals/move.js
|
|
||||||
|
|
||||||
|
// =include ../modals/create.js
|
||||||
|
// =include ../modals/move.js
|
||||||
}
|
}
|
@ -1,162 +1,146 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express')
|
||||||
var router = express.Router();
|
var router = express.Router()
|
||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird')
|
||||||
const validator = require('validator');
|
const validator = require('validator')
|
||||||
const _ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin
|
* Admin
|
||||||
*/
|
*/
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.redirect('/admin/profile');
|
res.redirect('/admin/profile')
|
||||||
});
|
})
|
||||||
|
|
||||||
router.get('/profile', (req, res) => {
|
router.get('/profile', (req, res) => {
|
||||||
|
if (res.locals.isGuest) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(res.locals.isGuest) {
|
res.render('pages/admin/profile', { adminTab: 'profile' })
|
||||||
return res.render('error-forbidden');
|
})
|
||||||
}
|
|
||||||
|
|
||||||
res.render('pages/admin/profile', { adminTab: 'profile' });
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/profile', (req, res) => {
|
router.post('/profile', (req, res) => {
|
||||||
|
if (res.locals.isGuest) {
|
||||||
if(res.locals.isGuest) {
|
return res.render('error-forbidden')
|
||||||
return res.render('error-forbidden');
|
}
|
||||||
}
|
|
||||||
|
return db.User.findById(req.user.id).then((usr) => {
|
||||||
return db.User.findById(req.user.id).then((usr) => {
|
usr.name = _.trim(req.body.name)
|
||||||
usr.name = _.trim(req.body.name);
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
if(usr.provider === 'local' && req.body.password !== '********') {
|
let nPwd = _.trim(req.body.password)
|
||||||
let nPwd = _.trim(req.body.password);
|
if (nPwd.length < 6) {
|
||||||
if(nPwd.length < 6) {
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
} else {
|
||||||
} else {
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
usr.password = pwd
|
||||||
usr.password = pwd;
|
return usr.save()
|
||||||
return usr.save();
|
})
|
||||||
});
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
return usr.save()
|
||||||
return usr.save();
|
}
|
||||||
}
|
}).then(() => {
|
||||||
}).then(() => {
|
return res.json({ msg: 'OK' })
|
||||||
return res.json({ msg: 'OK' });
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
res.status(400).json({ msg: err.message })
|
||||||
res.status(400).json({ msg: err.message });
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/stats', (req, res) => {
|
router.get('/stats', (req, res) => {
|
||||||
|
if (res.locals.isGuest) {
|
||||||
if(res.locals.isGuest) {
|
return res.render('error-forbidden')
|
||||||
return res.render('error-forbidden');
|
}
|
||||||
}
|
|
||||||
|
Promise.all([
|
||||||
Promise.all([
|
db.Entry.count(),
|
||||||
db.Entry.count(),
|
db.UplFile.count(),
|
||||||
db.UplFile.count(),
|
db.User.count()
|
||||||
db.User.count()
|
]).spread((totalEntries, totalUploads, totalUsers) => {
|
||||||
]).spread((totalEntries, totalUploads, totalUsers) => {
|
return res.render('pages/admin/stats', {
|
||||||
return res.render('pages/admin/stats', {
|
totalEntries, totalUploads, totalUsers, adminTab: 'stats'
|
||||||
totalEntries, totalUploads, totalUsers,
|
}) || true
|
||||||
adminTab: 'stats'
|
}).catch((err) => {
|
||||||
}) || true;
|
throw err
|
||||||
}).catch((err) => {
|
})
|
||||||
throw err;
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users', (req, res) => {
|
router.get('/users', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
if(!res.locals.rights.manage) {
|
return res.render('error-forbidden')
|
||||||
return res.render('error-forbidden');
|
}
|
||||||
}
|
|
||||||
|
db.User.find({})
|
||||||
db.User.find({})
|
.select('-password -rights')
|
||||||
.select('-password -rights')
|
.sort('name email')
|
||||||
.sort('name email')
|
.exec().then((usrs) => {
|
||||||
.exec().then((usrs) => {
|
res.render('pages/admin/users', { adminTab: 'users', usrs })
|
||||||
res.render('pages/admin/users', { adminTab: 'users', usrs });
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users/:id', (req, res) => {
|
router.get('/users/:id', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
if(!res.locals.rights.manage) {
|
return res.render('error-forbidden')
|
||||||
return res.render('error-forbidden');
|
}
|
||||||
}
|
|
||||||
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
if(!validator.isMongoId(req.params.id)) {
|
return res.render('error-forbidden')
|
||||||
return res.render('error-forbidden');
|
}
|
||||||
}
|
|
||||||
|
db.User.findById(req.params.id)
|
||||||
db.User.findById(req.params.id)
|
.select('-password -providerId')
|
||||||
.select('-password -providerId')
|
.exec().then((usr) => {
|
||||||
.exec().then((usr) => {
|
let usrOpts = {
|
||||||
|
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
||||||
let usrOpts = {
|
canChangeName: (usr.email !== 'guest'),
|
||||||
canChangeEmail: (usr.email !== 'guest' && usr.provider === 'local' && usr.email !== req.app.locals.appconfig.admin),
|
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
||||||
canChangeName: (usr.email !== 'guest'),
|
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
||||||
canChangePassword: (usr.email !== 'guest' && usr.provider === 'local'),
|
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
||||||
canChangeRole: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin)),
|
}
|
||||||
canBeDeleted: (usr.email !== 'guest' && !(usr.provider === 'local' && usr.email === req.app.locals.appconfig.admin))
|
|
||||||
};
|
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts })
|
||||||
|
})
|
||||||
res.render('pages/admin/users-edit', { adminTab: 'users', usr, usrOpts });
|
})
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/users/:id', (req, res) => {
|
router.post('/users/:id', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
if(!res.locals.rights.manage) {
|
return res.status(401).json({ msg: 'Unauthorized' })
|
||||||
return res.status(401).json({ msg: 'Unauthorized' });
|
}
|
||||||
}
|
|
||||||
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
if(!validator.isMongoId(req.params.id)) {
|
return res.status(400).json({ msg: 'Invalid User ID' })
|
||||||
return res.status(400).json({ msg: 'Invalid User ID' });
|
}
|
||||||
}
|
|
||||||
|
return db.User.findById(req.params.id).then((usr) => {
|
||||||
return db.User.findById(req.params.id).then((usr) => {
|
usr.name = _.trim(req.body.name)
|
||||||
usr.name = _.trim(req.body.name);
|
usr.rights = JSON.parse(req.body.rights)
|
||||||
usr.rights = JSON.parse(req.body.rights);
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
if(usr.provider === 'local' && req.body.password !== '********') {
|
let nPwd = _.trim(req.body.password)
|
||||||
let nPwd = _.trim(req.body.password);
|
if (nPwd.length < 6) {
|
||||||
if(nPwd.length < 6) {
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
} else {
|
||||||
} else {
|
return db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
usr.password = pwd
|
||||||
usr.password = pwd;
|
return usr.save()
|
||||||
return usr.save();
|
})
|
||||||
});
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
return usr.save()
|
||||||
return usr.save();
|
}
|
||||||
}
|
}).then(() => {
|
||||||
}).then(() => {
|
return res.json({ msg: 'OK' })
|
||||||
return res.json({ msg: 'OK' });
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
res.status(400).json({ msg: err.message })
|
||||||
res.status(400).json({ msg: err.message });
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/settings', (req, res) => {
|
router.get('/settings', (req, res) => {
|
||||||
|
if (!res.locals.rights.manage) {
|
||||||
|
return res.render('error-forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
if(!res.locals.rights.manage) {
|
res.render('pages/admin/settings', { adminTab: 'settings' })
|
||||||
return res.render('error-forbidden');
|
})
|
||||||
}
|
|
||||||
|
|
||||||
res.render('pages/admin/settings', { adminTab: 'settings' });
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router
|
||||||
|
@ -1,80 +1,80 @@
|
|||||||
var express = require('express');
|
'use strict'
|
||||||
var router = express.Router();
|
|
||||||
var passport = require('passport');
|
const express = require('express')
|
||||||
var ExpressBrute = require('express-brute');
|
const router = express.Router()
|
||||||
var ExpressBruteMongooseStore = require('express-brute-mongoose');
|
const passport = require('passport')
|
||||||
var moment = require('moment');
|
const ExpressBrute = require('express-brute')
|
||||||
|
const ExpressBruteMongooseStore = require('express-brute-mongoose')
|
||||||
|
const moment = require('moment')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Express-Brute
|
* Setup Express-Brute
|
||||||
*/
|
*/
|
||||||
var EBstore = new ExpressBruteMongooseStore(db.Bruteforce);
|
const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
|
||||||
var bruteforce = new ExpressBrute(EBstore, {
|
const bruteforce = new ExpressBrute(EBstore, {
|
||||||
freeRetries: 5,
|
freeRetries: 5,
|
||||||
minWait: 60 * 1000,
|
minWait: 60 * 1000,
|
||||||
maxWait: 5 * 60 * 1000,
|
maxWait: 5 * 60 * 1000,
|
||||||
refreshTimeoutOnRequest: false,
|
refreshTimeoutOnRequest: false,
|
||||||
failCallback(req, res, next, nextValidRequestDate) {
|
failCallback (req, res, next, nextValidRequestDate) {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
class: 'error',
|
class: 'error',
|
||||||
title: 'Too many attempts!',
|
title: 'Too many attempts!',
|
||||||
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
|
message: "You've made too many failed attempts in a short period of time, please try again " + moment(nextValidRequestDate).fromNow() + '.',
|
||||||
iconClass: 'fa-times'
|
iconClass: 'fa-times'
|
||||||
});
|
})
|
||||||
res.redirect('/login');
|
res.redirect('/login')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login form
|
* Login form
|
||||||
*/
|
*/
|
||||||
router.get('/login', function(req, res, next) {
|
router.get('/login', function (req, res, next) {
|
||||||
res.render('auth/login', {
|
res.render('auth/login', {
|
||||||
usr: res.locals.usr
|
usr: res.locals.usr
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
router.post('/login', bruteforce.prevent, function(req, res, next) {
|
|
||||||
passport.authenticate('local', function(err, user, info) {
|
|
||||||
|
|
||||||
if (err) { return next(err); }
|
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||||
|
passport.authenticate('local', function (err, user, info) {
|
||||||
|
if (err) { return next(err) }
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
title: 'Invalid login',
|
title: 'Invalid login',
|
||||||
message: "The email or password is invalid."
|
message: 'The email or password is invalid.'
|
||||||
});
|
})
|
||||||
return res.redirect('/login');
|
return res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
req.logIn(user, function(err) {
|
req.logIn(user, function (err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err) }
|
||||||
req.brute.reset(function () {
|
req.brute.reset(function () {
|
||||||
return res.redirect('/');
|
return res.redirect('/')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
})(req, res, next)
|
||||||
})(req, res, next);
|
})
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Social Login
|
* Social Login
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }));
|
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
|
||||||
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
|
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
|
||||||
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }));
|
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
|
||||||
|
|
||||||
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }));
|
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout
|
* Logout
|
||||||
*/
|
*/
|
||||||
router.get('/logout', function(req, res) {
|
router.get('/logout', function (req, res) {
|
||||||
req.logout();
|
req.logout()
|
||||||
res.redirect('/');
|
res.redirect('/')
|
||||||
});
|
})
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router
|
||||||
|
@ -1,184 +1,160 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var express = require('express');
|
const express = require('express')
|
||||||
var router = express.Router();
|
const router = express.Router()
|
||||||
|
|
||||||
var readChunk = require('read-chunk'),
|
const readChunk = require('read-chunk')
|
||||||
fileType = require('file-type'),
|
const fileType = require('file-type')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$");
|
const validPathRe = new RegExp('^([a-z0-9\\/-]+\\.[a-z0-9]+)$')
|
||||||
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$");
|
const validPathThumbsRe = new RegExp('^([0-9]+\\.png)$')
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// SERVE UPLOADS FILES
|
// SERVE UPLOADS FILES
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
router.get('/t/*', (req, res, next) => {
|
router.get('/t/*', (req, res, next) => {
|
||||||
|
let fileName = req.params[0]
|
||||||
let fileName = req.params[0];
|
if (!validPathThumbsRe.test(fileName)) {
|
||||||
if(!validPathThumbsRe.test(fileName)) {
|
return res.sendStatus(404).end()
|
||||||
return res.sendStatus(404).end();
|
}
|
||||||
}
|
|
||||||
|
// todo: Authentication-based access
|
||||||
//todo: Authentication-based access
|
|
||||||
|
res.sendFile(fileName, {
|
||||||
res.sendFile(fileName, {
|
root: lcdata.getThumbsPath(),
|
||||||
root: lcdata.getThumbsPath(),
|
dotfiles: 'deny'
|
||||||
dotfiles: 'deny'
|
}, (err) => {
|
||||||
}, (err) => {
|
if (err) {
|
||||||
if (err) {
|
res.status(err.status).end()
|
||||||
res.status(err.status).end();
|
}
|
||||||
}
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
||||||
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
|
||||||
|
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
if (!destFolderPath) {
|
||||||
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
if(!destFolderPath) {
|
return true
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
}
|
||||||
return true;
|
|
||||||
}
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
Promise.map(req.files, (f) => {
|
let destFilePath = ''
|
||||||
|
|
||||||
let destFilename = '';
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
||||||
let destFilePath = '';
|
destFilename = fname
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
|
||||||
|
return readChunk(f.path, 0, 262)
|
||||||
destFilename = fname;
|
}).then((buf) => {
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
// -> Check MIME type by magic number
|
||||||
|
|
||||||
return readChunk(f.path, 0, 262);
|
let mimeInfo = fileType(buf)
|
||||||
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
}).then((buf) => {
|
return Promise.reject(new Error('Invalid file type.'))
|
||||||
|
}
|
||||||
//-> Check MIME type by magic number
|
return true
|
||||||
|
}).then(() => {
|
||||||
let mimeInfo = fileType(buf);
|
// -> Move file to final destination
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
|
||||||
return Promise.reject(new Error('Invalid file type.'));
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
}
|
}).then(() => {
|
||||||
return true;
|
return {
|
||||||
|
ok: true,
|
||||||
}).then(() => {
|
filename: destFilename,
|
||||||
|
filesize: f.size
|
||||||
//-> Move file to final destination
|
}
|
||||||
|
}).reflect()
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
}, {concurrency: 3}).then((results) => {
|
||||||
|
let uplResults = _.map(results, (r) => {
|
||||||
}).then(() => {
|
if (r.isFulfilled()) {
|
||||||
return {
|
return r.value()
|
||||||
ok: true,
|
} else {
|
||||||
filename: destFilename,
|
return {
|
||||||
filesize: f.size
|
ok: false,
|
||||||
};
|
msg: r.reason().message
|
||||||
}).reflect();
|
}
|
||||||
|
}
|
||||||
}, {concurrency: 3}).then((results) => {
|
})
|
||||||
let uplResults = _.map(results, (r) => {
|
res.json({ ok: true, results: uplResults })
|
||||||
if(r.isFulfilled()) {
|
return true
|
||||||
return r.value();
|
}).catch((err) => {
|
||||||
} else {
|
res.json({ ok: false, msg: err.message })
|
||||||
return {
|
return true
|
||||||
ok: false,
|
})
|
||||||
msg: r.reason().message
|
})
|
||||||
};
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
res.json({ ok: true, results: uplResults });
|
|
||||||
return true;
|
|
||||||
}).catch((err) => {
|
|
||||||
res.json({ ok: false, msg: err.message });
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
||||||
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
|
||||||
|
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
if (!destFolderPath) {
|
||||||
|
res.json({ ok: false, msg: 'Invalid Folder' })
|
||||||
if(!destFolderPath) {
|
return true
|
||||||
res.json({ ok: false, msg: 'Invalid Folder' });
|
}
|
||||||
return true;
|
|
||||||
}
|
Promise.map(req.files, (f) => {
|
||||||
|
let destFilename = ''
|
||||||
Promise.map(req.files, (f) => {
|
let destFilePath = ''
|
||||||
|
|
||||||
let destFilename = '';
|
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
||||||
let destFilePath = '';
|
destFilename = fname
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
|
||||||
|
// -> Move file to final destination
|
||||||
destFilename = fname;
|
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
return fs.moveAsync(f.path, destFilePath, { clobber: false })
|
||||||
|
}).then(() => {
|
||||||
//-> Move file to final destination
|
return {
|
||||||
|
ok: true,
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
filename: destFilename,
|
||||||
|
filesize: f.size
|
||||||
}).then(() => {
|
}
|
||||||
return {
|
}).reflect()
|
||||||
ok: true,
|
}, {concurrency: 3}).then((results) => {
|
||||||
filename: destFilename,
|
let uplResults = _.map(results, (r) => {
|
||||||
filesize: f.size
|
if (r.isFulfilled()) {
|
||||||
};
|
return r.value()
|
||||||
}).reflect();
|
} else {
|
||||||
|
return {
|
||||||
}, {concurrency: 3}).then((results) => {
|
ok: false,
|
||||||
let uplResults = _.map(results, (r) => {
|
msg: r.reason().message
|
||||||
if(r.isFulfilled()) {
|
}
|
||||||
return r.value();
|
}
|
||||||
} else {
|
})
|
||||||
return {
|
res.json({ ok: true, results: uplResults })
|
||||||
ok: false,
|
return true
|
||||||
msg: r.reason().message
|
}).catch((err) => {
|
||||||
};
|
res.json({ ok: false, msg: err.message })
|
||||||
}
|
return true
|
||||||
});
|
})
|
||||||
res.json({ ok: true, results: uplResults });
|
})
|
||||||
return true;
|
})
|
||||||
}).catch((err) => {
|
|
||||||
res.json({ ok: false, msg: err.message });
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/*', (req, res, next) => {
|
router.get('/*', (req, res, next) => {
|
||||||
|
let fileName = req.params[0]
|
||||||
let fileName = req.params[0];
|
if (!validPathRe.test(fileName)) {
|
||||||
if(!validPathRe.test(fileName)) {
|
return res.sendStatus(404).end()
|
||||||
return res.sendStatus(404).end();
|
}
|
||||||
}
|
|
||||||
|
// todo: Authentication-based access
|
||||||
//todo: Authentication-based access
|
|
||||||
|
res.sendFile(fileName, {
|
||||||
res.sendFile(fileName, {
|
root: git.getRepoPath() + '/uploads/',
|
||||||
root: git.getRepoPath() + '/uploads/',
|
dotfiles: 'deny'
|
||||||
dotfiles: 'deny'
|
}, (err) => {
|
||||||
}, (err) => {
|
if (err) {
|
||||||
if (err) {
|
res.status(err.status).end()
|
||||||
res.status(err.status).end();
|
}
|
||||||
}
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
});
|
module.exports = router
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
@ -1,95 +1,95 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
module.exports = (socket) => {
|
const _ = require('lodash')
|
||||||
|
|
||||||
if(!socket.request.user.logged_in) {
|
module.exports = (socket) => {
|
||||||
return;
|
if (!socket.request.user.logged_in) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------
|
// -----------------------------------------
|
||||||
// SEARCH
|
// SEARCH
|
||||||
//-----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
socket.on('search', (data, cb) => {
|
socket.on('search', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
entries.search(data.terms).then((results) => {
|
entries.search(data.terms).then((results) => {
|
||||||
return cb(results) || true;
|
return cb(results) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
//-----------------------------------------
|
// -----------------------------------------
|
||||||
// UPLOADS
|
// UPLOADS
|
||||||
//-----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
socket.on('uploadsGetFolders', (data, cb) => {
|
socket.on('uploadsGetFolders', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFolders().then((f) => {
|
upl.getUploadsFolders().then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsCreateFolder', (data, cb) => {
|
socket.on('uploadsCreateFolder', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.createUploadsFolder(data.foldername).then((f) => {
|
upl.createUploadsFolder(data.foldername).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsGetImages', (data, cb) => {
|
socket.on('uploadsGetImages', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFiles('image', data.folder).then((f) => {
|
upl.getUploadsFiles('image', data.folder).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsGetFiles', (data, cb) => {
|
socket.on('uploadsGetFiles', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.getUploadsFiles('binary', data.folder).then((f) => {
|
upl.getUploadsFiles('binary', data.folder).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsDeleteFile', (data, cb) => {
|
socket.on('uploadsDeleteFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.deleteUploadsFile(data.uid).then((f) => {
|
upl.deleteUploadsFile(data.uid).then((f) => {
|
||||||
return cb(f) || true;
|
return cb(f) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsFetchFileFromURL', (data, cb) => {
|
socket.on('uploadsFetchFileFromURL', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
|
upl.downloadFromUrl(data.folder, data.fetchUrl).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsRenameFile', (data, cb) => {
|
socket.on('uploadsRenameFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
|
upl.moveUploadsFile(data.uid, data.folder, data.filename).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
socket.on('uploadsMoveFile', (data, cb) => {
|
socket.on('uploadsMoveFile', (data, cb) => {
|
||||||
cb = cb || _.noop;
|
cb = cb || _.noop
|
||||||
upl.moveUploadsFile(data.uid, data.folder).then((f) => {
|
upl.moveUploadsFile(data.uid, data.folder).then((f) => {
|
||||||
return cb({ ok: true }) || true;
|
return cb({ ok: true }) || true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return cb({
|
return cb({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: err.message
|
msg: err.message
|
||||||
}) || true;
|
}) || true
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
}
|
||||||
};
|
|
||||||
|
@ -1,215 +1,210 @@
|
|||||||
var gulp = require("gulp");
|
'use strict'
|
||||||
var watch = require('gulp-watch');
|
|
||||||
var merge = require('merge-stream');
|
const gulp = require('gulp')
|
||||||
var babel = require("gulp-babel");
|
const watch = require('gulp-watch')
|
||||||
var uglify = require('gulp-uglify');
|
const merge = require('merge-stream')
|
||||||
var concat = require('gulp-concat');
|
const babel = require('gulp-babel')
|
||||||
var nodemon = require('gulp-nodemon');
|
const uglify = require('gulp-uglify')
|
||||||
var plumber = require('gulp-plumber');
|
const concat = require('gulp-concat')
|
||||||
var zip = require('gulp-zip');
|
const nodemon = require('gulp-nodemon')
|
||||||
var tar = require('gulp-tar');
|
const plumber = require('gulp-plumber')
|
||||||
var gzip = require('gulp-gzip');
|
const zip = require('gulp-zip')
|
||||||
var sass = require('gulp-sass');
|
const tar = require('gulp-tar')
|
||||||
var cleanCSS = require('gulp-clean-css');
|
const gzip = require('gulp-gzip')
|
||||||
var include = require("gulp-include");
|
const sass = require('gulp-sass')
|
||||||
var run = require('run-sequence');
|
const cleanCSS = require('gulp-clean-css')
|
||||||
var _ = require('lodash');
|
const include = require('gulp-include')
|
||||||
|
const run = require('run-sequence')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths
|
* Paths
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
var paths = {
|
const paths = {
|
||||||
scripts: {
|
scripts: {
|
||||||
combine: [
|
combine: [
|
||||||
'./node_modules/socket.io-client/dist/socket.io.min.js',
|
'./node_modules/socket.io-client/dist/socket.io.min.js',
|
||||||
'./node_modules/jquery/dist/jquery.min.js',
|
'./node_modules/jquery/dist/jquery.min.js',
|
||||||
'./node_modules/vue/dist/vue.min.js',
|
'./node_modules/vue/dist/vue.min.js',
|
||||||
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
|
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
|
||||||
'./node_modules/jquery-simple-upload/simpleUpload.min.js',
|
'./node_modules/jquery-simple-upload/simpleUpload.min.js',
|
||||||
'./node_modules/jquery-contextmenu/dist/jquery.contextMenu.min.js',
|
'./node_modules/jquery-contextmenu/dist/jquery.contextMenu.min.js',
|
||||||
'./node_modules/sticky-js/dist/sticky.min.js',
|
'./node_modules/sticky-js/dist/sticky.min.js',
|
||||||
'./node_modules/simplemde/dist/simplemde.min.js',
|
'./node_modules/simplemde/dist/simplemde.min.js',
|
||||||
'./node_modules/ace-builds/src-min-noconflict/ace.js',
|
'./node_modules/ace-builds/src-min-noconflict/ace.js',
|
||||||
'./node_modules/ace-builds/src-min-noconflict/ext-modelist.js',
|
'./node_modules/ace-builds/src-min-noconflict/ext-modelist.js',
|
||||||
'./node_modules/ace-builds/src-min-noconflict/mode-markdown.js',
|
'./node_modules/ace-builds/src-min-noconflict/mode-markdown.js',
|
||||||
'./node_modules/ace-builds/src-min-noconflict/theme-tomorrow_night.js',
|
'./node_modules/ace-builds/src-min-noconflict/theme-tomorrow_night.js',
|
||||||
'./node_modules/filesize.js/dist/filesize.min.js',
|
'./node_modules/filesize.js/dist/filesize.min.js',
|
||||||
'./node_modules/lodash/lodash.min.js'
|
'./node_modules/lodash/lodash.min.js'
|
||||||
],
|
],
|
||||||
ace: [
|
ace: [
|
||||||
'./node_modules/ace-builds/src-min-noconflict/mode-*.js',
|
'./node_modules/ace-builds/src-min-noconflict/mode-*.js',
|
||||||
'!./node_modules/ace-builds/src-min-noconflict/mode-markdown.js'
|
'!./node_modules/ace-builds/src-min-noconflict/mode-markdown.js'
|
||||||
],
|
],
|
||||||
compile: [
|
compile: [
|
||||||
'./client/js/*.js'
|
'./client/js/*.js'
|
||||||
],
|
],
|
||||||
watch: [
|
watch: [
|
||||||
'./client/js/**/*.js'
|
'./client/js/**/*.js'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
combine: [
|
combine: [
|
||||||
'./node_modules/highlight.js/styles/tomorrow.css',
|
'./node_modules/highlight.js/styles/tomorrow.css',
|
||||||
'./node_modules/simplemde/dist/simplemde.min.css'
|
'./node_modules/simplemde/dist/simplemde.min.css'
|
||||||
],
|
],
|
||||||
compile: [
|
compile: [
|
||||||
'./client/scss/*.scss'
|
'./client/scss/*.scss'
|
||||||
],
|
],
|
||||||
includes: [
|
includes: [
|
||||||
'./node_modules/requarks-core' //! MUST BE LAST
|
'./node_modules/requarks-core' //! MUST BE LAST
|
||||||
],
|
],
|
||||||
watch: [
|
watch: [
|
||||||
'./client/scss/**/*.scss',
|
'./client/scss/**/*.scss',
|
||||||
'../core/core-client/scss/**/*.scss'
|
'../core/core-client/scss/**/*.scss'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
fonts: [
|
fonts: [
|
||||||
'../node_modules/requarks-core/core-client/fonts/**/*' //! MUST BE LAST
|
'../node_modules/requarks-core/core-client/fonts/**/*' //! MUST BE LAST
|
||||||
],
|
],
|
||||||
deploy: [
|
deploy: [
|
||||||
'./**/*',
|
'./**/*',
|
||||||
'!node_modules', '!node_modules/**',
|
'!node_modules', '!node_modules/**',
|
||||||
'!coverage', '!coverage/**',
|
'!coverage', '!coverage/**',
|
||||||
'!client/js', '!client/js/**',
|
'!client/js', '!client/js/**',
|
||||||
'!client/scss', '!client/scss/**',
|
'!client/scss', '!client/scss/**',
|
||||||
'!dist', '!dist/**',
|
'!dist', '!dist/**',
|
||||||
'!tests', '!tests/**',
|
'!tests', '!tests/**',
|
||||||
'!data', '!data/**',
|
'!data', '!data/**',
|
||||||
'!repo', '!repo/**',
|
'!repo', '!repo/**',
|
||||||
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
|
'!.babelrc', '!.gitattributes', '!.gitignore', '!.snyk', '!.travis.yml',
|
||||||
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
|
'!gulpfile.js', '!inch.json', '!config.yml', '!wiki.sublime-project'
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Starts server in development mode
|
* TASK - Starts server in development mode
|
||||||
*/
|
*/
|
||||||
gulp.task('server', ['scripts', 'css', 'fonts'], function() {
|
gulp.task('server', ['scripts', 'css', 'fonts'], function () {
|
||||||
nodemon({
|
nodemon({
|
||||||
script: './server',
|
script: './server',
|
||||||
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
||||||
ext: 'js json',
|
ext: 'js json',
|
||||||
env: { 'NODE_ENV': 'development' }
|
env: { 'NODE_ENV': 'development' }
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Process all scripts processes
|
* TASK - Process all scripts processes
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts", ['scripts-libs', 'scripts-app']);
|
gulp.task('scripts', ['scripts-libs', 'scripts-app'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine js libraries
|
* TASK - Combine js libraries
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts-libs", function () {
|
gulp.task('scripts-libs', function () {
|
||||||
|
return merge(
|
||||||
|
|
||||||
return merge(
|
gulp.src(paths.scripts.combine)
|
||||||
|
.pipe(concat('libs.js', {newLine: ';\n'}))
|
||||||
|
.pipe(uglify({ mangle: false }))
|
||||||
|
.pipe(gulp.dest('./assets/js')),
|
||||||
|
|
||||||
gulp.src(paths.scripts.combine)
|
gulp.src(paths.scripts.ace)
|
||||||
.pipe(concat('libs.js', {newLine: ';\n'}))
|
.pipe(gulp.dest('./assets/js/ace'))
|
||||||
.pipe(uglify({ mangle: false }))
|
|
||||||
.pipe(gulp.dest("./assets/js")),
|
|
||||||
|
|
||||||
gulp.src(paths.scripts.ace)
|
)
|
||||||
.pipe(gulp.dest("./assets/js/ace"))
|
})
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine, make compatible and compress js app scripts
|
* TASK - Combine, make compatible and compress js app scripts
|
||||||
*/
|
*/
|
||||||
gulp.task("scripts-app", function () {
|
gulp.task('scripts-app', function () {
|
||||||
|
return gulp.src(paths.scripts.compile)
|
||||||
return gulp.src(paths.scripts.compile)
|
.pipe(plumber())
|
||||||
.pipe(plumber())
|
.pipe(include({ extensions: 'js' }))
|
||||||
.pipe(include({ extensions: "js" }))
|
.pipe(babel())
|
||||||
.pipe(babel())
|
.pipe(uglify())
|
||||||
.pipe(uglify())
|
.pipe(plumber.stop())
|
||||||
.pipe(plumber.stop())
|
.pipe(gulp.dest('./assets/js'))
|
||||||
.pipe(gulp.dest("./assets/js"));
|
})
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Process all css processes
|
* TASK - Process all css processes
|
||||||
*/
|
*/
|
||||||
gulp.task("css", ['css-libs', 'css-app']);
|
gulp.task('css', ['css-libs', 'css-app'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine css libraries
|
* TASK - Combine css libraries
|
||||||
*/
|
*/
|
||||||
gulp.task("css-libs", function () {
|
gulp.task('css-libs', function () {
|
||||||
return gulp.src(paths.css.combine)
|
return gulp.src(paths.css.combine)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(concat('libs.css'))
|
.pipe(concat('libs.css'))
|
||||||
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
||||||
.pipe(plumber.stop())
|
.pipe(plumber.stop())
|
||||||
.pipe(gulp.dest("./assets/css"));
|
.pipe(gulp.dest('./assets/css'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Combine app css
|
* TASK - Combine app css
|
||||||
*/
|
*/
|
||||||
gulp.task("css-app", function () {
|
gulp.task('css-app', function () {
|
||||||
return gulp.src(paths.css.compile)
|
return gulp.src(paths.css.compile)
|
||||||
.pipe(plumber())
|
.pipe(plumber())
|
||||||
.pipe(sass.sync({ includePaths: paths.css.includes }))
|
.pipe(sass.sync({ includePaths: paths.css.includes }))
|
||||||
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
.pipe(cleanCSS({ keepSpecialComments: 0 }))
|
||||||
.pipe(plumber.stop())
|
.pipe(plumber.stop())
|
||||||
.pipe(gulp.dest("./assets/css"));
|
.pipe(gulp.dest('./assets/css'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Copy web fonts
|
* TASK - Copy web fonts
|
||||||
*/
|
*/
|
||||||
gulp.task("fonts", function () {
|
gulp.task('fonts', function () {
|
||||||
return gulp.src(paths.fonts)
|
return gulp.src(paths.fonts)
|
||||||
.pipe(gulp.dest("./assets/fonts"));
|
.pipe(gulp.dest('./assets/fonts'))
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Start dev watchers
|
* TASK - Start dev watchers
|
||||||
*/
|
*/
|
||||||
gulp.task('watch', function() {
|
gulp.task('watch', function () {
|
||||||
return merge(
|
return merge(
|
||||||
watch(paths.scripts.watch, {base: './'}, function() { return gulp.start('scripts-app'); }),
|
watch(paths.scripts.watch, {base: './'}, function () { return gulp.start('scripts-app') }),
|
||||||
watch(paths.css.watch, {base: './'}, function() { return gulp.start('css-app'); })
|
watch(paths.css.watch, {base: './'}, function () { return gulp.start('css-app') })
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Starts development server with watchers
|
* TASK - Starts development server with watchers
|
||||||
*/
|
*/
|
||||||
gulp.task('default', ['watch', 'server']);
|
gulp.task('default', ['watch', 'server'])
|
||||||
|
|
||||||
gulp.task('dev', function() {
|
|
||||||
|
|
||||||
paths.css.includes.pop();
|
|
||||||
paths.css.includes.push('../core');
|
|
||||||
|
|
||||||
paths.fonts.pop();
|
gulp.task('dev', function () {
|
||||||
paths.fonts.push('../core/core-client/fonts/**/*');
|
paths.css.includes.pop()
|
||||||
|
paths.css.includes.push('../core')
|
||||||
|
|
||||||
return run('default');
|
paths.fonts.pop()
|
||||||
|
paths.fonts.push('../core/core-client/fonts/**/*')
|
||||||
|
|
||||||
});
|
return run('default')
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TASK - Creates deployment packages
|
* TASK - Creates deployment packages
|
||||||
*/
|
*/
|
||||||
gulp.task('deploy', ['scripts', 'css', 'fonts'], function() {
|
gulp.task('deploy', ['scripts', 'css', 'fonts'], function () {
|
||||||
var zipStream = gulp.src(paths.deploy)
|
var zipStream = gulp.src(paths.deploy)
|
||||||
.pipe(zip('wiki-js.zip'))
|
.pipe(zip('wiki-js.zip'))
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'))
|
||||||
|
|
||||||
var targzStream = gulp.src(paths.deploy)
|
var targzStream = gulp.src(paths.deploy)
|
||||||
.pipe(tar('wiki-js.tar'))
|
.pipe(tar('wiki-js.tar'))
|
||||||
.pipe(gzip())
|
.pipe(gzip())
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'))
|
||||||
|
|
||||||
return merge(zipStream, targzStream);
|
return merge(zipStream, targzStream)
|
||||||
});
|
})
|
||||||
|
@ -1,500 +1,452 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
fs = Promise.promisifyAll(require("fs-extra")),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
_ = require('lodash'),
|
const _ = require('lodash')
|
||||||
farmhash = require('farmhash'),
|
const farmhash = require('farmhash')
|
||||||
moment = require('moment');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entries Model
|
* Entries Model
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_repoPath: 'repo',
|
_repoPath: 'repo',
|
||||||
_cachePath: 'data/cache',
|
_cachePath: 'data/cache',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Entries model
|
* Initialize Entries model
|
||||||
*
|
*
|
||||||
* @return {Object} Entries model instance
|
* @return {Object} Entries model instance
|
||||||
*/
|
*/
|
||||||
init() {
|
init () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
||||||
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache')
|
||||||
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
|
|
||||||
|
return self
|
||||||
return self;
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Check if a document already exists
|
||||||
/**
|
*
|
||||||
* Check if a document already exists
|
* @param {String} entryPath The entry path
|
||||||
*
|
* @return {Promise<Boolean>} True if exists, false otherwise
|
||||||
* @param {String} entryPath The entry path
|
*/
|
||||||
* @return {Promise<Boolean>} True if exists, false otherwise
|
exists (entryPath) {
|
||||||
*/
|
let self = this
|
||||||
exists(entryPath) {
|
|
||||||
|
return self.fetchOriginal(entryPath, {
|
||||||
let self = this;
|
parseMarkdown: false,
|
||||||
|
parseMeta: false,
|
||||||
return self.fetchOriginal(entryPath, {
|
parseTree: false,
|
||||||
parseMarkdown: false,
|
includeMarkdown: false,
|
||||||
parseMeta: false,
|
includeParentInfo: false,
|
||||||
parseTree: false,
|
cache: false
|
||||||
includeMarkdown: false,
|
}).then(() => {
|
||||||
includeParentInfo: false,
|
return true
|
||||||
cache: false
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
}).then(() => {
|
return false
|
||||||
return true;
|
})
|
||||||
}).catch((err) => {
|
},
|
||||||
return false;
|
|
||||||
});
|
/**
|
||||||
|
* Fetch a document from cache, otherwise the original
|
||||||
},
|
*
|
||||||
|
* @param {String} entryPath The entry path
|
||||||
/**
|
* @return {Promise<Object>} Page Data
|
||||||
* Fetch a document from cache, otherwise the original
|
*/
|
||||||
*
|
fetch (entryPath) {
|
||||||
* @param {String} entryPath The entry path
|
let self = this
|
||||||
* @return {Promise<Object>} Page Data
|
|
||||||
*/
|
let cpath = self.getCachePath(entryPath)
|
||||||
fetch(entryPath) {
|
|
||||||
|
return fs.statAsync(cpath).then((st) => {
|
||||||
let self = this;
|
return st.isFile()
|
||||||
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
let cpath = self.getCachePath(entryPath);
|
return false
|
||||||
|
}).then((isCache) => {
|
||||||
return fs.statAsync(cpath).then((st) => {
|
if (isCache) {
|
||||||
return st.isFile();
|
// Load from cache
|
||||||
}).catch((err) => {
|
|
||||||
return false;
|
return fs.readFileAsync(cpath).then((contents) => {
|
||||||
}).then((isCache) => {
|
return JSON.parse(contents)
|
||||||
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
if(isCache) {
|
winston.error('Corrupted cache file. Deleting it...')
|
||||||
|
fs.unlinkSync(cpath)
|
||||||
// Load from cache
|
return false
|
||||||
|
})
|
||||||
return fs.readFileAsync(cpath).then((contents) => {
|
} else {
|
||||||
return JSON.parse(contents);
|
// Load original
|
||||||
}).catch((err) => {
|
|
||||||
winston.error('Corrupted cache file. Deleting it...');
|
return self.fetchOriginal(entryPath)
|
||||||
fs.unlinkSync(cpath);
|
}
|
||||||
return false;
|
})
|
||||||
});
|
},
|
||||||
|
|
||||||
} else {
|
/**
|
||||||
|
* Fetches the original document entry
|
||||||
// Load original
|
*
|
||||||
|
* @param {String} entryPath The entry path
|
||||||
return self.fetchOriginal(entryPath);
|
* @param {Object} options The options
|
||||||
|
* @return {Promise<Object>} Page data
|
||||||
}
|
*/
|
||||||
|
fetchOriginal (entryPath, options) {
|
||||||
});
|
let self = this
|
||||||
|
|
||||||
},
|
let fpath = self.getFullPath(entryPath)
|
||||||
|
let cpath = self.getCachePath(entryPath)
|
||||||
/**
|
|
||||||
* Fetches the original document entry
|
options = _.defaults(options, {
|
||||||
*
|
parseMarkdown: true,
|
||||||
* @param {String} entryPath The entry path
|
parseMeta: true,
|
||||||
* @param {Object} options The options
|
parseTree: true,
|
||||||
* @return {Promise<Object>} Page data
|
includeMarkdown: false,
|
||||||
*/
|
includeParentInfo: true,
|
||||||
fetchOriginal(entryPath, options) {
|
cache: true
|
||||||
|
})
|
||||||
let self = this;
|
|
||||||
|
return fs.statAsync(fpath).then((st) => {
|
||||||
let fpath = self.getFullPath(entryPath);
|
if (st.isFile()) {
|
||||||
let cpath = self.getCachePath(entryPath);
|
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
||||||
|
// Parse contents
|
||||||
options = _.defaults(options, {
|
|
||||||
parseMarkdown: true,
|
let pageData = {
|
||||||
parseMeta: true,
|
markdown: (options.includeMarkdown) ? contents : '',
|
||||||
parseTree: true,
|
html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
|
||||||
includeMarkdown: false,
|
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
||||||
includeParentInfo: true,
|
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
||||||
cache: true
|
}
|
||||||
});
|
|
||||||
|
if (!pageData.meta.title) {
|
||||||
return fs.statAsync(fpath).then((st) => {
|
pageData.meta.title = _.startCase(entryPath)
|
||||||
if(st.isFile()) {
|
}
|
||||||
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
|
||||||
|
pageData.meta.path = entryPath
|
||||||
// Parse contents
|
|
||||||
|
// Get parent
|
||||||
let pageData = {
|
|
||||||
markdown: (options.includeMarkdown) ? contents : '',
|
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
||||||
html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
|
return (pageData.parent = parentData)
|
||||||
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
return (pageData.parent = false)
|
||||||
};
|
}) : Promise.resolve(true)
|
||||||
|
|
||||||
if(!pageData.meta.title) {
|
return parentPromise.then(() => {
|
||||||
pageData.meta.title = _.startCase(entryPath);
|
// Cache to disk
|
||||||
}
|
|
||||||
|
if (options.cache) {
|
||||||
pageData.meta.path = entryPath;
|
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
|
||||||
|
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
||||||
// Get parent
|
winston.error('Unable to write to cache! Performance may be affected.')
|
||||||
|
winston.error(err)
|
||||||
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
return true
|
||||||
return (pageData.parent = parentData);
|
})
|
||||||
}).catch((err) => {
|
} else {
|
||||||
return (pageData.parent = false);
|
return true
|
||||||
}) : Promise.resolve(true);
|
}
|
||||||
|
}).return(pageData)
|
||||||
return parentPromise.then(() => {
|
})
|
||||||
|
} else {
|
||||||
// Cache to disk
|
return false
|
||||||
|
}
|
||||||
if(options.cache) {
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false);
|
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'))
|
||||||
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
})
|
||||||
winston.error('Unable to write to cache! Performance may be affected.');
|
},
|
||||||
return true;
|
|
||||||
});
|
/**
|
||||||
} else {
|
* Parse raw url path and make it safe
|
||||||
return true;
|
*
|
||||||
}
|
* @param {String} urlPath The url path
|
||||||
|
* @return {String} Safe entry path
|
||||||
}).return(pageData);
|
*/
|
||||||
|
parsePath (urlPath) {
|
||||||
});
|
let wlist = new RegExp('[^a-z0-9/-]', 'g')
|
||||||
} else {
|
|
||||||
return false;
|
urlPath = _.toLower(urlPath).replace(wlist, '')
|
||||||
}
|
|
||||||
}).catch((err) => {
|
if (urlPath === '/') {
|
||||||
return Promise.reject(new Promise.OperationalError('Entry ' + entryPath + ' does not exist!'));
|
urlPath = 'home'
|
||||||
});
|
}
|
||||||
|
|
||||||
},
|
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p) })
|
||||||
|
|
||||||
/**
|
return _.join(urlParts, '/')
|
||||||
* Parse raw url path and make it safe
|
},
|
||||||
*
|
|
||||||
* @param {String} urlPath The url path
|
/**
|
||||||
* @return {String} Safe entry path
|
* Gets the parent information.
|
||||||
*/
|
*
|
||||||
parsePath(urlPath) {
|
* @param {String} entryPath The entry path
|
||||||
|
* @return {Promise<Object|False>} The parent information.
|
||||||
let wlist = new RegExp('[^a-z0-9/\-]','g');
|
*/
|
||||||
|
getParentInfo (entryPath) {
|
||||||
urlPath = _.toLower(urlPath).replace(wlist, '');
|
let self = this
|
||||||
|
|
||||||
if(urlPath === '/') {
|
if (_.includes(entryPath, '/')) {
|
||||||
urlPath = 'home';
|
let parentParts = _.initial(_.split(entryPath, '/'))
|
||||||
}
|
let parentPath = _.join(parentParts, '/')
|
||||||
|
let parentFile = _.last(parentParts)
|
||||||
let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p); });
|
let fpath = self.getFullPath(parentPath)
|
||||||
|
|
||||||
return _.join(urlParts, '/');
|
return fs.statAsync(fpath).then((st) => {
|
||||||
|
if (st.isFile()) {
|
||||||
},
|
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
||||||
|
let pageMeta = mark.parseMeta(contents)
|
||||||
/**
|
|
||||||
* Gets the parent information.
|
return {
|
||||||
*
|
path: parentPath,
|
||||||
* @param {String} entryPath The entry path
|
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
|
||||||
* @return {Promise<Object|False>} The parent information.
|
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
|
||||||
*/
|
}
|
||||||
getParentInfo(entryPath) {
|
})
|
||||||
|
} else {
|
||||||
let self = this;
|
return Promise.reject(new Error('Parent entry is not a valid file.'))
|
||||||
|
}
|
||||||
if(_.includes(entryPath, '/')) {
|
})
|
||||||
|
} else {
|
||||||
let parentParts = _.initial(_.split(entryPath, '/'));
|
return Promise.reject(new Error('Parent entry is root.'))
|
||||||
let parentPath = _.join(parentParts,'/');
|
}
|
||||||
let parentFile = _.last(parentParts);
|
},
|
||||||
let fpath = self.getFullPath(parentPath);
|
|
||||||
|
/**
|
||||||
return fs.statAsync(fpath).then((st) => {
|
* Gets the full original path of a document.
|
||||||
if(st.isFile()) {
|
*
|
||||||
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
* @param {String} entryPath The entry path
|
||||||
|
* @return {String} The full path.
|
||||||
let pageMeta = mark.parseMeta(contents);
|
*/
|
||||||
|
getFullPath (entryPath) {
|
||||||
return {
|
return path.join(this._repoPath, entryPath + '.md')
|
||||||
path: parentPath,
|
},
|
||||||
title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
|
|
||||||
subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
|
/**
|
||||||
};
|
* Gets the full cache path of a document.
|
||||||
|
*
|
||||||
});
|
* @param {String} entryPath The entry path
|
||||||
} else {
|
* @return {String} The full cache path.
|
||||||
return Promise.reject(new Error('Parent entry is not a valid file.'));
|
*/
|
||||||
}
|
getCachePath (entryPath) {
|
||||||
});
|
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json')
|
||||||
|
},
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error('Parent entry is root.'));
|
/**
|
||||||
}
|
* Gets the entry path from full path.
|
||||||
|
*
|
||||||
},
|
* @param {String} fullPath The full path
|
||||||
|
* @return {String} The entry path
|
||||||
/**
|
*/
|
||||||
* Gets the full original path of a document.
|
getEntryPathFromFullPath (fullPath) {
|
||||||
*
|
let absRepoPath = path.resolve(ROOTPATH, this._repoPath)
|
||||||
* @param {String} entryPath The entry path
|
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'), '/').value()
|
||||||
* @return {String} The full path.
|
},
|
||||||
*/
|
|
||||||
getFullPath(entryPath) {
|
/**
|
||||||
return path.join(this._repoPath, entryPath + '.md');
|
* Update an existing document
|
||||||
},
|
*
|
||||||
|
* @param {String} entryPath The entry path
|
||||||
/**
|
* @param {String} contents The markdown-formatted contents
|
||||||
* Gets the full cache path of a document.
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
*
|
*/
|
||||||
* @param {String} entryPath The entry path
|
update (entryPath, contents) {
|
||||||
* @return {String} The full cache path.
|
let self = this
|
||||||
*/
|
let fpath = self.getFullPath(entryPath)
|
||||||
getCachePath(entryPath) {
|
|
||||||
return path.join(this._cachePath, farmhash.fingerprint32(entryPath) + '.json');
|
return fs.statAsync(fpath).then((st) => {
|
||||||
},
|
if (st.isFile()) {
|
||||||
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
/**
|
return self.updateCache(entryPath)
|
||||||
* Gets the entry path from full path.
|
})
|
||||||
*
|
} else {
|
||||||
* @param {String} fullPath The full path
|
return Promise.reject(new Error('Entry does not exist!'))
|
||||||
* @return {String} The entry path
|
}
|
||||||
*/
|
}).catch((err) => {
|
||||||
getEntryPathFromFullPath(fullPath) {
|
winston.error(err)
|
||||||
let absRepoPath = path.resolve(ROOTPATH, this._repoPath);
|
return Promise.reject(new Error('Failed to save document.'))
|
||||||
return _.chain(fullPath).replace(absRepoPath, '').replace('.md', '').replace(new RegExp('\\\\', 'g'),'/').value();
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an existing document
|
* Update local cache and search index
|
||||||
*
|
*
|
||||||
* @param {String} entryPath The entry path
|
* @param {String} entryPath The entry path
|
||||||
* @param {String} contents The markdown-formatted contents
|
* @return {Promise} Promise of the operation
|
||||||
* @return {Promise<Boolean>} True on success, false on failure
|
*/
|
||||||
*/
|
updateCache (entryPath) {
|
||||||
update(entryPath, contents) {
|
let self = this
|
||||||
|
|
||||||
let self = this;
|
return self.fetchOriginal(entryPath, {
|
||||||
let fpath = self.getFullPath(entryPath);
|
parseMarkdown: true,
|
||||||
|
parseMeta: true,
|
||||||
return fs.statAsync(fpath).then((st) => {
|
parseTree: true,
|
||||||
if(st.isFile()) {
|
includeMarkdown: true,
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
includeParentInfo: true,
|
||||||
return self.updateCache(entryPath);
|
cache: true
|
||||||
});
|
}).then((pageData) => {
|
||||||
} else {
|
return {
|
||||||
return Promise.reject(new Error('Entry does not exist!'));
|
entryPath,
|
||||||
}
|
meta: pageData.meta,
|
||||||
}).catch((err) => {
|
parent: pageData.parent || {},
|
||||||
winston.error(err);
|
text: mark.removeMarkdown(pageData.markdown)
|
||||||
return Promise.reject(new Error('Failed to save document.'));
|
}
|
||||||
});
|
}).then((content) => {
|
||||||
|
return db.Entry.findOneAndUpdate({
|
||||||
},
|
_id: content.entryPath
|
||||||
|
}, {
|
||||||
/**
|
_id: content.entryPath,
|
||||||
* Update local cache and search index
|
title: content.meta.title || content.entryPath,
|
||||||
*
|
subtitle: content.meta.subtitle || '',
|
||||||
* @param {String} entryPath The entry path
|
parent: content.parent.title || '',
|
||||||
* @return {Promise} Promise of the operation
|
content: content.text || ''
|
||||||
*/
|
}, {
|
||||||
updateCache(entryPath) {
|
new: true,
|
||||||
|
upsert: true
|
||||||
let self = this;
|
})
|
||||||
|
})
|
||||||
return self.fetchOriginal(entryPath, {
|
},
|
||||||
parseMarkdown: true,
|
|
||||||
parseMeta: true,
|
/**
|
||||||
parseTree: true,
|
* Create a new document
|
||||||
includeMarkdown: true,
|
*
|
||||||
includeParentInfo: true,
|
* @param {String} entryPath The entry path
|
||||||
cache: true
|
* @param {String} contents The markdown-formatted contents
|
||||||
}).then((pageData) => {
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
return {
|
*/
|
||||||
entryPath,
|
create (entryPath, contents) {
|
||||||
meta: pageData.meta,
|
let self = this
|
||||||
parent: pageData.parent || {},
|
|
||||||
text: mark.removeMarkdown(pageData.markdown)
|
return self.exists(entryPath).then((docExists) => {
|
||||||
};
|
if (!docExists) {
|
||||||
}).then((content) => {
|
return self.makePersistent(entryPath, contents).then(() => {
|
||||||
return db.Entry.findOneAndUpdate({
|
return self.updateCache(entryPath)
|
||||||
_id: content.entryPath
|
})
|
||||||
}, {
|
} else {
|
||||||
_id: content.entryPath,
|
return Promise.reject(new Error('Entry already exists!'))
|
||||||
title: content.meta.title || content.entryPath,
|
}
|
||||||
subtitle: content.meta.subtitle || '',
|
}).catch((err) => {
|
||||||
parent: content.parent.title || '',
|
winston.error(err)
|
||||||
content: content.text || ''
|
return Promise.reject(new Error('Something went wrong.'))
|
||||||
}, {
|
})
|
||||||
new: true,
|
},
|
||||||
upsert: true
|
|
||||||
});
|
/**
|
||||||
});
|
* Makes a document persistent to disk and git repository
|
||||||
|
*
|
||||||
},
|
* @param {String} entryPath The entry path
|
||||||
|
* @param {String} contents The markdown-formatted contents
|
||||||
/**
|
* @return {Promise<Boolean>} True on success, false on failure
|
||||||
* Create a new document
|
*/
|
||||||
*
|
makePersistent (entryPath, contents) {
|
||||||
* @param {String} entryPath The entry path
|
let self = this
|
||||||
* @param {String} contents The markdown-formatted contents
|
let fpath = self.getFullPath(entryPath)
|
||||||
* @return {Promise<Boolean>} True on success, false on failure
|
|
||||||
*/
|
return fs.outputFileAsync(fpath, contents).then(() => {
|
||||||
create(entryPath, contents) {
|
return git.commitDocument(entryPath)
|
||||||
|
})
|
||||||
let self = this;
|
},
|
||||||
|
|
||||||
return self.exists(entryPath).then((docExists) => {
|
/**
|
||||||
if(!docExists) {
|
* Move a document
|
||||||
return self.makePersistent(entryPath, contents).then(() => {
|
*
|
||||||
return self.updateCache(entryPath);
|
* @param {String} entryPath The current entry path
|
||||||
});
|
* @param {String} newEntryPath The new entry path
|
||||||
} else {
|
* @return {Promise} Promise of the operation
|
||||||
return Promise.reject(new Error('Entry already exists!'));
|
*/
|
||||||
}
|
move (entryPath, newEntryPath) {
|
||||||
}).catch((err) => {
|
let self = this
|
||||||
winston.error(err);
|
|
||||||
return Promise.reject(new Error('Something went wrong.'));
|
if (_.isEmpty(entryPath) || entryPath === 'home') {
|
||||||
});
|
return Promise.reject(new Error('Invalid path!'))
|
||||||
|
}
|
||||||
},
|
|
||||||
|
return git.moveDocument(entryPath, newEntryPath).then(() => {
|
||||||
/**
|
return git.commitDocument(newEntryPath).then(() => {
|
||||||
* Makes a document persistent to disk and git repository
|
// Delete old cache version
|
||||||
*
|
|
||||||
* @param {String} entryPath The entry path
|
let oldEntryCachePath = self.getCachePath(entryPath)
|
||||||
* @param {String} contents The markdown-formatted contents
|
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err
|
||||||
* @return {Promise<Boolean>} True on success, false on failure
|
|
||||||
*/
|
// Delete old index entry
|
||||||
makePersistent(entryPath, contents) {
|
|
||||||
|
ws.emit('searchDel', {
|
||||||
let self = this;
|
auth: WSInternalKey,
|
||||||
let fpath = self.getFullPath(entryPath);
|
entryPath
|
||||||
|
})
|
||||||
return fs.outputFileAsync(fpath, contents).then(() => {
|
|
||||||
return git.commitDocument(entryPath);
|
// Create cache for new entry
|
||||||
});
|
|
||||||
|
return self.updateCache(newEntryPath)
|
||||||
},
|
})
|
||||||
|
})
|
||||||
/**
|
},
|
||||||
* Move a document
|
|
||||||
*
|
/**
|
||||||
* @param {String} entryPath The current entry path
|
* Generate a starter page content based on the entry path
|
||||||
* @param {String} newEntryPath The new entry path
|
*
|
||||||
* @return {Promise} Promise of the operation
|
* @param {String} entryPath The entry path
|
||||||
*/
|
* @return {Promise<String>} Starter content
|
||||||
move(entryPath, newEntryPath) {
|
*/
|
||||||
|
getStarter (entryPath) {
|
||||||
let self = this;
|
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
|
||||||
|
|
||||||
if(_.isEmpty(entryPath) || entryPath === 'home') {
|
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
|
||||||
return Promise.reject(new Error('Invalid path!'));
|
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle)
|
||||||
}
|
})
|
||||||
|
},
|
||||||
return git.moveDocument(entryPath, newEntryPath).then(() => {
|
|
||||||
return git.commitDocument(newEntryPath).then(() => {
|
/**
|
||||||
|
* Searches entries based on terms.
|
||||||
// Delete old cache version
|
*
|
||||||
|
* @param {String} terms The terms to search for
|
||||||
let oldEntryCachePath = self.getCachePath(entryPath);
|
* @return {Promise<Object>} Promise of the search results
|
||||||
fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true; });
|
*/
|
||||||
|
search (terms) {
|
||||||
// Delete old index entry
|
terms = _.chain(terms)
|
||||||
|
.deburr()
|
||||||
ws.emit('searchDel', {
|
.toLower()
|
||||||
auth: WSInternalKey,
|
.trim()
|
||||||
entryPath
|
.replace(/[^a-z0-9\- ]/g, '')
|
||||||
});
|
.split(' ')
|
||||||
|
.filter((f) => { return !_.isEmpty(f) })
|
||||||
// Create cache for new entry
|
.join(' ')
|
||||||
|
.value()
|
||||||
return self.updateCache(newEntryPath);
|
|
||||||
|
return db.Entry.find(
|
||||||
});
|
{ $text: { $search: terms } },
|
||||||
});
|
{ score: { $meta: 'textScore' }, title: 1 }
|
||||||
|
)
|
||||||
},
|
.sort({ score: { $meta: 'textScore' } })
|
||||||
|
.limit(10)
|
||||||
/**
|
.exec()
|
||||||
* Generate a starter page content based on the entry path
|
.then((hits) => {
|
||||||
*
|
if (hits.length < 5) {
|
||||||
* @param {String} entryPath The entry path
|
let regMatch = new RegExp('^' + _.split(terms, ' ')[0])
|
||||||
* @return {Promise<String>} Starter content
|
return db.Entry.find({
|
||||||
*/
|
_id: { $regex: regMatch }
|
||||||
getStarter(entryPath) {
|
}, '_id')
|
||||||
|
.sort('_id')
|
||||||
let self = this;
|
.limit(5)
|
||||||
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')));
|
.exec()
|
||||||
|
.then((matches) => {
|
||||||
return fs.readFileAsync(path.join(ROOTPATH, 'client/content/create.md'), 'utf8').then((contents) => {
|
return {
|
||||||
return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle);
|
match: hits,
|
||||||
});
|
suggest: (matches) ? _.map(matches, '_id') : []
|
||||||
|
}
|
||||||
},
|
})
|
||||||
|
} else {
|
||||||
/**
|
return {
|
||||||
* Searches entries based on terms.
|
match: _.filter(hits, (h) => { return h._doc.score >= 1 }),
|
||||||
*
|
suggest: []
|
||||||
* @param {String} terms The terms to search for
|
}
|
||||||
* @return {Promise<Object>} Promise of the search results
|
}
|
||||||
*/
|
}).catch((err) => {
|
||||||
search(terms) {
|
winston.error(err)
|
||||||
|
return {
|
||||||
let self = this;
|
match: [],
|
||||||
terms = _.chain(terms)
|
suggest: []
|
||||||
.deburr()
|
}
|
||||||
.toLower()
|
})
|
||||||
.trim()
|
}
|
||||||
.replace(/[^a-z0-9\- ]/g, '')
|
|
||||||
.split(' ')
|
}
|
||||||
.filter((f) => { return !_.isEmpty(f); })
|
|
||||||
.join(' ')
|
|
||||||
.value();
|
|
||||||
|
|
||||||
return db.Entry.find(
|
|
||||||
{ $text: { $search: terms } },
|
|
||||||
{ score: { $meta: "textScore" }, title: 1 }
|
|
||||||
)
|
|
||||||
.sort({ score: { $meta: "textScore" } })
|
|
||||||
.limit(10)
|
|
||||||
.exec()
|
|
||||||
.then((hits) => {
|
|
||||||
|
|
||||||
if(hits.length < 5) {
|
|
||||||
let regMatch = new RegExp('^' + _.split(terms, ' ')[0]);
|
|
||||||
return db.Entry.find({
|
|
||||||
_id: { $regex: regMatch }
|
|
||||||
}, '_id')
|
|
||||||
.sort('_id')
|
|
||||||
.limit(5)
|
|
||||||
.exec()
|
|
||||||
.then((matches) => {
|
|
||||||
return {
|
|
||||||
match: hits,
|
|
||||||
suggest: (matches) ? _.map(matches, '_id') : []
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
match: _.filter(hits, (h) => { return h._doc.score >= 1; }),
|
|
||||||
suggest: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
|
|
||||||
winston.error(err);
|
|
||||||
return {
|
|
||||||
match: [],
|
|
||||||
suggest: []
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -1,258 +1,231 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var Git = require("git-wrapper2-promise"),
|
const Git = require('git-wrapper2-promise')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
path = require('path'),
|
const path = require('path')
|
||||||
os = require('os'),
|
const fs = Promise.promisifyAll(require('fs'))
|
||||||
fs = Promise.promisifyAll(require("fs")),
|
const _ = require('lodash')
|
||||||
moment = require('moment'),
|
const URL = require('url')
|
||||||
_ = require('lodash'),
|
|
||||||
URL = require('url');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git Model
|
* Git Model
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_git: null,
|
_git: null,
|
||||||
_url: '',
|
_url: '',
|
||||||
_repo: {
|
_repo: {
|
||||||
path: '',
|
path: '',
|
||||||
branch: 'master',
|
branch: 'master',
|
||||||
exists: false
|
exists: false
|
||||||
},
|
},
|
||||||
_signature: {
|
_signature: {
|
||||||
name: 'Wiki',
|
name: 'Wiki',
|
||||||
email: 'user@example.com'
|
email: 'user@example.com'
|
||||||
},
|
},
|
||||||
_opts: {
|
_opts: {
|
||||||
clone: {},
|
clone: {},
|
||||||
push: {}
|
push: {}
|
||||||
},
|
},
|
||||||
onReady: null,
|
onReady: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Git model
|
* Initialize Git model
|
||||||
*
|
*
|
||||||
* @return {Object} Git model instance
|
* @return {Object} Git model instance
|
||||||
*/
|
*/
|
||||||
init() {
|
init () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
// -> Build repository path
|
||||||
//-> Build repository path
|
|
||||||
|
if (_.isEmpty(appconfig.paths.repo)) {
|
||||||
if(_.isEmpty(appconfig.paths.repo)) {
|
self._repo.path = path.join(ROOTPATH, 'repo')
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo');
|
} else {
|
||||||
} else {
|
self._repo.path = appconfig.paths.repo
|
||||||
self._repo.path = appconfig.paths.repo;
|
}
|
||||||
}
|
|
||||||
|
// -> Initialize repository
|
||||||
//-> Initialize repository
|
|
||||||
|
self.onReady = self._initRepo(appconfig)
|
||||||
self.onReady = self._initRepo(appconfig);
|
|
||||||
|
// Define signature
|
||||||
// Define signature
|
|
||||||
|
self._signature.name = appconfig.git.signature.name || 'Wiki'
|
||||||
self._signature.name = appconfig.git.signature.name || 'Wiki';
|
self._signature.email = appconfig.git.signature.email || 'user@example.com'
|
||||||
self._signature.email = appconfig.git.signature.email || 'user@example.com';
|
|
||||||
|
return self
|
||||||
return self;
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Initialize Git repository
|
||||||
/**
|
*
|
||||||
* Initialize Git repository
|
* @param {Object} appconfig The application config
|
||||||
*
|
* @return {Object} Promise
|
||||||
* @param {Object} appconfig The application config
|
*/
|
||||||
* @return {Object} Promise
|
_initRepo (appconfig) {
|
||||||
*/
|
let self = this
|
||||||
_initRepo(appconfig) {
|
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...')
|
||||||
let self = this;
|
|
||||||
|
// -> Check if path is accessible
|
||||||
winston.info('[' + PROCNAME + '][GIT] Checking Git repository...');
|
|
||||||
|
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
||||||
//-> Check if path is accessible
|
if (err.code !== 'EEXIST') {
|
||||||
|
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.')
|
||||||
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
}
|
||||||
if(err.code !== 'EEXIST') {
|
}).then(() => {
|
||||||
winston.error('[' + PROCNAME + '][GIT] Invalid Git repository path or missing permissions.');
|
self._git = new Git({ 'git-dir': self._repo.path })
|
||||||
}
|
|
||||||
}).then(() => {
|
// -> Check if path already contains a git working folder
|
||||||
|
|
||||||
self._git = new Git({ 'git-dir': self._repo.path });
|
return self._git.isRepo().then((isRepo) => {
|
||||||
|
self._repo.exists = isRepo
|
||||||
//-> Check if path already contains a git working folder
|
return (!isRepo) ? self._git.exec('init') : true
|
||||||
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
return self._git.isRepo().then((isRepo) => {
|
self._repo.exists = false
|
||||||
self._repo.exists = isRepo;
|
})
|
||||||
return (!isRepo) ? self._git.exec('init') : true;
|
}).then(() => {
|
||||||
}).catch((err) => {
|
// Initialize remote
|
||||||
self._repo.exists = false;
|
|
||||||
});
|
let urlObj = URL.parse(appconfig.git.url)
|
||||||
|
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '')
|
||||||
}).then(() => {
|
self._url = URL.format(urlObj)
|
||||||
|
|
||||||
// Initialize remote
|
return self._git.exec('remote', 'show').then((cProc) => {
|
||||||
|
let out = cProc.stdout.toString()
|
||||||
let urlObj = URL.parse(appconfig.git.url);
|
if (_.includes(out, 'origin')) {
|
||||||
urlObj.auth = appconfig.git.auth.username + ((appconfig.git.auth.type !== 'ssh') ? ':' + appconfig.git.auth.password : '');
|
return true
|
||||||
self._url = URL.format(urlObj);
|
} else {
|
||||||
|
return Promise.join(
|
||||||
return self._git.exec('remote', 'show').then((cProc) => {
|
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
||||||
let out = cProc.stdout.toString();
|
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
||||||
if(_.includes(out, 'origin')) {
|
).then(() => {
|
||||||
return true;
|
return self._git.exec('remote', ['add', 'origin', self._url])
|
||||||
} else {
|
})
|
||||||
return Promise.join(
|
}
|
||||||
self._git.exec('config', ['--local', 'user.name', self._signature.name]),
|
})
|
||||||
self._git.exec('config', ['--local', 'user.email', self._signature.email])
|
}).catch((err) => {
|
||||||
).then(() => {
|
winston.error('[' + PROCNAME + '][GIT] Git remote error!')
|
||||||
return self._git.exec('remote', ['add', 'origin', self._url]);
|
throw err
|
||||||
});
|
}).then(() => {
|
||||||
}
|
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.')
|
||||||
});
|
return true
|
||||||
|
})
|
||||||
}).catch((err) => {
|
},
|
||||||
winston.error('[' + PROCNAME + '][GIT] Git remote error!');
|
|
||||||
throw err;
|
/**
|
||||||
}).then(() => {
|
* Gets the repo path.
|
||||||
winston.info('[' + PROCNAME + '][GIT] Git repository is OK.');
|
*
|
||||||
return true;
|
* @return {String} The repo path.
|
||||||
});
|
*/
|
||||||
|
getRepoPath () {
|
||||||
},
|
return this._repo.path || path.join(ROOTPATH, 'repo')
|
||||||
|
},
|
||||||
/**
|
|
||||||
* Gets the repo path.
|
/**
|
||||||
*
|
* Sync with the remote repository
|
||||||
* @return {String} The repo path.
|
*
|
||||||
*/
|
* @return {Promise} Resolve on sync success
|
||||||
getRepoPath() {
|
*/
|
||||||
|
resync () {
|
||||||
return this._repo.path || path.join(ROOTPATH, 'repo');
|
let self = this
|
||||||
|
|
||||||
},
|
// Fetch
|
||||||
|
|
||||||
/**
|
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...')
|
||||||
* Sync with the remote repository
|
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
||||||
*
|
winston.info('[' + PROCNAME + '][GIT] Pull completed.')
|
||||||
* @return {Promise} Resolve on sync success
|
})
|
||||||
*/
|
.catch((err) => {
|
||||||
resync() {
|
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!')
|
||||||
|
throw err
|
||||||
let self = this;
|
})
|
||||||
|
.then(() => {
|
||||||
// Fetch
|
// Check for changes
|
||||||
|
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing pull from remote repository...');
|
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
||||||
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
let out = cProc.stdout.toString()
|
||||||
winston.info('[' + PROCNAME + '][GIT] Pull completed.');
|
|
||||||
})
|
if (_.includes(out, 'commit')) {
|
||||||
.catch((err) => {
|
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...')
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to fetch from git origin!');
|
return self._git.push('origin', self._repo.branch).then(() => {
|
||||||
throw err;
|
return winston.info('[' + PROCNAME + '][GIT] Push completed.')
|
||||||
})
|
})
|
||||||
.then(() => {
|
} else {
|
||||||
|
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.')
|
||||||
// Check for changes
|
}
|
||||||
|
|
||||||
return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => {
|
return true
|
||||||
let out = cProc.stdout.toString();
|
})
|
||||||
|
})
|
||||||
if(_.includes(out, 'commit')) {
|
.catch((err) => {
|
||||||
|
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!')
|
||||||
winston.info('[' + PROCNAME + '][GIT] Performing push to remote repository...');
|
throw err
|
||||||
return self._git.push('origin', self._repo.branch).then(() => {
|
})
|
||||||
return winston.info('[' + PROCNAME + '][GIT] Push completed.');
|
},
|
||||||
});
|
|
||||||
|
/**
|
||||||
} else {
|
* Commits a document.
|
||||||
|
*
|
||||||
winston.info('[' + PROCNAME + '][GIT] Push skipped. Repository is already in sync.');
|
* @param {String} entryPath The entry path
|
||||||
|
* @return {Promise} Resolve on commit success
|
||||||
}
|
*/
|
||||||
|
commitDocument (entryPath) {
|
||||||
return true;
|
let self = this
|
||||||
|
let gitFilePath = entryPath + '.md'
|
||||||
});
|
let commitMsg = ''
|
||||||
|
|
||||||
})
|
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
||||||
.catch((err) => {
|
let out = cProc.stdout.toString()
|
||||||
winston.error('[' + PROCNAME + '][GIT] Unable to push changes to remote!');
|
return _.includes(out, gitFilePath)
|
||||||
throw err;
|
}).then((isTracked) => {
|
||||||
});
|
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath
|
||||||
|
return self._git.add(gitFilePath)
|
||||||
},
|
}).then(() => {
|
||||||
|
return self._git.commit(commitMsg).catch((err) => {
|
||||||
/**
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
* Commits a document.
|
})
|
||||||
*
|
})
|
||||||
* @param {String} entryPath The entry path
|
},
|
||||||
* @return {Promise} Resolve on commit success
|
|
||||||
*/
|
/**
|
||||||
commitDocument(entryPath) {
|
* Move a document.
|
||||||
|
*
|
||||||
let self = this;
|
* @param {String} entryPath The current entry path
|
||||||
let gitFilePath = entryPath + '.md';
|
* @param {String} newEntryPath The new entry path
|
||||||
let commitMsg = '';
|
* @return {Promise<Boolean>} Resolve on success
|
||||||
|
*/
|
||||||
return self._git.exec('ls-files', gitFilePath).then((cProc) => {
|
moveDocument (entryPath, newEntryPath) {
|
||||||
let out = cProc.stdout.toString();
|
let self = this
|
||||||
return _.includes(out, gitFilePath);
|
let gitFilePath = entryPath + '.md'
|
||||||
}).then((isTracked) => {
|
let gitNewFilePath = newEntryPath + '.md'
|
||||||
commitMsg = (isTracked) ? 'Updated ' + gitFilePath : 'Added ' + gitFilePath;
|
|
||||||
return self._git.add(gitFilePath);
|
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
||||||
}).then(() => {
|
let out = cProc.stdout.toString()
|
||||||
return self._git.commit(commitMsg).catch((err) => {
|
if (_.includes(out, 'fatal')) {
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
|
||||||
});
|
throw new Error(errorMsg)
|
||||||
});
|
}
|
||||||
|
return true
|
||||||
},
|
})
|
||||||
|
},
|
||||||
/**
|
|
||||||
* Move a document.
|
/**
|
||||||
*
|
* Commits uploads changes.
|
||||||
* @param {String} entryPath The current entry path
|
*
|
||||||
* @param {String} newEntryPath The new entry path
|
* @param {String} msg The commit message
|
||||||
* @return {Promise<Boolean>} Resolve on success
|
* @return {Promise} Resolve on commit success
|
||||||
*/
|
*/
|
||||||
moveDocument(entryPath, newEntryPath) {
|
commitUploads (msg) {
|
||||||
|
let self = this
|
||||||
let self = this;
|
msg = msg || 'Uploads repository sync'
|
||||||
let gitFilePath = entryPath + '.md';
|
|
||||||
let gitNewFilePath = newEntryPath + '.md';
|
return self._git.add('uploads').then(() => {
|
||||||
|
return self._git.commit(msg).catch((err) => {
|
||||||
return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => {
|
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
||||||
let out = cProc.stdout.toString();
|
})
|
||||||
if(_.includes(out, 'fatal')) {
|
})
|
||||||
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')));
|
}
|
||||||
throw new Error(errorMsg);
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commits uploads changes.
|
|
||||||
*
|
|
||||||
* @param {String} msg The commit message
|
|
||||||
* @return {Promise} Resolve on commit success
|
|
||||||
*/
|
|
||||||
commitUploads(msg) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
msg = msg || "Uploads repository sync";
|
|
||||||
|
|
||||||
return self._git.add('uploads').then(() => {
|
|
||||||
return self._git.commit(msg).catch((err) => {
|
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -1,32 +1,26 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal Authentication
|
* Internal Authentication
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_curKey: false,
|
_curKey: false,
|
||||||
|
|
||||||
init(inKey) {
|
init (inKey) {
|
||||||
|
this._curKey = inKey
|
||||||
|
|
||||||
this._curKey = inKey;
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
return this;
|
generateKey () {
|
||||||
|
return crypto.randomBytes(20).toString('hex')
|
||||||
|
},
|
||||||
|
|
||||||
},
|
validateKey (inKey) {
|
||||||
|
return inKey === this._curKey
|
||||||
|
}
|
||||||
|
|
||||||
generateKey() {
|
}
|
||||||
|
|
||||||
return crypto.randomBytes(20).toString('hex');
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
validateKey(inKey) {
|
|
||||||
|
|
||||||
return inKey === this._curKey;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -1,187 +1,176 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
multer = require('multer'),
|
const multer = require('multer')
|
||||||
os = require('os'),
|
const os = require('os')
|
||||||
_ = require('lodash');
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local Data Storage
|
* Local Data Storage
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_uploadsPath: './repo/uploads',
|
_uploadsPath: './repo/uploads',
|
||||||
_uploadsThumbsPath: './data/thumbs',
|
_uploadsThumbsPath: './data/thumbs',
|
||||||
|
|
||||||
uploadImgHandler: null,
|
uploadImgHandler: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Local Data Storage model
|
* Initialize Local Data Storage model
|
||||||
*
|
*
|
||||||
* @return {Object} Local Data Storage model instance
|
* @return {Object} Local Data Storage model instance
|
||||||
*/
|
*/
|
||||||
init() {
|
init () {
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
|
||||||
|
this.createBaseDirectories(appconfig)
|
||||||
this.createBaseDirectories(appconfig);
|
this.initMulter(appconfig)
|
||||||
this.initMulter(appconfig);
|
|
||||||
|
return this
|
||||||
return this;
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Init Multer upload handlers
|
||||||
/**
|
*
|
||||||
* Init Multer upload handlers
|
* @param {Object} appconfig The application config
|
||||||
*
|
* @return {boolean} Void
|
||||||
* @param {Object} appconfig The application config
|
*/
|
||||||
* @return {boolean} Void
|
initMulter (appconfig) {
|
||||||
*/
|
let maxFileSizes = {
|
||||||
initMulter(appconfig) {
|
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
||||||
|
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
||||||
let maxFileSizes = {
|
}
|
||||||
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
|
||||||
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
// -> IMAGES
|
||||||
};
|
|
||||||
|
this.uploadImgHandler = multer({
|
||||||
//-> IMAGES
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, f, cb) => {
|
||||||
this.uploadImgHandler = multer({
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
storage: multer.diskStorage({
|
}
|
||||||
destination: (req, f, cb) => {
|
}),
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
fileFilter: (req, f, cb) => {
|
||||||
}
|
// -> Check filesize
|
||||||
}),
|
|
||||||
fileFilter: (req, f, cb) => {
|
if (f.size > maxFileSizes.img) {
|
||||||
|
return cb(null, false)
|
||||||
//-> Check filesize
|
}
|
||||||
|
|
||||||
if(f.size > maxFileSizes.img) {
|
// -> Check MIME type (quick check only)
|
||||||
return cb(null, false);
|
|
||||||
}
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
||||||
|
return cb(null, false)
|
||||||
//-> Check MIME type (quick check only)
|
}
|
||||||
|
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
cb(null, true)
|
||||||
return cb(null, false);
|
}
|
||||||
}
|
}).array('imgfile', 20)
|
||||||
|
|
||||||
cb(null, true);
|
// -> FILES
|
||||||
}
|
|
||||||
}).array('imgfile', 20);
|
this.uploadFileHandler = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
//-> FILES
|
destination: (req, f, cb) => {
|
||||||
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
this.uploadFileHandler = multer({
|
}
|
||||||
storage: multer.diskStorage({
|
}),
|
||||||
destination: (req, f, cb) => {
|
fileFilter: (req, f, cb) => {
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
|
// -> Check filesize
|
||||||
}
|
|
||||||
}),
|
if (f.size > maxFileSizes.file) {
|
||||||
fileFilter: (req, f, cb) => {
|
return cb(null, false)
|
||||||
|
}
|
||||||
//-> Check filesize
|
|
||||||
|
cb(null, true)
|
||||||
if(f.size > maxFileSizes.file) {
|
}
|
||||||
return cb(null, false);
|
}).array('binfile', 20)
|
||||||
}
|
|
||||||
|
return true
|
||||||
cb(null, true);
|
},
|
||||||
}
|
|
||||||
}).array('binfile', 20);
|
/**
|
||||||
|
* Creates a base directories (Synchronous).
|
||||||
return true;
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
},
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
/**
|
createBaseDirectories (appconfig) {
|
||||||
* Creates a base directories (Synchronous).
|
winston.info('[SERVER] Checking data directories...')
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
try {
|
||||||
* @return {Void} Void
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
|
||||||
*/
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
|
||||||
createBaseDirectories(appconfig) {
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
|
||||||
winston.info('[SERVER] Checking data directories...');
|
|
||||||
|
if (os.type() !== 'Windows_NT') {
|
||||||
try {
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644')
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data));
|
}
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'));
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'));
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
|
||||||
|
|
||||||
if(os.type() !== 'Windows_NT') {
|
if (os.type() !== 'Windows_NT') {
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644');
|
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644')
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo));
|
winston.error(err)
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'));
|
}
|
||||||
|
|
||||||
if(os.type() !== 'Windows_NT') {
|
winston.info('[SERVER] Data and Repository directories are OK.')
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
|
|
||||||
}
|
return
|
||||||
|
},
|
||||||
} catch (err) {
|
|
||||||
winston.error(err);
|
/**
|
||||||
}
|
* Gets the uploads path.
|
||||||
|
*
|
||||||
winston.info('[SERVER] Data and Repository directories are OK.');
|
* @return {String} The uploads path.
|
||||||
|
*/
|
||||||
return;
|
getUploadsPath () {
|
||||||
|
return this._uploadsPath
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the uploads path.
|
* Gets the thumbnails folder path.
|
||||||
*
|
*
|
||||||
* @return {String} The uploads path.
|
* @return {String} The thumbs path.
|
||||||
*/
|
*/
|
||||||
getUploadsPath() {
|
getThumbsPath () {
|
||||||
return this._uploadsPath;
|
return this._uploadsThumbsPath
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the thumbnails folder path.
|
* Check if filename is valid and unique
|
||||||
*
|
*
|
||||||
* @return {String} The thumbs path.
|
* @param {String} f The filename
|
||||||
*/
|
* @param {String} fld The containing folder
|
||||||
getThumbsPath() {
|
* @param {boolean} isImage Indicates if image
|
||||||
return this._uploadsThumbsPath;
|
* @return {Promise<String>} Promise of the accepted filename
|
||||||
},
|
*/
|
||||||
|
validateUploadsFilename (f, fld, isImage) {
|
||||||
/**
|
let fObj = path.parse(f)
|
||||||
* Check if filename is valid and unique
|
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9-]+/g, '')
|
||||||
*
|
let fext = _.toLower(fObj.ext)
|
||||||
* @param {String} f The filename
|
|
||||||
* @param {String} fld The containing folder
|
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
||||||
* @param {boolean} isImage Indicates if image
|
fext = '.png'
|
||||||
* @return {Promise<String>} Promise of the accepted filename
|
}
|
||||||
*/
|
|
||||||
validateUploadsFilename(f, fld, isImage) {
|
f = fname + fext
|
||||||
|
let fpath = path.resolve(this._uploadsPath, fld, f)
|
||||||
let fObj = path.parse(f);
|
|
||||||
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
|
return fs.statAsync(fpath).then((s) => {
|
||||||
let fext = _.toLower(fObj.ext);
|
throw new Error('File ' + f + ' already exists.')
|
||||||
|
}).catch((err) => {
|
||||||
if(isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
if (err.code === 'ENOENT') {
|
||||||
fext = '.png';
|
return f
|
||||||
}
|
}
|
||||||
|
throw err
|
||||||
f = fname + fext;
|
})
|
||||||
let fpath = path.resolve(this._uploadsPath, fld, f);
|
}
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((s) => {
|
}
|
||||||
throw new Error('File ' + f + ' already exists.');
|
|
||||||
}).catch((err) => {
|
|
||||||
if(err.code === 'ENOENT') {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -1,292 +1,255 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
var path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
readChunk = require('read-chunk'),
|
const readChunk = require('read-chunk')
|
||||||
fileType = require('file-type'),
|
const fileType = require('file-type')
|
||||||
mime = require('mime-types'),
|
const mime = require('mime-types')
|
||||||
farmhash = require('farmhash'),
|
const farmhash = require('farmhash')
|
||||||
moment = require('moment'),
|
const chokidar = require('chokidar')
|
||||||
chokidar = require('chokidar'),
|
const sharp = require('sharp')
|
||||||
sharp = require('sharp'),
|
const _ = require('lodash')
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads - Agent
|
* Uploads - Agent
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_uploadsPath: './repo/uploads',
|
_uploadsPath: './repo/uploads',
|
||||||
_uploadsThumbsPath: './data/thumbs',
|
_uploadsThumbsPath: './data/thumbs',
|
||||||
|
|
||||||
_watcher: null,
|
_watcher: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Uploads model
|
* Initialize Uploads model
|
||||||
*
|
*
|
||||||
* @return {Object} Uploads model instance
|
* @return {Object} Uploads model instance
|
||||||
*/
|
*/
|
||||||
init() {
|
init () {
|
||||||
|
let self = this
|
||||||
let self = this;
|
|
||||||
|
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
|
||||||
|
// Disable Sharp cache, as it cause file locks issues when deleting uploads.
|
||||||
// Disable Sharp cache, as it cause file locks issues when deleting uploads.
|
sharp.cache(false)
|
||||||
sharp.cache(false);
|
|
||||||
|
return self
|
||||||
return self;
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Watch the uploads folder for changes
|
||||||
/**
|
*
|
||||||
* Watch the uploads folder for changes
|
* @return {Void} Void
|
||||||
*
|
*/
|
||||||
* @return {Void} Void
|
watch () {
|
||||||
*/
|
let self = this
|
||||||
watch() {
|
|
||||||
|
self._watcher = chokidar.watch(self._uploadsPath, {
|
||||||
let self = this;
|
persistent: true,
|
||||||
|
ignoreInitial: true,
|
||||||
self._watcher = chokidar.watch(self._uploadsPath, {
|
cwd: self._uploadsPath,
|
||||||
persistent: true,
|
depth: 1,
|
||||||
ignoreInitial: true,
|
awaitWriteFinish: true
|
||||||
cwd: self._uploadsPath,
|
})
|
||||||
depth: 1,
|
|
||||||
awaitWriteFinish: true
|
// -> Add new upload file
|
||||||
});
|
|
||||||
|
self._watcher.on('add', (p) => {
|
||||||
//-> Add new upload file
|
let pInfo = self.parseUploadsRelPath(p)
|
||||||
|
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
||||||
self._watcher.on('add', (p) => {
|
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
|
||||||
|
}).then(() => {
|
||||||
let pInfo = self.parseUploadsRelPath(p);
|
return git.commitUploads('Uploaded ' + p)
|
||||||
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
})
|
||||||
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true });
|
})
|
||||||
}).then(() => {
|
|
||||||
return git.commitUploads('Uploaded ' + p);
|
// -> Remove upload file
|
||||||
});
|
|
||||||
|
self._watcher.on('unlink', (p) => {
|
||||||
});
|
return git.commitUploads('Deleted/Renamed ' + p)
|
||||||
|
})
|
||||||
//-> Remove upload file
|
},
|
||||||
|
|
||||||
self._watcher.on('unlink', (p) => {
|
/**
|
||||||
|
* Initial Uploads scan
|
||||||
let pInfo = self.parseUploadsRelPath(p);
|
*
|
||||||
return git.commitUploads('Deleted/Renamed ' + p);
|
* @return {Promise<Void>} Promise of the scan operation
|
||||||
|
*/
|
||||||
});
|
initialScan () {
|
||||||
|
let self = this
|
||||||
},
|
|
||||||
|
return fs.readdirAsync(self._uploadsPath).then((ls) => {
|
||||||
/**
|
// Get all folders
|
||||||
* Initial Uploads scan
|
|
||||||
*
|
return Promise.map(ls, (f) => {
|
||||||
* @return {Promise<Void>} Promise of the scan operation
|
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } })
|
||||||
*/
|
}).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => {
|
||||||
initialScan() {
|
let folderNames = _.map(arrDirs, 'filename')
|
||||||
|
folderNames.unshift('')
|
||||||
let self = this;
|
|
||||||
|
// Add folders to DB
|
||||||
return fs.readdirAsync(self._uploadsPath).then((ls) => {
|
|
||||||
|
return db.UplFolder.remove({}).then(() => {
|
||||||
// Get all folders
|
return db.UplFolder.insertMany(_.map(folderNames, (f) => {
|
||||||
|
return {
|
||||||
return Promise.map(ls, (f) => {
|
_id: 'f:' + f,
|
||||||
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
|
name: f
|
||||||
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
|
}
|
||||||
|
}))
|
||||||
let folderNames = _.map(arrDirs, 'filename');
|
}).then(() => {
|
||||||
folderNames.unshift('');
|
// Travel each directory and scan files
|
||||||
|
|
||||||
// Add folders to DB
|
let allFiles = []
|
||||||
|
|
||||||
return db.UplFolder.remove({}).then(() => {
|
return Promise.map(folderNames, (fldName) => {
|
||||||
return db.UplFolder.insertMany(_.map(folderNames, (f) => {
|
let fldPath = path.join(self._uploadsPath, fldName)
|
||||||
return {
|
return fs.readdirAsync(fldPath).then((fList) => {
|
||||||
_id: 'f:' + f,
|
return Promise.map(fList, (f) => {
|
||||||
name: f
|
return upl.processFile(fldName, f).then((mData) => {
|
||||||
};
|
if (mData) {
|
||||||
}));
|
allFiles.push(mData)
|
||||||
}).then(() => {
|
}
|
||||||
|
return true
|
||||||
// Travel each directory and scan files
|
})
|
||||||
|
}, {concurrency: 3})
|
||||||
let allFiles = [];
|
})
|
||||||
|
}, {concurrency: 1}).finally(() => {
|
||||||
return Promise.map(folderNames, (fldName) => {
|
// Add files to DB
|
||||||
|
|
||||||
let fldPath = path.join(self._uploadsPath, fldName);
|
return db.UplFile.remove({}).then(() => {
|
||||||
return fs.readdirAsync(fldPath).then((fList) => {
|
if (_.isArray(allFiles) && allFiles.length > 0) {
|
||||||
return Promise.map(fList, (f) => {
|
return db.UplFile.insertMany(allFiles)
|
||||||
return upl.processFile(fldName, f).then((mData) => {
|
} else {
|
||||||
if(mData) {
|
return true
|
||||||
allFiles.push(mData);
|
}
|
||||||
}
|
})
|
||||||
return true;
|
})
|
||||||
});
|
})
|
||||||
}, {concurrency: 3});
|
})
|
||||||
});
|
}).then(() => {
|
||||||
}, {concurrency: 1}).finally(() => {
|
// Watch for new changes
|
||||||
|
|
||||||
// Add files to DB
|
return upl.watch()
|
||||||
|
})
|
||||||
return db.UplFile.remove({}).then(() => {
|
},
|
||||||
if(_.isArray(allFiles) && allFiles.length > 0) {
|
|
||||||
return db.UplFile.insertMany(allFiles);
|
/**
|
||||||
} else {
|
* Parse relative Uploads path
|
||||||
return true;
|
*
|
||||||
}
|
* @param {String} f Relative Uploads path
|
||||||
});
|
* @return {Object} Parsed path (folder and filename)
|
||||||
|
*/
|
||||||
});
|
parseUploadsRelPath (f) {
|
||||||
|
let fObj = path.parse(f)
|
||||||
});
|
return {
|
||||||
|
folder: fObj.dir,
|
||||||
});
|
filename: fObj.base
|
||||||
|
}
|
||||||
}).then(() => {
|
},
|
||||||
|
|
||||||
// Watch for new changes
|
/**
|
||||||
|
* Get metadata from file and generate thumbnails if necessary
|
||||||
return upl.watch();
|
*
|
||||||
|
* @param {String} fldName The folder name
|
||||||
});
|
* @param {String} f The filename
|
||||||
|
* @return {Promise<Object>} Promise of the file metadata
|
||||||
},
|
*/
|
||||||
|
processFile (fldName, f) {
|
||||||
/**
|
let self = this
|
||||||
* Parse relative Uploads path
|
|
||||||
*
|
let fldPath = path.join(self._uploadsPath, fldName)
|
||||||
* @param {String} f Relative Uploads path
|
let fPath = path.join(fldPath, f)
|
||||||
* @return {Object} Parsed path (folder and filename)
|
let fPathObj = path.parse(fPath)
|
||||||
*/
|
let fUid = farmhash.fingerprint32(fldName + '/' + f)
|
||||||
parseUploadsRelPath(f) {
|
|
||||||
|
return fs.statAsync(fPath).then((s) => {
|
||||||
let fObj = path.parse(f);
|
if (!s.isFile()) { return false }
|
||||||
return {
|
|
||||||
folder: fObj.dir,
|
// Get MIME info
|
||||||
filename: fObj.base
|
|
||||||
};
|
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262))
|
||||||
|
if (_.isNil(mimeInfo)) {
|
||||||
},
|
mimeInfo = {
|
||||||
|
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
|
||||||
/**
|
}
|
||||||
* Get metadata from file and generate thumbnails if necessary
|
}
|
||||||
*
|
|
||||||
* @param {String} fldName The folder name
|
// Images
|
||||||
* @param {String} f The filename
|
|
||||||
* @return {Promise<Object>} Promise of the file metadata
|
if (s.size < 3145728) { // ignore files larger than 3MB
|
||||||
*/
|
if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
processFile(fldName, f) {
|
return self.getImageMetadata(fPath).then((mImgData) => {
|
||||||
|
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'))
|
||||||
let self = this;
|
let cacheThumbnailPathStr = path.format(cacheThumbnailPath)
|
||||||
|
|
||||||
let fldPath = path.join(self._uploadsPath, fldName);
|
let mData = {
|
||||||
let fPath = path.join(fldPath, f);
|
_id: fUid,
|
||||||
let fPathObj = path.parse(fPath);
|
category: 'image',
|
||||||
let fUid = farmhash.fingerprint32(fldName + '/' + f);
|
mime: mimeInfo.mime,
|
||||||
|
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
|
||||||
return fs.statAsync(fPath).then((s) => {
|
folder: 'f:' + fldName,
|
||||||
|
filename: f,
|
||||||
if(!s.isFile()) { return false; }
|
basename: fPathObj.name,
|
||||||
|
filesize: s.size
|
||||||
// Get MIME info
|
}
|
||||||
|
|
||||||
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
|
// Generate thumbnail
|
||||||
if(_.isNil(mimeInfo)) {
|
|
||||||
mimeInfo = {
|
return fs.statAsync(cacheThumbnailPathStr).then((st) => {
|
||||||
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
|
return st.isFile()
|
||||||
};
|
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||||
}
|
return false
|
||||||
|
}).then((thumbExists) => {
|
||||||
// Images
|
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
|
||||||
|
return self.generateThumbnail(fPath, cacheThumbnailPathStr)
|
||||||
if(s.size < 3145728) { // ignore files larger than 3MB
|
}).return(mData)
|
||||||
if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
})
|
||||||
return self.getImageMetadata(fPath).then((mImgData) => {
|
})
|
||||||
|
}
|
||||||
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
|
}
|
||||||
let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
|
|
||||||
|
// Other Files
|
||||||
let mData = {
|
|
||||||
_id: fUid,
|
return {
|
||||||
category: 'image',
|
_id: fUid,
|
||||||
mime: mimeInfo.mime,
|
category: 'binary',
|
||||||
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
|
mime: mimeInfo.mime,
|
||||||
folder: 'f:' + fldName,
|
folder: 'f:' + fldName,
|
||||||
filename: f,
|
filename: f,
|
||||||
basename: fPathObj.name,
|
basename: fPathObj.name,
|
||||||
filesize: s.size
|
filesize: s.size
|
||||||
};
|
}
|
||||||
|
})
|
||||||
// Generate thumbnail
|
},
|
||||||
|
|
||||||
return fs.statAsync(cacheThumbnailPathStr).then((st) => {
|
/**
|
||||||
return st.isFile();
|
* Generate thumbnail of image
|
||||||
}).catch((err) => {
|
*
|
||||||
return false;
|
* @param {String} sourcePath The source path
|
||||||
}).then((thumbExists) => {
|
* @param {String} destPath The destination path
|
||||||
|
* @return {Promise<Object>} Promise returning the resized image info
|
||||||
return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => {
|
*/
|
||||||
return self.generateThumbnail(fPath, cacheThumbnailPathStr);
|
generateThumbnail (sourcePath, destPath) {
|
||||||
}).return(mData);
|
return sharp(sourcePath)
|
||||||
|
.withoutEnlargement()
|
||||||
});
|
.resize(150, 150)
|
||||||
|
.background('white')
|
||||||
});
|
.embed()
|
||||||
}
|
.flatten()
|
||||||
}
|
.toFormat('png')
|
||||||
|
.toFile(destPath)
|
||||||
// Other Files
|
},
|
||||||
|
|
||||||
return {
|
/**
|
||||||
_id: fUid,
|
* Gets the image metadata.
|
||||||
category: 'binary',
|
*
|
||||||
mime: mimeInfo.mime,
|
* @param {String} sourcePath The source path
|
||||||
folder: 'f:' + fldName,
|
* @return {Object} The image metadata.
|
||||||
filename: f,
|
*/
|
||||||
basename: fPathObj.name,
|
getImageMetadata (sourcePath) {
|
||||||
filesize: s.size
|
return sharp(sourcePath).metadata()
|
||||||
};
|
}
|
||||||
|
|
||||||
});
|
}
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate thumbnail of image
|
|
||||||
*
|
|
||||||
* @param {String} sourcePath The source path
|
|
||||||
* @param {String} destPath The destination path
|
|
||||||
* @return {Promise<Object>} Promise returning the resized image info
|
|
||||||
*/
|
|
||||||
generateThumbnail(sourcePath, destPath) {
|
|
||||||
|
|
||||||
return sharp(sourcePath)
|
|
||||||
.withoutEnlargement()
|
|
||||||
.resize(150,150)
|
|
||||||
.background('white')
|
|
||||||
.embed()
|
|
||||||
.flatten()
|
|
||||||
.toFormat('png')
|
|
||||||
.toFile(destPath);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the image metadata.
|
|
||||||
*
|
|
||||||
* @param {String} sourcePath The source path
|
|
||||||
* @return {Object} The image metadata.
|
|
||||||
*/
|
|
||||||
getImageMetadata(sourcePath) {
|
|
||||||
|
|
||||||
return sharp(sourcePath).metadata();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -1,308 +1,280 @@
|
|||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
const path = require('path'),
|
const path = require('path')
|
||||||
Promise = require('bluebird'),
|
const Promise = require('bluebird')
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
const fs = Promise.promisifyAll(require('fs-extra'))
|
||||||
multer = require('multer'),
|
const request = require('request')
|
||||||
request = require('request'),
|
const url = require('url')
|
||||||
url = require('url'),
|
const farmhash = require('farmhash')
|
||||||
farmhash = require('farmhash'),
|
const _ = require('lodash')
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$')
|
||||||
const maxDownloadFileSize = 3145728; // 3 MB
|
const maxDownloadFileSize = 3145728 // 3 MB
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads
|
* Uploads
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
_uploadsPath: './repo/uploads',
|
_uploadsPath: './repo/uploads',
|
||||||
_uploadsThumbsPath: './data/thumbs',
|
_uploadsThumbsPath: './data/thumbs',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Local Data Storage model
|
* Initialize Local Data Storage model
|
||||||
*
|
*
|
||||||
* @return {Object} Uploads model instance
|
* @return {Object} Uploads model instance
|
||||||
*/
|
*/
|
||||||
init() {
|
init () {
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
|
||||||
|
return this
|
||||||
return this;
|
},
|
||||||
|
|
||||||
},
|
/**
|
||||||
|
* Gets the thumbnails folder path.
|
||||||
/**
|
*
|
||||||
* Gets the thumbnails folder path.
|
* @return {String} The thumbs path.
|
||||||
*
|
*/
|
||||||
* @return {String} The thumbs path.
|
getThumbsPath () {
|
||||||
*/
|
return this._uploadsThumbsPath
|
||||||
getThumbsPath() {
|
},
|
||||||
return this._uploadsThumbsPath;
|
|
||||||
},
|
/**
|
||||||
|
* Gets the uploads folders.
|
||||||
/**
|
*
|
||||||
* Gets the uploads folders.
|
* @return {Array<String>} The uploads folders.
|
||||||
*
|
*/
|
||||||
* @return {Array<String>} The uploads folders.
|
getUploadsFolders () {
|
||||||
*/
|
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
||||||
getUploadsFolders() {
|
return (results) ? _.map(results, 'name') : [{ name: '' }]
|
||||||
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => {
|
})
|
||||||
return (results) ? _.map(results, 'name') : [{ name: '' }];
|
},
|
||||||
});
|
|
||||||
},
|
/**
|
||||||
|
* Creates an uploads folder.
|
||||||
/**
|
*
|
||||||
* Creates an uploads folder.
|
* @param {String} folderName The folder name
|
||||||
*
|
* @return {Promise} Promise of the operation
|
||||||
* @param {String} folderName The folder name
|
*/
|
||||||
* @return {Promise} Promise of the operation
|
createUploadsFolder (folderName) {
|
||||||
*/
|
let self = this
|
||||||
createUploadsFolder(folderName) {
|
|
||||||
|
folderName = _.kebabCase(_.trim(folderName))
|
||||||
let self = this;
|
|
||||||
|
if (_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
||||||
folderName = _.kebabCase(_.trim(folderName));
|
return Promise.resolve(self.getUploadsFolders())
|
||||||
|
}
|
||||||
if(_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
|
||||||
return Promise.resolve(self.getUploadsFolders());
|
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
||||||
}
|
return db.UplFolder.findOneAndUpdate({
|
||||||
|
_id: 'f:' + folderName
|
||||||
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
}, {
|
||||||
return db.UplFolder.findOneAndUpdate({
|
name: folderName
|
||||||
_id: 'f:' + folderName
|
}, {
|
||||||
}, {
|
upsert: true
|
||||||
name: folderName
|
})
|
||||||
}, {
|
}).then(() => {
|
||||||
upsert: true
|
return self.getUploadsFolders()
|
||||||
});
|
})
|
||||||
}).then(() => {
|
},
|
||||||
return self.getUploadsFolders();
|
|
||||||
});
|
/**
|
||||||
|
* Check if folder is valid and exists
|
||||||
},
|
*
|
||||||
|
* @param {String} folderName The folder name
|
||||||
/**
|
* @return {Boolean} True if valid
|
||||||
* Check if folder is valid and exists
|
*/
|
||||||
*
|
validateUploadsFolder (folderName) {
|
||||||
* @param {String} folderName The folder name
|
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
||||||
* @return {Boolean} True if valid
|
return (f) ? path.resolve(this._uploadsPath, folderName) : false
|
||||||
*/
|
})
|
||||||
validateUploadsFolder(folderName) {
|
},
|
||||||
|
|
||||||
return db.UplFolder.findOne({ name: folderName }).then((f) => {
|
/**
|
||||||
return (f) ? path.resolve(this._uploadsPath, folderName) : false;
|
* Adds one or more uploads files.
|
||||||
});
|
*
|
||||||
|
* @param {Array<Object>} arrFiles The uploads files
|
||||||
},
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
/**
|
addUploadsFiles (arrFiles) {
|
||||||
* Adds one or more uploads files.
|
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
||||||
*
|
// this._uploadsDb.Files.insert(arrFiles);
|
||||||
* @param {Array<Object>} arrFiles The uploads files
|
}
|
||||||
* @return {Void} Void
|
return
|
||||||
*/
|
},
|
||||||
addUploadsFiles(arrFiles) {
|
|
||||||
if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
/**
|
||||||
//this._uploadsDb.Files.insert(arrFiles);
|
* Gets the uploads files.
|
||||||
}
|
*
|
||||||
return;
|
* @param {String} cat Category type
|
||||||
},
|
* @param {String} fld Folder
|
||||||
|
* @return {Array<Object>} The files matching the query
|
||||||
/**
|
*/
|
||||||
* Gets the uploads files.
|
getUploadsFiles (cat, fld) {
|
||||||
*
|
return db.UplFile.find({
|
||||||
* @param {String} cat Category type
|
category: cat,
|
||||||
* @param {String} fld Folder
|
folder: 'f:' + fld
|
||||||
* @return {Array<Object>} The files matching the query
|
}).sort('filename').exec()
|
||||||
*/
|
},
|
||||||
getUploadsFiles(cat, fld) {
|
|
||||||
|
/**
|
||||||
return db.UplFile.find({
|
* Deletes an uploads file.
|
||||||
category: cat,
|
*
|
||||||
folder: 'f:' + fld
|
* @param {string} uid The file unique ID
|
||||||
}).sort('filename').exec();
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
},
|
deleteUploadsFile (uid) {
|
||||||
|
let self = this
|
||||||
/**
|
|
||||||
* Deletes an uploads file.
|
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
||||||
*
|
if (f) {
|
||||||
* @param {string} uid The file unique ID
|
return self.deleteUploadsFileTry(f, 0)
|
||||||
* @return {Promise} Promise of the operation
|
}
|
||||||
*/
|
return true
|
||||||
deleteUploadsFile(uid) {
|
})
|
||||||
|
},
|
||||||
let self = this;
|
|
||||||
|
deleteUploadsFileTry (f, attempt) {
|
||||||
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
|
let self = this
|
||||||
if(f) {
|
|
||||||
return self.deleteUploadsFileTry(f, 0);
|
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './'
|
||||||
}
|
|
||||||
return true;
|
return Promise.join(
|
||||||
});
|
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
||||||
},
|
fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
|
||||||
|
).catch((err) => {
|
||||||
deleteUploadsFileTry(f, attempt) {
|
if (err.code === 'EBUSY' && attempt < 5) {
|
||||||
|
return Promise.delay(100).then(() => {
|
||||||
let self = this;
|
return self.deleteUploadsFileTry(f, attempt + 1)
|
||||||
|
})
|
||||||
let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './';
|
} else {
|
||||||
|
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
|
||||||
return Promise.join(
|
return true
|
||||||
fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')),
|
}
|
||||||
fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename))
|
})
|
||||||
).catch((err) => {
|
},
|
||||||
if(err.code === 'EBUSY' && attempt < 5) {
|
|
||||||
return Promise.delay(100).then(() => {
|
/**
|
||||||
return self.deleteUploadsFileTry(f, attempt + 1);
|
* Downloads a file from url.
|
||||||
});
|
*
|
||||||
} else {
|
* @param {String} fFolder The folder
|
||||||
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.');
|
* @param {String} fUrl The full URL
|
||||||
return true;
|
* @return {Promise} Promise of the operation
|
||||||
}
|
*/
|
||||||
});
|
downloadFromUrl (fFolder, fUrl) {
|
||||||
|
let fUrlObj = url.parse(fUrl)
|
||||||
},
|
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
|
||||||
|
let destFolder = _.chain(fFolder).trim().toLower().value()
|
||||||
/**
|
|
||||||
* Downloads a file from url.
|
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
*
|
if (!destFolderPath) {
|
||||||
* @param {String} fFolder The folder
|
return Promise.reject(new Error('Invalid Folder'))
|
||||||
* @param {String} fUrl The full URL
|
}
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
||||||
downloadFromUrl(fFolder, fUrl) {
|
let destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
let self = this;
|
return new Promise((resolve, reject) => {
|
||||||
|
let rq = request({
|
||||||
let fUrlObj = url.parse(fUrl);
|
url: fUrl,
|
||||||
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'));
|
method: 'GET',
|
||||||
let destFolder = _.chain(fFolder).trim().toLower().value();
|
followRedirect: true,
|
||||||
|
maxRedirects: 5,
|
||||||
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
timeout: 10000
|
||||||
|
})
|
||||||
if(!destFolderPath) {
|
|
||||||
return Promise.reject(new Error('Invalid Folder'));
|
let destFileStream = fs.createWriteStream(destFilePath)
|
||||||
}
|
let curFileSize = 0
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
|
rq.on('data', (data) => {
|
||||||
|
curFileSize += data.length
|
||||||
let destFilePath = path.resolve(destFolderPath, destFilename);
|
if (curFileSize > maxDownloadFileSize) {
|
||||||
|
rq.abort()
|
||||||
return new Promise((resolve, reject) => {
|
destFileStream.destroy()
|
||||||
|
fs.remove(destFilePath)
|
||||||
let rq = request({
|
reject(new Error('Remote file is too large!'))
|
||||||
url: fUrl,
|
}
|
||||||
method: 'GET',
|
}).on('error', (err) => {
|
||||||
followRedirect: true,
|
destFileStream.destroy()
|
||||||
maxRedirects: 5,
|
fs.remove(destFilePath)
|
||||||
timeout: 10000
|
reject(err)
|
||||||
});
|
})
|
||||||
|
|
||||||
let destFileStream = fs.createWriteStream(destFilePath);
|
destFileStream.on('finish', () => {
|
||||||
let curFileSize = 0;
|
resolve(true)
|
||||||
|
})
|
||||||
rq.on('data', (data) => {
|
|
||||||
curFileSize += data.length;
|
rq.pipe(destFileStream)
|
||||||
if(curFileSize > maxDownloadFileSize) {
|
})
|
||||||
rq.abort();
|
})
|
||||||
destFileStream.destroy();
|
})
|
||||||
fs.remove(destFilePath);
|
},
|
||||||
reject(new Error('Remote file is too large!'));
|
|
||||||
}
|
/**
|
||||||
}).on('error', (err) => {
|
* Move/Rename a file
|
||||||
destFileStream.destroy();
|
*
|
||||||
fs.remove(destFilePath);
|
* @param {String} uid The file ID
|
||||||
reject(err);
|
* @param {String} fld The destination folder
|
||||||
});
|
* @param {String} nFilename The new filename (optional)
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
destFileStream.on('finish', () => {
|
*/
|
||||||
resolve(true);
|
moveUploadsFile (uid, fld, nFilename) {
|
||||||
});
|
let self = this
|
||||||
|
|
||||||
rq.pipe(destFileStream);
|
return db.UplFolder.findById('f:' + fld).then((folder) => {
|
||||||
|
if (folder) {
|
||||||
});
|
return db.UplFile.findById(uid).then((originFile) => {
|
||||||
|
// -> Check if rename is valid
|
||||||
});
|
|
||||||
|
let nameCheck = null
|
||||||
});
|
if (nFilename) {
|
||||||
|
let originFileObj = path.parse(originFile.filename)
|
||||||
},
|
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name)
|
||||||
|
} else {
|
||||||
/**
|
nameCheck = Promise.resolve(originFile.filename)
|
||||||
* Move/Rename a file
|
}
|
||||||
*
|
|
||||||
* @param {String} uid The file ID
|
return nameCheck.then((destFilename) => {
|
||||||
* @param {String} fld The destination folder
|
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './'
|
||||||
* @param {String} nFilename The new filename (optional)
|
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename)
|
||||||
* @return {Promise} Promise of the operation
|
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename)
|
||||||
*/
|
let preMoveOps = []
|
||||||
moveUploadsFile(uid, fld, nFilename) {
|
|
||||||
|
// -> Check for invalid operations
|
||||||
let self = this;
|
|
||||||
|
if (sourceFilePath === destFilePath) {
|
||||||
return db.UplFolder.findById('f:' + fld).then((folder) => {
|
return Promise.reject(new Error('Invalid Operation!'))
|
||||||
if(folder) {
|
}
|
||||||
return db.UplFile.findById(uid).then((originFile) => {
|
|
||||||
|
// -> Delete DB entry
|
||||||
//-> Check if rename is valid
|
|
||||||
|
preMoveOps.push(db.UplFile.findByIdAndRemove(uid))
|
||||||
let nameCheck = null;
|
|
||||||
if(nFilename) {
|
// -> Move thumbnail ahead to avoid re-generation
|
||||||
let originFileObj = path.parse(originFile.filename);
|
|
||||||
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name);
|
if (originFile.category === 'image') {
|
||||||
} else {
|
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename)
|
||||||
nameCheck = Promise.resolve(originFile.filename);
|
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png')
|
||||||
}
|
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png')
|
||||||
|
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath))
|
||||||
return nameCheck.then((destFilename) => {
|
} else {
|
||||||
|
preMoveOps.push(Promise.resolve(true))
|
||||||
let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './';
|
}
|
||||||
let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename);
|
|
||||||
let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename);
|
// -> Proceed to move actual file
|
||||||
let preMoveOps = [];
|
|
||||||
|
return Promise.all(preMoveOps).then(() => {
|
||||||
//-> Check for invalid operations
|
return fs.moveAsync(sourceFilePath, destFilePath, {
|
||||||
|
clobber: false
|
||||||
if(sourceFilePath === destFilePath) {
|
})
|
||||||
return Promise.reject(new Error('Invalid Operation!'));
|
})
|
||||||
}
|
})
|
||||||
|
})
|
||||||
//-> Delete DB entry
|
} else {
|
||||||
|
return Promise.reject(new Error('Invalid Destination Folder'))
|
||||||
preMoveOps.push(db.UplFile.findByIdAndRemove(uid));
|
}
|
||||||
|
})
|
||||||
//-> Move thumbnail ahead to avoid re-generation
|
}
|
||||||
|
|
||||||
if(originFile.category === 'image') {
|
}
|
||||||
let fUid = farmhash.fingerprint32(folder.name + '/' + destFilename);
|
|
||||||
let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png');
|
|
||||||
let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png');
|
|
||||||
preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath));
|
|
||||||
} else {
|
|
||||||
preMoveOps.push(Promise.resolve(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Proceed to move actual file
|
|
||||||
|
|
||||||
return Promise.all(preMoveOps).then(() => {
|
|
||||||
return fs.moveAsync(sourceFilePath, destFilePath, {
|
|
||||||
clobber: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error('Invalid Destination Folder'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
@ -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');
|
|
Loading…
Reference in new issue