Add self-labeling controls (#1141)

* Add self-label modal

* Use the shield-exclamation icon consistently on post moderation

* Wire up self-labeling

* Bump @atproto/api@0.6.0

* Bump @atproto/dev-env@^0.2.3

* Add e2e test for self-labeling

* Fix types
This commit is contained in:
Paul Frazee 2023-08-09 17:34:16 -07:00 committed by GitHub
parent 48813a96d6
commit 03d152675e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 443 additions and 124 deletions

View file

@ -41,6 +41,7 @@ import {isDesktopWeb, isAndroid, isIOS} from 'platform/detection'
import {GalleryModel} from 'state/models/media/gallery'
import {Gallery} from './photos/Gallery'
import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
import {LabelsBtn} from './labels/LabelsBtn'
import {SelectLangBtn} from './select-language/SelectLangBtn'
type Props = ComposerOpts & {
@ -67,6 +68,7 @@ export const ComposePost = observer(function ComposePost({
initQuote,
)
const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
const [labels, setLabels] = useState<string[]>([])
const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set())
const gallery = useMemo(() => new GalleryModel(store), [store])
@ -145,75 +147,59 @@ export const ComposePost = observer(function ComposePost({
[gallery, track],
)
const onPressPublish = useCallback(
async (rt: RichText) => {
if (isProcessing || rt.graphemeLength > MAX_GRAPHEME_LENGTH) {
return
}
if (store.preferences.requireAltTextEnabled && gallery.needsAltText) {
return
}
const onPressPublish = async (rt: RichText) => {
if (isProcessing || rt.graphemeLength > MAX_GRAPHEME_LENGTH) {
return
}
if (store.preferences.requireAltTextEnabled && gallery.needsAltText) {
return
}
setError('')
setError('')
if (rt.text.trim().length === 0 && gallery.isEmpty) {
setError('Did you want to say anything?')
return
}
if (rt.text.trim().length === 0 && gallery.isEmpty) {
setError('Did you want to say anything?')
return
}
setIsProcessing(true)
setIsProcessing(true)
try {
await apilib.post(store, {
rawText: rt.text,
replyTo: replyTo?.uri,
images: gallery.images,
quote: quote,
extLink: extLink,
onStateChange: setProcessingState,
knownHandles: autocompleteView.knownHandles,
langs: store.preferences.postLanguages,
})
} catch (e: any) {
if (extLink) {
setExtLink({
...extLink,
isLoading: true,
localThumb: undefined,
} as apilib.ExternalEmbedDraft)
}
setError(cleanError(e.message))
setIsProcessing(false)
return
} finally {
track('Create Post', {
imageCount: gallery.size,
})
if (replyTo && replyTo.uri) track('Post:Reply')
try {
await apilib.post(store, {
rawText: rt.text,
replyTo: replyTo?.uri,
images: gallery.images,
quote,
extLink,
labels,
onStateChange: setProcessingState,
knownHandles: autocompleteView.knownHandles,
langs: store.preferences.postLanguages,
})
} catch (e: any) {
if (extLink) {
setExtLink({
...extLink,
isLoading: true,
localThumb: undefined,
} as apilib.ExternalEmbedDraft)
}
if (!replyTo) {
store.me.mainFeed.onPostCreated()
}
onPost?.()
onClose()
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
},
[
isProcessing,
setError,
setIsProcessing,
replyTo,
autocompleteView.knownHandles,
extLink,
onClose,
onPost,
quote,
setExtLink,
store,
track,
gallery,
],
)
setError(cleanError(e.message))
setIsProcessing(false)
return
} finally {
track('Create Post', {
imageCount: gallery.size,
})
if (replyTo && replyTo.uri) track('Post:Reply')
}
if (!replyTo) {
store.me.mainFeed.onPostCreated()
}
onPost?.()
onClose()
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
}
const canPost = useMemo(
() =>
@ -246,6 +232,7 @@ export const ComposePost = observer(function ComposePost({
<Text style={[pal.link, s.f18]}>Cancel</Text>
</TouchableOpacity>
<View style={s.flex1} />
<LabelsBtn labels={labels} onChange={setLabels} />
{isProcessing ? (
<View style={styles.postBtn}>
<ActivityIndicator />
@ -407,7 +394,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
paddingTop: isDesktopWeb ? 10 : undefined,
paddingBottom: 10,
paddingBottom: isDesktopWeb ? 10 : 4,
paddingHorizontal: 20,
height: 55,
},

View file

@ -0,0 +1,53 @@
import React from 'react'
import {StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Button} from 'view/com/util/forms/Button'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {ShieldExclamation} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
export const LabelsBtn = observer(function LabelsBtn({
labels,
onChange,
}: {
labels: string[]
onChange: (v: string[]) => void
}) {
const pal = usePalette('default')
const store = useStores()
return (
<Button
type="default-light"
testID="labelsBtn"
style={styles.button}
accessibilityLabel="Content warnings"
accessibilityHint=""
onPress={() =>
store.shell.openModal({name: 'self-label', labels, onChange})
}>
<ShieldExclamation style={pal.link} size={26} />
{labels.length > 0 ? (
<FontAwesomeIcon
icon="check"
size={16}
style={pal.link as FontAwesomeIconStyle}
/>
) : null}
</Button>
)
})
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 14,
marginRight: 4,
},
label: {
maxWidth: 100,
},
})