4691b0068c
* Remove the search button from UI header when logged out (#25631) * Change account search to match by text when opted-in (#25599) Co-authored-by: Eugen Rochko <eugen@zeonfederated.com> * Fix ResolveURLService not resolving local URLs for remote content (#25637) * Remove `pkg-config` gem dependency (#25615) * Update Crowdin configuration file * Fix onboarding prompt being displayed because of disconnection gaps (#25617) * Use an Immutable Record as the root state (#25584) * Add index to backups on `user_id` column (#25647) * Fix rails `rewhere` deprecation warning in directories api controller (#25625) * Remove unused routes (#25578) * Fixing an issue with a missing argument (#2261) undefined * Update uri to version 0.12.2 (CVE fix) (#25657) * Change local and federated timelines to be in a single firehose column (#25641) * Fix HTTP 500 in `/api/v1/emails/check_confirmation` (#25595) * Rails 7 update (#24241) * Change dropdown icon above compose form from ellipsis to bars in web UI (#25661) * Prevent duplicate concurrent calls of `/api/*/instance` in web UI (#25663) * Revert "Rails 7 update" (#25667) * [Glitch] Remove the search button from UI header when logged out Port285a691936
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * [Glitch] Fix onboarding prompt being displayed because of disconnection gaps Port9934949fc4
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * [Glitch] Use an Immutable Record as the root state Port78ba12f0bf
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * [Glitch] Change local and federated timelines to be in a single firehose column Portcea9db5a0b
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * [Glitch] Change dropdown icon above compose form from ellipsis to bars in web UI Port0512537eb6
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * [Glitch] Prevent duplicate concurrent calls of `/api/*/instance` in web UI Port5b46345459
to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com> * Show local-only posts in “All” by default, and add back option to toggle it * Fix showing local only toots in "All" (#2265) * Fix warnings about missing dependency in hooks Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> * Add `allowLocalOnly` to timelineId Without this local-only toots will never be loaded. feedType is checked to be public to not show local-only toots in the "Remote" tab. Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> --------- Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> * Add regex filter back to firehose (#2266) * Add regex filter back to firehose The regex filter will apply to all tabs and not be automatically applied when pinned. Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> * Keep regex when pinned Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> --------- Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> --------- Signed-off-by: Claire <claire.github-309c@sitedethib.com> Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com> Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: jsgoldstein <jakegoldstein95@gmail.com> Co-authored-by: Eugen Rochko <eugen@zeonfederated.com> Co-authored-by: Renaud Chaput <renchap@gmail.com> Co-authored-by: Matt Jankowski <matt@jankowski.online> Co-authored-by: Vivianne <puttabutta@gmail.com> Co-authored-by: Daniel M Brasil <danielmbrasil@protonmail.com> Co-authored-by: mogaminsk <mgmnjp@icloud.com> Co-authored-by: Plastikmensch <Plastikmensch@users.noreply.github.com>
224 lines
8.9 KiB
JavaScript
224 lines
8.9 KiB
JavaScript
import PropTypes from 'prop-types';
|
|
import { PureComponent } from 'react';
|
|
|
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
|
|
import classNames from 'classnames';
|
|
import { Helmet } from 'react-helmet';
|
|
|
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
import { connect } from 'react-redux';
|
|
|
|
import { fetchServer, fetchExtendedDescription, 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.' },
|
|
});
|
|
|
|
const severityMessages = {
|
|
silence: {
|
|
title: messages.silenced,
|
|
explanation: messages.silencedExplanation,
|
|
},
|
|
|
|
suspend: {
|
|
title: messages.suspended,
|
|
explanation: messages.suspendedExplanation,
|
|
},
|
|
};
|
|
|
|
const mapStateToProps = state => ({
|
|
server: state.getIn(['server', 'server']),
|
|
extendedDescription: state.getIn(['server', 'extendedDescription']),
|
|
domainBlocks: state.getIn(['server', 'domainBlocks']),
|
|
});
|
|
|
|
class Section extends PureComponent {
|
|
|
|
static propTypes = {
|
|
title: PropTypes.string,
|
|
children: PropTypes.node,
|
|
open: PropTypes.bool,
|
|
onOpen: PropTypes.func,
|
|
};
|
|
|
|
state = {
|
|
collapsed: !this.props.open,
|
|
};
|
|
|
|
handleClick = () => {
|
|
const { onOpen } = this.props;
|
|
const { collapsed } = this.state;
|
|
|
|
this.setState({ collapsed: !collapsed }, () => onOpen && onOpen());
|
|
};
|
|
|
|
render () {
|
|
const { title, children } = this.props;
|
|
const { collapsed } = this.state;
|
|
|
|
return (
|
|
<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}
|
|
</div>
|
|
|
|
{!collapsed && (
|
|
<div className='about__section__body'>{children}</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
class About extends PureComponent {
|
|
|
|
static propTypes = {
|
|
server: ImmutablePropTypes.map,
|
|
extendedDescription: ImmutablePropTypes.map,
|
|
domainBlocks: ImmutablePropTypes.contains({
|
|
isLoading: PropTypes.bool,
|
|
isAvailable: PropTypes.bool,
|
|
items: ImmutablePropTypes.list,
|
|
}),
|
|
dispatch: PropTypes.func.isRequired,
|
|
intl: PropTypes.object.isRequired,
|
|
multiColumn: PropTypes.bool,
|
|
};
|
|
|
|
componentDidMount () {
|
|
const { dispatch } = this.props;
|
|
dispatch(fetchServer());
|
|
dispatch(fetchExtendedDescription());
|
|
}
|
|
|
|
handleDomainBlocksOpen = () => {
|
|
const { dispatch } = this.props;
|
|
dispatch(fetchDomainBlocks());
|
|
};
|
|
|
|
render () {
|
|
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
|
|
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 {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</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 />
|
|
</div>
|
|
|
|
<hr className='about__meta__divider' />
|
|
|
|
<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>}
|
|
</div>
|
|
</div>
|
|
|
|
<Section open title={intl.formatMessage(messages.title)}>
|
|
{extendedDescription.get('isLoading') ? (
|
|
<>
|
|
<Skeleton width='100%' />
|
|
<br />
|
|
<Skeleton width='100%' />
|
|
<br />
|
|
<Skeleton width='100%' />
|
|
<br />
|
|
<Skeleton width='70%' />
|
|
</>
|
|
) : (extendedDescription.get('content')?.length > 0 ? (
|
|
<div
|
|
className='prose'
|
|
dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }}
|
|
/>
|
|
) : (
|
|
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
|
))}
|
|
</Section>
|
|
|
|
<Section title={intl.formatMessage(messages.rules)}>
|
|
{!isLoading && (server.get('rules', []).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>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
))}
|
|
</Section>
|
|
|
|
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
|
|
{domainBlocks.get('isLoading') ? (
|
|
<>
|
|
<Skeleton width='100%' />
|
|
<br />
|
|
<Skeleton width='70%' />
|
|
</>
|
|
) : (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')}>
|
|
<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>
|
|
</div>
|
|
|
|
<p>{(block.get('comment') || '').length > 0 ? block.get('comment') : <FormattedMessage id='about.domain_blocks.no_reason_available' defaultMessage='Reason not available' />}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
|
))}
|
|
</Section>
|
|
|
|
<LinkFooter />
|
|
|
|
<div className='about__footer'>
|
|
<p><FormattedMessage id='about.disclaimer' defaultMessage='Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.' /></p>
|
|
</div>
|
|
</div>
|
|
|
|
<Helmet>
|
|
<title>{intl.formatMessage(messages.title)}</title>
|
|
<meta name='robots' content='all' />
|
|
</Helmet>
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
export default connect(mapStateToProps)(injectIntl(About));
|