[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,31 +1,31 @@
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 { Helmet } from 'react-helmet';
import classNames from "classnames";
import { Helmet } from "react-helmet";
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { fetchServer, fetchDomainBlocks } from 'mastodon/actions/server';
import Column from 'mastodon/components/column';
import { Icon } from 'mastodon/components/icon';
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
import { Skeleton } from 'mastodon/components/skeleton';
import Account from 'mastodon/containers/account_container';
import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { fetchServer, fetchDomainBlocks } from "mastodon/actions/server";
import Column from "mastodon/components/column";
import { Icon } from "mastodon/components/icon";
import { ServerHeroImage } from "mastodon/components/server_hero_image";
import { Skeleton } from "mastodon/components/skeleton";
import Account from "mastodon/containers/account_container";
import LinkFooter from "mastodon/features/ui/components/link_footer";
const messages = defineMessages({
title: { id: 'column.about', defaultMessage: 'About' },
rules: { id: 'about.rules', defaultMessage: 'Server rules' },
blocks: { id: 'about.blocks', defaultMessage: 'Moderated servers' },
silenced: { id: 'about.domain_blocks.silenced.title', defaultMessage: 'Limited' },
silencedExplanation: { id: 'about.domain_blocks.silenced.explanation', defaultMessage: 'You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.' },
suspended: { id: 'about.domain_blocks.suspended.title', defaultMessage: 'Suspended' },
suspendedExplanation: { id: 'about.domain_blocks.suspended.explanation', defaultMessage: 'No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.' },
title: { id: "column.about", defaultMessage: "About" },
rules: { id: "about.rules", defaultMessage: "Server rules" },
blocks: { id: "about.blocks", defaultMessage: "Moderated servers" },
silenced: { id: "about.domain_blocks.silenced.title", defaultMessage: "Limited" },
silencedExplanation: { id: "about.domain_blocks.silenced.explanation", defaultMessage: "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following." },
suspended: { id: "about.domain_blocks.suspended.title", defaultMessage: "Suspended" },
suspendedExplanation: { id: "about.domain_blocks.suspended.explanation", defaultMessage: "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible." },
});
const severityMessages = {
@@ -41,8 +41,8 @@ const severityMessages = {
};
const mapStateToProps = state => ({
server: state.getIn(['server', 'server']),
domainBlocks: state.getIn(['server', 'domainBlocks']),
server: state.getIn(["server", "server"]),
domainBlocks: state.getIn(["server", "domainBlocks"]),
});
class Section extends PureComponent {
@@ -70,9 +70,9 @@ class Section extends PureComponent {
const { collapsed } = this.state;
return (
<div className={classNames('about__section', { active: !collapsed })}>
<div className={classNames("about__section", { active: !collapsed })}>
<div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}>
<Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title}
<Icon id={collapsed ? "chevron-right" : "chevron-down"} fixedWidth /> {title}
</div>
{!collapsed && (
@@ -110,22 +110,22 @@ class About extends PureComponent {
render () {
const { multiColumn, intl, server, domainBlocks } = this.props;
const isLoading = server.get('isLoading');
const isLoading = server.get("isLoading");
return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
<div className='scrollable about'>
<div className='about__header'>
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {torment}' values={{ torment: <a href='https://doom.fandom.com/wiki/Argent_Energy' className='about__mail' target='_blank'>torment</a> }} /></p>
<ServerHeroImage blurhash={server.getIn(["thumbnail", "blurhash"])} src={server.getIn(["thumbnail", "url"])} srcSet={server.getIn(["thumbnail", "versions"])?.map((value, key) => `${value} ${key.replace("@", "")}`).join(", ")} className='about__header__hero' />
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get("domain")}</h1>
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {torment}' values={{ torment: <a href='https://doom.fandom.com/wiki/Argent_Energy' className='about__mail' target='_blank' rel="noreferrer">torment</a> }} /></p>
</div>
<div className='about__meta'>
<div className='about__meta__column'>
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} minimal />
<Account id={server.getIn(["contact", "account", "id"])} size={36} minimal />
</div>
<hr className='about__meta__divider' />
@@ -133,7 +133,7 @@ class About extends PureComponent {
<div className='about__meta__column'>
<h4><FormattedMessage id='about.contact' defaultMessage='Contact:' /></h4>
{isLoading ? <Skeleton width='10ch' /> : <a className='about__mail' href={`mailto:${server.getIn(['contact', 'email'])}`}>{server.getIn(['contact', 'email'])}</a>}
{isLoading ? <Skeleton width='10ch' /> : <a className='about__mail' href={`mailto:${server.getIn(["contact", "email"])}`}>{server.getIn(["contact", "email"])}</a>}
</div>
</div>
@@ -148,10 +148,10 @@ class About extends PureComponent {
<br />
<Skeleton width='70%' />
</>
) : (server.get('description')?.length > 0 ? (
) : (server.get("description")?.length > 0 ? (
<div
className='prose'
dangerouslySetInnerHTML={{ __html: server.get('description') }}
dangerouslySetInnerHTML={{ __html: server.get("description") }}
/>
) : (
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
@@ -159,13 +159,13 @@ class About extends PureComponent {
</Section>
<Section title={intl.formatMessage(messages.rules)}>
{!isLoading && (server.get('rules', ImmutableList()).isEmpty() ? (
{!isLoading && (server.get("rules", ImmutableList()).isEmpty() ? (
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
) : (
<ol className='rules-list'>
{server.get('rules').map(rule => (
<li key={rule.get('id')}>
<span className='rules-list__text'>{rule.get('text')}</span>
{server.get("rules").map(rule => (
<li key={rule.get("id")}>
<span className='rules-list__text'>{rule.get("text")}</span>
</li>
))}
</ol>
@@ -173,25 +173,25 @@ class About extends PureComponent {
</Section>
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
{domainBlocks.get('isLoading') ? (
{domainBlocks.get("isLoading") ? (
<>
<Skeleton width='100%' />
<br />
<Skeleton width='70%' />
</>
) : (domainBlocks.get('isAvailable') ? (
) : (domainBlocks.get("isAvailable") ? (
<>
<p><FormattedMessage id='about.domain_blocks.preamble' defaultMessage='Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.' /></p>
<div className='about__domain-blocks'>
{domainBlocks.get('items').map(block => (
<div className='about__domain-blocks__domain' key={block.get('domain')}>
{domainBlocks.get("items").map(block => (
<div className='about__domain-blocks__domain' key={block.get("domain")}>
<div className='about__domain-blocks__domain__header'>
<h6><span title={`SHA-256: ${block.get('digest')}`}>{block.get('domain')}</span></h6>
<span className='about__domain-blocks__domain__type' title={intl.formatMessage(severityMessages[block.get('severity')].explanation)}>{intl.formatMessage(severityMessages[block.get('severity')].title)}</span>
<h6><span title={`SHA-256: ${block.get("digest")}`}>{block.get("domain")}</span></h6>
<span className='about__domain-blocks__domain__type' title={intl.formatMessage(severityMessages[block.get("severity")].explanation)}>{intl.formatMessage(severityMessages[block.get("severity")].title)}</span>
</div>
<p>{(block.get('comment') || '').length > 0 ? block.get('comment') : <FormattedMessage id='about.domain_blocks.no_reason_available' defaultMessage='Reason not available' />}</p>
<p>{(block.get("comment") || "").length > 0 ? block.get("comment") : <FormattedMessage id='about.domain_blocks.no_reason_available' defaultMessage='Reason not available' />}</p>
</div>
))}
</div>
@@ -1,16 +1,16 @@
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 { is } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { is } from "immutable";
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";
const messages = defineMessages({
placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' },
placeholder: { id: "account_note.placeholder", defaultMessage: "Click to add a note" },
});
class InlineAlert extends PureComponent {
@@ -150,16 +150,16 @@ class AccountNote extends ImmutablePureComponent {
return (
<div className='account__header__account-note'>
<label htmlFor={`account-note-${account.get('id')}`}>
<label htmlFor={`account-note-${account.get("id")}`}>
<FormattedMessage id='account.account_note_header' defaultMessage='Note' /> <InlineAlert show={saved} />
</label>
<Textarea
id={`account-note-${account.get('id')}`}
id={`account-note-${account.get("id")}`}
className='account__header__account-note__content'
disabled={this.props.value === null || value === null}
placeholder={intl.formatMessage(messages.placeholder)}
value={value || ''}
value={value || ""}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onBlur={this.handleBlur}
@@ -1,15 +1,15 @@
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 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 Hashtag from 'mastodon/components/hashtag';
import Hashtag from "mastodon/components/hashtag";
const messages = defineMessages({
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' },
lastStatusAt: { id: "account.featured_tags.last_status_at", defaultMessage: "Last post on {date}" },
empty: { id: "account.featured_tags.last_status_never", defaultMessage: "No posts" },
});
class FeaturedTags extends ImmutablePureComponent {
@@ -28,22 +28,22 @@ class FeaturedTags extends ImmutablePureComponent {
render () {
const { account, featuredTags, intl } = this.props;
if (!account || account.get('suspended') || featuredTags.isEmpty()) {
if (!account || account.get("suspended") || featuredTags.isEmpty()) {
return null;
}
return (
<div className='getting-started__trends'>
<h4><FormattedMessage id='account.featured_tags.title' defaultMessage="{name}'s featured hashtags" values={{ name: <bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> }} /></h4>
<h4><FormattedMessage id='account.featured_tags.title' defaultMessage="{name}'s featured hashtags" values={{ name: <bdi dangerouslySetInnerHTML={{ __html: account.get("display_name_html") }} /> }} /></h4>
{featuredTags.take(3).map(featuredTag => (
<Hashtag
key={featuredTag.get('name')}
name={featuredTag.get('name')}
to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`}
uses={featuredTag.get('statuses_count') * 1}
key={featuredTag.get("name")}
name={featuredTag.get("name")}
to={`/@${account.get("acct")}/tagged/${featuredTag.get("name")}`}
uses={featuredTag.get("statuses_count") * 1}
withGraph={false}
description={((featuredTag.get('statuses_count') * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)}
description={((featuredTag.get("statuses_count") * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get("last_status_at"), { month: "short", day: "2-digit" }) }) : intl.formatMessage(messages.empty)}
/>
))}
</div>
@@ -1,9 +1,9 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } 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 { Icon } from 'mastodon/components/icon';
import { Icon } from "mastodon/components/icon";
export default class FollowRequestNote extends ImmutablePureComponent {
@@ -17,7 +17,7 @@ export default class FollowRequestNote extends ImmutablePureComponent {
return (
<div className='follow-request-banner'>
<div className='follow-request-banner__message'>
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi> }} />
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: account.get("display_name_html") }} /></bdi> }} />
</div>
<div className='follow-request-banner__action'>
@@ -1,84 +1,84 @@
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 { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import classNames from "classnames";
import { Helmet } from "react-helmet";
import { NavLink } from "react-router-dom";
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 { Avatar } from 'mastodon/components/avatar';
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
import Button from 'mastodon/components/button';
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { autoPlayGif, me, domain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { Avatar } from "mastodon/components/avatar";
import { Badge, AutomatedBadge, GroupBadge } from "mastodon/components/badge";
import Button from "mastodon/components/button";
import { FollowersCounter, FollowingCounter, StatusesCounter } from "mastodon/components/counters";
import { Icon } from "mastodon/components/icon";
import { IconButton } from "mastodon/components/icon_button";
import { ShortNumber } from "mastodon/components/short_number";
import DropdownMenuContainer from "mastodon/containers/dropdown_menu_container";
import { autoPlayGif, me, domain } from "mastodon/initial_state";
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from "mastodon/permissions";
import AccountNoteContainer from '../containers/account_note_container';
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
import AccountNoteContainer from "../containers/account_note_container";
import FollowRequestNoteContainer from "../containers/follow_request_note_container";
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
media: { id: 'account.media', defaultMessage: 'Media' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
unfollow: { id: "account.unfollow", defaultMessage: "Unfollow" },
follow: { id: "account.follow", defaultMessage: "Follow" },
cancel_follow_request: { id: "account.cancel_follow_request", defaultMessage: "Withdraw follow request" },
requested: { id: "account.requested", defaultMessage: "Awaiting approval. Click to cancel follow request" },
unblock: { id: "account.unblock", defaultMessage: "Unblock @{name}" },
edit_profile: { id: "account.edit_profile", defaultMessage: "Edit profile" },
linkVerifiedOn: { id: "account.link_verified_on", defaultMessage: "Ownership of this link was checked on {date}" },
account_locked: { id: "account.locked_info", defaultMessage: "This account privacy status is set to locked. The owner manually reviews who can follow them." },
mention: { id: "account.mention", defaultMessage: "Mention @{name}" },
direct: { id: "account.direct", defaultMessage: "Privately mention @{name}" },
unmute: { id: "account.unmute", defaultMessage: "Unmute @{name}" },
block: { id: "account.block", defaultMessage: "Block @{name}" },
mute: { id: "account.mute", defaultMessage: "Mute @{name}" },
report: { id: "account.report", defaultMessage: "Report @{name}" },
share: { id: "account.share", defaultMessage: "Share @{name}'s profile" },
media: { id: "account.media", defaultMessage: "Media" },
blockDomain: { id: "account.block_domain", defaultMessage: "Block domain {domain}" },
unblockDomain: { id: "account.unblock_domain", defaultMessage: "Unblock domain {domain}" },
hideReblogs: { id: "account.hide_reblogs", defaultMessage: "Hide boosts from @{name}" },
showReblogs: { id: "account.show_reblogs", defaultMessage: "Show boosts from @{name}" },
enableNotifications: { id: "account.enable_notifications", defaultMessage: "Notify me when @{name} posts" },
disableNotifications: { id: "account.disable_notifications", defaultMessage: "Stop notifying me when @{name} posts" },
pins: { id: "navigation_bar.pins", defaultMessage: "Pinned posts" },
preferences: { id: "navigation_bar.preferences", defaultMessage: "Preferences" },
follow_requests: { id: "navigation_bar.follow_requests", defaultMessage: "Follow requests" },
favourites: { id: "navigation_bar.favourites", defaultMessage: "Favorites" },
lists: { id: "navigation_bar.lists", defaultMessage: "Lists" },
followed_tags: { id: "navigation_bar.followed_tags", defaultMessage: "Followed hashtags" },
blocks: { id: "navigation_bar.blocks", defaultMessage: "Blocked users" },
domain_blocks: { id: "navigation_bar.domain_blocks", defaultMessage: "Blocked domains" },
mutes: { id: "navigation_bar.mutes", defaultMessage: "Muted users" },
endorse: { id: "account.endorse", defaultMessage: "Feature on profile" },
unendorse: { id: "account.unendorse", defaultMessage: "Don't feature on profile" },
add_or_remove_from_list: { id: "account.add_or_remove_from_list", defaultMessage: "Add or Remove from lists" },
admin_account: { id: "status.admin_account", defaultMessage: "Open moderation interface for @{name}" },
admin_domain: { id: "status.admin_domain", defaultMessage: "Open moderation interface for {domain}" },
languages: { id: "account.languages", defaultMessage: "Change subscribed languages" },
openOriginalPage: { id: "account.open_original_page", defaultMessage: "Open original page" },
});
const titleFromAccount = account => {
const displayName = account.get('display_name');
const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct');
const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
const displayName = account.get("display_name");
const acct = account.get("acct") === account.get("username") ? `${account.get("username")}@${domain}` : account.get("acct");
const prefix = displayName.trim().length === 0 ? account.get("username") : displayName;
return `${prefix} (@${acct})`;
};
const dateFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
month: "short",
day: "numeric",
year: "numeric",
hour12: false,
hour: '2-digit',
minute: '2-digit',
hour: "2-digit",
minute: "2-digit",
};
class Header extends ImmutablePureComponent {
@@ -118,7 +118,7 @@ class Header extends ImmutablePureComponent {
};
openEditProfile = () => {
window.open('/settings/profile', '_blank');
window.open("/settings/profile", "_blank");
};
isStatusesPageActive = (match, location) => {
@@ -134,11 +134,11 @@ class Header extends ImmutablePureComponent {
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");
}
};
@@ -147,11 +147,11 @@ class Header extends ImmutablePureComponent {
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");
}
};
@@ -166,15 +166,17 @@ class Header extends ImmutablePureComponent {
const { account } = this.props;
navigator.share({
url: account.get('url'),
url: account.get("url"),
}).catch((e) => {
if (e.name !== 'AbortError') console.error(e);
if (e.name !== "AbortError") {
console.error(e);
}
});
};
handleHashtagClick = e => {
const { router } = this.context;
const value = e.currentTarget.textContent.replace(/^#/, '');
const value = e.currentTarget.textContent.replace(/^#/, "");
if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
@@ -204,17 +206,17 @@ class Header extends ImmutablePureComponent {
return;
}
const links = node.querySelectorAll('a');
const links = node.querySelectorAll("a");
let link;
for (var i = 0; i < links.length; ++i) {
link = links[i];
if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.handleHashtagClick, false);
} else if (link.classList.contains('mention')) {
link.addEventListener('click', this.handleMentionClick, false);
if (link.textContent[0] === "#" || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === "#")) {
link.addEventListener("click", this.handleHashtagClick, false);
} else if (link.classList.contains("mention")) {
link.addEventListener("click", this.handleMentionClick, false);
}
}
}
@@ -235,169 +237,169 @@ class Header extends ImmutablePureComponent {
return null;
}
const suspended = account.get('suspended');
const isRemote = account.get('acct') !== account.get('username');
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
const suspended = account.get("suspended");
const isRemote = account.get("acct") !== account.get("username");
const remoteDomain = isRemote ? account.get("acct").split("@")[1] : null;
let info = [];
let actionBtn = '';
let bellBtn = '';
let lockedIcon = '';
let actionBtn = "";
let bellBtn = "";
let lockedIcon = "";
let menu = [];
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
if (me !== account.get("id") && account.getIn(["relationship", "followed_by"])) {
info.push(<span key='followed_by' className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
} else if (me !== account.get("id") && account.getIn(["relationship", "blocking"])) {
info.push(<span key='blocked' className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
}
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
if (me !== account.get("id") && account.getIn(["relationship", "muting"])) {
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
} else if (me !== account.get("id") && account.getIn(["relationship", "domain_blocking"])) {
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
}
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
if (account.getIn(["relationship", "requested"]) || account.getIn(["relationship", "following"])) {
bellBtn = <IconButton icon={account.getIn(["relationship", "notifying"]) ? "bell" : "bell-o"} size={24} active={account.getIn(["relationship", "notifying"])} title={intl.formatMessage(account.getIn(["relationship", "notifying"]) ? messages.disableNotifications : messages.enableNotifications, { name: account.get("username") })} onClick={this.props.onNotifyToggle} />;
}
if (me !== account.get('id')) {
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
actionBtn = '';
} else if (account.getIn(['relationship', 'requested'])) {
if (me !== account.get("id")) {
if (signedIn && !account.get("relationship")) { // Wait until the relationship is loaded
actionBtn = "";
} else if (account.getIn(["relationship", "requested"])) {
actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
} else if (!account.getIn(["relationship", "blocking"])) {
actionBtn = <Button disabled={account.getIn(["relationship", "blocked_by"])} className={classNames({ "button--destructive": account.getIn(["relationship", "following"]) })} text={intl.formatMessage(account.getIn(["relationship", "following"]) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
} else if (account.getIn(["relationship", "blocking"])) {
actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get("username") })} onClick={this.props.onBlock} />;
}
} else {
actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
}
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
actionBtn = '';
if (account.get("moved") && !account.getIn(["relationship", "following"])) {
actionBtn = "";
}
if (account.get('locked')) {
if (account.get("locked")) {
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
}
if (signedIn && account.get('id') !== me) {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
if (signedIn && account.get("id") !== me) {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get("username") }), action: this.props.onMention });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get("username") }), action: this.props.onDirect });
menu.push(null);
}
if (isRemote) {
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get("url") });
}
if ('share' in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
if ("share" in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get("username") }), action: this.handleShare });
menu.push(null);
}
if (account.get('id') === me) {
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
if (account.get("id") === me) {
menu.push({ text: intl.formatMessage(messages.edit_profile), href: "/settings/profile" });
menu.push({ text: intl.formatMessage(messages.preferences), href: "/settings/preferences" });
menu.push({ text: intl.formatMessage(messages.pins), to: "/pinned" });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push({ text: intl.formatMessage(messages.follow_requests), to: "/follow_requests" });
menu.push({ text: intl.formatMessage(messages.favourites), to: "/favourites" });
menu.push({ text: intl.formatMessage(messages.lists), to: "/lists" });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: "/followed_tags" });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
menu.push({ text: intl.formatMessage(messages.mutes), to: "/mutes" });
menu.push({ text: intl.formatMessage(messages.blocks), to: "/blocks" });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: "/domain_blocks" });
} else if (signedIn) {
if (account.getIn(['relationship', 'following'])) {
if (!account.getIn(['relationship', 'muting'])) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
if (account.getIn(["relationship", "following"])) {
if (!account.getIn(["relationship", "muting"])) {
if (account.getIn(["relationship", "showing_reblogs"])) {
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get("username") }), action: this.props.onReblogToggle });
} else {
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get("username") }), action: this.props.onReblogToggle });
}
menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
menu.push(null);
}
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
menu.push({ text: intl.formatMessage(account.getIn(["relationship", "endorsed"]) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
menu.push(null);
}
if (account.getIn(['relationship', 'muting'])) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
if (account.getIn(["relationship", "muting"])) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get("username") }), action: this.props.onMute });
} else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute, dangerous: true });
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get("username") }), action: this.props.onMute, dangerous: true });
}
if (account.getIn(['relationship', 'blocking'])) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
if (account.getIn(["relationship", "blocking"])) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get("username") }), action: this.props.onBlock });
} else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
menu.push({ text: intl.formatMessage(messages.block, { name: account.get("username") }), action: this.props.onBlock, dangerous: true });
}
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
menu.push({ text: intl.formatMessage(messages.report, { name: account.get("username") }), action: this.props.onReport, dangerous: true });
}
if (signedIn && isRemote) {
menu.push(null);
if (account.getIn(['relationship', 'domain_blocking'])) {
if (account.getIn(["relationship", "domain_blocking"])) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
} else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain, dangerous: true });
}
}
if ((account.get('id') !== me && (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
if ((account.get("id") !== me && (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
menu.push(null);
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get("username") }), href: `/admin/accounts/${account.get("id")}` });
}
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` });
}
}
const content = { __html: account.get('note_emojified') };
const displayNameHtml = { __html: account.get('display_name_html') };
const fields = account.get('fields');
const isLocal = account.get('acct').indexOf('@') === -1;
const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
const isIndexable = !account.get('noindex');
const content = { __html: account.get("note_emojified") };
const displayNameHtml = { __html: account.get("display_name_html") };
const fields = account.get("fields");
const isLocal = account.get("acct").indexOf("@") === -1;
const acct = isLocal && domain ? `${account.get("acct")}@${domain}` : account.get("acct");
const isIndexable = !account.get("noindex");
const badges = [];
if (account.get('bot')) {
if (account.get("bot")) {
badges.push(<AutomatedBadge key='bot-badge' />);
} else if (account.get('group')) {
} else if (account.get("group")) {
badges.push(<GroupBadge key='group-badge' />);
}
account.get('roles', []).forEach((role) => {
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} />);
account.get("roles", []).forEach((role) => {
badges.push(<Badge key={`role-badge-${role.get("id")}`} label={<span>{role.get("name")}</span>} domain={domain} />);
});
return (
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
<div className={classNames("account__header", { inactive: !!account.get("moved") })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{!(suspended || hidden || account.get("moved")) && account.getIn(["relationship", "requested_by"]) && <FollowRequestNoteContainer account={account} />}
<div className='account__header__image'>
<div className='account__header__info'>
{!suspended && info}
</div>
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
{!(suspended || hidden) && <img src={autoPlayGif ? account.get("header") : account.get("header_static")} alt='' className='parallax' />}
</div>
<div className='account__header__bar'>
<div className='account__header__tabs'>
<a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
<a className='avatar' href={account.get("avatar")} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
<Avatar account={suspended || hidden ? undefined : account} size={90} />
</a>
@@ -433,22 +435,22 @@ class Header extends ImmutablePureComponent {
{!(suspended || hidden) && (
<div className='account__header__extra'>
<div className='account__header__bio' ref={this.setRef}>
{(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
{(account.get("id") !== me && signedIn) && <AccountNoteContainer account={account} />}
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
{account.get("note").length > 0 && account.get("note") !== "<p></p>" && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
<div className='account__header__fields'>
<dl>
<dt><FormattedMessage id='account.joined_short' defaultMessage='Joined' /></dt>
<dd>{intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' })}</dd>
<dd>{intl.formatDate(account.get("created_at"), { year: "numeric", month: "short", day: "2-digit" })}</dd>
</dl>
{fields.map((pair, i) => (
<dl key={i} className={classNames({ verified: pair.get('verified_at') })}>
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
<dl key={i} className={classNames({ verified: pair.get("verified_at") })}>
<dt dangerouslySetInnerHTML={{ __html: pair.get("name_emojified") }} title={pair.get("name")} className='translate' />
<dd className='translate' title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
<dd className='translate' title={pair.get("value_plain")}>
{pair.get("verified_at") && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get("verified_at"), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get("value_emojified") }} />
</dd>
</dl>
))}
@@ -456,23 +458,23 @@ class Header extends ImmutablePureComponent {
</div>
<div className='account__header__extra__links'>
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get("acct")}`} title={intl.formatNumber(account.get("statuses_count"))}>
<ShortNumber
value={account.get('statuses_count')}
value={account.get("statuses_count")}
renderer={StatusesCounter}
/>
</NavLink>
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
<NavLink exact activeClassName='active' to={`/@${account.get("acct")}/following`} title={intl.formatNumber(account.get("following_count"))}>
<ShortNumber
value={account.get('following_count')}
value={account.get("following_count")}
renderer={FollowingCounter}
/>
</NavLink>
<NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
<NavLink exact activeClassName='active' to={`/@${account.get("acct")}/followers`} title={intl.formatNumber(account.get("followers_count"))}>
<ShortNumber
value={account.get('followers_count')}
value={account.get("followers_count")}
renderer={FollowersCounter}
/>
</NavLink>
@@ -483,8 +485,8 @@ class Header extends ImmutablePureComponent {
<Helmet>
<title>{titleFromAccount(account)}</title>
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
<link rel='canonical' href={account.get('url')} />
<meta name='robots' content={(isLocal && isIndexable) ? "all" : "noindex"} />
<link rel='canonical' href={account.get("url")} />
</Helmet>
</div>
);
@@ -1,17 +1,17 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { submitAccountNote } from 'mastodon/actions/account_notes';
import { submitAccountNote } from "mastodon/actions/account_notes";
import AccountNote from '../components/account_note';
import AccountNote from "../components/account_note";
const mapStateToProps = (state, { account }) => ({
value: account.getIn(['relationship', 'note']),
value: account.getIn(["relationship", "note"]),
});
const mapDispatchToProps = (dispatch, { account }) => ({
onSave (value) {
dispatch(submitAccountNote({ id: account.get('id'), value}));
dispatch(submitAccountNote({ id: account.get("id"), value}));
},
});
@@ -1,16 +1,16 @@
import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import { connect } from "react-redux";
import { makeGetAccount } from 'mastodon/selectors';
import { makeGetAccount } from "mastodon/selectors";
import FeaturedTags from '../components/featured_tags';
import FeaturedTags from "../components/featured_tags";
const mapStateToProps = () => {
const getAccount = makeGetAccount();
return (state, { accountId }) => ({
account: getAccount(state, accountId),
featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()),
featuredTags: state.getIn(["user_lists", "featured_tags", accountId, "items"], ImmutableList()),
});
};
@@ -1,16 +1,16 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
import { authorizeFollowRequest, rejectFollowRequest } from "mastodon/actions/accounts";
import FollowRequestNote from '../components/follow_request_note';
import FollowRequestNote from "../components/follow_request_note";
const mapDispatchToProps = (dispatch, { account }) => ({
onAuthorize () {
dispatch(authorizeFollowRequest(account.get('id')));
dispatch(authorizeFollowRequest(account.get("id")));
},
onReject () {
dispatch(rejectFollowRequest(account.get('id')));
dispatch(rejectFollowRequest(account.get("id")));
},
});
@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import FeaturedTags from 'mastodon/features/account/containers/featured_tags_container';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
import FeaturedTags from "mastodon/features/account/containers/featured_tags_container";
import { normalizeForLookup } from "mastodon/reducers/accounts_map";
const mapStateToProps = (state, { match: { params: { acct } } }) => {
const accountId = state.getIn(['accounts_map', normalizeForLookup(acct)]);
const accountId = state.getIn(["accounts_map", normalizeForLookup(acct)]);
if (!accountId) {
return {
@@ -1,13 +1,13 @@
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 { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon';
import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state';
import { Blurhash } from "mastodon/components/blurhash";
import { Icon } from "mastodon/components/icon";
import { autoPlayGif, displayMedia, useBlurhash } from "mastodon/initial_state";
export default class MediaItem extends ImmutablePureComponent {
@@ -18,7 +18,7 @@ export default class MediaItem extends ImmutablePureComponent {
};
state = {
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
visible: displayMedia !== "hide_all" && !this.props.attachment.getIn(["status", "sensitive"]) || displayMedia === "show_all",
loaded: false,
};
@@ -40,7 +40,7 @@ export default class MediaItem extends ImmutablePureComponent {
};
hoverToPlay () {
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
return !autoPlayGif && ["gifv", "video"].indexOf(this.props.attachment.get("type")) !== -1;
}
handleClick = e => {
@@ -61,8 +61,8 @@ export default class MediaItem extends ImmutablePureComponent {
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
const height = width;
const status = attachment.get('status');
const title = status.get('spoiler_text') || attachment.get('description');
const status = attachment.get("status");
const title = status.get("spoiler_text") || attachment.get("description");
let thumbnail, label, icon, content;
@@ -73,45 +73,45 @@ export default class MediaItem extends ImmutablePureComponent {
</span>
);
} else {
if (['audio', 'video'].includes(attachment.get('type'))) {
if (["audio", "video"].includes(attachment.get("type"))) {
content = (
<img
src={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
alt={attachment.get('description')}
lang={status.get('language')}
src={attachment.get("preview_url") || status.getIn(["account", "avatar_static"])}
alt={attachment.get("description")}
lang={status.get("language")}
onLoad={this.handleImageLoad}
/>
);
if (attachment.get('type') === 'audio') {
if (attachment.get("type") === "audio") {
label = <Icon id='music' />;
} else {
label = <Icon id='play' />;
}
} else if (attachment.get('type') === 'image') {
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
} else if (attachment.get("type") === "image") {
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;
content = (
<img
src={attachment.get('preview_url')}
alt={attachment.get('description')}
lang={status.get('language')}
src={attachment.get("preview_url")}
alt={attachment.get("description")}
lang={status.get("language")}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
);
} else if (attachment.get('type') === 'gifv') {
} else if (attachment.get("type") === "gifv") {
content = (
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.get('description')}
title={attachment.get('description')}
lang={status.get('language')}
aria-label={attachment.get("description")}
title={attachment.get("description")}
lang={status.get("language")}
role='application'
src={attachment.get('url')}
src={attachment.get("url")}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
autoPlay={autoPlayGif}
@@ -121,7 +121,7 @@ export default class MediaItem extends ImmutablePureComponent {
/>
);
label = 'GIF';
label = "GIF";
}
thumbnail = (
@@ -139,10 +139,10 @@ export default class MediaItem extends ImmutablePureComponent {
return (
<div className='account-gallery__item' style={{ width, height }}>
<a className='media-gallery__item-thumbnail' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
<a className='media-gallery__item-thumbnail' href={`/@${status.getIn(["account", "acct"])}/${status.get("id")}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'>
<Blurhash
hash={attachment.get('blurhash')}
className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })}
hash={attachment.get("blurhash")}
className={classNames("media-gallery__preview", { "media-gallery__preview--hidden": visible && loaded })}
dummy={!useBlurhash}
/>
@@ -1,29 +1,29 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import ColumnBackButton from 'mastodon/components/column_back_button';
import { LoadMore } from 'mastodon/components/load_more';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
import { getAccountGallery } from 'mastodon/selectors';
import { lookupAccount, fetchAccount } from "mastodon/actions/accounts";
import { openModal } from "mastodon/actions/modal";
import ColumnBackButton from "mastodon/components/column_back_button";
import { LoadMore } from "mastodon/components/load_more";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
import ScrollContainer from "mastodon/containers/scroll_container";
import BundleColumnError from "mastodon/features/ui/components/bundle_column_error";
import { normalizeForLookup } from "mastodon/reducers/accounts_map";
import { getAccountGallery } from "mastodon/selectors";
import { expandAccountMediaTimeline } from '../../actions/timelines';
import HeaderContainer from '../account_timeline/containers/header_container';
import Column from '../ui/components/column';
import { expandAccountMediaTimeline } from "../../actions/timelines";
import HeaderContainer from "../account_timeline/containers/header_container";
import Column from "../ui/components/column";
import MediaItem from './components/media_item';
import MediaItem from "./components/media_item";
const mapStateToProps = (state, { params: { acct, id } }) => {
const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
const accountId = id || state.getIn(["accounts_map", normalizeForLookup(acct)]);
if (!accountId) {
return {
@@ -33,12 +33,12 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
return {
accountId,
isAccount: !!state.getIn(['accounts', accountId]),
isAccount: !!state.getIn(["accounts", accountId]),
attachments: getAccountGallery(state, accountId),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
isLoading: state.getIn(["timelines", `account:${accountId}:media`, "isLoading"]),
hasMore: state.getIn(["timelines", `account:${accountId}:media`, "hasMore"]),
suspended: state.getIn(["accounts", accountId, "suspended"], false),
blockedBy: state.getIn(["relationships", accountId, "blocked_by"], false),
};
};
@@ -89,7 +89,9 @@ class AccountGallery extends ImmutablePureComponent {
_load () {
const { accountId, isAccount, dispatch } = this.props;
if (!isAccount) dispatch(fetchAccount(accountId));
if (!isAccount) {
dispatch(fetchAccount(accountId));
}
dispatch(expandAccountMediaTimeline(accountId));
}
@@ -115,7 +117,7 @@ class AccountGallery extends ImmutablePureComponent {
handleScrollToBottom = () => {
if (this.props.hasMore) {
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(["status", "id"]) : undefined);
}
};
@@ -139,25 +141,25 @@ class AccountGallery extends ImmutablePureComponent {
handleOpenMedia = attachment => {
const { dispatch } = this.props;
const statusId = attachment.getIn(['status', 'id']);
const lang = attachment.getIn(['status', 'language']);
const statusId = attachment.getIn(["status", "id"]);
const lang = attachment.getIn(["status", "language"]);
if (attachment.get('type') === 'video') {
if (attachment.get("type") === "video") {
dispatch(openModal({
modalType: 'VIDEO',
modalType: "VIDEO",
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
}));
} else if (attachment.get('type') === 'audio') {
} else if (attachment.get("type") === "audio") {
dispatch(openModal({
modalType: 'AUDIO',
modalType: "AUDIO",
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
}));
} else {
const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
const media = attachment.getIn(["status", "media_attachments"]);
const index = media.findIndex(x => x.get("id") === attachment.get("id"));
dispatch(openModal({
modalType: 'MEDIA',
modalType: "MEDIA",
modalProps: { media, index, statusId, lang },
}));
}
@@ -216,9 +218,9 @@ class AccountGallery extends ImmutablePureComponent {
) : (
<div role='feed' className='account-gallery__container' ref={this.handleRef}>
{attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
<LoadMoreMedia key={"more:" + attachments.getIn(index + 1, "id")} maxId={index > 0 ? attachments.getIn(index - 1, "id") : null} onLoadMore={this.handleLoadMore} />
) : (
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
<MediaItem key={attachment.get("id")} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
))}
{loadOlder}
@@ -1,16 +1,16 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { NavLink } from 'react-router-dom';
import { NavLink } from "react-router-dom";
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 InnerHeader from '../../account/components/header';
import InnerHeader from "../../account/components/header";
import MemorialNote from './memorial_note';
import MovedNote from './moved_note';
import MemorialNote from "./memorial_note";
import MovedNote from "./moved_note";
export default class Header extends ImmutablePureComponent {
@@ -73,17 +73,21 @@ export default class Header extends ImmutablePureComponent {
};
handleBlockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
const domain = this.props.account.get("acct").split("@")[1];
if (!domain) return;
if (!domain) {
return;
}
this.props.onBlockDomain(domain);
};
handleUnblockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
const domain = this.props.account.get("acct").split("@")[1];
if (!domain) return;
if (!domain) {
return;
}
this.props.onUnblockDomain(domain);
};
@@ -121,8 +125,8 @@ export default class Header extends ImmutablePureComponent {
return (
<div className='account-timeline__header'>
{(!hidden && account.get('memorial')) && <MemorialNote />}
{(!hidden && account.get('moved')) && <MovedNote from={account} to={account.get('moved')} />}
{(!hidden && account.get("memorial")) && <MemorialNote />}
{(!hidden && account.get("moved")) && <MovedNote from={account} to={account.get("moved")} />}
<InnerHeader
account={account}
@@ -149,9 +153,9 @@ export default class Header extends ImmutablePureComponent {
{!(hideTabs || hidden) && (
<div className='account__section-headline'>
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
<NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
<NavLink exact to={`/@${account.get("acct")}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
<NavLink exact to={`/@${account.get("acct")}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
<NavLink exact to={`/@${account.get("acct")}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
)}
</div>
@@ -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 { connect } from 'react-redux';
import { connect } from "react-redux";
import { revealAccount } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button';
import { domain } from 'mastodon/initial_state';
import { revealAccount } from "mastodon/actions/accounts";
import Button from "mastodon/components/button";
import { domain } from "mastodon/initial_state";
const mapDispatchToProps = (dispatch, { accountId }) => ({
@@ -1,4 +1,4 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
const MemorialNote = () => (
<div className='account-memorial-banner'>
@@ -1,12 +1,12 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { Link } from 'react-router-dom';
import { Link } from "react-router-dom";
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 { AvatarOverlay } from '../../../components/avatar_overlay';
import { DisplayName } from '../../../components/display_name';
import { AvatarOverlay } from "../../../components/avatar_overlay";
import { DisplayName } from "../../../components/display_name";
export default class MovedNote extends ImmutablePureComponent {
@@ -21,16 +21,16 @@ export default class MovedNote extends ImmutablePureComponent {
return (
<div className='moved-account-banner'>
<div className='moved-account-banner__message'>
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get('display_name_html') }} /></bdi> }} />
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get("display_name_html") }} /></bdi> }} />
</div>
<div className='moved-account-banner__action'>
<Link to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
<Link to={`/@${to.get("acct")}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
<DisplayName account={to} />
</Link>
<Link to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
<Link to={`/@${to.get("acct")}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
</div>
</div>
);
@@ -1,8 +1,8 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { openURL } from 'mastodon/actions/search';
import { openURL } from "mastodon/actions/search";
import {
followAccount,
@@ -11,24 +11,24 @@ import {
unmuteAccount,
pinAccount,
unpinAccount,
} from '../../../actions/accounts';
import { initBlockModal } from '../../../actions/blocks';
} from "../../../actions/accounts";
import { initBlockModal } from "../../../actions/blocks";
import {
mentionCompose,
directCompose,
} from '../../../actions/compose';
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
import { openModal } from '../../../actions/modal';
import { initMuteModal } from '../../../actions/mutes';
import { initReport } from '../../../actions/reports';
import { unfollowModal } from '../../../initial_state';
import { makeGetAccount, getAccountHidden } from '../../../selectors';
import Header from '../components/header';
} from "../../../actions/compose";
import { blockDomain, unblockDomain } from "../../../actions/domain_blocks";
import { openModal } from "../../../actions/modal";
import { initMuteModal } from "../../../actions/mutes";
import { initReport } from "../../../actions/reports";
import { unfollowModal } from "../../../initial_state";
import { makeGetAccount, getAccountHidden } from "../../../selectors";
import Header from "../components/header";
const messages = defineMessages({
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
cancelFollowRequestConfirm: { id: "confirmations.cancel_follow_request.confirm", defaultMessage: "Withdraw request" },
unfollowConfirm: { id: "confirmations.unfollow.confirm", defaultMessage: "Unfollow" },
blockDomainConfirm: { id: "confirmations.domain_block.confirm", defaultMessage: "Block entire domain" },
});
const makeMapStateToProps = () => {
@@ -36,7 +36,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, accountId),
domain: state.getIn(['meta', 'domain']),
domain: state.getIn(["meta", "domain"]),
hidden: getAccountHidden(state, accountId),
});
@@ -46,51 +46,51 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow (account) {
if (account.getIn(['relationship', 'following'])) {
if (account.getIn(["relationship", "following"])) {
if (unfollowModal) {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get("acct")}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
},
}));
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get("id")));
}
} else if (account.getIn(['relationship', 'requested'])) {
} else if (account.getIn(["relationship", "requested"])) {
if (unfollowModal) {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get("acct")}</strong> }} />,
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
},
}));
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get("id")));
}
} else {
dispatch(followAccount(account.get('id')));
dispatch(followAccount(account.get("id")));
}
},
onInteractionModal (account) {
dispatch(openModal({
modalType: 'INTERACTION',
modalType: "INTERACTION",
modalProps: {
type: 'follow',
accountId: account.get('id'),
url: account.get('uri'),
type: "follow",
accountId: account.get("id"),
url: account.get("uri"),
},
}));
},
onBlock (account) {
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
if (account.getIn(["relationship", "blocking"])) {
dispatch(unblockAccount(account.get("id")));
} else {
dispatch(initBlockModal(account));
}
@@ -105,26 +105,26 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onReblogToggle (account) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
dispatch(followAccount(account.get('id'), { reblogs: false }));
if (account.getIn(["relationship", "showing_reblogs"])) {
dispatch(followAccount(account.get("id"), { reblogs: false }));
} else {
dispatch(followAccount(account.get('id'), { reblogs: true }));
dispatch(followAccount(account.get("id"), { reblogs: true }));
}
},
onEndorseToggle (account) {
if (account.getIn(['relationship', 'endorsed'])) {
dispatch(unpinAccount(account.get('id')));
if (account.getIn(["relationship", "endorsed"])) {
dispatch(unpinAccount(account.get("id")));
} else {
dispatch(pinAccount(account.get('id')));
dispatch(pinAccount(account.get("id")));
}
},
onNotifyToggle (account) {
if (account.getIn(['relationship', 'notifying'])) {
dispatch(followAccount(account.get('id'), { notify: false }));
if (account.getIn(["relationship", "notifying"])) {
dispatch(followAccount(account.get("id"), { notify: false }));
} else {
dispatch(followAccount(account.get('id'), { notify: true }));
dispatch(followAccount(account.get("id"), { notify: true }));
}
},
@@ -133,8 +133,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onMute (account) {
if (account.getIn(['relationship', 'muting'])) {
dispatch(unmuteAccount(account.get('id')));
if (account.getIn(["relationship", "muting"])) {
dispatch(unmuteAccount(account.get("id")));
} else {
dispatch(initMuteModal(account));
}
@@ -142,7 +142,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onBlockDomain (domain) {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
@@ -157,28 +157,28 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onAddToList (account) {
dispatch(openModal({
modalType: 'LIST_ADDER',
modalType: "LIST_ADDER",
modalProps: {
accountId: account.get('id'),
accountId: account.get("id"),
},
}));
},
onChangeLanguages (account) {
dispatch(openModal({
modalType: 'SUBSCRIBED_LANGUAGES',
modalType: "SUBSCRIBED_LANGUAGES",
modalProps: {
accountId: account.get('id'),
accountId: account.get("id"),
},
}));
},
onOpenAvatar (account) {
dispatch(openModal({
modalType: 'IMAGE',
modalType: "IMAGE",
modalProps: {
src: account.get('avatar'),
alt: account.get('acct'),
src: account.get("avatar"),
alt: account.get("acct"),
},
}));
},
@@ -1,33 +1,33 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { TimelineHint } from 'mastodon/components/timeline_hint';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { me } from 'mastodon/initial_state';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
import { getAccountHidden } from 'mastodon/selectors';
import { TimelineHint } from "mastodon/components/timeline_hint";
import BundleColumnError from "mastodon/features/ui/components/bundle_column_error";
import { me } from "mastodon/initial_state";
import { normalizeForLookup } from "mastodon/reducers/accounts_map";
import { getAccountHidden } from "mastodon/selectors";
import { lookupAccount, fetchAccount } from '../../actions/accounts';
import { fetchFeaturedTags } from '../../actions/featured_tags';
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
import ColumnBackButton from '../../components/column_back_button';
import { LoadingIndicator } from '../../components/loading_indicator';
import StatusList from '../../components/status_list';
import Column from '../ui/components/column';
import { lookupAccount, fetchAccount } from "../../actions/accounts";
import { fetchFeaturedTags } from "../../actions/featured_tags";
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from "../../actions/timelines";
import ColumnBackButton from "../../components/column_back_button";
import { LoadingIndicator } from "../../components/loading_indicator";
import StatusList from "../../components/status_list";
import Column from "../ui/components/column";
import LimitedAccountHint from './components/limited_account_hint';
import HeaderContainer from './containers/header_container';
import LimitedAccountHint from "./components/limited_account_hint";
import HeaderContainer from "./containers/header_container";
const emptyList = ImmutableList();
const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => {
const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
const accountId = id || state.getIn(["accounts_map", normalizeForLookup(acct)]);
if (accountId === null) {
return {
@@ -42,20 +42,20 @@ const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = fa
};
}
const path = withReplies ? `${accountId}:with_replies` : `${accountId}${tagged ? `:${tagged}` : ''}`;
const path = withReplies ? `${accountId}:with_replies` : `${accountId}${tagged ? `:${tagged}` : ""}`;
return {
accountId,
remote: !!(state.getIn(['accounts', accountId, 'acct']) !== state.getIn(['accounts', accountId, 'username'])),
remoteUrl: state.getIn(['accounts', accountId, 'url']),
isAccount: !!state.getIn(['accounts', accountId]),
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
remote: !!(state.getIn(["accounts", accountId, "acct"]) !== state.getIn(["accounts", accountId, "username"])),
remoteUrl: state.getIn(["accounts", accountId, "url"]),
isAccount: !!state.getIn(["accounts", accountId]),
statusIds: state.getIn(["timelines", `account:${path}`, "items"], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(["timelines", `account:${accountId}:pinned${tagged ? `:${tagged}` : ""}`, "items"], emptyList),
isLoading: state.getIn(["timelines", `account:${path}`, "isLoading"]),
hasMore: state.getIn(["timelines", `account:${path}`, "hasMore"]),
suspended: state.getIn(["accounts", accountId, "suspended"], false),
hidden: getAccountHidden(state, accountId),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
blockedBy: state.getIn(["relationships", accountId, "blocked_by"], false),
};
};
@@ -1,29 +1,29 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { defineMessages, FormattedMessage, injectIntl } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import { is } from 'immutable';
import { is } from "immutable";
import { throttle, debounce } from 'lodash';
import { throttle, debounce } from "lodash";
import { Icon } from 'mastodon/components/icon';
import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { Icon } from "mastodon/components/icon";
import { formatTime, getPointerPosition, fileNameFromURL } from "mastodon/features/video";
import { Blurhash } from '../../components/blurhash';
import { displayMedia, useBlurhash } from '../../initial_state';
import { Blurhash } from "../../components/blurhash";
import { displayMedia, useBlurhash } from "../../initial_state";
import Visualizer from './visualizer';
import Visualizer from "./visualizer";
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
pause: { id: 'video.pause', defaultMessage: 'Pause' },
mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
download: { id: 'video.download', defaultMessage: 'Download file' },
hide: { id: 'audio.hide', defaultMessage: 'Hide audio' },
play: { id: "video.play", defaultMessage: "Play" },
pause: { id: "video.pause", defaultMessage: "Pause" },
mute: { id: "video.mute", defaultMessage: "Mute sound" },
unmute: { id: "video.unmute", defaultMessage: "Unmute sound" },
download: { id: "video.download", defaultMessage: "Download file" },
hide: { id: "audio.hide", defaultMessage: "Hide audio" },
});
const TICK_SIZE = 10;
@@ -66,7 +66,7 @@ class Audio extends PureComponent {
muted: false,
volume: 1,
dragging: false,
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== "hide_all" && !this.props.sensitive || displayMedia === "show_all"),
};
constructor (props) {
@@ -132,8 +132,8 @@ class Audio extends PureComponent {
};
componentDidMount () {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize, { passive: true });
window.addEventListener("scroll", this.handleScroll);
window.addEventListener("resize", this.handleResize, { passive: true });
}
componentDidUpdate (prevProps, prevState) {
@@ -150,11 +150,11 @@ class Audio extends PureComponent {
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
window.removeEventListener("scroll", this.handleScroll);
window.removeEventListener("resize", this.handleResize);
if (!this.state.paused && this.audio && this.props.deployPictureInPicture) {
this.props.deployPictureInPicture('audio', this._pack());
this.props.deployPictureInPicture("audio", this._pack());
}
}
@@ -181,7 +181,7 @@ class Audio extends PureComponent {
handlePlay = () => {
this.setState({ paused: false });
if (this.audioContext && this.audioContext.state === 'suspended') {
if (this.audioContext && this.audioContext.state === "suspended") {
this.audioContext.resume();
}
@@ -223,10 +223,10 @@ class Audio extends PureComponent {
};
handleVolumeMouseDown = e => {
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
document.addEventListener('touchend', this.handleVolumeMouseUp, true);
document.addEventListener("mousemove", this.handleMouseVolSlide, true);
document.addEventListener("mouseup", this.handleVolumeMouseUp, true);
document.addEventListener("touchmove", this.handleMouseVolSlide, true);
document.addEventListener("touchend", this.handleVolumeMouseUp, true);
this.handleMouseVolSlide(e);
@@ -235,17 +235,17 @@ class Audio extends PureComponent {
};
handleVolumeMouseUp = () => {
document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
document.removeEventListener("mousemove", this.handleMouseVolSlide, true);
document.removeEventListener("mouseup", this.handleVolumeMouseUp, true);
document.removeEventListener("touchmove", this.handleMouseVolSlide, true);
document.removeEventListener("touchend", this.handleVolumeMouseUp, true);
};
handleMouseDown = e => {
document.addEventListener('mousemove', this.handleMouseMove, true);
document.addEventListener('mouseup', this.handleMouseUp, true);
document.addEventListener('touchmove', this.handleMouseMove, true);
document.addEventListener('touchend', this.handleMouseUp, true);
document.addEventListener("mousemove", this.handleMouseMove, true);
document.addEventListener("mouseup", this.handleMouseUp, true);
document.addEventListener("touchmove", this.handleMouseMove, true);
document.addEventListener("touchend", this.handleMouseUp, true);
this.setState({ dragging: true });
this.audio.pause();
@@ -256,10 +256,10 @@ class Audio extends PureComponent {
};
handleMouseUp = () => {
document.removeEventListener('mousemove', this.handleMouseMove, true);
document.removeEventListener('mouseup', this.handleMouseUp, true);
document.removeEventListener('touchmove', this.handleMouseMove, true);
document.removeEventListener('touchend', this.handleMouseUp, true);
document.removeEventListener("mousemove", this.handleMouseMove, true);
document.removeEventListener("mouseup", this.handleMouseUp, true);
document.removeEventListener("touchmove", this.handleMouseMove, true);
document.removeEventListener("touchend", this.handleMouseUp, true);
this.setState({ dragging: false });
this.audio.play();
@@ -307,7 +307,7 @@ class Audio extends PureComponent {
this.audio.pause();
if (this.props.deployPictureInPicture) {
this.props.deployPictureInPicture('audio', this._pack());
this.props.deployPictureInPicture("audio", this._pack());
}
this.setState({ paused: true });
@@ -352,11 +352,11 @@ class Audio extends PureComponent {
handleDownload = () => {
fetch(this.props.src).then(res => res.blob()).then(blob => {
const element = document.createElement('a');
const element = document.createElement("a");
const objectURL = URL.createObjectURL(blob);
element.setAttribute('href', objectURL);
element.setAttribute('download', fileNameFromURL(this.props.src));
element.setAttribute("href", objectURL);
element.setAttribute("download", fileNameFromURL(this.props.src));
document.body.appendChild(element);
element.click();
@@ -370,7 +370,9 @@ class Audio extends PureComponent {
_renderCanvas () {
requestAnimationFrame(() => {
if (!this.audio) return;
if (!this.audio) {
return;
}
this.handleTimeUpdate();
this._clear();
@@ -407,15 +409,15 @@ class Audio extends PureComponent {
}
_getAccentColor () {
return this.props.accentColor || '#ffffff';
return this.props.accentColor || "#ffffff";
}
_getBackgroundColor () {
return this.props.backgroundColor || '#000000';
return this.props.backgroundColor || "#000000";
}
_getForegroundColor () {
return this.props.foregroundColor || '#ffffff';
return this.props.foregroundColor || "#ffffff";
}
seekBy (time) {
@@ -432,7 +434,7 @@ class Audio extends PureComponent {
// On the audio element or the seek bar, we can safely use the space bar
// for playback control because there are no buttons to press
if (e.key === ' ') {
if (e.key === " ") {
e.preventDefault();
e.stopPropagation();
this.togglePlay();
@@ -441,26 +443,26 @@ class Audio extends PureComponent {
handleKeyDown = e => {
switch(e.key) {
case 'k':
e.preventDefault();
e.stopPropagation();
this.togglePlay();
break;
case 'm':
e.preventDefault();
e.stopPropagation();
this.toggleMute();
break;
case 'j':
e.preventDefault();
e.stopPropagation();
this.seekBy(-10);
break;
case 'l':
e.preventDefault();
e.stopPropagation();
this.seekBy(10);
break;
case "k":
e.preventDefault();
e.stopPropagation();
this.togglePlay();
break;
case "m":
e.preventDefault();
e.stopPropagation();
this.toggleMute();
break;
case "j":
e.preventDefault();
e.stopPropagation();
this.seekBy(-10);
break;
case "l":
e.preventDefault();
e.stopPropagation();
this.seekBy(10);
break;
}
};
@@ -479,12 +481,12 @@ class Audio extends PureComponent {
}
return (
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), aspectRatio: '16 / 9' }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
<div className={classNames("audio-player", { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), aspectRatio: "16 / 9" }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
<Blurhash
hash={blurhash}
className={classNames('media-gallery__preview', {
'media-gallery__preview--hidden': revealed,
className={classNames("media-gallery__preview", {
"media-gallery__preview--hidden": revealed,
})}
dummy={!useBlurhash}
/>
@@ -492,7 +494,7 @@ class Audio extends PureComponent {
{(revealed || editable) && <audio
src={src}
ref={this.setAudioRef}
preload={autoPlay ? 'auto' : 'none'}
preload={autoPlay ? "auto" : "none"}
onPlay={this.handlePlay}
onPause={this.handlePause}
onProgress={this.handleProgress}
@@ -506,7 +508,7 @@ class Audio extends PureComponent {
className='audio-player__canvas'
width={this.state.width}
height={this.state.height}
style={{ width: '100%', position: 'absolute', top: 0, left: 0 }}
style={{ width: "100%", position: "absolute", top: 0, left: 0 }}
ref={this.setCanvasRef}
onClick={this.togglePlay}
onKeyDown={this.handleAudioKeyDown}
@@ -515,7 +517,7 @@ class Audio extends PureComponent {
lang={lang}
/>
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
<div className={classNames("spoiler-button", { "spoiler-button--hidden": revealed || editable })}>
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
<span className='spoiler-button__overlay__label'>
{warning}
@@ -528,14 +530,14 @@ class Audio extends PureComponent {
src={this.props.poster}
alt=''
style={{
position: 'absolute',
left: '50%',
top: '50%',
position: "absolute",
left: "50%",
top: "50%",
height: `calc(${(100 - 2 * 100 * PADDING / 982)}% - ${TICK_SIZE * 2}px)`,
aspectRatio: '1',
transform: 'translate(-50%, -50%)',
borderRadius: '50%',
pointerEvents: 'none',
aspectRatio: "1",
transform: "translate(-50%, -50%)",
borderRadius: "50%",
pointerEvents: "none",
}}
/>}
@@ -544,7 +546,7 @@ class Audio extends PureComponent {
<div className='video-player__seek__progress' style={{ width: `${progress}%`, backgroundColor: this._getAccentColor() }} />
<span
className={classNames('video-player__seek__handle', { active: dragging })}
className={classNames("video-player__seek__handle", { active: dragging })}
tabIndex={0}
style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
onKeyDown={this.handleAudioKeyDown}
@@ -554,10 +556,10 @@ class Audio extends PureComponent {
<div className='video-player__controls active'>
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay}><Icon id={paused ? "play" : "pause"} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? "volume-off" : "volume-up"} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
<div className={classNames("video-player__volume", { active: this.state.hovered })} ref={this.setVolumeRef} onMouseDown={this.handleVolumeMouseDown}>
<div className='video-player__volume__current' style={{ width: `${muted ? 0 : volume * 100}%`, backgroundColor: this._getAccentColor() }} />
<span
@@ -577,7 +579,7 @@ class Audio extends PureComponent {
<div className='video-player__buttons right'>
{!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
<a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
<Icon id={'download'} fixedWidth />
<Icon id={"download"} fixedWidth />
</a>
</div>
</div>
@@ -22,7 +22,7 @@ export default class Visualizer {
setCanvas(canvas) {
this.canvas = canvas;
if (canvas) {
this.context = canvas.getContext('2d');
this.context = canvas.getContext("2d");
}
}
@@ -1,28 +1,28 @@
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 ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import { fetchBlocks, expandBlocks } from "../../actions/blocks";
import ColumnBackButtonSlim from "../../components/column_back_button_slim";
import { LoadingIndicator } from "../../components/loading_indicator";
import ScrollableList from "../../components/scrollable_list";
import AccountContainer from "../../containers/account_container";
import Column from "../ui/components/column";
const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
heading: { id: "column.blocks", defaultMessage: "Blocked users" },
});
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true),
accountIds: state.getIn(["user_lists", "blocks", "items"]),
hasMore: !!state.getIn(["user_lists", "blocks", "next"]),
isLoading: state.getIn(["user_lists", "blocks", "isLoading"], true),
});
class Blocks extends ImmutablePureComponent {
@@ -1,30 +1,30 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks';
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import ColumnHeader from 'mastodon/components/column_header';
import StatusList from 'mastodon/components/status_list';
import Column from 'mastodon/features/ui/components/column';
import { getStatusList } from 'mastodon/selectors';
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from "mastodon/actions/bookmarks";
import { addColumn, removeColumn, moveColumn } from "mastodon/actions/columns";
import ColumnHeader from "mastodon/components/column_header";
import StatusList from "mastodon/components/status_list";
import Column from "mastodon/features/ui/components/column";
import { getStatusList } from "mastodon/selectors";
const messages = defineMessages({
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
heading: { id: "column.bookmarks", defaultMessage: "Bookmarks" },
});
const mapStateToProps = state => ({
statusIds: getStatusList(state, 'bookmarks'),
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
statusIds: getStatusList(state, "bookmarks"),
isLoading: state.getIn(["status_lists", "bookmarks", "isLoading"], true),
hasMore: !!state.getIn(["status_lists", "bookmarks", "next"]),
});
class Bookmarks extends ImmutablePureComponent {
@@ -49,7 +49,7 @@ class Bookmarks extends ImmutablePureComponent {
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('BOOKMARKS', {}));
dispatch(addColumn("BOOKMARKS", {}));
}
};
@@ -1,13 +1,13 @@
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { fetchServer } from 'mastodon/actions/server';
import { domain } from 'mastodon/initial_state';
import { fetchServer } from "mastodon/actions/server";
import { domain } from "mastodon/initial_state";
const mapStateToProps = state => ({
message: state.getIn(['server', 'server', 'registrations', 'message']),
message: state.getIn(["server", "server", "registrations", "message"]),
});
class ClosedRegistrationsModal extends ImmutablePureComponent {
@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { injectIntl, FormattedMessage } from 'react-intl';
import { injectIntl, FormattedMessage } from "react-intl";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import SettingToggle from '../../notifications/components/setting_toggle';
import SettingToggle from "../../notifications/components/setting_toggle";
class ColumnSettings extends PureComponent {
@@ -22,7 +22,7 @@ class ColumnSettings extends PureComponent {
return (
<div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
<SettingToggle settings={settings} settingPath={["other", "onlyMedia"]} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
</div>
);
@@ -1,16 +1,16 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { changeColumnParams } from '../../../actions/columns';
import { changeSetting } from '../../../actions/settings';
import ColumnSettings from '../components/column_settings';
import { changeColumnParams } from "../../../actions/columns";
import { changeSetting } from "../../../actions/settings";
import ColumnSettings from "../components/column_settings";
const mapStateToProps = (state, { columnId }) => {
const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const columns = state.getIn(["settings", "columns"]);
const index = columns.findIndex(c => c.get("uuid") === uuid);
return {
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
settings: (uuid && index >= 0) ? columns.get(index).get("params") : state.getIn(["settings", "community"]),
};
};
@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch, { columnId }) => {
if (columnId) {
dispatch(changeColumnParams(columnId, key, checked));
} else {
dispatch(changeSetting(['community', ...key], checked));
dispatch(changeSetting(["community", ...key], checked));
}
},
};
@@ -1,37 +1,37 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';
import { DismissableBanner } from "mastodon/components/dismissable_banner";
import { domain } from "mastodon/initial_state";
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { connectCommunityStream } from '../../actions/streaming';
import { expandCommunityTimeline } from '../../actions/timelines';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import StatusListContainer from '../ui/containers/status_list_container';
import { addColumn, removeColumn, moveColumn } from "../../actions/columns";
import { connectCommunityStream } from "../../actions/streaming";
import { expandCommunityTimeline } from "../../actions/timelines";
import Column from "../../components/column";
import ColumnHeader from "../../components/column_header";
import StatusListContainer from "../ui/containers/status_list_container";
import ColumnSettingsContainer from './containers/column_settings_container';
import ColumnSettingsContainer from "./containers/column_settings_container";
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' },
title: { id: "column.community", defaultMessage: "Local timeline" },
});
const mapStateToProps = (state, { columnId }) => {
const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']);
const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]);
const columns = state.getIn(["settings", "columns"]);
const index = columns.findIndex(c => c.get("uuid") === uuid);
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(["params", "other", "onlyMedia"]) : state.getIn(["settings", "community", "other", "onlyMedia"]);
const timelineState = state.getIn(["timelines", `community${onlyMedia ? ":media" : ""}`]);
return {
hasUnread: !!timelineState && timelineState.get('unread') > 0,
hasUnread: !!timelineState && timelineState.get("unread") > 0,
onlyMedia,
};
};
@@ -62,7 +62,7 @@ class CommunityTimeline extends PureComponent {
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
dispatch(addColumn("COMMUNITY", { other: { onlyMedia } }));
}
};
@@ -144,7 +144,7 @@ class CommunityTimeline extends PureComponent {
prepend={<DismissableBanner id='community_timeline'><FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} /></DismissableBanner>}
trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`}
timelineId={`community${onlyMedia ? ':media' : ''}`}
timelineId={`community${onlyMedia ? ":media" : ""}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
bindToDocument={!multiColumn}
@@ -1,26 +1,26 @@
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";
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
import DropdownMenuContainer from "../../../containers/dropdown_menu_container";
const messages = defineMessages({
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
edit_profile: { id: "account.edit_profile", defaultMessage: "Edit profile" },
pins: { id: "navigation_bar.pins", defaultMessage: "Pinned posts" },
preferences: { id: "navigation_bar.preferences", defaultMessage: "Preferences" },
follow_requests: { id: "navigation_bar.follow_requests", defaultMessage: "Follow requests" },
favourites: { id: "navigation_bar.favourites", defaultMessage: "Favorites" },
lists: { id: "navigation_bar.lists", defaultMessage: "Lists" },
followed_tags: { id: "navigation_bar.followed_tags", defaultMessage: "Followed hashtags" },
blocks: { id: "navigation_bar.blocks", defaultMessage: "Blocked users" },
domain_blocks: { id: "navigation_bar.domain_blocks", defaultMessage: "Blocked domains" },
mutes: { id: "navigation_bar.mutes", defaultMessage: "Muted users" },
filters: { id: "navigation_bar.filters", defaultMessage: "Muted words" },
logout: { id: "navigation_bar.logout", defaultMessage: "Logout" },
bookmarks: { id: "navigation_bar.bookmarks", defaultMessage: "Bookmarks" },
});
class ActionBar extends PureComponent {
@@ -40,20 +40,20 @@ class ActionBar extends PureComponent {
let menu = [];
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
menu.push({ text: intl.formatMessage(messages.edit_profile), href: "/settings/profile" });
menu.push({ text: intl.formatMessage(messages.preferences), href: "/settings/preferences" });
menu.push({ text: intl.formatMessage(messages.pins), to: "/pinned" });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push({ text: intl.formatMessage(messages.follow_requests), to: "/follow_requests" });
menu.push({ text: intl.formatMessage(messages.favourites), to: "/favourites" });
menu.push({ text: intl.formatMessage(messages.bookmarks), to: "/bookmarks" });
menu.push({ text: intl.formatMessage(messages.lists), to: "/lists" });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: "/followed_tags" });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
menu.push({ text: intl.formatMessage(messages.mutes), to: "/mutes" });
menu.push({ text: intl.formatMessage(messages.blocks), to: "/blocks" });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: "/domain_blocks" });
menu.push({ text: intl.formatMessage(messages.filters), href: "/filters" });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.logout), action: this.handleLogout });
@@ -1,8 +1,8 @@
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 { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import { Avatar } from "../../../components/avatar";
import { DisplayName } from "../../../components/display_name";
export default class AutosuggestAccount extends ImmutablePureComponent {
@@ -14,7 +14,7 @@ export default class AutosuggestAccount extends ImmutablePureComponent {
const { account } = this.props;
return (
<div className='autosuggest-account' title={account.get('acct')}>
<div className='autosuggest-account' title={account.get("acct")}>
<div className='autosuggest-account-icon'><Avatar account={account} size={18} /></div>
<DisplayName account={account} />
</div>
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { length } from 'stringz';
import { length } from "stringz";
export default class CharacterCounter extends PureComponent {
@@ -1,42 +1,42 @@
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 { length } from 'stringz';
import { length } from "stringz";
import { Icon } from 'mastodon/components/icon';
import { Icon } from "mastodon/components/icon";
import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import Button from '../../../components/button';
import { maxChars } from '../../../initial_state';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container';
import PollButtonContainer from '../containers/poll_button_container';
import PollFormContainer from '../containers/poll_form_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import UploadButtonContainer from '../containers/upload_button_container';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
import { countableText } from '../util/counter';
import AutosuggestInput from "../../../components/autosuggest_input";
import AutosuggestTextarea from "../../../components/autosuggest_textarea";
import Button from "../../../components/button";
import { maxChars } from "../../../initial_state";
import EmojiPickerDropdown from "../containers/emoji_picker_dropdown_container";
import LanguageDropdown from "../containers/language_dropdown_container";
import PollButtonContainer from "../containers/poll_button_container";
import PollFormContainer from "../containers/poll_form_container";
import PrivacyDropdownContainer from "../containers/privacy_dropdown_container";
import ReplyIndicatorContainer from "../containers/reply_indicator_container";
import SpoilerButtonContainer from "../containers/spoiler_button_container";
import UploadButtonContainer from "../containers/upload_button_container";
import UploadFormContainer from "../containers/upload_form_container";
import WarningContainer from "../containers/warning_container";
import { countableText } from "../util/counter";
import CharacterCounter from './character_counter';
import CharacterCounter from "./character_counter";
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
const allowedAroundShortCode = "><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d";
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: '(Optional) post title / content warning' },
publish: { id: 'compose_form.publish', defaultMessage: 'Promulgate' },
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
saveChanges: { id: 'compose_form.save_changes', defaultMessage: 'Save changes' },
placeholder: { id: "compose_form.placeholder", defaultMessage: "What is on your mind?" },
spoiler_placeholder: { id: "compose_form.spoiler_placeholder", defaultMessage: "(Optional) post title / content warning" },
publish: { id: "compose_form.publish", defaultMessage: "Promulgate" },
publishLoud: { id: "compose_form.publish_loud", defaultMessage: "{publish}!" },
saveChanges: { id: "compose_form.save_changes", defaultMessage: "Save changes" },
});
class ComposeForm extends ImmutablePureComponent {
@@ -93,7 +93,7 @@ class ComposeForm extends ImmutablePureComponent {
};
getFulltextForCharacterCounting = () => {
return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join('');
return [this.props.spoiler? this.props.spoilerText: "", countableText(this.props.text)].join("");
};
canSubmit = () => {
@@ -131,11 +131,11 @@ class ComposeForm extends ImmutablePureComponent {
};
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
this.props.onSuggestionSelected(tokenStart, token, value, ["text"]);
};
onSpoilerSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
this.props.onSuggestionSelected(tokenStart, token, value, ["spoiler_text"]);
};
handleChangeSpoilerText = (e) => {
@@ -156,7 +156,9 @@ class ComposeForm extends ImmutablePureComponent {
}
componentWillUnmount () {
if (this.timeout) clearTimeout(this.timeout);
if (this.timeout) {
clearTimeout(this.timeout);
}
}
componentDidUpdate (prevProps) {
@@ -175,7 +177,7 @@ class ComposeForm extends ImmutablePureComponent {
if (this.props.preselectDate !== prevProps.preselectDate && this.props.isInReply) {
selectionEnd = this.props.text.length;
selectionStart = this.props.text.search(/\s/) + 1;
} else if (typeof this.props.caretPosition === 'number') {
} else if (typeof this.props.caretPosition === "number") {
selectionStart = this.props.caretPosition;
selectionEnd = this.props.caretPosition;
} else {
@@ -228,14 +230,14 @@ class ComposeForm extends ImmutablePureComponent {
const { highlighted } = this.state;
const disabled = this.props.isSubmitting;
let publishText = '';
let publishText = "";
if (this.props.isEditing) {
publishText = intl.formatMessage(messages.saveChanges);
} else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
} else if (this.props.privacy === "private" || this.props.privacy === "direct") {
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
} else {
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
publishText = this.props.privacy !== "unlisted" ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
}
return (
@@ -244,7 +246,7 @@ class ComposeForm extends ImmutablePureComponent {
<ReplyIndicatorContainer />
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef} aria-hidden={!this.props.spoiler}>
<div className={`spoiler-input ${this.props.spoiler ? "spoiler-input--visible" : ""}`} ref={this.setRef} aria-hidden={!this.props.spoiler}>
<AutosuggestInput
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
@@ -256,7 +258,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSpoilerSuggestionSelected}
searchTokens={[':']}
searchTokens={[":"]}
id='cw-spoiler-input'
className='spoiler-input__input'
lang={this.props.lang}
@@ -264,7 +266,7 @@ class ComposeForm extends ImmutablePureComponent {
/>
</div>
<div className={classNames('compose-form__highlightable', { active: highlighted })}>
<div className={classNames("compose-form__highlightable", { active: highlighted })}>
<AutosuggestTextarea
ref={this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.placeholder)}
@@ -1,34 +1,34 @@
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 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 { assetHost } from 'mastodon/utils/config';
import { assetHost } from "mastodon/utils/config";
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
import { buildCustomEmojis, categoriesFromEmojis } from "../../emoji/emoji";
import { EmojiPicker as EmojiPickerAsync } from "../../ui/util/async-components";
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
people: { id: 'emoji_button.people', defaultMessage: 'People' },
nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
emoji: { id: "emoji_button.label", defaultMessage: "Insert emoji" },
emoji_search: { id: "emoji_button.search", defaultMessage: "Search..." },
custom: { id: "emoji_button.custom", defaultMessage: "Custom" },
recent: { id: "emoji_button.recent", defaultMessage: "Frequently used" },
search_results: { id: "emoji_button.search_results", defaultMessage: "Search results" },
people: { id: "emoji_button.people", defaultMessage: "People" },
nature: { id: "emoji_button.nature", defaultMessage: "Nature" },
food: { id: "emoji_button.food", defaultMessage: "Food & Drink" },
activity: { id: "emoji_button.activity", defaultMessage: "Activity" },
travel: { id: "emoji_button.travel", defaultMessage: "Travel & Places" },
objects: { id: "emoji_button.objects", defaultMessage: "Objects" },
symbols: { id: "emoji_button.symbols", defaultMessage: "Symbols" },
flags: { id: "emoji_button.flags", defaultMessage: "Flags" },
});
let EmojiPicker, Emoji; // load asynchronously
@@ -62,7 +62,7 @@ class ModifierPickerMenu extends PureComponent {
};
handleClick = e => {
this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
this.props.onSelect(e.currentTarget.getAttribute("data-index") * 1);
};
UNSAFE_componentWillReceiveProps (nextProps) {
@@ -84,13 +84,13 @@ class ModifierPickerMenu extends PureComponent {
};
attachListeners () {
document.addEventListener('click', this.handleDocumentClick, { capture: true });
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.addEventListener("click", this.handleDocumentClick, { capture: true });
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
removeListeners () {
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
setRef = c => {
@@ -101,7 +101,7 @@ class ModifierPickerMenu extends PureComponent {
const { active } = this.props;
return (
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? "block" : "none" }} ref={this.setRef}>
<button type='button' onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
@@ -182,23 +182,25 @@ class EmojiPickerMenuImpl extends PureComponent {
};
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, { capture: true });
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.addEventListener("click", this.handleDocumentClick, { capture: true });
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
// to wait for a frame before focusing
requestAnimationFrame(() => {
this.setState({ readyToFocus: true });
if (this.node) {
const element = this.node.querySelector('input[type="search"]');
if (element) element.focus();
const element = this.node.querySelector("input[type=\"search\"]");
if (element) {
element.focus();
}
}
});
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
setRef = c => {
@@ -260,21 +262,21 @@ class EmojiPickerMenuImpl extends PureComponent {
const { modifierOpen } = this.state;
const categoriesSort = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
"recent",
"people",
"nature",
"foods",
"activity",
"places",
"objects",
"symbols",
"flags",
];
categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());
return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<div className={classNames("emoji-picker-dropdown__menu", { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker
perLine={8}
emojiSize={22}
@@ -355,7 +357,7 @@ class EmojiPickerDropdown extends PureComponent {
};
onToggle = (e) => {
if (!this.state.loading && (!e.key || e.key === 'Enter')) {
if (!this.state.loading && (!e.key || e.key === "Enter")) {
if (this.state.active) {
this.onHideDropdown();
} else {
@@ -365,7 +367,7 @@ class EmojiPickerDropdown extends PureComponent {
};
handleKeyDown = e => {
if (e.key === 'Escape') {
if (e.key === "Escape") {
this.onHideDropdown();
}
};
@@ -387,13 +389,13 @@ class EmojiPickerDropdown extends PureComponent {
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
{button || <img
className={classNames('emojione', { 'pulse-loading': active && loading })}
className={classNames("emojione", { "pulse-loading": active && loading })}
alt='🙂'
src={`${assetHost}/emoji/1f642.svg`}
/>}
</div>
<Overlay show={active} placement={'bottom'} target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
<Overlay show={active} placement={"bottom"} target={this.findTarget} popperConfig={{ strategy: "fixed" }}>
{({ props, placement })=> (
<div {...props} style={{ ...props.style, width: 299 }}>
<div className={`dropdown-animation ${placement}`}>
@@ -1,23 +1,23 @@
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 { supportsPassiveEvents } from 'detect-passive-events';
import fuzzysort from 'fuzzysort';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from "detect-passive-events";
import fuzzysort from "fuzzysort";
import Overlay from "react-overlays/Overlay";
import { languages as preloadedLanguages } from 'mastodon/initial_state';
import { loupeIcon, deleteIcon } from 'mastodon/utils/icons';
import { languages as preloadedLanguages } from "mastodon/initial_state";
import { loupeIcon, deleteIcon } from "mastodon/utils/icons";
import TextIconButton from './text_icon_button';
import TextIconButton from "./text_icon_button";
const messages = defineMessages({
changeLanguage: { id: 'compose.language.change', defaultMessage: 'Change language' },
search: { id: 'compose.language.search', defaultMessage: 'Search languages...' },
clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
changeLanguage: { id: "compose.language.change", defaultMessage: "Change language" },
search: { id: "compose.language.search", defaultMessage: "Search languages..." },
clear: { id: "emoji_button.clear", defaultMessage: "Clear" },
});
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
@@ -38,7 +38,7 @@ class LanguageDropdownMenu extends PureComponent {
};
state = {
searchValue: '',
searchValue: "",
};
handleDocumentClick = e => {
@@ -49,22 +49,24 @@ class LanguageDropdownMenu extends PureComponent {
};
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, { capture: true });
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.addEventListener("click", this.handleDocumentClick, { capture: true });
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
// to wait for a frame before focusing
requestAnimationFrame(() => {
if (this.node) {
const element = this.node.querySelector('input[type="search"]');
if (element) element.focus();
const element = this.node.querySelector("input[type=\"search\"]");
if (element) {
element.focus();
}
}
});
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
setRef = c => {
@@ -83,7 +85,7 @@ class LanguageDropdownMenu extends PureComponent {
const { languages, value, frequentlyUsedLanguages } = this.props;
const { searchValue } = this.state;
if (searchValue === '') {
if (searchValue === "") {
return [...languages].sort((a, b) => {
// Push current selection to the top of the list
@@ -103,7 +105,7 @@ class LanguageDropdownMenu extends PureComponent {
}
return fuzzysort.go(searchValue, languages, {
keys: ['0', '1', '2'],
keys: ["0", "1", "2"],
limit: 5,
threshold: -10000,
}).map(result => result.obj);
@@ -122,7 +124,7 @@ class LanguageDropdownMenu extends PureComponent {
}
handleClick = e => {
const value = e.currentTarget.getAttribute('data-index');
const value = e.currentTarget.getAttribute("data-index");
e.preventDefault();
@@ -137,31 +139,31 @@ class LanguageDropdownMenu extends PureComponent {
let element = null;
switch(e.key) {
case 'Escape':
onClose();
break;
case 'Enter':
this.handleClick(e);
break;
case 'ArrowDown':
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
break;
case 'ArrowUp':
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
break;
case 'Tab':
if (e.shiftKey) {
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
} else {
case "Escape":
onClose();
break;
case "Enter":
this.handleClick(e);
break;
case "ArrowDown":
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
}
break;
case 'Home':
element = this.listNode.firstChild;
break;
case 'End':
element = this.listNode.lastChild;
break;
break;
case "ArrowUp":
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
break;
case "Tab":
if (e.shiftKey) {
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
} else {
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
}
break;
case "Home":
element = this.listNode.firstChild;
break;
case "End":
element = this.listNode.lastChild;
break;
}
if (element) {
@@ -178,44 +180,44 @@ class LanguageDropdownMenu extends PureComponent {
let element = null;
switch(e.key) {
case 'Tab':
case 'ArrowDown':
element = this.listNode.firstChild;
case "Tab":
case "ArrowDown":
element = this.listNode.firstChild;
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
break;
case 'Enter':
element = this.listNode.firstChild;
break;
case "Enter":
element = this.listNode.firstChild;
if (element) {
onChange(element.getAttribute('data-index'));
onClose();
}
break;
case 'Escape':
if (searchValue !== '') {
e.preventDefault();
this.handleClear();
}
if (element) {
onChange(element.getAttribute("data-index"));
onClose();
}
break;
case "Escape":
if (searchValue !== "") {
e.preventDefault();
this.handleClear();
}
break;
break;
}
};
handleClear = () => {
this.setState({ searchValue: '' });
this.setState({ searchValue: "" });
};
renderItem = lang => {
const { value } = this.props;
return (
<div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
<div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames("language-dropdown__dropdown__results__item", { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
<span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
</div>
);
@@ -224,7 +226,7 @@ class LanguageDropdownMenu extends PureComponent {
render () {
const { intl } = this.props;
const { searchValue } = this.state;
const isSearching = searchValue !== '';
const isSearching = searchValue !== "";
const results = this.search();
return (
@@ -255,7 +257,7 @@ class LanguageDropdown extends PureComponent {
state = {
open: false,
placement: 'bottom',
placement: "bottom",
};
handleToggle = () => {
@@ -299,7 +301,7 @@ class LanguageDropdown extends PureComponent {
const { open, placement } = this.state;
return (
<div className={classNames('privacy-dropdown', placement, { active: open })}>
<div className={classNames("privacy-dropdown", placement, { active: open })}>
<div className='privacy-dropdown__value' ref={this.setTargetRef} >
<TextIconButton
className='privacy-dropdown__value-icon'
@@ -310,7 +312,7 @@ class LanguageDropdown extends PureComponent {
/>
</div>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
<Overlay show={open} placement={"bottom"} flip target={this.findTarget} popperConfig={{ strategy: "fixed", onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
@@ -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 { Link } from 'react-router-dom';
import { Link } from "react-router-dom";
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 { Avatar } from '../../../components/avatar';
import { Avatar } from "../../../components/avatar";
import ActionBar from './action_bar';
import ActionBar from "./action_bar";
export default class NavigationBar extends ImmutablePureComponent {
@@ -20,11 +20,11 @@ export default class NavigationBar extends ImmutablePureComponent {
};
render () {
const username = this.props.account.get('acct');
const username = this.props.account.get("acct");
return (
<div className='navigation-bar'>
<Link to={`/@${username}`}>
<span style={{ display: 'none' }}>{username}</span>
<span style={{ display: "none" }}>{username}</span>
<Avatar account={this.props.account} size={46} />
</Link>
@@ -1,18 +1,18 @@
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 { IconButton } from '../../../components/icon_button';
import { IconButton } from "../../../components/icon_button";
const messages = defineMessages({
add_poll: { id: 'poll_button.add_poll', defaultMessage: 'Add a poll' },
remove_poll: { id: 'poll_button.remove_poll', defaultMessage: 'Remove poll' },
add_poll: { id: "poll_button.add_poll", defaultMessage: "Add a poll" },
remove_poll: { id: "poll_button.remove_poll", defaultMessage: "Remove poll" },
});
const iconStyle = {
height: null,
lineHeight: '27px',
lineHeight: "27px",
};
class PollButton extends PureComponent {
@@ -43,7 +43,7 @@ class PollButton extends PureComponent {
title={intl.formatMessage(active ? messages.remove_poll : messages.add_poll)}
disabled={disabled}
onClick={this.handleClick}
className={`compose-form__poll-button-icon ${active ? 'active' : ''}`}
className={`compose-form__poll-button-icon ${active ? "active" : ""}`}
size={18}
inverted
style={iconStyle}
@@ -1,27 +1,27 @@
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 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 AutosuggestInput from 'mastodon/components/autosuggest_input';
import { Icon } from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
import AutosuggestInput from "mastodon/components/autosuggest_input";
import { Icon } from "mastodon/components/icon";
import { IconButton } from "mastodon/components/icon_button";
const messages = defineMessages({
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },
add_option: { id: 'compose_form.poll.add_option', defaultMessage: 'Add a choice' },
remove_option: { id: 'compose_form.poll.remove_option', defaultMessage: 'Remove this choice' },
poll_duration: { id: 'compose_form.poll.duration', defaultMessage: 'Poll duration' },
switchToMultiple: { id: 'compose_form.poll.switch_to_multiple', defaultMessage: 'Change poll to allow multiple choices' },
switchToSingle: { id: 'compose_form.poll.switch_to_single', defaultMessage: 'Change poll to allow for a single choice' },
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
option_placeholder: { id: "compose_form.poll.option_placeholder", defaultMessage: "Choice {number}" },
add_option: { id: "compose_form.poll.add_option", defaultMessage: "Add a choice" },
remove_option: { id: "compose_form.poll.remove_option", defaultMessage: "Remove this choice" },
poll_duration: { id: "compose_form.poll.duration", defaultMessage: "Poll duration" },
switchToMultiple: { id: "compose_form.poll.switch_to_multiple", defaultMessage: "Change poll to allow multiple choices" },
switchToSingle: { id: "compose_form.poll.switch_to_single", defaultMessage: "Change poll to allow for a single choice" },
minutes: { id: "intervals.full.minutes", defaultMessage: "{number, plural, one {# minute} other {# minutes}}" },
hours: { id: "intervals.full.hours", defaultMessage: "{number, plural, one {# hour} other {# hours}}" },
days: { id: "intervals.full.days", defaultMessage: "{number, plural, one {# day} other {# days}}" },
});
class OptionIntl extends PureComponent {
@@ -58,7 +58,7 @@ class OptionIntl extends PureComponent {
};
handleCheckboxKeypress = e => {
if (e.key === 'Enter' || e.key === ' ') {
if (e.key === "Enter" || e.key === " ") {
this.handleToggleMultiple(e);
}
};
@@ -72,7 +72,7 @@ class OptionIntl extends PureComponent {
};
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
this.props.onSuggestionSelected(tokenStart, token, value, ["poll", "options", this.props.index]);
};
render () {
@@ -82,7 +82,7 @@ class OptionIntl extends PureComponent {
<li>
<label className='poll__option editable'>
<span
className={classNames('poll__input', { checkbox: isPollMultiple })}
className={classNames("poll__input", { checkbox: isPollMultiple })}
onClick={this.handleToggleMultiple}
onKeyPress={this.handleCheckboxKeypress}
role='button'
@@ -102,7 +102,7 @@ class OptionIntl extends PureComponent {
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
searchTokens={[':']}
searchTokens={[":"]}
autoFocus={autoFocus}
/>
</label>
@@ -137,7 +137,7 @@ class PollForm extends ImmutablePureComponent {
};
handleAddOption = () => {
this.props.onAddOption('');
this.props.onAddOption("");
};
handleSelectDuration = e => {
@@ -155,7 +155,7 @@ class PollForm extends ImmutablePureComponent {
return null;
}
const autoFocusIndex = options.indexOf('');
const autoFocusIndex = options.indexOf("");
return (
<div className='compose-form__poll-wrapper'>
@@ -1,27 +1,27 @@
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 { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from "detect-passive-events";
import Overlay from "react-overlays/Overlay";
import { Icon } from 'mastodon/components/icon';
import { Icon } from "mastodon/components/icon";
import { IconButton } from '../../../components/icon_button';
import { IconButton } from "../../../components/icon_button";
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
public_short: { id: "privacy.public.short", defaultMessage: "Public" },
public_long: { id: "privacy.public.long", defaultMessage: "Visible for all" },
unlisted_short: { id: "privacy.unlisted.short", defaultMessage: "Unlisted" },
unlisted_long: { id: "privacy.unlisted.long", defaultMessage: "Visible for all, but opted-out of discovery features" },
private_short: { id: "privacy.private.short", defaultMessage: "Followers only" },
private_long: { id: "privacy.private.long", defaultMessage: "Visible for followers only" },
direct_short: { id: "privacy.direct.short", defaultMessage: "Mentioned people only" },
direct_long: { id: "privacy.direct.long", defaultMessage: "Visible for mentioned users only" },
change_privacy: { id: "privacy.change", defaultMessage: "Adjust status privacy" },
});
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
@@ -45,50 +45,50 @@ class PrivacyDropdownMenu extends PureComponent {
handleKeyDown = e => {
const { items } = this.props;
const value = e.currentTarget.getAttribute('data-index');
const value = e.currentTarget.getAttribute("data-index");
const index = items.findIndex(item => {
return (item.value === value);
});
let element = null;
switch(e.key) {
case 'Escape':
this.props.onClose();
break;
case 'Enter':
this.handleClick(e);
break;
case 'ArrowDown':
element = this.node.childNodes[index + 1] || this.node.firstChild;
break;
case 'ArrowUp':
element = this.node.childNodes[index - 1] || this.node.lastChild;
break;
case 'Tab':
if (e.shiftKey) {
element = this.node.childNodes[index - 1] || this.node.lastChild;
} else {
case "Escape":
this.props.onClose();
break;
case "Enter":
this.handleClick(e);
break;
case "ArrowDown":
element = this.node.childNodes[index + 1] || this.node.firstChild;
}
break;
case 'Home':
element = this.node.firstChild;
break;
case 'End':
element = this.node.lastChild;
break;
break;
case "ArrowUp":
element = this.node.childNodes[index - 1] || this.node.lastChild;
break;
case "Tab":
if (e.shiftKey) {
element = this.node.childNodes[index - 1] || this.node.lastChild;
} else {
element = this.node.childNodes[index + 1] || this.node.firstChild;
}
break;
case "Home":
element = this.node.firstChild;
break;
case "End":
element = this.node.lastChild;
break;
}
if (element) {
element.focus();
this.props.onChange(element.getAttribute('data-index'));
this.props.onChange(element.getAttribute("data-index"));
e.preventDefault();
e.stopPropagation();
}
};
handleClick = e => {
const value = e.currentTarget.getAttribute('data-index');
const value = e.currentTarget.getAttribute("data-index");
e.preventDefault();
@@ -97,14 +97,16 @@ class PrivacyDropdownMenu extends PureComponent {
};
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, { capture: true });
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
document.addEventListener("click", this.handleDocumentClick, { capture: true });
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
if (this.focusedItem) {
this.focusedItem.focus({ preventScroll: true });
}
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
}
setRef = c => {
@@ -121,7 +123,7 @@ class PrivacyDropdownMenu extends PureComponent {
return (
<div style={{ ...style }} role='listbox' ref={this.setRef}>
{items.map(item => (
<div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames("privacy-dropdown__option", { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div className='privacy-dropdown__option__icon'>
<Icon id={item.icon} fixedWidth />
</div>
@@ -154,7 +156,7 @@ class PrivacyDropdown extends PureComponent {
state = {
open: false,
placement: 'bottom',
placement: "bottom",
};
handleToggle = () => {
@@ -178,7 +180,7 @@ class PrivacyDropdown extends PureComponent {
handleModalActionClick = (e) => {
e.preventDefault();
const { value } = this.options[e.currentTarget.getAttribute('data-index')];
const { value } = this.options[e.currentTarget.getAttribute("data-index")];
this.props.onModalClose();
this.props.onChange(value);
@@ -186,9 +188,9 @@ class PrivacyDropdown extends PureComponent {
handleKeyDown = e => {
switch(e.key) {
case 'Escape':
this.handleClose();
break;
case "Escape":
this.handleClose();
break;
}
};
@@ -200,10 +202,10 @@ class PrivacyDropdown extends PureComponent {
handleButtonKeyDown = (e) => {
switch(e.key) {
case ' ':
case 'Enter':
this.handleMouseDown();
break;
case " ":
case "Enter":
this.handleMouseDown();
break;
}
};
@@ -222,14 +224,14 @@ class PrivacyDropdown extends PureComponent {
const { intl: { formatMessage } } = this.props;
this.options = [
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
{ icon: "globe", value: "public", text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: "unlock", value: "unlisted", text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: "lock", value: "private", text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
];
if (!this.props.noDirect) {
this.options.push(
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
{ icon: "at", value: "direct", text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
);
}
}
@@ -253,8 +255,8 @@ class PrivacyDropdown extends PureComponent {
const valueOption = this.options.find(item => item.value === value);
return (
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === (placement === 'bottom' ? 0 : (this.options.length - 1)) })} ref={this.setTargetRef}>
<div className={classNames("privacy-dropdown", placement, { active: open })} onKeyDown={this.handleKeyDown}>
<div className={classNames("privacy-dropdown__value", { active: this.options.indexOf(valueOption) === (placement === "bottom" ? 0 : (this.options.length - 1)) })} ref={this.setTargetRef}>
<IconButton
className='privacy-dropdown__value-icon'
icon={valueOption.icon}
@@ -266,12 +268,12 @@ class PrivacyDropdown extends PureComponent {
onClick={this.handleToggle}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
style={{ height: null, lineHeight: '27px' }}
style={{ height: null, lineHeight: "27px" }}
disabled={disabled}
/>
</div>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} container={container} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
<Overlay show={open} placement={"bottom"} flip target={this.findTarget} container={container} popperConfig={{ strategy: "fixed", onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
@@ -1,18 +1,18 @@
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 AttachmentList from 'mastodon/components/attachment_list';
import AttachmentList from "mastodon/components/attachment_list";
import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import { IconButton } from '../../../components/icon_button';
import { Avatar } from "../../../components/avatar";
import { DisplayName } from "../../../components/display_name";
import { IconButton } from "../../../components/icon_button";
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
cancel: { id: "reply_indicator.cancel", defaultMessage: "Cancel" },
});
class ReplyIndicator extends ImmutablePureComponent {
@@ -34,7 +34,7 @@ class ReplyIndicator extends ImmutablePureComponent {
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
this.context.router.history.push(`/@${this.props.status.getIn(["account", "acct"])}`);
}
};
@@ -45,25 +45,25 @@ class ReplyIndicator extends ImmutablePureComponent {
return null;
}
const content = { __html: status.get('contentHtml') };
const content = { __html: status.get("contentHtml") };
return (
<div className='reply-indicator'>
<div className='reply-indicator__header'>
<div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /></div>
<a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
<div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>
<DisplayName account={status.get('account')} />
<a href={`/@${status.getIn(["account", "acct"])}`} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
<div className='reply-indicator__display-avatar'><Avatar account={status.get("account")} size={24} /></div>
<DisplayName account={status.get("account")} />
</a>
</div>
<div className='reply-indicator__content translate' dangerouslySetInnerHTML={content} />
{status.get('media_attachments').size > 0 && (
{status.get("media_attachments").size > 0 && (
<AttachmentList
compact
media={status.get('media_attachments')}
media={status.get("media_attachments")}
/>
)}
</div>
@@ -1,29 +1,29 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage, FormattedList } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePropTypes from "react-immutable-proptypes";
import { Icon } from 'mastodon/components/icon';
import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { Icon } from "mastodon/components/icon";
import { domain, searchEnabled } from "mastodon/initial_state";
import { HASHTAG_REGEX } from "mastodon/utils/hashtags";
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' },
placeholder: { id: "search.placeholder", defaultMessage: "Search" },
placeholderSignedIn: { id: "search.search_or_paste", defaultMessage: "Search or paste URL" },
});
const labelForRecentSearch = search => {
switch(search.get('type')) {
case 'account':
return `@${search.get('q')}`;
case 'hashtag':
return `#${search.get('q')}`;
default:
return search.get('q');
switch(search.get("type")) {
case "account":
return `@${search.get("q")}`;
case "hashtag":
return `#${search.get("q")}`;
default:
return search.get("q");
}
};
@@ -57,14 +57,30 @@ class Search extends PureComponent {
};
defaultOptions = [
{ label: <><mark>has:</mark> <FormattedList type='disjunction' value={['media', 'poll', 'embed']} /></>, action: e => { e.preventDefault(); this._insertText('has:'); } },
{ label: <><mark>is:</mark> <FormattedList type='disjunction' value={['reply', 'sensitive']} /></>, action: e => { e.preventDefault(); this._insertText('is:'); } },
{ label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => { e.preventDefault(); this._insertText('language:'); } },
{ label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => { e.preventDefault(); this._insertText('from:'); } },
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:'); } },
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:'); } },
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:'); } },
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library']} /></>, action: e => { e.preventDefault(); this._insertText('in:'); } }
{ label: <><mark>has:</mark> <FormattedList type='disjunction' value={["media", "poll", "embed"]} /></>, action: e => {
e.preventDefault(); this._insertText("has:");
} },
{ label: <><mark>is:</mark> <FormattedList type='disjunction' value={["reply", "sensitive"]} /></>, action: e => {
e.preventDefault(); this._insertText("is:");
} },
{ label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => {
e.preventDefault(); this._insertText("language:");
} },
{ label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => {
e.preventDefault(); this._insertText("from:");
} },
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => {
e.preventDefault(); this._insertText("before:");
} },
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => {
e.preventDefault(); this._insertText("during:");
} },
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => {
e.preventDefault(); this._insertText("after:");
} },
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={["all", "library"]} /></>, action: e => {
e.preventDefault(); this._insertText("in:");
} },
];
setRef = c => {
@@ -95,48 +111,48 @@ class Search extends PureComponent {
const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
switch(e.key) {
case 'Escape':
e.preventDefault();
this._unfocus();
case "Escape":
e.preventDefault();
this._unfocus();
break;
case 'ArrowDown':
e.preventDefault();
break;
case "ArrowDown":
e.preventDefault();
if (options.length > 0) {
this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) });
}
break;
case 'ArrowUp':
e.preventDefault();
if (options.length > 0) {
this.setState({ selectedOption: Math.max(selectedOption - 1, -1) });
}
break;
case 'Enter':
e.preventDefault();
if (selectedOption === -1) {
this._submit();
} else if (options.length > 0) {
options[selectedOption].action(e);
}
break;
case 'Delete':
if (selectedOption > -1 && options.length > 0) {
const search = options[selectedOption];
if (typeof search.forget === 'function') {
e.preventDefault();
search.forget(e);
if (options.length > 0) {
this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) });
}
}
break;
break;
case "ArrowUp":
e.preventDefault();
if (options.length > 0) {
this.setState({ selectedOption: Math.max(selectedOption - 1, -1) });
}
break;
case "Enter":
e.preventDefault();
if (selectedOption === -1) {
this._submit();
} else if (options.length > 0) {
options[selectedOption].action(e);
}
break;
case "Delete":
if (selectedOption > -1 && options.length > 0) {
const search = options[selectedOption];
if (typeof search.forget === "function") {
e.preventDefault();
search.forget(e);
}
}
break;
}
};
@@ -163,10 +179,10 @@ class Search extends PureComponent {
const { router } = this.context;
const { value, onClickSearchResult } = this.props;
const query = value.trim().replace(/^#/, '');
const query = value.trim().replace(/^#/, "");
router.history.push(`/tags/${query}`);
onClickSearchResult(query, 'hashtag');
onClickSearchResult(query, "hashtag");
this._unfocus();
};
@@ -174,10 +190,10 @@ class Search extends PureComponent {
const { router } = this.context;
const { value, onClickSearchResult } = this.props;
const query = value.trim().replace(/^@/, '');
const query = value.trim().replace(/^@/, "");
router.history.push(`/@${query}`);
onClickSearchResult(query, 'account');
onClickSearchResult(query, "account");
this._unfocus();
};
@@ -190,24 +206,24 @@ class Search extends PureComponent {
};
handleStatusSearch = () => {
this._submit('statuses');
this._submit("statuses");
};
handleAccountSearch = () => {
this._submit('accounts');
this._submit("accounts");
};
handleRecentSearchClick = search => {
const { onChange } = this.props;
const { router } = this.context;
if (search.get('type') === 'account') {
router.history.push(`/@${search.get('q')}`);
} else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`);
if (search.get("type") === "account") {
router.history.push(`/@${search.get("q")}`);
} else if (search.get("type") === "hashtag") {
router.history.push(`/tags/${search.get("q")}`);
} else {
onChange(search.get('q'));
this._submit(search.get('type'));
onChange(search.get("q"));
this._submit(search.get("type"));
}
this._unfocus();
@@ -216,19 +232,19 @@ class Search extends PureComponent {
handleForgetRecentSearchClick = search => {
const { onForgetSearchResult } = this.props;
onForgetSearchResult(search.get('q'));
onForgetSearchResult(search.get("q"));
};
_unfocus () {
document.querySelector('.ui').parentElement.focus();
document.querySelector(".ui").parentElement.focus();
}
_insertText (text) {
const { value, onChange } = this.props;
if (value === '') {
if (value === "") {
onChange(text);
} else if (value[value.length - 1] === ' ') {
} else if (value[value.length - 1] === " ") {
onChange(`${value}${text}`);
} else {
onChange(`${value} ${text}`);
@@ -246,7 +262,7 @@ class Search extends PureComponent {
}
if (openInRoute) {
router.history.push('/search');
router.history.push("/search");
}
this._unfocus();
@@ -278,34 +294,34 @@ class Search extends PureComponent {
const options = [];
if (trimmedValue.length > 0) {
const couldBeURL = trimmedValue.startsWith('https://') && !trimmedValue.includes(' ');
const couldBeURL = trimmedValue.startsWith("https://") && !trimmedValue.includes(" ");
if (couldBeURL) {
options.push({ key: 'open-url', label: <FormattedMessage id='search.quick_action.open_url' defaultMessage='Open URL in Mastodon' />, action: this.handleURLClick });
options.push({ key: "open-url", label: <FormattedMessage id='search.quick_action.open_url' defaultMessage='Open URL in Mastodon' />, action: this.handleURLClick });
}
const couldBeHashtag = (trimmedValue.startsWith('#') && trimmedValue.length > 1) || trimmedValue.match(HASHTAG_REGEX);
const couldBeHashtag = (trimmedValue.startsWith("#") && trimmedValue.length > 1) || trimmedValue.match(HASHTAG_REGEX);
if (couldBeHashtag) {
options.push({ key: 'go-to-hashtag', label: <FormattedMessage id='search.quick_action.go_to_hashtag' defaultMessage='Go to hashtag {x}' values={{ x: <mark>#{trimmedValue.replace(/^#/, '')}</mark> }} />, action: this.handleHashtagClick });
options.push({ key: "go-to-hashtag", label: <FormattedMessage id='search.quick_action.go_to_hashtag' defaultMessage='Go to hashtag {x}' values={{ x: <mark>#{trimmedValue.replace(/^#/, "")}</mark> }} />, action: this.handleHashtagClick });
}
const couldBeUsername = trimmedValue.match(/^@?[a-z0-9_-]+(@[^\s]+)?$/i);
if (couldBeUsername) {
options.push({ key: 'go-to-account', label: <FormattedMessage id='search.quick_action.go_to_account' defaultMessage='Go to profile {x}' values={{ x: <mark>@{trimmedValue.replace(/^@/, '')}</mark> }} />, action: this.handleAccountClick });
options.push({ key: "go-to-account", label: <FormattedMessage id='search.quick_action.go_to_account' defaultMessage='Go to profile {x}' values={{ x: <mark>@{trimmedValue.replace(/^@/, "")}</mark> }} />, action: this.handleAccountClick });
}
const couldBeStatusSearch = searchEnabled;
if (couldBeStatusSearch) {
options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
options.push({ key: "status-search", label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
}
const couldBeUserSearch = true;
if (couldBeUserSearch) {
options.push({ key: 'account-search', label: <FormattedMessage id='search.quick_action.account_search' defaultMessage='Profiles matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleAccountSearch });
options.push({ key: "account-search", label: <FormattedMessage id='search.quick_action.account_search' defaultMessage='Profiles matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleAccountSearch });
}
}
@@ -320,7 +336,7 @@ class Search extends PureComponent {
const hasValue = value.length > 0 || submitted;
return (
<div className={classNames('search', { active: expanded })}>
<div className={classNames("search", { active: expanded })}>
<input
ref={this.setRef}
className='search__input'
@@ -335,8 +351,8 @@ class Search extends PureComponent {
/>
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
<Icon id='search' className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
<Icon id='search' className={hasValue ? "" : "active"} />
<Icon id='times-circle' className={hasValue ? "active" : ""} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
<div className='search__popout'>
@@ -346,7 +362,7 @@ class Search extends PureComponent {
<div className='search__popout__menu'>
{recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => (
<button key={label} onMouseDown={action} className={classNames('search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i })}>
<button key={label} onMouseDown={action} className={classNames("search__popout__menu__item search__popout__menu__item--flex", { selected: selectedOption === i })}>
<span>{label}</span>
<button className='icon-button' onMouseDown={forget}><Icon id='times' /></button>
</button>
@@ -365,7 +381,7 @@ class Search extends PureComponent {
<div className='search__popout__menu'>
{options.map(({ key, label, action }, i) => (
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === i })}>
<button key={key} onMouseDown={action} className={classNames("search__popout__menu__item", { selected: selectedOption === i })}>
{label}
</button>
))}
@@ -378,7 +394,7 @@ class Search extends PureComponent {
{searchEnabled ? (
<div className='search__popout__menu'>
{this.defaultOptions.map(({ key, label, action }, i) => (
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
<button key={key} onMouseDown={action} className={classNames("search__popout__menu__item", { selected: selectedOption === ((options.length || recent.size) + i) })}>
{label}
</button>
))}
@@ -1,17 +1,17 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } 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 { Icon } from 'mastodon/components/icon';
import { LoadMore } from 'mastodon/components/load_more';
import { SearchSection } from 'mastodon/features/explore/components/search_section';
import { Icon } from "mastodon/components/icon";
import { LoadMore } from "mastodon/components/load_more";
import { SearchSection } from "mastodon/features/explore/components/search_section";
import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container';
import { ImmutableHashtag as Hashtag } from "../../../components/hashtag";
import AccountContainer from "../../../containers/account_container";
import StatusContainer from "../../../containers/status_container";
const INITIAL_PAGE_LIMIT = 10;
@@ -31,40 +31,40 @@ class SearchResults extends ImmutablePureComponent {
searchTerm: PropTypes.string,
};
handleLoadMoreAccounts = () => this.props.expandSearch('accounts');
handleLoadMoreAccounts = () => this.props.expandSearch("accounts");
handleLoadMoreStatuses = () => this.props.expandSearch('statuses');
handleLoadMoreStatuses = () => this.props.expandSearch("statuses");
handleLoadMoreHashtags = () => this.props.expandSearch('hashtags');
handleLoadMoreHashtags = () => this.props.expandSearch("hashtags");
render () {
const { results } = this.props;
let accounts, statuses, hashtags;
if (results.get('accounts') && results.get('accounts').size > 0) {
if (results.get("accounts") && results.get("accounts").size > 0) {
accounts = (
<SearchSection title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
{withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
{(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
{withoutLastResult(results.get("accounts")).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
{(results.get("accounts").size > INITIAL_PAGE_LIMIT && results.get("accounts").size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
</SearchSection>
);
}
if (results.get('hashtags') && results.get('hashtags').size > 0) {
if (results.get("hashtags") && results.get("hashtags").size > 0) {
hashtags = (
<SearchSection title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>}>
{withoutLastResult(results.get('hashtags')).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
{(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
{withoutLastResult(results.get("hashtags")).map(hashtag => <Hashtag key={hashtag.get("name")} hashtag={hashtag} />)}
{(results.get("hashtags").size > INITIAL_PAGE_LIMIT && results.get("hashtags").size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
</SearchSection>
);
}
if (results.get('statuses') && results.get('statuses').size > 0) {
if (results.get("statuses") && results.get("statuses").size > 0) {
statuses = (
<SearchSection title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>}>
{withoutLastResult(results.get('statuses')).map(statusId => <StatusContainer key={statusId} id={statusId} />)}
{(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
{withoutLastResult(results.get("statuses")).map(statusId => <StatusContainer key={statusId} id={statusId} />)}
{(results.get("statuses").size > INITIAL_PAGE_LIMIT && results.get("statuses").size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
</SearchSection>
);
}
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
const iconStyle = {
height: null,
lineHeight: '27px',
lineHeight: "27px",
minWidth: `${18 * 1.28571429}px`,
};
@@ -25,7 +25,7 @@ export default class TextIconButton extends PureComponent {
type='button'
title={title}
aria-label={title}
className={`text-icon-button ${active ? 'active' : ''}`}
className={`text-icon-button ${active ? "active" : ""}`}
aria-expanded={active}
onClick={this.props.onClick}
aria-controls={ariaControls} style={iconStyle}
@@ -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 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 spring from 'react-motion/lib/spring';
import spring from "react-motion/lib/spring";
import { Icon } from 'mastodon/components/icon';
import { Icon } from "mastodon/components/icon";
import Motion from '../../ui/util/optional_motion';
import Motion from "../../ui/util/optional_motion";
export default class Upload extends ImmutablePureComponent {
@@ -25,12 +25,12 @@ export default class Upload extends ImmutablePureComponent {
handleUndoClick = e => {
e.stopPropagation();
this.props.onUndo(this.props.media.get('id'));
this.props.onUndo(this.props.media.get("id"));
};
handleFocalPointClick = e => {
e.stopPropagation();
this.props.onOpenFocalPoint(this.props.media.get('id'));
this.props.onOpenFocalPoint(this.props.media.get("id"));
};
render () {
@@ -40,8 +40,8 @@ export default class Upload extends ImmutablePureComponent {
return null;
}
const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']);
const focusX = media.getIn(["meta", "focus", "x"]);
const focusY = media.getIn(["meta", "focus", "y"]);
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
@@ -49,13 +49,13 @@ export default class Upload extends ImmutablePureComponent {
<div className='compose-form__upload'>
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get("preview_url")})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload__actions'>
<button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
</div>
{(media.get('description') || '').length === 0 && (
{(media.get("description") || "").length === 0 && (
<div className='compose-form__upload__warning'>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
</div>
@@ -1,20 +1,20 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { IconButton } from '../../../components/icon_button';
import { IconButton } from "../../../components/icon_button";
const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add images, a video or an audio file' },
upload: { id: "upload_button.label", defaultMessage: "Add images, a video or an audio file" },
});
const makeMapStateToProps = () => {
const mapStateToProps = state => ({
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
acceptContentTypes: state.getIn(["media_attachments", "accept_content_types"]),
});
return mapStateToProps;
@@ -22,7 +22,7 @@ const makeMapStateToProps = () => {
const iconStyle = {
height: null,
lineHeight: '27px',
lineHeight: "27px",
};
class UploadButton extends ImmutablePureComponent {
@@ -64,16 +64,16 @@ class UploadButton extends ImmutablePureComponent {
<div className='compose-form__upload-button'>
<IconButton icon='paperclip' title={message} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
<label>
<span style={{ display: 'none' }}>{message}</span>
<span style={{ display: "none" }}>{message}</span>
<input
key={resetFileKey}
ref={this.setRef}
type='file'
multiple
accept={acceptContentTypes.toArray().join(',')}
accept={acceptContentTypes.toArray().join(",")}
onChange={this.handleChange}
disabled={disabled}
style={{ display: 'none' }}
style={{ display: "none" }}
/>
</label>
</div>
@@ -1,9 +1,9 @@
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 SensitiveButtonContainer from '../containers/sensitive_button_container';
import UploadContainer from '../containers/upload_container';
import UploadProgressContainer from '../containers/upload_progress_container';
import SensitiveButtonContainer from "../containers/sensitive_button_container";
import UploadContainer from "../containers/upload_container";
import UploadProgressContainer from "../containers/upload_progress_container";
export default class UploadForm extends ImmutablePureComponent {
@@ -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 spring from 'react-motion/lib/spring';
import spring from "react-motion/lib/spring";
import { Icon } from 'mastodon/components/icon';
import { Icon } from "mastodon/components/icon";
import Motion from '../../ui/util/optional_motion';
import Motion from "../../ui/util/optional_motion";
export default class UploadProgress extends PureComponent {
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import spring from 'react-motion/lib/spring';
import spring from "react-motion/lib/spring";
import Motion from '../../ui/util/optional_motion';
import Motion from "../../ui/util/optional_motion";
export default class Warning extends PureComponent {
@@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { makeGetAccount } from '../../../selectors';
import AutosuggestAccount from '../components/autosuggest_account';
import { makeGetAccount } from "../../../selectors";
import AutosuggestAccount from "../components/autosuggest_account";
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
@@ -1,4 +1,4 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import {
changeCompose,
@@ -9,25 +9,25 @@ import {
changeComposeSpoilerText,
insertEmojiCompose,
uploadCompose,
} from '../../../actions/compose';
import ComposeForm from '../components/compose_form';
} from "../../../actions/compose";
import ComposeForm from "../components/compose_form";
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
suggestions: state.getIn(['compose', 'suggestions']),
spoiler: state.getIn(['compose', 'spoiler']),
spoilerText: state.getIn(['compose', 'spoiler_text']),
privacy: state.getIn(['compose', 'privacy']),
focusDate: state.getIn(['compose', 'focusDate']),
caretPosition: state.getIn(['compose', 'caretPosition']),
preselectDate: state.getIn(['compose', 'preselectDate']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
isEditing: state.getIn(['compose', 'id']) !== null,
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
text: state.getIn(["compose", "text"]),
suggestions: state.getIn(["compose", "suggestions"]),
spoiler: state.getIn(["compose", "spoiler"]),
spoilerText: state.getIn(["compose", "spoiler_text"]),
privacy: state.getIn(["compose", "privacy"]),
focusDate: state.getIn(["compose", "focusDate"]),
caretPosition: state.getIn(["compose", "caretPosition"]),
preselectDate: state.getIn(["compose", "preselectDate"]),
isSubmitting: state.getIn(["compose", "is_submitting"]),
isEditing: state.getIn(["compose", "id"]) !== null,
isChangingUpload: state.getIn(["compose", "is_changing_upload"]),
isUploading: state.getIn(["compose", "is_uploading"]),
anyMedia: state.getIn(["compose", "media_attachments"]).size > 0,
isInReply: state.getIn(["compose", "in_reply_to"]) !== null,
lang: state.getIn(["compose", "language"]),
});
const mapDispatchToProps = (dispatch) => ({
@@ -1,35 +1,35 @@
import { Map as ImmutableMap } from 'immutable';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from "immutable";
import { connect } from "react-redux";
import { createSelector } from "reselect";
import { useEmoji } from '../../../actions/emojis';
import { changeSetting } from '../../../actions/settings';
import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
import { useEmoji } from "../../../actions/emojis";
import { changeSetting } from "../../../actions/settings";
import EmojiPickerDropdown from "../components/emoji_picker_dropdown";
const perLine = 8;
const lines = 2;
const DEFAULTS = [
'+1',
'grinning',
'kissing_heart',
'heart_eyes',
'laughing',
'stuck_out_tongue_winking_eye',
'sweat_smile',
'joy',
'yum',
'disappointed',
'thinking_face',
'weary',
'sob',
'sunglasses',
'heart',
'ok_hand',
"+1",
"grinning",
"kissing_heart",
"heart_eyes",
"laughing",
"stuck_out_tongue_winking_eye",
"sweat_smile",
"joy",
"yum",
"disappointed",
"thinking_face",
"weary",
"sob",
"sunglasses",
"heart",
"ok_hand",
];
const getFrequentlyUsedEmojis = createSelector([
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
state => state.getIn(["settings", "frequentlyUsedEmojis"], ImmutableMap()),
], emojiCounters => {
let emojis = emojiCounters
.keySeq()
@@ -47,10 +47,10 @@ const getFrequentlyUsedEmojis = createSelector([
});
const getCustomEmojis = createSelector([
state => state.get('custom_emojis'),
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
const aShort = a.get('shortcode').toLowerCase();
const bShort = b.get('shortcode').toLowerCase();
state => state.get("custom_emojis"),
], emojis => emojis.filter(e => e.get("visible_in_picker")).sort((a, b) => {
const aShort = a.get("shortcode").toLowerCase();
const bShort = b.get("shortcode").toLowerCase();
if (aShort < bShort) {
return -1;
@@ -63,17 +63,17 @@ const getCustomEmojis = createSelector([
const mapStateToProps = state => ({
custom_emojis: getCustomEmojis(state),
skinTone: state.getIn(['settings', 'skinTone']),
skinTone: state.getIn(["settings", "skinTone"]),
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
onSkinTone: skinTone => {
dispatch(changeSetting(['skinTone'], skinTone));
dispatch(changeSetting(["skinTone"], skinTone));
},
onPickEmoji: emoji => {
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
// eslint-disable-next-line react-hooks/rules-of-hooks
dispatch(useEmoji(emoji));
if (onPickEmoji) {
@@ -1,14 +1,14 @@
import { Map as ImmutableMap } from 'immutable';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from "immutable";
import { connect } from "react-redux";
import { createSelector } from "reselect";
import { changeComposeLanguage } from 'mastodon/actions/compose';
import { useLanguage } from 'mastodon/actions/languages';
import { changeComposeLanguage } from "mastodon/actions/compose";
import { useLanguage } from "mastodon/actions/languages";
import LanguageDropdown from '../components/language_dropdown';
import LanguageDropdown from "../components/language_dropdown";
const getFrequentlyUsedLanguages = createSelector([
state => state.getIn(['settings', 'frequentlyUsedLanguages'], ImmutableMap()),
state => state.getIn(["settings", "frequentlyUsedLanguages"], ImmutableMap()),
], languageCounters => (
languageCounters.keySeq()
.sort((a, b) => languageCounters.get(a) - languageCounters.get(b))
@@ -18,7 +18,7 @@ const getFrequentlyUsedLanguages = createSelector([
const mapStateToProps = state => ({
frequentlyUsedLanguages: getFrequentlyUsedLanguages(state),
value: state.getIn(['compose', 'language']),
value: state.getIn(["compose", "language"]),
});
const mapDispatchToProps = dispatch => ({
@@ -28,7 +28,7 @@ const mapDispatchToProps = dispatch => ({
},
onClose (value) {
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
// eslint-disable-next-line react-hooks/rules-of-hooks
dispatch(useLanguage(value));
},
@@ -1,28 +1,28 @@
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { openModal } from 'mastodon/actions/modal';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from "mastodon/actions/modal";
import { logOut } from "mastodon/utils/log_out";
import { me } from '../../../initial_state';
import NavigationBar from '../components/navigation_bar';
import { me } from "../../../initial_state";
import NavigationBar from "../components/navigation_bar";
const messages = defineMessages({
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
logoutMessage: { id: "confirmations.logout.message", defaultMessage: "Are you sure you want to log out?" },
logoutConfirm: { id: "confirmations.logout.confirm", defaultMessage: "Log out" },
});
const mapStateToProps = state => {
return {
account: state.getIn(['accounts', me]),
account: state.getIn(["accounts", me]),
};
};
const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
@@ -1,18 +1,18 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { addPoll, removePoll } from '../../../actions/compose';
import PollButton from '../components/poll_button';
import { addPoll, removePoll } from "../../../actions/compose";
import PollButton from "../components/poll_button";
const mapStateToProps = state => ({
unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
active: state.getIn(['compose', 'poll']) !== null,
unavailable: state.getIn(["compose", "is_uploading"]) || (state.getIn(["compose", "media_attachments"]).size > 0),
active: state.getIn(["compose", "poll"]) !== null,
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
if (getState().getIn(["compose", "poll"])) {
dispatch(removePoll());
} else {
dispatch(addPoll());
@@ -1,4 +1,4 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import {
addPollOption,
@@ -8,15 +8,15 @@ import {
clearComposeSuggestions,
fetchComposeSuggestions,
selectComposeSuggestion,
} from '../../../actions/compose';
import PollForm from '../components/poll_form';
} from "../../../actions/compose";
import PollForm from "../components/poll_form";
const mapStateToProps = state => ({
suggestions: state.getIn(['compose', 'suggestions']),
options: state.getIn(['compose', 'poll', 'options']),
lang: state.getIn(['compose', 'language']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
suggestions: state.getIn(["compose", "suggestions"]),
options: state.getIn(["compose", "poll", "options"]),
lang: state.getIn(["compose", "language"]),
expiresIn: state.getIn(["compose", "poll", "expires_in"]),
isMultiple: state.getIn(["compose", "poll", "multiple"]),
});
const mapDispatchToProps = dispatch => ({
@@ -1,12 +1,12 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { changeComposeVisibility } from '../../../actions/compose';
import { openModal, closeModal } from '../../../actions/modal';
import { isUserTouching } from '../../../is_mobile';
import PrivacyDropdown from '../components/privacy_dropdown';
import { changeComposeVisibility } from "../../../actions/compose";
import { openModal, closeModal } from "../../../actions/modal";
import { isUserTouching } from "../../../is_mobile";
import PrivacyDropdown from "../components/privacy_dropdown";
const mapStateToProps = state => ({
value: state.getIn(['compose', 'privacy']),
value: state.getIn(["compose", "privacy"]),
});
const mapDispatchToProps = dispatch => ({
@@ -17,7 +17,7 @@ const mapDispatchToProps = dispatch => ({
isUserTouching,
onModalOpen: props => dispatch(openModal({
modalType: 'ACTIONS',
modalType: "ACTIONS",
modalProps: props,
})),
onModalClose: () => dispatch(closeModal({
@@ -1,18 +1,18 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { cancelReplyCompose } from '../../../actions/compose';
import { makeGetStatus } from '../../../selectors';
import ReplyIndicator from '../components/reply_indicator';
import { cancelReplyCompose } from "../../../actions/compose";
import { makeGetStatus } from "../../../selectors";
import ReplyIndicator from "../components/reply_indicator";
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = state => {
let statusId = state.getIn(['compose', 'id'], null);
let statusId = state.getIn(["compose", "id"], null);
let editing = true;
if (statusId === null) {
statusId = state.getIn(['compose', 'in_reply_to']);
statusId = state.getIn(["compose", "in_reply_to"]);
editing = false;
}
@@ -1,4 +1,4 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import {
changeSearch,
@@ -8,14 +8,14 @@ import {
openURL,
clickSearchResult,
forgetSearchResult,
} from 'mastodon/actions/search';
} from "mastodon/actions/search";
import Search from '../components/search';
import Search from "../components/search";
const mapStateToProps = state => ({
value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']),
recent: state.getIn(['search', 'recent']).reverse(),
value: state.getIn(["search", "value"]),
submitted: state.getIn(["search", "submitted"]),
recent: state.getIn(["search", "recent"]).reverse(),
});
const mapDispatchToProps = dispatch => ({
@@ -1,20 +1,20 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { expandSearch } from 'mastodon/actions/search';
import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions';
import { expandSearch } from "mastodon/actions/search";
import { fetchSuggestions, dismissSuggestion } from "mastodon/actions/suggestions";
import SearchResults from '../components/search_results';
import SearchResults from "../components/search_results";
const mapStateToProps = state => ({
results: state.getIn(['search', 'results']),
suggestions: state.getIn(['suggestions', 'items']),
searchTerm: state.getIn(['search', 'searchTerm']),
results: state.getIn(["search", "results"]),
suggestions: state.getIn(["suggestions", "items"]),
searchTerm: state.getIn(["search", "searchTerm"]),
});
const mapDispatchToProps = dispatch => ({
fetchSuggestions: () => dispatch(fetchSuggestions()),
expandSearch: type => dispatch(expandSearch(type)),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get("id"))),
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchResults);
@@ -1,29 +1,29 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
import classNames from 'classnames';
import classNames from "classnames";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { changeComposeSensitivity } from 'mastodon/actions/compose';
import { changeComposeSensitivity } from "mastodon/actions/compose";
const messages = defineMessages({
marked: {
id: 'compose_form.sensitive.marked',
defaultMessage: '{count, plural, one {Media is marked as sensitive} other {Media is marked as sensitive}}',
id: "compose_form.sensitive.marked",
defaultMessage: "{count, plural, one {Media is marked as sensitive} other {Media is marked as sensitive}}",
},
unmarked: {
id: 'compose_form.sensitive.unmarked',
defaultMessage: '{count, plural, one {Media is not marked as sensitive} other {Media is not marked as sensitive}}',
id: "compose_form.sensitive.unmarked",
defaultMessage: "{count, plural, one {Media is not marked as sensitive} other {Media is not marked as sensitive}}",
},
});
const mapStateToProps = state => ({
active: state.getIn(['compose', 'sensitive']),
disabled: state.getIn(['compose', 'spoiler']),
mediaCount: state.getIn(['compose', 'media_attachments']).size,
active: state.getIn(["compose", "sensitive"]),
disabled: state.getIn(["compose", "spoiler"]),
mediaCount: state.getIn(["compose", "media_attachments"]).size,
});
const mapDispatchToProps = dispatch => ({
@@ -49,7 +49,7 @@ class SensitiveButton extends PureComponent {
return (
<div className='compose-form__sensitive-button'>
<label className={classNames('icon-button', { active })} title={intl.formatMessage(active ? messages.marked : messages.unmarked, { count: mediaCount })}>
<label className={classNames("icon-button", { active })} title={intl.formatMessage(active ? messages.marked : messages.unmarked, { count: mediaCount })}>
<input
name='mark-sensitive'
type='checkbox'
@@ -1,20 +1,20 @@
import { injectIntl, defineMessages } from 'react-intl';
import { injectIntl, defineMessages } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { changeComposeSpoilerness } from '../../../actions/compose';
import TextIconButton from '../components/text_icon_button';
import { changeComposeSpoilerness } from "../../../actions/compose";
import TextIconButton from "../components/text_icon_button";
const messages = defineMessages({
marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' },
unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Text is not hidden' },
marked: { id: "compose_form.spoiler.marked", defaultMessage: "Text is hidden behind warning" },
unmarked: { id: "compose_form.spoiler.unmarked", defaultMessage: "Text is not hidden" },
});
const mapStateToProps = (state, { intl }) => ({
label: 'CW',
title: intl.formatMessage(state.getIn(['compose', 'spoiler']) ? messages.marked : messages.unmarked),
active: state.getIn(['compose', 'spoiler']),
ariaControls: 'cw-spoiler-input',
label: "CW",
title: intl.formatMessage(state.getIn(["compose", "spoiler"]) ? messages.marked : messages.unmarked),
active: state.getIn(["compose", "spoiler"]),
ariaControls: "cw-spoiler-input",
});
const mapDispatchToProps = dispatch => ({
@@ -1,12 +1,12 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { uploadCompose } from '../../../actions/compose';
import UploadButton from '../components/upload_button';
import { uploadCompose } from "../../../actions/compose";
import UploadButton from "../components/upload_button";
const mapStateToProps = state => ({
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size + state.getIn(['compose', 'pending_media_attachments']) > 3 || state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')))),
unavailable: state.getIn(['compose', 'poll']) !== null,
resetFileKey: state.getIn(['compose', 'resetFileKey']),
disabled: state.getIn(["compose", "is_uploading"]) || (state.getIn(["compose", "media_attachments"]).size + state.getIn(["compose", "pending_media_attachments"]) > 3 || state.getIn(["compose", "media_attachments"]).some(m => ["video", "audio"].includes(m.get("type")))),
unavailable: state.getIn(["compose", "poll"]) !== null,
resetFileKey: state.getIn(["compose", "resetFileKey"]),
});
const mapDispatchToProps = dispatch => ({
@@ -1,10 +1,10 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { undoUploadCompose, initMediaEditModal, submitCompose } from '../../../actions/compose';
import Upload from '../components/upload';
import { undoUploadCompose, initMediaEditModal, submitCompose } from "../../../actions/compose";
import Upload from "../components/upload";
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
media: state.getIn(["compose", "media_attachments"]).find(item => item.get("id") === id),
});
const mapDispatchToProps = dispatch => ({
@@ -1,9 +1,9 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import UploadForm from '../components/upload_form';
import UploadForm from "../components/upload_form";
const mapStateToProps = state => ({
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
mediaIds: state.getIn(["compose", "media_attachments"]).map(item => item.get("id")),
});
export default connect(mapStateToProps)(UploadForm);
@@ -1,11 +1,11 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import UploadProgress from '../components/upload_progress';
import UploadProgress from "../components/upload_progress";
const mapStateToProps = state => ({
active: state.getIn(['compose', 'is_uploading']),
progress: state.getIn(['compose', 'progress']),
isProcessing: state.getIn(['compose', 'is_processing']),
active: state.getIn(["compose", "is_uploading"]),
progress: state.getIn(["compose", "progress"]),
isProcessing: state.getIn(["compose", "is_processing"]),
});
export default connect(mapStateToProps)(UploadProgress);
@@ -1,18 +1,18 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { me } from 'mastodon/initial_state';
import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags';
import { me } from "mastodon/initial_state";
import { HASHTAG_PATTERN_REGEX } from "mastodon/utils/hashtags";
import Warning from '../components/warning';
import Warning from "../components/warning";
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
needsLockWarning: state.getIn(["compose", "privacy"]) === "private" && !state.getIn(["accounts", me, "locked"]),
hashtagWarning: state.getIn(["compose", "privacy"]) !== "public" && HASHTAG_PATTERN_REGEX.test(state.getIn(["compose", "text"])),
directMessageWarning: state.getIn(["compose", "privacy"]) === "direct",
});
const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => {
@@ -1,48 +1,48 @@
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 { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import { Helmet } from "react-helmet";
import { Link } from "react-router-dom";
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import spring from 'react-motion/lib/spring';
import spring from "react-motion/lib/spring";
import { openModal } from 'mastodon/actions/modal';
import Column from 'mastodon/components/column';
import { Icon } from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';
import { openModal } from "mastodon/actions/modal";
import Column from "mastodon/components/column";
import { Icon } from "mastodon/components/icon";
import { logOut } from "mastodon/utils/log_out";
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
import { mascot } from '../../initial_state';
import { isMobile } from '../../is_mobile';
import Motion from '../ui/util/optional_motion';
import elephantUIPlane from "../../../images/elephant_ui_plane.svg";
import { changeComposing, mountCompose, unmountCompose } from "../../actions/compose";
import { mascot } from "../../initial_state";
import { isMobile } from "../../is_mobile";
import Motion from "../ui/util/optional_motion";
import ComposeFormContainer from './containers/compose_form_container';
import NavigationContainer from './containers/navigation_container';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import ComposeFormContainer from "./containers/compose_form_container";
import NavigationContainer from "./containers/navigation_container";
import SearchContainer from "./containers/search_container";
import SearchResultsContainer from "./containers/search_results_container";
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
start: { id: "getting_started.heading", defaultMessage: "Getting started" },
home_timeline: { id: "tabs_bar.home", defaultMessage: "Home" },
notifications: { id: "tabs_bar.notifications", defaultMessage: "Notifications" },
public: { id: "navigation_bar.public_timeline", defaultMessage: "Federated timeline" },
community: { id: "navigation_bar.community_timeline", defaultMessage: "Local timeline" },
preferences: { id: "navigation_bar.preferences", defaultMessage: "Preferences" },
logout: { id: "navigation_bar.logout", defaultMessage: "Logout" },
compose: { id: "navigation_bar.compose", defaultMessage: "Compose new post" },
logoutMessage: { id: "confirmations.logout.message", defaultMessage: "Are you sure you want to log out?" },
logoutConfirm: { id: "confirmations.logout.confirm", defaultMessage: "Log out" },
});
const mapStateToProps = (state, ownProps) => ({
columns: state.getIn(['settings', 'columns']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : false,
columns: state.getIn(["settings", "columns"]),
showSearch: ownProps.multiColumn ? state.getIn(["search", "submitted"]) && !state.getIn(["search", "hidden"]) : false,
});
class Compose extends PureComponent {
@@ -72,7 +72,7 @@ class Compose extends PureComponent {
e.stopPropagation();
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm),
@@ -102,16 +102,16 @@ class Compose extends PureComponent {
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
<nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
{!columns.some(column => column.get('id') === 'HOME') && (
{!columns.some(column => column.get("id") === "HOME") && (
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
{!columns.some(column => column.get("id") === "NOTIFICATIONS") && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
{!columns.some(column => column.get("id") === "COMMUNITY") && (
<Link to='/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
{!columns.some(column => column.get("id") === "PUBLIC") && (
<Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
@@ -133,7 +133,7 @@ class Compose extends PureComponent {
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? "hidden" : "visible" }}>
<SearchResultsContainer />
</div>
)}
@@ -1,9 +1,9 @@
import { urlRegex } from './url_regex';
import { urlRegex } from "./url_regex";
const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx';
const urlPlaceholder = "$2xxxxxxxxxxxxxxxxxxxxxxx";
export function countableText(inputText) {
return inputText
.replace(urlRegex, urlPlaceholder)
.replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '$1@$3');
.replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, "$1@$3");
}
@@ -1,23 +1,23 @@
import regexSupplant from 'twitter-text/dist/lib/regexSupplant';
import validDomain from 'twitter-text/dist/regexp/validDomain';
import validPortNumber from 'twitter-text/dist/regexp/validPortNumber';
import validUrlPath from 'twitter-text/dist/regexp/validUrlPath';
import validUrlPrecedingChars from 'twitter-text/dist/regexp/validUrlPrecedingChars';
import validUrlQueryChars from 'twitter-text/dist/regexp/validUrlQueryChars';
import validUrlQueryEndingChars from 'twitter-text/dist/regexp/validUrlQueryEndingChars';
import regexSupplant from "twitter-text/dist/lib/regexSupplant";
import validDomain from "twitter-text/dist/regexp/validDomain";
import validPortNumber from "twitter-text/dist/regexp/validPortNumber";
import validUrlPath from "twitter-text/dist/regexp/validUrlPath";
import validUrlPrecedingChars from "twitter-text/dist/regexp/validUrlPrecedingChars";
import validUrlQueryChars from "twitter-text/dist/regexp/validUrlQueryChars";
import validUrlQueryEndingChars from "twitter-text/dist/regexp/validUrlQueryEndingChars";
// The difference with twitter-text's extractURL is that the protocol isn't
// optional.
export const urlRegex = regexSupplant(
'(' + // $1 URL
'(#{validUrlPrecedingChars})' + // $2
'(https?:\\/\\/)' + // $3 Protocol
'(#{validDomain})' + // $4 Domain(s)
'(?::(#{validPortNumber}))?' + // $5 Port number (optional)
'(\\/#{validUrlPath}*)?' + // $6 URL Path
'(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?' + // $7 Query String
')',
"(" + // $1 URL
"(#{validUrlPrecedingChars})" + // $2
"(https?:\\/\\/)" + // $3 Protocol
"(#{validDomain})" + // $4 Domain(s)
"(?::(#{validPortNumber}))?" + // $5 Port number (optional)
"(\\/#{validUrlPath}*)?" + // $6 URL Path
"(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?" + // $7 Query String
")",
{
validUrlPrecedingChars,
validDomain,
@@ -26,5 +26,5 @@ export const urlRegex = regexSupplant(
validUrlQueryChars,
validUrlQueryEndingChars,
},
'gi',
"gi",
);
@@ -1,31 +1,31 @@
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 { Link } from 'react-router-dom';
import classNames from "classnames";
import { Link } from "react-router-dom";
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 AttachmentList from 'mastodon/components/attachment_list';
import AvatarComposite from 'mastodon/components/avatar_composite';
import { IconButton } from 'mastodon/components/icon_button';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import StatusContent from 'mastodon/components/status_content';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { autoPlayGif } from 'mastodon/initial_state';
import AttachmentList from "mastodon/components/attachment_list";
import AvatarComposite from "mastodon/components/avatar_composite";
import { IconButton } from "mastodon/components/icon_button";
import { RelativeTimestamp } from "mastodon/components/relative_timestamp";
import StatusContent from "mastodon/components/status_content";
import DropdownMenuContainer from "mastodon/containers/dropdown_menu_container";
import { autoPlayGif } from "mastodon/initial_state";
const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' },
open: { id: 'conversation.open', defaultMessage: 'View conversation' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
markAsRead: { id: 'conversation.mark_as_read', defaultMessage: 'Mark as read' },
delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
more: { id: "status.more", defaultMessage: "More" },
open: { id: "conversation.open", defaultMessage: "View conversation" },
reply: { id: "status.reply", defaultMessage: "Reply" },
markAsRead: { id: "conversation.mark_as_read", defaultMessage: "Mark as read" },
delete: { id: "conversation.delete", defaultMessage: "Delete conversation" },
muteConversation: { id: "status.mute_conversation", defaultMessage: "Mute conversation" },
unmuteConversation: { id: "status.unmute_conversation", defaultMessage: "Unmute conversation" },
});
class Conversation extends ImmutablePureComponent {
@@ -52,11 +52,11 @@ class Conversation extends ImmutablePureComponent {
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");
}
};
@@ -65,11 +65,11 @@ class Conversation extends ImmutablePureComponent {
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");
}
};
@@ -84,7 +84,7 @@ class Conversation extends ImmutablePureComponent {
markRead();
}
this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`);
this.context.router.history.push(`/@${lastStatus.getIn(["account", "acct"])}/${lastStatus.get("id")}`);
};
handleMarkAsRead = () => {
@@ -127,7 +127,7 @@ class Conversation extends ImmutablePureComponent {
null,
];
menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute });
menu.push({ text: intl.formatMessage(lastStatus.get("muted") ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute });
if (unread) {
menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead });
@@ -136,7 +136,7 @@ class Conversation extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
const names = accounts.map(a => <Link to={`/@${a.get('acct')}`} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Link>).reduce((prev, cur) => [prev, ', ', cur]);
const names = accounts.map(a => <Link to={`/@${a.get("acct")}`} key={a.get("id")} title={a.get("acct")}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get("display_name_html") }} /></bdi></Link>).reduce((prev, cur) => [prev, ", ", cur]);
const handlers = {
reply: this.handleReply,
@@ -148,7 +148,7 @@ class Conversation extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex={0}>
<div className={classNames("conversation focusable muted", { "conversation--unread": unread })} tabIndex={0}>
<div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
<AvatarComposite accounts={accounts} size={48} />
</div>
@@ -156,7 +156,7 @@ class Conversation extends ImmutablePureComponent {
<div className='conversation__content'>
<div className='conversation__content__info'>
<div className='conversation__content__relative-time'>
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get("created_at")} />
</div>
<div className='conversation__content__names' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
@@ -167,15 +167,15 @@ class Conversation extends ImmutablePureComponent {
<StatusContent
status={lastStatus}
onClick={this.handleClick}
expanded={!lastStatus.get('hidden')}
expanded={!lastStatus.get("hidden")}
onExpandedToggle={this.handleShowMore}
collapsible
/>
{lastStatus.get('media_attachments').size > 0 && (
{lastStatus.get("media_attachments").size > 0 && (
<AttachmentList
compact
media={lastStatus.get('media_attachments')}
media={lastStatus.get("media_attachments")}
/>
)}
@@ -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 { debounce } from 'lodash';
import { debounce } from "lodash";
import ScrollableList from '../../../components/scrollable_list';
import ConversationContainer from '../containers/conversation_container';
import ScrollableList from "../../../components/scrollable_list";
import ConversationContainer from "../containers/conversation_container";
export default class ConversationsList extends ImmutablePureComponent {
@@ -18,7 +18,7 @@ export default class ConversationsList extends ImmutablePureComponent {
onLoadMore: PropTypes.func,
};
getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id);
getCurrentIndex = id => this.props.conversations.findIndex(x => x.get("id") === id);
handleMoveUp = id => {
const elementIndex = this.getCurrentIndex(id) - 1;
@@ -51,8 +51,8 @@ export default class ConversationsList extends ImmutablePureComponent {
handleLoadOlder = debounce(() => {
const last = this.props.conversations.last();
if (last && last.get('last_status')) {
this.props.onLoadMore(last.get('last_status'));
if (last && last.get("last_status")) {
this.props.onLoadMore(last.get("last_status"));
}
}, 300, { leading: true });
@@ -63,8 +63,8 @@ export default class ConversationsList extends ImmutablePureComponent {
<ScrollableList {...other} isLoading={isLoading} showLoading={isLoading && conversations.isEmpty()} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
{conversations.map(item => (
<ConversationContainer
key={item.get('id')}
conversationId={item.get('id')}
key={item.get("id")}
conversationId={item.get("id")}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
scrollKey={this.props.scrollKey}
@@ -1,30 +1,30 @@
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { replyCompose } from 'mastodon/actions/compose';
import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations';
import { openModal } from 'mastodon/actions/modal';
import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'mastodon/actions/statuses';
import { makeGetStatus } from 'mastodon/selectors';
import { replyCompose } from "mastodon/actions/compose";
import { markConversationRead, deleteConversation } from "mastodon/actions/conversations";
import { openModal } from "mastodon/actions/modal";
import { muteStatus, unmuteStatus, hideStatus, revealStatus } from "mastodon/actions/statuses";
import { makeGetStatus } from "mastodon/selectors";
import Conversation from '../components/conversation';
import Conversation from "../components/conversation";
const messages = defineMessages({
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
replyConfirm: { id: "confirmations.reply.confirm", defaultMessage: "Reply" },
replyMessage: { id: "confirmations.reply.message", defaultMessage: "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?" },
});
const mapStateToProps = () => {
const getStatus = makeGetStatus();
return (state, { conversationId }) => {
const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
const lastStatusId = conversation.get('last_status', null);
const conversation = state.getIn(["conversations", "items"]).find(x => x.get("id") === conversationId);
const lastStatusId = conversation.get("last_status", null);
return {
accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
unread: conversation.get('unread'),
accounts: conversation.get("accounts").map(accountId => state.getIn(["accounts", accountId], null)),
unread: conversation.get("unread"),
lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }),
};
};
@@ -40,9 +40,9 @@ const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
if (state.getIn(["compose", "text"]).trim().length !== 0) {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
@@ -60,18 +60,18 @@ const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({
},
onMute (status) {
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')));
if (status.get("muted")) {
dispatch(unmuteStatus(status.get("id")));
} else {
dispatch(muteStatus(status.get('id')));
dispatch(muteStatus(status.get("id")));
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
if (status.get("hidden")) {
dispatch(revealStatus(status.get("id")));
} else {
dispatch(hideStatus(status.get('id')));
dispatch(hideStatus(status.get("id")));
}
},
@@ -1,12 +1,12 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { expandConversations } from '../../../actions/conversations';
import ConversationsList from '../components/conversations_list';
import { expandConversations } from "../../../actions/conversations";
import ConversationsList from "../components/conversations_list";
const mapStateToProps = state => ({
conversations: state.getIn(['conversations', 'items']),
isLoading: state.getIn(['conversations', 'isLoading'], true),
hasMore: state.getIn(['conversations', 'hasMore'], false),
conversations: state.getIn(["conversations", "items"]),
isLoading: state.getIn(["conversations", "isLoading"], true),
hasMore: state.getIn(["conversations", "hasMore"], false),
});
const mapDispatchToProps = dispatch => ({
@@ -1,22 +1,22 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { mountConversations, unmountConversations, expandConversations } from 'mastodon/actions/conversations';
import { connectDirectStream } from 'mastodon/actions/streaming';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { addColumn, removeColumn, moveColumn } from "mastodon/actions/columns";
import { mountConversations, unmountConversations, expandConversations } from "mastodon/actions/conversations";
import { connectDirectStream } from "mastodon/actions/streaming";
import Column from "mastodon/components/column";
import ColumnHeader from "mastodon/components/column_header";
import ConversationsListContainer from './containers/conversations_list_container';
import ConversationsListContainer from "./containers/conversations_list_container";
const messages = defineMessages({
title: { id: 'column.direct', defaultMessage: 'Private mentions' },
title: { id: "column.direct", defaultMessage: "Private mentions" },
});
class DirectTimeline extends PureComponent {
@@ -35,7 +35,7 @@ class DirectTimeline extends PureComponent {
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('DIRECT', {}));
dispatch(addColumn("DIRECT", {}));
}
};
@@ -1,38 +1,38 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { FormattedMessage, injectIntl, defineMessages } from "react-intl";
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import classNames from "classnames";
import { Link } from "react-router-dom";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import {
followAccount,
unfollowAccount,
unblockAccount,
unmuteAccount,
} from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
import { makeGetAccount } from 'mastodon/selectors';
} from "mastodon/actions/accounts";
import { openModal } from "mastodon/actions/modal";
import { Avatar } from "mastodon/components/avatar";
import Button from "mastodon/components/button";
import { DisplayName } from "mastodon/components/display_name";
import { ShortNumber } from "mastodon/components/short_number";
import { autoPlayGif, me, unfollowModal } from "mastodon/initial_state";
import { makeGetAccount } from "mastodon/selectors";
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
unfollow: { id: "account.unfollow", defaultMessage: "Unfollow" },
follow: { id: "account.follow", defaultMessage: "Follow" },
cancel_follow_request: { id: "account.cancel_follow_request", defaultMessage: "Withdraw follow request" },
cancelFollowRequestConfirm: { id: "confirmations.cancel_follow_request.confirm", defaultMessage: "Withdraw request" },
requested: { id: "account.requested", defaultMessage: "Awaiting approval. Click to cancel follow request" },
unblock: { id: "account.unblock_short", defaultMessage: "Unblock" },
unmute: { id: "account.unmute_short", defaultMessage: "Unmute" },
unfollowConfirm: { id: "confirmations.unfollow.confirm", defaultMessage: "Unfollow" },
edit_profile: { id: "account.edit_profile", defaultMessage: "Edit profile" },
});
const makeMapStateToProps = () => {
@@ -47,53 +47,53 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow(account) {
if (account.getIn(['relationship', 'following'])) {
if (account.getIn(["relationship", "following"])) {
if (unfollowModal) {
dispatch(
openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: (
<FormattedMessage
id='confirmations.unfollow.message'
defaultMessage='Are you sure you want to unfollow {name}?'
values={{ name: <strong>@{account.get('acct')}</strong> }}
values={{ name: <strong>@{account.get("acct")}</strong> }}
/>
),
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
} }),
);
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get("id")));
}
} else if (account.getIn(['relationship', 'requested'])) {
} else if (account.getIn(["relationship", "requested"])) {
if (unfollowModal) {
dispatch(openModal({
modalType: 'CONFIRM',
modalType: "CONFIRM",
modalProps: {
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get("acct")}</strong> }} />,
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
onConfirm: () => dispatch(unfollowAccount(account.get("id"))),
},
}));
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get("id")));
}
} else {
dispatch(followAccount(account.get('id')));
dispatch(followAccount(account.get("id")));
}
},
onBlock(account) {
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
if (account.getIn(["relationship", "blocking"])) {
dispatch(unblockAccount(account.get("id")));
}
},
onMute(account) {
if (account.getIn(['relationship', 'muting'])) {
dispatch(unmuteAccount(account.get('id')));
if (account.getIn(["relationship", "muting"])) {
dispatch(unmuteAccount(account.get("id")));
}
},
@@ -114,11 +114,11 @@ class AccountCard extends ImmutablePureComponent {
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");
}
};
@@ -127,11 +127,11 @@ class AccountCard extends ImmutablePureComponent {
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");
}
};
@@ -148,7 +148,7 @@ class AccountCard extends ImmutablePureComponent {
};
handleEditProfile = () => {
window.open('/settings/profile', '_blank');
window.open("/settings/profile", "_blank");
};
render() {
@@ -156,16 +156,16 @@ class AccountCard extends ImmutablePureComponent {
let actionBtn;
if (me !== account.get('id')) {
if (!account.get('relationship')) { // Wait until the relationship is loaded
actionBtn = '';
} else if (account.getIn(['relationship', 'requested'])) {
if (me !== account.get("id")) {
if (!account.get("relationship")) { // Wait until the relationship is loaded
actionBtn = "";
} else if (account.getIn(["relationship", "requested"])) {
actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
} else if (account.getIn(['relationship', 'muting'])) {
} else if (account.getIn(["relationship", "muting"])) {
actionBtn = <Button text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
} else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
} else if (account.getIn(['relationship', 'blocking'])) {
} else if (!account.getIn(["relationship", "blocking"])) {
actionBtn = <Button disabled={account.getIn(["relationship", "blocked_by"])} className={classNames({ "button--destructive": account.getIn(["relationship", "following"]) })} text={intl.formatMessage(account.getIn(["relationship", "following"]) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
} else if (account.getIn(["relationship", "blocking"])) {
actionBtn = <Button text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
}
} else {
@@ -174,11 +174,11 @@ class AccountCard extends ImmutablePureComponent {
return (
<div className='account-card'>
<Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
<Link to={`/@${account.get("acct")}`} className='account-card__permalink'>
<div className='account-card__header'>
<img
src={
autoPlayGif ? account.get('header') : account.get('header_static')
autoPlayGif ? account.get("header") : account.get("header_static")
}
alt=''
/>
@@ -190,26 +190,26 @@ class AccountCard extends ImmutablePureComponent {
</div>
</Link>
{account.get('note').length > 0 && (
{account.get("note").length > 0 && (
<div
className='account-card__bio translate'
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
dangerouslySetInnerHTML={{ __html: account.get("note_emojified") }}
/>
)}
<div className='account-card__actions'>
<div className='account-card__counters'>
<div className='account-card__counters__item'>
<ShortNumber value={account.get('statuses_count')} />
<ShortNumber value={account.get("statuses_count")} />
<small>
<FormattedMessage id='account.posts' defaultMessage='Posts' />
</small>
</div>
<div className='account-card__counters__item'>
<ShortNumber value={account.get('followers_count')} />{' '}
<ShortNumber value={account.get("followers_count")} />{" "}
<small>
<FormattedMessage
id='account.followers'
@@ -219,7 +219,7 @@ class AccountCard extends ImmutablePureComponent {
</div>
<div className='account-card__counters__item'>
<ShortNumber value={account.get('following_count')} />{' '}
<ShortNumber value={account.get("following_count")} />{" "}
<small>
<FormattedMessage
id='account.following'
@@ -1,37 +1,37 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodon/actions/columns';
import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { LoadMore } from 'mastodon/components/load_more';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
import ScrollContainer from 'mastodon/containers/scroll_container';
import { addColumn, removeColumn, moveColumn, changeColumnParams } from "mastodon/actions/columns";
import { fetchDirectory, expandDirectory } from "mastodon/actions/directory";
import Column from "mastodon/components/column";
import ColumnHeader from "mastodon/components/column_header";
import { LoadMore } from "mastodon/components/load_more";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
import { RadioButton } from "mastodon/components/radio_button";
import ScrollContainer from "mastodon/containers/scroll_container";
import AccountCard from './components/account_card';
import AccountCard from "./components/account_card";
const messages = defineMessages({
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
recentlyActive: { id: 'directory.recently_active', defaultMessage: 'Recently active' },
newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' },
local: { id: 'directory.local', defaultMessage: 'From {domain} only' },
federated: { id: 'directory.federated', defaultMessage: 'From known fediverse' },
title: { id: "column.directory", defaultMessage: "Browse profiles" },
recentlyActive: { id: "directory.recently_active", defaultMessage: "Recently active" },
newArrivals: { id: "directory.new_arrivals", defaultMessage: "New arrivals" },
local: { id: "directory.local", defaultMessage: "From {domain} only" },
federated: { id: "directory.federated", defaultMessage: "From known fediverse" },
});
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
domain: state.getIn(['meta', 'domain']),
accountIds: state.getIn(["user_lists", "directory", "items"], ImmutableList()),
isLoading: state.getIn(["user_lists", "directory", "isLoading"], true),
domain: state.getIn(["meta", "domain"]),
});
class Directory extends PureComponent {
@@ -65,12 +65,12 @@ class Directory extends PureComponent {
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state)));
dispatch(addColumn("DIRECTORY", this.getParams(this.props, this.state)));
}
};
getParams = (props, state) => ({
order: state.order === null ? (props.params.order || 'active') : state.order,
order: state.order === null ? (props.params.order || "active") : state.order,
local: state.local === null ? (props.params.local || false) : state.local,
});
@@ -106,7 +106,7 @@ class Directory extends PureComponent {
const { dispatch, columnId } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, ['order'], e.target.value));
dispatch(changeColumnParams(columnId, ["order"], e.target.value));
} else {
this.setState({ order: e.target.value });
}
@@ -116,9 +116,9 @@ class Directory extends PureComponent {
const { dispatch, columnId } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, ['local'], e.target.value === '1'));
dispatch(changeColumnParams(columnId, ["local"], e.target.value === "1"));
} else {
this.setState({ local: e.target.value === '1' });
this.setState({ local: e.target.value === "1" });
}
};
@@ -136,8 +136,8 @@ class Directory extends PureComponent {
<div className='scrollable'>
<div className='filter-form'>
<div className='filter-form__column' role='group'>
<RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
<RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === 'new'} onChange={this.handleChangeOrder} />
<RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === "active"} onChange={this.handleChangeOrder} />
<RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === "new"} onChange={this.handleChangeOrder} />
</div>
<div className='filter-form__column' role='group'>
@@ -1,30 +1,30 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import DomainContainer from '../../containers/domain_container';
import Column from '../ui/components/column';
import { fetchDomainBlocks, expandDomainBlocks } from "../../actions/domain_blocks";
import ColumnBackButtonSlim from "../../components/column_back_button_slim";
import { LoadingIndicator } from "../../components/loading_indicator";
import ScrollableList from "../../components/scrollable_list";
import DomainContainer from "../../containers/domain_container";
import Column from "../ui/components/column";
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
heading: { id: "column.domain_blocks", defaultMessage: "Blocked domains" },
unblockDomain: { id: "account.unblock_domain", defaultMessage: "Unblock domain {domain}" },
});
const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
domains: state.getIn(["domain_lists", "blocks", "items"]),
hasMore: !!state.getIn(["domain_lists", "blocks", "next"]),
});
class Blocks extends ImmutablePureComponent {
@@ -1,97 +1,97 @@
import emojify from '../emoji';
import emojify from "../emoji";
describe('emoji', () => {
describe('.emojify', () => {
it('ignores unknown shortcodes', () => {
expect(emojify(':foobarbazfake:')).toEqual(':foobarbazfake:');
describe("emoji", () => {
describe(".emojify", () => {
it("ignores unknown shortcodes", () => {
expect(emojify(":foobarbazfake:")).toEqual(":foobarbazfake:");
});
it('ignores shortcodes inside of tags', () => {
expect(emojify('<p data-foo=":smile:"></p>')).toEqual('<p data-foo=":smile:"></p>');
it("ignores shortcodes inside of tags", () => {
expect(emojify("<p data-foo=\":smile:\"></p>")).toEqual("<p data-foo=\":smile:\"></p>");
});
it('works with unclosed tags', () => {
expect(emojify('hello>')).toEqual('hello&gt;');
expect(emojify('<hello')).toEqual('');
it("works with unclosed tags", () => {
expect(emojify("hello>")).toEqual("hello&gt;");
expect(emojify("<hello")).toEqual("");
});
it('works with unclosed shortcodes', () => {
expect(emojify('smile:')).toEqual('smile:');
expect(emojify(':smile')).toEqual(':smile');
it("works with unclosed shortcodes", () => {
expect(emojify("smile:")).toEqual("smile:");
expect(emojify(":smile")).toEqual(":smile");
});
it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg">');
expect(emojify('👨‍👩‍👧‍👧')).toEqual(
'<img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg">');
expect(emojify('👩‍👩‍👦')).toEqual('<img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg">');
expect(emojify('\u2757')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
it("does unicode", () => {
expect(emojify("\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"👩‍👩‍👦‍👦\" title=\":woman-woman-boy-boy:\" src=\"/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg\">");
expect(emojify("👨‍👩‍👧‍👧")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"👨‍👩‍👧‍👧\" title=\":man-woman-girl-girl:\" src=\"/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg\">");
expect(emojify("👩‍👩‍👦")).toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"👩‍👩‍👦\" title=\":woman-woman-boy:\" src=\"/emoji/1f469-200d-1f469-200d-1f466.svg\">");
expect(emojify("\u2757")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\">");
});
it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> bar');
it("does multiple unicode", () => {
expect(emojify("\u2757 #\uFE0F\u20E3")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"#️⃣\" title=\":hash:\" src=\"/emoji/23-20e3.svg\">");
expect(emojify("\u2757#\uFE0F\u20E3")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\"><img draggable=\"false\" class=\"emojione\" alt=\"#️⃣\" title=\":hash:\" src=\"/emoji/23-20e3.svg\">");
expect(emojify("\u2757 #\uFE0F\u20E3 \u2757")).toEqual(
"<img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"#️⃣\" title=\":hash:\" src=\"/emoji/23-20e3.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\">");
expect(emojify("foo \u2757 #\uFE0F\u20E3 bar")).toEqual(
"foo <img draggable=\"false\" class=\"emojione\" alt=\"❗\" title=\":exclamation:\" src=\"/emoji/2757.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"#️⃣\" title=\":hash:\" src=\"/emoji/23-20e3.svg\"> bar");
});
it('ignores unicode inside of tags', () => {
expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).toEqual('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
it("ignores unicode inside of tags", () => {
expect(emojify("<p data-foo=\"\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66\"></p>")).toEqual("<p data-foo=\"\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66\"></p>");
});
it('does multiple emoji properly (issue 5188)', () => {
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
it("does multiple emoji properly (issue 5188)", () => {
expect(emojify("👌🌈💕")).toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"👌\" title=\":ok_hand:\" src=\"/emoji/1f44c.svg\"><img draggable=\"false\" class=\"emojione\" alt=\"🌈\" title=\":rainbow:\" src=\"/emoji/1f308.svg\"><img draggable=\"false\" class=\"emojione\" alt=\"💕\" title=\":two_hearts:\" src=\"/emoji/1f495.svg\">");
expect(emojify("👌 🌈 💕")).toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"👌\" title=\":ok_hand:\" src=\"/emoji/1f44c.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"🌈\" title=\":rainbow:\" src=\"/emoji/1f308.svg\"> <img draggable=\"false\" class=\"emojione\" alt=\"💕\" title=\":two_hearts:\" src=\"/emoji/1f495.svg\">");
});
it('does an emoji that has no shortcode', () => {
expect(emojify('👁‍🗨')).toEqual('<img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg">');
it("does an emoji that has no shortcode", () => {
expect(emojify("👁‍🗨")).toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"👁‍🗨\" title=\"\" src=\"/emoji/1f441-200d-1f5e8.svg\">");
});
it('does an emoji whose filename is irregular', () => {
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg">');
it("does an emoji whose filename is irregular", () => {
expect(emojify("↙️")).toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"↙️\" title=\":arrow_lower_left:\" src=\"/emoji/2199.svg\">");
});
it('avoid emojifying on invisible text', () => {
expect(emojify('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>'))
.toEqual('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>');
expect(emojify('<span class="invisible">:luigi:</span>', { ':luigi:': { static_url: 'luigi.exe' } }))
.toEqual('<span class="invisible">:luigi:</span>');
it("avoid emojifying on invisible text", () => {
expect(emojify("<a href=\"http://example.com/test%F0%9F%98%84\"><span class=\"invisible\">http://</span><span class=\"ellipsis\">example.com/te</span><span class=\"invisible\">st😄</span></a>"))
.toEqual("<a href=\"http://example.com/test%F0%9F%98%84\"><span class=\"invisible\">http://</span><span class=\"ellipsis\">example.com/te</span><span class=\"invisible\">st😄</span></a>");
expect(emojify("<span class=\"invisible\">:luigi:</span>", { ":luigi:": { static_url: "luigi.exe" } }))
.toEqual("<span class=\"invisible\">:luigi:</span>");
});
it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<br>😴</span>😇'))
.toEqual('<span class="invisible">😄<br>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
it("avoid emojifying on invisible text with nested tags", () => {
expect(emojify("<span class=\"invisible\">😄<span class=\"foo\">bar</span>😴</span>😇"))
.toEqual("<span class=\"invisible\">😄<span class=\"foo\">bar</span>😴</span><img draggable=\"false\" class=\"emojione\" alt=\"😇\" title=\":innocent:\" src=\"/emoji/1f607.svg\">");
expect(emojify("<span class=\"invisible\">😄<span class=\"invisible\">😕</span>😴</span>😇"))
.toEqual("<span class=\"invisible\">😄<span class=\"invisible\">😕</span>😴</span><img draggable=\"false\" class=\"emojione\" alt=\"😇\" title=\":innocent:\" src=\"/emoji/1f607.svg\">");
expect(emojify("<span class=\"invisible\">😄<br>😴</span>😇"))
.toEqual("<span class=\"invisible\">😄<br>😴</span><img draggable=\"false\" class=\"emojione\" alt=\"😇\" title=\":innocent:\" src=\"/emoji/1f607.svg\">");
});
it('does not emojify emojis with textual presentation VS15 character', () => {
expect(emojify('✴︎')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15
.toEqual('✴︎');
it("does not emojify emojis with textual presentation VS15 character", () => {
expect(emojify("✴︎")) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15
.toEqual("✴︎");
});
it('does an simple emoji properly', () => {
expect(emojify('♀♂'))
.toEqual('<img draggable="false" class="emojione" alt="♀" title=":female_sign:" src="/emoji/2640.svg"><img draggable="false" class="emojione" alt="♂" title=":male_sign:" src="/emoji/2642.svg">');
it("does an simple emoji properly", () => {
expect(emojify("♀♂"))
.toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"♀\" title=\":female_sign:\" src=\"/emoji/2640.svg\"><img draggable=\"false\" class=\"emojione\" alt=\"♂\" title=\":male_sign:\" src=\"/emoji/2642.svg\">");
});
it('does an emoji containing ZWJ properly', () => {
expect(emojify('💂‍♀️💂‍♂️'))
.toEqual('<img draggable="false" class="emojione" alt="💂\u200D♀️" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"><img draggable="false" class="emojione" alt="💂\u200D♂️" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg">');
it("does an emoji containing ZWJ properly", () => {
expect(emojify("💂‍♀️💂‍♂️"))
.toEqual("<img draggable=\"false\" class=\"emojione\" alt=\"💂\u200D♀️\" title=\":female-guard:\" src=\"/emoji/1f482-200d-2640-fe0f_border.svg\"><img draggable=\"false\" class=\"emojione\" alt=\"💂\u200D♂️\" title=\":male-guard:\" src=\"/emoji/1f482-200d-2642-fe0f_border.svg\">");
});
it('keeps ordering as expected (issue fixed by PR 20677)', () => {
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>'))
.toEqual('<p><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>');
it("keeps ordering as expected (issue fixed by PR 20677)", () => {
expect(emojify("<p>💕 <a class=\"hashtag\" href=\"https://example.com/tags/foo\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">#<span>foo</span></a> test: foo.</p>"))
.toEqual("<p><img draggable=\"false\" class=\"emojione\" alt=\"💕\" title=\":two_hearts:\" src=\"/emoji/1f495.svg\"> <a class=\"hashtag\" href=\"https://example.com/tags/foo\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">#<span>foo</span></a> test: foo.</p>");
});
});
});
@@ -1,177 +1,177 @@
import { emojiIndex } from 'emoji-mart';
import { pick } from 'lodash';
import { emojiIndex } from "emoji-mart";
import { pick } from "lodash";
import { search } from '../emoji_mart_search_light';
import { search } from "../emoji_mart_search_light";
const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']);
const trimEmojis = emoji => pick(emoji, ["id", "unified", "native", "custom"]);
describe('emoji_index', () => {
it('should give same result for emoji_index_light and emoji-mart', () => {
describe("emoji_index", () => {
it("should give same result for emoji_index_light and emoji-mart", () => {
const expected = [
{
id: 'pineapple',
unified: '1f34d',
native: '🍍',
id: "pineapple",
unified: "1f34d",
native: "🍍",
},
];
expect(search('pineapple').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('pineapple').map(trimEmojis)).toEqual(expected);
expect(search("pineapple").map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("pineapple").map(trimEmojis)).toEqual(expected);
});
it('orders search results correctly', () => {
it("orders search results correctly", () => {
const expected = [
{
id: 'apple',
unified: '1f34e',
native: '🍎',
id: "apple",
unified: "1f34e",
native: "🍎",
},
{
id: 'pineapple',
unified: '1f34d',
native: '🍍',
id: "pineapple",
unified: "1f34d",
native: "🍍",
},
{
id: 'green_apple',
unified: '1f34f',
native: '🍏',
id: "green_apple",
unified: "1f34f",
native: "🍏",
},
{
id: 'iphone',
unified: '1f4f1',
native: '📱',
id: "iphone",
unified: "1f4f1",
native: "📱",
},
];
expect(search('apple').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected);
expect(search("apple").map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("apple").map(trimEmojis)).toEqual(expected);
});
it('can include/exclude categories', () => {
expect(search('flag', { include: ['people'] })).toEqual([]);
expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]);
it("can include/exclude categories", () => {
expect(search("flag", { include: ["people"] })).toEqual([]);
expect(emojiIndex.search("flag", { include: ["people"] })).toEqual([]);
});
it('(different behavior from emoji-mart) do not erases custom emoji if not passed again', () => {
it("(different behavior from emoji-mart) do not erases custom emoji if not passed again", () => {
const custom = [
{
id: 'mastodon',
name: 'mastodon',
short_names: ['mastodon'],
text: '',
id: "mastodon",
name: "mastodon",
short_names: ["mastodon"],
text: "",
emoticons: [],
keywords: ['mastodon'],
imageUrl: 'http://example.com',
keywords: ["mastodon"],
imageUrl: "http://example.com",
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
search("", { custom });
emojiIndex.search("", { custom });
const expected = [];
const lightExpected = [
{
id: 'mastodon',
id: "mastodon",
custom: true,
},
];
expect(search('masto').map(trimEmojis)).toEqual(lightExpected);
expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
expect(search("masto").map(trimEmojis)).toEqual(lightExpected);
expect(emojiIndex.search("masto").map(trimEmojis)).toEqual(expected);
});
it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => {
it("(different behavior from emoji-mart) erases custom emoji if another is passed", () => {
const custom = [
{
id: 'mastodon',
name: 'mastodon',
short_names: ['mastodon'],
text: '',
id: "mastodon",
name: "mastodon",
short_names: ["mastodon"],
text: "",
emoticons: [],
keywords: ['mastodon'],
imageUrl: 'http://example.com',
keywords: ["mastodon"],
imageUrl: "http://example.com",
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
search("", { custom });
emojiIndex.search("", { custom });
const expected = [];
expect(search('masto', { custom: [] }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
expect(search("masto", { custom: [] }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("masto").map(trimEmojis)).toEqual(expected);
});
it('handles custom emoji', () => {
it("handles custom emoji", () => {
const custom = [
{
id: 'mastodon',
name: 'mastodon',
short_names: ['mastodon'],
text: '',
id: "mastodon",
name: "mastodon",
short_names: ["mastodon"],
text: "",
emoticons: [],
keywords: ['mastodon'],
imageUrl: 'http://example.com',
keywords: ["mastodon"],
imageUrl: "http://example.com",
custom: true,
},
];
search('', { custom });
emojiIndex.search('', { custom });
search("", { custom });
emojiIndex.search("", { custom });
const expected = [
{
id: 'mastodon',
id: "mastodon",
custom: true,
},
];
expect(search('masto', { custom }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('masto', { custom }).map(trimEmojis)).toEqual(expected);
expect(search("masto", { custom }).map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("masto", { custom }).map(trimEmojis)).toEqual(expected);
});
it('should filter only emojis we care about, exclude pineapple', () => {
const emojisToShowFilter = emoji => emoji.unified !== '1F34D';
expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain('pineapple');
expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain('pineapple');
it("should filter only emojis we care about, exclude pineapple", () => {
const emojisToShowFilter = emoji => emoji.unified !== "1F34D";
expect(search("apple", { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain("pineapple");
expect(emojiIndex.search("apple", { emojisToShowFilter }).map((obj) => obj.id))
.not.toContain("pineapple");
});
it('does an emoji whose unified name is irregular', () => {
it("does an emoji whose unified name is irregular", () => {
const expected = [
{
'id': 'water_polo',
'unified': '1f93d',
'native': '🤽',
"id": "water_polo",
"unified": "1f93d",
"native": "🤽",
},
{
'id': 'man-playing-water-polo',
'unified': '1f93d-200d-2642-fe0f',
'native': '🤽‍♂️',
"id": "man-playing-water-polo",
"unified": "1f93d-200d-2642-fe0f",
"native": "🤽‍♂️",
},
{
'id': 'woman-playing-water-polo',
'unified': '1f93d-200d-2640-fe0f',
'native': '🤽‍♀️',
"id": "woman-playing-water-polo",
"unified": "1f93d-200d-2640-fe0f",
"native": "🤽‍♀️",
},
];
expect(search('polo').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('polo').map(trimEmojis)).toEqual(expected);
expect(search("polo").map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("polo").map(trimEmojis)).toEqual(expected);
});
it('can search for thinking_face', () => {
it("can search for thinking_face", () => {
const expected = [
{
id: 'thinking_face',
unified: '1f914',
native: '🤔',
id: "thinking_face",
unified: "1f914",
native: "🤔",
},
];
expect(search('thinking_fac').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('thinking_fac').map(trimEmojis)).toEqual(expected);
expect(search("thinking_fac").map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("thinking_fac").map(trimEmojis)).toEqual(expected);
});
it('can search for woman-facepalming', () => {
it("can search for woman-facepalming", () => {
const expected = [
{
id: 'woman-facepalming',
unified: '1f926-200d-2640-fe0f',
native: '🤦‍♀️',
id: "woman-facepalming",
unified: "1f926-200d-2640-fe0f",
native: "🤦‍♀️",
},
];
expect(search('woman-facep').map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search('woman-facep').map(trimEmojis)).toEqual(expected);
expect(search("woman-facep").map(trimEmojis)).toEqual(expected);
expect(emojiIndex.search("woman-facep").map(trimEmojis)).toEqual(expected);
});
});
+43 -41
View File
@@ -1,10 +1,10 @@
import Trie from 'substring-trie';
import Trie from "substring-trie";
import { assetHost } from 'mastodon/utils/config';
import { assetHost } from "mastodon/utils/config";
import { autoPlayGif } from '../../initial_state';
import { autoPlayGif } from "../../initial_state";
import unicodeMapping from './emoji_unicode_mapping_light';
import unicodeMapping from "./emoji_unicode_mapping_light";
const trie = new Trie(Object.keys(unicodeMapping));
@@ -14,12 +14,12 @@ const emojiFilenames = (emojis) => {
};
// Emoji requiring extra borders depending on theme
const darkEmoji = emojiFilenames(['🎱', '🐜', '⚫', '🖤', '⬛', '◼️', '◾', '◼️', '✒️', '▪️', '💣', '🎳', '📷', '📸', '♣️', '🕶️', '✴️', '🔌', '💂‍♀️', '📽️', '🍳', '🦍', '💂', '🔪', '🕳️', '🕹️', '🕋', '🖊️', '🖋️', '💂‍♂️', '🎤', '🎓', '🎥', '🎼', '♠️', '🎩', '🦃', '📼', '📹', '🎮', '🐃', '🏴', '🐞', '🕺', '📱', '📲', '🚲']);
const lightEmoji = emojiFilenames(['👽', '⚾', '🐔', '☁️', '💨', '🕊️', '👀', '🍥', '👻', '🐐', '❕', '❔', '⛸️', '🌩️', '🔊', '🔇', '📃', '🌧️', '🐏', '🍚', '🍙', '🐓', '🐑', '💀', '☠️', '🌨️', '🔉', '🔈', '💬', '💭', '🏐', '🏳️', '⚪', '⬜', '◽', '◻️', '▫️']);
const darkEmoji = emojiFilenames(["🎱", "🐜", "⚫", "🖤", "⬛", "◼️", "◾", "◼️", "✒️", "▪️", "💣", "🎳", "📷", "📸", "♣️", "🕶️", "✴️", "🔌", "💂‍♀️", "📽️", "🍳", "🦍", "💂", "🔪", "🕳️", "🕹️", "🕋", "🖊️", "🖋️", "💂‍♂️", "🎤", "🎓", "🎥", "🎼", "♠️", "🎩", "🦃", "📼", "📹", "🎮", "🐃", "🏴", "🐞", "🕺", "📱", "📲", "🚲"]);
const lightEmoji = emojiFilenames(["👽", "⚾", "🐔", "☁️", "💨", "🕊️", "👀", "🍥", "👻", "🐐", "❕", "❔", "⛸️", "🌩️", "🔊", "🔇", "📃", "🌧️", "🐏", "🍚", "🍙", "🐓", "🐑", "💀", "☠️", "🌨️", "🔉", "🔈", "💬", "💭", "🏐", "🏳️", "⚪", "⬜", "◽", "◻️", "▫️"]);
const emojiFilename = (filename) => {
const borderedEmoji = (document.body && document.body.classList.contains('theme-mastodon-light')) ? lightEmoji : darkEmoji;
return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
const borderedEmoji = (document.body && document.body.classList.contains("theme-mastodon-light")) ? lightEmoji : darkEmoji;
return borderedEmoji.includes(filename) ? (filename + "_border") : filename;
};
const emojifyTextNode = (node, customEmojis) => {
@@ -40,7 +40,7 @@ const emojifyTextNode = (node, customEmojis) => {
i += str.codePointAt(i) < 65536 ? 1 : 2;
}
} else {
while (i < str.length && str[i] !== ':' && !(unicode_emoji = trie.search(str.slice(i)))) {
while (i < str.length && str[i] !== ":" && !(unicode_emoji = trie.search(str.slice(i)))) {
i += str.codePointAt(i) < 65536 ? 1 : 2;
}
}
@@ -51,8 +51,8 @@ const emojifyTextNode = (node, customEmojis) => {
}
let rend, replacement = null;
if (str[i] === ':') { // Potentially the start of a custom emoji :shortcode:
rend = str.indexOf(':', i + 1) + 1;
if (str[i] === ":") { // Potentially the start of a custom emoji :shortcode:
rend = str.indexOf(":", i + 1) + 1;
// no matching ending ':', skip
if (!rend) {
@@ -72,14 +72,14 @@ const emojifyTextNode = (node, customEmojis) => {
// now got a replacee as ':shortcode:'
// if you want additional emoji handler, add statements below which set replacement and return true.
const filename = autoPlayGif ? custom_emoji.url : custom_emoji.static_url;
replacement = document.createElement('img');
replacement.setAttribute('draggable', 'false');
replacement.setAttribute('class', 'emojione custom-emoji');
replacement.setAttribute('alt', shortcode);
replacement.setAttribute('title', shortcode);
replacement.setAttribute('src', filename);
replacement.setAttribute('data-original', custom_emoji.url);
replacement.setAttribute('data-static', custom_emoji.static_url);
replacement = document.createElement("img");
replacement.setAttribute("draggable", "false");
replacement.setAttribute("class", "emojione custom-emoji");
replacement.setAttribute("alt", shortcode);
replacement.setAttribute("title", shortcode);
replacement.setAttribute("src", filename);
replacement.setAttribute("data-original", custom_emoji.url);
replacement.setAttribute("data-static", custom_emoji.static_url);
} else { // start of an unicode emoji
rend = i + unicode_emoji.length;
@@ -90,14 +90,14 @@ const emojifyTextNode = (node, customEmojis) => {
}
const { filename, shortCode } = unicodeMapping[unicode_emoji];
const title = shortCode ? `:${shortCode}:` : '';
const title = shortCode ? `:${shortCode}:` : "";
replacement = document.createElement('img');
replacement.setAttribute('draggable', 'false');
replacement.setAttribute('class', 'emojione');
replacement.setAttribute('alt', unicode_emoji);
replacement.setAttribute('title', title);
replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
replacement = document.createElement("img");
replacement.setAttribute("draggable", "false");
replacement.setAttribute("class", "emojione");
replacement.setAttribute("alt", unicode_emoji);
replacement.setAttribute("title", title);
replacement.setAttribute("src", `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
}
// Add the processed-up-to-now string and the emoji replacement
@@ -114,23 +114,25 @@ const emojifyTextNode = (node, customEmojis) => {
const emojifyNode = (node, customEmojis) => {
for (const child of node.childNodes) {
switch(child.nodeType) {
case Node.TEXT_NODE:
emojifyTextNode(child, customEmojis);
break;
case Node.ELEMENT_NODE:
if (!child.classList.contains('invisible'))
emojifyNode(child, customEmojis);
break;
case Node.TEXT_NODE:
emojifyTextNode(child, customEmojis);
break;
case Node.ELEMENT_NODE:
if (!child.classList.contains("invisible")) {
emojifyNode(child, customEmojis);
}
break;
}
}
};
const emojify = (str, customEmojis = {}) => {
const wrapper = document.createElement('div');
const wrapper = document.createElement("div");
wrapper.innerHTML = str;
if (!Object.keys(customEmojis).length)
if (!Object.keys(customEmojis).length) {
customEmojis = null;
}
emojifyNode(wrapper, customEmojis);
@@ -143,24 +145,24 @@ export const buildCustomEmojis = (customEmojis) => {
const emojis = [];
customEmojis.forEach(emoji => {
const shortcode = emoji.get('shortcode');
const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
const name = shortcode.replace(':', '');
const shortcode = emoji.get("shortcode");
const url = autoPlayGif ? emoji.get("url") : emoji.get("static_url");
const name = shortcode.replace(":", "");
emojis.push({
id: name,
name,
short_names: [name],
text: '',
text: "",
emoticons: [],
keywords: [name],
imageUrl: url,
custom: true,
customCategory: emoji.get('category'),
customCategory: emoji.get("category"),
});
});
return emojis;
};
export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get("category") ? `custom-${emoji.get("category")}` : "custom"), new Set(["custom"]));
+10 -10
View File
@@ -1,5 +1,5 @@
import type { BaseEmoji, EmojiData, NimbleEmojiIndex } from 'emoji-mart';
import type { Category, Data, Emoji } from 'emoji-mart/dist-es/utils/data';
import { type BaseEmoji, type EmojiData, type NimbleEmojiIndex } from "emoji-mart";
import { type Category, type Data, type Emoji } from "emoji-mart/dist-es/utils/data";
/*
* The 'search' property, although not defined in the [`Emoji`]{@link node_modules/@types/emoji-mart/dist-es/utils/data.d.ts#Emoji} type,
@@ -17,15 +17,15 @@ export type Skins = null;
export type FilenameData = string[] | string[][];
export type ShortCodesToEmojiDataKey =
| EmojiData['id']
| BaseEmoji['native']
| keyof NimbleEmojiIndex['emojis'];
| EmojiData["id"]
| BaseEmoji["native"]
| keyof NimbleEmojiIndex["emojis"];
export type SearchData = [
BaseEmoji['native'],
Emoji['short_names'],
BaseEmoji["native"],
Emoji["short_names"],
Search,
Emoji['unified'],
Emoji["unified"],
];
export type ShortCodesToEmojiData = Record<
@@ -38,7 +38,7 @@ export type EmojiCompressed = [
ShortCodesToEmojiData,
Skins,
Category[],
Data['aliases'],
Data["aliases"],
EmojisWithoutShortCodes,
];
@@ -49,4 +49,4 @@ export type EmojiCompressed = [
*/
declare const emojiCompressed: EmojiCompressed;
export default emojiCompressed; // eslint-disable-line import/no-default-export
export default emojiCompressed;
@@ -1,5 +1,4 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here due to preval */
// @preval
// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
// This file contains the compressed version of the emoji data from
@@ -7,13 +6,13 @@
// It's designed to be emitted in an array format to take up less space
// over the wire.
const { emojiIndex } = require('emoji-mart');
let data = require('emoji-mart/data/all.json');
const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
const { emojiIndex } = require("emoji-mart");
let data = require("emoji-mart/data/all.json");
const { uncompress: emojiMartUncompress } = require("emoji-mart/dist/utils/data");
const emojiMap = require('./emoji_map.json');
const { unicodeToFilename } = require('./unicode_to_filename');
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
const emojiMap = require("./emoji_map.json");
const { unicodeToFilename } = require("./unicode_to_filename");
const { unicodeToUnifiedName } = require("./unicode_to_unified_name");
if(data.compressed) {
@@ -22,8 +21,8 @@ if(data.compressed) {
const emojiMartData = data;
const excluded = ['®', '©', '™'];
const skinTones = ['🏻', '🏼', '🏽', '🏾', '🏿'];
const excluded = ["®", "©", "™"];
const skinTones = ["🏻", "🏼", "🏽", "🏾", "🏿"];
const shortcodeMap = {};
const shortCodesToEmojiData = {};
@@ -33,8 +32,8 @@ Object.keys(emojiIndex.emojis).forEach(key => {
let emoji = emojiIndex.emojis[key];
// Emojis with skin tone modifiers are stored like this
if (Object.prototype.hasOwnProperty.call(emoji, '1')) {
emoji = emoji['1'];
if (Object.prototype.hasOwnProperty.call(emoji, "1")) {
emoji = emoji["1"];
}
shortcodeMap[emoji.native] = emoji.id;
@@ -42,7 +41,7 @@ Object.keys(emojiIndex.emojis).forEach(key => {
const stripModifiers = unicode => {
skinTones.forEach(tone => {
unicode = unicode.replace(tone, '');
unicode = unicode.replace(tone, "");
});
return unicode;
@@ -58,7 +57,7 @@ Object.keys(emojiMap).forEach(key => {
let shortcode = shortcodeMap[normalizedKey];
if (!shortcode) {
shortcode = shortcodeMap[normalizedKey + '\uFE0F'];
shortcode = shortcodeMap[normalizedKey + "\uFE0F"];
}
const filename = emojiMap[key];
@@ -70,7 +69,7 @@ Object.keys(emojiMap).forEach(key => {
filenameData.push(filename);
}
if (typeof shortcode === 'undefined') {
if (typeof shortcode === "undefined") {
emojisWithoutShortCodes.push(filenameData);
} else {
if (!Array.isArray(shortCodesToEmojiData[shortcode])) {
@@ -85,17 +84,17 @@ Object.keys(emojiIndex.emojis).forEach(key => {
let emoji = emojiIndex.emojis[key];
// Emojis with skin tone modifiers are stored like this
if (Object.prototype.hasOwnProperty.call(emoji, '1')) {
emoji = emoji['1'];
if (Object.prototype.hasOwnProperty.call(emoji, "1")) {
emoji = emoji["1"];
}
const { native } = emoji;
let { short_names, search, unified } = emojiMartData.emojis[key];
if (short_names[0] !== key) {
throw new Error('The compressor expects the first short_code to be the ' +
'key. It may need to be rewritten if the emoji change such that this ' +
'is no longer the case.');
throw new Error("The compressor expects the first short_code to be the " +
"key. It may need to be rewritten if the emoji change such that this " +
"is no longer the case.");
}
short_names = short_names.slice(1); // first short name can be inferred from the key
@@ -1,19 +1,19 @@
// The output of this module is designed to mimic emoji-mart's
// "data" object, such that we can use it for a light version of emoji-mart's
// emojiIndex.search functionality.
import type { BaseEmoji } from 'emoji-mart';
import type { Emoji } from 'emoji-mart/dist-es/utils/data';
import { type BaseEmoji } from "emoji-mart";
import { type Emoji } from "emoji-mart/dist-es/utils/data";
import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
import emojiCompressed from './emoji_compressed';
import { unicodeToUnifiedName } from './unicode_to_unified_name';
import { type Search, type ShortCodesToEmojiData } from "./emoji_compressed";
import emojiCompressed from "./emoji_compressed";
import { unicodeToUnifiedName } from "./unicode_to_unified_name";
type Emojis = {
[key in NonNullable<keyof ShortCodesToEmojiData>]: {
native: BaseEmoji['native'];
search: Search;
short_names: Emoji['short_names'];
unified: Emoji['unified'];
native: BaseEmoji["native"],
search: Search,
short_names: Emoji["short_names"],
unified: Emoji["unified"],
};
};
@@ -40,7 +40,9 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
unified = unicodeToUnifiedName(native);
}
if (short_names) short_names = [shortCode].concat(short_names);
if (short_names) {
short_names = [shortCode].concat(short_names);
}
emojis[shortCode] = {
native,
search,
@@ -1,8 +1,8 @@
// This code is largely borrowed from:
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
import * as data from './emoji_mart_data_light';
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
import * as data from "./emoji_mart_data_light";
import { getData, getSanitizedData, uniq, intersect } from "./emoji_utils";
let originalPool = {};
let index = {};
@@ -39,7 +39,9 @@ function clearCustomEmojis(pool) {
}
function addCustomToPool(custom, pool) {
if (customEmojisList.length) clearCustomEmojis(pool);
if (customEmojisList.length) {
clearCustomEmojis(pool);
}
custom.forEach((emoji) => {
let emojiId = emoji.id || emoji.short_names[0];
@@ -56,8 +58,9 @@ function addCustomToPool(custom, pool) {
function search(value, { emojisToShowFilter, maxResults, include, exclude, custom } = {}) {
if (custom !== undefined) {
if (customEmojisList !== custom)
if (customEmojisList !== custom) {
addCustomToPool(custom, originalPool);
}
} else {
custom = [];
}
@@ -70,8 +73,8 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
pool = originalPool;
if (value.length) {
if (value === '-' || value === '-1') {
return [emojisList['-1']];
if (value === "-" || value === "-1") {
return [emojisList["-1"]];
}
let values = value.toLowerCase().split(/[\s|,\-_]+/),
@@ -95,8 +98,8 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
});
if (custom.length) {
let customIsIncluded = include && include.length ? include.indexOf('custom') > -1 : true;
let customIsExcluded = exclude && exclude.length ? exclude.indexOf('custom') > -1 : false;
let customIsIncluded = include && include.length ? include.indexOf("custom") > -1 : true;
let customIsExcluded = exclude && exclude.length ? exclude.indexOf("custom") > -1 : false;
if (customIsIncluded && !customIsExcluded) {
addCustomToPool(custom, pool);
}
@@ -129,7 +132,9 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
if (subIndex !== -1) {
let score = subIndex + 1;
if (sub === id) score = 0;
if (sub === id) {
score = 0;
}
aIndex.results.push(emojisList[id]);
aIndex.pool[id] = emoji;
@@ -1,5 +1,5 @@
import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
import Picker from 'emoji-mart/dist-es/components/picker/picker';
import Emoji from "emoji-mart/dist-es/components/emoji/emoji";
import Picker from "emoji-mart/dist-es/components/picker/picker";
export {
Picker,
@@ -2,8 +2,8 @@
// (i.e. the svg filename) and a shortCode intended to be shown
// as a "title" attribute in an HTML element (aka tooltip).
import emojiCompressed from './emoji_compressed';
import { unicodeToFilename } from './unicode_to_filename';
import emojiCompressed from "./emoji_compressed";
import { unicodeToFilename } from "./unicode_to_filename";
const [
shortCodesToEmojiData,
@@ -1,7 +1,7 @@
// This code is largely borrowed from:
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/index.js
import * as data from './emoji_mart_data_light';
import * as data from "./emoji_mart_data_light";
const buildSearch = (data) => {
const search = [];
@@ -27,7 +27,7 @@ const buildSearch = (data) => {
addToSearch(data.keywords, false);
addToSearch(data.emoticons, false);
return search.join(',');
return search.join(",");
};
const _String = String;
@@ -40,9 +40,9 @@ const stringFromCodePoint = _String.fromCodePoint || function () {
let index = -1;
let length = arguments.length;
if (!length) {
return '';
return "";
}
let result = '';
let result = "";
while (++index < length) {
let codePoint = Number(arguments[index]);
if (
@@ -51,7 +51,7 @@ const stringFromCodePoint = _String.fromCodePoint || function () {
codePoint > 0x10FFFF || // not a valid Unicode code point
Math.floor(codePoint) !== codePoint // not an integer
) {
throw RangeError('Invalid code point: ' + codePoint);
throw RangeError("Invalid code point: " + codePoint);
}
if (codePoint <= 0xFFFF) { // BMP code point
codeUnits.push(codePoint);
@@ -75,12 +75,12 @@ const _JSON = JSON;
const COLONS_REGEX = /^(?::([^:]+):)(?::skin-tone-(\d):)?$/;
const SKINS = [
'1F3FA', '1F3FB', '1F3FC',
'1F3FD', '1F3FE', '1F3FF',
"1F3FA", "1F3FB", "1F3FC",
"1F3FD", "1F3FE", "1F3FF",
];
function unifiedToNative(unified) {
let unicodes = unified.split('-'),
let unicodes = unified.split("-"),
codePoints = unicodes.map((u) => `0x${u}`);
return stringFromCodePoint.apply(null, codePoints);
@@ -124,7 +124,7 @@ function getSanitizedData() {
function getData(emoji, skin, set) {
let emojiData = {};
if (typeof emoji === 'string') {
if (typeof emoji === "string") {
let matches = emoji.match(COLONS_REGEX);
if (matches) {
@@ -220,7 +220,7 @@ function deepMerge(a, b) {
value = b[key];
}
if (typeof value === 'object') {
if (typeof value === "object") {
value = deepMerge(originalValue, value);
}
@@ -232,13 +232,13 @@ function deepMerge(a, b) {
// https://github.com/sonicdoe/measure-scrollbar
function measureScrollbar() {
const div = document.createElement('div');
const div = document.createElement("div");
div.style.width = '100px';
div.style.height = '100px';
div.style.overflow = 'scroll';
div.style.position = 'absolute';
div.style.top = '-9999px';
div.style.width = "100px";
div.style.height = "100px";
div.style.overflow = "scroll";
div.style.position = "absolute";
div.style.top = "-9999px";
document.body.appendChild(div);
const scrollbarWidth = div.offsetWidth - div.clientWidth;
@@ -1,10 +1,9 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
// taken from:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => {
let result = '';
let result = "";
let charCode = 0;
let p = 0;
let i = 0;
@@ -12,7 +11,7 @@ exports.unicodeToFilename = (str) => {
charCode = str.charCodeAt(i++);
if (p) {
if (result.length > 0) {
result += '-';
result += "-";
}
result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16);
p = 0;
@@ -20,7 +19,7 @@ exports.unicodeToFilename = (str) => {
p = charCode;
} else {
if (result.length > 0) {
result += '-';
result += "-";
}
result += charCode.toString(16);
}
@@ -1,20 +1,19 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
function padLeft(str, num) {
while (str.length < num) {
str = '0' + str;
str = "0" + str;
}
return str;
}
exports.unicodeToUnifiedName = (str) => {
let output = '';
let output = "";
for (let i = 0; i < str.length; i += 2) {
if (i > 0) {
output += '-';
output += "-";
}
output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4);
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from "react-intl";
export const SearchSection = ({ title, onClickMore, children }) => (
<div className='search-results__section'>
@@ -1,15 +1,15 @@
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 { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import { Blurhash } from "mastodon/components/blurhash";
import { accountsCountRenderer } from "mastodon/components/hashtag";
import { RelativeTimestamp } from "mastodon/components/relative_timestamp";
import { ShortNumber } from "mastodon/components/short_number";
import { Skeleton } from "mastodon/components/skeleton";
export default class Story extends PureComponent {
@@ -39,17 +39,17 @@ export default class Story extends PureComponent {
const { thumbnailLoaded } = this.state;
return (
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
<a className={classNames("story", { expanded })} href={url} target='blank' rel='noopener'>
<div className='story__details'>
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === "number" ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
</div>
<div className='story__thumbnail'>
{thumbnail ? (
<>
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<div className={classNames("story__thumbnail__preview", { "story__thumbnail__preview--hidden": thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<img src={thumbnail} onLoad={this.handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
</>
) : <Skeleton />}
@@ -1,32 +1,32 @@
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 { Helmet } from 'react-helmet';
import { NavLink, Switch, Route } from 'react-router-dom';
import { Helmet } from "react-helmet";
import { NavLink, Switch, Route } from "react-router-dom";
import { connect } from 'react-redux';
import { connect } from "react-redux";
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { trendsEnabled } from 'mastodon/initial_state';
import Column from "mastodon/components/column";
import ColumnHeader from "mastodon/components/column_header";
import Search from "mastodon/features/compose/containers/search_container";
import { trendsEnabled } from "mastodon/initial_state";
import Links from './links';
import SearchResults from './results';
import Statuses from './statuses';
import Suggestions from './suggestions';
import Tags from './tags';
import Links from "./links";
import SearchResults from "./results";
import Statuses from "./statuses";
import Suggestions from "./suggestions";
import Tags from "./tags";
const messages = defineMessages({
title: { id: 'explore.title', defaultMessage: 'Explore' },
searchResults: { id: 'explore.search_results', defaultMessage: 'Search results' },
title: { id: "explore.title", defaultMessage: "Explore" },
searchResults: { id: "explore.search_results", defaultMessage: "Search results" },
});
const mapStateToProps = state => ({
layout: state.getIn(['meta', 'layout']),
isSearching: state.getIn(['search', 'submitted']) || !trendsEnabled,
layout: state.getIn(["meta", "layout"]),
isSearching: state.getIn(["search", "submitted"]) || !trendsEnabled,
});
class Explore extends PureComponent {
@@ -57,7 +57,7 @@ class Explore extends PureComponent {
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader
icon={isSearching ? 'search' : 'hashtag'}
icon={isSearching ? "search" : "hashtag"}
title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
onClick={this.handleHeaderClick}
multiColumn={multiColumn}
@@ -95,14 +95,14 @@ class Explore extends PureComponent {
<Route path='/explore/tags' component={Tags} />
<Route path='/explore/links' component={Links} />
<Route path='/explore/suggestions' component={Suggestions} />
<Route exact path={['/explore', '/explore/posts', '/search']}>
<Route exact path={["/explore", "/explore/posts", "/search"]}>
<Statuses multiColumn={multiColumn} />
</Route>
</Switch>
<Helmet>
<title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content={isSearching ? 'noindex' : 'all'} />
<meta name='robots' content={isSearching ? "noindex" : "all"} />
</Helmet>
</>
)}
@@ -1,20 +1,20 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { fetchTrendingLinks } from 'mastodon/actions/trends';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { fetchTrendingLinks } from "mastodon/actions/trends";
import { DismissableBanner } from "mastodon/components/dismissable_banner";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
import Story from './components/story';
import Story from "./components/story";
const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']),
isLoading: state.getIn(['trends', 'links', 'isLoading']),
links: state.getIn(["trends", "links", "items"]),
isLoading: state.getIn(["trends", "links", "isLoading"]),
});
class Links extends PureComponent {
@@ -57,18 +57,18 @@ class Links extends PureComponent {
{isLoading ? (<LoadingIndicator />) : links.map((link, i) => (
<Story
key={link.get('id')}
key={link.get("id")}
expanded={i === 0}
lang={link.get('language')}
url={link.get('url')}
title={link.get('title')}
publisher={link.get('provider_name')}
publishedAt={link.get('published_at')}
author={link.get('author_name')}
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
thumbnail={link.get('image')}
thumbnailDescription={link.get('image_description')}
blurhash={link.get('blurhash')}
lang={link.get("language")}
url={link.get("url")}
title={link.get("title")}
publisher={link.get("provider_name")}
publishedAt={link.get("published_at")}
author={link.get("author_name")}
sharedTimes={link.getIn(["history", 0, "accounts"]) * 1 + link.getIn(["history", 1, "accounts"]) * 1}
thumbnail={link.get("image")}
thumbnailDescription={link.get("image_description")}
blurhash={link.get("blurhash")}
/>
))}
</div>
@@ -1,32 +1,32 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import PropTypes from "prop-types";
import { PureComponent } from "react";
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
import { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { List as ImmutableList } from "immutable";
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { submitSearch, expandSearch } from 'mastodon/actions/search';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { Icon } from 'mastodon/components/icon';
import ScrollableList from 'mastodon/components/scrollable_list';
import Account from 'mastodon/containers/account_container';
import Status from 'mastodon/containers/status_container';
import { submitSearch, expandSearch } from "mastodon/actions/search";
import { ImmutableHashtag as Hashtag } from "mastodon/components/hashtag";
import { Icon } from "mastodon/components/icon";
import ScrollableList from "mastodon/components/scrollable_list";
import Account from "mastodon/containers/account_container";
import Status from "mastodon/containers/status_container";
import { SearchSection } from './components/search_section';
import { SearchSection } from "./components/search_section";
const messages = defineMessages({
title: { id: 'search_results.title', defaultMessage: 'Search for {q}' },
title: { id: "search_results.title", defaultMessage: "Search for {q}" },
});
const mapStateToProps = state => ({
isLoading: state.getIn(['search', 'isLoading']),
results: state.getIn(['search', 'results']),
q: state.getIn(['search', 'searchTerm']),
submittedType: state.getIn(['search', 'type']),
isLoading: state.getIn(["search", "isLoading"]),
results: state.getIn(["search", "results"]),
q: state.getIn(["search", "searchTerm"]),
submittedType: state.getIn(["search", "type"]),
});
const INITIAL_PAGE_LIMIT = 10;
@@ -45,7 +45,7 @@ const renderAccounts = accounts => hidePeek(accounts).map(id => (
));
const renderHashtags = hashtags => hidePeek(hashtags).map(hashtag => (
<Hashtag key={hashtag.get('name')} hashtag={hashtag} />
<Hashtag key={hashtag.get("name")} hashtag={hashtag} />
));
const renderStatuses = statuses => hidePeek(statuses).map(id => (
@@ -65,17 +65,17 @@ class Results extends PureComponent {
dispatch: PropTypes.func.isRequired,
q: PropTypes.string,
intl: PropTypes.object,
submittedType: PropTypes.oneOf(['accounts', 'statuses', 'hashtags']),
submittedType: PropTypes.oneOf(["accounts", "statuses", "hashtags"]),
};
state = {
type: this.props.submittedType || 'all',
type: this.props.submittedType || "all",
};
static getDerivedStateFromProps(props, state) {
if (props.submittedType !== state.type) {
return {
type: props.submittedType || 'all',
type: props.submittedType || "all",
};
}
@@ -91,7 +91,7 @@ class Results extends PureComponent {
dispatch(submitSearch());
}
this.setState({ type: 'all' });
this.setState({ type: "all" });
};
handleSelectAccounts = () => {
@@ -99,11 +99,11 @@ class Results extends PureComponent {
// If we originally searched for something else (but not everything),
// we need to resubmit the query for this specific type
if (submittedType !== 'accounts') {
dispatch(submitSearch('accounts'));
if (submittedType !== "accounts") {
dispatch(submitSearch("accounts"));
}
this.setState({ type: 'accounts' });
this.setState({ type: "accounts" });
};
handleSelectHashtags = () => {
@@ -111,11 +111,11 @@ class Results extends PureComponent {
// If we originally searched for something else (but not everything),
// we need to resubmit the query for this specific type
if (submittedType !== 'hashtags') {
dispatch(submitSearch('hashtags'));
if (submittedType !== "hashtags") {
dispatch(submitSearch("hashtags"));
}
this.setState({ type: 'hashtags' });
this.setState({ type: "hashtags" });
};
handleSelectStatuses = () => {
@@ -123,16 +123,16 @@ class Results extends PureComponent {
// If we originally searched for something else (but not everything),
// we need to resubmit the query for this specific type
if (submittedType !== 'statuses') {
dispatch(submitSearch('statuses'));
if (submittedType !== "statuses") {
dispatch(submitSearch("statuses"));
}
this.setState({ type: 'statuses' });
this.setState({ type: "statuses" });
};
handleLoadMoreAccounts = () => this._loadMore('accounts');
handleLoadMoreStatuses = () => this._loadMore('statuses');
handleLoadMoreHashtags = () => this._loadMore('hashtags');
handleLoadMoreAccounts = () => this._loadMore("accounts");
handleLoadMoreStatuses = () => this._loadMore("statuses");
handleLoadMoreHashtags = () => this._loadMore("hashtags");
_loadMore (type) {
const { dispatch } = this.props;
@@ -142,7 +142,7 @@ class Results extends PureComponent {
handleLoadMore = () => {
const { type } = this.state;
if (type !== 'all') {
if (type !== "all") {
this._loadMore(type);
}
};
@@ -152,56 +152,56 @@ class Results extends PureComponent {
const { type } = this.state;
// We request 1 more result than we display so we can tell if there'd be a next page
const hasMore = type !== 'all' ? results.get(type, ImmutableList()).size > INITIAL_PAGE_LIMIT && results.get(type).size % INITIAL_PAGE_LIMIT === 1 : false;
const hasMore = type !== "all" ? results.get(type, ImmutableList()).size > INITIAL_PAGE_LIMIT && results.get(type).size % INITIAL_PAGE_LIMIT === 1 : false;
let filteredResults;
const accounts = results.get('accounts', ImmutableList());
const hashtags = results.get('hashtags', ImmutableList());
const statuses = results.get('statuses', ImmutableList());
const accounts = results.get("accounts", ImmutableList());
const hashtags = results.get("hashtags", ImmutableList());
const statuses = results.get("statuses", ImmutableList());
switch(type) {
case 'all':
filteredResults = (accounts.size + hashtags.size + statuses.size) > 0 ? (
<>
{accounts.size > 0 && (
<SearchSection key='accounts' title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>} onClickMore={this.handleLoadMoreAccounts}>
{accounts.take(INITIAL_DISPLAY).map(id => <Account key={id} id={id} />)}
</SearchSection>
)}
case "all":
filteredResults = (accounts.size + hashtags.size + statuses.size) > 0 ? (
<>
{accounts.size > 0 && (
<SearchSection key='accounts' title={<><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>} onClickMore={this.handleLoadMoreAccounts}>
{accounts.take(INITIAL_DISPLAY).map(id => <Account key={id} id={id} />)}
</SearchSection>
)}
{hashtags.size > 0 && (
<SearchSection key='hashtags' title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>} onClickMore={this.handleLoadMoreHashtags}>
{hashtags.take(INITIAL_DISPLAY).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
</SearchSection>
)}
{hashtags.size > 0 && (
<SearchSection key='hashtags' title={<><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></>} onClickMore={this.handleLoadMoreHashtags}>
{hashtags.take(INITIAL_DISPLAY).map(hashtag => <Hashtag key={hashtag.get("name")} hashtag={hashtag} />)}
</SearchSection>
)}
{statuses.size > 0 && (
<SearchSection key='statuses' title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>} onClickMore={this.handleLoadMoreStatuses}>
{statuses.take(INITIAL_DISPLAY).map(id => <Status key={id} id={id} />)}
</SearchSection>
)}
</>
) : [];
break;
case 'accounts':
filteredResults = renderAccounts(accounts);
break;
case 'hashtags':
filteredResults = renderHashtags(hashtags);
break;
case 'statuses':
filteredResults = renderStatuses(statuses);
break;
{statuses.size > 0 && (
<SearchSection key='statuses' title={<><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></>} onClickMore={this.handleLoadMoreStatuses}>
{statuses.take(INITIAL_DISPLAY).map(id => <Status key={id} id={id} />)}
</SearchSection>
)}
</>
) : [];
break;
case "accounts":
filteredResults = renderAccounts(accounts);
break;
case "hashtags":
filteredResults = renderHashtags(hashtags);
break;
case "statuses":
filteredResults = renderStatuses(statuses);
break;
}
return (
<>
<div className='account__section-headline'>
<button onClick={this.handleSelectAll} className={type === 'all' ? 'active' : undefined}><FormattedMessage id='search_results.all' defaultMessage='All' /></button>
<button onClick={this.handleSelectAccounts} className={type === 'accounts' ? 'active' : undefined}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button>
<button onClick={this.handleSelectHashtags} className={type === 'hashtags' ? 'active' : undefined}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button>
<button onClick={this.handleSelectStatuses} className={type === 'statuses' ? 'active' : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
<button onClick={this.handleSelectAll} className={type === "all" ? "active" : undefined}><FormattedMessage id='search_results.all' defaultMessage='All' /></button>
<button onClick={this.handleSelectAccounts} className={type === "accounts" ? "active" : undefined}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button>
<button onClick={this.handleSelectHashtags} className={type === "hashtags" ? "active" : undefined}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button>
<button onClick={this.handleSelectStatuses} className={type === "statuses" ? "active" : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
</div>
<div className='explore__search-results' data-nosnippet>
@@ -1,22 +1,22 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { fetchTrendingStatuses, expandTrendingStatuses } from 'mastodon/actions/trends';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import StatusList from 'mastodon/components/status_list';
import { getStatusList } from 'mastodon/selectors';
import { fetchTrendingStatuses, expandTrendingStatuses } from "mastodon/actions/trends";
import { DismissableBanner } from "mastodon/components/dismissable_banner";
import StatusList from "mastodon/components/status_list";
import { getStatusList } from "mastodon/selectors";
const mapStateToProps = state => ({
statusIds: getStatusList(state, 'trending'),
isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'trending', 'next']),
statusIds: getStatusList(state, "trending"),
isLoading: state.getIn(["status_lists", "trending", "isLoading"], true),
hasMore: !!state.getIn(["status_lists", "trending", "next"]),
});
class Statuses extends PureComponent {
@@ -1,18 +1,18 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { fetchSuggestions } from 'mastodon/actions/suggestions';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import AccountCard from 'mastodon/features/directory/components/account_card';
import { fetchSuggestions } from "mastodon/actions/suggestions";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
import AccountCard from "mastodon/features/directory/components/account_card";
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
isLoading: state.getIn(['suggestions', 'isLoading']),
suggestions: state.getIn(["suggestions", "items"]),
isLoading: state.getIn(["suggestions", "isLoading"]),
});
class Suggestions extends PureComponent {
@@ -44,7 +44,7 @@ class Suggestions extends PureComponent {
return (
<div className='explore__suggestions scrollable' data-nosnippet>
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
<AccountCard key={suggestion.get('account')} id={suggestion.get('account')} />
<AccountCard key={suggestion.get("account")} id={suggestion.get("account")} />
))}
</div>
);
@@ -1,19 +1,19 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { fetchTrendingHashtags } from "mastodon/actions/trends";
import { DismissableBanner } from "mastodon/components/dismissable_banner";
import { ImmutableHashtag as Hashtag } from "mastodon/components/hashtag";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
const mapStateToProps = state => ({
hashtags: state.getIn(['trends', 'tags', 'items']),
isLoadingHashtags: state.getIn(['trends', 'tags', 'isLoading']),
hashtags: state.getIn(["trends", "tags", "items"]),
isLoadingHashtags: state.getIn(["trends", "tags", "isLoading"]),
});
class Tags extends PureComponent {
@@ -55,7 +55,7 @@ class Tags extends PureComponent {
{banner}
{isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => (
<Hashtag key={hashtag.get('name')} hashtag={hashtag} />
<Hashtag key={hashtag.get("name")} hashtag={hashtag} />
))}
</div>
);
@@ -1,30 +1,30 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'mastodon/actions/favourites';
import ColumnHeader from 'mastodon/components/column_header';
import StatusList from 'mastodon/components/status_list';
import Column from 'mastodon/features/ui/components/column';
import { getStatusList } from 'mastodon/selectors';
import { addColumn, removeColumn, moveColumn } from "mastodon/actions/columns";
import { fetchFavouritedStatuses, expandFavouritedStatuses } from "mastodon/actions/favourites";
import ColumnHeader from "mastodon/components/column_header";
import StatusList from "mastodon/components/status_list";
import Column from "mastodon/features/ui/components/column";
import { getStatusList } from "mastodon/selectors";
const messages = defineMessages({
heading: { id: 'column.favourites', defaultMessage: 'Favorites' },
heading: { id: "column.favourites", defaultMessage: "Favorites" },
});
const mapStateToProps = state => ({
statusIds: getStatusList(state, 'favourites'),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
statusIds: getStatusList(state, "favourites"),
isLoading: state.getIn(["status_lists", "favourites", "isLoading"], true),
hasMore: !!state.getIn(["status_lists", "favourites", "next"]),
});
class Favourites extends ImmutablePureComponent {
@@ -49,7 +49,7 @@ class Favourites extends ImmutablePureComponent {
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('FAVOURITES', {}));
dispatch(addColumn("FAVOURITES", {}));
}
};
@@ -1,31 +1,31 @@
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 { Helmet } from 'react-helmet';
import { Helmet } from "react-helmet";
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import ImmutablePureComponent from "react-immutable-pure-component";
import { connect } from "react-redux";
import { debounce } from 'lodash';
import { debounce } from "lodash";
import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import AccountContainer from 'mastodon/containers/account_container';
import Column from 'mastodon/features/ui/components/column';
import { fetchFavourites, expandFavourites } from "mastodon/actions/interactions";
import ColumnHeader from "mastodon/components/column_header";
import { Icon } from "mastodon/components/icon";
import { LoadingIndicator } from "mastodon/components/loading_indicator";
import ScrollableList from "mastodon/components/scrollable_list";
import AccountContainer from "mastodon/containers/account_container";
import Column from "mastodon/features/ui/components/column";
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
refresh: { id: "refresh", defaultMessage: "Refresh" },
});
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']),
hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']),
isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true),
accountIds: state.getIn(["user_lists", "favourited_by", props.params.statusId, "items"]),
hasMore: !!state.getIn(["user_lists", "favourited_by", props.params.statusId, "next"]),
isLoading: state.getIn(["user_lists", "favourited_by", props.params.statusId, "isLoading"], true),
});
class Favourites extends ImmutablePureComponent {
@@ -1,16 +1,16 @@
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 { connect } from 'react-redux';
import ImmutablePropTypes from "react-immutable-proptypes";
import { connect } from "react-redux";
import Button from 'mastodon/components/button';
import { toServerSideType } from 'mastodon/utils/filters';
import Button from "mastodon/components/button";
import { toServerSideType } from "mastodon/utils/filters";
const mapStateToProps = (state, { filterId }) => ({
filter: state.getIn(['filters', filterId]),
filter: state.getIn(["filters", filterId]),
});
class AddedToFilter extends PureComponent {
@@ -31,7 +31,7 @@ class AddedToFilter extends PureComponent {
const { filter, contextType } = this.props;
let expiredMessage = null;
if (filter.get('expires_at') && filter.get('expires_at') < new Date()) {
if (filter.get("expires_at") && filter.get("expires_at") < new Date()) {
expiredMessage = (
<>
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='filter_modal.added.expired_title' defaultMessage='Expired filter!' /></h4>
@@ -46,7 +46,7 @@ class AddedToFilter extends PureComponent {
}
let contextMismatchMessage = null;
if (contextType && !filter.get('context').includes(toServerSideType(contextType))) {
if (contextType && !filter.get("context").includes(toServerSideType(contextType))) {
contextMismatchMessage = (
<>
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='filter_modal.added.context_mismatch_title' defaultMessage='Context mismatch!' /></h4>
@@ -61,7 +61,7 @@ class AddedToFilter extends PureComponent {
}
const settings_link = (
<a href={`/filters/${filter.get('id')}/edit`}>
<a href={`/filters/${filter.get("id")}/edit`}>
<FormattedMessage
id='filter_modal.added.settings_link'
defaultMessage='settings page'
@@ -76,7 +76,7 @@ class AddedToFilter extends PureComponent {
<FormattedMessage
id='filter_modal.added.short_explanation'
defaultMessage='This post has been added to the following filter category: {title}.'
values={{ title: filter.get('title') }}
values={{ title: filter.get("title") }}
/>
</p>
@@ -1,28 +1,28 @@
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 { connect } from 'react-redux';
import { connect } from "react-redux";
import fuzzysort from 'fuzzysort';
import fuzzysort from "fuzzysort";
import { Icon } from 'mastodon/components/icon';
import { toServerSideType } from 'mastodon/utils/filters';
import { loupeIcon, deleteIcon } from 'mastodon/utils/icons';
import { Icon } from "mastodon/components/icon";
import { toServerSideType } from "mastodon/utils/filters";
import { loupeIcon, deleteIcon } from "mastodon/utils/icons";
const messages = defineMessages({
search: { id: 'filter_modal.select_filter.search', defaultMessage: 'Search or create' },
clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
search: { id: "filter_modal.select_filter.search", defaultMessage: "Search or create" },
clear: { id: "emoji_button.clear", defaultMessage: "Clear" },
});
const mapStateToProps = (state, { contextType }) => ({
filters: Array.from(state.get('filters').values()).map((filter) => [
filter.get('id'),
filter.get('title'),
filter.get('keywords')?.map((keyword) => keyword.get('keyword')).join('\n'),
filter.get('expires_at') && filter.get('expires_at') < new Date(),
contextType && !filter.get('context').includes(toServerSideType(contextType)),
filters: Array.from(state.get("filters").values()).map((filter) => [
filter.get("id"),
filter.get("title"),
filter.get("keywords")?.map((keyword) => keyword.get("keyword")).join("\n"),
filter.get("expires_at") && filter.get("expires_at") < new Date(),
contextType && !filter.get("context").includes(toServerSideType(contextType)),
]),
});
@@ -36,19 +36,19 @@ class SelectFilter extends PureComponent {
};
state = {
searchValue: '',
searchValue: "",
};
search () {
const { filters } = this.props;
const { searchValue } = this.state;
if (searchValue === '') {
if (searchValue === "") {
return filters;
}
return fuzzysort.go(searchValue, filters, {
keys: ['1', '2'],
keys: ["1", "2"],
limit: 5,
threshold: -10000,
}).map(result => result.obj);
@@ -61,7 +61,7 @@ class SelectFilter extends PureComponent {
<span className='language-dropdown__dropdown__results__item__common-name'>
(
{filter[3] && <FormattedMessage id='filter_modal.select_filter.expired' defaultMessage='expired' />}
{filter[3] && filter[4] && ', '}
{filter[3] && filter[4] && ", "}
{filter[4] && <FormattedMessage id='filter_modal.select_filter.context_mismatch' defaultMessage='does not apply to this context' />}
)
</span>
@@ -97,29 +97,29 @@ class SelectFilter extends PureComponent {
let element = null;
switch(e.key) {
case ' ':
case 'Enter':
e.currentTarget.click();
break;
case 'ArrowDown':
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
break;
case 'ArrowUp':
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
break;
case 'Tab':
if (e.shiftKey) {
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
} else {
case " ":
case "Enter":
e.currentTarget.click();
break;
case "ArrowDown":
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
}
break;
case 'Home':
element = this.listNode.firstChild;
break;
case 'End':
element = this.listNode.lastChild;
break;
break;
case "ArrowUp":
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
break;
case "Tab":
if (e.shiftKey) {
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
} else {
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
}
break;
case "Home":
element = this.listNode.firstChild;
break;
case "End":
element = this.listNode.lastChild;
break;
}
if (element) {
@@ -133,26 +133,26 @@ class SelectFilter extends PureComponent {
let element = null;
switch(e.key) {
case 'Tab':
case 'ArrowDown':
element = this.listNode.firstChild;
case "Tab":
case "ArrowDown":
element = this.listNode.firstChild;
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
break;
break;
}
};
handleClear = () => {
this.setState({ searchValue: '' });
this.setState({ searchValue: "" });
};
handleItemClick = e => {
const value = e.currentTarget.getAttribute('data-index');
const value = e.currentTarget.getAttribute("data-index");
e.preventDefault();
@@ -169,7 +169,7 @@ class SelectFilter extends PureComponent {
const { intl } = this.props;
const { searchValue } = this.state;
const isSearching = searchValue !== '';
const isSearching = searchValue !== "";
const results = this.search();
return (
@@ -1,26 +1,26 @@
import PropTypes from 'prop-types';
import { useRef, useCallback, useEffect } from 'react';
import PropTypes from "prop-types";
import { useRef, useCallback, useEffect } from "react";
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { useIntl, defineMessages, FormattedMessage } from "react-intl";
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { Helmet } from "react-helmet";
import { NavLink } from "react-router-dom";
import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { addColumn } from "mastodon/actions/columns";
import { changeSetting } from "mastodon/actions/settings";
import { connectPublicStream, connectCommunityStream } from "mastodon/actions/streaming";
import { expandPublicTimeline, expandCommunityTimeline } from "mastodon/actions/timelines";
import { DismissableBanner } from "mastodon/components/dismissable_banner";
import initialState, { domain } from "mastodon/initial_state";
import { useAppDispatch, useAppSelector } from "mastodon/store";
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import SettingToggle from '../notifications/components/setting_toggle';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from "../../components/column";
import ColumnHeader from "../../components/column_header";
import SettingToggle from "../notifications/components/setting_toggle";
import StatusListContainer from "../ui/containers/status_list_container";
const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
title: { id: "column.firehose", defaultMessage: "Live feeds" },
});
// TODO: use a proper React context later on
@@ -34,9 +34,9 @@ const useIdentity = () => ({
const ColumnSettings = () => {
const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose']));
const settings = useAppSelector((state) => state.getIn(["settings", "firehose"]));
const onChange = useCallback(
(key, checked) => dispatch(changeSetting(['firehose', ...key], checked)),
(key, checked) => dispatch(changeSetting(["firehose", ...key], checked)),
[dispatch],
);
@@ -45,7 +45,7 @@ const ColumnSettings = () => {
<div className='column-settings__row'>
<SettingToggle
settings={settings}
settingPath={['onlyMedia']}
settingPath={["onlyMedia"]}
onChange={onChange}
label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />}
/>
@@ -60,21 +60,21 @@ const Firehose = ({ feedType, multiColumn }) => {
const { signedIn } = useIdentity();
const columnRef = useRef(null);
const onlyMedia = useAppSelector((state) => state.getIn(['settings', 'firehose', 'onlyMedia'], false));
const hasUnread = useAppSelector((state) => state.getIn(['timelines', `${feedType}${onlyMedia ? ':media' : ''}`, 'unread'], 0) > 0);
const onlyMedia = useAppSelector((state) => state.getIn(["settings", "firehose", "onlyMedia"], false));
const hasUnread = useAppSelector((state) => state.getIn(["timelines", `${feedType}${onlyMedia ? ":media" : ""}`, "unread"], 0) > 0);
const handlePin = useCallback(
() => {
switch(feedType) {
case 'community':
dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
break;
case 'public':
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
break;
case 'public:remote':
dispatch(addColumn('REMOTE', { other: { onlyMedia, onlyRemote: true } }));
break;
case "community":
dispatch(addColumn("COMMUNITY", { other: { onlyMedia } }));
break;
case "public":
dispatch(addColumn("PUBLIC", { other: { onlyMedia } }));
break;
case "public:remote":
dispatch(addColumn("REMOTE", { other: { onlyMedia, onlyRemote: true } }));
break;
}
},
[dispatch, onlyMedia, feedType],
@@ -83,15 +83,15 @@ const Firehose = ({ feedType, multiColumn }) => {
const handleLoadMore = useCallback(
(maxId) => {
switch(feedType) {
case 'community':
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
break;
case 'public':
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
break;
case 'public:remote':
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote: true }));
break;
case "community":
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
break;
case "public":
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
break;
case "public:remote":
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote: true }));
break;
}
},
[dispatch, onlyMedia, feedType],
@@ -103,30 +103,30 @@ const Firehose = ({ feedType, multiColumn }) => {
let disconnect;
switch(feedType) {
case 'community':
dispatch(expandCommunityTimeline({ onlyMedia }));
if (signedIn) {
disconnect = dispatch(connectCommunityStream({ onlyMedia }));
}
break;
case 'public':
dispatch(expandPublicTimeline({ onlyMedia }));
if (signedIn) {
disconnect = dispatch(connectPublicStream({ onlyMedia }));
}
break;
case 'public:remote':
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote: true }));
if (signedIn) {
disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote: true }));
}
break;
case "community":
dispatch(expandCommunityTimeline({ onlyMedia }));
if (signedIn) {
disconnect = dispatch(connectCommunityStream({ onlyMedia }));
}
break;
case "public":
dispatch(expandPublicTimeline({ onlyMedia }));
if (signedIn) {
disconnect = dispatch(connectPublicStream({ onlyMedia }));
}
break;
case "public:remote":
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote: true }));
if (signedIn) {
disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote: true }));
}
break;
}
return () => disconnect?.();
}, [dispatch, signedIn, feedType, onlyMedia]);
const prependBanner = feedType === 'community' ? (
const prependBanner = feedType === "community" ? (
<DismissableBanner id='community_timeline'>
<FormattedMessage
id='dismissable_banner.community_timeline'
@@ -144,7 +144,7 @@ const Firehose = ({ feedType, multiColumn }) => {
</DismissableBanner>
);
const emptyMessage = feedType === 'community' ? (
const emptyMessage = feedType === "community" ? (
<FormattedMessage
id='empty_column.community'
defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!'
@@ -185,7 +185,7 @@ const Firehose = ({ feedType, multiColumn }) => {
<StatusListContainer
prepend={prependBanner}
timelineId={`${feedType}${onlyMedia ? ':media' : ''}`}
timelineId={`${feedType}${onlyMedia ? ":media" : ""}`}
onLoadMore={handleLoadMore}
trackScroll
scrollKey='firehose'
@@ -1,19 +1,19 @@
import PropTypes from 'prop-types';
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl } from "react-intl";
import { Link } from 'react-router-dom';
import { Link } from "react-router-dom";
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 { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import { IconButton } from '../../../components/icon_button';
import { Avatar } from "../../../components/avatar";
import { DisplayName } from "../../../components/display_name";
import { IconButton } from "../../../components/icon_button";
const messages = defineMessages({
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
authorize: { id: "follow_request.authorize", defaultMessage: "Authorize" },
reject: { id: "follow_request.reject", defaultMessage: "Reject" },
});
class AccountAuthorize extends ImmutablePureComponent {
@@ -27,12 +27,12 @@ class AccountAuthorize extends ImmutablePureComponent {
render () {
const { intl, account, onAuthorize, onReject } = this.props;
const content = { __html: account.get('note_emojified') };
const content = { __html: account.get("note_emojified") };
return (
<div className='account-authorize__wrapper'>
<div className='account-authorize'>
<Link to={`/@${account.get('acct')}`} className='detailed-status__display-name'>
<Link to={`/@${account.get("acct")}`} className='detailed-status__display-name'>
<div className='account-authorize__avatar'><Avatar account={account} size={48} /></div>
<DisplayName account={account} />
</Link>
@@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import { connect } from "react-redux";
import { authorizeFollowRequest, rejectFollowRequest } from '../../../actions/accounts';
import { makeGetAccount } from '../../../selectors';
import AccountAuthorize from '../components/account_authorize';
import { authorizeFollowRequest, rejectFollowRequest } from "../../../actions/accounts";
import { makeGetAccount } from "../../../selectors";
import AccountAuthorize from "../components/account_authorize";
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();

Some files were not shown because too many files have changed in this diff Show More