@ -6,6 +6,26 @@ import { relationshipSchema } from './relationship';
|
||||
import { roleSchema } from './role';
|
||||
import { coerceObject, dateSchema, filteredArray } from './utils';
|
||||
|
||||
const getDomainFromURL = (account: Pick<Account, 'url'>): string => {
|
||||
try {
|
||||
const url = account.url;
|
||||
return new URL(url).host;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const guessFqn = (account: Pick<Account, 'acct' | 'url'>): string => {
|
||||
const acct = account.acct;
|
||||
const [user, domain] = acct.split('@');
|
||||
|
||||
if (domain) {
|
||||
return acct;
|
||||
} else {
|
||||
return [user, getDomainFromURL(account)].join('@');
|
||||
}
|
||||
};
|
||||
|
||||
const filterBadges = (tags?: string[]) =>
|
||||
tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') }));
|
||||
|
||||
@ -13,9 +33,12 @@ const preprocessAccount = (account: any) => {
|
||||
if (!account?.acct) return null;
|
||||
|
||||
const username = account.username || account.acct.split('@')[0];
|
||||
const fqn = guessFqn(account);
|
||||
|
||||
return {
|
||||
username,
|
||||
fqn,
|
||||
domain: fqn.split('@')[1] || '',
|
||||
avatar_static: account.avatar_static || account.avatar,
|
||||
header_static: account.header_static || account.header,
|
||||
local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
|
||||
@ -120,6 +143,7 @@ const baseAccountSchema = z.object({
|
||||
header_description: z.string().catch(''),
|
||||
|
||||
verified: z.boolean().optional().catch(undefined),
|
||||
domain: z.string().catch(''),
|
||||
|
||||
__meta: coerceObject({
|
||||
pleroma: z.any().optional().catch(undefined),
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
|
||||
import { makeGetAccount } from 'pl-fe/selectors';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
|
||||
import type { MinifiedAccount } from 'pl-fe/pl-hooks/minifiers/minifyAccount';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
|
||||
@ -9,12 +10,10 @@ const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
|
||||
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const;
|
||||
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const;
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const fetchDraftStatuses = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||
const accountUrl = queryClient.getQueryData<MinifiedAccount>(['accounts', 'entities', state.me])!.url;
|
||||
|
||||
return KVStore.getItem(`drafts:${accountUrl}`).then((statuses) => {
|
||||
dispatch({
|
||||
@ -27,7 +26,7 @@ const fetchDraftStatuses = () =>
|
||||
const saveDraftStatus = (composeId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||
const accountUrl = queryClient.getQueryData<MinifiedAccount>(['accounts', 'entities', state.me])!.url;
|
||||
|
||||
const compose = state.compose.get(composeId)!;
|
||||
|
||||
@ -46,7 +45,7 @@ const saveDraftStatus = (composeId: string) =>
|
||||
const cancelDraftStatus = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||
const accountUrl = queryClient.getQueryData<MinifiedAccount>(['accounts', 'entities', state.me])!.url;
|
||||
|
||||
dispatch({
|
||||
type: CANCEL_DRAFT_STATUS,
|
||||
|
||||
@ -7,7 +7,7 @@ import { getNotificationStatus } from 'pl-fe/features/notifications/components/n
|
||||
import { normalizeNotification } from 'pl-fe/normalizers';
|
||||
import { importEntities } from 'pl-fe/pl-hooks/importer';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
|
||||
// import { getFilters, regexFromFilters } from 'pl-fe/selectors';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { unescapeHTML } from 'pl-fe/utils/html';
|
||||
import { joinPublicPath } from 'pl-fe/utils/static';
|
||||
@ -60,26 +60,27 @@ const updateNotificationsQueue = (notification: Notification, intlMessages: Reco
|
||||
if (!notification.type) return; // drop invalid notifications
|
||||
if (notification.type === 'chat_mention') return; // Drop chat notifications, handle them per-chat
|
||||
|
||||
const filters = getFilters(getState(), { contextType: 'notifications' });
|
||||
// TODO: Restore filtering
|
||||
// const filters = getFilters(getState(), { contextType: 'notifications' });
|
||||
|
||||
const status = getNotificationStatus(notification);
|
||||
|
||||
let filtered: boolean | null = false;
|
||||
// let filtered: boolean | null = false;
|
||||
|
||||
const isOnNotificationsPage = curPath === '/notifications';
|
||||
|
||||
if (notification.type === 'mention' || notification.type === 'status') {
|
||||
const regex = regexFromFilters(filters);
|
||||
const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
|
||||
filtered = regex && regex.test(searchIndex);
|
||||
}
|
||||
// if (notification.type === 'mention' || notification.type === 'status') {
|
||||
// const regex = regexFromFilters(filters);
|
||||
// const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content);
|
||||
// filtered = regex && regex.test(searchIndex);
|
||||
// }
|
||||
|
||||
// Desktop notifications
|
||||
try {
|
||||
// eslint-disable-next-line compat/compat
|
||||
const isNotificationsEnabled = window.Notification?.permission === 'granted';
|
||||
|
||||
if (!filtered && isNotificationsEnabled) {
|
||||
if (/* !filtered && */ isNotificationsEnabled) {
|
||||
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }) as string;
|
||||
const body = (status && status.spoiler_text.length > 0) ? status.spoiler_text : unescapeHTML(status ? status.content : '');
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@ import { defineMessage } from 'react-intl';
|
||||
import { patchMe } from 'pl-fe/actions/me';
|
||||
import { getClient } from 'pl-fe/api';
|
||||
import messages from 'pl-fe/messages';
|
||||
import { makeGetAccount } from 'pl-fe/selectors';
|
||||
import { MinifiedAccount } from 'pl-fe/pl-hooks/minifiers/minifyAccount';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
@ -13,8 +14,6 @@ import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const FE_NAME = 'pl_fe';
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
/** Options when changing/saving settings. */
|
||||
type SettingOpts = {
|
||||
/** Whether to display an alert when settings are saved. */
|
||||
@ -71,7 +70,7 @@ const updateSettingsStore = (settings: any) =>
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
const accountUrl = getAccount(state, state.me as string)!.url;
|
||||
const accountUrl = queryClient.getQueryData<MinifiedAccount>(['accounts', 'entities', state.me])!.url;
|
||||
|
||||
return updateAuthAccount(accountUrl, settings);
|
||||
}
|
||||
|
||||
@ -80,7 +80,6 @@ const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true }) => {
|
||||
if (!statusId) return null;
|
||||
|
||||
const renderStatus = (statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
@ -11,8 +11,8 @@ import AccountContainer from 'pl-fe/containers/account-container';
|
||||
import StatusTypeIcon from 'pl-fe/features/status/components/status-type-icon';
|
||||
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
|
||||
import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
|
||||
import { useAppDispatch, useAppSelector, useSettings } from 'pl-fe/hooks';
|
||||
import { makeGetStatus, type SelectedStatus } from 'pl-fe/selectors';
|
||||
import { useAppDispatch, useSettings } from 'pl-fe/hooks';
|
||||
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
|
||||
import { useModalsStore } from 'pl-fe/stores';
|
||||
import { textForScreenReader } from 'pl-fe/utils/status';
|
||||
|
||||
@ -27,6 +27,8 @@ import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
||||
import StatusInfo from './statuses/status-info';
|
||||
import { Card, Icon, Stack, Text } from './ui';
|
||||
|
||||
import type { SelectedStatus } from 'pl-fe/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
reblogged_by: { id: 'status.reblogged_by', defaultMessage: '{name} reposted' },
|
||||
});
|
||||
@ -80,8 +82,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||
const didShowCard = useRef(false);
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const actualStatus = useAppSelector(state => status.reblog_id && getStatus(state, { id: status.reblog_id }) || status)!;
|
||||
const actualStatus = useStatus(status.reblog_id || undefined).data || status;
|
||||
|
||||
const isReblog = status.reblog_id;
|
||||
const statusUrl = `/@${actualStatus.account.acct}/posts/${actualStatus.id}`;
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import Status, { IStatus } from 'pl-fe/components/status';
|
||||
import { useAppSelector } from 'pl-fe/hooks';
|
||||
import { makeGetStatus } from 'pl-fe/selectors';
|
||||
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
|
||||
|
||||
interface IStatusContainer extends Omit<IStatus, 'status'> {
|
||||
id: string;
|
||||
@ -12,14 +11,12 @@ interface IStatusContainer extends Omit<IStatus, 'status'> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy Status wrapper accepting a status ID instead of the full entity.
|
||||
* @deprecated Use the Status component directly.
|
||||
* Status wrapper accepting a status ID instead of the full entity.
|
||||
*/
|
||||
const StatusContainer: React.FC<IStatusContainer> = (props) => {
|
||||
const { id, contextType, ...rest } = props;
|
||||
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const status = useAppSelector(state => getStatus(state, { id, contextType }));
|
||||
const { data: status } = useStatus(id);
|
||||
|
||||
if (status) {
|
||||
return <Status status={status} {...rest} />;
|
||||
|
||||
@ -47,7 +47,6 @@ const Conversation: React.FC<IConversation> = ({ conversationId, onMoveUp, onMov
|
||||
}
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
id={lastStatusId}
|
||||
unread={unread}
|
||||
|
||||
@ -141,7 +141,6 @@ const SearchResults = () => {
|
||||
|
||||
if (results.statuses && results.statuses.length > 0) {
|
||||
searchResults = results.statuses.map((statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
@ -152,7 +151,6 @@ const SearchResults = () => {
|
||||
resultsIds = results.statuses;
|
||||
} else if (!submitted && !filterByAccount && trendingStatuses && trendingStatuses.length !== 0) {
|
||||
searchResults = trendingStatuses.map((statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer
|
||||
key={statusId}
|
||||
id={statusId}
|
||||
|
||||
@ -42,7 +42,6 @@ const ThreadStatus: React.FC<IThreadStatus> = (props): JSX.Element => {
|
||||
<div className='thread__status relative pb-4'>
|
||||
{renderConnector()}
|
||||
{isLoaded ? (
|
||||
// @ts-ignore FIXME
|
||||
<StatusContainer {...props} showGroup={false} />
|
||||
) : (
|
||||
<PlaceholderStatus variant='default' />
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useRelationship } from 'pl-fe/api/hooks/accounts/useRelationship';
|
||||
import { useAppSelector, useClient } from 'pl-fe/hooks';
|
||||
import { useClient } from 'pl-fe/hooks';
|
||||
import { type MinifiedAccount, minifyAccount } from 'pl-fe/pl-hooks/minifiers/minifyAccount';
|
||||
import { normalizeAccount } from 'pl-fe/pl-hooks/normalizers/normalizeAccount';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
|
||||
interface UseAccountOpts {
|
||||
withRelationship?: boolean;
|
||||
@ -24,19 +27,14 @@ const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => {
|
||||
enabled: opts.withRelationship,
|
||||
});
|
||||
|
||||
const movedQuery = useAccount(opts.withMoveTarget && accountQuery.data?.moved_id || undefined);
|
||||
|
||||
const data: Account | null = useAppSelector((state) => {
|
||||
const account = accountQuery.data;
|
||||
if (!account) return null;
|
||||
|
||||
return {
|
||||
...account,
|
||||
account,
|
||||
let data;
|
||||
if (accountQuery.data) {
|
||||
data = {
|
||||
...accountQuery.data,
|
||||
relationship: relationshipQuery.relationship,
|
||||
moved: movedQuery.data || null,
|
||||
moved: opts.withMoveTarget && queryClient.getQueryData(['accounts', 'entities', accountQuery.data?.moved_id]) as MinifiedAccount || null,
|
||||
};
|
||||
});
|
||||
} else data = null;
|
||||
|
||||
return { ...accountQuery, data };
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { useIntl } from 'react-intl';
|
||||
|
||||
import { useAccount, useGroup } from 'pl-fe/api/hooks';
|
||||
import { useAppSelector, useClient } from 'pl-fe/hooks';
|
||||
import { importEntities } from 'pl-fe/pl-hooks/importer';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { selectAccount, selectAccounts } from 'pl-fe/selectors';
|
||||
|
||||
@ -13,6 +14,76 @@ import { normalizeStatus } from '../../normalizers/normalizeStatus';
|
||||
|
||||
type Account = ReturnType<typeof selectAccount>;
|
||||
|
||||
// const toServerSideType = (columnType: string): Filter['context'][0] => {
|
||||
// switch (columnType) {
|
||||
// case 'home':
|
||||
// case 'notifications':
|
||||
// case 'public':
|
||||
// case 'thread':
|
||||
// return columnType;
|
||||
// default:
|
||||
// if (columnType.includes('list:')) {
|
||||
// return 'home';
|
||||
// } else {
|
||||
// return 'public'; // community, account, hashtag
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// type FilterContext = { contextType?: string };
|
||||
|
||||
// const getFilters = (state: RootState, query: FilterContext) =>
|
||||
// state.filters.filter((filter) =>
|
||||
// (!query?.contextType || filter.context.includes(toServerSideType(query.contextType)))
|
||||
// && (filter.expires_at === null || Date.parse(filter.expires_at) > new Date().getTime()),
|
||||
// );
|
||||
|
||||
// const escapeRegExp = (string: string) =>
|
||||
// string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
|
||||
// const regexFromFilters = (filters: ImmutableList<Filter>) => {
|
||||
// if (filters.size === 0) return null;
|
||||
|
||||
// return new RegExp(filters.map(filter =>
|
||||
// filter.keywords.map(keyword => {
|
||||
// let expr = escapeRegExp(keyword.keyword);
|
||||
|
||||
// if (keyword.whole_word) {
|
||||
// if (/^[\w]/.test(expr)) {
|
||||
// expr = `\\b${expr}`;
|
||||
// }
|
||||
|
||||
// if (/[\w]$/.test(expr)) {
|
||||
// expr = `${expr}\\b`;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return expr;
|
||||
// }).join('|'),
|
||||
// ).join('|'), 'i');
|
||||
// };
|
||||
|
||||
// const checkFiltered = (index: string, filters: ImmutableList<Filter>) =>
|
||||
// filters.reduce((result: Array<string>, filter) =>
|
||||
// result.concat(filter.keywords.reduce((result: Array<string>, keyword) => {
|
||||
// let expr = escapeRegExp(keyword.keyword);
|
||||
|
||||
// if (keyword.whole_word) {
|
||||
// if (/^[\w]/.test(expr)) {
|
||||
// expr = `\\b${expr}`;
|
||||
// }
|
||||
|
||||
// if (/[\w]$/.test(expr)) {
|
||||
// expr = `${expr}\\b`;
|
||||
// }
|
||||
// }
|
||||
|
||||
// const regex = new RegExp(expr);
|
||||
|
||||
// if (regex.test(index)) return result.concat(filter.title);
|
||||
// return result;
|
||||
// }, [])), []);
|
||||
|
||||
const importStatus = (status: MinifiedStatus) => {
|
||||
queryClient.setQueryData<MinifiedStatus>(
|
||||
['statuses', 'entities', status.id],
|
||||
@ -29,6 +100,7 @@ const useStatus = (statusId?: string) => {
|
||||
queryFn: () => client.statuses.getStatus(statusId!, {
|
||||
language: intl.locale,
|
||||
})
|
||||
.then(status => (importEntities({ statuses: [status] }, { withParents: false }), status))
|
||||
.then(normalizeStatus)
|
||||
.then(minifyStatus),
|
||||
enabled: !!statusId,
|
||||
@ -51,9 +123,13 @@ const useStatus = (statusId?: string) => {
|
||||
|
||||
return {
|
||||
...status,
|
||||
account,
|
||||
account: account!,
|
||||
accounts,
|
||||
group,
|
||||
// quote,
|
||||
// reblog,
|
||||
// poll
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -47,6 +47,8 @@ const importEntities = (entities: {
|
||||
polls?: Array<BasePoll>;
|
||||
statuses?: Array<BaseStatus>;
|
||||
relationships?: Array<BaseRelationship>;
|
||||
}, options = {
|
||||
withParents: true,
|
||||
}) => {
|
||||
const accounts: Record<string, BaseAccount> = {};
|
||||
const groups: Record<string, BaseGroup> = {};
|
||||
@ -55,14 +57,15 @@ const importEntities = (entities: {
|
||||
const relationships: Record<string, BaseRelationship> = {};
|
||||
const statuses: Record<string, BaseStatus> = {};
|
||||
|
||||
const processAccount = (account: BaseAccount) => {
|
||||
accounts[account.id] = account;
|
||||
const processAccount = (account: BaseAccount, withParent = true) => {
|
||||
if (withParent) accounts[account.id] = account;
|
||||
|
||||
if (account.moved) processAccount(account.moved);
|
||||
if (account.relationship) relationships[account.relationship.id] = account.relationship;
|
||||
};
|
||||
|
||||
const processNotification = (notification: DeduplicatedNotification) => {
|
||||
notifications[notification.id] = notification;
|
||||
const processNotification = (notification: DeduplicatedNotification, withParent = true) => {
|
||||
if (withParent) notifications[notification.id] = notification;
|
||||
|
||||
processAccount(notification.account);
|
||||
if (notification.type === 'move') processAccount(notification.target);
|
||||
@ -72,9 +75,9 @@ const importEntities = (entities: {
|
||||
processStatus(notification.status);
|
||||
};
|
||||
|
||||
const processStatus = (status: BaseStatus) => {
|
||||
const processStatus = (status: BaseStatus, withParent = true) => {
|
||||
if (status.account) {
|
||||
statuses[status.id] = status;
|
||||
if (withParent) statuses[status.id] = status;
|
||||
processAccount(status.account);
|
||||
}
|
||||
|
||||
@ -84,12 +87,15 @@ const importEntities = (entities: {
|
||||
if (status.group) groups[status.group.id] = status.group;
|
||||
};
|
||||
|
||||
entities.accounts?.forEach(processAccount);
|
||||
entities.groups?.forEach(group => groups[group.id] = group);
|
||||
entities.notifications?.forEach(processNotification);
|
||||
entities.polls?.forEach(poll => polls[poll.id] = poll);
|
||||
entities.relationships?.forEach(relationship => relationships[relationship.id] = relationship);
|
||||
entities.statuses?.forEach(processStatus);
|
||||
if (options.withParents) {
|
||||
entities.groups?.forEach(group => groups[group.id] = group);
|
||||
entities.polls?.forEach(poll => polls[poll.id] = poll);
|
||||
entities.relationships?.forEach(relationship => relationships[relationship.id] = relationship);
|
||||
}
|
||||
|
||||
entities.accounts?.forEach((account) => processAccount(account, options.withParents));
|
||||
entities.notifications?.forEach((notification) => processNotification(notification, options.withParents));
|
||||
entities.statuses?.forEach((status) => processStatus(status, options.withParents));
|
||||
|
||||
if (!isEmpty(accounts)) dispatch(importAccounts(Object.values(accounts)));
|
||||
if (!isEmpty(groups)) dispatch(importGroups(Object.values(groups)));
|
||||
|
||||
10
packages/pl-fe/src/pl-hooks/minifiers/minifyAccount.ts
Normal file
10
packages/pl-fe/src/pl-hooks/minifiers/minifyAccount.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { Account } from '../normalizers/normalizeAccount';
|
||||
|
||||
const minifyAccount = ({ moved, ...account }: Account) => ({
|
||||
...account,
|
||||
moved_id: moved?.id || null,
|
||||
});
|
||||
|
||||
type MinifiedAccount = ReturnType<typeof minifyAccount>;
|
||||
|
||||
export { minifyAccount, type MinifiedAccount };
|
||||
37
packages/pl-fe/src/pl-hooks/normalizers/normalizeAccount.ts
Normal file
37
packages/pl-fe/src/pl-hooks/normalizers/normalizeAccount.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
|
||||
import emojify from 'pl-fe/features/emoji';
|
||||
import { unescapeHTML } from 'pl-fe/utils/html';
|
||||
import { makeEmojiMap } from 'pl-fe/utils/normalizers';
|
||||
|
||||
import type { Account as BaseAccount } from 'pl-api';
|
||||
|
||||
const normalizeAccount = (account: BaseAccount) => {
|
||||
const missingAvatar = require('pl-fe/assets/images/avatar-missing.png');
|
||||
const missingHeader = require('pl-fe/assets/images/header-missing.png');
|
||||
const note = account.note === '<p></p>' ? '' : account.note;
|
||||
|
||||
const emojiMap = makeEmojiMap(account.emojis);
|
||||
|
||||
return {
|
||||
...account,
|
||||
avatar: account.avatar || account.avatar_static || missingAvatar,
|
||||
avatar_static: account.avatar_static || account.avatar || missingAvatar,
|
||||
header: account.header || account.header_static || missingHeader,
|
||||
header_static: account.header_static || account.header || missingHeader,
|
||||
note,
|
||||
display_name_html: emojify(escapeTextContentForBrowser(account.display_name), emojiMap),
|
||||
note_emojified: emojify(account.note, emojiMap),
|
||||
note_plain: unescapeHTML(account.note),
|
||||
fields: account.fields.map(field => ({
|
||||
...field,
|
||||
name_emojified: emojify(escapeTextContentForBrowser(field.name), emojiMap),
|
||||
value_emojified: emojify(field.value, emojiMap),
|
||||
value_plain: unescapeHTML(field.value),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
type Account = ReturnType<typeof normalizeAccount>;
|
||||
|
||||
export { normalizeAccount, type Account };
|
||||
@ -8,7 +8,7 @@ import DOMPurify from 'isomorphic-dompurify';
|
||||
import { type Account as BaseAccount, type Status as BaseStatus, type MediaAttachment, mentionSchema, type Translation } from 'pl-api';
|
||||
|
||||
import emojify from 'pl-fe/features/emoji';
|
||||
import { stripCompatibilityFeatures, unescapeHTML } from 'pl-fe/utils/html';
|
||||
import { unescapeHTML } from 'pl-fe/utils/html';
|
||||
import { makeEmojiMap } from 'pl-fe/utils/normalizers';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
@ -58,7 +58,7 @@ const buildSearchContent = (status: Pick<BaseStatus, 'poll' | 'mentions' | 'spoi
|
||||
return unescapeHTML(fields.join('\n\n')) || '';
|
||||
};
|
||||
|
||||
const calculateContent = (text: string, emojiMap: any, hasQuote?: boolean) => DOMPurify.sanitize(stripCompatibilityFeatures(emojify(text, emojiMap), hasQuote), { USE_PROFILES: { html: true } });
|
||||
const calculateContent = (text: string, emojiMap: any, hasQuote?: boolean) => emojify(text, emojiMap);
|
||||
const calculateSpoiler = (text: string, emojiMap: any) => DOMPurify.sanitize(emojify(escapeTextContentForBrowser(text), emojiMap), { USE_PROFILES: { html: true } });
|
||||
|
||||
const calculateStatus = (status: BaseStatus, oldStatus?: OldStatus): CalculatedValues => {
|
||||
|
||||
@ -13,7 +13,7 @@ import { validId } from 'pl-fe/utils/auth';
|
||||
import ConfigDB from 'pl-fe/utils/config-db';
|
||||
import { shouldFilter } from 'pl-fe/utils/timelines';
|
||||
|
||||
import type { Account as BaseAccount, Filter, MediaAttachment, Relationship } from 'pl-api';
|
||||
import type { Account as BaseAccount, MediaAttachment, Relationship } from 'pl-api';
|
||||
import type { EntityStore } from 'pl-fe/entity-store/types';
|
||||
import type { Account, Group } from 'pl-fe/normalizers';
|
||||
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
|
||||
@ -49,76 +49,6 @@ const makeGetAccount = () => createSelector([
|
||||
};
|
||||
});
|
||||
|
||||
const toServerSideType = (columnType: string): Filter['context'][0] => {
|
||||
switch (columnType) {
|
||||
case 'home':
|
||||
case 'notifications':
|
||||
case 'public':
|
||||
case 'thread':
|
||||
return columnType;
|
||||
default:
|
||||
if (columnType.includes('list:')) {
|
||||
return 'home';
|
||||
} else {
|
||||
return 'public'; // community, account, hashtag
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type FilterContext = { contextType?: string };
|
||||
|
||||
const getFilters = (state: RootState, query: FilterContext) =>
|
||||
state.filters.filter((filter) =>
|
||||
(!query?.contextType || filter.context.includes(toServerSideType(query.contextType)))
|
||||
&& (filter.expires_at === null || Date.parse(filter.expires_at) > new Date().getTime()),
|
||||
);
|
||||
|
||||
const escapeRegExp = (string: string) =>
|
||||
string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
|
||||
const regexFromFilters = (filters: ImmutableList<Filter>) => {
|
||||
if (filters.size === 0) return null;
|
||||
|
||||
return new RegExp(filters.map(filter =>
|
||||
filter.keywords.map(keyword => {
|
||||
let expr = escapeRegExp(keyword.keyword);
|
||||
|
||||
if (keyword.whole_word) {
|
||||
if (/^[\w]/.test(expr)) {
|
||||
expr = `\\b${expr}`;
|
||||
}
|
||||
|
||||
if (/[\w]$/.test(expr)) {
|
||||
expr = `${expr}\\b`;
|
||||
}
|
||||
}
|
||||
|
||||
return expr;
|
||||
}).join('|'),
|
||||
).join('|'), 'i');
|
||||
};
|
||||
|
||||
const checkFiltered = (index: string, filters: ImmutableList<Filter>) =>
|
||||
filters.reduce((result: Array<string>, filter) =>
|
||||
result.concat(filter.keywords.reduce((result: Array<string>, keyword) => {
|
||||
let expr = escapeRegExp(keyword.keyword);
|
||||
|
||||
if (keyword.whole_word) {
|
||||
if (/^[\w]/.test(expr)) {
|
||||
expr = `\\b${expr}`;
|
||||
}
|
||||
|
||||
if (/[\w]$/.test(expr)) {
|
||||
expr = `${expr}\\b`;
|
||||
}
|
||||
}
|
||||
|
||||
const regex = new RegExp(expr);
|
||||
|
||||
if (regex.test(index)) return result.concat(filter.title);
|
||||
return result;
|
||||
}, [])), []);
|
||||
|
||||
type APIStatus = { id: string; username?: string };
|
||||
|
||||
const makeGetStatus = () => createSelector(
|
||||
@ -133,13 +63,12 @@ const makeGetStatus = () => createSelector(
|
||||
},
|
||||
(state: RootState, { id }: APIStatus) => state.polls.get(id) || null,
|
||||
(_state: RootState, { username }: APIStatus) => username,
|
||||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
(state: RootState) => getLocale('en'),
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features, locale) => {
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, me, features, locale) => {
|
||||
if (!statusBase) return null;
|
||||
const { account } = statusBase;
|
||||
const accountUsername = account.acct;
|
||||
@ -149,17 +78,12 @@ const makeGetStatus = () => createSelector(
|
||||
return null;
|
||||
}
|
||||
|
||||
const filtered = features.filtersV2
|
||||
? statusBase.filtered
|
||||
: features.filters && account.id !== me && checkFiltered(statusReblog?.search_index || statusBase.search_index || '', filters) || [];
|
||||
|
||||
return {
|
||||
...statusBase,
|
||||
reblog: statusReblog || null,
|
||||
quote: statusQuote || null,
|
||||
group: statusGroup || null,
|
||||
poll,
|
||||
filtered,
|
||||
};
|
||||
// if (map.currentLanguage === null && map.content_map?.size) {
|
||||
// let currentLanguage: string | null = null;
|
||||
@ -329,9 +253,6 @@ export {
|
||||
selectAccounts,
|
||||
selectOwnAccount,
|
||||
makeGetAccount,
|
||||
getFilters,
|
||||
regexFromFilters,
|
||||
makeGetStatus,
|
||||
type SelectedStatus,
|
||||
type AccountGalleryAttachment,
|
||||
getAccountGallery,
|
||||
|
||||
Reference in New Issue
Block a user