1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

scroll to new comments from recent and notifications

This commit is contained in:
Fedor Katurov 2025-01-24 17:46:24 +07:00
parent 5ef19f49c5
commit 0e4d2bf44d
8 changed files with 124 additions and 54 deletions

View file

@ -8,6 +8,8 @@ import { URLS } from '~/constants/urls';
import { INode } from '~/types'; import { INode } from '~/types';
import { getPrettyDate } from '~/utils/dom'; import { getPrettyDate } from '~/utils/dom';
import { getNewCommentAnchor } from '../../../constants/dom/links';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
interface Props { interface Props {
@ -16,12 +18,11 @@ interface Props {
onClick?: MouseEventHandler; onClick?: MouseEventHandler;
} }
const NodeHorizontalCard: FC<Props> = ({ node, hasNew, onClick }) => { const NodeHorizontalCard: FC<Props> = ({ node, hasNew, onClick }) => (
return (
<Anchor <Anchor
key={node.id} key={node.id}
className={styles.item} className={styles.item}
href={URLS.NODE_URL(node.id)} href={getNewCommentAnchor(URLS.NODE_URL(node.id))}
onClick={onClick} onClick={onClick}
> >
<div <div
@ -42,6 +43,5 @@ const NodeHorizontalCard: FC<Props> = ({ node, hasNew, onClick }) => {
</div> </div>
</Anchor> </Anchor>
); );
};
export { NodeHorizontalCard }; export { NodeHorizontalCard };

View file

@ -9,6 +9,8 @@ import { Square } from '~/components/common/Square';
import { NotificationItem } from '~/types/notifications'; import { NotificationItem } from '~/types/notifications';
import { formatText, getURLFromString } from '~/utils/dom'; import { formatText, getURLFromString } from '~/utils/dom';
import { getCommentAnchor } from '../../../constants/dom/links';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
interface NotificationCommentProps { interface NotificationCommentProps {
@ -17,7 +19,10 @@ interface NotificationCommentProps {
} }
const NotificationComment: FC<NotificationCommentProps> = ({ item, isNew }) => ( const NotificationComment: FC<NotificationCommentProps> = ({ item, isNew }) => (
<Anchor href={item.url} className={styles.link}> <Anchor
href={getCommentAnchor(item.url, item.itemId)}
className={styles.link}
>
<div className={classNames(styles.message, { [styles.new]: isNew })}> <div className={classNames(styles.message, { [styles.new]: isNew })}>
<div className={styles.icon}> <div className={styles.icon}>
<Avatar <Avatar

View file

@ -0,0 +1,15 @@
export const NEW_COMMENT_ANCHOR_NAME = 'new-comment';
export const COMMENT_ANCHOR_PREFIX = 'comment';
export const getCommentId = (id: number) =>
[COMMENT_ANCHOR_PREFIX, id].join('-');
export const getNewCommentAnchor = (url: string) =>
[url, NEW_COMMENT_ANCHOR_NAME].join('#');
export const getCommentAnchor = (url: string, commentId: number) =>
[url, getCommentId(commentId)].join('#');
export const isCommentAnchor = (hash: string | undefined) =>
hash?.startsWith(COMMENT_ANCHOR_PREFIX) ||
hash?.startsWith(NEW_COMMENT_ANCHOR_NAME);

View file

@ -8,6 +8,7 @@ import { CommentWrapper } from '~/containers/comments/CommentWrapper';
import { IComment, ICommentGroup, IFile } from '~/types'; import { IComment, ICommentGroup, IFile } from '~/types';
import { CommendDeleted } from '../../../../../components/node/CommendDeleted'; import { CommendDeleted } from '../../../../../components/node/CommendDeleted';
import { getCommentId } from '../../../../../constants/dom/links';
import { CommentContent } from './components/CommentContent'; import { CommentContent } from './components/CommentContent';
import { CommentDistance } from './components/CommentDistance'; import { CommentDistance } from './components/CommentDistance';
@ -83,6 +84,9 @@ const Comment: FC<Props> = memo(
); );
return ( return (
<>
<a id={getCommentId(comment.id)} className={styles.anchor} />
<CommentContent <CommentContent
prefix={prefix} prefix={prefix}
saveComment={saveComment} saveComment={saveComment}
@ -95,6 +99,7 @@ const Comment: FC<Props> = memo(
onShowImageModal={onShowImageModal} onShowImageModal={onShowImageModal}
key={comment.id} key={comment.id}
/> />
</>
); );
})} })}
</div> </div>

