Merge pull request #331 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-04-02 14:29:52 +02:00
committed by GitHub
46 changed files with 605 additions and 127 deletions
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "مثبت",
"closed": "مغلق",
"reply": "رد",
"link": "رابط"
"link": "رابط",
"close": "إغلاق"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "স্টিকি",
"closed": "বন্ধ",
"reply": "উত্তর",
"link": "লিংক"
"link": "লিংক",
"close": "বন্ধ করুন"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Připnutý",
"closed": "Zavřeno",
"reply": "Odpovědět",
"link": "Odkaz"
"link": "Odkaz",
"close": "Zavřít"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fikseret",
"closed": "Lukket",
"reply": "Svar",
"link": "Link"
"link": "Link",
"close": "Luk"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Angeheftet",
"closed": "Geschlossen",
"reply": "Antworten",
"link": "Link"
"link": "Link",
"close": "Schließen"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Κολλημένος",
"closed": "Κλειστό",
"reply": "Απάντηση",
"link": "Σύνδεσμος"
"link": "Σύνδεσμος",
"close": "Κλείσιμο"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Sticky",
"closed": "Closed",
"reply": "Reply",
"link": "Link"
"link": "Link",
"close": "Close"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fijado",
"closed": "Cerrado",
"reply": "Responder",
"link": "Enlace"
"link": "Enlace",
"close": "Cerrar"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "چسبیده",
"closed": "بسته",
"reply": "پاسخ",
"link": "لینک"
"link": "لینک",
"close": "بستن"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Tikki",
"closed": "Suljettu",
"reply": "Vastaa",
"link": "Linkki"
"link": "Linkki",
"close": "Sulje"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Malagkit",
"closed": "Sarado",
"reply": "Tumugon",
"link": "Link"
"link": "Link",
"close": "Isara"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Épinglé",
"closed": "Fermé",
"reply": "Répondre",
"link": "Lien"
"link": "Lien",
"close": "Fermer"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "מדבקה",
"closed": "סגור",
"reply": "הגב",
"link": "קישור"
"link": "קישור",
"close": "סגור"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "चिपकाया",
"closed": "बंद",
"reply": "जवाब दें",
"link": "लिंक"
"link": "लिंक",
"close": "बंद करें"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Rögzített",
"closed": "Zárt",
"reply": "Válaszolás",
"link": "Hivatkozás"
"link": "Hivatkozás",
"close": "Bezárás"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Menempel",
"closed": "Tutup",
"reply": "Balas",
"link": "Tautan"
"link": "Tautan",
"close": "Tutup"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Incollato",
"closed": "Chiuso",
"reply": "Commenta",
"link": "Link"
"link": "Link",
"close": "Chiudi"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "固定",
"closed": "閉鎖",
"reply": "返信",
"link": "リンク"
"link": "リンク",
"close": "閉じる"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "고정된",
"closed": "닫힘",
"reply": "답장",
"link": "링크"
"link": "링크",
"close": "닫기"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "स्टिकी",
"closed": "बंद",
"reply": "प्रतिसाद",
"link": "लिंक"
"link": "लिंक",
"close": "बंद करा"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Vastgemaakt",
"closed": "Gesloten",
"reply": "Antwoord",
"link": "Link"
"link": "Link",
"close": "Sluiten"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Klistret",
"closed": "Stengt",
"reply": "Svar",
"link": "Lenke"
"link": "Lenke",
"close": "Lukk"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Przyklejony",
"closed": "Zamknięty",
"reply": "Odpowiedz",
"link": "Link"
"link": "Link",
"close": "Zamknij"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fixado",
"closed": "Fechado",
"reply": "Responder",
"link": "Link"
"link": "Link",
"close": "Fechar"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Lipit",
"closed": "Închis",
"reply": "Răspunde",
"link": "Legătură"
"link": "Legătură",
"close": "Închide"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Прикреплено",
"closed": "Закрыто",
"reply": "Ответить",
"link": "Ссылка"
"link": "Ссылка",
"close": "Закрыть"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Ngjitës",
"closed": "Mbyllur",
"reply": "Përgjigju",
"link": "Lidhje"
"link": "Lidhje",
"close": "Mbylle"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Klistrad",
"closed": "Stängd",
"reply": "Svara",
"link": "Länk"
"link": "Länk",
"close": "Stäng"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "స్టికీ",
"closed": "మూసివేయబడినది",
"reply": "స్పందించు",
"link": "లింక్"
"link": "లింక్",
"close": "మూసి"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "ปักหมุด",
"closed": "ปิด",
"reply": "ตอบ",
"link": "ลิงก์"
"link": "ลิงก์",
"close": "ปิด"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Yapışkan",
"closed": "Kapalı",
"reply": "Yanıtla",
"link": "Bağlantı"
"link": "Bağlantı",
"close": "Kapat"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Прикріплений",
"closed": "Закрито",
"reply": "Відповісти",
"link": "Посилання"
"link": "Посилання",
"close": "Закрити"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "چسپی",
"closed": "بند",
"reply": "جواب دیں",
"link": "لنک"
"link": "لنک",
"close": "بند کریں"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Dán",
"closed": "Đóng",
"reply": "Trả lời",
"link": "Liên kết"
"link": "Liên kết",
"close": "Đóng"
}
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "置顶",
"closed": "关闭",
"reply": "回复",
"link": "链接"
"link": "链接",
"close": "关闭"
}
@@ -34,4 +34,10 @@
.bannerCnt {
padding-top: 35px;
}
}
@media (min-width: 640px) {
.bannerCnt {
padding: 5px 0 2px 0;
}
}
+1 -1
View File
@@ -22,4 +22,4 @@
.hrWrapper hr {
margin: 0;
}
}
+1 -1
View File
@@ -1 +1 @@
export { Thumbnail, Media } from './media';
export { default } from './media';
+67
View File
@@ -18,6 +18,10 @@
align-items: center;
}
.thumbnail {
float: left;
}
.thumbnailBig img, .thumbnailBig video {
max-width: 250px;
max-height: 250px;
@@ -27,3 +31,66 @@
max-width: 125px;
max-height: 125px;
}
.hide {
display: none !important;
}
.thumbnail .show {
display: block;
}
.content img, .content video, .content iframe, .content audio {
box-sizing: border-box;
max-width: 100%;
max-height: 100%;
}
.content img {
cursor: pointer;
}
.content iframe {
border: none;
}
.fileInfo {
padding-bottom: 5px;
text-align: center;
color: var(--post-mobile-file-info-text-color);
font-size: 9pt;
}
.fileInfo a {
color: var(--post-link-text-color);
text-decoration: var(--post-link-text-decoration);
}
.fileInfo a:hover {
color: var(--post-link-text-color-hover);
}
.closeButton {
text-align: center;
padding: 5px 0 10px 0;
}
.thumbnailReplyDesktop {
margin: 3px 20px 5px 20px;
}
.thumbnailMobile {
margin: 8px 10px 5px 5px;
}
.mediaMobile img, .mediaMobile video, .mediaMobile iframe, .mediaMobile audio {
padding: 3px 0 5px 0;
}
.mediaDesktopReply img, .mediaDesktopReply video, .mediaDesktopReply iframe, .mediaDesktopReply audio {
padding: 3px 20px 5px 20px;
}
.mediaDesktopOp img, .mediaDesktopOp video, .mediaDesktopOp iframe, .mediaDesktopOp audio {
padding: 3px 20px 5px 0;
}
+72 -11
View File
@@ -1,30 +1,40 @@
import React from 'react';
import React, { useState } from 'react';
import styles from './media.module.css';
import { CommentMediaInfo } from '../../lib/utils/media-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import Embed from '../embed';
import { useTranslation } from 'react-i18next';
interface ThumbnailProps {
interface MediaProps {
commentMediaInfo?: CommentMediaInfo;
isMobile: boolean;
isReply: boolean;
linkHeight?: number;
linkWidth?: number;
showThumbnail: boolean;
setShowThumbnail: (showThumbnail: boolean) => void;
toggleExpanded?: () => void;
}
const ThumbnailBig = ({ style, children }: { style: React.CSSProperties; children: React.ReactNode }) => (
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 }: { style: React.CSSProperties; children: React.ReactNode }) => (
<span className={styles.thumbnailSmall} style={style}>
const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailProps) => (
<span className={`${styles.thumbnailSmall} ${thumbnailSmallPadding}`} style={style}>
{children}
</span>
);
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 = <img src={gifFrameUrl} alt='' />;
}
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 ? (
<ThumbnailSmall style={thumbnailStyle}>{mediaComponent}</ThumbnailSmall>
<ThumbnailSmall style={thumbnailDimensions} thumbnailSmallPadding={thumbnailSmallPadding}>
{mediaComponent}
</ThumbnailSmall>
) : (
<ThumbnailBig style={thumbnailStyle}>{mediaComponent}</ThumbnailBig>
<ThumbnailBig style={thumbnailDimensions}>{mediaComponent}</ThumbnailBig>
);
};
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 (
<span className={styles.content}>
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`} onClick={() => setShowThumbnail(false)}>
<Thumbnail
commentMediaInfo={commentMediaInfo}
isMobile={isMobile}
isReply={isReply}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
{isMobile && commentMediaInfo?.type && <div className={styles.fileInfo}>{commentMediaInfo.type}</div>}
</span>
<span className={`${showThumbnail ? styles.hide : styles.show} ${mediaClass}`}>
{commentMediaInfo?.type === 'iframe' ? (
<Embed url={commentMediaInfo.url} />
) : commentMediaInfo?.type === 'gif' ? (
<img src={commentMediaInfo.url} alt='' onClick={() => setShowThumbnail(true)} />
) : commentMediaInfo?.type === 'video' ? (
<video src={commentMediaInfo.url} controls autoPlay loop muted />
) : commentMediaInfo?.type === 'image' ? (
<img src={commentMediaInfo.url} alt='' onClick={() => setShowThumbnail(true)} />
) : commentMediaInfo?.type === 'webpage' ? (
<img src={commentMediaInfo.thumbnail} alt='' onClick={() => setShowThumbnail(true)} />
) : null}
{isMobile && commentMediaInfo?.type && (
<div className={styles.fileInfo}>
<a href={commentMediaInfo.url} target='_blank' rel='noopener noreferrer'>
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '...' : commentMediaInfo?.url}
</a>{' '}
({commentMediaInfo?.type})
</div>
)}
{isMobile && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
<div className={styles.closeButton}>
<span className='button' onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
</div>
)}
</span>
</span>
);
};
export default Media;
+122 -25
View File
@@ -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;
}
}
+192 -26
View File
@@ -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 (
<div className={styles.replyDesktop}>
<div className={styles.sideArrows}>{'>>'}</div>
{content}
</div>
index < 5 && (
<div className={styles.replyDesktop}>
<div className={styles.sideArrows}>{'>>'}</div>
<div className={styles.reply}>
<div className={styles.postInfo}>
<span className={styles.checkbox}>
<input type='checkbox' />
</span>
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.userAddress}>(u/{shortAddress}) </span>
</span>
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
<span className={styles.postNum}>
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${reply.cid}`} className={styles.linkToPost} title='Link to post'>
c/
</Link>
<span className={styles.replyToPost} title='Reply to post'>
{shortCid}
</span>
</span>
{pinned && (
<span className={styles.stickyIconWrapper}>
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
</span>
)}
</span>
<span className={styles.postMenuBtn}></span>
</div>
{link && (
<div className={styles.file}>
<div className={styles.fileText}>
{t('link')}:{' '}
<a href={link} target='_blank' rel='noopener noreferrer'>
{link.length > 30 ? link.slice(0, 30) + '...' : link}
</a>
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
]
</span>
)}
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={false}
isReply={true}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
</div>
)}
{content && (
<blockquote className={styles.postMessage}>
<Markdown content={content} />
</blockquote>
)}
</div>
</div>
)
);
};
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 (
<div className={styles.postDesktop}>
@@ -46,16 +125,35 @@ const PostDesktop = ({ post }: Comment) => {
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '...' : commentMediaInfo?.url}
</a>{' '}
({commentMediaInfo?.type})
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
]
</span>
)}
</div>
{hasThumbnail && (
<span className={styles.fileThumb}>
<Thumbnail commentMediaInfo={commentMediaInfo} isMobile={false} isReply={false} linkHeight={linkHeight} linkWidth={linkWidth} />
</span>
<Media
commentMediaInfo={commentMediaInfo}
isMobile={false}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
</div>
)}
<div className={styles.postInfo}>
{title && <span className={styles.subject}>{displayTitle}</span>}{' '}
<span className={styles.checkbox}>
<input type='checkbox' />
</span>
{title && <span className={styles.subject}>{displayTitle} </span>}
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.userAddress}>(u/{shortAddress}) </span>
@@ -86,6 +184,7 @@ const PostDesktop = ({ post }: Comment) => {
</span>
<span className={styles.postMenuBtn}></span>
</div>
{!content && <div className={styles.spacer} />}
{content && (
<blockquote className={styles.postMessage}>
<Markdown content={displayContent} />
@@ -97,19 +196,82 @@ const PostDesktop = ({ post }: Comment) => {
)}
</blockquote>
)}
{(replies.length > 5 || pinned) && (
<span className={styles.summary}>
<span className={styles.expandButton} />
<span>
{repliesCount} replies {linkCount > 0 && `and ${linkCount} links`} omitted.{' '}
</span>
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>Click here</Link> to view.
</span>
)}
{!pinned &&
replies.map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyDesktop index={index} reply={reply} />
</div>
))}
</div>
);
};
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 && (
<div className={styles.replyMobile}>
<div className={styles.reply}>
<div className={styles.replyContainer}>
<div className={styles.postInfo}>
<span className={styles.postMenuBtn}>...</span>
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.address}>(u/{shortAddress})</span>
</span>
<span className={styles.dateTimePostNum}>
{getFormattedDate(timestamp)} <span className={styles.linkToPost}>c/</span>
<span className={styles.replyToPost}>{shortCid}</span>
</span>
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={true}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
<blockquote className={styles.postMessage}>
<Markdown content={content} />
</blockquote>
</div>
</div>
</div>
)
);
};
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 (
<div className={styles.postMobile}>
@@ -139,10 +301,15 @@ const PostMobile = ({ post }: Comment) => {
</span>
</div>
{hasThumbnail && (
<span className={styles.fileThumb}>
<Thumbnail commentMediaInfo={commentMediaInfo} isMobile={true} isReply={false} linkHeight={linkHeight} linkWidth={linkWidth} />
{commentMediaInfo?.type && <div className={styles.fileInfo}>{commentMediaInfo.type}</div>}
</span>
<Media
commentMediaInfo={commentMediaInfo}
isMobile={true}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
{content && (
<blockquote className={`${styles.postMessage} ${styles.clampLines}`}>
@@ -166,25 +333,24 @@ const PostMobile = ({ post }: Comment) => {
</Link>
</div>
</div>
{!pinned &&
replies.map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyMobile index={index} reply={reply} />
</div>
))}
</div>
</div>
);
};
const Post = ({ post }: Comment) => {
const replies = useReplies(post);
return (
<div className={styles.thread}>
<div className={styles.postContainer}>
<PostDesktop post={post} />
<PostMobile post={post} />
</div>
{replies.map((reply) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyDesktop reply={reply} />
</div>
))}
</div>
);
};
+20
View File
@@ -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;
-19
View File
@@ -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;
};
+45
View File
@@ -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;
+9 -9
View File
@@ -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 = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>;
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 (
<div className={styles.content}>
<Virtuoso