Ghost/ghost/admin/lib/koenig-editor/addon/components/koenig-card-image.js
Kevin Ansfield cb7a038b08 Koenig - Fixed backspace not removing card after inserting Unsplash image
refs https://github.com/TryGhost/Ghost/issues/9724
- focus was not being returned to the editor after the Unsplash modal was closed so although the image card looked selected the editor was not responding to any key commands
2018-08-11 18:30:59 +01:00

264 lines
7.2 KiB
JavaScript

import $ from 'jquery';
import Component from '@ember/component';
import countWords, {stripTags} from '../utils/count-words';
import layout from '../templates/components/koenig-card-image';
import {
IMAGE_EXTENSIONS,
IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {computed, set, setProperties} 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() {},
deleteCard() {},
moveCursorToNextSection() {},
moveCursorToPrevSection() {},
addParagraphAfterCard() {},
registerComponent() {},
imageSelector: computed('payload.imageSelector', function () {
let selector = this.payload.imageSelector;
let imageSelectors = {
unsplash: 'gh-unsplash'
};
return imageSelectors[selector];
}),
counts: computed('payload.{src,caption}', function () {
let wordCount = 0;
let imageCount = 0;
if (this.payload.src) {
imageCount += 1;
}
if (this.payload.caption) {
wordCount += countWords(stripTags(this.payload.caption));
}
return {wordCount, imageCount};
}),
kgImgStyle: computed('payload.cardWidth', function () {
let cardWidth = this.payload.cardWidth;
if (cardWidth === 'wide') {
return 'image-wide';
}
if (cardWidth === 'full') {
return 'image-full';
}
return 'image-normal';
}),
toolbar: computed('payload.{cardWidth,src}', function () {
let cardWidth = this.payload.cardWidth;
let items = [];
if (this.payload.src) {
items.push({
title: 'Regular',
icon: 'koenig/kg-img-regular',
iconClass: `${!cardWidth ? 'fill-blue-l2' : 'fill-white'}`,
action: run.bind(this, this._changeCardWidth, '')
});
items.push({
title: 'Wide',
icon: 'koenig/kg-img-wide',
iconClass: `${cardWidth === 'wide' ? 'fill-blue-l2' : 'fill-white'}`,
action: run.bind(this, this._changeCardWidth, 'wide')
});
items.push({
title: 'Full',
icon: 'koenig/kg-img-full',
iconClass: `${cardWidth === 'full' ? 'fill-blue-l2' : 'fill-white'}`,
action: run.bind(this, this._changeCardWidth, 'full')
});
items.push({divider: true});
items.push({
title: 'Replace image',
icon: 'koenig/kg-replace',
iconClass: 'fill-white',
action: run.bind(this, this._triggerFileDialog)
});
}
if (items.length > 0) {
return {items};
}
}),
init() {
this._super(...arguments);
if (!this.payload) {
this.set('payload', {});
}
this.registerComponent(this);
},
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);
},
selectFromImageSelector({src, caption, alt}) {
let {payload, saveCard} = this;
let searchTerm;
setProperties(payload, {src, caption, alt, searchTerm});
this.send('closeImageSelector');
saveCard(payload, false);
},
closeImageSelector() {
if (!this.payload.src) {
return this.deleteCard();
}
set(this.payload, 'imageSelector', undefined);
// ensure focus is returned to the editor so that the card which
// appears selected behaves as if it's selected
this.editor.focus();
}
},
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]]);
}
},
_changeCardWidth(cardWidth) {
this._updatePayloadAttr('cardWidth', cardWidth);
},
_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();
}
});