[build] upgrade eslint to 9.37.0 (#88)

Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/masto-fe-standalone/pulls/88
Co-authored-by: Zoë Bijl <moiety@noreply.codeberg.org>
Co-committed-by: Zoë Bijl <moiety@noreply.codeberg.org>
This commit is contained in:
Zoë Bijl
2025-10-12 13:42:02 +02:00
committed by tobi
parent 75d7a62693
commit 1ff70886a1
975 changed files with 22196 additions and 21964 deletions
@@ -1,30 +1,30 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { Skeleton } from 'flavours/glitch/components/skeleton';
import { me } from 'flavours/glitch/initial_state';
import { Skeleton } from "flavours/glitch/components/skeleton";
import { me } from "flavours/glitch/initial_state";
import { Avatar } from './avatar';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import Permalink from './permalink';
import { RelativeTimestamp } from './relative_timestamp';
import { Avatar } from "./avatar";
import { DisplayName } from "./display_name";
import { IconButton } from "./icon_button";
import Permalink from "./permalink";
import { RelativeTimestamp } from "./relative_timestamp";
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
follow: { id: "account.follow", defaultMessage: "Follow" },
unfollow: { id: "account.unfollow", defaultMessage: "Unfollow" },
requested: { id: "account.requested", defaultMessage: "Awaiting approval. Click to cancel follow request" },
unblock: { id: "account.unblock", defaultMessage: "Unblock @{name}" },
unmute: { id: "account.unmute", defaultMessage: "Unmute @{name}" },
mute_notifications: { id: "account.mute_notifications", defaultMessage: "Mute notifications from @{name}" },
unmute_notifications: { id: "account.unmute_notifications", defaultMessage: "Unmute notifications from @{name}" },
mute: { id: "account.mute", defaultMessage: "Mute @{name}" },
block: { id: "account.block", defaultMessage: "Block @{name}" },
});
class Account extends ImmutablePureComponent {
@@ -102,8 +102,8 @@ class Account extends ImmutablePureComponent {
if (hidden) {
return (
<>
{account.get('display_name')}
{account.get('username')}
{account.get("display_name")}
{account.get("username")}
</>
);
}
@@ -114,48 +114,48 @@ class Account extends ImmutablePureComponent {
if (actionIcon) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
}
} else if (account.get('id') !== me && !small && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
const muting = account.getIn(['relationship', 'muting']);
} else if (account.get("id") !== me && !small && account.get("relationship", null) !== null) {
const following = account.getIn(["relationship", "following"]);
const requested = account.getIn(["relationship", "requested"]);
const blocking = account.getIn(["relationship", "blocking"]);
const muting = account.getIn(["relationship", "muting"]);
if (requested) {
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
} else if (blocking) {
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get("username") })} onClick={this.handleBlock} />;
} else if (muting) {
let hidingNotificationsButton;
if (account.getIn(['relationship', 'muting_notifications'])) {
hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />;
if (account.getIn(["relationship", "muting_notifications"])) {
hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get("username") })} onClick={this.handleUnmuteNotifications} />;
} else {
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get("username") })} onClick={this.handleMuteNotifications} />;
}
buttons = (
<>
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get("username") })} onClick={this.handleMute} />
{hidingNotificationsButton}
</>
);
} else if (defaultAction === 'mute') {
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get('username') })} onClick={this.handleMute} />;
} else if (defaultAction === 'block') {
buttons = <IconButton icon='lock' title={intl.formatMessage(messages.block, { name: account.get('username') })} onClick={this.handleBlock} />;
} else if (!account.get('moved') || following) {
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
} else if (defaultAction === "mute") {
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get("username") })} onClick={this.handleMute} />;
} else if (defaultAction === "block") {
buttons = <IconButton icon='lock' title={intl.formatMessage(messages.block, { name: account.get("username") })} onClick={this.handleBlock} />;
} else if (!account.get("moved") || following) {
buttons = <IconButton icon={following ? "user-times" : "user-plus"} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
}
}
let mute_expires_at;
if (account.get('mute_expires_at')) {
mute_expires_at = <div><RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></div>;
if (account.get("mute_expires_at")) {
mute_expires_at = <div><RelativeTimestamp timestamp={account.get("mute_expires_at")} futureDate /></div>;
}
return small ? (
<Permalink
className='account small'
href={account.get('url')}
to={`/@${account.get('acct')}`}
href={account.get("url")}
to={`/@${account.get("acct")}`}
>
<div className='account__avatar-wrapper'>
<Avatar
@@ -171,7 +171,7 @@ class Account extends ImmutablePureComponent {
) : (
<div className='account'>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<Permalink key={account.get("id")} className='account__display-name' title={account.get("acct")} href={account.get("url")} to={`/@${account.get("acct")}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div>
{mute_expires_at}
<DisplayName account={account} />
@@ -1,14 +1,14 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedNumber } from 'react-intl';
import { FormattedNumber } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { Sparklines, SparklinesCurve } from "react-sparklines";
import api from 'flavours/glitch/api';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import api from "flavours/glitch/api";
import { Skeleton } from "flavours/glitch/components/skeleton";
const percIncrease = (a, b) => {
let percent;
@@ -48,7 +48,7 @@ export default class Counter extends PureComponent {
componentDidMount () {
const { measure, start_at, end_at, params } = this.props;
api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
api().post("/api/v1/admin/measures", { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
this.setState({
loading: false,
data: res.data,
@@ -78,7 +78,7 @@ export default class Counter extends PureComponent {
content = (
<>
<span className='sparkline__value__total'>{measure.human_value || <FormattedNumber value={measure.total} />}</span>
{measure.previous_total && (<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>)}
{measure.previous_total && (<span className={classNames("sparkline__value__change", { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && "+"}<FormattedNumber value={percentChange} style='percent' /></span>)}
</>
);
}
@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedNumber } from 'react-intl';
import { FormattedNumber } from "react-intl";
import api from 'flavours/glitch/api';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import { roundTo10 } from 'flavours/glitch/utils/numbers';
import api from "flavours/glitch/api";
import { Skeleton } from "flavours/glitch/components/skeleton";
import { roundTo10 } from "flavours/glitch/utils/numbers";
export default class Dimension extends PureComponent {
@@ -26,7 +26,7 @@ export default class Dimension extends PureComponent {
componentDidMount () {
const { start_at, end_at, dimension, limit, params } = this.props;
api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
api().post("/api/v1/admin/dimensions", { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
this.setState({
loading: false,
data: res.data,
@@ -74,7 +74,7 @@ export default class Dimension extends PureComponent {
</td>
<td className='dimension__item__value'>
{typeof item.human_value !== 'undefined' ? item.human_value : <FormattedNumber value={item.value} />}
{typeof item.human_value !== "undefined" ? item.human_value : <FormattedNumber value={item.value} />}
</td>
</tr>
))}
@@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedNumber, FormattedMessage } from 'react-intl';
import { FormattedNumber, FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import api from 'flavours/glitch/api';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import api from "flavours/glitch/api";
import { Skeleton } from "flavours/glitch/components/skeleton";
export default class ImpactReport extends PureComponent {
@@ -27,8 +27,8 @@ export default class ImpactReport extends PureComponent {
include_subdomains: true,
};
api().post('/api/v1/admin/measures', {
keys: ['instance_accounts', 'instance_follows', 'instance_followers'],
api().post("/api/v1/admin/measures", {
keys: ["instance_accounts", "instance_follows", "instance_followers"],
start_at: null,
end_at: null,
instance_accounts: params,
@@ -63,7 +63,7 @@ export default class ImpactReport extends PureComponent {
</td>
</tr>
<tr className={classNames('dimension__item', { negative: !loading && data[1].total > 0 })}>
<tr className={classNames("dimension__item", { negative: !loading && data[1].total > 0 })}>
<td className='dimension__item__key'>
<FormattedMessage id='admin.impact_report.instance_follows' defaultMessage='Followers their users would lose' />
</td>
@@ -73,7 +73,7 @@ export default class ImpactReport extends PureComponent {
</td>
</tr>
<tr className={classNames('dimension__item', { negative: !loading && data[2].total > 0 })}>
<tr className={classNames("dimension__item", { negative: !loading && data[2].total > 0 })}>
<td className='dimension__item__key'>
<FormattedMessage id='admin.impact_report.instance_followers' defaultMessage='Followers our users would lose' />
</td>
@@ -1,17 +1,17 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { injectIntl, defineMessages } from 'react-intl';
import { injectIntl, defineMessages } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import api from 'flavours/glitch/api';
import api from "flavours/glitch/api";
const messages = defineMessages({
legal: { id: 'report.categories.legal', defaultMessage: 'Legal' },
other: { id: 'report.categories.other', defaultMessage: 'Other' },
spam: { id: 'report.categories.spam', defaultMessage: 'Spam' },
violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' },
legal: { id: "report.categories.legal", defaultMessage: "Legal" },
other: { id: "report.categories.other", defaultMessage: "Other" },
spam: { id: "report.categories.spam", defaultMessage: "Spam" },
violation: { id: "report.categories.violation", defaultMessage: "Content violates one or more server rules" },
});
class Category extends PureComponent {
@@ -37,11 +37,11 @@ class Category extends PureComponent {
const { id, text, disabled, selected, children } = this.props;
return (
<div tabIndex={0} role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
<div tabIndex={0} role='button' className={classNames("report-reason-selector__category", { selected, disabled })} onClick={this.handleClick}>
{selected && <input type='hidden' name='report[category]' value={id} />}
<div className='report-reason-selector__category__label'>
<span className={classNames('poll__input', { active: selected, disabled })} />
<span className={classNames("poll__input", { active: selected, disabled })} />
{text}
</div>
@@ -78,8 +78,8 @@ class Rule extends PureComponent {
const { id, text, disabled, selected } = this.props;
return (
<div tabIndex={0} role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
<span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
<div tabIndex={0} role='button' className={classNames("report-reason-selector__rule", { selected, disabled })} onClick={this.handleClick}>
<span className={classNames("poll__input", { checkbox: true, active: selected, disabled })} />
{selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
{text}
</div>
@@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent {
};
componentDidMount() {
api().get('/api/v1/instance').then(res => {
api().get("/api/v1/instance").then(res => {
this.setState({
rules: res.data.rules,
});
@@ -150,10 +150,10 @@ class ReportReasonSelector extends PureComponent {
return (
<div className='report-reason-selector'>
<Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} />
<Category id='legal' text={intl.formatMessage(messages.legal)} selected={category === 'legal'} onSelect={this.handleSelect} disabled={disabled} />
<Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} />
<Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}>
<Category id='other' text={intl.formatMessage(messages.other)} selected={category === "other"} onSelect={this.handleSelect} disabled={disabled} />
<Category id='legal' text={intl.formatMessage(messages.legal)} selected={category === "legal"} onSelect={this.handleSelect} disabled={disabled} />
<Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === "spam"} onSelect={this.handleSelect} disabled={disabled} />
<Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === "violation"} onSelect={this.handleSelect} disabled={disabled}>
{rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)}
</Category>
</div>
@@ -1,20 +1,20 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
import { FormattedMessage, FormattedNumber, FormattedDate } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import api from 'flavours/glitch/api';
import { roundTo10 } from 'flavours/glitch/utils/numbers';
import api from "flavours/glitch/api";
import { roundTo10 } from "flavours/glitch/utils/numbers";
const dateForCohort = cohort => {
const timeZone = 'UTC';
const timeZone = "UTC";
switch(cohort.frequency) {
case 'day':
return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />;
default:
return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />;
case "day":
return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />;
default:
return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />;
}
};
@@ -34,7 +34,7 @@ export default class Retention extends PureComponent {
componentDidMount () {
const { start_at, end_at, frequency } = this.props;
api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
api().post("/api/v1/admin/retention", { start_at, end_at, frequency }).then(res => {
this.setState({
loading: false,
data: res.data,
@@ -96,7 +96,7 @@ export default class Retention extends PureComponent {
return (
<td key={retention.date}>
<div className={classNames('retention__table__box', 'retention__table__average', `retention__table__box--${roundTo10(average * 100)}`)}>
<div className={classNames("retention__table__box", "retention__table__average", `retention__table__box--${roundTo10(average * 100)}`)}>
<FormattedNumber value={average} style='percent' />
</div>
</td>
@@ -122,7 +122,7 @@ export default class Retention extends PureComponent {
{cohort.data.slice(1).map(retention => (
<td key={retention.date}>
<div className={classNames('retention__table__box', `retention__table__box--${roundTo10(retention.rate * 100)}`)}>
<div className={classNames("retention__table__box", `retention__table__box--${roundTo10(retention.rate * 100)}`)}>
<FormattedNumber value={retention.rate} style='percent' />
</div>
</td>
@@ -134,13 +134,13 @@ export default class Retention extends PureComponent {
);
}
let title = null;
let title;
switch(frequency) {
case 'day':
title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />;
break;
default:
title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
case "day":
title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />;
break;
default:
title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
}
return (
@@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import api from 'flavours/glitch/api';
import Hashtag from 'flavours/glitch/components/hashtag';
import api from "flavours/glitch/api";
import Hashtag from "flavours/glitch/components/hashtag";
export default class Trends extends PureComponent {
@@ -22,7 +22,7 @@ export default class Trends extends PureComponent {
componentDidMount () {
const { limit } = this.props;
api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
api().get("/api/v1/admin/trends/tags", { params: { limit } }).then(res => {
this.setState({
loading: false,
data: res.data,
@@ -57,7 +57,7 @@ export default class Trends extends PureComponent {
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
history={hashtag.history.reverse().map(day => day.uses)}
className={classNames(hashtag.requires_review && 'trends__item--requires-review', !hashtag.trendable && !hashtag.requires_review && 'trends__item--disabled')}
className={classNames(hashtag.requires_review && "trends__item--requires-review", !hashtag.trendable && !hashtag.requires_review && "trends__item--disabled")}
/>
))}
</div>
@@ -1,11 +1,11 @@
import { useCallback, useState } from 'react';
import * as React from 'react';
import { useCallback, useState } from "react";
import * as React from "react";
import { TransitionMotion, spring } from 'react-motion';
import { TransitionMotion, spring } from "react-motion";
import { reduceMotion } from '../initial_state';
import { reduceMotion } from "../initial_state";
import { ShortNumber } from './short_number';
import { ShortNumber } from "./short_number";
const obfuscatedCount = (count: number) => {
if (count < 0) {
@@ -13,13 +13,13 @@ const obfuscatedCount = (count: number) => {
} else if (count <= 1) {
return count;
} else {
return '1+';
return "1+";
}
};
interface Props {
value: number;
obfuscate?: boolean;
value: number,
obfuscate?: boolean,
}
export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
const [previousValue, setPreviousValue] = useState(value);
@@ -64,7 +64,7 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
<span
key={key}
style={{
position: direction * style.y > 0 ? 'absolute' : 'static',
position: direction * style.y > 0 ? "absolute" : "static",
transform: `translateY(${style.y * 100}%)`,
}}
>
@@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
const filename = url => url.split("/").pop().split("#")[0].split("?")[0];
export default class AttachmentList extends ImmutablePureComponent {
@@ -22,7 +22,7 @@ export default class AttachmentList extends ImmutablePureComponent {
const { media, compact } = this.props;
return (
<div className={classNames('attachment-list', { compact })}>
<div className={classNames("attachment-list", { compact })}>
{!compact && (
<div className='attachment-list__icon'>
<Icon id='link' />
@@ -31,13 +31,13 @@ export default class AttachmentList extends ImmutablePureComponent {
<ul className='attachment-list__list'>
{media.map(attachment => {
const displayUrl = attachment.get('remote_url') || attachment.get('url');
const displayUrl = attachment.get("remote_url") || attachment.get("url");
return (
<li key={attachment.get('id')}>
<li key={attachment.get("id")}>
<a href={displayUrl} target='_blank' rel='noopener noreferrer'>
{compact && <Icon id='link' />}
{compact && ' ' }
{compact && " " }
{displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
</a>
</li>
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
import { assetHost } from 'flavours/glitch/utils/config';
import unicodeMapping from "flavours/glitch/features/emoji/emoji_unicode_mapping_light";
import { assetHost } from "flavours/glitch/utils/config";
export default class AutosuggestEmoji extends PureComponent {
@@ -17,7 +17,7 @@ export default class AutosuggestEmoji extends PureComponent {
if (emoji.custom) {
url = emoji.imageUrl;
} else {
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, "")];
if (!mapping) {
return null;
@@ -1,19 +1,19 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { ShortNumber } from "flavours/glitch/components/short_number";
interface Props {
tag: {
name: string;
url?: string;
name: string,
url?: string,
history?: {
uses: number;
accounts: string;
day: string;
}[];
following?: boolean;
type: 'hashtag';
};
uses: number,
accounts: string,
day: string,
}[],
following?: boolean,
type: "hashtag",
},
}
export const AutosuggestHashtag: React.FC<Props> = ({ tag }) => {
@@ -1,14 +1,14 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
import AutosuggestAccountContainer from "flavours/glitch/features/compose/containers/autosuggest_account_container";
import AutosuggestEmoji from './autosuggest_emoji';
import { AutosuggestHashtag } from './autosuggest_hashtag';
import AutosuggestEmoji from "./autosuggest_emoji";
import { AutosuggestHashtag } from "./autosuggest_hashtag";
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
let word;
@@ -59,7 +59,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
static defaultProps = {
autoFocus: true,
searchTokens: ['@', ':', '#'],
searchTokens: ["@", ":", "#"],
};
state = {
@@ -100,39 +100,39 @@ export default class AutosuggestInput extends ImmutablePureComponent {
}
switch(e.key) {
case 'Escape':
if (suggestions.size === 0 || suggestionsHidden) {
document.querySelector('.ui').parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
}
case "Escape":
if (suggestions.size === 0 || suggestionsHidden) {
document.querySelector(".ui").parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
}
break;
case 'ArrowDown':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
}
break;
case "ArrowDown":
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
}
break;
case 'ArrowUp':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
}
break;
case "ArrowUp":
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
}
break;
case 'Enter':
case 'Tab':
break;
case "Enter":
case "Tab":
// Select suggestion
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
}
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
}
break;
break;
}
if (e.defaultPrevented || !this.props.onKeyDown) {
@@ -151,7 +151,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
};
onSuggestionClick = (e) => {
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute("data-index"));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.input.focus();
@@ -171,19 +171,19 @@ export default class AutosuggestInput extends ImmutablePureComponent {
const { selectedSuggestion } = this.state;
let inner, key;
if (suggestion.type === 'emoji') {
if (suggestion.type === "emoji") {
inner = <AutosuggestEmoji emoji={suggestion} />;
key = suggestion.id;
} else if (suggestion.type ==='hashtag') {
} else if (suggestion.type ==="hashtag") {
inner = <AutosuggestHashtag tag={suggestion} />;
key = suggestion.name;
} else if (suggestion.type === 'account') {
} else if (suggestion.type === "account") {
inner = <AutosuggestAccountContainer id={suggestion.id} />;
key = suggestion.id;
}
return (
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames("autosuggest-textarea__suggestions__item", { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
{inner}
</div>
);
@@ -196,7 +196,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
return (
<div className='autosuggest-input'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>
<span style={{ display: "none" }}>{placeholder}</span>
<input
type='text'
@@ -220,7 +220,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
/>
</label>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? "" : "autosuggest-textarea__suggestions--visible"}`}>
{suggestions.map(this.renderSuggestion)}
</div>
</div>
@@ -1,16 +1,16 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import Textarea from 'react-textarea-autosize';
import Textarea from "react-textarea-autosize";
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
import AutosuggestAccountContainer from "flavours/glitch/features/compose/containers/autosuggest_account_container";
import AutosuggestEmoji from './autosuggest_emoji';
import { AutosuggestHashtag } from './autosuggest_hashtag';
import AutosuggestEmoji from "./autosuggest_emoji";
import { AutosuggestHashtag } from "./autosuggest_hashtag";
const textAtCursorMatchesToken = (str, caretPosition) => {
let word;
@@ -24,7 +24,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
word = str.slice(left, right + caretPosition);
}
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
if (!word || word.trim().length < 3 || ["@", ":", "#"].indexOf(word[0]) === -1) {
return [null, null];
}
@@ -97,39 +97,39 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
switch(e.key) {
case 'Escape':
if (suggestions.size === 0 || suggestionsHidden) {
document.querySelector('.ui').parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
}
case "Escape":
if (suggestions.size === 0 || suggestionsHidden) {
document.querySelector(".ui").parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
}
break;
case 'ArrowDown':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
}
break;
case "ArrowDown":
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
}
break;
case 'ArrowUp':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
}
break;
case "ArrowUp":
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
}
break;
case 'Enter':
case 'Tab':
break;
case "Enter":
case "Tab":
// Select suggestion
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
}
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
}
break;
break;
}
if (e.defaultPrevented || !this.props.onKeyDown) {
@@ -151,7 +151,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
};
onSuggestionClick = (e) => {
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute("data-index"));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus();
@@ -178,19 +178,19 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
const { selectedSuggestion } = this.state;
let inner, key;
if (suggestion.type === 'emoji') {
if (suggestion.type === "emoji") {
inner = <AutosuggestEmoji emoji={suggestion} />;
key = suggestion.id;
} else if (suggestion.type === 'hashtag') {
} else if (suggestion.type === "hashtag") {
inner = <AutosuggestHashtag tag={suggestion} />;
key = suggestion.name;
} else if (suggestion.type === 'account') {
} else if (suggestion.type === "account") {
inner = <AutosuggestAccountContainer id={suggestion.id} />;
key = suggestion.id;
}
return (
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames("autosuggest-textarea__suggestions__item", { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
{inner}
</div>
);
@@ -204,7 +204,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>
<span style={{ display: "none" }}>{placeholder}</span>
<Textarea
ref={this.setTextarea}
@@ -229,7 +229,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
</div>,
<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? "" : "autosuggest-textarea__suggestions--visible"}`}>
{suggestions.map(this.renderSuggestion)}
</div>
</div>,
@@ -1,17 +1,17 @@
import * as React from 'react';
import * as React from "react";
import classNames from 'classnames';
import classNames from "classnames";
import { useHovering } from 'flavours/glitch/hooks/useHovering';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import type { Account } from 'flavours/glitch/types/resources';
import { useHovering } from "flavours/glitch/hooks/useHovering";
import { autoPlayGif } from "flavours/glitch/initial_state";
import { type Account } from "flavours/glitch/types/resources";
interface Props {
account: Account | undefined;
className?: string;
size: number;
style?: React.CSSProperties;
inline?: boolean;
account: Account | undefined,
className?: string,
size: number,
style?: React.CSSProperties,
inline?: boolean,
}
export const Avatar: React.FC<Props> = ({
@@ -33,23 +33,23 @@ export const Avatar: React.FC<Props> = ({
if (account) {
style.backgroundImage = `url(${account.get(
hovering ? 'avatar' : 'avatar_static',
hovering ? "avatar" : "avatar_static",
)})`;
}
return (
<div
className={classNames(
'account__avatar',
{ 'account__avatar-inline': inline },
"account__avatar",
{ "account__avatar-inline": inline },
className,
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={style}
data-avatar-of={account && `@${account.get('acct')}`}
data-avatar-of={account && `@${account.get("acct")}`}
role='img'
aria-label={account?.get('acct')}
aria-label={account?.get("acct")}
/>
);
};
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { autoPlayGif } from 'flavours/glitch/initial_state';
import { autoPlayGif } from "flavours/glitch/initial_state";
export default class AvatarComposite extends PureComponent {
@@ -22,10 +22,10 @@ export default class AvatarComposite extends PureComponent {
let width = 50;
let height = 100;
let top = 'auto';
let left = 'auto';
let bottom = 'auto';
let right = 'auto';
let top = "auto";
let left = "auto";
let bottom = "auto";
let right = "auto";
if (size === 1) {
width = 100;
@@ -37,35 +37,35 @@ export default class AvatarComposite extends PureComponent {
if (size === 2) {
if (index === 0) {
right = '1px';
right = "1px";
} else {
left = '1px';
left = "1px";
}
} else if (size === 3) {
if (index === 0) {
right = '1px';
right = "1px";
} else if (index > 0) {
left = '1px';
left = "1px";
}
if (index === 1) {
bottom = '1px';
bottom = "1px";
} else if (index > 1) {
top = '1px';
top = "1px";
}
} else if (size === 4) {
if (index === 0 || index === 2) {
right = '1px';
right = "1px";
}
if (index === 1 || index === 3) {
left = '1px';
left = "1px";
}
if (index < 2) {
bottom = '1px';
bottom = "1px";
} else {
top = '1px';
top = "1px";
}
}
@@ -76,12 +76,12 @@ export default class AvatarComposite extends PureComponent {
bottom: bottom,
width: `${width}%`,
height: `${height}%`,
backgroundSize: 'cover',
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
backgroundSize: "cover",
backgroundImage: `url(${account.get(animate ? "avatar" : "avatar_static")})`,
};
return (
<div key={account.get('id')} style={style} data-avatar-of={`@${account.get('acct')}`} />
<div key={account.get("id")} style={style} data-avatar-of={`@${account.get("acct")}`} />
);
}
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { autoPlayGif } from 'flavours/glitch/initial_state';
import { autoPlayGif } from "flavours/glitch/initial_state";
export default class AvatarOverlay extends PureComponent {
@@ -21,17 +21,17 @@ export default class AvatarOverlay extends PureComponent {
const { account, friend, animate } = this.props;
const baseStyle = {
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
backgroundImage: `url(${account.get(animate ? "avatar" : "avatar_static")})`,
};
const overlayStyle = {
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
backgroundImage: `url(${friend.get(animate ? "avatar" : "avatar_static")})`,
};
return (
<div className='account__avatar-overlay'>
<div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} />
<div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} />
<div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get("acct")}`} />
<div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get("acct")}`} />
</div>
);
}
@@ -1,14 +1,14 @@
import { useRef, useEffect } from 'react';
import * as React from 'react';
import { useRef, useEffect } from "react";
import * as React from "react";
import { decode } from 'blurhash';
import { decode } from "blurhash";
interface Props extends React.HTMLAttributes<HTMLCanvasElement> {
hash: string;
width?: number;
height?: number;
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
children?: never;
hash: string,
width?: number,
height?: number,
dummy?: boolean, // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
children?: never,
}
const Blurhash: React.FC<Props> = ({
hash,
@@ -26,16 +26,18 @@ const Blurhash: React.FC<Props> = ({
// eslint-disable-next-line no-self-assign
canvas.width = canvas.width; // resets canvas
if (dummy || !hash) return;
if (dummy || !hash) {
return;
}
try {
const pixels = decode(hash, width, height);
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext("2d");
const imageData = new ImageData(pixels, width, height);
ctx?.putImageData(imageData, 0, 0);
} catch (err) {
console.error('Blurhash decoding failure', { err, hash });
console.error("Blurhash decoding failure", { err, hash });
}
}, [dummy, hash, width, height]);
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import classNames from 'classnames';
import classNames from "classnames";
export default class Button extends PureComponent {
@@ -32,16 +32,18 @@ export default class Button extends PureComponent {
render () {
let attrs = {
className: classNames('button', this.props.className, {
'button-secondary': this.props.secondary,
'button--block': this.props.block,
className: classNames("button", this.props.className, {
"button-secondary": this.props.secondary,
"button--block": this.props.block,
}),
disabled: this.props.disabled,
onClick: this.handleClick,
ref: this.setRef,
};
if (this.props.title) attrs.title = this.props.title;
if (this.props.title) {
attrs.title = this.props.title;
}
return (
<button {...attrs}>
@@ -1,6 +1,6 @@
interface Props {
size: number;
strokeWidth: number;
size: number,
strokeWidth: number,
}
export const CircularProgress: React.FC<Props> = ({ size, strokeWidth }) => {
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { supportsPassiveEvents } from 'detect-passive-events';
import { supportsPassiveEvents } from "detect-passive-events";
import { scrollTop } from '../scroll';
import { scrollTop } from "../scroll";
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
@@ -23,12 +23,12 @@ export default class Column extends PureComponent {
if (this.props.bindToDocument) {
scrollable = document.scrollingElement;
} else {
scrollable = this.node.querySelector('.scrollable');
scrollable = this.node.querySelector(".scrollable");
// Some columns have nested `.scrollable` containers, with the outer one
// being a wrapper while the actual scrollable content is deeper.
if (scrollable.classList.contains('scrollable--flex')) {
scrollable = scrollable?.querySelector('.scrollable') || scrollable;
if (scrollable.classList.contains("scrollable--flex")) {
scrollable = scrollable?.querySelector(".scrollable") || scrollable;
}
}
@@ -40,7 +40,7 @@ export default class Column extends PureComponent {
}
handleWheel = () => {
if (typeof this._interruptScrollAnimation !== 'function') {
if (typeof this._interruptScrollAnimation !== "function") {
return;
}
@@ -53,17 +53,17 @@ export default class Column extends PureComponent {
componentDidMount () {
if (this.props.bindToDocument) {
document.addEventListener('wheel', this.handleWheel, listenerOptions);
document.addEventListener("wheel", this.handleWheel, listenerOptions);
} else {
this.node.addEventListener('wheel', this.handleWheel, listenerOptions);
this.node.addEventListener("wheel", this.handleWheel, listenerOptions);
}
}
componentWillUnmount () {
if (this.props.bindToDocument) {
document.removeEventListener('wheel', this.handleWheel, listenerOptions);
document.removeEventListener("wheel", this.handleWheel, listenerOptions);
} else {
this.node.removeEventListener('wheel', this.handleWheel, listenerOptions);
this.node.removeEventListener("wheel", this.handleWheel, listenerOptions);
}
}
@@ -71,7 +71,7 @@ export default class Column extends PureComponent {
const { children, extraClasses, name, label } = this.props;
return (
<div role='region' aria-label={label} data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
<div role='region' aria-label={label} data-column={name} className={`column ${extraClasses || ""}`} ref={this.setRef}>
{children}
</div>
);
@@ -1,10 +1,10 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { createPortal } from "react-dom";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
export default class ColumnBackButton extends PureComponent {
@@ -23,7 +23,7 @@ export default class ColumnBackButton extends PureComponent {
if (router.history.location?.state?.fromMastodon) {
router.history.goBack();
} else {
router.history.push('/');
router.history.push("/");
}
};
@@ -43,7 +43,7 @@ export default class ColumnBackButton extends PureComponent {
// The portal container and the component may be rendered to the DOM in
// the same React render pass, so the container might not be available at
// the time `render()` is called.
const container = document.getElementById('tabs-bar__portal');
const container = document.getElementById("tabs-bar__portal");
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
export default class ColumnBackButtonSlim extends PureComponent {
@@ -19,7 +19,7 @@ export default class ColumnBackButtonSlim extends PureComponent {
if (router.route.location.key) {
router.history.goBack();
} else {
router.history.push('/');
router.history.push("/");
}
};
@@ -1,18 +1,18 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { createPortal } from "react-dom";
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { FormattedMessage, injectIntl, defineMessages } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
show: { id: "column_header.show_settings", defaultMessage: "Show settings" },
hide: { id: "column_header.hide_settings", defaultMessage: "Hide settings" },
moveLeft: { id: "column_header.moveLeft_settings", defaultMessage: "Move column to the left" },
moveRight: { id: "column_header.moveRight_settings", defaultMessage: "Move column to the right" },
});
class ColumnHeader extends PureComponent {
@@ -68,7 +68,7 @@ class ColumnHeader extends PureComponent {
if (router.history.location?.state?.fromMastodon) {
router.history.goBack();
} else {
router.history.push('/');
router.history.push("/");
}
};
@@ -78,7 +78,7 @@ class ColumnHeader extends PureComponent {
handlePin = () => {
if (!this.props.pinned) {
this.context.router.history.replace('/');
this.context.router.history.replace("/");
}
this.props.onPin();
@@ -89,21 +89,21 @@ class ColumnHeader extends PureComponent {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', {
'active': active,
const wrapperClassName = classNames("column-header__wrapper", {
"active": active,
});
const buttonClassName = classNames('column-header', {
'active': active,
const buttonClassName = classNames("column-header", {
"active": active,
});
const collapsibleClassName = classNames('column-header__collapsible', {
'collapsed': collapsed,
'animating': animating,
const collapsibleClassName = classNames("column-header__collapsible", {
"collapsed": collapsed,
"animating": animating,
});
const collapsibleButtonClassName = classNames('column-header__button', {
'active': !collapsed,
const collapsibleButtonClassName = classNames("column-header__button", {
"active": !collapsed,
});
let extraContent, pinButton, moveButtons, backButton, collapseButton;
@@ -200,7 +200,7 @@ class ColumnHeader extends PureComponent {
// The portal container and the component may be rendered to the DOM in
// the same React render pass, so the container might not be available at
// the time `render()` is called.
const container = document.getElementById('tabs-bar__portal');
const container = document.getElementById("tabs-bar__portal");
if (container === null) {
// The container wasn't available, force a re-render so that the
// component can eventually be inserted in the container and not scroll
@@ -1,6 +1,6 @@
import React from 'react';
import React from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
export const StatusesCounter = (
displayNumber: React.ReactNode,
@@ -1,18 +1,18 @@
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';
import { type PropsWithChildren } from "react";
import { useCallback, useState } from "react";
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, useIntl } from "react-intl";
import { bannerSettings } from 'flavours/glitch/settings';
import { bannerSettings } from "flavours/glitch/settings";
import { IconButton } from './icon_button';
import { IconButton } from "./icon_button";
const messages = defineMessages({
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
dismiss: { id: "dismissable_banner.dismiss", defaultMessage: "Dismiss" },
});
interface Props {
id: string;
id: string,
}
export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
@@ -1,20 +1,20 @@
import React from 'react';
import React from "react";
import classNames from 'classnames';
import classNames from "classnames";
import type { List } from 'immutable';
import { type List } from "immutable";
import type { Account } from 'flavours/glitch/types/resources';
import { type Account } from "flavours/glitch/types/resources";
import { autoPlayGif } from '../initial_state';
import { autoPlayGif } from "../initial_state";
import { Skeleton } from './skeleton';
import { Skeleton } from "./skeleton";
interface Props {
account?: Account;
others?: List<Account>;
localDomain?: string;
inline?: boolean;
account?: Account,
others?: List<Account>,
localDomain?: string,
inline?: boolean,
}
export class DisplayName extends React.PureComponent<Props> {
@@ -26,11 +26,13 @@ export class DisplayName extends React.PureComponent<Props> {
}
const emojis =
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
currentTarget.querySelectorAll<HTMLImageElement>("img.custom-emoji");
emojis.forEach((emoji) => {
const originalSrc = emoji.getAttribute('data-original');
if (originalSrc != null) emoji.src = originalSrc;
const originalSrc = emoji.getAttribute("data-original");
if (originalSrc != null) {
emoji.src = originalSrc;
}
});
};
@@ -42,11 +44,13 @@ export class DisplayName extends React.PureComponent<Props> {
}
const emojis =
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
currentTarget.querySelectorAll<HTMLImageElement>("img.custom-emoji");
emojis.forEach((emoji) => {
const staticSrc = emoji.getAttribute('data-static');
if (staticSrc != null) emoji.src = staticSrc;
const staticSrc = emoji.getAttribute("data-static");
if (staticSrc != null) {
emoji.src = staticSrc;
}
});
};
@@ -67,22 +71,22 @@ export class DisplayName extends React.PureComponent<Props> {
displayName = others
.take(2)
.map((a) => (
<bdi key={a.get('id')}>
<bdi key={a.get("id")}>
<strong
className='display-name__html'
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
dangerouslySetInnerHTML={{ __html: a.get("display_name_html") }}
/>
</bdi>
))
.reduce((prev, cur) => [prev, ', ', cur]);
.reduce((prev, cur) => [prev, ", ", cur]);
if (others.size - 2 > 0) {
suffix = `+${others.size - 2}`;
}
} else if (account) {
let acct = account.get('acct');
let acct = account.get("acct");
if (!acct.includes('@') && localDomain) {
if (!acct.includes("@") && localDomain) {
acct = `${acct}@${localDomain}`;
}
@@ -91,7 +95,7 @@ export class DisplayName extends React.PureComponent<Props> {
<strong
className='display-name__html'
dangerouslySetInnerHTML={{
__html: account.get('display_name_html'),
__html: account.get("display_name_html"),
}}
/>
</bdi>
@@ -114,12 +118,12 @@ export class DisplayName extends React.PureComponent<Props> {
return (
<span
className={classNames('display-name', { inline })}
className={classNames("display-name", { inline })}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{displayName}
{inline ? ' ' : null}
{inline ? " " : null}
{suffix}
</span>
);
@@ -1,20 +1,20 @@
import { useCallback } from 'react';
import * as React from 'react';
import { useCallback } from "react";
import * as React from "react";
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, useIntl } from "react-intl";
import { IconButton } from './icon_button';
import { IconButton } from "./icon_button";
const messages = defineMessages({
unblockDomain: {
id: 'account.unblock_domain',
defaultMessage: 'Unblock domain {domain}',
id: "account.unblock_domain",
defaultMessage: "Unblock domain {domain}",
},
});
interface Props {
domain: string;
onUnblockDomain: (domain: string) => void;
domain: string,
onUnblockDomain: (domain: string) => void,
}
export const Domain: React.FC<Props> = ({ domain, onUnblockDomain }) => {
@@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import { PureComponent, cloneElement, Children } from 'react';
import PropTypes from "prop-types";
import { PureComponent, cloneElement, Children } from "react";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from "detect-passive-events";
import Overlay from "react-overlays/Overlay";
import { CircularProgress } from "./circular_progress";
import { IconButton } from './icon_button';
import { IconButton } from "./icon_button";
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
let id = 0;
@@ -44,9 +44,9 @@ class DropdownMenu extends PureComponent {
};
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, { capture: true });
document.addEventListener('keydown', this.handleKeyDown, { capture: true });
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.addEventListener("click", this.handleDocumentClick, { capture: true });
document.addEventListener("keydown", this.handleKeyDown, { capture: true });
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus({ preventScroll: true });
@@ -54,9 +54,9 @@ class DropdownMenu extends PureComponent {
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
document.removeEventListener('keydown', this.handleKeyDown, { capture: true });
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
document.removeEventListener("keydown", this.handleKeyDown, { capture: true });
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
setRef = c => {
@@ -68,33 +68,33 @@ class DropdownMenu extends PureComponent {
};
handleKeyDown = e => {
const items = Array.from(this.node.querySelectorAll('a, button'));
const items = Array.from(this.node.querySelectorAll("a, button"));
const index = items.indexOf(document.activeElement);
let element = null;
switch(e.key) {
case 'ArrowDown':
element = items[index+1] || items[0];
break;
case 'ArrowUp':
element = items[index-1] || items[items.length-1];
break;
case 'Tab':
if (e.shiftKey) {
element = items[index-1] || items[items.length-1];
} else {
case "ArrowDown":
element = items[index+1] || items[0];
}
break;
case 'Home':
element = items[0];
break;
case 'End':
element = items[items.length-1];
break;
case 'Escape':
this.props.onClose();
break;
break;
case "ArrowUp":
element = items[index-1] || items[items.length-1];
break;
case "Tab":
if (e.shiftKey) {
element = items[index-1] || items[items.length-1];
} else {
element = items[index+1] || items[0];
}
break;
case "Home":
element = items[0];
break;
case "End":
element = items[items.length-1];
break;
case "Escape":
this.props.onClose();
break;
}
if (element) {
@@ -105,7 +105,7 @@ class DropdownMenu extends PureComponent {
};
handleItemKeyPress = e => {
if (e.key === 'Enter' || e.key === ' ') {
if (e.key === "Enter" || e.key === " ") {
this.handleClick(e);
}
};
@@ -120,10 +120,10 @@ class DropdownMenu extends PureComponent {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
}
const { text, href = '#', target = '_blank', method, dangerous } = option;
const { text, href = "#", target = "_blank", method, dangerous } = option;
return (
<li className={classNames('dropdown-menu__item', { 'dropdown-menu__item--dangerous': dangerous })} key={`${text}-${i}`}>
<li className={classNames("dropdown-menu__item", { "dropdown-menu__item--dangerous": dangerous })} key={`${text}-${i}`}>
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
{text}
</a>
@@ -137,7 +137,7 @@ class DropdownMenu extends PureComponent {
let renderItem = this.props.renderItem || this.renderItem;
return (
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
<div className={classNames("dropdown-menu__container", { "dropdown-menu__container--loading": loading })} ref={this.setRef}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
@@ -149,7 +149,7 @@ class DropdownMenu extends PureComponent {
)}
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
<ul className={classNames("dropdown-menu__container__list", { "dropdown-menu__container__list--scrollable": scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
@@ -186,7 +186,7 @@ export default class Dropdown extends PureComponent {
};
static defaultProps = {
title: 'Menu',
title: "Menu",
};
state = {
@@ -197,7 +197,7 @@ export default class Dropdown extends PureComponent {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
this.props.onOpen(this.state.id, this.handleItemClick, type !== "click");
}
};
@@ -217,35 +217,35 @@ export default class Dropdown extends PureComponent {
handleButtonKeyDown = (e) => {
switch(e.key) {
case ' ':
case 'Enter':
this.handleMouseDown();
break;
case " ":
case "Enter":
this.handleMouseDown();
break;
}
};
handleKeyPress = (e) => {
switch(e.key) {
case ' ':
case 'Enter':
this.handleClick(e);
e.stopPropagation();
e.preventDefault();
break;
case " ":
case "Enter":
this.handleClick(e);
e.stopPropagation();
e.preventDefault();
break;
}
};
handleItemClick = e => {
const { onItemClick } = this.props;
const i = Number(e.currentTarget.getAttribute('data-index'));
const i = Number(e.currentTarget.getAttribute("data-index"));
const item = this.props.items[i];
this.handleClose();
if (typeof onItemClick === 'function') {
if (typeof onItemClick === "function") {
e.preventDefault();
onItemClick(item, i);
} else if (item && typeof item.action === 'function') {
} else if (item && typeof item.action === "function") {
e.preventDefault();
item.action();
} else if (item && item.to) {
@@ -314,7 +314,7 @@ export default class Dropdown extends PureComponent {
<span ref={this.setTargetRef}>
{button}
</span>
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
<Overlay show={open} offset={[5, 5]} placement={"bottom"} flip target={this.findTarget} popperConfig={{ strategy: "fixed" }}>
{({ props, arrowProps, placement }) => (
<div {...props}>
<div className={`dropdown-animation dropdown-menu ${placement}`}>
@@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { openDropdownMenu, closeDropdownMenu } from 'flavours/glitch/actions/dropdown_menu';
import { fetchHistory } from 'flavours/glitch/actions/history';
import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
import { openDropdownMenu, closeDropdownMenu } from "flavours/glitch/actions/dropdown_menu";
import { fetchHistory } from "flavours/glitch/actions/history";
import DropdownMenu from "flavours/glitch/components/dropdown_menu";
/**
*
@@ -12,8 +12,8 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
const mapStateToProps = (state, { statusId }) => ({
openDropdownId: state.dropdownMenu.openId,
openedViaKeyboard: state.dropdownMenu.keyboard,
items: state.getIn(['history', statusId, 'items']),
loading: state.getIn(['history', statusId, 'loading']),
items: state.getIn(["history", statusId, "items"]),
loading: state.getIn(["history", statusId, "loading"]),
});
const mapDispatchToProps = (dispatch, { statusId }) => ({
@@ -1,22 +1,22 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage, injectIntl } from 'react-intl';
import { FormattedMessage, injectIntl } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { openModal } from 'flavours/glitch/actions/modal';
import { Icon } from 'flavours/glitch/components/icon';
import InlineAccount from 'flavours/glitch/components/inline_account';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { openModal } from "flavours/glitch/actions/modal";
import { Icon } from "flavours/glitch/components/icon";
import InlineAccount from "flavours/glitch/components/inline_account";
import { RelativeTimestamp } from "flavours/glitch/components/relative_timestamp";
import DropdownMenu from './containers/dropdown_menu_container';
import DropdownMenu from "./containers/dropdown_menu_container";
const mapDispatchToProps = (dispatch, { statusId }) => ({
onItemClick (index) {
dispatch(openModal({
modalType: 'COMPARE_HISTORY',
modalType: "COMPARE_HISTORY",
modalProps: { index, statusId },
}));
},
@@ -44,17 +44,17 @@ class EditedTimestamp extends PureComponent {
};
renderItem = (item, index, { onClick, onKeyPress }) => {
const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
const formattedName = <InlineAccount accountId={item.get('account')} />;
const formattedDate = <RelativeTimestamp timestamp={item.get("created_at")} short={false} />;
const formattedName = <InlineAccount accountId={item.get("account")} />;
const label = item.get('original') ? (
const label = item.get("original") ? (
<FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
) : (
<FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
);
return (
<li className='dropdown-menu__item edited-timestamp__history__item' key={item.get('created_at')}>
<li className='dropdown-menu__item edited-timestamp__history__item' key={item.get("created_at")}>
<button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
</li>
);
@@ -66,7 +66,7 @@ class EditedTimestamp extends PureComponent {
return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hourCycle: 'h23', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hourCycle: "h23", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" }) }} /> <Icon id='caret-down' />
</button>
</DropdownMenu>
);
@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import StackTrace from 'stacktrace-js';
import StackTrace from "stacktrace-js";
import { version, source_url } from 'flavours/glitch/initial_state';
import { version, source_url } from "flavours/glitch/initial_state";
export default class ErrorBoundary extends PureComponent {
@@ -34,7 +34,7 @@ export default class ErrorBoundary extends PureComponent {
StackTrace.fromError(error).then((stackframes) => {
this.setState({
mappedStackTrace: stackframes.map((sf) => sf.toString()).join('\n'),
mappedStackTrace: stackframes.map((sf) => sf.toString()).join("\n"),
});
}).catch(() => {
this.setState({
@@ -45,23 +45,23 @@ export default class ErrorBoundary extends PureComponent {
handleCopyStackTrace = () => {
const { errorMessage, stackTrace, mappedStackTrace } = this.state;
const textarea = document.createElement('textarea');
const textarea = document.createElement("textarea");
let contents = [errorMessage, stackTrace];
if (mappedStackTrace) {
contents.push(mappedStackTrace);
}
textarea.textContent = contents.join('\n\n\n');
textarea.style.position = 'fixed';
textarea.textContent = contents.join("\n\n\n");
textarea.style.position = "fixed";
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
document.execCommand("copy");
} catch (e) {
console.error(e);
} finally {
document.body.removeChild(textarea);
}
@@ -77,7 +77,7 @@ export default class ErrorBoundary extends PureComponent {
return this.props.children;
}
const likelyBrowserAddonIssue = errorMessage && errorMessage.includes('NotFoundError');
const likelyBrowserAddonIssue = errorMessage && errorMessage.includes("NotFoundError");
return (
<div className='error-boundary'>
@@ -98,7 +98,7 @@ export default class ErrorBoundary extends PureComponent {
)}
</p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? "copied" : ""}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
</div>
<Helmet>
@@ -1,14 +1,14 @@
import { useCallback, useState } from 'react';
import * as React from 'react';
import { useCallback, useState } from "react";
import * as React from "react";
interface Props {
src: string;
key: string;
alt?: string;
lang?: string;
width: number;
height: number;
onClick?: () => void;
src: string,
key: string,
alt?: string,
lang?: string,
width: number,
height: number,
onClick?: () => void,
}
export const GIFV: React.FC<Props> = ({
@@ -37,7 +37,7 @@ export const GIFV: React.FC<Props> = ({
);
return (
<div className='gifv' style={{ position: 'relative' }}>
<div className='gifv' style={{ position: "relative" }}>
{loading && (
<canvas
width={width}
@@ -64,7 +64,7 @@ export const GIFV: React.FC<Props> = ({
playsInline
onClick={handleClick}
onLoadedData={handleLoadedData}
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
style={{ position: loading ? "absolute" : "static", top: 0, left: 0 }}
/>
</div>
);
@@ -1,19 +1,19 @@
// @ts-check
import PropTypes from 'prop-types';
import { Component } from 'react';
import PropTypes from "prop-types";
import { Component } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { Sparklines, SparklinesCurve } from "react-sparklines";
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import { ShortNumber } from "flavours/glitch/components/short_number";
import { Skeleton } from "flavours/glitch/components/skeleton";
import Permalink from './permalink';
import Permalink from "./permalink";
class SilentErrorBoundary extends Component {
@@ -58,12 +58,12 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
// @ts-expect-error
export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag
name={hashtag.get('name')}
href={hashtag.get('url')}
to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
name={hashtag.get("name")}
href={hashtag.get("url")}
to={`/tags/${hashtag.get("name")}`}
people={hashtag.getIn(["history", 0, "accounts"]) * 1 + hashtag.getIn(["history", 1, "accounts"]) * 1}
// @ts-expect-error
history={hashtag.get('history')?.reverse().map((day) => day.get('uses')).toArray()}
history={hashtag.get("history")?.reverse().map((day) => day.get("uses")).toArray()}
/>
);
@@ -73,7 +73,7 @@ ImmutableHashtag.propTypes = {
// @ts-expect-error
const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}>
<div className={classNames("trends__item", className)}>
<div className='trends__item__name'>
<Permalink href={href} to={to}>
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
@@ -82,21 +82,21 @@ const Hashtag = ({ name, href, to, people, uses, history, className, description
{description ? (
<span>{description}</span>
) : (
!isNaN(people) && (typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />)
!isNaN(people) && (typeof people !== "undefined" ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />)
)}
</div>
{typeof uses !== 'undefined' && (
{typeof uses !== "undefined" && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && typeof history !== 'undefined' && (
{withGraph && typeof history !== "undefined" && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
<SparklinesCurve style={{ fill: 'none' }} />
<SparklinesCurve style={{ fill: "none" }} />
</Sparklines>
</SilentErrorBoundary>
</div>
@@ -1,12 +1,12 @@
import * as React from 'react';
import * as React from "react";
import classNames from 'classnames';
import classNames from "classnames";
interface Props extends React.HTMLAttributes<HTMLImageElement> {
id: string;
className?: string;
fixedWidth?: boolean;
children?: never;
id: string,
className?: string,
fixedWidth?: boolean,
children?: never,
}
export const Icon: React.FC<Props> = ({
@@ -16,7 +16,7 @@ export const Icon: React.FC<Props> = ({
...other
}) => (
<i
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
className={classNames("fa", `fa-${id}`, className, { "fa-fw": fixedWidth })}
{...other}
/>
);
@@ -1,37 +1,37 @@
import * as React from 'react';
import * as React from "react";
import classNames from 'classnames';
import classNames from "classnames";
import { AnimatedNumber } from './animated_number';
import { Icon } from './icon';
import { AnimatedNumber } from "./animated_number";
import { Icon } from "./icon";
interface Props {
className?: string;
title: string;
icon: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
size: number;
active: boolean;
expanded?: boolean;
style?: React.CSSProperties;
activeStyle?: React.CSSProperties;
disabled: boolean;
inverted?: boolean;
animate: boolean;
overlay: boolean;
tabIndex: number;
label?: string;
counter?: number;
obfuscateCount?: boolean;
href?: string;
ariaHidden: boolean;
className?: string,
title: string,
icon: string,
onClick?: React.MouseEventHandler<HTMLButtonElement>,
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>,
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>,
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>,
size: number,
active: boolean,
expanded?: boolean,
style?: React.CSSProperties,
activeStyle?: React.CSSProperties,
disabled: boolean,
inverted?: boolean,
animate: boolean,
overlay: boolean,
tabIndex: number,
label?: string,
counter?: number,
obfuscateCount?: boolean,
href?: string,
ariaHidden: boolean,
}
interface States {
activate: boolean;
deactivate: boolean;
activate: boolean,
deactivate: boolean,
}
export class IconButton extends React.PureComponent<Props, States> {
static defaultProps = {
@@ -50,7 +50,9 @@ export class IconButton extends React.PureComponent<Props, States> {
};
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (!nextProps.animate) return;
if (!nextProps.animate) {
return;
}
if (this.props.active && !nextProps.active) {
this.setState({ activate: false, deactivate: true });
@@ -67,12 +69,6 @@ export class IconButton extends React.PureComponent<Props, States> {
}
};
handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
if (this.props.onKeyPress && !this.props.disabled) {
this.props.onKeyPress(e);
}
};
handleMouseDown: React.MouseEventHandler<HTMLButtonElement> = (e) => {
if (!this.props.disabled && this.props.onMouseDown) {
this.props.onMouseDown(e);
@@ -87,7 +83,7 @@ export class IconButton extends React.PureComponent<Props, States> {
render() {
// Hack required for some icons which have an overriden size
let containerSize = '1.28571429em';
let containerSize = "1.28571429em";
if (this.props.style?.fontSize) {
containerSize = `${this.props.size * 1.28571429}px`;
}
@@ -102,7 +98,7 @@ export class IconButton extends React.PureComponent<Props, States> {
if (!this.props.label) {
style.width = containerSize;
} else {
style.textAlign = 'left';
style.textAlign = "left";
}
const {
@@ -123,24 +119,24 @@ export class IconButton extends React.PureComponent<Props, States> {
const { activate, deactivate } = this.state;
const classes = classNames(className, 'icon-button', {
const classes = classNames(className, "icon-button", {
active,
disabled,
inverted,
activate,
deactivate,
overlayed: overlay,
'icon-button--with-counter': typeof counter !== 'undefined',
"icon-button--with-counter": typeof counter !== "undefined",
});
if (typeof counter !== 'undefined') {
style.width = 'auto';
if (typeof counter !== "undefined") {
style.width = "auto";
}
let contents = (
<>
<Icon id={icon} fixedWidth aria-hidden='true' />{' '}
{typeof counter !== 'undefined' && (
<Icon id={icon} fixedWidth aria-hidden='true' />{" "}
{typeof counter !== "undefined" && (
<span className='icon-button__counter'>
<AnimatedNumber value={counter} obfuscate={obfuscateCount} />
</span>
@@ -168,7 +164,6 @@ export class IconButton extends React.PureComponent<Props, States> {
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleKeyDown}
onKeyPress={this.handleKeyPress}
style={style}
tabIndex={tabIndex}
disabled={disabled}
@@ -1,14 +1,14 @@
import * as React from 'react';
import * as React from "react";
import { Icon } from './icon';
import { Icon } from "./icon";
const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num);
const formatNumber = (num: number): number | string => (num > 40 ? "40+" : num);
interface Props {
id: string;
count: number;
issueBadge: boolean;
className: string;
id: string,
count: number,
issueBadge: boolean,
className: string,
}
export const IconWithBadge: React.FC<Props> = ({
id,
@@ -1,10 +1,10 @@
import { PureComponent } from 'react';
import { PureComponent } from "react";
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { Avatar } from 'flavours/glitch/components/avatar';
import { makeGetAccount } from 'flavours/glitch/selectors';
import { Avatar } from "flavours/glitch/components/avatar";
import { makeGetAccount } from "flavours/glitch/selectors";
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
@@ -27,7 +27,7 @@ class InlineAccount extends PureComponent {
return (
<span className='inline-account'>
<Avatar size={13} account={account} /> <strong>{account.get('username')}</strong>
<Avatar size={13} account={account} /> <strong>{account.get("username")}</strong>
</span>
);
}
@@ -1,10 +1,10 @@
import PropTypes from 'prop-types';
import { cloneElement, Component } from 'react';
import PropTypes from "prop-types";
import { cloneElement, Component } from "react";
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
import getRectFromEntry from "../features/ui/util/get_rect_from_entry";
import scheduleIdleTask from "../features/ui/util/schedule_idle_task";
// Diff these props in the "unrendered" state
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
const updateOnPropsForUnrendered = ["id", "index", "listLength", "cachedHeight"];
export default class IntersectionObserverArticle extends Component {
@@ -111,7 +111,7 @@ export default class IntersectionObserverArticle extends Component {
if (!isIntersecting && (isHidden || cachedHeight)) {
style.height = `${this.height || cachedHeight || 150}px`;
style.opacity = 0;
style.overflow = 'hidden';
style.overflow = "hidden";
}
return (
@@ -2,13 +2,13 @@
// ~ 😘 kibi!
// Package imports.
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import classNames from 'classnames';
import classNames from "classnames";
// Utils.
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
import { assignHandlers } from "flavours/glitch/utils/react_helpers";
// Handlers.
const handlers = {
@@ -45,7 +45,7 @@ export default class Link extends PureComponent {
title,
...rest
} = this.props;
const computedClass = classNames('link', className, `role-${role}`);
const computedClass = classNames("link", className, `role-${role}`);
// We assume that our `onClick` is a routing function and give it
// the qualities of a link even if no `href` is provided. However,
@@ -57,10 +57,10 @@ export default class Link extends PureComponent {
conditionalProps.onClick = click;
} else if (onClick) {
conditionalProps.onClick = click;
conditionalProps.role = 'link';
conditionalProps.role = "link";
conditionalProps.tabIndex = 0;
} else {
conditionalProps.role = 'presentation';
conditionalProps.role = "presentation";
}
// If we were provided a `role` it overwrites any that we may have
@@ -1,17 +1,17 @@
import { useCallback } from 'react';
import { useCallback } from "react";
import { useIntl, defineMessages } from 'react-intl';
import { useIntl, defineMessages } from "react-intl";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
load_more: { id: "status.load_more", defaultMessage: "Load more" },
});
interface Props {
disabled: boolean;
maxId: string;
onClick: (maxId: string) => void;
disabled: boolean,
maxId: string,
onClick: (maxId: string) => void,
}
export const LoadGap: React.FC<Props> = ({ disabled, maxId, onClick }) => {
@@ -1,9 +1,9 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
interface Props {
onClick: (event: React.MouseEvent) => void;
disabled?: boolean;
visible?: boolean;
onClick: (event: React.MouseEvent) => void,
disabled?: boolean,
visible?: boolean,
}
export const LoadMore: React.FC<Props> = ({
onClick,
@@ -15,7 +15,7 @@ export const LoadMore: React.FC<Props> = ({
type='button'
className='load-more'
disabled={disabled || !visible}
style={{ visibility: visible ? 'visible' : 'hidden' }}
style={{ visibility: visible ? "visible" : "hidden" }}
onClick={onClick}
>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
@@ -1,8 +1,8 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
interface Props {
onClick: (event: React.MouseEvent) => void;
count: number;
onClick: (event: React.MouseEvent) => void,
count: number,
}
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
@@ -1,4 +1,4 @@
import { CircularProgress } from './circular_progress';
import { CircularProgress } from "./circular_progress";
export const LoadingIndicator: React.FC = () => (
<div className='loading-indicator'>
@@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import noop from 'lodash/noop';
import noop from "lodash/noop";
import Bundle from 'flavours/glitch/features/ui/components/bundle';
import { MediaGallery, Video, Audio } from 'flavours/glitch/features/ui/util/async-components';
import Bundle from "flavours/glitch/features/ui/components/bundle";
import { MediaGallery, Video, Audio } from "flavours/glitch/features/ui/util/async-components";
export default class MediaAttachments extends ImmutablePureComponent {
@@ -24,7 +24,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
};
updateOnProps = [
'status',
"status",
];
renderLoadingMediaGallery = () => {
@@ -53,53 +53,53 @@ export default class MediaAttachments extends ImmutablePureComponent {
render () {
const { status, width, height, revealed } = this.props;
const mediaAttachments = status.get('media_attachments');
const language = status.getIn(['language', 'translation']) || status.get('language') || this.props.lang;
const mediaAttachments = status.get("media_attachments");
const language = status.getIn(["language", "translation"]) || status.get("language") || this.props.lang;
if (mediaAttachments.size === 0) {
return null;
}
if (mediaAttachments.getIn([0, 'type']) === 'audio') {
if (mediaAttachments.getIn([0, "type"]) === "audio") {
const audio = mediaAttachments.get(0);
const description = audio.getIn(['translation', 'description']) || audio.get('description');
const description = audio.getIn(["translation", "description"]) || audio.get("description");
return (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={audio.get('url')}
src={audio.get("url")}
alt={description}
lang={language}
width={width}
height={height}
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={audio.getIn(['meta', 'colors', 'background'])}
foregroundColor={audio.getIn(['meta', 'colors', 'foreground'])}
accentColor={audio.getIn(['meta', 'colors', 'accent'])}
duration={audio.getIn(['meta', 'original', 'duration'], 0)}
poster={audio.get("preview_url") || status.getIn(["account", "avatar_static"])}
backgroundColor={audio.getIn(["meta", "colors", "background"])}
foregroundColor={audio.getIn(["meta", "colors", "foreground"])}
accentColor={audio.getIn(["meta", "colors", "accent"])}
duration={audio.getIn(["meta", "original", "duration"], 0)}
/>
)}
</Bundle>
);
} else if (mediaAttachments.getIn([0, 'type']) === 'video') {
} else if (mediaAttachments.getIn([0, "type"]) === "video") {
const video = mediaAttachments.get(0);
const description = video.getIn(['translation', 'description']) || video.get('description');
const description = video.getIn(["translation", "description"]) || video.get("description");
return (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={video.get('preview_url')}
frameRate={video.getIn(['meta', 'original', 'frame_rate'])}
blurhash={video.get('blurhash')}
src={video.get('url')}
preview={video.get("preview_url")}
frameRate={video.getIn(["meta", "original", "frame_rate"])}
blurhash={video.get("blurhash")}
src={video.get("url")}
alt={description}
lang={language}
width={width}
height={height}
inline
sensitive={status.get('sensitive')}
sensitive={status.get("sensitive")}
revealed={revealed}
onOpenVideo={noop}
/>
@@ -113,7 +113,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
<Component
media={mediaAttachments}
lang={language}
sensitive={status.get('sensitive')}
sensitive={status.get("sensitive")}
defaultWidth={width}
revealed={revealed}
height={height}
@@ -1,40 +1,40 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import { is } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { is } from "immutable";
import ImmutablePropTypes from "react-immutable-proptypes";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { Blurhash } from 'flavours/glitch/components/blurhash';
import { autoPlayGif, displayMedia } from 'flavours/glitch/initial_state';
import { Blurhash } from "flavours/glitch/components/blurhash";
import { autoPlayGif, displayMedia } from "flavours/glitch/initial_state";
import { IconButton } from './icon_button';
import { IconButton } from "./icon_button";
const messages = defineMessages({
hidden: {
defaultMessage: 'Media hidden',
id: 'status.media_hidden',
defaultMessage: "Media hidden",
id: "status.media_hidden",
},
sensitive: {
defaultMessage: 'Sensitive',
id: 'media_gallery.sensitive',
defaultMessage: "Sensitive",
id: "media_gallery.sensitive",
},
toggle: {
defaultMessage: 'Click to view',
id: 'status.sensitive_toggle',
defaultMessage: "Click to view",
id: "status.sensitive_toggle",
},
toggle_visible: {
defaultMessage: '{number, plural, one {Hide image} other {Hide images}}',
id: 'media_gallery.toggle_visible',
defaultMessage: "{number, plural, one {Hide image} other {Hide images}}",
id: "media_gallery.toggle_visible",
},
warning: {
defaultMessage: 'Sensitive content',
id: 'status.sensitive_warning',
defaultMessage: "Sensitive content",
id: "status.sensitive_warning",
},
});
@@ -84,7 +84,7 @@ class Item extends PureComponent {
hoverToPlay () {
const { attachment } = this.props;
return !this.getAutoPlay() && attachment.get('type') === 'gifv';
return !this.getAutoPlay() && attachment.get("type") === "gifv";
}
handleClick = (e) => {
@@ -117,7 +117,7 @@ class Item extends PureComponent {
displayWidth,
visible,
useBlurhash,
contentStyles
contentStyles,
} = this.props;
let badges = [], thumbnail;
@@ -133,51 +133,51 @@ class Item extends PureComponent {
height = 50;
}
if (attachment.get('description')?.length > 0) {
if (attachment.get("description")?.length > 0) {
badges.push(<span key='alt' className='media-gallery__gifv__label'>ALT</span>);
}
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
if (attachment.get('type') === 'unknown') {
if (attachment.get("type") === "unknown") {
return (
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
<div className={classNames("media-gallery__item", { standalone, "media-gallery__item--tall": height === 100, "media-gallery__item--wide": width === 100 })} key={attachment.get("id")}>
<a className='media-gallery__item-thumbnail' href={attachment.get("remote_url") || attachment.get("url")} style={{ cursor: "pointer" }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
hash={attachment.get("blurhash")}
className='media-gallery__preview'
dummy={!useBlurhash}
/>
</a>
</div>
);
} else if (attachment.get('type') === 'image') {
const previewUrl = attachment.get('preview_url');
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
} else if (attachment.get("type") === "image") {
const previewUrl = attachment.get("preview_url");
const previewWidth = attachment.getIn(["meta", "small", "width"]);
const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const originalUrl = attachment.get("url");
const originalWidth = attachment.getIn(["meta", "original", "width"]);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
const hasSize = typeof originalWidth === "number" && typeof previewWidth === "number";
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
const sizes = hasSize && (displayWidth > 0) ? `${displayWidth * (width / 100)}px` : null;
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
const focusX = attachment.getIn(["meta", "focus", "x"]) || 0;
const focusY = attachment.getIn(["meta", "focus", "y"]) || 0;
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
thumbnail = (
<a
className='media-gallery__item-thumbnail'
href={attachment.get('remote_url') || originalUrl}
href={attachment.get("remote_url") || originalUrl}
onClick={this.handleClick}
target='_blank'
rel='noopener noreferrer'
>
<img
className={letterbox ? 'letterbox' : null}
className={letterbox ? "letterbox" : null}
src={previewUrl}
srcSet={srcSet}
sizes={sizes}
@@ -189,20 +189,20 @@ class Item extends PureComponent {
/>
</a>
);
} else if (attachment.get('type') === 'gifv') {
} else if (attachment.get("type") === "gifv") {
const autoPlay = this.getAutoPlay();
badges.push(<span key='gif' className='media-gallery__gifv__label'>GIF</span>);
thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
<div className={classNames("media-gallery__gifv", { autoplay: autoPlay })}>
<video
className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
className={`media-gallery__item-gifv-thumbnail${letterbox ? " letterbox" : ""}`}
aria-label={description}
title={description}
lang={lang}
role='application'
src={attachment.get('url')}
src={attachment.get("url")}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
@@ -216,12 +216,12 @@ class Item extends PureComponent {
}
return (
<div className={classNames('media-gallery__item', { standalone, letterbox, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')} style={contentStyles}>
<div className={classNames("media-gallery__item", { standalone, letterbox, "media-gallery__item--tall": height === 100, "media-gallery__item--wide": width === 100 })} key={attachment.get("id")} style={contentStyles}>
<Blurhash
hash={attachment.get('blurhash')}
hash={attachment.get("blurhash")}
dummy={!useBlurhash}
className={classNames('media-gallery__preview', {
'media-gallery__preview--hidden': visible && this.state.loaded,
className={classNames("media-gallery__preview", {
"media-gallery__preview--hidden": visible && this.state.loaded,
})}
/>
@@ -264,21 +264,21 @@ class MediaGallery extends PureComponent {
};
state = {
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== "hide_all" && !this.props.sensitive || displayMedia === "show_all"),
width: this.props.defaultWidth,
};
componentDidMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
window.addEventListener("resize", this.handleResize, { passive: true });
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener("resize", this.handleResize);
}
UNSAFE_componentWillReceiveProps (nextProps) {
if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
this.setState({ visible: displayMedia !== "hide_all" && !nextProps.sensitive || displayMedia === "show_all" });
} else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ visible: nextProps.visible });
}
@@ -336,14 +336,14 @@ class MediaGallery extends PureComponent {
isStandaloneEligible() {
const { media, standalone } = this.props;
return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
return standalone && media.size === 1 && media.getIn([0, "meta", "small", "aspect"]);
}
render () {
const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay, useBlurhash } = this.props;
const { visible } = this.state;
const size = media.take(4).size;
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
const uncached = media.every(attachment => attachment.get("type") === "unknown");
const width = this.state.width || defaultWidth;
@@ -351,10 +351,10 @@ class MediaGallery extends PureComponent {
const style = {};
const computedClass = classNames('media-gallery', { 'full-width': fullwidth });
const computedClass = classNames("media-gallery", { "full-width": fullwidth });
if (this.isStandaloneEligible()) { // TODO: cropImages setting
style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
style.aspectRatio = `${this.props.media.getIn([0, "meta", "small", "aspect"])}`;
}
if (this.isStandaloneEligible()) {
@@ -373,7 +373,7 @@ class MediaGallery extends PureComponent {
} else {
children = media.map((attachment, i) => (
<Item
key={attachment.get('id')}
key={attachment.get("id")}
autoplay={autoplay}
onClick={this.handleClick}
attachment={attachment}
@@ -384,7 +384,7 @@ class MediaGallery extends PureComponent {
displayWidth={width}
visible={visible || uncached}
useBlurhash={useBlurhash}
contentStyles={{ aspectRatio: '16 / 9' }}
contentStyles={{ aspectRatio: "16 / 9" }}
/>
));
}
@@ -413,7 +413,7 @@ class MediaGallery extends PureComponent {
return (
<div className={computedClass} style={style} ref={this.handleRef}>
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
<div className={classNames("spoiler-button", { "spoiler-button--minified": visible && !uncached, "spoiler-button--click-thru": uncached })}>
{spoilerButton}
{visible && sensitive && (
<span className='sensitive-marker'>
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import 'wicg-inert';
import { multiply } from 'color-blend';
import { createBrowserHistory } from 'history';
import "wicg-inert";
import { multiply } from "color-blend";
import { createBrowserHistory } from "history";
export default class ModalRoot extends PureComponent {
@@ -26,15 +26,15 @@ export default class ModalRoot extends PureComponent {
activeElement = this.props.children ? document.activeElement : null;
handleKeyUp = (e) => {
if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
if ((e.key === "Escape" || e.key === "Esc" || e.keyCode === 27)
&& !!this.props.children && !this.props.noEsc) {
this.props.onClose();
}
};
handleKeyDown = (e) => {
if (e.key === 'Tab') {
const focusable = Array.from(this.node.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((x) => window.getComputedStyle(x).display !== 'none');
if (e.key === "Tab") {
const focusable = Array.from(this.node.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])")).filter((x) => window.getComputedStyle(x).display !== "none");
const index = focusable.indexOf(e.target);
let element;
@@ -54,8 +54,8 @@ export default class ModalRoot extends PureComponent {
};
componentDidMount () {
window.addEventListener('keyup', this.handleKeyUp, false);
window.addEventListener('keydown', this.handleKeyDown, false);
window.addEventListener("keyup", this.handleKeyUp, false);
window.addEventListener("keydown", this.handleKeyDown, false);
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
if (this.props.children) {
@@ -67,13 +67,13 @@ export default class ModalRoot extends PureComponent {
if (!!nextProps.children && !this.props.children) {
this.activeElement = document.activeElement;
this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true));
this.getSiblings().forEach(sibling => sibling.setAttribute("inert", true));
}
}
componentDidUpdate (prevProps) {
if (!this.props.children && !!prevProps.children) {
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
this.getSiblings().forEach(sibling => sibling.removeAttribute("inert"));
// Because of the wicg-inert polyfill, the activeElement may not be
// immediately selectable, we have to wait for observers to run, as
@@ -96,14 +96,14 @@ export default class ModalRoot extends PureComponent {
}
componentWillUnmount () {
window.removeEventListener('keyup', this.handleKeyUp);
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener("keyup", this.handleKeyUp);
window.removeEventListener("keydown", this.handleKeyDown);
}
_handleModalOpen () {
this._modalHistoryKey = Date.now();
this.unlistenHistory = this.history.listen((_, action) => {
if (action === 'POP') {
if (action === "POP") {
this.props.onClose();
}
});
@@ -151,7 +151,7 @@ export default class ModalRoot extends PureComponent {
return (
<div className='modal-root' ref={this.setRef}>
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
<div style={{ pointerEvents: visible ? "auto" : "none" }}>
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.7)` : null }} />
<div role='dialog' className='modal-root__container'>{children}</div>
</div>
@@ -1,10 +1,10 @@
import { PureComponent } from 'react';
import { PureComponent } from "react";
import { Switch, Route, withRouter } from 'react-router-dom';
import { Switch, Route, withRouter } from "react-router-dom";
import AccountNavigation from 'flavours/glitch/features/account/navigation';
import Trends from 'flavours/glitch/features/getting_started/containers/trends_container';
import { showTrends } from 'flavours/glitch/initial_state';
import AccountNavigation from "flavours/glitch/features/account/navigation";
import Trends from "flavours/glitch/features/getting_started/containers/trends_container";
import { showTrends } from "flavours/glitch/initial_state";
const DefaultNavigation = () => (
showTrends ? (
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as React from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
export const NotSignedInIndicator: React.FC = () => (
<div className='scrollable scrollable--flex'>
@@ -6,21 +6,21 @@
// Package imports //
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePureComponent from "react-immutable-pure-component";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
const messages = defineMessages({
btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' },
btnNone : { id: 'notification_purge.btn_none', defaultMessage: 'Select\nnone' },
btnInvert : { id: 'notification_purge.btn_invert', defaultMessage: 'Invert\nselection' },
btnApply : { id: 'notification_purge.btn_apply', defaultMessage: 'Clear\nselected' },
btnAll : { id: "notification_purge.btn_all", defaultMessage: "Select\nall" },
btnNone : { id: "notification_purge.btn_none", defaultMessage: "Select\nnone" },
btnInvert : { id: "notification_purge.btn_invert", defaultMessage: "Invert\nselection" },
btnApply : { id: "notification_purge.btn_apply", defaultMessage: "Clear\nselected" },
});
class NotificationPurgeButtons extends ImmutablePureComponent {
@@ -40,11 +40,11 @@ class NotificationPurgeButtons extends ImmutablePureComponent {
//className='active'
return (
<div className='column-header__notif-cleaning-buttons'>
<button onClick={this.props.onMarkAll} className={classNames('column-header__button', { active: markNewForDelete })}>
<button onClick={this.props.onMarkAll} className={classNames("column-header__button", { active: markNewForDelete })}>
<b></b><br />{intl.formatMessage(messages.btnAll)}
</button>
<button onClick={this.props.onMarkNone} className={classNames('column-header__button', { active: !markNewForDelete })}>
<button onClick={this.props.onMarkNone} className={classNames("column-header__button", { active: !markNewForDelete })}>
<b></b><br />{intl.formatMessage(messages.btnNone)}
</button>
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
export default class Permalink extends PureComponent {
@@ -40,7 +40,7 @@ export default class Permalink extends PureComponent {
} = this.props;
return (
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? " " + className : ""}`}>
{children}
</a>
);
@@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { Icon } from 'flavours/glitch/components/icon';
import { removePictureInPicture } from "flavours/glitch/actions/picture_in_picture";
import { Icon } from "flavours/glitch/components/icon";
class PictureInPicturePlaceholder extends PureComponent {
@@ -1,39 +1,39 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import escapeTextContentForBrowser from 'escape-html';
import spring from 'react-motion/lib/spring';
import escapeTextContentForBrowser from "escape-html";
import spring from "react-motion/lib/spring";
import { Icon } from 'flavours/glitch/components/icon';
import emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import { Icon } from "flavours/glitch/components/icon";
import emojify from "flavours/glitch/features/emoji/emoji";
import Motion from "flavours/glitch/features/ui/util/optional_motion";
import { RelativeTimestamp } from './relative_timestamp';
import { RelativeTimestamp } from "./relative_timestamp";
const messages = defineMessages({
closed: {
id: 'poll.closed',
defaultMessage: 'Closed',
id: "poll.closed",
defaultMessage: "Closed",
},
voted: {
id: 'poll.voted',
defaultMessage: 'You voted for this answer',
id: "poll.voted",
defaultMessage: "You voted for this answer",
},
votes: {
id: 'poll.votes',
defaultMessage: '{votes, plural, one {# vote} other {# votes}}',
id: "poll.votes",
defaultMessage: "{votes, plural, one {# vote} other {# votes}}",
},
});
const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
const makeEmojiMap = record => record.get("emojis").reduce((obj, emoji) => {
obj[`:${emoji.get("shortcode")}:`] = emoji.toJS();
return obj;
}, {});
@@ -59,8 +59,8 @@ class Poll extends ImmutablePureComponent {
static getDerivedStateFromProps (props, state) {
const { poll } = props;
const expires_at = poll.get('expires_at');
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
const expires_at = poll.get("expires_at");
const expired = poll.get("expired") || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
return (expired === state.expired) ? null : { expired };
}
@@ -80,7 +80,7 @@ class Poll extends ImmutablePureComponent {
const { poll } = this.props;
clearTimeout(this._timer);
if (!this.state.expired) {
const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now();
const delay = (new Date(poll.get("expires_at"))).getTime() - Date.now();
this._timer = setTimeout(() => {
this.setState({ expired: true });
}, delay);
@@ -88,7 +88,7 @@ class Poll extends ImmutablePureComponent {
}
_toggleOption = value => {
if (this.props.poll.get('multiple')) {
if (this.props.poll.get("multiple")) {
const tmp = { ...this.state.selected };
if (tmp[value]) {
delete tmp[value];
@@ -108,8 +108,8 @@ class Poll extends ImmutablePureComponent {
};
handleOptionKeyPress = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
this._toggleOption(e.target.getAttribute('data-index'));
if (e.key === "Enter" || e.key === " ") {
this._toggleOption(e.target.getAttribute("data-index"));
e.stopPropagation();
e.preventDefault();
}
@@ -137,14 +137,14 @@ class Poll extends ImmutablePureComponent {
renderOption (option, optionIndex, showResults) {
const { poll, lang, disabled, intl } = this.props;
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
const pollVotesCount = poll.get("voters_count") || poll.get("votes_count");
const percent = pollVotesCount === 0 ? 0 : (option.get("votes_count") / pollVotesCount) * 100;
const leading = poll.get("options").filterNot(other => other.get("title") === option.get("title")).every(other => option.get("votes_count") >= other.get("votes_count"));
const active = !!this.state.selected[`${optionIndex}`];
const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));
const voted = option.get("voted") || (poll.get("own_votes") && poll.get("own_votes").includes(optionIndex));
const title = option.getIn(['translation', 'title']) || option.get('title');
let titleHtml = option.getIn(['translation', 'titleHtml']) || option.get('titleHtml');
const title = option.getIn(["translation", "title"]) || option.get("title");
let titleHtml = option.getIn(["translation", "titleHtml"]) || option.get("titleHtml");
if (!titleHtml) {
const emojiMap = makeEmojiMap(poll);
@@ -152,11 +152,11 @@ class Poll extends ImmutablePureComponent {
}
return (
<li key={option.get('title')}>
<label className={classNames('poll__option', { selectable: !showResults })}>
<li key={option.get("title")}>
<label className={classNames("poll__option", { selectable: !showResults })}>
<input
name='vote-options'
type={poll.get('multiple') ? 'checkbox' : 'radio'}
type={poll.get("multiple") ? "checkbox" : "radio"}
value={optionIndex}
checked={active}
onChange={this.handleOptionChange}
@@ -165,9 +165,9 @@ class Poll extends ImmutablePureComponent {
{!showResults && (
<span
className={classNames('poll__input', { checkbox: poll.get('multiple'), active })}
className={classNames("poll__input", { checkbox: poll.get("multiple"), active })}
tabIndex={0}
role={poll.get('multiple') ? 'checkbox' : 'radio'}
role={poll.get("multiple") ? "checkbox" : "radio"}
onKeyPress={this.handleOptionKeyPress}
aria-checked={active}
aria-label={title}
@@ -179,7 +179,7 @@ class Poll extends ImmutablePureComponent {
<span
className='poll__number'
title={intl.formatMessage(messages.votes, {
votes: option.get('votes_count'),
votes: option.get("votes_count"),
})}
>
{Math.round(percent)}%
@@ -200,7 +200,7 @@ class Poll extends ImmutablePureComponent {
{showResults && (
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
{({ width }) =>
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
<span className={classNames("poll__chart", { leading })} style={{ width: `${width}%` }} />
}
</Motion>
)}
@@ -216,22 +216,22 @@ class Poll extends ImmutablePureComponent {
return null;
}
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
const showResults = poll.get('voted') || revealed || expired;
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get("expires_at")} futureDate />;
const showResults = poll.get("voted") || revealed || expired;
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
let votesCount = null;
if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) {
votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />;
if (poll.get("voters_count") !== null && poll.get("voters_count") !== undefined) {
votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get("voters_count") }} />;
} else {
votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />;
votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get("votes_count") }} />;
}
return (
<div className='poll'>
<ul>
{poll.get('options').map((option, i) => this.renderOption(option, i, showResults))}
{poll.get("options").map((option, i) => this.renderOption(option, i, showResults))}
</ul>
<div className='poll__footer'>
@@ -239,7 +239,7 @@ class Poll extends ImmutablePureComponent {
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
{poll.get('expires_at') && <> · {timeRemaining}</>}
{poll.get("expires_at") && <> · {timeRemaining}</>}
</div>
</div>
);
@@ -1,13 +1,13 @@
import * as React from 'react';
import * as React from "react";
import classNames from 'classnames';
import classNames from "classnames";
interface Props {
value: string;
checked: boolean;
name: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
label: React.ReactNode;
value: string,
checked: boolean,
name: string,
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void,
label: React.ReactNode,
}
export const RadioButton: React.FC<Props> = ({
@@ -27,7 +27,7 @@ export const RadioButton: React.FC<Props> = ({
onChange={onChange}
/>
<span className={classNames('radio-button__input', { checked })} />
<span className={classNames("radio-button__input", { checked })} />
<span>{label}</span>
</label>
@@ -1,6 +1,6 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import illustration from 'flavours/glitch/images/elephant_ui_working.svg';
import illustration from "flavours/glitch/images/elephant_ui_working.svg";
const RegenerationIndicator = () => (
<div className='regeneration-indicator'>
@@ -1,69 +1,69 @@
import { Component } from 'react';
import { Component } from "react";
import type { IntlShape } from 'react-intl';
import { injectIntl, defineMessages } from 'react-intl';
import { type IntlShape } from "react-intl";
import { injectIntl, defineMessages } from "react-intl";
const messages = defineMessages({
today: { id: 'relative_time.today', defaultMessage: 'today' },
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
today: { id: "relative_time.today", defaultMessage: "today" },
just_now: { id: "relative_time.just_now", defaultMessage: "now" },
just_now_full: {
id: 'relative_time.full.just_now',
defaultMessage: 'just now',
id: "relative_time.full.just_now",
defaultMessage: "just now",
},
seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' },
seconds: { id: "relative_time.seconds", defaultMessage: "{number}s" },
seconds_full: {
id: 'relative_time.full.seconds',
defaultMessage: '{number, plural, one {# second} other {# seconds}} ago',
id: "relative_time.full.seconds",
defaultMessage: "{number, plural, one {# second} other {# seconds}} ago",
},
minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
minutes: { id: "relative_time.minutes", defaultMessage: "{number}m" },
minutes_full: {
id: 'relative_time.full.minutes',
defaultMessage: '{number, plural, one {# minute} other {# minutes}} ago',
id: "relative_time.full.minutes",
defaultMessage: "{number, plural, one {# minute} other {# minutes}} ago",
},
hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
hours: { id: "relative_time.hours", defaultMessage: "{number}h" },
hours_full: {
id: 'relative_time.full.hours',
defaultMessage: '{number, plural, one {# hour} other {# hours}} ago',
id: "relative_time.full.hours",
defaultMessage: "{number, plural, one {# hour} other {# hours}} ago",
},
days: { id: 'relative_time.days', defaultMessage: '{number}d' },
days: { id: "relative_time.days", defaultMessage: "{number}d" },
days_full: {
id: 'relative_time.full.days',
defaultMessage: '{number, plural, one {# day} other {# days}} ago',
id: "relative_time.full.days",
defaultMessage: "{number, plural, one {# day} other {# days}} ago",
},
moments_remaining: {
id: 'time_remaining.moments',
defaultMessage: 'Moments remaining',
id: "time_remaining.moments",
defaultMessage: "Moments remaining",
},
seconds_remaining: {
id: 'time_remaining.seconds',
defaultMessage: '{number, plural, one {# second} other {# seconds}} left',
id: "time_remaining.seconds",
defaultMessage: "{number, plural, one {# second} other {# seconds}} left",
},
minutes_remaining: {
id: 'time_remaining.minutes',
defaultMessage: '{number, plural, one {# minute} other {# minutes}} left',
id: "time_remaining.minutes",
defaultMessage: "{number, plural, one {# minute} other {# minutes}} left",
},
hours_remaining: {
id: 'time_remaining.hours',
defaultMessage: '{number, plural, one {# hour} other {# hours}} left',
id: "time_remaining.hours",
defaultMessage: "{number, plural, one {# hour} other {# hours}} left",
},
days_remaining: {
id: 'time_remaining.days',
defaultMessage: '{number, plural, one {# day} other {# days}} left',
id: "time_remaining.days",
defaultMessage: "{number, plural, one {# day} other {# days}} left",
},
});
const dateFormatOptions = {
hour12: false,
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
} as const;
const shortDateFormatOptions = {
month: 'short',
day: 'numeric',
month: "short",
day: "numeric",
} as const;
const SECOND = 1000;
@@ -77,25 +77,25 @@ const selectUnits = (delta: number) => {
const absDelta = Math.abs(delta);
if (absDelta < MINUTE) {
return 'second';
return "second";
} else if (absDelta < HOUR) {
return 'minute';
return "minute";
} else if (absDelta < DAY) {
return 'hour';
return "hour";
}
return 'day';
return "day";
};
const getUnitDelay = (units: string) => {
switch (units) {
case 'second':
case "second":
return SECOND;
case 'minute':
case "minute":
return MINUTE;
case 'hour':
case "hour":
return HOUR;
case 'day':
case "day":
return DAY;
default:
return MAX_DELAY;
@@ -147,7 +147,7 @@ export const timeAgoString = (
} else {
relativeTime = intl.formatDate(date, {
...shortDateFormatOptions,
year: 'numeric',
year: "numeric",
});
}
@@ -190,14 +190,14 @@ const timeRemainingString = (
};
interface Props {
intl: IntlShape;
timestamp: string;
year: number;
futureDate?: boolean;
short?: boolean;
intl: IntlShape,
timestamp: string,
year: number,
futureDate?: boolean,
short?: boolean,
}
interface States {
now: number;
now: number,
}
class RelativeTimestamp extends Component<Props, States> {
state = {
@@ -260,7 +260,7 @@ class RelativeTimestamp extends Component<Props, States> {
render() {
const { timestamp, intl, year, futureDate, short } = this.props;
const timeGiven = timestamp.includes('T');
const timeGiven = timestamp.includes("T");
const date = new Date(timestamp);
const relativeTime = futureDate
? timeRemainingString(intl, date, this.state.now, timeGiven)
@@ -1,12 +1,12 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { type PropsWithChildren } from "react";
import React from "react";
import { createBrowserHistory } from 'history';
import { Router as OriginalRouter } from 'react-router';
import { createBrowserHistory } from "history";
import { Router as OriginalRouter } from "react-router";
interface MastodonLocationState {
fromMastodon?: boolean;
mastodonModalKey?: string;
fromMastodon?: boolean,
mastodonModalKey?: string,
}
const browserHistory = createBrowserHistory<
@@ -1,23 +1,23 @@
import PropTypes from 'prop-types';
import { Children, cloneElement, PureComponent } from 'react';
import PropTypes from "prop-types";
import { Children, cloneElement, PureComponent } from "react";
import classNames from 'classnames';
import classNames from "classnames";
import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import { connect } from "react-redux";
import { supportsPassiveEvents } from 'detect-passive-events';
import { throttle } from 'lodash';
import { supportsPassiveEvents } from "detect-passive-events";
import { throttle } from "lodash";
import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container';
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import IntersectionObserverWrapper from 'flavours/glitch/features/ui/util/intersection_observer_wrapper';
import IntersectionObserverArticleContainer from "flavours/glitch/containers/intersection_observer_article_container";
import ScrollContainer from "flavours/glitch/containers/scroll_container";
import IntersectionObserverWrapper from "flavours/glitch/features/ui/util/intersection_observer_wrapper";
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from "../features/ui/util/fullscreen";
import { LoadMore } from './load_more';
import { LoadPending } from './load_pending';
import { LoadingIndicator } from './loading_indicator';
import { LoadMore } from "./load_more";
import { LoadPending } from "./load_pending";
import { LoadingIndicator } from "./loading_indicator";
const MOUSE_IDLE_DELAY = 300;
@@ -236,7 +236,7 @@ class ScrollableList extends PureComponent {
attachIntersectionObserver () {
let nodeOptions = {
root: this.node,
rootMargin: '300% 0px',
rootMargin: "300% 0px",
};
this.intersectionObserverWrapper
@@ -249,21 +249,21 @@ class ScrollableList extends PureComponent {
attachScrollListener () {
if (this.props.bindToDocument) {
document.addEventListener('scroll', this.handleScroll);
document.addEventListener('wheel', this.handleWheel, listenerOptions);
document.addEventListener("scroll", this.handleScroll);
document.addEventListener("wheel", this.handleWheel, listenerOptions);
} else {
this.node.addEventListener('scroll', this.handleScroll);
this.node.addEventListener('wheel', this.handleWheel, listenerOptions);
this.node.addEventListener("scroll", this.handleScroll);
this.node.addEventListener("wheel", this.handleWheel, listenerOptions);
}
}
detachScrollListener () {
if (this.props.bindToDocument) {
document.removeEventListener('scroll', this.handleScroll);
document.removeEventListener('wheel', this.handleWheel, listenerOptions);
document.removeEventListener("scroll", this.handleScroll);
document.removeEventListener("wheel", this.handleWheel, listenerOptions);
} else {
this.node.removeEventListener('scroll', this.handleScroll);
this.node.removeEventListener('wheel', this.handleWheel, listenerOptions);
this.node.removeEventListener("scroll", this.handleScroll);
this.node.removeEventListener("wheel", this.handleWheel, listenerOptions);
}
}
@@ -324,7 +324,7 @@ class ScrollableList extends PureComponent {
);
} else if (isLoading || childrenCount > 0 || hasMore || !emptyMessage) {
scrollableArea = (
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
<div className={classNames("scrollable", { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
<div role='feed' className='item-list'>
{prepend}
@@ -356,7 +356,7 @@ class ScrollableList extends PureComponent {
);
} else {
scrollableArea = (
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef}>
<div className={classNames("scrollable scrollable--flex", { fullscreen })} ref={this.setRef}>
{alwaysPrepend && prepend}
<div className='empty-column-indicator'>
@@ -1,25 +1,25 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { FormattedMessage, defineMessages, injectIntl } from "react-intl";
import { Link } from 'react-router-dom';
import { Link } from "react-router-dom";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { fetchServer } from 'flavours/glitch/actions/server';
import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import Account from 'flavours/glitch/containers/account_container';
import { domain } from 'flavours/glitch/initial_state';
import { fetchServer } from "flavours/glitch/actions/server";
import { ServerHeroImage } from "flavours/glitch/components/server_hero_image";
import { ShortNumber } from "flavours/glitch/components/short_number";
import { Skeleton } from "flavours/glitch/components/skeleton";
import Account from "flavours/glitch/containers/account_container";
import { domain } from "flavours/glitch/initial_state";
const messages = defineMessages({
aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
aboutActiveUsers: { id: "server_banner.about_active_users", defaultMessage: "People using this server during the last 30 days (Monthly Active Users)" },
});
const mapStateToProps = state => ({
server: state.getIn(['server', 'server']),
server: state.getIn(["server", "server"]),
});
class ServerBanner extends PureComponent {
@@ -37,15 +37,15 @@ class ServerBanner extends PureComponent {
render () {
const { server, intl } = this.props;
const isLoading = server.get('isLoading');
const isLoading = server.get("isLoading");
return (
<div className='server-banner'>
<div className='server-banner__introduction'>
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank' rel="noreferrer">Mastodon</a> }} />
</div>
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
<ServerHeroImage blurhash={server.getIn(["thumbnail", "blurhash"])} src={server.getIn(["thumbnail", "url"])} className='server-banner__hero' />
<div className='server-banner__description'>
{isLoading ? (
@@ -56,14 +56,14 @@ class ServerBanner extends PureComponent {
<br />
<Skeleton width='70%' />
</>
) : server.get('description')}
) : server.get("description")}
</div>
<div className='server-banner__meta'>
<div className='server-banner__meta__column'>
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} />
<Account id={server.getIn(["contact", "account", "id"])} size={36} />
</div>
<div className='server-banner__meta__column'>
@@ -77,7 +77,7 @@ class ServerBanner extends PureComponent {
</>
) : (
<>
<strong className='server-banner__number'><ShortNumber value={server.getIn(['usage', 'users', 'active_month'])} /></strong>
<strong className='server-banner__number'><ShortNumber value={server.getIn(["usage", "users", "active_month"])} /></strong>
<br />
<span className='server-banner__number-label' title={intl.formatMessage(messages.aboutActiveUsers)}><FormattedMessage id='server_banner.active_users' defaultMessage='active users' /></span>
</>
@@ -1,15 +1,15 @@
import { useCallback, useState } from 'react';
import * as React from 'react';
import { useCallback, useState } from "react";
import * as React from "react";
import classNames from 'classnames';
import classNames from "classnames";
import { Blurhash } from './blurhash';
import { Blurhash } from "./blurhash";
interface Props {
src: string;
srcSet?: string;
blurhash?: string;
className?: string;
src: string,
srcSet?: string,
blurhash?: string,
className?: string,
}
export const ServerHeroImage: React.FC<Props> = ({
@@ -26,7 +26,7 @@ export const ServerHeroImage: React.FC<Props> = ({
return (
<div
className={classNames('image', { loaded }, className)}
className={classNames("image", { loaded }, className)}
role='presentation'
>
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
export default class SettingText extends PureComponent {
@@ -21,7 +21,7 @@ export default class SettingText extends PureComponent {
return (
<label>
<span style={{ display: 'none' }}>{label}</span>
<span style={{ display: "none" }}>{label}</span>
<input
className='setting-text'
value={settings.getIn(settingPath)}
@@ -1,47 +1,11 @@
import { memo } from 'react';
import React, { memo } from "react";
import { FormattedMessage, FormattedNumber } from 'react-intl';
import { FormattedMessage, FormattedNumber } from "react-intl";
import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers';
type ShortNumberRenderer = (
displayNumber: JSX.Element,
pluralReady: number,
) => JSX.Element;
interface ShortNumberProps {
value: number;
renderer?: ShortNumberRenderer;
children?: ShortNumberRenderer;
}
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
value,
renderer,
children,
}) => {
const shortNumber = toShortNumber(value);
const [, division] = shortNumber;
if (children && renderer) {
console.warn(
'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.',
);
}
const customRenderer = children ?? renderer ?? null;
const displayNumber = <ShortNumberCounter value={shortNumber} />;
return (
customRenderer?.(displayNumber, pluralReady(value, division)) ??
displayNumber
);
};
export const ShortNumber = memo(ShortNumberRenderer);
import { toShortNumber, pluralReady, DECIMAL_UNITS } from "../utils/numbers";
interface ShortNumberCounterProps {
value: number[];
value: number[],
}
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
const [rawNumber, unit, maxFractionDigits = 0] = value;
@@ -88,3 +52,39 @@ const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
return count;
}
};
type ShortNumberRenderer = (
displayNumber: React.JSX.Element,
pluralReady: number,
) => React.JSX.Element;
interface ShortNumberProps {
value: number,
renderer?: ShortNumberRenderer,
children?: ShortNumberRenderer,
}
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
value,
renderer,
children,
}) => {
const shortNumber = toShortNumber(value);
const [, division] = shortNumber;
if (children && renderer) {
console.warn(
"Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.",
);
}
const customRenderer = children ?? renderer ?? null;
const displayNumber = <ShortNumberCounter value={shortNumber} />;
return (
customRenderer?.(displayNumber, pluralReady(value, division)) ??
displayNumber
);
};
export const ShortNumber = memo(ShortNumberRenderer);
@@ -1,8 +1,8 @@
import * as React from 'react';
import * as React from "react";
interface Props {
width?: number | string;
height?: number | string;
width?: number | string,
height?: number | string,
}
export const Skeleton: React.FC<Props> = ({ width, height }) => (
@@ -1,52 +1,52 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { injectIntl, FormattedMessage } from 'react-intl';
import { injectIntl, FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { HotKeys } from 'react-hotkeys';
import { HotKeys } from "react-hotkeys";
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
import PollContainer from 'flavours/glitch/containers/poll_container';
import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import { displayMedia } from 'flavours/glitch/initial_state';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import PictureInPicturePlaceholder from "flavours/glitch/components/picture_in_picture_placeholder";
import PollContainer from "flavours/glitch/containers/poll_container";
import NotificationOverlayContainer from "flavours/glitch/features/notifications/containers/overlay_container";
import { displayMedia } from "flavours/glitch/initial_state";
import { autoUnfoldCW } from "flavours/glitch/utils/content_warning";
import Card from '../features/status/components/card';
import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import Card from "../features/status/components/card";
import Bundle from "../features/ui/components/bundle";
import { MediaGallery, Video, Audio } from "../features/ui/util/async-components";
import AttachmentList from './attachment_list';
import StatusActionBar from './status_action_bar';
import StatusContent from './status_content';
import StatusHeader from './status_header';
import StatusIcons from './status_icons';
import StatusPrepend from './status_prepend';
import AttachmentList from "./attachment_list";
import StatusActionBar from "./status_action_bar";
import StatusContent from "./status_content";
import StatusHeader from "./status_header";
import StatusIcons from "./status_icons";
import StatusPrepend from "./status_prepend";
const domParser = new DOMParser();
export const textForScreenReader = (intl, status, rebloggedByText = false, expanded = false) => {
const displayName = status.getIn(['account', 'display_name']);
const displayName = status.getIn(["account", "display_name"]);
const spoilerText = status.getIn(['translation', 'spoiler_text']) || status.get('spoiler_text');
const contentHtml = status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
const contentText = domParser.parseFromString(contentHtml, 'text/html').documentElement.textContent;
const spoilerText = status.getIn(["translation", "spoiler_text"]) || status.get("spoiler_text");
const contentHtml = status.getIn(["translation", "contentHtml"]) || status.get("contentHtml");
const contentText = domParser.parseFromString(contentHtml, "text/html").documentElement.textContent;
const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
displayName.length === 0 ? status.getIn(["account", "acct"]).split("@")[0] : displayName,
spoilerText && !expanded ? spoilerText : contentText,
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
intl.formatDate(status.get("created_at"), { hour: "2-digit", minute: "2-digit", month: "short", day: "numeric" }),
status.getIn(["account", "acct"]),
];
if (rebloggedByText) {
values.push(rebloggedByText);
}
return values.join(', ');
return values.join(", ");
};
export const defaultMediaVisibility = (status, settings) => {
@@ -54,15 +54,15 @@ export const defaultMediaVisibility = (status, settings) => {
return undefined;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
if (status.get("reblog", null) !== null && typeof status.get("reblog") === "object") {
status = status.get("reblog");
}
if (settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text')) {
if (settings.getIn(["media", "reveal_behind_cw"]) && !!status.get("spoiler_text")) {
return true;
}
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
return (displayMedia !== "hide_all" && !status.get("sensitive") || displayMedia === "show_all");
};
class Status extends ImmutablePureComponent {
@@ -133,26 +133,26 @@ class Status extends ImmutablePureComponent {
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'account',
'settings',
'prepend',
'muted',
'notification',
'hidden',
'expanded',
'unread',
'pictureInPicture',
'previousId',
'nextInReplyToId',
'rootId',
"status",
"account",
"settings",
"prepend",
"muted",
"notification",
"hidden",
"expanded",
"unread",
"pictureInPicture",
"previousId",
"nextInReplyToId",
"rootId",
];
updateOnStates = [
'isExpanded',
'isCollapsed',
'showMedia',
'forceFilter',
"isExpanded",
"isCollapsed",
"showMedia",
"forceFilter",
];
// If our settings have changed to disable collapsed statuses, then we
@@ -172,13 +172,13 @@ class Status extends ImmutablePureComponent {
update.expandedProp = nextProps.expanded;
updated = true;
}
if (nextProps.status?.get('hidden') !== prevState.statusPropHidden) {
update.statusPropHidden = nextProps.status?.get('hidden');
if (nextProps.status?.get("hidden") !== prevState.statusPropHidden) {
update.statusPropHidden = nextProps.status?.get("hidden");
updated = true;
}
// Update state based on new props
if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
if (!nextProps.settings.getIn(["collapsed", "enabled"])) {
if (prevState.isCollapsed) {
update.isCollapsed = false;
updated = true;
@@ -186,8 +186,8 @@ class Status extends ImmutablePureComponent {
}
// Handle uncollapsing toots when the shared CW state is expanded
if (nextProps.settings.getIn(['content_warnings', 'shared_state']) &&
nextProps.status?.get('spoiler_text')?.length && nextProps.status?.get('hidden') === false &&
if (nextProps.settings.getIn(["content_warnings", "shared_state"]) &&
nextProps.status?.get("spoiler_text")?.length && nextProps.status?.get("hidden") === false &&
prevState.statusPropHidden !== false && prevState.isCollapsed
) {
update.isCollapsed = false;
@@ -200,7 +200,9 @@ class Status extends ImmutablePureComponent {
nextProps.expanded !== undefined
) {
update.isExpanded = nextProps.expanded;
if (nextProps.expanded) update.isCollapsed = false;
if (nextProps.expanded) {
update.isCollapsed = false;
}
updated = true;
}
@@ -209,14 +211,14 @@ class Status extends ImmutablePureComponent {
updated = true;
}
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
if (nextProps.status && nextProps.status.get("id") !== prevState.statusId) {
update.showMedia = defaultMediaVisibility(nextProps.status, nextProps.settings);
update.statusId = nextProps.status.get('id');
update.statusId = nextProps.status.get("id");
updated = true;
}
if (nextProps.settings.getIn(['media', 'reveal_behind_cw']) !== prevState.revealBehindCW) {
update.revealBehindCW = nextProps.settings.getIn(['media', 'reveal_behind_cw']);
if (nextProps.settings.getIn(["media", "reveal_behind_cw"]) !== prevState.revealBehindCW) {
update.revealBehindCW = nextProps.settings.getIn(["media", "reveal_behind_cw"]);
if (update.revealBehindCW) {
update.showMedia = defaultMediaVisibility(nextProps.status, nextProps.settings);
}
@@ -257,26 +259,30 @@ class Status extends ImmutablePureComponent {
// Prevent a crash when node is undefined. Not completely sure why this
// happens, might be because status === null.
if (node === undefined) return;
if (node === undefined) {
return;
}
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
const autoCollapseSettings = settings.getIn(["collapsed", "auto"]);
// Don't autocollapse if CW state is shared and status is explicitly revealed,
// as it could cause surprising changes when receiving notifications
if (settings.getIn(['content_warnings', 'shared_state']) && status.get('spoiler_text').length && !status.get('hidden')) return;
if (settings.getIn(["content_warnings", "shared_state"]) && status.get("spoiler_text").length && !status.get("hidden")) {
return;
}
let autoCollapseHeight = parseInt(autoCollapseSettings.get('height'));
if (status.get('media_attachments').size && !muted) {
let autoCollapseHeight = parseInt(autoCollapseSettings.get("height"));
if (status.get("media_attachments").size && !muted) {
autoCollapseHeight += 210;
}
if (collapse ||
autoCollapseSettings.get('all') ||
(autoCollapseSettings.get('notifications') && muted) ||
(autoCollapseSettings.get('lengthy') && node.clientHeight > autoCollapseHeight) ||
(autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by') ||
(autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null) ||
(autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size > 0)
autoCollapseSettings.get("all") ||
(autoCollapseSettings.get("notifications") && muted) ||
(autoCollapseSettings.get("lengthy") && node.clientHeight > autoCollapseHeight) ||
(autoCollapseSettings.get("reblogs") && prepend === "reblogged_by") ||
(autoCollapseSettings.get("replies") && status.get("in_reply_to_id", null) !== null) ||
(autoCollapseSettings.get("media") && !(status.get("spoiler_text").length) && status.get("media_attachments").size > 0)
) {
this.setCollapsed(true);
// Hack to fix timeline jumps on second rendering when auto-collapsing
@@ -285,21 +291,27 @@ class Status extends ImmutablePureComponent {
// Hack to fix timeline jumps when a preview card is fetched
this.setState({
showCard: !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'),
showCard: !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get("card") && this.props.settings.get("inline_preview_cards"),
});
}
// Hack to fix timeline jumps on second rendering when auto-collapsing
// or on subsequent rendering when a preview card has been fetched
getSnapshotBeforeUpdate() {
if (!this.props.getScrollPosition) return null;
if (!this.props.getScrollPosition) {
return null;
}
const { muted, hidden, status, settings } = this.props;
const doShowCard = !muted && !hidden && status && status.get('card') && settings.get('inline_preview_cards');
const doShowCard = !muted && !hidden && status && status.get("card") && settings.get("inline_preview_cards");
if (this.state.autoCollapsed || (doShowCard && !this.state.showCard)) {
if (doShowCard) this.setState({ showCard: true });
if (this.state.autoCollapsed) this.setState({ autoCollapsed: false });
if (doShowCard) {
this.setState({ showCard: true });
}
if (this.state.autoCollapsed) {
this.setState({ autoCollapsed: false });
}
return this.props.getScrollPosition();
} else {
return null;
@@ -329,7 +341,7 @@ class Status extends ImmutablePureComponent {
// `setCollapsed()` automatically checks for us whether toot collapsing
// is enabled, so we don't have to.
setCollapsed = (value) => {
if (this.props.settings.getIn(['collapsed', 'enabled'])) {
if (this.props.settings.getIn(["collapsed", "enabled"])) {
if (value) {
this.setExpansion(false);
}
@@ -340,7 +352,7 @@ class Status extends ImmutablePureComponent {
};
setExpansion = (value) => {
if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
if (this.props.settings.getIn(["content_warnings", "shared_state"]) && this.props.status.get("hidden") === value) {
this.props.onToggleHidden(this.props.status);
}
@@ -359,11 +371,14 @@ class Status extends ImmutablePureComponent {
const { router } = this.context;
const { status } = this.props;
const { isCollapsed } = this.state;
if (!router) return;
if (!router) {
return;
}
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
if (isCollapsed) this.setCollapsed(false);
else if (e.shiftKey) {
if (isCollapsed) {
this.setCollapsed(false);
} else if (e.shiftKey) {
this.setCollapsed(true);
document.getSelection().removeAllRanges();
} else if (this.props.onClick) {
@@ -372,9 +387,9 @@ class Status extends ImmutablePureComponent {
} else {
if (destination === undefined) {
destination = `/@${
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
status.getIn(["reblog", "account", "acct"], status.getIn(["account", "acct"]))
}/${
status.getIn(['reblog', 'id'], status.get('id'))
status.getIn(["reblog", "id"], status.get("id"))
}`;
}
router.history.push(destination);
@@ -388,37 +403,37 @@ class Status extends ImmutablePureComponent {
};
handleExpandedToggle = () => {
if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
if (this.props.settings.getIn(["content_warnings", "shared_state"])) {
this.props.onToggleHidden(this.props.status);
} else if (this.props.status.get('spoiler_text')) {
} else if (this.props.status.get("spoiler_text")) {
this.setExpansion(!this.state.isExpanded);
}
};
handleOpenVideo = (options) => {
const { status } = this.props;
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
const lang = status.getIn(["translation", "language"]) || status.get("language");
this.props.onOpenVideo(status.get("id"), status.getIn(["media_attachments", 0]), lang, options);
};
handleOpenMedia = (media, index) => {
const { status } = this.props;
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenMedia(status.get('id'), media, index, lang);
const lang = status.getIn(["translation", "language"]) || status.get("language");
this.props.onOpenMedia(status.get("id"), media, index, lang);
};
handleHotkeyOpenMedia = e => {
const { status, onOpenMedia, onOpenVideo } = this.props;
const statusId = status.get('id');
const statusId = status.get("id");
e.preventDefault();
if (status.get('media_attachments').size > 0) {
const lang = status.getIn(['translation', 'language']) || status.get('language');
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(statusId, status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
if (status.get("media_attachments").size > 0) {
const lang = status.getIn(["translation", "language"]) || status.get("language");
if (status.getIn(["media_attachments", 0, "type"]) === "video") {
onOpenVideo(statusId, status.getIn(["media_attachments", 0]), lang, { startTime: 0 });
} else {
onOpenMedia(statusId, status.get('media_attachments'), 0, lang);
onOpenMedia(statusId, status.get("media_attachments"), 0, lang);
}
}
};
@@ -448,29 +463,30 @@ class Status extends ImmutablePureComponent {
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this.props.status.get('account'), this.context.router.history);
this.props.onMention(this.props.status.get("account"), this.context.router.history);
};
handleHotkeyOpen = () => {
const status = this.props.status;
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
this.context.router.history.push(`/@${status.getIn(["account", "acct"])}/${status.get("id")}`);
};
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(["account", "acct"])}`);
};
handleHotkeyMoveUp = e => {
this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute("data-featured"));
};
handleHotkeyMoveDown = e => {
this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute("data-featured"));
};
handleHotkeyCollapse = () => {
if (!this.props.settings.getIn(['collapsed', 'enabled']))
if (!this.props.settings.getIn(["collapsed", "enabled"])) {
return;
}
this.setCollapsed(!this.state.isCollapsed);
};
@@ -497,15 +513,15 @@ class Status extends ImmutablePureComponent {
};
renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />;
return <div className='media-gallery' style={{ height: "110px" }} />;
}
renderLoadingVideoPlayer () {
return <div className='video-player' style={{ height: '110px' }} />;
return <div className='video-player' style={{ height: "110px" }} />;
}
renderLoadingAudioPlayer () {
return <div className='audio-player' style={{ height: '110px' }} />;
return <div className='audio-player' style={{ height: "110px" }} />;
}
render () {
@@ -549,7 +565,7 @@ class Status extends ImmutablePureComponent {
let media = contentMedia;
let mediaIcons = contentMediaIcons;
if (settings.getIn(['content_warnings', 'media_outside'])) {
if (settings.getIn(["content_warnings", "media_outside"])) {
media = extraMedia;
mediaIcons = extraMediaIcons;
}
@@ -558,7 +574,7 @@ class Status extends ImmutablePureComponent {
return null;
}
const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
const isExpanded = settings.getIn(["content_warnings", "shared_state"]) ? !status.get("hidden") : this.state.isExpanded;
const handlers = {
reply: this.handleHotkeyReply,
@@ -582,17 +598,17 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div ref={this.handleRef} className='status focusable' tabIndex={0}>
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
<span>{status.get('content')}</span>
<span>{status.getIn(["account", "display_name"]) || status.getIn(["account", "username"])}</span>
<span>{status.get("content")}</span>
</div>
</HotKeys>
);
}
const connectUp = previousId && previousId === status.get('in_reply_to_id');
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
const matchedFilters = status.get('matched_filters');
const connectUp = previousId && previousId === status.get("in_reply_to_id");
const connectToRoot = rootId && rootId === status.get("in_reply_to_id");
const connectReply = nextInReplyToId && nextInReplyToId === status.get("id");
const matchedFilters = status.get("matched_filters");
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
const minHandlers = this.props.muted ? {} : {
@@ -603,8 +619,8 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={minHandlers}>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
{' '}
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(", ")}.
{" "}
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
</button>
@@ -616,8 +632,8 @@ class Status extends ImmutablePureComponent {
// If user backgrounds for collapsed statuses are enabled, then we
// initialize our background accordingly. This will only be rendered if
// the status is collapsed.
if (settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds'])) {
background = status.getIn(['account', 'header']);
if (settings.getIn(["collapsed", "backgrounds", "user_backgrounds"])) {
background = status.getIn(["account", "header"]);
}
// This handles our media attachments.
@@ -628,78 +644,78 @@ class Status extends ImmutablePureComponent {
// `media`, we snatch the thumbnail to use as our `background` if media
// backgrounds for collapsed statuses are enabled.
attachments = status.get('media_attachments');
attachments = status.get("media_attachments");
if (pictureInPicture.get('inUse')) {
if (pictureInPicture.get("inUse")) {
media.push(<PictureInPicturePlaceholder />);
mediaIcons.push('video-camera');
mediaIcons.push("video-camera");
} else if (attachments.size > 0) {
const language = status.getIn(['translation', 'language']) || status.get('language');
const language = status.getIn(["translation", "language"]) || status.get("language");
if (muted || attachments.some(item => item.get('type') === 'unknown')) {
if (muted || attachments.some(item => item.get("type") === "unknown")) {
media.push(
<AttachmentList
compact
media={status.get('media_attachments')}
media={status.get("media_attachments")}
/>,
);
} else if (attachments.getIn([0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
} else if (attachments.getIn([0, "type"]) === "audio") {
const attachment = status.getIn(["media_attachments", 0]);
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
media.push(
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={attachment.get('url')}
src={attachment.get("url")}
alt={description}
lang={language}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get("preview_url") || status.getIn(["account", "avatar_static"])}
backgroundColor={attachment.getIn(["meta", "colors", "background"])}
foregroundColor={attachment.getIn(["meta", "colors", "foreground"])}
accentColor={attachment.getIn(["meta", "colors", "accent"])}
duration={attachment.getIn(["meta", "original", "duration"], 0)}
width={this.props.cachedMediaWidth}
height={110}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
sensitive={status.get('sensitive')}
blurhash={attachment.get('blurhash')}
deployPictureInPicture={pictureInPicture.get("available") ? this.handleDeployPictureInPicture : undefined}
sensitive={status.get("sensitive")}
blurhash={attachment.get("blurhash")}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
useBlurhash={settings.getIn(["media", "use_blurhash"])}
/>
)}
</Bundle>,
);
mediaIcons.push('music');
} else if (attachments.getIn([0, 'type']) === 'video') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
mediaIcons.push("music");
} else if (attachments.getIn([0, "type"]) === "video") {
const attachment = status.getIn(["media_attachments", 0]);
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
media.push(
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (<Component
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
preview={attachment.get("preview_url")}
frameRate={attachment.getIn(["meta", "original", "frame_rate"])}
blurhash={attachment.get("blurhash")}
src={attachment.get("url")}
alt={description}
lang={language}
inline
sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
sensitive={status.get("sensitive")}
letterbox={settings.getIn(["media", "letterbox"])}
fullwidth={!rootId && settings.getIn(["media", "fullwidth"])}
preventPlayback={isCollapsed || !isExpanded}
onOpenVideo={this.handleOpenVideo}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
deployPictureInPicture={pictureInPicture.get("available") ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
useBlurhash={settings.getIn(["media", "use_blurhash"])}
/>)}
</Bundle>,
);
mediaIcons.push('video-camera');
mediaIcons.push("video-camera");
} else { // Media type is 'image' or 'gifv'
media.push(
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
@@ -707,60 +723,60 @@ class Status extends ImmutablePureComponent {
<Component
media={attachments}
lang={language}
sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
sensitive={status.get("sensitive")}
letterbox={settings.getIn(["media", "letterbox"])}
fullwidth={!rootId && settings.getIn(["media", "fullwidth"])}
hidden={isCollapsed || !isExpanded}
onOpenMedia={this.handleOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
useBlurhash={settings.getIn(["media", "use_blurhash"])}
/>
)}
</Bundle>,
);
mediaIcons.push('picture-o');
mediaIcons.push("picture-o");
}
if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) {
background = attachments.getIn([0, 'preview_url']);
if (!status.get("sensitive") && !(status.get("spoiler_text").length > 0) && settings.getIn(["collapsed", "backgrounds", "preview_images"])) {
background = attachments.getIn([0, "preview_url"]);
}
} else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) {
} else if (status.get("card") && settings.get("inline_preview_cards") && !this.props.muted) {
media.push(
<Card
onOpenMedia={this.handleOpenMedia}
card={status.get('card')}
card={status.get("card")}
compact
sensitive={status.get('sensitive')}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
sensitive={status.get("sensitive")}
useBlurhash={settings.getIn(["media", "use_blurhash"])}
/>,
);
mediaIcons.push('link');
mediaIcons.push("link");
}
if (status.get('poll')) {
const language = status.getIn(['translation', 'language']) || status.get('language');
contentMedia.push(<PollContainer pollId={status.get('poll')} lang={language} />);
contentMediaIcons.push('tasks');
if (status.get("poll")) {
const language = status.getIn(["translation", "language"]) || status.get("language");
contentMedia.push(<PollContainer pollId={status.get("poll")} lang={language} />);
contentMediaIcons.push("tasks");
}
// Here we prepare extra data-* attributes for CSS selectors.
// Users can use those for theming, hiding avatars etc via UserStyle
const selectorAttribs = {
'data-status-by': `@${status.getIn(['account', 'acct'])}`,
"data-status-by": `@${status.getIn(["account", "acct"])}`,
};
if (this.props.prepend && account) {
const notifKind = {
favourite: 'favourited',
reblog: 'boosted',
reblogged_by: 'boosted',
status: 'posted',
favourite: "favourited",
reblog: "boosted",
reblogged_by: "boosted",
status: "posted",
}[this.props.prepend];
selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
selectorAttribs[`data-${notifKind}-by`] = `@${account.get("acct")}`;
prepend = (
<StatusPrepend
@@ -772,19 +788,19 @@ class Status extends ImmutablePureComponent {
);
}
if (this.props.prepend === 'reblog') {
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
if (this.props.prepend === "reblog") {
rebloggedByText = intl.formatMessage({ id: "status.reblogged_by", defaultMessage: "{name} boosted" }, { name: account.get("acct") });
}
const computedClass = classNames('status', `status-${status.get('visibility')}`, {
const computedClass = classNames("status", `status-${status.get("visibility")}`, {
collapsed: isCollapsed,
'has-background': isCollapsed && background,
'status__wrapper-reply': !!status.get('in_reply_to_id'),
'status--in-thread': !!rootId,
'status--first-in-thread': previousId && (!connectUp || connectToRoot),
"has-background": isCollapsed && background,
"status__wrapper-reply": !!status.get("in_reply_to_id"),
"status--in-thread": !!rootId,
"status--first-in-thread": previousId && (!connectUp || connectToRoot),
unread,
muted,
}, 'focusable');
}, "focusable");
return (
<HotKeys handlers={handlers}>
@@ -794,13 +810,13 @@ class Status extends ImmutablePureComponent {
{...selectorAttribs}
ref={handleRef}
tabIndex={0}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}
data-featured={featured ? "true" : null}
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get("hidden"))}
data-nosnippet={status.getIn(["account", "noindex"], true) || undefined}
>
{!muted && prepend}
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
{(connectReply || connectUp || connectToRoot) && <div className={classNames("status__line", { "status__line--full": connectReply, "status__line--first": !status.get("in_reply_to_id") && !connectToRoot })} />}
<header className='status__info'>
<span>
@@ -817,10 +833,10 @@ class Status extends ImmutablePureComponent {
<StatusIcons
status={status}
mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
collapsible={settings.getIn(['collapsed', 'enabled'])}
collapsible={settings.getIn(["collapsed", "enabled"])}
collapsed={isCollapsed}
setCollapsed={setCollapsed}
settings={settings.get('status_icons')}
settings={settings.get("status_icons")}
/>
</header>
<StatusContent
@@ -833,15 +849,15 @@ class Status extends ImmutablePureComponent {
onTranslate={this.handleTranslate}
parseClick={parseClick}
disabled={!router}
tagLinks={settings.get('tag_misleading_links')}
rewriteMentions={settings.get('rewrite_mentions')}
tagLinks={settings.get("tag_misleading_links")}
rewriteMentions={settings.get("rewrite_mentions")}
/>
{!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? (
{!isCollapsed || !(muted || !settings.getIn(["collapsed", "show_action_bar"])) ? (
<StatusActionBar
status={status}
account={status.get('account')}
showReplyCount={settings.get('show_reply_count')}
account={status.get("account")}
showReplyCount={settings.get("show_reply_count")}
onFilter={matchedFilters ? this.handleFilterClick : null}
{...other}
/>
@@ -1,53 +1,53 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { me } from 'flavours/glitch/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import DropdownMenuContainer from "flavours/glitch/containers/dropdown_menu_container";
import { me } from "flavours/glitch/initial_state";
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from "flavours/glitch/permissions";
import { accountAdminLink, statusAdminLink } from "flavours/glitch/utils/backend_links";
import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp';
import { IconButton } from "./icon_button";
import { RelativeTimestamp } from "./relative_timestamp";
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
edit: { id: 'status.edit', defaultMessage: 'Edit' },
direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
share: { id: 'status.share', defaultMessage: 'Share' },
more: { id: 'status.more', defaultMessage: 'More' },
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
hide: { id: 'status.hide', defaultMessage: 'Hide post' },
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
delete: { id: "status.delete", defaultMessage: "Delete" },
redraft: { id: "status.redraft", defaultMessage: "Delete & re-draft" },
edit: { id: "status.edit", defaultMessage: "Edit" },
direct: { id: "status.direct", defaultMessage: "Privately mention @{name}" },
mention: { id: "status.mention", defaultMessage: "Mention @{name}" },
mute: { id: "account.mute", defaultMessage: "Mute @{name}" },
block: { id: "account.block", defaultMessage: "Block @{name}" },
reply: { id: "status.reply", defaultMessage: "Reply" },
share: { id: "status.share", defaultMessage: "Share" },
more: { id: "status.more", defaultMessage: "More" },
replyAll: { id: "status.replyAll", defaultMessage: "Reply to thread" },
reblog: { id: "status.reblog", defaultMessage: "Boost" },
reblog_private: { id: "status.reblog_private", defaultMessage: "Boost with original visibility" },
cancel_reblog_private: { id: "status.cancel_reblog_private", defaultMessage: "Unboost" },
cannot_reblog: { id: "status.cannot_reblog", defaultMessage: "This post cannot be boosted" },
favourite: { id: "status.favourite", defaultMessage: "Favorite" },
bookmark: { id: "status.bookmark", defaultMessage: "Bookmark" },
open: { id: "status.open", defaultMessage: "Expand this status" },
report: { id: "status.report", defaultMessage: "Report @{name}" },
muteConversation: { id: "status.mute_conversation", defaultMessage: "Mute conversation" },
unmuteConversation: { id: "status.unmute_conversation", defaultMessage: "Unmute conversation" },
pin: { id: "status.pin", defaultMessage: "Pin on profile" },
unpin: { id: "status.unpin", defaultMessage: "Unpin from profile" },
embed: { id: "status.embed", defaultMessage: "Embed" },
admin_account: { id: "status.admin_account", defaultMessage: "Open moderation interface for @{name}" },
admin_status: { id: "status.admin_status", defaultMessage: "Open this post in the moderation interface" },
admin_domain: { id: "status.admin_domain", defaultMessage: "Open moderation interface for {domain}" },
copy: { id: "status.copy", defaultMessage: "Copy link to post" },
hide: { id: "status.hide", defaultMessage: "Hide post" },
edited: { id: "status.edited", defaultMessage: "Edited {date}" },
filter: { id: "status.filter", defaultMessage: "Filter this post" },
openOriginalPage: { id: "account.open_original_page", defaultMessage: "Open original page" },
});
class StatusActionBar extends ImmutablePureComponent {
@@ -85,10 +85,10 @@ class StatusActionBar extends ImmutablePureComponent {
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'showReplyCount',
'withCounters',
'withDismiss',
"status",
"showReplyCount",
"withCounters",
"withDismiss",
];
handleReplyClick = () => {
@@ -97,13 +97,13 @@ class StatusActionBar extends ImmutablePureComponent {
if (signedIn) {
this.props.onReply(this.props.status, this.context.router.history);
} else {
this.props.onInteractionModal('reply', this.props.status);
this.props.onInteractionModal("reply", this.props.status);
}
};
handleShareClick = () => {
navigator.share({
url: this.props.status.get('url'),
url: this.props.status.get("url"),
});
};
@@ -113,7 +113,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (signedIn) {
this.props.onFavourite(this.props.status, e);
} else {
this.props.onInteractionModal('favourite', this.props.status);
this.props.onInteractionModal("favourite", this.props.status);
}
};
@@ -123,7 +123,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (signedIn) {
this.props.onReblog(this.props.status, e);
} else {
this.props.onInteractionModal('reblog', this.props.status);
this.props.onInteractionModal("reblog", this.props.status);
}
};
@@ -148,15 +148,15 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
this.props.onMention(this.props.status.get("account"), this.context.router.history);
};
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
this.props.onDirect(this.props.status.get("account"), this.context.router.history);
};
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
this.props.onMute(this.props.status.get("account"));
};
handleBlockClick = () => {
@@ -166,9 +166,9 @@ class StatusActionBar extends ImmutablePureComponent {
handleOpen = () => {
let state = { ...this.context.router.history.location.state };
if (state.mastodonModalKey) {
this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
this.context.router.history.replace(`/@${this.props.status.getIn(["account", "acct"])}/${this.props.status.get("id")}`);
} else {
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
this.context.router.history.push(`/@${this.props.status.getIn(["account", "acct"])}/${this.props.status.get("id")}`);
}
};
@@ -185,7 +185,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleCopy = () => {
const url = this.props.status.get('url');
const url = this.props.status.get("url");
navigator.clipboard.writeText(url);
};
@@ -201,26 +201,26 @@ class StatusActionBar extends ImmutablePureComponent {
const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
const { permissions, signedIn } = this.context.identity;
const mutingConversation = status.get('muted');
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
const writtenByMe = status.getIn(['account', 'id']) === me;
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
const mutingConversation = status.get("muted");
const publicStatus = ["public", "unlisted"].includes(status.get("visibility"));
const pinnableStatus = ["public", "unlisted", "private"].includes(status.get("visibility"));
const writtenByMe = status.getIn(["account", "id"]) === me;
const isRemote = status.getIn(["account", "username"]) !== status.getIn(["account", "acct"]);
let menu = [];
let reblogIcon = 'retweet';
let reblogIcon = "retweet";
let replyIcon;
let replyTitle;
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
if (publicStatus && isRemote) {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get("url") });
}
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
if (publicStatus && 'share' in navigator) {
if (publicStatus && "share" in navigator) {
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
}
@@ -232,7 +232,7 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push(null);
if (writtenByMe && pinnableStatus) {
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
menu.push({ text: intl.formatMessage(status.get("pinned") ? messages.unpin : messages.pin), action: this.handlePinClick });
menu.push(null);
}
@@ -246,8 +246,8 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(["account", "username"]) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(["account", "username"]) }), action: this.handleDirectClick });
menu.push(null);
if (!this.props.onFilter) {
@@ -255,40 +255,40 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push(null);
}
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(["account", "username"]) }), action: this.handleMuteClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(["account", "username"]) }), action: this.handleBlockClick, dangerous: true });
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(["account", "username"]) }), action: this.handleReport, dangerous: true });
if (((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
menu.push(null);
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
if (accountAdminLink !== undefined) {
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: accountAdminLink(status.getIn(['account', 'id'])) });
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(["account", "username"]) }), href: accountAdminLink(status.getIn(["account", "id"])) });
}
if (statusAdminLink !== undefined) {
menu.push({ text: intl.formatMessage(messages.admin_status), href: statusAdminLink(status.getIn(['account', 'id']), status.get('id')) });
menu.push({ text: intl.formatMessage(messages.admin_status), href: statusAdminLink(status.getIn(["account", "id"]), status.get("id")) });
}
}
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
const domain = status.getIn(['account', 'acct']).split('@')[1];
const domain = status.getIn(["account", "acct"]).split("@")[1];
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
}
}
}
}
if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply';
if (status.get("in_reply_to_id", null) === null) {
replyIcon = "reply";
replyTitle = intl.formatMessage(messages.reply);
} else {
replyIcon = 'reply-all';
replyIcon = "reply-all";
replyTitle = intl.formatMessage(messages.replyAll);
}
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
const reblogPrivate = status.getIn(["account", "id"]) === me && status.get("visibility") === "private";
let reblogTitle = '';
if (status.get('reblogged')) {
let reblogTitle = "";
if (status.get("reblogged")) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
} else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog);
@@ -309,12 +309,12 @@ class StatusActionBar extends ImmutablePureComponent {
title={replyTitle}
icon={replyIcon}
onClick={this.handleReplyClick}
counter={showReplyCount ? status.get('replies_count') : undefined}
counter={showReplyCount ? status.get("replies_count") : undefined}
obfuscateCount
/>
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
<IconButton className={classNames("status__action-bar-button", { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get("reblogged")} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get("reblogs_count") : undefined} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get("favourited")} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get("favourites_count") : undefined} />
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get("bookmarked")} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
{filterButton}
@@ -331,8 +331,8 @@ class StatusActionBar extends ImmutablePureComponent {
</div>
<div className='status__action-bar-spacer' />
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
<a href={status.get("url")} className='status__relative-time' target='_blank' rel="noopener noreferrer">
<RelativeTimestamp timestamp={status.get("created_at")} />{status.get("edited_at") && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get("edited_at"), { hour12: false, year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" }) })}> *</abbr>}
</a>
</div>
);
@@ -1,23 +1,23 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage, injectIntl } from 'react-intl';
import { FormattedMessage, injectIntl } from "react-intl";
import classnames from 'classnames';
import classnames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
import { Icon } from "flavours/glitch/components/icon";
import { autoPlayGif } from "flavours/glitch/initial_state";
import { decode as decodeIDNA } from "flavours/glitch/utils/idna";
import Permalink from './permalink';
import Permalink from "./permalink";
const textMatchesTarget = (text, origin, host) => {
return (text === origin || text === host
|| text.startsWith(origin + '/') || text.startsWith(host + '/')
|| 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
|| text.startsWith(origin + "/") || text.startsWith(host + "/")
|| "www." + text === host || ("www." + text).startsWith(host + "/"));
};
const isLinkMisleading = (link) => {
@@ -29,30 +29,32 @@ const isLinkMisleading = (link) => {
const walk = (node) => {
switch (node.nodeType) {
case Node.TEXT_NODE:
linkTextParts.push(node.textContent);
break;
case Node.ELEMENT_NODE:
if (node.classList.contains('invisible')) return;
const children = node.childNodes;
for (let i = 0; i < children.length; i++) {
walk(children[i]);
}
break;
case Node.TEXT_NODE:
linkTextParts.push(node.textContent);
break;
case Node.ELEMENT_NODE:
if (node.classList.contains("invisible")) {
return;
}
const children = node.childNodes;
for (let i = 0; i < children.length; i++) {
walk(children[i]);
}
break;
}
};
walk(link);
const linkText = linkTextParts.join('');
const linkText = linkTextParts.join("");
const targetURL = new URL(link.href);
if (targetURL.protocol === 'magnet:') {
return !linkText.startsWith('magnet:');
if (targetURL.protocol === "magnet:") {
return !linkText.startsWith("magnet:");
}
if (targetURL.protocol === 'xmpp:') {
return !(linkText === targetURL.href || 'xmpp:' + linkText === targetURL.href);
if (targetURL.protocol === "xmpp:") {
return !(linkText === targetURL.href || "xmpp:" + linkText === targetURL.href);
}
// The following may not work with international domain names
@@ -61,10 +63,10 @@ const isLinkMisleading = (link) => {
}
// The link hasn't been recognized, maybe it features an international domain name
const hostname = decodeIDNA(targetURL.hostname).normalize('NFKC');
const hostname = decodeIDNA(targetURL.hostname).normalize("NFKC");
const host = targetURL.host.replace(targetURL.hostname, hostname);
const origin = targetURL.origin.replace(targetURL.host, host);
const text = linkText.normalize('NFKC');
const text = linkText.normalize("NFKC");
return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host));
};
@@ -93,7 +95,7 @@ class StatusContent extends PureComponent {
static defaultProps = {
tagLinks: true,
rewriteMentions: 'no',
rewriteMentions: "no",
};
state = {
@@ -108,60 +110,64 @@ class StatusContent extends PureComponent {
return;
}
const links = node.querySelectorAll('a');
const links = node.querySelectorAll("a");
for (var i = 0; i < links.length; ++i) {
let link = links[i];
if (link.classList.contains('status-link')) {
if (link.classList.contains("status-link")) {
continue;
}
link.classList.add('status-link');
link.classList.add("status-link");
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
let mention = this.props.status.get("mentions").find(item => link.href === item.get("url"));
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', `@${mention.get('acct')}`);
if (rewriteMentions !== 'no') {
while (link.firstChild) link.removeChild(link.firstChild);
link.appendChild(document.createTextNode('@'));
const acctSpan = document.createElement('span');
acctSpan.textContent = rewriteMentions === 'acct' ? mention.get('acct') : mention.get('username');
link.addEventListener("click", this.onMentionClick.bind(this, mention), false);
link.setAttribute("title", `@${mention.get("acct")}`);
if (rewriteMentions !== "no") {
while (link.firstChild) {
link.removeChild(link.firstChild);
}
link.appendChild(document.createTextNode("@"));
const acctSpan = document.createElement("span");
acctSpan.textContent = rewriteMentions === "acct" ? mention.get("acct") : mention.get("username");
link.appendChild(acctSpan);
}
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else if (link.textContent[0] === "#" || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === "#")) {
link.addEventListener("click", this.onHashtagClick.bind(this, link.text), false);
} else {
link.addEventListener('click', this.onLinkClick.bind(this), false);
link.setAttribute('title', link.href);
link.classList.add('unhandled-link');
link.addEventListener("click", this.onLinkClick.bind(this), false);
link.setAttribute("title", link.href);
link.classList.add("unhandled-link");
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener nofollow noreferrer');
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener nofollow noreferrer");
try {
if (tagLinks && isLinkMisleading(link)) {
// Add a tag besides the link to display its origin
const url = new URL(link.href);
const tag = document.createElement('span');
tag.classList.add('link-origin-tag');
const tag = document.createElement("span");
tag.classList.add("link-origin-tag");
switch (url.protocol) {
case 'xmpp:':
tag.textContent = `[${url.href}]`;
break;
case 'magnet:':
tag.textContent = '(magnet)';
break;
default:
tag.textContent = `[${url.host}]`;
case "xmpp:":
tag.textContent = `[${url.href}]`;
break;
case "magnet:":
tag.textContent = "(magnet)";
break;
default:
tag.textContent = `[${url.host}]`;
}
link.insertAdjacentText('beforeend', ' ');
link.insertAdjacentElement('beforeend', tag);
link.insertAdjacentText("beforeend", " ");
link.insertAdjacentElement("beforeend", tag);
}
} catch (e) {
// The URL is invalid, remove the href just to be safe
if (tagLinks && e instanceof TypeError) link.removeAttribute('href');
if (tagLinks && e instanceof TypeError) {
link.removeAttribute("href");
}
}
}
}
@@ -172,11 +178,11 @@ class StatusContent extends PureComponent {
return;
}
const emojis = currentTarget.querySelectorAll('.custom-emoji');
const emojis = currentTarget.querySelectorAll(".custom-emoji");
for (var i = 0; i < emojis.length; i++) {
let emoji = emojis[i];
emoji.src = emoji.getAttribute('data-original');
emoji.src = emoji.getAttribute("data-original");
}
};
@@ -185,11 +191,11 @@ class StatusContent extends PureComponent {
return;
}
const emojis = currentTarget.querySelectorAll('.custom-emoji');
const emojis = currentTarget.querySelectorAll(".custom-emoji");
for (var i = 0; i < emojis.length; i++) {
let emoji = emojis[i];
emoji.src = emoji.getAttribute('data-static');
emoji.src = emoji.getAttribute("data-static");
}
};
@@ -199,23 +205,27 @@ class StatusContent extends PureComponent {
componentDidUpdate () {
this._updateStatusLinks();
if (this.props.onUpdate) this.props.onUpdate();
if (this.props.onUpdate) {
this.props.onUpdate();
}
}
onLinkClick = (e) => {
if (this.props.collapsed) {
if (this.props.parseClick) this.props.parseClick(e);
if (this.props.parseClick) {
this.props.parseClick(e);
}
}
};
onMentionClick = (mention, e) => {
if (this.props.parseClick) {
this.props.parseClick(e, `/@${mention.get('acct')}`);
this.props.parseClick(e, `/@${mention.get("acct")}`);
}
};
onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '');
hashtag = hashtag.replace(/^#/, "");
if (this.props.parseClick) {
this.props.parseClick(e, `/tags/${hashtag}`);
@@ -238,7 +248,7 @@ class StatusContent extends PureComponent {
let element = e.target;
while (element !== e.currentTarget) {
if (['button', 'video', 'a', 'label', 'canvas'].includes(element.localName) || element.getAttribute('role') === 'button') {
if (["button", "video", "a", "label", "canvas"].includes(element.localName) || element.getAttribute("role") === "button") {
return;
}
element = element.parentNode;
@@ -283,28 +293,28 @@ class StatusContent extends PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const content = { __html: status.getIn(['translation', 'contentHtml']) || status.get('contentHtml') };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
const language = status.getIn(['translation', 'language']) || status.get('language');
const classNames = classnames('status__content', {
'status__content--with-action': parseClick && !disabled,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
const content = { __html: status.getIn(["translation", "contentHtml"]) || status.get("contentHtml") };
const spoilerContent = { __html: status.getIn(["translation", "spoilerHtml"]) || status.get("spoilerHtml") };
const language = status.getIn(["translation", "language"]) || status.get("language");
const classNames = classnames("status__content", {
"status__content--with-action": parseClick && !disabled,
"status__content--with-spoiler": status.get("spoiler_text").length > 0,
});
if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = '';
if (status.get("spoiler_text").length > 0) {
let mentionsPlaceholder = "";
const mentionLinks = status.get('mentions').map(item => (
const mentionLinks = status.get("mentions").map(item => (
<Permalink
to={`/@${item.get('acct')}`}
href={item.get('url')}
key={item.get('id')}
to={`/@${item.get("acct")}`}
href={item.get("url")}
key={item.get("id")}
className='mention'
>
@<span>{item.get('username')}</span>
@<span>{item.get("username")}</span>
</Permalink>
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
)).reduce((aggregate, item) => [...aggregate, item, " "], []);
let toggleText = null;
if (hidden) {
@@ -345,10 +355,10 @@ class StatusContent extends PureComponent {
return (
<div className={classNames} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p
style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}
style={{ marginBottom: hidden && status.get("mentions").isEmpty() ? "0px" : null }}
>
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={language} />
{' '}
{" "}
<button type='button' className='status__content__spoiler-link' onClick={this.handleSpoilerClick} aria-expanded={!hidden}>
{toggleText}
</button>
@@ -356,7 +366,7 @@ class StatusContent extends PureComponent {
{mentionsPlaceholder}
<div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
<div className={`status__content__spoiler ${!hidden ? "status__content__spoiler--visible" : ""}`}>
<div
ref={this.setContentsRef}
key={`contents-${tagLinks}`}
@@ -1,13 +1,13 @@
// Package imports.
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
// Mastodon imports.
import { Avatar } from './avatar';
import AvatarOverlay from './avatar_overlay';
import { DisplayName } from './display_name';
import { Avatar } from "./avatar";
import AvatarOverlay from "./avatar_overlay";
import { DisplayName } from "./display_name";
export default class StatusHeader extends PureComponent {
@@ -25,7 +25,7 @@ export default class StatusHeader extends PureComponent {
handleAccountClick = (e) => {
const { status } = this.props;
this.handleClick(status.getIn(['account', 'acct']), e);
this.handleClick(status.getIn(["account", "acct"]), e);
};
// Rendering.
@@ -35,7 +35,7 @@ export default class StatusHeader extends PureComponent {
friend,
} = this.props;
const account = status.get('account');
const account = status.get("account");
let statusAvatar;
if (friend === undefined || friend === null) {
@@ -47,7 +47,7 @@ export default class StatusHeader extends PureComponent {
return (
<div className='status__info__account'>
<a
href={account.get('url')}
href={account.get("url")}
target='_blank'
className='status__avatar'
onClick={this.handleAccountClick}
@@ -56,7 +56,7 @@ export default class StatusHeader extends PureComponent {
{statusAvatar}
</a>
<a
href={account.get('url')}
href={account.get("url")}
target='_blank'
className='status__display-name'
onClick={this.handleAccountClick}
@@ -1,37 +1,41 @@
// Package imports.
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
// Mastodon imports.
import { Icon } from 'flavours/glitch/components/icon';
import { languages } from 'flavours/glitch/initial_state';
import { Icon } from "flavours/glitch/components/icon";
import { languages } from "flavours/glitch/initial_state";
import { IconButton } from './icon_button';
import VisibilityIcon from './status_visibility_icon';
import { IconButton } from "./icon_button";
import VisibilityIcon from "./status_visibility_icon";
// Messages for use with internationalization stuff.
const messages = defineMessages({
collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
inReplyTo: { id: 'status.in_reply_to', defaultMessage: 'This toot is a reply' },
previewCard: { id: 'status.has_preview_card', defaultMessage: 'Features an attached preview card' },
pictures: { id: 'status.has_pictures', defaultMessage: 'Features attached pictures' },
poll: { id: 'status.is_poll', defaultMessage: 'This toot is a poll' },
video: { id: 'status.has_video', defaultMessage: 'Features attached videos' },
audio: { id: 'status.has_audio', defaultMessage: 'Features attached audio files' },
localOnly: { id: 'status.local_only', defaultMessage: 'Only visible from your instance' },
collapse: { id: "status.collapse", defaultMessage: "Collapse" },
uncollapse: { id: "status.uncollapse", defaultMessage: "Uncollapse" },
inReplyTo: { id: "status.in_reply_to", defaultMessage: "This toot is a reply" },
previewCard: { id: "status.has_preview_card", defaultMessage: "Features an attached preview card" },
pictures: { id: "status.has_pictures", defaultMessage: "Features attached pictures" },
poll: { id: "status.is_poll", defaultMessage: "This toot is a poll" },
video: { id: "status.has_video", defaultMessage: "Features attached videos" },
audio: { id: "status.has_audio", defaultMessage: "Features attached audio files" },
localOnly: { id: "status.local_only", defaultMessage: "Only visible from your instance" },
});
const LanguageIcon = ({ language }) => {
if (!languages) return null;
if (!languages) {
return null;
}
const lang = languages.find((lang) => lang[0] === language);
if (!lang) return null;
if (!lang) {
return null;
}
return (
<span className='text-icon' title={`${lang[2]} (${lang[1]})`} aria-hidden='true'>
@@ -69,11 +73,11 @@ class StatusIcons extends PureComponent {
const { intl } = this.props;
const message = {
'link': messages.previewCard,
'picture-o': messages.pictures,
'tasks': messages.poll,
'video-camera': messages.video,
'music': messages.audio,
"link": messages.previewCard,
"picture-o": messages.pictures,
"tasks": messages.poll,
"video-camera": messages.video,
"music": messages.audio,
}[mediaIcon];
return message && intl.formatMessage(message);
@@ -105,8 +109,8 @@ class StatusIcons extends PureComponent {
return (
<div className='status__info__icons'>
{settings.get('language') && status.get('language') && <LanguageIcon language={status.get('language')} />}
{settings.get('reply') && status.get('in_reply_to_id', null) !== null ? (
{settings.get("language") && status.get("language") && <LanguageIcon language={status.get("language")} />}
{settings.get("reply") && status.get("in_reply_to_id", null) !== null ? (
<Icon
className='status__reply-icon'
fixedWidth
@@ -115,15 +119,15 @@ class StatusIcons extends PureComponent {
title={intl.formatMessage(messages.inReplyTo)}
/>
) : null}
{settings.get('local_only') && status.get('local_only') &&
{settings.get("local_only") && status.get("local_only") &&
<Icon
fixedWidth
id='home'
aria-hidden='true'
title={intl.formatMessage(messages.localOnly)}
/>}
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
{settings.get("media") && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
{settings.get("visibility") && <VisibilityIcon visibility={status.get("visibility")} />}
{collapsible && (
<IconButton
className='status__collapse-button'
@@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import RegenerationIndicator from 'flavours/glitch/components/regeneration_indicator';
import StatusContainer from 'flavours/glitch/containers/status_container';
import RegenerationIndicator from "flavours/glitch/components/regeneration_indicator";
import StatusContainer from "flavours/glitch/containers/status_container";
import { LoadGap } from './load_gap';
import ScrollableList from './scrollable_list';
import { LoadGap } from "./load_gap";
import ScrollableList from "./scrollable_list";
export default class StatusList extends ImmutablePureComponent {
@@ -93,7 +93,7 @@ export default class StatusList extends ImmutablePureComponent {
let scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId, index) => statusId === null ? (
<LoadGap
key={'gap:' + statusIds.get(index + 1)}
key={"gap:" + statusIds.get(index + 1)}
disabled={isLoading}
maxId={index > 0 ? statusIds.get(index - 1) : null}
onClick={onLoadMore}
@@ -1,13 +1,13 @@
// Package imports //
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { Icon } from 'flavours/glitch/components/icon';
import { me } from 'flavours/glitch/initial_state';
import { Icon } from "flavours/glitch/components/icon";
import { me } from "flavours/glitch/initial_state";
export default class StatusPrepend extends PureComponent {
@@ -20,7 +20,7 @@ export default class StatusPrepend extends PureComponent {
handleClick = (e) => {
const { account, parseClick } = this.props;
parseClick(e, `/@${account.get('acct')}`);
parseClick(e, `/@${account.get("acct")}`);
};
Message = () => {
@@ -28,77 +28,77 @@ export default class StatusPrepend extends PureComponent {
let link = (
<a
onClick={this.handleClick}
href={account.get('url')}
href={account.get("url")}
className='status__display-name'
>
<b
dangerouslySetInnerHTML={{
__html : account.get('display_name_html') || account.get('username'),
__html : account.get("display_name_html") || account.get("username"),
}}
/>
</a>
);
switch (type) {
case 'featured':
return (
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
);
case 'reblogged_by':
return (
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} boosted'
values={{ name : link }}
/>
);
case 'favourite':
return (
<FormattedMessage
id='notification.favourite'
defaultMessage='{name} favorited your status'
values={{ name : link }}
/>
);
case 'reblog':
return (
<FormattedMessage
id='notification.reblog'
defaultMessage='{name} boosted your status'
values={{ name : link }}
/>
);
case 'status':
return (
<FormattedMessage
id='notification.status'
defaultMessage='{name} just posted'
values={{ name: link }}
/>
);
case 'poll':
if (me === account.get('id')) {
case "featured":
return (
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
);
case "reblogged_by":
return (
<FormattedMessage
id='notification.own_poll'
defaultMessage='Your poll has ended'
id='status.reblogged_by'
defaultMessage='{name} boosted'
values={{ name : link }}
/>
);
} else {
case "favourite":
return (
<FormattedMessage
id='notification.poll'
defaultMessage='A poll you have voted in has ended'
id='notification.favourite'
defaultMessage='{name} favorited your status'
values={{ name : link }}
/>
);
case "reblog":
return (
<FormattedMessage
id='notification.reblog'
defaultMessage='{name} boosted your status'
values={{ name : link }}
/>
);
case "status":
return (
<FormattedMessage
id='notification.status'
defaultMessage='{name} just posted'
values={{ name: link }}
/>
);
case "poll":
if (me === account.get("id")) {
return (
<FormattedMessage
id='notification.own_poll'
defaultMessage='Your poll has ended'
/>
);
} else {
return (
<FormattedMessage
id='notification.poll'
defaultMessage='A poll you have voted in has ended'
/>
);
}
case "update":
return (
<FormattedMessage
id='notification.update'
defaultMessage='{name} edited a post'
values={{ name: link }}
/>
);
}
case 'update':
return (
<FormattedMessage
id='notification.update'
defaultMessage='{name} edited a post'
values={{ name: link }}
/>
);
}
return null;
};
@@ -110,32 +110,32 @@ export default class StatusPrepend extends PureComponent {
let iconId;
switch(type) {
case 'favourite':
iconId = 'star';
break;
case 'featured':
iconId = 'thumb-tack';
break;
case 'poll':
iconId = 'tasks';
break;
case 'reblog':
case 'reblogged_by':
iconId = 'retweet';
break;
case 'status':
iconId = 'bell';
break;
case 'update':
iconId = 'pencil';
break;
case "favourite":
iconId = "star";
break;
case "featured":
iconId = "thumb-tack";
break;
case "poll":
iconId = "tasks";
break;
case "reblog":
case "reblogged_by":
iconId = "retweet";
break;
case "status":
iconId = "bell";
break;
case "update":
iconId = "pencil";
break;
}
return !type ? null : (
<aside className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend' : 'notification__message'}>
<div className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}>
<aside className={type === "reblogged_by" || type === "featured" ? "status__prepend" : "notification__message"}>
<div className={type === "reblogged_by" || type === "featured" ? "status__prepend-icon-wrapper" : "notification__favourite-icon-wrapper"}>
<Icon
className={`status__prepend-icon ${type === 'favourite' ? 'star-icon' : ''}`}
className={`status__prepend-icon ${type === "favourite" ? "star-icon" : ""}`}
id={iconId}
/>
</div>
@@ -1,17 +1,17 @@
// Package imports //
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePureComponent from "react-immutable-pure-component";
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from "flavours/glitch/components/icon";
const messages = defineMessages({
public: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
public: { id: "privacy.public.short", defaultMessage: "Public" },
unlisted: { id: "privacy.unlisted.short", defaultMessage: "Unlisted" },
private: { id: "privacy.private.short", defaultMessage: "Followers only" },
direct: { id: "privacy.direct.short", defaultMessage: "Mentioned people only" },
});
class VisibilityIcon extends ImmutablePureComponent {
@@ -26,10 +26,10 @@ class VisibilityIcon extends ImmutablePureComponent {
const { withLabel, visibility, intl } = this.props;
const visibilityIcon = {
public: 'globe',
unlisted: 'unlock',
private: 'lock',
direct: 'envelope',
public: "globe",
unlisted: "unlock",
private: "lock",
direct: "envelope",
}[visibility];
const label = intl.formatMessage(messages[visibility]);
@@ -43,7 +43,7 @@ class VisibilityIcon extends ImmutablePureComponent {
/>);
if (withLabel) {
return (<span style={{ whiteSpace: 'nowrap' }}>{icon} {label}</span>);
return (<span style={{ whiteSpace: "nowrap" }}>{icon} {label}</span>);
} else {
return icon;
}
@@ -1,10 +1,10 @@
import * as React from 'react';
import React from "react";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
interface Props {
resource: JSX.Element;
url: string;
resource: React.JSX.Element,
url: string,
}
export const TimelineHint: React.FC<Props> = ({ resource, url }) => (