diff --git a/src/actions/moderation.tsx b/src/actions/moderation.tsx index bdaebba5c..52b418b11 100644 --- a/src/actions/moderation.tsx +++ b/src/actions/moderation.tsx @@ -9,7 +9,6 @@ import { Stack, Text } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { selectAccount } from 'soapbox/selectors'; import toast from 'soapbox/toast'; -import { isLocal } from 'soapbox/utils/accounts'; import type { AppDispatch, RootState } from 'soapbox/store'; @@ -79,7 +78,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () = const account = selectAccount(state, accountId)!; const acct = account.acct; const name = account.username; - const local = isLocal(account); + const local = account.local; const message = ( diff --git a/src/components/event-preview.tsx b/src/components/event-preview.tsx index 4226bd5ca..ec1031662 100644 --- a/src/components/event-preview.tsx +++ b/src/components/event-preview.tsx @@ -10,7 +10,7 @@ import Icon from './icon'; import { Button, HStack, Stack, Text } from './ui'; import VerificationBadge from './verification-badge'; -import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities'; +import type { Status as StatusEntity } from 'soapbox/types/entities'; const messages = defineMessages({ eventBanner: { id: 'event.banner', defaultMessage: 'Event banner' }, @@ -30,7 +30,7 @@ const EventPreview: React.FC = ({ status, className, hideAction, const me = useAppSelector((state) => state.me); - const account = status.account as AccountEntity; + const account = status.account; const event = status.event!; const banner = event.banner; diff --git a/src/components/profile-hover-card.tsx b/src/components/profile-hover-card.tsx index 9c06dd92c..0def77693 100644 --- a/src/components/profile-hover-card.tsx +++ b/src/components/profile-hover-card.tsx @@ -14,7 +14,6 @@ import Badge from 'soapbox/components/badge'; import ActionButton from 'soapbox/features/ui/components/action-button'; import { UserPanel } from 'soapbox/features/ui/util/async-components'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; -import { isLocal } from 'soapbox/utils/accounts'; import { showProfileHoverCard } from './hover-ref-wrapper'; import { dateFormatOptions } from './relative-timestamp'; @@ -117,7 +116,7 @@ export const ProfileHoverCard: React.FC = ({ visible = true } badges={badges} /> - {isLocal(account) ? ( + {account.local ? ( = ({ status, onCancel, compose }) => const handleExpandClick: MouseEventHandler = (e) => { if (!status) return; - const account = status.account as AccountEntity; + const account = status.account; if (!compose && e.button === 0) { const statusUrl = `/@${account.acct}/posts/${status.id}`; @@ -79,7 +79,7 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => return null; } - const account = status.account as AccountEntity; + const account = status.account; let actions = {}; if (onCancel) { diff --git a/src/components/status-action-bar.tsx b/src/components/status-action-bar.tsx index d16df70cc..aad3c1f82 100644 --- a/src/components/status-action-bar.tsx +++ b/src/components/status-action-bar.tsx @@ -23,14 +23,13 @@ import { HStack } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; -import { isLocal, isRemote } from 'soapbox/utils/accounts'; import copy from 'soapbox/utils/copy'; import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts'; import GroupPopover from './groups/popover/group-popover'; import type { Menu } from 'soapbox/components/dropdown-menu'; -import type { Account, Group, Status } from 'soapbox/types/entities'; +import type { Group, Status } from 'soapbox/types/entities'; const messages = defineMessages({ adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' }, @@ -132,7 +131,7 @@ const StatusActionBar: React.FC = ({ const unmuteGroup = useUnmuteGroup(group as Group); const isMutingGroup = !!group?.relationship?.muting; const deleteGroupStatus = useDeleteGroupStatus(group as Group, status.id); - const blockGroupMember = useBlockGroupMember(group as Group, status?.account as any); + const blockGroupMember = useBlockGroupMember(group as Group, status.account); const me = useAppSelector(state => state.me); const { groupRelationship } = useGroupRelationship(status.group?.id); @@ -267,20 +266,20 @@ const StatusActionBar: React.FC = ({ }; const handleMentionClick: React.EventHandler = (e) => { - dispatch(mentionCompose(status.account as Account)); + dispatch(mentionCompose(status.account)); }; const handleDirectClick: React.EventHandler = (e) => { - dispatch(directCompose(status.account as Account)); + dispatch(directCompose(status.account)); }; const handleChatClick: React.EventHandler = (e) => { - const account = status.account as Account; + const account = status.account; dispatch(launchChat(account.id, history)); }; const handleMuteClick: React.EventHandler = (e) => { - dispatch(initMuteModal(status.account as Account)); + dispatch(initMuteModal(status.account)); }; const handleMuteGroupClick: React.EventHandler = () => @@ -305,7 +304,7 @@ const StatusActionBar: React.FC = ({ }; const handleBlockClick: React.EventHandler = (e) => { - const account = status.account as Account; + const account = status.account; dispatch(openModal('CONFIRM', { icon: require('@tabler/icons/ban.svg'), @@ -333,7 +332,7 @@ const StatusActionBar: React.FC = ({ }; const handleReport: React.EventHandler = (e) => { - dispatch(initReport(ReportableEntities.STATUS, status.account as Account, { status })); + dispatch(initReport(ReportableEntities.STATUS, status.account, { status })); }; const handleConversationMuteClick: React.EventHandler = (e) => { @@ -347,7 +346,7 @@ const StatusActionBar: React.FC = ({ }; const onModerate: React.MouseEventHandler = (e) => { - const account = status.account as Account; + const account = status.account; dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id })); }; @@ -360,7 +359,7 @@ const StatusActionBar: React.FC = ({ }; const handleDeleteFromGroup: React.EventHandler = () => { - const account = status.account as Account; + const account = status.account; dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.deleteHeading), @@ -379,10 +378,10 @@ const StatusActionBar: React.FC = ({ const handleBlockFromGroup = () => { dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.groupBlockFromGroupHeading), - message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: (status.account as any).username }), + message: intl.formatMessage(messages.groupBlockFromGroupMessage, { name: status.account.username }), confirm: intl.formatMessage(messages.groupBlockConfirm), onConfirm: () => { - blockGroupMember({ account_ids: [(status.account as any).id] }, { + blockGroupMember({ account_ids: [status.account.id] }, { onSuccess() { toast.success(intl.formatMessage(messages.blocked, { name: account?.acct })); }, @@ -415,7 +414,7 @@ const StatusActionBar: React.FC = ({ icon: require('@tabler/icons/clipboard-copy.svg'), }); - if (features.embeds && isLocal(account)) { + if (features.embeds && account.local) { menu.push({ text: intl.formatMessage(messages.embed), action: handleEmbed, @@ -449,7 +448,7 @@ const StatusActionBar: React.FC = ({ }); } - if (features.federating && isRemote(account)) { + if (features.federating && !account.local) { menu.push({ text: intl.formatMessage(messages.external, { domain }), action: handleExternalClick, @@ -554,7 +553,7 @@ const StatusActionBar: React.FC = ({ if (isGroupStatus && !!status.group) { const group = status.group as Group; - const account = status.account as Account; + const account = status.account; const isGroupOwner = groupRelationship?.role === GroupRoles.OWNER; const isGroupAdmin = groupRelationship?.role === GroupRoles.ADMIN; const isStatusFromOwner = group.owner.id === account.id; diff --git a/src/components/status-reply-mentions.tsx b/src/components/status-reply-mentions.tsx index 2b8e8633d..4296b38d7 100644 --- a/src/components/status-reply-mentions.tsx +++ b/src/components/status-reply-mentions.tsx @@ -8,7 +8,7 @@ import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper'; import { useAppDispatch } from 'soapbox/hooks'; import { isPubkey } from 'soapbox/utils/nostr'; -import type { Account, Status } from 'soapbox/types/entities'; +import type { Status } from 'soapbox/types/entities'; interface IStatusReplyMentions { status: Status; @@ -21,7 +21,7 @@ const StatusReplyMentions: React.FC = ({ status, hoverable const handleOpenMentionsModal: React.MouseEventHandler = (e) => { e.stopPropagation(); - const account = status.account as Account; + const account = status.account; dispatch(openModal('MENTIONS', { username: account.acct, diff --git a/src/components/status.tsx b/src/components/status.tsx index c2411e6dc..f226a8429 100644 --- a/src/components/status.tsx +++ b/src/components/status.tsx @@ -24,10 +24,7 @@ import StatusInfo from './statuses/status-info'; import Tombstone from './tombstone'; import { Card, Icon, Stack, Text } from './ui'; -import type { - Account as AccountEntity, - Status as StatusEntity, -} from 'soapbox/types/entities'; +import type { Status as StatusEntity } from 'soapbox/types/entities'; // Defined in components/scrollable-list export type ScrollPosition = { height: number; top: number }; @@ -168,7 +165,7 @@ const Status: React.FC = (props) => { const handleHotkeyMention = (e?: KeyboardEvent): void => { e?.preventDefault(); - dispatch(mentionCompose(actualStatus.account as AccountEntity)); + dispatch(mentionCompose(actualStatus.account)); }; const handleHotkeyOpen = (): void => { diff --git a/src/components/translate-button.tsx b/src/components/translate-button.tsx index e74b9b82a..7a9c31976 100644 --- a/src/components/translate-button.tsx +++ b/src/components/translate-button.tsx @@ -3,11 +3,10 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses'; import { useAppDispatch, useAppSelector, useFeatures, useInstance } from 'soapbox/hooks'; -import { isLocal } from 'soapbox/utils/accounts'; import { Stack, Button, Text } from './ui'; -import type { Account, Status } from 'soapbox/types/entities'; +import type { Status } from 'soapbox/types/entities'; interface ITranslateButton { status: Status; @@ -28,7 +27,7 @@ const TranslateButton: React.FC = ({ status }) => { target_languages: targetLanguages, } = instance.pleroma.metadata.translation; - const renderTranslate = (me || allowUnauthenticated) && (allowRemote || isLocal(status.account as Account)) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language; + const renderTranslate = (me || allowUnauthenticated) && (allowRemote || status.account.local) && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language; const supportsLanguages = (!sourceLanguages || sourceLanguages.includes(status.language!)) && (!targetLanguages || targetLanguages.includes(intl.locale)); diff --git a/src/features/account/components/header.tsx b/src/features/account/components/header.tsx index 21c4b51d0..a8ac8440d 100644 --- a/src/features/account/components/header.tsx +++ b/src/features/account/components/header.tsx @@ -28,7 +28,7 @@ import { ChatKeys, useChats } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { Account } from 'soapbox/schemas'; import toast from 'soapbox/toast'; -import { isDefaultHeader, isLocal, isRemote } from 'soapbox/utils/accounts'; +import { isDefaultHeader } from 'soapbox/utils/accounts'; import copy from 'soapbox/utils/copy'; import { MASTODON, parseVersion } from 'soapbox/utils/features'; @@ -287,7 +287,7 @@ const Header: React.FC = ({ account }) => { return []; } - if (features.rssFeeds && isLocal(account)) { + if (features.rssFeeds && account.local) { menu.push({ text: intl.formatMessage(messages.subscribeFeed), action: handleRssFeedClick, @@ -303,7 +303,7 @@ const Header: React.FC = ({ account }) => { }); } - if (features.federating && isRemote(account)) { + if (features.federating && !account.local) { const domain = account.fqn.split('@')[1]; menu.push({ @@ -453,7 +453,7 @@ const Header: React.FC = ({ account }) => { }); } - if (isRemote(account)) { + if (!account.local) { const domain = account.fqn.split('@')[1]; menu.push(null); diff --git a/src/features/event/components/event-header.tsx b/src/features/event/components/event-header.tsx index bca538ad9..6a778f5da 100644 --- a/src/features/event/components/event-header.tsx +++ b/src/features/event/components/event-header.tsx @@ -19,7 +19,6 @@ import { Button, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, Me import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; import VerificationBadge from 'soapbox/components/verification-badge'; import { useAppDispatch, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks'; -import { isRemote } from 'soapbox/utils/accounts'; import copy from 'soapbox/utils/copy'; import { download } from 'soapbox/utils/download'; import { shortNumberFormat } from 'soapbox/utils/numbers'; @@ -29,7 +28,7 @@ import EventActionButton from '../components/event-action-button'; import EventDate from '../components/event-date'; import type { Menu as MenuType } from 'soapbox/components/dropdown-menu'; -import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities'; +import type { Status as StatusEntity } from 'soapbox/types/entities'; const messages = defineMessages({ bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' }, @@ -89,7 +88,7 @@ const EventHeader: React.FC = ({ status }) => { ); } - const account = status.account as AccountEntity; + const account = status.account; const event = status.event; const banner = event.banner; @@ -217,7 +216,7 @@ const EventHeader: React.FC = ({ status }) => { }, ]; - if (features.federating && isRemote(account)) { + if (features.federating && !account.local) { menu.push({ text: intl.formatMessage(messages.external, { domain }), action: handleExternalClick, diff --git a/src/features/scheduled-statuses/components/scheduled-status.tsx b/src/features/scheduled-statuses/components/scheduled-status.tsx index 5e2fe03fa..7243bffd4 100644 --- a/src/features/scheduled-statuses/components/scheduled-status.tsx +++ b/src/features/scheduled-statuses/components/scheduled-status.tsx @@ -13,7 +13,7 @@ import { buildStatus } from '../builder'; import ScheduledStatusActionBar from './scheduled-status-action-bar'; -import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities'; +import type { Status as StatusEntity } from 'soapbox/types/entities'; interface IScheduledStatus { statusId: string; @@ -28,7 +28,7 @@ const ScheduledStatus: React.FC = ({ statusId, ...other }) => if (!status) return null; - const account = status.account as AccountEntity; + const account = status.account; return (
diff --git a/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx b/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx index dcb96c69d..2e7bcdb7e 100644 --- a/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx +++ b/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx @@ -12,7 +12,6 @@ import OutlineBox from 'soapbox/components/outline-box'; import { Button, Text, HStack, Modal, Stack, Toggle } from 'soapbox/components/ui'; import { useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; -import { isLocal } from 'soapbox/utils/accounts'; import { getBadges } from 'soapbox/utils/badges'; import BadgeInput from './badge-input'; @@ -115,7 +114,7 @@ const AccountModerationModal: React.FC = ({ onClose, ac - {(ownAccount.admin && isLocal(account)) && ( + {(ownAccount.admin && account.local) && ( }>
diff --git a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx index a67439241..31b7f4879 100644 --- a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx +++ b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx @@ -7,7 +7,7 @@ import { fetchRules } from 'soapbox/actions/rules'; import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; import StatusCheckBox from 'soapbox/features/report/components/status-check-box'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; -import { isRemote, getDomain } from 'soapbox/utils/accounts'; +import { getDomain } from 'soapbox/utils/accounts'; import type { Account } from 'soapbox/schemas'; @@ -31,7 +31,7 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => { const statusIds = useAppSelector((state) => OrderedSet(state.timelines.get(`account:${account.id}:with_replies`)!.items).union(state.reports.new.status_ids) as OrderedSet); const isBlocked = useAppSelector((state) => state.reports.new.block); const isForward = useAppSelector((state) => state.reports.new.forward); - const canForward = isRemote(account) && features.federating; + const canForward = !account.local && features.federating; const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting); const [showAdditionalStatuses, setShowAdditionalStatuses] = useState(false); diff --git a/src/features/ui/components/pending-status.tsx b/src/features/ui/components/pending-status.tsx index 5630de5ca..49dd2bcd8 100644 --- a/src/features/ui/components/pending-status.tsx +++ b/src/features/ui/components/pending-status.tsx @@ -14,7 +14,7 @@ import { buildStatus } from '../util/pending-status-builder'; import PollPreview from './poll-preview'; -import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities'; +import type { Status as StatusEntity } from 'soapbox/types/entities'; const shouldHaveCard = (pendingStatus: StatusEntity) => { return Boolean(pendingStatus.content.match(/https?:\/\/\S*/)); @@ -54,7 +54,7 @@ const PendingStatus: React.FC = ({ idempotencyKey, className, mu if (!status) return null; if (!status.account) return null; - const account = status.account as AccountEntity; + const account = status.account; return (
diff --git a/src/features/ui/components/profile-info-panel.tsx b/src/features/ui/components/profile-info-panel.tsx index 244434b8b..f3556cf7f 100644 --- a/src/features/ui/components/profile-info-panel.tsx +++ b/src/features/ui/components/profile-info-panel.tsx @@ -7,7 +7,6 @@ import Markup from 'soapbox/components/markup'; import { dateFormatOptions } from 'soapbox/components/relative-timestamp'; import { Icon, HStack, Stack, Text } from 'soapbox/components/ui'; import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks'; -import { isLocal } from 'soapbox/utils/accounts'; import { badgeToTag, getBadges as getAccountBadges } from 'soapbox/utils/badges'; import { capitalize } from 'soapbox/utils/strings'; @@ -176,7 +175,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => )}
- {isLocal(account) ? ( + {account.local ? ( = ({ params, children }) => { {(account && account.fields.length > 0) && ( )} - {(features.accountEndorsements && account && isLocal(account)) ? ( + {(features.accountEndorsements && account && account.local) ? ( ) : me && features.suggestions && ( diff --git a/src/schemas/account.ts b/src/schemas/account.ts index 88128c35e..fec6c18b2 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -38,6 +38,7 @@ const baseAccountSchema = z.object({ header_static: z.string().url().optional().catch(undefined), id: z.string(), last_status_at: z.string().datetime().optional().catch(undefined), + local: z.boolean().catch(false), location: z.string().optional().catch(undefined), locked: z.boolean().catch(false), moved: z.literal(null).catch(null), @@ -65,6 +66,7 @@ const baseAccountSchema = z.object({ hide_follows: z.boolean().catch(false), hide_follows_count: z.boolean().catch(false), is_admin: z.boolean().catch(false), + is_local: z.boolean().optional().catch(undefined), is_moderator: z.boolean().catch(false), is_suggested: z.boolean().catch(false), location: z.string().optional().catch(undefined), @@ -134,6 +136,7 @@ const transformAccount = ({ pleroma, other_setti fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`), header_static: account.header_static || account.header, moderator: pleroma?.is_moderator || false, + local: pleroma?.is_local !== undefined ? pleroma.is_local : account.acct.split('@')[1] === undefined, location: account.location || pleroma?.location || other_settings?.location || '', note_emojified: emojify(account.note, customEmojiMap), pleroma: (() => { diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 51122dfc5..c23344e87 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -26,13 +26,6 @@ export const getAcct = (account: Pick, displayFqn: bool displayFqn === true ? account.fqn : account.acct ); -export const isLocal = (account: Pick): boolean => { - const domain: string = account.acct.split('@')[1]; - return domain === undefined ? true : false; -}; - -export const isRemote = (account: Pick): boolean => !isLocal(account); - /** Default header filenames from various backends */ const DEFAULT_HEADERS: string[] = [ '/headers/original/missing.png', // Mastodon