0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Updated ActivityPub Feed and Replies design (#20992)

- Added activity icon for Replies
- Updated Replies design
- Updated hard-coded Profile values to more realistic ones
- Renamed ActivityPub nav item and moved it to the top of the navbar
- Added a check for post attachments
This commit is contained in:
Djordje Vlaisavljevic 2024-09-12 16:03:49 +01:00 committed by GitHub
parent 625c89e37f
commit a087e329ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 51 additions and 26 deletions

View file

@ -3,7 +3,7 @@ import React from 'react';
import APAvatar, {AvatarBadge} from './global/APAvatar';
import ActivityItem from './activities/ActivityItem';
import MainNavigation from './navigation/MainNavigation';
import {Button} from '@tryghost/admin-x-design-system';
import {Button, NoValueLabel} from '@tryghost/admin-x-design-system';
import getUsername from '../utils/get-username';
import {useBrowseInboxForUser, useBrowseOutboxForUser, useFollowersForUser} from '../MainContent';
@ -65,7 +65,7 @@ const getExtendedDescription = (activity: Activity): JSX.Element | null => {
return (
<div
dangerouslySetInnerHTML={{__html: activity.object?.content || ''}}
className='ml-2 mt-2 text-sm text-grey-600'
className='mt-2'
/>
);
}
@ -92,7 +92,7 @@ const getActorUrl = (activity: Activity): string | null => {
const getActivityBadge = (activity: Activity): AvatarBadge => {
switch (activity.type) {
case ACTVITY_TYPE.CREATE:
return 'user-fill'; // TODO: Change this
return 'comment-fill';
case ACTVITY_TYPE.FOLLOW:
return 'user-fill';
case ACTVITY_TYPE.LIKE:
@ -171,19 +171,23 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
<MainNavigation title='Activities' />
<div className='z-0 flex w-full flex-col items-center'>
{activities.length === 0 && (
<div className='mt-8 font-bold'>This is an empty state when there are no activities</div>
<div className='mt-8'>
<NoValueLabel icon='bell'>
When other Fediverse users interact with you, you&apos;ll see it here.
</NoValueLabel>
</div>
)}
{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)}>
<APAvatar author={activity.actor} badge={getActivityBadge(activity)} />
<div>
<div className='pt-[2px]'>
<div className='text-grey-600'>
<span className='mr-1 font-bold text-black'>{activity.actor.name}</span>
{getUsername(activity.actor)}
</div>
<div className='text-sm'>{getActivityDescription(activity, activityObjectsMap)}</div>
<div className=''>{getActivityDescription(activity, activityObjectsMap)}</div>
{getExtendedDescription(activity)}
</div>
{isFollower(activity.actor.id) === false && (

View file

@ -35,7 +35,7 @@ const Profile: React.FC<ProfileProps> = ({}) => {
id: 'likes',
title: 'Likes',
contents: (
<div>
<div className='ap-likes'>
{liked.length === 0 ? (
<NoValueLabel icon='heart'>
You haven&apos;t liked anything yet.
@ -141,10 +141,10 @@ const Profile: React.FC<ProfileProps> = ({}) => {
<div className='inline-flex rounded-lg border-4 border-white'>
<APAvatar size='lg' />
</div>
<Heading className='mt-4' level={3}>John Doe</Heading>
<span className='mt-1 text-[1.5rem] text-grey-800'>@index@site.com</span>
<p className='mt-3 text-[1.5rem]'>This is a summary/bio/etc which could be kinda long in certain cases but not always, so...</p>
<a className='mt-3 block text-[1.5rem] underline' href='#'>www.coolsite.com</a>
<Heading className='mt-4' level={3}>Building ActivityPub</Heading>
<span className='mt-1 text-[1.5rem] text-grey-800'>@index@activitypub.ghost.org</span>
<p className='mt-3 text-[1.5rem]'>Ghost is federating over ActivityPub to become part of the world&apos;s largest publishing network</p>
<a className='mt-3 block text-[1.5rem] underline' href='https://activitypub.ghost.org'>activitypub.ghost.org</a>
<TabView<'posts' | 'likes' | 'following' | 'followers'> containerClassName='mt-6' selectedTab={selectedTab} tabs={tabs} onTabChange={setSelectedTab} />
</div>
</div>

View file

@ -20,7 +20,7 @@ const ActivityItem: React.FC<ActivityItemProps> = ({children, url = null}) => {
const Item = (
<div className='flex w-full max-w-[560px] flex-col hover:bg-grey-75'>
<div className='flex w-full items-center gap-4 border-b border-grey-100 px-2 py-4'>
<div className='flex w-full gap-4 border-b border-grey-100 px-2 py-4'>
{childrenArray[0]}
{childrenArray[1]}
{childrenArray[2]}

View file

@ -70,7 +70,7 @@ ${image &&
};
const FeedItemDivider: React.FC = () => (
<div className="mx-[-32px] my-4 h-px w-[120%] bg-grey-200"></div>
<div className="mx-[-32px] my-5 h-px w-[120%] bg-grey-200"></div>
);
const ArticleModal: React.FC<ArticleModalProps> = ({object, actor, comments, allComments}) => {

View file

@ -89,10 +89,10 @@ function renderInboxAttachment(object: ObjectProperties) {
// }
return (
<div className='min-w-[160px]'>
<div className='relative'>
{attachment[0] && <div className='relative'>
<img className={`h-[100px] w-[160px] rounded-md object-cover`} src={attachment[0].url} />
<div className='absolute bottom-1 right-1 z-10 rounded-full border border-[rgba(255,255,255,0.25)] bg-black px-2 py-0.5 font-semibold text-white'>+ {attachmentCount - 1}</div>
</div>
</div>}
</div>
);
}
@ -103,7 +103,7 @@ function renderInboxAttachment(object: ObjectProperties) {
case 'image/gif':
return (
<div className='min-w-[160px]'>
<img className={`h-[100px] w-[160px] rounded-md object-cover`} src={attachment.url} />
{attachment && <img className={`h-[100px] w-[160px] rounded-md object-cover`} src={attachment.url} />}
</div>
);
case 'video/mp4':
@ -302,7 +302,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
</div>
<div className={`absolute -inset-x-3 -inset-y-0 z-0 rounded transition-colors`}></div>
</div>
<div className="mx-[-32px] my-4 h-px w-[120%] bg-grey-200"></div>
<div className="mx-[-32px] my-3 h-px w-[120%] bg-grey-200"></div>
</div>
)}
@ -312,12 +312,12 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
return (
<>
{object && (
<div className={`group/article relative cursor-pointer pt-5`} onClick={onClick}>
<div className={`group/article relative cursor-pointer pt-2`} onClick={onClick}>
{(type === 'Announce' && object.type === 'Note') && <div className='z-10 mb-2 flex items-center gap-3 text-grey-700'>
<div className='z-10 flex w-10 justify-end'><Icon colorClass='text-grey-700' name='reload' size={'sm'}></Icon></div>
<span className='z-10'>{actor.name} reposted</span>
</div>}
<div className={`border-1 z-10 -my-1 grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-x-3 gap-y-2 border-b-grey-200 pb-4`} data-test-activity>
<div className={`border-1 z-10 -my-1 grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-x-3 gap-y-2 border-b-grey-200`} data-test-activity>
<div className='relative z-10 pt-[3px]'>
<APAvatar author={author}/>
</div>

View file

@ -3,7 +3,7 @@ import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';
import {Icon} from '@tryghost/admin-x-design-system';
type AvatarSize = 'xs' | 'sm' | 'lg';
export type AvatarBadge = 'user-fill' | 'heart-fill' | undefined;
export type AvatarBadge = 'user-fill' | 'heart-fill' | 'comment-fill' | undefined;
interface APAvatarProps {
author?: ActorProperties;
@ -25,6 +25,9 @@ const APAvatar: React.FC<APAvatarProps> = ({author, size, badge}) => {
case 'heart-fill':
badgeColor = ' bg-red-500';
break;
case 'comment-fill':
badgeColor = ' bg-purple-500';
break;
}
switch (size) {
@ -47,7 +50,7 @@ const APAvatar: React.FC<APAvatarProps> = ({author, size, badge}) => {
return (
<>
{author && author.icon?.url ? (
<a className='relative z-10 pt-[3px] transition-opacity hover:opacity-80' href={author.url} rel='noopener noreferrer' target='_blank'>
<a className='relative z-10 h-10 w-10 pt-[3px] transition-opacity hover:opacity-80' href={author.url} rel='noopener noreferrer' target='_blank'>
<img
className={imageClass}
src={author.icon.url}

View file

@ -37,3 +37,17 @@ animation: bump 0.3s ease-in-out;
margin-top: 1.5rem !important;
}
.ap-likes .ellipsis::after {
content: "…";
}
.ap-likes .invisible {
display: inline-block;
font-size: 0;
height: 0;
line-height: 0;
position: absolute;
width: 0;
}

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 1.34C5.66 1.34 0.5 5.59 0.5 10.81a8.58 8.58 0 0 0 3.18 6.54l-2.3 4.59a0.49 0.49 0 0 0 0.09 0.57 0.5 0.5 0 0 0 0.57 0.1l6.15 -2.86a13.44 13.44 0 0 0 3.81 0.54c6.34 0 11.5 -4.25 11.5 -9.48S18.34 1.34 12 1.34Z" fill="currentColor" stroke-width="1.5px">
</path>
</svg>

After

Width:  |  Height:  |  Size: 342 B

View file

@ -17,6 +17,11 @@
{{#unless this.session.user.isContributor}}
<div class="gh-nav-top">
<ul class="gh-nav-list gh-nav-main">
{{#if (feature "ActivityPub")}}
<li>
<LinkTo @route="activitypub-x" @current-when="activitypub-x">{{svg-jar "star"}}ActivityPub</LinkTo>
</li>
{{/if}}
{{#if (gh-user-can-admin this.session.user)}}
<li class="relative gh-nav-list-home">
<LinkTo @route="dashboard" @alt="Dashboard" title="Dashboard" data-test-nav="dashboard">{{svg-jar "house"}} Dashboard</LinkTo>
@ -128,11 +133,6 @@
<LinkTo @route="demo-x" @current-when="demo-x">{{svg-jar "star"}}AdminX Demo</LinkTo>
</li>
{{/if}}
{{#if (feature "ActivityPub")}}
<li>
<LinkTo @route="activitypub-x" @current-when="activitypub-x">{{svg-jar "star"}}ActivityPub Demo</LinkTo>
</li>
{{/if}}
</ul>
{{#if this.session.user.isOwnerOnly}}