Use kind 10004 for communities list

This commit is contained in:
hzrd149 2023-11-17 11:49:45 -06:00
parent c356916905
commit 90700ebbf8
12 changed files with 90 additions and 54 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Use kind 10004 for communities list instead of kind 30001

View File

@ -4,6 +4,7 @@ import { getMatchLink, getMatchNostrLink } from "../regexp";
import { ReactionGroup } from "./reactions";
import { parseCoordinate } from "./events";
/** @deprecated */
export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
export const COMMUNITY_DEFINITION_KIND = 34550;
export const COMMUNITY_APPROVAL_KIND = 4550;
@ -91,7 +92,7 @@ export function getCommunityPostVote(grouped: ReactionGroup[]) {
return { up, down, vote };
}
export function getEventCommunityPointer(event: NostrEvent){
export function getEventCommunityPointer(event: NostrEvent) {
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
return communityTag ? parseCoordinate(communityTag[1], true) : null;
}

View File

@ -5,15 +5,22 @@ import { AddressPointer } from "nostr-tools/lib/types/nip19";
import { DraftNostrEvent, NostrEvent, PTag, isATag, isDTag, isETag, isPTag, isRTag } from "../../types/nostr-event";
import { parseCoordinate } from "./events";
export const MUTE_LIST_KIND = 10000;
export const PIN_LIST_KIND = 10001;
export const BOOKMARK_LIST_KIND = 10003;
export const COMMUNITIES_LIST_KIND = 10004;
export const CHATS_LIST_KIND = 10005;
export const PEOPLE_LIST_KIND = 30000;
export const NOTE_LIST_KIND = 30001;
export const PIN_LIST_KIND = 10001;
export const MUTE_LIST_KIND = 10000;
export const BOOKMARK_LIST_SET_KIND = 30003;
export function getListName(event: NostrEvent) {
if (event.kind === Kind.Contacts) return "Following";
if (event.kind === PIN_LIST_KIND) return "Pins";
if (event.kind === MUTE_LIST_KIND) return "Mute";
if (event.kind === PIN_LIST_KIND) return "Pins";
if (event.kind === BOOKMARK_LIST_KIND) return "Bookmarks";
if (event.kind === COMMUNITIES_LIST_KIND) return "Communities";
return (
event.tags.find((t) => t[0] === "name")?.[1] ||
event.tags.find((t) => t[0] === "title")?.[1] ||
@ -31,7 +38,14 @@ export function isJunkList(event: NostrEvent) {
return /^(chats\/([0-9a-f]{64}|null)|notifications)\/lastOpened$/.test(name);
}
export function isSpecialListKind(kind: number) {
return kind === Kind.Contacts || kind === PIN_LIST_KIND || kind === MUTE_LIST_KIND;
return (
kind === Kind.Contacts ||
kind === MUTE_LIST_KIND ||
kind === PIN_LIST_KIND ||
kind === BOOKMARK_LIST_KIND ||
kind === COMMUNITIES_LIST_KIND ||
kind === CHATS_LIST_KIND
);
}
export function cloneList(list: NostrEvent, keepCreatedAt = false): DraftNostrEvent {

View File

@ -1,5 +1,5 @@
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
import { NOTE_LIST_KIND, getParsedCordsFromList } from "../helpers/nostr/lists";
import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getParsedCordsFromList } from "../helpers/nostr/lists";
import { RequestOptions } from "../services/replaceable-event-requester";
import useCurrentAccount from "./use-current-account";
import useReplaceableEvent from "./use-replaceable-event";
@ -8,7 +8,10 @@ export default function useJoinedCommunitiesList(pubkey?: string, opts?: Request
const account = useCurrentAccount();
const key = pubkey ?? account?.pubkey;
const list = useReplaceableEvent(
// TODO: remove at some future date when apps have transitioned to using k:10004 for communities
// https://github.com/nostr-protocol/nips/pull/880
/** @deprecated */
const oldList = useReplaceableEvent(
key
? {
kind: NOTE_LIST_KIND,
@ -19,11 +22,19 @@ export default function useJoinedCommunitiesList(pubkey?: string, opts?: Request
[],
opts,
);
const list = useReplaceableEvent(key ? { kind: COMMUNITIES_LIST_KIND, pubkey: key } : undefined, [], opts);
const pointers = list ? getParsedCordsFromList(list).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND) : [];
let useList = list || oldList;
console.log(list, oldList);
return {
list,
pointers,
};
// if both exist, use the newest one
if (list && oldList) {
useList = list.created_at > oldList.created_at ? list : oldList;
}
const pointers = useList
? getParsedCordsFromList(useList).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND)
: [];
return { list: useList, pointers };
}

View File

@ -1,13 +1,8 @@
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
import { getEventCoordinate } from "../helpers/nostr/events";
import { NOTE_LIST_KIND } from "../helpers/nostr/lists";
import { COMMUNITIES_LIST_KIND } from "../helpers/nostr/lists";
import { NostrEvent } from "../types/nostr-event";
import useEventCount from "./use-event-count";
export default function useCountCommunityMembers(community: NostrEvent) {
return useEventCount({
"#a": [getEventCoordinate(community)],
"#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER],
kinds: [NOTE_LIST_KIND],
});
return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [COMMUNITIES_LIST_KIND] });
}

View File

@ -63,15 +63,9 @@ class EventExistsService {
const request = new NostrRequest([nextRelay], 500);
const limitFilter = Array.isArray(filter) ? filter.map((f) => ({ ...f, limit: 1 })) : { ...filter, limit: 1 };
request.start(limitFilter);
request.onEvent.subscribe(() => {
sub.next(true);
this.log(`Found event for`, filter);
});
request.onEvent.subscribe(() => sub.next(true));
await request.onComplete;
if (sub.value === undefined) {
sub.next(false);
this.log(`couldn't find event for`, filter);
}
if (sub.value === undefined) sub.next(false);
})();
}
}

