import type { AppBskyActorDefs, AppBskyFeedDefs, AppBskyUnspeccedGetPostThreadV2 } from '@atcute/bluesky';
import type { ModerationDecision } from '@atcute/bluesky-moderation';
import type { ResourceUri } from '@atcute/lexicons';
import { useQueryClient } from '@tanstack/react-query';
import type { ResolvedLink } from '#/lib/api/resolve';
import { useNonReactiveCallback } from '#/lib/hooks/use-non-reactive-callback';
import type { VideoAsset } from '#/lib/media/video/types';
import { recordUriToShareUrl } from '#/lib/routes/app-links';
import { RQKEY_LINK } from '#/state/queries/resolve-link-key';
import { composerDialogHandle } from '#/components/dialogs/handles';
import * as Toast from '#/components/Toast';
import { m } from '#/paraglide/messages';
export interface ComposerOptsPostRef {
uri: ResourceUri;
cid: string;
text: string;
langs?: string[];
author: AppBskyActorDefs.ProfileViewBasic;
embed?: AppBskyFeedDefs.PostView['embed'];
moderation?: ModerationDecision;
}
export type OnPostSuccessData =
| {
replyToUri?: string;
posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[];
}
| undefined;
export interface ComposerOpts {
replyTo?: ComposerOptsPostRef;
onPost?: (postUri: string | undefined) => void;
onPostSuccess?: (data: OnPostSuccessData) => void;
quote?: AppBskyFeedDefs.PostView;
mention?: string; // handle of user to mention
text?: string;
videoUri?: VideoAsset;
}
/**
* registry id for the singleton composer dialog. this stable constant allows the discard flow to close all
* other dialogs without threading an id through context.
*/
export const COMPOSER_DIALOG_ID = 'composer';
/**
* thin imperative API over the global composer dialog (see `composerDialogHandle` in
* `#/components/dialogs/handles`). a hook rather than a plain function because precaching the quoted post
* needs the active account's query client.
*/
export function useOpenComposer() {
const queryClient = useQueryClient();
const openComposer = useNonReactiveCallback((opts: ComposerOpts) => {
if (opts.quote) {
const appUrl = recordUriToShareUrl(opts.quote.uri);
if (appUrl) {
const resolved: ResolvedLink = {
type: 'record',
kind: 'post',
record: {
cid: opts.quote.cid,
uri: opts.quote.uri,
},
view: opts.quote,
};
queryClient.setQueryData(RQKEY_LINK(appUrl), resolved);
}
}
const author = opts.replyTo?.author || opts.quote?.author;
const isBlocked = !!(
author &&
(author.viewer?.blocking || author.viewer?.blockedBy || author.viewer?.blockingByList)
);
if (isBlocked) {
Toast.show(m['common.block.interactionError'](), {
type: 'warning',
});
return;
}
// Never replace an already open composer.
if (composerDialogHandle.isOpen) {
return;
}
composerDialogHandle.openWithPayload(opts);
});
return { openComposer };
}
/** closes the global composer without raising its discard prompt. */
export function closeComposer(): void {
if (composerDialogHandle.isOpen) {
composerDialogHandle.close();
}
}