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:
parent
5ef19f49c5
commit
0e4d2bf44d
8 changed files with 124 additions and 54 deletions
|
@ -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 };
|
||||||
|
|
|
@ -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
|
||||||
|
|
15
src/constants/dom/links.ts
Normal file
15
src/constants/dom/links.ts
Normal 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);
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -13,3 +13,9 @@
|
||||||
.more {
|
.more {
|
||||||
margin-bottom: $gap;
|
margin-bottom: $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newCommentAnchor {
|
||||||
|
position: relative;
|
||||||
|
top: -($header_height * 2);
|
||||||
|
display: block;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue