collapse the dual-world type layer and delete the widening shims
The types/bsky post/profile/starterPack unions drop their @atproto/api arms, and dangerousIsType/validate go with the old-world guards they wrapped. The moderation subjects.ts widening shim and rich-text-helpers' asSdkFacets both existed only to bridge branded and unbranded views, so their 55 and 14 callers now go straight to @bsky.app/sdk/moderation and the raw facets. Boundary fallout: lexicon token defs are camelCase schema objects needing .value, and the branded string slots that the widening used to absorb are now cast or branded at their producers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+87
-116
@@ -1,10 +1,7 @@
|
||||
import type * as AgeRange from 'expo-age-range'
|
||||
import {
|
||||
ageAssuranceRuleIDs as ids,
|
||||
type AppBskyAgeassuranceDefs,
|
||||
type AppBskyAgeassuranceGetState,
|
||||
} from '@atproto/api'
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {type OtherRequiredData} from '#/ageAssurance/data'
|
||||
import {IS_DEV, IS_E2E} from '#/env'
|
||||
import {type Geolocation} from '#/geolocation'
|
||||
@@ -31,33 +28,38 @@ export const otherRequiredData: OtherRequiredData = {
|
||||
}
|
||||
|
||||
const serverStateEnabled = false || IS_E2E
|
||||
export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined =
|
||||
serverStateEnabled
|
||||
? {
|
||||
state: {
|
||||
lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(),
|
||||
status: 'unknown',
|
||||
access: 'unknown',
|
||||
},
|
||||
metadata: {
|
||||
accountCreatedAt: new Date(2023, 1, 1).toISOString(),
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
export const serverState:
|
||||
| app.bsky.ageassurance.getState.$OutputBody
|
||||
| undefined = serverStateEnabled
|
||||
? {
|
||||
state: {
|
||||
lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(),
|
||||
status: 'unknown',
|
||||
access: 'unknown',
|
||||
},
|
||||
metadata: {
|
||||
accountCreatedAt: toDatetimeString(new Date(2023, 1, 1)),
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
|
||||
export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
export const config: app.bsky.ageassurance.defs.Config = {
|
||||
regions: [
|
||||
{
|
||||
countryCode: 'AA',
|
||||
regionCode: undefined,
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
$type: ids.Default,
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'full',
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
/*
|
||||
* `platforms` is not in the generated `ConfigRegion` yet (the server sends
|
||||
* it and `getAgeAssuranceRegionConfigForGeolocation` reads it), so this
|
||||
* fixture asserts the shape until the lexicon carries the field.
|
||||
*/
|
||||
{
|
||||
// On-device verification region, native-only (web users in TX are not
|
||||
// age assured). KWS is included as a fallback for when the device
|
||||
@@ -68,65 +70,55 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
minAccessAge: 18,
|
||||
additionalVerificationMethods: ['device'],
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
} as app.bsky.ageassurance.defs.ConfigRegion,
|
||||
{
|
||||
countryCode: 'GB',
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 13,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
countryCode: 'AU',
|
||||
minAccessAge: 16,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAccountNewerThan.build({
|
||||
date: '2025-12-10T00:00:00Z',
|
||||
access: 'none',
|
||||
$type: ids.IfAccountNewerThan,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 16,
|
||||
access: 'safe',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 16,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -134,20 +126,17 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'SD',
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 13,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -155,20 +144,17 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'WY',
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 13,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -176,20 +162,17 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'OH',
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 13,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -197,15 +180,13 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'MS',
|
||||
minAccessAge: 18,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -213,20 +194,17 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'VA',
|
||||
minAccessAge: 16,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 16,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 16,
|
||||
access: 'full',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -234,45 +212,38 @@ export const config: AppBskyAgeassuranceDefs.Config = {
|
||||
regionCode: 'TN',
|
||||
minAccessAge: 18,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
countryCode: 'BR',
|
||||
minAccessAge: 13,
|
||||
rules: [
|
||||
{
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfAssuredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 18,
|
||||
access: 'full',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
|
||||
age: 13,
|
||||
access: 'safe',
|
||||
$type: ids.IfDeclaredOverAge,
|
||||
},
|
||||
{
|
||||
}),
|
||||
app.bsky.ageassurance.defs.configRegionRuleDefault.build({
|
||||
access: 'none',
|
||||
$type: ids.Default,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {keepPreviousData, useQuery} from '@tanstack/react-query'
|
||||
|
||||
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences'
|
||||
|
||||
@@ -9,7 +9,7 @@ import Animated, {
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useSession} from '#/state/session'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useProfilesQuery} from '#/state/queries/profile'
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {useRef} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useEffect, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationUI} from '@bsky.app/sdk/moderation'
|
||||
import {moderateUserList, type ModerationUI} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -8,7 +8,6 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateUserList} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {precacheList} from '#/state/queries/feed'
|
||||
|
||||
@@ -103,7 +103,17 @@ export function Embed({
|
||||
// ignore any unknowns
|
||||
e.media.view !== null
|
||||
) {
|
||||
return <Embed embed={e.media.view} style={style} />
|
||||
/*
|
||||
* Every media arm's view is a `$Typed<...>` of a def that `PostView.embed`
|
||||
* also admits, but TS will not narrow the parsed union back into the raw
|
||||
* embed union, so the arm is re-asserted here.
|
||||
*/
|
||||
return (
|
||||
<Embed
|
||||
embed={e.media.view as app.bsky.feed.defs.PostView['embed']}
|
||||
style={style}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -8,7 +9,6 @@ import {differenceInSeconds} from 'date-fns'
|
||||
import {app} from '#/lexicons'
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useSession} from '#/state/session'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useMemo} from 'react'
|
||||
|
||||
import {moderateFeedGenerator} from '#/lib/moderation/subjects'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useMemo} from 'react'
|
||||
|
||||
import {moderateUserList} from '#/lib/moderation/subjects'
|
||||
import {moderateUserList} from '@bsky.app/sdk/moderation'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as ListCard from '#/components/ListCard'
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {getChatInviteCodeFromUrl} from '#/lib/strings/url-helpers'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||
@@ -287,7 +286,7 @@ export function QuoteEmbed({
|
||||
if (!bsky.isType(app.bsky.feed.post, quote.record)) return undefined
|
||||
const {text, facets} = quote.record
|
||||
return text.trim()
|
||||
? new RichTextAPI({text: text, facets: asSdkFacets(facets)})
|
||||
? new RichTextAPI({text: text, facets: facets})
|
||||
: undefined
|
||||
}, [quote.record])
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -8,7 +8,6 @@ import {useNavigation} from '@react-navigation/native'
|
||||
import {chat} from '#/lexicons'
|
||||
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -6,12 +6,11 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {getModerationCauseKey} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {forceLTR} from '#/lib/strings/bidi'
|
||||
import {NON_BREAKING_SPACE} from '#/lib/strings/constants'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom'
|
||||
import {msg, plural} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -8,7 +8,6 @@ import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {getModerationCauseKey} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {Keyboard, View} from 'react-native'
|
||||
import {type ModerationOpts, type ModerationUI} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
moderateFeedGenerator,
|
||||
moderateProfile,
|
||||
type ModerationOpts,
|
||||
type ModerationUI,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
|
||||
import {moderateFeedGenerator, moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useSession} from '#/state/session'
|
||||
|
||||
@@ -59,7 +59,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) {
|
||||
await client.call(
|
||||
com.atproto.moderation.createReport,
|
||||
{
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal,
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||
subject: {
|
||||
$type: 'com.atproto.admin.defs#repoRef',
|
||||
// the persisted account did is already resolved
|
||||
|
||||
@@ -7,11 +7,10 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import {TextInput, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||
|
||||
@@ -8,7 +8,7 @@ import {Plural, Trans} from '@lingui/react/macro'
|
||||
import {app} from '#/lexicons'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
|
||||
import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||
import {logger} from '#/logger'
|
||||
import {type ImageMeta} from '#/state/gallery'
|
||||
@@ -169,7 +169,7 @@ function DialogInner({
|
||||
// We want to be working with a blank state here, so let's get the
|
||||
// serialized version and turn it back into a RichText
|
||||
const serialized = richTextToString(
|
||||
new RichTextAPI({text, facets: asSdkFacets(facets)}),
|
||||
new RichTextAPI({text, facets: facets}),
|
||||
false,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {useCallback, useEffect} from 'react'
|
||||
import {type ScrollView, View} from 'react-native'
|
||||
import Animated, {useAnimatedRef, useSharedValue} from 'react-native-reanimated'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import {LayoutAnimation, type TextInput, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {MAX_GROUP_NAME_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
|
||||
|
||||
@@ -10,7 +10,7 @@ import {useLingui} from '@lingui/react/macro'
|
||||
import {chat} from '#/lexicons'
|
||||
import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
|
||||
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
|
||||
import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useConvoActive} from '#/state/messages/convo'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
@@ -86,7 +86,7 @@ export let MessageContextMenu = ({
|
||||
const str = richTextToString(
|
||||
new RichText({
|
||||
text: message.text,
|
||||
facets: asSdkFacets(message.facets),
|
||||
facets: message.facets,
|
||||
}),
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import Animated, {
|
||||
ZoomOut,
|
||||
} from 'react-native-reanimated'
|
||||
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {plural} from '@lingui/core/macro'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
@@ -31,9 +32,7 @@ import * as bsky from '#/types/bsky'
|
||||
import {app, chat} from '#/lexicons'
|
||||
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {type ConvoItem} from '#/state/messages/convo/types'
|
||||
@@ -257,7 +256,7 @@ let MessageItem = ({
|
||||
|
||||
const rt = new RichTextAPI({
|
||||
text: message.text,
|
||||
facets: asSdkFacets(message.facets),
|
||||
facets: message.facets,
|
||||
})
|
||||
|
||||
const isEmojiOnly = isOnlyEmoji(message.text)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {chat} from '#/lexicons'
|
||||
import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
|
||||
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {type Shadow} from '#/state/cache/profile-shadow'
|
||||
import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {useEffect} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {useMemo} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {
|
||||
useTrendingSettings,
|
||||
|
||||
@@ -50,7 +50,7 @@ export function AppealForm({
|
||||
await client.call(
|
||||
com.atproto.moderation.createReport,
|
||||
{
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal,
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||
/*
|
||||
* `useLabelSubject` derives one shape or the other from the label's
|
||||
* `cid`: an at-uri plus cid for a record, or the label's `uri` reused
|
||||
|
||||
@@ -30,93 +30,93 @@ export const NEW_TO_OLD_REASONS_MAP: Record<
|
||||
tools.ozone.report.defs.ReasonType,
|
||||
com.atproto.moderation.defs.ReasonType
|
||||
> = {
|
||||
[tools.ozone.report.defs.reasonAppeal]:
|
||||
com.atproto.moderation.defs.reasonAppeal,
|
||||
[tools.ozone.report.defs.reasonOther]:
|
||||
com.atproto.moderation.defs.reasonOther,
|
||||
[tools.ozone.report.defs.reasonAppeal.value]:
|
||||
com.atproto.moderation.defs.reasonAppeal.value,
|
||||
[tools.ozone.report.defs.reasonOther.value]:
|
||||
com.atproto.moderation.defs.reasonOther.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonViolenceAnimal]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceThreats]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceGraphicContent]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceGlorification]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceExtremistContent]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceTrafficking]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceOther]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonViolenceAnimal.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceThreats.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceGraphicContent.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceGlorification.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceExtremistContent.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceTrafficking.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonViolenceOther.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonSexualAbuseContent]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualNCII]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualDeepfake]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualAnimal]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualUnlabeled]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualOther]:
|
||||
com.atproto.moderation.defs.reasonSexual,
|
||||
[tools.ozone.report.defs.reasonSexualAbuseContent.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
[tools.ozone.report.defs.reasonSexualNCII.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
[tools.ozone.report.defs.reasonSexualDeepfake.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
[tools.ozone.report.defs.reasonSexualAnimal.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
[tools.ozone.report.defs.reasonSexualUnlabeled.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
[tools.ozone.report.defs.reasonSexualOther.value]:
|
||||
com.atproto.moderation.defs.reasonSexual.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonChildSafetyCSAM]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonChildSafetyGroom]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonChildSafetyPrivacy]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonChildSafetyHarassment]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonChildSafetyOther]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonChildSafetyCSAM.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonChildSafetyGroom.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonChildSafetyPrivacy.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonChildSafetyHarassment.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonChildSafetyOther.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonHarassmentTroll]:
|
||||
com.atproto.moderation.defs.reasonRude,
|
||||
[tools.ozone.report.defs.reasonHarassmentTargeted]:
|
||||
com.atproto.moderation.defs.reasonRude,
|
||||
[tools.ozone.report.defs.reasonHarassmentHateSpeech]:
|
||||
com.atproto.moderation.defs.reasonRude,
|
||||
[tools.ozone.report.defs.reasonHarassmentDoxxing]:
|
||||
com.atproto.moderation.defs.reasonRude,
|
||||
[tools.ozone.report.defs.reasonHarassmentOther]:
|
||||
com.atproto.moderation.defs.reasonRude,
|
||||
[tools.ozone.report.defs.reasonHarassmentTroll.value]:
|
||||
com.atproto.moderation.defs.reasonRude.value,
|
||||
[tools.ozone.report.defs.reasonHarassmentTargeted.value]:
|
||||
com.atproto.moderation.defs.reasonRude.value,
|
||||
[tools.ozone.report.defs.reasonHarassmentHateSpeech.value]:
|
||||
com.atproto.moderation.defs.reasonRude.value,
|
||||
[tools.ozone.report.defs.reasonHarassmentDoxxing.value]:
|
||||
com.atproto.moderation.defs.reasonRude.value,
|
||||
[tools.ozone.report.defs.reasonHarassmentOther.value]:
|
||||
com.atproto.moderation.defs.reasonRude.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonMisleadingBot]:
|
||||
com.atproto.moderation.defs.reasonMisleading,
|
||||
[tools.ozone.report.defs.reasonMisleadingImpersonation]:
|
||||
com.atproto.moderation.defs.reasonMisleading,
|
||||
[tools.ozone.report.defs.reasonMisleadingSpam]:
|
||||
com.atproto.moderation.defs.reasonSpam,
|
||||
[tools.ozone.report.defs.reasonMisleadingScam]:
|
||||
com.atproto.moderation.defs.reasonMisleading,
|
||||
[tools.ozone.report.defs.reasonMisleadingElections]:
|
||||
com.atproto.moderation.defs.reasonMisleading,
|
||||
[tools.ozone.report.defs.reasonMisleadingOther]:
|
||||
com.atproto.moderation.defs.reasonMisleading,
|
||||
[tools.ozone.report.defs.reasonMisleadingBot.value]:
|
||||
com.atproto.moderation.defs.reasonMisleading.value,
|
||||
[tools.ozone.report.defs.reasonMisleadingImpersonation.value]:
|
||||
com.atproto.moderation.defs.reasonMisleading.value,
|
||||
[tools.ozone.report.defs.reasonMisleadingSpam.value]:
|
||||
com.atproto.moderation.defs.reasonSpam.value,
|
||||
[tools.ozone.report.defs.reasonMisleadingScam.value]:
|
||||
com.atproto.moderation.defs.reasonMisleading.value,
|
||||
[tools.ozone.report.defs.reasonMisleadingElections.value]:
|
||||
com.atproto.moderation.defs.reasonMisleading.value,
|
||||
[tools.ozone.report.defs.reasonMisleadingOther.value]:
|
||||
com.atproto.moderation.defs.reasonMisleading.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonRuleSiteSecurity]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonRuleProhibitedSales]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonRuleBanEvasion]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonRuleOther]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonRuleSiteSecurity.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonRuleProhibitedSales.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonRuleBanEvasion.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonRuleOther.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
|
||||
[tools.ozone.report.defs.reasonSelfHarmContent]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonSelfHarmED]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonSelfHarmStunts]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonSelfHarmSubstances]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonSelfHarmOther]:
|
||||
com.atproto.moderation.defs.reasonViolation,
|
||||
[tools.ozone.report.defs.reasonSelfHarmContent.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonSelfHarmED.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonSelfHarmStunts.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonSelfHarmSubstances.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
[tools.ozone.report.defs.reasonSelfHarmOther.value]:
|
||||
com.atproto.moderation.defs.reasonViolation.value,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,26 +130,26 @@ export const OLD_TO_NEW_REASONS_MAP: Record<
|
||||
>,
|
||||
tools.ozone.report.defs.ReasonType
|
||||
> = {
|
||||
[com.atproto.moderation.defs.reasonSpam]: [
|
||||
tools.ozone.report.defs.reasonMisleadingSpam,
|
||||
[com.atproto.moderation.defs.reasonSpam.value]: [
|
||||
tools.ozone.report.defs.reasonMisleadingSpam.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonViolation]: [
|
||||
tools.ozone.report.defs.reasonRuleOther,
|
||||
[com.atproto.moderation.defs.reasonViolation.value]: [
|
||||
tools.ozone.report.defs.reasonRuleOther.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonMisleading]: [
|
||||
tools.ozone.report.defs.reasonMisleadingOther,
|
||||
[com.atproto.moderation.defs.reasonMisleading.value]: [
|
||||
tools.ozone.report.defs.reasonMisleadingOther.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonSexual]: [
|
||||
tools.ozone.report.defs.reasonSexualUnlabeled,
|
||||
[com.atproto.moderation.defs.reasonSexual.value]: [
|
||||
tools.ozone.report.defs.reasonSexualUnlabeled.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonRude]: [
|
||||
tools.ozone.report.defs.reasonHarassmentOther,
|
||||
[com.atproto.moderation.defs.reasonRude.value]: [
|
||||
tools.ozone.report.defs.reasonHarassmentOther.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonOther]: [
|
||||
tools.ozone.report.defs.reasonOther,
|
||||
[com.atproto.moderation.defs.reasonOther.value]: [
|
||||
tools.ozone.report.defs.reasonOther.value,
|
||||
],
|
||||
[com.atproto.moderation.defs.reasonAppeal]: [
|
||||
tools.ozone.report.defs.reasonAppeal,
|
||||
[com.atproto.moderation.defs.reasonAppeal.value]: [
|
||||
tools.ozone.report.defs.reasonAppeal.value,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -159,14 +159,14 @@ export const OLD_TO_NEW_REASONS_MAP: Record<
|
||||
*/
|
||||
export const OTHER_REPORT_REASONS: Set<tools.ozone.report.defs.ReasonType> =
|
||||
new Set([
|
||||
tools.ozone.report.defs.reasonViolenceOther,
|
||||
tools.ozone.report.defs.reasonSexualOther,
|
||||
tools.ozone.report.defs.reasonChildSafetyOther,
|
||||
tools.ozone.report.defs.reasonHarassmentOther,
|
||||
tools.ozone.report.defs.reasonMisleadingOther,
|
||||
tools.ozone.report.defs.reasonRuleOther,
|
||||
tools.ozone.report.defs.reasonSelfHarmOther,
|
||||
tools.ozone.report.defs.reasonOther,
|
||||
tools.ozone.report.defs.reasonViolenceOther.value,
|
||||
tools.ozone.report.defs.reasonSexualOther.value,
|
||||
tools.ozone.report.defs.reasonChildSafetyOther.value,
|
||||
tools.ozone.report.defs.reasonHarassmentOther.value,
|
||||
tools.ozone.report.defs.reasonMisleadingOther.value,
|
||||
tools.ozone.report.defs.reasonRuleOther.value,
|
||||
tools.ozone.report.defs.reasonSelfHarmOther.value,
|
||||
tools.ozone.report.defs.reasonOther.value,
|
||||
])
|
||||
|
||||
/**
|
||||
@@ -174,10 +174,10 @@ export const OTHER_REPORT_REASONS: Set<tools.ozone.report.defs.ReasonType> =
|
||||
*/
|
||||
export const BSKY_LABELER_ONLY_REPORT_REASONS: Set<tools.ozone.report.defs.ReasonType> =
|
||||
new Set([
|
||||
tools.ozone.report.defs.reasonChildSafetyCSAM,
|
||||
tools.ozone.report.defs.reasonChildSafetyGroom,
|
||||
tools.ozone.report.defs.reasonChildSafetyOther,
|
||||
tools.ozone.report.defs.reasonViolenceExtremistContent,
|
||||
tools.ozone.report.defs.reasonChildSafetyCSAM.value,
|
||||
tools.ozone.report.defs.reasonChildSafetyGroom.value,
|
||||
tools.ozone.report.defs.reasonChildSafetyOther.value,
|
||||
tools.ozone.report.defs.reasonViolenceExtremistContent.value,
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
|
||||
const nciiOption = {
|
||||
title: 'Non-consensual intimate imagery',
|
||||
reason: tools.ozone.report.defs.reasonSexualNCII,
|
||||
reason: tools.ozone.report.defs.reasonSexualNCII.value,
|
||||
}
|
||||
|
||||
const otherOption = {
|
||||
title: 'Unlabeled adult content',
|
||||
reason: tools.ozone.report.defs.reasonSexualUnlabeled,
|
||||
reason: tools.ozone.report.defs.reasonSexualUnlabeled.value,
|
||||
}
|
||||
|
||||
function selectNciiOption(state: ReportState = initialState) {
|
||||
|
||||
@@ -124,7 +124,7 @@ export function reducer(state: ReportState, action: ReportAction): ReportState {
|
||||
}
|
||||
case 'selectOption': {
|
||||
const isNcii =
|
||||
action.option.reason === tools.ozone.report.defs.reasonSexualNCII
|
||||
action.option.reason === tools.ozone.report.defs.reasonSexualNCII.value
|
||||
return {
|
||||
...state,
|
||||
selectedOption: action.option,
|
||||
|
||||
@@ -37,27 +37,27 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Spam`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingSpam,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingSpam.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Scam`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingScam,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingScam.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Fake account or bot`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingBot,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingBot.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Impersonation`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingImpersonation,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingImpersonation.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`False information about elections`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingElections,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingElections.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other misleading content`),
|
||||
reason: tools.ozone.report.defs.reasonMisleadingOther,
|
||||
reason: tools.ozone.report.defs.reasonMisleadingOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -70,27 +70,27 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Unlabeled adult content`),
|
||||
reason: tools.ozone.report.defs.reasonSexualUnlabeled,
|
||||
reason: tools.ozone.report.defs.reasonSexualUnlabeled.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Adult sexual abuse content`),
|
||||
reason: tools.ozone.report.defs.reasonSexualAbuseContent,
|
||||
reason: tools.ozone.report.defs.reasonSexualAbuseContent.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Non-consensual intimate imagery`),
|
||||
reason: tools.ozone.report.defs.reasonSexualNCII,
|
||||
reason: tools.ozone.report.defs.reasonSexualNCII.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Deepfake adult content`),
|
||||
reason: tools.ozone.report.defs.reasonSexualDeepfake,
|
||||
reason: tools.ozone.report.defs.reasonSexualDeepfake.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Animal sexual abuse`),
|
||||
reason: tools.ozone.report.defs.reasonSexualAnimal,
|
||||
reason: tools.ozone.report.defs.reasonSexualAnimal.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other sexual violence content`),
|
||||
reason: tools.ozone.report.defs.reasonSexualOther,
|
||||
reason: tools.ozone.report.defs.reasonSexualOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -101,23 +101,23 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Trolling`),
|
||||
reason: tools.ozone.report.defs.reasonHarassmentTroll,
|
||||
reason: tools.ozone.report.defs.reasonHarassmentTroll.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Targeted harassment`),
|
||||
reason: tools.ozone.report.defs.reasonHarassmentTargeted,
|
||||
reason: tools.ozone.report.defs.reasonHarassmentTargeted.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Hate speech`),
|
||||
reason: tools.ozone.report.defs.reasonHarassmentHateSpeech,
|
||||
reason: tools.ozone.report.defs.reasonHarassmentHateSpeech.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Doxxing`),
|
||||
reason: tools.ozone.report.defs.reasonHarassmentDoxxing,
|
||||
reason: tools.ozone.report.defs.reasonHarassmentDoxxing.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other harassing or hateful content`),
|
||||
reason: tools.ozone.report.defs.reasonHarassmentOther,
|
||||
reason: tools.ozone.report.defs.reasonHarassmentOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -128,31 +128,32 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Animal welfare`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceAnimal,
|
||||
reason: tools.ozone.report.defs.reasonViolenceAnimal.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Threats or incitement`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceThreats,
|
||||
reason: tools.ozone.report.defs.reasonViolenceThreats.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Graphic violent content`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceGraphicContent,
|
||||
reason: tools.ozone.report.defs.reasonViolenceGraphicContent.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Glorification of violence`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceGlorification,
|
||||
reason: tools.ozone.report.defs.reasonViolenceGlorification.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Extremist content`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceExtremistContent,
|
||||
reason:
|
||||
tools.ozone.report.defs.reasonViolenceExtremistContent.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Human trafficking`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceTrafficking,
|
||||
reason: tools.ozone.report.defs.reasonViolenceTrafficking.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other violent content`),
|
||||
reason: tools.ozone.report.defs.reasonViolenceOther,
|
||||
reason: tools.ozone.report.defs.reasonViolenceOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -163,23 +164,23 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Child Sexual Abuse Material (CSAM)`),
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyCSAM,
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyCSAM.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Grooming or predatory behavior`),
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyGroom,
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyGroom.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Privacy violation of a minor`),
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyPrivacy,
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyPrivacy.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Minor harassment or bullying`),
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyHarassment,
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyHarassment.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other child safety issue`),
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyOther,
|
||||
reason: tools.ozone.report.defs.reasonChildSafetyOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -190,23 +191,23 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Content promoting or depicting self-harm`),
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmContent,
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmContent.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Eating disorders`),
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmED,
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmED.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Dangerous challenges or activities`),
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmStunts,
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmStunts.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Dangerous substances or drug abuse`),
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmSubstances,
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmSubstances.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other dangerous content`),
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmOther,
|
||||
reason: tools.ozone.report.defs.reasonSelfHarmOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -217,19 +218,19 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Hacking or system attacks`),
|
||||
reason: tools.ozone.report.defs.reasonRuleSiteSecurity,
|
||||
reason: tools.ozone.report.defs.reasonRuleSiteSecurity.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Promoting or selling prohibited items or services`),
|
||||
reason: tools.ozone.report.defs.reasonRuleProhibitedSales,
|
||||
reason: tools.ozone.report.defs.reasonRuleProhibitedSales.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Banned user returning`),
|
||||
reason: tools.ozone.report.defs.reasonRuleBanEvasion,
|
||||
reason: tools.ozone.report.defs.reasonRuleBanEvasion.value,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other network rule-breaking`),
|
||||
reason: tools.ozone.report.defs.reasonRuleOther,
|
||||
reason: tools.ozone.report.defs.reasonRuleOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -240,7 +241,7 @@ export function useReportOptions() {
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Other`),
|
||||
reason: tools.ozone.report.defs.reasonOther,
|
||||
reason: tools.ozone.report.defs.reasonOther.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Vendored
+7
-5
@@ -1,4 +1,4 @@
|
||||
import {type Did} from '@atproto/syntax'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import packageJson from '#/../package.json'
|
||||
|
||||
/**
|
||||
@@ -74,14 +74,16 @@ export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || ''
|
||||
/**
|
||||
* The DID of the Bluesky appview to proxy to
|
||||
*/
|
||||
export const BLUESKY_PROXY_DID: Did =
|
||||
process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app'
|
||||
export const BLUESKY_PROXY_DID: DidString =
|
||||
(process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID as DidString) ||
|
||||
'did:web:api.bsky.app'
|
||||
|
||||
/**
|
||||
* The DID of the chat service to proxy to
|
||||
*/
|
||||
export const CHAT_PROXY_DID: Did =
|
||||
process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat'
|
||||
export const CHAT_PROXY_DID: DidString =
|
||||
(process.env.EXPO_PUBLIC_CHAT_PROXY_DID as DidString) ||
|
||||
'did:web:api.bsky.chat'
|
||||
|
||||
/**
|
||||
* Metrics API host
|
||||
|
||||
@@ -55,7 +55,9 @@ function DialogInner({
|
||||
const {_, i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
const [liveLink, setLiveLink] = useState(embed.external.uri)
|
||||
/* Holds the user-editable link text, so it is a plain string rather than
|
||||
* the branded `uri` the view it was seeded from carries. */
|
||||
const [liveLink, setLiveLink] = useState<string>(embed.external.uri)
|
||||
const [liveLinkError, setLiveLinkError] = useState('')
|
||||
const tick = useTickEveryMinute()
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export function DialogInner({
|
||||
await client.call(
|
||||
com.atproto.moderation.createReport,
|
||||
{
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal,
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||
subject: {
|
||||
$type: 'com.atproto.repo.strongRef',
|
||||
// a status view's uri is an at-uri produced by the appview
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {moderateStatus} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {moderateStatus} from '#/lib/moderation/subjects'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {toNiceDomain} from '#/lib/strings/url-helpers'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {useMemo} from 'react'
|
||||
import {retry} from '@atproto/common-web'
|
||||
import {type l, type $Typed} from '@atproto/lex'
|
||||
import {type $Typed, type l, type UriString} from '@atproto/lex'
|
||||
import {type AtIdentifierString, AtUri, toDatetimeString} from '@atproto/syntax'
|
||||
import {moderateStatus} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
@@ -10,7 +11,6 @@ import {isAfter, parseISO} from 'date-fns'
|
||||
import {uploadBlob} from '#/lib/api'
|
||||
import {imageToThumb} from '#/lib/api/resolve'
|
||||
import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta'
|
||||
import {moderateStatus} from '#/lib/moderation/subjects'
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import {useAppConfig} from '#/state/appConfig'
|
||||
import {
|
||||
@@ -335,7 +335,7 @@ export function useUpsertLiveStatusMutation(
|
||||
$type: 'app.bsky.actor.defs#statusView',
|
||||
status: 'app.bsky.actor.status#live',
|
||||
isActive: true,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
expiresAt: toDatetimeString(expiresAt),
|
||||
embed:
|
||||
record.embed && image
|
||||
? {
|
||||
@@ -343,7 +343,8 @@ export function useUpsertLiveStatusMutation(
|
||||
external: {
|
||||
...record.embed.external,
|
||||
$type: 'app.bsky.embed.external#viewExternal',
|
||||
thumb: image,
|
||||
// an opengraph image URL from link resolution
|
||||
thumb: image as UriString,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
|
||||
@@ -15,7 +15,12 @@ import {type FeedAPI, type FeedAPIResponse} from './types'
|
||||
// we use this fallback marker post to drive this instead. see Feed.tsx
|
||||
// for the usage.
|
||||
// -prf
|
||||
export const FALLBACK_MARKER_POST: app.bsky.feed.defs.FeedViewPost = {
|
||||
/*
|
||||
* A synthetic marker, not a real view: its `uri`/`did`/`indexedAt` are
|
||||
* deliberately not well-formed, so the literal is asserted rather than branded.
|
||||
* Only `post.uri` is ever read (see Feed.tsx).
|
||||
*/
|
||||
export const FALLBACK_MARKER_POST = {
|
||||
post: {
|
||||
uri: 'fallback-marker-post',
|
||||
cid: 'fake',
|
||||
@@ -26,7 +31,7 @@ export const FALLBACK_MARKER_POST: app.bsky.feed.defs.FeedViewPost = {
|
||||
},
|
||||
indexedAt: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
} as unknown as app.bsky.feed.defs.FeedViewPost
|
||||
|
||||
export class HomeFeedAPI implements FeedAPI {
|
||||
client: Client
|
||||
|
||||
+7
-5
@@ -1,11 +1,13 @@
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {subDays, subMinutes} from 'date-fns'
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
import {type app} from '#/lexicons'
|
||||
|
||||
const DID = `did:plc:z72i7hdynmk6r22z27h6tvur`
|
||||
const NOW = new Date()
|
||||
const POST_1_DATE = subMinutes(NOW, 2).toISOString()
|
||||
const POST_2_DATE = subMinutes(NOW, 4).toISOString()
|
||||
const POST_3_DATE = subMinutes(NOW, 5).toISOString()
|
||||
const POST_1_DATE = toDatetimeString(subMinutes(NOW, 2))
|
||||
const POST_2_DATE = toDatetimeString(subMinutes(NOW, 4))
|
||||
const POST_3_DATE = toDatetimeString(subMinutes(NOW, 5))
|
||||
|
||||
export const DEMO_FEED = {
|
||||
feed: [
|
||||
@@ -31,7 +33,7 @@ export const DEMO_FEED = {
|
||||
issuer: DID,
|
||||
uri: `at://${DID}/app.bsky.graph.verification/post1`,
|
||||
isValid: true,
|
||||
createdAt: subDays(NOW, 11).toISOString(),
|
||||
createdAt: toDatetimeString(subDays(NOW, 11)),
|
||||
},
|
||||
],
|
||||
verifiedStatus: 'valid',
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import {
|
||||
hasMutedWord as sdkHasMutedWord,
|
||||
moderateFeedGenerator as sdkModerateFeedGenerator,
|
||||
moderateNotification as sdkModerateNotification,
|
||||
moderatePost as sdkModeratePost,
|
||||
moderateProfile as sdkModerateProfile,
|
||||
moderateStatus as sdkModerateStatus,
|
||||
moderateUserList as sdkModerateUserList,
|
||||
type ModerationDecision,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {app, chat} from '#/lexicons'
|
||||
|
||||
/*
|
||||
* TRANSITIONAL. The moderation implementation now comes from
|
||||
* `@bsky.app/sdk/moderation`, whose subject types are the generated
|
||||
* `#/lexicons` views - so their `did`/`uri`/`cid` fields are branded
|
||||
* (`DidString`, `AtUriString`). Many read paths still emit the identically
|
||||
* shaped `@atproto/api` views, whose same fields are plain `string`.
|
||||
*
|
||||
* A plain `string` is not assignable to a branded template-literal type, so
|
||||
* every `moderate*` call taking an unmigrated view fails to typecheck even
|
||||
* though the value is byte-identical - the runtime only ever reads `.did`,
|
||||
* `.labels` and `.viewer`, none of which the brand affects.
|
||||
*
|
||||
* These wrappers widen each subject parameter to accept a view from either
|
||||
* world and drop the brand on the way in. Delete this module once every
|
||||
* producer emits `#/lexicons` views (the `@atproto/api` removal pass) and point
|
||||
* callers back at `@bsky.app/sdk/moderation` directly.
|
||||
*/
|
||||
|
||||
type AnyProfileSubject =
|
||||
| app.bsky.actor.defs.ProfileViewBasic
|
||||
| app.bsky.actor.defs.ProfileView
|
||||
| app.bsky.actor.defs.ProfileViewDetailed
|
||||
| chat.bsky.actor.defs.ProfileViewBasic
|
||||
| app.bsky.actor.defs.ProfileViewBasic
|
||||
| app.bsky.actor.defs.ProfileView
|
||||
| app.bsky.actor.defs.ProfileViewDetailed
|
||||
| chat.bsky.actor.defs.ProfileViewBasic
|
||||
|
||||
type AnyPostSubject = app.bsky.feed.defs.PostView | app.bsky.feed.defs.PostView
|
||||
|
||||
type AnyUserListSubject =
|
||||
| app.bsky.graph.defs.ListViewBasic
|
||||
| app.bsky.graph.defs.ListView
|
||||
| app.bsky.graph.defs.ListViewBasic
|
||||
| app.bsky.graph.defs.ListView
|
||||
|
||||
type AnyFeedGeneratorSubject =
|
||||
| app.bsky.feed.defs.GeneratorView
|
||||
| app.bsky.feed.defs.GeneratorView
|
||||
|
||||
type AnyNotificationSubject =
|
||||
| app.bsky.notification.listNotifications.Notification
|
||||
| app.bsky.notification.listNotifications.Notification
|
||||
|
||||
export function moderateProfile(
|
||||
subject: AnyProfileSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModerateProfile(subject as app.bsky.actor.defs.ProfileView, opts)
|
||||
}
|
||||
|
||||
export function moderateStatus(
|
||||
subject: AnyProfileSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModerateStatus(subject as app.bsky.actor.defs.ProfileView, opts)
|
||||
}
|
||||
|
||||
export function moderatePost(
|
||||
subject: AnyPostSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModeratePost(subject as app.bsky.feed.defs.PostView, opts)
|
||||
}
|
||||
|
||||
export function moderateUserList(
|
||||
subject: AnyUserListSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModerateUserList(subject as app.bsky.graph.defs.ListView, opts)
|
||||
}
|
||||
|
||||
export function moderateFeedGenerator(
|
||||
subject: AnyFeedGeneratorSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModerateFeedGenerator(
|
||||
subject as app.bsky.feed.defs.GeneratorView,
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
export function moderateNotification(
|
||||
subject: AnyNotificationSubject,
|
||||
opts: ModerationOpts,
|
||||
): ModerationDecision {
|
||||
return sdkModerateNotification(
|
||||
subject as app.bsky.notification.listNotifications.Notification,
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Widens `facets`/`actor` for the same reason the `moderate*` wrappers widen
|
||||
* their subjects: mute-word matching reads only `text`/`features`/`langs`, none
|
||||
* of which the brand affects.
|
||||
*/
|
||||
export function hasMutedWord(params: {
|
||||
mutedWords: app.bsky.actor.defs.MutedWord[]
|
||||
text: string
|
||||
facets?: app.bsky.richtext.facet.Main[] | app.bsky.richtext.facet.Main[]
|
||||
outlineTags?: string[]
|
||||
languages?: string[]
|
||||
actor?: AnyProfileSubject
|
||||
}): boolean {
|
||||
return sdkHasMutedWord({
|
||||
...params,
|
||||
facets: params.facets as app.bsky.richtext.facet.Main[] | undefined,
|
||||
actor: params.actor as app.bsky.actor.defs.ProfileView | undefined,
|
||||
})
|
||||
}
|
||||
@@ -30,26 +30,3 @@ export function richTextToString(rt: RichText, loose: boolean): string {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Widens facets typed by the legacy `@atproto/api` codegen into the shape the
|
||||
* SDK's `RichText` accepts.
|
||||
*
|
||||
* The two are the same lexicon and identical at runtime; they differ only in
|
||||
* that the SDK brands `did`/`uri` as template literal types, which makes the
|
||||
* legacy `string` versions unassignable. Call this where facets read off an
|
||||
* `@atproto/api` view type are handed to `new RichText(...)`.
|
||||
*
|
||||
* Transitional: it goes away once the view types come from the SDK too.
|
||||
*/
|
||||
export function asSdkFacets(
|
||||
facets: {index: {byteStart: number; byteEnd: number}; features: unknown[]}[],
|
||||
): app.bsky.richtext.facet.Main[]
|
||||
export function asSdkFacets(
|
||||
facets:
|
||||
| {index: {byteStart: number; byteEnd: number}; features: unknown[]}[]
|
||||
| undefined,
|
||||
): app.bsky.richtext.facet.Main[] | undefined
|
||||
export function asSdkFacets(facets: unknown) {
|
||||
return facets as app.bsky.richtext.facet.Main[] | undefined
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
getMain,
|
||||
type InferMethodError,
|
||||
LexError,
|
||||
type Main,
|
||||
type Procedure,
|
||||
type Query,
|
||||
@@ -27,6 +28,19 @@ export function getErrorStatus(e: unknown): number | undefined {
|
||||
return e instanceof XrpcResponseError ? e.status : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* The lexicon error code (`err.error`), or undefined when `e` carries none.
|
||||
* Gated on `LexError` (the base of every `XrpcError`) rather than `XrpcError`,
|
||||
* so the non-XRPC lex errors are covered too.
|
||||
*
|
||||
* Prefer {@link matchXrpcError} where the method that threw is known: it
|
||||
* constrains the compared name to that method's declared errors. Use this only
|
||||
* where the source method is genuinely ambiguous.
|
||||
*/
|
||||
export function getErrorName(e: unknown): string | undefined {
|
||||
return e instanceof LexError ? e.error : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Same nsid means `e` was thrown for this method schema, so `e` can be
|
||||
* treated as an `XrpcResponseError<M>` - which is what lets the SDK's
|
||||
|
||||
@@ -175,7 +175,7 @@ export function CustomFeedScreenInner({
|
||||
const isVideoFeed = useMemo(() => {
|
||||
const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri)
|
||||
const feedIsVideoMode =
|
||||
feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo
|
||||
feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo.value
|
||||
const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode
|
||||
return IS_NATIVE && _isVideoFeed
|
||||
}, [feedInfo])
|
||||
|
||||
@@ -43,7 +43,7 @@ export function ListHiddenScreen({
|
||||
const goBack = useGoBack()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist.value
|
||||
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const listBlockMutation = useListBlockMutation()
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ScrollEdgeEffect,
|
||||
ScrollEdgeEffectProvider,
|
||||
} from '@bsky.app/expo-scroll-edge-effect'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {
|
||||
type RouteProp,
|
||||
@@ -19,7 +20,6 @@ import * as bsky from '#/types/bsky'
|
||||
import {chat} from '#/lexicons'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useViewportZoomLock} from '#/lib/hooks/useViewportZoomLock'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -2,12 +2,12 @@ import {useEffect} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {ImageBackground} from 'expo-image'
|
||||
import {type ThemeName} from '@bsky.app/alf'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {chat} from '#/lexicons'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useJoinLinkPreviewsQuery} from '#/state/queries/join-links'
|
||||
|
||||
@@ -103,7 +103,7 @@ function DialogInner() {
|
||||
await client.call(
|
||||
com.atproto.moderation.createReport,
|
||||
{
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal,
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||
subject: {
|
||||
$type: 'com.atproto.admin.defs#repoRef',
|
||||
// the persisted account did is already resolved
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {type GestureResponderEvent, View} from 'react-native'
|
||||
import {
|
||||
moderateProfile,
|
||||
type ModerationDecision,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
@@ -12,7 +13,6 @@ import {chat} from '#/lexicons'
|
||||
import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {decrementBadgeCount} from '#/lib/notifications/notifications'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type ActiveConvoStates} from '#/state/messages/convo'
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {chat} from '#/lexicons'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {useCreateJoinLink} from '#/state/queries/messages/create-join-link'
|
||||
import {useDisableJoinLink} from '#/state/queries/messages/disable-join-link'
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {LayoutAnimation, View} from 'react-native'
|
||||
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {HITSLOP_20} from '#/lib/constants'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
} from '#/lib/routes/types'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {
|
||||
convertBskyAppUrlIfNeeded,
|
||||
getChatInviteCodeFromUrl,
|
||||
@@ -168,7 +167,7 @@ function MessageInputPostEmbed({
|
||||
return {
|
||||
rt: new RichTextAPI({
|
||||
text: post.record.text,
|
||||
facets: asSdkFacets(post.record.facets),
|
||||
facets: post.record.facets,
|
||||
}),
|
||||
record: post.record,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -10,7 +10,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {niceDate} from '#/lib/strings/time'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
@@ -196,7 +195,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||
() =>
|
||||
new RichTextAPI({
|
||||
text: record.text,
|
||||
facets: asSdkFacets(record.facets),
|
||||
facets: record.facets,
|
||||
}),
|
||||
[record],
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -209,7 +208,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
|
||||
() =>
|
||||
new RichTextAPI({
|
||||
text: record.text,
|
||||
facets: asSdkFacets(record.facets),
|
||||
facets: record.facets,
|
||||
}),
|
||||
[record],
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -269,7 +268,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
|
||||
() =>
|
||||
new RichTextAPI({
|
||||
text: record.text,
|
||||
facets: asSdkFacets(record.facets),
|
||||
facets: record.facets,
|
||||
}),
|
||||
[record],
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {memo, useCallback, useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {msg, plural} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -10,7 +10,6 @@ import {Plural, Trans} from '@lingui/react/macro'
|
||||
import {MAX_LABELERS} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {isAppLabeler} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {memo, useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {
|
||||
moderateProfile,
|
||||
type ModerationDecision,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
@@ -11,7 +12,6 @@ import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {logger} from '#/logger'
|
||||
import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
|
||||
@@ -7,11 +7,10 @@ import Animated, {
|
||||
} from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {scheduleOnRN} from 'react-native-worklets'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {useIsFocused} from '@react-navigation/native'
|
||||
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -8,7 +8,6 @@ import {Trans} from '@lingui/react/macro'
|
||||
import {app} from '#/lexicons'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {makeListLink} from '#/lib/routes/links'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {logger} from '#/logger'
|
||||
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
|
||||
import {
|
||||
@@ -40,8 +39,8 @@ export function Header({
|
||||
const {_} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
const {currentAccount} = useSession()
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist.value
|
||||
const isBlocking = !!list.viewer?.blocked
|
||||
const isMuting = !!list.viewer?.muted
|
||||
const playHaptic = useHaptics()
|
||||
@@ -130,7 +129,7 @@ export function Header({
|
||||
list.description
|
||||
? new RichTextAPI({
|
||||
text: list.description,
|
||||
facets: asSdkFacets(list.descriptionFacets),
|
||||
facets: list.descriptionFacets,
|
||||
})
|
||||
: undefined,
|
||||
[list],
|
||||
|
||||
@@ -58,8 +58,8 @@ export function MoreOptionsMenu({
|
||||
const {mutateAsync: muteList} = useListMuteMutation()
|
||||
const {mutateAsync: blockList} = useListBlockMutation()
|
||||
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value
|
||||
const isModList = list.purpose === app.bsky.graph.defs.modlist.value
|
||||
const isBlocking = !!list.viewer?.blocked
|
||||
const isMuting = !!list.viewer?.muted
|
||||
const isPinned = Boolean(savedFeedConfig?.pinned)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useCallback, useMemo, useRef, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useAnimatedRef} from 'react-native-reanimated'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateUserList, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -12,7 +12,6 @@ import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {moderateUserList} from '#/lib/moderation/subjects'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
@@ -156,7 +155,7 @@ function ProfileListScreenLoaded({
|
||||
const {rkey} = route.params
|
||||
const feedSectionRef = useRef<SectionRef>(null)
|
||||
const aboutSectionRef = useRef<SectionRef>(null)
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist
|
||||
const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value
|
||||
const isScreenFocused = useIsFocused()
|
||||
const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1
|
||||
const isOwner = currentAccount?.did === list.creator.did
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createHitslop} from '#/lib/constants'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useSession} from '#/state/session'
|
||||
|
||||
@@ -2,10 +2,10 @@ import {useMemo} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {
|
||||
useTrendingSettings,
|
||||
|
||||
@@ -79,8 +79,8 @@ export function AutomationLabelSettingsScreen({}: Props) {
|
||||
|
||||
return existing
|
||||
},
|
||||
checkCommitted: res => {
|
||||
const exists = !!res.data.labels?.some(l => l.val === 'bot')
|
||||
checkCommitted: profile => {
|
||||
const exists = !!profile.labels?.some(l => l.val === 'bot')
|
||||
return exists === wasAdded
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import {useState} from 'react'
|
||||
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
|
||||
import {useReducedMotion} from 'react-native-reanimated'
|
||||
import {removeNuxs} from '@bsky.app/sdk'
|
||||
import {moderateProfile} from '@bsky.app/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
@@ -10,7 +11,6 @@ import {app} from '#/lexicons'
|
||||
import {HELP_DESK_URL, HITSLOP_10} from '#/lib/constants'
|
||||
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
|
||||
import {useApplyPullRequestOTAUpdate} from '#/lib/hooks/useOTAUpdates'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NavigationProp,
|
||||
|
||||
@@ -69,8 +69,8 @@ export function PwiOptOut() {
|
||||
|
||||
return existing
|
||||
},
|
||||
checkCommitted: res => {
|
||||
const exists = !!res.data.labels?.some(
|
||||
checkCommitted: profile => {
|
||||
const exists = !!profile.labels?.some(
|
||||
l => l.val === '!no-unauthenticated',
|
||||
)
|
||||
return exists === wasAdded
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
type NavigationProp,
|
||||
} from '#/lib/routes/types'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
|
||||
import {logger} from '#/logger'
|
||||
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||
@@ -413,7 +412,7 @@ function Header({
|
||||
const richText = record.description
|
||||
? new RichTextAPI({
|
||||
text: record.description,
|
||||
facets: asSdkFacets(record.descriptionFacets),
|
||||
facets: record.descriptionFacets,
|
||||
})
|
||||
: undefined
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export function Takendown() {
|
||||
await client.call(
|
||||
com.atproto.moderation.createReport,
|
||||
{
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal,
|
||||
reasonType: tools.ozone.report.defs.reasonAppeal.value,
|
||||
subject: {
|
||||
$type: 'com.atproto.admin.defs#repoRef',
|
||||
// the persisted account did is already resolved
|
||||
|
||||
@@ -54,7 +54,6 @@ import {
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {logger} from '#/logger'
|
||||
import {useA11y} from '#/state/a11y'
|
||||
import {
|
||||
@@ -831,7 +830,7 @@ function Overlay({
|
||||
: undefined
|
||||
const richText = new RichTextAPI({
|
||||
text: record?.text || '',
|
||||
facets: asSdkFacets(record?.facets),
|
||||
facets: record?.facets,
|
||||
})
|
||||
const handle = sanitizeHandle(post.author.handle, '@')
|
||||
|
||||
|
||||
Vendored
+22
-5
@@ -1,3 +1,4 @@
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
@@ -15,9 +16,13 @@ import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/stat
|
||||
import {castAsShadow, type Shadow} from './types'
|
||||
export type {Shadow} from './types'
|
||||
|
||||
/**
|
||||
* `'pending'` is the optimistic sentinel written before the real record uri
|
||||
* comes back, so the two uri slots admit it alongside a real at-uri.
|
||||
*/
|
||||
export interface PostShadow {
|
||||
likeUri: string | undefined
|
||||
repostUri: string | undefined
|
||||
likeUri: AtUriString | 'pending' | undefined
|
||||
repostUri: AtUriString | 'pending' | undefined
|
||||
isDeleted: boolean
|
||||
embed:
|
||||
| app.bsky.embed.record.View
|
||||
@@ -130,7 +135,12 @@ function mergeShadow(
|
||||
(bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed) &&
|
||||
bsky.isType(app.bsky.embed.recordWithMedia.view, shadow.embed))
|
||||
) {
|
||||
embed = shadow.embed
|
||||
/*
|
||||
* `isType` asserts a present, matching `$type` at runtime but narrows to
|
||||
* the schema's input type, whose `$type` is optional - so the value does
|
||||
* not satisfy the `$Typed` arm of `PostView['embed']` without this cast.
|
||||
*/
|
||||
embed = shadow.embed as typeof post.embed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +153,15 @@ function mergeShadow(
|
||||
bookmarkCount: bookmarkCount,
|
||||
viewer: {
|
||||
...(post.viewer || {}),
|
||||
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
||||
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
||||
/*
|
||||
* The optimistic `'pending'` sentinel is not a real at-uri; consumers only
|
||||
* test these for presence while the write is in flight.
|
||||
*/
|
||||
like: (('likeUri' in shadow ? shadow.likeUri : post.viewer?.like) ??
|
||||
undefined) as AtUriString | undefined,
|
||||
repost: (('repostUri' in shadow
|
||||
? shadow.repostUri
|
||||
: post.viewer?.repost) ?? undefined) as AtUriString | undefined,
|
||||
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
|
||||
bookmarked:
|
||||
'bookmarked' in shadow ? shadow.bookmarked : post.viewer?.bookmarked,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {type DidString, toDatetimeString} from '@atproto/syntax'
|
||||
import {type Client, XrpcResponseError, type $Typed} from '@atproto/lex'
|
||||
import {EventEmitter} from 'eventemitter3'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
@@ -97,7 +98,7 @@ export class Convo {
|
||||
|
||||
private chatClient: Client
|
||||
private events: MessagesEventBus
|
||||
private senderUserDid: string
|
||||
private senderUserDid: DidString
|
||||
|
||||
private status: ConvoStatus = ConvoStatus.Uninitialized
|
||||
private error: ConvoError | undefined
|
||||
@@ -1420,7 +1421,7 @@ export class Convo {
|
||||
$type: 'chat.bsky.convo.defs#messageView',
|
||||
id: nanoid(),
|
||||
rev: '__fake__',
|
||||
sentAt: new Date().toISOString(),
|
||||
sentAt: toDatetimeString(new Date()),
|
||||
sender: {
|
||||
$type: 'chat.bsky.convo.defs#messageViewSender',
|
||||
did: this.senderUserDid,
|
||||
@@ -1462,10 +1463,10 @@ export class Convo {
|
||||
* @param emoji - must be one grapheme
|
||||
*/
|
||||
async addReaction(messageId: string, emoji: string) {
|
||||
const optimisticReaction = {
|
||||
const optimisticReaction: chat.bsky.convo.defs.ReactionView = {
|
||||
value: emoji,
|
||||
sender: {did: this.senderUserDid},
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
}
|
||||
let restore: null | (() => void) = null
|
||||
if (this.pastMessages.has(messageId)) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {useCallback} from 'react'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useMemo, useRef} from 'react'
|
||||
import {type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {app} from '#/lexicons'
|
||||
import {CustomFeedAPI} from '#/lib/api/feed/custom'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner} from '#/lib/api/feed-manip'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {t} from '@lingui/core/macro'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -13,10 +14,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
|
||||
import {moderateFeedGenerator} from '#/lib/moderation/subjects'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {GCTIME, STALE} from '#/state/queries'
|
||||
import {RQKEY as listQueryKey} from '#/state/queries/list'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
@@ -99,7 +98,7 @@ export function hydrateFeedGenerator(
|
||||
|
||||
const description = new RichText({
|
||||
text: view.description || '',
|
||||
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||
facets: (view.descriptionFacets || [])?.slice(),
|
||||
})
|
||||
|
||||
if (!view.descriptionFacets) {
|
||||
@@ -142,7 +141,7 @@ export function hydrateList(
|
||||
|
||||
const description = new RichText({
|
||||
text: view.description || '',
|
||||
facets: asSdkFacets((view.descriptionFacets || [])?.slice()),
|
||||
facets: (view.descriptionFacets || [])?.slice(),
|
||||
})
|
||||
|
||||
if (!view.descriptionFacets) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {useCallback} from 'react'
|
||||
import {type Client, type $Typed} from '@atproto/lex'
|
||||
import {type $Typed, type Client, type Unknown$TypedObject} from '@atproto/lex'
|
||||
import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
@@ -22,10 +23,10 @@ export type KnownChatInvitePreview =
|
||||
| $Typed<chat.bsky.group.defs.InvalidJoinLinkPreviewView>
|
||||
|
||||
/**
|
||||
* The full open-union shape, including the `{$type: string}` fallback for
|
||||
* future variants.
|
||||
* The full open-union shape, including lex's `Unknown$TypedObject` fallback for
|
||||
* unrecognized future variants.
|
||||
*/
|
||||
export type ChatInvitePreview = KnownChatInvitePreview | {$type: string}
|
||||
export type ChatInvitePreview = KnownChatInvitePreview | Unknown$TypedObject
|
||||
|
||||
/**
|
||||
* Narrows a preview to one of the three known variants, filtering out the
|
||||
@@ -112,7 +113,9 @@ export function setJoinLinkPreviewRequestedForCode(
|
||||
...preview,
|
||||
viewer: {
|
||||
...preview.viewer,
|
||||
requestedAt: requested ? new Date().toISOString() : undefined,
|
||||
requestedAt: requested
|
||||
? toDatetimeString(new Date())
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ export function useListMembershipAddMutation({
|
||||
* Needed for optimistic update of starter pack query
|
||||
*/
|
||||
subject?: bsky.profile.AnyProfileView
|
||||
onSuccess?: (data: {uri: string; cid: string}) => void
|
||||
onSuccess?: (data: {uri: AtUriString; cid: string}) => void
|
||||
onError?: (error: Error) => void
|
||||
} = {}) {
|
||||
const {currentAccount} = useSession()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<
|
||||
{uri: string; cid: string},
|
||||
{uri: AtUriString; cid: string},
|
||||
Error,
|
||||
{listUri: string; actorDid: string}
|
||||
>({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {toDatetimeString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import * as bsky from '#/types/bsky'
|
||||
@@ -52,7 +53,7 @@ export function useCreateJoinLink(
|
||||
enabledStatus: 'enabled',
|
||||
joinRule,
|
||||
requireApproval,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt: toDatetimeString(new Date()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -742,11 +742,18 @@ export function ListConvosProviderInner({
|
||||
return {
|
||||
...convo,
|
||||
members: [...convo.members, ...relatedProfilesSansMembers],
|
||||
/*
|
||||
* `logAddReaction.message` is the wider message union (it can
|
||||
* carry a deleted message too), while the reaction view's
|
||||
* `message` slot is only the live `messageView` - the server
|
||||
* never pairs a reaction with a deleted message, so the arm is
|
||||
* asserted rather than re-narrowed.
|
||||
*/
|
||||
lastReaction: {
|
||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||
reaction: log.reaction,
|
||||
message: log.message,
|
||||
},
|
||||
} as chat.bsky.convo.defs.ConvoView['lastReaction'],
|
||||
rev: log.rev,
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {describe, expect, it, jest} from '@jest/globals'
|
||||
|
||||
import {groupNotifications} from '../util'
|
||||
import {app} from '#/lexicons'
|
||||
import {type Notification} from '../types'
|
||||
import {groupNotifications} from '../util'
|
||||
|
||||
jest.mock('#/state/queries/profile', () => ({precacheProfile: jest.fn()}))
|
||||
|
||||
type Notification = app.bsky.notification.listNotifications.Notification
|
||||
|
||||
/*
|
||||
* Fixture builder. The generated view brands `did`/`uri`/`cid`/`indexedAt`, and
|
||||
* these are plain test strings, so the whole literal is asserted once - the
|
||||
* grouping logic under test only compares them as strings.
|
||||
*/
|
||||
function makeFollowNotification(
|
||||
did: string,
|
||||
did: DidString,
|
||||
starterPackUri?: string,
|
||||
): Notification {
|
||||
return {
|
||||
@@ -31,7 +36,7 @@ function makeFollowNotification(
|
||||
: undefined,
|
||||
isRead: false,
|
||||
indexedAt: '2026-07-28T12:00:00.000Z',
|
||||
}
|
||||
} as Notification
|
||||
}
|
||||
|
||||
describe('groupNotifications', () => {
|
||||
|
||||
@@ -26,8 +26,8 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import {app} from '#/lexicons'
|
||||
|
||||
/**
|
||||
* A `listNotifications` notification, widened with the `starterPack` the server
|
||||
* attaches to a `follow` notification sourced from a starter pack. The pinned
|
||||
* lexicon does not declare the field yet, so it is added here rather than read
|
||||
* off an `unknown`. Drop this alias once the generated type carries it.
|
||||
*/
|
||||
export type Notification =
|
||||
app.bsky.notification.listNotifications.Notification & {
|
||||
starterPack?: app.bsky.graph.defs.StarterPackViewBasic
|
||||
}
|
||||
|
||||
export type NotificationType =
|
||||
| StarterPackNotificationType
|
||||
| OtherNotificationType
|
||||
@@ -50,8 +61,8 @@ type OtherNotificationType =
|
||||
|
||||
type FeedNotificationBase = {
|
||||
_reactKey: string
|
||||
notification: app.bsky.notification.listNotifications.Notification
|
||||
additional?: app.bsky.notification.listNotifications.Notification[]
|
||||
notification: Notification
|
||||
additional?: Notification[]
|
||||
subjectUri?: string
|
||||
subject?:
|
||||
| app.bsky.feed.defs.PostView
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type AtUriString} from '@atproto/syntax'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {
|
||||
hasMutedWord,
|
||||
moderateNotification,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
import chunk from 'lodash.chunk'
|
||||
|
||||
import {labelIsHideableOffense} from '#/lib/moderation'
|
||||
import {hasMutedWord, moderateNotification} from '#/lib/moderation/subjects'
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {precacheProfile} from '../profile'
|
||||
import {
|
||||
type FeedNotification,
|
||||
type FeedPage,
|
||||
type Notification,
|
||||
type NotificationType,
|
||||
} from './types'
|
||||
|
||||
@@ -108,7 +112,7 @@ export async function fetchPage({
|
||||
// =
|
||||
|
||||
export function shouldFilterNotif(
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
notif: Notification,
|
||||
moderationOpts: ModerationOpts | undefined,
|
||||
): boolean {
|
||||
const containsImperative = !!notif.author.labels?.some(labelIsHideableOffense)
|
||||
@@ -138,9 +142,7 @@ export function shouldFilterNotif(
|
||||
return moderateNotification(notif, moderationOpts).ui('contentList').filter
|
||||
}
|
||||
|
||||
export function groupNotifications(
|
||||
notifs: app.bsky.notification.listNotifications.Notification[],
|
||||
): FeedNotification[] {
|
||||
export function groupNotifications(notifs: Notification[]): FeedNotification[] {
|
||||
const groupedNotifs: FeedNotification[] = []
|
||||
for (const notif of notifs) {
|
||||
const ts = +new Date(notif.indexedAt)
|
||||
@@ -249,9 +251,7 @@ async function fetchSubjects(
|
||||
}
|
||||
}
|
||||
|
||||
function toKnownType(
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
): NotificationType {
|
||||
function toKnownType(notif: Notification): NotificationType {
|
||||
if (notif.reason === 'like') {
|
||||
if (notif.reasonSubject?.includes('feed.generator')) {
|
||||
return 'feedgen-like'
|
||||
@@ -279,7 +279,7 @@ function toKnownType(
|
||||
|
||||
function getSubjectUri(
|
||||
type: NotificationType,
|
||||
notif: app.bsky.notification.listNotifications.Notification,
|
||||
notif: Notification,
|
||||
): string | undefined {
|
||||
if (
|
||||
type === 'reply' ||
|
||||
|
||||
@@ -55,10 +55,10 @@ export function usePinnedPostMutation() {
|
||||
: undefined
|
||||
return existing
|
||||
},
|
||||
checkCommitted: res =>
|
||||
checkCommitted: profile =>
|
||||
pinCurrentPost
|
||||
? res.data.pinnedPost?.uri === postUri
|
||||
: !res.data.pinnedPost,
|
||||
? profile.pinnedPost?.uri === postUri
|
||||
: !profile.pinnedPost,
|
||||
})
|
||||
|
||||
if (pinCurrentPost) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {AppState} from 'react-native'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type AtIdentifierString, type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {
|
||||
moderatePost,
|
||||
type ModerationDecision,
|
||||
type ModerationPrefs,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
@@ -28,7 +29,6 @@ import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
|
||||
import {DISCOVER_FEED_URI} from '#/lib/constants'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
@@ -78,7 +78,7 @@ export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) {
|
||||
|
||||
export interface FeedPostSliceItem {
|
||||
_reactKey: string
|
||||
uri: string
|
||||
uri: AtUriString
|
||||
post: app.bsky.feed.defs.PostView
|
||||
record: app.bsky.feed.post.Main
|
||||
moderation: ModerationDecision
|
||||
|
||||
@@ -190,7 +190,7 @@ function usePostLikeMutation(
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the like
|
||||
{uri: AtUriString}, // responds with the uri of the like
|
||||
Error,
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
@@ -319,7 +319,7 @@ function usePostRepostMutation(
|
||||
const pdsClient = usePdsClient()
|
||||
const ax = useAnalytics()
|
||||
return useMutation<
|
||||
{uri: string}, // responds with the uri of the repost
|
||||
{uri: AtUriString}, // responds with the uri of the repost
|
||||
Error,
|
||||
{uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present
|
||||
>({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useRef} from 'react'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {AtUri, type HandleString} from '@atproto/syntax'
|
||||
import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {networkRetry, retry} from '#/lib/async/retry'
|
||||
@@ -177,7 +177,7 @@ export function useToggleQuoteDetachmentMutation() {
|
||||
action,
|
||||
}: {
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quoteUri: string
|
||||
quoteUri: AtUriString
|
||||
action: 'detach' | 'reattach'
|
||||
}) => {
|
||||
// cache here since post shadow mutates original object
|
||||
|
||||
@@ -58,7 +58,7 @@ export function mergePostgateRecords(
|
||||
export function createEmbedViewDetachedRecord({
|
||||
uri,
|
||||
}: {
|
||||
uri: string
|
||||
uri: AtUriString
|
||||
}): $Typed<app.bsky.embed.record.View> {
|
||||
const record: $Typed<app.bsky.embed.record.ViewDetached> = {
|
||||
$type: 'app.bsky.embed.record#viewDetached',
|
||||
@@ -86,7 +86,7 @@ export function createMaybeDetachedQuoteEmbed({
|
||||
| {
|
||||
post: app.bsky.feed.defs.PostView
|
||||
quote: undefined
|
||||
quoteUri: string
|
||||
quoteUri: AtUriString
|
||||
detached: true
|
||||
}):
|
||||
| app.bsky.embed.record.View
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {moderateFeedGenerator} from '#/lib/moderation/subjects'
|
||||
import {moderateFeedGenerator} from '@bsky.app/sdk/moderation'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {moderateUserList} from '#/lib/moderation/subjects'
|
||||
import {moderateUserList} from '@bsky.app/sdk/moderation'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {app} from '#/lexicons'
|
||||
import {useModerationOpts} from '../preferences/moderation-opts'
|
||||
|
||||
@@ -146,7 +146,7 @@ interface ProfileUpdateParams {
|
||||
) => Un$Typed<app.bsky.actor.profile.Main>)
|
||||
newUserAvatar?: ImageMeta | undefined | null
|
||||
newUserBanner?: ImageMeta | undefined | null
|
||||
checkCommitted?: (res: app.bsky.actor.getProfile.Response) => boolean
|
||||
checkCommitted?: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean
|
||||
}
|
||||
export function useProfileUpdateMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
@@ -206,21 +206,21 @@ export function useProfileUpdateMutation() {
|
||||
appviewClient,
|
||||
profile.did,
|
||||
checkCommitted ||
|
||||
(res => {
|
||||
(profile => {
|
||||
if (typeof newUserAvatar !== 'undefined') {
|
||||
if (newUserAvatar === null && res.data.avatar) {
|
||||
if (newUserAvatar === null && profile.avatar) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.avatar === profile.avatar) {
|
||||
} else if (profile.avatar === profile.avatar) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (typeof newUserBanner !== 'undefined') {
|
||||
if (newUserBanner === null && res.data.banner) {
|
||||
if (newUserBanner === null && profile.banner) {
|
||||
// url hasn't cleared yet
|
||||
return false
|
||||
} else if (res.data.banner === profile.banner) {
|
||||
} else if (profile.banner === profile.banner) {
|
||||
// url hasn't changed yet
|
||||
return false
|
||||
}
|
||||
@@ -229,8 +229,8 @@ export function useProfileUpdateMutation() {
|
||||
return true
|
||||
}
|
||||
return (
|
||||
res.data.displayName === updates.displayName &&
|
||||
res.data.description === updates.description
|
||||
profile.displayName === updates.displayName &&
|
||||
profile.description === updates.description
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -381,7 +381,7 @@ function useProfileFollowMutation(
|
||||
const queryClient = useQueryClient()
|
||||
const {captureAction} = useProgressGuideControls()
|
||||
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined
|
||||
if (currentAccount) {
|
||||
@@ -637,7 +637,7 @@ function useProfileBlockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const pdsClient = usePdsClient()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||
return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({
|
||||
mutationFn: async ({did}) => {
|
||||
if (!currentAccount) {
|
||||
throw new Error('Not signed in')
|
||||
@@ -679,23 +679,16 @@ function useProfileUnblockMutation() {
|
||||
async function whenAppViewReady(
|
||||
client: Client,
|
||||
actor: string,
|
||||
fn: (res: app.bsky.actor.getProfile.Response) => boolean,
|
||||
fn: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean,
|
||||
) {
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
fn,
|
||||
/*
|
||||
* `checkCommitted` callbacks are still written against the legacy
|
||||
* `Response` envelope, so wrap the lex output until those consumers move.
|
||||
*/
|
||||
async () => ({
|
||||
success: true,
|
||||
headers: {},
|
||||
data: await client.call(app.bsky.actor.getProfile, {
|
||||
async () =>
|
||||
await client.call(app.bsky.actor.getProfile, {
|
||||
actor: actor as AtIdentifierString,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -200,12 +200,31 @@ function mergeList(a?: string[], b?: string[]): string[] | undefined {
|
||||
* operator. v2 renames v1's singular operators to plural arrays, and `lang` to
|
||||
* `language`.
|
||||
*/
|
||||
/**
|
||||
* The filter subset of the `searchPostsV2` params, with the format-constrained
|
||||
* strings (uri, at-identifier, language, datetime) left unbranded: every value
|
||||
* here is parsed from user input, so the brand is asserted by the caller rather
|
||||
* than re-validated here. `q`, `limit`, `cursor` and `sort` are the caller's.
|
||||
*/
|
||||
type SearchPostsV2FilterParams = {
|
||||
[K in Exclude<
|
||||
keyof app.bsky.feed.searchPostsV2.$Params,
|
||||
'query' | 'limit' | 'cursor' | 'sort'
|
||||
>]?: app.bsky.feed.searchPostsV2.$Params[K] extends
|
||||
| readonly (infer _E)[]
|
||||
| undefined
|
||||
? string[]
|
||||
: app.bsky.feed.searchPostsV2.$Params[K] extends string | undefined
|
||||
? string
|
||||
: app.bsky.feed.searchPostsV2.$Params[K]
|
||||
}
|
||||
|
||||
export function buildSearchPostsV2Filters(
|
||||
embedded: Omit<ExtractedSearchParams, 'q'>,
|
||||
filters?: SearchFilters,
|
||||
): app.bsky.feed.searchPostsV2.$Params {
|
||||
): SearchPostsV2FilterParams {
|
||||
const apiFilters = filters ? filtersToApiParams(filters) : {}
|
||||
const params: app.bsky.feed.searchPostsV2.$Params = {}
|
||||
const params: SearchPostsV2FilterParams = {}
|
||||
|
||||
const authors = mergeList(
|
||||
embedded.author ? [embedded.author] : undefined,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {moderatePost} from '@bsky.app/sdk/moderation'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {type SearchFilters} from '#/screens/Search/searchParams'
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderatePost, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {
|
||||
type ApiThreadItem,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import {AppBskyFeedPost} from '@atproto/api'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -167,42 +165,3 @@ describe('types/bsky lexicon schema helpers (#/lexicons)', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('types/bsky validator helpers (@atproto/api)', () => {
|
||||
it('dangerousIsType accepts the right $type without validating the body', () => {
|
||||
expect(
|
||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||
validPost,
|
||||
AppBskyFeedPost.isRecord,
|
||||
),
|
||||
).toBe(true)
|
||||
// Right $type, invalid body - still passes the fast guard.
|
||||
expect(
|
||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||
invalidPost,
|
||||
AppBskyFeedPost.isRecord,
|
||||
),
|
||||
).toBe(true)
|
||||
expect(
|
||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
||||
wrongType,
|
||||
AppBskyFeedPost.isRecord,
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('validate fully validates the body', () => {
|
||||
expect(
|
||||
bsky.validate<AppBskyFeedPost.Record>(
|
||||
validPost,
|
||||
AppBskyFeedPost.validateRecord,
|
||||
),
|
||||
).toBe(true)
|
||||
expect(
|
||||
bsky.validate<AppBskyFeedPost.Record>(
|
||||
invalidPost,
|
||||
AppBskyFeedPost.validateRecord,
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import {
|
||||
type $Typed as $TypedApi,
|
||||
type AppBskyEmbedRecord,
|
||||
type AppBskyFeedDefs,
|
||||
} from '@atproto/api'
|
||||
import {type $Typed} from '@atproto/lex'
|
||||
|
||||
import {type app} from '#/lexicons'
|
||||
@@ -109,38 +104,25 @@ const starterPackViewBasic = {
|
||||
const asEmbed = (v: unknown) => v as app.bsky.feed.defs.PostView['embed']
|
||||
|
||||
/*
|
||||
* Type-level assertions for the dual-world widening. These are compile-time
|
||||
* only: each widened arm must accept both the `#/lexicons` view and the
|
||||
* `@atproto/api` view, and `parseEmbed` must accept a `PostView.embed` from
|
||||
* either world, because both worlds have live producers.
|
||||
* Type-level assertions for the embed union. Compile-time only: each arm must
|
||||
* accept the `#/lexicons` view of its def, and `parseEmbed` must accept a
|
||||
* `PostView.embed`.
|
||||
*/
|
||||
type Assignable<From, To> = From extends To ? true : false
|
||||
type Expect<T extends true> = T
|
||||
|
||||
type _PostArmAcceptsNewWorld = Expect<
|
||||
type _PostArmAcceptsView = Expect<
|
||||
Assignable<
|
||||
{type: 'post'; view: $Typed<app.bsky.embed.record.ViewRecord>},
|
||||
EmbedType<'post'>
|
||||
>
|
||||
>
|
||||
type _PostArmAcceptsOldWorld = Expect<
|
||||
Assignable<
|
||||
{type: 'post'; view: $TypedApi<AppBskyEmbedRecord.ViewRecord>},
|
||||
EmbedType<'post'>
|
||||
>
|
||||
>
|
||||
type _ParseEmbedAcceptsNewWorld = Expect<
|
||||
type _ParseEmbedAcceptsPostViewEmbed = Expect<
|
||||
Assignable<
|
||||
app.bsky.feed.defs.PostView['embed'],
|
||||
Parameters<typeof parseEmbed>[0]
|
||||
>
|
||||
>
|
||||
type _ParseEmbedAcceptsOldWorld = Expect<
|
||||
Assignable<
|
||||
AppBskyFeedDefs.PostView['embed'],
|
||||
Parameters<typeof parseEmbed>[0]
|
||||
>
|
||||
>
|
||||
type _ParseEmbedReturnsEmbed = Expect<
|
||||
Assignable<ReturnType<typeof parseEmbed>, Embed>
|
||||
>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||
|
||||
import {type app} from '#/lexicons'
|
||||
import {
|
||||
type AnyStarterPackView,
|
||||
@@ -30,25 +28,19 @@ const fullView = {
|
||||
}
|
||||
|
||||
/*
|
||||
* Type-level assertions for the dual-world widening: the alias must accept a
|
||||
* starter pack view from either world, because both have live producers. These
|
||||
* are compile-time only - a failure surfaces as a typecheck error.
|
||||
* Type-level assertions for the view alias: it must accept both the basic and
|
||||
* the full starter pack view. Compile-time only - a failure surfaces as a
|
||||
* typecheck error.
|
||||
*/
|
||||
type Assignable<From, To> = From extends To ? true : false
|
||||
type Expect<T extends true> = T
|
||||
|
||||
type _AcceptsNewBasicView = Expect<
|
||||
type _AcceptsBasicView = Expect<
|
||||
Assignable<app.bsky.graph.defs.StarterPackViewBasic, AnyStarterPackView>
|
||||
>
|
||||
type _AcceptsNewFullView = Expect<
|
||||
type _AcceptsFullView = Expect<
|
||||
Assignable<app.bsky.graph.defs.StarterPackView, AnyStarterPackView>
|
||||
>
|
||||
type _AcceptsOldBasicView = Expect<
|
||||
Assignable<AppBskyGraphDefs.StarterPackViewBasic, AnyStarterPackView>
|
||||
>
|
||||
type _AcceptsOldFullView = Expect<
|
||||
Assignable<AppBskyGraphDefs.StarterPackView, AnyStarterPackView>
|
||||
>
|
||||
|
||||
describe('types/bsky/starterPack guards', () => {
|
||||
describe('isBasicView', () => {
|
||||
|
||||
+11
-70
@@ -10,71 +10,15 @@ import {
|
||||
type ValidateOptions,
|
||||
type ValidationResult,
|
||||
} from '@atproto/lex'
|
||||
import {type ValidationResult as LegacyValidationResult} from '@atproto/lexicon'
|
||||
|
||||
export * as post from '#/types/bsky/post'
|
||||
export * as profile from '#/types/bsky/profile'
|
||||
export * as starterPack from '#/types/bsky/starterPack'
|
||||
|
||||
/**
|
||||
* Fast type checking without full schema validation, for use with data we
|
||||
* trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
|
||||
* utils do not assert the type of the entire object, only the `$type` string.
|
||||
*
|
||||
* For full validation of the object schema, use the `validate` export from
|
||||
* this file.
|
||||
*
|
||||
* Usage:
|
||||
* ```ts
|
||||
* import * as bsky from '#/types/bsky'
|
||||
*
|
||||
* if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
|
||||
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function dangerousIsType<R extends {$type?: string}>(
|
||||
record: unknown,
|
||||
identity: <V>(v: V) => v is V & {$type: NonNullable<R['$type']>},
|
||||
): record is R {
|
||||
return identity(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully validates the object schema, which has a performance cost.
|
||||
*
|
||||
* For faster checks with data we trust, like that from our app view, use the
|
||||
* `dangerousIsType` export from this same file.
|
||||
*
|
||||
* Usage:
|
||||
* ```ts
|
||||
* import * as bsky from '#/types/bsky'
|
||||
*
|
||||
* if (bsky.validate(item, AppBskyFeedPost.validateRecord)) {
|
||||
* // `item` has type `$Typed<AppBskyFeedPost.Record>` here
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function validate<R extends {$type?: string}>(
|
||||
record: unknown,
|
||||
validator: (v: unknown) => LegacyValidationResult<R>,
|
||||
): record is R {
|
||||
return validator(record).success
|
||||
}
|
||||
|
||||
/*
|
||||
* Two families of helpers live in this file, distinguished by the validation
|
||||
* surface they consume.
|
||||
*
|
||||
* `dangerousIsType` and `validate` (above) operate on the standalone
|
||||
* `is*`/`validate*` functions exported by '@atproto/api' - the record and the
|
||||
* validator are passed separately, e.g.
|
||||
* `dangerousIsType(v, AppBskyFeedPost.isRecord)`.
|
||||
*
|
||||
* `isType`, `matches`, `parse`, and `safeParse` (below) operate on the
|
||||
* generated lexicon schema objects from '#/lexicons'. That codegen attaches
|
||||
* the validation surface directly to each schema, so these helpers take the
|
||||
* schema object itself:
|
||||
* These helpers operate on the generated lexicon schema objects from
|
||||
* '#/lexicons'. The codegen attaches the validation surface directly to each
|
||||
* schema, so these helpers take the schema object itself:
|
||||
*
|
||||
* ```ts
|
||||
* import {app} from '#/lexicons'
|
||||
@@ -104,9 +48,8 @@ type TypedSchema = RecordSchema | TypedObjectSchema
|
||||
* string; it does NOT assert the rest of the object matches the schema. An
|
||||
* invalid record with the right `$type` will pass.
|
||||
*
|
||||
* This is the '#/lexicons' equivalent of {@link dangerousIsType}. For full
|
||||
* validation of the object schema, use {@link matches}, {@link parse}, or
|
||||
* {@link safeParse} from this same file.
|
||||
* For full validation of the object schema, use {@link matches}, {@link parse},
|
||||
* or {@link safeParse} from this same file.
|
||||
*
|
||||
* Usage:
|
||||
* ```ts
|
||||
@@ -126,10 +69,9 @@ export function isType<S extends TypedSchema>(
|
||||
* Deliberately NOT delegating to the schema's `isTypeOf`: the generated
|
||||
* `TypedObjectSchema.isTypeOf` treats a MISSING `$type` as a match
|
||||
* (maybe-typed semantics), which would let any plain object satisfy any
|
||||
* def-schema check and break `$type`-discriminated unions. The old
|
||||
* `dangerousIsType` required a present, matching `$type`, and so do we.
|
||||
* The nullish/object guard also mirrors the old `is$typed` behavior of
|
||||
* returning false (not throwing) for null/undefined input.
|
||||
* def-schema check and break `$type`-discriminated unions. We require a
|
||||
* present, matching `$type`. The nullish/object guard also returns false
|
||||
* (not throws) for null/undefined input.
|
||||
*/
|
||||
return (
|
||||
value != null &&
|
||||
@@ -142,9 +84,8 @@ export function isType<S extends TypedSchema>(
|
||||
* Fully validates the object against the schema (strict, no coercion), which
|
||||
* has a performance cost, and narrows the value on success.
|
||||
*
|
||||
* This is the '#/lexicons' equivalent of {@link validate}. For faster checks
|
||||
* with data we trust, like that from our app view, use {@link isType} from this
|
||||
* same file.
|
||||
* For faster checks with data we trust, like that from our app view, use
|
||||
* {@link isType} from this same file.
|
||||
*
|
||||
* Usage:
|
||||
* ```ts
|
||||
@@ -166,7 +107,7 @@ export function matches<S extends Schema>(
|
||||
|
||||
/**
|
||||
* Fully validates and parses the value against the schema, returning the typed
|
||||
* value or throwing a `LexValidationError` on failure. Parsing may apply
|
||||
* value or throwing an `LexValidationError` on failure. Parsing may apply
|
||||
* schema transformations such as default values.
|
||||
*
|
||||
* Prefer {@link safeParse} where you want to branch on failure without a
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user