mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
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:
parent
5903dd7fb4
commit
12710eaefa
2 changed files with 53 additions and 30 deletions
|
@ -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'>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue