UNPKG

@atlaskit/editor-plugin-image-upload

Version:

Image upload plugin for @atlaskit/editor-core

190 lines (184 loc) 8.11 kB
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) } } }); }; };