View File

@ -66,7 +66,7 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
<UserAvatarLink pubkey={community.pubkey} size="sm" />
<Text>by</Text>
<UserLink pubkey={community.pubkey} />
{countMembers && (
{countMembers !== undefined && countMembers > 0 && (
<Tag variant="solid" ml="auto" alignSelf="flex-end" textShadow="none">
<TagLeftIcon as={User01} boxSize={4} />
<TagLabel>{readablizeSats(countMembers)}</TagLabel>

View File

@ -2,11 +2,11 @@ import { useCallback } from "react";
import dayjs from "dayjs";
import { Button, ButtonProps, useToast } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
import { DraftNostrEvent, NostrEvent, isDTag } from "../../../types/nostr-event";
import useJoinedCommunitiesList from "../../../hooks/use-communities-joined-list";
import useCurrentAccount from "../../../hooks/use-current-account";
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityName } from "../../../helpers/nostr/communities";
import { NOTE_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
import { getCommunityName } from "../../../helpers/nostr/communities";
import { COMMUNITIES_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
import { getEventCoordinate } from "../../../helpers/nostr/events";
import { useSigningContext } from "../../../providers/signing-provider";
import NostrPublishAction from "../../../classes/nostr-publish-action";
@ -27,11 +27,11 @@ export default function CommunityJoinButton({
const handleClick = useCallback(async () => {
try {
const favList = list || {
kind: NOTE_LIST_KIND,
const favList = {
kind: COMMUNITIES_LIST_KIND,
content: "",
created_at: dayjs().unix(),
tags: [["d", SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER]],
tags: list?.tags.filter((t) => !isDTag(t)) ?? [],
};
let draft: DraftNostrEvent;

View File

@ -10,7 +10,7 @@ import { ErrorBoundary } from "../../components/error-boundary";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import useSubjects from "../../hooks/use-subjects";
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
import { NOTE_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists";
import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists";
import { useNavigate } from "react-router-dom";
import { ChevronLeftIcon } from "../../components/icons";
import { parseCoordinate } from "../../helpers/nostr/events";
@ -19,17 +19,12 @@ import { AddressPointer } from "nostr-tools/lib/types/nip19";
export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays: string[] = []) {
const readRelays = useReadRelayUrls(additionalRelays);
const muteListSubjects = useMemo(() => {
const communityListsSubjects = useMemo(() => {
return pubkeys.map((pubkey) =>
replaceableEventLoaderService.requestEvent(
readRelays,
NOTE_LIST_KIND,
pubkey,
SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER,
),
replaceableEventLoaderService.requestEvent(readRelays, COMMUNITIES_LIST_KIND, pubkey),
);
}, [pubkeys]);
return useSubjects(muteListSubjects);
return useSubjects(communityListsSubjects);
}
function CommunityCardWithMembers({ pointer, pubkeys }: { pointer: AddressPointer; pubkeys: string[] }) {

View File

@ -16,7 +16,7 @@ import useTimelineLoader from "../../../hooks/use-timeline-loader";
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityRelays } from "../../../helpers/nostr/communities";
import { getEventCoordinate } from "../../../helpers/nostr/events";
import { NOTE_LIST_KIND } from "../../../helpers/nostr/lists";
import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND } from "../../../helpers/nostr/lists";
import IntersectionObserverProvider from "../../../providers/intersection-observer";
import useSubject from "../../../hooks/use-subject";
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
@ -40,15 +40,27 @@ function UserCard({ pubkey }: { pubkey: string }) {
export default function ({ community, onClose, ...props }: Omit<ModalProps, "children"> & { community: NostrEvent }) {
const communityCoordinate = getEventCoordinate(community);
const readRelays = useReadRelayUrls(getCommunityRelays(community));
const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, {
"#a": [communityCoordinate],
"#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER],
kinds: [NOTE_LIST_KIND],
});
const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, [
{
"#a": [communityCoordinate],
"#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER],
kinds: [NOTE_LIST_KIND],
},
{ "#a": [communityCoordinate], kinds: [COMMUNITIES_LIST_KIND] },
]);
const lists = useSubject(timeline.timeline);
const callback = useTimelineCurserIntersectionCallback(timeline);
// TODO: remove at some future date when apps have transitioned to using k:10004 for communities
// https://github.com/nostr-protocol/nips/pull/880
const listsByPubkey: Record<string, NostrEvent> = {};
for (const list of lists) {
if (!listsByPubkey[list.pubkey] || listsByPubkey[list.pubkey].created_at < list.created_at) {
listsByPubkey[list.pubkey] = list;
}
}
return (
<IntersectionObserverProvider callback={callback}>
<Modal onClose={onClose} size="4xl" {...props}>

View File

@ -87,7 +87,8 @@ function ListCardRender({
hideCreator = false,
...props
}: Omit<CardProps, "children"> & { list: NostrEvent; hideCreator?: boolean }) {
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
const isSpecialList = isSpecialListKind(list.kind);
const link = isSpecialList ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
// if there is a parent intersection observer, register this card
const ref = useRef<HTMLDivElement | null>(null);
@ -118,9 +119,8 @@ function ListCardRender({
<ListCardContent list={list} />
</CardBody>
<CardFooter p="2">
<NoteZapButton event={list} size="sm" variant="ghost" />
{/* TODO: reactions are tagging every user in list */}
<SimpleLikeButton event={list} variant="ghost" size="sm" />
{!isSpecialList && <NoteZapButton event={list} size="sm" variant="ghost" />}
{!isSpecialList && <SimpleLikeButton event={list} variant="ghost" size="sm" />}
<ButtonGroup size="sm" variant="ghost" ml="auto">
<ListFavoriteButton list={list} />
<ListMenu list={list} aria-label="list menu" />

View File

@ -9,7 +9,14 @@ import { getEventUID } from "../../helpers/nostr/events";
import useUserLists from "../../hooks/use-user-lists";
import NewListModal from "./components/new-list-modal";
import { getSharableEventAddress } from "../../helpers/nip19";
import { MUTE_LIST_KIND, NOTE_LIST_KIND, PEOPLE_LIST_KIND, PIN_LIST_KIND } from "../../helpers/nostr/lists";
import {
BOOKMARK_LIST_KIND,
COMMUNITIES_LIST_KIND,
MUTE_LIST_KIND,
NOTE_LIST_KIND,
PEOPLE_LIST_KIND,
PIN_LIST_KIND,
} from "../../helpers/nostr/lists";
import useFavoriteLists from "../../hooks/use-favorite-lists";
import VerticalPageLayout from "../../components/vertical-page-layout";
@ -51,6 +58,8 @@ function ListsPage() {
<ListCard cord={`${Kind.Contacts}:${account.pubkey}`} hideCreator />
<ListCard cord={`${MUTE_LIST_KIND}:${account.pubkey}`} hideCreator />
<ListCard cord={`${PIN_LIST_KIND}:${account.pubkey}`} hideCreator />
<ListCard cord={`${COMMUNITIES_LIST_KIND}:${account.pubkey}`} hideCreator />
<ListCard cord={`${BOOKMARK_LIST_KIND}:${account.pubkey}`} hideCreator />
</SimpleGrid>
{peopleLists.length > 0 && (
<>