Merge tag 'v3.4.0' into hometown-dev
This commit is contained in:
@ -3,6 +3,8 @@
|
||||
exports[`<DisplayName /> renders display name + account name 1`] = `
|
||||
<span
|
||||
className="display-name"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<bdi>
|
||||
<strong
|
||||
|
||||
@ -78,8 +78,10 @@ class Account extends ImmutablePureComponent {
|
||||
|
||||
let buttons;
|
||||
|
||||
if (onActionClick && actionIcon) {
|
||||
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
|
||||
if (actionIcon) {
|
||||
if (onActionClick) {
|
||||
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
|
||||
}
|
||||
} else if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
|
||||
@ -6,7 +6,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import classNames from 'classnames';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||
let word;
|
||||
@ -55,7 +54,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
autoFocus: true,
|
||||
searchTokens: ImmutableList(['@', ':', '#']),
|
||||
searchTokens: ['@', ':', '#'],
|
||||
};
|
||||
|
||||
state = {
|
||||
|
||||
@ -11,45 +11,30 @@ export default class DisplayName extends React.PureComponent {
|
||||
localDomain: PropTypes.string,
|
||||
};
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
handleMouseEnter = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
emoji.src = emoji.getAttribute('data-original');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
handleMouseLeave = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-static');
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -81,7 +66,7 @@ export default class DisplayName extends React.PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className='display-name' ref={this.setRef}>
|
||||
<span className='display-name' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
{displayName} {suffix}
|
||||
</span>
|
||||
);
|
||||
|
||||
@ -177,7 +177,6 @@ export default class Dropdown extends React.PureComponent {
|
||||
disabled: PropTypes.bool,
|
||||
status: ImmutablePropTypes.map,
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
dropdownPlacement: PropTypes.string,
|
||||
|
||||
@ -2,10 +2,35 @@
|
||||
import React from 'react';
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Permalink from './permalink';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
|
||||
class SilentErrorBoundary extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
state = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
componentDidCatch () {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.state.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to render counter of how much people are talking about hashtag
|
||||
*
|
||||
@ -51,17 +76,19 @@ const Hashtag = ({ hashtag }) => (
|
||||
</div>
|
||||
|
||||
<div className='trends__item__sparkline'>
|
||||
<Sparklines
|
||||
width={50}
|
||||
height={28}
|
||||
data={hashtag
|
||||
.get('history')
|
||||
.reverse()
|
||||
.map((day) => day.get('uses'))
|
||||
.toArray()}
|
||||
>
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
<SilentErrorBoundary>
|
||||
<Sparklines
|
||||
width={50}
|
||||
height={28}
|
||||
data={hashtag
|
||||
.get('history')
|
||||
.reverse()
|
||||
.map((day) => day.get('uses'))
|
||||
.toArray()}
|
||||
>
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
</SilentErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
9
app/javascript/mastodon/components/logo.js
Normal file
9
app/javascript/mastodon/components/logo.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const Logo = () => (
|
||||
<svg viewBox='0 0 216.4144 232.00976' className='logo'>
|
||||
<use xlinkHref='#mastodon-svg-logo' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Logo;
|
||||
@ -11,7 +11,7 @@ import { debounce } from 'lodash';
|
||||
import Blurhash from 'mastodon/components/blurhash';
|
||||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide {number, plural, one {image} other {images}}' },
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: '{number, plural, one {Hide image} other {Hide images}}' },
|
||||
});
|
||||
|
||||
class Item extends React.PureComponent {
|
||||
|
||||
@ -153,7 +153,7 @@ class Poll extends ImmutablePureComponent {
|
||||
</span>}
|
||||
|
||||
<span
|
||||
className='poll__option__text'
|
||||
className='poll__option__text translate'
|
||||
dangerouslySetInnerHTML={{ __html: titleEmojified }}
|
||||
/>
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'mastodon/../images/elephant_ui_working.svg';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
const RegenerationIndicator = () => (
|
||||
<div className='regeneration-indicator'>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
@ -15,4 +15,4 @@ const MissingIndicator = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MissingIndicator;
|
||||
export default RegenerationIndicator;
|
||||
|
||||
@ -224,11 +224,12 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, scrollKey } = this.props;
|
||||
|
||||
const mutingConversation = status.get('muted');
|
||||
const anonymousAccess = !me;
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const federated = !status.get('local_only');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
|
||||
let menu = [];
|
||||
|
||||
@ -239,19 +240,22 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
menu.push(null);
|
||||
|
||||
if (status.getIn(['account', 'id']) === me || withDismiss) {
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
|
||||
if (writtenByMe && publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (writtenByMe || withDismiss) {
|
||||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
if (writtenByMe) {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
} else {
|
||||
|
||||
@ -75,35 +75,38 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
_updateStatusEmojis () {
|
||||
const node = this.node;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
handleMouseEnter = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
emoji.src = emoji.getAttribute('data-original');
|
||||
}
|
||||
}
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
handleMouseLeave = ({ currentTarget }) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-static');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateStatusLinks();
|
||||
this._updateStatusEmojis();
|
||||
}
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
@ -122,14 +125,6 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
this.startXY = [e.clientX, e.clientY];
|
||||
}
|
||||
@ -175,10 +170,6 @@ export default class StatusContent extends React.PureComponent {
|
||||
render () {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('content').length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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']);
|
||||
@ -225,16 +216,16 @@ export default class StatusContent extends React.PureComponent {
|
||||
}
|
||||
|
||||
const output = [
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} />
|
||||
<span dangerouslySetInnerHTML={spoilerContent} className='translate' />
|
||||
{' '}
|
||||
{status.get('activity_pub_type') === 'Article' ? '' : <span class="show_more_button"><button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button></span>}
|
||||
</p>
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} dangerouslySetInnerHTML={content} />
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
{renderViewThread && showThreadButton}
|
||||
@ -248,8 +239,8 @@ export default class StatusContent extends React.PureComponent {
|
||||
return output;
|
||||
} else if (this.props.onClick) {
|
||||
const output = [
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
|
||||
<div className='status__content__text status__content__text--visible' dangerouslySetInnerHTML={content} />
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
|
||||
@ -264,8 +255,8 @@ export default class StatusContent extends React.PureComponent {
|
||||
return output;
|
||||
} else {
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0'>
|
||||
<div className='status__content__text status__content__text--visible' dangerouslySetInnerHTML={content} />
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user