0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Refactored handleViewContent so it can be reused (#21468)

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>
This commit is contained in:
Djordje Vlaisavljevic 2024-10-31 09:58:47 +00:00 committed by GitHub
parent 5f59ddaacc
commit becfd13141
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 73 additions and 74 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@tryghost/admin-x-activitypub",
"version": "0.3.5",
"version": "0.3.6",
"license": "MIT",
"repository": {
"type": "git",

View file

@ -1,15 +1,14 @@
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';
@ -17,8 +16,6 @@ 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({
@ -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);
@ -120,11 +81,7 @@ 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}
@ -132,12 +89,7 @@ const Inbox: React.FC<InboxProps> = ({}) => {
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>

View file

@ -1,5 +1,5 @@
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';
@ -7,9 +7,9 @@ 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,
@ -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',
@ -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>
@ -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>

View file

@ -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();
});
}
@ -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'
@ -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' />}

View file

@ -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;
};