This commit is contained in:
Mario 2025-06-15 01:45:10 -04:00
parent ab55cad02e
commit be07032ad7

View file

@ -14,13 +14,13 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
import DisabledIcon from '@/material-icons/400-24px/close-fill.svg?react';
import EnabledIcon from '@/material-icons/400-24px/done-fill.svg?react';
import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
import { Account } from 'mastodon/components/account';
import Column from 'mastodon/components/column';
import { Icon } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
import { Skeleton } from 'mastodon/components/skeleton';
import { LinkFooter} from 'mastodon/features/ui/components/link_footer';
import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
const messages = defineMessages({
title: { id: 'column.about', defaultMessage: 'About' },
@ -40,6 +40,10 @@ const messages = defineMessages({
enabled: { id: 'about.enabled', defaultMessage: 'Enabled' },
disabled: { id: 'about.disabled', defaultMessage: 'Disabled' },
capabilities: { id: 'about.kmyblue_capabilities', defaultMessage: 'Features available in this server' },
joinFediverse: {
id: 'about.join_fediverse',
defaultMessage: "Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse."
},
});
const severityMessages = {
@ -59,14 +63,13 @@ const severityMessages = {
},
};
const mapStateToProps = state => ({
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,
@ -85,49 +88,64 @@ class Section extends PureComponent {
this.setState({ collapsed: !collapsed }, () => onOpen && onOpen());
};
render () {
handleKeyDown = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.handleClick();
}
};
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}>
<div
className="about__section__title"
role="button"
tabIndex={0}
onClick={this.handleClick}
onKeyDown={this.handleKeyDown}
aria-expanded={!collapsed}
>
<Icon id={collapsed ? 'chevron-right' : 'chevron-down'} icon={collapsed ? ChevronRightIcon : ExpandMoreIcon} /> {title}
</div>
{!collapsed && (
<div className='about__section__body'>{children}</div>
)}
{!collapsed && <div className="about__section__body">{children}</div>}
</div>
);
}
}
class CapabilityIcon extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
state: PropTypes.bool,
};
render () {
render() {
const { intl, state } = this.props;
if (state) {
return (
<span className='capability-icon enabled'><Icon id='check' icon={EnabledIcon} title={intl.formatMessage(messages.enabled)} />{intl.formatMessage(messages.enabled)}</span>
<span className="capability-icon enabled">
<Icon id="check" icon={EnabledIcon} title={intl.formatMessage(messages.enabled)} />
{intl.formatMessage(messages.enabled)}
</span>
);
} else {
return (
<span className='capability-icon disabled'><Icon id='times' icon={DisabledIcon} title={intl.formatMessage(messages.disabled)} />{intl.formatMessage(messages.disabled)}</span>
<span className="capability-icon disabled">
<Icon id="times" icon={DisabledIcon} title={intl.formatMessage(messages.disabled)} />
{intl.formatMessage(messages.disabled)}
</span>
);
}
}
}
class About extends PureComponent {
static propTypes = {
server: ImmutablePropTypes.map,
extendedDescription: ImmutablePropTypes.map,
@ -141,7 +159,7 @@ class About extends PureComponent {
multiColumn: PropTypes.bool,
};
componentDidMount () {
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchServer());
dispatch(fetchExtendedDescription());
@ -152,11 +170,11 @@ class About extends PureComponent {
dispatch(fetchDomainBlocks());
};
render () {
render() {
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
const isLoading = server.get('isLoading');
const fedibirdCapabilities = server.get('fedibird_capabilities') || []; // thinking about isLoading is true
const fedibirdCapabilities = server.get('fedibird_capabilities') || [];
const isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted');
const isPublicVisibility = !fedibirdCapabilities.includes('kmyblue_no_public_visibility');
const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction');
@ -169,59 +187,88 @@ class About extends PureComponent {
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='Social media powered by You!' /></p>
<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="Social media powered by You!" />
</p>
</div>
<div className='about__meta'>
<div className='about__meta__column'>
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
<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' />
<hr className="about__meta__divider" />
<div className='about__meta__column'>
<h4><FormattedMessage id='about.contact' defaultMessage='Contact:' /></h4>
<div className="about__meta__column">
<h4>
<FormattedMessage id="about.contact" defaultMessage="Contact:" />
</h4>
{isLoading ? <Skeleton width='10ch' /> : <a className='about__mail' href={emailLink}>{server.getIn(['contact', 'email'])}</a>}
{isLoading ? (
<Skeleton width="10ch" />
) : (
<a className="about__mail" href={emailLink}>
{server.getIn(['contact', 'email'])}
</a>
)}
</div>
</div>
<Section open title={intl.formatMessage(messages.title)}>
{extendedDescription.get('isLoading') ? (
<>
<Skeleton width='100%' />
<Skeleton width="100%" />
<br />
<Skeleton width='100%' />
<Skeleton width="100%" />
<br />
<Skeleton width='100%' />
<Skeleton width="100%" />
<br />
<Skeleton width='70%' />
<Skeleton width="70%" />
</>
) : (extendedDescription.get('content')?.length > 0 ? (
<div
className='prose'
dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }}
/>
) : 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>
))}
<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', ImmutableList()).isEmpty() ? (
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
<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 => (
<ol className="rules-list">
{server.get('rules').map((rule) => (
<li key={rule.get('id')}>
<div className='rules-list__text'>{rule.get('text')}</div>
{rule.get('hint').length > 0 && (<div className='rules-list__hint'>{rule.get('hint')}</div>)}
<div className="rules-list__text">{rule.get('text')}</div>
{!!rule.get('hint') && rule.get('hint').length > 0 && (
<div className="rules-list__hint">{rule.get('hint')}</div>
)}
</li>
))}
</ol>
@ -229,23 +276,38 @@ class About extends PureComponent {
</Section>
<Section title={intl.formatMessage(messages.capabilities)}>
<p><FormattedMessage id='about.kmyblue_capability' defaultMessage='Server unique features are configured as follows.' /></p>
<p>
<FormattedMessage
id="about.kmyblue_capability"
defaultMessage="This server is using unique features are configured as follows."
/>
</p>
{!isLoading && (
<ol className='rules-list'>
<ol className="rules-list">
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.emojiReaction)}: <CapabilityIcon state={isEmojiReaction} intl={intl} /></span>
<span className="rules-list__text">
{intl.formatMessage(messages.emojiReaction)}: <CapabilityIcon state={isEmojiReaction} intl={intl} />
</span>
</li>
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.publicVisibility)}: <CapabilityIcon state={isPublicVisibility} intl={intl} /></span>
<span className="rules-list__text">
{intl.formatMessage(messages.publicVisibility)}: <CapabilityIcon state={isPublicVisibility} intl={intl} />
</span>
</li>
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.publicUnlistedVisibility)}: <CapabilityIcon state={isPublicUnlistedVisibility} intl={intl} /></span>
<span className="rules-list__text">
{intl.formatMessage(messages.publicUnlistedVisibility)}: <CapabilityIcon state={isPublicUnlistedVisibility} intl={intl} />
</span>
</li>
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.localTimeline)}: <CapabilityIcon state={isLocalTimeline} intl={intl} /></span>
<span className="rules-list__text">
{intl.formatMessage(messages.localTimeline)}: <CapabilityIcon state={isLocalTimeline} intl={intl} />
</span>
</li>
<li>
<span className='rules-list__text'>{intl.formatMessage(messages.fullTextSearch)}: <CapabilityIcon state={isFullTextSearch} intl={intl} /></span>
<span className="rules-list__text">
{intl.formatMessage(messages.fullTextSearch)}: <CapabilityIcon state={isFullTextSearch} intl={intl} />
</span>
</li>
</ol>
)}
@ -254,49 +316,75 @@ class About extends PureComponent {
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
{domainBlocks.get('isLoading') ? (
<>
<Skeleton width='100%' />
<Skeleton width="100%" />
<br />
<Skeleton width='70%' />
<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>
<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>
{domainBlocks.get('items').size > 0 && (
<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_ex') || block.get('severity')].title)}</span>
<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_ex') || 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>
)}
</>
) : (
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
))}
<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='Join the Fediverse, become part of a community, and break free from Big Tech™'s stranglehold on public discourse.' /></p>
<div className="about__footer">
<p>
<FormattedMessage {...messages.joinFediverse} />
</p>
</div>
</div>
<Helmet>
<title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content='all' />
<meta name="robots" content="all" />
</Helmet>
</Column>
);
}
}
export default connect(mapStateToProps)(injectIntl(About));