Added open reply notification in sidebar functionality (#21014)

no refs

Added logic to open the notification in the sidebar when the
notification is clicked if it is a reply notification
This commit is contained in:
Michael Barrett 2024-09-16 16:49:52 +01:00 committed by GitHub
parent 5903dd7fb4
commit 12710eaefa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 30 deletions

View File

@ -1,9 +1,12 @@
import NiceModal from '@ebay/nice-modal-react';
import React from 'react';
import {Button, NoValueLabel} from '@tryghost/admin-x-design-system';
import {ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
import APAvatar, {AvatarBadge} from './global/APAvatar';
import ActivityItem from './activities/ActivityItem';
import ActivityItem, {type Activity} from './activities/ActivityItem';
import ArticleModal from './feed/ArticleModal';
import MainNavigation from './navigation/MainNavigation';
import {Button, NoValueLabel} from '@tryghost/admin-x-design-system';
import getUsername from '../utils/get-username';
import {useBrowseInboxForUser, useBrowseOutboxForUser, useFollowersForUser} from '../MainContent';
@ -18,28 +21,7 @@ enum ACTVITY_TYPE {
FOLLOW = 'Follow'
}
type Actor = {
id: string
name: string
preferredUsername: string
url: string
}
type ActivityObject = {
name: string
url: string
inReplyTo: string | null
content: string
}
type Activity = {
id: string
type: ACTVITY_TYPE
object?: ActivityObject
actor: Actor
}
const getActivityDescription = (activity: Activity, activityObjectsMap: Map<string, ActivityObject>): string => {
const getActivityDescription = (activity: Activity, activityObjectsMap: Map<string, ObjectProperties>): string => {
switch (activity.type) {
case ACTVITY_TYPE.CREATE:
const object = activityObjectsMap.get(activity.object?.inReplyTo || '');
@ -76,7 +58,7 @@ const getExtendedDescription = (activity: Activity): JSX.Element | null => {
const getActivityUrl = (activity: Activity): string | null => {
if (activity.object) {
return activity.object.url;
return activity.object.url || null;
}
return null;
@ -119,7 +101,7 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
// This allows us to quickly look up an object associated with an activity
// We could just make a http request to get the object, but this is more
// efficient seeming though we already have the data in the inbox and outbox
const activityObjectsMap = new Map<string, ActivityObject>();
const activityObjectsMap = new Map<string, ObjectProperties>();
outboxActivities.forEach((activity) => {
if (activity.object) {
@ -161,6 +143,24 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
// to show the most recent activities first
.reverse();
// Create a map of activity comments, grouping them by the parent activity
// This allows us to quickly look up all comments for a given activity
const commentsMap = new Map<string, Activity[]>();
for (const activity of activities) {
if (activity.type === ACTVITY_TYPE.CREATE && activity.object?.inReplyTo) {
const comments = commentsMap.get(activity.object.inReplyTo) ?? [];
comments.push(activity);
commentsMap.set(activity.object.inReplyTo, comments.reverse());
}
}
const getCommentsForObject = (id: string) => {
return commentsMap.get(id) ?? [];
};
// Retrieve followers for the user
const {data: followers = []} = useFollowersForUser(user);
@ -182,7 +182,20 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
{activities.length > 0 && (
<div className='mt-8 flex w-full max-w-[560px] flex-col'>
{activities?.map(activity => (
<ActivityItem key={activity.id} url={getActivityUrl(activity) || getActorUrl(activity)}>
<ActivityItem
key={activity.id}
url={getActivityUrl(activity) || getActorUrl(activity)}
onClick={
activity.type === ACTVITY_TYPE.CREATE ? () => {
NiceModal.show(ArticleModal, {
object: activity.object,
actor: activity.actor,
comments: getCommentsForObject(activity.object.id),
allComments: commentsMap
});
} : undefined
}
>
<APAvatar author={activity.actor} badge={getActivityBadge(activity)} />
<div className='pt-[2px]'>
<div className='text-grey-600'>

View File

@ -13,13 +13,18 @@ export type Activity = {
interface ActivityItemProps {
children?: ReactNode;
url?: string | null;
onClick?: () => void;
}
const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null}) => {
const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null, onClick}) => {
const childrenArray = React.Children.toArray(children);
const Item = (
<div className='flex w-full max-w-[560px] flex-col hover:bg-grey-75'>
<div className='flex w-full max-w-[560px] flex-col hover:bg-grey-75' onClick={() => {
if (!url && onClick) {
onClick();
}
}}>
<div className='flex w-full gap-4 border-b border-grey-100 px-2 py-4'>
{childrenArray[0]}
{childrenArray[1]}
@ -30,7 +35,12 @@ const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null}) => {
if (url) {
return (
<a href={url} rel='noreferrer' target='_blank'>
<a href={url} rel='noreferrer' target='_blank' onClick={(e) => {
if (onClick) {
e.preventDefault();
onClick();
}
}}>
{Item}
</a>
);