View file

@ -15,3 +15,9 @@
.highlighted { .highlighted {
box-shadow: $color_primary 0 0 0px 2px; box-shadow: $color_primary 0 0 0px 2px;
} }
.anchor {
display: block;
position: relative;
top: -($header_height * 2);
}

View file

@ -1,9 +1,13 @@
import { FC, useMemo } from 'react'; import { FC, useEffect, useMemo } from 'react';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { LoadMoreButton } from '~/components/input/LoadMoreButton'; import { LoadMoreButton } from '~/components/input/LoadMoreButton';
import { ANNOUNCE_USER_ID, BORIS_NODE_ID } from '~/constants/boris/constants'; import { ANNOUNCE_USER_ID, BORIS_NODE_ID } from '~/constants/boris/constants';
import {
isCommentAnchor,
NEW_COMMENT_ANCHOR_NAME,
} from '~/constants/dom/links';
import { Comment } from '~/containers/node/NodeComments/components/Comment'; import { Comment } from '~/containers/node/NodeComments/components/Comment';
import { useGrouppedComments } from '~/hooks/node/useGrouppedComments'; import { useGrouppedComments } from '~/hooks/node/useGrouppedComments';
import { ICommentGroup } from '~/types'; import { ICommentGroup } from '~/types';
@ -18,6 +22,11 @@ interface Props {
order: 'ASC' | 'DESC'; order: 'ASC' | 'DESC';
} }
const isFirstGroupWithNewCommentt = (
group: ICommentGroup,
prevGroup: ICommentGroup | undefined,
) => group.hasNew && (!prevGroup || !prevGroup.hasNew);
const NodeComments: FC<Props> = observer(({ order }) => { const NodeComments: FC<Props> = observer(({ order }) => {
const user = useUserContext(); const user = useUserContext();
const { node } = useNodeContext(); const { node } = useNodeContext();
@ -35,7 +44,7 @@ const NodeComments: FC<Props> = observer(({ order }) => {
onSaveComment, onSaveComment,
} = useCommentContext(); } = useCommentContext();
const groupped: ICommentGroup[] = useGrouppedComments( const groupped = useGrouppedComments(
comments, comments,
order, order,
lastSeenCurrent ?? undefined, lastSeenCurrent ?? undefined,
@ -59,11 +68,33 @@ const NodeComments: FC<Props> = observer(({ order }) => {
return null; return null;
} }
useEffect(() => {
const anchor = location.hash?.replace('#', '');
if (!isLoading && isCommentAnchor(anchor)) {
setTimeout(
() =>
document
.getElementById(anchor)
?.scrollIntoView({ behavior: 'smooth' }),
300,
);
}
}, [isLoading]);
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
{order === 'DESC' && more} {order === 'DESC' && more}
{groupped.map((group) => ( {groupped.map((group, index) => (
<>
{isFirstGroupWithNewCommentt(group, groupped.at(index - 1)) && (
<a
id={NEW_COMMENT_ANCHOR_NAME}
className={styles.newCommentAnchor}
/>
)}
<Comment <Comment
nodeId={node.id!} nodeId={node.id!}
key={group.ids.join()} key={group.ids.join()}
@ -79,6 +110,7 @@ const NodeComments: FC<Props> = observer(({ order }) => {
isSame={group.user.id === user.id} isSame={group.user.id === user.id}
saveComment={onSaveComment} saveComment={onSaveComment}
/> />
</>
))} ))}
{order === 'ASC' && more} {order === 'ASC' && more}

View file

@ -13,3 +13,9 @@
.more { .more {
margin-bottom: $gap; margin-bottom: $gap;
} }
.newCommentAnchor {
position: relative;
top: -($header_height * 2);
display: block;
}

View file

@ -2,6 +2,7 @@ import { ShallowUser } from '../auth';
export interface NotificationItem { export interface NotificationItem {
id: number; id: number;
itemId: number;
url: string; url: string;
type: NotificationType; type: NotificationType;
title: string; title: string;