Bluesky app fork with some witchin' additions 💫

[APP-1013] Configure and apply default post interaction settings from user preferences (#7664)

* Add interaction settings screen

* Move header out of interaction settings form

* WIP hook it up

* Thread through default settings into composer

* Update copy pasta

* Handle edited state

* Copy feedback

* Sentence case

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* Update copy

* Bump SDK

* Fix new type error

* Less in your face

* Remove new dep

* Add slot

* Copy edit

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

authored by

Eric Bailey
surfdude29
and committed by
GitHub
9cd4f920 1db2668a

+298 -98
+1 -1
package.json
··· 54 54 "icons:optimize": "svgo -f ./assets/icons" 55 55 }, 56 56 "dependencies": { 57 - "@atproto/api": "^0.13.31", 57 + "@atproto/api": "^0.13.33", 58 58 "@bitdrift/react-native": "^0.6.2", 59 59 "@braintree/sanitize-url": "^6.0.2", 60 60 "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
+9
src/Navigation.tsx
··· 70 70 import {MessagesConversationScreen} from '#/screens/Messages/Conversation' 71 71 import {MessagesSettingsScreen} from '#/screens/Messages/Settings' 72 72 import {ModerationScreen} from '#/screens/Moderation' 73 + import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings' 73 74 import {PostLikedByScreen} from '#/screens/Post/PostLikedBy' 74 75 import {PostQuotesScreen} from '#/screens/Post/PostQuotes' 75 76 import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy' ··· 154 155 name="ModerationBlockedAccounts" 155 156 getComponent={() => ModerationBlockedAccounts} 156 157 options={{title: title(msg`Blocked Accounts`), requireAuth: true}} 158 + /> 159 + <Stack.Screen 160 + name="ModerationInteractionSettings" 161 + getComponent={() => ModerationInteractionSettings} 162 + options={{ 163 + title: title(msg`Post Interaction Settings`), 164 + requireAuth: true, 165 + }} 157 166 /> 158 167 <Stack.Screen 159 168 name="Settings"
+56 -26
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 40 40 import {Text} from '#/components/Typography' 41 41 42 42 export type PostInteractionSettingsFormProps = { 43 + canSave?: boolean 43 44 onSave: () => void 44 45 isSaving?: boolean 45 46 ··· 58 59 }: PostInteractionSettingsFormProps & { 59 60 control: Dialog.DialogControlProps 60 61 }) { 62 + const t = useTheme() 61 63 const {_} = useLingui() 64 + 62 65 return ( 63 66 <Dialog.Outer control={control}> 64 67 <Dialog.Handle /> 65 68 <Dialog.ScrollableInner 66 69 label={_(msg`Edit post interaction settings`)} 67 70 style={[{maxWidth: 500}, a.w_full]}> 68 - <PostInteractionSettingsForm {...rest} /> 71 + <View style={[a.gap_md]}> 72 + <Header /> 73 + <PostInteractionSettingsForm {...rest} /> 74 + <Text 75 + style={[ 76 + a.pt_sm, 77 + a.text_sm, 78 + a.leading_snug, 79 + t.atoms.text_contrast_medium, 80 + ]}> 81 + <Trans> 82 + You can set default interaction settings in{' '} 83 + <Text style={[a.font_bold, t.atoms.text_contrast_medium]}> 84 + Settings &rarr; Moderation &rarr; Interaction settings. 85 + </Text> 86 + </Trans> 87 + </Text> 88 + </View> 69 89 <Dialog.Close /> 70 90 </Dialog.ScrollableInner> 71 91 </Dialog.Outer> 92 + ) 93 + } 94 + 95 + export function Header() { 96 + return ( 97 + <View style={[a.gap_md, a.pb_sm]}> 98 + <Text style={[a.text_2xl, a.font_bold]}> 99 + <Trans>Post interaction settings</Trans> 100 + </Text> 101 + <Text style={[a.text_md, a.pb_xs]}> 102 + <Trans>Customize who can interact with this post.</Trans> 103 + </Text> 104 + <Divider /> 105 + </View> 72 106 ) 73 107 } 74 108 ··· 203 237 <Dialog.ScrollableInner 204 238 label={_(msg`Edit post interaction settings`)} 205 239 style={[{maxWidth: 500}, a.w_full]}> 206 - {isLoading ? ( 207 - <View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}> 208 - <Loader size="xl" /> 209 - </View> 210 - ) : ( 211 - <PostInteractionSettingsForm 212 - replySettingsDisabled={!isThreadgateOwnedByViewer} 213 - isSaving={isSaving} 214 - onSave={onSave} 215 - postgate={postgateValue} 216 - onChangePostgate={setEditedPostgate} 217 - threadgateAllowUISettings={allowUIValue} 218 - onChangeThreadgateAllowUISettings={setEditedAllowUISettings} 219 - /> 220 - )} 240 + <View style={[a.gap_md]}> 241 + <Header /> 242 + 243 + {isLoading ? ( 244 + <View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}> 245 + <Loader size="xl" /> 246 + </View> 247 + ) : ( 248 + <PostInteractionSettingsForm 249 + replySettingsDisabled={!isThreadgateOwnedByViewer} 250 + isSaving={isSaving} 251 + onSave={onSave} 252 + postgate={postgateValue} 253 + onChangePostgate={setEditedPostgate} 254 + threadgateAllowUISettings={allowUIValue} 255 + onChangeThreadgateAllowUISettings={setEditedAllowUISettings} 256 + /> 257 + )} 258 + </View> 221 259 </Dialog.ScrollableInner> 222 260 ) 223 261 } 224 262 225 263 export function PostInteractionSettingsForm({ 264 + canSave = true, 226 265 onSave, 227 266 isSaving, 228 267 postgate, ··· 283 322 return ( 284 323 <View> 285 324 <View style={[a.flex_1, a.gap_md]}> 286 - <Text style={[a.text_2xl, a.font_bold]}> 287 - <Trans>Post interaction settings</Trans> 288 - </Text> 289 - 290 325 <View style={[a.gap_lg]}> 291 - <Text style={[a.text_md]}> 292 - <Trans>Customize who can interact with this post.</Trans> 293 - </Text> 294 - 295 - <Divider /> 296 - 297 326 <View style={[a.gap_sm]}> 298 327 <Text style={[a.font_bold, a.text_lg]}> 299 328 <Trans>Quote settings</Trans> ··· 435 464 </View> 436 465 437 466 <Button 467 + disabled={!canSave || isSaving} 438 468 label={_(msg`Save`)} 439 469 onPress={onSave} 440 470 color="primary"
+1
src/lib/routes/types.ts
··· 12 12 ModerationModlists: undefined 13 13 ModerationMutedAccounts: undefined 14 14 ModerationBlockedAccounts: undefined 15 + ModerationInteractionSettings: undefined 15 16 Settings: undefined 16 17 Profile: {name: string; hideBackButton?: boolean} 17 18 ProfileFollowers: {name: string}
+1
src/routes.ts
··· 13 13 ModerationModlists: '/moderation/modlists', 14 14 ModerationMutedAccounts: '/moderation/muted-accounts', 15 15 ModerationBlockedAccounts: '/moderation/blocked-accounts', 16 + ModerationInteractionSettings: '/moderation/interaction-settings', 16 17 // profiles, threads, lists 17 18 Profile: ['/profile/:name', '/profile/:name/rss'], 18 19 ProfileFollowers: '/profile/:name/followers',
+16
src/screens/Moderation/index.tsx
··· 28 28 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 29 29 import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' 30 30 import {Props as SVGIconProps} from '#/components/icons/common' 31 + import {EditBig_Stroke2_Corner0_Rounded as EditBig} from '#/components/icons/EditBig' 31 32 import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' 32 33 import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' 33 34 import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' ··· 199 200 a.overflow_hidden, 200 201 t.atoms.bg_contrast_25, 201 202 ]}> 203 + <Link 204 + label={_(msg`View your default post interaction settings`)} 205 + testID="interactionSettingsBtn" 206 + to="/moderation/interaction-settings"> 207 + {state => ( 208 + <SubItem 209 + title={_(msg`Interaction settings`)} 210 + icon={EditBig} 211 + style={[ 212 + (state.hovered || state.pressed) && [t.atoms.bg_contrast_50], 213 + ]} 214 + /> 215 + )} 216 + </Link> 217 + <Divider /> 202 218 <Button 203 219 testID="mutedWordsBtn" 204 220 label={_(msg`Open muted words and tags settings`)}
+127
src/screens/ModerationInteractionSettings/index.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import deepEqual from 'lodash.isequal' 6 + 7 + import {logger} from '#/logger' 8 + import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings' 9 + import {createPostgateRecord} from '#/state/queries/postgate/util' 10 + import { 11 + usePreferencesQuery, 12 + UsePreferencesQueryResponse, 13 + } from '#/state/queries/preferences' 14 + import { 15 + threadgateAllowUISettingToAllowRecordValue, 16 + threadgateRecordToAllowUISetting, 17 + } from '#/state/queries/threadgate' 18 + import * as Toast from '#/view/com/util/Toast' 19 + import {atoms as a, useGutters} from '#/alf' 20 + import {Admonition} from '#/components/Admonition' 21 + import {PostInteractionSettingsForm} from '#/components/dialogs/PostInteractionSettingsDialog' 22 + import * as Layout from '#/components/Layout' 23 + import {Loader} from '#/components/Loader' 24 + 25 + export function Screen() { 26 + const gutters = useGutters(['base']) 27 + const {data: preferences} = usePreferencesQuery() 28 + return ( 29 + <Layout.Screen testID="ModerationInteractionSettingsScreen"> 30 + <Layout.Header.Outer> 31 + <Layout.Header.BackButton /> 32 + <Layout.Header.Content> 33 + <Layout.Header.TitleText> 34 + <Trans>Post Interaction Settings</Trans> 35 + </Layout.Header.TitleText> 36 + </Layout.Header.Content> 37 + <Layout.Header.Slot /> 38 + </Layout.Header.Outer> 39 + <Layout.Content> 40 + <View style={[gutters, a.gap_xl]}> 41 + <Admonition type="tip"> 42 + <Trans> 43 + The following settings will be used as your defaults when creating 44 + new posts. You can edit these for a specific post from the 45 + composer. 46 + </Trans> 47 + </Admonition> 48 + {preferences ? ( 49 + <Inner preferences={preferences} /> 50 + ) : ( 51 + <View style={[gutters, a.justify_center, a.align_center]}> 52 + <Loader size="xl" /> 53 + </View> 54 + )} 55 + </View> 56 + </Layout.Content> 57 + </Layout.Screen> 58 + ) 59 + } 60 + 61 + function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) { 62 + const {_} = useLingui() 63 + const {mutateAsync: setPostInteractionSettings, isPending} = 64 + usePostInteractionSettingsMutation() 65 + const [error, setError] = React.useState<string | undefined>(undefined) 66 + 67 + const allowUI = React.useMemo(() => { 68 + return threadgateRecordToAllowUISetting({ 69 + $type: 'app.bsky.feed.threadgate', 70 + post: '', 71 + createdAt: new Date().toString(), 72 + allow: preferences.postInteractionSettings.threadgateAllowRules, 73 + }) 74 + }, [preferences.postInteractionSettings.threadgateAllowRules]) 75 + const postgate = React.useMemo(() => { 76 + return createPostgateRecord({ 77 + post: '', 78 + embeddingRules: 79 + preferences.postInteractionSettings.postgateEmbeddingRules, 80 + }) 81 + }, [preferences.postInteractionSettings.postgateEmbeddingRules]) 82 + 83 + const [maybeEditedAllowUI, setAllowUI] = React.useState(allowUI) 84 + const [maybeEditedPostgate, setEditedPostgate] = React.useState(postgate) 85 + 86 + const wasEdited = React.useMemo(() => { 87 + return ( 88 + !deepEqual(allowUI, maybeEditedAllowUI) || 89 + !deepEqual(postgate.embeddingRules, maybeEditedPostgate.embeddingRules) 90 + ) 91 + }, [postgate, allowUI, maybeEditedAllowUI, maybeEditedPostgate]) 92 + 93 + const onSave = React.useCallback(async () => { 94 + setError('') 95 + 96 + try { 97 + await setPostInteractionSettings({ 98 + threadgateAllowRules: 99 + threadgateAllowUISettingToAllowRecordValue(maybeEditedAllowUI), 100 + postgateEmbeddingRules: maybeEditedPostgate.embeddingRules ?? [], 101 + }) 102 + Toast.show(_(msg`Settings saved`)) 103 + } catch (e: any) { 104 + logger.error(`Failed to save post interaction settings`, { 105 + context: 'ModerationInteractionSettingsScreen', 106 + safeMessage: e.message, 107 + }) 108 + setError(_(msg`Failed to save settings. Please try again.`)) 109 + } 110 + }, [_, maybeEditedPostgate, maybeEditedAllowUI, setPostInteractionSettings]) 111 + 112 + return ( 113 + <> 114 + <PostInteractionSettingsForm 115 + canSave={wasEdited} 116 + isSaving={isPending} 117 + onSave={onSave} 118 + postgate={maybeEditedPostgate} 119 + onChangePostgate={setEditedPostgate} 120 + threadgateAllowUISettings={maybeEditedAllowUI} 121 + onChangeThreadgateAllowUISettings={setAllowUI} 122 + /> 123 + 124 + {error && <Admonition type="error">{error}</Admonition>} 125 + </> 126 + ) 127 + }
+1 -1
src/screens/Settings/components/ExportCarDialog.tsx
··· 36 36 const saveRes = await saveBytesToDisk( 37 37 'repo.car', 38 38 downloadRes.data, 39 - downloadRes.headers['content-type'], 39 + downloadRes.headers['content-type'] || 'application/vnd.ipld.car', 40 40 ) 41 41 42 42 if (saveRes) {
+20
src/state/queries/post-interaction-settings.ts
··· 1 + import {AppBskyActorDefs} from '@atproto/api' 2 + import {useMutation, useQueryClient} from '@tanstack/react-query' 3 + 4 + import {preferencesQueryKey} from '#/state/queries/preferences' 5 + import {useAgent} from '#/state/session' 6 + 7 + export function usePostInteractionSettingsMutation() { 8 + const qc = useQueryClient() 9 + const agent = useAgent() 10 + return useMutation({ 11 + async mutationFn(props: AppBskyActorDefs.PostInteractionSettingsPref) { 12 + await agent.setPostInteractionSettings(props) 13 + }, 14 + async onSuccess() { 15 + await qc.invalidateQueries({ 16 + queryKey: preferencesQueryKey, 17 + }) 18 + }, 19 + }) 20 + }
+4
src/state/queries/preferences/const.ts
··· 39 39 activeProgressGuide: undefined, 40 40 nuxs: [], 41 41 }, 42 + postInteractionSettings: { 43 + threadgateAllowRules: undefined, 44 + postgateEmbeddingRules: [], 45 + }, 42 46 }
+9 -1
src/view/com/composer/Composer.tsx
··· 83 83 useLanguagePrefs, 84 84 useLanguagePrefsApi, 85 85 } from '#/state/preferences/languages' 86 + import {usePreferencesQuery} from '#/state/queries/preferences' 86 87 import {useProfileQuery} from '#/state/queries/profile' 87 88 import {Gif} from '#/state/queries/tenor' 88 89 import {useAgent, useSession} from '#/state/session' ··· 169 170 const discardPromptControl = Prompt.usePromptControl() 170 171 const {closeAllDialogs} = useDialogStateControlContext() 171 172 const {closeAllModals} = useModalControls() 173 + const {data: preferences} = usePreferencesQuery() 172 174 173 175 const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) 174 176 const [isPublishing, setIsPublishing] = useState(false) ··· 177 179 178 180 const [composerState, composerDispatch] = useReducer( 179 181 composerReducer, 180 - {initImageUris, initQuoteUri: initQuote?.uri, initText, initMention}, 182 + { 183 + initImageUris, 184 + initQuoteUri: initQuote?.uri, 185 + initText, 186 + initMention, 187 + initInteractionSettings: preferences?.postInteractionSettings, 188 + }, 181 189 createComposerState, 182 190 ) 183 191
+21 -4
src/view/com/composer/state/composer.ts
··· 1 1 import {ImagePickerAsset} from 'expo-image-picker' 2 - import {AppBskyFeedPostgate, AppBskyRichtextFacet, RichText} from '@atproto/api' 2 + import { 3 + AppBskyFeedPostgate, 4 + AppBskyRichtextFacet, 5 + BskyPreferences, 6 + RichText, 7 + } from '@atproto/api' 3 8 import {nanoid} from 'nanoid/non-secure' 4 9 5 10 import {SelfLabel} from '#/lib/moderation' ··· 13 18 import {ComposerImage, createInitialImages} from '#/state/gallery' 14 19 import {createPostgateRecord} from '#/state/queries/postgate/util' 15 20 import {Gif} from '#/state/queries/tenor' 16 - import {threadgateViewToAllowUISetting} from '#/state/queries/threadgate' 21 + import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate' 17 22 import {ThreadgateAllowUISetting} from '#/state/queries/threadgate' 18 23 import {ComposerOpts} from '#/state/shell/composer' 19 24 import { ··· 477 482 initMention, 478 483 initImageUris, 479 484 initQuoteUri, 485 + initInteractionSettings, 480 486 }: { 481 487 initText: string | undefined 482 488 initMention: string | undefined 483 489 initImageUris: ComposerOpts['imageUris'] 484 490 initQuoteUri: string | undefined 491 + initInteractionSettings: 492 + | BskyPreferences['postInteractionSettings'] 493 + | undefined 485 494 }): ComposerState { 486 495 let media: ImagesMedia | undefined 487 496 if (initImageUris?.length) { ··· 591 600 }, 592 601 }, 593 602 ], 594 - postgate: createPostgateRecord({post: ''}), 595 - threadgate: threadgateViewToAllowUISetting(undefined), 603 + postgate: createPostgateRecord({ 604 + post: '', 605 + embeddingRules: initInteractionSettings?.postgateEmbeddingRules || [], 606 + }), 607 + threadgate: threadgateRecordToAllowUISetting({ 608 + $type: 'app.bsky.feed.threadgate', 609 + post: '', 610 + createdAt: new Date().toString(), 611 + allow: initInteractionSettings?.threadgateAllowRules, 612 + }), 596 613 }, 597 614 } 598 615 }
+32 -65
yarn.lock
··· 72 72 tlds "^1.234.0" 73 73 zod "^3.23.8" 74 74 75 - "@atproto/api@^0.13.31": 76 - version "0.13.31" 77 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.31.tgz#23ca2c9118eefddf6e0206f759e56b6726b68483" 78 - integrity sha512-i2cUQuwe+3j8rgPJj4YWRjSQeJunGqJ3IzesnvbODjjZh3IS9jB80BZ/pTe/AvNg6JCBbqeWJjWDVKeFHaiZAw== 75 + "@atproto/api@^0.13.33": 76 + version "0.13.33" 77 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.33.tgz#01d31a1cfd1be311e11324b810b8a83fe4cbf9b0" 78 + integrity sha512-d8AOvtxo2J2zrmcakJTUtLdz2ns+pAqywNXhPxPzHrHcw79D6MKBLHR0vr8oxkGwhDBQTsHiQWTk4gSo8PF7YA== 79 79 dependencies: 80 - "@atproto/common-web" "^0.3.2" 81 - "@atproto/lexicon" "^0.4.5" 82 - "@atproto/syntax" "^0.3.1" 83 - "@atproto/xrpc" "^0.6.6" 80 + "@atproto/common-web" "^0.4.0" 81 + "@atproto/lexicon" "^0.4.6" 82 + "@atproto/syntax" "^0.3.2" 83 + "@atproto/xrpc" "^0.6.8" 84 84 await-lock "^2.2.2" 85 85 multiformats "^9.9.0" 86 86 tlds "^1.234.0" ··· 169 169 uint8arrays "3.0.0" 170 170 zod "^3.23.8" 171 171 172 - "@atproto/common-web@^0.3.2": 173 - version "0.3.2" 174 - resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.2.tgz#4cf78ad4d24fed801882f3d35afc39bceccdff51" 175 - integrity sha512-Vx0JtL1/CssJbFAb0UOdvTrkbUautsDfHNOXNTcX2vyPIxH9xOameSqLLunM1hZnOQbJwyjmQCt6TV+bhnanDg== 172 + "@atproto/common-web@^0.4.0": 173 + version "0.4.0" 174 + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.0.tgz#b1407ae3f964f0ee23c2c3184f38041bac99d1f4" 175 + integrity sha512-ZYL0P9myHybNgwh/hBY0HaBzqiLR1B5/ie5bJpLQAg0whRzNA28t8/nU2vh99tbsWcAF0LOD29M8++LyENJLNQ== 176 176 dependencies: 177 177 graphemer "^1.4.0" 178 178 multiformats "^9.9.0" ··· 293 293 multiformats "^9.9.0" 294 294 zod "^3.23.8" 295 295 296 - "@atproto/lexicon@^0.4.5": 297 - version "0.4.5" 298 - resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.5.tgz#4fcf3731193c674286e9e8d677bbab5dd530b817" 299 - integrity sha512-fljWqMGKn+XWtTprBcS3F1hGBREnQYh6qYHv2sjENucc7REms1gtmZXSerB9N6pVeHVNOnXiILdukeAcic5OEw== 296 + "@atproto/lexicon@^0.4.6": 297 + version "0.4.6" 298 + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.6.tgz#74b2a0f3e4c867b33f75430d4ccec70c47e41576" 299 + integrity sha512-RbiwXcnTuLp9vQrNoQ7xly8HyifKkovqCYtbfXVwqdylWYKPhmRsYkRfcPNv/lILhT9Lm0GVnxNwGGwvvgIsfA== 300 300 dependencies: 301 - "@atproto/common-web" "^0.3.2" 302 - "@atproto/syntax" "^0.3.1" 301 + "@atproto/common-web" "^0.4.0" 302 + "@atproto/syntax" "^0.3.2" 303 303 iso-datestring-validator "^2.2.2" 304 304 multiformats "^9.9.0" 305 305 zod "^3.23.8" ··· 447 447 resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.1.tgz#4346418728f9643d783d2ffcf7c77e132e1f53d4" 448 448 integrity sha512-fzW0Mg1QUOVCWUD3RgEsDt6d1OZ6DdFmbKcDdbzUfh0t4rhtRAC05KbZYmxuMPWDAiJ4BbbQ5dkAc/mNypMXkw== 449 449 450 + "@atproto/syntax@^0.3.2": 451 + version "0.3.2" 452 + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.2.tgz#188f8dccba11e5ace1bf83cbff8ed9e1a3d2d66c" 453 + integrity sha512-JLMhTbXER1Im98RrozfsLAZARGIAzKCZEm+Inh1IF00XU6tHcoGKS+HOw0Uy4R2r04yvxoFs8fswmwAhmMpMdw== 454 + 450 455 "@atproto/xrpc-server@^0.7.4": 451 456 version "0.7.4" 452 457 resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.7.4.tgz#dfac8f7276c1c971a35eaba627eb6372088441c3" ··· 473 478 "@atproto/lexicon" "^0.4.4" 474 479 zod "^3.23.8" 475 480 476 - "@atproto/xrpc@^0.6.6": 477 - version "0.6.6" 478 - resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.6.tgz#28f58270ef4a8056f7f718bd52512e74bcd3702f" 479 - integrity sha512-umXEYVMo9/pyIBoKmIAIi64RXDW9tSXY+wqztlQ6I2GZtjLfNZqmAWU+wADk3SxUe54mvjxxGyA4TtyGtDMfhA== 481 + "@atproto/xrpc@^0.6.8": 482 + version "0.6.8" 483 + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.8.tgz#cede54e17b6f8863f78e16f27f87c1966446eea6" 484 + integrity sha512-+KW0NcwdFyLziccYimX6tPkORiwwxlJPqlkVL9bJyj8nJ0aB8cyqo9HXkziMI+R6ansB1BuWQ0tfdPlLLwrUcA== 480 485 dependencies: 481 - "@atproto/lexicon" "^0.4.5" 486 + "@atproto/lexicon" "^0.4.6" 482 487 zod "^3.23.8" 483 488 484 489 "@aws-crypto/crc32@3.0.0": ··· 3290 3295 "@babel/parser" "^7.25.9" 3291 3296 "@babel/types" "^7.25.9" 3292 3297 3293 - "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": 3298 + "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": 3294 3299 version "7.25.9" 3295 3300 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" 3296 3301 integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== ··· 3348 3353 "@babel/helper-split-export-declaration" "^7.24.5" 3349 3354 "@babel/parser" "^7.24.5" 3350 3355 "@babel/types" "^7.24.5" 3351 - debug "^4.3.1" 3352 - globals "^11.1.0" 3353 - 3354 - "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": 3355 - version "7.25.9" 3356 - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" 3357 - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== 3358 - dependencies: 3359 - "@babel/code-frame" "^7.25.9" 3360 - "@babel/generator" "^7.25.9" 3361 - "@babel/parser" "^7.25.9" 3362 - "@babel/template" "^7.25.9" 3363 - "@babel/types" "^7.25.9" 3364 3356 debug "^4.3.1" 3365 3357 globals "^11.1.0" 3366 3358 ··· 17652 17644 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" 17653 17645 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== 17654 17646 17655 - "string-width-cjs@npm:string-width@^4.2.0": 17656 - version "4.2.3" 17657 - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 17658 - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 17659 - dependencies: 17660 - emoji-regex "^8.0.0" 17661 - is-fullwidth-code-point "^3.0.0" 17662 - strip-ansi "^6.0.1" 17663 - 17664 - string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 17647 + "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 17665 17648 version "4.2.3" 17666 17649 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 17667 17650 integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== ··· 17761 17744 dependencies: 17762 17745 safe-buffer "~5.1.0" 17763 17746 17764 - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": 17747 + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: 17765 17748 version "6.0.1" 17766 17749 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 17767 17750 integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== ··· 17774 17757 integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 17775 17758 dependencies: 17776 17759 ansi-regex "^4.1.0" 17777 - 17778 - strip-ansi@^6.0.0, strip-ansi@^6.0.1: 17779 - version "6.0.1" 17780 - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 17781 - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 17782 - dependencies: 17783 - ansi-regex "^5.0.1" 17784 17760 17785 17761 strip-ansi@^7.0.1: 17786 17762 version "7.1.0" ··· 19056 19032 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 19057 19033 integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== 19058 19034 19059 - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": 19035 + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: 19060 19036 version "7.0.0" 19061 19037 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 19062 19038 integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== ··· 19069 19045 version "6.2.0" 19070 19046 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 19071 19047 integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 19072 - dependencies: 19073 - ansi-styles "^4.0.0" 19074 - string-width "^4.1.0" 19075 - strip-ansi "^6.0.0" 19076 - 19077 - wrap-ansi@^7.0.0: 19078 - version "7.0.0" 19079 - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 19080 - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 19081 19048 dependencies: 19082 19049 ansi-styles "^4.0.0" 19083 19050 string-width "^4.1.0"