docbrown/app/javascript/article-form/components/EditorBody.jsx
Nick Taylor f0963c1bfa
Drag and drop an image in the editor (#10145)
* Refactored drag n drop functionality to a hook and component.

* Add the snackbar to the article editor.

* Now you can drag and drop images in the post body content.

* Cleaning up a few things.

* Now post cover images can be dragged and dropped.

* Removed unused empty function.

* Added @testing-library/preact-hooks so that we can test hooks.

* Some renaming, small refactor.

* Added tests

* Added more tests.

* Added more tests.

* Added message about dropping only one image in post body.

* Added message about dropping only one image for cover image.

* put onDrop into it's own meethod.

* More tests.

* Added some API documentation.

* Updated editor help with drag and drop image help.

* Changed wording.

* Now the alt text for markdown removes the file extension.

* Updated help wording. Thanks @rhymes

* Now the image markdown adds a new line at the end as we currently do not support inline images.
2020-09-04 14:12:59 -04:00

90 lines
2.6 KiB
JavaScript

import { h } from 'preact';
import PropTypes from 'prop-types';
import Textarea from 'preact-textarea-autosize';
import { useEffect, useRef } from 'preact/hooks';
import { Toolbar } from './Toolbar';
import {
handleImageDrop,
handleImageFailure,
onDragOver,
onDragExit,
} from './dragAndDropHelpers';
import { useDragAndDrop } from '@utilities/dragAndDrop';
function handleImageSuccess(textAreaRef) {
return function (response) {
// Function is within the component to be able to access
// textarea ref.
const editableBodyElement = textAreaRef.current.base;
const { links, image } = response;
const altText = image[0].name.replace(/\.[^.]+$/, '');
const markdownImageLink = `![${altText}](${links[0]})\n`;
const { selectionStart, selectionEnd, value } = editableBodyElement;
const before = value.substring(0, selectionStart);
const after = value.substring(selectionEnd, value.length);
editableBodyElement.value = `${before + markdownImageLink} ${after}`;
editableBodyElement.selectionStart =
selectionStart + markdownImageLink.length;
editableBodyElement.selectionEnd = editableBodyElement.selectionStart;
// Dispatching a new event so that linkstate, https://github.com/developit/linkstate,
// the function used to create the onChange prop gets called correctly.
editableBodyElement.dispatchEvent(new Event('input'));
};
}
export const EditorBody = ({
onChange,
defaultValue,
switchHelpContext,
version,
}) => {
const textAreaRef = useRef(null);
const { setElement } = useDragAndDrop({
onDrop: handleImageDrop(
handleImageSuccess(textAreaRef),
handleImageFailure,
),
onDragOver,
onDragExit,
});
useEffect(() => {
if (textAreaRef.current) {
setElement(textAreaRef.current.base);
}
});
return (
<div
data-testid="article-form__body"
className="crayons-article-form__body text-padding"
>
<Toolbar version={version} />
<Textarea
className="crayons-textfield crayons-textfield--ghost crayons-article-form__body__field"
id="article_body_markdown"
aria-label="Post Content"
placeholder="Write your post content here..."
value={defaultValue}
onInput={onChange}
onFocus={(_event) => {
switchHelpContext(_event);
}}
name="body_markdown"
ref={textAreaRef}
/>
</div>
);
};
EditorBody.propTypes = {
onChange: PropTypes.func.isRequired,
defaultValue: PropTypes.string.isRequired,
switchHelpContext: PropTypes.func.isRequired,
version: PropTypes.string.isRequired,
};
EditorBody.displayName = 'EditorBody';