work on turning pl-hooks into a separate library

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-13 00:15:58 +02:00
parent dc5fa13e64
commit 8a047d7c3a
78 changed files with 340 additions and 318 deletions

View file

@ -96,7 +96,7 @@ const baseAccountSchema = z.object({
acct: z.string().catch(''),
url: z.string().url(),
display_name: z.string().catch(''),
note: z.string().catch(''),
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
avatar: z.string().catch(''),
avatar_static: z.string().url().catch(''),
header: z.string().url().catch(''),

View file

@ -41,7 +41,7 @@ const baseStatusSchema = z.object({
uri: z.string().url().catch(''),
created_at: dateSchema,
account: accountSchema,
content: z.string().catch(''),
content: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
visibility: z.string().catch('public'),
sensitive: z.coerce.boolean(),
spoiler_text: z.string().catch(''),

View file

@ -1,10 +1,10 @@
import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship } from 'pl-api';
import { importEntities } from 'pl-hooks/importer';
import { getClient, type PlfeResponse } from 'pl-fe/api';
import { Entities } from 'pl-fe/entity-store/entities';
import { selectAccount } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { importEntities } from 'pl-hooks/importer';
import type { Map as ImmutableMap } from 'immutable';
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';

View file

@ -1,7 +1,8 @@
import { importEntities } from 'pl-hooks/importer';
import { fetchRelationships } from 'pl-fe/actions/accounts';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { filterBadges, getTagDiff } from 'pl-fe/utils/badges';
import { deleteFromTimelines } from './timelines';

View file

@ -1,7 +1,7 @@
import { importEntities } from 'pl-hooks/importer';
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';

View file

@ -7,6 +7,7 @@
* @see module:pl-fe/actions/security
*/
import { credentialAccountSchema, PlApiClient, type CreateAccountParams, type Token } from 'pl-api';
import { importEntities } from 'pl-hooks/importer';
import { defineMessages } from 'react-intl';
import { createAccount } from 'pl-fe/actions/accounts';
@ -17,7 +18,6 @@ import { startOnboarding } from 'pl-fe/actions/onboarding';
import { type PlfeResponse, getClient } from 'pl-fe/api';
import * as BuildConfig from 'pl-fe/build-config';
import { custom } from 'pl-fe/custom';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { queryClient } from 'pl-fe/queries/client';
import { selectAccount } from 'pl-fe/selectors';
import { unsetSentryAccount } from 'pl-fe/sentry';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { PaginatedResponse, Status } from 'pl-api';

View file

@ -1,11 +1,11 @@
import throttle from 'lodash/throttle';
import { importEntities } from 'pl-hooks/importer';
import { defineMessages, IntlShape } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { isNativeEmoji } from 'pl-fe/features/emoji';
import emojiSearch from 'pl-fe/features/emoji/search';
import { Language } from 'pl-fe/features/preferences';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount, selectOwnAccount, makeGetAccount } from 'pl-fe/selectors';
import { tagHistory } from 'pl-fe/settings';
import { useModalsStore, useSettingsStore } from 'pl-fe/stores';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import type { Account, Conversation, PaginatedResponse, Status } from 'pl-api';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { fetchRelationships } from './accounts';

View file

@ -1,8 +1,8 @@
import { queryClient } from 'pl-fe/queries/client';
import KVStore from 'pl-fe/storage/kv-store';
import type { Account } from 'pl-fe/pl-hooks/normalizers/normalizeAccount';
import type { AppDispatch, RootState } from 'pl-fe/store';
import type { Account } from 'pl-hooks/normalizers/normalizeAccount';
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import type { Status } from 'pl-api';

View file

@ -1,8 +1,8 @@
import { importEntities } from 'pl-hooks/importer';
import { defineMessages } from 'react-intl';
import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOURCE_SUCCESS } from 'pl-fe/actions/statuses';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast from 'pl-fe/toast';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { AppDispatch, RootState } from 'pl-fe/store';
import { fetchRelationships } from './accounts';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import type { PaginatedResponse, Status } from 'pl-api';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { Account, PaginatedResponse } from 'pl-api';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { StatusEdit } from 'pl-api';

View file

@ -1,7 +1,7 @@
import { importEntities } from 'pl-hooks/importer';
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast, { type IToastOptions } from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import { setSentryAccount } from 'pl-fe/sentry';
import KVStore from 'pl-fe/storage/kv-store';

View file

@ -1,11 +1,11 @@
import IntlMessageFormat from 'intl-messageformat';
import 'intl-pluralrules';
import { importEntities } from 'pl-hooks/importer';
import { defineMessages } from 'react-intl';
import { FILTER_TYPES, type FilterType } from 'pl-fe/features/notifications';
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
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 { useSettingsStore } from 'pl-fe/stores';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import type { Status } from 'pl-api';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { Poll } from 'pl-api';

View file

@ -1,6 +1,5 @@
import mapValues from 'lodash/mapValues';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { importEntities } from 'pl-hooks/importer';
import { verifyCredentials } from './auth';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useSettingsStore } from 'pl-fe/stores';
import { fetchRelationships } from './accounts';

