0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Updated thread implementation to work with posts in admin-x-activitypub (#22301)

refs
[AP-721](https://linear.app/ghost/issue/AP-721/update-getactivitythread-to-work-with-posts-instead-of-activities)

Updated thread implementation to work with posts instead of activities
in `admin-x-activitypub` as part of the posts migration
This commit is contained in:
Michael Barrett 2025-02-27 20:13:37 +00:00 committed by GitHub
parent b5f1a414b6
commit b1c496f46d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 22 additions and 19 deletions

View file

@ -38,8 +38,8 @@ export interface SearchResults {
accounts: AccountSearchResult[]; accounts: AccountSearchResult[];
} }
export interface ActivityThread { export interface Thread {
items: Activity[]; posts: Post[];
} }
export type ActivityPubCollectionResponse<T> = {data: T[], next: string | null}; export type ActivityPubCollectionResponse<T> = {data: T[], next: string | null};
@ -426,10 +426,10 @@ export class ActivityPubAPI {
}; };
} }
async getThread(id: string): Promise<ActivityThread> { async getThread(id: string): Promise<Thread> {
const url = new URL(`.ghost/activitypub/thread/${encodeURIComponent(id)}`, this.apiUrl); const url = new URL(`.ghost/activitypub/thread/${encodeURIComponent(id)}`, this.apiUrl);
const json = await this.fetchJSON(url); const json = await this.fetchJSON(url);
return json as ActivityThread; return json as Thread;
} }
get accountApiUrl() { get accountApiUrl() {

View file

@ -379,10 +379,10 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
const [isFocused] = useState(focusReply ? 1 : 0); const [isFocused] = useState(focusReply ? 1 : 0);
const {threadQuery, addToThread} = useThreadForUser('index', activityId); const {threadQuery, addToThread} = useThreadForUser('index', activityId);
const {data: activityThread, isLoading: isLoadingThread} = threadQuery; const {data: thread, isLoading: isLoadingThread} = threadQuery;
const activtyThreadActivityIdx = (activityThread?.items ?? []).findIndex(item => item.object.id === activityId); const threadPostIdx = (thread?.posts ?? []).findIndex(item => item.object.id === activityId);
const activityThreadChildren = (activityThread?.items ?? []).slice(activtyThreadActivityIdx + 1); const threadChildren = (thread?.posts ?? []).slice(threadPostIdx + 1);
const activityThreadParents = (activityThread?.items ?? []).slice(0, activtyThreadActivityIdx); const threadParents = (thread?.posts ?? []).slice(0, threadPostIdx);
const modalSize = width === 'narrow' ? MODAL_SIZE_SM : MODAL_SIZE_LG; const modalSize = width === 'narrow' ? MODAL_SIZE_SM : MODAL_SIZE_LG;
const modal = useModal(); const modal = useModal();
@ -720,7 +720,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
gridTemplateColumns: `1fr minmax(0,${currentGridWidth}) 1fr` gridTemplateColumns: `1fr minmax(0,${currentGridWidth}) 1fr`
} : undefined} } : undefined}
> >
{(canNavigateBack || (activityThreadParents.length > 0)) ? ( {(canNavigateBack || (threadParents.length > 0)) ? (
<div className='col-[1/2] flex items-center justify-between'> <div className='col-[1/2] flex items-center justify-between'>
<Button className='transition-color flex h-10 w-10 items-center justify-center rounded-full bg-white hover:bg-gray-100' icon='arrow-left' size='sm' unstyled onClick={navigateBack}/> <Button className='transition-color flex h-10 w-10 items-center justify-center rounded-full bg-white hover:bg-gray-100' icon='arrow-left' size='sm' unstyled onClick={navigateBack}/>
</div> </div>
@ -853,7 +853,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
)} )}
<div className='grow overflow-y-auto'> <div className='grow overflow-y-auto'>
<div className={`mx-auto px-8 pb-10 pt-5`} style={{maxWidth: currentMaxWidth}}> <div className={`mx-auto px-8 pb-10 pt-5`} style={{maxWidth: currentMaxWidth}}>
{activityThreadParents.map((item) => { {threadParents.map((item) => {
return ( return (
<> <>
<FeedItem <FeedItem
@ -883,7 +883,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
layout={'modal'} layout={'modal'}
object={object} object={object}
repostCount={object.repostCount ?? 0} repostCount={object.repostCount ?? 0}
showHeader={(canNavigateBack || (activityThreadParents.length > 0))} showHeader={(canNavigateBack || (threadParents.length > 0))}
type='Note' type='Note'
onCommentClick={() => { onCommentClick={() => {
repliesRef.current?.scrollIntoView({ repliesRef.current?.scrollIntoView({
@ -938,8 +938,8 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
{isLoadingThread && <LoadingIndicator size='lg' />} {isLoadingThread && <LoadingIndicator size='lg' />}
<div ref={repliesRef}> <div ref={repliesRef}>
{activityThreadChildren.map((item, index) => { {threadChildren.map((item, index) => {
const showDivider = index !== activityThreadChildren.length - 1; const showDivider = index !== threadChildren.length - 1;
return ( return (
<React.Fragment key={item.id}> <React.Fragment key={item.id}>

View file

@ -3,7 +3,6 @@ import {
type AccountSearchResult, type AccountSearchResult,
ActivityPubAPI, ActivityPubAPI,
ActivityPubCollectionResponse, ActivityPubCollectionResponse,
ActivityThread,
FollowAccount, FollowAccount,
type GetAccountFollowsResponse, type GetAccountFollowsResponse,
type Profile, type Profile,
@ -591,19 +590,23 @@ export function useThreadForUser(handle: string, id: string) {
const siteUrl = await getSiteUrl(); const siteUrl = await getSiteUrl();
const api = createActivityPubAPI(handle, siteUrl); const api = createActivityPubAPI(handle, siteUrl);
return api.getThread(id); return api.getThread(id).then((response) => {
return {
posts: response.posts.map(mapPostToActivity)
};
});
} }
}); });
const addToThread = (activity: Activity) => { const addToThread = (activity: Activity) => {
// Add the activity to the thread stored in the thread query cache // Add the activity to the thread stored in the thread query cache
queryClient.setQueryData(queryKey, (current: ActivityThread | undefined) => { queryClient.setQueryData(queryKey, (current: {posts: Activity[]} | undefined) => {
if (!current) { if (!current) {
return current; return current;
} }
return { return {
items: [...current.items, activity] posts: [...current.posts, activity]
}; };
}); });
}; };

View file

@ -30,7 +30,7 @@ const Inbox: React.FC = () => {
const activities = (data?.pages.flatMap(page => page.posts) ?? Array.from({length: 5}, (_, index) => ({id: `placeholder-${index}`, object: {}}))); const activities = (data?.pages.flatMap(page => page.posts) ?? Array.from({length: 5}, (_, index) => ({id: `placeholder-${index}`, object: {}})));
const observerRef = useRef<IntersectionObserver | null>(null); const observerRef = useRef<IntersectionObserver | null>(null);
const loadMoreRef = useRef<HTMLLIElement | null>(null); const loadMoreRef = useRef<HTMLDivElement | null>(null);
const endLoadMoreRef = useRef<HTMLDivElement | null>(null); const endLoadMoreRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
@ -103,7 +103,7 @@ const Inbox: React.FC = () => {
<Separator /> <Separator />
)} )}
{index === loadMoreIndex && ( {index === loadMoreIndex && (
<li ref={loadMoreRef} className='h-1'></li> <div ref={loadMoreRef} className='h-1'></div>
)} )}
</li> </li>
))} ))}