Merge remote-tracking branch 'origin/main' into dashboard

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
environments/review-main-yi2y9f/deployments/4540^2
marcin mikołajczak 5 months ago
commit b36b636493

@ -1,23 +0,0 @@
import { __stub } from 'soapbox/api';
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
import { fetchRules, RULES_FETCH_REQUEST, RULES_FETCH_SUCCESS } from './rules';
describe('fetchRules()', () => {
it('sets the rules', async () => {
const rules = await import('soapbox/__fixtures__/rules.json');
__stub((mock) => {
mock.onGet('/api/v1/instance/rules').reply(200, rules);
});
const store = mockStore(rootState);
await store.dispatch(fetchRules());
const actions = store.getActions();
expect(actions[0].type).toEqual(RULES_FETCH_REQUEST);
expect(actions[1].type).toEqual(RULES_FETCH_SUCCESS);
expect(actions[1].payload[0].id).toEqual('1');
});
});

@ -1,32 +0,0 @@
import api from '../api';
import type { Rule } from 'soapbox/reducers/rules';
import type { RootState } from 'soapbox/store';
const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
type RulesFetchRequestAction = {
type: typeof RULES_FETCH_REQUEST;
}
type RulesFetchRequestSuccessAction = {
type: typeof RULES_FETCH_SUCCESS;
payload: Rule[];
}
export type RulesActions = RulesFetchRequestAction | RulesFetchRequestSuccessAction
const fetchRules = () => (dispatch: React.Dispatch<RulesActions>, getState: () => RootState) => {
dispatch({ type: RULES_FETCH_REQUEST });
return api(getState)
.get('/api/v1/instance/rules')
.then((response) => dispatch({ type: RULES_FETCH_SUCCESS, payload: response.data }));
};
export {
fetchRules,
RULES_FETCH_REQUEST,
RULES_FETCH_SUCCESS,
};

