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": "مثبت", "sticky": "مثبت",
"closed": "مغلق", "closed": "مغلق",
"reply": "رد", "reply": "رد",
"link": "رابط" "link": "رابط",
"close": "إغلاق"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "স্টিকি", "sticky": "স্টিকি",
"closed": "বন্ধ", "closed": "বন্ধ",
"reply": "উত্তর", "reply": "উত্তর",
"link": "লিংক" "link": "লিংক",
"close": "বন্ধ করুন"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Připnutý", "sticky": "Připnutý",
"closed": "Zavřeno", "closed": "Zavřeno",
"reply": "Odpovědět", "reply": "Odpovědět",
"link": "Odkaz" "link": "Odkaz",
"close": "Zavřít"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fikseret", "sticky": "Fikseret",
"closed": "Lukket", "closed": "Lukket",
"reply": "Svar", "reply": "Svar",
"link": "Link" "link": "Link",
"close": "Luk"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Angeheftet", "sticky": "Angeheftet",
"closed": "Geschlossen", "closed": "Geschlossen",
"reply": "Antworten", "reply": "Antworten",
"link": "Link" "link": "Link",
"close": "Schließen"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Κολλημένος", "sticky": "Κολλημένος",
"closed": "Κλειστό", "closed": "Κλειστό",
"reply": "Απάντηση", "reply": "Απάντηση",
"link": "Σύνδεσμος" "link": "Σύνδεσμος",
"close": "Κλείσιμο"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Sticky", "sticky": "Sticky",
"closed": "Closed", "closed": "Closed",
"reply": "Reply", "reply": "Reply",
"link": "Link" "link": "Link",
"close": "Close"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fijado", "sticky": "Fijado",
"closed": "Cerrado", "closed": "Cerrado",
"reply": "Responder", "reply": "Responder",
"link": "Enlace" "link": "Enlace",
"close": "Cerrar"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "چسبیده", "sticky": "چسبیده",
"closed": "بسته", "closed": "بسته",
"reply": "پاسخ", "reply": "پاسخ",
"link": "لینک" "link": "لینک",
"close": "بستن"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Tikki", "sticky": "Tikki",
"closed": "Suljettu", "closed": "Suljettu",
"reply": "Vastaa", "reply": "Vastaa",
"link": "Linkki" "link": "Linkki",
"close": "Sulje"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Malagkit", "sticky": "Malagkit",
"closed": "Sarado", "closed": "Sarado",
"reply": "Tumugon", "reply": "Tumugon",
"link": "Link" "link": "Link",
"close": "Isara"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Épinglé", "sticky": "Épinglé",
"closed": "Fermé", "closed": "Fermé",
"reply": "Répondre", "reply": "Répondre",
"link": "Lien" "link": "Lien",
"close": "Fermer"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "מדבקה", "sticky": "מדבקה",
"closed": "סגור", "closed": "סגור",
"reply": "הגב", "reply": "הגב",
"link": "קישור" "link": "קישור",
"close": "סגור"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "चिपकाया", "sticky": "चिपकाया",
"closed": "बंद", "closed": "बंद",
"reply": "जवाब दें", "reply": "जवाब दें",
"link": "लिंक" "link": "लिंक",
"close": "बंद करें"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Rögzített", "sticky": "Rögzített",
"closed": "Zárt", "closed": "Zárt",
"reply": "Válaszolás", "reply": "Válaszolás",
"link": "Hivatkozás" "link": "Hivatkozás",
"close": "Bezárás"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Menempel", "sticky": "Menempel",
"closed": "Tutup", "closed": "Tutup",
"reply": "Balas", "reply": "Balas",
"link": "Tautan" "link": "Tautan",
"close": "Tutup"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Incollato", "sticky": "Incollato",
"closed": "Chiuso", "closed": "Chiuso",
"reply": "Commenta", "reply": "Commenta",
"link": "Link" "link": "Link",
"close": "Chiudi"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "固定", "sticky": "固定",
"closed": "閉鎖", "closed": "閉鎖",
"reply": "返信", "reply": "返信",
"link": "リンク" "link": "リンク",
"close": "閉じる"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "고정된", "sticky": "고정된",
"closed": "닫힘", "closed": "닫힘",
"reply": "답장", "reply": "답장",
"link": "링크" "link": "링크",
"close": "닫기"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "स्टिकी", "sticky": "स्टिकी",
"closed": "बंद", "closed": "बंद",
"reply": "प्रतिसाद", "reply": "प्रतिसाद",
"link": "लिंक" "link": "लिंक",
"close": "बंद करा"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Vastgemaakt", "sticky": "Vastgemaakt",
"closed": "Gesloten", "closed": "Gesloten",
"reply": "Antwoord", "reply": "Antwoord",
"link": "Link" "link": "Link",
"close": "Sluiten"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Klistret", "sticky": "Klistret",
"closed": "Stengt", "closed": "Stengt",
"reply": "Svar", "reply": "Svar",
"link": "Lenke" "link": "Lenke",
"close": "Lukk"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Przyklejony", "sticky": "Przyklejony",
"closed": "Zamknięty", "closed": "Zamknięty",
"reply": "Odpowiedz", "reply": "Odpowiedz",
"link": "Link" "link": "Link",
"close": "Zamknij"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Fixado", "sticky": "Fixado",
"closed": "Fechado", "closed": "Fechado",
"reply": "Responder", "reply": "Responder",
"link": "Link" "link": "Link",
"close": "Fechar"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Lipit", "sticky": "Lipit",
"closed": "Închis", "closed": "Închis",
"reply": "Răspunde", "reply": "Răspunde",
"link": "Legătură" "link": "Legătură",
"close": "Închide"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Прикреплено", "sticky": "Прикреплено",
"closed": "Закрыто", "closed": "Закрыто",
"reply": "Ответить", "reply": "Ответить",
"link": "Ссылка" "link": "Ссылка",
"close": "Закрыть"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Ngjitës", "sticky": "Ngjitës",
"closed": "Mbyllur", "closed": "Mbyllur",
"reply": "Përgjigju", "reply": "Përgjigju",
"link": "Lidhje" "link": "Lidhje",
"close": "Mbylle"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Klistrad", "sticky": "Klistrad",
"closed": "Stängd", "closed": "Stängd",
"reply": "Svara", "reply": "Svara",
"link": "Länk" "link": "Länk",
"close": "Stäng"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "స్టికీ", "sticky": "స్టికీ",
"closed": "మూసివేయబడినది", "closed": "మూసివేయబడినది",
"reply": "స్పందించు", "reply": "స్పందించు",
"link": "లింక్" "link": "లింక్",
"close": "మూసి"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "ปักหมุด", "sticky": "ปักหมุด",
"closed": "ปิด", "closed": "ปิด",
"reply": "ตอบ", "reply": "ตอบ",
"link": "ลิงก์" "link": "ลิงก์",
"close": "ปิด"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Yapışkan", "sticky": "Yapışkan",
"closed": "Kapalı", "closed": "Kapalı",
"reply": "Yanıtla", "reply": "Yanıtla",
"link": "Bağlantı" "link": "Bağlantı",
"close": "Kapat"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Прикріплений", "sticky": "Прикріплений",
"closed": "Закрито", "closed": "Закрито",
"reply": "Відповісти", "reply": "Відповісти",
"link": "Посилання" "link": "Посилання",
"close": "Закрити"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "چسپی", "sticky": "چسپی",
"closed": "بند", "closed": "بند",
"reply": "جواب دیں", "reply": "جواب دیں",
"link": "لنک" "link": "لنک",
"close": "بند کریں"
} }
+2 -1
View File
@@ -35,5 +35,6 @@
"sticky": "Dán", "sticky": "Dán",
"closed": "Đóng", "closed": "Đóng",
"reply": "Trả lời", "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": "置顶", "sticky": "置顶",
"closed": "关闭", "closed": "关闭",
"reply": "回复", "reply": "回复",
"link": "链接" "link": "链接",
"close": "关闭"
} }
@@ -34,4 +34,10 @@
.bannerCnt { .bannerCnt {
padding-top: 35px; padding-top: 35px;
} }
}
@media (min-width: 640px) {
.bannerCnt {
padding: 5px 0 2px 0;
}
} }
+1 -1
View File
@@ -22,4 +22,4 @@
.hrWrapper hr { .hrWrapper hr {
margin: 0; 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; align-items: center;
} }
.thumbnail {
float: left;
}
.thumbnailBig img, .thumbnailBig video { .thumbnailBig img, .thumbnailBig video {
max-width: 250px; max-width: 250px;
max-height: 250px; max-height: 250px;
@@ -27,3 +31,66 @@
max-width: 125px; max-width: 125px;
max-height: 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 styles from './media.module.css';
import { CommentMediaInfo } from '../../lib/utils/media-utils'; import { CommentMediaInfo } from '../../lib/utils/media-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import Embed from '../embed';
import { useTranslation } from 'react-i18next';
interface ThumbnailProps { interface MediaProps {
commentMediaInfo?: CommentMediaInfo; commentMediaInfo?: CommentMediaInfo;
isMobile: boolean; isMobile: boolean;
isReply: boolean; isReply: boolean;
linkHeight?: number; linkHeight?: number;
linkWidth?: number; linkWidth?: number;
showThumbnail: boolean;
setShowThumbnail: (showThumbnail: boolean) => void;
toggleExpanded?: () => 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}> <span className={styles.thumbnailBig} style={style}>
{children} {children}
</span> </span>
); );
const ThumbnailSmall = ({ style, children }: { style: React.CSSProperties; children: React.ReactNode }) => ( const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailProps) => (
<span className={styles.thumbnailSmall} style={style}> <span className={`${styles.thumbnailSmall} ${thumbnailSmallPadding}`} style={style}>
{children} {children}
</span> </span>
); );
export const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth }: ThumbnailProps) => { const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth }: MediaProps) => {
let displayWidth, displayHeight; let displayWidth, displayHeight;
const maxThumbnailSize = isMobile || isReply ? 125 : 250; const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) { if (linkWidth && linkHeight) {
@@ -51,15 +61,66 @@ export const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, lin
mediaComponent = <img src={gifFrameUrl} alt='' />; 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 ? ( 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 = () => { const Media = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
return <></>; 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); background-image: var(--post-unhide-button-background-image);
} }
.postDesktop .fileThumb {
float: left;
}
.postDesktop .fileText a { .postDesktop .fileText a {
color: var(--post-link-text-color); color: var(--post-link-text-color);
text-decoration: var(--post-link-text-decoration); text-decoration: var(--post-link-text-decoration);
@@ -58,27 +54,31 @@
width: 100%; width: 100%;
} }
.postDesktop .checkbox, .replyDesktop .checkbox {
padding: 3px 7px 3px 4px;
}
.postDesktop .subject { .postDesktop .subject {
color: var(--post-subject-text-color); color: var(--post-subject-text-color);
font-weight: var(--post-subject-font-weight); font-weight: var(--post-subject-font-weight);
} }
.postDesktop .name { .postDesktop .name, .replyDesktop .name {
color: var(--post-name-text-color); color: var(--post-name-text-color);
font-weight: var(--post-name-font-weight); 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; color: unset;
text-decoration: 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; cursor: pointer;
color: var(--button-desktop-text-color-hover); color: var(--button-desktop-text-color-hover);
} }
.postDesktop .stickyIconWrapper { .postDesktop .stickyIconWrapper, .replyDesktop .stickyIconWrapper {
padding-left: 5px; padding-left: 5px;
} }
@@ -86,13 +86,24 @@
padding-left: 3px; padding-left: 3px;
} }
.postDesktop .stickyIconWrapper img, .postDesktop .closedIconWrapper img { .postDesktop .stickyIconWrapper img, .postDesktop .closedIconWrapper img, .replyDesktop .stickyIconWrapper img, .replyDesktop .closedIconWrapper img {
width: 16px; width: 16px;
height: 16px; height: 16px;
border: none; border: none;
image-rendering: pixelated; 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 { .postDesktop .replyButton {
padding-left: 10px; padding-left: 10px;
} }
@@ -108,17 +119,22 @@
color: var(--button-desktop-text-color-hover); color: var(--button-desktop-text-color-hover);
} }
.postDesktop .postMenuBtn { .postDesktop .postMenuBtn, .replyDesktop .postMenuBtn {
padding-left: 5px; padding-left: 5px;
color: var(--post-menu-button-color); color: var(--post-menu-button-color);
} }
.postDesktop .postMenuBtn:hover { .postDesktop .postMenuBtn:hover, .replyDesktop .postMenuBtn:hover {
cursor: pointer; cursor: pointer;
color: var(--post-menu-button-color-hover); color: var(--post-menu-button-color-hover);
} }
.postDesktop .postMessage { .postDesktop .spacer {
width: 100%;
padding: 5px;
}
.postDesktop .postMessage, .replyDesktop .postMessage {
padding: 1em 40px; padding: 1em 40px;
} }
@@ -137,6 +153,33 @@
text-decoration: var(--post-content-link-text-decoration-hover); 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 { .postMobile hr {
padding-bottom: 30px; padding-bottom: 30px;
} }
@@ -167,7 +210,7 @@
.postMobile .postMenuBtn { .postMobile .postMenuBtn {
color: var(--post-mobile-menu-button-color); color: var(--post-mobile-menu-button-color);
line-height: 1em; line-height: 1em;
width: 10px; width: 1em;
text-align: center; text-align: center;
transition: transform 0.1s; transition: transform 0.1s;
transform: rotate(90deg); transform: rotate(90deg);
@@ -201,18 +244,6 @@
text-align: right; 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 { .postMobile .postMessage {
padding: 10px; padding: 10px;
font-size: var(--post-mobile-content-font-size); font-size: var(--post-mobile-content-font-size);
@@ -249,14 +280,80 @@
text-decoration: var(--post-content-link-text-decoration-hover); 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) { @media (max-width: 640px) {
.postDesktop { .postDesktop {
display: none; display: none;
} }
.replyDesktop {
display: none;
}
} }
@media (min-width: 641px) { @media (min-width: 641px) {
.postMobile { .postMobile {
display: none; display: none;
} }
.replyMobile {
display: none;
}
} }
+192 -26
View File
@@ -1,34 +1,113 @@
import { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks'; import { Comment } from '@plebbit/plebbit-react-hooks';
import { getCommentMediaInfoMemoized, getHasThumbnail } from '../../lib/utils/media-utils'; import { getCommentMediaInfoMemoized, getHasThumbnail } from '../../lib/utils/media-utils';
import { getFormattedDate } from '../../lib/utils/time-utils'; import { getFormattedDate } from '../../lib/utils/time-utils';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useReplies from '../../hooks/use-replies'; import useReplies from '../../hooks/use-replies';
import styles from './post.module.css'; import styles from './post.module.css';
import Markdown from '../markdown'; 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 ( return (
<div className={styles.replyDesktop}> index < 5 && (
<div className={styles.sideArrows}>{'>>'}</div> <div className={styles.replyDesktop}>
{content} <div className={styles.sideArrows}>{'>>'}</div>
</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 PostDesktop = ({ post }: Comment) => {
const { t } = useTranslation(); 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 { displayName, shortAddress } = author || {};
const displayTitle = title && title.length > 75 ? title.slice(0, 75) + '...' : title; const displayTitle = title && title.length > 75 ? title.slice(0, 75) + '...' : title;
const displayContent = content && content.length > 1000 ? content.slice(0, 1000) + '(...)' : content; const displayContent = content && content.length > 1000 ? content.slice(0, 1000) + '(...)' : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post); const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link); 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 ( return (
<div className={styles.postDesktop}> <div className={styles.postDesktop}>
@@ -46,16 +125,35 @@ const PostDesktop = ({ post }: Comment) => {
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '...' : commentMediaInfo?.url} {commentMediaInfo.url.length > 30 ? commentMediaInfo?.url.slice(0, 30) + '...' : commentMediaInfo?.url}
</a>{' '} </a>{' '}
({commentMediaInfo?.type}) ({commentMediaInfo?.type})
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
]
</span>
)}
</div> </div>
{hasThumbnail && ( {hasThumbnail && (
<span className={styles.fileThumb}> <Media
<Thumbnail commentMediaInfo={commentMediaInfo} isMobile={false} isReply={false} linkHeight={linkHeight} linkWidth={linkWidth} /> commentMediaInfo={commentMediaInfo}
</span> isMobile={false}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)} )}
</div> </div>
)} )}
<div className={styles.postInfo}> <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.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span> <span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.userAddress}>(u/{shortAddress}) </span> <span className={styles.userAddress}>(u/{shortAddress}) </span>
@@ -86,6 +184,7 @@ const PostDesktop = ({ post }: Comment) => {
</span> </span>
<span className={styles.postMenuBtn}></span> <span className={styles.postMenuBtn}></span>
</div> </div>
{!content && <div className={styles.spacer} />}
{content && ( {content && (
<blockquote className={styles.postMessage}> <blockquote className={styles.postMessage}>
<Markdown content={displayContent} /> <Markdown content={displayContent} />
@@ -97,19 +196,82 @@ const PostDesktop = ({ post }: Comment) => {
)} )}
</blockquote> </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> </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 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 { address, displayName, shortAddress } = author || {};
const linkCount = countLinksInCommentReplies(post); const linkCount = useCountLinksInReplies(post);
const displayTitle = title && title.length > 30 ? title.slice(0, 30) + '(...)' : title; const displayTitle = title && title.length > 30 ? title.slice(0, 30) + '(...)' : title;
const displayContent = content && content.length > 1000 ? content.slice(0, 1000) : content; const displayContent = content && content.length > 1000 ? content.slice(0, 1000) : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post); const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link); const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
const replies = useReplies(post);
return ( return (
<div className={styles.postMobile}> <div className={styles.postMobile}>
@@ -139,10 +301,15 @@ const PostMobile = ({ post }: Comment) => {
</span> </span>
</div> </div>
{hasThumbnail && ( {hasThumbnail && (
<span className={styles.fileThumb}> <Media
<Thumbnail commentMediaInfo={commentMediaInfo} isMobile={true} isReply={false} linkHeight={linkHeight} linkWidth={linkWidth} /> commentMediaInfo={commentMediaInfo}
{commentMediaInfo?.type && <div className={styles.fileInfo}>{commentMediaInfo.type}</div>} isMobile={true}
</span> isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)} )}
{content && ( {content && (
<blockquote className={`${styles.postMessage} ${styles.clampLines}`}> <blockquote className={`${styles.postMessage} ${styles.clampLines}`}>
@@ -166,25 +333,24 @@ const PostMobile = ({ post }: Comment) => {
</Link> </Link>
</div> </div>
</div> </div>
{!pinned &&
replies.map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyMobile index={index} reply={reply} />
</div>
))}
</div> </div>
</div> </div>
); );
}; };
const Post = ({ post }: Comment) => { const Post = ({ post }: Comment) => {
const replies = useReplies(post);
return ( return (
<div className={styles.thread}> <div className={styles.thread}>
<div className={styles.postContainer}> <div className={styles.postContainer}>
<PostDesktop post={post} /> <PostDesktop post={post} />
<PostMobile post={post} /> <PostMobile post={post} />
</div> </div>
{replies.map((reply) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyDesktop reply={reply} />
</div>
))}
</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: #000080;
--post-menu-button-color-hover: red; --post-menu-button-color-hover: red;
--post-greentext-color: #789922; --post-greentext-color: #789922;
--post-replies-expand-button-background-image: url("/public/assets/buttons/post-expand-plus-red.png");
/* post mobile */ /* post mobile */
--post-mobile-background-color: #f5e9e1; --post-mobile-background-color: #f5e9e1;
@@ -98,6 +99,11 @@
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700; --post-form-toggle-font-weight: 700;
@@ -184,6 +190,9 @@
--post-menu-button-color: #34345c; --post-menu-button-color: #34345c;
--post-menu-button-color-hover: #d00; --post-menu-button-color-hover: #d00;
--post-greentext-color: #789922; --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-background-color: #d6daf0;
--post-mobile-info-border-bottom: 1px solid #b7c5d9; --post-mobile-info-border-bottom: 1px solid #b7c5d9;
--post-mobile-info-background-color: #c9cde8; --post-mobile-info-background-color: #c9cde8;
@@ -198,8 +207,14 @@
--post-mobile-abbr-text-color: #707070; --post-mobile-abbr-text-color: #707070;
--post-mobile-abbr-font-size: 10pt; --post-mobile-abbr-font-size: 10pt;
--post-mobile-content-font-size: 11pt; --post-mobile-content-font-size: 11pt;
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700; --post-form-toggle-font-weight: 700;
@@ -264,6 +279,9 @@
--post-menu-button-color: #000080; --post-menu-button-color: #000080;
--post-menu-button-color-hover: red; --post-menu-button-color-hover: red;
--post-greentext-color: #789922; --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-background-color: #f5e9e1;
--post-mobile-info-border-bottom: 1px solid #d9bfb7; --post-mobile-info-border-bottom: 1px solid #d9bfb7;
--post-mobile-info-background-color: #ead6ca; --post-mobile-info-background-color: #ead6ca;
@@ -278,8 +296,14 @@
--post-mobile-abbr-text-color: #707070; --post-mobile-abbr-text-color: #707070;
--post-mobile-abbr-font-size: 12pt; --post-mobile-abbr-font-size: 12pt;
--post-mobile-content-font-size: 13pt; --post-mobile-content-font-size: 13pt;
--post-thumbnail-background-color: rgba(0, 0, 0, 0.05); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700; --post-form-toggle-font-weight: 700;
@@ -357,7 +381,14 @@
--post-menu-button-color: #34345c; --post-menu-button-color: #34345c;
--post-menu-button-color-hover: #d00; --post-menu-button-color-hover: #d00;
--post-greentext-color: #789922; --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); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
@@ -432,8 +463,15 @@
--post-menu-button-color: #5F89AC; --post-menu-button-color: #5F89AC;
--post-menu-button-color-hover: #81a2be; --post-menu-button-color-hover: #81a2be;
--post-greentext-color: #b5bd68; --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); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700; --post-form-toggle-font-weight: 700;
@@ -502,8 +540,15 @@
--post-menu-button-color: #FF6600; --post-menu-button-color: #FF6600;
--post-menu-button-color-hover: #FF3300; --post-menu-button-color-hover: #FF3300;
--post-greentext-color: #789922; --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); --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 */
--post-form-toggle-font-size: 22px; --post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700; --post-form-toggle-font-weight: 700;
+9 -9
View File
@@ -15,22 +15,17 @@ interface SubplebbitProps {
const Subplebbit = ({ subplebbits }: SubplebbitProps) => { const Subplebbit = ({ subplebbits }: SubplebbitProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>(); const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbit = subplebbits.find((s) => s?.address === subplebbitAddress);
const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[]; const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[];
const { shortAddress, state, title } = subplebbit || {};
const sortType = 'active'; const sortType = 'active';
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType }); 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 loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
const loadingString = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>; const loadingString = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>;
useEffect(() => {
document.title = title ? title : shortAddress;
}, [title, shortAddress]);
const Footer = () => { const Footer = () => {
let footerContent; let footerContent;
if (feed.length === 0) { if (feed.length === 0) {
@@ -57,6 +52,11 @@ const Subplebbit = ({ subplebbits }: SubplebbitProps) => {
}, [subplebbitAddress, sortType]); }, [subplebbitAddress, sortType]);
const lastVirtuosoState = lastVirtuosoStates?.[subplebbitAddress + sortType]; const lastVirtuosoState = lastVirtuosoStates?.[subplebbitAddress + sortType];
useEffect(() => {
document.title = title ? title : shortAddress;
}, [title, shortAddress]);
return ( return (
<div className={styles.content}> <div className={styles.content}>
<Virtuoso <Virtuoso