Merge remote-tracking branch 'origin/main' into no-untranslated-jsx

This commit is contained in:
danidfra 2024-10-23 10:44:54 -03:00
commit fb5003682f
12 changed files with 87 additions and 65 deletions

View File

@ -133,7 +133,7 @@
"no-irregular-whitespace": "error", "no-irregular-whitespace": "error",
"no-loop-func": "error", "no-loop-func": "error",
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn", "no-nested-ternary": "error",
"no-restricted-imports": [ "no-restricted-imports": [
"error", "error",
{ {

View File

@ -27,10 +27,10 @@ function logInNostr(pubkey: string) {
secret, secret,
})); }));
dispatch(setNostrPubkey(undefined));
const { access_token } = dispatch(authLoggedIn(token)); const { access_token } = dispatch(authLoggedIn(token));
return await dispatch(verifyCredentials(access_token as string)); await dispatch(verifyCredentials(access_token as string));
dispatch(setNostrPubkey(undefined));
}; };
} }

View File

@ -241,7 +241,8 @@ const saveSettings = (opts?: SettingOpts) =>
const getLocale = (state: RootState, fallback = 'en') => { const getLocale = (state: RootState, fallback = 'en') => {
const localeWithVariant = (getSettings(state).get('locale') as string).replace('_', '-'); const localeWithVariant = (getSettings(state).get('locale') as string).replace('_', '-');
const locale = localeWithVariant.split('-')[0]; const locale = localeWithVariant.split('-')[0];
return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : Object.keys(messages).includes(locale) ? locale : fallback; const fallbackLocale = Object.keys(messages).includes(locale) ? locale : fallback;
return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : fallbackLocale;
}; };
type SettingsAction = type SettingsAction =

View File

@ -2,7 +2,7 @@ import { NRelay, NRelay1, NostrSigner } from '@nostrify/nostrify';
import React, { createContext, useContext, useState, useEffect, useMemo } from 'react'; import React, { createContext, useContext, useState, useEffect, useMemo } from 'react';
import { NKeys } from 'soapbox/features/nostr/keys'; import { NKeys } from 'soapbox/features/nostr/keys';
import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { useAppSelector } from 'soapbox/hooks';
import { useInstance } from 'soapbox/hooks/useInstance'; import { useInstance } from 'soapbox/hooks/useInstance';
interface NostrContextType { interface NostrContextType {
@ -25,13 +25,11 @@ export const NostrProvider: React.FC<NostrProviderProps> = ({ children }) => {
const [relay, setRelay] = useState<NRelay1>(); const [relay, setRelay] = useState<NRelay1>();
const [isRelayOpen, setIsRelayOpen] = useState(false); const [isRelayOpen, setIsRelayOpen] = useState(false);
const { account } = useOwnAccount();
const url = instance.nostr?.relay; const url = instance.nostr?.relay;
const accountPubkey = useAppSelector((state) => state.meta.pubkey ?? account?.nostr.pubkey); const accountPubkey = useAppSelector(({ meta, auth }) => meta.pubkey ?? auth.users.get(auth.me!)?.id);
const signer = useMemo( const signer = useMemo(
() => (accountPubkey ? NKeys.get(accountPubkey) : undefined) ?? window.nostr, () => accountPubkey ? NKeys.get(accountPubkey) ?? window.nostr : undefined,
[accountPubkey, window.nostr], [accountPubkey, window.nostr],
); );

View File

@ -51,7 +51,9 @@ const Domain: React.FC<IDomain> = ({ domain }) => {
})); }));
}; };
const domainState = domain.last_checked_at ? (domain.resolves ? 'active' : 'error') : 'pending'; const resolveState = domain.resolves ? 'active' : 'error';
const domainState = domain.last_checked_at ? resolveState : 'pending';
const domainStateLabel = { const domainStateLabel = {
active: <FormattedMessage id='admin.domains.resolve.success_label' defaultMessage='Resolves correctly' />, active: <FormattedMessage id='admin.domains.resolve.success_label' defaultMessage='Resolves correctly' />,
error: <FormattedMessage id='admin.domains.resolve.fail_label' defaultMessage='Not resolving' />, error: <FormattedMessage id='admin.domains.resolve.fail_label' defaultMessage='Not resolving' />,

View File

@ -141,13 +141,19 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
/></p>} /></p>}
</>); </>);
const confirmationHeading = needsConfirmation
? intl.formatMessage(messages.needsConfirmationHeader)
: undefined;
const approvalHeading = needsApproval
? intl.formatMessage(messages.needsApprovalHeader)
: undefined;
const heading = confirmationHeading || approvalHeading;
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/outline/check.svg'), icon: require('@tabler/icons/outline/check.svg'),
heading: needsConfirmation heading: heading,
? intl.formatMessage(messages.needsConfirmationHeader)
: needsApproval
? intl.formatMessage(messages.needsApprovalHeader)
: undefined,
message, message,
confirm: intl.formatMessage(messages.close), confirm: intl.formatMessage(messages.close),
})); }));

