diff --git a/apps/admin-x-activitypub/package.json b/apps/admin-x-activitypub/package.json index 00b4f5dec7..6f6b49c59b 100644 --- a/apps/admin-x-activitypub/package.json +++ b/apps/admin-x-activitypub/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/admin-x-activitypub", - "version": "0.3.35", + "version": "0.3.36", "license": "MIT", "repository": { "type": "git", diff --git a/apps/admin-x-activitypub/src/components/Activities.tsx b/apps/admin-x-activitypub/src/components/Activities.tsx index 9a86906c25..97e167fc53 100644 --- a/apps/admin-x-activitypub/src/components/Activities.tsx +++ b/apps/admin-x-activitypub/src/components/Activities.tsx @@ -9,14 +9,13 @@ import ArticleModal from './feed/ArticleModal'; import MainNavigation from './navigation/MainNavigation'; import NotificationItem from './activities/NotificationItem'; import Separator from './global/Separator'; -import ViewProfileModal from './modals/ViewProfileModal'; import getUsername from '../utils/get-username'; import stripHtml from '../utils/strip-html'; import truncate from '../utils/truncate'; import {GET_ACTIVITIES_QUERY_KEY_NOTIFICATIONS, useActivitiesForUser} from '../hooks/useActivityPubQueries'; import {type NotificationType} from './activities/NotificationIcon'; -import {useRouting} from '@tryghost/admin-x-framework/routing'; +import {handleProfileClick} from '../utils/handle-profile-click'; interface ActivitiesProps {} @@ -39,15 +38,15 @@ const getExtendedDescription = (activity: GroupedActivity): JSX.Element | null = if (Boolean(activity.type === ACTIVITY_TYPE.CREATE && activity.object?.inReplyTo)) { return (
); } else if (activity.type === ACTIVITY_TYPE.LIKE && !activity.object?.name && activity.object?.content) { return (
); } @@ -114,53 +113,39 @@ const groupActivities = (activities: Activity[]): GroupedActivity[] => { }; const getGroupDescription = (group: GroupedActivity): JSX.Element => { - const actorNames = group.actors.map(actor => actor.name); - const [firstActor, secondActor, ...otherActors] = actorNames; + const [firstActor, secondActor, ...otherActors] = group.actors; const hasOthers = otherActors.length > 0; - let actorText = <>; + const actorClass = 'cursor-pointer font-semibold hover:underline'; + + const actorText = ( + <> + handleProfileClick(firstActor, e)} + >{firstActor.name} + {secondActor && ( + <> + {hasOthers ? ', ' : ' and '} + handleProfileClick(secondActor, e)} + >{secondActor.name} + + )} + {hasOthers && ' and others'} + + ); switch (group.type) { case ACTIVITY_TYPE.FOLLOW: - actorText = ( - <> - {firstActor} - {secondActor && ` and `} - {secondActor && {secondActor}} - {hasOthers && ' and others'} - - ); - - return ( - <> - {actorText} started following you - - ); + return <>{actorText} started following you; case ACTIVITY_TYPE.LIKE: - const postType = group.object?.type === 'Article' ? 'post' : 'note'; - actorText = ( - <> - {firstActor} - {secondActor && ( - <> - {hasOthers ? ', ' : ' and '} - {secondActor} - - )} - {hasOthers && ' and others'} - - ); - - return ( - <> - {actorText} liked your {postType}{' '} - {group.object?.name || ''} - - ); + return <>{actorText} liked your post {group.object?.name || ''}; case ACTIVITY_TYPE.CREATE: if (group.object?.inReplyTo && typeof group.object?.inReplyTo !== 'string') { const content = stripHtml(group.object.inReplyTo.name); - return <>{group.actors[0].name} replied to your post {truncate(content, 80)}; + return <>{actorText} replied to your post {truncate(content, 80)}; } } return <>; @@ -179,8 +164,6 @@ const Activities: React.FC = ({}) => { const maxAvatars = 5; - const {updateRoute} = useRouting(); - const {getActivitiesQuery} = useActivitiesForUser({ handle: user, includeOwn: true, @@ -190,6 +173,7 @@ const Activities: React.FC = ({}) => { }, key: GET_ACTIVITIES_QUERY_KEY_NOTIFICATIONS }); + const {data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading} = getActivitiesQuery; const groupedActivities = (data?.pages.flatMap((page) => { const filtered = page.data.filter((activity, index, self) => index === self.findIndex(a => a.id === activity.id)); @@ -222,7 +206,7 @@ const Activities: React.FC = ({}) => { }; }, [hasNextPage, isFetchingNextPage, fetchNextPage]); - const handleActivityClick = (group: GroupedActivity) => { + const handleActivityClick = (group: GroupedActivity, index: number) => { switch (group.type) { case ACTIVITY_TYPE.CREATE: NiceModal.show(ArticleModal, { @@ -238,16 +222,14 @@ const Activities: React.FC = ({}) => { activityId: group.id, object: group.object, actor: group.object.attributedTo as ActorProperties, - width: 'wide' + width: group.object?.type === 'Article' ? 'wide' : 'narrow' }); break; case ACTIVITY_TYPE.FOLLOW: if (group.actors.length > 1) { - updateRoute('profile'); + toggleOpen(group.id || `${group.type}_${index}`); } else { - NiceModal.show(ViewProfileModal, { - profile: getUsername(group.actors[0]) - }); + handleProfileClick(group.actors[0]); } break; } @@ -278,7 +260,7 @@ const Activities: React.FC = ({}) => { handleActivityClick(group)} + onClick={() => handleActivityClick(group, index)} > @@ -301,7 +283,7 @@ const Activities: React.FC = ({}) => { {group.actors.length > 1 && (
-
+
{openStates[group.id || `${group.type}_${index}`] && group.actors.length > 1 && (
{group.actors.map(actor => ( -
+
handleProfileClick(actor, e)} + > {actor.name} {getUsername(actor)} diff --git a/apps/admin-x-activitypub/src/components/Inbox.tsx b/apps/admin-x-activitypub/src/components/Inbox.tsx index c49fb98696..4fb7fc7537 100644 --- a/apps/admin-x-activitypub/src/components/Inbox.tsx +++ b/apps/admin-x-activitypub/src/components/Inbox.tsx @@ -7,7 +7,6 @@ import NewPostModal from './modals/NewPostModal'; import NiceModal from '@ebay/nice-modal-react'; import React, {useEffect, useRef} from 'react'; import Separator from './global/Separator'; -import ViewProfileModal from './modals/ViewProfileModal'; import getName from '../utils/get-name'; import getUsername from '../utils/get-username'; import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub'; @@ -19,6 +18,7 @@ import { useSuggestedProfiles, useUserDataForUser } from '../hooks/useActivityPubQueries'; +import {handleProfileClick} from '../utils/handle-profile-click'; import {handleViewContent} from '../utils/content-handlers'; import {useRouting} from '@tryghost/admin-x-framework/routing'; @@ -149,9 +149,9 @@ const Inbox: React.FC = ({layout}) => { return (
  • - NiceModal.show(ViewProfileModal, { - profile: getUsername(actor) - })}> + handleProfileClick(actor)} + >
    {getName(actor)} diff --git a/apps/admin-x-activitypub/src/components/Profile.tsx b/apps/admin-x-activitypub/src/components/Profile.tsx index f91cd0e532..ea851516e1 100644 --- a/apps/admin-x-activitypub/src/components/Profile.tsx +++ b/apps/admin-x-activitypub/src/components/Profile.tsx @@ -194,7 +194,6 @@ const FollowingTab: React.FC = () => { handleUserClick(item)} > @@ -231,7 +230,6 @@ const FollowersTab: React.FC = () => { handleUserClick(item)} > diff --git a/apps/admin-x-activitypub/src/components/activities/NotificationIcon.tsx b/apps/admin-x-activitypub/src/components/activities/NotificationIcon.tsx index 1cc673fffd..dc4a73e4bf 100644 --- a/apps/admin-x-activitypub/src/components/activities/NotificationIcon.tsx +++ b/apps/admin-x-activitypub/src/components/activities/NotificationIcon.tsx @@ -4,7 +4,7 @@ import {Icon} from '@tryghost/admin-x-design-system'; export type NotificationType = 'like' | 'follow' | 'reply'; interface NotificationIconProps { - notificationType: 'like' | 'follow' | 'reply'; + notificationType: NotificationType; className?: string; } @@ -32,7 +32,7 @@ const NotificationIcon: React.FC = ({notificationType, cl } return ( -
    +
    ); diff --git a/apps/admin-x-activitypub/src/components/activities/NotificationItem.tsx b/apps/admin-x-activitypub/src/components/activities/NotificationItem.tsx index 64843a2a80..1e293b6ec1 100644 --- a/apps/admin-x-activitypub/src/components/activities/NotificationItem.tsx +++ b/apps/admin-x-activitypub/src/components/activities/NotificationItem.tsx @@ -20,9 +20,12 @@ interface NotificationItemProps { const NotificationItem = ({children, onClick, url, className}: NotificationItemProps) => { return ( - +
    ); }; diff --git a/apps/admin-x-activitypub/src/components/feed/FeedItem.tsx b/apps/admin-x-activitypub/src/components/feed/FeedItem.tsx index da17f43025..a8f8194216 100644 --- a/apps/admin-x-activitypub/src/components/feed/FeedItem.tsx +++ b/apps/admin-x-activitypub/src/components/feed/FeedItem.tsx @@ -10,6 +10,7 @@ import getReadingTime from '../../utils/get-reading-time'; import getRelativeTimestamp from '../../utils/get-relative-timestamp'; import getUsername from '../../utils/get-username'; import stripHtml from '../../utils/strip-html'; +import {handleProfileClick} from '../../utils/handle-profile-click'; import {renderTimestamp} from '../../utils/render-timestamp'; function getAttachment(object: ObjectProperties) { @@ -241,9 +242,18 @@ const FeedItem: React.FC = ({actor, object, layout, type, comment
    - {author.name} + handleProfileClick(actor, e)} + > + {author.name} +
    - {getUsername(author)} + handleProfileClick(actor, e)} + > + {getUsername(author)} +
    {renderTimestamp(object)}
    @@ -400,7 +410,12 @@ const FeedItem: React.FC = ({actor, object, layout, type, comment
    - {author.name} + handleProfileClick(actor, e)} + >{author.name} + {getRelativeTimestamp(date)}
    diff --git a/apps/admin-x-activitypub/src/components/global/APAvatar.tsx b/apps/admin-x-activitypub/src/components/global/APAvatar.tsx index 594ebc2d0d..9de312ddaa 100644 --- a/apps/admin-x-activitypub/src/components/global/APAvatar.tsx +++ b/apps/admin-x-activitypub/src/components/global/APAvatar.tsx @@ -1,40 +1,30 @@ +import NiceModal from '@ebay/nice-modal-react'; import React, {useEffect, useState} from 'react'; +import ViewProfileModal from '../modals/ViewProfileModal'; import clsx from 'clsx'; import getUsername from '../../utils/get-username'; import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub'; import {Icon} from '@tryghost/admin-x-design-system'; type AvatarSize = '2xs' | 'xs' | 'sm' | 'lg' | 'notification'; -export type AvatarBadge = 'user-fill' | 'heart-fill' | 'comment-fill' | undefined; interface APAvatarProps { - author?: ActorProperties; + author: ActorProperties | undefined; size?: AvatarSize; - badge?: AvatarBadge; } -const APAvatar: React.FC = ({author, size, badge}) => { +const APAvatar: React.FC = ({author, size}) => { let iconSize = 18; - let containerClass = 'shrink-0 items-center justify-center relative z-10 flex'; + let containerClass = `shrink-0 items-center justify-center relative cursor-pointer z-10 flex ${size === 'lg' ? '' : 'hover:opacity-80'}`; let imageClass = 'z-10 rounded-md w-10 h-10 object-cover'; - const badgeClass = `w-6 h-6 z-20 rounded-full absolute -bottom-2 -right-[0.6rem] border-2 border-white content-box flex items-center justify-center`; - let badgeColor = ''; const [iconUrl, setIconUrl] = useState(author?.icon?.url); useEffect(() => { setIconUrl(author?.icon?.url); }, [author?.icon?.url]); - switch (badge) { - case 'user-fill': - badgeColor = 'bg-blue-500'; - break; - case 'heart-fill': - badgeColor = 'bg-red-500'; - break; - case 'comment-fill': - badgeColor = 'bg-purple-500'; - break; + if (!author) { + return null; } switch (size) { @@ -69,37 +59,42 @@ const APAvatar: React.FC = ({author, size, badge}) => { containerClass = clsx(containerClass, 'bg-grey-100'); } - const BadgeElement = badge && ( -
    - -
    - ); + const onClick = (e: React.MouseEvent) => { + e.stopPropagation(); + NiceModal.show(ViewProfileModal, { + profile: getUsername(author as ActorProperties) + }); + }; + + const title = `${author?.name} ${getUsername(author as ActorProperties)}`; if (iconUrl) { return ( - + ); } return ( -
    +
    - {BadgeElement}
    ); }; diff --git a/apps/admin-x-activitypub/src/components/modals/ViewProfileModal.tsx b/apps/admin-x-activitypub/src/components/modals/ViewProfileModal.tsx index 6f4a35b1a6..f600a26068 100644 --- a/apps/admin-x-activitypub/src/components/modals/ViewProfileModal.tsx +++ b/apps/admin-x-activitypub/src/components/modals/ViewProfileModal.tsx @@ -16,6 +16,7 @@ import FollowButton from '../global/FollowButton'; import Separator from '../global/Separator'; import getName from '../../utils/get-name'; import getUsername from '../../utils/get-username'; +import {handleProfileClick} from '../../utils/handle-profile-click'; const noop = () => {}; @@ -83,7 +84,9 @@ const ActorList: React.FC = ({ {actors.map(({actor, isFollowing}, index) => { return ( - + handleProfileClick(actor)} + >
    diff --git a/apps/admin-x-activitypub/src/utils/handle-profile-click.ts b/apps/admin-x-activitypub/src/utils/handle-profile-click.ts new file mode 100644 index 0000000000..947e7a04a3 --- /dev/null +++ b/apps/admin-x-activitypub/src/utils/handle-profile-click.ts @@ -0,0 +1,11 @@ +import NiceModal from '@ebay/nice-modal-react'; +import ViewProfileModal from '../components/modals/ViewProfileModal'; +import getUsername from './get-username'; +import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub'; + +export const handleProfileClick = (actor: ActorProperties, e?: React.MouseEvent) => { + e?.stopPropagation(); + NiceModal.show(ViewProfileModal, { + profile: getUsername(actor) + }); +};