fix catalogs popups logic and styling

This commit is contained in:
plebeius.eth
2023-08-24 15:28:49 +02:00
parent af46da2f83
commit 87ee38df3d
4 changed files with 327 additions and 143 deletions
+16 -14
View File
@@ -8,8 +8,7 @@ export const Threads = styled.div`
.thread { .thread {
width: 180px; width: 180px;
position:relative; position: relative;
position:relative;
max-height: 320px; max-height: 320px;
vertical-align: top; vertical-align: top;
display: inline-block; display: inline-block;
@@ -31,6 +30,7 @@ export const Threads = styled.div`
padding-left:5px;padding-right:5px; padding-left:5px;padding-right:5px;
border-radius:3px; border-radius:3px;
} }
.thread_popup_content{ .thread_popup_content{
color: #dedede; color: #dedede;
display: inline-block; display: inline-block;
@@ -38,6 +38,7 @@ export const Threads = styled.div`
white-space:nowrap; white-space:nowrap;
font-size:13px; font-size:13px;
} }
.thread_popup_lastReply{ .thread_popup_lastReply{
color: #bbbfbd; color: #bbbfbd;
display: block; display: block;
@@ -47,34 +48,35 @@ export const Threads = styled.div`
text-overflow: ellipsis; text-overflow: ellipsis;
font-size:90%; font-size:90%;
} }
.thread_popup_title{ .thread_popup_title{
color:#a89baa color:#a89baa
} }
.thread_popup_author{ .thread_popup_author{
color:#519860;font-weight:bold color:#519860;font-weight:bold
} }
.thread_popup_authorAdmin{ .thread_popup_authorAdmin{
color:#682071; color:#682071;
} }
video, audio, img { video, audio, img {
max-width: 150px; max-width: 150px;
max-height: 150px; max-height: 150px;
} }
.file-thumb { .file-thumb {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
display: inline-block; display: inline-flex;
text-align: center; justify-content: center;
line-height: 150px; align-items: center;
margin-bottom: 5px; margin-bottom: 5px;
} }
.card { .card {
display: inline-block; box-shadow: 0 0 5px rgba(0, 0, 0, .25);
margin: auto; border: 0;
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
border: 0;
vertical-align: middle;
} }
.thread-icons { .thread-icons {
@@ -133,9 +135,9 @@ export const Threads = styled.div`
.teaser { .teaser {
display: block; display: block;
padding: 0 15px; padding: 0 15px;
height:200px; overflow: hidden;
overflow:hidden;
} }
${({ selectedStyle }) => { ${({ selectedStyle }) => {
switch (selectedStyle) { switch (selectedStyle) {
case 'Yotsuba': case 'Yotsuba':
+86 -4
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { Link, useNavigate} from 'react-router-dom'; import { Link, useNavigate} from 'react-router-dom';
@@ -21,6 +21,7 @@ import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal'; import SettingsModal from '../modals/SettingsModal';
import countLinks from '../../utils/countLinks'; import countLinks from '../../utils/countLinks';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getFormattedTime from '../../utils/getFormattedTime';
import handleShareClick from '../../utils/handleShareClick'; import handleShareClick from '../../utils/handleShareClick';
import handleStyleChange from '../../utils/handleStyleChange'; import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError'; import useError from '../../hooks/useError';
@@ -75,6 +76,51 @@ const CatalogPost = ({post}) => {
const threadMenuRefs = useRef({}); const threadMenuRefs = useRef({});
const postMenuRef = useRef(null); const postMenuRef = useRef(null);
const postMenuCatalogRef = useRef(null); const postMenuCatalogRef = useRef(null);
const popupRef = useRef(null);
const threadRefs = useRef([]);
const [isEnoughSpaceOnRight, setIsEnoughSpaceOnRight] = useState(null);
const [isEnoughSpaceOnLeft,setIsEnoughSpaceOnLeft] = useState(null);
const [isCalculationDone,setIsCalculationDone] = useState(false);
const [hoverTimeoutId, setHoverTimeoutId] = useState(null);
useLayoutEffect(() => {
const executeLayoutEffectLogic = () => {
let ref;
ref = threadRefs.current[isHoveringOnThread];
if (ref && popupRef.current) {
const threadRect = ref.getBoundingClientRect();
const popupRect = popupRef.current.getBoundingClientRect();
const viewportWidth = document.documentElement.clientWidth;
const spaceOnRight = viewportWidth - (threadRect.left + threadRect.width);
const spaceOnLeft = threadRect.left;
const popupWidth = popupRect.width;
setIsEnoughSpaceOnLeft(spaceOnLeft >= popupWidth + 10);
setIsEnoughSpaceOnRight(spaceOnRight >= popupWidth + 10);
setIsCalculationDone(true);
}
};
if (isHoveringOnThread) {
const timeoutId = setTimeout(executeLayoutEffectLogic, 500);
return () => clearTimeout(timeoutId);
}
}, [isHoveringOnThread]);
const handleMouseOnLeaveThread = () => {
if (hoverTimeoutId) {
clearTimeout(hoverTimeoutId);
setHoverTimeoutId(null);
setIsHoveringOnThread("")
} else if (isHoveringOnThread !== "") {
setIsHoveringOnThread("");
setIsEnoughSpaceOnRight(null);
setIsCalculationDone(false);
}
};
useEffect(() => { useEffect(() => {
@@ -272,16 +318,51 @@ const CatalogPost = ({post}) => {
let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight)); let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight));
displayWidth = `${thread.linkWidth * scale}px`; displayWidth = `${thread.linkWidth * scale}px`;
displayHeight = `${thread.linkHeight * scale}px`; displayHeight = `${thread.linkHeight * scale}px`;
} else { } else if (thread.link) {
displayWidth = '150px'; displayWidth = '150px';
displayHeight = '150px'; displayHeight = '150px';
} else {
displayWidth = '0px';
displayHeight = '0px';
} }
return ( return (
<div key={`thread-`} className="thread" <div key={`thread-`} className="thread"
onMouseOver={() => {setIsHoveringOnThread(thread.cid)}} onMouseOver={() => {setIsHoveringOnThread(thread.cid)}}
onMouseLeave={() => {setIsHoveringOnThread('')}}> onMouseLeave={() => {handleMouseOnLeaveThread()}}>
{isHoveringOnThread === thread.cid ?
<div ref={popupRef}
onMouseOver={() => handleMouseOnLeaveThread()}
style={{
opacity: isCalculationDone && isEnoughSpaceOnRight !== null ? 1 : 0,
visibility: isCalculationDone && isEnoughSpaceOnRight !== null ? 'visible' : 'hidden',
left: isCalculationDone && isEnoughSpaceOnRight !== null && isEnoughSpaceOnRight ? '100%' : 'auto',
right: isCalculationDone && isEnoughSpaceOnRight !== null && !isEnoughSpaceOnRight ? '100%' : 'auto',}}
className="thread_popup">
<p
style={isEnoughSpaceOnLeft !== null && !isEnoughSpaceOnLeft
&& !isEnoughSpaceOnRight
?
{overflow:"visible",whiteSpace:"normal"}
: null
}
className="thread_popup_content">
<span className="thread_popup_title" >
{thread.title ? `${thread.title} ` : "Posted "}
</span>
by
<span className="thread_popup_author">
{thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
</span>
{thread.timestamp ? getFormattedTime(thread.timestamp) : null}</p>
<div>
{thread.replyCount > 0 ?
<p className="thread_popup_lastReply">
Last reply by <span className="thread_popup_author"> Anonymous </span>
{getFormattedTime(thread.lastReplyTimestamp)}</p>
: null}</div>
</div> : null }
{commentMediaInfo?.url ? ( {commentMediaInfo?.url ? (
<Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
@@ -491,7 +572,8 @@ const CatalogPost = ({post}) => {
</BoardForm> </BoardForm>
<Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
<div key={`t-`} className="teaser"> <div key={`t-`} className="teaser" style={{maxHeight: `calc(320px - ${displayHeight})`}}
ref={(el) => (threadRefs.current[thread.cid] = el)}>
<b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b> <b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b>
{thread.content ? `: ${thread.content}` : null} {thread.content ? `: ${thread.content}` : null}
</div> </div>
+136 -118
View File
@@ -1,4 +1,4 @@
import React, { useCallback,useLayoutEffect, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useLayoutEffect, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { Link, useNavigate, useParams } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
@@ -23,6 +23,7 @@ import OfflineIndicator from '../OfflineIndicator';
import VerifiedAuthor from '../VerifiedAuthor'; import VerifiedAuthor from '../VerifiedAuthor';
import countLinks from '../../utils/countLinks'; import countLinks from '../../utils/countLinks';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getFormattedTime from '../../utils/getFormattedTime';
import handleShareClick from '../../utils/handleShareClick'; import handleShareClick from '../../utils/handleShareClick';
import handleStyleChange from '../../utils/handleStyleChange'; import handleStyleChange from '../../utils/handleStyleChange';
import useAnonModeRef from '../../hooks/useAnonModeRef'; import useAnonModeRef from '../../hooks/useAnonModeRef';
@@ -88,62 +89,52 @@ const CatalogPost = ({post}) => {
const [isEnoughSpaceOnRight, setIsEnoughSpaceOnRight] = useState(null); const [isEnoughSpaceOnRight, setIsEnoughSpaceOnRight] = useState(null);
const [isEnoughSpaceOnLeft,setIsEnoughSpaceOnLeft] = useState(null); const [isEnoughSpaceOnLeft,setIsEnoughSpaceOnLeft] = useState(null);
const [isCalculationDone,setIsCalculationDone] = useState(false); const [isCalculationDone,setIsCalculationDone] = useState(false);
const [hoverTimeoutId, setHoverTimeoutId] = useState(null);
const [isHoveringForMenu, setIsHoveringForMenu] = useState(false);
useLayoutEffect(() => { useLayoutEffect(() => {
setIsEnoughSpaceOnRight(null); const executeLayoutEffectLogic = () => {
setIsEnoughSpaceOnLeft(null); let ref;
const calculateSpaceOnRight = () => { if (isHoveringOnThread === 'rules' || isHoveringOnThread === 'description') {
if (thread.cid === isHoveringOnThread){ ref = popupRef.current;
if(threadRefs.current[thread.cid] && popupRef.current) { } else {
const threadRect = threadRefs.current[thread.cid].getBoundingClientRect(); ref = threadRefs.current[isHoveringOnThread];
const popupRect = popupRef.current.getBoundingClientRect(); }
// Get the width of the viewport area inside the browser window
const viewportWidth = document.documentElement.clientWidth;
// Calculate the space on the right side (relative to the viewport) if (ref && popupRef.current) {
const spaceOnRight = viewportWidth - (threadRect.left + threadRect.width); const threadRect = ref.getBoundingClientRect();
const spaceOnLeft = threadRect.left; const popupRect = popupRef.current.getBoundingClientRect();
// Define the popup width const viewportWidth = document.documentElement.clientWidth;
const popupWidth = popupRect.width; const spaceOnRight = viewportWidth - (threadRect.left + threadRect.width);
// Check if there is enough space for the popup on the right side const spaceOnLeft = threadRect.left;
const EnoughSpaceOnRight = spaceOnRight >= popupWidth + 10; const popupWidth = popupRect.width;
const EnoughSpaceOnLeft = spaceOnLeft >= popupWidth + 10; setIsEnoughSpaceOnLeft(spaceOnLeft >= popupWidth + 10);
setIsEnoughSpaceOnLeft(EnoughSpaceOnLeft); setIsEnoughSpaceOnRight(spaceOnRight >= popupWidth + 10);
setIsEnoughSpaceOnRight(EnoughSpaceOnRight); setIsCalculationDone(true);
return setIsCalculationDone(true); }
}}
}; };
if(!isCalculationDone && isHoveringOnThread){
calculateSpaceOnRight(); if (isHoveringOnThread) {
const timeoutId = setTimeout(executeLayoutEffectLogic, 500);
return () => clearTimeout(timeoutId);
} }
}, [isHoveringOnThread]); }, [isHoveringOnThread]);
const timeElapsedCalc = (date) => {
const currentDateTime = new Date()
const targetToDate = new Date(date * 1000 ); // unix timestamp * 1000
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
const targetDateTime = new Date(`${monthNames[targetToDate.getMonth()]} ${targetToDate.getDate()} ${targetToDate.getFullYear()} ${targetToDate.getHours()}:${targetToDate.getMinutes()}:${targetToDate.getSeconds()}`)
const elapsedDateTime = currentDateTime - targetDateTime;
const elapsedDay = Math.floor(elapsedDateTime / (1000 * 60 * 60 * 24));
const elapsedHour = Math.floor((elapsedDateTime / (1000 * 60 * 60)) % 24);
const elapsedMinute = Math.floor((elapsedDateTime / (1000 * 60)) % 60);
if(elapsedDay > 0){
return `${elapsedDay} ${elapsedDay > 0 ? "days" : "day"}`;
}else if(elapsedHour > 0 ){
return `${elapsedHour} ${elapsedHour > 0 ? "hours" : "hour"}`;
}else if(elapsedMinute > 0){
return `${elapsedMinute} ${elapsedMinute > 0 ? "minutes" : "minute"}`
}
}
const handleMouseOnLeaveThread = () => { const handleMouseOnLeaveThread = () => {
setIsHoveringOnThread(""); setIsHoveringForMenu(false);
setIsEnoughSpaceOnRight(null); if (hoverTimeoutId) {
setIsCalculationDone(false); clearTimeout(hoverTimeoutId);
} setHoverTimeoutId(null);
setIsHoveringOnThread("")
} else if (isHoveringOnThread !== "") {
setIsHoveringOnThread("");
setIsEnoughSpaceOnRight(null);
setIsCalculationDone(false);
}
};
const handleOutsideClick = useCallback((e) => { const handleOutsideClick = useCallback((e) => {
@@ -323,32 +314,39 @@ const CatalogPost = ({post}) => {
let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight)); let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight));
displayWidth = `${thread.linkWidth * scale}px`; displayWidth = `${thread.linkWidth * scale}px`;
displayHeight = `${thread.linkHeight * scale}px`; displayHeight = `${thread.linkHeight * scale}px`;
} else { } else if (thread.link) {
displayWidth = '150px'; displayWidth = '150px';
displayHeight = '150px'; displayHeight = '150px';
} else {
displayWidth = '0px';
displayHeight = '0px';
} }
if (post.type === 'rules') { if (post.type === 'rules') {
return ( return (
<div className='thread' <div className='thread'
onMouseOver={() => setIsHoveringOnThread("rules")}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}> onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
{isHoveringOnThread === 'rules' ? {isHoveringOnThread === 'rules' ?
<div style={{left:"100%"}} <div style={{left:"100%"}}
onMouseOver={() => setIsHoveringOnThread("")}
className={"thread_popup"}> className={"thread_popup"}>
<p className="thread_popup_content" style={{color:"white"}}> <p className="thread_popup_content" style={{color:"white"}}>
<span className="thread_popup_title">Rules </span> <span className="thread_popup_title">Rules </span>
by by
<span className="thread_popup_authorAdmin"> ## Board Admins </span> <span className="thread_popup_authorAdmin"> ## Board Admins </span>
{timeElapsedCalc(subplebbit.createdAt)}</p> {getFormattedTime(subplebbit.createdAt)}</p>
<div>
<p className="thread_popup_content">Last reply by
<span className="thread_popup_authorAdmin"> Anonymous </span>
560 days ago</p>
</div>
</div> : null } </div> : null }
<BoardForm selectedStyle={selectedStyle} style={{all: 'unset'}}> <BoardForm selectedStyle={selectedStyle} style={{all: 'unset'}}
onMouseOver={() => {
setIsHoveringForMenu('rules');
if (!hoverTimeoutId) {
const timeoutId = setTimeout(() => {
setIsHoveringOnThread("rules");
}, 500);
setHoverTimeoutId(timeoutId);
}
}}>
<div className='meta' title="(R)eplies / (L)ink Replies"> <div className='meta' title="(R)eplies / (L)ink Replies">
R:&nbsp;<b>0</b>&nbsp;/&nbsp;L:&nbsp;<b>0</b> R:&nbsp;<b>0</b>&nbsp;/&nbsp;L:&nbsp;<b>0</b>
<div className='thread-icons' <div className='thread-icons'
@@ -361,7 +359,7 @@ const CatalogPost = ({post}) => {
imageRendering: "pixelated",}} /> imageRendering: "pixelated",}} />
</div> </div>
<PostMenu <PostMenu
style={{ display: isHoveringOnThread === "rules" ? 'inline-block' : 'none', style={{ display: isHoveringForMenu === "rules" ? 'inline-block' : 'none',
position: 'absolute', lineHeight: '1em', marginTop: '-1px', outline: 'none', position: 'absolute', lineHeight: '1em', marginTop: '-1px', outline: 'none',
zIndex: '999'}} zIndex: '999'}}
title="Post menu" title="Post menu"
@@ -407,8 +405,17 @@ const CatalogPost = ({post}) => {
</PostMenuCatalog>, document.body </PostMenuCatalog>, document.body
)} )}
</BoardForm> </BoardForm>
<Link style={{all: "unset", cursor: "pointer"}} to={`/p/${selectedAddress}/rules`}> <Link style={{all: "unset", cursor: "pointer"}} to={`/p/${selectedAddress}/rules`}
<div className="teaser"> onMouseOver={() => {
setIsHoveringForMenu('rules');
if (!hoverTimeoutId) {
const timeoutId = setTimeout(() => {
setIsHoveringOnThread("rules");
}, 500);
setHoverTimeoutId(timeoutId);
}
}}>
<div className="teaser" style={{maxHeight: '312px'}}>
<b>Rules</b> <b>Rules</b>
{": " + subplebbit.rules?.map((rule, index) => `${index + 1}. ${rule}`).join(' ')} {": " + subplebbit.rules?.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
</div> </div>
@@ -419,26 +426,29 @@ const CatalogPost = ({post}) => {
return ( return (
<> <>
<div className='thread' <div className='thread'
onMouseOver={() => setIsHoveringOnThread("description")}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}> onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
{/* <!-- hovering div --> */}
{isHoveringOnThread === 'description' ? {isHoveringOnThread === 'description' ?
<div style={{left:"100%"}} <div style={{left:"100%"}}
onMouseOver={() => setIsHoveringOnThread("")}
className={"thread_popup"}> className={"thread_popup"}>
<p className="thread_popup_content" style={{color:"white"}}> <p className="thread_popup_content" style={{color:"white"}}>
<span className="thread_popup_title">Welcome to {subplebbit.title || subplebbit.address} </span> <span className="thread_popup_title">Welcome to {subplebbit.title || subplebbit.address} </span>
by by
<span className="thread_popup_authorAdmin"> ## Board Admins </span> <span className="thread_popup_authorAdmin"> ## Board Admins </span>
{timeElapsedCalc(subplebbit.createdAt)} ago</p> {getFormattedTime(subplebbit.createdAt)}</p>
<div>
<p className="thread_popup_content">Last reply by
<span className="thread_popup_authorAdmin"> Anonymous </span>
560 days ago</p>
</div>
</div> : null } </div> : null }
<Link style={{all: 'unset', cursor: 'pointer'}} to={`/p/${selectedAddress}/description`}> <Link style={{all: 'unset', cursor: 'pointer'}} to={`/p/${selectedAddress}/description`}>
{subplebbit.suggested?.avatarUrl ? ( {subplebbit.suggested?.avatarUrl ? (
<img className='card' <img className='card'
onMouseOver={() => {
setIsHoveringForMenu('description');
if (!hoverTimeoutId) {
const timeoutId = setTimeout(() => {
setIsHoveringOnThread("description");
}, 500);
setHoverTimeoutId(timeoutId);
}
}}
src={subplebbit.suggested.avatarUrl} alt="board avatar" /> src={subplebbit.suggested.avatarUrl} alt="board avatar" />
) : null} ) : null}
</Link> </Link>
@@ -452,8 +462,17 @@ const CatalogPost = ({post}) => {
imageRendering: "pixelated",}} /> imageRendering: "pixelated",}} />
</div> </div>
) : null} ) : null}
<BoardForm selectedStyle={selectedStyle} style={{all: 'unset'}}> <BoardForm selectedStyle={selectedStyle} style={{all: 'unset'}}
<div style={{marginTop: '5px'}} className='meta' title="(R)eplies / (L)ink Replies"> onMouseOver={() => {
setIsHoveringForMenu('description');
if (!hoverTimeoutId) {
const timeoutId = setTimeout(() => {
setIsHoveringOnThread("description");
}, 500);
setHoverTimeoutId(timeoutId);
}
}}>
<div className='meta' title="(R)eplies / (L)ink Replies">
R:&nbsp;<b>0</b>&nbsp;/&nbsp;L:&nbsp;<b>0</b> R:&nbsp;<b>0</b>&nbsp;/&nbsp;L:&nbsp;<b>0</b>
{subplebbit.suggested?.avatarUrl ? null : ( {subplebbit.suggested?.avatarUrl ? null : (
<div className='thread-icons' <div className='thread-icons'
@@ -467,7 +486,7 @@ const CatalogPost = ({post}) => {
</div> </div>
)} )}
<PostMenu <PostMenu
style={{ display: isHoveringOnThread === "description" ? 'inline-block' : 'none', style={{ display: isHoveringForMenu === "description" ? 'inline-block' : 'none',
position: 'absolute', lineHeight: '1em', marginTop: '-1px', outline: 'none', position: 'absolute', lineHeight: '1em', marginTop: '-1px', outline: 'none',
zIndex: '999'}} zIndex: '999'}}
title="Post menu" title="Post menu"
@@ -542,9 +561,17 @@ const CatalogPost = ({post}) => {
)} )}
</BoardForm> </BoardForm>
<Link style={{all: "unset", cursor: "pointer"}} to={`/p/${selectedAddress}/description`} <Link style={{all: "unset", cursor: "pointer"}} to={`/p/${selectedAddress}/description`}
onClick={() => setSelectedThread("description")}> onClick={() => setSelectedThread("description")} onMouseOver={() => {
<div className="teaser"> setIsHoveringForMenu('description');
<b>Welcome to {subplebbit.title || subplebbit.address}</b> if (!hoverTimeoutId) {
const timeoutId = setTimeout(() => {
setIsHoveringOnThread("description");
}, 500);
setHoverTimeoutId(timeoutId);
}
}}>
<div className="teaser" style={{maxHeight: '170px'}}>
<b>Welcome to {subplebbit.title || subplebbit.address}!</b>
{": " + subplebbit.description} {": " + subplebbit.description}
</div> </div>
</Link> </Link>
@@ -553,38 +580,40 @@ const CatalogPost = ({post}) => {
) )
} else { } else {
return ( return (
<div key={`thread-`} className="thread"> <div key={`thread-`} className="thread"
{isHoveringOnThread === thread.cid ? onMouseLeave={()=>{handleMouseOnLeaveThread()}} onMouseOver={() => setIsHoveringOnThread(thread.cid)}>
<div ref={popupRef} {isHoveringOnThread === thread.cid ?
style={{ <div ref={popupRef}
opacity: isCalculationDone && isEnoughSpaceOnRight !== null ? 1 : 0, onMouseOver={() => handleMouseOnLeaveThread()}
left: isCalculationDone && isEnoughSpaceOnRight !== null && isEnoughSpaceOnRight ? '100%' : 'auto', style={{
right: isCalculationDone && isEnoughSpaceOnRight !== null && !isEnoughSpaceOnRight ? '100%' : 'auto',}} opacity: isCalculationDone && isEnoughSpaceOnRight !== null ? 1 : 0,
className="thread_popup"> visibility: isCalculationDone && isEnoughSpaceOnRight !== null ? 'visible' : 'hidden',
<p left: isCalculationDone && isEnoughSpaceOnRight !== null && isEnoughSpaceOnRight ? '100%' : 'auto',
style={isEnoughSpaceOnLeft !== null && !isEnoughSpaceOnLeft right: isCalculationDone && isEnoughSpaceOnRight !== null && !isEnoughSpaceOnRight ? '100%' : 'auto',}}
&& !isEnoughSpaceOnRight className="thread_popup">
? <p
{overflow:"visible",whiteSpace:"normal"} style={isEnoughSpaceOnLeft !== null && !isEnoughSpaceOnLeft
: null && !isEnoughSpaceOnRight
} ?
className="thread_popup_content"> {overflow:"visible",whiteSpace:"normal"}
<span className="thread_popup_title" > : null
{thread.title ? `${thread.title} ` : "Posted "} }
</span> className="thread_popup_content">
by <span className="thread_popup_title" >
<span className="thread_popup_author"> {thread.title ? `${thread.title} ` : "Posted "}
{thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "} </span>
</span> by
{thread.timestamp ? timeElapsedCalc(thread.timestamp) : null} ago</p> <span className="thread_popup_author">
<div> {thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
{thread.replyCount > 0 ? </span>
<p className="thread_popup_lastReply"> {thread.timestamp ? getFormattedTime(thread.timestamp) : null}</p>
Last reply by <span className="thread_popup_author"> Anonymous </span> <div>
{timeElapsedCalc(thread.lastReplyTimestamp)} ago</p> {thread.replyCount > 0 ?
: null}</div> <p className="thread_popup_lastReply">
</div> : null } Last reply by <span className="thread_popup_author"> Anonymous </span>
{getFormattedTime(thread.lastReplyTimestamp)}</p>
: null}</div>
</div> : null }
{commentMediaInfo?.url ? ( {commentMediaInfo?.url ? (
<Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${selectedAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${selectedAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
@@ -593,8 +622,6 @@ const CatalogPost = ({post}) => {
<span className="file-thumb" style={{width: displayWidth, height: displayHeight}}> <span className="file-thumb" style={{width: displayWidth, height: displayHeight}}>
<img className="card" key={`img-`} <img className="card" key={`img-`}
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type} src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
onMouseOver={() => setIsHoveringOnThread(thread.cid)}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}
onError={(e) => { onError={(e) => {
e.target.src = fallbackImgUrl e.target.src = fallbackImgUrl
e.target.onerror = null; e.target.onerror = null;
@@ -606,8 +633,6 @@ const CatalogPost = ({post}) => {
<span className="file-thumb" style={{width: displayWidth, height: displayHeight}}> <span className="file-thumb" style={{width: displayWidth, height: displayHeight}}>
<img className="card" key={`img-`} <img className="card" key={`img-`}
src={commentMediaInfo.url} alt={commentMediaInfo.type} src={commentMediaInfo.url} alt={commentMediaInfo.type}
onMouseOver={() => setIsHoveringOnThread(thread.cid)}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}
onError={(e) => { onError={(e) => {
e.target.src = fallbackImgUrl e.target.src = fallbackImgUrl
e.target.onerror = null;}} /> e.target.onerror = null;}} />
@@ -616,8 +641,6 @@ const CatalogPost = ({post}) => {
{commentMediaInfo?.type === "video" ? ( {commentMediaInfo?.type === "video" ? (
<span className="file-thumb" style={{width: displayWidth, height: displayHeight}}> <span className="file-thumb" style={{width: displayWidth, height: displayHeight}}>
<video className="card" key={`fti-`} <video className="card" key={`fti-`}
onMouseOver={() => setIsHoveringOnThread(thread.cid)}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}
src={commentMediaInfo.url} src={commentMediaInfo.url}
alt={commentMediaInfo.type} alt={commentMediaInfo.type}
onError={(e) => e.target.src = fallbackImgUrl} /> onError={(e) => e.target.src = fallbackImgUrl} />
@@ -628,8 +651,6 @@ const CatalogPost = ({post}) => {
key={`fti-`} key={`fti-`}
src={commentMediaInfo.url} src={commentMediaInfo.url}
alt={commentMediaInfo.type} alt={commentMediaInfo.type}
onMouseOver={() => setIsHoveringOnThread(thread.cid)}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}
onError={(e) => e.target.src = fallbackImgUrl} /> onError={(e) => e.target.src = fallbackImgUrl} />
) : null} ) : null}
</Link> </Link>
@@ -820,11 +841,8 @@ const CatalogPost = ({post}) => {
</BoardForm> </BoardForm>
<Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${selectedAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${selectedAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
<div key={`t-`} className="teaser"> <div key={`t-`} className="teaser" style={{maxHeight: `calc(320px - ${displayHeight})`}}>
<div style={{cursor:"text"}} <div style={{cursor:"text"}}
onMouseOver={() => {
!thread.thumbnailUrl && setIsHoveringOnThread(thread.cid)}}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}
ref={(el) => (threadRefs.current[thread.cid] = el)}> ref={(el) => (threadRefs.current[thread.cid] = el)}>
<b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b> <b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b>
{thread.content ? `: ${thread.content}` : null} {thread.content ? `: ${thread.content}` : null}
+86 -4
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { Link, useNavigate} from 'react-router-dom'; import { Link, useNavigate} from 'react-router-dom';
@@ -21,6 +21,7 @@ import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal'; import SettingsModal from '../modals/SettingsModal';
import countLinks from '../../utils/countLinks'; import countLinks from '../../utils/countLinks';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import getFormattedTime from '../../utils/getFormattedTime';
import handleShareClick from '../../utils/handleShareClick'; import handleShareClick from '../../utils/handleShareClick';
import handleStyleChange from '../../utils/handleStyleChange'; import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError'; import useError from '../../hooks/useError';
@@ -75,6 +76,51 @@ const CatalogPost = ({post}) => {
const threadMenuRefs = useRef({}); const threadMenuRefs = useRef({});
const postMenuRef = useRef(null); const postMenuRef = useRef(null);
const postMenuCatalogRef = useRef(null); const postMenuCatalogRef = useRef(null);
const popupRef = useRef(null);
const threadRefs = useRef([]);
const [isEnoughSpaceOnRight, setIsEnoughSpaceOnRight] = useState(null);
const [isEnoughSpaceOnLeft,setIsEnoughSpaceOnLeft] = useState(null);
const [isCalculationDone,setIsCalculationDone] = useState(false);
const [hoverTimeoutId, setHoverTimeoutId] = useState(null);
useLayoutEffect(() => {
const executeLayoutEffectLogic = () => {
let ref;
ref = threadRefs.current[isHoveringOnThread];
if (ref && popupRef.current) {
const threadRect = ref.getBoundingClientRect();
const popupRect = popupRef.current.getBoundingClientRect();
const viewportWidth = document.documentElement.clientWidth;
const spaceOnRight = viewportWidth - (threadRect.left + threadRect.width);
const spaceOnLeft = threadRect.left;
const popupWidth = popupRect.width;
setIsEnoughSpaceOnLeft(spaceOnLeft >= popupWidth + 10);
setIsEnoughSpaceOnRight(spaceOnRight >= popupWidth + 10);
setIsCalculationDone(true);
}
};
if (isHoveringOnThread) {
const timeoutId = setTimeout(executeLayoutEffectLogic, 500);
return () => clearTimeout(timeoutId);
}
}, [isHoveringOnThread]);
const handleMouseOnLeaveThread = () => {
if (hoverTimeoutId) {
clearTimeout(hoverTimeoutId);
setHoverTimeoutId(null);
setIsHoveringOnThread("")
} else if (isHoveringOnThread !== "") {
setIsHoveringOnThread("");
setIsEnoughSpaceOnRight(null);
setIsCalculationDone(false);
}
};
useEffect(() => { useEffect(() => {
@@ -272,16 +318,51 @@ const CatalogPost = ({post}) => {
let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight)); let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight));
displayWidth = `${thread.linkWidth * scale}px`; displayWidth = `${thread.linkWidth * scale}px`;
displayHeight = `${thread.linkHeight * scale}px`; displayHeight = `${thread.linkHeight * scale}px`;
} else { } else if (thread.link) {
displayWidth = '150px'; displayWidth = '150px';
displayHeight = '150px'; displayHeight = '150px';
} else {
displayWidth = '0px';
displayHeight = '0px';
} }
return ( return (
<div key={`thread-`} className="thread" <div key={`thread-`} className="thread"
onMouseOver={() => {setIsHoveringOnThread(thread.cid)}} onMouseOver={() => {setIsHoveringOnThread(thread.cid)}}
onMouseLeave={() => {setIsHoveringOnThread('')}}> onMouseLeave={() => {handleMouseOnLeaveThread()}}>
{isHoveringOnThread === thread.cid ?
<div ref={popupRef}
onMouseOver={() => handleMouseOnLeaveThread()}
style={{
opacity: isCalculationDone && isEnoughSpaceOnRight !== null ? 1 : 0,
visibility: isCalculationDone && isEnoughSpaceOnRight !== null ? 'visible' : 'hidden',
left: isCalculationDone && isEnoughSpaceOnRight !== null && isEnoughSpaceOnRight ? '100%' : 'auto',
right: isCalculationDone && isEnoughSpaceOnRight !== null && !isEnoughSpaceOnRight ? '100%' : 'auto',}}
className="thread_popup">
<p
style={isEnoughSpaceOnLeft !== null && !isEnoughSpaceOnLeft
&& !isEnoughSpaceOnRight
?
{overflow:"visible",whiteSpace:"normal"}
: null
}
className="thread_popup_content">
<span className="thread_popup_title" >
{thread.title ? `${thread.title} ` : "Posted "}
</span>
by
<span className="thread_popup_author">
{thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
</span>
{thread.timestamp ? getFormattedTime(thread.timestamp) : null}</p>
<div>
{thread.replyCount > 0 ?
<p className="thread_popup_lastReply">
Last reply by <span className="thread_popup_author"> Anonymous </span>
{getFormattedTime(thread.lastReplyTimestamp)}</p>
: null}</div>
</div> : null }
{commentMediaInfo?.url ? ( {commentMediaInfo?.url ? (
<Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
@@ -491,7 +572,8 @@ const CatalogPost = ({post}) => {
</BoardForm> </BoardForm>
<Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`} <Link style={{all: "unset", cursor: "pointer"}} key={`link2-`} to={`/p/${thread.subplebbitAddress}/c/${thread.cid}`}
onClick={() => setSelectedThread(thread.cid)}> onClick={() => setSelectedThread(thread.cid)}>
<div key={`t-`} className="teaser"> <div key={`t-`} className="teaser" style={{maxHeight: `calc(320px - ${displayHeight})`}}
ref={(el) => (threadRefs.current[thread.cid] = el)}>
<b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b> <b key={`b2-`}>{thread.title ? `${thread.title}` : null}</b>
{thread.content ? `: ${thread.content}` : null} {thread.content ? `: ${thread.content}` : null}
</div> </div>