Merge commit '6a4d14b178fc00de989b4a2758f2a1e4adf5ed33' into glitch-soc/merge-upstream
Conflicts: - `lib/sanitize_ext/sanitize_config.rb`: Glitch-soc allows a wider range of HTML than upstream. Upstream added `:all => %w(lang)`, which was not supported by glitch-soc, added it there too.
This commit is contained in:
commit
487c814eb9
144 files changed
+1363
-1158
No files matched your search
@@ -5,7 +5,6 @@ import type { IntlShape } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
|
||||
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
||||
import { Select } from '@/mastodon/components/form_fields';
|
||||
@@ -123,14 +122,13 @@ export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
|
||||
};
|
||||
|
||||
const selectRules = (state: RootState) => {
|
||||
const rules = state.server.getIn([
|
||||
'server',
|
||||
'rules',
|
||||
]) as ImmutableList<Rule> | null;
|
||||
const rules = state.server.server.item?.rules;
|
||||
|
||||
if (!rules) {
|
||||
return [];
|
||||
}
|
||||
return rules.toJS() as Rule[];
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
const rulesSelector = createSelector(
|
||||
|
||||
@@ -18,6 +18,7 @@ import { LinkFooter} from 'mastodon/features/ui/components/link_footer';
|
||||
|
||||
import { Section } from './components/section';
|
||||
import { RulesSection } from './components/rules';
|
||||
import { getColumnSkipLinkId } from '../ui/components/skip_links';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.about', defaultMessage: 'About' },
|
||||
@@ -41,10 +42,10 @@ const severityMessages = {
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
server: state.getIn(['server', 'server']),
|
||||
server: state.server.server,
|
||||
locale: state.getIn(['meta', 'locale']),
|
||||
extendedDescription: state.getIn(['server', 'extendedDescription']),
|
||||
domainBlocks: state.getIn(['server', 'domainBlocks']),
|
||||
extendedDescription: state.server.extendedDescription,
|
||||
domainBlocks: state.server.domainBlocks,
|
||||
});
|
||||
|
||||
class About extends PureComponent {
|
||||
@@ -76,21 +77,21 @@ class About extends PureComponent {
|
||||
|
||||
render () {
|
||||
const { multiColumn, intl, server, extendedDescription, domainBlocks, locale } = this.props;
|
||||
const isLoading = server.get('isLoading');
|
||||
const isLoading = server.isLoading;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
|
||||
<div className='scrollable about'>
|
||||
<div className='scrollable about' id={getColumnSkipLinkId(1)}>
|
||||
<div className='about__header'>
|
||||
<ServerHeroImage
|
||||
withAltBadge
|
||||
alt={server.getIn(['thumbnail', 'description']) ?? ''}
|
||||
blurhash={server.getIn(['thumbnail', 'blurhash'])}
|
||||
src={server.getIn(['thumbnail', 'url'])}
|
||||
srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')}
|
||||
alt={server.item?.thumbnail.description ?? ''}
|
||||
blurhash={server.item?.thumbnail.blurhash}
|
||||
src={server.item?.thumbnail.url}
|
||||
srcSet={Object.keys(server.item?.thumbnail.versions ?? {}).map((key) => `${server.item?.thumbnail.versions && server.item.thumbnail.versions[key]} ${key.replace('@', '')}`).join(', ')}
|
||||
className='about__header__hero'
|
||||
/>
|
||||
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
|
||||
<h1>{isLoading ? <Skeleton width='10ch' /> : server.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' rel='noopener'>Mastodon</a> }} /></p>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +99,7 @@ class About extends PureComponent {
|
||||
<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.item?.contact?.account?.id} size={36} minimal />
|
||||
</div>
|
||||
|
||||
<hr className='about__meta__divider' />
|
||||
@@ -106,12 +107,12 @@ 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.item?.contact?.email}`}>{server.item?.contact?.email}</a>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section open title={intl.formatMessage(messages.title)}>
|
||||
{extendedDescription.get('isLoading') ? (
|
||||
{extendedDescription.isLoading ? (
|
||||
<>
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
@@ -121,10 +122,10 @@ class About extends PureComponent {
|
||||
<br />
|
||||
<Skeleton width='70%' />
|
||||
</>
|
||||
) : (extendedDescription.get('content')?.length > 0 ? (
|
||||
) : (extendedDescription.item?.content?.length > 0 ? (
|
||||
<div
|
||||
className='prose'
|
||||
dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }}
|
||||
dangerouslySetInnerHTML={{ __html: extendedDescription.item?.content }}
|
||||
/>
|
||||
) : (
|
||||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||
@@ -134,26 +135,26 @@ class About extends PureComponent {
|
||||
<RulesSection />
|
||||
|
||||
<Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}>
|
||||
{domainBlocks.get('isLoading') ? (
|
||||
{domainBlocks.isLoading ? (
|
||||
<>
|
||||
<Skeleton width='100%' />
|
||||
<br />
|
||||
<Skeleton width='70%' />
|
||||
</>
|
||||
) : (domainBlocks.get('isAvailable') ? (
|
||||
) : (domainBlocks.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>
|
||||
|
||||
{domainBlocks.get('items').size > 0 && (
|
||||
{domainBlocks.items.length > 0 && (
|
||||
<div className='about__domain-blocks'>
|
||||
{domainBlocks.get('items').map(block => (
|
||||
<div className='about__domain-blocks__domain' key={block.get('domain')}>
|
||||
{domainBlocks.items.map(block => (
|
||||
<div className='about__domain-blocks__domain' key={block.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.digest}`}>{block.domain}</span></h6>
|
||||
<span className='about__domain-blocks__domain__type' title={intl.formatMessage(severityMessages[block.severity].explanation)}>{intl.formatMessage(severityMessages[block.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.comment ?? '').length > 0 ? block.comment : <FormattedMessage id='about.domain_blocks.no_reason_available' defaultMessage='Reason not available' />}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -164,7 +165,7 @@ class About extends PureComponent {
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<LinkFooter />
|
||||
<LinkFooter context='about' />
|
||||
|
||||
<div className='about__footer'>
|
||||
<p><FormattedMessage id='about.disclaimer' defaultMessage='Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.' /></p>
|
||||
|
||||
@@ -37,10 +37,7 @@ const selectTags = createAppSelector(
|
||||
[
|
||||
(state) => state.profileEdit,
|
||||
(state) =>
|
||||
state.server.getIn(
|
||||
['server', 'accounts', 'max_featured_tags'],
|
||||
10,
|
||||
) as number,
|
||||
state.server.server.item?.configuration.accounts.max_featured_tags ?? 0,
|
||||
],
|
||||
(profileEdit, maxTags) => ({
|
||||
tags: profileEdit.profile?.featuredTags ?? [],
|
||||
|
||||
@@ -133,12 +133,7 @@ export const AccountEdit: FC = () => {
|
||||
|
||||
const maxFieldCount = useAppSelector(
|
||||
(state) =>
|
||||
(state.server.getIn([
|
||||
'server',
|
||||
'configuration',
|
||||
'accounts',
|
||||
'max_profile_fields',
|
||||
]) as number | undefined) ?? 4,
|
||||
state.server.server.item?.configuration.accounts.max_profile_fields ?? 4,
|
||||
);
|
||||
|
||||
const handleOpenModal = useCallback(
|
||||
|
||||
@@ -36,13 +36,7 @@ export const BioModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
|
||||
);
|
||||
const [newBio, setNewBio] = useState(bio ?? '');
|
||||
const maxLength = useAppSelector(
|
||||
(state) =>
|
||||
state.server.getIn([
|
||||
'server',
|
||||
'configuration',
|
||||
'accounts',
|
||||
'max_note_length',
|
||||
]) as number | undefined,
|
||||
(state) => state.server.server.item?.configuration.accounts.max_note_length,
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -9,8 +9,6 @@ import type { FC, FocusEventHandler } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { closeModal } from '@/mastodon/actions/modal';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import type { FieldStatus } from '@/mastodon/components/form_fields';
|
||||
@@ -97,15 +95,10 @@ const messages = defineMessages({
|
||||
// We have two different values- the hard limit set by the server,
|
||||
// and the soft limit for mobile display.
|
||||
const selectFieldLimits = createAppSelector(
|
||||
[
|
||||
(state) =>
|
||||
state.server.getIn(['server', 'configuration', 'accounts']) as
|
||||
| ImmutableMap<string, number>
|
||||
| undefined,
|
||||
],
|
||||
[(state) => state.server.server.item?.configuration.accounts],
|
||||
(accounts) => ({
|
||||
nameLimit: accounts?.get('profile_field_name_limit'),
|
||||
valueLimit: accounts?.get('profile_field_value_limit'),
|
||||
nameLimit: accounts?.profile_field_name_limit,
|
||||
valueLimit: accounts?.profile_field_value_limit,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -84,15 +84,8 @@ export const ImageAltTextField: FC<{
|
||||
}> = ({ imageSrc, altText, onChange, hideTip }) => {
|
||||
const altLimit = useAppSelector(
|
||||
(state) =>
|
||||
state.server.getIn(
|
||||
[
|
||||
'server',
|
||||
'configuration',
|
||||
'accounts',
|
||||
'max_header_description_length',
|
||||
],
|
||||
150,
|
||||
) as number,
|
||||
state.server.server.item?.configuration.accounts
|
||||
.max_header_description_length ?? 0,
|
||||
);
|
||||
|
||||
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||
|
||||
@@ -33,12 +33,7 @@ export const NameModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
|
||||
);
|
||||
const maxLength = useAppSelector(
|
||||
(state) =>
|
||||
state.server.getIn([
|
||||
'server',
|
||||
'configuration',
|
||||
'accounts',
|
||||
'max_display_name_length',
|
||||
]) as number | undefined,
|
||||
state.server.server.item?.configuration.accounts.max_display_name_length,
|
||||
);
|
||||
|
||||
const [newName, setNewName] = useState(displayName ?? '');
|
||||
|
||||
@@ -2,14 +2,15 @@ import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
import { EmptyState } from '@/mastodon/components/empty_state';
|
||||
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
|
||||
@@ -28,8 +29,8 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
blockedBy,
|
||||
withoutAddCollectionButton,
|
||||
}) => {
|
||||
const { acct } = useParams<{ acct?: string }>();
|
||||
const me = useCurrentAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -116,12 +117,12 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (acct) {
|
||||
if (account) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured.other'
|
||||
defaultMessage='{acct} has not featured anything yet.'
|
||||
values={{ acct }}
|
||||
values={{ acct: <DisplayName variant='simple' account={account} /> }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -31,7 +31,7 @@ const selectServerName = createAppSelector(
|
||||
[
|
||||
(state) => state.accounts,
|
||||
(_, accountId: string) => accountId,
|
||||
(state) => state.server.getIn(['server', 'domain']) as string | undefined,
|
||||
(state) => state.server.server.item?.domain,
|
||||
],
|
||||
(accounts, accountId, serverDomain) => {
|
||||
const acct = accounts.getIn([accountId, 'acct']) as string | undefined;
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface CollectionLockupProps {
|
||||
withTimestamp?: boolean;
|
||||
sideContent?: React.ReactNode;
|
||||
className?: string;
|
||||
headingLevel?: 'h2' | 'h3' | 'h4';
|
||||
}
|
||||
|
||||
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||
@@ -54,6 +55,7 @@ export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||
withAuthorHandle = true,
|
||||
withTimestamp,
|
||||
sideContent,
|
||||
headingLevel = 'h3',
|
||||
className,
|
||||
}) => {
|
||||
const { id, name } = collection;
|
||||
@@ -70,7 +72,7 @@ export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||
sideContent={sideContent}
|
||||
>
|
||||
<ListItemLink
|
||||
as='h3'
|
||||
as={headingLevel}
|
||||
to={getCollectionPath(id)}
|
||||
subtitle={
|
||||
<CollectionInfo
|
||||
|
||||
@@ -24,11 +24,13 @@ import classes from './share_modal.module.scss';
|
||||
const messages = defineMessages({
|
||||
shareTextOwn: {
|
||||
id: 'collection.share_template_own',
|
||||
defaultMessage: 'Check out my new collection: {link}',
|
||||
defaultMessage: 'Check out my new collection:',
|
||||
description: 'Collection links are appended after a new line',
|
||||
},
|
||||
shareTextOther: {
|
||||
id: 'collection.share_template_other',
|
||||
defaultMessage: 'Check out this cool collection: {link}',
|
||||
defaultMessage: 'Check out this cool collection:',
|
||||
description: 'Collection links are appended after a new line',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,13 +53,10 @@ export const CollectionShareModal: React.FC<{
|
||||
}, [collectionLink]);
|
||||
|
||||
const handleShareViaPost = useCallback(() => {
|
||||
const shareMessage = isOwnCollection
|
||||
? intl.formatMessage(messages.shareTextOwn, {
|
||||
link: collectionLink,
|
||||
})
|
||||
: intl.formatMessage(messages.shareTextOther, {
|
||||
link: collectionLink,
|
||||
});
|
||||
let shareMessage = isOwnCollection
|
||||
? intl.formatMessage(messages.shareTextOwn)
|
||||
: intl.formatMessage(messages.shareTextOther);
|
||||
shareMessage += `\n\n${collectionLink}`;
|
||||
|
||||
onClose();
|
||||
dispatch(changeCompose(shareMessage));
|
||||
@@ -93,7 +92,7 @@ export const CollectionShareModal: React.FC<{
|
||||
/>
|
||||
|
||||
<div className={classes.preview}>
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
<CollectionPreviewCard collection={collection} headingLevel='h2' />
|
||||
</div>
|
||||
|
||||
<CopyLinkField
|
||||
|
||||
@@ -82,10 +82,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
autoFocus: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
highlighted: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.textareaRef = createRef(null);
|
||||
@@ -220,8 +216,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
Promise.resolve().then(() => {
|
||||
this.textareaRef.current.setSelectionRange(selectionStart, selectionEnd);
|
||||
this.textareaRef.current.focus();
|
||||
this.setState({ highlighted: true });
|
||||
this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700);
|
||||
}).catch(console.error);
|
||||
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
||||
this.textareaRef.current.focus();
|
||||
@@ -252,7 +246,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, onDrop, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props;
|
||||
const { highlighted } = this.state;
|
||||
|
||||
return (
|
||||
<form className='compose-form' onSubmit={this.handleSubmit}>
|
||||
@@ -260,7 +253,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
{!withoutNavigation && <NavigationBar />}
|
||||
<Warning />
|
||||
|
||||
<div className={classNames('compose-form__highlightable', { active: highlighted })} ref={this.setRef}>
|
||||
<div className='compose-form__highlightable' ref={this.setRef}>
|
||||
<EditIndicator />
|
||||
|
||||
<div className='compose-form__dropdowns'>
|
||||
|
||||
@@ -58,7 +58,7 @@ const mapStateToProps = state => ({
|
||||
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
||||
maxChars: state.getIn(['server', 'server', 'item', 'configuration', 'statuses', 'max_characters'], 500),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, props) => ({
|
||||
|
||||
@@ -13,7 +13,7 @@ const GettingStarted: React.FC = () => {
|
||||
<Column>
|
||||
<NavigationPanel multiColumn />
|
||||
|
||||
<LinkFooter multiColumn />
|
||||
<LinkFooter context='multi-column' />
|
||||
|
||||
<Helmet>
|
||||
<title>
|
||||
|
||||
+2
-2
@@ -212,12 +212,12 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className='inline-follow-suggestions__header'>
|
||||
<h3 id={uniqueId}>
|
||||
<h2 id={uniqueId} className='inline-follow-suggestions__title'>
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.who_to_follow'
|
||||
defaultMessage='Who to follow'
|
||||
/>
|
||||
</h3>
|
||||
</h2>
|
||||
|
||||
<div className='inline-follow-suggestions__header__actions'>
|
||||
<button className='link-button' onClick={handleDismiss} type='button'>
|
||||
|
||||
@@ -419,9 +419,7 @@ const InteractionModal: React.FC<{
|
||||
}> = ({ accountId, url, intent }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const signupUrl = useAppSelector(
|
||||
(state) =>
|
||||
(state.server.getIn(['server', 'registrations', 'url'], null) ||
|
||||
'/auth/sign_up') as string,
|
||||
(state) => state.server.server.item?.registrations.url ?? '/auth/sign_up',
|
||||
);
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const name = <DisplayName account={account} variant='simple' />;
|
||||
|
||||
@@ -36,7 +36,7 @@ export const CollapsiblePanel: React.FC<{
|
||||
}, [setExpanded]);
|
||||
|
||||
return (
|
||||
<div className='navigation-panel__list-panel'>
|
||||
<li className='navigation-panel__list-panel'>
|
||||
<div className='navigation-panel__list-panel__header'>
|
||||
<ColumnLink
|
||||
transparent
|
||||
@@ -64,7 +64,7 @@ export const CollapsiblePanel: React.FC<{
|
||||
}
|
||||
title={expanded ? collapseTitle : expandTitle}
|
||||
onClick={handleClick}
|
||||
aria-controls={`${accessibilityId}-content`}
|
||||
ariaControls={`${accessibilityId}-content`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -80,6 +80,6 @@ export const CollapsiblePanel: React.FC<{
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -20,10 +20,7 @@ export const SignInBanner: React.FC = () => {
|
||||
let signupButton: React.ReactNode;
|
||||
|
||||
const signupUrl = useAppSelector(
|
||||
(state) =>
|
||||
(state.server.getIn(['server', 'registrations', 'url'], null) as
|
||||
| string
|
||||
| null) ?? '/auth/sign_up',
|
||||
(state) => state.server.server.item?.registrations.url ?? '/auth/sign_up',
|
||||
);
|
||||
|
||||
if (sso_redirect) {
|
||||
|
||||
@@ -37,21 +37,21 @@ export const Trends: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navigation-panel__portal'>
|
||||
<aside className='navigation-panel__portal'>
|
||||
<div className='getting-started__trends'>
|
||||
<h4>
|
||||
<h2 className='getting-started__trends-heading'>
|
||||
<Link to={'/explore/tags'}>
|
||||
<FormattedMessage
|
||||
id='trends.trending_now'
|
||||
defaultMessage='Trending now'
|
||||
/>
|
||||
</Link>
|
||||
</h4>
|
||||
</h2>
|
||||
|
||||
{trends.take(4).map((hashtag) => (
|
||||
<Hashtag key={hashtag.get('name') as string} hashtag={hashtag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
@@ -249,124 +249,150 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
|
||||
{banner && <div className='navigation-panel__banner'>{banner}</div>}
|
||||
|
||||
<div className='navigation-panel__menu'>
|
||||
<ul className='navigation-panel__menu'>
|
||||
{signedIn && (
|
||||
<>
|
||||
{!multiColumn && (
|
||||
<ColumnLink
|
||||
to='/publish'
|
||||
icon='plus'
|
||||
iconComponent={AddIcon}
|
||||
activeIconComponent={AddIcon}
|
||||
text={intl.formatMessage(messages.compose)}
|
||||
className='button navigation-panel__compose-button'
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
to='/publish'
|
||||
icon='plus'
|
||||
iconComponent={AddIcon}
|
||||
activeIconComponent={AddIcon}
|
||||
text={intl.formatMessage(messages.compose)}
|
||||
className='button navigation-panel__compose-button'
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/home'
|
||||
icon='home'
|
||||
iconComponent={HomeIcon}
|
||||
activeIconComponent={HomeActiveIcon}
|
||||
text={intl.formatMessage(messages.home)}
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/home'
|
||||
icon='home'
|
||||
iconComponent={HomeIcon}
|
||||
activeIconComponent={HomeActiveIcon}
|
||||
text={intl.formatMessage(messages.home)}
|
||||
/>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
|
||||
{trendsEnabled && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/explore'
|
||||
icon='explore'
|
||||
iconComponent={TrendingUpIcon}
|
||||
text={intl.formatMessage(messages.explore)}
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/explore'
|
||||
icon='explore'
|
||||
iconComponent={TrendingUpIcon}
|
||||
text={intl.formatMessage(messages.explore)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{(canViewFeed(signedIn, permissions, localLiveFeedAccess) ||
|
||||
canViewFeed(signedIn, permissions, remoteLiveFeedAccess)) && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={
|
||||
canViewFeed(signedIn, permissions, localLiveFeedAccess)
|
||||
? '/public/local'
|
||||
: '/public/remote'
|
||||
}
|
||||
icon='globe'
|
||||
iconComponent={PublicIcon}
|
||||
isActive={isFirehoseActive}
|
||||
text={intl.formatMessage(
|
||||
canViewFeed(signedIn, permissions, localLiveFeedAccess) &&
|
||||
canViewFeed(signedIn, permissions, remoteLiveFeedAccess)
|
||||
? messages.firehose
|
||||
: messages.firehose_singular,
|
||||
)}
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={
|
||||
canViewFeed(signedIn, permissions, localLiveFeedAccess)
|
||||
? '/public/local'
|
||||
: '/public/remote'
|
||||
}
|
||||
icon='globe'
|
||||
iconComponent={PublicIcon}
|
||||
isActive={isFirehoseActive}
|
||||
text={intl.formatMessage(
|
||||
canViewFeed(signedIn, permissions, localLiveFeedAccess) &&
|
||||
canViewFeed(signedIn, permissions, remoteLiveFeedAccess)
|
||||
? messages.firehose
|
||||
: messages.firehose_singular,
|
||||
)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<NotificationsLink />
|
||||
<li>
|
||||
<NotificationsLink />
|
||||
</li>
|
||||
|
||||
<FollowRequestsLink />
|
||||
<li>
|
||||
<FollowRequestsLink />
|
||||
</li>
|
||||
|
||||
<AnnualReportNavItem />
|
||||
<li>
|
||||
<AnnualReportNavItem />
|
||||
</li>
|
||||
|
||||
<hr />
|
||||
<li role='separator' />
|
||||
|
||||
<ListPanel />
|
||||
|
||||
<FollowedTagsPanel />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/favourites'
|
||||
icon='star'
|
||||
iconComponent={StarIcon}
|
||||
activeIconComponent={StarActiveIcon}
|
||||
text={intl.formatMessage(messages.favourites)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/bookmarks'
|
||||
icon='bookmarks'
|
||||
iconComponent={BookmarksIcon}
|
||||
activeIconComponent={BookmarksActiveIcon}
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
{areCollectionsEnabled() && (
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
to='/favourites'
|
||||
icon='star'
|
||||
iconComponent={StarIcon}
|
||||
activeIconComponent={StarActiveIcon}
|
||||
text={intl.formatMessage(messages.favourites)}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/bookmarks'
|
||||
icon='bookmarks'
|
||||
iconComponent={BookmarksIcon}
|
||||
activeIconComponent={BookmarksActiveIcon}
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
</li>
|
||||
{areCollectionsEnabled() && (
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/conversations'
|
||||
icon='at'
|
||||
iconComponent={AlternateEmailIcon}
|
||||
text={intl.formatMessage(messages.direct)}
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/conversations'
|
||||
icon='at'
|
||||
iconComponent={AlternateEmailIcon}
|
||||
text={intl.formatMessage(messages.direct)}
|
||||
/>
|
||||
</li>
|
||||
|
||||
<hr />
|
||||
<li role='separator' />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
href='/settings/preferences'
|
||||
icon='cog'
|
||||
iconComponent={SettingsIcon}
|
||||
text={intl.formatMessage(messages.preferences)}
|
||||
/>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
href='/settings/preferences'
|
||||
icon='cog'
|
||||
iconComponent={SettingsIcon}
|
||||
text={intl.formatMessage(messages.preferences)}
|
||||
/>
|
||||
</li>
|
||||
|
||||
<MoreLink />
|
||||
<li>
|
||||
<MoreLink />
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='navigation-panel__legal'>
|
||||
<li className='navigation-panel__legal'>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/about'
|
||||
@@ -374,16 +400,16 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
iconComponent={InfoIcon}
|
||||
text={intl.formatMessage(messages.about)}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{!signedIn && (
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<li className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
|
||||
{disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner />}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div className='flex-spacer' />
|
||||
|
||||
|
||||
+12
-1
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { List } from 'immutable';
|
||||
import type { List, Map } from 'immutable';
|
||||
|
||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
||||
@@ -23,8 +23,19 @@ export const EmbeddedStatusContent: React.FC<{
|
||||
},
|
||||
[mentions],
|
||||
);
|
||||
const hrefToCollection = useCallback(
|
||||
(href: string) => {
|
||||
const collections = status.get('tagged_collections') as List<
|
||||
Map<'url' | 'id', string>
|
||||
>;
|
||||
const collection = collections.find((item) => item.get('url') === href);
|
||||
return collection?.get('id');
|
||||
},
|
||||
[status],
|
||||
);
|
||||
const htmlHandlers = useElementHandledLink({
|
||||
hashtagAccountId: status.get('account') as string | undefined,
|
||||
hrefToCollectionId: hrefToCollection,
|
||||
hrefToMention,
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -106,10 +106,10 @@ export const NotificationAdminReport: React.FC<{
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__label'>
|
||||
<h2 className='notification-group__main__header__label'>
|
||||
{message}
|
||||
<RelativeTimestamp timestamp={report.created_at} />
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{report.comment.length > 0 && (
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ export const NotificationCollection: React.FC<{
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__label'>
|
||||
<h2 className='notification-group__main__header__label'>
|
||||
{type === 'added_to_collection' && (
|
||||
<FormattedMessage
|
||||
id='notification.added_to_collection'
|
||||
@@ -79,7 +79,7 @@ export const NotificationCollection: React.FC<{
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
|
||||
+2
-2
@@ -125,7 +125,7 @@ export const NotificationGroupWithStatus: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main__header__label'>
|
||||
<h2 className='notification-group__main__header__label'>
|
||||
<span>{label}</span>
|
||||
{timestamp && (
|
||||
<>
|
||||
@@ -135,7 +135,7 @@ export const NotificationGroupWithStatus: React.FC<{
|
||||
<RelativeTimestamp timestamp={timestamp} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{statusId && (
|
||||
|
||||
+2
-2
@@ -101,12 +101,12 @@ export const NotificationWithStatus: React.FC<{
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-ungrouped__header'>
|
||||
<h2 className='notification-ungrouped__header'>
|
||||
<div className='notification-ungrouped__header__icon'>
|
||||
<Icon icon={icon} id={iconId} />
|
||||
</div>
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<StatusQuoteManager
|
||||
id={statusId}
|
||||
|
||||
@@ -272,7 +272,12 @@ export const DetailedStatus: React.FC<{
|
||||
);
|
||||
|
||||
if (taggedCollection) {
|
||||
media = <CollectionPreviewCard collection={taggedCollection.toJS()} />;
|
||||
media = (
|
||||
<CollectionPreviewCard
|
||||
collection={taggedCollection.toJS()}
|
||||
headingLevel='h2'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<Card
|
||||
@@ -286,7 +291,10 @@ export const DetailedStatus: React.FC<{
|
||||
const firstLinkedCollection = status.get('tagged_collections').first();
|
||||
if (firstLinkedCollection) {
|
||||
media = (
|
||||
<CollectionPreviewCard collection={firstLinkedCollection.toJS()} />
|
||||
<CollectionPreviewCard
|
||||
collection={firstLinkedCollection.toJS()}
|
||||
headingLevel='h2'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const ComposePanel: React.FC = () => {
|
||||
{signedIn && !hideComposer && <ComposeFormContainer singleColumn />}
|
||||
{signedIn && hideComposer && <div className='compose-form' />}
|
||||
|
||||
<LinkFooter multiColumn={!singleColumn} />
|
||||
<LinkFooter context={singleColumn ? 'default' : 'multi-column'} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
.wrapper {
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&[data-context='default'] {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
&[data-context='multi-column'] {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
&[data-context='about'] {
|
||||
margin-top: 60px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
|
||||
@media screen and (width >= 1175px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
display: inline;
|
||||
margin-inline-end: 0.3em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: inline;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: ' · ';
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -11,94 +11,110 @@ import {
|
||||
termsOfServiceEnabled,
|
||||
} from 'mastodon/initial_state';
|
||||
|
||||
const DividingCircle: React.FC = () => <span aria-hidden>{' · '}</span>;
|
||||
import classes from './link_footer.module.scss';
|
||||
|
||||
export const LinkFooter: React.FC<{
|
||||
multiColumn: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
context?: 'default' | 'multi-column' | 'about';
|
||||
}> = ({ context = 'default' }) => {
|
||||
const multiColumn = context === 'multi-column';
|
||||
|
||||
return (
|
||||
<div className='link-footer'>
|
||||
<p>
|
||||
<strong>{domain}</strong>:{' '}
|
||||
<Link to='/about' target={multiColumn ? '_blank' : undefined}>
|
||||
<FormattedMessage
|
||||
id='footer.about_this_server'
|
||||
defaultMessage='About'
|
||||
/>
|
||||
</Link>
|
||||
{statusPageUrl && (
|
||||
<>
|
||||
<DividingCircle />
|
||||
<a href={statusPageUrl} target='_blank' rel='noopener'>
|
||||
<FormattedMessage id='footer.status' defaultMessage='Status' />
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{canProfileDirectory && (
|
||||
<>
|
||||
<DividingCircle />
|
||||
<Link to='/directory'>
|
||||
<footer className={classes.wrapper} data-context={context}>
|
||||
<section>
|
||||
<h2 className={classes.heading}>{`${domain}:`}</h2>
|
||||
<ul className={classes.list}>
|
||||
<li>
|
||||
<Link to='/about' target={multiColumn ? '_blank' : undefined}>
|
||||
<FormattedMessage
|
||||
id='footer.directory'
|
||||
defaultMessage='Profiles directory'
|
||||
id='footer.about_this_server'
|
||||
defaultMessage='About'
|
||||
/>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<DividingCircle />
|
||||
<Link
|
||||
to='/privacy-policy'
|
||||
target={multiColumn ? '_blank' : undefined}
|
||||
rel='privacy-policy'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='footer.privacy_policy'
|
||||
defaultMessage='Privacy policy'
|
||||
/>
|
||||
</Link>
|
||||
{termsOfServiceEnabled && (
|
||||
<>
|
||||
<DividingCircle />
|
||||
</li>
|
||||
{statusPageUrl && (
|
||||
<li>
|
||||
<a href={statusPageUrl} target='_blank' rel='noopener'>
|
||||
<FormattedMessage id='footer.status' defaultMessage='Status' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{canProfileDirectory && (
|
||||
<li>
|
||||
<Link to='/directory'>
|
||||
<FormattedMessage
|
||||
id='footer.directory'
|
||||
defaultMessage='Profiles directory'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<Link
|
||||
to='/terms-of-service'
|
||||
to='/privacy-policy'
|
||||
target={multiColumn ? '_blank' : undefined}
|
||||
rel='terms-of-service'
|
||||
rel='privacy-policy'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='footer.terms_of_service'
|
||||
defaultMessage='Terms of service'
|
||||
id='footer.privacy_policy'
|
||||
defaultMessage='Privacy policy'
|
||||
/>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Mastodon</strong>:{' '}
|
||||
<a href='https://joinmastodon.org' target='_blank' rel='noopener'>
|
||||
<FormattedMessage id='footer.about' defaultMessage='About' />
|
||||
</a>
|
||||
<DividingCircle />
|
||||
<a href='https://joinmastodon.org/apps' target='_blank' rel='noopener'>
|
||||
<FormattedMessage id='footer.get_app' defaultMessage='Get the app' />
|
||||
</a>
|
||||
<DividingCircle />
|
||||
<Link to='/keyboard-shortcuts'>
|
||||
<FormattedMessage
|
||||
id='footer.keyboard_shortcuts'
|
||||
defaultMessage='Keyboard shortcuts'
|
||||
/>
|
||||
</Link>
|
||||
<DividingCircle />
|
||||
<a href={source_url} rel='noopener' target='_blank'>
|
||||
<FormattedMessage
|
||||
id='footer.source_code'
|
||||
defaultMessage='View source code'
|
||||
/>
|
||||
</a>
|
||||
<DividingCircle />
|
||||
<span className='version'>v{version}</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
{termsOfServiceEnabled && (
|
||||
<li>
|
||||
<Link
|
||||
to='/terms-of-service'
|
||||
target={multiColumn ? '_blank' : undefined}
|
||||
rel='terms-of-service'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='footer.terms_of_service'
|
||||
defaultMessage='Terms of service'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2 className={classes.heading}>Mastodon:</h2>
|
||||
<ul className={classes.list}>
|
||||
<li>
|
||||
<a href='https://joinmastodon.org' target='_blank' rel='noopener'>
|
||||
<FormattedMessage id='footer.about' defaultMessage='About' />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='https://joinmastodon.org/apps'
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='footer.get_app'
|
||||
defaultMessage='Get the app'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link to='/keyboard-shortcuts'>
|
||||
<FormattedMessage
|
||||
id='footer.keyboard_shortcuts'
|
||||
defaultMessage='Keyboard shortcuts'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href={source_url} rel='noopener' target='_blank'>
|
||||
<FormattedMessage
|
||||
id='footer.source_code'
|
||||
defaultMessage='View source code'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className={classes.version}>v{version}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
@@ -84,10 +84,7 @@ const NotificationsButton = () => {
|
||||
const LoginOrSignUp: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const signupUrl = useAppSelector(
|
||||
(state) =>
|
||||
(state.server.getIn(['server', 'registrations', 'url'], null) as
|
||||
| string
|
||||
| null) ?? '/auth/sign_up',
|
||||
(state) => state.server.server.item?.registrations.url ?? '/auth/sign_up',
|
||||
);
|
||||
|
||||
const openClosedRegistrationsModal = useCallback(() => {
|
||||
@@ -95,7 +92,7 @@ const LoginOrSignUp: React.FC = () => {
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchServer());
|
||||
void dispatch(fetchServer());
|
||||
}, [dispatch]);
|
||||
|
||||
if (sso_redirect) {
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ReportCollectionModal: React.FC<{
|
||||
const account = useAccount(account_id);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchServer());
|
||||
void dispatch(fetchServer());
|
||||
}, [dispatch]);
|
||||
|
||||
const [submitState, setSubmitState] = useState<
|
||||
|
||||
Reference in new issue
Block a user