diff options
author | Eric Bailey <git@esb.lol> | 2024-09-13 16:48:28 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-13 16:48:28 -0500 |
commit | d76f9abdd718e24848a9b8f67486129aee421427 (patch) | |
tree | e5574c807eea6010746006a234e3a79dc0684552 /src/state/shell/composer/index.tsx | |
parent | cac43127f0163c84a921afd806d91e1df10ea568 (diff) | |
download | voidsky-d76f9abdd718e24848a9b8f67486129aee421427.tar.zst |
"N" keyboard shortcut to open a new post modal (#5197)
* feat: Add hook on web app to open composer with 'N' keyboard shortcut * Extract, don't fire open composer if already open * Ignore interactive elements --------- Co-authored-by: João Gabriel <joaog@nocorp.io> Co-authored-by: Hailey <me@haileyok.com>
Diffstat (limited to 'src/state/shell/composer/index.tsx')
-rw-r--r-- | src/state/shell/composer/index.tsx | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx new file mode 100644 index 000000000..612388ff8 --- /dev/null +++ b/src/state/shell/composer/index.tsx @@ -0,0 +1,107 @@ +import React from 'react' +import { + AppBskyActorDefs, + AppBskyEmbedRecord, + AppBskyRichtextFacet, + ModerationDecision, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import * as Toast from '#/view/com/util/Toast' + +export interface ComposerOptsPostRef { + uri: string + cid: string + text: string + author: AppBskyActorDefs.ProfileViewBasic + embed?: AppBskyEmbedRecord.ViewRecord['embed'] + moderation?: ModerationDecision +} +export interface ComposerOptsQuote { + uri: string + cid: string + text: string + facets?: AppBskyRichtextFacet.Main[] + indexedAt: string + author: AppBskyActorDefs.ProfileViewBasic + embeds?: AppBskyEmbedRecord.ViewRecord['embeds'] +} +export interface ComposerOpts { + replyTo?: ComposerOptsPostRef + onPost?: (postUri: string | undefined) => void + quote?: ComposerOptsQuote + quoteCount?: number + mention?: string // handle of user to mention + openEmojiPicker?: (pos: DOMRect | undefined) => void + text?: string + imageUris?: {uri: string; width: number; height: number}[] +} + +type StateContext = ComposerOpts | undefined +type ControlsContext = { + openComposer: (opts: ComposerOpts) => void + closeComposer: () => boolean +} + +const stateContext = React.createContext<StateContext>(undefined) +const controlsContext = React.createContext<ControlsContext>({ + openComposer(_opts: ComposerOpts) {}, + closeComposer() { + return false + }, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const {_} = useLingui() + const [state, setState] = React.useState<StateContext>() + + const openComposer = useNonReactiveCallback((opts: ComposerOpts) => { + const author = opts.replyTo?.author || opts.quote?.author + const isBlocked = Boolean( + author && + (author.viewer?.blocking || + author.viewer?.blockedBy || + author.viewer?.blockingByList), + ) + if (isBlocked) { + Toast.show( + _(msg`Cannot interact with a blocked user`), + 'exclamation-circle', + ) + } else { + setState(opts) + } + }) + + const closeComposer = useNonReactiveCallback(() => { + let wasOpen = !!state + setState(undefined) + return wasOpen + }) + + const api = React.useMemo( + () => ({ + openComposer, + closeComposer, + }), + [openComposer, closeComposer], + ) + + return ( + <stateContext.Provider value={state}> + <controlsContext.Provider value={api}> + {children} + </controlsContext.Provider> + </stateContext.Provider> + ) +} + +export function useComposerState() { + return React.useContext(stateContext) +} + +export function useComposerControls() { + return React.useContext(controlsContext) +} |