Skip to content

Commit

Permalink
Updated activitypub search suggestions to be dynamic
Browse files Browse the repository at this point in the history
refs [TryGhost/ActivityPub#60](TryGhost/ActivityPub#60)

Updated activitypub search suggestions to be dynamic
  • Loading branch information
mike182uk committed Oct 3, 2024
1 parent c26aaca commit f499cec
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 75 deletions.
37 changes: 37 additions & 0 deletions apps/admin-x-activitypub/src/api/activitypub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,4 +1091,41 @@ describe('ActivityPubAPI', function () {
expect(actual.following).toEqual([]);
});
});

describe('getProfile', function () {
test('It returns a profile', async function () {
const handle = '@[email protected]';

const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
[`https://activitypub.api/.ghost/activitypub/profile/${handle}`]: {
response: JSONResponse({
handle,
name: 'Foo Bar'
})
}
});

const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);

const actual = await api.getProfile(handle);
const expected = {
handle,
name: 'Foo Bar'
};

expect(actual).toEqual(expected);
});
});
});
10 changes: 8 additions & 2 deletions apps/admin-x-activitypub/src/api/activitypub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export type Actor = any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Activity = any;

export interface ProfileSearchResult {
export interface Profile {
actor: Actor;
handle: string;
followerCount: number;
Expand All @@ -12,7 +12,7 @@ export interface ProfileSearchResult {
}

export interface SearchResults {
profiles: ProfileSearchResult[];
profiles: Profile[];
}

export interface GetFollowersForProfileResponse {
Expand Down Expand Up @@ -390,4 +390,10 @@ export class ActivityPubAPI {
profiles: []
};
}

async getProfile(handle: string): Promise<Profile> {
const url = new URL(`.ghost/activitypub/profile/${handle}`, this.apiUrl);
const json = await this.fetchJSON(url);
return json as Profile;
}
}
79 changes: 9 additions & 70 deletions apps/admin-x-activitypub/src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import MainNavigation from './navigation/MainNavigation';
import NiceModal from '@ebay/nice-modal-react';
import ProfileSearchResultModal from './search/ProfileSearchResultModal';

import {useSearchForUser} from '../hooks/useActivityPubQueries';
import {useSearchForUser, useSuggestedProfiles} from '../hooks/useActivityPubQueries';

interface SearchResultItem {
actor: ActorProperties;
Expand Down Expand Up @@ -73,72 +73,8 @@ const SearchResult: React.FC<SearchResultProps> = ({result, update}) => {

const Search: React.FC<SearchProps> = ({}) => {
// Initialise suggested profiles
const [suggested, setSuggested] = useState<SearchResultItem[]>([
{
actor: {
id: 'https://mastodon.social/@quillmatiq',
name: 'Anuj Ahooja',
preferredUsername: '@[email protected]',
image: {
url: 'https://anujahooja.com/assets/images/image12.jpg?v=601ebe30'
},
icon: {
url: 'https://anujahooja.com/assets/images/image12.jpg?v=601ebe30'
}
} as ActorProperties,
handle: '@[email protected]',
followerCount: 436,
followingCount: 634,
isFollowing: false,
posts: []
},
{
actor: {
id: 'https://flipboard.social/@miaq',
name: 'Mia Quagliarello',
preferredUsername: '@[email protected]',
image: {
url: 'https://m-cdn.flipboard.social/accounts/avatars/109/824/428/955/351/328/original/383f288b81ab280c.png'
},
icon: {
url: 'https://m-cdn.flipboard.social/accounts/avatars/109/824/428/955/351/328/original/383f288b81ab280c.png'
}
} as ActorProperties,
handle: '@[email protected]',
followerCount: 533,
followingCount: 335,
isFollowing: false,
posts: []
},
{
actor: {
id: 'https://techpolicy.social/@mallory',
name: 'Mallory',
preferredUsername: '@[email protected]',
image: {
url: 'https://techpolicy.social/system/accounts/avatars/109/378/338/180/403/396/original/20b043b0265cac73.jpeg'
},
icon: {
url: 'https://techpolicy.social/system/accounts/avatars/109/378/338/180/403/396/original/20b043b0265cac73.jpeg'
}
} as ActorProperties,
handle: '@[email protected]',
followerCount: 1100,
followingCount: 11,
isFollowing: false,
posts: []
}
]);

const updateSuggested = (id: string, updated: Partial<SearchResultItem>) => {
const index = suggested.findIndex(result => result.actor.id === id);

setSuggested((current) => {
const newSuggested = [...current];
newSuggested[index] = {...newSuggested[index], ...updated};
return newSuggested;
});
};
const {suggestedProfilesQuery, updateSuggestedProfile} = useSuggestedProfiles('index', ['@[email protected]', '@[email protected]', '@[email protected]']);
const {data: suggested = [], isLoading: isLoadingSuggested} = suggestedProfilesQuery;

// Initialise search query
const queryInputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -220,11 +156,14 @@ const Search: React.FC<SearchProps> = ({}) => {
{showSuggested && (
<>
<span className='mb-1 flex w-full max-w-[560px] font-semibold'>Suggested accounts</span>
{isLoadingSuggested && (
<LoadingIndicator size='sm'/>
)}
{suggested.map(profile => (
<SearchResult
key={profile.actor.id}
result={profile}
update={updateSuggested}
key={(profile as SearchResultItem).actor.id}
result={profile as SearchResultItem}
update={updateSuggestedProfile}
/>
))}
</>
Expand Down
50 changes: 47 additions & 3 deletions apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Activity} from '../components/activities/ActivityItem';
import {ActivityPubAPI, type ProfileSearchResult, type SearchResults} from '../api/activitypub';
import {ActivityPubAPI, type Profile, type SearchResults} from '../api/activitypub';
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
import {useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query';

Expand Down Expand Up @@ -246,15 +246,15 @@ export function useSearchForUser(handle: string, query: string) {
}
});

const updateProfileSearchResult = (id: string, updated: Partial<ProfileSearchResult>) => {
const updateProfileSearchResult = (id: string, updated: Partial<Profile>) => {
queryClient.setQueryData(queryKey, (current: SearchResults | undefined) => {
if (!current) {
return current;
}

return {
...current,
profiles: current.profiles.map((item: ProfileSearchResult) => {
profiles: current.profiles.map((item: Profile) => {
if (item.actor.id === id) {
return {...item, ...updated};
}
Expand Down Expand Up @@ -306,3 +306,47 @@ export function useFollowingForProfile(handle: string) {
}
});
}

export function useSuggestedProfiles(handle: string, handles: string[]) {
const siteUrl = useSiteUrl();
const api = createActivityPubAPI(handle, siteUrl);
const queryClient = useQueryClient();
const queryKey = ['profiles', {handles}];

const suggestedProfilesQuery = useQuery({
queryKey,
async queryFn() {
return Promise.all(
handles.map(h => api.getProfile(h))
);
}
});

const updateSuggestedProfile = (id: string, updated: Partial<Profile>) => {
queryClient.setQueryData(queryKey, (current: Profile[] | undefined) => {
if (!current) {
return current;
}

return current.map((item: Profile) => {
if (item.actor.id === id) {
return {...item, ...updated};
}
return item;
});
});
};

return {suggestedProfilesQuery, updateSuggestedProfile};
}

export function useProfileForUser(handle: string, fullHandle: string) {
const siteUrl = useSiteUrl();
const api = createActivityPubAPI(handle, siteUrl);
return useQuery({
queryKey: [`profile:${fullHandle}`],
async queryFn() {
return api.getProfile(fullHandle);
}
});
}

0 comments on commit f499cec

Please sign in to comment.