maintain cursor position while inserting an image (#15508)

* maintain cursor position while inserting an image

* undo small unintended change
This commit is contained in:
Suzanne Aitchison 2021-11-30 08:56:36 +00:00 committed by GitHub
parent 7caafc8ea7
commit c9b097e574
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 13 deletions

View file

@ -182,6 +182,7 @@ const V2EditorImageUpload = ({
{...buttonProps}
icon={ImageIcon}
onClick={(e) => {
buttonProps.onClick?.(e);
useNativeUpload
? initNativeImagePicker(e)
: document.getElementById('image-upload-field').click();

View file

@ -59,6 +59,7 @@ const getPreviousMatchingSibling = (element, selector) => {
export const MarkdownToolbar = ({ textAreaId }) => {
const [textArea, setTextArea] = useState(null);
const [overflowMenuOpen, setOverflowMenuOpen] = useState(false);
const [storedCursorPosition, setStoredCursorPosition] = useState({});
const smallScreen = useMediaQuery(`(max-width: ${BREAKPOINTS.Medium - 1}px)`);
const markdownSyntaxFormatters = {
@ -190,9 +191,11 @@ export const MarkdownToolbar = ({ textAreaId }) => {
};
const handleImageUploadStarted = () => {
const { textBeforeSelection, textAfterSelection, selectionEnd } =
const { textBeforeSelection, textAfterSelection } =
getSelectionData(textArea);
const { selectionEnd } = storedCursorPosition;
const textWithPlaceholder = `${textBeforeSelection}\n${UPLOADING_IMAGE_PLACEHOLDER}${textAfterSelection}`;
textArea.value = textWithPlaceholder;
// Make sure Editor text area updates via linkstate
@ -203,27 +206,46 @@ export const MarkdownToolbar = ({ textAreaId }) => {
// Set cursor to the end of the placeholder
const newCursorPosition =
selectionEnd + UPLOADING_IMAGE_PLACEHOLDER.length + 1;
textArea.setSelectionRange(newCursorPosition, newCursorPosition);
};
const handleImageUploadSuccess = (imageMarkdown) => {
const handleImageUploadEnd = (imageMarkdown = '') => {
const {
selectionStart,
selectionEnd,
value: currentTextAreaValue,
} = textArea;
const indexOfPlaceholder = currentTextAreaValue.indexOf(
UPLOADING_IMAGE_PLACEHOLDER,
);
// User has deleted placeholder, nothing to do
if (indexOfPlaceholder === -1) return;
const newTextValue = textArea.value.replace(
UPLOADING_IMAGE_PLACEHOLDER,
imageMarkdown,
);
textArea.value = newTextValue;
// Make sure Editor text area updates via linkstate
textArea.dispatchEvent(new Event('input'));
};
const handleImageUploadError = () => {
const newTextValue = textArea.value.replace(
UPLOADING_IMAGE_PLACEHOLDER,
'',
);
textArea.value = newTextValue;
// Make sure Editor text area updates via linkstate
textArea.dispatchEvent(new Event('input'));
// The change to image markdown length does not affect cursor position
if (indexOfPlaceholder > selectionStart) {
textArea.setSelectionRange(selectionStart, selectionEnd);
return;
}
const differenceInLength =
imageMarkdown.length - UPLOADING_IMAGE_PLACEHOLDER.length;
textArea.setSelectionRange(
selectionStart + differenceInLength,
selectionEnd + differenceInLength,
);
};
const getSecondaryFormatterButtons = (isOverflow) =>
@ -308,10 +330,14 @@ export const MarkdownToolbar = ({ textAreaId }) => {
<ImageUploader
editorVersion="v2"
onImageUploadStart={handleImageUploadStarted}
onImageUploadSuccess={handleImageUploadSuccess}
onImageUploadError={handleImageUploadError}
onImageUploadSuccess={handleImageUploadEnd}
onImageUploadError={handleImageUploadEnd}
buttonProps={{
onKeyUp: (e) => handleToolbarButtonKeyPress(e, 'toolbar-btn'),
onClick: () => {
const { selectionStart, selectionEnd } = textArea;
setStoredCursorPosition({ selectionStart, selectionEnd });
},
tooltip: smallScreen ? null : (
<span aria-hidden="true">Upload image</span>
),

View file

@ -98,6 +98,28 @@ describe('Upload image', () => {
.should('have.text', 'Error message');
});
it('maintains writing position when image is uploaded', () => {
cy.intercept('/image_uploads', { delay: 2000 });
cy.findByLabelText('Post Content').as('editorBody');
// Type some text and place the cursor between one & two
cy.get('@editorBody')
.clear()
.type('one two{leftarrow}{leftarrow}{leftarrow}');
cy.findByLabelText(/Upload image/, { selector: 'button' }).click();
cy.findByLabelText(/Upload image/, { selector: 'input' }).attachFile(
'/images/admin-image.png',
);
// Check that when we continue typing after selecting an image, my cursor is after the placeholder text, which was inserted at my cursor position
cy.get('@editorBody').should('have.focus').type(' more text ');
cy.get('@editorBody').should(
'have.value',
'one \n![Uploading image](...) more text two',
);
});
it('Uploads a cover image in the editor', () => {
cy.findByRole('form', { name: 'Edit post' }).within(() => {
cy.findByLabelText(/Add a cover image/).attachFile(