Files Management + Editor Modal + Code Editor fixes

pull/33/head
NGPixel 8 years ago
parent eb2e724f37
commit 9caaeee682

@ -12,17 +12,18 @@
[![Known Vulnerabilities](https://snyk.io/test/github/requarks/wiki/badge.svg)](https://snyk.io/test/github/requarks/wiki)
##### A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown
*Under development*
*Under active development*
### Documentation
- [Installation Guide](https://wiki.requarks.io/install)
- [Official Website](https://wiki.requarks.io/)
- [Installation Guide](https://wiki.requarks.io/get-started.html)
##### Milestones
- [ ] Account Management
- [x] Assets Management
- [x] Images
- [ ] Files/Documents
- [x] Files/Documents
- [x] Authentication
- [x] Strategies
- [x] Local
@ -46,6 +47,10 @@
- [x] Markdown Editor
- [x] Basic Formatting
- [ ] Links
- [x] Image Selection modal
- [x] File Selection modal
- [x] Inline Code
- [x] Code Editor modal
- [ ] Table Editor
- [x] Move Entry
- [x] Navigation

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,12 +1,6 @@
let codeEditor = ace.edit("codeblock-editor");
codeEditor.setTheme("ace/theme/tomorrow_night");
codeEditor.getSession().setMode("ace/mode/markdown");
codeEditor.setOption('fontSize', '14px');
codeEditor.setOption('hScrollBarAlwaysVisible', false);
codeEditor.setOption('wrap', true);
let modelist = ace.require("ace/ext/modelist");
let codeEditor = null;
// ACE - Mode Loader
@ -33,7 +27,8 @@ let vueCodeBlock = new Vue({
el: '#modal-editor-codeblock',
data: {
modes: modelist.modesByName,
modeSelected: 'text'
modeSelected: 'text',
initContent: ''
},
watch: {
modeSelected: (val, oldVal) => {
@ -45,19 +40,28 @@ let vueCodeBlock = new Vue({
},
methods: {
open: (ev) => {
$('#modal-editor-codeblock').addClass('is-active');
_.delay(() => {
codeEditor.resize();
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.setAutoScrollEditorIntoView(true);
codeEditor.renderer.updateFull();
}, 1000);
}, 300);
},
cancel: (ev) => {
mdeModalOpenState = false;
$('#modal-editor-codeblock').removeClass('is-active');
vueCodeBlock.initContent = '';
},
insertCode: (ev) => {

@ -0,0 +1,365 @@
let vueFile = new Vue({
el: '#modal-editor-file',
data: {
isLoading: false,
isLoadingText: '',
newFolderName: '',
newFolderShow: false,
newFolderError: false,
folders: [],
currentFolder: '',
currentFile: '',
files: [],
uploadSucceeded: false,
postUploadChecks: 0,
renameFileShow: false,
renameFileId: '',
renameFileFilename: '',
deleteFileShow: false,
deleteFileId: '',
deleteFileFilename: ''
},
methods: {
open: () => {
mdeModalOpenState = true;
$('#modal-editor-file').addClass('is-active');
vueFile.refreshFolders();
},
cancel: (ev) => {
mdeModalOpenState = false;
$('#modal-editor-file').removeClass('is-active');
},
// -------------------------------------------
// INSERT LINK TO FILE
// -------------------------------------------
selectFile: (fileId) => {
vueFile.currentFile = fileId;
},
insertFileLink: (ev) => {
if(mde.codemirror.doc.somethingSelected()) {
mde.codemirror.execCommand('singleSelection');
}
let selFile = _.find(vueFile.files, ['_id', vueFile.currentFile]);
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();
},
// -------------------------------------------
// NEW FOLDER
// -------------------------------------------
newFolder: (ev) => {
vueFile.newFolderName = '';
vueFile.newFolderError = false;
vueFile.newFolderShow = true;
_.delay(() => { $('#txt-editor-file-newfoldername').focus(); }, 400);
},
newFolderDiscard: (ev) => {
vueFile.newFolderShow = false;
},
newFolderCreate: (ev) => {
let regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
vueFile.newFolderName = _.kebabCase(_.trim(vueFile.newFolderName));
if(_.isEmpty(vueFile.newFolderName) || !regFolderName.test(vueFile.newFolderName)) {
vueFile.newFolderError = true;
return;
}
vueFile.newFolderDiscard();
vueFile.isLoadingText = 'Creating new folder...';
vueFile.isLoading = true;
Vue.nextTick(() => {
socket.emit('uploadsCreateFolder', { foldername: vueFile.newFolderName }, (data) => {
vueFile.folders = data;
vueFile.currentFolder = vueFile.newFolderName;
vueFile.files = [];
vueFile.isLoading = false;
});
});
},
// -------------------------------------------
// RENAME FILE
// -------------------------------------------
renameFile: () => {
let c = _.find(vueFile.files, ['_id', vueFile.renameFileId ]);
vueFile.renameFileFilename = c.basename || '';
vueFile.renameFileShow = true;
_.delay(() => {
$('#txt-editor-renamefile').focus();
_.defer(() => { $('#txt-editor-file-rename').select(); });
}, 400);
},
renameFileDiscard: () => {
vueFile.renameFileShow = false;
},
renameFileGo: () => {
vueFile.renameFileDiscard();
vueFile.isLoadingText = 'Renaming file...';
vueFile.isLoading = true;
Vue.nextTick(() => {
socket.emit('uploadsRenameFile', { uid: vueFile.renameFileId, folder: vueFile.currentFolder, filename: vueFile.renameFileFilename }, (data) => {
if(data.ok) {
vueFile.waitChangeComplete(vueFile.files.length, false);
} else {
vueFile.isLoading = false;
alerts.pushError('Rename error', data.msg);
}
});
});
},
// -------------------------------------------
// MOVE FILE
// -------------------------------------------
moveFile: (uid, fld) => {
vueFile.isLoadingText = 'Moving file...';
vueFile.isLoading = true;
Vue.nextTick(() => {
socket.emit('uploadsMoveFile', { uid, folder: fld }, (data) => {
if(data.ok) {
vueFile.loadFiles();
} else {
vueFile.isLoading = false;
alerts.pushError('Rename error', data.msg);
}
});
});
},
// -------------------------------------------
// DELETE FILE
// -------------------------------------------
deleteFileWarn: (show) => {
if(show) {
let c = _.find(vueFile.files, ['_id', vueFile.deleteFileId ]);
vueFile.deleteFileFilename = c.filename || 'this file';
}
vueFile.deleteFileShow = show;
},
deleteFileGo: () => {
vueFile.deleteFileWarn(false);
vueFile.isLoadingText = 'Deleting file...';
vueFile.isLoading = true;
Vue.nextTick(() => {
socket.emit('uploadsDeleteFile', { uid: vueFile.deleteFileId }, (data) => {
vueFile.loadFiles();
});
});
},
// -------------------------------------------
// LOAD FROM REMOTE
// -------------------------------------------
selectFolder: (fldName) => {
vueFile.currentFolder = fldName;
vueFile.loadFiles();
},
refreshFolders: () => {
vueFile.isLoadingText = 'Fetching folders list...';
vueFile.isLoading = true;
vueFile.currentFolder = '';
vueFile.currentImage = '';
Vue.nextTick(() => {
socket.emit('uploadsGetFolders', { }, (data) => {
vueFile.folders = data;
vueFile.loadFiles();
});
});
},
loadFiles: (silent) => {
if(!silent) {
vueFile.isLoadingText = 'Fetching files...';
vueFile.isLoading = true;
}
return new Promise((resolve, reject) => {
Vue.nextTick(() => {
socket.emit('uploadsGetFiles', { folder: vueFile.currentFolder }, (data) => {
vueFile.files = data;
if(!silent) {
vueFile.isLoading = false;
}
vueFile.attachContextMenus();
resolve(true);
});
});
});
},
waitChangeComplete: (oldAmount, expectChange) => {
expectChange = (_.isBoolean(expectChange)) ? expectChange : true;
vueFile.postUploadChecks++;
vueFile.isLoadingText = 'Processing...';
Vue.nextTick(() => {
vueFile.loadFiles(true).then(() => {
if((vueFile.files.length !== oldAmount) === expectChange) {
vueFile.postUploadChecks = 0;
vueFile.isLoading = false;
} else if(vueFile.postUploadChecks > 5) {
vueFile.postUploadChecks = 0;
vueFile.isLoading = false;
alerts.pushError('Unable to fetch updated listing', 'Try again later');
} else {
_.delay(() => {
vueFile.waitChangeComplete(oldAmount, expectChange);
}, 1500);
}
});
});
},
// -------------------------------------------
// IMAGE CONTEXT MENU
// -------------------------------------------
attachContextMenus: () => {
let moveFolders = _.map(vueFile.folders, (f) => {
return {
name: (f !== '') ? f : '/ (root)',
icon: 'fa-folder',
callback: (key, opt) => {
let moveFileId = _.toString($(opt.$trigger).data('uid'));
let moveFileDestFolder = _.nth(vueFile.folders, key);
vueFile.moveFile(moveFileId, moveFileDestFolder);
}
};
});
$.contextMenu('destroy', '.editor-modal-file-choices > figure');
$.contextMenu({
selector: '.editor-modal-file-choices > figure',
appendTo: '.editor-modal-file-choices',
position: (opt, x, y) => {
$(opt.$trigger).addClass('is-contextopen');
let trigPos = $(opt.$trigger).position();
let trigDim = { w: $(opt.$trigger).width() / 5, h: $(opt.$trigger).height() / 2 };
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
},
events: {
hide: (opt) => {
$(opt.$trigger).removeClass('is-contextopen');
}
},
items: {
rename: {
name: "Rename",
icon: "fa-edit",
callback: (key, opt) => {
vueFile.renameFileId = _.toString(opt.$trigger[0].dataset.uid);
vueFile.renameFile();
}
},
move: {
name: "Move to...",
icon: "fa-folder-open-o",
items: moveFolders
},
delete: {
name: "Delete",
icon: "fa-trash",
callback: (key, opt) => {
vueFile.deleteFileId = _.toString(opt.$trigger[0].dataset.uid);
vueFile.deleteFileWarn(true);
}
}
}
});
}
}
});
$('#btn-editor-file-upload input').on('change', (ev) => {
let curFileAmount = vueFile.files.length;
$(ev.currentTarget).simpleUpload("/uploads/file", {
name: 'binfile',
data: {
folder: vueFile.currentFolder
},
limit: 20,
expect: 'json',
maxFileSize: 0,
init: (totalUploads) => {
vueFile.uploadSucceeded = false;
vueFile.isLoadingText = 'Preparing to upload...';
vueFile.isLoading = true;
},
progress: (progress) => {
vueFile.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
},
success: (data) => {
if(data.ok) {
let failedUpls = _.filter(data.results, ['ok', false]);
if(failedUpls.length) {
_.forEach(failedUpls, (u) => {
alerts.pushError('Upload error', u.msg);
});
if(failedUpls.length < data.results.length) {
alerts.push({
title: 'Some uploads succeeded',
message: 'Files that are not mentionned in the errors above were uploaded successfully.'
});
vueFile.uploadSucceeded = true;
}
} else {
vueFile.uploadSucceeded = true;
}
} else {
alerts.pushError('Upload error', data.msg);
}
},
error: (error) => {
alerts.pushError(error.message, this.upload.file.name);
},
finish: () => {
if(vueFile.uploadSucceeded) {
vueFile.waitChangeComplete(curFileAmount, true);
} else {
vueFile.isLoading = false;
}
}
});
});

@ -78,7 +78,7 @@ let vueImage = new Vue({
vueImage.newFolderName = '';
vueImage.newFolderError = false;
vueImage.newFolderShow = true;
_.delay(() => { $('#txt-editor-newfoldername').focus(); }, 400);
_.delay(() => { $('#txt-editor-image-newfoldername').focus(); }, 400);
},
newFolderDiscard: (ev) => {
vueImage.newFolderShow = false;
@ -115,7 +115,7 @@ let vueImage = new Vue({
fetchFromUrl: (ev) => {
vueImage.fetchFromUrlURL = '';
vueImage.fetchFromUrlShow = true;
_.delay(() => { $('#txt-editor-fetchimgurl').focus(); }, 400);
_.delay(() => { $('#txt-editor-image-fetchurl').focus(); }, 400);
},
fetchFromUrlDiscard: (ev) => {
vueImage.fetchFromUrlShow = false;
@ -149,8 +149,8 @@ let vueImage = new Vue({
vueImage.renameImageFilename = c.basename || '';
vueImage.renameImageShow = true;
_.delay(() => {
$('#txt-editor-renameimage').focus();
_.defer(() => { $('#txt-editor-renameimage').select(); });
$('#txt-editor-image-rename').focus();
_.defer(() => { $('#txt-editor-image-rename').select(); });
}, 400);
},
renameImageDiscard: () => {
@ -301,10 +301,10 @@ let vueImage = new Vue({
};
});
$.contextMenu('destroy', '.editor-modal-imagechoices > figure');
$.contextMenu('destroy', '.editor-modal-image-choices > figure');
$.contextMenu({
selector: '.editor-modal-imagechoices > figure',
appendTo: '.editor-modal-imagechoices',
selector: '.editor-modal-image-choices > figure',
appendTo: '.editor-modal-image-choices',
position: (opt, x, y) => {
$(opt.$trigger).addClass('is-contextopen');
let trigPos = $(opt.$trigger).position();
@ -345,7 +345,7 @@ let vueImage = new Vue({
}
});
$('#btn-editor-uploadimage input').on('change', (ev) => {
$('#btn-editor-image-upload input').on('change', (ev) => {
let curImageAmount = vueImage.images.length;

@ -13,6 +13,7 @@ if($('#mk-editor').length === 1) {
});
//=include editor-image.js
//=include editor-file.js
//=include editor-codeblock.js
var mde = new SimpleMDE({
@ -103,7 +104,9 @@ if($('#mk-editor').length === 1) {
{
name: "file",
action: (editor) => {
//todo
if(!mdeModalOpenState) {
vueFile.open();
}
},
className: "fa fa-file-text-o",
title: "Insert File",
@ -133,9 +136,7 @@ if($('#mk-editor').length === 1) {
mdeModalOpenState = true;
if(mde.codemirror.doc.somethingSelected()) {
codeEditor.setValue(mde.codemirror.doc.getSelection());
} else {
codeEditor.setValue('');
vueCodeBlock.initContent = mde.codemirror.doc.getSelection();
}
vueCodeBlock.open();
@ -170,7 +171,21 @@ if($('#mk-editor').length === 1) {
//-> Save
$('.btn-edit-save, .btn-create-save').on('click', (ev) => {
saveCurrentDocument(ev);
});
$(window).bind('keydown', (ev) => {
if (ev.ctrlKey || ev.metaKey) {
switch (String.fromCharCode(ev.which).toLowerCase()) {
case 's':
ev.preventDefault();
saveCurrentDocument(ev);
break;
}
}
});
let saveCurrentDocument = (ev) => {
$.ajax(window.location.href, {
data: {
markdown: mde.value()
@ -186,7 +201,6 @@ if($('#mk-editor').length === 1) {
}, (rXHR, rStatus, err) => {
alerts.pushError('Something went wrong', 'Save operation failed.');
});
});
}
}

@ -4,6 +4,9 @@ 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();

@ -67,7 +67,7 @@
}
#btn-editor-uploadimage {
#btn-editor-image-upload, #btn-editor-file-upload {
position: relative;
overflow: hidden;
@ -94,7 +94,7 @@
}
.editor-modal-imagechoices {
.editor-modal-image-choices {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
@ -188,6 +188,85 @@
}
.editor-modal-file-choices {
overflow: auto;
overflow-x: hidden;
> em {
display: flex;
align-items: center;
padding: 25px;
color: mc('grey', '500');
> i {
font-size: 32px;
margin-right: 10px;
color: mc('grey', '300');
}
}
> figure {
display: flex;
background-color: #FAFAFA;
border-radius: 3px;
padding: 5px;
height: 34px;
margin: 0 0 5px 0;
cursor: pointer;
justify-content: flex-start;
align-items: center;
transition: background-color 0.4s ease;
> i {
width: 16px;
}
> span {
font-size: 14px;
flex: 0 1 auto;
padding: 0 15px;
color: mc('grey', '600');
&:first-of-type {
flex: 1 0 auto;
color: mc('grey', '800');
}
&:last-of-type {
width: 100px;
}
}
&:hover {
background-color: #DDD;
}
&.is-active {
background-color: mc('green', '500');
color: #FFF;
> span, strong {
color: #FFF;
}
}
&.is-contextopen {
background-color: mc('blue', '500');
color: #FFF;
> span, strong {
color: #FFF;
}
}
}
}
.editor-modal-imagealign {
.control > span {
@ -215,6 +294,8 @@
overflow-x: hidden;
}
// CODE MIRROR
.CodeMirror {
border-left: none;
border-right: none;
@ -245,16 +326,18 @@
font-size: 14px;
}
// ACE EDITOR
.ace-container {
position: relative;
}
.ace_scroller {
/*.ace_scroller {
width: 100%;
}
.ace_content {
height: 100%;
}
}*/
#page-type-source .ace-container {
min-height: 95vh;
@ -267,13 +350,6 @@
position: relative;
width: 100%;
height: 100%;
#codeblock-editor {
width: 100%;
height: 100%;
min-height: 500px;
}
}
#source-display, #codeblock-editor {
@ -282,27 +358,4 @@
left: 0;
bottom: 0;
right: 0;
}
.modallayer {
position: fixed;
top: 100px;
width: 100%;
background-color: rgba(255,255,255,0.95);
border-bottom: 1px solid mc('grey', '500');
z-index: 6;
padding: 20px;
border-bottom: 1px solid #CCC;
box-shadow: 0 2px 3px rgba(17,17,17,.1);
display: none;
> h3, .column > h3 {
color: mc('grey', '700');
font-size: 24px;
font-weight: 300;
}
}
.modallayer-content {
}

@ -31,6 +31,15 @@ paths:
repo: ./repo
data: ./data
# ---------------------------------------------------------------------
# Upload Limits
# ---------------------------------------------------------------------
# In megabytes (MB)
uploads:
maxImageFileSize: 3
maxOtherFileSize: 100
# ---------------------------------------------------------------------
# Site Language
# ---------------------------------------------------------------------

@ -53,7 +53,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let destFilename = '';
let destFilePath = '';
return lcdata.validateUploadsFilename(f.originalname, destFolder).then((fname) => {
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
destFilename = fname;
destFilePath = path.resolve(destFolderPath, destFilename);
@ -106,6 +106,61 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
});
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value();
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if(!destFolderPath) {
res.json({ ok: false, msg: 'Invalid Folder' });
return true;
}
Promise.map(req.files, (f) => {
let destFilename = '';
let destFilePath = '';
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
destFilename = fname;
destFilePath = path.resolve(destFolderPath, destFilename);
//-> Move file to final destination
return fs.moveAsync(f.path, destFilePath, { clobber: false });
}).then(() => {
return {
ok: true,
filename: destFilename,
filesize: f.size
};
}).reflect();
}, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => {
if(r.isFulfilled()) {
return r.value();
} else {
return {
ok: false,
msg: r.reason().message
};
}
});
res.json({ ok: true, results: uplResults });
return true;
}).catch((err) => {
res.json({ ok: false, msg: err.message });
return true;
});
});
});
router.get('/*', (req, res, next) => {
let fileName = req.params[0];

@ -42,6 +42,13 @@ module.exports = (socket) => {
});
});
socket.on('uploadsGetFiles', (data, cb) => {
cb = cb || _.noop;
upl.getUploadsFiles('binary', data.folder).then((f) => {
return cb(f) || true;
});
});
socket.on('uploadsDeleteFile', (data, cb) => {
cb = cb || _.noop;
upl.deleteUploadsFile(data.uid).then((f) => {

@ -1,4 +0,0 @@
.sidebar {
background-color: #FFF;
}

@ -4,6 +4,7 @@ var path = require('path'),
Promise = require('bluebird'),
fs = Promise.promisifyAll(require('fs-extra')),
multer = require('multer'),
os = require('os'),
_ = require('lodash');
/**
@ -44,6 +45,13 @@ module.exports = {
*/
initMulter(appconfig) {
let maxFileSizes = {
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
};
//-> IMAGES
this.uploadImgHandler = multer({
storage: multer.diskStorage({
destination: (req, f, cb) => {
@ -52,9 +60,9 @@ module.exports = {
}),
fileFilter: (req, f, cb) => {
//-> Check filesize (3 MB max)
//-> Check filesize
if(f.size > 3145728) {
if(f.size > maxFileSizes.img) {
return cb(null, false);
}
@ -68,6 +76,26 @@ module.exports = {
}
}).array('imgfile', 20);
//-> FILES
this.uploadFileHandler = multer({
storage: multer.diskStorage({
destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'));
}
}),
fileFilter: (req, f, cb) => {
//-> Check filesize
if(f.size > maxFileSizes.file) {
return cb(null, false);
}
cb(null, true);
}
}).array('binfile', 20);
return true;
},
@ -88,8 +116,17 @@ module.exports = {
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'));
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'));
if(os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '644');
}
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo));
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'));
if(os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './upload'), '644');
}
} catch (err) {
winston.error(err);
}
@ -125,13 +162,13 @@ module.exports = {
* @param {String} fld The containing folder
* @return {Promise<String>} Promise of the accepted filename
*/
validateUploadsFilename(f, fld) {
validateUploadsFilename(f, fld, isImage) {
let fObj = path.parse(f);
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
let fext = _.toLower(fObj.ext);
if(!_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
if(isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
fext = '.png';
}

@ -5,6 +5,7 @@ var path = require('path'),
fs = Promise.promisifyAll(require('fs-extra')),
readChunk = require('read-chunk'),
fileType = require('file-type'),
mime = require('mime-types'),
farmhash = require('farmhash'),
moment = require('moment'),
chokidar = require('chokidar'),
@ -199,6 +200,11 @@ module.exports = {
// Get MIME info
let mimeInfo = fileType(readChunk.sync(fPath, 0, 262));
if(_.isNil(mimeInfo)) {
mimeInfo = {
mime: mime.lookup(fPathObj.ext) || 'application/octet-stream'
};
}
// Images
@ -244,7 +250,7 @@ module.exports = {
_id: fUid,
category: 'binary',
mime: mimeInfo.mime,
folder: fldName,
folder: 'f:' + fldName,
filename: f,
basename: fPathObj.name,
filesize: s.size

@ -43,7 +43,7 @@
"connect-flash": "^0.1.1",
"connect-mongo": "^1.3.2",
"cookie-parser": "^1.4.3",
"cron": "^1.1.1",
"cron": "^1.2.1",
"express": "^4.14.0",
"express-brute": "^1.0.0",
"express-brute-mongoose": "0.0.7",
@ -53,13 +53,13 @@
"filesize.js": "^1.0.2",
"fs-extra": "^1.0.0",
"git-wrapper2-promise": "^0.2.9",
"highlight.js": "^9.8.0",
"highlight.js": "^9.9.0",
"i18next": "^4.1.1",
"i18next-express-middleware": "^1.0.2",
"i18next-node-fs-backend": "^0.1.3",
"js-yaml": "^3.7.0",
"lodash": "^4.17.2",
"markdown-it": "^8.2.1",
"markdown-it": "^8.2.2",
"markdown-it-abbr": "^1.0.4",
"markdown-it-anchor": "^2.6.0",
"markdown-it-attrs": "^0.8.0",
@ -68,10 +68,11 @@
"markdown-it-external-links": "0.0.6",
"markdown-it-footnote": "^3.0.1",
"markdown-it-task-lists": "^1.4.1",
"mime-types": "^2.1.13",
"moment": "^2.17.1",
"moment-timezone": "^0.5.10",
"mongoose": "^4.7.2",
"multer": "^1.2.0",
"mongoose": "^4.7.3",
"multer": "^1.2.1",
"passport": "^0.3.2",
"passport-facebook": "^2.1.1",
"passport-google-oauth20": "^1.0.0",
@ -87,8 +88,7 @@
"serve-favicon": "^2.3.2",
"sharp": "^0.16.1",
"simplemde": "^1.11.2",
"snyk": "^1.19.1",
"socket.io": "^1.6.0",
"socket.io": "^1.7.2",
"sticky-js": "^1.0.7",
"validator": "^6.2.0",
"validator-as-promised": "^1.0.2",
@ -100,17 +100,16 @@
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"codacy-coverage": "^2.0.0",
"filesize.js": "^1.0.1",
"font-awesome": "^4.6.3",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-clean-css": "^2.2.1",
"gulp-clean-css": "^2.3.2",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.0",
"gulp-include": "^2.3.1",
"gulp-nodemon": "^2.2.1",
"gulp-plumber": "^1.1.0",
"gulp-sass": "^2.3.2",
"gulp-sass": "^3.0.0",
"gulp-tar": "^1.9.0",
"gulp-uglify": "^2.0.0",
"gulp-watch": "^4.3.11",
@ -125,10 +124,10 @@
"mocha-lcov-reporter": "^1.2.0",
"nodemon": "^1.11.0",
"run-sequence": "^1.2.2",
"snyk": "^1.21.2",
"snyk": "^1.22.1",
"sticky-js": "^1.1.6",
"twemoji-awesome": "^1.0.4",
"vue": "^2.1.4"
"vue": "^2.1.6"
},
"snyk": true
}

@ -0,0 +1,80 @@
.modal#modal-editor-file
.modal-background
.modal-container
.modal-content.is-expanded
header.is-green
span Insert File
p.modal-notify(v-bind:class="{ 'is-active': isLoading }")
span {{ isLoadingText }}
i
.modal-toolbar.is-green
a.button(v-on:click="newFolder")
i.fa.fa-folder
span New Folder
a.button#btn-editor-file-upload
i.fa.fa-upload
span Upload File
label
input(type="file", multiple)
section.is-gapless
.columns.is-stretched
.column.is-one-quarter.modal-sidebar.is-green(style={'max-width':'350px'})
.model-sidebar-header Folders
ul.model-sidebar-list
li(v-for="fld in folders")
a(v-on:click="selectFolder(fld)", v-bind:class="{ 'is-active': currentFolder === fld }")
i.icon-folder2
span / {{ fld }}
.column.editor-modal-file-choices
figure(v-for="fl in files", v-bind:class="{ 'is-active': currentFile === fl._id }", v-on:click="selectFile(fl._id)", v-bind:data-uid="fl._id")
i(class='icon-file')
span: strong {{ fl.filename }}
span {{ fl.mime }}
span {{ fl.filesize | filesize }}
em(v-show="files.length < 1")
i.icon-marquee-minus
| This folder is empty.
footer
a.button.is-grey.is-outlined(v-on:click="cancel") Discard
a.button.is-green(v-on:click="insertFileLink") Insert Link to File
.modal.is-superimposed(v-bind:class="{ 'is-active': newFolderShow }")
.modal-background
.modal-container
.modal-content
header.is-light-blue New Folder
section
label.label Enter the new folder name:
p.control.is-fullwidth
input.input#txt-editor-file-newfoldername(type='text', placeholder='folder name', v-model='newFolderName', v-on:keyup.enter="newFolderCreate", v-on:keyup.esc="newFolderDiscard")
span.help.is-danger(v-show="newFolderError") This folder name is invalid!
footer
a.button.is-grey.is-outlined(v-on:click="newFolderDiscard") Discard
a.button.is-light-blue(v-on:click="newFolderCreate") Create
.modal.is-superimposed(v-bind:class="{ 'is-active': renameFileShow }")
.modal-background
.modal-container
.modal-content
header.is-indigo Rename File
section
label.label Enter the new filename (without the extension) of the file:
p.control.is-fullwidth
input.input#txt-editor-file-rename(type='text', placeholder='filename', v-model='renameFileFilename')
span.help.is-danger.is-hidden This filename is invalid!
footer
a.button.is-grey.is-outlined(v-on:click="renameFileDiscard") Discard
a.button.is-light-blue(v-on:click="renameFileGo") Rename
.modal.is-superimposed(v-bind:class="{ 'is-active': deleteFileShow }")
.modal-background
.modal-container
.modal-content
header.is-red Delete file?
section
span Are you sure you want to delete #[strong {{deleteFileFilename}}]?
footer
a.button.is-grey.is-outlined(v-on:click="deleteFileWarn(false)") Discard
a.button.is-red(v-on:click="deleteFileGo") Delete

@ -13,7 +13,7 @@
a.button(v-on:click="newFolder")
i.fa.fa-folder
span New Folder
a.button#btn-editor-uploadimage
a.button#btn-editor-image-upload
i.fa.fa-upload
span Upload Image
label
@ -38,7 +38,7 @@
option(value='center') Centered
option(value='right') Right
option(value='logo') Page Logo
.column.editor-modal-imagechoices
.column.editor-modal-image-choices
figure(v-for="img in images", v-bind:class="{ 'is-active': currentImage === img._id }", v-on:click="selectImage(img._id)", v-bind:data-uid="img._id")
img(v-bind:src="'/uploads/t/' + img._id + '.png'")
span: strong {{ img.basename }}
@ -58,7 +58,7 @@
section
label.label Enter the new folder name:
p.control.is-fullwidth
input.input#txt-editor-newfoldername(type='text', placeholder='folder name', v-model='newFolderName', v-on:keyup.enter="newFolderCreate", v-on:keyup.esc="newFolderDiscard")
input.input#txt-editor-image-newfoldername(type='text', placeholder='folder name', v-model='newFolderName', v-on:keyup.enter="newFolderCreate", v-on:keyup.esc="newFolderDiscard")
span.help.is-danger(v-show="newFolderError") This folder name is invalid!
footer
a.button.is-grey.is-outlined(v-on:click="newFolderDiscard") Discard
@ -72,7 +72,7 @@
section
label.label Enter full URL path to the image:
p.control.is-fullwidth
input.input#txt-editor-fetchimgurl(type='text', placeholder='http://www.example.com/some-image.png', v-model='fetchFromUrlURL')
input.input#txt-editor-image-fetchurl(type='text', placeholder='http://www.example.com/some-image.png', v-model='fetchFromUrlURL')
span.help.is-danger.is-hidden This URL path is invalid!
footer
a.button.is-grey.is-outlined(v-on:click="fetchFromUrlDiscard") Discard
@ -86,7 +86,7 @@
section
label.label Enter the new filename (without the extension) of the image:
p.control.is-fullwidth
input.input#txt-editor-renameimage(type='text', placeholder='filename', v-model='renameImageFilename')
input.input#txt-editor-image-rename(type='text', placeholder='filename', v-model='renameImageFilename')
span.help.is-danger.is-hidden This filename is invalid!
footer
a.button.is-grey.is-outlined(v-on:click="renameImageDiscard") Discard

@ -1,5 +1,5 @@
.modallayer#modal-editor-link
//.modallayer#modal-editor-link
.modallayer-content
.tabs.is-boxed
ul

@ -22,4 +22,5 @@ block content
include ../modals/edit-discard.pug
include ../modals/editor-link.pug
include ../modals/editor-image.pug
include ../modals/editor-file.pug
include ../modals/editor-codeblock.pug
Loading…
Cancel
Save