diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/state/models/liked-by-view.ts | 17 | ||||
-rw-r--r-- | src/state/models/reposted-by-view.ts | 10 | ||||
-rw-r--r-- | src/state/models/user-followers-view.ts | 111 | ||||
-rw-r--r-- | src/state/models/user-follows-view.ts | 112 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollowers.tsx | 141 | ||||
-rw-r--r-- | src/view/com/profile/ProfileFollows.tsx | 141 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 21 | ||||
-rw-r--r-- | src/view/routes/index.tsx | 22 | ||||
-rw-r--r-- | src/view/routes/types.ts | 2 | ||||
-rw-r--r-- | src/view/screens/stacks/ProfileFollowers.tsx | 39 | ||||
-rw-r--r-- | src/view/screens/stacks/ProfileFollows.tsx | 39 | ||||
-rw-r--r-- | todos.txt | 10 | ||||
-rw-r--r-- | yarn.lock | 4 |
14 files changed, 645 insertions, 26 deletions
diff --git a/package.json b/package.json index c7abd25c6..622dcdaa0 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "@adxp/auth": "*", "@adxp/common": "*", - "@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878", + "@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#6d700ac04affe31030120975c128f1849c8ae98e", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", diff --git a/src/state/models/liked-by-view.ts b/src/state/models/liked-by-view.ts index e9548f27b..91c94093d 100644 --- a/src/state/models/liked-by-view.ts +++ b/src/state/models/liked-by-view.ts @@ -73,7 +73,11 @@ export class LikedByViewModel implements bsky.LikedByView.Response { } async refresh() { - await this._refresh() + await this._fetch(true) + } + + async loadMore() { + // TODO } // state transitions @@ -105,8 +109,8 @@ export class LikedByViewModel implements bsky.LikedByView.Response { }) } - private async _fetch() { - this._xLoading() + private async _fetch(isRefreshing = false) { + this._xLoading(isRefreshing) await new Promise(r => setTimeout(r, 250)) // DEBUG try { const res = (await this.rootStore.api.mainPds.view( @@ -120,13 +124,6 @@ export class LikedByViewModel implements bsky.LikedByView.Response { } } - private async _refresh() { - this._xLoading(true) - // TODO: refetch and update items - await new Promise(r => setTimeout(r, 250)) // DEBUG - this._xIdle() - } - private _replaceAll(res: bsky.LikedByView.Response) { this.likedBy.length = 0 let counter = 0 diff --git a/src/state/models/reposted-by-view.ts b/src/state/models/reposted-by-view.ts index a5f6cce25..74698767e 100644 --- a/src/state/models/reposted-by-view.ts +++ b/src/state/models/reposted-by-view.ts @@ -73,7 +73,11 @@ export class RepostedByViewModel implements bsky.RepostedByView.Response { } async refresh() { - await this._refresh() + await this._fetch(true) + } + + async loadMore() { + // TODO } // state transitions @@ -105,8 +109,8 @@ export class RepostedByViewModel implements bsky.RepostedByView.Response { }) } - private async _fetch() { - this._xLoading() + private async _fetch(isRefreshing = false) { + this._xLoading(isRefreshing) await new Promise(r => setTimeout(r, 250)) // DEBUG try { const res = (await this.rootStore.api.mainPds.view( diff --git a/src/state/models/user-followers-view.ts b/src/state/models/user-followers-view.ts new file mode 100644 index 000000000..af10e57d5 --- /dev/null +++ b/src/state/models/user-followers-view.ts @@ -0,0 +1,111 @@ +import {makeAutoObservable} from 'mobx' +import {bsky} from '@adxp/mock-api' +import {RootStoreModel} from './root-store' + +type Subject = bsky.UserFollowersView.Response['subject'] +export type FollowerItem = + bsky.UserFollowersView.Response['followers'][number] & {_reactKey: string} + +export class UserFollowersViewModel implements bsky.UserFollowersView.Response { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + params: bsky.UserFollowersView.Params + + // data + subject: Subject = {did: '', name: '', displayName: ''} + followers: FollowerItem[] = [] + + constructor( + public rootStore: RootStoreModel, + params: bsky.UserFollowersView.Params, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.subject.did !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async setup() { + await this._fetch() + } + + async refresh() { + await this._fetch(true) + } + + async loadMore() { + // TODO + } + + // state transitions + // = + + private _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + private _xIdle(err: string = '') { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = err + } + + // loader functions + // = + + private async _fetch(isRefreshing = false) { + this._xLoading(isRefreshing) + await new Promise(r => setTimeout(r, 250)) // DEBUG + try { + const res = (await this.rootStore.api.mainPds.view( + 'blueskyweb.xyz:UserFollowersView', + this.params, + )) as bsky.UserFollowersView.Response + this._replaceAll(res) + this._xIdle() + } catch (e: any) { + this._xIdle(`Failed to load feed: ${e.toString()}`) + } + } + + private _replaceAll(res: bsky.UserFollowersView.Response) { + this.subject.did = res.subject.did + this.subject.name = res.subject.name + this.subject.displayName = res.subject.displayName + this.followers.length = 0 + let counter = 0 + for (const item of res.followers) { + this._append({_reactKey: `item-${counter++}`, ...item}) + } + } + + private _append(item: FollowerItem) { + this.followers.push(item) + } +} diff --git a/src/state/models/user-follows-view.ts b/src/state/models/user-follows-view.ts new file mode 100644 index 000000000..fd890a662 --- /dev/null +++ b/src/state/models/user-follows-view.ts @@ -0,0 +1,112 @@ +import {makeAutoObservable} from 'mobx' +import {bsky} from '@adxp/mock-api' +import {RootStoreModel} from './root-store' + +type Subject = bsky.UserFollowsView.Response['subject'] +export type FollowItem = bsky.UserFollowsView.Response['follows'][number] & { + _reactKey: string +} + +export class UserFollowsViewModel implements bsky.UserFollowsView.Response { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + params: bsky.UserFollowsView.Params + + // data + subject: Subject = {did: '', name: '', displayName: ''} + follows: FollowItem[] = [] + + constructor( + public rootStore: RootStoreModel, + params: bsky.UserFollowsView.Params, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.subject.did !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async setup() { + await this._fetch() + } + + async refresh() { + await this._fetch(true) + } + + async loadMore() { + // TODO + } + + // state transitions + // = + + private _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + private _xIdle(err: string = '') { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = err + } + + // loader functions + // = + + private async _fetch(isRefreshing = false) { + this._xLoading(isRefreshing) + await new Promise(r => setTimeout(r, 250)) // DEBUG + try { + const res = (await this.rootStore.api.mainPds.view( + 'blueskyweb.xyz:UserFollowsView', + this.params, + )) as bsky.UserFollowsView.Response + this._replaceAll(res) + this._xIdle() + } catch (e: any) { + this._xIdle(`Failed to load feed: ${e.toString()}`) + } + } + + private _replaceAll(res: bsky.UserFollowsView.Response) { + this.subject.did = res.subject.did + this.subject.name = res.subject.name + this.subject.displayName = res.subject.displayName + this.follows.length = 0 + let counter = 0 + for (const item of res.follows) { + this._append({_reactKey: `item-${counter++}`, ...item}) + } + } + + private _append(item: FollowItem) { + this.follows.push(item) + } +} diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx new file mode 100644 index 000000000..06cc0c14d --- /dev/null +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -0,0 +1,141 @@ +import React, {useState, useEffect} from 'react' +import {observer} from 'mobx-react-lite' +import { + ActivityIndicator, + FlatList, + Image, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native' +import {OnNavigateContent} from '../../routes/types' +import { + UserFollowersViewModel, + FollowerItem, +} from '../../../state/models/user-followers-view' +import {useStores} from '../../../state' +import {s} from '../../lib/styles' +import {AVIS} from '../../lib/assets' + +export const ProfileFollowers = observer(function ProfileFollowers({ + name, + onNavigateContent, +}: { + name: string + onNavigateContent: OnNavigateContent +}) { + const store = useStores() + const [view, setView] = useState<UserFollowersViewModel | undefined>() + + useEffect(() => { + if (view?.params.user === name) { + console.log('User followers doing nothing') + return // no change needed? or trigger refresh? + } + console.log('Fetching user followers', name) + const newView = new UserFollowersViewModel(store, {user: name}) + setView(newView) + newView + .setup() + .catch(err => console.error('Failed to fetch user followers', err)) + }, [name, view?.params.user, store]) + + // loading + // = + if ( + !view || + (view.isLoading && !view.isRefreshing) || + view.params.user !== name + ) { + return ( + <View> + <ActivityIndicator /> + </View> + ) + } + + // error + // = + if (view.hasError) { + return ( + <View> + <Text>{view.error}</Text> + </View> + ) + } + + // loaded + // = + const renderItem = ({item}: {item: FollowerItem}) => ( + <User item={item} onNavigateContent={onNavigateContent} /> + ) + return ( + <View> + <FlatList + data={view.followers} + keyExtractor={item => item._reactKey} + renderItem={renderItem} + /> + </View> + ) +}) + +const User = ({ + item, + onNavigateContent, +}: { + item: FollowerItem + onNavigateContent: OnNavigateContent +}) => { + const onPressOuter = () => { + onNavigateContent('Profile', { + name: item.name, + }) + } + return ( + <TouchableOpacity style={styles.outer} onPress={onPressOuter}> + <View style={styles.layout}> + <View style={styles.layoutAvi}> + <Image + style={styles.avi} + source={AVIS[item.name] || AVIS['alice.com']} + /> + </View> + <View style={styles.layoutContent}> + <Text style={[s.f15, s.bold]}>{item.displayName}</Text> + <Text style={[s.f14, s.gray]}>@{item.name}</Text> + </View> + </View> + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + outer: { + borderTopWidth: 1, + borderTopColor: '#e8e8e8', + backgroundColor: '#fff', + }, + layout: { + flexDirection: 'row', + }, + layoutAvi: { + width: 60, + paddingLeft: 10, + paddingTop: 10, + paddingBottom: 10, + }, + avi: { + width: 40, + height: 40, + borderRadius: 30, + resizeMode: 'cover', + }, + layoutContent: { + flex: 1, + paddingRight: 10, + paddingTop: 10, + paddingBottom: 10, + }, +}) diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx new file mode 100644 index 000000000..bb5859852 --- /dev/null +++ b/src/view/com/profile/ProfileFollows.tsx @@ -0,0 +1,141 @@ +import React, {useState, useEffect} from 'react' +import {observer} from 'mobx-react-lite' +import { + ActivityIndicator, + FlatList, + Image, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native' +import {OnNavigateContent} from '../../routes/types' +import { + UserFollowsViewModel, + FollowItem, +} from '../../../state/models/user-follows-view' +import {useStores} from '../../../state' +import {s} from '../../lib/styles' +import {AVIS} from '../../lib/assets' + +export const ProfileFollows = observer(function ProfileFollows({ + name, + onNavigateContent, +}: { + name: string + onNavigateContent: OnNavigateContent +}) { + const store = useStores() + const [view, setView] = useState<UserFollowsViewModel | undefined>() + + useEffect(() => { + if (view?.params.user === name) { + console.log('User follows doing nothing') + return // no change needed? or trigger refresh? + } + console.log('Fetching user follows', name) + const newView = new UserFollowsViewModel(store, {user: name}) + setView(newView) + newView + .setup() + .catch(err => console.error('Failed to fetch user follows', err)) + }, [name, view?.params.user, store]) + + // loading + // = + if ( + !view || + (view.isLoading && !view.isRefreshing) || + view.params.user !== name + ) { + return ( + <View> + <ActivityIndicator /> + </View> + ) + } + + // error + // = + if (view.hasError) { + return ( + <View> + <Text>{view.error}</Text> + </View> + ) + } + + // loaded + // = + const renderItem = ({item}: {item: FollowItem}) => ( + <User item={item} onNavigateContent={onNavigateContent} /> + ) + return ( + <View> + <FlatList + data={view.follows} + keyExtractor={item => item._reactKey} + renderItem={renderItem} + /> + </View> + ) +}) + +const User = ({ + item, + onNavigateContent, +}: { + item: FollowItem + onNavigateContent: OnNavigateContent +}) => { + const onPressOuter = () => { + onNavigateContent('Profile', { + name: item.name, + }) + } + return ( + <TouchableOpacity style={styles.outer} onPress={onPressOuter}> + <View style={styles.layout}> + <View style={styles.layoutAvi}> + <Image + style={styles.avi} + source={AVIS[item.name] || AVIS['alice.com']} + /> + </View> + <View style={styles.layoutContent}> + <Text style={[s.f15, s.bold]}>{item.displayName}</Text> + <Text style={[s.f14, s.gray]}>@{item.name}</Text> + </View> + </View> + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + outer: { + borderTopWidth: 1, + borderTopColor: '#e8e8e8', + backgroundColor: '#fff', + }, + layout: { + flexDirection: 'row', + }, + layoutAvi: { + width: 60, + paddingLeft: 10, + paddingTop: 10, + paddingBottom: 10, + }, + avi: { + width: 40, + height: 40, + borderRadius: 30, + resizeMode: 'cover', + }, + layoutContent: { + flex: 1, + paddingRight: 10, + paddingTop: 10, + paddingBottom: 10, + }, +}) diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 1eace94aa..6a6d04140 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -6,6 +6,7 @@ import { Image, StyleSheet, Text, + TouchableOpacity, View, } from 'react-native' import {OnNavigateContent} from '../../routes/types' @@ -18,8 +19,8 @@ import Toast from '../util/Toast' export const ProfileHeader = observer(function ProfileHeader({ user, -}: // onNavigateContent, -{ + onNavigateContent, +}: { user: string onNavigateContent: OnNavigateContent }) { @@ -53,6 +54,12 @@ export const ProfileHeader = observer(function ProfileHeader({ err => console.error('Failed to toggle follow', err), ) } + const onPressFollowers = () => { + onNavigateContent('ProfileFollowers', {name: user}) + } + const onPressFollows = () => { + onNavigateContent('ProfileFollows', {name: user}) + } // loading // = @@ -91,16 +98,18 @@ export const ProfileHeader = observer(function ProfileHeader({ <Text style={[s.mb5, s.f15, s['lh15-1.3']]}>{view.description}</Text> )} <View style={s.flexRow}> - <View style={[s.flexRow, s.mr10]}> + <TouchableOpacity + style={[s.flexRow, s.mr10]} + onPress={onPressFollowers}> <Text style={[s.bold, s.mr2]}>{view.followersCount}</Text> <Text style={s.gray}> {pluralize(view.followersCount, 'follower')} </Text> - </View> - <View style={[s.flexRow, s.mr10]}> + </TouchableOpacity> + <TouchableOpacity style={[s.flexRow, s.mr10]} onPress={onPressFollows}> <Text style={[s.bold, s.mr2]}>{view.followsCount}</Text> <Text style={s.gray}>following</Text> - </View> + </TouchableOpacity> <View style={[s.flexRow, s.mr10]}> <Text style={[s.bold, s.mr2]}>{view.postsCount}</Text> <Text style={s.gray}>{pluralize(view.postsCount, 'post')}</Text> diff --git a/src/view/routes/index.tsx b/src/view/routes/index.tsx index 23340a7fe..675edb3e8 100644 --- a/src/view/routes/index.tsx +++ b/src/view/routes/index.tsx @@ -25,6 +25,8 @@ import {PostThread} from '../screens/stacks/PostThread' import {PostLikedBy} from '../screens/stacks/PostLikedBy' import {PostRepostedBy} from '../screens/stacks/PostRepostedBy' import {Profile} from '../screens/stacks/Profile' +import {ProfileFollowers} from '../screens/stacks/ProfileFollowers' +import {ProfileFollows} from '../screens/stacks/ProfileFollows' const linking: LinkingOptions<RootTabsParamList> = { prefixes: [ @@ -40,6 +42,8 @@ const linking: LinkingOptions<RootTabsParamList> = { NotificationsTab: 'notifications', MenuTab: 'menu', Profile: 'profile/:name', + ProfileFollowers: 'profile/:name/followers', + ProfileFollows: 'profile/:name/follows', PostThread: 'profile/:name/post/:recordKey', PostLikedBy: 'profile/:name/post/:recordKey/liked-by', PostRepostedBy: 'profile/:name/post/:recordKey/reposted-by', @@ -93,6 +97,11 @@ function HomeStackCom() { <HomeTabStack.Screen name="Home" component={Home} /> <HomeTabStack.Screen name="Composer" component={Composer} /> <HomeTabStack.Screen name="Profile" component={Profile} /> + <HomeTabStack.Screen + name="ProfileFollowers" + component={ProfileFollowers} + /> + <HomeTabStack.Screen name="ProfileFollows" component={ProfileFollows} /> <HomeTabStack.Screen name="PostThread" component={PostThread} /> <HomeTabStack.Screen name="PostLikedBy" component={PostLikedBy} /> <HomeTabStack.Screen name="PostRepostedBy" component={PostRepostedBy} /> @@ -109,6 +118,11 @@ function SearchStackCom() { options={HIDE_HEADER} /> <SearchTabStack.Screen name="Profile" component={Profile} /> + <SearchTabStack.Screen + name="ProfileFollowers" + component={ProfileFollowers} + /> + <SearchTabStack.Screen name="ProfileFollows" component={ProfileFollows} /> <SearchTabStack.Screen name="PostThread" component={PostThread} /> <SearchTabStack.Screen name="PostLikedBy" component={PostLikedBy} /> <SearchTabStack.Screen name="PostRepostedBy" component={PostRepostedBy} /> @@ -124,6 +138,14 @@ function NotificationsStackCom() { component={Notifications} /> <NotificationsTabStack.Screen name="Profile" component={Profile} /> + <NotificationsTabStack.Screen + name="ProfileFollowers" + component={ProfileFollowers} + /> + <NotificationsTabStack.Screen + name="ProfileFollows" + component={ProfileFollows} + /> <NotificationsTabStack.Screen name="PostThread" component={PostThread} /> <NotificationsTabStack.Screen name="PostLikedBy" diff --git a/src/view/routes/types.ts b/src/view/routes/types.ts index 2a9e1046a..a67f282a6 100644 --- a/src/view/routes/types.ts +++ b/src/view/routes/types.ts @@ -6,6 +6,8 @@ export type RootTabsParamList = { NotificationsTab: undefined MenuTab: undefined Profile: {name: string} + ProfileFollowers: {name: string} + ProfileFollows: {name: string} PostThread: {name: string; recordKey: string} PostLikedBy: {name: string; recordKey: string} PostRepostedBy: {name: string; recordKey: string} diff --git a/src/view/screens/stacks/ProfileFollowers.tsx b/src/view/screens/stacks/ProfileFollowers.tsx new file mode 100644 index 000000000..48fbb4e13 --- /dev/null +++ b/src/view/screens/stacks/ProfileFollowers.tsx @@ -0,0 +1,39 @@ +import React, {useLayoutEffect} from 'react' +import {TouchableOpacity} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Shell} from '../../shell' +import type {RootTabsScreenProps} from '../../routes/types' +import {ProfileFollowers as ProfileFollowersComponent} from '../../com/profile/ProfileFollowers' + +export const ProfileFollowers = ({ + navigation, + route, +}: RootTabsScreenProps<'ProfileFollowers'>) => { + const {name} = route.params + + useLayoutEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: 'Followers', + headerLeft: () => ( + <TouchableOpacity onPress={() => navigation.goBack()}> + <FontAwesomeIcon icon="arrow-left" /> + </TouchableOpacity> + ), + }) + }, [navigation]) + + const onNavigateContent = (screen: string, props: Record<string, string>) => { + // @ts-ignore it's up to the callers to supply correct params -prf + navigation.push(screen, props) + } + + return ( + <Shell> + <ProfileFollowersComponent + name={name} + onNavigateContent={onNavigateContent} + /> + </Shell> + ) +} diff --git a/src/view/screens/stacks/ProfileFollows.tsx b/src/view/screens/stacks/ProfileFollows.tsx new file mode 100644 index 000000000..6fce3d798 --- /dev/null +++ b/src/view/screens/stacks/ProfileFollows.tsx @@ -0,0 +1,39 @@ +import React, {useLayoutEffect} from 'react' +import {TouchableOpacity} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Shell} from '../../shell' +import type {RootTabsScreenProps} from '../../routes/types' +import {ProfileFollows as ProfileFollowsComponent} from '../../com/profile/ProfileFollows' + +export const ProfileFollows = ({ + navigation, + route, +}: RootTabsScreenProps<'ProfileFollows'>) => { + const {name} = route.params + + useLayoutEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: 'Following', + headerLeft: () => ( + <TouchableOpacity onPress={() => navigation.goBack()}> + <FontAwesomeIcon icon="arrow-left" /> + </TouchableOpacity> + ), + }) + }, [navigation]) + + const onNavigateContent = (screen: string, props: Record<string, string>) => { + // @ts-ignore it's up to the callers to supply correct params -prf + navigation.push(screen, props) + } + + return ( + <Shell> + <ProfileFollowsComponent + name={name} + onNavigateContent={onNavigateContent} + /> + </Shell> + ) +} diff --git a/todos.txt b/todos.txt index 29e365ba0..a7153726f 100644 --- a/todos.txt +++ b/todos.txt @@ -1,8 +1,5 @@ Paul's todo list -- Profile view - - Followers list - - Follows list - Composer - Check on navigation stack during a bunch of replies - Search view @@ -11,4 +8,9 @@ Paul's todo list - * - Linking - Web linking - - App linking \ No newline at end of file + - App linking +- Pagination + - Liked by + - Reposted by + - Followers list + - Follows list \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8adea6b04..54d29f308 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,9 +55,9 @@ ucans "0.9.0-alpha3" uint8arrays "^3.0.0" -"@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878": +"@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#6d700ac04affe31030120975c128f1849c8ae98e": version "0.0.1" - resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878" + resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#6d700ac04affe31030120975c128f1849c8ae98e" dependencies: ajv "^8.11.0" ajv-formats "^2.1.1" |