View File

@ -88,12 +88,14 @@ const Backups = () => {
</Card> </Card>
); );
const body = showLoading ? <Spinner /> : backups.isEmpty() ? emptyMessage : ( const backupsContent = backups.isEmpty() ? emptyMessage : (
<div className='mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2'> <div className='mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2'>
{backups.map((backup) => <Backup key={backup.id} backup={backup} />)} {backups.map((backup) => <Backup key={backup.id} backup={backup} />)}
</div> </div>
); );
const body = showLoading ? <Spinner /> : backupsContent;
return ( return (
<Column label={intl.formatMessage(messages.heading)}> <Column label={intl.formatMessage(messages.heading)}>
{body} {body}

View File

@ -70,50 +70,56 @@ const Filters = () => {
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
itemClassName='pb-4 last:pb-0' itemClassName='pb-4 last:pb-0'
> >
{filters.map((filter) => ( {filters.map((filter) => {
<div key={filter.id} className='rounded-lg bg-gray-100 p-4 dark:bg-primary-800'>
<Stack space={2}> const truthMessageFilter = filter.filter_action === 'hide' ?
<Stack className='grow' space={1}> <FormattedMessage id='filters.filters_list_hide_completely' defaultMessage='Hide content' /> :
<Text weight='medium'> <FormattedMessage id='filters.filters_list_warn' defaultMessage='Display warning' />;
<FormattedMessage id='filters.filters_list_phrases_label' defaultMessage='Keywords or phrases:' />
{' '} {/* eslint-disable-line formatjs/no-literal-string-in-jsx */} const falseMessageFilter = (filter.filter_action === 'hide' ?
<Text theme='muted' tag='span'>{filter.keywords.map(keyword => keyword.keyword).join(', ')}</Text> <FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /> :
</Text> <FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' />);
<Text weight='medium'>
<FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' /> return (
{' '} {/* eslint-disable-line formatjs/no-literal-string-in-jsx */} <div key={filter.id} className='rounded-lg bg-gray-100 p-4 dark:bg-primary-800'>
<Text theme='muted' tag='span'>{filter.context.map(context => contexts[context] ? intl.formatMessage(contexts[context]) : context).join(', ')}</Text> <Stack space={2}>
</Text> <Stack className='grow' space={1}>
<HStack space={4} wrap>
<Text weight='medium'> <Text weight='medium'>
{filtersV2 ? ( <FormattedMessage id='filters.filters_list_phrases_label' defaultMessage='Keywords or phrases:' />
filter.filter_action === 'hide' ? {' '} {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
<FormattedMessage id='filters.filters_list_hide_completely' defaultMessage='Hide content' /> : <Text theme='muted' tag='span'>{filter.keywords.map(keyword => keyword.keyword).join(', ')}</Text>
<FormattedMessage id='filters.filters_list_warn' defaultMessage='Display warning' />
) : (filter.filter_action === 'hide' ?
<FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /> :
<FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' />)}
</Text> </Text>
{filter.expires_at && ( <Text weight='medium'>
<FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' />
{' '} {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}
<Text theme='muted' tag='span'>{filter.context.map(context => contexts[context] ? intl.formatMessage(contexts[context]) : context).join(', ')}</Text>
</Text>
<HStack space={4} wrap>
<Text weight='medium'> <Text weight='medium'>
{new Date(filter.expires_at).getTime() <= Date.now() {filtersV2 ? truthMessageFilter : falseMessageFilter}
? <FormattedMessage id='filters.filters_list_expired' defaultMessage='Expired' />
: <RelativeTimestamp timestamp={filter.expires_at} className='whitespace-nowrap' futureDate />}
</Text> </Text>
)} {filter.expires_at && (
<Text weight='medium'>
{new Date(filter.expires_at).getTime() <= Date.now()
? <FormattedMessage id='filters.filters_list_expired' defaultMessage='Expired' />
: <RelativeTimestamp timestamp={filter.expires_at} className='whitespace-nowrap' futureDate />}
</Text>
)}
</HStack>
</Stack>
<HStack space={2} justifyContent='end'>
<Button theme='primary' onClick={handleFilterEdit(filter.id)}>
{intl.formatMessage(messages.edit)}
</Button>
<Button theme='danger' onClick={handleFilterDelete(filter.id)}>
{intl.formatMessage(messages.delete)}
</Button>
</HStack> </HStack>
</Stack> </Stack>
<HStack space={2} justifyContent='end'> </div>
<Button theme='primary' onClick={handleFilterEdit(filter.id)}> );
{intl.formatMessage(messages.edit)} },
</Button> )}
<Button theme='danger' onClick={handleFilterDelete(filter.id)}>
{intl.formatMessage(messages.delete)}
</Button>
</HStack>
</Stack>
</div>
))}
</ScrollableList> </ScrollableList>
</Column> </Column>
); );

View File

@ -121,7 +121,7 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
</OutlineBox> </OutlineBox>
<List> <List>
{(ownAccount.admin && account.local) && ( {(ownAccount.admin && (account.local || features.nostr)) && (
<ListItem label={<FormattedMessage id='account_moderation_modal.fields.account_role' defaultMessage='Staff level' />}> <ListItem label={<FormattedMessage id='account_moderation_modal.fields.account_role' defaultMessage='Staff level' />}>
<div className='w-auto'> <div className='w-auto'>
<StaffRolePicker account={account} /> <StaffRolePicker account={account} />

View File

@ -22,6 +22,8 @@ const CaptchaModal: React.FC<ICaptchaModal> = ({ onClose }) => {
xPosition, xPosition,
} = useCaptcha(); } = useCaptcha();
const messageButton = tryAgain ? <FormattedMessage id='nostr_signup.captcha_try_again_button' defaultMessage='Try again' /> : <FormattedMessage id='nostr_signup.captcha_check_button' defaultMessage='Check' />;
return ( return (
<Modal <Modal
title={<FormattedMessage id='nostr_signup.captcha_title' defaultMessage='Human Verification' />} width='sm' title={<FormattedMessage id='nostr_signup.captcha_title' defaultMessage='Human Verification' />} width='sm'
@ -46,10 +48,7 @@ const CaptchaModal: React.FC<ICaptchaModal> = ({ onClose }) => {
> >
{isSubmitting ? ( {isSubmitting ? (
<FormattedMessage id='nostr_signup.captcha_check_button.checking' defaultMessage='Checking…' /> <FormattedMessage id='nostr_signup.captcha_check_button.checking' defaultMessage='Checking…' />
) : (tryAgain ? ) : messageButton}
<FormattedMessage id='nostr_signup.captcha_try_again_button' defaultMessage='Try again' /> :
<FormattedMessage id='nostr_signup.captcha_check_button' defaultMessage='Check' />
)}
</Button> </Button>
<Button onClick={loadCaptcha}> <Button onClick={loadCaptcha}>
<FormattedMessage id='nostr_signup.captcha_reset_button' defaultMessage='Reset puzzle' /> <FormattedMessage id='nostr_signup.captcha_reset_button' defaultMessage='Reset puzzle' />

View File

@ -44,9 +44,11 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
const [localeLoading, setLocaleLoading] = useState(true); const [localeLoading, setLocaleLoading] = useState(true);
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const { hasNostr, isRelayOpen } = useNostr(); const { hasNostr, isRelayOpen, signer } = useNostr();
const { isSubscribed } = useSignerStream(); const { isSubscribed } = useSignerStream();
const nostrLoading = Boolean(hasNostr && signer && (!isRelayOpen || !isSubscribed));
/** Whether to display a loading indicator. */ /** Whether to display a loading indicator. */
const showLoading = [ const showLoading = [
me === null, me === null,
@ -55,7 +57,7 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
localeLoading, localeLoading,
instance.isLoading, instance.isLoading,
swUpdating, swUpdating,
hasNostr && me && (!isRelayOpen || !isSubscribed), nostrLoading,
].some(Boolean); ].some(Boolean);
// Load the user's locale // Load the user's locale
@ -68,14 +70,14 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
// Load initial data from the API // Load initial data from the API
useEffect(() => { useEffect(() => {
if (!instance.isLoading) { if (!instance.isLoading && !nostrLoading) {
dispatch(loadInitial()).then(() => { dispatch(loadInitial()).then(() => {
setIsLoaded(true); setIsLoaded(true);
}).catch(() => { }).catch(() => {
setIsLoaded(true); setIsLoaded(true);
}); });
} }
}, [instance.isLoading]); }, [instance.isLoading, nostrLoading]);
// intl is part of loading. // intl is part of loading.
// It's important nothing in here depends on intl. // It's important nothing in here depends on intl.

View File

@ -33,12 +33,18 @@ export const AdminAccountRecord = ImmutableRecord({
const normalizePleromaAccount = (account: ImmutableMap<string, any>) => { const normalizePleromaAccount = (account: ImmutableMap<string, any>) => {
if (!account.get('account')) { if (!account.get('account')) {
const isAdmin = account.getIn(['roles', 'admin']);
const isModerator = account.getIn(['roles', 'moderator']) ? 'moderator' : null;
const accountRole = isAdmin ? 'admin' : isModerator;
return account.withMutations(account => { return account.withMutations(account => {
account.set('approved', account.get('is_approved')); account.set('approved', account.get('is_approved'));
account.set('confirmed', account.get('is_confirmed')); account.set('confirmed', account.get('is_confirmed'));
account.set('disabled', !account.get('is_active')); account.set('disabled', !account.get('is_active'));
account.set('invite_request', account.get('registration_reason')); account.set('invite_request', account.get('registration_reason'));
account.set('role', account.getIn(['roles', 'admin']) ? 'admin' : (account.getIn(['roles', 'moderator']) ? 'moderator' : null)); account.set('role', accountRole);
}); });
} }