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

View file

@ -20,7 +20,7 @@ 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 { ServerHeroImage } from 'mastodon/components/server_hero_image';
import { Skeleton } from 'mastodon/components/skeleton'; 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({ const messages = defineMessages({
title: { id: 'column.about', defaultMessage: 'About' }, title: { id: 'column.about', defaultMessage: 'About' },
@ -40,6 +40,10 @@ const messages = defineMessages({
enabled: { id: 'about.enabled', defaultMessage: 'Enabled' }, enabled: { id: 'about.enabled', defaultMessage: 'Enabled' },
disabled: { id: 'about.disabled', defaultMessage: 'Disabled' }, disabled: { id: 'about.disabled', defaultMessage: 'Disabled' },
capabilities: { id: 'about.kmyblue_capabilities', defaultMessage: 'Features available in this server' }, 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 = { const severityMessages = {
@ -59,14 +63,13 @@ const severityMessages = {
}, },
}; };
const mapStateToProps = state => ({ const mapStateToProps = (state) => ({
server: state.getIn(['server', 'server']), server: state.getIn(['server', 'server']),
extendedDescription: state.getIn(['server', 'extendedDescription']), extendedDescription: state.getIn(['server', 'extendedDescription']),
domainBlocks: state.getIn(['server', 'domainBlocks']), domainBlocks: state.getIn(['server', 'domainBlocks']),
}); });
class Section extends PureComponent { class Section extends PureComponent {
static propTypes = { static propTypes = {
title: PropTypes.string, title: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
@ -85,49 +88,64 @@ class Section extends PureComponent {
this.setState({ collapsed: !collapsed }, () => onOpen && onOpen()); 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 { title, children } = this.props;
const { collapsed } = this.state; const { collapsed } = this.state;
return ( 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}> <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} <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} icon={collapsed ? ChevronRightIcon : ExpandMoreIcon} /> {title}
</div> </div>
{!collapsed && ( {!collapsed && <div className="about__section__body">{children}</div>}
<div className='about__section__body'>{children}</div>
)}
</div> </div>
); );
} }
} }
class CapabilityIcon extends PureComponent { class CapabilityIcon extends PureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
state: PropTypes.bool, state: PropTypes.bool,
}; };
render () { render() {
const { intl, state } = this.props; const { intl, state } = this.props;
if (state) { if (state) {
return ( 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 { } else {
return ( 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 { class About extends PureComponent {
static propTypes = { static propTypes = {
server: ImmutablePropTypes.map, server: ImmutablePropTypes.map,
extendedDescription: ImmutablePropTypes.map, extendedDescription: ImmutablePropTypes.map,
@ -141,7 +159,7 @@ class About extends PureComponent {
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
}; };
componentDidMount () { componentDidMount() {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(fetchServer()); dispatch(fetchServer());
dispatch(fetchExtendedDescription()); dispatch(fetchExtendedDescription());
@ -152,11 +170,11 @@ class About extends PureComponent {
dispatch(fetchDomainBlocks()); dispatch(fetchDomainBlocks());
}; };
render () { render() {
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props; const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
const isLoading = server.get('isLoading'); 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 isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted');
const isPublicVisibility = !fedibirdCapabilities.includes('kmyblue_no_public_visibility'); const isPublicVisibility = !fedibirdCapabilities.includes('kmyblue_no_public_visibility');
const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction'); const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction');
@ -169,59 +187,88 @@ class About extends PureComponent {
return ( return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
<div className='scrollable about'> <div className="scrollable about">
<div className='about__header'> <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' /> <ServerHeroImage
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1> blurhash={server.getIn(['thumbnail', 'blurhash'])}
<p><FormattedMessage id='about.powered_by' defaultMessage='Social media powered by You!' /></p> 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>
<div className='about__meta'> <div className="about__meta">
<div className='about__meta__column'> <div className="about__meta__column">
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4> <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> </div>
<hr className='about__meta__divider' /> <hr className="about__meta__divider" />
<div className='about__meta__column'> <div className="about__meta__column">
<h4><FormattedMessage id='about.contact' defaultMessage='Contact:' /></h4> <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>
</div> </div>
<Section open title={intl.formatMessage(messages.title)}> <Section open title={intl.formatMessage(messages.title)}>
{extendedDescription.get('isLoading') ? ( {extendedDescription.get('isLoading') ? (
<> <>
<Skeleton width='100%' /> <Skeleton width="100%" />
<br /> <br />
<Skeleton width='100%' /> <Skeleton width="100%" />
<br /> <br />
<Skeleton width='100%' /> <Skeleton width="100%" />
<br /> <br />
<Skeleton width='70%' /> <Skeleton width="70%" />
</> </>
) : (extendedDescription.get('content')?.length > 0 ? ( ) : extendedDescription.get('content')?.length > 0 ? (
<div <div className="prose" dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }} />
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>
<Section title={intl.formatMessage(messages.rules)}> <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> <p>
<FormattedMessage
id="about.not_available"
defaultMessage="This information has not been made available on this server."
/>
</p>
) : ( ) : (
<ol className='rules-list'> <ol className="rules-list">
{server.get('rules').map(rule => ( {server.get('rules').map((rule) => (
<li key={rule.get('id')}> <li key={rule.get('id')}>
<div className='rules-list__text'>{rule.get('text')}</div> <div className="rules-list__text">{rule.get('text')}</div>
{rule.get('hint').length > 0 && (<div className='rules-list__hint'>{rule.get('hint')}</div>)} {!!rule.get('hint') && rule.get('hint').length > 0 && (
<div className="rules-list__hint">{rule.get('hint')}</div>
)}
</li> </li>
))} ))}
</ol> </ol>
@ -229,23 +276,38 @@ class About extends PureComponent {
</Section> </Section>
<Section title={intl.formatMessage(messages.capabilities)}> <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 && ( {!isLoading && (
<ol className='rules-list'> <ol className="rules-list">
<li> <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>
<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>
<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>
<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>
<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> </li>
</ol> </ol>
)} )}
@ -254,49 +316,75 @@ class About extends PureComponent {
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}> <Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
{domainBlocks.get('isLoading') ? ( {domainBlocks.get('isLoading') ? (
<> <>
<Skeleton width='100%' /> <Skeleton width="100%" />
<br /> <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 && ( {domainBlocks.get('items').size > 0 && (
<div className='about__domain-blocks'> <div className="about__domain-blocks">
{domainBlocks.get('items').map(block => ( {domainBlocks.get('items').map((block) => (
<div className='about__domain-blocks__domain' key={block.get('domain')}> <div className="about__domain-blocks__domain" key={block.get('domain')}>
<div className='about__domain-blocks__domain__header'> <div className="about__domain-blocks__domain__header">
<h6><span title={`SHA-256: ${block.get('digest')}`}>{block.get('domain')}</span></h6> <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> <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> </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>
))} ))}
</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> </Section>
<LinkFooter /> <LinkFooter />
<div className='about__footer'> <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> <p>
<FormattedMessage {...messages.joinFediverse} />
</p>
</div> </div>
</div> </div>
<Helmet> <Helmet>
<title>{intl.formatMessage(messages.title)}</title> <title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content='all' /> <meta name="robots" content="all" />
</Helmet> </Helmet>
</Column> </Column>
); );
} }
} }
export default connect(mapStateToProps)(injectIntl(About)); export default connect(mapStateToProps)(injectIntl(About));