mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(post): add post count and highlight functionality to u/address
This commit is contained in:
@@ -20,7 +20,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boardSubtitle {
|
.boardSubtitle {
|
||||||
margin-top: 3px;
|
|
||||||
font-size: var(--board-address-font-size);
|
font-size: var(--board-address-font-size);
|
||||||
color: var(--board-address-text-color);
|
color: var(--board-address-text-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '.
|
|||||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isValidURL } from '../../lib/utils/url-utils';
|
import { isValidURL } from '../../lib/utils/url-utils';
|
||||||
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
|
import useAuthorAddressClick from '../../hooks/use-author-address-click';
|
||||||
import useEditCommentPrivileges from '../../hooks/use-author-privileges';
|
import useEditCommentPrivileges from '../../hooks/use-author-privileges';
|
||||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||||
import useHide from '../../hooks/use-hide';
|
import useHide from '../../hooks/use-hide';
|
||||||
@@ -47,6 +48,9 @@ const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
|
|||||||
|
|
||||||
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: address, subplebbitAddress });
|
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: address, subplebbitAddress });
|
||||||
|
|
||||||
|
const handleUserAddressClick = useAuthorAddressClick();
|
||||||
|
const numberOfPostsByAuthor = document.querySelectorAll(`.${shortAddress}`).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.postInfo}>
|
<div className={styles.postInfo}>
|
||||||
{!isHidden && <EditMenu isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} post={post} />}
|
{!isHidden && <EditMenu isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} post={post} />}
|
||||||
@@ -75,7 +79,24 @@ const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
|
|||||||
)}
|
)}
|
||||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && <span className={styles.userAddress}>(u/{shortAddress || accountShortAddress}) </span>}
|
{!(isDescription || isRules) && (
|
||||||
|
<>
|
||||||
|
(u/
|
||||||
|
<Tooltip
|
||||||
|
children={
|
||||||
|
<span
|
||||||
|
title='Highlight posts by this user address'
|
||||||
|
className={styles.userAddress}
|
||||||
|
onClick={() => handleUserAddressClick(shortAddress || accountShortAddress)}
|
||||||
|
>
|
||||||
|
{shortAddress || accountShortAddress}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
content={`${numberOfPostsByAuthor} ${numberOfPostsByAuthor === 1 ? 'post' : 'posts'} by this user address`}
|
||||||
|
/>
|
||||||
|
){' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.dateTime}>
|
<span className={styles.dateTime}>
|
||||||
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />
|
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />
|
||||||
@@ -272,7 +293,15 @@ const Reply = ({ openReplyModal, reply, roles }: PostProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.replyDesktop}>
|
<div className={styles.replyDesktop}>
|
||||||
<div className={styles.sideArrows}>{'>>'}</div>
|
<div className={styles.sideArrows}>{'>>'}</div>
|
||||||
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight} ${hidden && styles.postDesktopHidden}`} id={cid}>
|
<div
|
||||||
|
className={`
|
||||||
|
${styles.reply}
|
||||||
|
${isRouteLinkToReply && styles.highlight}
|
||||||
|
${hidden && styles.postDesktopHidden}
|
||||||
|
${cid}
|
||||||
|
${post?.author?.shortAddress}
|
||||||
|
`}
|
||||||
|
>
|
||||||
<PostInfo openReplyModal={openReplyModal} post={post} roles={roles} />
|
<PostInfo openReplyModal={openReplyModal} post={post} roles={roles} />
|
||||||
{link && !hidden && isValidURL(link) && <PostMedia post={post} />}
|
{link && !hidden && isValidURL(link) && <PostMedia post={post} />}
|
||||||
{content && !hidden && <PostMessage post={post} />}
|
{content && !hidden && <PostMessage post={post} />}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import styles from '../../views/post/post.module.css';
|
|||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
|
import useAuthorAddressClick from '../../hooks/use-author-address-click';
|
||||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||||
import useHide from '../../hooks/use-hide';
|
import useHide from '../../hooks/use-hide';
|
||||||
import useReplies from '../../hooks/use-replies';
|
import useReplies from '../../hooks/use-replies';
|
||||||
@@ -45,6 +46,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
|
|||||||
|
|
||||||
const stateString = useStateString(post);
|
const stateString = useStateString(post);
|
||||||
|
|
||||||
|
const handleUserAddressClick = useAuthorAddressClick();
|
||||||
|
const numberOfPostsByAuthor = document.querySelectorAll(`.${shortAddress}`).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.postInfo}>
|
<div className={styles.postInfo}>
|
||||||
@@ -65,7 +69,24 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
|
|||||||
)}
|
)}
|
||||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && <span className={styles.address}>(u/{shortAddress || accountShortAddress})</span>}
|
{!(isDescription || isRules) && (
|
||||||
|
<>
|
||||||
|
(u/
|
||||||
|
<Tooltip
|
||||||
|
children={
|
||||||
|
<span
|
||||||
|
title='Highlight posts by this user address'
|
||||||
|
className={styles.userAddress}
|
||||||
|
onClick={() => handleUserAddressClick(shortAddress || accountShortAddress)}
|
||||||
|
>
|
||||||
|
{shortAddress || accountShortAddress}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
content={`${numberOfPostsByAuthor} ${numberOfPostsByAuthor === 1 ? 'post' : 'posts'} by this user address`}
|
||||||
|
/>
|
||||||
|
){' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{pinned && (
|
{pinned && (
|
||||||
<span className={styles.stickyIconWrapper}>
|
<span className={styles.stickyIconWrapper}>
|
||||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||||
@@ -224,7 +245,15 @@ const Reply = ({ openReplyModal, reply, roles }: PostProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.replyMobile}>
|
<div className={styles.replyMobile}>
|
||||||
<div className={styles.reply}>
|
<div className={styles.reply}>
|
||||||
<div className={`${styles.replyContainer} ${isRouteLinkToReply && styles.highlight}`} id={cid}>
|
<div
|
||||||
|
className={`
|
||||||
|
${styles.replyContainer}
|
||||||
|
${isRouteLinkToReply && styles.highlight}
|
||||||
|
${hidden && styles.postDesktopHidden}
|
||||||
|
${cid}
|
||||||
|
${post?.author?.shortAddress}
|
||||||
|
`}
|
||||||
|
>
|
||||||
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} roles={roles} />
|
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} roles={roles} />
|
||||||
{content && !hidden && <PostMessageMobile post={post} />}
|
{content && !hidden && <PostMessageMobile post={post} />}
|
||||||
<ReplyBacklinks post={reply} />
|
<ReplyBacklinks post={reply} />
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ interface ReplyQuotePreviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
||||||
const targetElement = document.getElementById(cid);
|
const targetElements = document.querySelectorAll(`.${cid}`);
|
||||||
|
|
||||||
if (!targetElement) return;
|
if (targetElements.length === 0) return;
|
||||||
|
|
||||||
const isInViewport = (element: HTMLElement) => {
|
const isInViewport = (element: HTMLElement) => {
|
||||||
const bounding = element.getBoundingClientRect();
|
const bounding = element.getBoundingClientRect();
|
||||||
@@ -29,10 +29,19 @@ const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isInViewport(targetElement)) {
|
let anyInView = false;
|
||||||
targetElement.classList.add('highlight');
|
|
||||||
} else {
|
targetElements.forEach((element) => {
|
||||||
targetElement.classList.remove('highlight');
|
const htmlElement = element as HTMLElement;
|
||||||
|
if (isInViewport(htmlElement)) {
|
||||||
|
htmlElement.classList.add('highlight');
|
||||||
|
anyInView = true;
|
||||||
|
} else {
|
||||||
|
htmlElement.classList.remove('highlight');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!anyInView) {
|
||||||
onElementOutOfView();
|
onElementOutOfView();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -88,10 +97,10 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
|||||||
|
|
||||||
const handleMouseLeave = (cid: string | null) => {
|
const handleMouseLeave = (cid: string | null) => {
|
||||||
if (cid) {
|
if (cid) {
|
||||||
const targetElement = document.getElementById(cid);
|
const targetElements = document.querySelectorAll(`.${cid}`);
|
||||||
if (targetElement) {
|
targetElements.forEach((element) => {
|
||||||
targetElement.classList.remove('highlight');
|
element.classList.remove('highlight');
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
setHoveredCid(null);
|
setHoveredCid(null);
|
||||||
setOutOfViewCid(null);
|
setOutOfViewCid(null);
|
||||||
@@ -174,10 +183,10 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
|||||||
|
|
||||||
const handleMouseLeave = (cid: string | null) => {
|
const handleMouseLeave = (cid: string | null) => {
|
||||||
if (cid) {
|
if (cid) {
|
||||||
const targetElement = document.getElementById(cid);
|
const targetElements = document.querySelectorAll(`.${cid}`);
|
||||||
if (targetElement) {
|
targetElements.forEach((element) => {
|
||||||
targetElement.classList.remove('highlight');
|
element.classList.remove('highlight');
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
setHoveredCid(null);
|
setHoveredCid(null);
|
||||||
setOutOfViewCid(null);
|
setOutOfViewCid(null);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const Tooltip = ({ content, children }: TooltipProps) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const hover = useHover(context, { move: false, delay: { open: 250, close: 0 } });
|
const hover = useHover(context, { move: false, delay: { open: 500, close: 0 } });
|
||||||
const focus = useFocus(context);
|
const focus = useFocus(context);
|
||||||
const dismiss = useDismiss(context);
|
const dismiss = useDismiss(context);
|
||||||
const role = useRole(context, { role: 'tooltip' });
|
const role = useRole(context, { role: 'tooltip' });
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const useAuthorAddressClick = () => {
|
||||||
|
const handleUserAddressClick = (shortAddress: string | undefined) => {
|
||||||
|
if (!shortAddress) return;
|
||||||
|
|
||||||
|
// Select the elements corresponding to the clicked short address
|
||||||
|
const elements = document.querySelectorAll(`.${shortAddress}`);
|
||||||
|
|
||||||
|
// Check if the clicked address is already highlighted
|
||||||
|
const isAlreadyHighlighted = Array.from(elements).some((element) => element.classList.contains('highlight'));
|
||||||
|
|
||||||
|
// Remove highlight from all elements with the .highlight class
|
||||||
|
const prevElements = document.querySelectorAll('.highlight');
|
||||||
|
prevElements.forEach((element) => {
|
||||||
|
element.classList.remove('highlight');
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the clicked address was already highlighted, don't add the highlight back
|
||||||
|
if (isAlreadyHighlighted) return;
|
||||||
|
|
||||||
|
// Highlight the new elements
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.classList.add('highlight');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return handleUserAddressClick;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAuthorAddressClick;
|
||||||
+16
-10
@@ -143,10 +143,23 @@ const Footer = () => {
|
|||||||
{t('about')}
|
{t('about')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{downloadAppLink && (
|
||||||
|
<li>
|
||||||
|
<a href={downloadAppLink} target='_blank' rel='noopener noreferrer'>
|
||||||
|
{t('download_app')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li>
|
<li>
|
||||||
<a href='https://twitter.com/getplebchan' target='_blank' rel='noopener noreferrer'>
|
<Link
|
||||||
Twitter
|
to='/faq'
|
||||||
</a>
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('work in progress');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
FAQ
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://t.me/plebbit' target='_blank' rel='noopener noreferrer'>
|
<a href='https://t.me/plebbit' target='_blank' rel='noopener noreferrer'>
|
||||||
@@ -163,13 +176,6 @@ const Footer = () => {
|
|||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{downloadAppLink && (
|
|
||||||
<li>
|
|
||||||
<a href={downloadAppLink} target='_blank' rel='noopener noreferrer'>
|
|
||||||
{t('download_app')}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
<li>
|
<li>
|
||||||
<a href='https://etherscan.io/token/0xEA81DaB2e0EcBc6B5c4172DE4c22B6Ef6E55Bd8f' target='_blank' rel='noopener noreferrer'>
|
<a href='https://etherscan.io/token/0xEA81DaB2e0EcBc6B5c4172DE4c22B6Ef6E55Bd8f' target='_blank' rel='noopener noreferrer'>
|
||||||
{t('token')}
|
{t('token')}
|
||||||
|
|||||||
@@ -66,6 +66,14 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userAddress {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postDesktop .userAddress:hover {
|
||||||
|
color: var(--button-desktop-text-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
.postDesktop .subject {
|
.postDesktop .subject {
|
||||||
color: var(--post-subject-text-color);
|
color: var(--post-subject-text-color);
|
||||||
font-weight: var(--post-subject-font-weight);
|
font-weight: var(--post-subject-font-weight);
|
||||||
|
|||||||
Reference in New Issue
Block a user