View file

@ -1,9 +1,9 @@
import { Account } from 'pl-hooks/normalizers/normalizeAccount';
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 { Account } from 'pl-fe/pl-hooks/normalizers/normalizeAccount';
import { queryClient } from 'pl-fe/queries/client';
import KVStore from 'pl-fe/storage/kv-store';
import { useSettingsStore } from 'pl-fe/stores';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { Status as BaseStatus, PaginatedResponse } from 'pl-api';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore, useSettingsStore } from 'pl-fe/stores';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { shouldHaveCard } from 'pl-fe/utils/status';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { fetchRelationships } from './accounts';

View file

@ -1,9 +1,8 @@
import { Map as ImmutableMap } from 'immutable';
import { importEntities } from 'pl-hooks/importer';
import { getLocale } from 'pl-fe/actions/settings';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useSettingsStore } from 'pl-fe/stores';
import { shouldFilter } from 'pl-fe/utils/timelines';

View file

@ -1,5 +1,6 @@
import { importEntities } from 'pl-hooks/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,7 +1,7 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { HStack, Icon, Text } from 'pl-fe/components/ui';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IQuotedStatusIndicator {
/** The quoted status id. */

View file

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect, useRef } from 'react';
import { defineMessages, useIntl, FormattedList, FormattedMessage } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
@ -12,7 +13,6 @@ 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, 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';

View file

