Added drag/drop and paste support for video files

refs https://github.com/TryGhost/Team/issues/1229

- generalized `insertImageCards()` to `insertCardsFromFiles()` and added support for video cards
- added `canInsertCardsFromFiles()` function so the editor can check before starting an editor run loop and generating an undo point
This commit is contained in:
Kevin Ansfield 2021-11-29 13:38:31 +00:00
parent 8fa3096b41
commit b5ccca852e
2 changed files with 50 additions and 36 deletions

View File

@ -21,6 +21,7 @@ import {TrackedObject} from 'tracked-built-ins';
import {action} from '@ember/object';
import {assign} from '@ember/polyfills';
import {camelize, capitalize} from '@ember/string';
import {canInsertCardsFromFiles, insertCardsFromFiles} from '../utils/insert-cards-from-files';
import {captureMessage} from '@sentry/browser';
import {createParserPlugins} from '@tryghost/kg-parser-plugins';
import {getContentFromPasteEvent} from 'mobiledoc-kit/utils/parse-utils';
@ -29,7 +30,6 @@ import {getOwner} from '@ember/application';
import {getParent} from '../lib/dnd/utils';
import {utils as ghostHelperUtils} from '@tryghost/helpers';
import {guidFor} from '@ember/object/internals';
import {insertImageCards} from '../utils/insert-cards-from-files';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {svgJar} from 'ghost-admin/helpers/svg-jar';
@ -956,19 +956,11 @@ export default Component.extend({
return;
}
// if we have image files pasted, create an image card for each and set
// the payload.files property which will cause the image to be auto-uploaded
// NOTE: browser support varies as of May 2018:
// - Safari: will paste all images
// - Chrome: will only paste the first image
// - Firefox: will not paste any images
let images = Array.from(event.clipboardData.files).filter(file => file.type.indexOf('image') > -1);
if (images.length > 0) {
event.preventDefault();
event.stopImmediatePropagation();
// if we have files pasted, create a card for each and set the
// payload.files property which will cause the file to be auto-uploaded
if (canInsertCardsFromFiles(event.clipboardData.files)) {
editor.run((postEditor) => {
insertImageCards(images, postEditor);
insertCardsFromFiles(event.clipboardData.files, postEditor);
});
return;
}
@ -1139,14 +1131,11 @@ export default Component.extend({
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);
});
this._scrollCursorIntoView({jumpToCard: true});
}
if (canInsertCardsFromFiles(event.dataTransfer.files)) {
this.editor.run((postEditor) => {
insertCardsFromFiles(event.dataTransfer.files, postEditor);
});
this._scrollCursorIntoView({jumpToCard: true});
}
},

View File

@ -1,12 +1,22 @@
// helper function to insert image cards at or after the current active section
export function canInsertCardsFromFiles(files) {
return filterAllowedFiles(files).length > 0;
}
// helper function to insert cards at or after the current active section
// used when pasting or dropping image files
export function insertImageCards(files, postEditor) {
let {builder, editor} = postEditor;
let collection = editor.post.sections;
export function insertCardsFromFiles(_files, postEditor) {
const files = filterAllowedFiles(_files);
if (!files.length) {
return;
}
const {builder, editor} = postEditor;
const collection = editor.post.sections;
let section = editor.activeSection;
// when dropping an image on the editor before it's had focus there will be
// no active section so we insert the image at the end of the document
// when dropping an file on the editor before it's had focus there will be
// no active section so we insert the card at the end of the document
if (!section) {
section = editor.post.sections.tail;
@ -14,7 +24,7 @@ export function insertImageCards(files, postEditor) {
// we use `insertSectionBefore` and don't want the image to be added
// before the last card
if (!section.isMarkerable) {
let blank = builder.createMarkupSection();
const blank = builder.createMarkupSection();
postEditor.insertSectionAtEnd(blank);
postEditor.setRange(blank.toRange());
section = postEditor._range.head.section;
@ -29,8 +39,8 @@ export function insertImageCards(files, postEditor) {
// list items cannot contain card sections so insert a blank paragraph after
// the whole list ready to be replaced by the image cards
if (section.isListItem) {
let list = section.parent;
let blank = builder.createMarkupSection();
const list = section.parent;
const blank = builder.createMarkupSection();
if (list.next) {
postEditor.insertSectionBefore(collection, blank, list.next);
} else {
@ -40,15 +50,16 @@ export function insertImageCards(files, postEditor) {
section = postEditor._range.head.section;
}
// insert an image card for each image, keep track of the last card to be
// insert a card for each file, keep track of the last card to be
// inserted so that the cursor can be placed on it at the end
let lastImageSection;
let lastCardSection;
files.forEach((file) => {
let payload = {
const cardName = getCardNameFromFile(file);
const payload = {
files: [file]
};
lastImageSection = builder.createCardSection('image', payload);
postEditor.insertSectionBefore(collection, lastImageSection, section);
lastCardSection = builder.createCardSection(cardName, payload);
postEditor.insertSectionBefore(collection, lastCardSection, section);
});
// remove the current section if it's blank - avoids unexpected blank
@ -58,5 +69,19 @@ export function insertImageCards(files, postEditor) {
}
// place cursor on the last inserted image
postEditor.setRange(lastImageSection.tailPosition());
postEditor.setRange(lastCardSection.tailPosition());
}
function filterAllowedFiles(files) {
return Array.from(files).filter(file => file.type.match(/^(image|video)/));
}
function getCardNameFromFile(file) {
if (file.type.startsWith('image')) {
return 'image';
}
if (file.type.startsWith('video')) {
return 'video';
}
}