Add paste image (#10212)
Co-authored-by: Nick Taylor <nick@iamdeveloper.com> Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com> Co-authored-by: Nick Taylor <nick@dev.to> Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
parent
39a63c17f2
commit
9ef1534d83
5 changed files with 161 additions and 1 deletions
|
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
|
|||
import Textarea from 'preact-textarea-autosize';
|
||||
import { useEffect, useRef } from 'preact/hooks';
|
||||
import { Toolbar } from './Toolbar';
|
||||
import { handleImagePasted } from './pasteImageHelpers';
|
||||
import {
|
||||
handleImageDrop,
|
||||
handleImageFailure,
|
||||
onDragOver,
|
||||
onDragExit,
|
||||
} from './dragAndDropHelpers';
|
||||
import { usePasteImage } from '@utilities/pasteImage';
|
||||
import { useDragAndDrop } from '@utilities/dragAndDrop';
|
||||
|
||||
function handleImageSuccess(textAreaRef) {
|
||||
|
|
@ -17,7 +19,9 @@ function handleImageSuccess(textAreaRef) {
|
|||
// textarea ref.
|
||||
const editableBodyElement = textAreaRef.current.base;
|
||||
const { links, image } = response;
|
||||
const altText = image[0].name.replace(/\.[^.]+$/, '');
|
||||
const altText = image[0]
|
||||
? image[0].name.replace(/\.[^.]+$/, '')
|
||||
: 'alt text';
|
||||
const markdownImageLink = `\n`;
|
||||
const { selectionStart, selectionEnd, value } = editableBodyElement;
|
||||
const before = value.substring(0, selectionStart);
|
||||
|
|
@ -50,9 +54,17 @@ export const EditorBody = ({
|
|||
onDragExit,
|
||||
});
|
||||
|
||||
const setPasteElement = usePasteImage({
|
||||
onPaste: handleImagePasted(
|
||||
handleImageSuccess(textAreaRef),
|
||||
handleImageFailure,
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (textAreaRef.current) {
|
||||
setElement(textAreaRef.current.base);
|
||||
setPasteElement(textAreaRef.current.base);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { matchesDataTransferType } from '../pasteImageHelpers';
|
||||
|
||||
describe('pasteImageHelpers', () => {
|
||||
describe('matchesDataTransferType', () => {
|
||||
it('returns false if no types are provided', () => {
|
||||
expect(matchesDataTransferType([])).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if at least one type matches Files', () => {
|
||||
const result = matchesDataTransferType(['example', 'Files', 'other']);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if no types match type Files', () => {
|
||||
const result = matchesDataTransferType(['example', 'other']);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if at least one type matches a custom type provided', () => {
|
||||
const result = matchesDataTransferType(['example', 'other'], 'other');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if no types match a custom type provided', () => {
|
||||
const result = matchesDataTransferType(['example', 'Files'], 'other');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
55
app/javascript/article-form/components/pasteImageHelpers.js
Normal file
55
app/javascript/article-form/components/pasteImageHelpers.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { addSnackbarItem } from '../../Snackbar';
|
||||
import { processImageUpload } from '../actions';
|
||||
|
||||
/**
|
||||
* Determines if at least one type of drag and drop datum type matches the data transfer type to match.
|
||||
*
|
||||
* @param {string[]} types An array of data transfer types.
|
||||
* @param {string} dataTransferType The data transfer type to match.
|
||||
*/
|
||||
export function matchesDataTransferType(
|
||||
types = [],
|
||||
dataTransferType = 'Files',
|
||||
) {
|
||||
return types.some((type) => type === dataTransferType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for when image is pasted.
|
||||
*
|
||||
* @param {function} handleImageSuccess Callback for when image upload succeeds
|
||||
* @param {function} handleImageFailure Callback for when image upload fails
|
||||
*/
|
||||
export function handleImagePasted(handleImageSuccess, handleImageFailure) {
|
||||
return function (event) {
|
||||
if (!event.clipboardData || !event.clipboardData.items) return;
|
||||
if (!matchesDataTransferType(event.clipboardData.types)) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const { files } = event.clipboardData;
|
||||
|
||||
if (files.length > 1) {
|
||||
addSnackbarItem({
|
||||
message: 'Only one image can be pasted at a time.',
|
||||
addCloseButton: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
processImageUpload(files, handleImageSuccess, handleImageFailure);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for when image upload fails.
|
||||
*
|
||||
* @param {Error} error an error
|
||||
* @param {string} error.message an error message
|
||||
*/
|
||||
export function handleImageFailure({ message }) {
|
||||
addSnackbarItem({
|
||||
message,
|
||||
addCloseButton: true,
|
||||
});
|
||||
}
|
||||
25
app/javascript/utilities/__tests__/pasteImage.test.js
Normal file
25
app/javascript/utilities/__tests__/pasteImage.test.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { renderHook, act } from '@testing-library/preact-hooks';
|
||||
import { fireEvent } from '@testing-library/preact';
|
||||
import { usePasteImage } from '../pasteImage';
|
||||
|
||||
describe('usePasteImage', () => {
|
||||
it('listens for paste events on the set element', () => {
|
||||
const handlePasteImage = jest.fn();
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div id="paste-area"></div>
|
||||
`;
|
||||
const element = document.getElementById('paste-area');
|
||||
|
||||
const {
|
||||
result: { current: setElement },
|
||||
} = renderHook(() => usePasteImage({ onPaste: handlePasteImage }));
|
||||
|
||||
act(() => {
|
||||
setElement(element);
|
||||
});
|
||||
|
||||
fireEvent.paste(element, { clipboardData: {} });
|
||||
expect(handlePasteImage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
39
app/javascript/utilities/pasteImage.js
Normal file
39
app/javascript/utilities/pasteImage.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { useEffect, useState } from 'preact/hooks';
|
||||
|
||||
/**
|
||||
* A custom Preact hook used to attach a catch all for pasting images to a DOM element.
|
||||
* @example
|
||||
* function SomeComponent(props) {
|
||||
* const { setElement } = usePasteImage({
|
||||
* onPaste: somePasteHandler
|
||||
* });
|
||||
*
|
||||
* const someDomRef = useRef(null);
|
||||
*
|
||||
* useEffect(() => {
|
||||
* if (someDomRef.current) {
|
||||
* setElement(someDomRef.current);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* return <textarea ref={someDomRef}>I'm a text area</textarea>;
|
||||
* };
|
||||
*
|
||||
* @param {object} props
|
||||
* @param {Function} props.onPaste The handler that runs when the paste event is fired.
|
||||
*/
|
||||
export function usePasteImage({ onPaste }) {
|
||||
const [element, setElement] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!element) return;
|
||||
|
||||
element.addEventListener('paste', onPaste);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('paste', onPaste);
|
||||
};
|
||||
}, [element, onPaste]);
|
||||
|
||||
return setElement;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue