mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Focused reply box when clicking comment button on feed item
ref https://linear.app/tryghost/issue/AP-396 I think it's nicer API to pass in a focused property, rather than an element ref, but I don't have much experience here, so it might be the wrong approach!
This commit is contained in:
parent
3e6b3bda8a
commit
403cac4c42
4 changed files with 30 additions and 14 deletions
|
@ -44,10 +44,10 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
return commentsMap.get(id) ?? [];
|
return commentsMap.get(id) ?? [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewContent = (object: ObjectProperties, actor: ActorProperties, comments: Activity[]) => {
|
const handleViewContent = (object: ObjectProperties, actor: ActorProperties, comments: Activity[], focusReply = false) => {
|
||||||
setArticleContent(object);
|
setArticleContent(object);
|
||||||
setArticleActor(actor);
|
setArticleActor(actor);
|
||||||
NiceModal.show(ArticleModal, {object, actor, comments, allComments: commentsMap});
|
NiceModal.show(ArticleModal, {object, actor, comments, allComments: commentsMap, focusReply});
|
||||||
};
|
};
|
||||||
|
|
||||||
function getContentAuthor(activity: Activity) {
|
function getContentAuthor(activity: Activity) {
|
||||||
|
@ -101,6 +101,12 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
layout={layout}
|
layout={layout}
|
||||||
object={activity.object}
|
object={activity.object}
|
||||||
type={activity.type}
|
type={activity.type}
|
||||||
|
onCommentClick={() => handleViewContent(
|
||||||
|
activity.object,
|
||||||
|
getContentAuthor(activity),
|
||||||
|
getCommentsForObject(activity.object.id),
|
||||||
|
true
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{index < activities.length - 1 && (
|
{index < activities.length - 1 && (
|
||||||
<div className="h-px w-full bg-grey-200"></div>
|
<div className="h-px w-full bg-grey-200"></div>
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface ArticleModalProps {
|
||||||
actor: ActorProperties;
|
actor: ActorProperties;
|
||||||
comments: Activity[];
|
comments: Activity[];
|
||||||
allComments: Map<string, Activity[]>;
|
allComments: Map<string, Activity[]>;
|
||||||
|
focusReply: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArticleBody: React.FC<{heading: string, image: string|undefined, html: string}> = ({heading, image, html}) => {
|
const ArticleBody: React.FC<{heading: string, image: string|undefined, html: string}> = ({heading, image, html}) => {
|
||||||
|
@ -74,7 +75,7 @@ const FeedItemDivider: React.FC = () => (
|
||||||
<div className="mx-[-32px] h-px w-[120%] bg-grey-200"></div>
|
<div className="mx-[-32px] h-px w-[120%] bg-grey-200"></div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ArticleModal: React.FC<ArticleModalProps> = ({object, actor, comments, allComments}) => {
|
const ArticleModal: React.FC<ArticleModalProps> = ({object, actor, comments, allComments, focusReply}) => {
|
||||||
const MODAL_SIZE_SM = 640;
|
const MODAL_SIZE_SM = 640;
|
||||||
const MODAL_SIZE_LG = 2800;
|
const MODAL_SIZE_LG = 2800;
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ const ArticleModal: React.FC<ArticleModalProps> = ({object, actor, comments, all
|
||||||
object={object}
|
object={object}
|
||||||
type='Note'
|
type='Note'
|
||||||
/>
|
/>
|
||||||
<APReplyBox object={object}/>
|
<APReplyBox focused={focusReply} object={object}/>
|
||||||
<FeedItemDivider />
|
<FeedItemDivider />
|
||||||
|
|
||||||
{/* {object.content && <div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>} */}
|
{/* {object.content && <div dangerouslySetInnerHTML={({__html: object.content})} className='ap-note-content text-pretty text-[1.5rem] text-grey-900'></div>} */}
|
||||||
|
|
|
@ -205,11 +205,12 @@ interface FeedItemProps {
|
||||||
comments?: Activity[];
|
comments?: Activity[];
|
||||||
last?: boolean;
|
last?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
onCommentClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comments = [], last, onClick = noop}) => {
|
const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comments = [], last, onClick = noop, onCommentClick}) => {
|
||||||
const timestamp =
|
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'});
|
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'});
|
||||||
|
|
||||||
|
@ -292,7 +293,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Menu items={menuItems} position='end' trigger={UserMenuTrigger}/>
|
<Menu items={menuItems} position='end' trigger={UserMenuTrigger}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`relative z-10 col-start-2 col-end-3 w-full gap-4`}>
|
<div className={`relative z-10 col-start-2 col-end-3 w-full gap-4`}>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
|
@ -303,7 +304,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||||
commentCount={comments.length}
|
commentCount={comments.length}
|
||||||
likeCount={1}
|
likeCount={1}
|
||||||
object={object}
|
object={object}
|
||||||
onCommentClick={onLikeClick}
|
onCommentClick={onCommentClick}
|
||||||
onLikeClick={onLikeClick}
|
onLikeClick={onLikeClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -348,7 +349,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||||
commentCount={comments.length}
|
commentCount={comments.length}
|
||||||
likeCount={1}
|
likeCount={1}
|
||||||
object={object}
|
object={object}
|
||||||
onCommentClick={onLikeClick}
|
onCommentClick={onCommentClick}
|
||||||
onLikeClick={onLikeClick}
|
onLikeClick={onLikeClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -395,7 +396,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||||
commentCount={comments.length}
|
commentCount={comments.length}
|
||||||
likeCount={1}
|
likeCount={1}
|
||||||
object={object}
|
object={object}
|
||||||
onCommentClick={onLikeClick}
|
onCommentClick={onCommentClick}
|
||||||
onLikeClick={onLikeClick}
|
onLikeClick={onLikeClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -432,7 +433,7 @@ const FeedItem: React.FC<FeedItemProps> = ({actor, object, layout, type, comment
|
||||||
commentCount={comments.length}
|
commentCount={comments.length}
|
||||||
likeCount={1}
|
likeCount={1}
|
||||||
object={object}
|
object={object}
|
||||||
onCommentClick={onLikeClick}
|
onCommentClick={onCommentClick}
|
||||||
onLikeClick={onLikeClick}
|
onLikeClick={onLikeClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {HTMLProps, useId, useState} from 'react';
|
import React, {HTMLProps, useEffect, useId, useRef, useState} from 'react';
|
||||||
|
|
||||||
import * as FormPrimitive from '@radix-ui/react-form';
|
import * as FormPrimitive from '@radix-ui/react-form';
|
||||||
import APAvatar from './APAvatar';
|
import APAvatar from './APAvatar';
|
||||||
|
@ -10,7 +10,6 @@ import {useReplyMutationForUser, useUserDataForUser} from '../../hooks/useActivi
|
||||||
// import {useFocusContext} from '@tryghost/admin-x-design-system/types/providers/DesignSystemProvider';
|
// import {useFocusContext} from '@tryghost/admin-x-design-system/types/providers/DesignSystemProvider';
|
||||||
|
|
||||||
export interface APTextAreaProps extends HTMLProps<HTMLTextAreaElement> {
|
export interface APTextAreaProps extends HTMLProps<HTMLTextAreaElement> {
|
||||||
inputRef?: React.RefObject<HTMLTextAreaElement>;
|
|
||||||
title?: string;
|
title?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
@ -20,10 +19,10 @@ export interface APTextAreaProps extends HTMLProps<HTMLTextAreaElement> {
|
||||||
className?: string;
|
className?: string;
|
||||||
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
object: ObjectProperties;
|
object: ObjectProperties;
|
||||||
|
focused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const APReplyBox: React.FC<APTextAreaProps> = ({
|
const APReplyBox: React.FC<APTextAreaProps> = ({
|
||||||
inputRef,
|
|
||||||
title,
|
title,
|
||||||
value,
|
value,
|
||||||
rows = 1,
|
rows = 1,
|
||||||
|
@ -32,6 +31,7 @@ const APReplyBox: React.FC<APTextAreaProps> = ({
|
||||||
hint,
|
hint,
|
||||||
className,
|
className,
|
||||||
object,
|
object,
|
||||||
|
focused,
|
||||||
// onChange,
|
// onChange,
|
||||||
// onFocus,
|
// onFocus,
|
||||||
// onBlur,
|
// onBlur,
|
||||||
|
@ -43,6 +43,14 @@ const APReplyBox: React.FC<APTextAreaProps> = ({
|
||||||
|
|
||||||
const {data: user} = useUserDataForUser('index');
|
const {data: user} = useUserDataForUser('index');
|
||||||
|
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (textareaRef.current && focused) {
|
||||||
|
textareaRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [focused]);
|
||||||
|
|
||||||
const styles = clsx(
|
const styles = clsx(
|
||||||
'ap-textarea order-2 w-full resize-none rounded-lg border py-2 pr-3 text-[1.5rem] transition-all dark:text-white',
|
'ap-textarea order-2 w-full resize-none rounded-lg border py-2 pr-3 text-[1.5rem] transition-all dark:text-white',
|
||||||
error ? 'border-red' : 'border-transparent placeholder:text-grey-500 dark:placeholder:text-grey-800',
|
error ? 'border-red' : 'border-transparent placeholder:text-grey-500 dark:placeholder:text-grey-800',
|
||||||
|
@ -76,7 +84,7 @@ const APReplyBox: React.FC<APTextAreaProps> = ({
|
||||||
<FormPrimitive.Field name={id} asChild>
|
<FormPrimitive.Field name={id} asChild>
|
||||||
<FormPrimitive.Control asChild>
|
<FormPrimitive.Control asChild>
|
||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={textareaRef}
|
||||||
className={styles}
|
className={styles}
|
||||||
id={id}
|
id={id}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
|
|
Loading…
Add table
Reference in a new issue