Support Mastodon rule hints
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
93e9760274
commit
f69d1d95e4
|
@ -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,
|
|
||||||
};
|
|
|
@ -11,7 +11,7 @@ import List, { ListItem } from 'soapbox/components/list';
|
||||||
import StatusContent from 'soapbox/components/status-content';
|
import StatusContent from 'soapbox/components/status-content';
|
||||||
import { Avatar, HStack, Icon, Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui';
|
import { Avatar, HStack, Icon, Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui';
|
||||||
import AccountContainer from 'soapbox/containers/account-container';
|
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 ConfirmationStep from './steps/confirmation-step';
|
||||||
import OtherActionsStep from './steps/other-actions-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 entityType = useAppSelector((state) => state.reports.new.entityType);
|
||||||
const isBlocked = useAppSelector((state) => state.reports.new.block);
|
const isBlocked = useAppSelector((state) => state.reports.new.block);
|
||||||
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
|
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 ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||||
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
||||||
const selectedChatMessage = useAppSelector((state) => state.reports.new.chat_message);
|
const selectedChatMessage = useAppSelector((state) => state.reports.new.chat_message);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { OrderedSet } from 'immutable';
|
import { OrderedSet } from 'immutable';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports';
|
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 { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
|
||||||
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
|
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
|
||||||
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||||
|
@ -44,10 +43,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
|
||||||
dispatch(changeReportForward(event.target.checked));
|
dispatch(changeReportForward(event.target.checked));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchRules());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space={4}>
|
<Stack space={4}>
|
||||||
{features.reportMultipleStatuses && (
|
{features.reportMultipleStatuses && (
|
||||||
|
|
|
@ -3,11 +3,11 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { changeReportComment, changeReportRule, ReportableEntities } from 'soapbox/actions/reports';
|
import { changeReportComment, changeReportRule, ReportableEntities } from 'soapbox/actions/reports';
|
||||||
import { fetchRules } from 'soapbox/actions/rules';
|
|
||||||
import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
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 { Account } from 'soapbox/schemas';
|
||||||
|
import type { Rule } from 'soapbox/schemas/rule';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
|
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 entityType = useAppSelector((state) => state.reports.new.entityType);
|
||||||
const comment = useAppSelector((state) => state.reports.new.comment);
|
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 ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||||
const shouldRequireRule = rules.length > 0;
|
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';
|
let ruleTypeToFilter = 'content';
|
||||||
|
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
|
@ -83,10 +83,6 @@ const ReasonStep: React.FC<IReasonStep> = () => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchRules());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rules.length > 0 && rulesListRef.current) {
|
if (rules.length > 0 && rulesListRef.current) {
|
||||||
const { clientHeight } = rulesListRef.current;
|
const { clientHeight } = rulesListRef.current;
|
||||||
|
@ -136,7 +132,7 @@ const ReasonStep: React.FC<IReasonStep> = () => {
|
||||||
>
|
>
|
||||||
{rule.text}
|
{rule.text}
|
||||||
</Text>
|
</Text>
|
||||||
<Text tag='span' theme='muted' size='sm'>{rule.subtext}</Text>
|
<Text tag='span' theme='muted' size='sm'>{rule.hint}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -48,7 +48,6 @@ import profile_hover_card from './profile-hover-card';
|
||||||
import push_notifications from './push-notifications';
|
import push_notifications from './push-notifications';
|
||||||
import relationships from './relationships';
|
import relationships from './relationships';
|
||||||
import reports from './reports';
|
import reports from './reports';
|
||||||
import rules from './rules';
|
|
||||||
import scheduled_statuses from './scheduled-statuses';
|
import scheduled_statuses from './scheduled-statuses';
|
||||||
import search from './search';
|
import search from './search';
|
||||||
import security from './security';
|
import security from './security';
|
||||||
|
@ -110,7 +109,6 @@ const reducers = {
|
||||||
push_notifications,
|
push_notifications,
|
||||||
relationships,
|
relationships,
|
||||||
reports,
|
reports,
|
||||||
rules,
|
|
||||||
scheduled_statuses,
|
scheduled_statuses,
|
||||||
search,
|
search,
|
||||||
security,
|
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';
|
import { coerceObject } from './utils';
|
||||||
|
|
||||||
const ruleSchema = coerceObject({
|
const ruleSchema = z.preprocess((data: any) => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
hint: data.hint || data.subtext,
|
||||||
|
};
|
||||||
|
}, coerceObject({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
text: z.string().catch(''),
|
text: z.string().catch(''),
|
||||||
});
|
hint: z.string().catch(''),
|
||||||
|
rule_type: z.enum(['account', 'content', 'group']).nullable().catch(null),
|
||||||
|
}));
|
||||||
|
|
||||||
type Rule = z.infer<typeof ruleSchema>;
|
type Rule = z.infer<typeof ruleSchema>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue