Updated design of Notes in the feed and in the drawer
ref https://linear.app/tryghost/issue/AP-282/render-notes-in-the-frontend
This commit is contained in:
parent
5b70fc6b1c
commit
6171a417de
@ -27,7 +27,8 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||
setArticleContent(object);
|
||||
setArticleActor(actor);
|
||||
NiceModal.show(ArticleModal, {
|
||||
object: object
|
||||
object: object,
|
||||
actor: actor
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
import FeedItem from './FeedItem';
|
||||
import MainHeader from '../navigation/MainHeader';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import articleBodyStyles from '../articleBodyStyles';
|
||||
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {Button, Modal} from '@tryghost/admin-x-design-system';
|
||||
import {ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {renderAttachment} from './FeedItem';
|
||||
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
|
||||
|
||||
interface ArticleModalProps {
|
||||
object: ObjectProperties;
|
||||
actor: ActorProperties;
|
||||
}
|
||||
|
||||
const ArticleBody: React.FC<{heading: string, image: string|undefined, html: string}> = ({heading, image, html}) => {
|
||||
@ -62,7 +63,7 @@ ${image &&
|
||||
);
|
||||
};
|
||||
|
||||
const ArticleModal: React.FC<ArticleModalProps> = ({object}) => {
|
||||
const ArticleModal: React.FC<ArticleModalProps> = ({object, actor}) => {
|
||||
const modal = useModal();
|
||||
return (
|
||||
<Modal
|
||||
@ -82,8 +83,9 @@ const ArticleModal: React.FC<ArticleModalProps> = ({object}) => {
|
||||
<div className='mt-10 w-auto'>
|
||||
{object.type === 'Note' && (
|
||||
<div className='mx-auto max-w-[580px]'>
|
||||
{object.content && <div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>}
|
||||
{renderAttachment(object)}
|
||||
<FeedItem actor={actor} layout='modal' object={object} type='Note'/>
|
||||
{/* {object.content && <div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>} */}
|
||||
{/* {renderAttachment(object)} */}
|
||||
</div>)}
|
||||
{object.type === 'Article' && <ArticleBody heading={object.name} html={object.content} image={object?.image}/>}
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@ import getUsername from '../../utils/get-username';
|
||||
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||
import {Button, Heading, Icon} from '@tryghost/admin-x-design-system';
|
||||
|
||||
export function renderAttachment(object: ObjectProperties) {
|
||||
export function renderAttachment(object: ObjectProperties, layout: string) {
|
||||
let attachment;
|
||||
if (object.image) {
|
||||
attachment = object.image;
|
||||
@ -23,16 +23,16 @@ export function renderAttachment(object: ObjectProperties) {
|
||||
const attachmentCount = attachment.length;
|
||||
|
||||
let gridClass = '';
|
||||
if (attachmentCount === 1) {
|
||||
if (layout === 'modal') {
|
||||
gridClass = 'grid-cols-1'; // Single image, full width
|
||||
} else if (attachmentCount === 2) {
|
||||
gridClass = 'grid-cols-2'; // Two images, side by side
|
||||
gridClass = 'grid-cols-2 auto-rows-[150px]'; // Two images, side by side
|
||||
} else if (attachmentCount === 3 || attachmentCount === 4) {
|
||||
gridClass = 'grid-cols-2'; // Three or four images, two per row
|
||||
gridClass = 'grid-cols-2 auto-rows-[150px]'; // Three or four images, two per row
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`attachment-gallery mt-2 grid auto-rows-[150px] ${gridClass} gap-2`}>
|
||||
<div className={`attachment-gallery mt-2 grid ${gridClass} gap-2`}>
|
||||
{attachment.map((item, index) => (
|
||||
<img key={item.url} alt={`attachment-${index}`} className={`h-full w-full rounded-md object-cover ${attachmentCount === 3 && index === 0 ? 'row-span-2' : ''}`} src={item.url} />
|
||||
))}
|
||||
@ -48,7 +48,7 @@ export function renderAttachment(object: ObjectProperties) {
|
||||
case 'video/mp4':
|
||||
case 'video/webm':
|
||||
return <div className='relative mb-4 mt-2'>
|
||||
<video className='h-[300px] w-full rounded object-cover' src={attachment.url}/>
|
||||
<video className='h-[300px] w-full rounded object-cover' src={attachment.url} controls/>
|
||||
</div>;
|
||||
|
||||
case 'audio/mpeg':
|
||||
@ -69,18 +69,6 @@ interface FeedItemProps {
|
||||
}
|
||||
|
||||
const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type}) => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(object.content || '', 'text/html');
|
||||
|
||||
const plainTextContent = doc.body.textContent;
|
||||
let previewContent = '';
|
||||
if (object.preview) {
|
||||
const previewDoc = parser.parseFromString(object.preview.content || '', 'text/html');
|
||||
previewContent = previewDoc.body.textContent || '';
|
||||
} else if (object.type === 'Note') {
|
||||
previewContent = plainTextContent || '';
|
||||
}
|
||||
|
||||
const timestamp =
|
||||
new Date(object?.published ?? new Date()).toLocaleDateString('default', {year: 'numeric', month: 'short', day: '2-digit'}) + ', ' + new Date(object?.published ?? new Date()).toLocaleTimeString('default', {hour: '2-digit', minute: '2-digit'});
|
||||
|
||||
@ -101,79 +89,47 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type}) => {
|
||||
author = typeof object.attributedTo === 'object' ? object.attributedTo as ActorProperties : actor;
|
||||
}
|
||||
|
||||
if (layout === 'feed') {
|
||||
return (
|
||||
<>
|
||||
{object && (
|
||||
<div className='group/article relative cursor-pointer pt-4'>
|
||||
{(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='flex items-start gap-3'>
|
||||
<APAvatar author={author} />
|
||||
<div className='border-1 z-10 -mt-1 flex w-full flex-col items-start justify-between border-b border-b-grey-200 pb-4' data-test-activity>
|
||||
<div className='relative z-10 mb-2 flex w-full flex-col overflow-visible text-[1.5rem]'>
|
||||
<div className='flex'>
|
||||
<span className='truncate whitespace-nowrap font-bold' data-test-activity-heading>{author.name}</span>
|
||||
<span className='whitespace-nowrap text-grey-700 before:mx-1 before:content-["·"]' title={`${timestamp}`}>{getRelativeTimestamp(date)}</span>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<span className='truncate text-grey-700'>{getUsername(author)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative z-10 w-full gap-4'>
|
||||
<div className='flex flex-col'>
|
||||
{object.name && <Heading className='mb-1 leading-tight' level={4} data-test-activity-heading>{object.name}</Heading>}
|
||||
<div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>
|
||||
{/* <p className='text-pretty text-md text-grey-900'>{object.content}</p> */}
|
||||
{renderAttachment(object)}
|
||||
<div className='mt-3 flex gap-2'>
|
||||
<Button className={`self-start text-grey-500 transition-all hover:text-grey-800 ${isClicked ? 'bump' : ''} ${isLiked ? 'ap-red-heart text-red *:!fill-red hover:text-red' : ''}`} hideLabel={true} icon='heart' id='like' size='md' unstyled={true} onClick={handleLikeClick}/>
|
||||
<span className={`text-grey-800 ${isLiked ? 'opacity-100' : 'opacity-0'}`}>1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{object && (
|
||||
<div className={`group/article relative cursor-pointer ${(layout === 'feed') ? 'pt-5' : ''}`}>
|
||||
{(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 ${(layout === 'modal') ? 'gap-y-2' : 'gap-y-1'} border-b border-b-grey-200 pb-4`} data-test-activity>
|
||||
<div className='relative z-10 pt-[3px]'>
|
||||
<APAvatar author={author}/>
|
||||
</div>
|
||||
{/* <div className='border-1 z-10 -mt-1 flex w-full flex-col items-start justify-between border-b border-b-grey-200 pb-4' data-test-activity> */}
|
||||
<div className='relative z-10 flex w-full flex-col overflow-visible text-[1.5rem]'>
|
||||
<div className='flex'>
|
||||
<span className='truncate whitespace-nowrap font-bold' data-test-activity-heading>{author.name}</span>
|
||||
<span className='whitespace-nowrap text-grey-700 before:mx-1 before:content-["·"]' title={`${timestamp}`}>{getRelativeTimestamp(date)}</span>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<span className='truncate text-grey-700'>{getUsername(author)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='absolute -inset-x-3 -inset-y-0 z-0 rounded transition-colors group-hover/article:bg-grey-75'></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (layout === 'inbox') {
|
||||
return (
|
||||
<>
|
||||
{object && (
|
||||
<div className='border-1 group/article relative z-10 flex cursor-pointer flex-col items-start justify-between border-b border-b-grey-200 py-5' data-test-activity>
|
||||
<div className='relative z-10 mb-3 grid w-full grid-cols-[20px_auto_1fr_auto] items-center gap-2 text-base'>
|
||||
<img className='w-5' src={actor.icon?.url}/>
|
||||
<span className='truncate font-semibold'>{actor.name}</span>
|
||||
{/* <span className='truncate text-grey-800'>{getUsername(actor)}</span> */}
|
||||
<span className='ml-auto text-right text-grey-800'>{timestamp}</span>
|
||||
</div>
|
||||
<div className='relative z-10 grid w-full grid-cols-[auto_170px] gap-4'>
|
||||
<div className={`relative z-10 ${(layout === 'modal') ? 'col-start-1' : 'col-start-2'} col-end-3 w-full gap-4`}>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex w-full justify-between gap-4'>
|
||||
<Heading className='mb-1 line-clamp-2 leading-tight' level={5} data-test-activity-heading>{object.name}</Heading>
|
||||
</div>
|
||||
<p className='mb-6 line-clamp-2 max-w-prose text-pretty text-md text-grey-800'>{previewContent}</p>
|
||||
<div className='flex gap-2'>
|
||||
{object.name && <Heading className='mb-1 leading-tight' level={4} data-test-activity-heading>{object.name}</Heading>}
|
||||
<div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>
|
||||
{/* <p className='text-pretty text-md text-grey-900'>{object.content}</p> */}
|
||||
{renderAttachment(object, layout)}
|
||||
<div className='mt-3 flex gap-2'>
|
||||
<Button className={`self-start text-grey-500 transition-all hover:text-grey-800 ${isClicked ? 'bump' : ''} ${isLiked ? 'ap-red-heart text-red *:!fill-red hover:text-red' : ''}`} hideLabel={true} icon='heart' id='like' size='md' unstyled={true} onClick={handleLikeClick}/>
|
||||
<span className={`text-grey-800 ${isLiked ? 'opacity-100' : 'opacity-0'}`}>1</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* {image && <div className='relative min-w-[33%] grow'>
|
||||
<img className='absolute h-full w-full rounded object-cover' height='140px' src={image} width='170px'/>
|
||||
</div>} */}
|
||||
</div>
|
||||
<div className='absolute -inset-x-3 -inset-y-1 z-0 rounded transition-colors group-hover/article:bg-grey-50'></div>
|
||||
{/* <div className='absolute inset-0 z-0 rounded from-white to-grey-50 transition-colors group-hover/article:bg-gradient-to-r'></div> */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
<div className={`absolute -inset-x-3 -inset-y-0 z-0 rounded transition-colors ${(layout === 'feed') ? 'group-hover/article:bg-grey-75' : ''} `}></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedItem;
|
Loading…
Reference in New Issue
Block a user