Image uploading progress in comments editor + post editor (#18369)
* Image upload loading all cases covered * Image upload failure added to EditorBody * Created imageUploadHelpers file * Minor changes * Nit fixes * Error fix * Added documentation * Added/Updated tests
This commit is contained in:
parent
6bb523f480
commit
8fa4f8f7b0
7 changed files with 148 additions and 61 deletions
|
|
@ -102,7 +102,7 @@ export function generateMainImage({ payload, successCb, failureCb, signal }) {
|
|||
const { image } = payload;
|
||||
return successCb({ links, image });
|
||||
})
|
||||
.catch(failureCb);
|
||||
.catch((message) => failureCb(message));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,6 +114,7 @@ export function generateMainImage({ payload, successCb, failureCb, signal }) {
|
|||
*/
|
||||
export function processImageUpload(
|
||||
images,
|
||||
handleImageUploading,
|
||||
handleImageSuccess,
|
||||
handleImageFailure,
|
||||
) {
|
||||
|
|
@ -121,6 +122,7 @@ export function processImageUpload(
|
|||
if (images.length > 0 && validateFileInputs()) {
|
||||
const payload = { image: images };
|
||||
|
||||
handleImageUploading();
|
||||
generateMainImage({
|
||||
payload,
|
||||
successCb: handleImageSuccess,
|
||||
|
|
|
|||
|
|
@ -4,39 +4,16 @@ import { useLayoutEffect, useRef } from 'preact/hooks';
|
|||
import { Toolbar } from './Toolbar';
|
||||
import { handleImagePasted } from './pasteImageHelpers';
|
||||
import {
|
||||
handleImageDrop,
|
||||
handleImageFailure,
|
||||
onDragOver,
|
||||
onDragExit,
|
||||
} from './dragAndDropHelpers';
|
||||
handleImageUploadSuccess,
|
||||
handleImageUploading,
|
||||
handleImageUploadFailure,
|
||||
} from './imageUploadHelpers';
|
||||
import { handleImageDrop, onDragOver, onDragExit } from './dragAndDropHelpers';
|
||||
import { usePasteImage } from '@utilities/pasteImage';
|
||||
import { useDragAndDrop } from '@utilities/dragAndDrop';
|
||||
import { fetchSearch } from '@utilities/search';
|
||||
import { AutocompleteTriggerTextArea } from '@crayons/AutocompleteTriggerTextArea';
|
||||
|
||||
function handleImageSuccess(textAreaRef) {
|
||||
return function (response) {
|
||||
// Function is within the component to be able to access
|
||||
// textarea ref.
|
||||
const editableBodyElement = textAreaRef.current;
|
||||
const { links } = response;
|
||||
|
||||
const markdownImageLink = `\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,
|
||||
|
|
@ -47,8 +24,9 @@ export const EditorBody = ({
|
|||
|
||||
const { setElement } = useDragAndDrop({
|
||||
onDrop: handleImageDrop(
|
||||
handleImageSuccess(textAreaRef),
|
||||
handleImageFailure,
|
||||
handleImageUploading(textAreaRef),
|
||||
handleImageUploadSuccess(textAreaRef),
|
||||
handleImageUploadFailure(textAreaRef),
|
||||
),
|
||||
onDragOver,
|
||||
onDragExit,
|
||||
|
|
@ -56,8 +34,9 @@ export const EditorBody = ({
|
|||
|
||||
const setPasteElement = usePasteImage({
|
||||
onPaste: handleImagePasted(
|
||||
handleImageSuccess(textAreaRef),
|
||||
handleImageFailure,
|
||||
handleImageUploading(textAreaRef),
|
||||
handleImageUploadSuccess(textAreaRef),
|
||||
handleImageUploadFailure(textAreaRef),
|
||||
),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ export function matchesDataTransferType(
|
|||
}
|
||||
|
||||
// TODO: Document functions
|
||||
export function handleImageDrop(handleImageSuccess, handleImageFailure) {
|
||||
export function handleImageDrop(
|
||||
handleImageUploading,
|
||||
handleImageSuccess,
|
||||
handleImageFailure,
|
||||
) {
|
||||
return function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
|
|
@ -37,7 +41,12 @@ export function handleImageDrop(handleImageSuccess, handleImageFailure) {
|
|||
return;
|
||||
}
|
||||
|
||||
processImageUpload(files, handleImageSuccess, handleImageFailure);
|
||||
processImageUpload(
|
||||
files,
|
||||
handleImageUploading,
|
||||
handleImageSuccess,
|
||||
handleImageFailure,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
97
app/javascript/article-form/components/imageUploadHelpers.js
Normal file
97
app/javascript/article-form/components/imageUploadHelpers.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { handleImageFailure } from './dragAndDropHelpers';
|
||||
|
||||
// Placeholder text displayed while an image is uploading
|
||||
const UPLOADING_IMAGE_PLACEHOLDER = '';
|
||||
|
||||
/**
|
||||
* Handles image uploading by showing UPLOADING_IMAGE_PLACEHOLDER text.
|
||||
*
|
||||
* @param {useRef} textAreaRef The reference of the text area with content.
|
||||
*/
|
||||
export function handleImageUploading(textAreaRef) {
|
||||
return function () {
|
||||
// Function is within the component to be able to access
|
||||
// textarea ref.
|
||||
const editableBodyElement = textAreaRef.current;
|
||||
|
||||
const { selectionStart, selectionEnd, value } = editableBodyElement;
|
||||
const before = value.substring(0, selectionStart);
|
||||
const after = value.substring(selectionEnd, value.length);
|
||||
const newSelectionStart = `${before}\n${UPLOADING_IMAGE_PLACEHOLDER}`
|
||||
.length;
|
||||
|
||||
editableBodyElement.value = `${before}\n${UPLOADING_IMAGE_PLACEHOLDER}\n${after}`;
|
||||
editableBodyElement.selectionStart = newSelectionStart;
|
||||
editableBodyElement.selectionEnd = newSelectionStart;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles image upload successfully by replacing UPLOADING_IMAGE_PLACEHOLDER with image link.
|
||||
*
|
||||
* @param {useRef} textAreaRef The reference of the text area with content.
|
||||
*/
|
||||
export function handleImageUploadSuccess(textAreaRef) {
|
||||
return function (response) {
|
||||
// Function is within the component to be able to access
|
||||
// textarea ref.
|
||||
const editableBodyElement = textAreaRef.current;
|
||||
const { links } = response;
|
||||
|
||||
const markdownImageLink = `\n`;
|
||||
const { selectionStart, selectionEnd, value } = editableBodyElement;
|
||||
if (value.includes(UPLOADING_IMAGE_PLACEHOLDER)) {
|
||||
const newSelectedStart =
|
||||
value.indexOf(UPLOADING_IMAGE_PLACEHOLDER, 0) +
|
||||
markdownImageLink.length;
|
||||
|
||||
editableBodyElement.value = value.replace(
|
||||
UPLOADING_IMAGE_PLACEHOLDER,
|
||||
markdownImageLink,
|
||||
);
|
||||
editableBodyElement.selectionStart = newSelectedStart;
|
||||
editableBodyElement.selectionEnd = newSelectedStart;
|
||||
} else {
|
||||
const before = value.substring(0, selectionStart);
|
||||
const after = value.substring(selectionEnd, value.length);
|
||||
|
||||
editableBodyElement.value = `${before}\n${markdownImageLink}\n${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'));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles image upload failure by removing UPLOADING_IMAGE_PLACEHOLDER text and showing error.
|
||||
*
|
||||
* @param {useRef} textAreaRef The reference of the text area with content.
|
||||
*/
|
||||
export function handleImageUploadFailure(textAreaRef) {
|
||||
return function (message) {
|
||||
// Function is within the component to be able to access
|
||||
// textarea ref.
|
||||
handleImageFailure(message);
|
||||
const editableBodyElement = textAreaRef.current;
|
||||
|
||||
const { value } = editableBodyElement;
|
||||
if (value.includes(`\n${UPLOADING_IMAGE_PLACEHOLDER}\n`)) {
|
||||
const newSelectionStart = value.indexOf(
|
||||
`\n${UPLOADING_IMAGE_PLACEHOLDER}\n`,
|
||||
0,
|
||||
);
|
||||
|
||||
editableBodyElement.value = value.replace(
|
||||
`\n${UPLOADING_IMAGE_PLACEHOLDER}\n`,
|
||||
'',
|
||||
);
|
||||
editableBodyElement.selectionStart = newSelectionStart;
|
||||
editableBodyElement.selectionEnd = newSelectionStart;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -20,7 +20,11 @@ export function matchesDataTransferType(
|
|||
* @param {function} handleImageSuccess Callback for when image upload succeeds
|
||||
* @param {function} handleImageFailure Callback for when image upload fails
|
||||
*/
|
||||
export function handleImagePasted(handleImageSuccess, handleImageFailure) {
|
||||
export function handleImagePasted(
|
||||
handleImageUploading,
|
||||
handleImageSuccess,
|
||||
handleImageFailure,
|
||||
) {
|
||||
return function (event) {
|
||||
if (!event.clipboardData || !event.clipboardData.items) return;
|
||||
if (!matchesDataTransferType(event.clipboardData.types)) return;
|
||||
|
|
@ -37,7 +41,12 @@ export function handleImagePasted(handleImageSuccess, handleImageFailure) {
|
|||
return;
|
||||
}
|
||||
|
||||
processImageUpload(files, handleImageSuccess, handleImageFailure);
|
||||
processImageUpload(
|
||||
files,
|
||||
handleImageUploading,
|
||||
handleImageSuccess,
|
||||
handleImageFailure,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ import { h } from 'preact';
|
|||
import { useState, useRef, useLayoutEffect } from 'preact/hooks';
|
||||
import { populateTemplates } from '../../responseTemplates/responseTemplates';
|
||||
import { handleImagePasted } from '../../article-form/components/pasteImageHelpers';
|
||||
import {
|
||||
handleImageUploading,
|
||||
handleImageUploadSuccess,
|
||||
handleImageUploadFailure,
|
||||
} from '../../article-form/components/imageUploadHelpers';
|
||||
import {
|
||||
handleImageDrop,
|
||||
handleImageFailure,
|
||||
onDragOver,
|
||||
onDragExit,
|
||||
} from '../../article-form/components/dragAndDropHelpers';
|
||||
|
|
@ -26,25 +30,6 @@ const getClosestTemplatesContainer = (element) =>
|
|||
.closest('.comment-form__inner')
|
||||
?.querySelector('.response-templates-container');
|
||||
|
||||
const handleImageSuccess = (textAreaRef) => {
|
||||
return function (response) {
|
||||
// Function is within the component to be able to access
|
||||
// textarea ref.
|
||||
const editableBodyElement = textAreaRef.current;
|
||||
const { links } = response;
|
||||
|
||||
const markdownImageLink = `\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;
|
||||
};
|
||||
};
|
||||
|
||||
export const CommentTextArea = ({ vanillaTextArea }) => {
|
||||
const [templatesVisible, setTemplatesVisible] = useState(false);
|
||||
const textAreaRef = useRef(null);
|
||||
|
|
@ -52,8 +37,9 @@ export const CommentTextArea = ({ vanillaTextArea }) => {
|
|||
|
||||
const { setElement } = useDragAndDrop({
|
||||
onDrop: handleImageDrop(
|
||||
handleImageSuccess(textAreaRef),
|
||||
handleImageFailure,
|
||||
handleImageUploading(textAreaRef),
|
||||
handleImageUploadSuccess(textAreaRef),
|
||||
handleImageUploadFailure(textAreaRef),
|
||||
),
|
||||
onDragOver,
|
||||
onDragExit,
|
||||
|
|
@ -61,8 +47,9 @@ export const CommentTextArea = ({ vanillaTextArea }) => {
|
|||
|
||||
const setPasteElement = usePasteImage({
|
||||
onPaste: handleImagePasted(
|
||||
handleImageSuccess(textAreaRef),
|
||||
handleImageFailure,
|
||||
handleImageUploading(textAreaRef),
|
||||
handleImageUploadSuccess(textAreaRef),
|
||||
handleImageUploadFailure(textAreaRef),
|
||||
),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ describe('Upload image', () => {
|
|||
'/images/admin-image.png',
|
||||
);
|
||||
|
||||
cy.findByLabelText('Post Content')
|
||||
.invoke('val')
|
||||
.should('match', /!\[Uploading image\]/);
|
||||
|
||||
cy.findByLabelText('Post Content')
|
||||
.invoke('val')
|
||||
.should('match', /!\[Image description\]/);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue