NIP-65: Relay list metada (#238)
This commit is contained in:
@ -2,10 +2,11 @@ import "./Layout.css";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
import { randomSample } from "Util";
|
||||
import Envelope from "Icons/Envelope";
|
||||
import Bell from "Icons/Bell";
|
||||
import Search from "Icons/Search";
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import { init, setRelays } from "State/Login";
|
||||
import { System } from "Nostr/System";
|
||||
@ -147,8 +148,7 @@ export default function Layout() {
|
||||
const rsp = await fetch("https://api.nostr.watch/v1/online");
|
||||
if (rsp.ok) {
|
||||
const online: string[] = await rsp.json();
|
||||
const pickRandom = online.sort(() => (Math.random() >= 0.5 ? 1 : -1)).slice(0, 4); // pick 4 random relays
|
||||
|
||||
const pickRandom = randomSample(online, 4);
|
||||
const relayObjects = pickRandom.map(a => [a, { read: true, write: true }]);
|
||||
newRelays = Object.fromEntries(relayObjects);
|
||||
dispatch(
|
||||
|
@ -4,19 +4,21 @@ import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { unwrap } from "Util";
|
||||
import { formatShort } from "Number";
|
||||
import RelaysMetadata from "Element/RelaysMetadata";
|
||||
import { Tab, TabElement } from "Element/Tabs";
|
||||
import Link from "Icons/Link";
|
||||
import Qr from "Icons/Qr";
|
||||
import Zap from "Icons/Zap";
|
||||
import Envelope from "Icons/Envelope";
|
||||
import useRelaysFeed from "Feed/RelaysFeed";
|
||||
import { useUserProfile } from "Feed/ProfileFeed";
|
||||
import useZapsFeed from "Feed/ZapsFeed";
|
||||
import { default as ZapElement, parseZap } from "Element/Zap";
|
||||
import FollowButton from "Element/FollowButton";
|
||||
import { extractLnAddress, parseId, hexToBech32 } from "Util";
|
||||
import Avatar from "Element/Avatar";
|
||||
import LogoutButton from "Element/LogoutButton";
|
||||
import Timeline from "Element/Timeline";
|
||||
import Text from "Element/Text";
|
||||
import SendSats from "Element/SendSats";
|
||||
@ -46,6 +48,7 @@ const FOLLOWS = 3;
|
||||
const ZAPS = 4;
|
||||
const MUTED = 5;
|
||||
const BLOCKED = 6;
|
||||
const RELAYS = 7;
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { formatMessage } = useIntl();
|
||||
@ -69,6 +72,7 @@ export default function ProfilePage() {
|
||||
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
|
||||
const website_url =
|
||||
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
|
||||
const relays = useRelaysFeed(id);
|
||||
const zapFeed = useZapsFeed(id);
|
||||
const zaps = useMemo(() => {
|
||||
const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id);
|
||||
@ -85,8 +89,12 @@ export default function ProfilePage() {
|
||||
Zaps: { text: formatMessage(messages.Zaps), value: ZAPS },
|
||||
Muted: { text: formatMessage(messages.Muted), value: MUTED },
|
||||
Blocked: { text: formatMessage(messages.Blocked), value: BLOCKED },
|
||||
Relays: { text: formatMessage(messages.Relays), value: RELAYS },
|
||||
};
|
||||
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
|
||||
const optionalTabs = [zapsTotal > 0 && ProfileTab.Zaps, relays.length > 0 && ProfileTab.Relays].filter(a =>
|
||||
unwrap(a)
|
||||
) as Tab[];
|
||||
|
||||
useEffect(() => {
|
||||
setTab(ProfileTab.Notes);
|
||||
@ -204,6 +212,9 @@ export default function ProfilePage() {
|
||||
case BLOCKED: {
|
||||
return isMe ? <BlockList variant="blocked" /> : null;
|
||||
}
|
||||
case RELAYS: {
|
||||
return <RelaysMetadata relays={relays} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +240,6 @@ export default function ProfilePage() {
|
||||
)}
|
||||
{isMe ? (
|
||||
<>
|
||||
<LogoutButton />
|
||||
<button type="button" onClick={() => navigate("/settings")}>
|
||||
<FormattedMessage {...messages.Settings} />
|
||||
</button>
|
||||
@ -282,7 +292,8 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="tabs main-content" ref={horizontalScroll}>
|
||||
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)}
|
||||
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Muted].map(renderTab)}
|
||||
{optionalTabs.map(renderTab)}
|
||||
{isMe && renderTab(ProfileTab.Blocked)}
|
||||
</div>
|
||||
{tabContent()}
|
||||
|
@ -33,4 +33,7 @@ export default defineMessages({
|
||||
NostrPlebsNip: {
|
||||
defaultMessage: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`,
|
||||
},
|
||||
Relays: {
|
||||
defaultMessage: "Relays",
|
||||
},
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { DefaultImgProxy, setPreferences, UserPreferences } from "State/Login";
|
||||
import { RootState } from "State/Store";
|
||||
import emoji from "@jukben/emoji-search";
|
||||
|
||||
import messages from "./messages";
|
||||
import { unwrap } from "Util";
|
||||
@ -197,6 +198,40 @@ const PreferencesPage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
<FormattedMessage {...messages.ReactionEmoji} />
|
||||
</div>
|
||||
<small>
|
||||
<FormattedMessage {...messages.ReactionEmojiHelp} />
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
className="emoji-selector"
|
||||
value={perf.reactionEmoji}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
reactionEmoji: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
}>
|
||||
<option value="+">
|
||||
+ <FormattedMessage {...messages.Default} />
|
||||
</option>
|
||||
{emoji("").map(({ name, char }) => {
|
||||
return (
|
||||
<option value={char}>
|
||||
{name} {char}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card flex">
|
||||
<div className="flex f-col f-grow">
|
||||
<div>
|
||||
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { randomSample } from "Util";
|
||||
import Relay from "Element/Relay";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { RootState } from "State/Store";
|
||||
@ -20,6 +21,14 @@ const RelaySettingsPage = () => {
|
||||
const ev = await publisher.saveRelays();
|
||||
publisher.broadcast(ev);
|
||||
publisher.broadcastForBootstrap(ev);
|
||||
try {
|
||||
const onlineRelays = await fetch("https://api.nostr.watch/v1/online").then(r => r.json());
|
||||
const settingsEv = await publisher.saveRelaysSettings();
|
||||
const rs = Object.keys(relays).concat(randomSample(onlineRelays, 20));
|
||||
publisher.broadcastAll(settingsEv, rs);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function addRelay() {
|
||||
|
@ -55,4 +55,6 @@ export default defineMessages({
|
||||
DisplayName: { defaultMessage: "Display name" },
|
||||
Buy: { defaultMessage: "Buy" },
|
||||
Nip05: { defaultMessage: "NIP-05" },
|
||||
ReactionEmoji: { defaultMessage: "Reaction emoji" },
|
||||
ReactionEmojiHelp: { defaultMessage: "Emoji to send when reactiong to a note" },
|
||||
});
|
||||
|
Reference in New Issue
Block a user