Skip to content

Commit

Permalink
Refactored handleViewContent so it can be reused (TryGhost#21468)
Browse files Browse the repository at this point in the history
ref
https://linear.app/ghost/issue/AP-540/clicking-comment-icon-on-posts-and-likes-tabs-of-your-profile-doesnt

- We want to open posts in the drawer from multiple views (Inbox,
Profile etc.) and this change allows us to do so by pulling
`handleViewContent` from `Inbox.tsx` into a utility function. At the
same time, we’ve simplified the function so it uses less props to
achieve the same functionality.
- Also added a simple fix for scrolling the reply-box into view when
opening a long `article` by clicking on the reply icon. We probably
still need to figure out a more robust solution, because the height of
the `iframe` and the fact it takes some time to load it sometimes gets
in the way.

Co-authored-by: Michael Barrett <mike@ghost.org>
  • Loading branch information
2 people authored and tilak999 committed Nov 20, 2024
1 parent a2cb8c8 commit f4f9cda
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 74 deletions.
2 changes: 1 addition & 1 deletion apps/admin-x-activitypub/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tryghost/admin-x-activitypub",
"version": "0.3.5",
"version": "0.3.6",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
58 changes: 5 additions & 53 deletions apps/admin-x-activitypub/src/components/Inbox.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import APAvatar from './global/APAvatar';
import ActivityItem, {type Activity} from './activities/ActivityItem';
import ActivityItem from './activities/ActivityItem';
import ActivityPubWelcomeImage from '../assets/images/ap-welcome.png';
import ArticleModal from './feed/ArticleModal';
import FeedItem from './feed/FeedItem';
import MainNavigation from './navigation/MainNavigation';
import NiceModal from '@ebay/nice-modal-react';
import React, {useEffect, useRef, useState} from 'react';
import React, {useEffect, useRef} from 'react';
import ViewProfileModal from './global/ViewProfileModal';
import getUsername from '../utils/get-username';
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
import {Button, Heading, LoadingIndicator} from '@tryghost/admin-x-design-system';
import {handleViewContent} from '../utils/content-handlers';
import {useActivitiesForUser, useSuggestedProfiles} from '../hooks/useActivityPubQueries';
import {useLayout} from '../hooks/layout';
import {useRouting} from '@tryghost/admin-x-framework/routing';

interface InboxProps {}

const Inbox: React.FC<InboxProps> = ({}) => {
const [, setArticleContent] = useState<ObjectProperties | null>(null);
const [, setArticleActor] = useState<ActorProperties | null>(null);
const {layout, setFeed, setInbox} = useLayout();

const {getActivitiesQuery, updateActivity} = useActivitiesForUser({
Expand All @@ -39,42 +36,6 @@ const Inbox: React.FC<InboxProps> = ({}) => {
return !activity.object.inReplyTo;
});

const handleViewContent = (activityId: string, object: ObjectProperties, actor: ActorProperties, focusReply = false) => {
setArticleContent(object);
setArticleActor(actor);
NiceModal.show(ArticleModal, {
activityId,
object,
actor,
focusReply,
updateActivity
});
};

function getContentAuthor(activity: Activity) {
const actor = activity.actor;
const attributedTo = activity.object.attributedTo;

if (!attributedTo) {
return actor;
}

if (typeof attributedTo === 'string') {
return actor;
}

if (Array.isArray(attributedTo)) {
const found = attributedTo.find(item => typeof item !== 'string');
if (found) {
return found;
} else {
return actor;
}
}

return attributedTo;
}

// Intersection observer to fetch more activities when the user scrolls
// to the bottom of the page
const observerRef = useRef<IntersectionObserver | null>(null);
Expand Down Expand Up @@ -120,24 +81,15 @@ const Inbox: React.FC<InboxProps> = ({}) => {
<li
key={activity.id}
data-test-view-article
onClick={() => handleViewContent(
activity.id,
activity.object,
getContentAuthor(activity)
)}
onClick={() => handleViewContent(activity, false, updateActivity)}
>
<FeedItem
actor={activity.actor}
commentCount={activity.object.replyCount ?? 0}
layout={layout}
object={activity.object}
type={activity.type}
onCommentClick={() => handleViewContent(
activity.id,
activity.object,
getContentAuthor(activity),
true
)}
onCommentClick={() => handleViewContent(activity, true, updateActivity)}
/>
{index < activities.length - 1 && (
<div className="h-px w-full bg-grey-200"></div>
Expand Down
20 changes: 6 additions & 14 deletions apps/admin-x-activitypub/src/components/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import APAvatar from './global/APAvatar';
import ActivityItem, {type Activity} from './activities/ActivityItem';
import ActivityItem from './activities/ActivityItem';
import FeedItem from './feed/FeedItem';
import MainNavigation from './navigation/MainNavigation';
import NiceModal from '@ebay/nice-modal-react';
import React, {useEffect, useRef, useState} from 'react';
import getUsername from '../utils/get-username';
import {ActorProperties} from '@tryghost/admin-x-framework/api/activitypub';

import ArticleModal from './feed/ArticleModal';
import ViewProfileModal from './global/ViewProfileModal';
import {Button, Heading, List, NoValueLabel, Tab, TabView} from '@tryghost/admin-x-design-system';
import {handleViewContent} from '../utils/content-handlers';
import {
useFollowersCountForUser,
useFollowersForUser,
Expand Down Expand Up @@ -75,14 +75,6 @@ const Profile: React.FC<ProfileProps> = ({}) => {
});
};

const handlePostClick = (activity: Activity) => {
NiceModal.show(ArticleModal, {
object: activity.object,
actor: activity.actor,
comments: activity.object.replies || []
});
};

const tabs = [
{
id: 'posts',
Expand All @@ -99,14 +91,14 @@ const Profile: React.FC<ProfileProps> = ({}) => {
<li
key={activity.id}
data-test-view-article
onClick={() => handlePostClick(activity)}
onClick={() => handleViewContent(activity, false)}
>
<FeedItem
actor={activity.object?.attributedTo || activity.actor}
layout={layout}
object={activity.object}
type={activity.type}
onCommentClick={() => {}}
onCommentClick={() => handleViewContent(activity, true)}
/>
{index < posts.length - 1 && (
<div className="h-px w-full bg-grey-200"></div>
Expand Down Expand Up @@ -144,14 +136,14 @@ const Profile: React.FC<ProfileProps> = ({}) => {
<li
key={activity.id}
data-test-view-article
onClick={() => handlePostClick(activity)}
onClick={() => handleViewContent(activity, false)}
>
<FeedItem
actor={activity.object?.attributedTo || activity.actor}
layout={layout}
object={Object.assign({}, activity.object, {liked: true})}
type={activity.type}
onCommentClick={() => {}}
onCommentClick={() => handleViewContent(activity, true)}
/>
{index < liked.length - 1 && (
<div className="h-px w-full bg-grey-200"></div>
Expand Down
24 changes: 18 additions & 6 deletions apps/admin-x-activitypub/src/components/feed/ArticleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,10 @@ const ArticleBody: React.FC<{heading: string, image: string|undefined, excerpt:
}
function initializeResize() {
document.body.style.opacity = '0.5';
document.body.style.transition = 'opacity 0.3s ease';
resizeIframe();
waitForImages().then(() => {
isFullyLoaded = true;
document.body.style.opacity = '1';
resizeIframe();
});
}
Expand Down Expand Up @@ -237,6 +233,16 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
object.replyCount = object.replyCount + 1;
}

const replyBoxRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (focusReply && replyBoxRef.current) {
setTimeout(() => {
replyBoxRef.current?.scrollIntoView({block: 'center'});
}, 100);
}
}, [focusReply]);

return (
<Modal
align='right'
Expand Down Expand Up @@ -291,7 +297,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
</>
);
})}

{object.type === 'Note' && (
<FeedItem
actor={actor}
Expand All @@ -314,7 +320,13 @@ const ArticleModal: React.FC<ArticleModalProps> = ({
/>
)}

<APReplyBox focused={isFocused} object={object} onNewReply={handleNewReply}/>
<div ref={replyBoxRef}>
<APReplyBox
focused={isFocused}
object={object}
onNewReply={handleNewReply}
/>
</div>
<FeedItemDivider />

{isLoadingThread && <LoadingIndicator size='lg' />}
Expand Down
43 changes: 43 additions & 0 deletions apps/admin-x-activitypub/src/utils/content-handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ArticleModal from '../components/feed/ArticleModal';
import NiceModal from '@ebay/nice-modal-react';
import {type Activity} from '../components/activities/ActivityItem';

export const handleViewContent = (
activity: Activity,
focusReply = false,
updateActivity: (id: string, updated: Partial<Activity>) => void = () => {}
) => {
const authorActor = getContentAuthor(activity);
NiceModal.show(ArticleModal, {
activityId: activity.id,
object: activity.object,
actor: authorActor,
comments: Array.isArray(activity.object.replies) ? activity.object.replies : [],
focusReply,
updateActivity
});
};

export const getContentAuthor = (activity: Activity) => {
const actor = activity.actor;
const attributedTo = activity.object.attributedTo;

if (!attributedTo) {
return actor;
}

if (typeof attributedTo === 'string') {
return actor;
}

if (Array.isArray(attributedTo)) {
const found = attributedTo.find(item => typeof item !== 'string');
if (found) {
return found;
} else {
return actor;
}
}

return attributedTo;
};

0 comments on commit f4f9cda

Please sign in to comment.