diff --git a/src/components/styled/views/Catalog.styled.jsx b/src/components/styled/views/Catalog.styled.jsx
index ade45065..d8d49b82 100644
--- a/src/components/styled/views/Catalog.styled.jsx
+++ b/src/components/styled/views/Catalog.styled.jsx
@@ -8,8 +8,7 @@ export const Threads = styled.div`
.thread {
width: 180px;
- position:relative;
- position:relative;
+ position: relative;
max-height: 320px;
vertical-align: top;
display: inline-block;
@@ -31,6 +30,7 @@ export const Threads = styled.div`
padding-left:5px;padding-right:5px;
border-radius:3px;
}
+
.thread_popup_content{
color: #dedede;
display: inline-block;
@@ -38,6 +38,7 @@ export const Threads = styled.div`
white-space:nowrap;
font-size:13px;
}
+
.thread_popup_lastReply{
color: #bbbfbd;
display: block;
@@ -47,34 +48,35 @@ export const Threads = styled.div`
text-overflow: ellipsis;
font-size:90%;
}
+
.thread_popup_title{
color:#a89baa
}
+
.thread_popup_author{
color:#519860;font-weight:bold
}
+
.thread_popup_authorAdmin{
color:#682071;
}
+
video, audio, img {
max-width: 150px;
max-height: 150px;
- }
+}
- .file-thumb {
+.file-thumb {
background-color: rgba(0, 0, 0, 0.05);
- display: inline-block;
- text-align: center;
- line-height: 150px;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
margin-bottom: 5px;
}
.card {
- display: inline-block;
- margin: auto;
- box-shadow: 0 0 5px rgba(0, 0, 0, .25);
- border: 0;
- vertical-align: middle;
+ box-shadow: 0 0 5px rgba(0, 0, 0, .25);
+ border: 0;
}
.thread-icons {
@@ -133,9 +135,9 @@ export const Threads = styled.div`
.teaser {
display: block;
padding: 0 15px;
- height:200px;
- overflow:hidden;
+ overflow: hidden;
}
+
${({ selectedStyle }) => {
switch (selectedStyle) {
case 'Yotsuba':
diff --git a/src/components/views/AllCatalog.jsx b/src/components/views/AllCatalog.jsx
index 88bd8f1b..da8a0bf6 100755
--- a/src/components/views/AllCatalog.jsx
+++ b/src/components/views/AllCatalog.jsx
@@ -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 { Helmet } from 'react-helmet-async';
import { Link, useNavigate} from 'react-router-dom';
@@ -21,6 +21,7 @@ import OfflineIndicator from '../OfflineIndicator';
import SettingsModal from '../modals/SettingsModal';
import countLinks from '../../utils/countLinks';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
+import getFormattedTime from '../../utils/getFormattedTime';
import handleShareClick from '../../utils/handleShareClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useError from '../../hooks/useError';
@@ -75,6 +76,51 @@ const CatalogPost = ({post}) => {
const threadMenuRefs = useRef({});
const postMenuRef = 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(() => {
@@ -272,16 +318,51 @@ const CatalogPost = ({post}) => {
let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight));
displayWidth = `${thread.linkWidth * scale}px`;
displayHeight = `${thread.linkHeight * scale}px`;
- } else {
+ } else if (thread.link) {
displayWidth = '150px';
displayHeight = '150px';
+ } else {
+ displayWidth = '0px';
+ displayHeight = '0px';
}
return (
{setIsHoveringOnThread(thread.cid)}}
- onMouseLeave={() => {setIsHoveringOnThread('')}}>
+ onMouseLeave={() => {handleMouseOnLeaveThread()}}>
+ {isHoveringOnThread === thread.cid ?
+
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">
+
+
+ {thread.title ? `${thread.title} ` : "Posted "}
+
+ by
+
+ {thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
+
+ {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
+
+ {thread.replyCount > 0 ?
+
+ Last reply by Anonymous
+ {getFormattedTime(thread.lastReplyTimestamp)}
+ : null}
+
: null }
{commentMediaInfo?.url ? (
setSelectedThread(thread.cid)}>
@@ -491,7 +572,8 @@ const CatalogPost = ({post}) => {
setSelectedThread(thread.cid)}>
-
+
(threadRefs.current[thread.cid] = el)}>
{thread.title ? `${thread.title}` : null}
{thread.content ? `: ${thread.content}` : null}
diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx
index f608af8b..52d718a4 100755
--- a/src/components/views/Catalog.jsx
+++ b/src/components/views/Catalog.jsx
@@ -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 { Helmet } from 'react-helmet-async';
import { Link, useNavigate, useParams } from 'react-router-dom';
@@ -23,6 +23,7 @@ import OfflineIndicator from '../OfflineIndicator';
import VerifiedAuthor from '../VerifiedAuthor';
import countLinks from '../../utils/countLinks';
import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
+import getFormattedTime from '../../utils/getFormattedTime';
import handleShareClick from '../../utils/handleShareClick';
import handleStyleChange from '../../utils/handleStyleChange';
import useAnonModeRef from '../../hooks/useAnonModeRef';
@@ -88,64 +89,54 @@ const CatalogPost = ({post}) => {
const [isEnoughSpaceOnRight, setIsEnoughSpaceOnRight] = useState(null);
const [isEnoughSpaceOnLeft,setIsEnoughSpaceOnLeft] = useState(null);
const [isCalculationDone,setIsCalculationDone] = useState(false);
+ const [hoverTimeoutId, setHoverTimeoutId] = useState(null);
+ const [isHoveringForMenu, setIsHoveringForMenu] = useState(false);
+
useLayoutEffect(() => {
- setIsEnoughSpaceOnRight(null);
- setIsEnoughSpaceOnLeft(null);
- const calculateSpaceOnRight = () => {
- if (thread.cid === isHoveringOnThread){
- if(threadRefs.current[thread.cid] && popupRef.current) {
- const threadRect = threadRefs.current[thread.cid].getBoundingClientRect();
- const popupRect = popupRef.current.getBoundingClientRect();
- // Get the width of the viewport area inside the browser window
- const viewportWidth = document.documentElement.clientWidth;
+ const executeLayoutEffectLogic = () => {
+ let ref;
+ if (isHoveringOnThread === 'rules' || isHoveringOnThread === 'description') {
+ ref = popupRef.current;
+ } else {
+ ref = threadRefs.current[isHoveringOnThread];
+ }
- // Calculate the space on the right side (relative to the viewport)
- const spaceOnRight = viewportWidth - (threadRect.left + threadRect.width);
- const spaceOnLeft = threadRect.left;
- // Define the popup width
- const popupWidth = popupRect.width;
- // Check if there is enough space for the popup on the right side
- const EnoughSpaceOnRight = spaceOnRight >= popupWidth + 10;
- const EnoughSpaceOnLeft = spaceOnLeft >= popupWidth + 10;
- setIsEnoughSpaceOnLeft(EnoughSpaceOnLeft);
- setIsEnoughSpaceOnRight(EnoughSpaceOnRight);
- return setIsCalculationDone(true);
- }}
+ 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(!isCalculationDone && isHoveringOnThread){
- calculateSpaceOnRight();
+
+ if (isHoveringOnThread) {
+ const timeoutId = setTimeout(executeLayoutEffectLogic, 500);
+ return () => clearTimeout(timeoutId);
}
+
}, [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 = () => {
- setIsHoveringOnThread("");
- setIsEnoughSpaceOnRight(null);
- setIsCalculationDone(false);
- }
-
+ setIsHoveringForMenu(false);
+ if (hoverTimeoutId) {
+ clearTimeout(hoverTimeoutId);
+ setHoverTimeoutId(null);
+ setIsHoveringOnThread("")
+ } else if (isHoveringOnThread !== "") {
+ setIsHoveringOnThread("");
+ setIsEnoughSpaceOnRight(null);
+ setIsCalculationDone(false);
+ }
+ };
+
const handleOutsideClick = useCallback((e) => {
if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
setOpenMenuCid(null);
@@ -323,32 +314,39 @@ const CatalogPost = ({post}) => {
let scale = Math.min(1, 150 / Math.max(thread.linkWidth, thread.linkHeight));
displayWidth = `${thread.linkWidth * scale}px`;
displayHeight = `${thread.linkHeight * scale}px`;
- } else {
+ } else if (thread.link) {
displayWidth = '150px';
displayHeight = '150px';
+ } else {
+ displayWidth = '0px';
+ displayHeight = '0px';
}
if (post.type === 'rules') {
return (
setIsHoveringOnThread("rules")}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
{isHoveringOnThread === 'rules' ?
setIsHoveringOnThread("")}
className={"thread_popup"}>
Rules
by
## Board Admins
- {timeElapsedCalc(subplebbit.createdAt)}
-
-
Last reply by
- Anonymous
- 560 days ago
-
+ {getFormattedTime(subplebbit.createdAt)}
: null }
-
+ {
+ setIsHoveringForMenu('rules');
+ if (!hoverTimeoutId) {
+ const timeoutId = setTimeout(() => {
+ setIsHoveringOnThread("rules");
+ }, 500);
+ setHoverTimeoutId(timeoutId);
+ }
+ }}>
R:
0 / L:
0
{
imageRendering: "pixelated",}} />
{
, document.body
)}
-
-
+
{
+ setIsHoveringForMenu('rules');
+ if (!hoverTimeoutId) {
+ const timeoutId = setTimeout(() => {
+ setIsHoveringOnThread("rules");
+ }, 500);
+ setHoverTimeoutId(timeoutId);
+ }
+ }}>
+
Rules
{": " + subplebbit.rules?.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
@@ -419,26 +426,29 @@ const CatalogPost = ({post}) => {
return (
<>
setIsHoveringOnThread("description")}
onMouseLeave={()=>{handleMouseOnLeaveThread()}}>
- {/* */}
{isHoveringOnThread === 'description' ?
setIsHoveringOnThread("")}
className={"thread_popup"}>
Welcome to {subplebbit.title || subplebbit.address}
by
## Board Admins
- {timeElapsedCalc(subplebbit.createdAt)} ago
-
-
Last reply by
- Anonymous
- 560 days ago
-
+ {getFormattedTime(subplebbit.createdAt)}
: null }
{subplebbit.suggested?.avatarUrl ? (
![]()
{
+ setIsHoveringForMenu('description');
+ if (!hoverTimeoutId) {
+ const timeoutId = setTimeout(() => {
+ setIsHoveringOnThread("description");
+ }, 500);
+ setHoverTimeoutId(timeoutId);
+ }
+ }}
src={subplebbit.suggested.avatarUrl} alt="board avatar" />
) : null}
@@ -452,8 +462,17 @@ const CatalogPost = ({post}) => {
imageRendering: "pixelated",}} />
) : null}
-
-
+
{
+ setIsHoveringForMenu('description');
+ if (!hoverTimeoutId) {
+ const timeoutId = setTimeout(() => {
+ setIsHoveringOnThread("description");
+ }, 500);
+ setHoverTimeoutId(timeoutId);
+ }
+ }}>
+
R:
0 / L:
0
{subplebbit.suggested?.avatarUrl ? null : (
{
)}
{
)}
setSelectedThread("description")}>
-
-
Welcome to {subplebbit.title || subplebbit.address}
+ onClick={() => setSelectedThread("description")} onMouseOver={() => {
+ setIsHoveringForMenu('description');
+ if (!hoverTimeoutId) {
+ const timeoutId = setTimeout(() => {
+ setIsHoveringOnThread("description");
+ }, 500);
+ setHoverTimeoutId(timeoutId);
+ }
+ }}>
+
+ Welcome to {subplebbit.title || subplebbit.address}!
{": " + subplebbit.description}
@@ -553,38 +580,40 @@ const CatalogPost = ({post}) => {
)
} else {
return (
-
- {isHoveringOnThread === thread.cid ?
-
-
-
- {thread.title ? `${thread.title} ` : "Posted "}
-
- by
-
- {thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
-
- {thread.timestamp ? timeElapsedCalc(thread.timestamp) : null} ago
-
- {thread.replyCount > 0 ?
-
- Last reply by Anonymous
- {timeElapsedCalc(thread.lastReplyTimestamp)} ago
- : null}
-
: null }
-
+
{handleMouseOnLeaveThread()}} onMouseOver={() => setIsHoveringOnThread(thread.cid)}>
+ {isHoveringOnThread === thread.cid ?
+
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">
+
+
+ {thread.title ? `${thread.title} ` : "Posted "}
+
+ by
+
+ {thread.author.displayName ?` ${thread.author.displayName} ` : " Anonymous "}
+
+ {thread.timestamp ? getFormattedTime(thread.timestamp) : null}
+
+ {thread.replyCount > 0 ?
+
+ Last reply by Anonymous
+ {getFormattedTime(thread.lastReplyTimestamp)}
+ : null}
+
: null }
{commentMediaInfo?.url ? (
setSelectedThread(thread.cid)}>
@@ -593,8 +622,6 @@ const CatalogPost = ({post}) => {
setIsHoveringOnThread(thread.cid)}
- onMouseLeave={()=>{handleMouseOnLeaveThread()}}
onError={(e) => {
e.target.src = fallbackImgUrl
e.target.onerror = null;
@@ -606,8 +633,6 @@ const CatalogPost = ({post}) => {
setIsHoveringOnThread(thread.cid)}
- onMouseLeave={()=>{handleMouseOnLeaveThread()}}
onError={(e) => {
e.target.src = fallbackImgUrl
e.target.onerror = null;}} />
@@ -616,8 +641,6 @@ const CatalogPost = ({post}) => {
{commentMediaInfo?.type === "video" ? (