Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `app/javascript/mastodon/features/compose/components/poll_form.js`:
  Upstream bumped poll option character limit, but we already had
  a higher one, kept ours.
- `app/validators/poll_validator.rb`:
  Upstream bumped poll option character limit, but we already had
  a higher one, kept ours.
- `config/initializers/content_security_policy.rb`:
  Upstream added a rule, the way we compute ours is different, but
  that added rule has been ported.
- `package.json`:
  No real conflict, dependency update. Performed the same update.
- `yarn.lock`:
  No real conflict, dependency update. Performed the same update.
This commit is contained in:
Thibaut Girka
2020-04-02 20:32:00 +02:00
62 changed files with 553 additions and 516 deletions

View File

@ -396,6 +396,7 @@ export function fetchFollowersFail(id, error) {
type: FOLLOWERS_FETCH_FAIL,
id,
error,
skipNotFound: true,
};
};
@ -482,6 +483,7 @@ export function fetchFollowingFail(id, error) {
type: FOLLOWING_FETCH_FAIL,
id,
error,
skipNotFound: true,
};
};
@ -571,6 +573,7 @@ export function fetchRelationshipsFail(error) {
type: RELATIONSHIPS_FETCH_FAIL,
error,
skipLoading: true,
skipNotFound: true,
};
};

View File

@ -34,11 +34,11 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u
};
};
export function showAlertForError(error) {
export function showAlertForError(error, skipNotFound = false) {
if (error.response) {
const { data, status, statusText, headers } = error.response;
if (status === 404 || status === 410) {
if (skipNotFound && (status === 404 || status === 410)) {
// Skip these errors as they are reflected in the UI
return { type: ALERT_NOOP };
}

View File

@ -27,4 +27,5 @@ export const fetchAccountIdentityProofsFail = (accountId, err) => ({
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
accountId,
err,
skipNotFound: true,
});

View File

@ -149,6 +149,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) {
timeline,
error,
skipLoading: !isLoadingMore,
skipNotFound: timeline.startsWith('account:'),
};
};

View File

@ -76,8 +76,9 @@ class ColumnHeader extends React.PureComponent {
handlePin = () => {
if (!this.props.pinned) {
this.historyBack();
this.context.router.history.replace('/');
}
this.props.onPin();
}

View File

@ -127,15 +127,7 @@ class Poll extends ImmutablePureComponent {
return (
<li key={option.get('title')}>
{showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
<label className={classNames('poll__text', { selectable: !showResults })}>
<label className={classNames('poll__option', { selectable: !showResults })}>
<input
name='vote-options'
type={poll.get('multiple') ? 'checkbox' : 'radio'}
@ -157,12 +149,26 @@ class Poll extends ImmutablePureComponent {
/>
)}
{showResults && <span className='poll__number'>
{!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />}
{Math.round(percent)}%
</span>}
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
<span
className='poll__option__text'
dangerouslySetInnerHTML={{ __html: titleEmojified }}
/>
{!!voted && <span className='poll__voted'>
<Icon id='check' className='poll__voted__mark' title={intl.formatMessage(messages.voted)} />
</span>}
</label>
{showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
</li>
);
}

View File

@ -432,16 +432,10 @@ class Status extends ImmutablePureComponent {
</a>
</div>
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} showThread={showThread} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
{media}
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
<button className='status__content__read-more-button' onClick={this.handleClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
</button>
)}
<StatusActionBar status={status} account={account} {...other} />
</div>
</div>

View File

@ -20,6 +20,7 @@ export default class StatusContent extends React.PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
expanded: PropTypes.bool,
showThread: PropTypes.bool,
onExpandedToggle: PropTypes.func,
onClick: PropTypes.func,
collapsable: PropTypes.bool,
@ -181,6 +182,7 @@ export default class StatusContent extends React.PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderReadMore = this.props.onClick && status.get('collapsed');
const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
@ -195,6 +197,12 @@ export default class StatusContent extends React.PureComponent {
directionStyle.direction = 'rtl';
}
const showThreadButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
</button>
);
const readMoreButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth />
@ -229,6 +237,8 @@ export default class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div>
);
} else if (this.props.onClick) {
@ -237,6 +247,8 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div>,
];
@ -251,6 +263,8 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div>
);
}

View File

@ -75,7 +75,7 @@ class Option extends React.PureComponent {
return (
<li>
<label className='poll__text editable'>
<label className='poll__option editable'>
<span
className={classNames('poll__input', { checkbox: isPollMultiple })}
onClick={this.handleToggleMultiple}

View File

@ -160,7 +160,7 @@ class Conversation extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
<div className='conversation__avatar'>
<div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
<AvatarComposite accounts={accounts} size={48} />
</div>

View File

@ -8,7 +8,7 @@ export default function errorsMiddleware() {
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
if (action.type.match(isFail)) {
dispatch(showAlertForError(action.error));
dispatch(showAlertForError(action.error, action.skipNotFound));
}
}

View File

@ -261,7 +261,6 @@ export default function compose(state = initialState, action) {
});
case COMPOSE_SPOILERNESS_CHANGE:
return state.withMutations(map => {
map.set('spoiler_text', '');
map.set('spoiler', !state.get('spoiler'));
map.set('idempotencyKey', uuid());

View File

@ -142,7 +142,7 @@ html {
}
.compose-form__autosuggest-wrapper,
.poll__text input[type="text"],
.poll__option input[type="text"],
.compose-form .spoiler-input__input,
.compose-form__poll-wrapper select,
.search__input,

View File

@ -1028,13 +1028,11 @@
}
.display-name {
color: $light-text-color;
strong {
color: $inverted-text-color;
}
span {
color: $light-text-color;
}
}
.status__content {
@ -1333,7 +1331,6 @@
border-radius: 50%;
overflow: hidden;
position: relative;
cursor: default;
& > div {
float: left;
@ -6570,6 +6567,7 @@ noscript {
padding: 10px;
padding-top: 12px;
position: relative;
cursor: pointer;
}
&__unread {

View File

@ -8,20 +8,18 @@
}
&__chart {
position: absolute;
top: 0;
left: 0;
height: 100%;
display: inline-block;
border-radius: 4px;
background: darken($ui-primary-color, 14%);
display: block;
background: darken($ui-primary-color, 5%);
height: 5px;
min-width: 1%;
&.leading {
background: $ui-highlight-color;
}
}
&__text {
&__option {
position: relative;
display: flex;
padding: 6px 0;
@ -29,6 +27,13 @@
cursor: default;
overflow: hidden;
&__text {
display: inline-block;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: calc(100% - 45px - 25px);
}
input[type=radio],
input[type=checkbox] {
display: none;
@ -112,19 +117,18 @@
&__number {
display: inline-block;
width: 52px;
width: 45px;
font-weight: 700;
padding: 0 10px;
padding-left: 8px;
text-align: right;
margin-top: auto;
margin-bottom: auto;
flex: 0 0 52px;
flex: 0 0 45px;
}
&__vote__mark {
float: left;
line-height: 18px;
&__voted {
padding: 0 5px;
display: inline-block;
&__mark {
font-size: 18px;
}
}
&__footer {
@ -199,7 +203,7 @@
display: flex;
align-items: center;
.poll__text {
.poll__option {
flex: 0 0 auto;
width: calc(100% - (23px + 6px));
margin-right: 6px;