Add endorsed accounts to profiles in web UI (#34568)
This commit is contained in:
		@@ -1,18 +1,18 @@
 | 
			
		||||
import { createAction } from '@reduxjs/toolkit';
 | 
			
		||||
 | 
			
		||||
import { apiRemoveAccountFromFollowers } from 'mastodon/api/accounts';
 | 
			
		||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
			
		||||
import {
 | 
			
		||||
  apiRemoveAccountFromFollowers,
 | 
			
		||||
  apiGetEndorsedAccounts,
 | 
			
		||||
} from 'mastodon/api/accounts';
 | 
			
		||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
 | 
			
		||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
 | 
			
		||||
 | 
			
		||||
import { importFetchedAccounts } from './importer';
 | 
			
		||||
 | 
			
		||||
export const revealAccount = createAction<{
 | 
			
		||||
  id: string;
 | 
			
		||||
}>('accounts/revealAccount');
 | 
			
		||||
 | 
			
		||||
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
 | 
			
		||||
  'accounts/importAccounts',
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
 | 
			
		||||
  return {
 | 
			
		||||
    payload: {
 | 
			
		||||
@@ -104,3 +104,12 @@ export const removeAccountFromFollowers = createDataLoadingThunk(
 | 
			
		||||
    apiRemoveAccountFromFollowers(accountId),
 | 
			
		||||
  (relationship) => ({ relationship }),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const fetchEndorsedAccounts = createDataLoadingThunk(
 | 
			
		||||
  'accounts/endorsements',
 | 
			
		||||
  ({ accountId }: { accountId: string }) => apiGetEndorsedAccounts(accountId),
 | 
			
		||||
  (data, { dispatch }) => {
 | 
			
		||||
    dispatch(importFetchedAccounts(data));
 | 
			
		||||
    return data;
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
import api from '../api';
 | 
			
		||||
 | 
			
		||||
export const FEATURED_TAGS_FETCH_REQUEST = 'FEATURED_TAGS_FETCH_REQUEST';
 | 
			
		||||
export const FEATURED_TAGS_FETCH_SUCCESS = 'FEATURED_TAGS_FETCH_SUCCESS';
 | 
			
		||||
export const FEATURED_TAGS_FETCH_FAIL    = 'FEATURED_TAGS_FETCH_FAIL';
 | 
			
		||||
 | 
			
		||||
export const fetchFeaturedTags = (id) => (dispatch, getState) => {
 | 
			
		||||
  if (getState().getIn(['user_lists', 'featured_tags', id, 'items'])) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dispatch(fetchFeaturedTagsRequest(id));
 | 
			
		||||
 | 
			
		||||
  api().get(`/api/v1/accounts/${id}/featured_tags`)
 | 
			
		||||
    .then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data)))
 | 
			
		||||
    .catch(err => dispatch(fetchFeaturedTagsFail(id, err)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fetchFeaturedTagsRequest = (id) => ({
 | 
			
		||||
  type: FEATURED_TAGS_FETCH_REQUEST,
 | 
			
		||||
  id,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const fetchFeaturedTagsSuccess = (id, tags) => ({
 | 
			
		||||
  type: FEATURED_TAGS_FETCH_SUCCESS,
 | 
			
		||||
  id,
 | 
			
		||||
  tags,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const fetchFeaturedTagsFail = (id, error) => ({
 | 
			
		||||
  type: FEATURED_TAGS_FETCH_FAIL,
 | 
			
		||||
  id,
 | 
			
		||||
  error,
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										7
									
								
								app/javascript/mastodon/actions/featured_tags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/javascript/mastodon/actions/featured_tags.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { apiGetFeaturedTags } from 'mastodon/api/accounts';
 | 
			
		||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
 | 
			
		||||
 | 
			
		||||
export const fetchFeaturedTags = createDataLoadingThunk(
 | 
			
		||||
  'accounts/featured_tags',
 | 
			
		||||
  ({ accountId }: { accountId: string }) => apiGetFeaturedTags(accountId),
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										7
									
								
								app/javascript/mastodon/actions/importer/accounts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/javascript/mastodon/actions/importer/accounts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { createAction } from '@reduxjs/toolkit';
 | 
			
		||||
 | 
			
		||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
			
		||||
 | 
			
		||||
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
 | 
			
		||||
  'accounts/importAccounts',
 | 
			
		||||
);
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import { createPollFromServerJSON } from 'mastodon/models/poll';
 | 
			
		||||
 | 
			
		||||
import { importAccounts } from '../accounts_typed';
 | 
			
		||||
 | 
			
		||||
import { importAccounts } from './accounts';
 | 
			
		||||
import { normalizeStatus } from './normalizer';
 | 
			
		||||
import { importPolls } from './polls';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import { apiRequestPost } from 'mastodon/api';
 | 
			
		||||
import { apiRequestPost, apiRequestGet } from 'mastodon/api';
 | 
			
		||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
			
		||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
 | 
			
		||||
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
 | 
			
		||||
 | 
			
		||||
export const apiSubmitAccountNote = (id: string, value: string) =>
 | 
			
		||||
  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
 | 
			
		||||
@@ -23,3 +25,9 @@ export const apiRemoveAccountFromFollowers = (id: string) =>
 | 
			
		||||
  apiRequestPost<ApiRelationshipJSON>(
 | 
			
		||||
    `v1/accounts/${id}/remove_from_followers`,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
export const apiGetFeaturedTags = (id: string) =>
 | 
			
		||||
  apiRequestGet<ApiHashtagJSON>(`v1/accounts/${id}/featured_tags`);
 | 
			
		||||
 | 
			
		||||
export const apiGetEndorsedAccounts = (id: string) =>
 | 
			
		||||
  apiRequestGet<ApiAccountJSON>(`v1/accounts/${id}/endorsements`);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,21 @@ import { useParams } from 'react-router';
 | 
			
		||||
import type { Map as ImmutableMap } from 'immutable';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
 | 
			
		||||
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
 | 
			
		||||
import { fetchFeaturedTags } from 'mastodon/actions/featured_tags';
 | 
			
		||||
import { expandAccountFeaturedTimeline } from 'mastodon/actions/timelines';
 | 
			
		||||
import { Account } from 'mastodon/components/account';
 | 
			
		||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
 | 
			
		||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
			
		||||
import { RemoteHint } from 'mastodon/components/remote_hint';
 | 
			
		||||
import StatusContainer from 'mastodon/containers/status_container';
 | 
			
		||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
 | 
			
		||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 | 
			
		||||
import Column from 'mastodon/features/ui/components/column';
 | 
			
		||||
import { useAccountId } from 'mastodon/hooks/useAccountId';
 | 
			
		||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
 | 
			
		||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
			
		||||
 | 
			
		||||
import { AccountHeader } from '../account_timeline/components/account_header';
 | 
			
		||||
import Column from '../ui/components/column';
 | 
			
		||||
 | 
			
		||||
import { EmptyMessage } from './components/empty_message';
 | 
			
		||||
import { FeaturedTag } from './components/featured_tag';
 | 
			
		||||
import type { TagMap } from './components/featured_tag';
 | 
			
		||||
@@ -29,7 +31,9 @@ interface Params {
 | 
			
		||||
  id?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AccountFeatured = () => {
 | 
			
		||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
 | 
			
		||||
  multiColumn,
 | 
			
		||||
}) => {
 | 
			
		||||
  const accountId = useAccountId();
 | 
			
		||||
  const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
 | 
			
		||||
  const forceEmptyState = suspended || blockedBy || hidden;
 | 
			
		||||
@@ -40,7 +44,8 @@ const AccountFeatured = () => {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (accountId) {
 | 
			
		||||
      void dispatch(expandAccountFeaturedTimeline(accountId));
 | 
			
		||||
      dispatch(fetchFeaturedTags(accountId));
 | 
			
		||||
      void dispatch(fetchFeaturedTags({ accountId }));
 | 
			
		||||
      void dispatch(fetchEndorsedAccounts({ accountId }));
 | 
			
		||||
    }
 | 
			
		||||
  }, [accountId, dispatch]);
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +72,17 @@ const AccountFeatured = () => {
 | 
			
		||||
        ImmutableList(),
 | 
			
		||||
      ) as ImmutableList<string>,
 | 
			
		||||
  );
 | 
			
		||||
  const featuredAccountIds = useAppSelector(
 | 
			
		||||
    (state) =>
 | 
			
		||||
      state.user_lists.getIn(
 | 
			
		||||
        ['featured_accounts', accountId, 'items'],
 | 
			
		||||
        ImmutableList(),
 | 
			
		||||
      ) as ImmutableList<string>,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (accountId === null) {
 | 
			
		||||
    return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isLoading) {
 | 
			
		||||
    return (
 | 
			
		||||
@@ -78,7 +94,11 @@ const AccountFeatured = () => {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (featuredStatusIds.isEmpty() && featuredTags.isEmpty()) {
 | 
			
		||||
  if (
 | 
			
		||||
    featuredStatusIds.isEmpty() &&
 | 
			
		||||
    featuredTags.isEmpty() &&
 | 
			
		||||
    featuredAccountIds.isEmpty()
 | 
			
		||||
  ) {
 | 
			
		||||
    return (
 | 
			
		||||
      <AccountFeaturedWrapper accountId={accountId}>
 | 
			
		||||
        <EmptyMessage
 | 
			
		||||
@@ -131,6 +151,19 @@ const AccountFeatured = () => {
 | 
			
		||||
            ))}
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
        {!featuredAccountIds.isEmpty() && (
 | 
			
		||||
          <>
 | 
			
		||||
            <h4 className='column-subheading'>
 | 
			
		||||
              <FormattedMessage
 | 
			
		||||
                id='account.featured.accounts'
 | 
			
		||||
                defaultMessage='Profiles'
 | 
			
		||||
              />
 | 
			
		||||
            </h4>
 | 
			
		||||
            {featuredAccountIds.map((featuredAccountId) => (
 | 
			
		||||
              <Account key={featuredAccountId} id={featuredAccountId} />
 | 
			
		||||
            ))}
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
        <RemoteHint accountId={accountId} />
 | 
			
		||||
      </div>
 | 
			
		||||
    </Column>
 | 
			
		||||
 
 | 
			
		||||
@@ -147,7 +147,7 @@ export const AccountGallery: React.FC<{
 | 
			
		||||
    [dispatch],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (accountId && !isAccount) {
 | 
			
		||||
  if (accountId === null) {
 | 
			
		||||
    return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,6 @@ const messages = defineMessages({
 | 
			
		||||
    id: 'account.disable_notifications',
 | 
			
		||||
    defaultMessage: 'Stop notifying me when @{name} posts',
 | 
			
		||||
  },
 | 
			
		||||
  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
 | 
			
		||||
  preferences: {
 | 
			
		||||
    id: 'navigation_bar.preferences',
 | 
			
		||||
    defaultMessage: 'Preferences',
 | 
			
		||||
@@ -451,7 +450,6 @@ export const AccountHeader: React.FC<{
 | 
			
		||||
        text: intl.formatMessage(messages.preferences),
 | 
			
		||||
        href: '/settings/preferences',
 | 
			
		||||
      });
 | 
			
		||||
      arr.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
 | 
			
		||||
      arr.push(null);
 | 
			
		||||
      arr.push({
 | 
			
		||||
        text: intl.formatMessage(messages.follow_requests),
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@ import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
 | 
			
		||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
 | 
			
		||||
 | 
			
		||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
 | 
			
		||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
 | 
			
		||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
 | 
			
		||||
import { ColumnBackButton } from '../../components/column_back_button';
 | 
			
		||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
			
		||||
@@ -27,7 +26,7 @@ import { LimitedAccountHint } from './components/limited_account_hint';
 | 
			
		||||
const emptyList = ImmutableList();
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => {
 | 
			
		||||
  const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
 | 
			
		||||
  const accountId = id || state.accounts_map[normalizeForLookup(acct)];
 | 
			
		||||
 | 
			
		||||
  if (accountId === null) {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -86,7 +85,6 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
			
		||||
      dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispatch(fetchFeaturedTags(accountId));
 | 
			
		||||
    dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
 | 
			
		||||
 | 
			
		||||
    if (accountId === me) {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import { useAppDispatch } from 'mastodon/store';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
 | 
			
		||||
  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
 | 
			
		||||
  preferences: {
 | 
			
		||||
    id: 'navigation_bar.preferences',
 | 
			
		||||
    defaultMessage: 'Preferences',
 | 
			
		||||
@@ -53,7 +52,6 @@ export const ActionBar: React.FC = () => {
 | 
			
		||||
        text: intl.formatMessage(messages.preferences),
 | 
			
		||||
        href: '/settings/preferences',
 | 
			
		||||
      },
 | 
			
		||||
      { text: intl.formatMessage(messages.pins), to: '/pinned' },
 | 
			
		||||
      null,
 | 
			
		||||
      {
 | 
			
		||||
        text: intl.formatMessage(messages.follow_requests),
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ import { LimitedAccountHint } from '../account_timeline/components/limited_accou
 | 
			
		||||
import Column from '../ui/components/column';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { params: { acct, id } }) => {
 | 
			
		||||
  const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
 | 
			
		||||
  const accountId = id || state.accounts_map[normalizeForLookup(acct)];
 | 
			
		||||
 | 
			
		||||
  if (!accountId) {
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ import { LimitedAccountHint } from '../account_timeline/components/limited_accou
 | 
			
		||||
import Column from '../ui/components/column';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { params: { acct, id } }) => {
 | 
			
		||||
  const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
 | 
			
		||||
  const accountId = id || state.accounts_map[normalizeForLookup(acct)];
 | 
			
		||||
 | 
			
		||||
  if (!accountId) {
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,27 +11,25 @@ interface Params {
 | 
			
		||||
  id?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useAccountId() {
 | 
			
		||||
export const useAccountId = () => {
 | 
			
		||||
  const { acct, id } = useParams<Params>();
 | 
			
		||||
  const dispatch = useAppDispatch();
 | 
			
		||||
  const accountId = useAppSelector(
 | 
			
		||||
    (state) =>
 | 
			
		||||
      id ??
 | 
			
		||||
      (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined),
 | 
			
		||||
      id ?? (acct ? state.accounts_map[normalizeForLookup(acct)] : undefined),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const account = useAppSelector((state) =>
 | 
			
		||||
    accountId ? state.accounts.get(accountId) : undefined,
 | 
			
		||||
  );
 | 
			
		||||
  const isAccount = !!account;
 | 
			
		||||
  const accountInStore = !!account;
 | 
			
		||||
 | 
			
		||||
  const dispatch = useAppDispatch();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!accountId) {
 | 
			
		||||
    if (typeof accountId === 'undefined' && acct) {
 | 
			
		||||
      dispatch(lookupAccount(acct));
 | 
			
		||||
    } else if (!isAccount) {
 | 
			
		||||
    } else if (accountId && !accountInStore) {
 | 
			
		||||
      dispatch(fetchAccount(accountId));
 | 
			
		||||
    }
 | 
			
		||||
  }, [dispatch, accountId, acct, isAccount]);
 | 
			
		||||
  }, [dispatch, accountId, acct, accountInStore]);
 | 
			
		||||
 | 
			
		||||
  return accountId;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
 | 
			
		||||
import { useAppSelector } from 'mastodon/store';
 | 
			
		||||
 | 
			
		||||
export function useAccountVisibility(accountId?: string) {
 | 
			
		||||
  const blockedBy = useAppSelector(
 | 
			
		||||
    (state) => !!state.relationships.getIn([accountId, 'blocked_by'], false),
 | 
			
		||||
export function useAccountVisibility(accountId?: string | null) {
 | 
			
		||||
  const blockedBy = useAppSelector((state) =>
 | 
			
		||||
    accountId
 | 
			
		||||
      ? !!state.relationships.getIn([accountId, 'blocked_by'], false)
 | 
			
		||||
      : false,
 | 
			
		||||
  );
 | 
			
		||||
  const suspended = useAppSelector(
 | 
			
		||||
    (state) => !!state.accounts.getIn([accountId, 'suspended'], false),
 | 
			
		||||
  const suspended = useAppSelector((state) =>
 | 
			
		||||
    accountId ? !!state.accounts.getIn([accountId, 'suspended'], false) : false,
 | 
			
		||||
  );
 | 
			
		||||
  const hidden = useAppSelector((state) =>
 | 
			
		||||
    accountId ? Boolean(getAccountHidden(state, accountId)) : false,
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@
 | 
			
		||||
  "account.enable_notifications": "Notify me when @{name} posts",
 | 
			
		||||
  "account.endorse": "Feature on profile",
 | 
			
		||||
  "account.featured": "Featured",
 | 
			
		||||
  "account.featured.accounts": "Profiles",
 | 
			
		||||
  "account.featured.hashtags": "Hashtags",
 | 
			
		||||
  "account.featured.posts": "Posts",
 | 
			
		||||
  "account.featured_tags.last_status_at": "Last post on {date}",
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ import { Map as ImmutableMap } from 'immutable';
 | 
			
		||||
import {
 | 
			
		||||
  followAccountSuccess,
 | 
			
		||||
  unfollowAccountSuccess,
 | 
			
		||||
  importAccounts,
 | 
			
		||||
  revealAccount,
 | 
			
		||||
} from 'mastodon/actions/accounts_typed';
 | 
			
		||||
import { importAccounts } from 'mastodon/actions/importer/accounts';
 | 
			
		||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
			
		||||
import { me } from 'mastodon/initial_state';
 | 
			
		||||
import type { Account } from 'mastodon/models/account';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
import { Map as ImmutableMap } from 'immutable';
 | 
			
		||||
 | 
			
		||||
import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts';
 | 
			
		||||
import { importAccounts } from '../actions/accounts_typed';
 | 
			
		||||
import { domain } from '../initial_state';
 | 
			
		||||
 | 
			
		||||
const pattern = new RegExp(`@${domain}$`, 'gi');
 | 
			
		||||
 | 
			
		||||
export const normalizeForLookup = str =>
 | 
			
		||||
  str.toLowerCase().replace(pattern, '');
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableMap();
 | 
			
		||||
 | 
			
		||||
export default function accountsMap(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case ACCOUNT_LOOKUP_FAIL:
 | 
			
		||||
    return action.error?.response?.status === 404 ? state.set(normalizeForLookup(action.acct), null) : state;
 | 
			
		||||
  case importAccounts.type:
 | 
			
		||||
    return state.withMutations(map => action.payload.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id)));
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								app/javascript/mastodon/reducers/accounts_map.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								app/javascript/mastodon/reducers/accounts_map.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import { createReducer } from '@reduxjs/toolkit';
 | 
			
		||||
import type { UnknownAction } from '@reduxjs/toolkit';
 | 
			
		||||
 | 
			
		||||
import type { AxiosError } from 'axios';
 | 
			
		||||
 | 
			
		||||
import { ACCOUNT_LOOKUP_FAIL } from 'mastodon/actions/accounts';
 | 
			
		||||
import { importAccounts } from 'mastodon/actions/importer/accounts';
 | 
			
		||||
import { domain } from 'mastodon/initial_state';
 | 
			
		||||
 | 
			
		||||
interface AccountLookupFailAction extends UnknownAction {
 | 
			
		||||
  acct: string;
 | 
			
		||||
  error?: AxiosError;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const pattern = new RegExp(`@${domain}$`, 'gi');
 | 
			
		||||
 | 
			
		||||
export const normalizeForLookup = (str: string) =>
 | 
			
		||||
  str.toLowerCase().replace(pattern, '');
 | 
			
		||||
 | 
			
		||||
const initialState: Record<string, string | null> = {};
 | 
			
		||||
 | 
			
		||||
export const accountsMapReducer = createReducer(initialState, (builder) => {
 | 
			
		||||
  builder
 | 
			
		||||
    .addCase(importAccounts, (state, action) => {
 | 
			
		||||
      action.payload.accounts.forEach((account) => {
 | 
			
		||||
        state[normalizeForLookup(account.acct)] = account.id;
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .addMatcher(
 | 
			
		||||
      (action: UnknownAction): action is AccountLookupFailAction =>
 | 
			
		||||
        action.type === ACCOUNT_LOOKUP_FAIL,
 | 
			
		||||
      (state, action) => {
 | 
			
		||||
        if (action.error?.response?.status === 404) {
 | 
			
		||||
          state[normalizeForLookup(action.acct)] = null;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
@@ -4,7 +4,7 @@ import { loadingBarReducer } from 'react-redux-loading-bar';
 | 
			
		||||
import { combineReducers } from 'redux-immutable';
 | 
			
		||||
 | 
			
		||||
import { accountsReducer } from './accounts';
 | 
			
		||||
import accounts_map from './accounts_map';
 | 
			
		||||
import { accountsMapReducer } from './accounts_map';
 | 
			
		||||
import { alertsReducer } from './alerts';
 | 
			
		||||
import announcements from './announcements';
 | 
			
		||||
import { composeReducer } from './compose';
 | 
			
		||||
@@ -49,7 +49,7 @@ const reducers = {
 | 
			
		||||
  user_lists,
 | 
			
		||||
  status_lists,
 | 
			
		||||
  accounts: accountsReducer,
 | 
			
		||||
  accounts_map,
 | 
			
		||||
  accounts_map: accountsMapReducer,
 | 
			
		||||
  statuses,
 | 
			
		||||
  relationships: relationshipsReducer,
 | 
			
		||||
  settings,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,7 @@ import {
 | 
			
		||||
  fetchDirectory
 | 
			
		||||
} from 'mastodon/actions/directory';
 | 
			
		||||
import {
 | 
			
		||||
  FEATURED_TAGS_FETCH_REQUEST,
 | 
			
		||||
  FEATURED_TAGS_FETCH_SUCCESS,
 | 
			
		||||
  FEATURED_TAGS_FETCH_FAIL,
 | 
			
		||||
  fetchFeaturedTags
 | 
			
		||||
} from 'mastodon/actions/featured_tags';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
@@ -31,6 +29,7 @@ import {
 | 
			
		||||
  FOLLOW_REQUESTS_EXPAND_FAIL,
 | 
			
		||||
  authorizeFollowRequestSuccess,
 | 
			
		||||
  rejectFollowRequestSuccess,
 | 
			
		||||
  fetchEndorsedAccounts,
 | 
			
		||||
} from '../actions/accounts';
 | 
			
		||||
import {
 | 
			
		||||
  BLOCKS_FETCH_REQUEST,
 | 
			
		||||
@@ -191,21 +190,27 @@ export default function userLists(state = initialState, action) {
 | 
			
		||||
  case MUTES_FETCH_FAIL:
 | 
			
		||||
  case MUTES_EXPAND_FAIL:
 | 
			
		||||
    return state.setIn(['mutes', 'isLoading'], false);
 | 
			
		||||
  case FEATURED_TAGS_FETCH_SUCCESS:
 | 
			
		||||
    return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id);
 | 
			
		||||
  case FEATURED_TAGS_FETCH_REQUEST:
 | 
			
		||||
    return state.setIn(['featured_tags', action.id, 'isLoading'], true);
 | 
			
		||||
  case FEATURED_TAGS_FETCH_FAIL:
 | 
			
		||||
    return state.setIn(['featured_tags', action.id, 'isLoading'], false);
 | 
			
		||||
  default:
 | 
			
		||||
    if(fetchDirectory.fulfilled.match(action))
 | 
			
		||||
    if (fetchEndorsedAccounts.fulfilled.match(action))
 | 
			
		||||
      return normalizeList(state, ['featured_accounts', action.meta.arg.accountId], action.payload, undefined);
 | 
			
		||||
    else if (fetchEndorsedAccounts.pending.match(action))
 | 
			
		||||
      return state.setIn(['featured_accounts', action.meta.arg.accountId, 'isLoading'], true);
 | 
			
		||||
    else if (fetchEndorsedAccounts.rejected.match(action))
 | 
			
		||||
      return state.setIn(['featured_accounts', action.meta.arg.accountId, 'isLoading'], false);
 | 
			
		||||
    else if (fetchFeaturedTags.fulfilled.match(action))
 | 
			
		||||
      return normalizeFeaturedTags(state, ['featured_tags', action.meta.arg.accountId], action.payload, action.meta.arg.accountId);
 | 
			
		||||
    else if (fetchFeaturedTags.pending.match(action))
 | 
			
		||||
      return state.setIn(['featured_tags', action.meta.arg.accountId, 'isLoading'], true);
 | 
			
		||||
    else if (fetchFeaturedTags.rejected.match(action))
 | 
			
		||||
      return state.setIn(['featured_tags', action.meta.arg.accountId, 'isLoading'], false);
 | 
			
		||||
    else if (fetchDirectory.fulfilled.match(action))
 | 
			
		||||
      return normalizeList(state, ['directory'], action.payload.accounts, undefined);
 | 
			
		||||
    else if( expandDirectory.fulfilled.match(action))
 | 
			
		||||
    else if (expandDirectory.fulfilled.match(action))
 | 
			
		||||
      return appendToList(state, ['directory'], action.payload.accounts, undefined);
 | 
			
		||||
    else if(fetchDirectory.pending.match(action) ||
 | 
			
		||||
    else if (fetchDirectory.pending.match(action) ||
 | 
			
		||||
     expandDirectory.pending.match(action))
 | 
			
		||||
      return state.setIn(['directory', 'isLoading'], true);
 | 
			
		||||
    else if(fetchDirectory.rejected.match(action) ||
 | 
			
		||||
    else if (fetchDirectory.rejected.match(action) ||
 | 
			
		||||
     expandDirectory.rejected.match(action))
 | 
			
		||||
      return state.setIn(['directory', 'isLoading'], false);
 | 
			
		||||
    else
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user