feat: follow sets page
This commit is contained in:
@ -12,6 +12,8 @@ import usePreferences from "@/Hooks/usePreferences";
|
||||
import { dedupe, findTag, getDisplayName, hexToBech32 } from "@/Utils";
|
||||
import { useWallet } from "@/Wallet";
|
||||
|
||||
import { ProxyImg } from "../ProxyImg";
|
||||
|
||||
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
|
||||
const wallet = useWallet();
|
||||
const defaultZapAmount = usePreferences(s => s.defaultZapAmount);
|
||||
@ -62,29 +64,33 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
|
||||
}
|
||||
}
|
||||
|
||||
const picture = findTag(ev, "image");
|
||||
return (
|
||||
<FollowListBase
|
||||
pubkeys={ids}
|
||||
className={className}
|
||||
title={findTag(ev, "title") ?? findTag(ev, "d")}
|
||||
actions={
|
||||
<>
|
||||
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Zap all {n} sats"
|
||||
id="IVbtTS"
|
||||
values={{
|
||||
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
|
||||
}}
|
||||
/>
|
||||
</AsyncButton>
|
||||
</>
|
||||
}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
{picture && <ProxyImg src={picture} className="br max-h-44 w-full object-cover mb-4" />}
|
||||
<FollowListBase
|
||||
pubkeys={ids}
|
||||
className={className}
|
||||
title={findTag(ev, "title") ?? findTag(ev, "d")}
|
||||
actions={
|
||||
<>
|
||||
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Zap all {n} sats"
|
||||
id="IVbtTS"
|
||||
values={{
|
||||
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
|
||||
}}
|
||||
/>
|
||||
</AsyncButton>
|
||||
</>
|
||||
}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ export default memo(function EventComponent(props: NoteProps) {
|
||||
case EventKind.ZapstrTrack:
|
||||
content = <ZapstrEmbed ev={ev} />;
|
||||
break;
|
||||
case EventKind.StarterPackSet:
|
||||
case EventKind.FollowSet:
|
||||
case EventKind.ContactList:
|
||||
content = <PubkeyList ev={ev} className={className} />;
|
||||
|
@ -61,17 +61,6 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "suggested",
|
||||
path: `${base}/suggested`,
|
||||
show: Boolean(pubKey),
|
||||
element: (
|
||||
<>
|
||||
<Icon name="thumbs-up" />
|
||||
<FormattedMessage defaultMessage="Suggested Follows" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "trending/hashtags",
|
||||
path: `${base}/trending/hashtags`,
|
||||
@ -105,6 +94,17 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
tab: "follow-sets",
|
||||
path: `${base}/follow-sets`,
|
||||
show: true,
|
||||
element: (
|
||||
<>
|
||||
<Icon name="thumbs-up" />
|
||||
<FormattedMessage defaultMessage="Follow Sets" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
] as Array<{
|
||||
tab: RootTabRoutePath;
|
||||
path: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HexKey, NostrPrefix } from "@snort/system";
|
||||
import { NostrPrefix } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
@ -61,7 +61,7 @@ export default function SuggestedProfiles() {
|
||||
</select>
|
||||
</div>
|
||||
<FollowListBase
|
||||
pubkeys={userList as HexKey[]}
|
||||
pubkeys={userList}
|
||||
profilePreviewProps={{
|
||||
options: {
|
||||
about: true,
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { HexKey } from "@snort/system";
|
||||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import ProfilePreview, { ProfilePreviewProps } from "@/Components/User/ProfilePreview";
|
||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import useWoT from "@/Hooks/useWoT";
|
||||
|
||||
import AsyncButton from "../Button/AsyncButton";
|
||||
import messages from "../messages";
|
||||
|
||||
export interface FollowListBaseProps {
|
||||
pubkeys: HexKey[];
|
||||
pubkeys: string[];
|
||||
title?: ReactNode;
|
||||
showFollowAll?: boolean;
|
||||
className?: string;
|
||||
@ -27,7 +27,8 @@ export default function FollowListBase({
|
||||
profilePreviewProps,
|
||||
}: FollowListBaseProps) {
|
||||
const control = useFollowsControls();
|
||||
const login = useLogin();
|
||||
const readonly = useLogin(s => s.readonly);
|
||||
const wot = useWoT();
|
||||
|
||||
async function followAll() {
|
||||
await control.addFollow(pubkeys);
|
||||
@ -37,15 +38,17 @@ export default function FollowListBase({
|
||||
<div className="flex flex-col gap-2">
|
||||
{(showFollowAll ?? true) && (
|
||||
<div className="flex items-center">
|
||||
<div className="grow font-bold">{title}</div>
|
||||
<div className="grow font-bold text-xl">{title}</div>
|
||||
{actions}
|
||||
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}>
|
||||
<FormattedMessage {...messages.FollowAll} />
|
||||
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={readonly}>
|
||||
<FormattedMessage defaultMessage="Follow All" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
<div className={className}>
|
||||
{pubkeys?.slice(0, 20).map(a => <ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />)}
|
||||
<div className={classNames("flex flex-col gap-2", className)}>
|
||||
{wot.sortPubkeys(pubkeys).map(a => (
|
||||
<ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -277,6 +277,8 @@ export default function KindName({ kind }: { kind: number }) {
|
||||
return <FormattedMessage defaultMessage="Community Definition" />;
|
||||
case 38383:
|
||||
return <FormattedMessage defaultMessage="Peer-to-peer Order events" />;
|
||||
case 39089:
|
||||
return <FormattedMessage defaultMessage="Starter Pack" />;
|
||||
case 39701:
|
||||
return <FormattedMessage defaultMessage="Web bookmarks" />;
|
||||
default:
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { EventKind, NostrLink, NostrPrefix, parseNostrLink } from "@snort/system";
|
||||
import { Bech32Regex, unwrap } from "@snort/shared";
|
||||
import { EventKind, NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
|
||||
import { useEventFeed } from "@snort/system-react";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
@ -9,24 +9,25 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { rootTabItems } from "@/Components/Feed/RootTabItems";
|
||||
import { RootTabs } from "@/Components/Feed/RootTabs";
|
||||
import Icon from "@/Components/Icons/Icon";
|
||||
import KindName from "@/Components/kind-name";
|
||||
import DisplayName from "@/Components/User/DisplayName";
|
||||
import useLogin from "@/Hooks/useLogin";
|
||||
import { LogoHeader } from "@/Pages/Layout/LogoHeader";
|
||||
import NotificationsHeader from "@/Pages/Layout/NotificationsHeader";
|
||||
import { bech32ToHex } from "@/Utils";
|
||||
import { bech32ToHex, findTag } from "@/Utils";
|
||||
|
||||
export function Header() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const pageName = decodeURIComponent(location.pathname.split("/")[1]);
|
||||
const pathSplit = location.pathname.split("/");
|
||||
const pageName = decodeURIComponent(pathSplit[1]);
|
||||
|
||||
const nostrLink = useMemo(() => {
|
||||
try {
|
||||
return parseNostrLink(pageName);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
const nostrEntity = pathSplit.find(a => a.match(Bech32Regex));
|
||||
if (nostrEntity) {
|
||||
return tryParseNostrLink(nostrEntity);
|
||||
}
|
||||
}, [pageName]);
|
||||
}, [pathSplit]);
|
||||
|
||||
const { publicKey, tags } = useLogin(s => ({
|
||||
publicKey: s.publicKey,
|
||||
@ -115,17 +116,20 @@ export function Header() {
|
||||
function NoteTitle({ link }: { link: NostrLink }) {
|
||||
const ev = useEventFeed(link);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return { name: <DisplayName pubkey={ev?.pubkey ?? ""} /> };
|
||||
}, [ev?.pubkey]);
|
||||
|
||||
if (!ev?.pubkey) {
|
||||
return <FormattedMessage defaultMessage="Note" />;
|
||||
}
|
||||
|
||||
const title = findTag(ev, "title");
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage defaultMessage="Note by {name}" values={values} />
|
||||
<FormattedMessage
|
||||
defaultMessage="{note_type} by {name}{title}"
|
||||
values={{
|
||||
note_type: <KindName kind={ev.kind} />,
|
||||
name: <DisplayName pubkey={ev.pubkey} />,
|
||||
title: title ? ` - ${title}` : "",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { dedupe, unwrap } from "@snort/shared";
|
||||
import { EventKind, parseNostrLink } from "@snort/system";
|
||||
import { parseNostrLink } from "@snort/system";
|
||||
import { useEventFeed } from "@snort/system-react";
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@ -27,7 +27,8 @@ export function ListFeedPage() {
|
||||
);
|
||||
|
||||
if (!data) return <PageSpinner />;
|
||||
if (data.kind !== EventKind.ContactList && data.kind !== EventKind.FollowSet) {
|
||||
const hasPTags = data.tags.some(a => a[0] === "p");
|
||||
if (!hasPTags) {
|
||||
return (
|
||||
<b>
|
||||
<FormattedMessage defaultMessage="Must be a contact list or pubkey list" />
|
||||
|
80
packages/app/src/Pages/Root/FollowSets.tsx
Normal file
80
packages/app/src/Pages/Root/FollowSets.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { dedupe } from "@snort/shared";
|
||||
import { EventKind, NostrLink, RequestBuilder } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import AsyncButton from "@/Components/Button/AsyncButton";
|
||||
import { AvatarGroup } from "@/Components/User/AvatarGroup";
|
||||
import DisplayName from "@/Components/User/DisplayName";
|
||||
import { ProfileLink } from "@/Components/User/ProfileLink";
|
||||
import useFollowsControls from "@/Hooks/useFollowControls";
|
||||
import useWoT from "@/Hooks/useWoT";
|
||||
import { findTag } from "@/Utils";
|
||||
|
||||
export default function FollowSetsPage() {
|
||||
const sub = new RequestBuilder("follow-sets");
|
||||
sub.withFilter().kinds([EventKind.StarterPackSet, EventKind.FollowSet]);
|
||||
|
||||
const data = useRequestBuilder(sub);
|
||||
const wot = useWoT();
|
||||
const control = useFollowsControls();
|
||||
const dataSorted = wot.sortEvents(data);
|
||||
|
||||
return (
|
||||
<div className="p flex flex-col gap-4">
|
||||
{dataSorted.map(a => {
|
||||
const title = findTag(a, "title") ?? findTag(a, "d") ?? a.content;
|
||||
const pTags = wot.sortPubkeys(dedupe(a.tags.filter(a => a[0] === "p").map(a => a[1])));
|
||||
const isFollowingAll = pTags.every(a => control.isFollowing(a));
|
||||
if (pTags.length === 0) return;
|
||||
const link = NostrLink.fromEvent(a);
|
||||
return (
|
||||
<div key={a.id} className="p br bg-gray-ultradark flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-xl">{title}</div>
|
||||
<div className="text-gray-medium font-medium flex items-center gap-2">
|
||||
<Link to={`/${link.encode()}`} state={a}>
|
||||
<FormattedMessage defaultMessage="{n} people" values={{ n: pTags.length }} />
|
||||
</Link>
|
||||
-
|
||||
<Link to={`/list-feed/${link.encode()}`}>
|
||||
<FormattedMessage defaultMessage="View Feed" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!isFollowingAll && (
|
||||
<div className="flex gap-4">
|
||||
<AsyncButton
|
||||
className="secondary"
|
||||
onClick={async () => {
|
||||
await control.addFollow(pTags);
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Follow All" />
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<AvatarGroup ids={pTags.slice(0, 10)} size={40} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
defaultMessage="<dark>Created by</dark> {name}"
|
||||
values={{
|
||||
dark: c => <span className="text-gray-medium">{c}</span>,
|
||||
name: (
|
||||
<ProfileLink pubkey={a.pubkey}>
|
||||
<DisplayName pubkey={a.pubkey} />
|
||||
</ProfileLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import SuggestedProfiles from "@/Components/SuggestedProfiles";
|
||||
import TrendingHashtags from "@/Components/Trending/TrendingHashtags";
|
||||
import TrendingNotes from "@/Components/Trending/TrendingPosts";
|
||||
import Discover from "@/Pages/Discover";
|
||||
@ -6,6 +5,7 @@ import HashTagsPage from "@/Pages/HashTagsPage";
|
||||
import { ConversationsTab } from "@/Pages/Root/ConversationsTab";
|
||||
import { DefaultTab } from "@/Pages/Root/DefaultTab";
|
||||
import { FollowedByFriendsTab } from "@/Pages/Root/FollowedByFriendsTab";
|
||||
import FollowSetsPage from "@/Pages/Root/FollowSets";
|
||||
import { ForYouTab } from "@/Pages/Root/ForYouTab";
|
||||
import MediaPosts from "@/Pages/Root/Media";
|
||||
import { NotesTab } from "@/Pages/Root/NotesTab";
|
||||
@ -25,7 +25,8 @@ export type RootTabRoutePath =
|
||||
| "suggested"
|
||||
| "t/:tag"
|
||||
| "topics"
|
||||
| "media";
|
||||
| "media"
|
||||
| "follow-sets";
|
||||
|
||||
export type RootTabRoute = {
|
||||
path: RootTabRoutePath;
|
||||
@ -69,14 +70,6 @@ export const RootTabRoutes: RootTabRoute[] = [
|
||||
path: "trending/hashtags",
|
||||
element: <TrendingHashtags />,
|
||||
},
|
||||
{
|
||||
path: "suggested",
|
||||
element: (
|
||||
<div className="p">
|
||||
<SuggestedProfiles />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "t/:tag",
|
||||
element: <HashTagsPage />,
|
||||
@ -89,4 +82,8 @@ export const RootTabRoutes: RootTabRoute[] = [
|
||||
path: "media",
|
||||
element: <MediaPosts />,
|
||||
},
|
||||
{
|
||||
path: "follow-sets",
|
||||
element: <FollowSetsPage />,
|
||||
},
|
||||
];
|
||||
|
@ -465,6 +465,9 @@
|
||||
"9HU8vw": {
|
||||
"defaultMessage": "Reply"
|
||||
},
|
||||
"9RNiUn": {
|
||||
"defaultMessage": "View Feed"
|
||||
},
|
||||
"9SvQep": {
|
||||
"defaultMessage": "Follows {n}"
|
||||
},
|
||||
@ -493,9 +496,6 @@
|
||||
"defaultMessage": "Parent",
|
||||
"description": "Link to parent note in thread"
|
||||
},
|
||||
"ALdW69": {
|
||||
"defaultMessage": "Note by {name}"
|
||||
},
|
||||
"AN0Z7Q": {
|
||||
"defaultMessage": "Muted Words"
|
||||
},
|
||||
@ -587,9 +587,6 @@
|
||||
"C8FsOr": {
|
||||
"defaultMessage": "Popular Servers"
|
||||
},
|
||||
"C8HhVE": {
|
||||
"defaultMessage": "Suggested Follows"
|
||||
},
|
||||
"CA1efg": {
|
||||
"defaultMessage": "Video sets"
|
||||
},
|
||||
@ -608,6 +605,9 @@
|
||||
"CM0k0d": {
|
||||
"defaultMessage": "Prune follow list"
|
||||
},
|
||||
"CSOaM+": {
|
||||
"defaultMessage": "{note_type} by {name}{title}"
|
||||
},
|
||||
"CVWeJ6": {
|
||||
"defaultMessage": "Trending People"
|
||||
},
|
||||
@ -1398,6 +1398,9 @@
|
||||
"V20Og0": {
|
||||
"defaultMessage": "Labeling"
|
||||
},
|
||||
"V93INS": {
|
||||
"defaultMessage": "<dark>Created by</dark> {name}"
|
||||
},
|
||||
"VOjC1i": {
|
||||
"defaultMessage": "Pick which upload service you want to upload attachments to"
|
||||
},
|
||||
@ -1608,6 +1611,12 @@
|
||||
"c3g2hL": {
|
||||
"defaultMessage": "Broadcast Again"
|
||||
},
|
||||
"c6BMLV": {
|
||||
"defaultMessage": "Starter Pack"
|
||||
},
|
||||
"cF3ruj": {
|
||||
"defaultMessage": "Follow All"
|
||||
},
|
||||
"cFbU1B": {
|
||||
"defaultMessage": "Using Alby? Go to {link} to get your NWC config!"
|
||||
},
|
||||
@ -1786,6 +1795,9 @@
|
||||
"gDzDRs": {
|
||||
"defaultMessage": "Emoji to send when reactiong to a note"
|
||||
},
|
||||
"gPxSgn": {
|
||||
"defaultMessage": "Follow Sets"
|
||||
},
|
||||
"gXgY3+": {
|
||||
"defaultMessage": "Not all clients support this yet"
|
||||
},
|
||||
@ -1810,6 +1822,9 @@
|
||||
"grQ+mI": {
|
||||
"defaultMessage": "Proof of Work"
|
||||
},
|
||||
"grRQTM": {
|
||||
"defaultMessage": "{n} people"
|
||||
},
|
||||
"gtNjNP": {
|
||||
"defaultMessage": "Basic protocol flow description"
|
||||
},
|
||||
|
@ -154,6 +154,7 @@
|
||||
"9+Ddtu": "Next",
|
||||
"92gdbw": "Relay Discovery",
|
||||
"9HU8vw": "Reply",
|
||||
"9RNiUn": "View Feed",
|
||||
"9SvQep": "Follows {n}",
|
||||
"9V0wg3": "Calendar Event RSVP",
|
||||
"9WRlF4": "Send",
|
||||
@ -163,7 +164,6 @@
|
||||
"9wO4wJ": "Lightning Invoice",
|
||||
"A86fJ+": "Generic Repost",
|
||||
"ADmfQT": "Parent",
|
||||
"ALdW69": "Note by {name}",
|
||||
"AN0Z7Q": "Muted Words",
|
||||
"ASRK0S": "This author has been muted",
|
||||
"AedFVZ": "Create or update a product",
|
||||
@ -194,13 +194,13 @@
|
||||
"C7642/": "Quote Repost",
|
||||
"C81/uG": "Logout",
|
||||
"C8FsOr": "Popular Servers",
|
||||
"C8HhVE": "Suggested Follows",
|
||||
"CA1efg": "Video sets",
|
||||
"CHTbO3": "Failed to load invoice",
|
||||
"CJ0biq": "Poll Response",
|
||||
"CJx5Nd": "Profile Zaps",
|
||||
"CM+Cfj": "Follow List",
|
||||
"CM0k0d": "Prune follow list",
|
||||
"CSOaM+": "{note_type} by {name}{title}",
|
||||
"CVWeJ6": "Trending People",
|
||||
"CYkOCI": "and {count} others you follow",
|
||||
"Cdxwi0": "Repository announcements",
|
||||
@ -463,6 +463,7 @@
|
||||
"UsCzPc": "Share a personalized invitation with friends!",
|
||||
"UxgyeY": "Your referral code is {code}",
|
||||
"V20Og0": "Labeling",
|
||||
"V93INS": "<dark>Created by</dark> {name}",
|
||||
"VOjC1i": "Pick which upload service you want to upload attachments to",
|
||||
"VR5eHw": "Public key (npub/nprofile)",
|
||||
"VcwrfF": "Yes please",
|
||||
@ -533,6 +534,8 @@
|
||||
"c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}",
|
||||
"c3LlRO": "{n}KiB",
|
||||
"c3g2hL": "Broadcast Again",
|
||||
"c6BMLV": "Starter Pack",
|
||||
"cF3ruj": "Follow All",
|
||||
"cFbU1B": "Using Alby? Go to {link} to get your NWC config!",
|
||||
"cG/bKQ": "Native nostr wallet connection",
|
||||
"cHCwbF": "Photography",
|
||||
@ -592,6 +595,7 @@
|
||||
"g5pX+a": "About",
|
||||
"g985Wp": "Failed to send vote",
|
||||
"gDzDRs": "Emoji to send when reactiong to a note",
|
||||
"gPxSgn": "Follow Sets",
|
||||
"gXgY3+": "Not all clients support this yet",
|
||||
"gczcC5": "Subscribe",
|
||||
"geppt8": "{count} ({count2} in memory)",
|
||||
@ -600,6 +604,7 @@
|
||||
"gl1NeW": "Lists",
|
||||
"go2/QF": "User server list",
|
||||
"grQ+mI": "Proof of Work",
|
||||
"grRQTM": "{n} people",
|
||||
"gtNjNP": "Basic protocol flow description",
|
||||
"h1gtUi": "Poll",
|
||||
"h7jvCs": "{site} is more fun together!",
|
||||
|
@ -46,6 +46,7 @@ const enum EventKind {
|
||||
CurationSet = 30_004, // NIP-51
|
||||
InterestSet = 30_015, // NIP-15
|
||||
EmojiSet = 30_030, // NIP-51
|
||||
StarterPackSet = 39_089, // NIP-51
|
||||
|
||||
Badge = 30009, // NIP-58
|
||||
ProfileBadges = 30008, // NIP-58
|
||||
|
Reference in New Issue
Block a user