mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
Improve list background loading
Fix delete events not getting published to outbox
This commit is contained in:
parent
30a3849ef3
commit
cab89b637f
5
.changeset/clean-horses-hang.md
Normal file
5
.changeset/clean-horses-hang.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Fix delete events not getting published to outbox
|
5
.changeset/neat-gorillas-tickle.md
Normal file
5
.changeset/neat-gorillas-tickle.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Improve list background loading
|
@ -103,6 +103,7 @@
|
||||
"react-window": "^1.8.10",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-wiki-link": "^2.0.1",
|
||||
"rx-nostr": "^3.4.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"three": "^0.170.0",
|
||||
"three-spritetext": "^1.9.0",
|
||||
|
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -273,6 +273,9 @@ importers:
|
||||
remark-wiki-link:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
rx-nostr:
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.1
|
||||
rxjs:
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
@ -3314,6 +3317,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
nostr-typedef@0.9.0:
|
||||
resolution: {integrity: sha512-nLTzhlYcRnLQGUJ5YfvGAUDyGFHjGH6Qozltl/wV3UXelmiUwjrwI8IIxQNkbgVMv+zmbFi/m1xKHxIvVfG09w==}
|
||||
|
||||
nostr-wasm@0.1.0:
|
||||
resolution: {integrity: sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==}
|
||||
|
||||
@ -3837,6 +3843,9 @@ packages:
|
||||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
rx-nostr@3.4.1:
|
||||
resolution: {integrity: sha512-u/FkKUERrrJWVrDxMvhYCntRldyHS14CmHE9bRJefl8StU48x27MxsMDHIjZ1zDKtaOtBvvrpv+vCEaleIB/Mw==}
|
||||
|
||||
rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
|
||||
@ -8153,6 +8162,8 @@ snapshots:
|
||||
nostr-wasm: 0.1.0
|
||||
typescript: 5.6.3
|
||||
|
||||
nostr-typedef@0.9.0: {}
|
||||
|
||||
nostr-wasm@0.1.0: {}
|
||||
|
||||
nth-check@2.1.1:
|
||||
@ -8752,6 +8763,11 @@ snapshots:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
rx-nostr@3.4.1:
|
||||
dependencies:
|
||||
nostr-typedef: 0.9.0
|
||||
rxjs: 7.8.1
|
||||
|
||||
rxjs@7.8.1:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { NostrEvent, kinds, nip18, nip25 } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { map, throttleTime } from "rxjs/operators";
|
||||
import { getZapPayment } from "applesauce-core/helpers";
|
||||
@ -10,7 +9,7 @@ import RelaySet from "./relay-set";
|
||||
import clientRelaysService from "../services/client-relays";
|
||||
import { getPubkeysMentionedInContent } from "../helpers/nostr/post";
|
||||
import { TORRENT_COMMENT_KIND } from "../helpers/nostr/torrents";
|
||||
import { MUTE_LIST_KIND, getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import { eventStore, queryStore } from "../services/event-store";
|
||||
|
||||
export const NotificationTypeSymbol = Symbol("notificationType");
|
||||
@ -121,7 +120,7 @@ export default class AccountNotifications {
|
||||
private filterEvent(event: CategorizedEvent) {
|
||||
// ignore if muted
|
||||
// TODO: this should be moved somewhere more performant
|
||||
const muteList = eventStore.getReplaceable(MUTE_LIST_KIND, this.pubkey);
|
||||
const muteList = eventStore.getReplaceable(kinds.Mutelist, this.pubkey);
|
||||
const mutedPubkeys = muteList ? getPubkeysFromList(muteList).map((p) => p.pubkey) : [];
|
||||
if (mutedPubkeys.includes(event.pubkey)) return false;
|
||||
|
||||
|
@ -2,12 +2,11 @@ import { useCallback, useState } from "react";
|
||||
import { MenuItem } from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useUserPinList from "../../hooks/use-user-pin-list";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import { PIN_LIST_KIND, isEventInList, listAddEvent, listRemoveEvent } from "../../helpers/nostr/lists";
|
||||
import { isEventInList, listAddEvent, listRemoveEvent } from "../../helpers/nostr/lists";
|
||||
import { PinIcon } from "../icons";
|
||||
import { usePublishEvent } from "../../providers/global/publish-provider";
|
||||
|
||||
@ -30,7 +29,7 @@ export default function PinEventMenuItem({ event }: { event: NostrEvent }) {
|
||||
const togglePin = useCallback(async () => {
|
||||
setLoading(true);
|
||||
let draft: DraftNostrEvent = {
|
||||
kind: PIN_LIST_KIND,
|
||||
kind: kinds.Pinlist,
|
||||
created_at: dayjs().unix(),
|
||||
content: list?.content ?? "",
|
||||
tags: list?.tags ? Array.from(list.tags) : [],
|
||||
|
@ -12,8 +12,8 @@ import {
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { getEmojis, getPackName } from "applesauce-core/helpers/emoji";
|
||||
|
||||
import { getEmojis, getPackName } from "../../../helpers/nostr/emoji-packs";
|
||||
import UserAvatarLink from "../../user/user-avatar-link";
|
||||
import UserLink from "../../user/user-link";
|
||||
import EmojiPackFavoriteButton from "../../../views/emoji-packs/components/emoji-pack-favorite-button";
|
||||
|
@ -10,7 +10,7 @@ import { ListCardContent } from "../../../views/lists/components/list-card";
|
||||
import { createCoordinate } from "../../../classes/batch-kind-pubkey-loader";
|
||||
import { getSharableEventAddress } from "../../../services/event-relay-hint";
|
||||
|
||||
export default function EmbeddedList({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
|
||||
export default function EmbeddedSetOrList({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
|
||||
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
|
||||
const description = getListDescription(list);
|
||||
|
||||
|
@ -6,16 +6,7 @@ import { kinds } from "nostr-tools";
|
||||
import EmbeddedNote from "./event-types/embedded-note";
|
||||
import useSingleEvent from "../../hooks/use-single-event";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
CHANNELS_LIST_KIND,
|
||||
COMMUNITIES_LIST_KIND,
|
||||
NOTE_LIST_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import { LIST_KINDS, SET_KINDS } from "../../helpers/nostr/lists";
|
||||
import { STEMSTR_TRACK_KIND } from "../../helpers/nostr/stemstr";
|
||||
import { TORRENT_COMMENT_KIND, TORRENT_KIND } from "../../helpers/nostr/torrents";
|
||||
import { FLARE_VIDEO_KIND } from "../../helpers/nostr/video";
|
||||
@ -25,9 +16,8 @@ import { safeDecode } from "../../helpers/nip19";
|
||||
import type { EmbeddedGoalOptions } from "./event-types/embedded-goal";
|
||||
|
||||
import LoadingNostrLink from "../loading-nostr-link";
|
||||
import RelayCard from "../../views/relays/components/relay-card";
|
||||
import EmbeddedRepost from "./event-types/embedded-repost";
|
||||
import EmbeddedList from "./event-types/embedded-list";
|
||||
import EmbeddedSetOrList from "./event-types/embedded-list";
|
||||
import EmbeddedReaction from "./event-types/embedded-reaction";
|
||||
import EmbeddedDM from "./event-types/embedded-dm";
|
||||
import EmbeddedUnknown from "./event-types/embedded-unknown";
|
||||
@ -66,23 +56,17 @@ export function EmbedEvent({
|
||||
return <EmbeddedDM dm={event} {...cardProps} />;
|
||||
case kinds.LiveEvent:
|
||||
return <EmbeddedStream event={event} {...cardProps} />;
|
||||
case GOAL_KIND:
|
||||
case kinds.ZapGoal:
|
||||
return <EmbeddedGoal goal={event} {...cardProps} {...goalProps} />;
|
||||
case EMOJI_PACK_KIND:
|
||||
case kinds.Emojisets:
|
||||
return <EmbeddedEmojiPack pack={event} {...cardProps} />;
|
||||
case PEOPLE_LIST_KIND:
|
||||
case NOTE_LIST_KIND:
|
||||
case BOOKMARK_LIST_KIND:
|
||||
case COMMUNITIES_LIST_KIND:
|
||||
case CHANNELS_LIST_KIND:
|
||||
return <EmbeddedList list={event} {...cardProps} />;
|
||||
case kinds.LongFormArticle:
|
||||
return <EmbeddedArticle article={event} {...cardProps} />;
|
||||
case kinds.BadgeDefinition:
|
||||
return <EmbeddedBadge badge={event} {...cardProps} />;
|
||||
case kinds.LiveChatMessage:
|
||||
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
||||
case COMMUNITY_DEFINITION_KIND:
|
||||
case kinds.CommunityDefinition:
|
||||
return <EmbeddedCommunity community={event} {...cardProps} />;
|
||||
case STEMSTR_TRACK_KIND:
|
||||
return <EmbeddedStemstrTrack track={event} {...cardProps} />;
|
||||
@ -103,6 +87,9 @@ export function EmbedEvent({
|
||||
return <EmbeddedZapRecept zap={event} {...cardProps} />;
|
||||
}
|
||||
|
||||
if (SET_KINDS.includes(event.kind) || LIST_KINDS.includes(event.kind))
|
||||
return <EmbeddedSetOrList list={event} {...cardProps} />;
|
||||
|
||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||
};
|
||||
|
||||
|
@ -11,20 +11,15 @@ import {
|
||||
MenuOptionGroup,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useUserLists from "../../hooks/use-user-lists";
|
||||
import {
|
||||
NOTE_LIST_KIND,
|
||||
listAddEvent,
|
||||
listRemoveEvent,
|
||||
getEventPointersFromList,
|
||||
getListName,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import useUserSets from "../../hooks/use-user-lists";
|
||||
import { listAddEvent, listRemoveEvent, getEventPointersFromList, getListName } from "../../helpers/nostr/lists";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { getEventCoordinate } from "../../helpers/nostr/event";
|
||||
import { BookmarkIcon, BookmarkedIcon, PlusCircleIcon } from "../icons";
|
||||
import NewListModal from "../../views/lists/components/new-list-modal";
|
||||
import NewSetModal from "../../views/lists/components/new-set-modal";
|
||||
import useEventBookmarkActions from "../../hooks/use-event-bookmark-actions";
|
||||
import { usePublishEvent } from "../../providers/global/publish-provider";
|
||||
|
||||
@ -33,33 +28,37 @@ export default function BookmarkEventButton({
|
||||
...props
|
||||
}: { event: NostrEvent } & Omit<IconButtonProps, "icon">) {
|
||||
const publish = usePublishEvent();
|
||||
const newListModal = useDisclosure();
|
||||
const newSetModal = useDisclosure();
|
||||
const account = useCurrentAccount();
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const { isLoading: loadingBookmark, toggleBookmark, isBookmarked } = useEventBookmarkActions(event);
|
||||
const lists = useUserLists(account?.pubkey).filter((list) => list.kind === NOTE_LIST_KIND);
|
||||
const bookmarkSets = useUserSets(account?.pubkey).filter(
|
||||
(set) => set.kind === kinds.Genericlists || set.kind === kinds.Bookmarksets,
|
||||
);
|
||||
|
||||
const inLists = lists.filter((list) => getEventPointersFromList(list).some((p) => p.id === event.id));
|
||||
const inSets = bookmarkSets.filter((list) => getEventPointersFromList(list).some((p) => p.id === event.id));
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (cords: string | string[]) => {
|
||||
if (!Array.isArray(cords)) return;
|
||||
|
||||
setLoading(true);
|
||||
const addToList = lists.find((list) => !inLists.includes(list) && cords.includes(getEventCoordinate(list)));
|
||||
const removeFromList = lists.find((list) => inLists.includes(list) && !cords.includes(getEventCoordinate(list)));
|
||||
const addToSet = bookmarkSets.find((set) => !inSets.includes(set) && cords.includes(getEventCoordinate(set)));
|
||||
const removeFromSet = bookmarkSets.find(
|
||||
(set) => inSets.includes(set) && !cords.includes(getEventCoordinate(set)),
|
||||
);
|
||||
|
||||
if (addToList) {
|
||||
const draft = listAddEvent(addToList, event);
|
||||
if (addToSet) {
|
||||
const draft = listAddEvent(addToSet, event);
|
||||
await publish("Add to list", draft);
|
||||
} else if (removeFromList) {
|
||||
const draft = listRemoveEvent(removeFromList, event);
|
||||
} else if (removeFromSet) {
|
||||
const draft = listRemoveEvent(removeFromSet, event);
|
||||
await publish("Remove from list", draft);
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
[lists, event.id, publish],
|
||||
[bookmarkSets, event.id, publish],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -67,7 +66,7 @@ export default function BookmarkEventButton({
|
||||
<Menu isLazy closeOnSelect={false}>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
icon={inLists.length > 0 || isBookmarked ? <BookmarkedIcon /> : <BookmarkIcon />}
|
||||
icon={inSets.length > 0 || isBookmarked ? <BookmarkedIcon /> : <BookmarkIcon />}
|
||||
isDisabled={account?.readonly ?? true}
|
||||
{...props}
|
||||
/>
|
||||
@ -80,37 +79,37 @@ export default function BookmarkEventButton({
|
||||
Bookmark
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
{lists.length > 0 && (
|
||||
{bookmarkSets.length > 0 && (
|
||||
<MenuOptionGroup
|
||||
type="checkbox"
|
||||
value={inLists.map((list) => getEventCoordinate(list))}
|
||||
value={inSets.map((set) => getEventCoordinate(set))}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
{bookmarkSets.map((set) => (
|
||||
<MenuItemOption
|
||||
key={getEventCoordinate(list)}
|
||||
value={getEventCoordinate(list)}
|
||||
key={getEventCoordinate(set)}
|
||||
value={getEventCoordinate(set)}
|
||||
isDisabled={account?.readonly || isLoading}
|
||||
isTruncated
|
||||
maxW="90vw"
|
||||
>
|
||||
{getListName(list)}
|
||||
{getListName(set)}
|
||||
</MenuItemOption>
|
||||
))}
|
||||
</MenuOptionGroup>
|
||||
)}
|
||||
<MenuDivider />
|
||||
<MenuItem icon={<PlusCircleIcon />} onClick={newListModal.onOpen}>
|
||||
<MenuItem icon={<PlusCircleIcon />} onClick={newSetModal.onOpen}>
|
||||
New list
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{newListModal.isOpen && (
|
||||
<NewListModal
|
||||
onClose={newListModal.onClose}
|
||||
{newSetModal.isOpen && (
|
||||
<NewSetModal
|
||||
onClose={newSetModal.onClose}
|
||||
isOpen
|
||||
onCreated={newListModal.onClose}
|
||||
initKind={NOTE_LIST_KIND}
|
||||
onCreated={newSetModal.onClose}
|
||||
initKind={kinds.Bookmarksets}
|
||||
allowSelectKind={false}
|
||||
/>
|
||||
)}
|
||||
|
@ -15,11 +15,12 @@ import {
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import useUserLists from "../../hooks/use-user-lists";
|
||||
import useUserSets from "../../hooks/use-user-lists";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { PEOPLE_LIST_KIND, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { getEventCoordinate, getEventUID } from "../../helpers/nostr/event";
|
||||
import useFavoriteLists from "../../hooks/use-favorite-lists";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
@ -58,7 +59,7 @@ export default function PeopleListSelection({
|
||||
} & Omit<ButtonProps, "children">) {
|
||||
const modal = useDisclosure();
|
||||
const account = useCurrentAccount();
|
||||
const lists = useUserLists(account?.pubkey);
|
||||
const lists = useUserSets(account?.pubkey);
|
||||
const { lists: favoriteLists } = useFavoriteLists();
|
||||
const { selected, setSelected, listEvent } = usePeopleListContext();
|
||||
|
||||
@ -128,7 +129,7 @@ export default function PeopleListSelection({
|
||||
</Heading>
|
||||
<SimpleGrid columns={2} spacing="2">
|
||||
{lists
|
||||
.filter((l) => l.kind === PEOPLE_LIST_KIND)
|
||||
.filter((l) => l.kind === kinds.Followsets)
|
||||
.map((list) => (
|
||||
<ListCard key={getEventUID(list)} list={list} onClick={() => selectList(list)} />
|
||||
))}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Divider, Flex, IconButton, Image, Text } from "@chakra-ui/react";
|
||||
import { getEmojis, getPackName } from "applesauce-core/helpers/emoji";
|
||||
|
||||
import { DislikeIcon, LikeIcon } from "./icons";
|
||||
import useCurrentAccount from "../hooks/use-current-account";
|
||||
import useReplaceableEvent from "../hooks/use-replaceable-event";
|
||||
import { getEmojis, getPackCordsFromFavorites, getPackName } from "../helpers/nostr/emoji-packs";
|
||||
import { getPackCordsFromFavorites } from "../helpers/nostr/emoji-packs";
|
||||
import useFavoriteEmojiPacks from "../hooks/use-favorite-emoji-packs";
|
||||
import useAppSettings from "../hooks/use-app-settings";
|
||||
|
||||
|
@ -11,12 +11,12 @@ import {
|
||||
MenuDivider,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ChevronDownIcon, FollowIcon, MuteIcon, PlusCircleIcon, UnfollowIcon, UnmuteIcon } from "../icons";
|
||||
import useUserLists from "../../hooks/use-user-lists";
|
||||
import useUserSets from "../../hooks/use-user-lists";
|
||||
import {
|
||||
PEOPLE_LIST_KIND,
|
||||
createEmptyContactList,
|
||||
listAddPerson,
|
||||
listRemovePerson,
|
||||
@ -28,7 +28,7 @@ import { getEventCoordinate } from "../../helpers/nostr/event";
|
||||
import { useSigningContext } from "../../providers/global/signing-provider";
|
||||
import useUserContactList from "../../hooks/use-user-contact-list";
|
||||
import useAsyncErrorHandler from "../../hooks/use-async-error-handler";
|
||||
import NewListModal from "../../views/lists/components/new-list-modal";
|
||||
import NewSetModal from "../../views/lists/components/new-set-modal";
|
||||
import useUserMuteActions from "../../hooks/use-user-mute-actions";
|
||||
import { useMuteModalContext } from "../../providers/route/mute-modal-provider";
|
||||
import { usePublishEvent } from "../../providers/global/publish-provider";
|
||||
@ -40,7 +40,7 @@ function UsersLists({ pubkey }: { pubkey: string }) {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const newListModal = useDisclosure();
|
||||
|
||||
const lists = useUserLists(account.pubkey).filter((list) => list.kind === PEOPLE_LIST_KIND);
|
||||
const lists = useUserSets(account.pubkey).filter((list) => list.kind === kinds.Followsets);
|
||||
|
||||
const inLists = lists.filter((list) => getPubkeysFromList(list).some((p) => p.pubkey === pubkey));
|
||||
|
||||
@ -93,7 +93,7 @@ function UsersLists({ pubkey }: { pubkey: string }) {
|
||||
New list
|
||||
</MenuItem>
|
||||
|
||||
{newListModal.isOpen && <NewListModal onClose={newListModal.onClose} isOpen onCreated={newListModal.onClose} />}
|
||||
{newListModal.isOpen && <NewSetModal onClose={newListModal.onClose} isOpen onCreated={newListModal.onClose} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import { parseCoordinate } from "./event";
|
||||
|
||||
/** @deprecated */
|
||||
export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
|
||||
export const COMMUNITY_DEFINITION_KIND = kinds.CommunityDefinition;
|
||||
export const COMMUNITY_APPROVAL_KIND = kinds.CommunityPostApproval;
|
||||
|
||||
export function getCommunityName(community: NostrEvent) {
|
||||
const name = community.tags.find(isDTag)?.[1];
|
||||
@ -71,7 +69,7 @@ export function validateCommunity(community: NostrEvent) {
|
||||
export function buildApprovalMap(events: Iterable<NostrEvent>, mods: string[]) {
|
||||
const approvals = new Map<string, NostrEvent[]>();
|
||||
for (const event of events) {
|
||||
if (event.kind === COMMUNITY_APPROVAL_KIND && mods.includes(event.pubkey)) {
|
||||
if (event.kind === kinds.CommunityPostApproval && mods.includes(event.pubkey)) {
|
||||
for (const tag of event.tags) {
|
||||
if (isETag(tag)) {
|
||||
const arr = approvals.get(tag[1]);
|
||||
@ -85,6 +83,6 @@ export function buildApprovalMap(events: Iterable<NostrEvent>, mods: string[]) {
|
||||
}
|
||||
|
||||
export function getEventCommunityPointer(event: NostrEvent) {
|
||||
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
|
||||
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(kinds.CommunityDefinition + ":"));
|
||||
return communityTag ? parseCoordinate(communityTag[1], true) : null;
|
||||
}
|
||||
|
@ -1,19 +1,6 @@
|
||||
import { getTagValue } from "applesauce-core/helpers";
|
||||
import { NostrEvent, isATag } from "../../types/nostr-event";
|
||||
|
||||
export const EMOJI_PACK_KIND = 30030;
|
||||
export const USER_EMOJI_LIST_KIND = 10030;
|
||||
|
||||
export function getPackName(pack: NostrEvent) {
|
||||
return getTagValue(pack, "title") || getTagValue(pack, "d");
|
||||
}
|
||||
|
||||
export function getEmojis(pack: NostrEvent) {
|
||||
return pack.tags
|
||||
.filter((t) => t[0] === "emoji" && t[1] && t[2])
|
||||
.map((t) => ({ name: t[1] as string, url: t[2] as string }));
|
||||
}
|
||||
|
||||
export function getPackCordsFromFavorites(event: NostrEvent) {
|
||||
return event.tags.filter(isATag).map((t) => t[1]);
|
||||
}
|
||||
|
@ -6,25 +6,41 @@ import { PTag, isATag, isDTag, isPTag, isRTag } from "../../types/nostr-event";
|
||||
import { getEventCoordinate, replaceOrAddSimpleTag } from "./event";
|
||||
import { getRelayVariations, safeRelayUrls } from "../relay";
|
||||
|
||||
export const MUTE_LIST_KIND = kinds.Mutelist;
|
||||
export const PIN_LIST_KIND = kinds.Pinlist;
|
||||
export const BOOKMARK_LIST_KIND = kinds.BookmarkList;
|
||||
export const COMMUNITIES_LIST_KIND = kinds.CommunitiesList;
|
||||
export const CHANNELS_LIST_KIND = kinds.PublicChatsList;
|
||||
|
||||
export const PEOPLE_LIST_KIND = kinds.Followsets;
|
||||
export const NOTE_LIST_KIND = 30001;
|
||||
export const BOOKMARK_LIST_SET_KIND = kinds.Bookmarksets;
|
||||
export const LIST_KINDS = [
|
||||
kinds.Mutelist,
|
||||
kinds.Pinlist,
|
||||
kinds.RelayList,
|
||||
kinds.BookmarkList,
|
||||
kinds.CommunitiesList,
|
||||
kinds.PublicChatsList,
|
||||
kinds.BlockedRelaysList,
|
||||
kinds.SearchRelaysList,
|
||||
kinds.InterestsList,
|
||||
kinds.UserEmojiList,
|
||||
kinds.DirectMessageRelaysList,
|
||||
];
|
||||
export const SET_KINDS = [
|
||||
kinds.Followsets,
|
||||
kinds.Bookmarksets,
|
||||
kinds.Genericlists,
|
||||
kinds.Relaysets,
|
||||
kinds.Interestsets,
|
||||
kinds.Emojisets,
|
||||
kinds.Curationsets,
|
||||
];
|
||||
|
||||
export function getListName(event: NostrEvent) {
|
||||
if (event.kind === kinds.Contacts) return "Following";
|
||||
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";
|
||||
if (event.kind === kinds.Mutelist) return "Mute";
|
||||
if (event.kind === kinds.Pinlist) return "Pins";
|
||||
if (event.kind === kinds.BookmarkList) return "Bookmarks";
|
||||
if (event.kind === kinds.CommunitiesList) return "Communities";
|
||||
if (event.kind === kinds.InterestsList) return "Interests";
|
||||
if (event.kind === kinds.PublicChatsList) return "Public Chats";
|
||||
|
||||
return (
|
||||
event.tags.find((t) => t[0] === "name")?.[1] ||
|
||||
event.tags.find((t) => t[0] === "title")?.[1] ||
|
||||
event.tags.find((t) => t[0] === "name")?.[1] ||
|
||||
event.tags.find(isDTag)?.[1]
|
||||
);
|
||||
}
|
||||
@ -41,18 +57,13 @@ export function setListDescription(draft: EventTemplate, description: string) {
|
||||
export function isJunkList(event: NostrEvent) {
|
||||
const name = event.tags.find(isDTag)?.[1];
|
||||
if (!name) return false;
|
||||
if (event.kind !== PEOPLE_LIST_KIND) return false;
|
||||
if (event.kind !== kinds.Followsets) return false;
|
||||
return /^(chats\/([0-9a-f]{64}|null)|notifications)\/lastOpened$/.test(name);
|
||||
}
|
||||
|
||||
/** Check if is kind is list */
|
||||
export function isSpecialListKind(kind: number) {
|
||||
return (
|
||||
kind === kinds.Contacts ||
|
||||
kind === MUTE_LIST_KIND ||
|
||||
kind === PIN_LIST_KIND ||
|
||||
kind === BOOKMARK_LIST_KIND ||
|
||||
kind === COMMUNITIES_LIST_KIND ||
|
||||
kind === CHANNELS_LIST_KIND
|
||||
);
|
||||
return kind === kinds.Contacts || LIST_KINDS.includes(kind);
|
||||
}
|
||||
|
||||
export function cloneList(list: NostrEvent, keepCreatedAt = false): EventTemplate {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { DraftNostrEvent, NostrEvent, isPTag } from "../../types/nostr-event";
|
||||
import { MUTE_LIST_KIND, getPubkeysFromList, isPubkeyInList, listAddPerson, listRemovePerson } from "./lists";
|
||||
import { getPubkeysFromList, isPubkeyInList, listAddPerson, listRemovePerson } from "./lists";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
export function getPubkeysFromMuteList(muteList: NostrEvent | DraftNostrEvent) {
|
||||
const expirations = getPubkeysExpiration(muteList);
|
||||
@ -37,7 +38,7 @@ export function createEmptyMuteList(): DraftNostrEvent {
|
||||
created_at: dayjs().unix(),
|
||||
content: "",
|
||||
tags: [],
|
||||
kind: MUTE_LIST_KIND,
|
||||
kind: kinds.Mutelist,
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,7 +71,7 @@ export function pruneExpiredPubkeys(muteList: NostrEvent | DraftNostrEvent) {
|
||||
const expirations = getPubkeysExpiration(muteList);
|
||||
const now = dayjs().unix();
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: MUTE_LIST_KIND,
|
||||
kind: kinds.Mutelist,
|
||||
content: muteList.content,
|
||||
created_at: now,
|
||||
tags: muteList.tags.filter((tag) => {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { getEventCoordinate } from "../helpers/nostr/event";
|
||||
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)], kinds: [COMMUNITIES_LIST_KIND] });
|
||||
return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [kinds.CommunitiesList] });
|
||||
}
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../types/nostr-event";
|
||||
import { useSigningContext } from "../providers/global/signing-provider";
|
||||
import userUserBookmarksList from "./use-user-bookmarks-list";
|
||||
import { getEventCoordinate, isReplaceable, pointerMatchEvent } from "../helpers/nostr/event";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
listAddCoordinate,
|
||||
listAddEvent,
|
||||
listRemoveCoordinate,
|
||||
listRemoveEvent,
|
||||
} from "../helpers/nostr/lists";
|
||||
import { listAddCoordinate, listAddEvent, listRemoveCoordinate, listRemoveEvent } from "../helpers/nostr/lists";
|
||||
import { usePublishEvent } from "../providers/global/publish-provider";
|
||||
|
||||
export default function useEventBookmarkActions(event: NostrEvent) {
|
||||
@ -28,7 +23,7 @@ export default function useEventBookmarkActions(event: NostrEvent) {
|
||||
const removeBookmark = useCallback(async () => {
|
||||
setLoading(true);
|
||||
let draft: DraftNostrEvent = {
|
||||
kind: BOOKMARK_LIST_KIND,
|
||||
kind: kinds.BookmarkList,
|
||||
content: bookmarkList?.content ?? "",
|
||||
tags: bookmarkList?.tags ?? [],
|
||||
created_at: dayjs().unix(),
|
||||
@ -46,7 +41,7 @@ export default function useEventBookmarkActions(event: NostrEvent) {
|
||||
const addBookmark = useCallback(async () => {
|
||||
setLoading(true);
|
||||
let draft: DraftNostrEvent = {
|
||||
kind: BOOKMARK_LIST_KIND,
|
||||
kind: kinds.BookmarkList,
|
||||
content: bookmarkList?.content ?? "",
|
||||
tags: bookmarkList?.tags ?? [],
|
||||
created_at: dayjs().unix(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import { USER_EMOJI_LIST_KIND } from "../helpers/nostr/emoji-packs";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export const FAVORITE_LISTS_IDENTIFIER = "nostrudel-favorite-lists";
|
||||
@ -13,7 +14,7 @@ export default function useFavoriteEmojiPacks(
|
||||
const account = useCurrentAccount();
|
||||
const key = pubkey || account?.pubkey;
|
||||
const favoritePacks = useReplaceableEvent(
|
||||
key ? { kind: USER_EMOJI_LIST_KIND, pubkey: key } : undefined,
|
||||
key ? { kind: kinds.Emojisets, pubkey: key } : undefined,
|
||||
additionalRelays,
|
||||
opts,
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { BOOKMARK_LIST_KIND, getAddressPointersFromList, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { getAddressPointersFromList, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
@ -9,7 +10,7 @@ export default function userUserBookmarksList(pubkey?: string, relays: string[]
|
||||
const account = useCurrentAccount();
|
||||
const key = pubkey ?? account?.pubkey;
|
||||
|
||||
const list = useReplaceableEvent(key ? { kind: BOOKMARK_LIST_KIND, pubkey: key } : undefined, relays, opts);
|
||||
const list = useReplaceableEvent(key ? { kind: kinds.BookmarkList, pubkey: key } : undefined, relays, opts);
|
||||
|
||||
const addressPointers = useMemo(() => (list ? getAddressPointersFromList(list) : []), [list]);
|
||||
const eventPointers = useMemo(() => (list ? getEventPointersFromList(list) : []), [list]);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { CHANNELS_LIST_KIND, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
@ -7,7 +8,7 @@ export default function useUserChannelsList(pubkey?: string, relays: string[] =
|
||||
const account = useCurrentAccount();
|
||||
const key = pubkey ?? account?.pubkey;
|
||||
|
||||
const list = useReplaceableEvent(key ? { kind: CHANNELS_LIST_KIND, pubkey: key } : undefined, relays, opts);
|
||||
const list = useReplaceableEvent(key ? { kind: kinds.PublicChatsList, pubkey: key } : undefined, relays, opts);
|
||||
|
||||
const pointers = list ? getEventPointersFromList(list) : [];
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
|
||||
import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getAddressPointersFromList } from "../helpers/nostr/lists";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
|
||||
import { getAddressPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
@ -14,7 +16,7 @@ export default function useUserCommunitiesList(pubkey?: string, relays?: Iterabl
|
||||
const oldList = useReplaceableEvent(
|
||||
key
|
||||
? {
|
||||
kind: NOTE_LIST_KIND,
|
||||
kind: kinds.Genericlists,
|
||||
identifier: SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER,
|
||||
pubkey: key,
|
||||
}
|
||||
@ -22,7 +24,7 @@ export default function useUserCommunitiesList(pubkey?: string, relays?: Iterabl
|
||||
[],
|
||||
opts,
|
||||
);
|
||||
const list = useReplaceableEvent(key ? { kind: COMMUNITIES_LIST_KIND, pubkey: key } : undefined, relays, opts);
|
||||
const list = useReplaceableEvent(key ? { kind: kinds.CommunitiesList, pubkey: key } : undefined, relays, opts);
|
||||
|
||||
let useList = list || oldList;
|
||||
|
||||
@ -32,7 +34,7 @@ export default function useUserCommunitiesList(pubkey?: string, relays?: Iterabl
|
||||
}
|
||||
|
||||
const pointers = useList
|
||||
? getAddressPointersFromList(useList).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND)
|
||||
? getAddressPointersFromList(useList).filter((cord) => cord.kind === kinds.CommunityDefinition)
|
||||
: [];
|
||||
|
||||
return { list: useList, pointers };
|
||||
|
@ -1,28 +1,29 @@
|
||||
import { useCallback } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useStoreQuery } from "applesauce-react/hooks";
|
||||
import { TimelineQuery } from "applesauce-core/queries";
|
||||
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND, isJunkList } from "../helpers/nostr/lists";
|
||||
import { SET_KINDS, isJunkList } from "../helpers/nostr/lists";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { truncateId } from "../helpers/string";
|
||||
import userSetsService from "../services/user-sets";
|
||||
|
||||
export default function useUserLists(pubkey?: string, additionalRelays?: Iterable<string>) {
|
||||
export default function useUserSets(pubkey?: string, additionalRelays?: Iterable<string>, alwaysRequest?: boolean) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return !isJunkList(event);
|
||||
}, []);
|
||||
|
||||
const { timeline } = useTimelineLoader(
|
||||
`${truncateId(pubkey ?? "anon")}-lists`,
|
||||
readRelays,
|
||||
pubkey
|
||||
? {
|
||||
authors: [pubkey],
|
||||
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
|
||||
}
|
||||
: undefined,
|
||||
{ eventFilter },
|
||||
useEffect(() => {
|
||||
if (pubkey) userSetsService.requestSets(pubkey, readRelays, alwaysRequest);
|
||||
}, [pubkey, readRelays.urls.join("|"), alwaysRequest]);
|
||||
|
||||
return (
|
||||
useStoreQuery(
|
||||
TimelineQuery,
|
||||
pubkey
|
||||
? [
|
||||
{
|
||||
authors: [pubkey],
|
||||
kinds: SET_KINDS,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
)?.filter((e) => !isJunkList(e)) ?? []
|
||||
);
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { MUTE_LIST_KIND } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export default function useUserMuteList(
|
||||
@ -7,5 +7,5 @@ export default function useUserMuteList(
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
return useReplaceableEvent(pubkey && { kind: MUTE_LIST_KIND, pubkey }, additionalRelays, opts);
|
||||
return useReplaceableEvent(pubkey && { kind: kinds.Mutelist, pubkey }, additionalRelays, opts);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { PEOPLE_LIST_KIND, getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import useUserMuteList from "./use-user-mute-list";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
@ -12,7 +13,7 @@ export default function useUserMuteLists(
|
||||
) {
|
||||
const muteList = useUserMuteList(pubkey, additionalRelays, opts);
|
||||
const altMuteList = useReplaceableEvent(
|
||||
pubkey && { kind: PEOPLE_LIST_KIND, pubkey, identifier: "mute" },
|
||||
pubkey && { kind: kinds.Followsets, pubkey, identifier: "mute" },
|
||||
additionalRelays,
|
||||
opts,
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PIN_LIST_KIND, getPointersFromList } from "../helpers/nostr/lists";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { getPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
@ -7,7 +8,7 @@ export default function useUserPinList(pubkey?: string, relays: string[] = [], o
|
||||
const account = useCurrentAccount();
|
||||
const key = pubkey ?? account?.pubkey;
|
||||
|
||||
const list = useReplaceableEvent(key ? { kind: PIN_LIST_KIND, pubkey: key } : undefined, relays, opts);
|
||||
const list = useReplaceableEvent(key ? { kind: kinds.Pinlist, pubkey: key } : undefined, relays, opts);
|
||||
|
||||
const pointers = list ? getPointersFromList(list) : [];
|
||||
|
||||
|
@ -30,6 +30,8 @@ import { Tag } from "../../types/nostr-event";
|
||||
import { EmbedEvent } from "../../components/embed-event";
|
||||
import { useWriteRelays } from "../../hooks/use-client-relays";
|
||||
import { usePublishEvent } from "../global/publish-provider";
|
||||
import { useUserOutbox } from "../../hooks/use-user-mailboxes";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
|
||||
type DeleteEventContextType = {
|
||||
isLoading: boolean;
|
||||
@ -46,13 +48,15 @@ export function useDeleteEventContext() {
|
||||
}
|
||||
|
||||
export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||
const account = useCurrentAccount();
|
||||
const publish = usePublishEvent();
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [event, setEvent] = useState<Event>();
|
||||
const [defer, setDefer] = useState<Deferred<void>>();
|
||||
const [reason, setReason] = useState("");
|
||||
|
||||
const writeRelays = useWriteRelays();
|
||||
const outbox = useUserOutbox(account?.pubkey);
|
||||
const writeRelays = useWriteRelays(outbox);
|
||||
|
||||
const deleteEvent = useCallback((event: Event) => {
|
||||
setEvent(event);
|
||||
@ -77,7 +81,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||
content: reason,
|
||||
created_at: dayjs().unix(),
|
||||
};
|
||||
await publish("Delete", draft, undefined, false);
|
||||
await publish("Delete", draft);
|
||||
defer?.resolve();
|
||||
} catch (e) {
|
||||
defer?.reject();
|
||||
|
@ -1,7 +1,4 @@
|
||||
import { DBSchema } from "idb";
|
||||
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { RelayInformationDocument } from "../relay-info";
|
||||
import { AppSettings } from "../../helpers/app-settings";
|
||||
|
||||
export interface SchemaV1 {
|
||||
@ -30,7 +27,7 @@ export interface SchemaV1 {
|
||||
value: { name: string; domain: string; pubkey: string; relays: string[]; updated: number };
|
||||
indexes: { name: string; domain: string; pubkey: string; updated: number };
|
||||
};
|
||||
relayInfo: { key: string; value: RelayInformationDocument };
|
||||
relayInfo: { key: string; value: any };
|
||||
relayScoreboardStats: {
|
||||
key: string;
|
||||
value: {
|
||||
|
@ -1,79 +1,43 @@
|
||||
import db from "./db";
|
||||
import { fetchWithProxy } from "../helpers/request";
|
||||
import { isHexKey } from "../helpers/nip19";
|
||||
import { validateRelayURL } from "../helpers/relay";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import { Nip11Registry } from "rx-nostr";
|
||||
|
||||
export type RelayInformationDocument = {
|
||||
name: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
pubkey?: string;
|
||||
contact: string;
|
||||
supported_nips?: number[];
|
||||
software: string;
|
||||
version: string;
|
||||
payments_url?: string;
|
||||
};
|
||||
import db from "./db";
|
||||
import { logger } from "../helpers/debug";
|
||||
|
||||
function sanitizeInfo(info: RelayInformationDocument) {
|
||||
if (info.pubkey && !isHexKey(info.pubkey)) {
|
||||
delete info.pubkey;
|
||||
const log = logger.extend("Nip11Registry");
|
||||
|
||||
const tx = db.transaction("relayInfo", "readonly");
|
||||
let loaded = 0;
|
||||
let cursor = await tx.objectStore("relayInfo").openCursor();
|
||||
while (cursor) {
|
||||
try {
|
||||
Nip11Registry.set(cursor.key, cursor.value);
|
||||
loaded++;
|
||||
} catch (error) {}
|
||||
cursor = await cursor.continue();
|
||||
}
|
||||
|
||||
log(`Loaded ${loaded} relay info`);
|
||||
|
||||
async function getInfo(relay: string | AbstractRelay, alwaysFetch = false) {
|
||||
relay = typeof relay === "string" ? relay : relay.url;
|
||||
|
||||
let info = Nip11Registry.get(relay);
|
||||
|
||||
if (!info || alwaysFetch) {
|
||||
info = await Nip11Registry.fetch(relay);
|
||||
db.put("relayInfo", info, relay);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
async function fetchInfo(relay: string) {
|
||||
const url = validateRelayURL(relay);
|
||||
url.protocol = url.protocol === "ws:" ? "http" : "https";
|
||||
|
||||
const infoDoc = await fetchWithProxy(url, { headers: { Accept: "application/nostr+json" } }).then(
|
||||
(res) => res.json() as Promise<RelayInformationDocument>,
|
||||
);
|
||||
|
||||
sanitizeInfo(infoDoc);
|
||||
|
||||
memoryCache.set(relay, infoDoc);
|
||||
await db.put("relayInfo", infoDoc, relay);
|
||||
|
||||
return infoDoc;
|
||||
}
|
||||
|
||||
const memoryCache = new Map<string, RelayInformationDocument>();
|
||||
async function getInfo(relay: string, alwaysFetch = false) {
|
||||
const url = validateRelayURL(relay).toString();
|
||||
if (memoryCache.has(url) && !alwaysFetch) return memoryCache.get(url)!;
|
||||
|
||||
const cached = await db.get("relayInfo", url);
|
||||
if (cached && !alwaysFetch) {
|
||||
memoryCache.set(url, cached);
|
||||
return cached as RelayInformationDocument;
|
||||
}
|
||||
|
||||
return fetchInfo(relay);
|
||||
}
|
||||
|
||||
const pending: Record<string, ReturnType<typeof getInfo> | undefined> = {};
|
||||
function dedupedGetIdentity(relay: string | AbstractRelay, alwaysFetch = false) {
|
||||
relay = typeof relay === "string" ? relay : relay.url;
|
||||
|
||||
const request = pending[relay];
|
||||
if (request) return request;
|
||||
return (pending[relay] = getInfo(relay, alwaysFetch).then((v) => {
|
||||
delete pending[relay];
|
||||
return v;
|
||||
}));
|
||||
}
|
||||
|
||||
export const relayInfoService = {
|
||||
cache: memoryCache,
|
||||
fetchInfo,
|
||||
getInfo: dedupedGetIdentity,
|
||||
};
|
||||
export const relayInfoService = { getInfo };
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.relayInfoService = relayInfoService;
|
||||
// @ts-ignore
|
||||
window.Nip11Registry = Nip11Registry;
|
||||
}
|
||||
|
||||
export default relayInfoService;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { combineLatest, distinct, filter } from "rxjs";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import { USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
|
||||
import { isFromCache } from "applesauce-core/helpers";
|
||||
|
||||
import { COMMON_CONTACT_RELAYS } from "../const";
|
||||
import { logger } from "../helpers/debug";
|
||||
@ -10,13 +12,18 @@ import clientRelaysService from "./client-relays";
|
||||
import { offlineMode } from "./offline-mode";
|
||||
import replaceableEventsService from "./replaceable-events";
|
||||
import { APP_SETTING_IDENTIFIER, APP_SETTINGS_KIND } from "./user-app-settings";
|
||||
import { queryStore } from "./event-store";
|
||||
import { eventStore, queryStore } from "./event-store";
|
||||
import { Account } from "../classes/accounts/account";
|
||||
import { MultiSubscription } from "applesauce-net/subscription";
|
||||
import relayPoolService from "./relay-pool";
|
||||
import { localRelay } from "./local-relay";
|
||||
|
||||
const log = logger.extend("UserEventSync");
|
||||
function downloadEvents(account: Account) {
|
||||
const relays = clientRelaysService.readRelays.value;
|
||||
|
||||
const cleanup: (() => void)[] = [];
|
||||
|
||||
const requestReplaceable = (relays: Iterable<string>, kind: number, d?: string) => {
|
||||
replaceableEventsService.requestEvent(relays, kind, account.pubkey, d, {
|
||||
alwaysRequest: true,
|
||||
@ -43,9 +50,29 @@ function downloadEvents(account: Account) {
|
||||
alwaysRequest: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (mailboxes?.outboxes && mailboxes.outboxes.length > 0) {
|
||||
log(`Loading delete events`);
|
||||
const sub = new MultiSubscription(relayPoolService);
|
||||
sub.setRelays(
|
||||
localRelay
|
||||
? [...mailboxes.outboxes.map((r) => relayPoolService.requestRelay(r)), localRelay as AbstractRelay]
|
||||
: mailboxes.outboxes,
|
||||
);
|
||||
sub.setFilters([{ kinds: [kinds.EventDeletion], authors: [account.pubkey] }]);
|
||||
|
||||
sub.open();
|
||||
sub.onEvent.subscribe((e) => {
|
||||
eventStore.add(e);
|
||||
if (!isFromCache(e) && localRelay) localRelay.publish(e);
|
||||
});
|
||||
|
||||
cleanup.push(() => sub.close());
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
for (const fn of cleanup) fn();
|
||||
mailboxesSub.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
58
src/services/user-sets.ts
Normal file
58
src/services/user-sets.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { localRelay } from "./local-relay";
|
||||
import relayPoolService from "./relay-pool";
|
||||
import Process from "../classes/process";
|
||||
import { LightningIcon } from "../components/icons";
|
||||
import processManager from "./process-manager";
|
||||
import { logger } from "../helpers/debug";
|
||||
import BatchKindPubkeyLoader from "../classes/batch-kind-pubkey-loader";
|
||||
import { eventStore } from "./event-store";
|
||||
import { SET_KINDS } from "../helpers/nostr/lists";
|
||||
|
||||
class UserSetsService {
|
||||
log = logger.extend("UserSetsService");
|
||||
process: Process;
|
||||
|
||||
private loaded = new Map<string, boolean>();
|
||||
loaders = new SuperMap<AbstractRelay, BatchKindPubkeyLoader>((relay) => {
|
||||
const loader = new BatchKindPubkeyLoader(eventStore, relay, this.log.extend(relay.url));
|
||||
this.process.addChild(loader.process);
|
||||
return loader;
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this.process = new Process("UserSetsService", this);
|
||||
this.process.icon = LightningIcon;
|
||||
this.process.active = true;
|
||||
|
||||
processManager.registerProcess(this.process);
|
||||
}
|
||||
|
||||
requestSets(pubkey: string, urls: Iterable<string | URL | AbstractRelay>, alwaysRequest = true) {
|
||||
if (this.loaded.get(pubkey) && !alwaysRequest) return;
|
||||
|
||||
const loaders: BatchKindPubkeyLoader[] = [];
|
||||
|
||||
if (localRelay) loaders.push(this.loaders.get(localRelay as AbstractRelay));
|
||||
|
||||
const relays = relayPoolService.getRelays(urls);
|
||||
for (const relay of relays) loaders.push(this.loaders.get(relay));
|
||||
|
||||
for (const loader of loaders) {
|
||||
for (const kind of SET_KINDS) {
|
||||
loader.requestEvent(kind, pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userSetsService = new UserSetsService();
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.userSetsService = userSetsService;
|
||||
}
|
||||
|
||||
export default userSetsService;
|
@ -1,10 +1,11 @@
|
||||
import { useCallback } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import { CHANNELS_LIST_KIND, listAddEvent, listRemoveEvent } from "../../../helpers/nostr/lists";
|
||||
import { listAddEvent, listRemoveEvent } from "../../../helpers/nostr/lists";
|
||||
import useUserChannelsList from "../../../hooks/use-user-channels-list";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
||||
@ -20,7 +21,7 @@ export default function ChannelJoinButton({
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const favList = {
|
||||
kind: CHANNELS_LIST_KIND,
|
||||
kind: kinds.PublicChatsList,
|
||||
content: list?.content ?? "",
|
||||
created_at: dayjs().unix(),
|
||||
tags: list?.tags ?? [],
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
LinkBox,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useChannelMetadata from "../../../hooks/use-channel-metadata";
|
||||
@ -28,7 +29,6 @@ import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserDnsIdentity from "../../../components/user/user-dns-identity";
|
||||
import ChannelJoinButton from "./channel-join-button";
|
||||
import { ExternalLinkIcon } from "../../../components/icons";
|
||||
import { CHANNELS_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
|
||||
|
||||
@ -43,7 +43,7 @@ function UserCard({ pubkey }: { pubkey: string }) {
|
||||
}
|
||||
function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: Iterable<string> }) {
|
||||
const { loader, timeline: userLists } = useTimelineLoader(`${channel.id}-members`, relays, {
|
||||
kinds: [CHANNELS_LIST_KIND],
|
||||
kinds: [kinds.PublicChatsList],
|
||||
"#e": [channel.id],
|
||||
});
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useCallback } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent, isDTag } from "../../../types/nostr-event";
|
||||
import useUserCommunitiesList from "../../../hooks/use-user-communities-list";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import { getCommunityName } from "../../../helpers/nostr/communities";
|
||||
import { COMMUNITIES_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||
import { listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
||||
@ -24,7 +25,7 @@ export default function CommunityJoinButton({
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const favList = {
|
||||
kind: COMMUNITIES_LIST_KIND,
|
||||
kind: kinds.CommunitiesList,
|
||||
content: list?.content ?? "",
|
||||
created_at: dayjs().unix(),
|
||||
tags: list?.tags.filter((t) => !isDTag(t)) ?? [],
|
||||
|
@ -28,13 +28,7 @@ import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import CommunityCard from "./components/community-card";
|
||||
import CommunityCreateModal, { FormValues } from "./components/community-create-modal";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import {
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
COMMUNITY_DEFINITION_KIND,
|
||||
buildApprovalMap,
|
||||
getCommunityMods,
|
||||
getCommunityName,
|
||||
} from "../../helpers/nostr/communities";
|
||||
import { buildApprovalMap, getCommunityMods, getCommunityName } from "../../helpers/nostr/communities";
|
||||
import { getImageSize } from "../../helpers/image";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
@ -62,7 +56,7 @@ function CommunitiesHomePage() {
|
||||
|
||||
const createCommunity = async (values: FormValues) => {
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: COMMUNITY_DEFINITION_KIND,
|
||||
kind: kinds.CommunityDefinition,
|
||||
created_at: dayjs().unix(),
|
||||
content: "",
|
||||
tags: [["d", values.name]],
|
||||
@ -92,7 +86,7 @@ function CommunitiesHomePage() {
|
||||
readRelays,
|
||||
communityCoordinates.length > 0
|
||||
? {
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, COMMUNITY_APPROVAL_KIND],
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, kinds.CommunityPostApproval],
|
||||
"#a": communityCoordinates.map((p) => createCoordinate(p.kind, p.pubkey, p.identifier)),
|
||||
}
|
||||
: undefined,
|
||||
@ -113,7 +107,7 @@ function CommunitiesHomePage() {
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && (showUnapproved.isOpen ? true : approvalMap.has(e.id)))
|
||||
.filter((e) => e.kind !== kinds.CommunityPostApproval && (showUnapproved.isOpen ? true : approvalMap.has(e.id)))
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
|
@ -5,10 +5,9 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import {
|
||||
getCommunityRelays as getCommunityRelays,
|
||||
getCommunityRelays,
|
||||
getCommunityImage,
|
||||
getCommunityName,
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
getCommunityMods,
|
||||
buildApprovalMap,
|
||||
} from "../../helpers/nostr/communities";
|
||||
@ -52,7 +51,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
||||
const communityRelays = getCommunityRelays(community);
|
||||
const readRelays = useReadRelays(communityRelays);
|
||||
const { loader } = useTimelineLoader(`${getEventUID(community)}-timeline`, readRelays, {
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, COMMUNITY_APPROVAL_KIND],
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, kinds.CommunityPostApproval],
|
||||
"#a": [communityCoordinate],
|
||||
});
|
||||
|
||||
@ -60,7 +59,9 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
||||
const events = useObservable(loader.timeline) ?? [];
|
||||
const mods = getCommunityMods(community);
|
||||
const approvals = buildApprovalMap(events, mods);
|
||||
const pending = events.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && !approvals.has(e.id) && !muteFilter(e));
|
||||
const pending = events.filter(
|
||||
(e) => e.kind !== kinds.CommunityPostApproval && !approvals.has(e.id) && !muteFilter(e),
|
||||
);
|
||||
|
||||
let active = "newest";
|
||||
if (location.pathname.endsWith("/newest")) active = "newest";
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useMemo } from "react";
|
||||
import { ModalProps } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import {
|
||||
COMMUNITY_DEFINITION_KIND,
|
||||
getCommunityDescription,
|
||||
getCommunityImage,
|
||||
getCommunityLinks,
|
||||
@ -41,7 +41,7 @@ export default function CommunityEditModal({
|
||||
|
||||
const updateCommunity = async (values: FormValues) => {
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: COMMUNITY_DEFINITION_KIND,
|
||||
kind: kinds.CommunityDefinition,
|
||||
created_at: dayjs().unix(),
|
||||
content: "",
|
||||
tags: [["d", getCommunityName(community)]],
|
||||
|
@ -11,13 +11,13 @@ import {
|
||||
ModalProps,
|
||||
SimpleGrid,
|
||||
} from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import { getCommunityRelays } from "../../../helpers/nostr/communities";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { COMMUNITIES_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
@ -45,7 +45,7 @@ export default function CommunityMembersModal({
|
||||
const communityCoordinate = getEventCoordinate(community);
|
||||
const readRelays = useReadRelays(getCommunityRelays(community));
|
||||
const { loader, timeline: lists } = useTimelineLoader(`${communityCoordinate}-members`, readRelays, [
|
||||
{ "#a": [communityCoordinate], kinds: [COMMUNITIES_LIST_KIND] },
|
||||
{ "#a": [communityCoordinate], kinds: [kinds.CommunitiesList] },
|
||||
]);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useCallback } from "react";
|
||||
import { Navigate, useParams } from "react-router-dom";
|
||||
import { Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { COMMUNITY_DEFINITION_KIND, validateCommunity } from "../../helpers/nostr/communities";
|
||||
import { validateCommunity } from "../../helpers/nostr/communities";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
@ -18,7 +19,7 @@ export default function CommunityFindByNameView() {
|
||||
|
||||
// if community name is a naddr, redirect
|
||||
const decoded = safeDecode(community);
|
||||
if (decoded?.type === "naddr" && decoded.data.kind === COMMUNITY_DEFINITION_KIND) {
|
||||
if (decoded?.type === "naddr" && decoded.data.kind === kinds.CommunityDefinition) {
|
||||
return <Navigate to={`/c/${decoded.data.identifier}/${decoded.data.pubkey}`} replace />;
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ export default function CommunityFindByNameView() {
|
||||
const { loader, timeline: communities } = useTimelineLoader(
|
||||
`${community}-find-communities`,
|
||||
readRelays,
|
||||
community ? { kinds: [COMMUNITY_DEFINITION_KIND], "#d": [community] } : undefined,
|
||||
community ? { kinds: [kinds.CommunityDefinition], "#d": [community] } : undefined,
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { Spinner } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import CommunityHomePage from "./community-home";
|
||||
import { getPubkeyFromDecodeResult, isHexKey, safeDecode } from "../../helpers/nip19";
|
||||
|
||||
@ -10,12 +11,12 @@ function useCommunityPointer() {
|
||||
|
||||
const decoded = community ? safeDecode(community) : undefined;
|
||||
if (decoded) {
|
||||
if (decoded.type === "naddr" && decoded.data.kind === COMMUNITY_DEFINITION_KIND) return decoded.data;
|
||||
if (decoded.type === "naddr" && decoded.data.kind === kinds.CommunityDefinition) return decoded.data;
|
||||
} else if (community && pubkey) {
|
||||
const hexPubkey = isHexKey(pubkey) ? pubkey : getPubkeyFromDecodeResult(safeDecode(pubkey));
|
||||
if (!hexPubkey) return;
|
||||
|
||||
return { kind: COMMUNITY_DEFINITION_KIND, pubkey: hexPubkey, identifier: community };
|
||||
return { kind: kinds.CommunityDefinition, pubkey: hexPubkey, identifier: community };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { COMMUNITY_APPROVAL_KIND, buildApprovalMap, getCommunityMods } from "../../../helpers/nostr/communities";
|
||||
import { buildApprovalMap, getCommunityMods } from "../../../helpers/nostr/communities";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import ApprovedEvent from "../components/community-approved-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
export default function CommunityNewestView() {
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
@ -18,7 +19,7 @@ export default function CommunityNewestView() {
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && approvalMap.has(e.id))
|
||||
.filter((e) => e.kind !== kinds.CommunityPostApproval && approvalMap.has(e.id))
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
|
@ -2,16 +2,12 @@ import { useCallback, useState } from "react";
|
||||
import { Button, Flex } from "@chakra-ui/react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import { getEventCoordinate, getEventUID } from "../../../helpers/nostr/event";
|
||||
import {
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
buildApprovalMap,
|
||||
getCommunityMods,
|
||||
getCommunityRelays,
|
||||
} from "../../../helpers/nostr/communities";
|
||||
import { buildApprovalMap, getCommunityMods, getCommunityRelays } from "../../../helpers/nostr/communities";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
@ -40,7 +36,7 @@ function ModPendingPost({ event, community, approvals }: PendingProps) {
|
||||
setLoading(true);
|
||||
const relay = communityRelays[0];
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: COMMUNITY_APPROVAL_KIND,
|
||||
kind: kinds.CommunityPostApproval,
|
||||
content: JSON.stringify(event),
|
||||
created_at: dayjs().unix(),
|
||||
tags: [
|
||||
@ -83,7 +79,9 @@ export default function CommunityPendingView() {
|
||||
|
||||
const mods = getCommunityMods(community);
|
||||
const approvals = buildApprovalMap(events, mods);
|
||||
const pending = events.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && !approvals.has(e.id) && !muteFilter(e));
|
||||
const pending = events.filter(
|
||||
(e) => e.kind !== kinds.CommunityPostApproval && !approvals.has(e.id) && !muteFilter(e),
|
||||
);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useCallback } from "react";
|
||||
import { Flex, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
import { getEmojis } from "applesauce-core/helpers/emoji";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
@ -10,7 +12,6 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import { EMOJI_PACK_KIND, getEmojis } from "../../helpers/nostr/emoji-packs";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function EmojiPacksBrowsePage() {
|
||||
@ -28,7 +29,7 @@ function EmojiPacksBrowsePage() {
|
||||
const { loader, timeline: packs } = useTimelineLoader(
|
||||
`${listId}-browse-emoji-packs`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [EMOJI_PACK_KIND] } : undefined,
|
||||
filter ? { ...filter, kinds: [kinds.Emojisets] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
@ -11,13 +11,12 @@ import {
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
ModalProps,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { EMOJI_PACK_KIND } from "../../../helpers/nostr/emoji-packs";
|
||||
import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import { getSharableEventAddress } from "../../../services/event-relay-hint";
|
||||
@ -33,7 +32,7 @@ export default function EmojiPackCreateModal({ onClose, ...props }: Omit<ModalPr
|
||||
|
||||
const submit = handleSubmit(async (values) => {
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: EMOJI_PACK_KIND,
|
||||
kind: kinds.Emojisets,
|
||||
created_at: dayjs().unix(),
|
||||
content: "",
|
||||
tags: [["d", values.title]],
|
||||
|
@ -11,12 +11,12 @@ import {
|
||||
Image,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { getEmojis, getPackName } from "applesauce-core/helpers/emoji";
|
||||
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
import UserLink from "../../../components/user/user-link";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import EmojiPackFavoriteButton from "./emoji-pack-favorite-button";
|
||||
import { getEmojis, getPackName } from "../../../helpers/nostr/emoji-packs";
|
||||
import EmojiPackMenu from "./emoji-pack-menu";
|
||||
import NoteZapButton from "../../../components/note/note-zap-button";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { IconButton, IconButtonProps } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { USER_EMOJI_LIST_KIND } from "../../../helpers/nostr/emoji-packs";
|
||||
import useFavoriteEmojiPacks from "../../../hooks/use-favorite-emoji-packs";
|
||||
import { listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
@ -22,7 +22,7 @@ export default function EmojiPackFavoriteButton({
|
||||
|
||||
const handleClick = async () => {
|
||||
const prev: DraftNostrEvent = favoritePacks || {
|
||||
kind: USER_EMOJI_LIST_KIND,
|
||||
kind: kinds.UserEmojiList,
|
||||
created_at: dayjs().unix(),
|
||||
content: "",
|
||||
tags: [],
|
||||
|
@ -18,6 +18,8 @@ import {
|
||||
TagLabel,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { getEmojis, getPackName } from "applesauce-core/helpers/emoji";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
@ -26,7 +28,6 @@ import { useDeleteEventContext } from "../../providers/route/delete-event-provid
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import EmojiPackMenu from "./components/emoji-pack-menu";
|
||||
import EmojiPackFavoriteButton from "./components/emoji-pack-favorite-button";
|
||||
import { EMOJI_PACK_KIND, getEmojis, getPackName } from "../../helpers/nostr/emoji-packs";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import UserAvatarLink from "../../components/user/user-avatar-link";
|
||||
@ -121,7 +122,7 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
|
||||
};
|
||||
const saveEdit = async () => {
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: EMOJI_PACK_KIND,
|
||||
kind: kinds.Emojisets,
|
||||
content: pack.content || "",
|
||||
created_at: dayjs().unix(),
|
||||
tags: [...pack.tags.filter((t) => t[0] !== "emoji"), ...draftEmojis.map(({ name, url }) => ["emoji", name, url])],
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Button, Flex, Heading, Link, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
import { getEventCoordinate, getEventUID } from "../../helpers/nostr/event";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { EMOJI_PACK_KIND, getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import { getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import useFavoriteEmojiPacks from "../../hooks/use-favorite-emoji-packs";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
@ -25,7 +25,7 @@ function UserEmojiPackMangerPage() {
|
||||
account.pubkey
|
||||
? {
|
||||
authors: [account.pubkey],
|
||||
kinds: [EMOJI_PACK_KIND],
|
||||
kinds: [kinds.Emojisets],
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Link,
|
||||
@ -12,10 +11,11 @@ import {
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useUserLists from "../../../hooks/use-user-lists";
|
||||
import useUserSets from "../../../hooks/use-user-lists";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { PEOPLE_LIST_KIND, getListName, getPubkeysFromList } from "../../../helpers/nostr/lists";
|
||||
import { getListName, getPubkeysFromList } from "../../../helpers/nostr/lists";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
@ -49,7 +49,7 @@ function Feed({ list, ...props }: { list: NostrEvent } & Omit<CardProps, "childr
|
||||
export default function FeedsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
const account = useCurrentAccount();
|
||||
const contacts = useUserContactList(account?.pubkey);
|
||||
const myLists = useUserLists(account?.pubkey).filter((list) => list.kind === PEOPLE_LIST_KIND);
|
||||
const myLists = useUserSets(account?.pubkey).filter((list) => list.kind === kinds.Followsets);
|
||||
const { lists: favoriteLists } = useFavoriteLists(account?.pubkey);
|
||||
|
||||
const { recent: recentFeeds, useThing: useFeed } = useRecentIds("feeds", 4);
|
||||
|
@ -2,11 +2,7 @@ import { Alert, AlertIcon, AlertTitle, Spinner } from "@chakra-ui/react";
|
||||
import { Navigate, useParams } from "react-router-dom";
|
||||
import { NostrEvent, kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
|
||||
import { FLARE_VIDEO_KIND } from "../../helpers/nostr/video";
|
||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||
@ -52,11 +48,12 @@ function RenderRedirect({ event, link }: { event?: NostrEvent; link: string }) {
|
||||
if (k === kinds.ShortTextNote) return <Navigate to={`/n/${link}`} replace />;
|
||||
if (k === TORRENT_KIND) return <Navigate to={`/torrents/${link}`} replace />;
|
||||
if (k === kinds.LiveEvent) return <Navigate to={`/streams/${link}`} replace />;
|
||||
if (k === EMOJI_PACK_KIND) return <Navigate to={`/emojis/${link}`} replace />;
|
||||
if (k === NOTE_LIST_KIND) return <Navigate to={`/lists/${link}`} replace />;
|
||||
if (k === PEOPLE_LIST_KIND) return <Navigate to={`/lists/${link}`} replace />;
|
||||
if (k === kinds.Emojisets) return <Navigate to={`/emojis/${link}`} replace />;
|
||||
if (k === kinds.Genericlists) return <Navigate to={`/lists/${link}`} replace />;
|
||||
if (k === kinds.Followsets) return <Navigate to={`/lists/${link}`} replace />;
|
||||
if (k === kinds.Bookmarksets) return <Navigate to={`/lists/${link}`} replace />;
|
||||
if (k === kinds.BadgeDefinition) return <Navigate to={`/badges/${link}`} replace />;
|
||||
if (k === COMMUNITY_DEFINITION_KIND) return <Navigate to={`/c/${link}`} replace />;
|
||||
if (k === kinds.CommunityDefinition) return <Navigate to={`/c/${link}`} replace />;
|
||||
if (k === FLARE_VIDEO_KIND) return <Navigate to={`/videos/${link}`} replace />;
|
||||
if (k === kinds.ChannelCreation) return <Navigate to={`/channels/${link}`} replace />;
|
||||
if (k === kinds.ShortTextNote) return <Navigate to={`/n/${link}`} replace />;
|
||||
|
@ -1,17 +1,11 @@
|
||||
import { Flex, Select, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import {
|
||||
MUTE_LIST_KIND,
|
||||
NOTE_LIST_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
getEventPointersFromList,
|
||||
getListName,
|
||||
getPubkeysFromList,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import { getEventPointersFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { useCallback, useState } from "react";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
@ -24,7 +18,7 @@ function BrowseListPage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
const showEmpty = useDisclosure();
|
||||
const showMute = useDisclosure();
|
||||
const [listKind, setListKind] = useState(PEOPLE_LIST_KIND);
|
||||
const [listKind, setListKind] = useState(kinds.Followsets);
|
||||
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
@ -33,8 +27,8 @@ function BrowseListPage() {
|
||||
return false;
|
||||
|
||||
if (
|
||||
(!showMute.isOpen && event.kind === PEOPLE_LIST_KIND && getListName(event) === "mute") ||
|
||||
event.kind === MUTE_LIST_KIND
|
||||
(!showMute.isOpen && event.kind === kinds.Followsets && getListName(event) === "mute") ||
|
||||
event.kind === kinds.Mutelist
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
@ -45,7 +39,7 @@ function BrowseListPage() {
|
||||
const { loader, timeline: lists } = useTimelineLoader(
|
||||
`${listId}-lists`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND] } : undefined,
|
||||
filter ? { ...filter, kinds: [kinds.Followsets, kinds.Genericlists, kinds.Bookmarksets] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
@ -56,8 +50,9 @@ function BrowseListPage() {
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Select w="sm" value={listKind} onChange={(e) => setListKind(parseInt(e.target.value))}>
|
||||
<option value={PEOPLE_LIST_KIND}>People List</option>
|
||||
<option value={NOTE_LIST_KIND}>Note List</option>
|
||||
<option value={kinds.Followsets}>People list</option>
|
||||
<option value={kinds.Genericlists}>Note list</option>
|
||||
<option value={kinds.Bookmarksets}>Bookmark sets</option>
|
||||
</Select>
|
||||
<Switch isChecked={showEmpty.isOpen} onChange={showEmpty.onToggle} whiteSpace="pre">
|
||||
Show Empty
|
||||
|
@ -30,7 +30,6 @@ import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import ListFavoriteButton from "./list-favorite-button";
|
||||
import ListMenu from "./list-menu";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
|
||||
import { CommunityIcon, NotesIcon } from "../../../components/icons";
|
||||
import User01 from "../../../components/icons/user-01";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
@ -46,7 +45,7 @@ export function ListCardContent({ list, ...props }: Omit<CardProps, "children">
|
||||
const people = getPubkeysFromList(list);
|
||||
const notes = getEventPointersFromList(list);
|
||||
const coordinates = getAddressPointersFromList(list);
|
||||
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
|
||||
const communities = coordinates.filter((cord) => cord.kind === kinds.CommunityDefinition);
|
||||
const articles = coordinates.filter((cord) => cord.kind === kinds.LongFormArticle);
|
||||
const references = getReferencesFromList(list);
|
||||
|
||||
|
@ -1,17 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { IconButton, IconButtonProps } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import useFavoriteLists, { FAVORITE_LISTS_IDENTIFIER } from "../../../hooks/use-favorite-lists";
|
||||
import {
|
||||
NOTE_LIST_KIND,
|
||||
isSpecialListKind,
|
||||
listAddCoordinate,
|
||||
listRemoveCoordinate,
|
||||
} from "../../../helpers/nostr/lists";
|
||||
import { isSpecialListKind, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
||||
export default function ListFavoriteButton({
|
||||
@ -26,8 +22,8 @@ export default function ListFavoriteButton({
|
||||
|
||||
if (isSpecialListKind(list.kind)) return null;
|
||||
|
||||
// NOTE: dont show favorite button for note lists
|
||||
if (list.kind === NOTE_LIST_KIND) return null;
|
||||
// NOTE: don't show favorite button for note lists
|
||||
if (list.kind === kinds.Genericlists) return null;
|
||||
|
||||
const handleClick = async () => {
|
||||
const prev: DraftNostrEvent = favoriteList || {
|
||||
|
@ -4,10 +4,9 @@ import { kinds } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { PEOPLE_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
|
||||
export default function ListFeedButton({ list, ...props }: { list: NostrEvent } & Omit<ButtonProps, "children">) {
|
||||
const shouldShowFeedButton = list.kind === PEOPLE_LIST_KIND || list.kind === kinds.Contacts;
|
||||
const shouldShowFeedButton = list.kind === kinds.Followsets || list.kind === kinds.Contacts;
|
||||
|
||||
if (!shouldShowFeedButton) return null;
|
||||
|
||||
|
@ -1,34 +1,20 @@
|
||||
import { Image, MenuItem } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent, isPTag } from "../../../types/nostr-event";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import DeleteEventMenuItem from "../../../components/common-menu-items/delete-event";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import { isSpecialListKind } from "../../../helpers/nostr/lists";
|
||||
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
|
||||
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
|
||||
|
||||
export default function ListMenu({ list, ...props }: { list: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
|
||||
const address = useShareableEventAddress(list);
|
||||
const isSpecial = isSpecialListKind(list.kind);
|
||||
|
||||
const hasPeople = list.tags.some(isPTag);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={list} />
|
||||
<CopyEmbedCodeMenuItem event={list} />
|
||||
{!isSpecial && <DeleteEventMenuItem event={list} label="Delete List" />}
|
||||
{hasPeople && (
|
||||
<MenuItem
|
||||
icon={<Image w="4" h="4" src="https://framerusercontent.com/images/3S3Pyvkh2tEvvKyX47QrUq7XQLk.png" />}
|
||||
onClick={() => window.open(`https://www.makeprisms.com/create/${address}`, "_blank")}
|
||||
>
|
||||
Create $prism
|
||||
</MenuItem>
|
||||
)}
|
||||
<DebugEventMenuItem event={list} />
|
||||
</DotsMenuButton>
|
||||
</>
|
||||
|
@ -14,29 +14,30 @@ import {
|
||||
ModalProps,
|
||||
Select,
|
||||
} from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export type NewListModalProps = Omit<ModalProps, "children"> & {
|
||||
export type NewSetModalProps = Omit<ModalProps, "children"> & {
|
||||
onCreated?: (list: NostrEvent) => void;
|
||||
initKind?: number;
|
||||
allowSelectKind?: boolean;
|
||||
};
|
||||
|
||||
export default function NewListModal({
|
||||
export default function NewSetModal({
|
||||
onClose,
|
||||
onCreated,
|
||||
initKind,
|
||||
allowSelectKind = true,
|
||||
...props
|
||||
}: NewListModalProps) {
|
||||
}: NewSetModalProps) {
|
||||
const publish = usePublishEvent();
|
||||
const { handleSubmit, register, formState } = useForm({
|
||||
defaultValues: {
|
||||
kind: initKind || PEOPLE_LIST_KIND,
|
||||
kind: initKind || kinds.Followsets,
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
@ -45,7 +46,10 @@ export default function NewListModal({
|
||||
const draft: DraftNostrEvent = {
|
||||
content: "",
|
||||
created_at: dayjs().unix(),
|
||||
tags: [["d", values.name]],
|
||||
tags: [
|
||||
["d", nanoid()],
|
||||
["title", values.name],
|
||||
],
|
||||
kind: values.kind,
|
||||
};
|
||||
const pub = await publish("Create list", draft);
|
||||
@ -66,8 +70,9 @@ export default function NewListModal({
|
||||
<FormControl isRequired>
|
||||
<FormLabel>List kind</FormLabel>
|
||||
<Select {...register("kind", { valueAsNumber: true, required: true })}>
|
||||
<option value={PEOPLE_LIST_KIND}>People List</option>
|
||||
<option value={NOTE_LIST_KIND}>Note List</option>
|
||||
<option value={kinds.Followsets}>People List</option>
|
||||
<option value={kinds.Genericlists}>Generic List</option>
|
||||
<option value={kinds.Bookmarksets}>Bookmark set</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
@ -1,34 +1,27 @@
|
||||
import { Button, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Flex, Heading, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { useNavigate, Link as RouterLink, Navigate } from "react-router-dom";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ExternalLinkIcon, PlusCircleIcon } from "../../components/icons";
|
||||
import ListCard from "./components/list-card";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import useUserLists from "../../hooks/use-user-lists";
|
||||
import NewListModal from "./components/new-list-modal";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
COMMUNITIES_LIST_KIND,
|
||||
MUTE_LIST_KIND,
|
||||
NOTE_LIST_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
PIN_LIST_KIND,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import useUserSets from "../../hooks/use-user-lists";
|
||||
import NewSetModal from "./components/new-set-modal";
|
||||
import useFavoriteLists from "../../hooks/use-favorite-lists";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { getSharableEventAddress } from "../../services/event-relay-hint";
|
||||
import Plus from "../../components/icons/plus";
|
||||
|
||||
function ListsHomePage() {
|
||||
const account = useCurrentAccount()!;
|
||||
const lists = useUserLists(account.pubkey);
|
||||
const sets = useUserSets(account.pubkey, undefined, true);
|
||||
const { lists: favoriteLists } = useFavoriteLists();
|
||||
const newList = useDisclosure();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const peopleLists = lists.filter((event) => event.kind === PEOPLE_LIST_KIND);
|
||||
const noteLists = lists.filter((event) => event.kind === NOTE_LIST_KIND);
|
||||
const followSets = sets.filter((event) => event.kind === kinds.Followsets);
|
||||
const genericSets = sets.filter((event) => event.kind === kinds.Genericlists);
|
||||
const bookmarkSets = sets.filter((event) => event.kind === kinds.Bookmarksets);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -37,16 +30,7 @@ function ListsHomePage() {
|
||||
Browse Lists
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
as={Link}
|
||||
href="https://listr.lol/"
|
||||
isExternal
|
||||
rightIcon={<ExternalLinkIcon />}
|
||||
leftIcon={<Image src="https://listr.lol/favicon.ico" w="1.2em" />}
|
||||
>
|
||||
Listr
|
||||
</Button>
|
||||
<Button leftIcon={<PlusCircleIcon />} onClick={newList.onOpen} colorScheme="primary">
|
||||
<Button leftIcon={<Plus boxSize={5} />} onClick={newList.onOpen} colorScheme="primary">
|
||||
New List
|
||||
</Button>
|
||||
</Flex>
|
||||
@ -56,30 +40,42 @@ function ListsHomePage() {
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
<ListCard cord={`${kinds.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 />
|
||||
<ListCard cord={`${kinds.Mutelist}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.Pinlist}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.CommunitiesList}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.BookmarkList}:${account.pubkey}`} hideCreator />
|
||||
</SimpleGrid>
|
||||
{peopleLists.length > 0 && (
|
||||
{followSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="lg" mt="2">
|
||||
People lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{peopleLists.map((event) => (
|
||||
{followSets.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
{noteLists.length > 0 && (
|
||||
{genericSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="lg" mt="2">
|
||||
Generic lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{genericSets.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
{bookmarkSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="lg" mt="2">
|
||||
Bookmark lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{noteLists.map((event) => (
|
||||
{genericSets.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
@ -99,7 +95,7 @@ function ListsHomePage() {
|
||||
)}
|
||||
|
||||
{newList.isOpen && (
|
||||
<NewListModal
|
||||
<NewSetModal
|
||||
isOpen
|
||||
onClose={newList.onClose}
|
||||
onCreated={(list) => navigate(`/lists/${getSharableEventAddress(list)}`)}
|
||||
|
@ -25,7 +25,6 @@ import ListMenu from "../components/list-menu";
|
||||
import ListFavoriteButton from "../components/list-favorite-button";
|
||||
import ListFeedButton from "../components/list-feed-button";
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
|
||||
import { EmbedEvent, EmbedEventPointer } from "../../../components/embed-event";
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
@ -48,7 +47,7 @@ function ListPage({ list }: { list: NostrEvent }) {
|
||||
const people = getPubkeysFromList(list);
|
||||
const notes = getEventPointersFromList(list);
|
||||
const coordinates = getAddressPointersFromList(list);
|
||||
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
|
||||
const communities = coordinates.filter((cord) => cord.kind === kinds.CommunityDefinition);
|
||||
const articles = coordinates.filter((cord) => cord.kind === kinds.LongFormArticle);
|
||||
const references = getReferencesFromList(list);
|
||||
|
||||
|
@ -14,14 +14,6 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
import { EmbedEvent } from "../../components/embed-event";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
BOOKMARK_LIST_SET_KIND,
|
||||
COMMUNITIES_LIST_KIND,
|
||||
MUTE_LIST_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
PIN_LIST_KIND,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
|
||||
|
||||
const UnknownEvent = memo(({ event }: { event: NostrEvent }) => {
|
||||
@ -46,15 +38,15 @@ const commonTimelineKinds = [
|
||||
kinds.Contacts,
|
||||
kinds.Metadata,
|
||||
kinds.EncryptedDirectMessage,
|
||||
MUTE_LIST_KIND,
|
||||
kinds.Mutelist,
|
||||
kinds.LiveChatMessage,
|
||||
kinds.EventDeletion,
|
||||
kinds.CommunityPostApproval,
|
||||
BOOKMARK_LIST_KIND,
|
||||
BOOKMARK_LIST_SET_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
PIN_LIST_KIND,
|
||||
COMMUNITIES_LIST_KIND,
|
||||
kinds.BookmarkList,
|
||||
kinds.Bookmarksets,
|
||||
kinds.Followsets,
|
||||
kinds.Pinlist,
|
||||
kinds.CommunitiesList,
|
||||
kinds.ZapGoal,
|
||||
];
|
||||
|
||||
|
@ -2,7 +2,17 @@ import { Badge, Button, ButtonProps, ComponentWithAs, Flex, IconProps, useDisclo
|
||||
import { Filter, kinds, nip19, NostrEvent } from "nostr-tools";
|
||||
import { Link as RouteLink, To } from "react-router-dom";
|
||||
|
||||
import { ArticleIcon, DirectMessagesIcon, ListsIcon, NotesIcon, RepostIcon } from "../../../components/icons";
|
||||
import {
|
||||
ArticleIcon,
|
||||
BookmarkIcon,
|
||||
ChannelsIcon,
|
||||
CommunityIcon,
|
||||
DirectMessagesIcon,
|
||||
ListsIcon,
|
||||
NotesIcon,
|
||||
RelayIcon,
|
||||
RepostIcon,
|
||||
} from "../../../components/icons";
|
||||
import AnnotationQuestion from "../../../components/icons/annotation-question";
|
||||
import { getSharableEventAddress } from "../../../services/event-relay-hint";
|
||||
import { npubEncode } from "nostr-tools/nip19";
|
||||
@ -10,6 +20,7 @@ import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import { useUserOutbox } from "../../../hooks/use-user-mailboxes";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import AlertTriangle from "../../../components/icons/alert-triangle";
|
||||
import MessageSquare02 from "../../../components/icons/message-square-02";
|
||||
|
||||
type KnownKind = {
|
||||
kind: number;
|
||||
@ -70,15 +81,29 @@ const KnownKinds: KnownKind[] = [
|
||||
link: (_e, p) => `/u/${nip19.npubEncode(p)}/dms`,
|
||||
},
|
||||
|
||||
{ kind: kinds.Followsets, name: "Lists", icon: ListsIcon, link: (_e, p) => `/u/${npubEncode(p)}/lists` },
|
||||
{
|
||||
kind: kinds.PublicChatsList,
|
||||
icon: ChannelsIcon,
|
||||
name: "Public Chats",
|
||||
link: (_e, p) => `/u/${npubEncode(p)}/lists`,
|
||||
},
|
||||
|
||||
{ kind: kinds.Followsets, name: "People Lists", icon: ListsIcon, link: (_e, p) => `/u/${npubEncode(p)}/lists` },
|
||||
{ kind: kinds.Genericlists, icon: ListsIcon, name: "Generic Lists", link: (_e, p) => `/u/${npubEncode(p)}/lists` },
|
||||
{ kind: kinds.Relaysets, icon: RelayIcon, name: "Relay Sets" },
|
||||
{ kind: kinds.Bookmarksets, icon: BookmarkIcon, name: "Bookmarks", link: (_e, p) => `/u/${npubEncode(p)}/lists` },
|
||||
|
||||
{ kind: kinds.Report, name: "Report", icon: AlertTriangle, link: (_e, p) => `/u/${npubEncode(p)}/reports` },
|
||||
|
||||
{ kind: kinds.Handlerinformation, name: "Application" },
|
||||
{ kind: kinds.Handlerrecommendation, name: "App recommendation" },
|
||||
|
||||
{ kind: kinds.CommunityDefinition, icon: CommunityIcon, name: "Communities" },
|
||||
|
||||
{ kind: kinds.BadgeAward, name: "Badge Award" },
|
||||
|
||||
{ kind: kinds.LiveChatMessage, icon: MessageSquare02, name: "Stream Chat" },
|
||||
|
||||
// common kinds
|
||||
{ kind: kinds.Metadata, hidden: true },
|
||||
{ kind: kinds.Contacts, hidden: true },
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
@ -8,7 +8,7 @@ import { getEventUID } from "../../helpers/nostr/event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import EmojiPackCard from "../emoji-packs/components/emoji-pack-card";
|
||||
import { EMOJI_PACK_KIND, getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import { getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import useFavoriteEmojiPacks from "../../hooks/use-favorite-emoji-packs";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -19,7 +19,7 @@ export default function UserEmojiPacksTab() {
|
||||
|
||||
const { loader, timeline: packs } = useTimelineLoader(pubkey + "-emoji-packs", readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [EMOJI_PACK_KIND],
|
||||
kinds: [kinds.Emojisets],
|
||||
});
|
||||
|
||||
const favoritePacks = useFavoriteEmojiPacks(pubkey);
|
||||
|
@ -1,104 +1,73 @@
|
||||
import { useCallback } from "react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
MUTE_LIST_KIND,
|
||||
NOTE_LIST_KIND,
|
||||
PEOPLE_LIST_KIND,
|
||||
PIN_LIST_KIND,
|
||||
isJunkList,
|
||||
} from "../../helpers/nostr/lists";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import { isJunkList } from "../../helpers/nostr/lists";
|
||||
import ListCard from "../lists/components/list-card";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import UserName from "../../components/user/user-name";
|
||||
import useUserSets from "../../hooks/use-user-lists";
|
||||
|
||||
export default function UserListsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useAdditionalRelayContext();
|
||||
const sets = useUserSets(pubkey).filter((e) => !isJunkList(e));
|
||||
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return !isJunkList(event);
|
||||
}, []);
|
||||
const { loader, timeline: lists } = useTimelineLoader(
|
||||
pubkey + "-lists",
|
||||
readRelays,
|
||||
[
|
||||
{
|
||||
authors: [pubkey],
|
||||
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
|
||||
},
|
||||
{
|
||||
"#p": [pubkey],
|
||||
kinds: [PEOPLE_LIST_KIND],
|
||||
},
|
||||
],
|
||||
{ eventFilter },
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const peopleLists = lists.filter((event) => event.pubkey === pubkey && event.kind === PEOPLE_LIST_KIND);
|
||||
const noteLists = lists.filter((event) => event.pubkey === pubkey && event.kind === NOTE_LIST_KIND);
|
||||
const otherLists = lists.filter((event) => event.pubkey !== pubkey && event.kind === PEOPLE_LIST_KIND);
|
||||
const followSets = sets.filter((event) => event.pubkey === pubkey && event.kind === kinds.Followsets);
|
||||
const genericSets = sets.filter((event) => event.pubkey === pubkey && event.kind === kinds.Genericlists);
|
||||
const bookmarkSets = sets.filter((event) => event.pubkey === pubkey && event.kind === kinds.Bookmarksets);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Heading size="md" mt="2">
|
||||
Special lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
<ListCard cord={`${kinds.Contacts}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${MUTE_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${PIN_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${BOOKMARK_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
</SimpleGrid>
|
||||
<Heading size="md" mt="2">
|
||||
Special lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
<ListCard cord={`${kinds.Contacts}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.Mutelist}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.Pinlist}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.BookmarkList}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.CommunitiesList}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.PublicChatsList}:${pubkey}`} hideCreator />
|
||||
</SimpleGrid>
|
||||
|
||||
{peopleLists.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
People lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{peopleLists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
{followSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
People lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{followSets.map((set) => (
|
||||
<ListCard key={getEventUID(set)} list={set} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
|
||||
{noteLists.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
Bookmark lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{noteLists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</IntersectionObserverProvider>
|
||||
{genericSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
Generic lists
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{genericSets.map((set) => (
|
||||
<ListCard key={getEventUID(set)} list={set} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Heading size="md" mt="2">
|
||||
Lists <UserName pubkey={pubkey} /> is in
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{otherLists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</IntersectionObserverProvider>
|
||||
{bookmarkSets.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
Bookmark sets
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{bookmarkSets.map((set) => (
|
||||
<ListCard key={getEventUID(set)} list={set} hideCreator />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { memo, useMemo } from "react";
|
||||
import { Flex, Heading, Link, SimpleGrid } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useOutletContext } from "react-router-dom";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import UserAvatarLink from "../../components/user/user-avatar-link";
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { MUTE_LIST_KIND, PEOPLE_LIST_KIND, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -43,8 +44,8 @@ export default function UserMutedByTab() {
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const { loader, timeline: lists } = useTimelineLoader(`${pubkey}-muted-by`, readRelays, [
|
||||
{ kinds: [MUTE_LIST_KIND], "#p": [pubkey] },
|
||||
{ kinds: [PEOPLE_LIST_KIND], "#d": ["mute"], "#p": [pubkey] },
|
||||
{ kinds: [kinds.Mutelist], "#p": [pubkey] },
|
||||
{ kinds: [kinds.Followsets], "#d": ["mute"], "#p": [pubkey] },
|
||||
]);
|
||||
|
||||
const pubkeys = useMemo(() => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user