feat: trending notes
This commit is contained in:
parent
1143cdfc88
commit
2ed38d1b97
34
packages/app/src/Element/TrendingPosts.tsx
Normal file
34
packages/app/src/Element/TrendingPosts.tsx
Normal 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} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
@ -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[]>();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await fetchTrendingUsers();
|
||||
if (data) {
|
||||
setUserList(data);
|
||||
async function loadTrendingUsers() {
|
||||
const api = new NostrBandApi();
|
||||
const users = await api.trendingProfiles();
|
||||
const keys = users.profiles.map(a => a.pubkey);
|
||||
setUserList(keys);
|
||||
}
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
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;
|
||||
}
|
||||
|
52
packages/app/src/NostrBand.tsx
Normal file
52
packages/app/src/NostrBand.tsx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
@ -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" },
|
||||
});
|
||||
|
@ -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}"
|
||||
},
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user