mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Added notification for reposts (#22109)
ref https://linear.app/ghost/issue/AP-695/show-a-notification-when-someone-reposts-your-content - When someone reposts your post or note, you’ll now receive a notification. If multiple accounts repost the same piece of content, those notifications will be grouped together, but only if they’re fetched as the same page of notifications. - Converted functions to React components - Bumped the package
This commit is contained in:
parent
2e2704427c
commit
a7aa59de72
5 changed files with 77 additions and 42 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@tryghost/admin-x-activitypub",
|
||||
"version": "0.3.58",
|
||||
"version": "0.3.59",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Activities from './components/Activities';
|
||||
import Inbox from './components/Inbox';
|
||||
import Notifications from './components/Notifications';
|
||||
import Profile from './components/Profile';
|
||||
import Search from './components/Search';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
@ -10,8 +10,8 @@ const MainContent = () => {
|
|||
switch (mainRoute) {
|
||||
case 'search':
|
||||
return <Search />;
|
||||
case 'activity':
|
||||
return <Activities />;
|
||||
case 'notifications':
|
||||
return <Notifications />;
|
||||
case 'profile':
|
||||
return <Profile />;
|
||||
default:
|
||||
|
|
|
@ -21,13 +21,14 @@ import {
|
|||
import {type NotificationType} from './activities/NotificationIcon';
|
||||
import {handleProfileClick} from '../utils/handle-profile-click';
|
||||
|
||||
interface ActivitiesProps {}
|
||||
interface NotificationsProps {}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
enum ACTIVITY_TYPE {
|
||||
CREATE = 'Create',
|
||||
LIKE = 'Like',
|
||||
FOLLOW = 'Follow'
|
||||
FOLLOW = 'Follow',
|
||||
REPOST = 'Announce'
|
||||
}
|
||||
|
||||
interface GroupedActivity {
|
||||
|
@ -37,26 +38,9 @@ interface GroupedActivity {
|
|||
id?: string;
|
||||
}
|
||||
|
||||
const getExtendedDescription = (activity: GroupedActivity): JSX.Element | null => {
|
||||
// If the activity is a reply
|
||||
if (Boolean(activity.type === ACTIVITY_TYPE.CREATE && activity.object?.inReplyTo)) {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{__html: stripHtml(activity.object?.content || '')}}
|
||||
className='ap-note-content mt-1 line-clamp-2 text-pretty text-grey-700'
|
||||
/>
|
||||
);
|
||||
} else if (activity.type === ACTIVITY_TYPE.LIKE && !activity.object?.name && activity.object?.content) {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{__html: stripHtml(activity.object?.content || '')}}
|
||||
className='ap-note-content mt-1 line-clamp-2 text-pretty text-grey-700'
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
interface NotificationGroupDescriptionProps {
|
||||
group: GroupedActivity;
|
||||
}
|
||||
|
||||
const getActivityBadge = (activity: GroupedActivity): NotificationType => {
|
||||
switch (activity.type) {
|
||||
|
@ -65,12 +49,10 @@ const getActivityBadge = (activity: GroupedActivity): NotificationType => {
|
|||
case ACTIVITY_TYPE.FOLLOW:
|
||||
return 'follow';
|
||||
case ACTIVITY_TYPE.LIKE:
|
||||
if (activity.object) {
|
||||
return 'like';
|
||||
}
|
||||
return 'like';
|
||||
case ACTIVITY_TYPE.REPOST:
|
||||
return 'repost';
|
||||
}
|
||||
|
||||
return 'like';
|
||||
};
|
||||
|
||||
const groupActivities = (activities: Activity[]): GroupedActivity[] => {
|
||||
|
@ -91,6 +73,12 @@ const groupActivities = (activities: Activity[]): GroupedActivity[] => {
|
|||
groupKey = `like_${activity.object.id}`;
|
||||
}
|
||||
break;
|
||||
case ACTIVITY_TYPE.REPOST:
|
||||
if (activity.object?.id) {
|
||||
// Group reposts by the target object
|
||||
groupKey = `announce_${activity.object.id}`;
|
||||
}
|
||||
break;
|
||||
case ACTIVITY_TYPE.CREATE:
|
||||
// Don't group creates/replies
|
||||
groupKey = `create_${activity.id}`;
|
||||
|
@ -116,7 +104,7 @@ const groupActivities = (activities: Activity[]): GroupedActivity[] => {
|
|||
return Object.values(groups);
|
||||
};
|
||||
|
||||
const getGroupDescription = (group: GroupedActivity): JSX.Element => {
|
||||
const NotificationGroupDescription: React.FC<NotificationGroupDescriptionProps> = ({group}) => {
|
||||
const [firstActor, secondActor, ...otherActors] = group.actors;
|
||||
const hasOthers = otherActors.length > 0;
|
||||
|
||||
|
@ -145,7 +133,9 @@ const getGroupDescription = (group: GroupedActivity): JSX.Element => {
|
|||
case ACTIVITY_TYPE.FOLLOW:
|
||||
return <>{actorText} started following you</>;
|
||||
case ACTIVITY_TYPE.LIKE:
|
||||
return <>{actorText} liked your post <span className='font-semibold'>{group.object?.name || ''}</span></>;
|
||||
return <>{actorText} liked your {group.object?.type === 'Article' ? 'post' : 'note'} <span className='font-semibold'>{group.object?.name || ''}</span></>;
|
||||
case ACTIVITY_TYPE.REPOST:
|
||||
return <>{actorText} reposted your {group.object?.type === 'Article' ? 'post' : 'note'} <span className='font-semibold'>{group.object?.name || ''}</span></>;
|
||||
case ACTIVITY_TYPE.CREATE:
|
||||
if (group.object?.inReplyTo && typeof group.object?.inReplyTo !== 'string') {
|
||||
let content = stripHtml(group.object.inReplyTo.content || '');
|
||||
|
@ -162,7 +152,7 @@ const getGroupDescription = (group: GroupedActivity): JSX.Element => {
|
|||
return <></>;
|
||||
};
|
||||
|
||||
const Activities: React.FC<ActivitiesProps> = ({}) => {
|
||||
const Notifications: React.FC<NotificationsProps> = () => {
|
||||
const user = 'index';
|
||||
|
||||
const [openStates, setOpenStates] = React.useState<{[key: string]: boolean}>({});
|
||||
|
@ -183,7 +173,7 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
includeOwn: true,
|
||||
includeReplies: true,
|
||||
filter: {
|
||||
type: ['Follow', 'Like', `Create:Note`]
|
||||
type: ['Follow', 'Like', `Create:Note`, `Announce:Note`, `Announce:Article`]
|
||||
},
|
||||
limit: 120,
|
||||
key: GET_ACTIVITIES_QUERY_KEY_NOTIFICATIONS
|
||||
|
@ -215,6 +205,14 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
|
||||
return true;
|
||||
})
|
||||
// Remove reposts that are not for our own posts
|
||||
.filter((activity) => {
|
||||
if (activity.type === ACTIVITY_TYPE.REPOST && activity.object?.attributedTo?.id !== userProfile?.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
// Remove create activities that are not replies to our own posts
|
||||
.filter((activity) => {
|
||||
if (
|
||||
|
@ -292,6 +290,14 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
handleProfileClick(group.actors[0]);
|
||||
}
|
||||
break;
|
||||
case ACTIVITY_TYPE.REPOST:
|
||||
NiceModal.show(ArticleModal, {
|
||||
activityId: group.id,
|
||||
object: group.object,
|
||||
actor: group.object.attributedTo as ActorProperties,
|
||||
width: group.object?.type === 'Article' ? 'wide' : 'narrow'
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -377,9 +383,18 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
</NotificationItem.Avatars>
|
||||
<NotificationItem.Content>
|
||||
<div className='line-clamp-2 text-pretty text-black'>
|
||||
{getGroupDescription(group)}
|
||||
<NotificationGroupDescription group={group} />
|
||||
</div>
|
||||
{getExtendedDescription(group)}
|
||||
{(
|
||||
(group.type === ACTIVITY_TYPE.CREATE && group.object?.inReplyTo) ||
|
||||
(group.type === ACTIVITY_TYPE.LIKE && !group.object?.name && group.object?.content) ||
|
||||
(group.type === ACTIVITY_TYPE.REPOST && !group.object?.name && group.object?.content)
|
||||
) && (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{__html: stripHtml(group.object?.content || '')}}
|
||||
className='ap-note-content mt-1 line-clamp-2 text-pretty text-grey-700'
|
||||
/>
|
||||
)}
|
||||
</NotificationItem.Content>
|
||||
</NotificationItem>
|
||||
{index < groupedActivities.length - 1 && <Separator />}
|
||||
|
@ -400,4 +415,4 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Activities;
|
||||
export default Notifications;
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {Icon} from '@tryghost/admin-x-design-system';
|
||||
|
||||
export type NotificationType = 'like' | 'follow' | 'reply';
|
||||
export type NotificationType = 'like' | 'follow' | 'reply' | 'repost';
|
||||
|
||||
interface NotificationIconProps {
|
||||
notificationType: NotificationType;
|
||||
|
@ -29,6 +29,11 @@ const NotificationIcon: React.FC<NotificationIconProps> = ({notificationType, cl
|
|||
iconColor = 'text-purple-500';
|
||||
badgeColor = 'bg-purple-100/50';
|
||||
break;
|
||||
case 'repost':
|
||||
icon = 'reload';
|
||||
iconColor = 'text-green-500';
|
||||
badgeColor = 'bg-green-100/50';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,9 +25,24 @@ const MainNavigation: React.FC<MainNavigationProps> = ({page}) => {
|
|||
unstyled
|
||||
onClick={() => updateRoute('feed')}
|
||||
/>
|
||||
<Button className={`${page === 'activities' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`} label='Notifications' unstyled onClick={() => updateRoute('activity')} />
|
||||
<Button className={`${page === 'search' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`} label='Search' unstyled onClick={() => updateRoute('search')} />
|
||||
<Button className={`${page === 'profile' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`} label='Profile' unstyled onClick={() => updateRoute('profile')} />
|
||||
<Button
|
||||
className={`${page === 'notifications' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`}
|
||||
label='Notifications'
|
||||
unstyled
|
||||
onClick={() => updateRoute('notifications')}
|
||||
/>
|
||||
<Button
|
||||
className={`${page === 'search' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`}
|
||||
label='Search'
|
||||
unstyled
|
||||
onClick={() => updateRoute('search')}
|
||||
/>
|
||||
<Button
|
||||
className={`${page === 'profile' ? 'font-bold text-grey-975' : 'text-grey-700 hover:text-grey-800'}`}
|
||||
label='Profile'
|
||||
unstyled
|
||||
onClick={() => updateRoute('profile')}
|
||||
/>
|
||||
</div>
|
||||
</MainHeader>
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue