mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-22 19:32:54 +03:00
Wired up ActivityPubAPI w/ ReactQuery
This is a bit of a stopgap, we'll want to eventually pull these hooks out into a shared file, but for now this is fine. This almost decouples us from admin-x-framework, but we're still using it to get the site url as well as some types that we can pull out later.
This commit is contained in:
parent
c6e407fb7e
commit
bfd3ee1209
@ -1,65 +1,48 @@
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import {ActivityPubAPI} from '../api/activitypub';
|
||||
import {Modal, TextField, showToast} from '@tryghost/admin-x-design-system';
|
||||
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
|
||||
import {useFollow} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {useQueryClient} from '@tryghost/admin-x-framework';
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
import {useState} from 'react';
|
||||
|
||||
// const sleep = (ms: number) => (
|
||||
// new Promise((resolve) => {
|
||||
// setTimeout(resolve, ms);
|
||||
// })
|
||||
// );
|
||||
function useFollow(handle: string, onSuccess: () => void, onError: () => void) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useMutation({
|
||||
async mutationFn(username: string) {
|
||||
return api.follow(username);
|
||||
},
|
||||
onSuccess,
|
||||
onError
|
||||
});
|
||||
}
|
||||
|
||||
const FollowSite = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
const mutation = useFollow();
|
||||
const client = useQueryClient();
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
|
||||
// mutation.isPending
|
||||
// mutation.isError
|
||||
// mutation.isSuccess
|
||||
// mutation.mutate({username: '@index@site.com'})
|
||||
// mutation.reset();
|
||||
|
||||
// State to manage the text field value
|
||||
const [profileName, setProfileName] = useState('');
|
||||
// const [success, setSuccess] = useState(false);
|
||||
const [errorMessage, setError] = useState(null);
|
||||
|
||||
const handleFollow = async () => {
|
||||
try {
|
||||
const url = new URL(`.ghost/activitypub/actions/follow/${profileName}`, siteUrl);
|
||||
await fetch(url, {
|
||||
method: 'POST'
|
||||
});
|
||||
// Perform the mutation
|
||||
// If successful, set the success state to true
|
||||
// setSuccess(true);
|
||||
showToast({
|
||||
message: 'Site followed',
|
||||
type: 'success'
|
||||
});
|
||||
async function onSuccess() {
|
||||
showToast({
|
||||
message: 'Site followed',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// // Because we don't return the new follower data from the API, we need to wait a bit to let it process and then update the query.
|
||||
// // This is a dirty hack and should be replaced with a better solution.
|
||||
// await sleep(2000);
|
||||
|
||||
modal.remove();
|
||||
// Refetch the following data.
|
||||
// At this point it might not be updated yet, but it will be eventually.
|
||||
await client.refetchQueries({queryKey: ['FollowingResponseData'], type: 'active'});
|
||||
updateRoute('');
|
||||
} catch (error) {
|
||||
// If there's an error, set the error state
|
||||
setError(errorMessage);
|
||||
}
|
||||
};
|
||||
modal.remove();
|
||||
updateRoute('');
|
||||
}
|
||||
async function onError() {
|
||||
setError(errorMessage);
|
||||
}
|
||||
const mutation = useFollow('index', onSuccess, onError);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -71,7 +54,7 @@ const FollowSite = NiceModal.create(() => {
|
||||
okLabel='Follow'
|
||||
size='sm'
|
||||
title='Follow a Ghost site'
|
||||
onOk={handleFollow}
|
||||
onOk={() => mutation.mutate(profileName)}
|
||||
>
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<TextField
|
||||
|
@ -1,10 +1,11 @@
|
||||
// import NiceModal from '@ebay/nice-modal-react';
|
||||
import ActivityPubWelcomeImage from '../assets/images/ap-welcome.png';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import articleBodyStyles from './articleBodyStyles';
|
||||
import {ActorProperties, ObjectProperties, useBrowseFollowersForUser, useBrowseFollowingForUser, useBrowseInboxForUser} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {ActivityPubAPI} from '../api/activitypub';
|
||||
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {Avatar, Button, ButtonGroup, Heading, List, ListItem, Page, SelectOption, SettingValue, ViewContainer, ViewTab} from '@tryghost/admin-x-design-system';
|
||||
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
interface ViewArticleProps {
|
||||
@ -12,13 +13,64 @@ interface ViewArticleProps {
|
||||
onBackToList: () => void;
|
||||
}
|
||||
|
||||
function useBrowseInboxForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`inbox:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getInbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function useFollowersCountForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`followersCount:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getFollowersCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function useFollowingCountForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`followingCount:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getFollowingCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ActivityPubComponent: React.FC = () => {
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
// TODO: Replace with actual user ID
|
||||
const {data: {items: activities = []} = {}} = useBrowseInboxForUser('index');
|
||||
const {data: {totalItems: followingCount = 0} = {}} = useBrowseFollowingForUser('index');
|
||||
const {data: {totalItems: followersCount = 0} = {}} = useBrowseFollowersForUser('index');
|
||||
const {data: activities = []} = useBrowseInboxForUser('index');
|
||||
const {data: followersCount = 0} = useFollowersCountForUser('index');
|
||||
const {data: followingCount = 0} = useFollowingCountForUser('index');
|
||||
|
||||
const [articleContent, setArticleContent] = useState<ObjectProperties | null>(null);
|
||||
const [, setArticleActor] = useState<ActorProperties | null>(null);
|
||||
|
@ -1,21 +1,50 @@
|
||||
import {} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import getUsername from '../utils/get-username';
|
||||
import {ActivityPubAPI} from '../api/activitypub';
|
||||
import {Avatar, Button, List, ListItem, Modal} from '@tryghost/admin-x-design-system';
|
||||
import {FollowingResponseData, useBrowseFollowersForUser, useFollow} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
|
||||
import {useMutation, useQuery} from '@tanstack/react-query';
|
||||
|
||||
interface ViewFollowersModalProps {
|
||||
following: FollowingResponseData[],
|
||||
animate?: boolean
|
||||
function useFollowersForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`followers:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getFollowers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ViewFollowersModal: React.FC<RoutingModalProps & ViewFollowersModalProps> = ({}) => {
|
||||
function useFollow(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useMutation({
|
||||
async mutationFn(username: string) {
|
||||
return api.follow(username);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ViewFollowersModal: React.FC<RoutingModalProps> = ({}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
// const modal = NiceModal.useModal();
|
||||
const mutation = useFollow();
|
||||
const mutation = useFollow('index');
|
||||
|
||||
const {data: {items = []} = {}} = useBrowseFollowersForUser('inbox');
|
||||
const {data: items = []} = useFollowersForUser('index');
|
||||
|
||||
const followers = Array.isArray(items) ? items : [items];
|
||||
return (
|
||||
@ -34,7 +63,7 @@ const ViewFollowersModal: React.FC<RoutingModalProps & ViewFollowersModalProps>
|
||||
<div className='mt-3 flex flex-col gap-4 pb-12'>
|
||||
<List>
|
||||
{followers.map(item => (
|
||||
<ListItem action={<Button color='grey' label='Follow back' link={true} onClick={() => mutation.mutate({username: getUsername(item)})} />} avatar={<Avatar image={item.icon} size='sm' />} detail={getUsername(item)} id='list-item' title={item.name}></ListItem>
|
||||
<ListItem action={<Button color='grey' label='Follow back' link={true} onClick={() => mutation.mutate(getUsername(item))} />} avatar={<Avatar image={item.icon} size='sm' />} detail={getUsername(item)} id='list-item' title={item.name}></ListItem>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
|
@ -1,26 +1,37 @@
|
||||
import {} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import getUsername from '../utils/get-username';
|
||||
import {ActivityPubAPI} from '../api/activitypub';
|
||||
import {Avatar, Button, List, ListItem, Modal} from '@tryghost/admin-x-design-system';
|
||||
import {FollowingResponseData, useBrowseFollowingForUser, useUnfollow} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
|
||||
interface ViewFollowingModalProps {
|
||||
following: FollowingResponseData[],
|
||||
animate?: boolean
|
||||
function useFollowingForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`following:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getFollowing();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ViewFollowingModal: React.FC<RoutingModalProps & ViewFollowingModalProps> = ({}) => {
|
||||
const ViewFollowingModal: React.FC<RoutingModalProps> = ({}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const mutation = useUnfollow();
|
||||
|
||||
const {data: {items = []} = {}} = useBrowseFollowingForUser('inbox');
|
||||
const {data: items = []} = useFollowingForUser('index');
|
||||
|
||||
const following = Array.isArray(items) ? items : [items];
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
mutation.reset();
|
||||
updateRoute('');
|
||||
}}
|
||||
cancelLabel=''
|
||||
@ -33,7 +44,7 @@ const ViewFollowingModal: React.FC<RoutingModalProps & ViewFollowingModalProps>
|
||||
<div className='mt-3 flex flex-col gap-4 pb-12'>
|
||||
<List>
|
||||
{following.map(item => (
|
||||
<ListItem action={<Button color='grey' label='Unfollow' link={true} onClick={() => mutation.mutate({username: getUsername(item)})} />} avatar={<Avatar image={item.icon} size='sm' />} detail={getUsername(item)} id='list-item' title={item.name}></ListItem>
|
||||
<ListItem action={<Button color='grey' label='Unfollow' link={true} />} avatar={<Avatar image={item.icon} size='sm' />} detail={getUsername(item)} id='list-item' title={item.name}></ListItem>
|
||||
))}
|
||||
</List>
|
||||
{/* <Table>
|
||||
|
Loading…
Reference in New Issue
Block a user