Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at feat/tealfm 167 lines 4.5 kB view raw
1import React from 'react' 2import { 3 type AppBskyActorDefs, 4 type AppBskyFeedDefs, 5 type AppBskyUnspeccedGetPostThreadV2, 6 type BlobRef, 7 type ModerationDecision, 8} from '@atproto/api' 9import {msg} from '@lingui/macro' 10import {useLingui} from '@lingui/react' 11import {useQueryClient} from '@tanstack/react-query' 12 13import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 14import {postUriToRelativePath, toBskyAppUrl} from '#/lib/strings/url-helpers' 15import {purgeTemporaryImageFiles} from '#/state/gallery' 16import {precacheResolveLinkQuery} from '#/state/queries/resolve-link' 17import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker' 18import * as Toast from '#/view/com/util/Toast' 19 20export interface ComposerOptsPostRef { 21 uri: string 22 cid: string 23 text: string 24 langs?: string[] 25 author: AppBskyActorDefs.ProfileViewBasic 26 embed?: AppBskyFeedDefs.PostView['embed'] 27 moderation?: ModerationDecision 28} 29 30export type OnPostSuccessData = 31 | { 32 replyToUri?: string 33 posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[] 34 } 35 | undefined 36 37export interface ComposerOpts { 38 replyTo?: ComposerOptsPostRef 39 onPost?: (postUri: string | undefined) => void 40 onPostSuccess?: (data: OnPostSuccessData) => void 41 quote?: AppBskyFeedDefs.PostView 42 mention?: string // handle of user to mention 43 openEmojiPicker?: (pos: EmojiPickerPosition | undefined) => void 44 text?: string 45 imageUris?: { 46 uri: string 47 width: number 48 height: number 49 altText?: string 50 blobRef?: BlobRef 51 }[] 52 videoUri?: { 53 uri: string 54 width: number 55 height: number 56 blobRef?: BlobRef 57 altText?: string 58 } 59 openGallery?: boolean 60} 61 62type StateContext = ComposerOpts | undefined 63type ControlsContext = { 64 openComposer: (opts: ComposerOpts) => void 65 closeComposer: () => boolean 66} 67 68const stateContext = React.createContext<StateContext>(undefined) 69stateContext.displayName = 'ComposerStateContext' 70const controlsContext = React.createContext<ControlsContext>({ 71 openComposer(_opts: ComposerOpts) {}, 72 closeComposer() { 73 return false 74 }, 75}) 76controlsContext.displayName = 'ComposerControlsContext' 77 78export function Provider({children}: React.PropsWithChildren<{}>) { 79 const {_} = useLingui() 80 const [state, setState] = React.useState<StateContext>() 81 const queryClient = useQueryClient() 82 83 const openComposer = useNonReactiveCallback((opts: ComposerOpts) => { 84 if (opts.quote) { 85 const path = postUriToRelativePath(opts.quote.uri) 86 if (path) { 87 const appUrl = toBskyAppUrl(path) 88 precacheResolveLinkQuery(queryClient, appUrl, { 89 type: 'record', 90 kind: 'post', 91 record: { 92 cid: opts.quote.cid, 93 uri: opts.quote.uri, 94 }, 95 view: opts.quote, 96 }) 97 } 98 } 99 const author = opts.replyTo?.author || opts.quote?.author 100 const isBlocked = Boolean( 101 author && 102 (author.viewer?.blocking || 103 author.viewer?.blockedBy || 104 author.viewer?.blockingByList), 105 ) 106 if (isBlocked) { 107 Toast.show( 108 _(msg`Cannot interact with a blocked user`), 109 'exclamation-circle', 110 ) 111 } else { 112 setState(prevOpts => { 113 if (prevOpts) { 114 // Never replace an already open composer. 115 return prevOpts 116 } 117 return opts 118 }) 119 } 120 }) 121 122 const closeComposer = useNonReactiveCallback(() => { 123 let wasOpen = !!state 124 if (wasOpen) { 125 setState(undefined) 126 purgeTemporaryImageFiles() 127 } 128 129 return wasOpen 130 }) 131 132 const api = React.useMemo( 133 () => ({ 134 openComposer, 135 closeComposer, 136 }), 137 [openComposer, closeComposer], 138 ) 139 140 return ( 141 <stateContext.Provider value={state}> 142 <controlsContext.Provider value={api}> 143 {children} 144 </controlsContext.Provider> 145 </stateContext.Provider> 146 ) 147} 148 149export function useComposerState() { 150 return React.useContext(stateContext) 151} 152 153export function useComposerControls() { 154 const {closeComposer} = React.useContext(controlsContext) 155 return React.useMemo(() => ({closeComposer}), [closeComposer]) 156} 157 158/** 159 * DO NOT USE DIRECTLY. The deprecation notice as a warning only, it's not 160 * actually deprecated. 161 * 162 * @deprecated use `#/lib/hooks/useOpenComposer` instead 163 */ 164export function useOpenComposer() { 165 const {openComposer} = React.useContext(controlsContext) 166 return React.useMemo(() => ({openComposer}), [openComposer]) 167}