Merge pull request #373 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-05-29 16:59:14 +02:00
committed by GitHub
24 changed files with 569 additions and 347 deletions
+5 -7
View File
@@ -10,8 +10,7 @@ const totalBanners = 57;
const ImageBanner = () => {
const [imagePath] = useState(() => {
const randomBannerIndex = Math.floor(Math.random() * totalBanners) + 1;
const cacheBuster = new Date().getTime();
return `assets/banners/banner-${randomBannerIndex}.jpg?cacheBuster=${cacheBuster}`;
return `/assets/banners/banner-${randomBannerIndex}.jpg`;
});
return <img src={imagePath} alt='' />;
@@ -28,20 +27,19 @@ const BoardBanner = () => {
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
const subplebbit = useSubplebbit({ subplebbitAddress });
const displayAddress =
subplebbit?.address && subplebbit?.address.length > 10 && !subplebbit?.address.includes('.') ? subplebbit?.address.slice(0, 13).concat('...') : subplebbit?.address;
const { address, shortAddress } = subplebbit || {};
const multisubMetadata = useMultisubMetadata();
const title = isInAllView ? multisubMetadata?.title : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${subplebbit?.address}`;
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
return (
<div className={styles.content}>
<div className={styles.bannerCnt}>
<ImageBanner key={isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : subplebbit?.address} />
<ImageBanner key={isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : address} />
</div>
<div className={styles.boardTitle}>{title || `p/${displayAddress}`}</div>
<div className={styles.boardTitle}>{title || `p/${shortAddress}`}</div>
<div className={styles.boardSubtitle}>{subtitle}</div>
<hr />
</div>
@@ -79,36 +79,20 @@
text-decoration: none;
}
.postMenuBtnPadding {
left: 5px;
top: 5px;
position: relative;
.post .postMenu {
visibility: hidden;
}
.postMenuBtn {
position: absolute;
bottom: 5px;
line-height: 1em;
color: var(--catalog-post-menu-btn-color);
opacity: 0;
transition: transform 0.1s;
.post:hover .postMenu {
visibility: visible;
}
.post:hover .postMenuBtn {
opacity: var(--catalog-post-menu-btn-opacity);
.loadingSkeleton {
width: 150px;
height: 150px;
display: inline-block;
}
.post:hover .postMenuBtn:hover {
cursor: pointer;
color: var(--catalog-post-menu-btn-hover-color);
opacity: var(--catalog-post-menu-btn-hover-opacity);
}
.postMenuBtn {
/* preserve unicode triangle for button, some browsers will use an emoji */
font-variant: normal;
font-feature-settings: "tnum";
-webkit-font-feature-settings: "tnum"; /* Chrome, Opera, and Safari */
-moz-font-feature-settings: "tnum"; /* Firefox */
font-variant-numeric: tabular-nums;
.fileDeleted {
image-rendering: pixelated;
}
+56 -69
View File
@@ -1,47 +1,31 @@
import { useEffect, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks';
import styles from './catalog-row.module.css';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { isAllView } from '../../lib/utils/view-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import styles from './catalog-row.module.css';
import PostMenuDesktop from '../post/post-desktop/post-menu-desktop/';
export const CatalogPostMedia = ({ commentMediaInfo }: { commentMediaInfo: any }) => {
import React, { useState } from 'react';
interface CatalogPostMediaProps {
commentMediaInfo: any;
isOutOfFeed?: boolean;
linkWidth?: number;
linkHeight?: number;
}
export const CatalogPostMedia = ({ commentMediaInfo, isOutOfFeed, linkWidth, linkHeight }: CatalogPostMediaProps) => {
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
const iframeThumbnail = patternThumbnailUrl || thumbnail;
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
let thumbnailComponent: React.ReactNode = null;
if (type === 'image') {
thumbnailComponent = <img src={url} alt='' />;
} else if (type === 'video') {
thumbnailComponent = thumbnail ? <img src={thumbnail} alt='' /> : <video src={`${url}#t=0.001`} />;
} else if (type === 'webpage') {
thumbnailComponent = <img src={thumbnail} alt='' />;
} else if (type === 'iframe') {
thumbnailComponent = iframeThumbnail ? <img src={iframeThumbnail} alt='' /> : null;
} else if (type === 'gif' && gifFrameUrl) {
thumbnailComponent = <img src={gifFrameUrl} alt='' />;
} else if (type === 'audio') {
thumbnailComponent = <audio src={url} controls />;
}
return thumbnailComponent;
};
interface CatalogPostProps {
post: Comment;
openMenu: boolean;
toggleMenu: () => void;
}
const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
const { t } = useTranslation();
const { cid, content, isDescription, isRules, link, linkHeight, linkWidth, locked, pinned, replyCount, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
const { type } = commentMediaInfo || {};
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [isLoaded, setIsLoaded] = useState(false);
const [hasError, setHasError] = useState(false);
const handleLoad = () => setIsLoaded(true);
const handleError = () => setHasError(true);
const loadingStyle = { display: isLoaded ? 'block' : 'none' };
let displayWidth, displayHeight;
const maxThumbnailSize = 150;
@@ -55,13 +39,47 @@ const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
displayHeight = `${maxThumbnailSize}px`;
}
if (type === 'audio' || isDescription || isRules) {
if (type === 'audio' || isOutOfFeed) {
displayWidth = 'unset';
displayHeight = 'unset';
}
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
let thumbnailComponent: React.ReactNode = null;
if (type === 'image' && !hasError) {
thumbnailComponent = <img src={url} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />;
} else if (type === 'video' && !hasError) {
thumbnailComponent = thumbnail ? (
<img src={thumbnail} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />
) : (
<video src={`${url}#t=0.001`} onLoad={handleLoad} onError={handleError} style={loadingStyle} />
);
} else if (type === 'webpage' && !hasError) {
thumbnailComponent = <img src={thumbnail} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />;
} else if (type === 'iframe' && iframeThumbnail && !hasError) {
thumbnailComponent = <img src={iframeThumbnail} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />;
} else if (type === 'gif' && gifFrameUrl && !hasError) {
thumbnailComponent = <img src={gifFrameUrl} alt='' onLoad={handleLoad} onError={handleError} style={loadingStyle} />;
} else if (type === 'audio') {
thumbnailComponent = <audio src={url} controls />;
}
return (
<div className={hasError ? '' : styles.mediaWrapper} style={thumbnailDimensions}>
{!isLoaded && !hasError && <span className={styles.loadingSkeleton} />}
{hasError ? <img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' /> : thumbnailComponent}
</div>
);
};
const CatalogPost = ({ post }: { post: Comment }) => {
const { t } = useTranslation();
const { cid, content, isDescription, isRules, link, linkHeight, linkWidth, locked, pinned, replyCount, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const location = useLocation();
const isInAllView = isAllView(location.pathname);
@@ -76,28 +94,13 @@ const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
</div>
);
useEffect(() => {
const handlePageClick = () => {
if (openMenu) {
toggleMenu();
}
};
document.addEventListener('click', handlePageClick);
return () => {
document.removeEventListener('click', handlePageClick);
};
}, [openMenu, toggleMenu]);
return (
<div className={styles.post}>
{hasThumbnail ? (
<Link to={postLink}>
<div className={styles.mediaPaddingWrapper}>
{threadIcons}
<div className={styles.mediaWrapper} style={thumbnailDimensions}>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} />
</div>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={isDescription || isRules} linkWidth={linkWidth} linkHeight={linkHeight} />
</div>
</Link>
) : (
@@ -111,18 +114,8 @@ const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
/ L: <b>{linkCount}</b>
</span>
)}
<span className={styles.postMenuBtnPadding}>
<span
className={styles.postMenuBtn}
title='Thread Menu'
onClick={(e) => {
e.stopPropagation();
toggleMenu();
}}
style={{ transform: openMenu ? 'rotate(90deg)' : 'rotate(0deg)' }}
>
▶
</span>
<span className={styles.postMenu}>
<PostMenuDesktop post={post} />
</span>
</div>
<Link to={postLink}>
@@ -141,16 +134,10 @@ interface CatalogRowProps {
}
const CatalogRow = ({ row }: CatalogRowProps) => {
const [postWithMenuOpen, setPostWithMenuOpen] = useState<number | null>(null);
const handleToggleMenu = (index: number) => {
setPostWithMenuOpen(postWithMenuOpen === index ? null : index);
};
return (
<div className={styles.row}>
{row.map((post, index) => (
<CatalogPost key={index} post={post} openMenu={postWithMenuOpen === index} toggleMenu={() => handleToggleMenu(index)} />
<CatalogPost key={index} post={post} />
))}
</div>
);
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks';
import { getPublicationType } from '../../lib/utils/challenge-utils';
import useChallenges from '../../hooks/use-challenges';
import useWindowWidth from '../../hooks/use-window-width';
import useIsMobile from '../../hooks/use-is-mobile';
import styles from './challenge-modal.module.css';
import _ from 'lodash';
@@ -54,7 +54,7 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
// react-draggable requires a ref to the modal node
const nodeRef = useRef(null);
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
const modalContent = (
<div className={styles.container} ref={nodeRef}>
@@ -18,6 +18,15 @@
display: inline-block;
}
@media (max-width: 640px) {
.subplebbitAvatar {
max-width: 125px;
max-height: 125px;
margin: 3px 10px 5px 5px;
display: inline-block;
}
}
.thumbnailSmall {
background-color: var(--media-thumbnail-background-color);
width: var(--width);
@@ -103,4 +112,8 @@
.mediaDesktopOp img, .mediaDesktopOp video, .mediaDesktopOp iframe, .mediaDesktopOp audio {
padding: 3px 20px 5px 0;
}
.fileDeleted {
image-rendering: pixelated;
}
+20 -28
View File
@@ -1,10 +1,10 @@
import React from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './comment-media.module.css';
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
import { getHostname } from '../../lib/utils/url-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useWindowWidth from '../../hooks/use-window-width';
import useIsMobile from '../../hooks/use-is-mobile';
import Embed, { canEmbed } from '../embed';
interface MediaProps {
@@ -18,29 +18,13 @@ interface MediaProps {
toggleExpanded?: () => void;
}
interface ThumbnailProps {
style: React.CSSProperties;
children: React.ReactNode;
thumbnailSmallPadding?: string;
}
const ThumbnailBig = ({ style, children }: ThumbnailProps) => (
<span className={styles.thumbnailBig} style={style}>
{children}
</span>
);
const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailProps) => (
<span className={`${styles.thumbnailSmall} ${thumbnailSmallPadding}`} style={style}>
{children}
</span>
);
const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => {
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
const [hasError, setHasError] = useState(false);
const handleError = () => setHasError(true);
let displayWidth, displayHeight;
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
@@ -63,7 +47,7 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
const hasThumbnail = getHasThumbnail(commentMediaInfo, url);
if (type === 'image') {
thumbnailComponent = <img src={url} alt='' onClick={() => setShowThumbnail(false)} />;
thumbnailComponent = <img src={url} alt='' onError={handleError} onClick={() => setShowThumbnail(false)} />;
} else if (type === 'video') {
thumbnailComponent = thumbnail ? <img src={thumbnail} alt='' /> : <video src={`${url}#t=0.001`} onClick={() => setShowThumbnail(false)} />;
} else if (type === 'webpage') {
@@ -76,15 +60,21 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
thumbnailComponent = <audio src={url} controls />;
}
if (hasError) {
thumbnailComponent = <img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' />;
}
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
const linkWithoutThumbnail = url && new URL(url);
return isOutOfFeed ? (
return hasError ? (
<img className={styles.fileDeleted} src='/assets/filedeleted-res.gif' alt='File deleted' />
) : isOutOfFeed ? (
<span className={styles.subplebbitAvatar}>{thumbnailComponent}</span>
) : isMobile || isReply ? (
<ThumbnailSmall style={thumbnailDimensions} thumbnailSmallPadding={thumbnailSmallPadding}>
<span className={`${styles.thumbnailSmall} ${thumbnailSmallPadding}`} style={thumbnailDimensions}>
{thumbnailComponent}
{isMobile &&
!hasThumbnail &&
@@ -96,16 +86,18 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
{getHostname(url) || (url.length > 30 ? url.slice(0, 30) + '...' : url)}
</a>
))}
</ThumbnailSmall>
</span>
) : (
<ThumbnailBig style={thumbnailDimensions}>{thumbnailComponent}</ThumbnailBig>
<span className={styles.thumbnailBig} style={thumbnailDimensions}>
{thumbnailComponent}
</span>
);
};
const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
const { t } = useTranslation();
const { thumbnail, type, url } = commentMediaInfo || {};
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
const mediaClass = isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp;
return (
@@ -142,7 +134,7 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
const { t } = useTranslation();
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
const { type, url } = commentMediaInfo || {};
return (
@@ -4,6 +4,7 @@ import { useLocation, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { useAccount } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils';
import { getFormattedDate } from '../../../lib/utils/time-utils';
import { isValidURL } from '../../../lib/utils/url-utils';
@@ -15,9 +16,8 @@ import CommentMedia from '../../comment-media';
import { canEmbed } from '../../embed';
import LoadingEllipsis from '../../loading-ellipsis';
import Markdown from '../../markdown';
import PostMenuDesktop from './post-menu-desktop/';
import { PostProps } from '../post';
import styles from '../post.module.css';
import PostMenu from '../post-menu';
import _ from 'lodash';
const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
@@ -82,12 +82,12 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
)}
{pinned && (
<span className={`${styles.stickyIconWrapper} ${!locked && styles.addPaddingBeforeReply}`}>
<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')} />
</span>
)}
{locked && (
<span className={`${styles.closedIconWrapper} ${styles.addPaddingBeforeReply} ${pinned && styles.addPaddingInBetween}`}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
<img src='/assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
{!isInPostView && !isReply && (
@@ -102,7 +102,7 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
</span>
)}
</span>
<PostMenu post={post} />
<PostMenuDesktop post={post} />
</div>
);
};
@@ -0,0 +1 @@
export { default } from './post-menu-desktop';
@@ -0,0 +1,116 @@
.postMenu {
position: absolute;
background-color: var(--post-menu-desktop-background-color);
border-bottom: var(--post-menu-desktop-border-bottom);
border-right: var(--post-menu-desktop-border-right);
outline: none;
z-index: 2;
}
.postMenuBtnWrapper {
position: relative;
display: inline-block;
padding-left: 15px;
}
.postMenuBtn {
color: var(--post-menu-desktop-button-color);
transition: transform 0.1s;
transform: rotate(90deg);
position: absolute;
bottom: -4px;
}
.postMenuBtn:hover {
cursor: pointer;
color: var(--post-menu-desktop-button-color-hover);
}
.postMenuItem {
cursor: pointer;
padding: 2px 4px;
border: 1px solid var(--post-menu-desktop-item-border-color);
background-color: var(--post-menu-desktop-item-background-color);
border-bottom: none;
}
.postMenuItem:hover {
background-color: var(--post-menu-desktop-item-hover-background-color);
}
.dropdown {
position: relative;
}
.dropdownMenu {
position: absolute;
left: 100%;
top: -1px;
display: none;
border-bottom: var(--post-menu-desktop-border-bottom);
border-right: var(--post-menu-desktop-border-right);
}
.dropdown a {
text-decoration: none;
color: inherit;
}
.dropdown:hover .dropdownMenu {
display: block;
}
.postMenuBtnCatalogWrapper {
left: 5px;
top: 5px;
position: relative;
}
.postMenuBtnCatalog {
position: absolute;
bottom: 5px;
line-height: 1em;
color: var(--catalog-post-menu-desktop-btn-color);
opacity: var(--catalog-post-menu-desktop-btn-opacity);
transition: transform 0.1s;
}
.postMenuBtnCatalog:hover {
cursor: pointer;
color: var(--catalog-post-menu-desktop-btn-hover-color);
opacity: var(--catalog-post-menu-desktop-btn-hover-opacity);
}
.postMenuBtnCatalog {
/* preserve unicode triangle for button, some browsers will use an emoji */
font-variant: normal;
font-feature-settings: "tnum";
-webkit-font-feature-settings: "tnum"; /* Chrome, Opera, and Safari */
-moz-font-feature-settings: "tnum"; /* Firefox */
font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
.postMenuBtn {
width: 1em;
text-align: center;
font-weight: 700;
font-size: 16px;
}
.postMenuBtnWrapper {
position: relative;
padding-right: 12px;
}
}
@media (min-width: 640px) {
.postMenuBtn {
right: -2px;
opacity: var(--post-menu-desktop-btn-opacity);
}
.stickyIconWrapper {
padding-right: 18px;
}
}
@@ -1,11 +1,14 @@
import { useState } from 'react';
import { createPortal } from 'react-dom';
import { useLocation } from 'react-router-dom';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { Comment } from '@plebbit/plebbit-react-hooks';
import styles from './post-menu.module.css';
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils';
import styles from './post-menu-desktop.module.css';
import { getCommentMediaInfo } from '../../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../../lib/utils/url-utils';
import { isCatalogView } from '../../../../lib/utils/view-utils';
interface PostMenuProps {
interface PostMenuDesktopProps {
cid: string;
isDescription?: boolean;
isRules?: boolean;
@@ -13,7 +16,7 @@ interface PostMenuProps {
onClose: () => void;
}
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuProps) => {
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
return (
<div
onClick={() => {
@@ -28,16 +31,23 @@ const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuProps) => {
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
const { refs, floatingStyles } = useFloating({
placement: 'right-start',
middleware: [flip(), shift({ padding: 10 })],
});
return (
<div
className={`${styles.postMenuItem} ${styles.dropdown}`}
onMouseOver={() => setIsImageSearchMenuOpen(true)}
onMouseLeave={() => setIsImageSearchMenuOpen(false)}
ref={refs.setReference}
onClick={onClose}
>
Image search »
{isImageSearchMenuOpen && (
<div className={styles.dropdownMenu}>
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Google</div>
</a>
@@ -53,19 +63,26 @@ const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void
);
};
const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuProps) => {
const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuDesktopProps) => {
const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
const { refs, floatingStyles } = useFloating({
placement: 'right-start',
middleware: [flip(), shift({ padding: 10 })],
});
return (
<div
className={`${styles.postMenuItem} ${styles.dropdown}`}
onMouseOver={() => setIsClientRedirectMenuOpen(true)}
onMouseLeave={() => setIsClientRedirectMenuOpen(false)}
ref={refs.setReference}
onClick={onClose}
>
View on »
{isClientRedirectMenuOpen && (
<div className={styles.dropdownMenu}>
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
<a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Seedit</div>
</a>
@@ -78,17 +95,19 @@ const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose
);
};
const PostMenu = ({ post }: { post: Comment }) => {
const { cid, isDescription, isRules, link, subplebbitAddress } = post;
const PostMenuDesktop = ({ post }: { post: Comment }) => {
const { cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
const { thumbnail, type, url } = commentMediaInfo || {};
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
const isInCatalogView = isCatalogView(useLocation().pathname);
const { refs, floatingStyles, context } = useFloating({
placement: 'bottom-start',
open: menuBtnRotated,
onOpenChange: setMenuBtnRotated,
middleware: [offset({ mainAxis: 10, crossAxis: 5 }), flip({ fallbackAxisSideDirection: 'end' }), shift({ padding: 10 })],
middleware: [offset({ mainAxis: isInCatalogView ? -2 : 6, crossAxis: isInCatalogView ? -1 : 5 }), flip({ fallbackAxisSideDirection: 'end' }), shift({ padding: 10 })],
whileElementsMounted: autoUpdate,
});
const click = useClick(context);
@@ -101,9 +120,9 @@ const PostMenu = ({ post }: { post: Comment }) => {
return (
<>
<span className={styles.postMenuBtnWrapper} ref={refs.setReference} {...getReferenceProps()}>
<span className={isInCatalogView ? styles.postMenuBtnCatalogWrapper : styles.postMenuBtnWrapper} ref={refs.setReference} {...getReferenceProps()}>
<span
className={styles.postMenuBtn}
className={isInCatalogView ? styles.postMenuBtnCatalog : styles.postMenuBtn}
title='Post menu'
onClick={() => setMenuBtnRotated((prev) => !prev)}
style={{ transform: menuBtnRotated ? 'rotate(90deg)' : 'rotate(0deg)' }}
@@ -111,17 +130,19 @@ const PostMenu = ({ post }: { post: Comment }) => {
▶
</span>
</span>
{menuBtnRotated && (
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
<ViewOnButton cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} />
</div>
</FloatingFocusManager>
)}
{menuBtnRotated &&
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
<ViewOnButton cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} />
</div>
</FloatingFocusManager>,
document.body,
)}
</>
);
};
export default PostMenu;
export default PostMenuDesktop;
-1
View File
@@ -1 +0,0 @@
export { default } from './post-menu';
@@ -1,86 +0,0 @@
.postMenu {
position: absolute;
background-color: var(--post-menu-background-color);
border-bottom: var(--post-menu-border-bottom);
border-right: var(--post-menu-border-right);
outline: none;
z-index: 2;
}
.postMenuBtnWrapper {
position: relative;
display: inline-block;
padding-left: 15px;
}
.postMenuBtn {
color: var(--post-menu-button-color);
transition: transform 0.1s;
transform: rotate(90deg);
position: absolute;
bottom: -4px;
}
.postMenuBtn:hover {
cursor: pointer;
color: var(--post-menu-button-color-hover);
}
.postMenuItem {
cursor: pointer;
padding: 2px 4px;
border: 1px solid var(--post-menu-item-border-color);
background-color: var(--post-menu-item-background-color);
border-bottom: none;
}
.postMenuItem:hover {
background-color: var(--post-menu-item-hover-background-color);
}
.dropdown {
position: relative;
}
.dropdownMenu {
position: absolute;
left: 100%;
top: -1px;
display: none;
border-bottom: var(--post-menu-border-bottom);
border-right: var(--post-menu-border-right);
}
.dropdown a {
text-decoration: none;
color: inherit;
}
.dropdown:hover .dropdownMenu {
display: block;
}
@media (max-width: 640px) {
.postMenuBtn {
width: 1em;
text-align: center;
font-weight: 700;
font-size: 16px;
}
.postMenuBtnWrapper {
position: relative;
padding-right: 12px;
}
}
@media (min-width: 640px) {
.postMenuBtn {
right: -2px;
opacity: var(--post-menu-btn-opacity);
}
.stickyIconWrapper {
padding-right: 18px;
}
}
@@ -0,0 +1 @@
export { default } from './post-menu-mobile';
@@ -0,0 +1,42 @@
.postMenuBtn {
color: var(--post-mobile-menu-button-color);
line-height: 1em;
width: 1em;
text-align: center;
transition: transform 0.1s;
transform: rotate(90deg);
-webkit-transform: rotate(90deg);
float: left;
font-weight: 700;
font-size: 16px;
height: 100%;
}
.postMenu {
font-size: 16px;
line-height: 2.5em;
background-color: var(--post-menu-mobile-background-color);
border: var(--post-menu-mobile-border);
border-right-width: var(---post-menu-mobile-border-right-width);
list-style: none;
padding: 0;
margin: 0;
white-space: nowrap;
}
.postMenu a {
text-decoration: none;
color: inherit;
}
.postMenuItem {
cursor: pointer;
position: relative;
padding: 2px 4px;
vertical-align: middle;
border-bottom: var(--post-menu-mobile-item-border-bottom);
}
.postMenuItem:hover {
background-color: var(--post-menu-mobile-item-hover-background-color);
}
@@ -0,0 +1,102 @@
import { useState } from 'react';
import { createPortal } from 'react-dom';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { Comment } from '@plebbit/plebbit-react-hooks';
import styles from './post-menu-mobile.module.css';
import { getCommentMediaInfo } from '../../../../lib/utils/media-utils';
import { copyShareLinkToClipboard, isValidURL } from '../../../../lib/utils/url-utils';
interface PostMenuMobileProps {
cid: string;
isDescription?: boolean;
isRules?: boolean;
subplebbitAddress: string;
onClose: () => void;
}
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuMobileProps) => {
return (
<div
onClick={() => {
copyShareLinkToClipboard(subplebbitAddress, cid);
onClose();
}}
>
<div className={styles.postMenuItem}>Copy link</div>
</div>
);
};
const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => {
return (
<div onClick={onClose}>
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on Google</div>
</a>
<a href={`https://www.yandex.com/images/search?img_url=${url}&rpt=imageview`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on Yandex</div>
</a>
<a href={`https://saucenao.com/search.php?url=${url}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>Search image on SauceNAO</div>
</a>
</div>
);
};
const ViewOnButtons = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuMobileProps) => {
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
return (
<div onClick={onClose}>
<a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>View on Seedit</div>
</a>
<a href={`https://plebones.netlify.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
<div className={styles.postMenuItem}>View on Plebones</div>
</a>
</div>
);
};
const PostMenuMobile = ({ post }: { post: Comment }) => {
const { cid, isDescription, isRules, link, subplebbitAddress } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
const { thumbnail, type, url } = commentMediaInfo || {};
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { refs, floatingStyles, context } = useFloating({
placement: 'bottom-start',
open: isMenuOpen,
onOpenChange: setIsMenuOpen,
middleware: [offset({ mainAxis: 3, crossAxis: 8 }), flip(), shift({ padding: 10 })],
whileElementsMounted: autoUpdate,
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
const headingId = useId();
const handleClose = () => setIsMenuOpen(false);
return (
<>
<span className={styles.postMenuBtn} title='Post menu' onClick={() => setIsMenuOpen((prev) => !prev)} ref={refs.setReference} {...getReferenceProps()}>
...
</span>
{isMenuOpen &&
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
<ViewOnButtons cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} />
</div>
</FloatingFocusManager>,
document.body,
)}
</>
);
};
export default PostMenuMobile;
@@ -3,6 +3,7 @@ import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { useAccount } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../post.module.css';
import { getCommentMediaInfo, getHasThumbnail } from '../../../lib/utils/media-utils';
import { getFormattedDate } from '../../../lib/utils/time-utils';
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
@@ -12,8 +13,8 @@ import useStateString from '../../../hooks/use-state-string';
import CommentMedia from '../../comment-media';
import LoadingEllipsis from '../../loading-ellipsis';
import Markdown from '../../markdown';
import PostMenuMobile from './post-menu-mobile';
import { PostProps } from '../post';
import styles from '../post.module.css';
import _ from 'lodash';
const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
@@ -45,9 +46,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
return (
<>
<div className={styles.postInfo}>
<span className={styles.postMenuBtn} title='Post menu'>
...
</span>
<PostMenuMobile post={post} />
<span className={styles.nameBlock}>
<span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}>
{shortDisplayName || _.capitalize(t('anonymous'))}
@@ -56,12 +55,12 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
{!(isDescription || isRules) && <span className={styles.address}>(u/{shortAddress || accountShortAddress})</span>}
{pinned && (
<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')} />
</span>
)}
{locked && (
<span className={`${styles.closedIconWrapper} ${pinned && styles.addPaddingInBetween}`}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
<img src='/assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
{title && (
+2 -2
View File
@@ -1,5 +1,5 @@
import { Role, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import useWindowWidth from '../../hooks/use-window-width';
import useIsMobile from '../../hooks/use-is-mobile';
import styles from './post.module.css';
import PostDesktop from './post-desktop';
import PostMobile from './post-mobile';
@@ -15,7 +15,7 @@ export interface PostProps {
const Post = ({ post, showAllReplies = false, openReplyModal }: PostProps) => {
const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress });
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
return (
<div className={styles.thread}>
+2 -2
View File
@@ -6,7 +6,7 @@ import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import { setAccount, useAccount } from '@plebbit/plebbit-react-hooks';
import { isValidURL } from '../../lib/utils/url-utils';
import useReply from '../../hooks/use-reply';
import useWindowWidth from '../../hooks/use-window-width';
import useIsMobile from '../../hooks/use-is-mobile';
import styles from './reply-modal.module.css';
import _ from 'lodash';
@@ -51,7 +51,7 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => {
}, [replyIndex, resetContent, closeModal]);
const nodeRef = useRef<HTMLDivElement>(null);
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
// on mobile, the position is absolute instead of fixed, so we need to calculate the top position
useEffect(() => {
+8
View File
@@ -0,0 +1,8 @@
import useWindowWidth from './use-window-width';
const useIsMobile = () => {
const windowWidth = useWindowWidth();
return windowWidth < 640;
};
export default useIsMobile;
+2 -2
View File
@@ -1,12 +1,12 @@
import { useState, useCallback } from 'react';
import useWindowWidth from './use-window-width';
import useIsMobile from './use-is-mobile';
const useReplyModal = () => {
const [showReplyModal, setShowReplyModal] = useState(false);
const [activeCid, setActiveCid] = useState<string | null>(null);
// on mobile, the position is absolute instead of fixed, so we need to calculate the top position
const isMobile = useWindowWidth() < 640;
const isMobile = useIsMobile();
const [scrollY, setScrollY] = useState<number>(0);
const closeModal = useCallback(() => {
+5 -1
View File
@@ -2,6 +2,8 @@ import { useEffect, useMemo } from 'react';
import { useAccount, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import { create } from 'zustand';
const pendingFetchCid: { [cid: string]: boolean } = {};
const useSubplebbitsStats = (options: any) => {
const { subplebbitAddresses, accountName } = options || {};
const account = useAccount({ accountName });
@@ -15,13 +17,15 @@ const useSubplebbitsStats = (options: any) => {
}
subplebbits.forEach((subplebbit) => {
if (subplebbit && subplebbit.statsCid && !subplebbitsStats[subplebbit.address]) {
if (subplebbit && subplebbit.statsCid && !subplebbitsStats[subplebbit.address] && !pendingFetchCid[subplebbit.statsCid]) {
pendingFetchCid[subplebbit.statsCid] = true;
account.plebbit
.fetchCid(subplebbit.statsCid)
.then((fetchedStats: any) => {
setSubplebbitStats(subplebbit.address, JSON.parse(fetchedStats));
})
.catch((error: any) => {
pendingFetchCid[subplebbit.statsCid] = false;
console.error('Fetching subplebbit stats failed', { subplebbitAddress: subplebbit.address, error });
});
}
+126 -84
View File
@@ -25,10 +25,10 @@
--body-font-size: 13px;
/* catalog post */
--catalog-post-menu-btn-color: navy;
--catalog-post-menu-btn-hover-color: red;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: navy;
--catalog-post-menu-desktop-btn-hover-color: red;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #f0e0d6;
@@ -97,9 +97,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #117743;
--post-name-font-weight: 700;
--post-menu-button-color: #000080;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: red;
--post-menu-desktop-button-color: #000080;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: red;
--post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-red.png");
@@ -119,13 +119,20 @@
--post-form-field-input-focus-border: 1px solid #ea8;
--post-form-field-input-color: #000;
/* post menu */
--post-menu-border-bottom: 2px solid #d9bfb7;
--post-menu-border-right: 1px solid #d9bfb7;
--post-menu-background-color: #f0e0d6;
--post-menu-item-border-color: #d9bfb7;
--post-menu-item-background-color: #f0e0d6;
--post-menu-item-hover-background-color: #ffe;
/* post menu desktop */
--post-menu-desktop-border-bottom: 2px solid #d9bfb7;
--post-menu-desktop-border-right: 1px solid #d9bfb7;
--post-menu-desktop-background-color: #f0e0d6;
--post-menu-desktop-item-border-color: #d9bfb7;
--post-menu-desktop-item-background-color: #f0e0d6;
--post-menu-desktop-item-hover-background-color: #ffe;
/* post menu mobile */
--post-menu-mobile-background-color: #f0e0d6;
--post-menu-mobile-border: 1px solid #d9bfb7;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #d9bfb7;
--post-menu-mobile-item-hover-background-color: #ffe;
/* post mobile */
--post-mobile-background-color: #f5e9e1;
@@ -204,10 +211,10 @@
--body-font-size: 13px;
/* catalog post */
--catalog-post-menu-btn-color: navy;
--catalog-post-menu-btn-hover-color: #d00;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: navy;
--catalog-post-menu-desktop-btn-hover-color: #d00;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #d6daf0;
@@ -275,9 +282,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #117743;
--post-name-font-weight: 700;
--post-menu-button-color: #34345c;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: #d00;
--post-menu-desktop-button-color: #34345c;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: #d00;
--post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-blue.png");
@@ -297,13 +304,20 @@
--post-form-field-input-focus-border: 1px solid #98e;
--post-form-field-input-color: #000;
/* post menu */
--post-menu-border-bottom: 2px solid #b7c5d9;
--post-menu-border-right: 1px solid #b7c5d9;
--post-menu-background-color: #d6daf0;
--post-menu-item-border-color: #b7c5d9;
--post-menu-item-background-color: #d6daf0;
--post-menu-item-hover-background-color: #eef2ff;
/* post menu desktop */
--post-menu-desktop-border-bottom: 2px solid #b7c5d9;
--post-menu-desktop-border-right: 1px solid #b7c5d9;
--post-menu-desktop-background-color: #d6daf0;
--post-menu-desktop-item-border-color: #b7c5d9;
--post-menu-desktop-item-background-color: #d6daf0;
--post-menu-desktop-item-hover-background-color: #eef2ff;
/* post menu mobile */
--post-menu-mobile-background-color: #d6daf0;
--post-menu-mobile-border: 1px solid #b7c5d9;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #b7c5d9;
--post-menu-mobile-item-hover-background-color: #eef2ff;
/* post mobile */
--post-mobile-background-color: #d6daf0;
@@ -377,10 +391,10 @@
--board-address-font-size: 10pt;
/* catalog post */
--catalog-post-menu-btn-color: navy;
--catalog-post-menu-btn-hover-color: red;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: navy;
--catalog-post-menu-desktop-btn-hover-color: red;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #f0e0d6;
@@ -429,9 +443,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #117743;
--post-name-font-weight: 700;
--post-menu-button-color: #000080;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: red;
--post-menu-desktop-button-color: #000080;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: red;
--post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-red.png");
@@ -445,13 +459,20 @@
--post-form-field-title-font-size: 16px;
--post-form-field-input-color: #000;
/* post menu */
--post-menu-border-bottom: 1px solid #d9bfb7;
--post-menu-border-right: unset;
--post-menu-background-color: #f0e0d6;
--post-menu-item-border-color: #d9bfb7;
--post-menu-item-background-color: #f0e0d6;
--post-menu-item-hover-background-color: #ffe;
/* post menu desktop */
--post-menu-desktop-border-bottom: 1px solid #d9bfb7;
--post-menu-desktop-border-right: unset;
--post-menu-desktop-background-color: #f0e0d6;
--post-menu-desktop-item-border-color: #d9bfb7;
--post-menu-desktop-item-background-color: #f0e0d6;
--post-menu-desktop-item-hover-background-color: #ffe;
/* post menu mobile */
--post-menu-mobile-background-color: #f0e0d6;
--post-menu-mobile-border: 1px solid #d9bfb7;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #d9bfb7;
--post-menu-mobile-item-hover-background-color: #ffe;
/* post mobile */
--post-mobile-background-color: #f5e9e1;
@@ -524,10 +545,10 @@
--body-font-size: 16px;
/* catalog post */
--catalog-post-menu-btn-color: navy;
--catalog-post-menu-btn-hover-color: red;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: navy;
--catalog-post-menu-desktop-btn-hover-color: red;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #d6daf0;
@@ -586,9 +607,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #117743;
--post-name-font-weight: 700;
--post-menu-button-color: #34345c;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: #d00;
--post-menu-desktop-button-color: #34345c;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: #d00;
--post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-blue.png");
@@ -602,13 +623,20 @@
--post-form-field-title-font-size: 16px;
--post-form-field-input-color: #000;
/* post menu */
--post-menu-border-bottom: 1px solid #b7c5d9;
--post-menu-border-right: unset;
--post-menu-background-color: #d6daf0;
--post-menu-item-border-color: #b7c5d9;
--post-menu-item-background-color: #d6daf0;
--post-menu-item-hover-background-color: #eef2ff;
/* post menu desktop */
--post-menu-desktop-border-bottom: 1px solid #b7c5d9;
--post-menu-desktop-border-right: unset;
--post-menu-desktop-background-color: #d6daf0;
--post-menu-desktop-item-border-color: #b7c5d9;
--post-menu-desktop-item-background-color: #d6daf0;
--post-menu-desktop-item-hover-background-color: #eef2ff;
/* post menu mobile */
--post-menu-mobile-background-color: #d6daf0;
--post-menu-mobile-border: 1px solid #b7c5d9;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #b7c5d9;
--post-menu-mobile-item-hover-background-color: #eef2ff;
/* post mobile */
--post-mobile-background-color: #d6daf0;
@@ -678,10 +706,10 @@
--board-address-font-size: x-small;
/* catalog post */
--catalog-post-menu-btn-color: #5f89ac;
--catalog-post-menu-btn-hover-color: #5f89ac;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: #5f89ac;
--catalog-post-menu-desktop-btn-hover-color: #5f89ac;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #282a2e;
@@ -744,9 +772,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #c5c8c6;
--post-name-font-weight: 700;
--post-menu-button-color: #5F89AC;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: #81a2be;
--post-menu-desktop-button-color: #5F89AC;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: #81a2be;
--post-greentext-color: #b5bd68;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-dark.png");
@@ -767,13 +795,20 @@
--post-form-field-input-appearance: none;
--post-form-field-input-outline: none;
/* post menu */
--post-menu-border-bottom: 1px solid #000;
--post-menu-border-right: unset;
--post-menu-background-color: #282a2e;
--post-menu-item-border-color: #000;
--post-menu-item-background-color: #282a2e;
--post-menu-item-hover-background-color: #1d1f21;
/* post menu desktop */
--post-menu-desktop-border-bottom: 1px solid #000;
--post-menu-desktop-border-right: unset;
--post-menu-desktop-background-color: #282a2e;
--post-menu-desktop-item-border-color: #000;
--post-menu-desktop-item-background-color: #282a2e;
--post-menu-desktop-item-hover-background-color: #1d1f21;
/* post menu mobile */
--post-menu-mobile-background-color: #282a2e;
--post-menu-mobile-border: 1px solid #000;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #000;
--post-menu-mobile-item-hover-background-color: #1d1f21;
/* post mobile */
--post-mobile-background-color: #282A2E;
@@ -858,10 +893,10 @@
--board-address-font-size: x-small;
/* catalog post */
--catalog-post-menu-btn-color: #f60;
--catalog-post-menu-btn-hover-color: #f30;
--catalog-post-menu-btn-opacity: 0.5;
--catalog-post-menu-btn-hover-opacity: 1;
--catalog-post-menu-desktop-btn-color: #f60;
--catalog-post-menu-desktop-btn-hover-color: #f30;
--catalog-post-menu-desktop-btn-opacity: 0.5;
--catalog-post-menu-desktop-btn-hover-opacity: 1;
/* challenge modal */
--challenge-modal-background-color: #ddd;
@@ -910,9 +945,9 @@
--post-subject-font-weight: 700;
--post-name-text-color: #004a99;
--post-name-font-weight: 700;
--post-menu-button-color: #FF6600;
--post-menu-btn-opacity: 0.8;
--post-menu-button-color-hover: #FF3300;
--post-menu-desktop-button-color: #FF6600;
--post-menu-desktop-btn-opacity: 0.8;
--post-menu-desktop-button-color-hover: #FF3300;
--post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-photon.png");
@@ -951,13 +986,20 @@
--post-form-field-input-focus-border: 1px solid #ea8;
--post-form-field-input-color: #000;
/* post menu */
--post-menu-border-bottom: 1px solid #ccc;
--post-menu-border-right: unset;
--post-menu-background-color: #ddd;
--post-menu-item-border-color: #ccc;
--post-menu-item-background-color: #ddd;
--post-menu-item-hover-background-color: #eee;
/* post menu desktop */
--post-menu-desktop-border-bottom: 1px solid #ccc;
--post-menu-desktop-border-right: unset;
--post-menu-desktop-background-color: #ddd;
--post-menu-desktop-item-border-color: #ccc;
--post-menu-desktop-item-background-color: #ddd;
--post-menu-desktop-item-hover-background-color: #eee;
/* post menu mobile */
--post-menu-mobile-background-color: #ddd;
--post-menu-mobile-border: 1px solid #ccc;
--post-menu-mobile-border-right-width: 2px;
--post-menu-mobile-item-border-bottom: 1px solid #ccc;
--post-menu-mobile-item-hover-background-color: #eee;
/* quotelink */
--post-quotelink-text-color: #f60;
+1 -1
View File
@@ -202,7 +202,7 @@ const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThrea
<div className={styles.title}>{boardTitle || boardShortAddress}</div>
<div className={styles.mediaContainer}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} />
<CatalogPostMedia commentMediaInfo={commentMediaInfo} isOutOfFeed={true} />
</Link>
</div>
<div className={styles.threadContent}>
+1 -2
View File
@@ -10,8 +10,7 @@ const totalNotFoundImages = 2;
const NotFoundImage = () => {
const [imagePath] = useState(() => {
const randomBannerIndex = Math.floor(Math.random() * totalNotFoundImages) + 1;
const cacheBuster = new Date().getTime();
return `assets/not-found/not-found-${randomBannerIndex}.jpg?cacheBuster=${cacheBuster}`;
return `/assets/not-found/not-found-${randomBannerIndex}.jpg`;
});
return <img src={imagePath} alt='' />;