Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `README.md`: Discarded upstream changes: we have our own README - `app/controllers/follower_accounts_controller.rb`: Port upstream's minor refactoring
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class FollowRequestNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, onAuthorize, onReject } = this.props;
|
||||
|
||||
return (
|
||||
<div className='follow-request-banner'>
|
||||
<div className='follow-request-banner__message'>
|
||||
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi> }} />
|
||||
</div>
|
||||
|
||||
<div className='follow-request-banner__action'>
|
||||
<button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}>
|
||||
<Icon id='check' fixedWidth />
|
||||
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
|
||||
</button>
|
||||
|
||||
<button type='button' className='button button-tertiary button--destructive' onClick={onReject}>
|
||||
<Icon id='times' fixedWidth />
|
||||
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
|
||||
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
@@ -311,6 +312,8 @@ class Header extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
|
||||
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{!suspended && info}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { connect } from 'react-redux';
|
||||
import FollowRequestNote from '../components/follow_request_note';
|
||||
import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { account }) => ({
|
||||
onAuthorize () {
|
||||
dispatch(authorizeFollowRequest(account.get('id')));
|
||||
},
|
||||
|
||||
onReject () {
|
||||
dispatch(rejectFollowRequest(account.get('id')));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(FollowRequestNote);
|
||||
@@ -104,6 +104,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
<video
|
||||
className='media-gallery__item-gifv-thumbnail'
|
||||
aria-label={attachment.get('description')}
|
||||
title={attachment.get('description')}
|
||||
role='application'
|
||||
src={attachment.get('url')}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
|
||||
@@ -16,7 +16,6 @@ import PollFormContainer from '../containers/poll_form_container';
|
||||
import UploadFormContainer from '../containers/upload_form_container';
|
||||
import WarningContainer from '../containers/warning_container';
|
||||
import LanguageDropdown from '../containers/language_dropdown_container';
|
||||
import { isMobile } from '../../../is_mobile';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import { countableText } from '../util/counter';
|
||||
@@ -62,14 +61,14 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onChangeSpoilerText: PropTypes.func.isRequired,
|
||||
onPaste: PropTypes.func.isRequired,
|
||||
onPickEmoji: PropTypes.func.isRequired,
|
||||
showSearch: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
anyMedia: PropTypes.bool,
|
||||
isInReply: PropTypes.bool,
|
||||
singleColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showSearch: false,
|
||||
autoFocus: false,
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
@@ -155,7 +154,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
// - Replying to zero or one users, places the cursor at the end of the textbox.
|
||||
// - Replying to more than one user, selects any usernames past the first;
|
||||
// this provides a convenient shortcut to drop everyone else from the conversation.
|
||||
if (this.props.focusDate !== prevProps.focusDate) {
|
||||
if (this.props.focusDate && this.props.focusDate !== prevProps.focusDate) {
|
||||
let selectionEnd, selectionStart;
|
||||
|
||||
if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply) {
|
||||
@@ -181,7 +180,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
} else if (this.props.spoiler !== prevProps.spoiler) {
|
||||
if (this.props.spoiler) {
|
||||
this.spoilerText.input.focus();
|
||||
} else {
|
||||
} else if (prevProps.spoiler) {
|
||||
this.autosuggestTextarea.textarea.focus();
|
||||
}
|
||||
}
|
||||
@@ -208,7 +207,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, showSearch } = this.props;
|
||||
const { intl, onPaste, autoFocus } = this.props;
|
||||
const disabled = this.props.isSubmitting;
|
||||
|
||||
let publishText = '';
|
||||
@@ -258,7 +257,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
onPaste={onPaste}
|
||||
autoFocus={!showSearch && !isMobile(window.innerWidth)}
|
||||
autoFocus={autoFocus}
|
||||
>
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ class PollForm extends ImmutablePureComponent {
|
||||
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
||||
<option value={3600}>{intl.formatMessage(messages.hours, { number: 1 })}</option>
|
||||
<option value={21600}>{intl.formatMessage(messages.hours, { number: 6 })}</option>
|
||||
<option value={43200}>{intl.formatMessage(messages.hours, { number: 12 })}</option>
|
||||
<option value={86400}>{intl.formatMessage(messages.days, { number: 1 })}</option>
|
||||
<option value={259200}>{intl.formatMessage(messages.days, { number: 3 })}</option>
|
||||
<option value={604800}>{intl.formatMessage(messages.days, { number: 7 })}</option>
|
||||
|
||||
@@ -123,27 +123,24 @@ class Search extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className='search'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
|
||||
<input
|
||||
ref={this.setRef}
|
||||
className='search__input'
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
ref={this.setRef}
|
||||
className='search__input'
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
|
||||
aria-label={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
/>
|
||||
|
||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
||||
<Icon id='search' className={hasValue ? '' : 'active'} />
|
||||
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
|
||||
</div>
|
||||
|
||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
|
||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this} container={this}>
|
||||
<SearchPopout />
|
||||
</Overlay>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,6 @@ const mapStateToProps = state => ({
|
||||
isEditing: state.getIn(['compose', 'id']) !== null,
|
||||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import Icon from 'mastodon/components/icon';
|
||||
import { logOut } from 'mastodon/utils/log_out';
|
||||
import Column from 'mastodon/components/column';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { isMobile } from '../../is_mobile';
|
||||
|
||||
const messages = defineMessages({
|
||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
@@ -115,7 +116,7 @@ class Compose extends React.PureComponent {
|
||||
<div className='drawer__inner' onFocus={this.onFocus}>
|
||||
<NavigationContainer onClose={this.onBlur} />
|
||||
|
||||
<ComposeFormContainer />
|
||||
<ComposeFormContainer autoFocus={!isMobile(window.innerWidth)} />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||
|
||||
@@ -24,16 +24,6 @@ const mapStateToProps = state => ({
|
||||
isSearching: state.getIn(['search', 'submitted']) || !showTrends,
|
||||
});
|
||||
|
||||
// Fix strange bug on Safari where <span> (rendered by FormattedMessage) disappears
|
||||
// after clicking around Explore top bar (issue #20885).
|
||||
// Removing width=100% from <a> also fixes it, as well as replacing <span> with <div>
|
||||
// We're choosing to wrap span with div to keep the changes local only to this tool bar.
|
||||
const WrapFormattedMessage = ({ children, ...props }) => <div><FormattedMessage {...props}>{children}</FormattedMessage></div>;
|
||||
WrapFormattedMessage.propTypes = {
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Explore extends React.PureComponent {
|
||||
@@ -78,12 +68,22 @@ class Explore extends React.PureComponent {
|
||||
{isSearching ? (
|
||||
<SearchResults />
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to='/explore'><WrapFormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
||||
<NavLink exact to='/explore/tags'><WrapFormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
||||
<NavLink exact to='/explore/links'><WrapFormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
||||
{signedIn && <NavLink exact to='/explore/suggestions'><WrapFormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
||||
<NavLink exact to='/explore'>
|
||||
<FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' />
|
||||
</NavLink>
|
||||
<NavLink exact to='/explore/tags'>
|
||||
<FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' />
|
||||
</NavLink>
|
||||
<NavLink exact to='/explore/links'>
|
||||
<FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' />
|
||||
</NavLink>
|
||||
{signedIn && (
|
||||
<NavLink exact to='/explore/suggestions'>
|
||||
<FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='For you' />
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Switch>
|
||||
@@ -97,7 +97,7 @@ class Explore extends React.PureComponent {
|
||||
<title>{intl.formatMessage(messages.title)}</title>
|
||||
<meta name='robots' content={isSearching ? 'noindex' : 'all'} />
|
||||
</Helmet>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Column>
|
||||
|
||||
@@ -194,7 +194,7 @@ class HashtagTimeline extends React.PureComponent {
|
||||
const following = tag.get('following');
|
||||
|
||||
followButton = (
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} active={following} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
|
||||
<Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||
if (this.mediaQuery.removeEventListener) {
|
||||
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
|
||||
} else {
|
||||
this.mediaQuery.removeListener(this.handleLayouteChange);
|
||||
this.mediaQuery.removeListener(this.handleLayoutChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,11 +291,11 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||
let descriptionLabel = null;
|
||||
|
||||
if (media.get('type') === 'audio') {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.audio_description' defaultMessage='Describe for people with hearing loss' />;
|
||||
descriptionLabel = <FormattedMessage id='upload_form.audio_description' defaultMessage='Describe for people who are hard of hearing' />;
|
||||
} else if (media.get('type') === 'video') {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.video_description' defaultMessage='Describe for people with hearing loss or visual impairment' />;
|
||||
descriptionLabel = <FormattedMessage id='upload_form.video_description' defaultMessage='Describe for people who are deaf, hard of hearing, blind or have low vision' />;
|
||||
} else {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.description' defaultMessage='Describe for the visually impaired' />;
|
||||
descriptionLabel = <FormattedMessage id='upload_form.description' defaultMessage='Describe for people who are blind or have low vision' />;
|
||||
}
|
||||
|
||||
let ocrMessage = '';
|
||||
|
||||
Reference in New Issue
Block a user