feat: Added details dialog

This commit is contained in:
florian 2023-07-29 13:44:56 +02:00
parent fbe06a623d
commit 13a61bce9c
17 changed files with 395 additions and 98 deletions

BIN
public/notfound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,28 +1,15 @@
import { useParams, useSearchParams } from 'react-router-dom';
import SlideShow from './components/SlideShow'; import SlideShow from './components/SlideShow';
import './App.css'; import './App.css';
import Disclaimer from './components/Disclaimer'; import Disclaimer from './components/Disclaimer';
import useDisclaimerState from './utils/useDisclaimerState'; import useDisclaimerState from './utils/useDisclaimerState';
import { defaultHashTags } from './components/env';
const App = () => { const App = () => {
const { disclaimerAccepted, setDisclaimerAccepted } = useDisclaimerState(); const { disclaimerAccepted, setDisclaimerAccepted } = useDisclaimerState();
const { tags, npub } = useParams();
const [searchParams] = useSearchParams();
const nsfw = searchParams.get('nsfw') === 'true';
console.log(`tags = ${tags}, npub = ${npub}, nsfw = ${nsfw}`);
let useTags = tags?.split(',') || [];
if (npub == undefined && (useTags == undefined || useTags.length == 0)) {
useTags = defaultHashTags;
}
return ( return (
<> <>
{disclaimerAccepted ? ( {disclaimerAccepted ? (
<SlideShow tags={useTags} npubs={npub ? [npub] : []} showNsfw={nsfw} /> <SlideShow />
) : ( ) : (
<Disclaimer disclaimerAccepted={disclaimerAccepted} setDisclaimerAccepted={setDisclaimerAccepted} /> <Disclaimer disclaimerAccepted={disclaimerAccepted} setDisclaimerAccepted={setDisclaimerAccepted} />
)} )}

View File

@ -1,18 +1,17 @@
import { useNavigate } from 'react-router-dom';
import './Disclaimer.css'; import './Disclaimer.css';
import { MouseEvent } from 'react'; import { MouseEvent } from 'react';
import useNav from '../utils/useNav';
const AdultContentInfo = () => { const AdultContentInfo = () => {
const navigate = useNavigate(); const { nav, currentSettings } = useNav();
const proceed = (e: MouseEvent<HTMLButtonElement>) => { const proceed = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
const nsfwPostfix = '?nsfw=true'; nav({ ...currentSettings, showNsfw: true });
navigate(`${window.location.pathname}${nsfwPostfix}`);
}; };
const goBack = (e: MouseEvent<HTMLButtonElement>) => { const goBack = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
navigate(`/`); nav({ npubs: [], tags: [], showNsfw: false });
}; };
return ( return (

View File

@ -1,29 +1,37 @@
import './SlideShow.css'; import './SlideShow.css';
import useImageLoaded from '../utils/useImageLoaded'; import useImageLoaded from '../utils/useImageLoaded';
import { createImgProxyUrl } from './nostrImageDownload';
import useNav from '../utils/useNav';
type AvatarImageProps = { type AvatarImageProps = {
src?: string; src?: string;
author?: string; author?: string;
npub?: string; npub?: string;
setShowGrid?: (showGrid: boolean) => void;
}; };
const AuthorProfile = ({ src, author, npub }: AvatarImageProps) => { const AuthorProfile = ({ src, author, npub, setShowGrid }: AvatarImageProps) => {
const avatarLoaded = useImageLoaded(src); const avatarLoaded = useImageLoaded(src);
const { nav, currentSettings } = useNav();
return ( return (
<div className="author-info"> <div
<a href={`https://nostrapp.link/#${npub}`} target="_blank"> className="author-info"
<div> onClick={() => {
{avatarLoaded && ( setShowGrid && setShowGrid(true);
<div npub && nav({ ...currentSettings, tags: [], npubs: [npub] });
className="author-image" }}
style={{ >
backgroundImage: `url(${src})`, <div>
}} {avatarLoaded && (
></div> <div
)} className="author-image"
{author} style={{
</div> backgroundImage: src ? `url(${createImgProxyUrl(src, 80, 80)})` : 'none',
</a> }}
></div>
)}
{author}
</div>
</div> </div>
); );
}; };

View File

@ -0,0 +1,37 @@
import useNav from '../../utils/useNav';
import { createImgProxyUrl } from '../nostrImageDownload';
import './DetailsView.css';
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
type DetailsAuthorProps = {
profile?: NDKUserProfile;
npub?: string;
setActiveImageIdx: (idx: number | undefined) => void;
};
const DetailsAuthor = ({ profile, npub, setActiveImageIdx }: DetailsAuthorProps) => {
const { nav, currentSettings } = useNav();
return (
<div
className="details-author"
onClick={() => {
setActiveImageIdx(undefined);
npub && nav({ ...currentSettings, tags: [], npubs: [npub] });
}}
>
<>
<div
className="author-image"
style={{
backgroundImage: profile?.image ? `url(${createImgProxyUrl(profile?.image, 80, 80)})` : 'none',
}}
></div>
{profile?.displayName || profile?.name}
</>
</div>
);
};
export default DetailsAuthor;

View File

@ -0,0 +1,96 @@
.details {
/*
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
justify-content: center;
align-items: center;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
font-size: 1.2rem;
animation: fadeIn 0.5s ease-in-out;
z-index: 500;
padding: 2em;
flex-direction: column;
gap: 24px;
width: 90%;
height: 85%;
*/
position: absolute;
z-index: 500;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
padding: 2em;
box-sizing: border-box;
display: flex;
align-items: center;
width: 100vw;
height: 100vh;
justify-content: center;
}
.details-contents {
display: grid;
grid-template-columns: max(60vw, 40vw) auto;
gap: 24px;
justify-items: center;
}
.details-contents .tag {
display: inline;
padding: 0.2em 0.6em;
margin-right: 0.2em;
border-radius: 24px;
background-color: #444;
color: white;
line-height: 2.2em;
font-size: 1rem;
cursor: pointer;
}
.details-contents .tag:hover {
background-color: #555;
}
.details-contents .detail-image {
object-fit: contain;
max-width: 100%;
max-height: 90vh;
border-radius: 12px;
}
.detail-description {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 30em;
width: 25em;
overflow-y: auto;
overflow-x: hidden;
}
.details-author {
display: flex;
flex-direction: row;
gap: 12px;
align-items: flex-start;
cursor: pointer;
}
@media screen and (max-width: 768px) {
.details {
overflow-y: scroll;
}
.details-contents {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
.detail-description {
max-width: 100%;
width: 100%;
}
}

View File

@ -0,0 +1,57 @@
import { NostrImage } from '../nostrImageDownload';
import './DetailsView.css';
import { useNDK } from '@nostr-dev-kit/ndk-react';
import DetailsAuthor from './Author';
import { useMemo } from 'react';
import uniq from 'lodash/uniq';
import useNav from '../../utils/useNav';
type DetailsViewProps = {
images: NostrImage[];
activeImageIdx: number | undefined;
setActiveImageIdx: (idx: number | undefined) => void;
};
const DetailsView = ({ images, activeImageIdx, setActiveImageIdx }: DetailsViewProps) => {
const { getProfile } = useNDK();
const currentImage = useMemo(
() => (activeImageIdx !== undefined ? images[activeImageIdx] : undefined),
[images, activeImageIdx]
);
const activeProfile = currentImage?.author !== undefined ? getProfile(currentImage?.author) : undefined;
const { nav, currentSettings } = useNav();
return (
<div className="details">
<div className="details-contents">
<img className="detail-image" src={currentImage?.url}></img>
<div className="detail-description">
<DetailsAuthor
profile={activeProfile}
npub={currentImage?.author}
setActiveImageIdx={setActiveImageIdx}
></DetailsAuthor>
<div>{currentImage?.content}</div>
<div>
{uniq(currentImage?.tags).map(t => (
<>
<span
className="tag"
onClick={() => {
setActiveImageIdx(undefined);
nav({ ...currentSettings, tags: [t], npubs: [] });
}}
>
{t}
</span>{' '}
</>
))}
</div>
</div>
</div>
</div>
);
};
export default DetailsView;

View File

@ -0,0 +1,27 @@
import { SyntheticEvent, useState } from 'react';
import { NostrImage, createImgProxyUrl } from '../nostrImageDownload';
interface GridImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
image: NostrImage;
}
const GridImage = ({ image, ...props }: GridImageProps) => {
const [loaded, setLoaded] = useState(false);
return (
<img
data-node-id={image.noteId}
onError={(e: SyntheticEvent<HTMLImageElement>) => {
e.currentTarget.src = '/notfound.png';
}}
className={`image ${loaded ? 'show' : ''}`}
onLoad={() => setLoaded(true)}
loading="lazy"
key={image.url}
src={createImgProxyUrl(image.url)}
{...props}
></img>
);
};
export default GridImage;

View File

@ -1,10 +1,27 @@
@keyframes showImage {
from {
opacity: 0;
visibility: visible;
}
to {
opacity: 1;
}
}
.gridview {
display: flex;
flex-direction: column;
align-items: start;
height: 100vh;
}
.imagegrid { .imagegrid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 1rem; grid-gap: 1rem;
padding: 1rem; padding: 1rem;
width: 100vw; width: 100vw;
height: 100vh;
overflow: scroll; overflow: scroll;
box-sizing: border-box; box-sizing: border-box;
} }
@ -15,6 +32,15 @@
object-fit: cover; object-fit: cover;
height: 200px; height: 200px;
cursor: pointer; cursor: pointer;
visibility: hidden;
}
.imagegrid .image.show {
animation-duration: 0.5s;
animation-timing-function: ease-in;
animation-name: showImage;
visibility: visible;
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {

View File

@ -1,8 +1,9 @@
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import Settings from '../Settings'; import { NostrImage, isVideo } from '../nostrImageDownload';
import { NostrImage, createImgProxyUrl, isVideo } from '../nostrImageDownload';
import './GridView.css'; import './GridView.css';
import Slide from '../SlideView/Slide'; import DetailsView from './DetailsView';
import GridImage from './GridImage';
import { Settings } from '../../utils/useNav';
type GridViewProps = { type GridViewProps = {
settings: Settings; settings: Settings;
@ -10,7 +11,7 @@ type GridViewProps = {
}; };
const GridView = ({ settings, images }: GridViewProps) => { const GridView = ({ settings, images }: GridViewProps) => {
const [activeImage, setActiveImage] = useState<NostrImage | undefined>(); const [activeImageIdx, setActiveImageIdx] = useState<number | undefined>();
const sortedImages = useMemo( const sortedImages = useMemo(
() => () =>
@ -20,20 +21,33 @@ const GridView = ({ settings, images }: GridViewProps) => {
[images] [images]
); );
const onKeyDown = (event: KeyboardEvent) => {
console.log(event);
if (event.key === 'ArrowRight') {
setActiveImageIdx(idx => (idx !== undefined && idx < sortedImages.length - 1 ? idx + 1 : idx));
}
if (event.key === 'ArrowLeft') {
setActiveImageIdx(idx => (idx !== undefined && idx > 0 ? idx - 1 : idx));
}
if (event.key === 'Escape') {
setActiveImageIdx(undefined);
}
};
useEffect(() => {
document.body.addEventListener('keydown', onKeyDown);
return () => {
document.body.removeEventListener('keydown', onKeyDown);
};
}, []);
return ( return (
<> <div className="gridview">
{activeImage && ( {activeImageIdx !== undefined ? (
<Slide <DetailsView images={sortedImages} activeImageIdx={activeImageIdx} setActiveImageIdx={setActiveImageIdx} />
url={activeImage.url} ) : null}
noteId={activeImage.noteId}
type={activeImage.type}
paused={false}
onAnimationEnded={() => setActiveImage(undefined)}
animationDuration={4}
></Slide>
)}
<div className="imagegrid"> <div className="imagegrid">
{sortedImages.map(image => {sortedImages.map((image, idx) =>
isVideo(image.url) ? ( isVideo(image.url) ? (
<video <video
className="image" className="image"
@ -44,18 +58,11 @@ const GridView = ({ settings, images }: GridViewProps) => {
preload="none" preload="none"
/> />
) : ( ) : (
<img <GridImage key={image.url} image={image} onClick={() => setActiveImageIdx(idx)}></GridImage>
onClick={() => setActiveImage(image)}
data-node-id={image.noteId}
className="image"
loading="lazy"
key={image.url}
src={createImgProxyUrl(image.url)}
></img>
) )
)} )}
</div> </div>
</> </div>
); );
}; };

View File

@ -1,38 +1,29 @@
import { FormEvent, useState } from 'react'; import { FormEvent, useState } from 'react';
import './Settings.css'; import './Settings.css';
import { useNavigate } from 'react-router-dom'; import useNav, { Settings } from '../utils/useNav';
type Settings = {
showNsfw: boolean;
tags: string[];
npubs: string[];
};
type SettingsProps = { type SettingsProps = {
onClose: () => void; onClose: () => void;
settings: Settings; settings: Settings;
}; };
const Settings = ({ onClose, settings }: SettingsProps) => { const SettingsDialog = ({ onClose, settings }: SettingsProps) => {
const [showNsfw, setShowNsfw] = useState(settings.showNsfw || false); const [showNsfw, setShowNsfw] = useState(settings.showNsfw || false);
const [tags, setTags] = useState(settings.tags || []); const [tags, setTags] = useState(settings.tags || []);
const [npubs, setNpubs] = useState(settings.npubs || []); const [npubs, setNpubs] = useState(settings.npubs || []);
const { nav, currentSettings } = useNav();
const navigate = useNavigate();
const onSubmit = (e: FormEvent) => { const onSubmit = (e: FormEvent) => {
e.preventDefault(); e.preventDefault();
const nsfwPostfix = showNsfw ? '?nsfw=true' : '';
const validTags = tags.filter(t => t.length > 0); const validTags = tags.filter(t => t.length > 0);
const validNpubs = npubs.filter(t => t.length > 0); const validNpubs = npubs.filter(t => t.length > 0);
if (validTags.length > 0) { if (validTags.length > 0) {
navigate(`/tags/${validTags.join('%2C')}${nsfwPostfix}`); nav({ ...currentSettings, tags: validTags, npubs: [], showNsfw });
} else if (validNpubs.length == 1) { } else if (validNpubs.length == 1) {
navigate(`/p/${validNpubs[0]}${nsfwPostfix}`); nav({ ...currentSettings, tags: [], npubs: validNpubs, showNsfw });
} else { } else {
navigate(`/${nsfwPostfix}`); nav({ ...currentSettings, tags: [], npubs: [], showNsfw });
} }
onClose(); onClose();
}; };
@ -79,4 +70,4 @@ const Settings = ({ onClose, settings }: SettingsProps) => {
); );
}; };
export default Settings; export default SettingsDialog;

View File

@ -57,16 +57,9 @@
} }
} }
.author-info a { .author-info {
color: white;
}
.author-info a:hover {
cursor: pointer; cursor: pointer;
color: white; color: white;
}
.author-info {
position: absolute; position: absolute;
bottom: 1em; bottom: 1em;
left: 1em; left: 1em;
@ -85,6 +78,7 @@
animation-duration: 0.5s; animation-duration: 0.5s;
animation-timing-function: ease-in; animation-timing-function: ease-in;
animation-name: showAuthor; animation-name: showAuthor;
background-color: #444;
} }
.slide { .slide {

View File

@ -11,7 +11,7 @@ import {
isVideo, isVideo,
prepareContent, prepareContent,
} from './nostrImageDownload'; } from './nostrImageDownload';
import { defaultRelays, nfswTags, nsfwNPubs } from './env'; import { blockedPublicKeys, defaultRelays, nfswTags, nsfwNPubs } from './env';
import Settings from './Settings'; import Settings from './Settings';
import SlideView from './SlideView'; import SlideView from './SlideView';
import GridView from './GridView'; import GridView from './GridView';
@ -23,6 +23,7 @@ import IconSettings from './Icons/IconSettings';
import IconPlay from './Icons/IconPlay'; import IconPlay from './Icons/IconPlay';
import IconGrid from './Icons/IconGrid'; import IconGrid from './Icons/IconGrid';
import { NDKEvent } from '@nostr-dev-kit/ndk'; import { NDKEvent } from '@nostr-dev-kit/ndk';
import useNav from '../utils/useNav';
/* /*
FEATURES: FEATURES:
@ -45,13 +46,14 @@ FEATURES:
- Prevent duplicate images (shuffle? histroy?) - Prevent duplicate images (shuffle? histroy?)
*/ */
const SlideShow = (settings: Settings) => { const SlideShow = () => {
const { ndk, loadNdk } = useNDK(); const { ndk, loadNdk } = useNDK();
const [posts, setPosts] = useState<NDKEvent[]>([]); const [posts, setPosts] = useState<NDKEvent[]>([]);
const images = useRef<NostrImage[]>([]); const images = useRef<NostrImage[]>([]);
const fetchTimeoutHandle = useRef(0); const fetchTimeoutHandle = useRef(0);
const [showGrid, setShowGrid] = useState(false); const [showGrid, setShowGrid] = useState(false);
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const { currentSettings: settings } = useNav();
const fetch = () => { const fetch = () => {
const postSubscription = ndk.subscribe(buildFilter(settings.tags, settings.npubs)); const postSubscription = ndk.subscribe(buildFilter(settings.tags, settings.npubs));
@ -59,6 +61,7 @@ const SlideShow = (settings: Settings) => {
postSubscription.on('event', (event: NDKEvent) => { postSubscription.on('event', (event: NDKEvent) => {
setPosts(oldPosts => { setPosts(oldPosts => {
if ( if (
!blockedPublicKeys.includes(event.pubkey.toLowerCase()) && // remove blocked authors
!isReply(event) && !isReply(event) &&
oldPosts.findIndex(p => p.id === event.id) === -1 && // not duplicate oldPosts.findIndex(p => p.id === event.id) === -1 && // not duplicate
(settings.showNsfw || !isNsfwRelated(event)) (settings.showNsfw || !isNsfwRelated(event))
@ -112,9 +115,12 @@ const SlideShow = (settings: Settings) => {
if (event.key === 'g' || event.key === 'G') { if (event.key === 'g' || event.key === 'G') {
setShowGrid(p => !p); setShowGrid(p => !p);
} }
if (event.key === 'Escape') { if (event.key === 's' || event.key === 'S') {
setShowSettings(s => !s); setShowSettings(s => !s);
} }
if (event.key === 'Escape') {
setShowSettings(false);
}
/* /*
if (event.key === "f" || event.key === "F") { if (event.key === "f" || event.key === "F") {
document?.getElementById("root")?.requestFullscreen(); document?.getElementById("root")?.requestFullscreen();
@ -124,9 +130,9 @@ const SlideShow = (settings: Settings) => {
useEffect(() => { useEffect(() => {
loadNdk(defaultRelays); loadNdk(defaultRelays);
window.addEventListener('keydown', onKeyDown); document.body.addEventListener('keydown', onKeyDown);
return () => { return () => {
window.removeEventListener('keydown', onKeyDown); document.body.removeEventListener('keydown', onKeyDown);
}; };
}, []); }, []);
@ -163,7 +169,7 @@ const SlideShow = (settings: Settings) => {
{showGrid ? ( {showGrid ? (
<GridView images={images.current} settings={settings}></GridView> <GridView images={images.current} settings={settings}></GridView>
) : ( ) : (
<SlideView images={images.current} settings={settings}></SlideView> <SlideView images={images.current} settings={settings} setShowGrid={setShowGrid}></SlideView>
)} )}
</> </>
); );

View File

@ -7,16 +7,17 @@ import { useNDK } from '@nostr-dev-kit/ndk-react';
import useDebouncedEffect from '../../utils/useDebouncedEffect'; import useDebouncedEffect from '../../utils/useDebouncedEffect';
import { useSwipeable } from 'react-swipeable'; import { useSwipeable } from 'react-swipeable';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import Settings from '../Settings';
import IconPause from '../Icons/IconPause'; import IconPause from '../Icons/IconPause';
import IconSpinner from '../Icons/IconSpinner'; import IconSpinner from '../Icons/IconSpinner';
import { Settings } from '../../utils/useNav';
type SlideViewProps = { type SlideViewProps = {
settings: Settings; settings: Settings;
images: NostrImage[]; images: NostrImage[];
setShowGrid: (showGrid: boolean) => void;
}; };
const SlideView = ({ settings, images }: SlideViewProps) => { const SlideView = ({ settings, images, setShowGrid }: SlideViewProps) => {
const { getProfile } = useNDK(); const { getProfile } = useNDK();
const [activeImages, setActiveImages] = useState<NostrImage[]>([]); const [activeImages, setActiveImages] = useState<NostrImage[]>([]);
const history = useRef<NostrImage[]>([]); const history = useRef<NostrImage[]>([]);
@ -130,7 +131,7 @@ const SlideView = ({ settings, images }: SlideViewProps) => {
useEffect(() => { useEffect(() => {
document.body.addEventListener('keydown', onKeyDown); document.body.addEventListener('keydown', onKeyDown);
return () => { return () => {
window.removeEventListener('keydown', onKeyDown); document.body.removeEventListener('keydown', onKeyDown);
console.log(`cleaining timeout in useEffect[] destructor `); console.log(`cleaining timeout in useEffect[] destructor `);
clearTimeout(viewTimeoutHandle.current); clearTimeout(viewTimeoutHandle.current);
}; };
@ -189,6 +190,7 @@ const SlideView = ({ settings, images }: SlideViewProps) => {
)} )}
{activeProfile && ( {activeProfile && (
<AuthorProfile <AuthorProfile
setShowGrid={setShowGrid}
src={urlFix(activeProfile.image || '')} src={urlFix(activeProfile.image || '')}
author={activeProfile.displayName || activeProfile.name} author={activeProfile.displayName || activeProfile.name}
npub={activeNpub} npub={activeNpub}

View File

@ -45,6 +45,7 @@ export const nfswTags = [
'thighstr', 'thighstr',
'tits', 'tits',
'titstr', 'titstr',
'lolita',
]; ];
export const nsfwNPubs = [ export const nsfwNPubs = [
@ -78,13 +79,21 @@ export const nsfwNPubs = [
'npub1ulafm4d3n7ukl7yzg4hfnhfjut74nym5p83e3d67l3j62yc6ysqqrancw2', // naked 'npub1ulafm4d3n7ukl7yzg4hfnhfjut74nym5p83e3d67l3j62yc6ysqqrancw2', // naked
'npub1ve4ztpqvlgu3v6hgrvc4lrdl2ernue7lq2h8tcgaksrkxlm7gnsqkjmz4e', // bluntkaraoke 'npub1ve4ztpqvlgu3v6hgrvc4lrdl2ernue7lq2h8tcgaksrkxlm7gnsqkjmz4e', // bluntkaraoke
'npub1wmsn8fch7kwt987jcdx06uuapn6pwzau59pvy0ql5d3xlmnxa2csj3c5p4', // StefsPicks 'npub1wmsn8fch7kwt987jcdx06uuapn6pwzau59pvy0ql5d3xlmnxa2csj3c5p4', // StefsPicks
'npub1xfu7047thly6aghl79z97kckkvwfvtcx88n6wq7c2tlng484d8xqv0kuvv', // Erandis Vol
'npub1y77j6jm5hw34xl5m85aumltv88arh2s7q383allkpfe4muarzc5qzfgru0', // sexy-models 'npub1y77j6jm5hw34xl5m85aumltv88arh2s7q383allkpfe4muarzc5qzfgru0', // sexy-models
'npub1ylrnf0xfp9wsmqthxlqjqyqj9yy27pnchjwjq93v3mq66ts7ftjs6x7dcq', // Welcome To The Jungle 'npub1ylrnf0xfp9wsmqthxlqjqyqj9yy27pnchjwjq93v3mq66ts7ftjs6x7dcq', // Welcome To The Jungle
'npub1csk2wg33ee9kutyps4nmevyv3putfegj7yd0emsp44ph32wvmamqs7uyan', // Lilura 'npub1csk2wg33ee9kutyps4nmevyv3putfegj7yd0emsp44ph32wvmamqs7uyan', // Lilura
'npub10m75ad8pc6wtlt67f6wjeug4hpqurc68842ve5ne47u9lkjqa0lq8ja88s', // 313Chris:hellokitty_headbang:
]; ];
export const nsfwPubKeys = nsfwNPubs.map(npub => (nip19.decode(npub).data as string).toLowerCase()); export const nsfwPublicKeys = nsfwNPubs.map(npub => (nip19.decode(npub).data as string).toLowerCase());
export const blockedNPubs = [
'npub1awxh85c5wasj60d42uvmzuza2uvjazff9m7skg2vf7x2f8gykwkqykxktf', // AIイラスト',
'npub1xfu7047thly6aghl79z97kckkvwfvtcx88n6wq7c2tlng484d8xqv0kuvv', // Erandis Vol
'npub1kf8sau5dejmcmfmzzj256rv728p5w7s0wytdyz8ypa0ne0y6k0vswhgu9w', // noname
];
export const blockedPublicKeys = blockedNPubs.map(npub => (nip19.decode(npub).data as string).toLowerCase());
export const spamAccounts = []; export const spamAccounts = [];
@ -97,5 +106,6 @@ export const defaultRelays = [
'wss://nostr.wine', 'wss://nostr.wine',
// "wss://nostr1.current.fyi/", // "wss://nostr1.current.fyi/",
'wss://purplepag.es/', // needed for user profiles 'wss://purplepag.es/', // needed for user profiles
'wss://n-word.sharivegas.com/', // needed for mostr.pub profiles
//"wss://feeds.nostr.band/pics", //"wss://feeds.nostr.band/pics",
]; ];

View File

@ -1,6 +1,6 @@
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { nfswTags, nsfwPubKeys } from './env'; import { nfswTags, nsfwPublicKeys } from './env';
export type NostrImage = { export type NostrImage = {
url: string; url: string;
@ -67,7 +67,7 @@ export const isNsfwRelated = (event: NDKEvent) => {
return ( return (
hasContentWarning(event) || // block content warning hasContentWarning(event) || // block content warning
hasNsfwTag(event) || // block nsfw tags hasNsfwTag(event) || // block nsfw tags
nsfwPubKeys.includes(event.pubkey.toLowerCase()) // block nsfw authors nsfwPublicKeys.includes(event.pubkey.toLowerCase()) // block nsfw authors
); );
}; };

50
src/utils/useNav.ts Normal file
View File

@ -0,0 +1,50 @@
import { useMemo } from 'react';
import { defaultHashTags } from '../components/env';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
export type Settings = {
showNsfw: boolean;
tags: string[];
npubs: string[];
};
const useNav = () => {
const { tags, npub } = useParams();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const currentSettings: Settings = useMemo(() => {
const nsfw = searchParams.get('nsfw') === 'true';
console.log(`tags = ${tags}, npub = ${npub}, nsfw = ${nsfw}`);
let useTags = tags?.split(',') || [];
if (npub == undefined && (useTags == undefined || useTags.length == 0)) {
useTags = defaultHashTags;
}
return {
tags: useTags,
npubs: npub ? [npub] : [],
showNsfw: nsfw,
};
}, [tags, npub, searchParams]);
const nav = (settings: Settings) => {
const nsfwPostfix = settings.showNsfw ? '?nsfw=true' : '';
const validTags = settings.tags.filter(t => t.length > 0);
const validNpubs = settings.npubs.filter(t => t.length > 0);
if (validTags.length > 0) {
navigate(`/tags/${validTags.join('%2C')}${nsfwPostfix}`);
} else if (validNpubs.length == 1) {
navigate(`/p/${validNpubs[0]}${nsfwPostfix}`);
} else {
navigate(`/${nsfwPostfix}`);
}
};
return { nav, currentSettings };
};
export default useNav;