@atlaskit/editor-plugin-image-upload
Version:
Image upload plugin for @atlaskit/editor-core
190 lines (184 loc) • 8.11 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _regeneratorRuntime from "@babel/runtime/regenerator";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { isPastedFile } from '@atlaskit/editor-common/paste';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { isClipboardEvent } from '../ui/hooks/clipboard';
import { isDragEvent, isDroppedFile } from '../ui/hooks/drag-drop';
import { canInsertMedia, isMediaSelected } from '../ui/hooks/utils';
import { insertExternalImage, startImageUpload } from './commands';
import { stateKey } from './plugin-key';
/**
* Microsoft Office includes a screenshot image when copying text content.
*
* This function determines whether or not we can ignore the image if it
* came from MS Office. We do this by checking for
*
* - plain text
* - HTML text which includes the MS Office namespace
* - the number of files and the file name/type
*
* It is easy to manually verify this using by using Office on Mac
* (or Excel if on Windows) and pasting into
* https://evercoder.github.io/clipboard-inspector/
*
* Note: image content in Word is stored in the `text/html` portion
* of the clipboard, not under `files` attachment like the screenshot.
*
* @returns boolean True if the paste event contains a screenshot from MS Office
*/
var hasScreenshotImageFromMSOffice = function hasScreenshotImageFromMSOffice(ev) {
var _ref = ev,
clipboardData = _ref.clipboardData;
if (!clipboardData || clipboardData.files.length !== 1) {
return false;
}
var textPlain = !!clipboardData.getData('text/plain');
var textHtml = clipboardData.getData('text/html');
var isOfficeXMLNamespace = textHtml.includes('urn:schemas-microsoft-com:office:office');
var file = clipboardData.files[0];
var isImagePNG = file.type === 'image/png' && file.name === 'image.png';
return isImagePNG && textPlain && isOfficeXMLNamespace;
};
var createReferenceEventFromEvent = function createReferenceEventFromEvent(event) {
var _event$dataTransfer, _event$clipboardData;
if (!isDragEvent(event) && !isClipboardEvent(event)) {
return null;
}
// Get files list and early exit if files is undefined
var files = isDragEvent(event) ? (_event$dataTransfer = event.dataTransfer) === null || _event$dataTransfer === void 0 ? void 0 : _event$dataTransfer.files : (_event$clipboardData = event.clipboardData) === null || _event$clipboardData === void 0 ? void 0 : _event$clipboardData.files;
if (!files) {
return null;
}
// Convert filelist into an array
var filesArray = Array.from(files);
// Creating a new DataTransfer object should remove any mutation that could be possible from the original event
var dataTransfer = filesArray.reduce(function (acc, value) {
acc.items.add(value);
return acc;
}, new DataTransfer());
return _objectSpread(_objectSpread({
type: isDragEvent(event) ? 'drop' : 'paste'
}, isDragEvent(event) && {
dataTransfer: dataTransfer
}), isClipboardEvent(event) && {
clipboardData: dataTransfer
});
};
var createDOMHandler = function createDOMHandler(pred, _eventName, uploadHandlerReference) {
return function (view, event) {
if (!pred(event)) {
return false;
}
var shouldUpload = !hasScreenshotImageFromMSOffice(event);
var referenceEvent = createReferenceEventFromEvent(event);
if (shouldUpload && referenceEvent) {
event.preventDefault();
event.stopPropagation();
// Insert external image into document
if (uploadHandlerReference.current) {
uploadHandlerReference.current(referenceEvent, function (options) {
insertExternalImage(options)(view.state, view.dispatch);
});
}
// Start image upload
startImageUpload(referenceEvent)(view.state, view.dispatch);
}
return shouldUpload;
};
};
var getNewActiveUpload = function getNewActiveUpload(tr, pluginState) {
var meta = tr.getMeta(stateKey);
if (meta && meta.name === 'START_UPLOAD') {
return {
event: meta.event
};
}
return pluginState.activeUpload;
};
export var createPlugin = function createPlugin(uploadHandlerReference) {
return function (_ref2) {
var dispatch = _ref2.dispatch,
providerFactory = _ref2.providerFactory;
return new SafePlugin({
state: {
init: function init(_config, state) {
return {
active: false,
enabled: canInsertMedia(state),
hidden: !state.schema.nodes.media || !state.schema.nodes.mediaSingle
};
},
apply: function apply(tr, pluginState, _oldState, newState) {
var newActive = isMediaSelected(newState);
var newEnabled = canInsertMedia(newState);
var newActiveUpload = getNewActiveUpload(tr, pluginState);
if (newActive !== pluginState.active || newEnabled !== pluginState.enabled || newActiveUpload !== pluginState.activeUpload) {
var newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, {
active: newActive,
enabled: newEnabled,
activeUpload: newActiveUpload
});
dispatch(stateKey, newPluginState);
return newPluginState;
}
return pluginState;
}
},
key: stateKey,
view: function view() {
var handleProvider = /*#__PURE__*/function () {
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(name, provider) {
var imageUploadProvider;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
if (!(name !== 'imageUploadProvider' || !provider)) {
_context.next = 2;
break;
}
return _context.abrupt("return");
case 2:
_context.prev = 2;
_context.next = 5;
return provider;
case 5:
imageUploadProvider = _context.sent;
uploadHandlerReference.current = imageUploadProvider;
_context.next = 12;
break;
case 9:
_context.prev = 9;
_context.t0 = _context["catch"](2);
uploadHandlerReference.current = null;
case 12:
case "end":
return _context.stop();
}
}, _callee, null, [[2, 9]]);
}));
return function handleProvider(_x, _x2) {
return _ref3.apply(this, arguments);
};
}();
providerFactory.subscribe('imageUploadProvider', handleProvider);
return {
destroy: function destroy() {
uploadHandlerReference.current = null;
providerFactory.unsubscribe('imageUploadProvider', handleProvider);
}
};
},
props: {
handleDOMEvents: {
drop: createDOMHandler(isDroppedFile, 'atlassian.editor.image.drop', uploadHandlerReference),
paste: createDOMHandler(function (event) {
return isPastedFile(event);
}, 'atlassian.editor.image.paste', uploadHandlerReference)
}
}
});
};
};