Koenig - Drag & drop image card creation + upload

refs https://github.com/TryGhost/Ghost/issues/9623
- add `dropTargetSelector` property so that `{{koenig-editor}}` can attach drag/drop events to a larger area
- add drag/drop event handlers to `{{koenig-editor}}`
    - ignore drops that occur on cards in edit mode (cards should handle drop events themselves if required)
    - extract image files from the drop event and use them to create image cards
- extract image card creation from image files into a helper function
This commit is contained in:
Kevin Ansfield 2018-05-30 17:14:39 +01:00
parent 7363ed62c6
commit dc2e85a6e0
2 changed files with 98 additions and 28 deletions

View File

@ -17,12 +17,13 @@
placeholder=bodyPlaceholder
autofocus=bodyAutofocus
spellcheck=true
headerOffset=headerOffset
onChange=(action "onBodyChange")
didCreateEditor=(action "onEditorCreated")
cursorDidExitAtTop=(action "focusTitle")
headerOffset=headerOffset
dropTargetSelector=".gh-koenig-editor-pane"
scrollContainerSelector=scrollContainerSelector
scrollOffsetTopSelector=scrollOffsetTopSelector
scrollOffsetBottomSelector=scrollOffsetBottomSelector
}}
</div>
</div>

View File

@ -105,6 +105,26 @@ function toggleSpecialFormatEditState(editor) {
}
}
// helper function to insert image cards at or after the current active section
// used when pasting or dropping image files
function insertImageCards(files, postEditor) {
let {builder, editor} = postEditor;
// remove the current section if it's blank - avoids unexpected blank line
// after the insert is complete
if (editor.activeSection && editor.activeSection.isBlank) {
postEditor.removeSection(editor.activeSection);
}
files.forEach((file) => {
let payload = {
files: [file]
};
let imageCard = builder.createCardSection('image', payload);
postEditor.insertSection(imageCard);
});
}
export default Component.extend({
layout,
@ -118,6 +138,10 @@ export default Component.extend({
spellcheck: true,
options: null,
headerOffset: 0,
dropTargetSelector: null,
scrollContainerSelector: null,
scrollOffsetTopSelector: null,
scrollOffsetBottomSelector: null,
// internal properties
editor: null,
@ -362,6 +386,14 @@ export default Component.extend({
if (this.scrollContainerSelector) {
this._scrollContainer = document.querySelector(this.scrollContainerSelector);
}
this._dropTarget = document.querySelector(this.dropTargetSelector) || this.element;
this._dragOverHandler = run.bind(this, this.handleDragOver);
this._dragLeaveHandler = run.bind(this, this.handleDragLeave);
this._dropHandler = run.bind(this, this.handleDrop);
this._dropTarget.addEventListener('dragover', this._dragOverHandler);
this._dropTarget.addEventListener('dragleave', this._dragLeaveHandler);
this._dropTarget.addEventListener('drop', this._dropHandler);
},
// our ember component has rendered, now we need to render the mobiledoc
@ -378,10 +410,15 @@ export default Component.extend({
},
willDestroyElement() {
let editor = this.editor;
let editorElement = this.element.querySelector('[data-kg="editor"]');
let {editor, _dropTarget} = this;
_dropTarget.removeEventListener('dragover', this._dragOverHandler);
_dropTarget.removeEventListener('dragleave', this._dragLeaveHandler);
_dropTarget.removeEventListener('drop', this._dropHandler);
let editorElement = this.element.querySelector('[data-kg="editor"]');
editorElement.removeEventListener('paste', this._pasteHandler);
editor.destroy();
this._super(...arguments);
},
@ -640,13 +677,12 @@ export default Component.extend({
/* custom event handlers ------------------------------------------------ */
handlePaste(event) {
let editor = this.editor;
let {target: element} = event;
let {editor} = this;
// don't trigger our paste handling for pastes within cards or outside
// of the editor canvas. Avoids double-paste of content when pasting
// into cards
if (!editor.cursor.isAddressable(element)) {
if (!editor.cursor.isAddressable(event.target)) {
return;
}
@ -662,19 +698,7 @@ export default Component.extend({
event.stopImmediatePropagation();
editor.run((postEditor) => {
let {builder} = postEditor;
if (editor.activeSection.isBlank) {
postEditor.removeSection(editor.activeSection);
}
images.forEach((image) => {
let payload = {
files: [image]
};
let imageCard = builder.createCardSection('image', payload);
postEditor.insertSection(imageCard);
});
insertImageCards(images, postEditor);
});
return;
}
@ -779,6 +803,51 @@ export default Component.extend({
}
},
handleDragOver(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';
}
// indicate to the browser that we want to handle drop behaviour here
event.stopPropagation();
event.preventDefault();
},
handleDragLeave(event) {
event.preventDefault();
},
handleDrop(event) {
// drops on cards that are in an edit state should be cancelled
// editable cards should handle drag-n-drop themselves if needed
let cardElem = event.target.closest('.__mobiledoc-card');
if (cardElem) {
let cardId = cardElem.firstChild.id;
let card = this.componentCards.findBy('destinationElementId', cardId);
if (card.isEditing) {
return;
}
}
event.preventDefault();
if (event.dataTransfer.files) {
let images = Array.from(event.dataTransfer.files).filter(file => file.type.indexOf('image') > -1);
if (images.length > 0) {
this.editor.run((postEditor) => {
insertImageCards(images, postEditor);
});
}
}
},
/* Ember event handlers ------------------------------------------------- */
// disable dragging
@ -941,14 +1010,6 @@ export default Component.extend({
}
},
// store a reference to the editor for the acceptance test helpers
_setExpandoProperty(editor) {
let config = getOwner(this).resolveRegistration('config:environment');
if (this.element && config.environment === 'test') {
this.element[TESTING_EXPANDO_PROPERTY] = editor;
}
},
_scrollCursorIntoView() {
// disable auto-scroll if the mouse or shift key is being used to create
// a selection - the browser handles scrolling well in this case
@ -1015,5 +1076,13 @@ export default Component.extend({
}
}
}
},
// store a reference to the editor for the acceptance test helpers
_setExpandoProperty(editor) {
let config = getOwner(this).resolveRegistration('config:environment');
if (this.element && config.environment === 'test') {
this.element[TESTING_EXPANDO_PROPERTY] = editor;
}
}
});