Image upload process + right-click menu UI
This commit is contained in:
parent
90afe796ee
commit
819d4ad346
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
@ -91,10 +91,23 @@ let vueImage = new Vue({
|
||||
fetchFromUrlDiscard: (ev) => {
|
||||
vueImage.fetchFromUrlShow = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a folder
|
||||
*
|
||||
* @param {string} fldName The folder name
|
||||
* @return {Void} Void
|
||||
*/
|
||||
selectFolder: (fldName) => {
|
||||
vueImage.currentFolder = fldName;
|
||||
vueImage.loadImages();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh folder list and load images from root
|
||||
*
|
||||
* @return {Void} Void
|
||||
*/
|
||||
refreshFolders: () => {
|
||||
vueImage.isLoading = true;
|
||||
vueImage.isLoadingText = 'Fetching folders list...';
|
||||
@ -107,6 +120,12 @@ let vueImage = new Vue({
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads images in selected folder
|
||||
*
|
||||
* @return {Void} Void
|
||||
*/
|
||||
loadImages: () => {
|
||||
vueImage.isLoading = true;
|
||||
vueImage.isLoadingText = 'Fetching images...';
|
||||
@ -114,14 +133,127 @@ let vueImage = new Vue({
|
||||
socket.emit('uploadsGetImages', { folder: vueImage.currentFolder }, (data) => {
|
||||
vueImage.images = data;
|
||||
vueImage.isLoading = false;
|
||||
vueImage.attachContextMenus();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Select an image
|
||||
*
|
||||
* @param {String} imageId The image identifier
|
||||
* @return {Void} Void
|
||||
*/
|
||||
selectImage: (imageId) => {
|
||||
vueImage.currentImage = imageId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set image alignment
|
||||
*
|
||||
* @param {String} align The alignment
|
||||
* @return {Void} Void
|
||||
*/
|
||||
selectAlignment: (align) => {
|
||||
vueImage.currentAlign = align;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach right-click context menus to images and folders
|
||||
*
|
||||
* @return {Void} Void
|
||||
*/
|
||||
attachContextMenus: () => {
|
||||
|
||||
let moveFolders = _.map(vueImage.folders, (f) => {
|
||||
return {
|
||||
name: (f !== '') ? f : '/ (root)',
|
||||
icon: 'fa-folder'
|
||||
};
|
||||
});
|
||||
|
||||
$.contextMenu('destroy', '.editor-modal-imagechoices > figure');
|
||||
$.contextMenu({
|
||||
selector: '.editor-modal-imagechoices > figure',
|
||||
appendTo: '.editor-modal-imagechoices',
|
||||
position: (opt, x, y) => {
|
||||
$(opt.$trigger).addClass('is-contextopen');
|
||||
let trigPos = $(opt.$trigger).position();
|
||||
let trigDim = { w: $(opt.$trigger).width() / 2, h: $(opt.$trigger).height() / 2 }
|
||||
opt.$menu.css({ top: trigPos.top + trigDim.h, left: trigPos.left + trigDim.w });
|
||||
},
|
||||
events: {
|
||||
hide: (opt) => {
|
||||
$(opt.$trigger).removeClass('is-contextopen');
|
||||
}
|
||||
},
|
||||
items: {
|
||||
rename: {
|
||||
name: "Rename",
|
||||
icon: "fa-edit",
|
||||
callback: (key, opt) => {
|
||||
alert("Clicked on " + key);
|
||||
}
|
||||
},
|
||||
move: {
|
||||
name: "Move to...",
|
||||
icon: "fa-folder-open-o",
|
||||
items: moveFolders
|
||||
},
|
||||
delete: {
|
||||
name: "Delete",
|
||||
icon: "fa-trash",
|
||||
callback: (key, opt) => {
|
||||
alert("Clicked on " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-editor-uploadimage input').on('change', (ev) => {
|
||||
|
||||
$(ev.currentTarget).simpleUpload("/uploads/img", {
|
||||
|
||||
name: 'imgfile',
|
||||
data: {
|
||||
folder: vueImage.currentFolder
|
||||
},
|
||||
limit: 20,
|
||||
expect: 'json',
|
||||
allowedExts: ["jpg", "jpeg", "gif", "png", "webp"],
|
||||
allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
||||
maxFileSize: 3145728, // max 3 MB
|
||||
|
||||
init: () => {
|
||||
vueImage.isLoading = true;
|
||||
vueImage.isLoadingText = 'Preparing to upload...';
|
||||
},
|
||||
|
||||
progress: function(progress) {
|
||||
vueImage.isLoadingText = 'Uploading...' + Math.round(progress) + '%';
|
||||
},
|
||||
|
||||
success: (data) => {
|
||||
if(data.ok) {
|
||||
|
||||
} else {
|
||||
alerts.pushError('Upload error', data.msg);
|
||||
}
|
||||
},
|
||||
|
||||
error: function(error) {
|
||||
vueImage.isLoading = false;
|
||||
alerts.pushError(error.message, this.upload.file.name);
|
||||
},
|
||||
|
||||
finish: () => {
|
||||
vueImage.isLoading = false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -14,6 +14,7 @@ $warning: $orange;
|
||||
@import 'bulma';
|
||||
@import './libs/twemoji-awesome';
|
||||
@import './libs/animate.min.css';
|
||||
@import './libs/jquery-contextmenu';
|
||||
|
||||
@import './components/_alerts';
|
||||
@import './components/_editor';
|
||||
|
@ -17,10 +17,12 @@
|
||||
|
||||
a {
|
||||
color: #FFF !important;
|
||||
border: none;
|
||||
transition: background-color 0.4s ease;
|
||||
|
||||
&.active, &:hover {
|
||||
&.active, &:hover, &:focus {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
border-color: #888;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
}
|
||||
@ -63,6 +65,33 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#btn-editor-uploadimage {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
> label {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
input[type=file] {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.editor-modal-imagechoices {
|
||||
@ -127,6 +156,20 @@
|
||||
|
||||
}
|
||||
|
||||
&.is-contextopen {
|
||||
background-color: $warning;
|
||||
color: #FFF;
|
||||
|
||||
> img {
|
||||
border-color: darken($warning, 10%);
|
||||
}
|
||||
|
||||
> span > strong {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
131
client/scss/libs/jquery-contextmenu.scss
Normal file
131
client/scss/libs/jquery-contextmenu.scss
Normal file
@ -0,0 +1,131 @@
|
||||
@charset "UTF-8";
|
||||
/*!
|
||||
* jQuery contextMenu - Plugin for simple contextMenu handling
|
||||
*
|
||||
* Version: v2.2.5-dev
|
||||
*
|
||||
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
|
||||
* Web: http://swisnl.github.io/jQuery-contextMenu/
|
||||
*
|
||||
* Copyright (c) 2011-2016 SWIS BV and contributors
|
||||
*
|
||||
* Licensed under
|
||||
* MIT License http://www.opensource.org/licenses/mit-license
|
||||
*
|
||||
* Date: 2016-08-27T11:09:08.919Z
|
||||
*/
|
||||
|
||||
.context-menu-icon {
|
||||
display: list-item;
|
||||
font-family: inherit;
|
||||
}
|
||||
.context-menu-icon::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 2em;
|
||||
font-family: FontAwesome;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
color: $primary;
|
||||
text-align: center;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
-o-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.context-menu-icon.context-menu-hover:before {
|
||||
color: #fff;
|
||||
}
|
||||
.context-menu-icon.context-menu-disabled::before {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.context-menu-list {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
min-width: 13em;
|
||||
max-width: 26em;
|
||||
padding: 0 0;
|
||||
margin: .3em;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
list-style-type: none;
|
||||
background: #fff;
|
||||
border: 1px solid $primary;
|
||||
border-radius: .2em;
|
||||
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .25);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
position: relative;
|
||||
padding: 7px 2em;
|
||||
color: #69707a;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: #fff;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.context-menu-separator {
|
||||
padding: 0;
|
||||
margin: .35em 0;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.context-menu-item.context-menu-hover {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
.context-menu-item.context-menu-disabled {
|
||||
color: #bbb;
|
||||
cursor: default;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.context-menu-input.context-menu-hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.context-menu-submenu:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: .5em;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: '';
|
||||
border-color: transparent transparent transparent #2f2f2f;
|
||||
border-style: solid;
|
||||
border-width: .25em 0 .25em .25em;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
-o-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.context-menu-item > .context-menu-list {
|
||||
top: .3em;
|
||||
/* re-positioned by js */
|
||||
right: -.3em;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.context-menu-item.context-menu-visible > .context-menu-list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.context-menu-accesskey {
|
||||
text-decoration: underline;
|
||||
}
|
@ -2,7 +2,13 @@
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var _ = require('lodash');
|
||||
|
||||
var readChunk = require('read-chunk'),
|
||||
fileType = require('file-type'),
|
||||
Promise = require('bluebird'),
|
||||
fs = Promise.promisifyAll(require('fs-extra')),
|
||||
path = require('path'),
|
||||
_ = require('lodash');
|
||||
|
||||
var validPathRe = new RegExp("^([a-z0-9\\/-]+\\.[a-z0-9]+)$");
|
||||
var validPathThumbsRe = new RegExp("^([0-9]+\\.png)$");
|
||||
@ -31,6 +37,54 @@ router.get('/t/*', (req, res, next) => {
|
||||
|
||||
});
|
||||
|
||||
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
||||
|
||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
||||
let destFolderPath = lcdata.validateUploadsFolder(destFolder);
|
||||
|
||||
Promise.map(req.files, (f) => {
|
||||
|
||||
let destFilename = '';
|
||||
let destFilePath = '';
|
||||
|
||||
return lcdata.validateUploadsFilename(f.originalname, destFolder).then((fname) => {
|
||||
|
||||
destFilename = fname;
|
||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
||||
|
||||
return readChunk(f.path, 0, 262);
|
||||
|
||||
}).then((buf) => {
|
||||
|
||||
//-> Check MIME type by magic number
|
||||
|
||||
let mimeInfo = fileType(buf);
|
||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||
return Promise.reject(new Error('Invalid file type.'));
|
||||
}
|
||||
return true;
|
||||
|
||||
}).then(() => {
|
||||
|
||||
//-> Move file to final destination
|
||||
|
||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
||||
|
||||
}).then(() => {
|
||||
return {
|
||||
filename: destFilename,
|
||||
filesize: f.size
|
||||
};
|
||||
});
|
||||
|
||||
}, {concurrency: 3}).then((results) => {
|
||||
res.json({ ok: true, results });
|
||||
}).catch((err) => {
|
||||
res.json({ ok: false, msg: err.message });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
router.get('/*', (req, res, next) => {
|
||||
|
||||
let fileName = req.params[0];
|
||||
|
@ -23,7 +23,7 @@ var paths = {
|
||||
'./node_modules/jquery/dist/jquery.min.js',
|
||||
'./node_modules/vue/dist/vue.min.js',
|
||||
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
|
||||
'./node_modules/jquery-contextmenu/dist/jquery.ui.position.min.js',
|
||||
'./node_modules/jquery-simple-upload/simpleUpload.min.js',
|
||||
'./node_modules/jquery-contextmenu/dist/jquery.contextMenu.min.js',
|
||||
'./node_modules/sticky-js/dist/sticky.min.js',
|
||||
'./node_modules/simplemde/dist/simplemde.min.js',
|
||||
|
@ -4,6 +4,7 @@ var path = require('path'),
|
||||
loki = require('lokijs'),
|
||||
Promise = require('bluebird'),
|
||||
fs = Promise.promisifyAll(require('fs-extra')),
|
||||
multer = require('multer'),
|
||||
_ = require('lodash');
|
||||
|
||||
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
||||
@ -20,6 +21,8 @@ module.exports = {
|
||||
_uploadsFolders: [],
|
||||
_uploadsDb: null,
|
||||
|
||||
uploadImgHandler: null,
|
||||
|
||||
/**
|
||||
* Initialize Local Data Storage model
|
||||
*
|
||||
@ -33,8 +36,7 @@ module.exports = {
|
||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.datadir.repo, 'uploads');
|
||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'thumbs');
|
||||
|
||||
|
||||
// Start in full or bare mode
|
||||
// Finish initialization tasks
|
||||
|
||||
switch(mode) {
|
||||
case 'agent':
|
||||
@ -42,6 +44,7 @@ module.exports = {
|
||||
break;
|
||||
case 'server':
|
||||
self.createBaseDirectories(appconfig);
|
||||
self.initMulter(appconfig);
|
||||
break;
|
||||
case 'ws':
|
||||
self.initDb(appconfig);
|
||||
@ -99,6 +102,42 @@ module.exports = {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Init Multer upload handlers
|
||||
*
|
||||
* @param {Object} appconfig The application config
|
||||
* @return {boolean} Void
|
||||
*/
|
||||
initMulter(appconfig) {
|
||||
|
||||
this.uploadImgHandler = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, f, cb) => {
|
||||
cb(null, path.resolve(ROOTPATH, appconfig.datadir.db, 'temp-upload'))
|
||||
}
|
||||
}),
|
||||
fileFilter: (req, f, cb) => {
|
||||
|
||||
//-> Check filesize (3 MB max)
|
||||
|
||||
if(f.size > 3145728) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
//-> Check MIME type (quick check only)
|
||||
|
||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
cb(null, true);
|
||||
}
|
||||
}).array('imgfile', 20);
|
||||
|
||||
return true;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the thumbnails folder path.
|
||||
*
|
||||
@ -122,6 +161,7 @@ module.exports = {
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db));
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './cache'));
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './thumbs'));
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './temp-upload'));
|
||||
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.repo));
|
||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.repo, './uploads'));
|
||||
@ -183,6 +223,50 @@ module.exports = {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if folder is valid and exists
|
||||
*
|
||||
* @param {String} folderName The folder name
|
||||
* @return {Boolean} True if valid
|
||||
*/
|
||||
validateUploadsFolder(folderName) {
|
||||
|
||||
folderName = (_.includes(this._uploadsFolders, folderName)) ? folderName : '';
|
||||
return path.resolve(this._uploadsPath, folderName);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if filename is valid and unique
|
||||
*
|
||||
* @param {String} f The filename
|
||||
* @param {String} fld The containing folder
|
||||
* @return {Promise<String>} Promise of the accepted filename
|
||||
*/
|
||||
validateUploadsFilename(f, fld) {
|
||||
|
||||
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)) {
|
||||
fext = '.png';
|
||||
}
|
||||
|
||||
f = fname + fext;
|
||||
let fpath = path.resolve(this._uploadsPath, fld, f);
|
||||
|
||||
return fs.statAsync(fpath).then((s) => {
|
||||
throw new Error('File ' + f + ' already exists.');
|
||||
}).catch((err) => {
|
||||
if(err.code === 'ENOENT') {
|
||||
return f;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the uploads files.
|
||||
*
|
||||
|
19
package.json
19
package.json
@ -52,6 +52,7 @@
|
||||
"express-validator": "^2.20.10",
|
||||
"farmhash": "^1.2.1",
|
||||
"file-type": "^3.8.0",
|
||||
"filesize.js": "^1.0.2",
|
||||
"fs-extra": "^0.30.0",
|
||||
"git-wrapper2-promise": "^0.2.9",
|
||||
"highlight.js": "^9.7.0",
|
||||
@ -59,7 +60,7 @@
|
||||
"i18next-express-middleware": "^1.0.2",
|
||||
"i18next-node-fs-backend": "^0.1.2",
|
||||
"js-yaml": "^3.6.1",
|
||||
"lodash": "^4.16.1",
|
||||
"lodash": "^4.16.2",
|
||||
"lokijs": "^1.4.1",
|
||||
"markdown-it": "^8.0.0",
|
||||
"markdown-it-abbr": "^1.0.4",
|
||||
@ -72,6 +73,7 @@
|
||||
"markdown-it-task-lists": "^1.4.1",
|
||||
"moment": "^2.15.1",
|
||||
"moment-timezone": "^0.5.5",
|
||||
"multer": "^1.2.0",
|
||||
"passport": "^0.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
@ -84,13 +86,13 @@
|
||||
"snyk": "^1.19.1",
|
||||
"socket.io": "^1.4.8",
|
||||
"sticky-js": "^1.0.7",
|
||||
"validator": "^5.7.0",
|
||||
"validator": "^6.0.0",
|
||||
"validator-as-promised": "^1.0.2",
|
||||
"winston": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ace-builds": "^1.2.5",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-es2015": "^6.16.0",
|
||||
"bulma": "^0.1.2",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^5.3.0",
|
||||
@ -99,11 +101,11 @@
|
||||
"font-awesome": "^4.6.3",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^6.1.2",
|
||||
"gulp-clean-css": "^2.0.12",
|
||||
"gulp-clean-css": "^2.0.13",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-gzip": "^1.4.0",
|
||||
"gulp-include": "^2.3.1",
|
||||
"gulp-nodemon": "^2.1.0",
|
||||
"gulp-nodemon": "^2.2.1",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-sass": "^2.3.2",
|
||||
"gulp-tar": "^1.9.0",
|
||||
@ -112,14 +114,15 @@
|
||||
"istanbul": "^0.4.5",
|
||||
"jquery": "^3.1.1",
|
||||
"jquery-contextmenu": "^2.2.4",
|
||||
"jquery-simple-upload": "^1.0.0",
|
||||
"jquery-smooth-scroll": "^2.0.0",
|
||||
"merge-stream": "^1.0.0",
|
||||
"mocha": "^3.0.2",
|
||||
"mocha": "^3.1.0",
|
||||
"mocha-lcov-reporter": "^1.2.0",
|
||||
"nodemon": "^1.10.2",
|
||||
"sticky-js": "^1.0.5",
|
||||
"sticky-js": "^1.1.0",
|
||||
"twemoji-awesome": "^1.0.4",
|
||||
"vue": "^1.0.27"
|
||||
"vue": "^1.0.28"
|
||||
},
|
||||
"snyk": true
|
||||
}
|
||||
|
@ -17,9 +17,11 @@
|
||||
span.icon.is-small: i.fa.fa-folder
|
||||
span New Folder
|
||||
.control.has-addons
|
||||
a.button.is-info.is-outlined(v-on:click="uploadImage")
|
||||
a.button.is-info.is-outlined#btn-editor-uploadimage(v-on:click="uploadImage")
|
||||
span.icon.is-small: i.fa.fa-upload
|
||||
span Upload Image
|
||||
label
|
||||
input(type="file", multiple)
|
||||
a.button.is-info.is-outlined(v-on:click="fetchFromUrl")
|
||||
span.icon.is-small: i.fa.fa-download
|
||||
span Fetch from URL
|
||||
|
Loading…
Reference in New Issue
Block a user