feat: Moved global and nsfw section to home page
This commit is contained in:
parent
f7b3dad4ac
commit
252612e7df
20
package.json
20
package.json
@ -12,36 +12,36 @@
|
||||
"analyze": "vite-bundle-visualizer"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nostr-dev-kit/ndk": "^2.4.2",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.2.6",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@nostr-dev-kit/ndk": "^2.5.0",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "^2.2.7",
|
||||
"@tanstack/react-query": "^5.24.8",
|
||||
"bech32": "^2.0.0",
|
||||
"jotai": "^2.6.5",
|
||||
"jotai": "^2.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^2.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lazy-load": "^4.0.1",
|
||||
"react-router-dom": "^6.22.1",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react-swipeable": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/react": "^18.2.57",
|
||||
"@types/react": "^18.2.63",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-swipeable": "^5.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@webbtc/webln-types": "^3.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.4",
|
||||
"vite": "^5.1.5",
|
||||
"vite-bundle-visualizer": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ h1 {
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
|
@ -30,7 +30,6 @@ const GridView = ({ settings, images, currentImage, setCurrentImage, setViewMode
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
console.log(event);
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
showNextImage();
|
||||
|
@ -4,9 +4,11 @@ import './Home.css';
|
||||
import { useGlobalState } from '../../utils/globalState';
|
||||
import usePeopleLists from '../../utils/useLists';
|
||||
import { createImgProxyUrl } from '../nostrImageDownload';
|
||||
import { useState } from 'react';
|
||||
|
||||
const Home = () => {
|
||||
const { nav, currentSettings } = useNav();
|
||||
const [showAdult, setShowAdult] = useState(currentSettings.showAdult || false);
|
||||
const [state] = useGlobalState();
|
||||
const topicKeys = Object.keys(topics);
|
||||
const lists = usePeopleLists(state.userNPub);
|
||||
@ -18,17 +20,34 @@ const Home = () => {
|
||||
<div className="topics">
|
||||
{topicKeys.map(tk => (
|
||||
<div
|
||||
key={tk}
|
||||
className="topic"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(170deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0) 100%), url(${createImgProxyUrl('https://slidestr.net/images/'+tk+'.jpg', 600, -1)})`,
|
||||
backgroundImage: `linear-gradient(170deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0) 100%), url(${createImgProxyUrl('https://slidestr.net/images/' + tk + '.jpg', 600, -1)})`,
|
||||
}}
|
||||
onClick={() =>
|
||||
nav({ ...currentSettings, topic: tk, npubs: [], tags: [], list: undefined, follows: false })
|
||||
nav({ ...currentSettings, topic: tk, npubs: [], tags: [], list: undefined, follows: false, showAdult })
|
||||
}
|
||||
>
|
||||
<div className="topic-title">{topics[tk].name || tk}</div>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className="topic"
|
||||
onClick={() =>
|
||||
nav({
|
||||
tags: [],
|
||||
npubs: [],
|
||||
showReplies: false,
|
||||
showReposts: false,
|
||||
follows: false,
|
||||
showAdult,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="topic-title">Global</div>
|
||||
<div>All content posted on nostr. Use with care!</div>
|
||||
</div>
|
||||
</div>
|
||||
{state.userNPub && (
|
||||
<>
|
||||
@ -47,6 +66,7 @@ const Home = () => {
|
||||
follows: true,
|
||||
showReplies: false,
|
||||
showReposts: true,
|
||||
showAdult,
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -60,18 +80,37 @@ const Home = () => {
|
||||
<div className="topics">
|
||||
{lists.map(l => (
|
||||
<div
|
||||
key={l.nevent}
|
||||
className="topic"
|
||||
style={{}}
|
||||
onClick={() =>
|
||||
nav({ ...currentSettings, tags: [], npubs: [], list: l.nevent, topic: undefined, follows: false })
|
||||
nav({
|
||||
...currentSettings,
|
||||
tags: [],
|
||||
npubs: [],
|
||||
list: l.nevent,
|
||||
topic: undefined,
|
||||
follows: false,
|
||||
showAdult,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="topic-title">{l.name}</div>
|
||||
{l.description && <div>{l.description}</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={`content-warning ${showAdult ? 'active' : ''}`} style={{ marginTop: '5em' }}>
|
||||
<div>
|
||||
<input name="adult" type="checkbox" checked={showAdult} onChange={() => setShowAdult(sa => !sa)} />
|
||||
</div>
|
||||
<label htmlFor="adult" onClick={() => setShowAdult(sa => !sa)} style={{ userSelect: 'none' }}>
|
||||
<div className="warning">NSFW / adult content</div>
|
||||
Allow adult content to be shown and ignore content warnings.
|
||||
</label>
|
||||
</div>
|
||||
<div className="footer">
|
||||
Made with 💜 by{' '}
|
||||
<a href="https://njump.me/npub1klr0dy2ul2dx9llk58czvpx73rprcmrvd5dc7ck8esg8f8es06qs427gxc" target="blank">
|
||||
|
@ -55,16 +55,17 @@
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
.login-dialog .login-address button {
|
||||
|
||||
.login-dialog button,
|
||||
.login-dialog button:visited,
|
||||
.login-dialog button:active {
|
||||
background-color: rgb(99, 19, 173);
|
||||
}
|
||||
.login-dialog .login-extension {
|
||||
align-self: center;
|
||||
color: #fff;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.login-dialog .login-extension button {
|
||||
height: 3em;
|
||||
background-color: rgb(99, 19, 173);
|
||||
.login-dialog .login-extension {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.login-dialog .close-button {
|
||||
|
@ -82,7 +82,7 @@ const MasonryImage = ({ image, onClick, index }: MasonryImageProps) => {
|
||||
)}
|
||||
</a>
|
||||
{(showAuthor || description || showTags.length > 0) && (
|
||||
<div style={{ display: 'block', lineHeight: '1.4em', paddingBottom: '.5em', paddingTop: '.5em', position: 'relative' }}>
|
||||
<div className="info-section">
|
||||
<div className="time">{image.timestamp && timeDifference(now, image.timestamp)}</div>
|
||||
{showAuthor && (
|
||||
<div style={{ paddingBottom: '.25em' }}>
|
||||
|
@ -64,6 +64,14 @@
|
||||
outline: 1px solid #fff;
|
||||
}
|
||||
|
||||
.mason-imagegrid .info-section {
|
||||
display: block;
|
||||
line-height: 1.4em;
|
||||
padding-bottom: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
padding: 1.2em;
|
||||
padding-bottom: 0.6em;
|
||||
@ -95,9 +103,9 @@
|
||||
.mason-imagegrid .time {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top:0px;
|
||||
top: 0px;
|
||||
color: #666;
|
||||
padding-top: .5em;
|
||||
padding-top: 0.5em;
|
||||
background-color: #111;
|
||||
padding-left: .4em;
|
||||
padding-left: 0.4em;
|
||||
}
|
@ -81,15 +81,25 @@
|
||||
.content-warning {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #ff563f;
|
||||
border: 1px solid #444;
|
||||
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
.content-warning.active {
|
||||
border: 1px solid #ff563f;
|
||||
}
|
||||
|
||||
.content-warning.active .warning {
|
||||
color: #ff563f;
|
||||
}
|
||||
|
||||
.content-warning .warning {
|
||||
color: #444;
|
||||
font-weight: 500;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings .replies,
|
||||
|
@ -182,7 +182,7 @@ const SettingsDialog = ({ onClose, setViewMode }: SettingsProps) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="content-warning">
|
||||
<div className={`content-warning ${showAdult ? 'active' : ''}`}>
|
||||
<div>
|
||||
<input name="adult" type="checkbox" checked={showAdult} onChange={e => setShowAdult(e.target.checked)} />
|
||||
</div>
|
||||
|
@ -91,6 +91,7 @@ const SlideShow = () => {
|
||||
const listAuthors = useAuthorsFromList(settings.list);
|
||||
const [contacts] = useAtom(followsAtom);
|
||||
|
||||
const filter = useMemo(() => {
|
||||
const authorsToQuery = settings.follows
|
||||
? contacts?.tags.filter(t => t[0] === 'p').map(t => t[1]) || []
|
||||
: listAuthors && listAuthors.length > 0
|
||||
@ -99,11 +100,25 @@ const SlideShow = () => {
|
||||
|
||||
const filterTags = settings.topic ? topics[settings.topic].tags : settings.tags;
|
||||
|
||||
const { events } = useEvents(buildFilter(filterTags, authorsToQuery, settings.showReposts), {
|
||||
return buildFilter(filterTags, authorsToQuery, settings.showReposts);
|
||||
}, [
|
||||
contacts?.tags,
|
||||
listAuthors,
|
||||
settings.follows,
|
||||
settings.npubs,
|
||||
settings.showReposts,
|
||||
settings.tags,
|
||||
settings.topic,
|
||||
]);
|
||||
|
||||
const { events } = useEvents(filter, {
|
||||
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
||||
// when seeing global, close stream because of too many updates.
|
||||
closeOnEose: settings.npubs.length == 0 && settings.tags.length == 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('set posts');
|
||||
setPosts(
|
||||
events
|
||||
.filter(
|
||||
|
@ -47,7 +47,19 @@ export const topics: { [key: string]: Topic } = {
|
||||
},
|
||||
lifestyle: {
|
||||
name: 'Lifestyle',
|
||||
tags: ['fashion', 'flowerstr', 'foodstr', 'style', 'weedstr', 'travel', 'travelstr', 'happy', 'life', 'love'],
|
||||
tags: [
|
||||
'fashion',
|
||||
'flowerstr',
|
||||
'foodstr',
|
||||
'style',
|
||||
'weedstr',
|
||||
'travel',
|
||||
'travelstr',
|
||||
'happy',
|
||||
'life',
|
||||
'love',
|
||||
'hiking',
|
||||
],
|
||||
},
|
||||
gardenandfarm: { name: 'Gardening und Farming', tags: ['gardening', 'gardenstr', 'nature', 'farming', 'farmstr'] },
|
||||
};
|
||||
@ -132,6 +144,7 @@ export const adultContentTags = [
|
||||
'nakedart',
|
||||
'nasstr',
|
||||
'nodestr',
|
||||
'naughty',
|
||||
'nsfw',
|
||||
'nude',
|
||||
'nudeart',
|
||||
@ -240,6 +253,7 @@ export const adultNPubs = [
|
||||
'npub1ylrnf0xfp9wsmqthxlqjqyqj9yy27pnchjwjq93v3mq66ts7ftjs6x7dcq', // Welcome To The Jungle
|
||||
'npub1z0xv9t5w6evrcg860kmgqq5tfj55mz84ta40uszjnfp9uhw2clkq63yrak', // ???
|
||||
'npub1f3n7hq0a6vyfsjrv9vfdwtasa0g98ve96he68rxsvq9x6cl8tvxqmv6ca4', // Lady Sex (nude anime)
|
||||
'npub1ylq5s3xsdmzgzvgzll6ghcs3qa8a9ajl955hj4tcpmyruvjsl8nq5wqhd8', // Dnera
|
||||
|
||||
'npub1t07mr7m65lg3ecr5eapu6qe4ayt2wgjpqjs8x58m5kx2r2cutsyqyzzzs9', // NOT NSFW but spammy ai pictures
|
||||
'npub1curnt7jtq8mhl9fcswnwvuvc9ccm6lvsdv4kzydx75v92kldrvdqh7sq09', // NOT NSFW but spammy ai pictures
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NDKEvent, NDKFilter, NDKTag } from '@nostr-dev-kit/ndk';
|
||||
import { adultContentTags, adultPublicKeys, mixedAdultNPubs } from './env';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { unixNow } from '../ngine/time';
|
||||
|
||||
export type Post = {
|
||||
event: NDKEvent;
|
||||
@ -23,7 +24,7 @@ export type NostrImage = {
|
||||
export const buildFilter = (tags: string[], authors: string[], withReposts = false) => {
|
||||
const filter: NDKFilter = {
|
||||
kinds: [1, 1063] as number[],
|
||||
limit: authors.length > 0 ? 1000 : 500,
|
||||
limit: authors.length > 0 ? 1000 : tags.length > 0 ? 500 : 500,
|
||||
};
|
||||
|
||||
if (withReposts) {
|
||||
@ -32,10 +33,10 @@ export const buildFilter = (tags: string[], authors: string[], withReposts = fal
|
||||
|
||||
if (authors && authors.length > 0) {
|
||||
filter.authors = authors;
|
||||
} else {
|
||||
if (tags && tags.length > 0) {
|
||||
} else if (tags && tags.length > 0) {
|
||||
filter['#t'] = tags;
|
||||
}
|
||||
} else {
|
||||
filter.since = unixNow() - 60 * 60 * 24; // 24h
|
||||
}
|
||||
|
||||
// console.log('filter', filter);
|
||||
@ -67,6 +68,8 @@ export const urlFix = (url: string) => {
|
||||
};
|
||||
|
||||
export const extractImageUrls = (text: string): string[] => {
|
||||
if (text == undefined) return [];
|
||||
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
const matchedUrls = (text.match(urlRegex) || []).map(u => urlFix(u));
|
||||
return uniq(matchedUrls);
|
||||
|
@ -15,16 +15,18 @@ export default function useEvents(filter: NDKFilter | NDKFilter[], opts?: Subscr
|
||||
const [eose, setEose] = useState(false);
|
||||
const [events, setEvents] = useState<NDKEvent[]>([]);
|
||||
const id = useMemo(() => {
|
||||
console.warn('new ID!!!');
|
||||
return hashSha256(filter);
|
||||
}, [filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (filter && !opts?.disable) {
|
||||
console.log('useEvents: new Subscription');
|
||||
// console.log('useEvents: new Subscription', filter, opts);
|
||||
setEvents([]);
|
||||
const relaySet = relays?.length ?? 0 > 0 ? NDKRelaySet.fromRelayUrls(relays as string[], ndk) : undefined;
|
||||
const sub = ndk.subscribe(filter, opts, relaySet);
|
||||
sub.on('event', (ev: NDKEvent) => {
|
||||
// console.log('new event ');
|
||||
setEvents(evs => {
|
||||
const newEvents = evs.concat([ev]).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
||||
return uniqBy(newEvents, (e: NDKEvent) => e.tagId());
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
import useEvent from '../ngine/hooks/useEvent';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
const useAuthorsFromList = (listAddr?: string) => {
|
||||
const useAuthorsFromList = (listAddr?: string): string[] => {
|
||||
const validListAttr = listAddr?.indexOf('naddr') == 0;
|
||||
const addr = listAddr ? (nip19.decode(listAddr).data as nip19.AddressPointer) : undefined;
|
||||
const addrIsDefined = addr && addr.pubkey && addr.identifier;
|
||||
@ -13,13 +14,17 @@ const useAuthorsFromList = (listAddr?: string) => {
|
||||
{ kinds: [30000], authors: authorFilter, '#d': identFilter },
|
||||
{ disable: !validListAttr && !addrIsDefined }
|
||||
);
|
||||
const authors: string[] =
|
||||
|
||||
const authors: string[] = useMemo(() => {
|
||||
return (
|
||||
(validListAttr &&
|
||||
listEvent
|
||||
?.getMatchingTags('p')
|
||||
.map(t => t[1])
|
||||
.flat()) ||
|
||||
[];
|
||||
[]
|
||||
);
|
||||
}, [listEvent, validListAttr]);
|
||||
|
||||
return authors;
|
||||
};
|
||||
|
@ -16,10 +16,14 @@ const usePeopleLists = (npub?: string) => {
|
||||
|
||||
return eventsWithName.map(e => {
|
||||
const nameTag = e.getMatchingTags('d').slice(0, 1).flat();
|
||||
const name = nameTag ? nameTag[1] : 'unknown';
|
||||
const titleTag = e.getMatchingTags('title').slice(0, 1).flat();
|
||||
const descriptionTag = e.getMatchingTags('description').slice(0, 1).flat();
|
||||
|
||||
const name = titleTag.length>0 ? titleTag[1] : nameTag ? nameTag[1] : 'unknown';
|
||||
const description = descriptionTag.length > 0 && descriptionTag[1];
|
||||
const people = e.tags.filter(t => t[0] === 'p')?.map(t => t[1]);
|
||||
|
||||
return { id: e.id, nevent: e.encode(), name, people };
|
||||
return { id: e.id, nevent: e.encode(), name, people, description };
|
||||
});
|
||||
}, [events]);
|
||||
|
||||
|
@ -52,6 +52,7 @@ const useNav = () => {
|
||||
}
|
||||
|
||||
const postfix = searchParams.length > 0 ? `?${searchParams.join('&')}` : '';
|
||||
console.log(settings);
|
||||
if (settings.topic) {
|
||||
navigate(`/topic/${settings.topic}${postfix}`);
|
||||
} else if (settings.follows) {
|
||||
@ -63,7 +64,7 @@ const useNav = () => {
|
||||
} else if (validNpubs.length == 1) {
|
||||
navigate(`/p/${validNpubs[0]}${postfix}`);
|
||||
} else {
|
||||
navigate(`/${postfix}`);
|
||||
navigate(`/global${postfix}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user