feat: add blur for nsfw media or spoilers

This commit is contained in:
Tom (plebeius.eth)
2024-12-18 16:40:45 +01:00
parent 06d3ebb02a
commit 2829e50d06
5 changed files with 226 additions and 89 deletions

View File

@@ -178,6 +178,7 @@
.joinButton {
order: 3;
margin-bottom: 3px;
margin-left: 5px;
}
}

View File

@@ -4,8 +4,7 @@
.expando {
display: block;
padding: 5px;
padding-right: 0;
padding-top: 5px;
clear: left;
position: relative;
}
@@ -17,19 +16,6 @@
.usertext {
unicode-bidi: isolate;
font-size: small;
width: 862px;
}
@media (max-width: 1200px) {
.usertext {
width: calc(100vw - 335px);
}
}
@media (max-width: 640px) {
.usertext {
width: 100%;
}
}
.markdown {
@@ -59,13 +45,16 @@
.mediaPreview {
display: inline-block;
position: relative;
max-width: 100%;
margin-bottom: 5px;
}
@media (max-width: 1200px) {
.mediaPreview img,
.mediaPreview video,
.mediaPreview iframe {
max-width: calc(100vw - 335px) !important;
max-width: calc(100vw - 435px) !important;
}
}
@@ -136,3 +125,102 @@
padding-right: 5px;
}
}
.blurContent {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.25);
backdrop-filter: blur(40px);
}
.unblurButton {
color: white;
position: absolute;
font-size: 13px;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
border: 1px solid #ffffff;
padding: 10px;
text-transform: uppercase;
}
.alwaysShowNsfwButton {
color: white;
position: absolute;
font-size: 12px;
z-index: 1;
top: calc(50% + 45px);
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.alwaysShowNsfwButton:hover {
text-decoration: underline;
}
.alwaysShowNsfwNotice {
background: #fafaf8;
border: 1px solid #e5e3da;
clear: left;
margin-top: 5px;
padding: 5px 10px;
position: relative;
unicode-bidi: isolate;
font-size: small;
margin-bottom: 10px;
}
.alwaysShowNsfwNotice p {
color: #222222;
font-weight: 400;
word-wrap: break-word;
line-height: 15px;
margin: 5px 0;
font-size: 12px;
}
.alwaysShowNsfwNotice button {
background-color: #4f86b5;
color: #ffffff;
margin-bottom: 0;
display: inline-block;
text-align: center;
text-transform: uppercase;
font-weight: bold;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
padding: 4px 12px 3px;
font-size: 12px;
line-height: 20px;
border-radius: 3px;
margin-left: auto;
display: block;
border-bottom: 2px solid #4270a2;
}
.alwaysShowNsfwNotice button:hover {
background-color: #4980ae;
}
@media (max-width: 770px) {
.mediaPreview {
width: 100%;
}
}
@media (max-width: 640px) {
.expando {
padding-left: 5px;
}
}

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import styles from './expando.module.css';
import Embed from '../embed';
@@ -14,6 +14,7 @@ interface ExpandoProps {
expanded: boolean;
link?: string;
modEditReason?: string;
nsfw?: boolean;
removed?: boolean;
showContent: boolean;
spoiler?: boolean;
@@ -28,6 +29,7 @@ const Expando = ({
expanded,
link,
modEditReason,
nsfw,
removed,
showContent,
spoiler = false,
@@ -35,7 +37,14 @@ const Expando = ({
}: ExpandoProps) => {
const { t } = useTranslation();
const [showSpoiler, setShowSpoiler] = useState(false);
const [hideContent, setHideContent] = useState(true);
const [alwaysShowNsfw, setAlwaysShowNsfw] = useState(false);
useEffect(() => {
if (!expanded) {
setHideContent(true);
}
}, [expanded]);
let mediaComponent = null;
@@ -53,51 +62,55 @@ const Expando = ({
return (
<div className={expanded ? styles.expando : styles.expandoHidden}>
<div
className={styles.expandoContent}
onClick={() => {
spoiler && !showSpoiler && setShowSpoiler(true);
}}
>
{spoiler && !showSpoiler && !(deleted || removed) && (
<>
<div className={styles.hideSpoiler} />
<span className={styles.showSpoilerButton}>{t('view_spoiler')}</span>
</>
)}
{link && !removed && commentMediaInfo?.type !== 'webpage' && (
<div className={styles.mediaPreview}>
<Link
to={link}
onClick={(e) => {
if (e.button === 0) {
e.preventDefault();
toggleExpanded && toggleExpanded();
}
}}
>
{mediaComponent}
</Link>
</div>
)}
{content && showContent && (
<div className={styles.usertext}>
<div className={styles.markdown}>
<Markdown content={content} />
{modEditReason && (
<p>
{t('mod_reason')}: {modEditReason}
</p>
{link && !removed && commentMediaInfo?.type !== 'webpage' && (
<div className={styles.mediaPreview} onClick={() => setHideContent(false)}>
{(nsfw || spoiler) && hideContent && link && commentMediaInfo?.type !== 'webpage' && !(deleted || removed) && (
<>
<div className={styles.blurContent} />
<span className={styles.unblurButton}>{nsfw && spoiler ? 'CLICK TO SEE NSFW SPOILER' : spoiler ? t('view_spoiler') : nsfw ? 'CLICK TO SEE NSFW' : ''}</span>
{nsfw && (
<span className={styles.alwaysShowNsfwButton} onClick={() => setAlwaysShowNsfw(!alwaysShowNsfw)}>
Always show NSFW media?
</span>
)}
{authorEditReason && !(removed || deleted) && (
<p>
{t('edit')}: {authorEditReason}
</p>
)}
</div>
</>
)}
<Link
to={link}
onClick={(e) => {
if (e.button === 0) {
e.preventDefault();
toggleExpanded && toggleExpanded();
}
}}
>
{mediaComponent}
</Link>
</div>
)}
{alwaysShowNsfw && (
<div className={styles.alwaysShowNsfwNotice}>
<p>Ok, we changed your preferences to always show NSFW media.</p>
<button onClick={() => setAlwaysShowNsfw(false)}>Undo</button>
</div>
)}
{content && showContent && (
<div className={styles.usertext}>
<div className={styles.markdown}>
<Markdown content={content} />
{modEditReason && (
<p>
{t('mod_reason')}: {modEditReason}
</p>
)}
{authorEditReason && !(removed || deleted) && (
<p>
{t('edit')}: {authorEditReason}
</p>
)}
</div>
)}
</div>
</div>
)}
</div>
);
};

View File

@@ -3,7 +3,7 @@ import styles from './post.module.css';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAuthorAddress, useBlock, useComment, useEditedComment, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks';
import { useTranslation } from 'react-i18next';
import { isAllView, isHomeView, isPostPageView, isProfileHiddenView, isSubplebbitView } from '../../lib/utils/view-utils';
import { isAllView, isPostPageView, isProfileHiddenView, isSubplebbitView } from '../../lib/utils/view-utils';
import { getHasThumbnail } from '../../lib/utils/media-utils';
import { getPostScore, formatScore } from '../../lib/utils/post-utils';
import { getHostname } from '../../lib/utils/url-utils';
@@ -20,6 +20,7 @@ import useUpvote from '../../hooks/use-upvote';
import _ from 'lodash';
import useIsMobile from '../../hooks/use-is-mobile';
import { usePinnedPostsStore } from '../../stores/use-pinned-posts-store';
import useWindowWidth from '../../hooks/use-window-width';
interface PostAuthorProps {
authorAddress: string;
@@ -122,7 +123,6 @@ const Post = ({ index, post = {} }: PostProps) => {
const isInAllView = isAllView(location.pathname);
const isInPostPageView = isPostPageView(location.pathname, params);
const isInProfileHiddenView = isProfileHiddenView(location.pathname);
const isInHomeView = isHomeView(location.pathname);
const isInSubplebbitView = isSubplebbitView(location.pathname, params);
const commentMediaInfo = useCommentMediaInfo(post);
@@ -161,9 +161,10 @@ const Post = ({ index, post = {} }: PostProps) => {
};
const isMobile = useIsMobile();
const windowWidth = useWindowWidth();
const pinnedPostsCount = usePinnedPostsStore((state) => state.pinnedPostsCount);
let rank = (index ?? 0) + 1;
if (!isInHomeView) {
if (isInSubplebbitView) {
rank = rank - pinnedPostsCount;
}
@@ -233,16 +234,17 @@ const Post = ({ index, post = {} }: PostProps) => {
</span>
)}
</p>
{(!(commentMediaInfo?.type === 'webpage') || (commentMediaInfo?.type === 'webpage' && content?.trim().length > 0)) && (
<ExpandButton
commentMediaInfo={commentMediaInfo}
content={content}
expanded={isExpanded}
hasThumbnail={hasThumbnail}
link={link}
toggleExpanded={toggleExpanded}
/>
)}
{(!(commentMediaInfo?.type === 'webpage') || (commentMediaInfo?.type === 'webpage' && content?.trim().length > 0)) &&
!(isInPostPageView && !link && content?.trim().length > 0) && (
<ExpandButton
commentMediaInfo={commentMediaInfo}
content={content}
expanded={isExpanded}
hasThumbnail={hasThumbnail}
link={link}
toggleExpanded={toggleExpanded}
/>
)}
<div className={styles.tagline}>
{t('submitted')} <span title={postDate}>{getFormattedTimeAgo(timestamp)}</span>{' '}
{edit && isInPostPageView && <span className={styles.timeEdit}>{t('last_edited', { timestamp: getFormattedTimeAgo(edit.timestamp) })}</span>}{' '}
@@ -295,23 +297,49 @@ const Post = ({ index, post = {} }: PostProps) => {
subplebbitAddress={subplebbitAddress}
/>
</div>
{!(windowWidth < 770) && (
<>
{isEditing ? (
<CommentEditForm commentCid={cid} hideCommentEditForm={hideCommentEditForm} />
) : (
<Expando
authorEditReason={edit?.reason}
commentMediaInfo={commentMediaInfo}
content={removed ? `[${_.lowerCase(t('removed'))}]` : deleted ? `[${_.lowerCase(t('deleted'))}]` : content}
expanded={isExpanded}
link={link}
modEditReason={reason}
nsfw={nsfw}
deleted={deleted}
removed={removed}
showContent={true}
spoiler={spoiler && (content || link)}
/>
)}
</>
)}
</div>
</div>
{isEditing ? (
<CommentEditForm commentCid={cid} hideCommentEditForm={hideCommentEditForm} />
) : (
<Expando
authorEditReason={edit?.reason}
commentMediaInfo={commentMediaInfo}
content={removed ? `[${_.lowerCase(t('removed'))}]` : deleted ? `[${_.lowerCase(t('deleted'))}]` : content}
expanded={isExpanded}
link={link}
modEditReason={reason}
deleted={deleted}
removed={removed}
showContent={true}
spoiler={spoiler && (content || link)}
/>
{windowWidth < 770 && (
<>
{isEditing ? (
<CommentEditForm commentCid={cid} hideCommentEditForm={hideCommentEditForm} />
) : (
<Expando
authorEditReason={edit?.reason}
commentMediaInfo={commentMediaInfo}
content={removed ? `[${_.lowerCase(t('removed'))}]` : deleted ? `[${_.lowerCase(t('deleted'))}]` : content}
expanded={isExpanded}
link={link}
modEditReason={reason}
nsfw={nsfw}
deleted={deleted}
removed={removed}
showContent={true}
spoiler={spoiler && (content || link)}
/>
)}
</>
)}
</div>
</div>

View File

@@ -11,6 +11,13 @@
padding-bottom: 15px;
}
@media (max-width: 640px) {
.morePostsSuggestion {
font-size: 11px;
padding-bottom: 10px;
}
}
.morePostsSuggestion a, .link {
color: var(--text-primary);
text-decoration: none;