Fix compose form submission reloading web interface (#19762)

* Fix compose form submission reloading web interface

Fix regression introduced by #19742

* Fix various compose form buttons being handled like submit buttons

* Fix coding style issue

* Fix missing onClick prop check
This commit is contained in:
Claire 2022-11-05 13:43:37 +01:00 committed by GitHub
parent 1e7ea50f4c
commit 9616f5bb22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 41 additions and 24 deletions

View file

@ -4,6 +4,7 @@ exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] =
<button <button
className="button button-secondary" className="button button-secondary"
onClick={[Function]} onClick={[Function]}
type="button"
/> />
`; `;
@ -11,6 +12,7 @@ exports[`<Button /> renders a button element 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
type="button"
/> />
`; `;
@ -19,6 +21,7 @@ exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = `
className="button" className="button"
disabled={true} disabled={true}
onClick={[Function]} onClick={[Function]}
type="button"
/> />
`; `;
@ -26,6 +29,7 @@ exports[`<Button /> renders class="button--block" if props.block given 1`] = `
<button <button
className="button button--block" className="button button--block"
onClick={[Function]} onClick={[Function]}
type="button"
/> />
`; `;
@ -33,6 +37,7 @@ exports[`<Button /> renders the children 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
type="button"
> >
<p> <p>
children children
@ -44,6 +49,7 @@ exports[`<Button /> renders the given text 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
type="button"
> >
foo foo
</button> </button>
@ -53,6 +59,7 @@ exports[`<Button /> renders the props.text instead of children 1`] = `
<button <button
className="button" className="button"
onClick={[Function]} onClick={[Function]}
type="button"
> >
foo foo
</button> </button>

View file

@ -16,8 +16,12 @@ export default class Button extends React.PureComponent {
children: PropTypes.node, children: PropTypes.node,
}; };
static defaultProps = {
type: 'button',
};
handleClick = (e) => { handleClick = (e) => {
if (!this.props.disabled) { if (!this.props.disabled && this.props.onClick) {
this.props.onClick(e); this.props.onClick(e);
} }
} }

View file

@ -141,6 +141,7 @@ export default class IconButton extends React.PureComponent {
return ( return (
<button <button
type='button'
aria-label={title} aria-label={title}
aria-pressed={pressed} aria-pressed={pressed}
aria-expanded={expanded} aria-expanded={expanded}

View file

@ -18,7 +18,7 @@ export default class LoadMore extends React.PureComponent {
const { disabled, visible } = this.props; const { disabled, visible } = this.props;
return ( return (
<button className='load-more' disabled={disabled || !visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}> <button type='button' className='load-more' disabled={disabled || !visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}>
<FormattedMessage id='status.load_more' defaultMessage='Load more' /> <FormattedMessage id='status.load_more' defaultMessage='Load more' />
</button> </button>
); );

View file

@ -93,7 +93,7 @@ class ComposeForm extends ImmutablePureComponent {
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia)); return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
} }
handleSubmit = () => { handleSubmit = (e) => {
if (this.props.text !== this.autosuggestTextarea.textarea.value) { if (this.props.text !== this.autosuggestTextarea.textarea.value) {
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly) // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
// Update the state to match the current text // Update the state to match the current text
@ -105,6 +105,10 @@ class ComposeForm extends ImmutablePureComponent {
} }
this.props.onSubmit(this.context.router ? this.context.router.history : null); this.props.onSubmit(this.context.router ? this.context.router.history : null);
if (e) {
e.preventDefault();
}
} }
onSuggestionsClearRequested = () => { onSuggestionsClearRequested = () => {
@ -217,7 +221,7 @@ class ComposeForm extends ImmutablePureComponent {
} }
return ( return (
<form className='compose-form'> <form className='compose-form' onSubmit={this.handleSubmit}>
<WarningContainer /> <WarningContainer />
<ReplyIndicatorContainer /> <ReplyIndicatorContainer />
@ -280,9 +284,8 @@ class ComposeForm extends ImmutablePureComponent {
<div className='compose-form__publish'> <div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'> <div className='compose-form__publish-button-wrapper'>
<Button <Button
type="submit" type='submit'
text={publishText} text={publishText}
onClick={this.handleSubmit}
disabled={!this.canSubmit()} disabled={!this.canSubmit()}
block block
/> />

View file

@ -96,12 +96,12 @@ class ModifierPickerMenu extends React.PureComponent {
return ( return (
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}> <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
<button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button> <button type='button' onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
</div> </div>
); );
} }

View file

@ -227,7 +227,7 @@ class LanguageDropdownMenu extends React.PureComponent {
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}> <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className='emoji-mart-search'> <div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus /> <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button> <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div> </div>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}> <div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>

View file

@ -157,7 +157,7 @@ class PollForm extends ImmutablePureComponent {
</ul> </ul>
<div className='poll__footer'> <div className='poll__footer'>
<button disabled={options.size >= 4} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button> <button type='button' disabled={options.size >= 4} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
{/* eslint-disable-next-line jsx-a11y/no-onchange */} {/* eslint-disable-next-line jsx-a11y/no-onchange */}
<select value={expiresIn} onChange={this.handleSelectDuration}> <select value={expiresIn} onChange={this.handleSelectDuration}>

View file

@ -22,6 +22,7 @@ export default class TextIconButton extends React.PureComponent {
return ( return (
<button <button
type='button'
title={title} title={title}
aria-label={title} aria-label={title}
className={`text-icon-button ${active ? 'active' : ''}`} className={`text-icon-button ${active ? 'active' : ''}`}

View file

@ -43,13 +43,13 @@ export default class Upload extends ImmutablePureComponent {
{({ scale }) => ( {({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}> <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload__actions'> <div className='compose-form__upload__actions'>
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button> <button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
{!isEditingStatus && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)} {!isEditingStatus && (<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
</div> </div>
{(media.get('description') || '').length === 0 && ( {(media.get('description') || '').length === 0 && (
<div className='compose-form__upload__warning'> <div className='compose-form__upload__warning'>
<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button> <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
</div> </div>
)} )}
</div> </div>

View file

@ -68,7 +68,7 @@ class Favourites extends ImmutablePureComponent {
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}
/> />

View file

@ -177,7 +177,7 @@ class SelectFilter extends React.PureComponent {
<div className='emoji-mart-search'> <div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus /> <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button> <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div> </div>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}> <div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>

View file

@ -126,6 +126,7 @@ class HomeTimeline extends React.PureComponent {
if (hasAnnouncements) { if (hasAnnouncements) {
announcementsButton = ( announcementsButton = (
<button <button
type='button'
className={classNames('column-header__button', { 'active': showAnnouncements })} className={classNames('column-header__button', { 'active': showAnnouncements })}
title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)} title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)} aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}

View file

@ -178,11 +178,11 @@ class ListTimeline extends React.PureComponent {
multiColumn={multiColumn} multiColumn={multiColumn}
> >
<div className='column-settings__row column-header__links'> <div className='column-settings__row column-header__links'>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}> <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' /> <Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
</button> </button>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}> <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' /> <Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button> </button>
</div> </div>

View file

@ -68,7 +68,7 @@ class Reblogs extends ImmutablePureComponent {
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}
/> />

View file

@ -247,7 +247,7 @@ export default class Card extends React.PureComponent {
{revealed && ( {revealed && (
<div className='status-card__actions'> <div className='status-card__actions'>
<div> <div>
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button> <button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>} {horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
</div> </div>
</div> </div>

View file

@ -619,7 +619,7 @@ class Status extends ImmutablePureComponent {
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button> <button type='button' className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
)} )}
/> />