+const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailProps) => (
+
{children}
);
-export const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth }: ThumbnailProps) => {
+const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth }: MediaProps) => {
let displayWidth, displayHeight;
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
@@ -51,15 +61,66 @@ export const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, lin
mediaComponent =
;
}
- const thumbnailStyle = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
+ const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
+ const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
return isMobile || isReply ? (
- {mediaComponent}
+
+ {mediaComponent}
+
) : (
- {mediaComponent}
+ {mediaComponent}
);
};
-export const Media = () => {
- return <>>;
+const Media = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
+ const { t } = useTranslation();
+ const mediaClass = isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp;
+
+ return (
+
+ setShowThumbnail(false)}>
+
+ {isMobile && commentMediaInfo?.type && {commentMediaInfo.type}
}
+
+
+ {commentMediaInfo?.type === 'iframe' ? (
+
+ ) : commentMediaInfo?.type === 'gif' ? (
+
setShowThumbnail(true)} />
+ ) : commentMediaInfo?.type === 'video' ? (
+
+ ) : commentMediaInfo?.type === 'image' ? (
+
setShowThumbnail(true)} />
+ ) : commentMediaInfo?.type === 'webpage' ? (
+
setShowThumbnail(true)} />
+ ) : null}
+ {isMobile && commentMediaInfo?.type && (
+
+ )}
+ {isMobile && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
+
+ setShowThumbnail(true)}>
+ {t('close')}
+
+
+ )}
+
+
+ );
};
+
+export default Media;
diff --git a/src/components/post/post.module.css b/src/components/post/post.module.css
index 371ec368..ea7ac08b 100644
--- a/src/components/post/post.module.css
+++ b/src/components/post/post.module.css
@@ -40,10 +40,6 @@
background-image: var(--post-unhide-button-background-image);
}
-.postDesktop .fileThumb {
- float: left;
-}
-
.postDesktop .fileText a {
color: var(--post-link-text-color);
text-decoration: var(--post-link-text-decoration);
@@ -58,27 +54,31 @@
width: 100%;
}
+.postDesktop .checkbox, .replyDesktop .checkbox {
+ padding: 3px 7px 3px 4px;
+}
+
.postDesktop .subject {
color: var(--post-subject-text-color);
font-weight: var(--post-subject-font-weight);
}
-.postDesktop .name {
+.postDesktop .name, .replyDesktop .name {
color: var(--post-name-text-color);
font-weight: var(--post-name-font-weight);
}
-.postDesktop .postNumLink a, .postDesktop .postNumLink span {
+.postDesktop .postNumLink a, .postDesktop .postNumLink span, .replyDesktop .postNumLink a, .replyDesktop .postNumLink span {
color: unset;
text-decoration: unset;
}
-.postDesktop .postNumLink a:hover, .postDesktop .postNumLink span:hover {
+.postDesktop .postNumLink a:hover, .postDesktop .postNumLink span:hover, .replyDesktop .postNumLink a:hover, .replyDesktop .postNumLink span:hover {
cursor: pointer;
color: var(--button-desktop-text-color-hover);
}
-.postDesktop .stickyIconWrapper {
+.postDesktop .stickyIconWrapper, .replyDesktop .stickyIconWrapper {
padding-left: 5px;
}
@@ -86,13 +86,24 @@
padding-left: 3px;
}
-.postDesktop .stickyIconWrapper img, .postDesktop .closedIconWrapper img {
+.postDesktop .stickyIconWrapper img, .postDesktop .closedIconWrapper img, .replyDesktop .stickyIconWrapper img, .replyDesktop .closedIconWrapper img {
width: 16px;
height: 16px;
border: none;
image-rendering: pixelated;
}
+.postDesktop .closeMedia {
+ color: var(--button-desktop-text-color);
+ text-decoration: underline;
+ text-transform: capitalize;
+}
+
+.postDesktop .closeMedia:hover {
+ cursor: pointer;
+ color: var(--button-desktop-text-color-hover);
+}
+
.postDesktop .replyButton {
padding-left: 10px;
}
@@ -108,17 +119,22 @@
color: var(--button-desktop-text-color-hover);
}
-.postDesktop .postMenuBtn {
+.postDesktop .postMenuBtn, .replyDesktop .postMenuBtn {
padding-left: 5px;
color: var(--post-menu-button-color);
}
-.postDesktop .postMenuBtn:hover {
+.postDesktop .postMenuBtn:hover, .replyDesktop .postMenuBtn:hover {
cursor: pointer;
color: var(--post-menu-button-color-hover);
}
-.postDesktop .postMessage {
+.postDesktop .spacer {
+ width: 100%;
+ padding: 5px;
+}
+
+.postDesktop .postMessage, .replyDesktop .postMessage {
padding: 1em 40px;
}
@@ -137,6 +153,33 @@
text-decoration: var(--post-content-link-text-decoration-hover);
}
+.postDesktop .summary {
+ padding-top: 10px;
+ color: var(--post-mobile-abbr-text-color);
+}
+
+.postDesktop .summary .expandButton {
+ background-image: var(--post-replies-expand-button-background-image);
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 18px;
+ height: 18px;
+ display: inline-block;
+ image-rendering: pixelated;
+ margin: 0 3px -4px 2px;
+ cursor: pointer;
+}
+
+.postDesktop .summary a {
+ color: var(--post-link-text-color);
+ text-decoration: var(--post-content-link-text-decoration);
+}
+
+.postDesktop .summary a:hover {
+ color: var(--post-link-text-color-hover);
+ text-decoration: var(--post-content-link-text-decoration-hover);
+}
+
.postMobile hr {
padding-bottom: 30px;
}
@@ -167,7 +210,7 @@
.postMobile .postMenuBtn {
color: var(--post-mobile-menu-button-color);
line-height: 1em;
- width: 10px;
+ width: 1em;
text-align: center;
transition: transform 0.1s;
transform: rotate(90deg);
@@ -201,18 +244,6 @@
text-align: right;
}
-.postMobile .fileThumb {
- float: left;
-}
-
-.postMobile .fileThumb .fileInfo {
- padding-bottom: 5px;
- text-align: center;
- color: var(--post-mobile-file-info-text-color);
- font-size: 9pt;
- display: block;
-}
-
.postMobile .postMessage {
padding: 10px;
font-size: var(--post-mobile-content-font-size);
@@ -249,14 +280,80 @@
text-decoration: var(--post-content-link-text-decoration-hover);
}
+.replyDesktop {
+ padding-bottom: 4px;
+}
+
+.replyDesktop .sideArrows {
+ color: var(--side-arrows-color);
+ float: left;
+ padding: 0 2px;
+}
+
+.replyDesktop .reply {
+ background-color: var(--reply-desktop-background-color);
+ border: var(--reply-desktop-border);
+ border-left: none;
+ border-top: none;
+ display: table;
+ padding: 2px;
+}
+
+.replyDesktop .postInfo {
+ padding: 3px 3px 0 0;
+}
+
+.replyMobile {
+ padding-top: 7px;
+}
+
+.replyMobile .replyContainer {
+ background-color: var(--post-mobile-background-color);
+}
+
+/* clearfix to the container of the floatied elements to contain them */
+.replyMobile .replyContainer::after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.replyMobile .postInfo {
+ padding: 5px;
+ border-bottom: var(--post-mobile-info-border-bottom);
+ background-color: var(--post-mobile-info-background-color);
+}
+
+.replyMobile .postInfo .postMenuBtn {
+ width: 1em;
+}
+
+.replyMobile .nameBlock {
+ display: inline;
+ float: left;
+}
+
+.replyMobile .dateTimePostNum {
+ float: right;
+ text-align: right;
+}
+
@media (max-width: 640px) {
.postDesktop {
display: none;
}
+
+ .replyDesktop {
+ display: none;
+ }
}
@media (min-width: 641px) {
.postMobile {
display: none;
}
+
+ .replyMobile {
+ display: none;
+ }
}
\ No newline at end of file
diff --git a/src/components/post/post.tsx b/src/components/post/post.tsx
index 350f6d17..8d450319 100644
--- a/src/components/post/post.tsx
+++ b/src/components/post/post.tsx
@@ -1,34 +1,113 @@
+import { useState } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks';
import { getCommentMediaInfoMemoized, getHasThumbnail } from '../../lib/utils/media-utils';
import { getFormattedDate } from '../../lib/utils/time-utils';
+import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useReplies from '../../hooks/use-replies';
import styles from './post.module.css';
import Markdown from '../markdown';
-import { Thumbnail } from '../media';
+import Media from '../media';
-import { countLinksInCommentReplies } from '../../lib/utils/comment-utils';
+const ReplyDesktop = ({ index, reply }: { index: number; reply: Comment }) => {
+ const { t } = useTranslation();
+ const { author, content, link, linkHeight, linkWidth, pinned, shortCid, subplebbitAddress, timestamp } = reply || {};
+ const { displayName, shortAddress } = author || {};
+
+ const commentMediaInfo = getCommentMediaInfoMemoized(reply);
+ const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
+ const [showThumbnail, setShowThumbnail] = useState(true);
-const ReplyDesktop = ({ reply }: Comment) => {
- const { content, link, linkHeight, linkWidth, shortCid, subplebbitAddress, timestamp } = reply || {};
return (
-
+ index < 5 && (
+
+
{'>>'}
+
+
+
+
+
+
+ {displayName || 'Anonymous'}
+ (u/{shortAddress})
+
+
{getFormattedDate(timestamp)}
+
+
+
+ c/
+
+
+ {shortCid}
+
+
+ {pinned && (
+
+
+
+ )}
+
+
▶
+
+ {link && (
+
+
+ {hasThumbnail && (
+
+ )}
+
+ )}
+ {content && (
+
+
+
+ )}
+
+
+ )
);
};
const PostDesktop = ({ post }: Comment) => {
const { t } = useTranslation();
- const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, shortCid, subplebbitAddress, timestamp, title } = post || {};
+ const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { displayName, shortAddress } = author || {};
const displayTitle = title && title.length > 75 ? title.slice(0, 75) + '...' : title;
const displayContent = content && content.length > 1000 ? content.slice(0, 1000) + '(...)' : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
+ const [showThumbnail, setShowThumbnail] = useState(true);
+
+ const replies = useReplies(post);
+ const visibleLinkCount = useCountLinksInReplies(post, 5);
+ const totalLinkCount = useCountLinksInReplies(post);
+ const repliesCount = pinned ? replyCount : replyCount - 5;
+ const linkCount = pinned ? totalLinkCount : totalLinkCount - visibleLinkCount;
return (
@@ -46,16 +125,35 @@ const PostDesktop = ({ post }: Comment) => {
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '...' : commentMediaInfo?.url}
{' '}
({commentMediaInfo?.type})
+ {!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
+
+ {' '}
+ [
+ setShowThumbnail(true)}>
+ {t('close')}
+
+ ]
+
+ )}
{hasThumbnail && (
-
-
-
+
)}
)}
- {title && {displayTitle}}{' '}
+
+
+
+ {title && {displayTitle} }
{displayName || 'Anonymous'}
(u/{shortAddress})
@@ -86,6 +184,7 @@ const PostDesktop = ({ post }: Comment) => {
▶
+ {!content && }
{content && (
@@ -97,19 +196,82 @@ const PostDesktop = ({ post }: Comment) => {
)}
)}
+ {(replies.length > 5 || pinned) && (
+
+
+
+ {repliesCount} replies {linkCount > 0 && `and ${linkCount} links`} omitted.{' '}
+
+ Click here to view.
+
+ )}
+ {!pinned &&
+ replies.map((reply, index) => (
+
+
+
+ ))}
);
};
+const ReplyMobile = ({ index, reply }: { index: number; reply: Comment }) => {
+ const { author, content, link, linkHeight, linkWidth, pinned, shortCid, subplebbitAddress, timestamp } = reply || {};
+ const { displayName, shortAddress } = author || {};
+
+ const commentMediaInfo = getCommentMediaInfoMemoized(reply);
+ const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
+ const [showThumbnail, setShowThumbnail] = useState(true);
+
+ return (
+ index < 5 && (
+
+
+
+
+ ...
+
+ {displayName || 'Anonymous'}
+ (u/{shortAddress})
+
+
+ {getFormattedDate(timestamp)} c/
+ {shortCid}
+
+
+ {hasThumbnail && (
+
+ )}
+
+
+
+
+
+
+ )
+ );
+};
+
const PostMobile = ({ post }: Comment) => {
- const { author, cid, content, link, linkHeight, linkWidth, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
+ const { author, cid, content, link, linkHeight, linkWidth, pinned, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { address, displayName, shortAddress } = author || {};
- const linkCount = countLinksInCommentReplies(post);
+ const linkCount = useCountLinksInReplies(post);
const displayTitle = title && title.length > 30 ? title.slice(0, 30) + '(...)' : title;
const displayContent = content && content.length > 1000 ? content.slice(0, 1000) : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
+ const [showThumbnail, setShowThumbnail] = useState(true);
+
+ const replies = useReplies(post);
return (
@@ -139,10 +301,15 @@ const PostMobile = ({ post }: Comment) => {
{hasThumbnail && (
-
-
- {commentMediaInfo?.type && {commentMediaInfo.type}
}
-
+
)}
{content && (
@@ -166,25 +333,24 @@ const PostMobile = ({ post }: Comment) => {
+ {!pinned &&
+ replies.map((reply, index) => (
+
+
+
+ ))}
);
};
const Post = ({ post }: Comment) => {
- const replies = useReplies(post);
-
return (
- {replies.map((reply) => (
-
-
-
- ))}
);
};
diff --git a/src/hooks/use-count-links-in-replies.ts b/src/hooks/use-count-links-in-replies.ts
new file mode 100644
index 00000000..bf092866
--- /dev/null
+++ b/src/hooks/use-count-links-in-replies.ts
@@ -0,0 +1,20 @@
+import { useMemo } from 'react';
+import { Comment } from '@plebbit/plebbit-react-hooks';
+import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils';
+
+const useCountLinksInReplies = (comment: Comment, firstXReplies?: number) => {
+ let linkCount = 0;
+ const flattenedReplies = useMemo(() => flattenCommentsPages(comment.replies), [comment.replies]);
+
+ const repliesToConsider = firstXReplies !== undefined ? flattenedReplies.slice(0, firstXReplies) : flattenedReplies;
+
+ for (let reply of repliesToConsider) {
+ if (reply.link) {
+ linkCount++;
+ }
+ }
+
+ return linkCount;
+};
+
+export default useCountLinksInReplies;
diff --git a/src/lib/utils/comment-utils.ts b/src/lib/utils/comment-utils.ts
deleted file mode 100644
index 75b500e9..00000000
--- a/src/lib/utils/comment-utils.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Comment } from '@plebbit/plebbit-react-hooks';
-
-export const countLinksInCommentReplies = (comment: Comment) => {
- let linkCount = 0;
-
- if (comment.replyCount > 0) {
- for (let reply of comment.replies.pages.topAll.comments) {
- if (reply.link) {
- linkCount++;
- }
-
- if (reply.replyCount > 0) {
- linkCount += countLinksInCommentReplies(reply);
- }
- }
- }
-
- return linkCount;
-};
diff --git a/src/themes.css b/src/themes.css
index 920c566b..b8b9910e 100644
--- a/src/themes.css
+++ b/src/themes.css
@@ -78,6 +78,7 @@
--post-menu-button-color: #000080;
--post-menu-button-color-hover: red;
--post-greentext-color: #789922;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-red.png");
/* post mobile */
--post-mobile-background-color: #f5e9e1;
@@ -98,6 +99,11 @@
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05);
+ /* reply desktop */
+ --side-arrows-color: #e0bfb7;
+ --reply-desktop-background-color: #f0e0d6;
+ --reply-desktop-border: 1px solid #d9bfb7;
+
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
@@ -184,6 +190,9 @@
--post-menu-button-color: #34345c;
--post-menu-button-color-hover: #d00;
--post-greentext-color: #789922;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-blue.png");
+
+ /* post mobile */
--post-mobile-background-color: #d6daf0;
--post-mobile-info-border-bottom: 1px solid #b7c5d9;
--post-mobile-info-background-color: #c9cde8;
@@ -198,8 +207,14 @@
--post-mobile-abbr-text-color: #707070;
--post-mobile-abbr-font-size: 10pt;
--post-mobile-content-font-size: 11pt;
+
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05);
+ /* reply desktop */
+ --side-arrows-color: #b7c5d9;
+ --reply-desktop-background-color: #d6daf0;
+ --reply-desktop-border: 1px solid #b7c5d9;
+
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
@@ -264,6 +279,9 @@
--post-menu-button-color: #000080;
--post-menu-button-color-hover: red;
--post-greentext-color: #789922;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-red.png");
+
+ /* post mobile */
--post-mobile-background-color: #f5e9e1;
--post-mobile-info-border-bottom: 1px solid #d9bfb7;
--post-mobile-info-background-color: #ead6ca;
@@ -278,8 +296,14 @@
--post-mobile-abbr-text-color: #707070;
--post-mobile-abbr-font-size: 12pt;
--post-mobile-content-font-size: 13pt;
+
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05);
+ /* reply desktop */
+ --side-arrows-color: #e0bfb7;
+ --reply-desktop-background-color: #f0e0d6;
+ --reply-desktop-border: 1px solid #d9bfb7;
+
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
@@ -357,7 +381,14 @@
--post-menu-button-color: #34345c;
--post-menu-button-color-hover: #d00;
--post-greentext-color: #789922;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-blue.png");
+
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05);
+
+ /* reply desktop */
+ --side-arrows-color: #b7c5d9;
+ --reply-desktop-background-color: #d6daf0;
+ --reply-desktop-border: 1px solid #b7c5d9;
/* post form */
--post-form-toggle-font-size: 22px;
@@ -432,8 +463,15 @@
--post-menu-button-color: #5F89AC;
--post-menu-button-color-hover: #81a2be;
--post-greentext-color: #b5bd68;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-dark.png");
+
--post-thumbnail-background-color: rgba(255, 255, 255, 0.01);
+ /* reply desktop */
+ --side-arrows-color: #c5c8c6;
+ --reply-desktop-background-color: #282a2e;
+ --reply-desktop-border: 1px solid #282a2e;
+
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
@@ -502,8 +540,15 @@
--post-menu-button-color: #FF6600;
--post-menu-button-color-hover: #FF3300;
--post-greentext-color: #789922;
+ --post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-photon.png");
+
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05);
+ /* reply desktop */
+ --side-arrows-color: #333;
+ --reply-desktop-background-color: #ddd;
+ --reply-desktop-border: 1px solid #ccc;
+
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
diff --git a/src/views/subplebbit/subplebbit.tsx b/src/views/subplebbit/subplebbit.tsx
index 33029a82..b47192b5 100644
--- a/src/views/subplebbit/subplebbit.tsx
+++ b/src/views/subplebbit/subplebbit.tsx
@@ -15,22 +15,17 @@ interface SubplebbitProps {
const Subplebbit = ({ subplebbits }: SubplebbitProps) => {
const { t } = useTranslation();
-
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
- const subplebbit = subplebbits.find((s) => s?.address === subplebbitAddress);
const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[];
- const { shortAddress, state, title } = subplebbit || {};
-
const sortType = 'active';
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType });
+
+ const subplebbit = subplebbits.find((s) => s?.address === subplebbitAddress);
+ const { shortAddress, state, title } = subplebbit || {};
+
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
-
const loadingString = {state === 'failed' ? state : }
;
- useEffect(() => {
- document.title = title ? title : shortAddress;
- }, [title, shortAddress]);
-
const Footer = () => {
let footerContent;
if (feed.length === 0) {
@@ -57,6 +52,11 @@ const Subplebbit = ({ subplebbits }: SubplebbitProps) => {
}, [subplebbitAddress, sortType]);
const lastVirtuosoState = lastVirtuosoStates?.[subplebbitAddress + sortType];
+
+ useEffect(() => {
+ document.title = title ? title : shortAddress;
+ }, [title, shortAddress]);
+
return (