@ -1,7 +1,7 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import Status, { IStatus } from 'pl-fe/components/status';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IStatusContainer extends Omit<IStatus, 'status'> {
id: string;

View file

@ -1,10 +1,10 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import Link from 'pl-fe/components/link';
import { Text } from 'pl-fe/components/ui';
import { useCompose } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IReplyGroupIndicator {
composeId: string;

View file

@ -1,8 +1,8 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { FormattedList, FormattedMessage } from 'react-intl';
import { useCompose, useFeatures } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import { useModalsStore } from 'pl-fe/stores';
interface IReplyMentions {

View file

@ -1,9 +1,9 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { cancelQuoteCompose } from 'pl-fe/actions/compose';
import QuotedStatus from 'pl-fe/components/quoted-status';
import { useAppDispatch, useCompose } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IQuotedStatusContainer {
composeId: string;

View file

@ -1,8 +1,8 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { cancelReplyCompose } from 'pl-fe/actions/compose';
import { useAppDispatch, useCompose } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import ReplyIndicator from '../components/reply-indicator';

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
@ -7,7 +8,6 @@ import Status from 'pl-fe/components/status';
import { Spinner } from 'pl-fe/components/ui';
import { useLogo } from 'pl-fe/hooks';
import { iframeId } from 'pl-fe/iframe';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IEmbeddedStatus {
params: {

View file

@ -1,4 +1,5 @@
import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect, useRef, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
@ -11,7 +12,6 @@ import { Stack } from 'pl-fe/components/ui';
import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status';
import PendingStatus from 'pl-fe/features/ui/components/pending-status';
import { useAppDispatch, useAppSelector } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import ComposeForm from '../compose/components/compose-form';
import { getDescendantsIds } from '../status/components/thread';

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useCallback } from 'react';
import { FormattedDate, FormattedMessage } from 'react-intl';
@ -8,7 +9,6 @@ import TranslateButton from 'pl-fe/components/translate-button';
import { HStack, Icon, Stack, Text } from 'pl-fe/components/ui';
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
import { usePlFeConfig } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import { useModalsStore } from 'pl-fe/stores';
type RouteParams = { statusId: string };

View file

@ -1,10 +1,10 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import ReactSwipeableViews from 'react-swipeable-views';
import EventPreview from 'pl-fe/components/event-preview';
import { Card, Icon } from 'pl-fe/components/ui';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import PlaceholderEventPreview from '../../placeholder/components/placeholder-event-preview';

View file

@ -1,3 +1,4 @@
import { useNotification } from 'pl-hooks/hooks/notifications/useNotification';
import React, { useCallback } from 'react';
import { defineMessages, useIntl, FormattedList, FormattedMessage, IntlShape, MessageDescriptor } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
@ -13,7 +14,6 @@ import AccountContainer from 'pl-fe/containers/account-container';
import StatusContainer from 'pl-fe/containers/status-container';
import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
import { useAppDispatch, useInstance, useLoggedIn } from 'pl-fe/hooks';
import { useNotification } from 'pl-fe/pl-hooks/hooks/notifications/useNotification';
import { useModalsStore, useSettingsStore } from 'pl-fe/stores';
import { NotificationType } from 'pl-fe/utils/notification';

View file

@ -1,5 +1,8 @@
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import { useMarker } from 'pl-hooks/hooks/markers/useMarkers';
import { useUpdateMarkerMutation } from 'pl-hooks/hooks/markers/useUpdateMarkerMutation';
import { useNotificationList } from 'pl-hooks/hooks/notifications/useNotificationList';
import React, { useCallback, useEffect, useRef } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -10,9 +13,6 @@ import ScrollableList from 'pl-fe/components/scrollable-list';
import { Column, Portal } from 'pl-fe/components/ui';
import PlaceholderNotification from 'pl-fe/features/placeholder/components/placeholder-notification';
import { useAppDispatch, useAppSelector, useSettings } from 'pl-fe/hooks';
import { useMarker } from 'pl-fe/pl-hooks/hooks/markers/useMarkers';
import { useUpdateMarkerMutation } from 'pl-fe/pl-hooks/hooks/markers/useUpdateMarkerMutation';
import { useNotificationList } from 'pl-fe/pl-hooks/hooks/notifications/useNotificationList';
import { compareId } from 'pl-fe/utils/comparators';
import { NotificationType } from 'pl-fe/utils/notification';

View file

@ -1,7 +1,7 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import QuotedStatus from 'pl-fe/components/quoted-status';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IQuotedStatusContainer {
/** Status ID to the quoted status. */

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Redirect } from 'react-router-dom';
@ -8,7 +9,6 @@ import PullToRefresh from 'pl-fe/components/pull-to-refresh';
import { Column, Stack } from 'pl-fe/components/ui';
import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status';
import { useAppDispatch, useLoggedIn } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import Thread from './components/thread';
import ThreadLoginCta from './components/thread-login-cta';

View file

@ -1,10 +1,10 @@
import { importEntities } from 'pl-hooks/importer';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { expandTimelineSuccess } from 'pl-fe/actions/timelines';
import { useAppDispatch, useTheme } from 'pl-fe/hooks';
import { useIsMobile } from 'pl-fe/hooks/useIsMobile';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { Column } from '../../components/ui';
import Timeline from '../ui/components/timeline';

View file

@ -1,10 +1,10 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Icon from 'pl-fe/components/icon';
import { Modal, Stack, Text } from 'pl-fe/components/ui';
import ReplyIndicator from 'pl-fe/features/compose/components/reply-indicator';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import type { BaseModalProps } from '../modal-root';
import type { Status as StatusEntity } from 'pl-fe/normalizers';

View file

@ -1,10 +1,10 @@
import L from 'leaflet';
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect, useRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button, Modal, Stack } from 'pl-fe/components/ui';
import { usePlFeConfig } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import type { BaseModalProps } from '../modal-root';

View file

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
@ -15,7 +16,6 @@ import Thread from 'pl-fe/features/status/components/thread';
import Video from 'pl-fe/features/video';
import { useAppDispatch } from 'pl-fe/hooks';
import { userTouching } from 'pl-fe/is-mobile';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import ImageLoader from '../image-loader';

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useEffect, useRef } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
@ -6,7 +7,6 @@ import ScrollableList from 'pl-fe/components/scrollable-list';
import { Modal, Spinner } from 'pl-fe/components/ui';
import AccountContainer from 'pl-fe/containers/account-container';
import { useAppDispatch } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import type { BaseModalProps } from '../modal-root';

View file

@ -1,10 +1,10 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Modal } from 'pl-fe/components/ui';
import Account from 'pl-fe/features/reply-mentions/account';
import { useCompose, useOwnAccount } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import { statusToMentionsAccountIdsArray } from 'pl-fe/reducers/compose';
import type { BaseModalProps } from '../modal-root';

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -7,7 +8,6 @@ import { RadioGroup, RadioItem } from 'pl-fe/components/radio';
import { Emoji, HStack, Icon, Modal, Spinner, Stack } from 'pl-fe/components/ui';
import NewFolderForm from 'pl-fe/features/bookmark-folders/components/new-folder-form';
import { useAppDispatch } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import type { BaseModalProps } from '../modal-root';

View file

@ -1,9 +1,9 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import Video from 'pl-fe/features/video';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
import type { BaseModalProps } from '../modal-root';
import type { MediaAttachment } from 'pl-api';

View file

@ -1,4 +1,6 @@
import clsx from 'clsx';
import { prefetchMarker } from 'pl-hooks/hooks/markers/useMarkers';
import { prefetchNotifications } from 'pl-hooks/hooks/notifications/useNotificationList';
import React, { Suspense, lazy, useEffect, useRef } from 'react';
import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom';
@ -33,8 +35,6 @@ import ProfileLayout from 'pl-fe/layouts/profile-layout';
import RemoteInstanceLayout from 'pl-fe/layouts/remote-instance-layout';
import SearchLayout from 'pl-fe/layouts/search-layout';
import StatusLayout from 'pl-fe/layouts/status-layout';
import { prefetchMarker } from 'pl-fe/pl-hooks/hooks/markers/useMarkers';
import { prefetchNotifications } from 'pl-fe/pl-hooks/hooks/notifications/useNotificationList';
import { useUiStore } from 'pl-fe/stores';
import { getVapidKey } from 'pl-fe/utils/auth';
import { isStandalone } from 'pl-fe/utils/state';

View file

@ -1,3 +1,4 @@
import { useStatus } from 'pl-hooks/hooks/statuses/useStatus';
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
@ -13,7 +14,6 @@ import {
WhoToFollowPanel,
} from 'pl-fe/features/ui/util/async-components';
import { useAppSelector, useFeatures } from 'pl-fe/hooks';
import { useStatus } from 'pl-fe/pl-hooks/hooks/statuses/useStatus';
interface IEventLayout {
params?: {

View file

@ -1,14 +1,13 @@
import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import sumBy from 'lodash/sumBy';
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema, type Relationship } from 'pl-api';
import { importEntities } from 'pl-hooks/importer';
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { Entities } from 'pl-fe/entity-store/entities';
import { useAppSelector, useClient, useFeatures, useLoggedIn, useOwnAccount } from 'pl-fe/hooks';
import { type ChatMessage, normalizeChatMessage } from 'pl-fe/normalizers';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { reOrderChatListItems } from 'pl-fe/utils/chats';
import { flattenPages, updatePageItem } from 'pl-fe/utils/queries';

View file

@ -1,9 +1,8 @@
import { useMutation, keepPreviousData, useQuery } from '@tanstack/react-query';
import { importEntities } from 'pl-hooks/importer';
import { fetchRelationships } from 'pl-fe/actions/accounts';
import { useAppDispatch, useClient } from 'pl-fe/hooks';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { removePageItem } from '../utils/queries';

View file

@ -15,7 +15,6 @@
"skipLibCheck": true,
"paths": {
"pl-fe/*": ["src/*"],
"pl-hooks/*": ["src/pl-hooks/*"],
},
"typeRoots": [
"./src/types",

View file

@ -154,7 +154,6 @@ const config = defineConfig(({ command }) => ({
resolve: {
alias: [
{ find: 'pl-fe', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
{ find: 'pl-hooks', replacement: fileURLToPath(new URL('./src/pl-hooks', import.meta.url)) },
],
},
test: {

View file

@ -0,0 +1,14 @@
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 60000, // 1 minute
gcTime: Infinity,
retry: false,
},
},
});
export { queryClient };

View file

@ -1,9 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { useRelationship } from 'pl-fe/api/hooks/accounts/useRelationship';
import { useClient } from 'pl-fe/hooks';
import { normalizeAccount } from 'pl-fe/pl-hooks/normalizers/normalizeAccount';
import { queryClient } from 'pl-fe/queries/client';
import { queryClient } from 'pl-hooks/client';
import { normalizeAccount } from 'pl-hooks/normalizers/normalizeAccount';
interface UseAccountOpts {
withRelationship?: boolean;

View file

@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';
import { useClient } from 'pl-fe/hooks';
import { queryClient } from 'pl-fe/queries/client';
import { queryClient } from 'pl-hooks/client';
import type { PlApiClient } from 'pl-api';

View file

@ -1,7 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { useClient } from 'pl-fe/hooks';
import { queryClient } from 'pl-fe/queries/client';
import { queryClient } from 'pl-hooks/client';
import type { Timeline } from './useMarkers';
import type { Marker } from 'pl-api';

View file

@ -1,16 +1,16 @@
import { useQuery } from '@tanstack/react-query';
import { useAppSelector, useClient } from 'pl-fe/hooks';
import { normalizeNotification, type Notification } from 'pl-fe/normalizers';
import { type MinifiedNotification, minifyNotification } from 'pl-fe/pl-hooks/minifiers/minifyNotification';
import { queryClient } from 'pl-fe/queries/client';
import { selectAccount, selectAccounts } from 'pl-fe/selectors';
import { queryClient } from 'pl-hooks/client';
import { type NormalizedNotification, normalizeNotification } from 'pl-hooks/normalizers/normalizeNotifications';
import { useAccount } from '../accounts/useAccount';
import { useStatus } from '../statuses/useStatus';
type Account = ReturnType<typeof selectAccount>;
const importNotification = (notification: MinifiedNotification) => {
queryClient.setQueryData<MinifiedNotification>(
const importNotification = (notification: NormalizedNotification) => {
queryClient.setQueryData<NormalizedNotification>(
['notifications', 'entities', notification.id],
existingNotification => existingNotification?.duplicate ? existingNotification : notification,
);
@ -22,25 +22,24 @@ const useNotification = (notificationId: string) => {
const notificationQuery = useQuery({
queryKey: ['notifications', 'entities', notificationId],
queryFn: () => client.notifications.getNotification(notificationId)
.then(normalizeNotification)
.then(minifyNotification),
.then(normalizeNotification),
});
const notification = notificationQuery.data;
const accountQuery = useAccount(notification?.account_id);
const moveTargetAccountQuery = useAccount(notification?.target_id);
const statusQuery = useStatus(notification?.status_id);
const data: Notification | null = useAppSelector((state) => {
const notification = notificationQuery.data;
if (!notification) return null;
const account = selectAccount(state, notification.account_id)!;
// @ts-ignore
const target = selectAccount(state, notification.target_id)!;
// @ts-ignore
const status = state.statuses.get(notification.status_id)!;
const accounts = selectAccounts(state, notification.account_ids).filter((account): account is Account => account !== undefined);
return {
...notification,
account,
target,
status,
account: accountQuery.data,
target: moveTargetAccountQuery.data,
status: statusQuery.data,
accounts,
};
});

View file

@ -1,17 +1,16 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { useClient } from 'pl-fe/hooks';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { deduplicateNotifications } from 'pl-fe/pl-hooks/normalizers/deduplicateNotifications';
import { queryClient } from 'pl-fe/queries/client';
import { flattenPages } from 'pl-fe/utils/queries';
import { queryClient } from 'pl-hooks/client';
import { importEntities } from 'pl-hooks/importer';
import { deduplicateNotifications } from 'pl-hooks/normalizers/deduplicateNotifications';
import { flattenPages } from 'pl-hooks/utils/queries';
import type { Notification as BaseNotification, PaginatedResponse, PlApiClient } from 'pl-api';
import type { NotificationType } from 'pl-fe/utils/notification';
type UseNotificationParams = {
types?: Array<NotificationType>;
excludeTypes?: Array<NotificationType>;
types?: Array<BaseNotification['type']>;
excludeTypes?: Array<BaseNotification['type']>;
}
const getQueryKey = (params: UseNotificationParams) => [

View file

@ -1,11 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import { useAppSelector, useClient } from 'pl-fe/hooks';
import { selectAccount, selectAccounts } from 'pl-fe/selectors';
import { useIntl } from 'react-intl';
import { useAccount } 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';
import { queryClient } from 'pl-hooks/client';
import { useAccount } from 'pl-hooks/hooks/accounts/useAccount';
import { importEntities } from 'pl-hooks/importer';
import { normalizeStatus, type Status } from '../../normalizers/normalizeStatus';
@ -106,7 +106,7 @@ const useStatus = (statusId?: string) => {
const status = statusQuery.data;
const { account } = useAccount(status?.account_id || undefined);
const { data: account } = useAccount(status?.account_id || undefined);
// : (Status & {
// account: Account;

View file

@ -1,10 +1,7 @@
import { importAccounts, importGroups, importPolls, importStatuses } from 'pl-fe/actions/importer';
import { importEntities as importEntityStoreEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { queryClient } from 'pl-fe/queries/client';
import { queryClient } from 'pl-hooks/client';
import { minifyNotification, type MinifiedNotification } from './minifiers/minifyNotification';
import { DeduplicatedNotification } from './normalizers/deduplicateNotifications';
import { type MinifiedNotification, minifyNotification } from './minifiers/minifyNotification';
import { DeduplicatedNotification } from './normalizers/normalizeNotifications';
import { normalizeStatus, type Status } from './normalizers/normalizeStatus';
import type {
@ -14,30 +11,34 @@ import type {
Relationship as BaseRelationship,
Status as BaseStatus,
} from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
let dispatch: AppDispatch;
const importAccount = (account: BaseAccount) => queryClient.setQueryData<BaseAccount>(
['accounts', 'entities', account.id], account,
);
import('pl-fe/store').then(value => dispatch = value.store.dispatch).catch(() => {});
const importGroup = (group: BaseGroup) => queryClient.setQueryData<BaseGroup>(
['groups', 'entities', group.id], group,
);
const importNotification = (notification: DeduplicatedNotification) => {
queryClient.setQueryData<MinifiedNotification>(
['notifications', 'entities', notification.id],
existingNotification => existingNotification?.duplicate ? existingNotification : minifyNotification(notification),
);
};
const importNotification = (notification: DeduplicatedNotification) => queryClient.setQueryData<MinifiedNotification>(
['notifications', 'entities', notification.id],
existingNotification => existingNotification?.duplicate ? existingNotification : minifyNotification(notification),
);
const importStatus = (status: BaseStatus) => {
queryClient.setQueryData<Status>(
['statuses', 'entities', status.id],
_ => normalizeStatus(status),
);
};
const importPoll = (poll: BasePoll) => queryClient.setQueryData<BasePoll>(
['polls', 'entities', poll.id], poll,
);
const isEmpty = (object: Record<string, any>) => {
for (const i in object) return false;
return true;
};
const importRelationship = (relationship: BaseRelationship) => queryClient.setQueryData<BaseRelationship>(
['relationships', 'entities', relationship.id], relationship,
);
const importStatus = (status: BaseStatus) => queryClient.setQueryData<Status>(
['statuses', 'entities', status.id],
_ => normalizeStatus(status),
);
const isEmpty = (object: Record<string, any>) => !Object.values(object).some(value => value);
const importEntities = (entities: {
accounts?: Array<BaseAccount>;
@ -56,16 +57,12 @@ const importEntities = (entities: {
const relationships: Record<string, BaseRelationship> = {};
const statuses: Record<string, BaseStatus> = {};
const processAccount = (account: BaseAccount, withParent = true) => {
if (withParent) accounts[account.id] = account;
const processAccount = (account: BaseAccount) => {
if (account.moved) processAccount(account.moved);
if (account.relationship) relationships[account.relationship.id] = account.relationship;
};
const processNotification = (notification: DeduplicatedNotification, withParent = true) => {
if (withParent) notifications[notification.id] = notification;
const processNotification = (notification: DeduplicatedNotification) => {
processAccount(notification.account);
if (notification.type === 'move') processAccount(notification.target);
@ -74,9 +71,8 @@ const importEntities = (entities: {
processStatus(notification.status);
};
const processStatus = (status: BaseStatus, withParent = true) => {
const processStatus = (status: BaseStatus) => {
if (status.account) {
if (withParent) statuses[status.id] = status;
processAccount(status.account);
}
@ -87,21 +83,23 @@ const importEntities = (entities: {
};
if (options.withParents) {
entities.accounts?.forEach(account => accounts[account.id] = account);
entities.groups?.forEach(group => groups[group.id] = group);
entities.notifications?.forEach(notification => notifications[notification.id] = notification);
entities.polls?.forEach(poll => polls[poll.id] = poll);
entities.relationships?.forEach(relationship => relationships[relationship.id] = relationship);
entities.statuses?.forEach(status => statuses[status.id] = status);
}
entities.accounts?.forEach((account) => processAccount(account, options.withParents));
entities.notifications?.forEach((notification) => processNotification(notification, options.withParents));
entities.statuses?.forEach((status) => processStatus(status, options.withParents));
entities.accounts?.forEach((account) => processAccount(account));
entities.notifications?.forEach((notification) => processNotification(notification));
entities.statuses?.forEach((status) => processStatus(status));
if (!isEmpty(accounts)) dispatch(importAccounts(Object.values(accounts)));
if (!isEmpty(groups)) dispatch(importGroups(Object.values(groups)));
if (!isEmpty(accounts)) Object.values(accounts).forEach(importAccount);
if (!isEmpty(groups)) Object.values(groups).forEach(importGroup);
if (!isEmpty(notifications)) Object.values(notifications).forEach(importNotification);
if (!isEmpty(polls)) dispatch(importPolls(Object.values(polls)));
if (!isEmpty(relationships)) dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
if (!isEmpty(statuses)) dispatch(importStatuses(Object.values(statuses)));
if (!isEmpty(polls)) Object.values(polls).forEach(importPoll);
if (!isEmpty(relationships)) Object.values(relationships).forEach(importRelationship);
if (!isEmpty(statuses)) Object.values(statuses).forEach(importStatus);
};

View file

@ -1,6 +1,6 @@
import omit from 'lodash/omit';
import { DeduplicatedNotification } from '../normalizers/deduplicateNotifications';
import { DeduplicatedNotification } from '../normalizers/normalizeNotifications';
import type { AccountWarning, RelationshipSeveranceEvent } from 'pl-api';

View file

@ -1,45 +0,0 @@
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import type { Account as BaseAccount, Notification as BaseNotification } from 'pl-api';
type DeduplicatedNotification = BaseNotification & {
accounts: Array<BaseAccount>;
duplicate?: boolean;
}
const STATUS_NOTIFICATION_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const deduplicateNotifications = (notifications: Array<BaseNotification>) => {
const deduplicatedNotifications: DeduplicatedNotification[] = [];
for (const notification of notifications) {
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(notification.account);
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: true });
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
}
return deduplicatedNotifications;
};
export { deduplicateNotifications, type DeduplicatedNotification };

View file

@ -1,37 +1,9 @@
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 = ({ moved, ...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,
moved_id: moved?.id || null,
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),
})),
};
};
const normalizeAccount = ({ moved, ...account }: BaseAccount) => ({
...account,
moved_id: moved?.id || null,
});
type Account = ReturnType<typeof normalizeAccount>;

View file

@ -0,0 +1,131 @@
import omit from 'lodash/omit';
import type { AccountWarning, Account as BaseAccount, Notification as BaseNotification, RelationshipSeveranceEvent } from 'pl-api';
type DeduplicatedNotification = BaseNotification & {
accounts: Array<BaseAccount>;
duplicate?: boolean;
}
const STATUS_NOTIFICATION_TYPES = [
'mention',
'status',
'reblog',
'favourite',
'poll',
'update',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const getNotificationStatus = (n: Pick<BaseNotification, 'type'>) => {
if (STATUS_NOTIFICATION_TYPES.includes(n.type))
// @ts-ignore
return n.status;
return null;
};
const normalizeNotifications = (notifications: Array<BaseNotification>): Array<NormalizedNotification> => {
const deduplicatedNotifications: DeduplicatedNotification[] = [];
for (const notification of notifications) {
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(notification.account);
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: true });
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
}
return deduplicatedNotifications.map(normalizeNotification);
};
const normalizeNotification = (notification: BaseNotification | DeduplicatedNotification) => {
// @ts-ignore
const minifiedNotification: {
duplicate: boolean;
account_id: string;
account_ids: string[];
created_at: string;
id: string;
group_key: string;
} & (
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
| {
type: 'mention';
subtype?: 'reply';
status_id: string;
}
| {
type: 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
status_id: string;
}
| {
type: 'admin.report';
report: Report;
}
| {
type: 'severed_relationships';
relationship_severance_event: RelationshipSeveranceEvent;
}
| {
type: 'moderation_warning';
moderation_warning: AccountWarning;
}
| {
type: 'move';
target_id: string;
}
| {
type: 'emoji_reaction';
emoji: string;
emoji_url: string | null;
status_id: string;
}
| {
type: 'chat_mention';
chat_message_id: string;
}
| {
type: 'participation_accepted' | 'participation_request';
status_id: string;
participation_message: string | null;
}
) = {
duplicate: false,
...omit(notification, ['account', 'accounts', 'status', 'target', 'chat_message']),
account_id: notification.account.id,
account_ids: ('accounts' in notification) ? notification.accounts.map(({ id }) => id) : [notification.account.id],
created_at: notification.created_at,
id: notification.id,
type: notification.type,
};
// @ts-ignore
if (notification.status) minifiedNotification.status_id = notification.status.id;
// @ts-ignore
if (notification.target) minifiedNotification.target_id = notification.target.id;
// @ts-ignore
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
return minifiedNotification;
};
type NormalizedNotification = ReturnType<typeof normalizeNotification>;
export { normalizeNotifications, normalizeNotification, type NormalizedNotification };

View file

@ -3,96 +3,12 @@
* Converts API statuses into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/status/}
*/
import escapeTextContentForBrowser from 'escape-html';
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 { queryClient } from 'pl-fe/queries/client';
import { unescapeHTML } from 'pl-fe/utils/html';
import { makeEmojiMap } from 'pl-fe/utils/normalizers';
const domParser = new DOMParser();
import { type Account as BaseAccount, type Status as BaseStatus, type MediaAttachment, mentionSchema } from 'pl-api';
type StatusApprovalStatus = Exclude<BaseStatus['approval_status'], null>;
type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 'group' | 'mutuals_only' | 'local';
type CalculatedValues = {
search_index: string;
contentHtml: string;
spoilerHtml: string;
contentMapHtml?: Record<string, string>;
spoilerMapHtml?: Record<string, string>;
expanded?: boolean | null;
hidden?: boolean | null;
translation?: Translation | null | false;
currentLanguage?: string;
};
type OldStatus = Pick<BaseStatus, 'content' | 'spoiler_text'> & CalculatedValues;
// Gets titles of poll options from status
const getPollOptionTitles = ({ poll }: Pick<BaseStatus, 'poll'>): readonly string[] => {
if (poll && typeof poll === 'object') {
return poll.options.map(({ title }) => title);
} else {
return [];
}
};
// Gets usernames of mentioned users from status
const getMentionedUsernames = (status: Pick<BaseStatus, 'mentions'>): Array<string> =>
status.mentions.map(({ acct }) => `@${acct}`);
// Creates search text from the status
const buildSearchContent = (status: Pick<BaseStatus, 'poll' | 'mentions' | 'spoiler_text' | 'content'>): string => {
const pollOptionTitles = getPollOptionTitles(status);
const mentionedUsernames = getMentionedUsernames(status);
const fields = [
status.spoiler_text,
status.content,
...pollOptionTitles,
...mentionedUsernames,
];
return unescapeHTML(fields.join('\n\n')) || '';
};
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 => {
if (oldStatus && oldStatus.content === status.content && oldStatus.spoiler_text === status.spoiler_text) {
const {
search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, expanded, translation, currentLanguage,
} = oldStatus;
return {
search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, expanded, translation, currentLanguage,
};
} else {
const searchContent = buildSearchContent(status);
const emojiMap = makeEmojiMap(status.emojis);
return {
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '',
contentHtml: calculateContent(status.content, emojiMap, !!status.quote),
spoilerHtml: calculateSpoiler(status.spoiler_text, emojiMap),
contentMapHtml: status.content_map
? Object.fromEntries(Object.entries(status.content_map)?.map(([key, value]) => [key, calculateContent(value, emojiMap, !!status.quote)]))
: undefined,
spoilerMapHtml: status.spoiler_text_map
? Object.fromEntries(Object.entries(status.spoiler_text_map).map(([key, value]) => [key, calculateSpoiler(value, emojiMap)]))
: undefined,
};
}
};
const normalizeStatus = ({ account, accounts, reblog, poll, group, quote, ...status }: BaseStatus & { accounts?: Array<BaseAccount> }) => {
const oldStatus = queryClient.getQueryData<OldStatus>(['statuses', 'entities', status.id]);
const calculated = calculateStatus(status, oldStatus);
// Sort the replied-to mention to the top
let mentions = status.mentions.toSorted((a, _b) => {
if (a.id === status.in_reply_to_account_id) {
@ -107,7 +23,7 @@ const normalizeStatus = ({ account, accounts, reblog, poll, group, quote, ...sta
const hasSelfMention = status.mentions.some(mention => account.id === mention.id);
if (isSelfReply && !hasSelfMention) {
const selfMention = mentionSchema.parse(status.account);
const selfMention = mentionSchema.parse(account);
mentions = [selfMention, ...mentions];
}
@ -151,13 +67,9 @@ const normalizeStatus = ({ account, accounts, reblog, poll, group, quote, ...sta
mentions,
expanded: null,
hidden: null,
/** Rewrite `<p></p>` to empty string. */
content: status.content === '<p></p>' ? '' : status.content,
filtered: status.filtered?.map(result => result.filter.title),
event,
media_attachments,
...calculated,
translation: (status.translation || calculated.translation || null) as Translation | null | false,
};
};

View file

@ -0,0 +1,12 @@
import type { InfiniteData } from '@tanstack/react-query';
import type { PaginatedResponse } from 'pl-api';
/** Flatten paginated results into a single array. */
const flattenPages = <T>(queryData: InfiniteData<Pick<PaginatedResponse<T>, 'items'>> | undefined) => {
return queryData?.pages.reduce<T[]>(
(prev: T[], curr) => [...prev, ...(curr.items)],
[],
);
};
export { flattenPages };

View file

@ -18,6 +18,7 @@
},
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@types/lodash": "^4.17.10",
"@types/node": "^20.14.12",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.0.0",
@ -32,6 +33,7 @@
},
"dependencies": {
"@tanstack/react-query": "^5.56.2",
"lodash": "^4.17.21",
"pl-api": "^0.0.37"
},
"module": "./dist/main.es.js",

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": "./",
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
@ -18,7 +19,11 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"pl-hooks/*": ["lib/*"],
},
},
"include": ["src", "lib"]
"include": ["lib"]
}

View file

@ -1,3 +1,4 @@
import { fileURLToPath, URL } from 'node:url';
import { resolve } from 'path';
import { defineConfig } from 'vite';
@ -16,4 +17,9 @@ export default defineConfig({
target: 'esnext',
sourcemap: true,
},
resolve: {
alias: [
{ find: 'pl-hooks', replacement: fileURLToPath(new URL('./lib', import.meta.url)) },
],
},
});

View file

@ -408,6 +408,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash@^4.17.10":
version "4.17.10"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.10.tgz#64f3edf656af2fe59e7278b73d3e62404144a6e6"
integrity sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==
"@types/node@^20.14.12":
version "20.14.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49"
@ -1863,7 +1868,7 @@ lodash.pick@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
lodash@~4.17.15:
lodash@^4.17.21, lodash@~4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==