From 6792677be81c2a763fa52b27c4624ea868587f9b Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 4 Aug 2026 04:20:43 +0300 Subject: [PATCH] flip the legacy view type imports to the generated lexicons Co-Authored-By: Claude Fable 5 --- src/view/com/composer/Composer.tsx | 21 +- src/view/com/composer/ComposerReplyTo.tsx | 39 +-- src/view/com/composer/drafts/state/api.ts | 42 +-- src/view/com/composer/drafts/state/queries.ts | 13 +- src/view/com/composer/drafts/state/schema.ts | 4 +- src/view/com/composer/state/composer.ts | 9 +- .../text-input/mobile/Autocomplete.tsx | 4 +- .../composer/text-input/web/Autocomplete.tsx | 4 +- .../composer/text-input/web/LinkDecorator.ts | 2 +- .../composer/text-input/web/TagDecorator.ts | 2 +- src/view/com/feeds/FeedPage.tsx | 6 +- src/view/com/feeds/FeedSourceCard.tsx | 16 +- src/view/com/feeds/MissingFeed.tsx | 2 +- src/view/com/lists/ListMembers.tsx | 4 +- src/view/com/lists/MyLists.tsx | 7 +- .../notifications/NotificationFeedItem.tsx | 47 +-- src/view/com/post-thread/PostLikedBy.tsx | 12 +- src/view/com/post-thread/PostQuotes.tsx | 15 +- src/view/com/post-thread/PostRepostedBy.tsx | 6 +- src/view/com/post/Post.tsx | 15 +- src/view/com/posts/PostFeed.tsx | 38 +-- src/view/com/posts/PostFeedErrorMessage.tsx | 15 +- src/view/com/posts/PostFeedItem.tsx | 60 ++-- src/view/com/posts/PostFeedReason.tsx | 11 +- src/view/com/posts/ViewFullThread.tsx | 2 +- src/view/com/profile/ProfileFollowers.tsx | 10 +- src/view/com/profile/ProfileFollows.tsx | 10 +- src/view/com/profile/ProfileMenu.tsx | 4 +- src/view/com/profile/ProfileSubpageHeader.tsx | 4 +- src/view/com/util/PostMeta.tsx | 4 +- src/view/com/util/UserInfoText.tsx | 4 +- src/view/screens/DebugMod.tsx | 295 +++++++++++++++--- src/view/screens/Feeds.tsx | 4 +- src/view/screens/Lists.tsx | 2 +- .../screens/ModerationBlockedAccounts.tsx | 6 +- src/view/screens/ModerationModlists.tsx | 2 +- src/view/screens/ModerationMutedAccounts.tsx | 4 +- src/view/screens/Profile.tsx | 4 +- src/view/shell/desktop/LeftNav.tsx | 6 +- 39 files changed, 466 insertions(+), 289 deletions(-) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 8108c41cd1..c297d9fd98 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -44,20 +44,15 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnUI} from 'react-native-worklets' import * as FileSystem from 'expo-file-system' import {type ImagePickerAsset} from 'expo-image-picker' -import { - AppBskyDraftCreateDraft, - AppBskyUnspeccedDefs, - AtUri, - ChatBskyGroupDefs, -} from '@atproto/api' import {type Client} from '@atproto/lex' -import {type AtUriString} from '@atproto/syntax' +import {type AtUriString, AtUri} from '@atproto/syntax' import {type RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueries, useQueryClient} from '@tanstack/react-query' +import * as bsky from '#/types/bsky' import * as apilib from '#/lib/api/index' import {EmbeddingDisabledError} from '#/lib/api/resolve' import {useAppState} from '#/lib/appState' @@ -149,7 +144,7 @@ import { IS_WEB_SAFARI, } from '#/env' import {type Gif} from '#/features/gifPicker/types' -import {app} from '#/lexicons' +import {app, chat} from '#/lexicons' import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' import { draftToComposerPosts, @@ -771,7 +766,7 @@ export const ComposePost = ({ const getDraftSaveError = useCallback( (e: unknown): string => { - if (e instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { + if (e instanceof app.bsky.draft.createDraft.DraftLimitReachedError) { return l`You've reached the maximum number of drafts` } return l`Failed to save draft` @@ -1010,7 +1005,7 @@ export const ComposePost = ({ const hasUnavailableChatInvite = linkQueries.some( q => q.data?.type === 'chat-invite' && - !ChatBskyGroupDefs.isJoinLinkPreviewView(q.data.view), + !bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, q.data.view), ) const canPost = @@ -1140,7 +1135,7 @@ export const ComposePost = ({ } if ( !res.thread.every(p => - AppBskyUnspeccedDefs.isThreadItemPost(p.value), + bsky.isType(app.bsky.unspecced.defs.threadItemPost, p.value), ) ) { throw new Error(`composer: app view returned non-post items`) @@ -1212,7 +1207,7 @@ export const ComposePost = ({ const resolved = q.data if ( resolved?.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolved.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolved.view) ) { ax.metric('groupchat:inviteLink:shared', { convoId: resolved.view.convoId, @@ -1251,7 +1246,7 @@ export const ComposePost = ({ void whenAppViewReady(client, initQuote.uri, res => { const anchor = res.thread.at(0) if ( - AppBskyUnspeccedDefs.isThreadItemPost(anchor?.value) && + bsky.isType(app.bsky.unspecced.defs.threadItemPost, anchor?.value) && anchor.value.post.quoteCount !== initQuote.quoteCount ) { onPost?.(postUri) diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 00a108bb54..c09c93b3a4 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -1,17 +1,12 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Pressable, View} from 'react-native' import {Image} from 'expo-image' -import { - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedPost, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' +import * as bsky from '#/types/bsky' +import {app} from '#/lexicons' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {type ComposerOptsPostRef} from '#/state/shell/composer' @@ -39,15 +34,15 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const quoteEmbed = useMemo(() => { if ( - AppBskyEmbedRecord.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record) && - AppBskyFeedPost.isRecord(embed.record.value) + bsky.isType(app.bsky.embed.record.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record) && + bsky.isType(app.bsky.feed.post, embed.record.value) ) { return embed } else if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record.record) && - AppBskyFeedPost.isRecord(embed.record.record.value) + bsky.isType(app.bsky.embed.recordWithMedia.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record.record) && + bsky.isType(app.bsky.feed.post, embed.record.record.value) ) { return embed.record } @@ -61,20 +56,20 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { : null const {images, totalNumber} = useMemo(() => { - if (AppBskyEmbedImages.isView(embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed)) { return {images: embed.images, totalNumber: embed.images.length} - } else if (AppBskyEmbedGallery.isView(embed)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed)) { return { images: galleryItemsToImages(embed.items), totalNumber: embed.items.length, } - } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { - if (AppBskyEmbedImages.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.recordWithMedia.view, embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed.media)) { return { images: embed.media.images, totalNumber: embed.media.images.length, } - } else if (AppBskyEmbedGallery.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed.media)) { return { images: galleryItemsToImages(embed.media.items), totalNumber: embed.media.items.length, @@ -145,12 +140,12 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { } function galleryItemsToImages( - items: AppBskyEmbedGallery.View['items'], -): AppBskyEmbedImages.ViewImage[] { + items: app.bsky.embed.gallery.View['items'], +): app.bsky.embed.images.ViewImage[] { // The reply-to thumbnail only renders up to 4 tiles; slicing here keeps // the existing layout switch valid for galleries up to 10 items. return items - .filter(AppBskyEmbedGallery.isViewImage) + .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item)) .slice(0, 4) .map(item => ({ thumb: item.thumbnail, @@ -164,7 +159,7 @@ function ComposerReplyToImages({ images, totalNumber, }: { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] totalNumber: number }) { const t = useTheme() diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index 34fa4f6db4..40ab2cd6de 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -1,10 +1,12 @@ /** * Type converters for Draft API - convert between ComposerState and server Draft types. */ -import {AppBskyDraftDefs, AtUri} from '@atproto/api' import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' +import {AtUri} from '@atproto/syntax' +import * as bsky from '#/types/bsky' +import {app} from '#/lexicons' import {type LinkResolvers, resolveLink} from '#/lib/api/resolve' import {getDeviceName} from '#/lib/deviceName' import {getImageDim} from '#/lib/media/manip' @@ -63,18 +65,18 @@ export async function composerStateToDraft( clients: LinkResolvers, state: ComposerState, ): Promise<{ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft localRefPaths: Map }> { const localRefPaths = new Map() - const posts: AppBskyDraftDefs.DraftPost[] = await Promise.all( + const posts: app.bsky.draft.defs.DraftPost[] = await Promise.all( state.thread.posts.map(post => { return postDraftToServerPost(clients, post, localRefPaths) }), ) - const draft: AppBskyDraftDefs.Draft = { + const draft: app.bsky.draft.defs.Draft = { $type: 'app.bsky.draft.defs#draft', deviceId: getDeviceId(), deviceName: getDeviceName().slice(0, 100), // max length of 100 in lex @@ -99,8 +101,8 @@ async function postDraftToServerPost( clients: LinkResolvers, post: PostDraft, localRefPaths: Map, -): Promise { - const draftPost: AppBskyDraftDefs.DraftPost = { +): Promise { + const draftPost: app.bsky.draft.defs.DraftPost = { $type: 'app.bsky.draft.defs#draftPost', text: post.richtext.text, } @@ -176,7 +178,7 @@ async function postDraftToServerPost( function serializeImages( images: ComposerImage[], localRefPaths: Map, -): AppBskyDraftDefs.DraftEmbedGalleryItems { +): app.bsky.draft.defs.DraftEmbedGalleryItems { return images.map(image => { const sourcePath = image.transformed?.path || image.source.path // Reuse existing localRefPath if present (editing draft), otherwise generate new @@ -208,7 +210,7 @@ function serializeImages( async function serializeVideo( videoState: VideoState, localRefPaths: Map, -): Promise { +): Promise { // Only save videos that have been compressed (have a video file) if (!videoState.video) { return undefined @@ -221,7 +223,7 @@ async function serializeVideo( localRefPaths.set(localRefPath, videoState.video.uri) // Read caption file contents as text - const captions: AppBskyDraftDefs.DraftEmbedCaption[] = [] + const captions: app.bsky.draft.defs.DraftEmbedCaption[] = [] for (const caption of videoState.captions) { if (caption.lang) { const content = await caption.file.text() @@ -252,7 +254,7 @@ function serializeGif(gifMedia: { type: 'gif' gif: Gif alt: string -}): AppBskyDraftDefs.DraftEmbedExternal | undefined { +}): app.bsky.draft.defs.DraftEmbedExternal | undefined { const gif = gifMedia.gif const gifFormat = gif.media_formats.gif || gif.media_formats.tinygif @@ -282,7 +284,7 @@ function serializeGif(gifMedia: { * both the `embedImages` and `embedGallery` paths in draftToComposerPosts. */ async function restoreDraftImages( - draftImages: AppBskyDraftDefs.DraftEmbedImage[], + draftImages: app.bsky.draft.defs.DraftEmbedImage[], loadedMedia: Map, ): Promise { const imagePromises = draftImages.map(async img => { @@ -338,7 +340,7 @@ export function draftViewToSummary({ view, analytics, }: { - view: AppBskyDraftDefs.DraftView + view: app.bsky.draft.defs.DraftView analytics: AnalyticsContextType }): DraftSummary { const meta = { @@ -378,7 +380,7 @@ export function draftViewToSummary({ // Process gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue meta.mediaCount++ meta.hasMedia = true const exists = storage.mediaExists(item.localRef.path) @@ -494,7 +496,7 @@ function parseGifFromUrl( * by initiating video processing for each entry. */ export async function draftToComposerPosts( - draft: AppBskyDraftDefs.Draft, + draft: app.bsky.draft.defs.Draft, loadedMedia: Map, ): Promise<{posts: PostDraft[]; restoredVideos: Map}> { const restoredVideos = new Map() @@ -523,8 +525,8 @@ export async function draftToComposerPosts( ) } if (post.embedGallery && post.embedGallery.items.length > 0) { - const galleryImages = post.embedGallery.items.filter( - AppBskyDraftDefs.isDraftEmbedImage, + const galleryImages = post.embedGallery.items.filter(item => + bsky.isType(app.bsky.draft.defs.draftEmbedImage, item), ) restoredImages.push( ...(await restoreDraftImages(galleryImages, loadedMedia)), @@ -644,7 +646,7 @@ export async function draftToComposerPosts( * Convert server threadgate rules back to UI settings. */ export function threadgateToUISettings( - threadgateAllow?: AppBskyDraftDefs.Draft['threadgateAllow'], + threadgateAllow?: app.bsky.draft.defs.Draft['threadgateAllow'], ): Array<{type: string; list?: string}> { if (!threadgateAllow) { return [] @@ -678,7 +680,9 @@ export function threadgateToUISettings( * Extract all localRef paths from a draft. * Used to identify which media files belong to a draft for cleanup. */ -export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { +export function extractLocalRefs( + draft: app.bsky.draft.defs.Draft, +): Set { const refs = new Set() for (const post of draft.posts) { if (post.embedImages) { @@ -688,7 +692,7 @@ export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue refs.add(item.localRef.path) } } diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts index a620f0ec0f..fa2d11771a 100644 --- a/src/view/com/composer/drafts/state/queries.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -1,10 +1,10 @@ -import {AppBskyDraftDefs} from '@atproto/api' import { useInfiniteQuery, useMutation, useQueryClient, } from '@tanstack/react-query' +import * as bsky from '#/types/bsky' import {isNetworkError} from '#/lib/strings/errors' import {matchXrpcError} from '#/lib/xrpc-error' import {useAppviewClient, useChatClient} from '#/state/session' @@ -52,7 +52,9 @@ export function useDraftsQuery() { * Load a draft's local media for editing. * Takes the full Draft object (from DraftSummary) to avoid re-fetching. */ -export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ +export async function loadDraftMedia( + draft: app.bsky.draft.defs.Draft, +): Promise<{ loadedMedia: Map }> { // Load local media files @@ -81,7 +83,7 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ // Load gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue try { const url = await storage.loadMediaFromLocal(item.localRef.path) loadedMedia.set(item.localRef.path, url) @@ -242,7 +244,7 @@ export function useDeleteDraftMutation() { draftId, }: { draftId: string - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft }) => { // Delete from server first - if this fails, we keep local media for retry await client.call(app.bsky.draft.deleteDraft, {id: draftId}) @@ -257,7 +259,8 @@ export function useDeleteDraftMutation() { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) + continue await storage.deleteMediaFromLocal(item.localRef.path) } } diff --git a/src/view/com/composer/drafts/state/schema.ts b/src/view/com/composer/drafts/state/schema.ts index 9f88aa07da..5fd5bbb861 100644 --- a/src/view/com/composer/drafts/state/schema.ts +++ b/src/view/com/composer/drafts/state/schema.ts @@ -1,8 +1,8 @@ +import {app} from '#/lexicons' /** * Types for draft display and local media tracking. * Server draft types come from @atproto/api. */ -import {type AppBskyDraftDefs} from '@atproto/api' /** * Reference to locally cached media file for display @@ -55,7 +55,7 @@ export type DraftSummary = { /** ISO timestamp of last update */ updatedAt: string /** The full draft data from the server */ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft /** All posts in the draft for full display */ posts: DraftPostDisplay[] /** Metadata about the draft for display purposes */ diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts index 34e6b69508..45a097ff83 100644 --- a/src/view/com/composer/state/composer.ts +++ b/src/view/com/composer/state/composer.ts @@ -1,5 +1,4 @@ import {type ImagePickerAsset} from 'expo-image-picker' -import {type AppBskyActorDefs, type AppBskyDraftDefs} from '@atproto/api' import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' @@ -141,8 +140,8 @@ export type ComposerAction = type: 'restore_from_draft' draftId: string posts: PostDraft[] - threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] - postgateEmbeddingRules: AppBskyDraftDefs.Draft['postgateEmbeddingRules'] + threadgateAllow: app.bsky.draft.defs.Draft['threadgateAllow'] + postgateEmbeddingRules: app.bsky.draft.defs.Draft['postgateEmbeddingRules'] /** Map of localRefPath -> loaded media path/URL */ loadedMedia: Map @@ -152,7 +151,7 @@ export type ComposerAction = | { type: 'clear' initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined } | { @@ -632,7 +631,7 @@ export function createComposerState({ initImageUris: ComposerOpts['imageUris'] initQuoteUri: string | undefined initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined }): ComposerState { let media: ImagesMedia | GalleryMedia | undefined diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index 9621ebd82c..b5a1e920ba 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,8 +1,8 @@ import {View} from 'react-native' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' +import {app} from '#/lexicons' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' @@ -70,7 +70,7 @@ function AutocompleteProfileCard({ totalItems, onPress, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic itemIndex: number totalItems: number onPress: () => void diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 267d4a4829..3ebc9fd235 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,6 +1,5 @@ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' import {ReactRenderer} from '@tiptap/react' @@ -11,6 +10,7 @@ import { } from '@tiptap/suggestion' import tippy, {type Instance as TippyInstance} from 'tippy.js' +import {app} from '#/lexicons' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {atoms as a, useTheme} from '#/alf' @@ -205,7 +205,7 @@ function AutocompleteProfileCard({ onHover, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic isSelected: boolean onPress: () => void onHover: () => void diff --git a/src/view/com/composer/text-input/web/LinkDecorator.ts b/src/view/com/composer/text-input/web/LinkDecorator.ts index 4843f0ddfc..bcb81b2cf5 100644 --- a/src/view/com/composer/text-input/web/LinkDecorator.ts +++ b/src/view/com/composer/text-input/web/LinkDecorator.ts @@ -14,7 +14,7 @@ * the facet-set. */ -import {URL_REGEX} from '@atproto/api' +import {URL_REGEX} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/composer/text-input/web/TagDecorator.ts b/src/view/com/composer/text-input/web/TagDecorator.ts index 8f1142b86c..34f6396abb 100644 --- a/src/view/com/composer/text-input/web/TagDecorator.ts +++ b/src/view/com/composer/text-input/web/TagDecorator.ts @@ -18,7 +18,7 @@ import { CASHTAG_REGEX, TAG_REGEX, TRAILING_PUNCTUATION_REGEX, -} from '@atproto/api' +} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 331c62e0d1..55c4d08c9e 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -7,12 +7,12 @@ import { useState, } from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {type NavigationProp, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {app} from '#/lexicons' import {DISCOVER_FEED_URI, VIDEO_FEED_URIS} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers' @@ -59,7 +59,7 @@ export function FeedPage({ isPageAdjacent: boolean renderEmptyState: () => JSX.Element renderEndOfFeed?: () => JSX.Element - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed feedInfo: FeedSourceInfo }) { const ax = useAnalytics() @@ -77,7 +77,7 @@ export function FeedPage({ const isVideoFeed = useMemo(() => { const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) const feedIsVideoMode = - feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO + feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode return IS_NATIVE && _isVideoFeed }, [feedInfo]) diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index ee62392556..0684459d69 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -1,14 +1,12 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type $Typed, - AppBskyFeedDefs, - type AppBskyGraphDefs, - AtUri, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' +import * as bsky from '#/types/bsky' +import {app} from '#/lexicons' import {sanitizeHandle} from '#/lib/strings/handles' import { type FeedSourceInfo, @@ -27,8 +25,8 @@ import {MissingFeed} from './MissingFeed' type FeedSourceCardProps = { feedUri: string feedData?: - | $Typed - | $Typed + | $Typed + | $Typed style?: StyleProp showSaveBtn?: boolean showDescription?: boolean @@ -46,7 +44,7 @@ export function FeedSourceCard({ }: FeedSourceCardProps) { if (feedData) { let feed: FeedSourceInfo - if (AppBskyFeedDefs.isGeneratorView(feedData)) { + if (bsky.isType(app.bsky.feed.defs.generatorView, feedData)) { feed = hydrateFeedGenerator(feedData) } else { feed = hydrateList(feedData) diff --git a/src/view/com/feeds/MissingFeed.tsx b/src/view/com/feeds/MissingFeed.tsx index bc2af31077..a6c2b34028 100644 --- a/src/view/com/feeds/MissingFeed.tsx +++ b/src/view/com/feeds/MissingFeed.tsx @@ -1,9 +1,9 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {AtUri} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' +import {AtUri} from '@atproto/syntax' import {cleanError} from '#/lib/strings/errors' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {getFeedTypeFromUri} from '#/state/queries/feed' diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 04876eefb2..b96ead838d 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -6,11 +6,11 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' +import {app} from '#/lexicons' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -43,7 +43,7 @@ type Item = | typeof LOAD_MORE_ERROR_ITEM | { kind: 'list_item' - listItem: AppBskyGraphDefs.ListItemView + listItem: app.bsky.graph.defs.ListItemView } export function ListMembers({ diff --git a/src/view/com/lists/MyLists.tsx b/src/view/com/lists/MyLists.tsx index 0ec9828102..3cbb0d2258 100644 --- a/src/view/com/lists/MyLists.tsx +++ b/src/view/com/lists/MyLists.tsx @@ -7,10 +7,10 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {app} from '#/lexicons' import {usePalette} from '#/lib/hooks/usePalette' import {cleanError} from '#/lib/strings/errors' import {s} from '#/lib/styles' @@ -38,7 +38,10 @@ export function MyLists({ filter: MyListsFilter inline?: boolean style?: StyleProp - renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element + renderItem?: ( + list: app.bsky.graph.defs.ListView, + index: number, + ) => JSX.Element testID?: string }) { const pal = usePalette('default') diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index a10e4b47ac..e15fd71572 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -8,17 +8,8 @@ import { TouchableOpacity, View, } from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyGraphDefs, - AppBskyGraphFollow, - AppBskyGraphStarterpack, - AtUri, -} from '@atproto/api' import {TID} from '@atproto/common-web' -import {type DidString} from '@atproto/syntax' +import {type DidString, AtUri} from '@atproto/syntax' import { type ModerationDecision, type ModerationOpts, @@ -76,13 +67,13 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' -import {chat} from '#/lexicons' +import {app, chat} from '#/lexicons' import * as bsky from '#/types/bsky' const MAX_AUTHORS = 5 interface Author { - profile: AppBskyActorDefs.ProfileView + profile: app.bsky.actor.defs.ProfileView href: string moderation: ModerationDecision } @@ -195,10 +186,7 @@ let NotificationFeedItem = ({ if (item.type !== 'follow') return false if ( item.notification.author.viewer?.following && - bsky.dangerousIsType( - item.notification.record, - AppBskyGraphFollow.isRecord, - ) + bsky.isType(app.bsky.graph.follow, item.notification.record) ) { let followingTimestamp try { @@ -734,7 +722,7 @@ export {NotificationFeedItem} function FollowedViaStarterPack({ starterPack, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: app.bsky.graph.defs.StarterPackViewBasic }) { const t = useTheme() const link = useStarterPackLink({view: starterPack}) @@ -768,12 +756,9 @@ function FollowedViaStarterPack({ } function getStarterPackName( - starterPack: AppBskyGraphDefs.StarterPackViewBasic, + starterPack: app.bsky.graph.defs.StarterPackViewBasic, ) { - return bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) + return bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? starterPack.record.name : undefined } @@ -807,7 +792,11 @@ function ExpandListPressable({ } } -function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function FollowBackButton({ + profile, +}: { + profile: app.bsky.actor.defs.ProfileView +}) { const {t: l} = useLingui() const {currentAccount, hasSession} = useSession() const profileShadow = useProfileShadow(profile) @@ -913,7 +902,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { ) } -function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function SayHelloBtn({profile}: {profile: app.bsky.actor.defs.ProfileView}) { const {t: l} = useLingui() const client = useChatClient() const {currentAccount} = useSession() @@ -1147,15 +1136,9 @@ function ExpandedAuthorProfileCard({ ) } -function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { +function AdditionalPostText({post}: {post?: app.bsky.feed.defs.PostView}) { const t = useTheme() - if ( - post && - bsky.dangerousIsType( - post?.record, - AppBskyFeedPost.isRecord, - ) - ) { + if (post && bsky.isType(app.bsky.feed.post, post?.record)) { const text = post.record.text return ( diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index 48669d99ce..24305ba22b 100644 --- a/src/view/com/post-thread/PostLikedBy.tsx +++ b/src/view/com/post-thread/PostLikedBy.tsx @@ -1,8 +1,8 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {app} from '#/lexicons' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -12,7 +12,13 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' -function renderItem({item, index}: {item: GetLikes.Like; index: number}) { +function renderItem({ + item, + index, +}: { + item: app.bsky.feed.getLikes.Like + index: number +}) { return ( page.posts.map(post => { if ( - !bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) || + !bsky.isType(app.bsky.feed.post, post.record) || !moderationOpts ) { return null diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 4c2db9eda0..bbc8537ac3 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -1,8 +1,8 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {app} from '#/lexicons' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -16,7 +16,7 @@ function renderItem({ item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) { return ( @@ -28,7 +28,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 98f6cfb9a0..025a18409d 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -1,10 +1,11 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useQueryClient} from '@tanstack/react-query' +import {AtUri} from '@atproto/syntax' +import {app} from '#/lexicons' import {MAX_POST_LINES} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {moderatePost} from '#/lib/moderation/subjects' @@ -45,18 +46,16 @@ export function Post({ style, onBeforePress, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView showReplyLine?: boolean hideTopBorder?: boolean style?: StyleProp onBeforePress?: () => void }) { const moderationOpts = useModerationOpts() - const record = useMemo( + const record = useMemo( () => - bsky.validate(post.record, AppBskyFeedPost.validateRecord) - ? post.record - : undefined, + bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], ) const postShadowed = usePostShadow(post) @@ -106,8 +105,8 @@ function PostInner({ style, onBeforePress: outerOnBeforePress, }: { - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI moderation: ModerationDecision showReplyLine?: boolean diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 2ccf3872dc..a4e599a64d 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -18,18 +18,12 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyActorDefs, - AppBskyEmbedExternal, - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedVideo, - type AppBskyFeedDefs, -} from '@atproto/api' import {type RichText as RichTextType} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' +import * as bsky from '#/types/bsky' +import {app} from '#/lexicons' import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' @@ -256,7 +250,7 @@ let PostFeed = ({ desktopFixedHeightOffset?: number ListHeaderComponent?: () => React.ReactElement extraData?: Record - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed initialNumToRender?: number isVideoFeed?: boolean lastFetchDate?: () => number @@ -290,7 +284,7 @@ let PostFeed = ({ () => new Set(), ) const onPressShowLess = useCallback( - (interaction: AppBskyFeedDefs.Interaction) => { + (interaction: app.bsky.feed.defs.Interaction) => { if (interaction.item) { const uri = interaction.item setHasPressedShowLessUris(prev => new Set([...prev, uri])) @@ -492,7 +486,7 @@ let PostFeed = ({ ) if ( item && - AppBskyEmbedVideo.isView(item.post.embed) && + bsky.isType(app.bsky.embed.video.view, item.post.embed) && !blockedOrMutedAuthors.includes(item.post.author.did) ) { videos.push({ @@ -1030,13 +1024,13 @@ let PostFeed = ({ // Events that should fire exactly once for every new post, regardless of // its position within a slice or video grid row. - const onPostSeen = (post: AppBskyFeedDefs.PostView) => { + const onPostSeen = (post: app.bsky.feed.defs.PostView) => { if (seenPerPostUrisRef.current.has(post.uri)) return seenPerPostUrisRef.current.add(post.uri) // Standard site embed view tracking if ( - AppBskyEmbedExternal.isView(post.embed) && + bsky.isType(app.bsky.embed.external.view, post.embed) && isStandardSiteEmbed(post.embed.external) ) { ax.metric('embed:standardSite:view', {url: post.embed.external.uri}) @@ -1044,13 +1038,21 @@ let PostFeed = ({ // Photo embed impression tracking if ( - AppBskyEmbedImages.isView(post.embed) || - AppBskyEmbedGallery.isView(post.embed) + bsky.isType(app.bsky.embed.images.view, post.embed) || + bsky.isType(app.bsky.embed.gallery.view, post.embed) ) { - const totalImages = AppBskyEmbedGallery.isView(post.embed) - ? post.embed.items.filter(AppBskyEmbedGallery.isViewImage).length + const totalImages = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) + ? post.embed.items.filter(item => + bsky.isType(app.bsky.embed.gallery.viewImage, item), + ).length : post.embed.images.length - const useExpandedLayout = AppBskyEmbedGallery.isView(post.embed) + const useExpandedLayout = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) ? totalImages > 4 : ax.features.enabled(ax.features.PostGalleryEmbedEnable) const layout = diff --git a/src/view/com/posts/PostFeedErrorMessage.tsx b/src/view/com/posts/PostFeedErrorMessage.tsx index 0298ce228b..135fcc7074 100644 --- a/src/view/com/posts/PostFeedErrorMessage.tsx +++ b/src/view/com/posts/PostFeedErrorMessage.tsx @@ -1,15 +1,12 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedGetAuthorFeed, - AtUri, -} from '@atproto/api' import {msg as msgLingui} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' +import {AtUri} from '@atproto/syntax' +import {app} from '#/lexicons' import {usePalette} from '#/lib/hooks/usePalette' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' @@ -45,7 +42,7 @@ export function PostFeedErrorMessage({ feedDesc: FeedDescriptor error?: Error onPressTryAgain: () => void - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const {_: _l} = useLingui() const knownError = useMemo( @@ -96,7 +93,7 @@ function FeedgenErrorMessage({ feedDesc: FeedDescriptor knownError: KnownError rawError?: Error - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const pal = usePalette('default') const {_: _l} = useLingui() @@ -242,8 +239,8 @@ function detectKnownError( return undefined } if ( - error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError || - error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError + error instanceof app.bsky.feed.getAuthorFeed.BlockedActorError || + error instanceof app.bsky.feed.getAuthorFeed.BlockedByActorError ) { return KnownError.Block } diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index aa3fd06976..43679fd39c 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -1,16 +1,11 @@ import {memo, useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyFeedPost, - AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useQueryClient} from '@tanstack/react-query' +import {AtUri} from '@atproto/syntax' +import {app} from '#/lexicons' import {type ReasonFeedSource} from '#/lib/api/feed/types' import {MAX_POST_LINES} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' @@ -58,15 +53,15 @@ import * as bsky from '#/types/bsky' import {PostFeedReason} from './PostFeedReason' interface FeedItemProps { - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main reason: - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | ReasonFeedSource | {[k: string]: unknown; $type: string} | undefined moderation: ModerationDecision - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined showReplyTo: boolean isThreadChild?: boolean isThreadLastChild?: boolean @@ -96,9 +91,9 @@ export function PostFeedItem({ rootPost, onShowLess, }: FeedItemProps & { - post: AppBskyFeedDefs.PostView - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: app.bsky.feed.defs.PostView + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode { const postShadowed = usePostShadow(post) const richText = useMemo( @@ -160,9 +155,9 @@ let FeedItemInner = ({ onShowLess, }: FeedItemProps & { richText: RichTextAPI - post: Shadow - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: Shadow + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode => { const ax = useAnalytics() const queryClient = useQueryClient() @@ -258,7 +253,9 @@ let FeedItemInner = ({ feedSourceInfo, post: { post, - reason: AppBskyFeedDefs.isReasonRepost(reason) ? reason : undefined, + reason: bsky.isType(app.bsky.feed.defs.reasonRepost, reason) + ? reason + : undefined, feedContext, reqId, }, @@ -282,9 +279,9 @@ let FeedItemInner = ({ * If `post[0]` in this slice is the actual root post (not an orphan thread), * then we may have a threadgate record to reference */ - const threadgateRecord = bsky.dangerousIsType( + const threadgateRecord = bsky.isType( + app.bsky.feed.threadgate, rootPost.threadgate?.record, - AppBskyFeedThreadgate.isRecord, ) ? rootPost.threadgate.record : undefined @@ -292,7 +289,11 @@ let FeedItemInner = ({ const {isActive: live} = useActorStatus(post.author) const viaRepost = useMemo(() => { - if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { + if ( + bsky.isType(app.bsky.feed.defs.reasonRepost, reason) && + reason.uri && + reason.cid + ) { return { uri: reason.uri, cid: reason.cid, @@ -305,10 +306,7 @@ let FeedItemInner = ({ }) const additionalPostAlerts: AppModerationCause[] = useMemo(() => { const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) - const rootPostUri = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const rootPostUri = bsky.isType(app.bsky.feed.post, post.record) ? post.record?.reply?.root?.uri || post.uri : undefined const isControlledByViewer = @@ -465,10 +463,10 @@ let PostContent = ({ }: { moderation: ModerationDecision richText: RichTextAPI - postEmbed: AppBskyFeedDefs.PostView['embed'] - postAuthor: AppBskyFeedDefs.PostView['author'] + postEmbed: app.bsky.feed.defs.PostView['embed'] + postAuthor: app.bsky.feed.defs.PostView['author'] onOpenEmbed: () => void - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView additionalPostAlerts?: AppModerationCause[] feedDescriptor?: string }): React.ReactNode => { @@ -476,11 +474,9 @@ let PostContent = ({ () => countLines(richText.text) >= MAX_POST_LINES, ) - const record = useMemo( + const record = useMemo( () => - bsky.validate(post.record, AppBskyFeedPost.validateRecord) - ? post.record - : undefined, + bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], ) diff --git a/src/view/com/posts/PostFeedReason.tsx b/src/view/com/posts/PostFeedReason.tsx index 57ac3b825c..1355c13088 100644 --- a/src/view/com/posts/PostFeedReason.tsx +++ b/src/view/com/posts/PostFeedReason.tsx @@ -1,10 +1,11 @@ import {StyleSheet, View} from 'react-native' -import {AppBskyFeedDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' +import * as bsky from '#/types/bsky' +import {app} from '#/lexicons' import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {makeProfileLink} from '#/lib/routes/links' @@ -24,8 +25,8 @@ export function PostFeedReason({ }: { reason: | ReasonFeedSource - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | {[k: string]: unknown; $type: string} moderation?: ModerationDecision onOpenReposter?: () => void @@ -64,7 +65,7 @@ export function PostFeedReason({ ) } - if (AppBskyFeedDefs.isReasonRepost(reason)) { + if (bsky.isType(app.bsky.feed.defs.reasonRepost, reason)) { const isOwner = reason.by.did === currentAccount?.did const reposter = createSanitizedDisplayName( reason.by, @@ -103,7 +104,7 @@ export function PostFeedReason({ ) } - if (AppBskyFeedDefs.isReasonPin(reason)) { + if (bsky.isType(app.bsky.feed.defs.reasonPin, reason)) { return ( + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -160,7 +160,7 @@ export function ProfileFollowers({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index b72161187c..505febc561 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -1,8 +1,8 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' +import {app} from '#/lexicons' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' @@ -23,7 +23,7 @@ function renderItem({ index, contextProfileDid, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number contextProfileDid: string | undefined }) { @@ -38,7 +38,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } @@ -143,7 +143,7 @@ export function ProfileFollows({name}: {name: string}) { }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) const renderItemWithContext = useCallback( - ({item, index}: {item: ActorDefs.ProfileView; index: number}) => + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -165,7 +165,7 @@ export function ProfileFollows({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 20446dd30c..82fbb44193 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -1,9 +1,9 @@ import {memo, useCallback, useMemo} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {app} from '#/lexicons' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {shareText, shareUrl} from '#/lib/sharing' @@ -71,7 +71,7 @@ import {useDevMode} from '#/storage/hooks/dev-mode' let ProfileMenu = ({ profile, }: { - profile: Shadow + profile: Shadow }): React.ReactNode => { const t = useTheme() const ax = useAnalytics() diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 24ec5aa2b3..de7a38514f 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,12 +1,12 @@ import {useCallback} from 'react' import {Pressable, View} from 'react-native' import Animated, {useAnimatedRef} from 'react-native-reanimated' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' +import {app} from '#/lexicons' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {makeProfileLink} from '#/lib/routes/links' @@ -37,7 +37,7 @@ export function ProfileSubpageHeader({ title: string | undefined avatar: string | undefined isOwner: boolean | undefined - purpose: AppBskyGraphDefs.ListPurpose | undefined + purpose: app.bsky.graph.defs.ListPurpose | undefined creator: | { did: string diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index f50e14b18f..49e34315df 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,11 +1,11 @@ import {memo, useCallback} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' +import {app} from '#/lexicons' import {makeProfileLink} from '#/lib/routes/links' import {forceLTR} from '#/lib/strings/bidi' import {NON_BREAKING_SPACE} from '#/lib/strings/constants' @@ -25,7 +25,7 @@ import {TimeElapsed} from './TimeElapsed' import {PreviewableUserAvatar} from './UserAvatar' interface PostMetaOpts { - author: AppBskyActorDefs.ProfileViewBasic + author: app.bsky.actor.defs.ProfileViewBasic moderation: ModerationDecision | undefined postHref: string timestamp: string diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 028b85d38c..904d94b14d 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,6 +1,6 @@ import {type StyleProp, type TextStyle} from 'react-native' -import {type AppBskyActorGetProfile} from '@atproto/api' +import {app} from '#/lexicons' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' @@ -19,7 +19,7 @@ export function UserInfoText({ style, }: { did: string - attr?: keyof AppBskyActorGetProfile.OutputSchema + attr?: keyof app.bsky.actor.getProfile.$OutputBody loading?: string failed?: string prefix?: string diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index ad0124eb49..c58edac3c9 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -1,17 +1,12 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' import {useSharedValue} from 'react-native-reanimated' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyFeedPost, - type ComAtprotoLabelDefs, - mock, -} from '@atproto/api' import { interpretLabelValueDefinition, type LabelPreference, LABELS, + moderatePost, + moderateProfile, type ModerationBehavior, type ModerationDecision, type ModerationOpts, @@ -20,7 +15,6 @@ import {RichText} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' -import {moderatePost, moderateProfile} from '#/lib/moderation/subjects' import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import { type CommonNavigatorParams, @@ -54,6 +48,7 @@ import { import * as Layout from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {H1, H3, P, Text} from '#/components/Typography' +import {type app, type com} from '#/lexicons' import {ScreenHider} from '../../components/moderation/ScreenHider' import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem' import {PagerHeaderProvider} from '../com/pager/PagerHeaderContext' @@ -63,6 +58,211 @@ const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( LABELS, ) as (keyof typeof LABELS)[] +const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq' + +/* + * Local test-data builders for this dev-only moderation debug screen. These + * replace the `mock` object the old api package used to export (the SDK does + * not ship one). Each builder returns a plain `#/lexicons` object literal with + * the same field values the old `mock` builders produced. Branded string slots + * (`did`/`at-uri`/`cid`/`lang`) are cast, since this is trusted mock data. + */ +const mock = { + post({ + text, + facets, + reply, + embed, + }: { + text: string + facets?: app.bsky.feed.post.Main['facets'] + reply?: app.bsky.feed.post.Main['reply'] + embed?: app.bsky.feed.post.Main['embed'] + }): app.bsky.feed.post.Main { + return { + $type: 'app.bsky.feed.post', + text, + facets, + reply, + embed, + langs: ['en'], + createdAt: + new Date().toISOString() as app.bsky.feed.post.Main['createdAt'], + } + }, + postView({ + record, + author, + embed, + replyCount, + repostCount, + likeCount, + viewer, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + embed?: app.bsky.feed.defs.PostView['embed'] + replyCount?: number + repostCount?: number + likeCount?: number + viewer?: app.bsky.feed.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.feed.defs.PostView { + return { + $type: 'app.bsky.feed.defs#postView', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + record, + embed, + replyCount, + repostCount, + likeCount, + indexedAt: + new Date().toISOString() as app.bsky.feed.defs.PostView['indexedAt'], + viewer, + labels, + } + }, + embedRecordView({ + record, + author, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.embed.record.View { + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewRecord', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + value: record, + labels, + indexedAt: + new Date().toISOString() as app.bsky.embed.record.ViewRecord['indexedAt'], + }, + } + }, + profileViewBasic({ + handle, + displayName, + description, + viewer, + labels, + }: { + handle: string + displayName?: string + description?: string + viewer?: app.bsky.actor.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.actor.defs.ProfileViewBasic & {description?: string} { + return { + did: `did:web:${handle}`, + handle: handle as app.bsky.actor.defs.ProfileViewBasic['handle'], + displayName, + description, + viewer, + labels, + } + }, + actorViewerState({ + muted, + mutedByList, + blockedBy, + blocking, + blockingByList, + following, + followedBy, + }: { + muted?: boolean + mutedByList?: app.bsky.graph.defs.ListViewBasic + blockedBy?: boolean + blocking?: string + blockingByList?: app.bsky.graph.defs.ListViewBasic + following?: string + followedBy?: string + }): app.bsky.actor.defs.ViewerState { + return { + muted, + mutedByList, + blockedBy, + blocking: blocking as app.bsky.actor.defs.ViewerState['blocking'], + blockingByList, + following: following as app.bsky.actor.defs.ViewerState['following'], + followedBy: followedBy as app.bsky.actor.defs.ViewerState['followedBy'], + } + }, + replyNotification({ + author, + record, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'reply', + reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`, + record, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + followNotification({ + author, + subjectDid, + labels, + }: { + author: app.bsky.actor.defs.ProfileViewBasic + subjectDid: string + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.graph.follow/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'follow', + record: { + $type: 'app.bsky.graph.follow', + createdAt: new Date().toISOString(), + subject: subjectDid, + }, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + label({ + val, + uri, + src, + }: { + val: string + uri: string + src?: string + }): com.atproto.label.defs.Label { + return { + src: (src || + 'did:plc:fake-labeler') as com.atproto.label.defs.Label['src'], + uri: uri as com.atproto.label.defs.Label['uri'], + val, + cts: new Date().toISOString() as com.atproto.label.defs.Label['cts'], + } + }, +} + export const DebugModScreen = ({}: NativeStackScreenProps< CommonNavigatorParams, 'DebugMod' @@ -74,7 +274,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< const [target, setTarget] = useState(['account']) const [visibility, setVisiblity] = useState(['warn']) const [customLabelDef, setCustomLabelDef] = - useState({ + useState({ identifier: 'custom', blurs: 'content', severity: 'alert', @@ -141,7 +341,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< blockingByList: undefined, }), }) - mockedProfile.did = did + mockedProfile.did = did as app.bsky.actor.defs.ProfileViewBasic['did'] mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' // @ts-expect-error ProfileViewBasic is close enough -esb mockedProfile.banner = @@ -165,36 +365,35 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }), ] : undefined, - embed: - target[0] === 'embed' - ? mock.embedRecordView({ - record: mock.post({ - text: 'Embed', - }), - labels: - scenario[0] === 'label' && target[0] === 'embed' - ? [ - mock.label({ - src: isSelfLabel ? did : undefined, - val: label[0], - uri: `at://${did}/app.bsky.feed.post/fake`, - }), - ] - : undefined, - author: profile, - }) - : { - $type: 'app.bsky.embed.images#view', - images: [ - { - thumb: - 'https://bsky.social/about/images/social-card-default-gradient.png', - fullsize: - 'https://bsky.social/about/images/social-card-default-gradient.png', - alt: '', - }, - ], - }, + embed: (target[0] === 'embed' + ? mock.embedRecordView({ + record: mock.post({ + text: 'Embed', + }), + labels: + scenario[0] === 'label' && target[0] === 'embed' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + author: profile, + }) + : { + $type: 'app.bsky.embed.images#view', + images: [ + { + thumb: + 'https://bsky.social/about/images/social-card-default-gradient.png', + fullsize: + 'https://bsky.social/about/images/social-card-default-gradient.png', + alt: '', + }, + ], + }) as app.bsky.feed.defs.PostView['embed'], }) }, [scenario, label, target, profile, isSelfLabel, did]) @@ -227,7 +426,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }) const [item] = groupNotifications([notif]) item.subject = mock.postView({ - record: notif.record as AppBskyFeedPost.Record, + record: notif.record as app.bsky.feed.post.Main, author: profile, labels: notif.labels, }) @@ -634,9 +833,9 @@ function CustomLabelForm({ def, setDef, }: { - def: ComAtprotoLabelDefs.LabelValueDefinition + def: com.atproto.label.defs.LabelValueDefinition setDef: React.Dispatch< - React.SetStateAction + React.SetStateAction > }) { const t = useTheme() @@ -838,7 +1037,7 @@ function MockPostFeedItem({ post, moderation, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView moderation: ModerationDecision }) { const t = useTheme() @@ -852,7 +1051,7 @@ function MockPostFeedItem({ return ( { if (!moderationOpts) return null @@ -113,7 +113,7 @@ export function ModerationBlockedAccounts({}: Props) { ) : ( item.did} + keyExtractor={(item: app.bsky.actor.defs.ProfileView) => item.did} refreshing={isPTRing} onRefresh={onRefresh} onEndReached={onEndReached} diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx index 4ef555a6c2..8db28327e7 100644 --- a/src/view/screens/ModerationModlists.tsx +++ b/src/view/screens/ModerationModlists.tsx @@ -1,10 +1,10 @@ import {useCallback} from 'react' -import {AtUri} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' +import {AtUri} from '@atproto/syntax' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import { type CommonNavigatorParams, diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 122464d301..db3ebe9e85 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -1,9 +1,9 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' +import {app} from '#/lexicons' import {type CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -68,7 +68,7 @@ export function ModerationMutedAccounts({}: Props) { item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) => { if (!moderationOpts) return null diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 9c4db67b01..4f7be3e0e2 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -2,7 +2,6 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' import {ScrollForwarderView} from 'react-native-scroll-forwarder' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' @@ -11,6 +10,7 @@ import {Trans} from '@lingui/react/macro' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {app} from '#/lexicons' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {useSetTitle} from '#/lib/hooks/useSetTitle' @@ -166,7 +166,7 @@ function ProfileScreenLoaded({ moderationOpts, hideBackButton, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed moderationOpts: ModerationOpts hideBackButton: boolean isPlaceholderProfile: boolean diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index d777b09e86..2a03b932d0 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,10 +1,10 @@ import {useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation, useNavigationState} from '@react-navigation/native' +import {app} from '#/lexicons' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {getCurrentRoute, isTab} from '#/lib/routes/helpers' @@ -239,7 +239,7 @@ function SwitchMenuItems({ accounts: | { account: SessionAccount - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed }[] | undefined signOutPromptControl: DialogControlProps @@ -350,7 +350,7 @@ function SwitchMenuItem({ profile, }: { account: SessionAccount - profile: AppBskyActorDefs.ProfileViewDetailed | undefined + profile: app.bsky.actor.defs.ProfileViewDetailed | undefined }) { const {t: l} = useLingui() const {onPressSwitchAccount, pendingDid} = useAccountSwitcher()