mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Updated activitypub search suggestions to be dynamic (#21202)
refs [TryGhost/ActivityPub#60](https://github.com/TryGhost/ActivityPub/pull/60) Updated activitypub search suggestions to be dynamic
This commit is contained in:
parent
1196688b0e
commit
0d8ea553bd
4 changed files with 101 additions and 75 deletions
|
@ -1091,4 +1091,41 @@ describe('ActivityPubAPI', function () {
|
|||
expect(actual.following).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProfile', function () {
|
||||
test('It returns a profile', async function () {
|
||||
const handle = '@foo@bar.baz';
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
@ -12,7 +12,7 @@ export interface ProfileSearchResult {
|
|||
}
|
||||
|
||||
export interface SearchResults {
|
||||
profiles: ProfileSearchResult[];
|
||||
profiles: Profile[];
|
||||
}
|
||||
|
||||
export interface GetFollowersForProfileResponse {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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: '@quillmatiq@mastodon.social',
|
||||
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: '@quillmatiq@mastodon.social',
|
||||
followerCount: 436,
|
||||
followingCount: 634,
|
||||
isFollowing: false,
|
||||
posts: []
|
||||
},
|
||||
{
|
||||
actor: {
|
||||
id: 'https://flipboard.social/@miaq',
|
||||
name: 'Mia Quagliarello',
|
||||
preferredUsername: '@miaq@flipboard.social',
|
||||
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: '@miaq@flipboard.social',
|
||||
followerCount: 533,
|
||||
followingCount: 335,
|
||||
isFollowing: false,
|
||||
posts: []
|
||||
},
|
||||
{
|
||||
actor: {
|
||||
id: 'https://techpolicy.social/@mallory',
|
||||
name: 'Mallory',
|
||||
preferredUsername: '@mallory@techpolicy.social',
|
||||
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: '@mallory@techpolicy.social',
|
||||
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', ['@quillmatiq@mastodon.social', '@miaq@flipboard.social', '@mallory@techpolicy.social']);
|
||||
const {data: suggested = [], isLoading: isLoadingSuggested} = suggestedProfilesQuery;
|
||||
|
||||
// Initialise search query
|
||||
const queryInputRef = useRef<HTMLInputElement>(null);
|
||||
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -246,7 +246,7 @@ 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;
|
||||
|
@ -254,7 +254,7 @@ export function useSearchForUser(handle: string, query: string) {
|
|||
|
||||
return {
|
||||
...current,
|
||||
profiles: current.profiles.map((item: ProfileSearchResult) => {
|
||||
profiles: current.profiles.map((item: Profile) => {
|
||||
if (item.actor.id === id) {
|
||||
return {...item, ...updated};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue