feat: trending notes

This commit is contained in:
Kieran 2023-05-10 12:32:09 +01:00
parent 1143cdfc88
commit 2ed38d1b97
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 154 additions and 55 deletions

View File

@ -0,0 +1,34 @@
import { useEffect, useState } from "react";
import { RawEvent, TaggedRawEvent } from "@snort/nostr";
import { FormattedMessage } from "react-intl";
import PageSpinner from "Element/PageSpinner";
import Note from "Element/Note";
import NostrBandApi from "NostrBand";
export default function TrendingNotes() {
const [posts, setPosts] = useState<Array<RawEvent>>();
async function loadTrendingNotes() {
const api = new NostrBandApi();
const trending = await api.trendingNotes();
setPosts(trending.notes.map(a => a.event));
}
useEffect(() => {
loadTrendingNotes().catch(console.error);
}, []);
if (!posts) return <PageSpinner />;
return (
<>
<h3>
<FormattedMessage defaultMessage="Trending Notes" />
</h3>
{posts.map(e => (
<Note key={e.id} data={e as TaggedRawEvent} related={[]} depth={0} />
))}
</>
);
}

View File

@ -4,37 +4,20 @@ import { FormattedMessage } from "react-intl";
import FollowListBase from "Element/FollowListBase";
import PageSpinner from "Element/PageSpinner";
import NostrBandApi from "NostrBand";
interface TrendingUser {
pubkey: HexKey;
}
interface TrendingUserResponse {
profiles: Array<TrendingUser>;
}
async function fetchTrendingUsers() {
try {
const res = await fetch(`https://api.nostr.band/v0/trending/profiles`);
if (res.ok) {
const data = (await res.json()) as TrendingUserResponse;
return data.profiles.map(a => a.pubkey);
}
} catch (e) {
console.warn(`Failed to load link preview`);
}
}
const TrendingUsers = () => {
export default function TrendingUsers() {
const [userList, setUserList] = useState<HexKey[]>();
async function loadTrendingUsers() {
const api = new NostrBandApi();
const users = await api.trendingProfiles();
const keys = users.profiles.map(a => a.pubkey);
setUserList(keys);
}
useEffect(() => {
(async () => {
const data = await fetchTrendingUsers();
if (data) {
setUserList(data);
}
})();
loadTrendingUsers().catch(console.error);
}, []);
if (!userList) return <PageSpinner />;
@ -42,11 +25,9 @@ const TrendingUsers = () => {
return (
<>
<h3>
<FormattedMessage defaultMessage="Trending Users" />
<FormattedMessage defaultMessage="Trending People" />
</h3>
<FollowListBase pubkeys={userList} showAbout={true} />
</>
);
};
export default TrendingUsers;
}

View File

@ -0,0 +1,52 @@
import { RawEvent } from "@snort/nostr";
export interface TrendingUser {
pubkey: string;
}
export interface TrendingUserResponse {
profiles: Array<TrendingUser>;
}
export interface TrendingNote {
event: RawEvent;
author: RawEvent; // kind0 event
}
export interface TrendingNoteResponse {
notes: Array<TrendingNote>;
}
export class NostrBandError extends Error {
body: string;
statusCode: number;
constructor(message: string, body: string, status: number) {
super(message);
this.body = body;
this.statusCode = status;
}
}
export default class NostrBandApi {
#url = "https://api.nostr.band";
async trendingProfiles() {
return await this.#json<TrendingUserResponse>("GET", "/v0/trending/profiles");
}
async trendingNotes() {
return await this.#json<TrendingNoteResponse>("GET", "/v0/trending/notes");
}
async #json<T>(method: string, path: string) {
const res = await fetch(`${this.#url}${path}`, {
method: method ?? "GET",
});
if (res.ok) {
return (await res.json()) as T;
} else {
throw new NostrBandError("Failed to load content from nostr.band", await res.text(), res.status);
}
}
}

View File

