mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Remove usage of jquery-file-upload (#815)
closes https://github.com/TryGhost/Ghost/issues/6661 - refactor `gh-profile-image` component to use native browser functionality - remove `jquery-file-upload` bower dependency
This commit is contained in:
parent
d860403b93
commit
327cbdf7a2
@ -1,7 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import Component from 'ember-component';
|
||||
import injectService from 'ember-service/inject';
|
||||
import request from 'ember-ajax/request';
|
||||
import run from 'ember-runloop';
|
||||
import {htmlSafe} from 'ember-string';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
@ -26,8 +26,12 @@ export default Component.extend({
|
||||
size: 180,
|
||||
debounce: 300,
|
||||
|
||||
imageFile: null,
|
||||
hasUploadedImage: false,
|
||||
|
||||
// closure actions
|
||||
setImage() {},
|
||||
|
||||
config: injectService(),
|
||||
ghostPaths: injectService(),
|
||||
|
||||
@ -43,28 +47,6 @@ export default Component.extend({
|
||||
this._setPlaceholderImage(this._defaultImageUrl);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
let size = this.get('size');
|
||||
let uploadElement = this.$('.js-file-input');
|
||||
|
||||
// while theoretically the 'add' and 'processalways' functions could be
|
||||
// added as properties of the hash passed to fileupload(), for some reason
|
||||
// they needed to be placed in an on() call for the add method to work correctly
|
||||
uploadElement.fileupload({
|
||||
url: this.get('ghostPaths.url').api('uploads'),
|
||||
dropZone: this.$('.js-img-dropzone'),
|
||||
previewMaxHeight: size,
|
||||
previewMaxWidth: size,
|
||||
previewCrop: true,
|
||||
maxNumberOfFiles: 1,
|
||||
autoUpload: false
|
||||
})
|
||||
.on('fileuploadadd', run.bind(this, this.queueFile))
|
||||
.on('fileuploadprocessalways', run.bind(this, this.triggerPreview));
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
@ -73,6 +55,32 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
dragOver(event) {
|
||||
if (!event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is needed to work around inconsistencies with dropping files
|
||||
// from Chrome's downloads bar
|
||||
let eA = event.dataTransfer.effectAllowed;
|
||||
event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy';
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
dragLeave(event) {
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
drop(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.dataTransfer.files) {
|
||||
this.send('imageSelected', event.dataTransfer.files);
|
||||
}
|
||||
},
|
||||
|
||||
setGravatar: task(function* () {
|
||||
yield timeout(this.get('debounce'));
|
||||
|
||||
@ -110,16 +118,6 @@ export default Component.extend({
|
||||
this.set('avatarStyle', htmlSafe(`background-image: url(${url}); display: ${display}`));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
let $input = this.$('.js-file-input');
|
||||
|
||||
this._super(...arguments);
|
||||
|
||||
if ($input.length && $input.data()['blueimp-fileupload']) {
|
||||
$input.fileupload('destroy');
|
||||
}
|
||||
},
|
||||
|
||||
queueFile(e, data) {
|
||||
let fileName = data.files[0].name;
|
||||
|
||||
@ -128,15 +126,40 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
triggerPreview(e, data) {
|
||||
let file = data.files[data.index];
|
||||
actions: {
|
||||
imageSelected(fileList) {
|
||||
// eslint-disable-next-line
|
||||
let imageFile = fileList[0];
|
||||
|
||||
if (file.preview) {
|
||||
this.set('hasUploadedImage', true);
|
||||
// necessary jQuery code because file.preview is a raw DOM object
|
||||
// potential todo: rename 'gravatar-img' class in the CSS to be something
|
||||
// that both the gravatar and the image preview can use that's not so confusing
|
||||
this.$('.js-img-preview').empty().append(this.$(file.preview).addClass('gravatar-img'));
|
||||
if (imageFile) {
|
||||
let reader = new FileReader();
|
||||
|
||||
this.set('imageFile', imageFile);
|
||||
this.setImage(imageFile);
|
||||
|
||||
reader.addEventListener('load', () => {
|
||||
let dataURL = reader.result;
|
||||
this.set('previewDataURL', dataURL);
|
||||
}, false);
|
||||
|
||||
reader.readAsDataURL(imageFile);
|
||||
}
|
||||
},
|
||||
|
||||
openFileDialog(event) {
|
||||
let fileInput = $(event.target)
|
||||
.closest('figure')
|
||||
.find('input[type="file"]');
|
||||
|
||||
if (fileInput.length > 0) {
|
||||
// reset file input value before clicking so that the same image
|
||||
// can be selected again
|
||||
fileInput.value = '';
|
||||
|
||||
// simulate click to open file dialog
|
||||
// using jQuery because IE11 doesn't support MouseEvent
|
||||
$(fileInput).click();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -8,8 +8,6 @@ import {isInvalidError} from 'ember-ajax/errors';
|
||||
import {isVersionMismatchError} from 'ghost-admin/services/ajax';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
const {Promise} = RSVP;
|
||||
|
||||
export default Controller.extend(ValidationEngine, {
|
||||
ajax: injectService(),
|
||||
application: injectController(),
|
||||
@ -95,22 +93,27 @@ export default Controller.extend(ValidationEngine, {
|
||||
* @return {Ember.RSVP.Promise} A promise that takes care of both calls
|
||||
*/
|
||||
_sendImage(user) {
|
||||
let image = this.get('profileImage');
|
||||
let formData = new FormData();
|
||||
let imageFile = this.get('profileImage');
|
||||
let uploadUrl = this.get('ghostPaths.url').api('uploads');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
image.formData = {};
|
||||
return image.submit()
|
||||
.done((response) => {
|
||||
let usersUrl = this.get('ghostPaths.url').api('users', user.id.toString());
|
||||
user.profile_image = response;
|
||||
formData.append('uploadimage', imageFile, imageFile.name);
|
||||
|
||||
return this.get('ajax').put(usersUrl, {
|
||||
data: {
|
||||
users: [user]
|
||||
}
|
||||
}).then(resolve).catch(reject);
|
||||
})
|
||||
.fail(reject);
|
||||
return this.get('ajax').post(uploadUrl, {
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'text'
|
||||
}).then((response) => {
|
||||
let imageUrl = JSON.parse(response);
|
||||
let usersUrl = this.get('ghostPaths.url').api('users', user.id.toString());
|
||||
user.profile_image = imageUrl;
|
||||
|
||||
return this.get('ajax').put(usersUrl, {
|
||||
data: {
|
||||
users: [user]
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -225,19 +228,17 @@ export default Controller.extend(ValidationEngine, {
|
||||
return this._sendImage(result.users[0])
|
||||
.then(() => {
|
||||
// fetch settings and private config for synchronous access before transitioning
|
||||
return RSVP.all(promises)
|
||||
.then(() => {
|
||||
return this.transitionToRoute('setup.three');
|
||||
});
|
||||
return RSVP.all(promises).then(() => {
|
||||
return this.transitionToRoute('setup.three');
|
||||
});
|
||||
}).catch((resp) => {
|
||||
this.get('notifications').showAPIError(resp, {key: 'setup.blog-details'});
|
||||
});
|
||||
} else {
|
||||
// fetch settings and private config for synchronous access before transitioning
|
||||
return RSVP.all(promises)
|
||||
.then(() => {
|
||||
return this.transitionToRoute('setup.three');
|
||||
});
|
||||
return RSVP.all(promises).then(() => {
|
||||
return this.transitionToRoute('setup.three');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -10,8 +10,6 @@ import {assign} from 'ember-platform';
|
||||
import {isEmberArray} from 'ember-array/utils';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
const {Promise} = RSVP;
|
||||
|
||||
export default Controller.extend(ValidationEngine, {
|
||||
ajax: injectService(),
|
||||
config: injectService(),
|
||||
@ -25,7 +23,7 @@ export default Controller.extend(ValidationEngine, {
|
||||
validationType: 'signup',
|
||||
|
||||
flowErrors: '',
|
||||
image: null,
|
||||
profileImage: null,
|
||||
|
||||
authenticate: task(function* (authStrategy, authentication) {
|
||||
try {
|
||||
@ -154,23 +152,30 @@ export default Controller.extend(ValidationEngine, {
|
||||
},
|
||||
|
||||
_sendImage() {
|
||||
let image = this.get('image');
|
||||
let formData = new FormData();
|
||||
let imageFile = this.get('profileImage');
|
||||
let uploadUrl = this.get('ghostPaths.url').api('uploads');
|
||||
|
||||
if (imageFile) {
|
||||
formData.append('uploadimage', imageFile, imageFile.name);
|
||||
|
||||
if (image) {
|
||||
return this.get('session.user').then((user) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
image.formData = {};
|
||||
return image.submit()
|
||||
.done((response) => {
|
||||
let usersUrl = this.get('ghostPaths.url').api('users', user.id.toString());
|
||||
user.image = response;
|
||||
return this.get('ajax').put(usersUrl, {
|
||||
data: {
|
||||
users: [user]
|
||||
}
|
||||
}).then(resolve).catch(reject);
|
||||
})
|
||||
.fail(reject);
|
||||
return this.get('ajax').post(uploadUrl, {
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'text'
|
||||
}).then((response) => {
|
||||
let imageUrl = JSON.parse(response);
|
||||
let usersUrl = this.get('ghostPaths.url').api('users', user.id.toString());
|
||||
// eslint-disable-next-line
|
||||
user.profile_image = imageUrl;
|
||||
|
||||
return this.get('ajax').put(usersUrl, {
|
||||
data: {
|
||||
users: [user]
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,25 @@
|
||||
<figure class="account-image js-file-upload">
|
||||
{{#unless hasUploadedImage}}
|
||||
<figure class="account-image">
|
||||
|
||||
{{#unless previewDataURL}}
|
||||
<div class="placeholder-img" style={{placeholderStyle}}></div>
|
||||
<div id="account-image" class="gravatar-img" style={{avatarStyle}}>
|
||||
<span class="sr-only">User image</span>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="js-img-preview"></div>
|
||||
{{#if previewDataURL}}
|
||||
<img src={{previewDataURL}} class="gravatar-img">
|
||||
{{/if}}
|
||||
|
||||
<span class="edit-account-image js-img-dropzone">
|
||||
<span class="edit-account-image" onclick={{action "openFileDialog"}} role="button">
|
||||
{{inline-svg "photos"}}
|
||||
<span class="sr-only">Upload an image</span>
|
||||
</span>
|
||||
<input type="file" class="file-uploader js-file-input" name="uploadimage">
|
||||
|
||||
{{gh-file-input
|
||||
alt=null
|
||||
name="uploadimage"
|
||||
multiple=false
|
||||
action=(action "imageSelected")
|
||||
accept=imageMimeTypes}}
|
||||
</figure>
|
||||
|
@ -4,7 +4,6 @@
|
||||
"devicejs": "0.2.7",
|
||||
"Faker": "3.1.0",
|
||||
"google-caja": "6005.0.0",
|
||||
"jquery-file-upload": "9.12.3",
|
||||
"jquery-ui": "1.11.4",
|
||||
"jquery.simulate.drag-sortable": "0.1.0",
|
||||
"jqueryui-touch-punch": "furf/jquery-ui-touch-punch#4bc009145202d9c7483ba85f3a236a8f3470354d",
|
||||
|
@ -186,11 +186,6 @@ module.exports = function (defaults) {
|
||||
app.import('bower_components/jquery-ui/ui/draggable.js');
|
||||
app.import('bower_components/jquery-ui/ui/droppable.js');
|
||||
app.import('bower_components/jquery-ui/ui/sortable.js');
|
||||
|
||||
app.import('bower_components/jquery-file-upload/js/jquery.fileupload.js');
|
||||
app.import('bower_components/blueimp-load-image/js/load-image.all.min.js');
|
||||
app.import('bower_components/jquery-file-upload/js/jquery.fileupload-process.js');
|
||||
app.import('bower_components/jquery-file-upload/js/jquery.fileupload-image.js');
|
||||
app.import('bower_components/google-caja/html-css-sanitizer-bundle.js');
|
||||
app.import('bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user