mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(reply): add floating quote preview
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment, useAccount, useBlock } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { useFloating, shift, size, autoUpdate, Placement } from '@floating-ui/react';
|
||||
import styles from '../post.module.css';
|
||||
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils';
|
||||
import { getFormattedDate } from '../../../lib/utils/time-utils';
|
||||
@@ -19,9 +20,59 @@ import LoadingEllipsis from '../../loading-ellipsis';
|
||||
import Markdown from '../../markdown';
|
||||
import PostMenuDesktop from './post-menu-desktop/';
|
||||
import EditMenu from '../edit-menu/edit-menu';
|
||||
import { PostProps } from '../post';
|
||||
import Post, { PostProps } from '../post';
|
||||
import _ from 'lodash';
|
||||
|
||||
const ReplyLink = ({
|
||||
reply,
|
||||
subplebbitAddress,
|
||||
hoveredCid,
|
||||
setHoveredCid,
|
||||
}: {
|
||||
reply: Comment;
|
||||
subplebbitAddress: string;
|
||||
hoveredCid: string | null;
|
||||
setHoveredCid: (cid: string | null) => void;
|
||||
}) => {
|
||||
const [placement, setPlacement] = useState<Placement>('right');
|
||||
|
||||
const { refs, floatingStyles, update } = useFloating({
|
||||
placement,
|
||||
middleware: [
|
||||
shift({ padding: 10 }),
|
||||
size({
|
||||
apply({ availableWidth, elements }) {
|
||||
if (availableWidth >= 250) {
|
||||
elements.floating.style.maxWidth = `${availableWidth - 5}px`;
|
||||
} else {
|
||||
setPlacement('left');
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
whileElementsMounted: autoUpdate,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
update();
|
||||
}, [placement, update]);
|
||||
|
||||
return (
|
||||
<span className={styles.backlink}>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${reply?.cid}`} ref={refs.setReference} onMouseOver={() => setHoveredCid(reply?.cid)} onMouseLeave={() => setHoveredCid(null)}>
|
||||
c/{reply?.shortCid}
|
||||
</Link>
|
||||
{hoveredCid === reply?.cid &&
|
||||
createPortal(
|
||||
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
||||
<Post post={reply} showReplies={false} />
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, locked, pinned, parentCid, postCid, replyCount, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
|
||||
@@ -46,6 +97,8 @@ const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
|
||||
|
||||
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: address, subplebbitAddress });
|
||||
|
||||
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className={styles.postInfo}>
|
||||
{!isHidden && <EditMenu commentCid={cid} isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} />}
|
||||
@@ -111,9 +164,7 @@ const PostInfo = ({ openReplyModal, post, roles, isHidden }: PostProps) => {
|
||||
replies.map(
|
||||
(reply: Comment, index: number) =>
|
||||
reply?.parentCid === cid && (
|
||||
<span key={index} className={styles.backlink}>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${reply?.cid}`}>c/{reply?.shortCid}</Link>
|
||||
</span>
|
||||
<ReplyLink key={index} reply={reply} subplebbitAddress={subplebbitAddress} hoveredCid={hoveredCid} setHoveredCid={setHoveredCid} />
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
@@ -224,7 +275,7 @@ const PostMessage = ({ post }: PostProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
|
||||
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
||||
const { cid, content, link, pinned, replyCount, subplebbitAddress } = post || {};
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const params = useParams();
|
||||
@@ -241,11 +292,10 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
const repliesCount = pinned ? replyCount : replyCount - 5;
|
||||
const linksCount = pinned ? totallinksCount : totallinksCount - visiblelinksCount;
|
||||
|
||||
// scroll to reply if pathname is reply permalink (backlink)
|
||||
const replyRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const replyIndex = replies.findIndex((reply) => location.pathname.startsWith(`/p/${subplebbitAddress}/c/${reply?.cid}`));
|
||||
|
||||
if (replyIndex !== -1 && replyRefs.current[replyIndex]) {
|
||||
replyRefs.current[replyIndex]?.scrollIntoView();
|
||||
}
|
||||
@@ -253,9 +303,13 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
|
||||
return (
|
||||
<div className={styles.postDesktop}>
|
||||
<div className={styles.hrWrapper}>
|
||||
<hr />
|
||||
</div>
|
||||
{showAllReplies ? (
|
||||
<div className={styles.hrWrapper}>
|
||||
<hr />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.replyQuotePreviewSpacer} />
|
||||
)}
|
||||
<div className={isHidden ? styles.postDesktopBlocked : ''}>
|
||||
{!isInPostPageView && !isDescription && !isRules && (
|
||||
<span className={styles.hideButtonWrapper}>
|
||||
@@ -289,6 +343,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
!isDescription &&
|
||||
!isRules &&
|
||||
replies &&
|
||||
showReplies &&
|
||||
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => {
|
||||
const isRouteLinkToReply = location.pathname.startsWith(`/p/${subplebbitAddress}/c/${reply?.cid}`);
|
||||
return (
|
||||
|
||||
@@ -181,7 +181,7 @@ const PostMessageMobile = ({ post }: PostProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
|
||||
const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { cid, content, pinned, replyCount, subplebbitAddress } = post || {};
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
@@ -234,6 +234,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
!isDescription &&
|
||||
!isRules &&
|
||||
replies &&
|
||||
showReplies &&
|
||||
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => {
|
||||
const isRouteLinkToReply = location.pathname.startsWith(`/p/${subplebbitAddress}/c/${reply?.cid}`);
|
||||
return (
|
||||
|
||||
@@ -420,6 +420,19 @@
|
||||
background: var(--reply-highlight-background-color) !important;
|
||||
}
|
||||
|
||||
.replyQuotePreview {
|
||||
background: var(--quote-preview-background);
|
||||
border: var(--quote-preview-border);
|
||||
border-right: var(--quote-preview-border-right);
|
||||
border-bottom: var(--quote-preview-border-bottom);
|
||||
padding-right: 7px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.postDesktop .replyQuotePreviewSpacer {
|
||||
padding: 1.5px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.postDesktop {
|
||||
display: none;
|
||||
|
||||
@@ -11,10 +11,11 @@ export interface PostProps {
|
||||
reply?: any;
|
||||
roles?: Role[];
|
||||
showAllReplies?: boolean;
|
||||
showReplies?: boolean;
|
||||
openReplyModal?: (cid: string) => void;
|
||||
}
|
||||
|
||||
const Post = ({ post, showAllReplies = false, openReplyModal }: PostProps) => {
|
||||
const Post = ({ post, showAllReplies = false, showReplies = true, openReplyModal }: PostProps) => {
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress });
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
@@ -22,9 +23,9 @@ const Post = ({ post, showAllReplies = false, openReplyModal }: PostProps) => {
|
||||
<div className={styles.thread}>
|
||||
<div className={styles.postContainer}>
|
||||
{isMobile ? (
|
||||
<PostMobile post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} openReplyModal={openReplyModal} />
|
||||
<PostMobile post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} openReplyModal={openReplyModal} />
|
||||
) : (
|
||||
<PostDesktop post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} openReplyModal={openReplyModal} />
|
||||
<PostDesktop post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} openReplyModal={openReplyModal} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => {
|
||||
<input
|
||||
type='text'
|
||||
defaultValue={displayName}
|
||||
placeholder={displayName ? undefined : _.capitalize(t('anonymous'))}
|
||||
placeholder={displayName ? undefined : _.capitalize(t('name'))}
|
||||
onChange={(e) => setAccount({ ...account, author: { ...account?.author, displayName: e.target.value } })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ interface EditCommentPrivileges {
|
||||
}
|
||||
|
||||
const useEditCommentPrivileges = ({ commentAuthorAddress, subplebbitAddress }: EditCommentPrivileges) => {
|
||||
const accountAuthorAddress = useAccount().author?.address;
|
||||
const accountAuthorAddress = useAccount()?.author?.address;
|
||||
const { roles } = useSubplebbit({ subplebbitAddress }) || {};
|
||||
|
||||
const commentAuthorRole = roles?.[commentAuthorAddress]?.role;
|
||||
|
||||
+27
-3
@@ -151,7 +151,11 @@
|
||||
--post-quotelink-text-color: navy;
|
||||
--post-quotelink-text-color-hover: red;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #f0e0d6;
|
||||
--quote-preview-border: 1px solid #d9bfb7;
|
||||
--quote-preview-border-right: 2px solid #d9bfb7;
|
||||
--quote-preview-border-bottom: 2px solid #d9bfb7;
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #e0bfb7;
|
||||
@@ -346,7 +350,11 @@
|
||||
--post-quotelink-text-color: #d00;
|
||||
--post-quotelink-text-color-hover: #d00;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #d6daf0;
|
||||
--quote-preview-border: 1px solid #b7c5d9;
|
||||
--quote-preview-border-right: 2px solid #b7c5d9;
|
||||
--quote-preview-border-bottom: 2px solid #b7c5d9;
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #b7c5d9;
|
||||
@@ -513,6 +521,10 @@
|
||||
--post-quotelink-text-color-hover: red;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #f0e0d6;
|
||||
--quote-preview-border: 1px solid rgba(0,0,0,.2);
|
||||
--quote-preview-border-right: 2px solid rgba(0,0,0,.2);
|
||||
--quote-preview-border-bottom: 2px solid rgba(0,0,0,.2);
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #e0bfb7;
|
||||
@@ -687,7 +699,11 @@
|
||||
--post-quotelink-text-color: #d00;
|
||||
--post-quotelink-text-color-hover: #d00;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #d6daf0;
|
||||
--quote-preview-border: 1px solid rgba(0,0,0,.2);
|
||||
--quote-preview-border-right: 2px solid rgba(0,0,0,.2);
|
||||
--quote-preview-border-bottom: 2px solid rgba(0,0,0,.2);
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #b7c5d9;
|
||||
@@ -874,6 +890,10 @@
|
||||
--post-quotelink-text-color-hover: #81a2be;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #282a2e;
|
||||
--quote-preview-border: 1px solid #333;
|
||||
--quote-preview-border-right: 1px solid #333;
|
||||
--quote-preview-border-bottom: 1px solid #333;
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #c5c8c6;
|
||||
@@ -1058,6 +1078,10 @@
|
||||
--post-quotelink-text-color-hover: #f30;
|
||||
--post-quotelink-text-decoration: underline;
|
||||
--post-quotelink-text-decoration-hover: underline;
|
||||
--quote-preview-background: #ddd;
|
||||
--quote-preview-border: 1px solid #ccc;
|
||||
--quote-preview-border-right: 1px solid #ccc;
|
||||
--quote-preview-border-bottom: 1px solid #ccc;
|
||||
|
||||
/* reply desktop */
|
||||
--side-arrows-color: #333;
|
||||
|
||||
Reference in New Issue
Block a user