@ -8,11 +8,10 @@ import { router } from "index";
import { SearchRelays } from "Const";
import { System } from "System";
import TrendingUsers from "Element/TrendingUsers";
import useHorizontalScroll from "Hooks/useHorizontalScroll";
import messages from "./messages";
import TrendingNotes from "Element/TrendingPosts";
const POSTS = 0;
const NOTES = 0;
const PROFILES = 1;
const SearchPage = () => {
@ -23,8 +22,8 @@ const SearchPage = () => {
const [sortPopular, setSortPopular] = useState<boolean>(true);
// tabs
const SearchTab = {
Posts: { text: formatMessage(messages.Posts), value: POSTS },
Profiles: { text: formatMessage(messages.People), value: PROFILES },
Posts: { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES },
Profiles: { text: formatMessage({ defaultMessage: "People" }), value: PROFILES },
};
const [tab, setTab] = useState<Tab>(SearchTab.Posts);
@ -57,7 +56,16 @@ const SearchPage = () => {
}, []);
function tabContent() {
if (!keyword) return null;
if (!keyword) {
switch (tab.value) {
case PROFILES:
return <TrendingUsers />;
case NOTES:
return <TrendingNotes />;
}
return null;
}
const pf = tab.value == PROFILES;
return (
<>
@ -72,6 +80,7 @@ const SearchPage = () => {
postsOnly={false}
noSort={pf && sortPopular}
method={"LIMIT_UNTIL"}
loadMore={false}
/>
</>
);
@ -102,20 +111,19 @@ const SearchPage = () => {
return (
<div className="main-content">
<h2>
<FormattedMessage {...messages.Search} />
<FormattedMessage defaultMessage="Search" />
</h2>
<div className="flex mb10">
<input
type="text"
className="f-grow mr10"
placeholder={formatMessage(messages.SearchPlaceholder)}
placeholder={formatMessage({ defaultMessage: "Search..." })}
value={search}
onChange={e => setSearch(e.target.value)}
autoFocus={true}
/>
</div>
<div className="tabs">{[SearchTab.Posts, SearchTab.Profiles].map(renderTab)}</div>
{!keyword && <TrendingUsers />}
{tabContent()}
</div>
);

View File

@ -22,8 +22,6 @@ export default defineMessages({
Sats: { defaultMessage: "{n} {n, plural, =1 {sat} other {sats}}" },
Following: { defaultMessage: "Following {n}" },
Settings: { defaultMessage: "Settings" },
Search: { defaultMessage: "Search" },
SearchPlaceholder: { defaultMessage: "Search..." },
Messages: { defaultMessage: "Messages" },
MarkAllRead: { defaultMessage: "Mark All Read" },
GetVerified: { defaultMessage: "Get Verified" },
@ -47,5 +45,4 @@ export default defineMessages({
Bookmarks: { defaultMessage: "Bookmarks" },
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex" },
People: { defaultMessage: "People" },
});

View File

@ -244,6 +244,9 @@
"B6+XJy": {
"defaultMessage": "zapped"
},
"B6H7eJ": {
"defaultMessage": "nsec, npub, nip-05, hex"
},
"BGCM48": {
"defaultMessage": "Write access to Snort relay, with 1 year of event retention"
},
@ -268,6 +271,9 @@
"CHTbO3": {
"defaultMessage": "Failed to load invoice"
},
"CVWeJ6": {
"defaultMessage": "Trending People"
},
"CmZ9ls": {
"defaultMessage": "{n} Muted"
},
@ -337,9 +343,6 @@
"FS3b54": {
"defaultMessage": "Done!"
},
"FSYL8G": {
"defaultMessage": "Trending Users"
},
"FdhSU2": {
"defaultMessage": "Claim Now"
},
@ -413,6 +416,9 @@
"Iwm6o2": {
"defaultMessage": "NIP-05 Shop"
},
"Ix8l+B": {
"defaultMessage": "Trending Notes"
},
"J+dIsA": {
"defaultMessage": "Subscriptions"
},
@ -422,9 +428,6 @@
"JHEHCk": {
"defaultMessage": "Zaps ({n})"
},
"JTgbT0": {
"defaultMessage": "Find Twitter follows"
},
"JXtsQW": {
"defaultMessage": "Fast Zap Donation"
},
@ -516,6 +519,9 @@
"NndBJE": {
"defaultMessage": "New users page"
},
"O9GTIc": {
"defaultMessage": "Profile picture"
},
"OEW7yJ": {
"defaultMessage": "Zaps"
},
@ -589,12 +595,20 @@
"RhDAoS": {
"defaultMessage": "Are you sure you want to delete {id}"
},
"RjpoYG": {
"defaultMessage": "Recent",
"description": "Sort order name"
},
"RoOyAh": {
"defaultMessage": "Relays"
},
"Rs4kCE": {
"defaultMessage": "Bookmark"
},
"RwFaYs": {
"defaultMessage": "Sort",
"description": "Label for sorting options for people search"
},
"SOqbe9": {
"defaultMessage": "Update Lightning Address"
},
@ -618,6 +632,9 @@
"defaultMessage": "Hex Salt..",
"description": "Hexidecimal 'salt' input for imgproxy"
},
"Tpy00S": {
"defaultMessage": "People"
},
"TwyMau": {
"defaultMessage": "Account"
},
@ -627,9 +644,6 @@
"ULotH9": {
"defaultMessage": "Amount: {amount} sats"
},
"UQ3pOC": {
"defaultMessage": "On Nostr, many people have the same username. User names and identity are separate things. You can get a unique identifier in the next step."
},
"UUPFlt": {
"defaultMessage": "Users must accept the content warning to show the content of your note."
},
@ -713,6 +727,9 @@
"ZUZedV": {
"defaultMessage": "Lightning Donation:"
},
"Zr5TMx": {
"defaultMessage": "Setup profile"
},
"a5UPxh": {
"defaultMessage": "Fund developers and platforms providing NIP-05 verification services"
},
@ -963,6 +980,10 @@
"mKhgP9": {
"defaultMessage": "{n,plural,=0{} =1{zapped} other{zapped}}"
},
"mTJFgF": {
"defaultMessage": "Popular",
"description": "Sort order name"
},
"mfe8RW": {
"defaultMessage": "Option: {n}"
},

View File

@ -79,6 +79,7 @@
"AyGauy": "Login",
"B4C47Y": "name too short",
"B6+XJy": "zapped",
"B6H7eJ": "nsec, npub, nip-05, hex",
"BGCM48": "Write access to Snort relay, with 1 year of event retention",
"BOUMjw": "No nostr users found for {twitterUsername}",
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
@ -87,6 +88,7 @@
"C5xzTC": "Premium",
"C81/uG": "Logout",
"CHTbO3": "Failed to load invoice",
"CVWeJ6": "Trending People",
"CmZ9ls": "{n} Muted",
"Cu/K85": "Translated from {lang}",
"D+KzKd": "Automatically zap every note when loaded",
@ -110,7 +112,6 @@
"FDguSC": "{n} Zaps",
"FP+D3H": "LNURL to forward zaps to",
"FS3b54": "Done!",
"FSYL8G": "Trending Users",
"FdhSU2": "Claim Now",
"FfYsOb": "An error has occured!",
"FmXUJg": "follows you",
@ -135,10 +136,10 @@
"INSqIz": "Twitter username...",
"IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.",
"Iwm6o2": "NIP-05 Shop",
"Ix8l+B": "Trending Notes",
"J+dIsA": "Subscriptions",
"JCIgkj": "Username",
"JHEHCk": "Zaps ({n})",
"JTgbT0": "Find Twitter follows",
"JXtsQW": "Fast Zap Donation",
"JkLHGw": "Website",
"JymXbw": "Private Key",
@ -169,6 +170,7 @@
"NepkXH": "Can't vote with {amount} sats, please set a different default zap amount",
"NfNk2V": "Your private key",
"NndBJE": "New users page",
"O9GTIc": "Profile picture",
"OEW7yJ": "Zaps",
"OKhRC6": "Share",
"OLEm6z": "Unknown login error",
@ -193,8 +195,10 @@
"RahCRH": "Expired",
"RfhLwC": "By: {author}",
"RhDAoS": "Are you sure you want to delete {id}",
"RjpoYG": "Recent",
"RoOyAh": "Relays",
"Rs4kCE": "Bookmark",
"RwFaYs": "Sort",
"SOqbe9": "Update Lightning Address",
"SX58hM": "Copy",
"SYQtZ7": "LN Address Proxy",
@ -202,10 +206,10 @@
"Ss0sWu": "Pay Now",
"TMfYfY": "Cashu token",
"TpgeGw": "Hex Salt..",
"Tpy00S": "People",
"TwyMau": "Account",
"UDYlxu": "Pending Subscriptions",
"ULotH9": "Amount: {amount} sats",
"UQ3pOC": "On Nostr, many people have the same username. User names and identity are separate things. You can get a unique identifier in the next step.",
"UUPFlt": "Users must accept the content warning to show the content of your note.",
"Up5U7K": "Block",
"VBadwB": "Hmm, can't find a key manager extension.. try reloading the page.",
@ -233,6 +237,7 @@
"ZKORll": "Activate Now",
"ZLmyG9": "Contributors",
"ZUZedV": "Lightning Donation:",
"Zr5TMx": "Setup profile",
"a5UPxh": "Fund developers and platforms providing NIP-05 verification services",
"aWpBzj": "Show more",
"b12Goz": "Mnemonic",
@ -315,6 +320,7 @@
"mKAr6h": "Follow all",
"mKh2HS": "File upload service",
"mKhgP9": "{n,plural,=0{} =1{zapped} other{zapped}}",
"mTJFgF": "Popular",
"mfe8RW": "Option: {n}",
"n1Whvj": "Switch",
"nDejmx": "Unblock",