@ -401,7 +401,6 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
const ownAccount = status.account.id === me;
const username = status.account.username;
const account = status.account;
const domain = account.fqn.split('@')[1];
const menu: Menu = [];
@ -455,6 +454,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
}
if (features.federating && !account.local) {
const { hostname: domain } = new URL(status.uri);
menu.push({
text: intl.formatMessage(messages.external, { domain }),
action: handleExternalClick,

@ -25,10 +25,11 @@ function useEntityLookup<TEntity extends Entity>(
const { schema = z.custom<TEntity>() } = opts;
const dispatch = useAppDispatch();
const [fetchedEntity, setFetchedEntity] = useState<TEntity | undefined>();
const [isFetching, setPromise] = useLoading(true);
const [error, setError] = useState<unknown>();
const entity = useAppSelector(state => findEntity(state, entityType, lookupFn));
const entity = useAppSelector(state => findEntity(state, entityType, lookupFn) ?? fetchedEntity);
const isEnabled = opts.enabled ?? true;
const isLoading = isFetching && !entity;
@ -36,6 +37,7 @@ function useEntityLookup<TEntity extends Entity>(
try {
const response = await setPromise(entityFn());
const entity = schema.parse(response.data);
setFetchedEntity(entity);
dispatch(importEntities([entity], entityType));
} catch (e) {
setError(e);

@ -0,0 +1,34 @@
import { nip19 } from 'nostr-tools';
import React from 'react';
import { Redirect } from 'react-router-dom';
import MissingIndicator from 'soapbox/components/missing-indicator';
interface INIP19Redirect {
params: {
bech32: string;
};
}
const Bech32Redirect: React.FC<INIP19Redirect> = ({ params }) => {
try {
const result = nip19.decode(params.bech32);
switch (result.type) {
case 'npub':
case 'nprofile':
return <Redirect to={`/@${params.bech32}`} />;
case 'note':
return <Redirect to={`/posts/${result.data}`} />;
case 'nevent':
return <Redirect to={`/posts/${result.data.id}`} />;
default:
return <MissingIndicator />;
}
} catch (e) {
return <MissingIndicator />;
}
};
export default Bech32Redirect;

@ -11,7 +11,7 @@ import List, { ListItem } from 'soapbox/components/list';
import StatusContent from 'soapbox/components/status-content';
import { Avatar, HStack, Icon, Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
import ConfirmationStep from './steps/confirmation-step';
import OtherActionsStep from './steps/other-actions-step';
@ -104,7 +104,7 @@ const ReportModal = ({ onClose }: IReportModal) => {
const entityType = useAppSelector((state) => state.reports.new.entityType);
const isBlocked = useAppSelector((state) => state.reports.new.block);
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
const rules = useAppSelector((state) => state.rules.items);
const { rules } = useInstance();
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
const selectedChatMessage = useAppSelector((state) => state.reports.new.chat_message);

@ -1,9 +1,8 @@
import { OrderedSet } from 'immutable';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules';
import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
@ -44,10 +43,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
dispatch(changeReportForward(event.target.checked));
};
useEffect(() => {
dispatch(fetchRules());
}, []);
return (
<Stack space={4}>
{features.reportMultipleStatuses && (

@ -3,11 +3,11 @@ import React, { useEffect, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { changeReportComment, changeReportRule, ReportableEntities } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules';
import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
import type { Account } from 'soapbox/schemas';
import type { Rule } from 'soapbox/schemas/rule';
const messages = defineMessages({
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
@ -31,7 +31,7 @@ const ReasonStep: React.FC<IReasonStep> = () => {
const entityType = useAppSelector((state) => state.reports.new.entityType);
const comment = useAppSelector((state) => state.reports.new.comment);
const rules = useAppSelector((state) => state.rules.items);
const { rules } = useInstance();
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
const shouldRequireRule = rules.length > 0;
@ -57,7 +57,7 @@ const ReasonStep: React.FC<IReasonStep> = () => {
}
};
const filterRuleType = (rule: any) => {
const filterRuleType = (rule: Rule) => {
let ruleTypeToFilter = 'content';
switch (entityType) {
@ -83,10 +83,6 @@ const ReasonStep: React.FC<IReasonStep> = () => {
return true;
};
useEffect(() => {
dispatch(fetchRules());
}, []);
useEffect(() => {
if (rules.length > 0 && rulesListRef.current) {
const { clientHeight } = rulesListRef.current;
@ -136,7 +132,7 @@ const ReasonStep: React.FC<IReasonStep> = () => {
>
{rule.text}
</Text>
<Text tag='span' theme='muted' size='sm'>{rule.subtext}</Text>
<Text tag='span' theme='muted' size='sm'>{rule.hint}</Text>
</Stack>
<input

@ -140,6 +140,7 @@ import {
EditIdentity,
Domains,
NostrRelays,
Bech32Redirect,
Relays,
} from './util/async-components';
import GlobalHotkeys from './util/global-hotkeys';
@ -285,6 +286,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
{features.events && <WrappedRoute path='/@:username/events/:statusId' publicRoute exact page={EventPage} component={EventInformation} content={children} />}
{features.events && <WrappedRoute path='/@:username/events/:statusId/discussion' publicRoute exact page={EventPage} component={EventDiscussion} content={children} />}
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
<WrappedRoute path='/posts/:statusId' publicRoute exact page={DefaultPage} component={Status} content={children} />
{features.groups && <WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} />}
{features.groupsDiscovery && <WrappedRoute path='/groups/discover' exact page={GroupsPage} component={GroupsDiscover} content={children} />}
@ -363,6 +365,8 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Redirect from='/auth/password/new' to='/reset-password' />
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
<WrappedRoute path='/:bech32([\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,})' publicRoute page={EmptyPage} component={Bech32Redirect} content={children} />
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
</Switch>
);

@ -171,4 +171,5 @@ export const EditIdentity = lazy(() => import('soapbox/features/edit-identity'))
export const Domains = lazy(() => import('soapbox/features/admin/domains'));
export const EditDomainModal = lazy(() => import('soapbox/features/ui/components/modals/edit-domain-modal'));
export const NostrRelays = lazy(() => import('soapbox/features/nostr-relays'));
export const Bech32Redirect = lazy(() => import('soapbox/features/nostr/Bech32Redirect'));
export const Relays = lazy(() => import('soapbox/features/admin/relays'));

@ -47,7 +47,6 @@ import profile_hover_card from './profile-hover-card';
import push_notifications from './push-notifications';
import relationships from './relationships';
import reports from './reports';
import rules from './rules';
import scheduled_statuses from './scheduled-statuses';
import search from './search';
import security from './security';
@ -108,7 +107,6 @@ const reducers = {
push_notifications,
relationships,
reports,
rules,
scheduled_statuses,
search,
security,

@ -1,31 +0,0 @@
import { RULES_FETCH_REQUEST, RULES_FETCH_SUCCESS } from 'soapbox/actions/rules';
import reducer from './rules';
const initialState = {
items: [],
isLoading: false,
};
describe('rules reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(initialState);
});
describe('RULES_FETCH_REQUEST', () => {
it('sets "needsOnboarding" to "true"', () => {
const action = { type: RULES_FETCH_REQUEST } as any;
expect(reducer(initialState, action).isLoading).toEqual(true);
});
});
describe('ONBOARDING_END', () => {
it('sets "needsOnboarding" to "false"', () => {
const action = { type: RULES_FETCH_SUCCESS, payload: [{ id: '123' }] } as any;
const result = reducer(initialState, action);
expect(result.isLoading).toEqual(false);
expect(result.items[0].id).toEqual('123');
});
});
});

@ -1,31 +0,0 @@
import { RULES_FETCH_REQUEST, RULES_FETCH_SUCCESS } from '../actions/rules';
import type { RulesActions } from '../actions/rules';
export type Rule = {
id: string;
text: string;
subtext: string;
rule_type: 'content' | 'account';
}
type RulesState = {
items: Rule[];
isLoading: boolean;
}
const initialState: RulesState = {
items: [],
isLoading: false,
};
export default function rules(state: RulesState = initialState, action: RulesActions): RulesState {
switch (action.type) {
case RULES_FETCH_REQUEST:
return { ...state, isLoading: true };
case RULES_FETCH_SUCCESS:
return { ...state, isLoading: false, items: action.payload };
default:
return state;
}
}

@ -2,10 +2,17 @@ import { z } from 'zod';
import { coerceObject } from './utils';
const ruleSchema = coerceObject({
const ruleSchema = z.preprocess((data: any) => {
return {
...data,
hint: data.hint || data.subtext,
};
}, coerceObject({
id: z.string(),
text: z.string().catch(''),
});
hint: z.string().catch(''),
rule_type: z.enum(['account', 'content', 'group']).nullable().catch(null),
}));
type Rule = z.infer<typeof ruleSchema>;

Loading…
Cancel
Save