Add graphic media self label (#5758)
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -4,9 +4,12 @@ import {ModerationUI} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {isJustAMute} from '#/lib/moderation'
|
import {ADULT_CONTENT_LABELS, isJustAMute} from '#/lib/moderation'
|
||||||
|
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||||
|
import {getDefinition, getLabelStrings} from '#/lib/moderation/useLabelInfo'
|
||||||
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
|
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
|
import {useLabelDefinitions} from '#/state/preferences'
|
||||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {
|
import {
|
||||||
@@ -34,10 +37,68 @@ export function ContentHider({
|
|||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const [override, setOverride] = React.useState(false)
|
const [override, setOverride] = React.useState(false)
|
||||||
const control = useModerationDetailsDialogControl()
|
const control = useModerationDetailsDialogControl()
|
||||||
|
const {labelDefs} = useLabelDefinitions()
|
||||||
|
const globalLabelStrings = useGlobalLabelStrings()
|
||||||
|
const {i18n} = useLingui()
|
||||||
|
|
||||||
const blur = modui?.blurs[0]
|
const blur = modui?.blurs[0]
|
||||||
const desc = useModerationCauseDescription(blur)
|
const desc = useModerationCauseDescription(blur)
|
||||||
|
|
||||||
|
const labelName = React.useMemo(() => {
|
||||||
|
if (!modui?.blurs || !blur) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
blur.type !== 'label' ||
|
||||||
|
(blur.type === 'label' && blur.source.type !== 'user')
|
||||||
|
) {
|
||||||
|
return desc.name
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasAdultContentLabel = false
|
||||||
|
const selfBlurNames = modui.blurs
|
||||||
|
.filter(cause => {
|
||||||
|
if (cause.type !== 'label') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (cause.source.type !== 'user') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (ADULT_CONTENT_LABELS.includes(cause.label.val)) {
|
||||||
|
if (hasAdultContentLabel) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hasAdultContentLabel = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.slice(0, 2)
|
||||||
|
.map(cause => {
|
||||||
|
if (cause.type !== 'label') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const def = cause.labelDef || getDefinition(labelDefs, cause.label)
|
||||||
|
if (def.identifier === 'porn' || def.identifier === 'sexual') {
|
||||||
|
return _(msg`Adult Content`)
|
||||||
|
}
|
||||||
|
return getLabelStrings(i18n.locale, globalLabelStrings, def).name
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selfBlurNames.length === 0) {
|
||||||
|
return desc.name
|
||||||
|
}
|
||||||
|
return [...new Set(selfBlurNames)].join(', ')
|
||||||
|
}, [
|
||||||
|
_,
|
||||||
|
modui?.blurs,
|
||||||
|
blur,
|
||||||
|
desc.name,
|
||||||
|
labelDefs,
|
||||||
|
i18n.locale,
|
||||||
|
globalLabelStrings,
|
||||||
|
])
|
||||||
|
|
||||||
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
@@ -99,8 +160,9 @@ export function ContentHider({
|
|||||||
web({
|
web({
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
}),
|
}),
|
||||||
]}>
|
]}
|
||||||
{desc.name}
|
numberOfLines={2}>
|
||||||
|
{labelName}
|
||||||
</Text>
|
</Text>
|
||||||
{!modui.noOverride && (
|
{!modui.noOverride && (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
|||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {AppModerationCause} from '#/components/Pills'
|
import {AppModerationCause} from '#/components/Pills'
|
||||||
|
|
||||||
|
export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
|
||||||
|
export const OTHER_SELF_LABELS = ['graphic-media']
|
||||||
|
export const SELF_LABELS = [...ADULT_CONTENT_LABELS, ...OTHER_SELF_LABELS]
|
||||||
|
|
||||||
|
export type AdultSelfLabel = (typeof ADULT_CONTENT_LABELS)[number]
|
||||||
|
export type OtherSelfLabel = (typeof OTHER_SELF_LABELS)[number]
|
||||||
|
export type SelfLabel = (typeof SELF_LABELS)[number]
|
||||||
|
|
||||||
export function getModerationCauseKey(
|
export function getModerationCauseKey(
|
||||||
cause: ModerationCause | AppModerationCause,
|
cause: ModerationCause | AppModerationCause,
|
||||||
): string {
|
): string {
|
||||||
|
|||||||
@@ -1,44 +1,52 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Keyboard, LayoutAnimation, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {ShieldExclamation} from '#/lib/icons'
|
import {ShieldExclamation} from '#/lib/icons'
|
||||||
|
import {
|
||||||
|
ADULT_CONTENT_LABELS,
|
||||||
|
AdultSelfLabel,
|
||||||
|
OTHER_SELF_LABELS,
|
||||||
|
OtherSelfLabel,
|
||||||
|
SelfLabel,
|
||||||
|
} from '#/lib/moderation'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
|
|
||||||
|
|
||||||
export function LabelsBtn({
|
export function LabelsBtn({
|
||||||
labels,
|
labels,
|
||||||
hasMedia,
|
hasMedia,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
labels: string[]
|
labels: SelfLabel[]
|
||||||
hasMedia: boolean
|
hasMedia: boolean
|
||||||
onChange: (v: string[]) => void
|
onChange: (v: SelfLabel[]) => void
|
||||||
}) {
|
}) {
|
||||||
const control = Dialog.useDialogControl()
|
const control = Dialog.useDialogControl()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const removeAdultLabel = () => {
|
const hasLabel = labels.length > 0
|
||||||
const final = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
|
|
||||||
onChange(final)
|
const updateAdultLabels = (newLabels: AdultSelfLabel[]) => {
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
const newLabel = newLabels[newLabels.length - 1]
|
||||||
|
const filtered = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
|
||||||
|
onChange([...filtered, newLabel].filter(Boolean) as SelfLabel[])
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAdultSelection =
|
const updateOtherLabels = (newLabels: OtherSelfLabel[]) => {
|
||||||
labels.includes('sexual') ||
|
const newLabel = newLabels[newLabels.length - 1]
|
||||||
labels.includes('nudity') ||
|
const filtered = labels.filter(l => !OTHER_SELF_LABELS.includes(l))
|
||||||
labels.includes('porn')
|
onChange([...filtered, newLabel].filter(Boolean) as SelfLabel[])
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasMedia && hasAdultSelection) {
|
if (!hasMedia && hasLabel) {
|
||||||
removeAdultLabel()
|
onChange([])
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,10 +72,9 @@ export function LabelsBtn({
|
|||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<DialogInner
|
<DialogInner
|
||||||
labels={labels}
|
labels={labels}
|
||||||
onChange={onChange}
|
|
||||||
hasAdultSelection={hasAdultSelection}
|
|
||||||
hasMedia={hasMedia}
|
hasMedia={hasMedia}
|
||||||
removeAdultLabel={removeAdultLabel}
|
updateAdultLabels={updateAdultLabels}
|
||||||
|
updateOtherLabels={updateOtherLabels}
|
||||||
/>
|
/>
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
</>
|
</>
|
||||||
@@ -76,16 +83,14 @@ export function LabelsBtn({
|
|||||||
|
|
||||||
function DialogInner({
|
function DialogInner({
|
||||||
labels,
|
labels,
|
||||||
onChange,
|
|
||||||
hasAdultSelection,
|
|
||||||
hasMedia,
|
hasMedia,
|
||||||
removeAdultLabel,
|
updateAdultLabels,
|
||||||
|
updateOtherLabels,
|
||||||
}: {
|
}: {
|
||||||
labels: string[]
|
labels: string[]
|
||||||
onChange: (v: string[]) => void
|
|
||||||
hasAdultSelection: boolean
|
|
||||||
hasMedia: boolean
|
hasMedia: boolean
|
||||||
removeAdultLabel: () => void
|
updateAdultLabels: (labels: AdultSelfLabel[]) => void
|
||||||
|
updateOtherLabels: (labels: OtherSelfLabel[]) => void
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const control = Dialog.useDialogContext()
|
const control = Dialog.useDialogContext()
|
||||||
@@ -95,104 +100,153 @@ function DialogInner({
|
|||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`Add a content warning`)}
|
label={_(msg`Add a content warning`)}
|
||||||
style={[{maxWidth: 500}, a.w_full]}>
|
style={[{maxWidth: 500}, a.w_full]}>
|
||||||
<View style={[a.flex_1, a.gap_md]}>
|
<View style={[a.flex_1]}>
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>
|
<View style={[a.gap_sm]}>
|
||||||
<Trans>Add a content warning</Trans>
|
<Text style={[a.text_2xl, a.font_bold]}>
|
||||||
</Text>
|
<Trans>Add a content warning</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||||
|
{hasMedia ? (
|
||||||
|
<Trans>
|
||||||
|
Choose self-labels that are applicable for the media you are
|
||||||
|
posting. If none are selected, this post is suitable for all
|
||||||
|
audiences.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
There are no self-labels that can be applied to this post.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View
|
<View style={[a.my_md, a.gap_lg]}>
|
||||||
style={[
|
|
||||||
a.border,
|
|
||||||
a.p_md,
|
|
||||||
t.atoms.border_contrast_high,
|
|
||||||
a.rounded_md,
|
|
||||||
]}>
|
|
||||||
<View
|
|
||||||
style={[a.flex_row, a.align_center, a.justify_between, a.pb_sm]}>
|
|
||||||
<Text style={[a.font_bold, a.text_lg]}>
|
|
||||||
<Trans>Adult Content</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
label={_(msg`Remove`)}
|
|
||||||
variant="ghost"
|
|
||||||
color="primary"
|
|
||||||
size="tiny"
|
|
||||||
onPress={removeAdultLabel}
|
|
||||||
disabled={!hasAdultSelection}
|
|
||||||
style={{opacity: hasAdultSelection ? 1 : 0}}
|
|
||||||
aria-hidden={!hasAdultSelection}>
|
|
||||||
<ButtonText>
|
|
||||||
<Trans>Remove</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
{hasMedia ? (
|
{hasMedia ? (
|
||||||
<>
|
<>
|
||||||
<ToggleButton.Group
|
<View>
|
||||||
label={_(msg`Adult Content labels`)}
|
<View
|
||||||
values={labels}
|
style={[
|
||||||
onChange={values => {
|
a.flex_row,
|
||||||
onChange(values)
|
a.align_center,
|
||||||
LayoutAnimation.configureNext(
|
a.justify_between,
|
||||||
LayoutAnimation.Presets.easeInEaseOut,
|
a.pb_sm,
|
||||||
)
|
]}>
|
||||||
}}>
|
<Text style={[a.font_bold, a.text_lg]}>
|
||||||
<ToggleButton.Button name="sexual" label={_(msg`Suggestive`)}>
|
<Trans>Adult Content</Trans>
|
||||||
<ToggleButton.ButtonText>
|
</Text>
|
||||||
<Trans>Suggestive</Trans>
|
</View>
|
||||||
</ToggleButton.ButtonText>
|
<View
|
||||||
</ToggleButton.Button>
|
style={[
|
||||||
<ToggleButton.Button name="nudity" label={_(msg`Nudity`)}>
|
a.p_md,
|
||||||
<ToggleButton.ButtonText>
|
a.rounded_sm,
|
||||||
<Trans>Nudity</Trans>
|
a.border,
|
||||||
</ToggleButton.ButtonText>
|
t.atoms.border_contrast_medium,
|
||||||
</ToggleButton.Button>
|
]}>
|
||||||
<ToggleButton.Button name="porn" label={_(msg`Porn`)}>
|
<Toggle.Group
|
||||||
<ToggleButton.ButtonText>
|
label={_(msg`Adult Content labels`)}
|
||||||
<Trans>Porn</Trans>
|
values={labels}
|
||||||
</ToggleButton.ButtonText>
|
onChange={values => {
|
||||||
</ToggleButton.Button>
|
updateAdultLabels(values as AdultSelfLabel[])
|
||||||
</ToggleButton.Group>
|
}}>
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
<Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
|
<Toggle.Item name="sexual" label={_(msg`Suggestive`)}>
|
||||||
{labels.includes('sexual') ? (
|
<Toggle.Radio />
|
||||||
<Trans>Pictures meant for adults.</Trans>
|
<Toggle.LabelText>
|
||||||
) : labels.includes('nudity') ? (
|
<Trans>Suggestive</Trans>
|
||||||
<Trans>Artistic or non-erotic nudity.</Trans>
|
</Toggle.LabelText>
|
||||||
) : labels.includes('porn') ? (
|
</Toggle.Item>
|
||||||
<Trans>Sexual activity or erotic nudity.</Trans>
|
<Toggle.Item name="nudity" label={_(msg`Nudity`)}>
|
||||||
) : (
|
<Toggle.Radio />
|
||||||
<Trans>If none are selected, suitable for all ages.</Trans>
|
<Toggle.LabelText>
|
||||||
)}
|
<Trans>Nudity</Trans>
|
||||||
</Text>
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item name="porn" label={_(msg`Porn`)}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Porn</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
<Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
|
||||||
|
{labels.includes('sexual') ? (
|
||||||
|
<Trans>Pictures meant for adults.</Trans>
|
||||||
|
) : labels.includes('nudity') ? (
|
||||||
|
<Trans>Artistic or non-erotic nudity.</Trans>
|
||||||
|
) : labels.includes('porn') ? (
|
||||||
|
<Trans>Sexual activity or erotic nudity.</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Does not contain adult content.</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_between,
|
||||||
|
a.pb_sm,
|
||||||
|
]}>
|
||||||
|
<Text style={[a.font_bold, a.text_lg]}>
|
||||||
|
<Trans>Other</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Toggle.Group
|
||||||
|
label={_(msg`Adult Content labels`)}
|
||||||
|
values={labels}
|
||||||
|
onChange={values => {
|
||||||
|
updateOtherLabels(values as OtherSelfLabel[])
|
||||||
|
}}>
|
||||||
|
<Toggle.Item
|
||||||
|
name="graphic-media"
|
||||||
|
label={_(msg`Graphic Media`)}>
|
||||||
|
<Toggle.Checkbox />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Graphic Media</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
</Toggle.Group>
|
||||||
|
<Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
|
||||||
|
{labels.includes('graphic-media') ? (
|
||||||
|
<Trans>
|
||||||
|
Media that may be disturbing or inappropriate for some
|
||||||
|
audiences.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
Does not contain graphic or disturbing content.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : null}
|
||||||
<View>
|
|
||||||
<Text style={t.atoms.text_contrast_medium}>
|
|
||||||
<Trans>
|
|
||||||
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
|
||||||
Not Applicable.
|
|
||||||
</Text>{' '}
|
|
||||||
This warning is only available for posts with media attached.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Button
|
<View style={[a.mt_sm]}>
|
||||||
label={_(msg`Done`)}
|
<Button
|
||||||
onPress={() => control.close()}
|
label={_(msg`Done`)}
|
||||||
color="primary"
|
onPress={() => control.close()}
|
||||||
size="large"
|
color="primary"
|
||||||
variant="solid"
|
size={isWeb ? 'small' : 'large'}
|
||||||
style={a.mt_xl}>
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Done</Trans>
|
<Trans>Done</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
</View>
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {ImagePickerAsset} from 'expo-image-picker'
|
import {ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {AppBskyFeedPostgate, RichText} from '@atproto/api'
|
import {AppBskyFeedPostgate, RichText} from '@atproto/api'
|
||||||
|
|
||||||
|
import {SelfLabel} from '#/lib/moderation'
|
||||||
import {insertMentionAt} from '#/lib/strings/mention-manip'
|
import {insertMentionAt} from '#/lib/strings/mention-manip'
|
||||||
import {
|
import {
|
||||||
isBskyPostUrl,
|
isBskyPostUrl,
|
||||||
@@ -48,7 +49,7 @@ export type EmbedDraft = {
|
|||||||
|
|
||||||
export type ComposerDraft = {
|
export type ComposerDraft = {
|
||||||
richtext: RichText
|
richtext: RichText
|
||||||
labels: string[]
|
labels: SelfLabel[]
|
||||||
postgate: AppBskyFeedPostgate.Record
|
postgate: AppBskyFeedPostgate.Record
|
||||||
threadgate: ThreadgateAllowUISetting[]
|
threadgate: ThreadgateAllowUISetting[]
|
||||||
embed: EmbedDraft
|
embed: EmbedDraft
|
||||||
@@ -56,7 +57,7 @@ export type ComposerDraft = {
|
|||||||
|
|
||||||
export type ComposerAction =
|
export type ComposerAction =
|
||||||
| {type: 'update_richtext'; richtext: RichText}
|
| {type: 'update_richtext'; richtext: RichText}
|
||||||
| {type: 'update_labels'; labels: string[]}
|
| {type: 'update_labels'; labels: SelfLabel[]}
|
||||||
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
|
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
|
||||||
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
|
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
|
||||||
| {type: 'embed_add_images'; images: ComposerImage[]}
|
| {type: 'embed_add_images'; images: ComposerImage[]}
|
||||||
|
|||||||
Reference in New Issue
Block a user