Ghost/ghost/admin/lib/koenig-editor/addon/components/koenig-card-image.js
Kevin Ansfield bd27295ede Koenig - Added drag-n-drop upload/replace support in image card
refs https://github.com/TryGhost/Ghost/issues/9623
- do not process drop in `{{koenig-editor}}` if the drop happened on a card and the card's  `handlesDragDrop` property is true
- allow `dragover` events on cards to bubble up to Ember's event handler in `{{koenig-editor}}`
- handle drag/drop in `{{koenig-card-image}}`
    - show different overlays when dragging files over the card to indicate an upload or replace action
    - start upload when a file is dropped on the card
2018-06-20 12:34:30 +01:00

212 lines
5.8 KiB
JavaScript

import $ from 'jquery';
import Component from '@ember/component';
import layout from '../templates/components/koenig-card-image';
import {
IMAGE_EXTENSIONS,
IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {computed, set} from '@ember/object';
import {htmlSafe} from '@ember/string';
import {isEmpty} from '@ember/utils';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
export default Component.extend({
ui: service(),
layout,
// attrs
files: null,
payload: null,
isSelected: false,
isEditing: false,
imageExtensions: IMAGE_EXTENSIONS,
imageMimeTypes: IMAGE_MIME_TYPES,
// properties
handlesDragDrop: true,
// closure actions
selectCard() {},
deselectCard() {},
editCard() {},
saveCard() {},
moveCursorToNextSection() {},
moveCursorToPrevSection() {},
addParagraphAfterCard() {},
kgImgStyle: computed('payload.imageStyle', function () {
let imageStyle = this.payload.imageStyle;
if (imageStyle === 'wide') {
return 'image-wide';
}
if (imageStyle === 'full') {
return 'image-full';
}
return 'image-normal';
}),
toolbar: computed('payload.{imageStyle,src}', function () {
let imageStyle = this.payload.imageStyle;
let items = [];
if (this.payload.src) {
items.push({
title: 'Regular',
icon: 'koenig/kg-img-regular',
iconClass: `${!imageStyle ? 'stroke-blue-l2' : 'stroke-white'}`,
action: run.bind(this, this._changeImageStyle, '')
});
items.push({
title: 'Wide',
icon: 'koenig/kg-img-wide',
iconClass: `${imageStyle === 'wide' ? 'stroke-blue-l2' : 'stroke-white'}`,
action: run.bind(this, this._changeImageStyle, 'wide')
});
items.push({
title: 'Full',
icon: 'koenig/kg-img-full',
iconClass: `${imageStyle === 'full' ? 'stroke-blue-l2' : 'stroke-white'}`,
action: run.bind(this, this._changeImageStyle, 'full')
});
items.push({divider: true});
items.push({
title: 'Replace image',
icon: 'koenig/kg-replace',
iconClass: '',
action: run.bind(this, this._triggerFileDialog)
});
}
if (items.length > 0) {
return {items};
}
}),
init() {
this._super(...arguments);
if (!this.payload) {
this.set('payload', {});
}
},
didReceiveAttrs() {
this._super(...arguments);
// `payload.files` can be set if we have an externaly set image that
// should be uploaded. Typical example would be from a paste or drag/drop
if (!isEmpty(this.payload.files)) {
run.schedule('afterRender', this, function () {
this.set('files', this.payload.files);
// we don't want to persist any file data in the document
delete this.payload.files;
});
}
},
actions: {
updateSrc(images) {
let [image] = images;
this._updatePayloadAttr('src', image.url);
},
updateCaption(caption) {
this._updatePayloadAttr('caption', caption);
},
/**
* Opens a file selection dialog - Triggered by "Upload Image" buttons,
* searches for the hidden file input within the .gh-setting element
* containing the clicked button then simulates a click
* @param {MouseEvent} event - MouseEvent fired by the button click
*/
triggerFileDialog(event) {
this._triggerFileDialog(event);
},
setPreviewSrc(files) {
let file = files[0];
if (file) {
let reader = new FileReader();
reader.onload = (e) => {
this.set('previewSrc', htmlSafe(e.target.result));
};
reader.readAsDataURL(file);
}
},
resetSrcs() {
this.set('previewSrc', null);
this._updatePayloadAttr('src', null);
}
},
dragOver(event) {
if (!event.dataTransfer) {
return;
}
// this is needed to work around inconsistencies with dropping files
// from Chrome's downloads bar
if (navigator.userAgent.indexOf('Chrome') > -1) {
let eA = event.dataTransfer.effectAllowed;
event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy';
}
event.stopPropagation();
event.preventDefault();
this.set('isDraggedOver', true);
},
dragLeave(event) {
event.preventDefault();
this.set('isDraggedOver', false);
},
drop(event) {
event.preventDefault();
this.set('isDraggedOver', false);
if (event.dataTransfer.files) {
this.set('files', [event.dataTransfer.files[0]]);
}
},
_changeImageStyle(imageStyle) {
this._updatePayloadAttr('imageStyle', imageStyle);
},
_updatePayloadAttr(attr, value) {
let payload = this.payload;
let save = this.saveCard;
set(payload, attr, value);
// update the mobiledoc and stay in edit mode
save(payload, false);
},
_triggerFileDialog(event) {
let target = event && event.target || this.element;
// simulate click to open file dialog
// using jQuery because IE11 doesn't support MouseEvent
$(target)
.closest('.__mobiledoc-card')
.find('input[type="file"]')
.click();
}
});