add option to quote zap receipts

This commit is contained in:
hzrd149 2024-07-19 11:56:57 -05:00
parent 5b842081a3
commit 7a486bbe4f
95 changed files with 430 additions and 403 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add menu to zap events

View File

@ -3,10 +3,10 @@ import dayjs from "dayjs";
import { logger } from "../helpers/debug";
import { safeRelayUrl, validateRelayURL } from "../helpers/relay";
import { offlineMode } from "../services/offline-mode";
import Subject, { PersistentSubject } from "./subject";
import verifyEventMethod from "../services/verify-event";
import SuperMap from "./super-map";
import verifyEventMethod from "../services/verify-event";
import { offlineMode } from "../services/offline-mode";
import processManager from "../services/process-manager";
import signingService from "../services/signing";
import accountService from "../services/account";

View File

@ -1,5 +1,6 @@
import Observable from "zen-observable";
import { nanoid } from "nanoid";
import ControlledObservable from "./controlled-observable";
/** An observable that is always open and stores the last value */

View File

@ -1,11 +1,11 @@
import { MenuItem } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { getSharableEventAddress } from "../../helpers/nip19";
import { CopyToClipboardIcon } from "../icons";
import relayHintService from "../../services/event-relay-hint";
export default function CopyEmbedCodeMenuItem({ event }: { event: NostrEvent }) {
const address = getSharableEventAddress(event);
const address = relayHintService.getSharableEventAddress(event);
return (
address && (

View File

@ -1,13 +1,13 @@
import { useCallback, useContext, useMemo } from "react";
import { MenuItem } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { ExternalLinkIcon } from "../icons";
import { getSharableEventAddress } from "../../helpers/nip19";
import { useCallback, useContext, useMemo } from "react";
import { AppHandlerContext } from "../../providers/route/app-handler-provider";
import relayHintService from "../../services/event-relay-hint";
export default function OpenInAppMenuItem({ event }: { event: NostrEvent }) {
const address = useMemo(() => getSharableEventAddress(event), [event]);
const address = useMemo(() => relayHintService.getSharableEventAddress(event), [event]);
const { openAddress } = useContext(AppHandlerContext);
const open = useCallback(() => address && openAddress(address), [address, openAddress]);

View File

@ -0,0 +1,27 @@
import { useCallback, useContext, useMemo } from "react";
import { MenuItem, useToast } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { QuoteEventIcon } from "../icons";
import useUserMetadata from "../../hooks/use-user-metadata";
import { PostModalContext } from "../../providers/route/post-modal-provider";
import relayHintService from "../../services/event-relay-hint";
export default function QuoteEventMenuItem({ event }: { event: NostrEvent }) {
const toast = useToast();
const address = useMemo(() => relayHintService.getSharableEventAddress(event), [event]);
const metadata = useUserMetadata(event.pubkey);
const { openModal } = useContext(PostModalContext);
const share = useCallback(async () => {
openModal({ cacheFormKey: null, initContent: "\nnostr:" + address });
}, [metadata, event, toast, address]);
return (
address && (
<MenuItem onClick={share} icon={<QuoteEventIcon />}>
Quote Event
</MenuItem>
)
);
}

View File

@ -1,22 +1,18 @@
import { useCallback } from "react";
import { MenuItem, useToast } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { getSharableEventAddress } from "../../helpers/nip19";
import { ShareIcon } from "../icons";
import { Signature } from "@noble/secp256k1";
import { descriptors } from "chart.js/dist/core/core.defaults";
import useUserMetadata from "../../hooks/use-user-metadata";
import { useCallback } from "react";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
let urlShareFailed = false;
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
export default function ShareLinkMenuItem({ event }: { event: NostrEvent }) {
const toast = useToast();
const address = getSharableEventAddress(event);
const address = useShareableEventAddress(event);
const metadata = useUserMetadata(event.pubkey);
const share = useCallback(async () => {
const handleClick = useCallback(async () => {
const data: ShareData = {
url: "https://njump.me/" + address,
title: event.tags.find((t) => t[0] === "title")?.[1] || "Nostr note by " + getDisplayName(metadata, event.pubkey),
@ -40,7 +36,7 @@ export default function ShareLinkMenuItem({ event }: { event: NostrEvent }) {
return (
address && (
<MenuItem onClick={share} icon={<ShareIcon />}>
<MenuItem onClick={handleClick} icon={<ShareIcon />}>
Share Link
</MenuItem>
)

View File

@ -5,10 +5,7 @@ import {
ModalContent,
ModalBody,
ModalCloseButton,
Flex,
Button,
Heading,
Text,
AccordionItem,
Accordion,
AccordionPanel,
@ -18,21 +15,17 @@ import {
ModalHeader,
Code,
AccordionPanelProps,
Card,
} from "@chakra-ui/react";
import { ModalProps } from "@chakra-ui/react";
import { nip19 } from "nostr-tools";
import { getContentTagRefs, getEventUID, getThreadReferences } from "../../helpers/nostr/event";
import { getContentTagRefs, getThreadReferences } from "../../helpers/nostr/event";
import { NostrEvent } from "../../types/nostr-event";
import RawValue from "./raw-value";
import { getSharableEventAddress } from "../../helpers/nip19";
import { usePublishEvent } from "../../providers/global/publish-provider";
import useSubject from "../../hooks/use-subject";
import { getEventRelays } from "../../services/event-relays";
import { RelayFavicon } from "../relay-favicon";
import { CopyIconButton } from "../copy-icon-button";
import DebugEventTags from "./event-tags";
import relayHintService from "../../services/event-relay-hint";
function Section({
label,
@ -75,8 +68,6 @@ export default function EventDebugModal({ event, ...props }: { event: NostrEvent
setLoading(false);
}, []);
const eventRelays = useSubject(getEventRelays(getEventUID(event)));
return (
<Modal size="6xl" {...props}>
<ModalOverlay />
@ -88,7 +79,7 @@ export default function EventDebugModal({ event, ...props }: { event: NostrEvent
<Section label="IDs">
<RawValue heading="Event Id" value={event.id} />
<RawValue heading="NIP-19 Encoded Id" value={nip19.noteEncode(event.id)} />
<RawValue heading="NIP-19 Pointer" value={getSharableEventAddress(event)} />
<RawValue heading="NIP-19 Pointer" value={relayHintService.getSharableEventAddress(event)} />
</Section>
<Section
@ -115,18 +106,6 @@ export default function EventDebugModal({ event, ...props }: { event: NostrEvent
<Heading size="sm">Tags referenced in content</Heading>
<JsonCode data={getContentTagRefs(event.content, event.tags)} />
</Section>
<Section label="Relays">
<Heading size="sm">Seen on:</Heading>
{eventRelays.map((url) => (
<Flex gap="2" key={url} alignItems="center">
<RelayFavicon size="sm" relay={url} />
<Text fontWeight="bold">{url}</Text>
</Flex>
))}
<Button onClick={broadcast} mr="auto" colorScheme="primary" isLoading={loading}>
Broadcast
</Button>
</Section>
</Accordion>
</ModalBody>
</ModalContent>

View File

@ -8,11 +8,11 @@ import {
getArticleTitle,
} from "../../../helpers/nostr/long-form";
import { NostrEvent } from "../../../types/nostr-event";
import { getSharableEventAddress } from "../../../helpers/nip19";
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
import Timestamp from "../../timestamp";
import { AppHandlerContext } from "../../../providers/route/app-handler-provider";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedArticle({ article, ...props }: Omit<CardProps, "children"> & { article: NostrEvent }) {
const toast = useToast();
@ -23,7 +23,7 @@ export default function EmbeddedArticle({ article, ...props }: Omit<CardProps, "
const { openAddress } = useContext(AppHandlerContext);
const open = () => {
const naddr = getSharableEventAddress(article);
const naddr = relayHintService.getSharableEventAddress(article);
if (naddr) openAddress(naddr);
else toast({ status: "error", description: "Failed to get address" });
};

View File

@ -1,3 +1,4 @@
import { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import {
Card,
@ -14,12 +15,12 @@ import {
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import { getBadgeDescription, getBadgeImage, getBadgeName } from "../../../helpers/nostr/badges";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedBadge({ badge, ...props }: Omit<CardProps, "children"> & { badge: NostrEvent }) {
const naddr = getSharableEventAddress(badge);
const naddr = useMemo(() => relayHintService.getSharableEventAddress(badge), [badge]);
const image = getBadgeImage(badge);
return (

View File

@ -13,7 +13,6 @@ import {
} from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { getEmojisFromPack, getPackName } from "../../../helpers/nostr/emoji-packs";
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
@ -21,10 +20,11 @@ import EmojiPackFavoriteButton from "../../../views/emoji-packs/components/emoji
import EmojiPackMenu from "../../../views/emoji-packs/components/emoji-pack-menu";
import { NostrEvent } from "../../../types/nostr-event";
import Timestamp from "../../timestamp";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedEmojiPack({ pack, ...props }: Omit<CardProps, "children"> & { pack: NostrEvent }) {
const emojis = getEmojisFromPack(pack);
const naddr = getSharableEventAddress(pack);
const naddr = relayHintService.getSharableEventAddress(pack);
return (
<Card {...props}>

View File

@ -1,3 +1,4 @@
import { useMemo } from "react";
import { Card, CardBody, CardProps, Flex, Heading, Image, Link, Text } from "@chakra-ui/react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
@ -6,7 +7,7 @@ import UserLink from "../../user/user-link";
import UserAvatar from "../../user/user-avatar";
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
import { getVideoDuration, getVideoImages, getVideoSummary, getVideoTitle } from "../../../helpers/nostr/flare";
import { getSharableEventAddress } from "../../../helpers/nip19";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedFlareVideo({ video, ...props }: Omit<CardProps, "children"> & { video: NostrEvent }) {
const navigate = useNavigate();
@ -17,7 +18,7 @@ export default function EmbeddedFlareVideo({ video, ...props }: Omit<CardProps,
const summary = getVideoSummary(video);
const isVertical = useBreakpointValue({ base: true, md: false });
const naddr = getSharableEventAddress(video);
const naddr = useMemo(() => relayHintService.getSharableEventAddress(video), [video]);
return (
<Card {...props} position="relative">

View File

@ -1,7 +1,7 @@
import { Card, CardBody, CardHeader, CardProps, Flex, Heading, Link, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { useMemo } from "react";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import { getGoalName } from "../../../helpers/nostr/goal";
import UserAvatarLink from "../../user/user-avatar-link";
@ -9,6 +9,7 @@ import UserLink from "../../user/user-link";
import GoalProgress from "../../../views/goals/components/goal-progress";
import GoalZapButton from "../../../views/goals/components/goal-zap-button";
import GoalTopZappers from "../../../views/goals/components/goal-top-zappers";
import relayHintService from "../../../services/event-relay-hint";
export type EmbeddedGoalOptions = {
showActions?: boolean;
@ -17,7 +18,7 @@ export type EmbeddedGoalOptions = {
export type EmbeddedGoalProps = Omit<CardProps, "children"> & { goal: NostrEvent } & EmbeddedGoalOptions;
export default function EmbeddedGoal({ goal, showActions = true, ...props }: EmbeddedGoalProps) {
const nevent = getSharableEventAddress(goal);
const nevent = useMemo(() => relayHintService.getSharableEventAddress(goal), [goal]);
return (
<Card {...props}>

View File

@ -3,15 +3,15 @@ import { Link as RouterLink } from "react-router-dom";
import { NostrEvent } from "../../../types/nostr-event";
import { getListDescription, getListName, isSpecialListKind } from "../../../helpers/nostr/lists";
import { getSharableEventAddress } from "../../../helpers/nip19";
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
import ListFeedButton from "../../../views/lists/components/list-feed-button";
import { ListCardContent } from "../../../views/lists/components/list-card";
import { createCoordinate } from "../../../classes/batch-kind-pubkey-loader";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedList({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : relayHintService.getSharableEventAddress(list);
const description = getListDescription(list);
return (

View File

@ -11,16 +11,16 @@ import EventVerificationIcon from "../../common-event/event-verification-icon";
import { TrustProvider } from "../../../providers/local/trust-provider";
import { NoteLink } from "../../note/note-link";
import Timestamp from "../../timestamp";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { CompactNoteContent } from "../../compact-note-content";
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
import HoverLinkOverlay from "../../hover-link-overlay";
import singleEventService from "../../../services/single-event";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedNote({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
const { showSignatureVerification } = useSubject(appSettings);
const navigate = useNavigateInDrawer();
const to = `/n/${getSharableEventAddress(event)}`;
const to = `/n/${relayHintService.getSharableEventAddress(event)}`;
const handleClick = useCallback<MouseEventHandler>(
(e) => {

View File

@ -21,7 +21,7 @@ import Timestamp from "../../timestamp";
import TrackStemstrButton from "../../../views/tracks/components/track-stemstr-button";
import TrackDownloadButton from "../../../views/tracks/components/track-download-button";
import TrackPlayer from "../../../views/tracks/components/track-player";
import QuoteRepostButton from "../../note/quote-repost-button";
import QuoteEventButton from "../../note/quote-event-button";
import NoteZapButton from "../../note/note-zap-button";
// example nevent1qqst32cnyhhs7jt578u7vp3y047dduuwjquztpvwqc43f3nvg8dh28gpzamhxue69uhhyetvv9ujuum5v4khxarj9eshquq4rxdxa
@ -53,7 +53,7 @@ export default function EmbeddedStemstrTrack({ track, ...props }: Omit<CardProps
Comment
</Button>
</Tooltip>
<QuoteRepostButton event={track} />
<QuoteEventButton event={track} />
<NoteZapButton event={track} />
</ButtonGroup>
<ButtonGroup size="sm" ml="auto">

View File

@ -1,4 +1,4 @@
import { Box, Card, CardProps, Divider, Flex, Link, Text } from "@chakra-ui/react";
import { Card, CardProps, Divider, Flex, Link } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { NostrEvent, isATag } from "../../../types/nostr-event";
@ -8,7 +8,7 @@ import ChatMessageContent from "../../../views/streams/stream/stream-chat/chat-m
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { parseStreamEvent } from "../../../helpers/nostr/stream";
import StreamStatusBadge from "../../../views/streams/components/status-badge";
import { getSharableEventAddress } from "../../../helpers/nip19";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedStreamMessage({
message,
@ -25,7 +25,7 @@ export default function EmbeddedStreamMessage({
<Flex gap="2" alignItems="center">
<Link
as={RouterLink}
to={`/streams/${getSharableEventAddress(streamEvent) ?? ""}`}
to={`/streams/${relayHintService.getSharableEventAddress(streamEvent) ?? ""}`}
fontWeight="bold"
fontSize="lg"
>

View File

@ -6,13 +6,13 @@ import { NostrEvent } from "../../../types/nostr-event";
import StreamStatusBadge from "../../../views/streams/components/status-badge";
import UserLink from "../../user/user-link";
import UserAvatar from "../../user/user-avatar";
import useEventNaddr from "../../../hooks/use-event-naddr";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
import Timestamp from "../../timestamp";
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
export default function EmbeddedStream({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
const stream = parseStreamEvent(event);
const naddr = useEventNaddr(stream.event, stream.relays);
const naddr = useShareableEventAddress(stream.event, stream.relays);
const isVertical = useBreakpointValue({ base: true, md: false });
const navigate = useNavigate();

View File

@ -16,7 +16,6 @@ import {
} from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { getSharableEventAddress } from "../../../helpers/nip19";
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
import { NostrEvent } from "../../../types/nostr-event";
@ -26,10 +25,11 @@ import { getTorrentMagnetLink, getTorrentSize, getTorrentTitle } from "../../../
import { formatBytes } from "../../../helpers/number";
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
import HoverLinkOverlay from "../../hover-link-overlay";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedTorrent({ torrent, ...props }: Omit<CardProps, "children"> & { torrent: NostrEvent }) {
const navigate = useNavigateInDrawer();
const link = `/torrents/${getSharableEventAddress(torrent)}`;
const link = `/torrents/${relayHintService.getSharableEventAddress(torrent)}`;
const handleClick = useCallback<MouseEventHandler>(
(e) => {

View File

@ -1,7 +1,6 @@
import { useContext, useMemo } from "react";
import { Box, Button, ButtonGroup, Card, CardBody, CardHeader, CardProps, Text } from "@chakra-ui/react";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import UserAvatarLink from "../../user/user-avatar-link";
import UserLink from "../../user/user-link";
@ -21,9 +20,10 @@ import { renderAudioUrl } from "../../external-embeds/types/audio";
import DebugEventButton from "../../debug-modal/debug-event-button";
import DebugEventTags from "../../debug-modal/event-tags";
import { AppHandlerContext } from "../../../providers/route/app-handler-provider";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedUnknown({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
const address = getSharableEventAddress(event);
const address = useMemo(()=> relayHintService.getSharableEventAddress(event), [event])
const { openAddress } = useContext(AppHandlerContext);
const alt = event.tags.find((t) => t[0] === "alt")?.[1];

View File

@ -18,10 +18,10 @@ import { NostrEvent } from "../../../types/nostr-event";
import UserLink from "../../user/user-link";
import { getPageForks, getPageSummary, getPageTitle } from "../../../helpers/nostr/wiki";
import HoverLinkOverlay from "../../hover-link-overlay";
import { getSharableEventAddress } from "../../../helpers/nip19";
import Timestamp from "../../timestamp";
import GitBranch01 from "../../icons/git-branch-01";
import UserName from "../../user/user-name";
import relayHintService from "../../../services/event-relay-hint";
export default function EmbeddedWikiPage({ page: page, ...props }: Omit<CardProps, "children"> & { page: NostrEvent }) {
const { address } = useMemo(() => getPageForks(page), [page]);
@ -31,7 +31,7 @@ export default function EmbeddedWikiPage({ page: page, ...props }: Omit<CardProp
<Card as={LinkBox} {...props}>
<CardHeader p="2" pb="0" display="flex" gap="2" alignItems="center">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${relayHintService.getSharableEventAddress(page)}`}>
{getPageTitle(page)}
</HoverLinkOverlay>
</Heading>

View File

@ -0,0 +1,54 @@
import { Box, ButtonGroup, Card, CardBody, CardHeader, CardProps, LinkBox, Text } from "@chakra-ui/react";
import { useMemo } from "react";
import { NostrEvent } from "../../../types/nostr-event";
import UserLink from "../../user/user-link";
import Timestamp from "../../timestamp";
import { getParsedZap, getZapRecipient } from "../../../helpers/nostr/zaps";
import TextNoteContents from "../../note/timeline-note/text-note-contents";
import UserAvatar from "../../user/user-avatar";
import { LightningIcon } from "../../icons";
import { readablizeSats } from "../../../helpers/bolt11";
import ZapReceiptMenu from "../../zap/zap-receipt-menu";
import { getPointerFromTag } from "../../../helpers/nip19";
import { EmbedEventPointer } from "../index";
export default function EmbeddedZapRecept({ zap, ...props }: Omit<CardProps, "children"> & { zap: NostrEvent }) {
const parsed = useMemo(() => getParsedZap(zap), [zap]);
if (!parsed) return null;
const recipient = getZapRecipient(parsed.request);
if (!recipient) return null;
const eTag = parsed.request.tags.find((t) => t[0] === "e" && t[1]);
const pointer = eTag && getPointerFromTag(eTag);
return (
<Card as={LinkBox} {...props}>
<CardHeader display="flex" p="2" gap="2" alignItems="center">
<UserAvatar pubkey={parsed.request.pubkey} size="sm" />
<UserLink pubkey={parsed.request.pubkey} fontWeight="bold" />
<Text>Zapped</Text>
<UserLink pubkey={recipient} fontWeight="bold" />
{parsed.payment.amount && (
<>
<LightningIcon color="yellow.500" boxSize={5} />
<Text>{readablizeSats(parsed.payment.amount / 1000)}</Text>
</>
)}
<Timestamp timestamp={parsed.event.created_at} ml="auto" />
<ButtonGroup size="sm" variant="ghost">
<ZapReceiptMenu zap={zap} aria-label="More Options" />
</ButtonGroup>
</CardHeader>
<CardBody px="2" pb="2" pt="0" display="flex" flexDirection="column" gap="2">
<Box>
<TextNoteContents event={parsed.request} />
</Box>
{pointer && <EmbedEventPointer pointer={pointer} />}
</CardBody>
</Card>
);
}

View File

@ -43,6 +43,7 @@ import LoadingNostrLink from "../loading-nostr-link";
import EmbeddedRepost from "./event-types/embedded-repost";
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
import EmbeddedWikiPage from "./event-types/embedded-wiki-page";
import EmbeddedZapRecept from "./event-types/embedded-zap-receipt";
const EmbeddedStemstrTrack = lazy(() => import("./event-types/embedded-stemstr-track"));
export type EmbedProps = {
@ -97,6 +98,8 @@ export function EmbedEvent({
return <EmbeddedRepost repost={event} {...cardProps} />;
case WIKI_PAGE_KIND:
return <EmbeddedWikiPage page={event} {...cardProps} />;
case kinds.Zap:
return <EmbeddedZapRecept zap={event} {...cardProps} />;
}
return <EmbeddedUnknown event={event} {...cardProps} />;

View File

@ -107,7 +107,7 @@ export const SearchIcon = SearchMd;
export const ReplyIcon = ReverseLeft;
export const RepostIcon = Repeat01;
export const QuoteRepostIcon = createIcon({
export const QuoteEventIcon = createIcon({
displayName: "QuoteRepostIcon",
d: "M19.4167 6.67891C20.4469 7.77257 21.0001 9 21.0001 10.9897C21.0001 14.4891 18.5436 17.6263 14.9695 19.1768L14.0768 17.7992C17.4121 15.9946 18.0639 13.6539 18.3245 12.178C17.7875 12.4557 17.0845 12.5533 16.3954 12.4895C14.591 12.3222 13.1689 10.8409 13.1689 9C13.1689 7.067 14.7359 5.5 16.6689 5.5C17.742 5.5 18.7681 5.99045 19.4167 6.67891ZM9.41669 6.67891C10.4469 7.77257 11.0001 9 11.0001 10.9897C11.0001 14.4891 8.54359 17.6263 4.96951 19.1768L4.07682 17.7992C7.41206 15.9946 8.06392 13.6539 8.32447 12.178C7.78747 12.4557 7.08452 12.5533 6.39539 12.4895C4.59102 12.3222 3.16895 10.8409 3.16895 9C3.16895 7.067 4.73595 5.5 6.66895 5.5C7.742 5.5 8.76814 5.99045 9.41669 6.67891Z",
defaultProps,

View File

@ -13,8 +13,8 @@ import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeli
import { getDMRecipient, getDMSender } from "../../../helpers/nostr/dms";
import UserName from "../../user/user-name";
import HoverLinkOverlay from "../../hover-link-overlay";
import { getSharableEventAddress } from "../../../helpers/nip19";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import relayHintService from "../../../services/event-relay-hint";
const kindColors: Record<number, FlexProps["bg"]> = {
[kinds.ShortTextNote]: "blue.500",
@ -73,7 +73,12 @@ function TimelineItem({ event }: { event: NostrEvent }) {
);
default:
return (
<HoverLinkOverlay as={RouterLink} to={`/l/${getSharableEventAddress(event)}`} noOfLines={1} isTruncated>
<HoverLinkOverlay
as={RouterLink}
to={`/l/${relayHintService.getSharableEventAddress(event)}`}
noOfLines={1}
isTruncated
>
{event.content}
</HoverLinkOverlay>
);

View File

@ -13,6 +13,7 @@ import {
import { Button, Flex, FlexProps, Spacer, useDisclosure } from "@chakra-ui/react";
import { useUnmount } from "react-use";
import { Link as RouterLink } from "react-router-dom";
import styled from "@emotion/styled";
import Lightbox, { RenderSlideContainerProps, Slide } from "yet-another-react-lightbox";
import Zoom from "yet-another-react-lightbox/plugins/zoom";
@ -32,8 +33,7 @@ declare module "yet-another-react-lightbox" {
import { NostrEvent } from "../types/nostr-event";
import UserAvatarLink from "./user/user-avatar-link";
import UserLink from "./user/user-link";
import styled from "@emotion/styled";
import { getSharableEventAddress } from "../helpers/nip19";
import relayHintService from "../services/event-relay-hint";
type RefType = MutableRefObject<HTMLElement | null>;
@ -100,7 +100,7 @@ function getRefPath(ref: RefType) {
}
function EventSlideHeader({ event, ...props }: { event: NostrEvent } & Omit<FlexProps, "children">) {
const encoded = useMemo(() => getSharableEventAddress(event), [event]);
const encoded = useMemo(() => relayHintService.getSharableEventAddress(event), [event]);
return (
<Flex gap="2" alignItems="center" p="2" {...props}>

View File

@ -1,17 +0,0 @@
import { memo } from "react";
import { NostrEvent } from "nostr-tools";
import { getEventRelays } from "../../services/event-relays";
import useSubject from "../../hooks/use-subject";
import { RelayIconStack, RelayIconStackProps } from "../relay-icon-stack";
import { getEventUID } from "../../helpers/nostr/event";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
export const EventRelays = memo(
({ event, ...props }: { event: NostrEvent } & Omit<RelayIconStackProps, "relays" | "maxRelays">) => {
const maxRelays = useBreakpointValue({ base: 3, md: undefined });
const eventRelays = useSubject(getEventRelays(getEventUID(event)));
return <RelayIconStack relays={eventRelays} direction="row-reverse" maxRelays={maxRelays} {...props} />;
},
);

View File

@ -1,4 +1,4 @@
import { useCallback } from "react";
import { useCallback, useMemo } from "react";
import { MenuItem, useDisclosure } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
@ -13,15 +13,17 @@ import OpenInAppMenuItem from "../common-menu-items/open-in-app";
import MuteUserMenuItem from "../common-menu-items/mute-user";
import DeleteEventMenuItem from "../common-menu-items/delete-event";
import CopyEmbedCodeMenuItem from "../common-menu-items/copy-embed-code";
import { getSharableEventAddress } from "../../helpers/nip19";
import Recording02 from "../icons/recording-02";
import { usePublishEvent } from "../../providers/global/publish-provider";
import DebugEventMenuItem from "../debug-modal/debug-event-menu-item";
import relayHintService from "../../services/event-relay-hint";
export default function NoteMenu({ event, ...props }: { event: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
const translationsModal = useDisclosure();
const publish = usePublishEvent();
const address = useMemo(() => relayHintService.getSharableEventAddress(event), [event])
const broadcast = useCallback(async () => {
await publish("Broadcast", event);
}, []);
@ -38,14 +40,14 @@ export default function NoteMenu({ event, ...props }: { event: NostrEvent } & Om
<MenuItem
as={RouterLink}
icon={<Recording02 />}
to={`/tools/transform/${getSharableEventAddress(event)}?tab=tts`}
to={`/tools/transform/${address}?tab=tts`}
>
Text to speech
</MenuItem>
<MenuItem
as={RouterLink}
icon={<Translate01 />}
to={`/tools/transform/${getSharableEventAddress(event)}?tab=translation`}
to={`/tools/transform/${address}?tab=translation`}
>
Translate
</MenuItem>

View File

@ -2,30 +2,28 @@ import { useContext } from "react";
import { ButtonProps, IconButton } from "@chakra-ui/react";
import { NostrEvent } from "nostr-tools";
import { QuoteRepostIcon } from "../icons";
import { QuoteEventIcon } from "../icons";
import { PostModalContext } from "../../providers/route/post-modal-provider";
import { getSharableEventAddress } from "../../helpers/nip19";
import relayHintService from "../../services/event-relay-hint";
export type QuoteRepostButtonProps = Omit<ButtonProps, "children" | "onClick"> & {
event: NostrEvent;
};
export default function QuoteRepostButton({
export default function QuoteEventButton({
event,
"aria-label": ariaLabel,
title = "Quote repost",
title = "Quote Note",
...props
}: QuoteRepostButtonProps) {
}: Omit<ButtonProps, "children" | "onClick"> & {
event: NostrEvent;
}) {
const { openModal } = useContext(PostModalContext);
const handleClick = () => {
const nevent = getSharableEventAddress(event);
const nevent = relayHintService.getSharableEventAddress(event);
openModal({ cacheFormKey: null, initContent: "\nnostr:" + nevent });
};
return (
<IconButton
icon={<QuoteRepostIcon />}
icon={<QuoteEventIcon />}
onClick={handleClick}
aria-label={ariaLabel || title}
title={title}

View File

@ -25,7 +25,7 @@ import useSubject from "../../../hooks/use-subject";
import appSettings from "../../../services/settings/app-settings";
import EventVerificationIcon from "../../common-event/event-verification-icon";
import RepostButton from "./components/repost-button";
import QuoteRepostButton from "../quote-repost-button";
import QuoteEventButton from "../quote-event-button";
import { ReplyIcon } from "../../icons";
import NoteContentWithWarning from "./note-content-with-warning";
import { TrustProvider } from "../../../providers/local/trust-provider";
@ -36,7 +36,6 @@ import ReplyForm from "../../../views/thread/components/reply-form";
import { getThreadReferences } from "../../../helpers/nostr/event";
import Timestamp from "../../timestamp";
import OpenInDrawerButton from "../open-in-drawer-button";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
import HoverLinkOverlay from "../../hover-link-overlay";
import NoteCommunityMetadata from "./note-community-metadata";
@ -46,6 +45,7 @@ import POWIcon from "../../pow/pow-icon";
import ReplyContext from "./components/reply-context";
import ZapBubbles from "./components/zap-bubbles";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import relayHintService from "../../../services/event-relay-hint";
export type TimelineNoteProps = Omit<CardProps, "children"> & {
event: NostrEvent;
@ -89,7 +89,7 @@ export function TimelineNote({
{clickable && (
<HoverLinkOverlay
as={RouterLink}
to={`/n/${getSharableEventAddress(event)}`}
to={`/n/${relayHintService.getSharableEventAddress(event)}`}
onClick={() => singleEventService.handleEvent(event)}
/>
)}
@ -97,7 +97,12 @@ export function TimelineNote({
<Flex flex="1" gap="2" alignItems="center">
<UserAvatarLink pubkey={event.pubkey} size="sm" />
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
<Link as={RouterLink} whiteSpace="nowrap" color="current" to={`/n/${getSharableEventAddress(event)}`}>
<Link
as={RouterLink}
whiteSpace="nowrap"
color="current"
to={`/n/${relayHintService.getSharableEventAddress(event)}`}
>
<Timestamp timestamp={event.created_at} />
</Link>
<POWIcon event={event} boxSize={5} />
@ -105,7 +110,7 @@ export function TimelineNote({
{showSignatureVerification && <EventVerificationIcon event={event} />}
{!hideDrawerButton && (
<OpenInDrawerButton
to={`/n/${getSharableEventAddress(event)}`}
to={`/n/${relayHintService.getSharableEventAddress(event)}`}
size="sm"
variant="ghost"
onClick={() => singleEventService.handleEvent(event)}
@ -127,7 +132,7 @@ export function TimelineNote({
<IconButton icon={<ReplyIcon />} aria-label="Reply" title="Reply" onClick={replyForm.onOpen} />
)}
<RepostButton event={event} />
<QuoteRepostButton event={event} />
<QuoteEventButton event={event} />
<NoteZapButton event={event} />
</ButtonGroup>
{!showReactionsOnNewLine && reactionButtons}

View File

@ -17,20 +17,19 @@ import { Link as RouterLink } from "react-router-dom";
import { NostrEvent } from "../../../types/nostr-event";
import { parseStreamEvent } from "../../../helpers/nostr/stream";
import useEventNaddr from "../../../hooks/use-event-naddr";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
import UserAvatar from "../../user/user-avatar";
import UserLink from "../../user/user-link";
import StreamStatusBadge from "../../../views/streams/components/status-badge";
import { useAsync } from "react-use";
import Timestamp from "../../timestamp";
import { EventRelays } from "../../note/event-relays";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
export default function StreamNote({ event, ...props }: CardProps & { event: NostrEvent }) {
const { value: stream, error } = useAsync(async () => parseStreamEvent(event), [event]);
const ref = useEventIntersectionRef(event);
const naddr = useEventNaddr(event);
const naddr = useShareableEventAddress(event);
if (!stream || error) return null;
@ -68,8 +67,6 @@ export default function StreamNote({ event, ...props }: CardProps & { event: Nos
<Divider />
<CardFooter p="2" display="flex" gap="2" alignItems="center">
<StreamStatusBadge stream={stream} />
<Spacer />
<EventRelays event={stream.event} />
</CardFooter>
</Card>
);

View File

@ -17,7 +17,6 @@ import {
import TimelineLoader from "../../../classes/timeline-loader";
import useSubject from "../../../hooks/use-subject";
import { getEventRelays } from "../../../services/event-relays";
import { NostrEvent } from "../../../types/nostr-event";
import { RelayFavicon } from "../../relay-favicon";
import { NoteLink } from "../../note/note-link";
@ -31,8 +30,8 @@ function EventRow({
relays,
...props
}: { event: NostrEvent; relays: string[] } & Omit<TableRowProps, "children">) {
const sub = useMemo(() => getEventRelays(event.id), [event.id]);
const seenRelays = useSubject(sub);
// const sub = useMemo(() => getEventRelays(event.id), [event.id]);
const seenRelays = true; //useSubject(sub);
const publish = usePublishEvent();
const ref = useEventIntersectionRef(event);
@ -65,7 +64,7 @@ function EventRow({
{broadcasting ? <Spinner size="xs" /> : <BroadcastEventIcon />}
</Td>
{relays.map((relay) => (
<Td key={relay} title={relay} p="2" backgroundColor={seenRelays.includes(relay) ? yes : no}>
<Td key={relay} title={relay} p="2" backgroundColor={/*seenRelays.includes(relay)*/ true ? yes : no}>
<RelayFavicon relay={relay} size="2xs" />
</Td>
))}

View File

@ -0,0 +1,20 @@
import { NostrEvent } from "../../types/nostr-event";
import { DotsMenuButton, MenuIconButtonProps } from "../dots-menu-button";
import OpenInAppMenuItem from "../common-menu-items/open-in-app";
import CopyEmbedCodeMenuItem from "../common-menu-items/copy-embed-code";
import DebugEventMenuItem from "../debug-modal/debug-event-menu-item";
import QuoteEventMenuItem from "../common-menu-items/quote-event";
export default function ZapReceiptMenu({ zap, ...props }: { zap: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
return (
<>
<DotsMenuButton {...props}>
<OpenInAppMenuItem event={zap} />
<QuoteEventMenuItem event={zap} />
<CopyEmbedCodeMenuItem event={zap} />
<DebugEventMenuItem event={zap} />
</DotsMenuButton>
</>
);
}

View File

@ -9,3 +9,4 @@ export const SEARCH_RELAYS = safeRelayUrls([
]);
export const WIKI_RELAYS = safeRelayUrls(["wss://relay.wikifreedia.xyz/"]);
export const COMMON_CONTACT_RELAY = safeRelayUrl("wss://purplepag.es") as string;
export const COMMON_CONTACT_RELAYS = [COMMON_CONTACT_RELAY];

View File

@ -1,8 +1,6 @@
import { getPublicKey, nip19 } from "nostr-tools";
import { NostrEvent, Tag, isATag, isDTag, isETag, isPTag } from "../types/nostr-event";
import { isReplaceable } from "./nostr/event";
import relayHintService from "../services/event-relay-hint";
import { Tag, isATag, isETag, isPTag } from "../types/nostr-event";
import { safeRelayUrls } from "./relay";
export function isHex(str?: string) {
@ -43,18 +41,6 @@ export function normalizeToHexPubkey(hex: string) {
return getPubkeyFromDecodeResult(decode) ?? null;
}
export function getSharableEventAddress(event: NostrEvent) {
const relays = relayHintService.getEventRelayHints(event, 2);
if (isReplaceable(event.kind)) {
const d = event.tags.find(isDTag)?.[1];
if (!d) return null;
return nip19.naddrEncode({ kind: event.kind, identifier: d, pubkey: event.pubkey, relays });
} else {
return nip19.neventEncode({ id: event.id, kind: event.kind, relays, author: event.pubkey });
}
}
export function encodeDecodeResult(result: nip19.DecodeResult) {
switch (result.type) {
case "naddr":

View File

@ -42,6 +42,10 @@ export function isProfileZap(event: NostrEvent) {
return !isNoteZap(event) && event.tags.some(isPTag);
}
export function getZapRecipient(event: NostrEvent) {
return event.tags.find((t) => t[0] === "p" && t[1])?.[1];
}
export function totalZaps(zaps: ParsedZap[]) {
return zaps.reduce((t, zap) => t + (zap.payment.amount || 0), 0);
}

View File

@ -1,10 +1,5 @@
import { SimpleRelay, SubscriptionOptions } from "nostr-idb";
import { AbstractRelay, Filter, SubCloser, SubscribeManyParams, Subscription } from "nostr-tools";
import { NostrQuery, NostrRequestFilter } from "../types/nostr-relay";
import { NostrEvent } from "../types/nostr-event";
import relayPoolService from "../services/relay-pool";
// NOTE: only use this for equality checks and querying
export function getRelayVariations(relay: string) {
if (relay.endsWith("/")) {
@ -59,11 +54,11 @@ export function safeRelayUrls(urls: Iterable<string>): string[] {
}
export function splitNostrFilterByPubkeys(
filter: NostrRequestFilter,
filter: Filter | Filter[],
relayPubkeyMap: Record<string, string[]>,
): Record<string, NostrRequestFilter> {
): Record<string, Filter | Filter[]> {
if (Array.isArray(filter)) {
const dir: Record<string, NostrQuery[]> = {};
const dir: Record<string, Filter[]> = {};
for (const query of filter) {
const split = splitQueryByPubkeys(query, relayPubkeyMap);
@ -77,8 +72,8 @@ export function splitNostrFilterByPubkeys(
} else return splitQueryByPubkeys(filter, relayPubkeyMap);
}
export function splitQueryByPubkeys(query: NostrQuery, relayPubkeyMap: Record<string, string[]>) {
const filtersByRelay: Record<string, NostrQuery> = {};
export function splitQueryByPubkeys(query: Filter, relayPubkeyMap: Record<string, string[]>) {
const filtersByRelay: Record<string, Filter> = {};
const allPubkeys = new Set(Object.values(relayPubkeyMap).flat());
for (const [relay, pubkeys] of Object.entries(relayPubkeyMap)) {
@ -142,6 +137,7 @@ export function subscribeMany(relays: string[], filters: Filter[], params: Subsc
let relay: AbstractRelay;
try {
const { default: relayPoolService } = await import("../services/relay-pool");
relay = relayPoolService.requestRelay(url);
await relayPoolService.requestConnect(relay);
// changed from nostr-tools

View File

@ -2,12 +2,12 @@ import clientRelaysService from "../services/client-relays";
import useSubject from "./use-subject";
export function useReadRelays(additional?: Iterable<string>) {
const set = useSubject(clientRelaysService.readRelays);
if (additional) return set.clone().merge(additional);
return set;
const readRelays = useSubject(clientRelaysService.readRelays);
if (additional) return readRelays.clone().merge(additional);
return readRelays;
}
export function useWriteRelays(additional?: Iterable<string>) {
const set = useSubject(clientRelaysService.writeRelays);
if (additional) return set.clone().merge(additional);
return set;
const writeRelays = useSubject(clientRelaysService.writeRelays);
if (additional) return writeRelays.clone().merge(additional);
return writeRelays;
}

View File

@ -1,9 +1,9 @@
import { useMemo } from "react";
import { Filter } from "nostr-tools";
import eventCountService from "../services/event-count";
import { NostrRequestFilter } from "../types/nostr-relay";
import useSubject from "./use-subject";
export default function useEventCount(filter?: NostrRequestFilter, alwaysRequest = false) {
export default function useEventCount(filter?: Filter | Filter[], alwaysRequest = false) {
const key = filter ? eventCountService.stringifyFilter(filter) : "empty";
const subject = useMemo(() => filter && eventCountService.requestCount(filter, alwaysRequest), [key, alwaysRequest]);
return useSubject(subject);

View File

@ -1,23 +0,0 @@
import { useMemo } from "react";
import { NostrEvent } from "../types/nostr-event";
import { nip19 } from "nostr-tools";
import { getEventRelays } from "../services/event-relays";
import relayScoreboardService from "../services/relay-scoreboard";
export default function useEventNaddr(event: NostrEvent, overrideRelays?: string[]) {
return useMemo(() => {
const identifier = event.tags.find((t) => t[0] === "d" && t[1])?.[1];
const relays = overrideRelays || getEventRelays(event.id).value;
const ranked = relayScoreboardService.getRankedRelays(relays);
const onlyTwo = ranked.slice(0, 2);
if (!identifier) return null;
return nip19.naddrEncode({
identifier,
relays: onlyTwo,
pubkey: event.pubkey,
kind: event.kind,
});
}, [event]);
}

View File

@ -1,8 +0,0 @@
import { useMemo } from "react";
import { getEventRelays } from "../services/event-relays";
import useSubject from "./use-subject";
export default function useEventRelays(eventId?: string) {
const sub = useMemo(() => (eventId ? getEventRelays(eventId) : undefined), [eventId]);
return useSubject(sub) ?? [];
}

View File

@ -0,0 +1,12 @@
import { useDisclosure } from "@chakra-ui/react";
import { useLocalStorage } from "react-use";
export default function useLocalStorageDisclosure(name: string) {
const [value, setValue] = useLocalStorage<boolean>(name);
return useDisclosure({
isOpen: value,
onOpen: () => setValue(true),
onClose: () => setValue(false),
defaultIsOpen: value,
});
}

View File

@ -0,0 +1,12 @@
import { useMemo } from "react";
import { NostrEvent } from "../types/nostr-event";
import relayHintService from "../services/event-relay-hint";
import useUserMailboxes from "./use-user-mailboxes";
export default function useShareableEventAddress(event: NostrEvent, overrideRelays?: string[]) {
const mailboxes = useUserMailboxes(event.pubkey);
return useMemo(() => {
return relayHintService.getSharableEventAddress(event, overrideRelays);
}, [event]);
}

View File

@ -1,16 +1,15 @@
import { useMemo } from "react";
import userMetadataService from "../services/user-metadata";
import { useReadRelays } from "./use-client-relays";
import useSubject from "./use-subject";
import { RequestOptions } from "../services/replaceable-events";
import { COMMON_CONTACT_RELAY } from "../const";
import { COMMON_CONTACT_RELAYS } from "../const";
export default function useUserMetadata(
pubkey?: string,
additionalRelays: Iterable<string> = [],
opts: RequestOptions = {},
) {
const readRelays = useReadRelays([...additionalRelays, COMMON_CONTACT_RELAY]);
export default function useUserMetadata(pubkey?: string, additionalRelays?: Iterable<string>, opts?: RequestOptions) {
const readRelays = useReadRelays(
additionalRelays ? [...additionalRelays, ...COMMON_CONTACT_RELAYS] : COMMON_CONTACT_RELAYS,
);
const subject = useMemo(
() => (pubkey ? userMetadataService.requestMetadata(pubkey, readRelays, opts) : undefined),

View File

@ -2,6 +2,7 @@ import "./polyfill";
import { createRoot } from "react-dom/client";
import { App } from "./app";
import { GlobalProviders } from "./providers/global";
import "./services/user-event-sync";
import "./services/username-search";

View File

@ -11,7 +11,6 @@ import { getAllRelayHints, isReplaceable } from "../../helpers/nostr/event";
import replaceableEventsService from "../../services/replaceable-events";
import eventReactionsService from "../../services/event-reactions";
import { localRelay } from "../../services/local-relay";
import { handleEventFromRelay } from "../../services/event-relays";
import deleteEventService from "../../services/delete-events";
import userMailboxesService from "../../services/user-mailboxes";
@ -86,10 +85,6 @@ export default function PublishProvider({ children }: PropsWithChildren) {
const pub = new PublishAction(label, relays, signed);
setLog((arr) => arr.concat(pub));
pub.onResult.subscribe(({ relay, success }) => {
if (success) handleEventFromRelay(relay, signed);
});
// send it to the local relay
if (localRelay) localRelay.publish(signed);

View File

@ -1,11 +1,10 @@
import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react";
import { kinds } from "nostr-tools";
import { Filter, kinds } from "nostr-tools";
import useCurrentAccount from "../../hooks/use-current-account";
import { getPubkeysFromList } from "../../helpers/nostr/lists";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import { NostrEvent } from "../../types/nostr-event";
import { NostrQuery } from "../../types/nostr-relay";
import useRouteSearchValue from "../../hooks/use-route-search-value";
export type ListId = "following" | "global" | string;
@ -17,7 +16,7 @@ export type PeopleListContextType = {
listEvent?: NostrEvent;
people: Person[] | undefined;
setSelected: (list: ListId) => void;
filter: NostrQuery | undefined;
filter: Filter | undefined;
};
const PeopleListContext = createContext<PeopleListContextType>({
setSelected: () => {},
@ -60,7 +59,7 @@ export default function PeopleListProvider({ children, initList }: PeopleListPro
const people = listEvent && getPubkeysFromList(listEvent);
const filter = useMemo<NostrQuery | undefined>(() => {
const filter = useMemo<Filter | undefined>(() => {
if (selected === "global") return {};
if (!people) return undefined;
return { authors: people.map((p) => p.pubkey) };

View File

@ -23,10 +23,9 @@ import { Event, kinds } from "nostr-tools";
import dayjs from "dayjs";
import createDefer, { Deferred } from "../../classes/deferred";
import useEventRelays from "../../hooks/use-event-relays";
import { RelayFavicon } from "../../components/relay-favicon";
import { ExternalLinkIcon } from "../../components/icons";
import { getEventCoordinate, getEventUID, isReplaceable } from "../../helpers/nostr/event";
import { getEventCoordinate, isReplaceable } from "../../helpers/nostr/event";
import { Tag } from "../../types/nostr-event";
import { EmbedEvent } from "../../components/embed-event";
import { useWriteRelays } from "../../hooks/use-client-relays";
@ -53,8 +52,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
const [defer, setDefer] = useState<Deferred<void>>();
const [reason, setReason] = useState("");
const eventRelays = useEventRelays(event && getEventUID(event));
const writeRelays = useWriteRelays(eventRelays);
const writeRelays = useWriteRelays();
const deleteEvent = useCallback((event: Event) => {
setEvent(event);

View File

@ -1,13 +1,12 @@
import dayjs from "dayjs";
import debug, { Debugger } from "debug";
import _throttle from "lodash.throttle";
import { kinds } from "nostr-tools";
import { Filter, kinds } from "nostr-tools";
import NostrSubscription from "../classes/nostr-subscription";
import SuperMap from "../classes/super-map";
import { NostrEvent } from "../types/nostr-event";
import Subject from "../classes/subject";
import { NostrQuery } from "../types/nostr-relay";
import { logger } from "../helpers/debug";
import db from "./db";
import createDefer, { Deferred } from "../classes/deferred";
@ -103,7 +102,7 @@ class ChannelMetadataRelayLoader {
// update the subscription
if (needsUpdate) {
if (this.requested.size > 0) {
const query: NostrQuery = {
const query: Filter = {
kinds: [kinds.ChannelMetadata],
"#e": Array.from(this.requested.keys()),
};

View File

@ -1,6 +1,6 @@
import { NostrEvent, kinds } from "nostr-tools";
import { getEventUID } from "nostr-idb";
import { getEventUID } from "../helpers/nostr/event";
import ControlledObservable from "../classes/controlled-observable";
const deleteEventStream = new ControlledObservable<NostrEvent>();

View File

@ -1,18 +1,18 @@
import { Filter } from "nostr-tools";
import stringify from "json-stringify-deterministic";
import Subject from "../classes/subject";
import SuperMap from "../classes/super-map";
import { NostrRequestFilter } from "../types/nostr-relay";
import { localRelay } from "./local-relay";
class EventCountService {
subjects = new SuperMap<string, Subject<number>>(() => new Subject<number>());
stringifyFilter(filter: NostrRequestFilter) {
stringifyFilter(filter: Filter | Filter[]) {
return stringify(filter);
}
requestCount(filter: NostrRequestFilter, alwaysRequest = false) {
requestCount(filter: Filter | Filter[], alwaysRequest = false) {
const key = this.stringifyFilter(filter);
const sub = this.subjects.get(key);
@ -26,7 +26,7 @@ class EventCountService {
return sub;
}
getCount(filter: NostrRequestFilter) {
getCount(filter: Filter | Filter[]) {
const key = this.stringifyFilter(filter);
const sub = this.subjects.get(key);
return sub;

View File

@ -1,25 +1,36 @@
import { createCoordinate } from "../classes/batch-kind-pubkey-loader";
import { NostrEvent } from "../types/nostr-event";
import { getEventRelays } from "./event-relays";
import relayScoreboardService from "./relay-scoreboard";
import { nip19 } from "nostr-tools";
import type { AddressPointer, EventPointer } from "nostr-tools/lib/types/nip19";
function pickBestRelays(relays: string[]) {
// ignore local relays
relays = relays.filter((url) => !url.includes("://localhost") && !url.includes("://192.168"));
import { NostrEvent, isDTag } from "../types/nostr-event";
import relayScoreboardService from "./relay-scoreboard";
import userMailboxesService from "./user-mailboxes";
import singleEventService from "./single-event";
import { isReplaceable } from "../helpers/nostr/event";
return relayScoreboardService.getRankedRelays(relays);
function pickBestRelays(relays: Iterable<string>) {
// ignore local relays
const urls = Array.from(relays).filter((url) => !url.includes("://localhost") && !url.includes("://192.168"));
return relayScoreboardService.getRankedRelays(urls);
}
function getAddressPointerRelayHint(pointer: AddressPointer): string | undefined {
let relays = getEventRelays(createCoordinate(pointer.kind, pointer.pubkey, pointer.identifier)).value;
return pickBestRelays(relays)[0];
const authorRelays = userMailboxesService.getMailboxes(pointer.pubkey).value;
return pickBestRelays(authorRelays?.outbox || [])[0];
}
function getEventPointerRelayHints(pointerOrId: string | EventPointer): string[] {
let relays =
typeof pointerOrId === "string" ? getEventRelays(pointerOrId).value : getEventRelays(pointerOrId.id).value;
return pickBestRelays(relays);
if (typeof pointerOrId === "string") {
const event = singleEventService.getSubject(pointerOrId).value;
if (event) {
const authorRelays = userMailboxesService.getMailboxes(event.pubkey).value;
return pickBestRelays(authorRelays?.outbox || []);
}
} else if (pointerOrId.author) {
const authorRelays = userMailboxesService.getMailboxes(pointerOrId.author).value;
return pickBestRelays(authorRelays?.outbox || []);
}
return [];
}
function getEventPointerRelayHint(pointerOrId: string | EventPointer): string | undefined {
return getEventPointerRelayHints(pointerOrId)[0];
@ -30,11 +41,21 @@ function getEventRelayHint(event: NostrEvent): string | undefined {
}
function getEventRelayHints(event: NostrEvent, count = 2): string[] {
// NOTE: in the future try to use the events authors relays
const authorRelays = userMailboxesService.getMailboxes(event.pubkey).value?.outbox || [];
let relays = getEventRelays(event.id).value;
return pickBestRelays(authorRelays).slice(0, count);
}
return pickBestRelays(relays).slice(0, count);
function getSharableEventAddress(event: NostrEvent, relays?: Iterable<string>) {
relays = relays || relayHintService.getEventRelayHints(event, 2);
if (isReplaceable(event.kind)) {
const d = event.tags.find(isDTag)?.[1];
if (!d) return null;
return nip19.naddrEncode({ kind: event.kind, identifier: d, pubkey: event.pubkey, relays: Array.from(relays) });
} else {
return nip19.neventEncode({ id: event.id, kind: event.kind, relays: Array.from(relays), author: event.pubkey });
}
}
const relayHintService = {
@ -42,6 +63,7 @@ const relayHintService = {
getEventRelayHint,
getEventPointerRelayHint,
getEventPointerRelayHints,
getSharableEventAddress,
getAddressPointerRelayHint,
pickBestRelays,
};

View File

@ -1,49 +0,0 @@
import { AbstractRelay } from "nostr-tools";
import { PersistentSubject } from "../classes/subject";
import { getEventUID } from "../helpers/nostr/event";
import { NostrEvent } from "../types/nostr-event";
const eventRelays = new Map<string, PersistentSubject<string[]>>();
export function getEventRelays(id: string) {
let relays = eventRelays.get(id);
if (!relays) {
relays = new PersistentSubject<string[]>([]);
eventRelays.set(id, relays);
}
return relays;
}
function addRelay(id: string, relay: string) {
const relays = getEventRelays(id);
if (!relays.value.includes(relay)) {
relays.next(relays.value.concat(relay));
}
}
export function handleEventFromRelay(relay: AbstractRelay, event: NostrEvent) {
const uid = getEventUID(event);
addRelay(uid, relay.url);
if (event.id !== uid) addRelay(event.id, relay.url);
}
// TODO: track events from relays
// relayPoolService.onRelayCreated.subscribe((relay) => {
// relay.onEvent.subscribe((message) => {
// handleEventFromRelay(relay, message[2]);
// });
// });
const eventRelaysService = {
getEventRelays,
handleEventFromRelay,
};
if (import.meta.env.DEV) {
//@ts-ignore
window.eventRelaysService = eventRelaysService;
}
export default eventRelaysService;

View File

@ -3,14 +3,14 @@ import dayjs from "dayjs";
import { nanoid } from "nanoid";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import { DraftNostrEvent, NostrEvent, isPTag } from "../types/nostr-event";
import createDefer, { Deferred } from "../classes/deferred";
import MultiSubscription from "../classes/multi-subscription";
import { getPubkeyFromDecodeResult, isHexKey, normalizeToHexPubkey } from "../helpers/nip19";
import { logger } from "../helpers/debug";
import { DraftNostrEvent, NostrEvent, isPTag } from "../types/nostr-event";
import createDefer, { Deferred } from "../classes/deferred";
import { alwaysVerify } from "./verify-event";
import { NostrConnectAccount } from "./account";
import { safeRelayUrl } from "../helpers/relay";
import { alwaysVerify } from "./verify-event";
import { truncateId } from "../helpers/string";
export function isErrorResponse(response: any): response is NostrConnectErrorResponse {

View File

@ -2,18 +2,18 @@ import { AbstractRelay, NostrEvent } from "nostr-tools";
import _throttle from "lodash.throttle";
import SuperMap from "../classes/super-map";
import { logger } from "../helpers/debug";
import { getEventCoordinate } from "../helpers/nostr/event";
import { localRelay } from "./local-relay";
import EventStore from "../classes/event-store";
import Subject from "../classes/subject";
import BatchKindPubkeyLoader, { createCoordinate } from "../classes/batch-kind-pubkey-loader";
import Process from "../classes/process";
import { logger } from "../helpers/debug";
import { getEventCoordinate } from "../helpers/nostr/event";
import { localRelay } from "./local-relay";
import relayPoolService from "./relay-pool";
import { alwaysVerify } from "./verify-event";
import { truncateId } from "../helpers/string";
import UserSquare from "../components/icons/user-square";
import Process from "../classes/process";
import processManager from "./process-manager";
import UserSquare from "../components/icons/user-square";
export type RequestOptions = {
/** Always request the event from the relays */

View File

@ -1,4 +1,5 @@
import { nip04, getPublicKey, finalizeEvent } from "nostr-tools";
import { hexToBytes } from "@noble/hashes/utils";
import { DraftNostrEvent, NostrEvent } from "../types/nostr-event";
import { Account } from "./account";
@ -6,7 +7,6 @@ import db from "./db";
import serialPortService from "./serial-port";
import amberSignerService from "./amber-signer";
import nostrConnectService from "./nostr-connect";
import { hexToBytes } from "@noble/hashes/utils";
import { alwaysVerify } from "./verify-event";
const decryptedKeys = new Map<string, string | Promise<string>>();

View File

@ -1,6 +0,0 @@
import { Filter } from "nostr-tools";
/** @deprecated use Filter instead */
export type NostrQuery = Filter;
export type NostrRequestFilter = Filter | Filter[];

View File

@ -35,7 +35,6 @@ import BadgeAwardCard from "./components/badge-award-card";
import TimelineLoader from "../../classes/timeline-loader";
import { ErrorBoundary } from "../../components/error-boundary";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
import { EventRelays } from "../../components/note/event-relays";
function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
const awards = useSubject(timeline.timeline);
@ -108,8 +107,6 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
<Spacer />
<EventRelays event={badge} />
<BadgeMenu aria-label="More options" badge={badge} />
</Flex>

View File

@ -5,11 +5,11 @@ import { Link as RouterLink } from "react-router-dom";
import { getBadgeAwardBadge, getBadgeAwardPubkeys, getBadgeImage, getBadgeName } from "../../../helpers/nostr/badges";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { NostrEvent } from "../../../types/nostr-event";
import { getSharableEventAddress } from "../../../helpers/nip19";
import UserLink from "../../../components/user/user-link";
import Timestamp from "../../../components/timestamp";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
const UserCard = memo(({ pubkey }: { pubkey: string }) => (
<Flex gap="2" alignItems="center">
@ -30,11 +30,11 @@ export default function BadgeAwardCard({ award, showImage = true }: { award: Nos
const awards = getBadgeAwardPubkeys(award);
const collapsed = !showAll.isOpen && awards.length > 10;
const naddr = getSharableEventAddress(badge);
const address = useShareableEventAddress(badge);
return (
<Card as={LinkBox} p="2" variant="outline" gap="2" flexDirection={["column", null, "row"]} ref={ref}>
{showImage && (
<Flex as={RouterLink} to={`/badges/${naddr}`} direction="column" overflow="hidden" gap="2" w="40" mx="auto">
<Flex as={RouterLink} to={`/badges/${address}`} direction="column" overflow="hidden" gap="2" w="40" mx="auto">
<Image aspectRatio={1} src={getBadgeImage(badge)?.src ?? ""} w="40" />
</Flex>
)}
@ -43,7 +43,7 @@ export default function BadgeAwardCard({ award, showImage = true }: { award: Nos
<UserAvatarLink pubkey={award.pubkey} size="sm" />
<UserLink pubkey={award.pubkey} fontWeight="bold" />
<Text>Awarded</Text>
<Link as={RouterLink} to={`/badges/${naddr}`} fontWeight="bold">
<Link as={RouterLink} to={`/badges/${address}`} fontWeight="bold">
{getBadgeName(badge)}
</Link>
<Text>To</Text>

View File

@ -5,7 +5,6 @@ import { kinds } from "nostr-tools";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import { getEventCoordinate } from "../../../helpers/nostr/event";
import BadgeMenu from "./badge-menu";
@ -13,9 +12,10 @@ import { getBadgeImage, getBadgeName } from "../../../helpers/nostr/badges";
import Timestamp from "../../../components/timestamp";
import useEventCount from "../../../hooks/use-event-count";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
function BadgeCard({ badge, ...props }: Omit<CardProps, "children"> & { badge: NostrEvent }) {
const naddr = getSharableEventAddress(badge);
const address = useShareableEventAddress(badge);
const image = getBadgeImage(badge);
const navigate = useNavigate();
@ -27,11 +27,11 @@ function BadgeCard({ badge, ...props }: Omit<CardProps, "children"> & { badge: N
return (
<Card ref={ref} variant="outline" {...props}>
{image && (
<Image src={image.src} cursor="pointer" onClick={() => navigate(`/badges/${naddr}`)} borderRadius="lg" />
<Image src={image.src} cursor="pointer" onClick={() => navigate(`/badges/${address}`)} borderRadius="lg" />
)}
<CardHeader display="flex" alignItems="center" p="2" pb="0">
<Heading size="md">
<Link as={RouterLink} to={`/badges/${naddr}`}>
<Link as={RouterLink} to={`/badges/${address}`}>
{getBadgeName(badge)}
</Link>
</Heading>

View File

@ -19,7 +19,6 @@ import { kinds } from "nostr-tools";
import { NostrEvent, isETag } from "../../../types/nostr-event";
import { getEventCommunityPointer, getPostSubject } from "../../../helpers/nostr/communities";
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
import { getSharableEventAddress } from "../../../helpers/nip19";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import { CompactNoteContent } from "../../../components/compact-note-content";
import { parseHardcodedNoteContent } from "../../../helpers/nostr/event";
@ -30,6 +29,7 @@ import { useReadRelays } from "../../../hooks/use-client-relays";
import useSingleEvent from "../../../hooks/use-single-event";
import CommunityPostMenu from "./community-post-menu";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
export function ApprovalIcon({ approval }: { approval: NostrEvent }) {
const ref = useEventIntersectionRef<HTMLAnchorElement>(approval);
@ -45,9 +45,10 @@ export type CommunityPostPropTypes = {
function PostSubject({ event }: { event: NostrEvent }) {
const subject = getPostSubject(event);
const address = useShareableEventAddress(event);
const navigate = useNavigateInDrawer();
const to = `/n/${getSharableEventAddress(event)}`;
const to = `/n/${address}`;
const handleClick = useCallback<MouseEventHandler>(
(e) => {
e.preventDefault();

View File

@ -19,8 +19,8 @@ import dayjs from "dayjs";
import { EMOJI_PACK_KIND } from "../../../helpers/nostr/emoji-packs";
import { DraftNostrEvent } from "../../../types/nostr-event";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { usePublishEvent } from "../../../providers/global/publish-provider";
import relayHintService from "../../../services/event-relay-hint";
export default function EmojiPackCreateModal({ onClose, ...props }: Omit<ModalProps, "children">) {
const publish = usePublishEvent();
@ -40,7 +40,7 @@ export default function EmojiPackCreateModal({ onClose, ...props }: Omit<ModalPr
};
const pub = await publish("Create emoji pack", draft);
if (pub) navigate(`/emojis/${getSharableEventAddress(pub.event)}`);
if (pub) navigate(`/emojis/${relayHintService.getSharableEventAddress(pub.event)}`);
});
return (

View File

@ -14,7 +14,6 @@ import {
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import EmojiPackFavoriteButton from "./emoji-pack-favorite-button";
import { getEmojisFromPack, getPackName } from "../../../helpers/nostr/emoji-packs";
@ -22,10 +21,11 @@ import EmojiPackMenu from "./emoji-pack-menu";
import NoteZapButton from "../../../components/note/note-zap-button";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
export default function EmojiPackCard({ pack, ...props }: Omit<CardProps, "children"> & { pack: NostrEvent }) {
const emojis = getEmojisFromPack(pack);
const naddr = getSharableEventAddress(pack);
const address = useShareableEventAddress(pack);
// if there is a parent intersection observer, register this card
const ref = useEventIntersectionRef(pack);
@ -34,7 +34,7 @@ export default function EmojiPackCard({ pack, ...props }: Omit<CardProps, "child
<Card ref={ref} variant="outline" {...props}>
<CardHeader display="flex" gap="2" alignItems="center" p="2" pb="0" flexWrap="wrap">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/emojis/${naddr}`}>
<HoverLinkOverlay as={RouterLink} to={`/emojis/${address}`}>
{getPackName(pack)}
</HoverLinkOverlay>
</Heading>

View File

@ -34,7 +34,7 @@ import Timestamp from "../../components/timestamp";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
import { usePublishEvent } from "../../providers/global/publish-provider";
import NoteZapButton from "../../components/note/note-zap-button";
import QuoteRepostButton from "../../components/note/quote-repost-button";
import QuoteEventButton from "../../components/note/quote-event-button";
function AddEmojiForm({ onAdd }: { onAdd: (values: { name: string; url: string }) => void }) {
const { register, handleSubmit, watch, getValues, reset } = useForm({
@ -175,7 +175,7 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
<ButtonGroup variant="ghost">
<NoteZapButton event={pack} />
<QuoteRepostButton event={pack} />
<QuoteEventButton event={pack} />
<EmojiPackFavoriteButton pack={pack} />
</ButtonGroup>

View File

@ -4,7 +4,6 @@ import { ButtonGroup, Card, CardBody, CardHeader, CardProps, Flex, Heading, Link
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import { getGoalClosedDate, getGoalName } from "../../../helpers/nostr/goal";
import GoalMenu from "./goal-menu";
@ -14,9 +13,10 @@ import GoalZapButton from "./goal-zap-button";
import GoalTopZappers from "./goal-top-zappers";
import Timestamp from "../../../components/timestamp";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
function GoalCard({ goal, ...props }: Omit<CardProps, "children"> & { goal: NostrEvent }) {
const nevent = getSharableEventAddress(goal);
const address = useShareableEventAddress(goal);
// if there is a parent intersection observer, register this card
const ref = useEventIntersectionRef(goal);
@ -27,7 +27,7 @@ function GoalCard({ goal, ...props }: Omit<CardProps, "children"> & { goal: Nost
<Card ref={ref} variant="outline" {...props}>
<CardHeader display="flex" gap="2" alignItems="center" p="2" pb="0" flexWrap="wrap">
<Heading size="md">
<Link as={RouterLink} to={`/goals/${nevent}`}>
<Link as={RouterLink} to={`/goals/${address}`}>
{getGoalName(goal)}
</Link>
</Heading>

View File

@ -26,7 +26,6 @@ import {
getReferencesFromList,
isSpecialListKind,
} from "../../../helpers/nostr/lists";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import ListFavoriteButton from "./list-favorite-button";
@ -41,6 +40,7 @@ import File02 from "../../../components/icons/file-02";
import SimpleLikeButton from "../../../components/event-reactions/simple-like-button";
import { createCoordinate } from "../../../classes/batch-kind-pubkey-loader";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import relayHintService from "../../../services/event-relay-hint";
export function ListCardContent({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
const people = getPubkeysFromList(list);
@ -83,7 +83,10 @@ export function ListCardContent({ list, ...props }: Omit<CardProps, "children">
export function createListLink(list: NostrEvent) {
const isSpecialList = isSpecialListKind(list.kind);
return "/lists/" + (isSpecialList ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list));
return (
"/lists/" +
(isSpecialList ? createCoordinate(list.kind, list.pubkey) : relayHintService.getSharableEventAddress(list))
);
}
function ListCardRender({

View File

@ -2,15 +2,15 @@ import { Image, MenuItem } from "@chakra-ui/react";
import { NostrEvent, isPTag } from "../../../types/nostr-event";
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
import { getSharableEventAddress } from "../../../helpers/nip19";
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 naddr = getSharableEventAddress(list);
const address = useShareableEventAddress(list);
const isSpecial = isSpecialListKind(list.kind);
const hasPeople = list.tags.some(isPTag);
@ -24,7 +24,7 @@ export default function ListMenu({ list, ...props }: { list: NostrEvent } & Omit
{hasPeople && (
<MenuItem
icon={<Image w="4" h="4" src="https://framerusercontent.com/images/3S3Pyvkh2tEvvKyX47QrUq7XQLk.png" />}
onClick={() => window.open(`https://www.makeprisms.com/create/${naddr}`, "_blank")}
onClick={() => window.open(`https://www.makeprisms.com/create/${address}`, "_blank")}
>
Create $prism
</MenuItem>

View File

@ -1,4 +1,4 @@
import { Button, Divider, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
import { Button, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
import { useNavigate, Link as RouterLink, Navigate } from "react-router-dom";
import { kinds } from "nostr-tools";
@ -8,7 +8,6 @@ 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 { getSharableEventAddress } from "../../helpers/nip19";
import {
BOOKMARK_LIST_KIND,
COMMUNITIES_LIST_KIND,
@ -19,6 +18,7 @@ import {
} from "../../helpers/nostr/lists";
import useFavoriteLists from "../../hooks/use-favorite-lists";
import VerticalPageLayout from "../../components/vertical-page-layout";
import relayHintService from "../../services/event-relay-hint";
function ListsHomePage() {
const account = useCurrentAccount()!;
@ -102,7 +102,7 @@ function ListsHomePage() {
<NewListModal
isOpen
onClose={newList.onClose}
onCreated={(list) => navigate(`/lists/${getSharableEventAddress(list)}`)}
onCreated={(list) => navigate(`/lists/${relayHintService.getSharableEventAddress(list)}`)}
/>
)}
</VerticalPageLayout>

View File

@ -1,5 +1,5 @@
import { ReactNode, forwardRef, memo, useMemo } from "react";
import { AvatarGroup, Flex, IconButton, IconButtonProps, Text, useDisclosure } from "@chakra-ui/react";
import { AvatarGroup, ButtonGroup, Flex, IconButton, IconButtonProps, Text, useDisclosure } from "@chakra-ui/react";
import { kinds, nip18, nip25 } from "nostr-tools";
import useCurrentAccount from "../../../hooks/use-current-account";
@ -25,6 +25,7 @@ import useSingleEvent from "../../../hooks/use-single-event";
import NotificationIconEntry from "./notification-icon-entry";
import { CategorizedEvent, NotificationType, typeSymbol } from "../../../classes/notifications";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import ZapReceiptMenu from "../../../components/zap/zap-receipt-menu";
export const ExpandableToggleButton = ({
toggle,
@ -128,7 +129,10 @@ const ZapNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ eve
</AvatarGroup>
<Text>{readablizeSats(zap.payment.amount / 1000)} sats</Text>
{zap.request.content && <Text>{zap.request.content}</Text>}
{eventJSX !== null && <ExpandableToggleButton aria-label="Toggle event" ml="auto" toggle={expanded} />}
<ButtonGroup size="sm" variant="ghost" ml="auto">
{eventJSX !== null && <ExpandableToggleButton aria-label="Toggle event" toggle={expanded} />}
<ZapReceiptMenu zap={zap.event} aria-label="More Options" />
</ButtonGroup>
</Flex>
{expanded.isOpen && eventJSX}
</NotificationIconEntry>

View File

@ -1,5 +1,5 @@
import { memo, useEffect, useMemo } from "react";
import { Button, ButtonGroup, Flex, IconButton, Input, useDisclosure } from "@chakra-ui/react";
import { memo, useMemo } from "react";
import { Button, ButtonGroup, Flex, IconButton, Input } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import dayjs from "dayjs";
@ -15,6 +15,7 @@ import NotificationItem from "./components/notification-item";
import NotificationTypeToggles from "./notification-type-toggles";
import { ChevronLeftIcon, ChevronRightIcon } from "../../components/icons";
import useRouteSearchValue from "../../hooks/use-route-search-value";
import useLocalStorageDisclosure from "../../hooks/use-localstorage-disclosure";
import { NotificationType, typeSymbol } from "../../classes/notifications";
const DATE_FORMAT = "YYYY-MM-DD";
@ -94,15 +95,11 @@ const NotificationsTimeline = memo(
function NotificationsPage() {
const { timeline } = useNotifications();
const showReplies = useDisclosure({ defaultIsOpen: localStorage.getItem("notifications-show-replies") !== "false" });
const showMentions = useDisclosure({
defaultIsOpen: localStorage.getItem("notifications-show-mentions") !== "false",
});
const showZaps = useDisclosure({ defaultIsOpen: localStorage.getItem("notifications-show-zaps") !== "false" });
const showReposts = useDisclosure({ defaultIsOpen: localStorage.getItem("notifications-show-reposts") !== "false" });
const showReactions = useDisclosure({
defaultIsOpen: localStorage.getItem("notifications-show-reactions") !== "false",
});
const showReplies = useLocalStorageDisclosure("notifications-show-replies");
const showMentions = useLocalStorageDisclosure("notifications-show-mentions");
const showZaps = useLocalStorageDisclosure("notifications-show-zaps");
const showReposts = useLocalStorageDisclosure("notifications-show-reposts");
const showReactions = useLocalStorageDisclosure("notifications-show-reactions");
const today = dayjs().format(DATE_FORMAT);
const { value: day, setValue: setDay } = useRouteSearchValue(
@ -142,15 +139,6 @@ function NotificationsPage() {
});
};
// save toggles to localStorage when changed
useEffect(() => {
localStorage.setItem("notifications-show-replies", String(showReplies.isOpen));
localStorage.setItem("notifications-show-mentions", String(showMentions.isOpen));
localStorage.setItem("notifications-show-zaps", String(showZaps.isOpen));
localStorage.setItem("notifications-show-reposts", String(showReposts.isOpen));
localStorage.setItem("notifications-show-reactions", String(showReactions.isOpen));
}, [showReplies.isOpen, showMentions.isOpen, showZaps.isOpen, showReposts.isOpen, showReactions.isOpen]);
const callback = useTimelineCurserIntersectionCallback(timeline);
return (

View File

@ -21,23 +21,24 @@ import UserLink from "../../components/user/user-link";
import { CompactNoteContent } from "../../components/compact-note-content";
import Timestamp from "../../components/timestamp";
import HoverLinkOverlay from "../../components/hover-link-overlay";
import { getSharableEventAddress } from "../../helpers/nip19";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
import { useNavigateInDrawer } from "../../providers/drawer-sub-view-provider";
import useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND];
function ReplyEntry({ event }: { event: NostrEvent }) {
const navigate = useNavigateInDrawer();
const address = useShareableEventAddress(event);
const onClick = useCallback<MouseEventHandler>(
(e) => {
e.preventDefault();
e.stopPropagation();
navigate(`/n/${getSharableEventAddress(event)}`);
navigate(`/n/${address}`);
},
[navigate],
[navigate, address],
);
return (
@ -47,7 +48,7 @@ function ReplyEntry({ event }: { event: NostrEvent }) {
<Timestamp timestamp={event.created_at} />
</Flex>
<CompactNoteContent event={event} maxLength={100} />
<HoverLinkOverlay as={RouterLink} to={`/n/${getSharableEventAddress(event)}`} onClick={onClick} />
<HoverLinkOverlay as={RouterLink} to={`/n/${address}`} onClick={onClick} />
</LinkBox>
);
}

View File

@ -13,6 +13,7 @@ import {
LinearScale,
CategoryScale,
} from "chart.js";
import { Filter } from "nostr-tools";
import _throttle from "lodash.throttle";
import { useAppTitle } from "../../../hooks/use-app-title";
@ -22,7 +23,6 @@ import { groupByTime } from "../../../helpers/notification";
import { useCallback, useEffect, useMemo, useState } from "react";
import EventStore from "../../../classes/event-store";
import { getSortedKinds, sortByDate } from "../../../helpers/nostr/event";
import { NostrQuery } from "../../../types/nostr-relay";
import relayPoolService from "../../../services/relay-pool";
import EventKindsPieChart from "../../../components/charts/event-kinds-pie-chart";
import EventKindsTable from "../../../components/charts/event-kinds-table";
@ -107,7 +107,7 @@ export default function RelayDetailsTab({ relay }: { relay: string }) {
const [loading, setLoading] = useState(false);
const loadMore = useCallback(() => {
setLoading(true);
const query: NostrQuery = { limit: 500 };
const query: Filter = { limit: 500 };
const last = store.getLastEvent();
if (last) query.until = last.created_at;

View File

@ -6,7 +6,7 @@ import { Link as RouterLink } from "react-router-dom";
import UserAvatar from "../../../components/user/user-avatar";
import UserLink from "../../../components/user/user-link";
import StreamStatusBadge from "./status-badge";
import useEventNaddr from "../../../hooks/use-event-naddr";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
import StreamHashtags from "./stream-hashtags";
import Timestamp from "../../../components/timestamp";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
@ -17,7 +17,7 @@ function StreamCard({ stream, ...props }: CardProps & { stream: ParsedStream })
// if there is a parent intersection observer, register this card
const ref = useEventIntersectionRef(stream.event);
const naddr = useEventNaddr(stream.event, stream.relays);
const naddr = useShareableEventAddress(stream.event, stream.relays);
return (
<Card {...props} ref={ref} position="relative">

View File

@ -4,22 +4,22 @@ import { Card, CardBody, CardHeader, CardProps, Flex, Heading, Link } from "@cha
import { ParsedStream } from "../../../helpers/nostr/stream";
import { getGoalName } from "../../../helpers/nostr/goal";
import GoalProgress from "../../goals/components/goal-progress";
import { getSharableEventAddress } from "../../../helpers/nip19";
import GoalTopZappers from "../../goals/components/goal-top-zappers";
import GoalZapButton from "../../goals/components/goal-zap-button";
import useStreamGoal from "../../../hooks/use-stream-goal";
import relayHintService from "../../../services/event-relay-hint";
export default function StreamGoal({ stream, ...props }: Omit<CardProps, "children"> & { stream: ParsedStream }) {
const goal = useStreamGoal(stream);
if (!goal) return null;
const nevent = getSharableEventAddress(goal);
const address = relayHintService.getSharableEventAddress(goal);
return (
<Card direction="column" gap="1" {...props}>
<CardHeader px="2" pt="2" pb="0">
<Heading size="md">
<Link as={RouterLink} to={`/goals/${nevent}`}>
<Link as={RouterLink} to={`/goals/${address}`}>
{getGoalName(goal)}
</Link>
</Heading>

View File

@ -1,10 +1,10 @@
import { useContext } from "react";
import { Button, ButtonProps } from "@chakra-ui/react";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { PostModalContext } from "../../../providers/route/post-modal-provider";
import { RepostIcon } from "../../../components/icons";
import { ParsedStream } from "../../../helpers/nostr/stream";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
export type StreamShareButtonProps = Omit<ButtonProps, "children" | "onClick"> & {
stream: ParsedStream;
@ -19,8 +19,8 @@ export default function StreamShareButton({
const { openModal } = useContext(PostModalContext);
const handleClick = () => {
const nevent = getSharableEventAddress(stream.event);
openModal({ initContent: "\nnostr:" + nevent });
const address = useShareableEventAddress(stream.event);
openModal({ initContent: "\nnostr:" + address });
};
return (

View File

@ -1,5 +1,6 @@
import { useCallback, useMemo } from "react";
import { Flex, Heading, SimpleGrid, Switch } from "@chakra-ui/react";
import { Filter } from "nostr-tools";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
@ -12,7 +13,6 @@ import PeopleListSelection from "../../components/people-list-selection/people-l
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
import useParsedStreams from "../../hooks/use-parsed-streams";
import { NostrRequestFilter } from "../../types/nostr-relay";
import { useAppTitle } from "../../hooks/use-app-title";
import { NostrEvent } from "../../types/nostr-event";
import VerticalPageLayout from "../../components/vertical-page-layout";
@ -36,7 +36,7 @@ function StreamsPage() {
);
const { filter, listId } = usePeopleListContext();
const query = useMemo<NostrRequestFilter | undefined>(() => {
const query = useMemo<Filter | Filter[] | undefined>(() => {
if (!filter) return undefined;
return [
{ authors: filter.authors, kinds: [STREAM_KIND] },

View File

@ -1,12 +1,11 @@
import { useCallback, useMemo } from "react";
import { kinds } from "nostr-tools";
import { Filter, kinds } from "nostr-tools";
import { getEventUID } from "../../../../helpers/nostr/event";
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../helpers/nostr/stream";
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
import { NostrEvent } from "../../../../types/nostr-event";
import useStreamGoal from "../../../../hooks/use-stream-goal";
import { NostrQuery } from "../../../../types/nostr-relay";
import useUserMuteFilter from "../../../../hooks/use-user-mute-filter";
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
import { useReadRelays } from "../../../../hooks/use-client-relays";
@ -29,7 +28,7 @@ export default function useStreamChatTimeline(stream: ParsedStream) {
const goal = useStreamGoal(stream);
const query = useMemo(() => {
const streamQuery: NostrQuery = {
const streamQuery: Filter = {
"#a": [getATag(stream)],
kinds: [STREAM_CHAT_MESSAGE_KIND, kinds.Zap],
};

View File

@ -5,7 +5,6 @@ import { ParsedStream } from "../../../../helpers/nostr/stream";
import UserAvatar from "../../../../components/user/user-avatar";
import UserLink from "../../../../components/user/user-link";
import { NostrEvent } from "../../../../types/nostr-event";
import { useRegisterIntersectionEntity } from "../../../../providers/local/intersection-observer";
import { LightningIcon } from "../../../../components/icons";
import { getParsedZap } from "../../../../helpers/nostr/zaps";
import { readablizeSats } from "../../../../helpers/bolt11";

View File

@ -1,5 +1,5 @@
import { memo } from "react";
import { Box, Flex, Text } from "@chakra-ui/react";
import { Box, ButtonGroup, Flex, Text } from "@chakra-ui/react";
import { ThreadItem } from "../../../../helpers/thread";
import { ParsedZap } from "../../../../helpers/nostr/zaps";
@ -10,6 +10,7 @@ import { LightningIcon } from "../../../../components/icons";
import { readablizeSats } from "../../../../helpers/bolt11";
import TextNoteContents from "../../../../components/note/timeline-note/text-note-contents";
import { TrustProvider } from "../../../../providers/local/trust-provider";
import ZapReceiptMenu from "../../../../components/zap/zap-receipt-menu";
const ZapEvent = memo(({ zap }: { zap: ParsedZap }) => {
if (!zap.payment.amount) return null;
@ -28,6 +29,10 @@ const ZapEvent = memo(({ zap }: { zap: ParsedZap }) => {
<Timestamp timestamp={zap.event.created_at} ml="2" />
<TextNoteContents event={zap.request} />
</Box>
<ButtonGroup ml="auto" size="sm" variant="ghost">
<ZapReceiptMenu zap={zap.event} aria-label="More Options" />
</ButtonGroup>
</Flex>
</TrustProvider>
);

View File

@ -14,12 +14,11 @@ import Expand01 from "../../../components/icons/expand-01";
import Minus from "../../../components/icons/minus";
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
import UserDnsIdentity from "../../../components/user/user-dns-identity";
import { getSharableEventAddress } from "../../../helpers/nip19";
import useAppSettings from "../../../hooks/use-app-settings";
import useThreadColorLevelProps from "../../../hooks/use-thread-color-level-props";
import POWIcon from "../../../components/pow/pow-icon";
import RepostButton from "../../../components/note/timeline-note/components/repost-button";
import QuoteRepostButton from "../../../components/note/quote-repost-button";
import QuoteEventButton from "../../../components/note/quote-event-button";
import NoteZapButton from "../../../components/note/note-zap-button";
import NoteProxyLink from "../../../components/note/timeline-note/components/note-proxy-link";
import BookmarkButton from "../../../components/note/bookmark-button";
@ -30,6 +29,7 @@ import NoteReactions from "../../../components/note/timeline-note/components/not
import ZapBubbles from "../../../components/note/timeline-note/components/zap-bubbles";
import DetailsTabs from "./details-tabs";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import relayHintService from "../../../services/event-relay-hint";
export type ThreadItemProps = {
post: ThreadItem;
@ -68,7 +68,12 @@ function ThreadPost({ post, initShowReplies, focusId, level = -1 }: ThreadItemPr
<UserLink pubkey={post.event.pubkey} fontWeight="bold" isTruncated />
<UserDnsIdentity pubkey={post.event.pubkey} onlyIcon />
<POWIcon event={post.event} boxSize={5} />
<Link as={RouterLink} whiteSpace="nowrap" color="current" to={`/n/${getSharableEventAddress(post.event)}`}>
<Link
as={RouterLink}
whiteSpace="nowrap"
color="current"
to={`/n/${relayHintService.getSharableEventAddress(post.event)}`}
>
<Timestamp timestamp={post.event.created_at} />
</Link>
{replies.length > 0 ? (
@ -109,7 +114,7 @@ function ThreadPost({ post, initShowReplies, focusId, level = -1 }: ThreadItemPr
<ButtonGroup variant="ghost" size="sm">
<IconButton aria-label="Reply" title="Reply" onClick={replyForm.onToggle} icon={<ReplyIcon />} />
<RepostButton event={post.event} />
<QuoteRepostButton event={post.event} />
<QuoteEventButton event={post.event} />
<NoteZapButton event={post.event} />
</ButtonGroup>
{!showReactionsOnNewLine && reactionButtons}

View File

@ -14,11 +14,10 @@ import useSingleEvent from "../../hooks/use-single-event";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
import LoadingNostrLink from "../../components/loading-nostr-link";
import UserName from "../../components/user/user-name";
import { getSharableEventAddress } from "../../helpers/nip19";
import UserAvatarLink from "../../components/user/user-avatar-link";
import { ReplyIcon } from "../../components/icons";
import TimelineNote from "../../components/note/timeline-note";
import TimelineLoader from "../../classes/timeline-loader";
import relayHintService from "../../services/event-relay-hint";
function CollapsedReplies({
pointer,
@ -44,7 +43,7 @@ function CollapsedReplies({
<UserAvatarLink pubkey={post.event.pubkey} size="xs" />
<UserName pubkey={post.event.pubkey} fontWeight="bold" />
{root.id !== pointer.id && <ReplyIcon />}
<Link as={RouterLink} to={`/n/${getSharableEventAddress(post.event)}`} isTruncated>
<Link as={RouterLink} to={`/n/${relayHintService.getSharableEventAddress(post.event)}`} isTruncated>
{post.event.content}
</Link>
</Card>

View File

@ -2,7 +2,7 @@ import { useContext } from "react";
import { SatelliteCDNFile } from "../../../helpers/satellite-cdn";
import { PostModalContext } from "../../../providers/route/post-modal-provider";
import { ButtonProps, IconButton } from "@chakra-ui/react";
import { QuoteRepostIcon } from "../../../components/icons";
import { QuoteEventIcon } from "../../../components/icons";
export type ShareFileButtonProps = Omit<ButtonProps, "children" | "onClick"> & {
file: SatelliteCDNFile;
@ -22,7 +22,7 @@ export default function ShareFileButton({
return (
<IconButton
icon={<QuoteRepostIcon />}
icon={<QuoteEventIcon />}
onClick={handleClick}
aria-label={ariaLabel || title}
title={title}

View File

@ -9,6 +9,7 @@ import MuteUserMenuItem from "../../../components/common-menu-items/mute-user";
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
import QuoteEventMenuItem from "../../../components/common-menu-items/quote-event";
export default function TorrentMenu({
torrent,
@ -20,6 +21,7 @@ export default function TorrentMenu({
<>
<DotsMenuButton {...props}>
<OpenInAppMenuItem event={torrent} />
<QuoteEventMenuItem event={torrent} />
<CopyEmbedCodeMenuItem event={torrent} />
<MuteUserMenuItem event={torrent} />
<DeleteEventMenuItem event={torrent} />

View File

@ -7,11 +7,11 @@ import { NostrEvent } from "../../../types/nostr-event";
import Timestamp from "../../../components/timestamp";
import UserLink from "../../../components/user/user-link";
import Magnet from "../../../components/icons/magnet";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { formatBytes } from "../../../helpers/number";
import TorrentMenu from "./torrent-menu";
import NoteZapButton from "../../../components/note/note-zap-button";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
type DisplayCategory = { name: string; tags: string[] };
@ -19,6 +19,7 @@ function TorrentTableRow({ torrent }: { torrent: NostrEvent }) {
const ref = useEventIntersectionRef<HTMLTableRowElement>(torrent);
const magnetLink = useMemo(() => getTorrentMagnetLink(torrent), [torrent]);
const address = useShareableEventAddress(torrent);
const categories: DisplayCategory[] = [];
const chain: string[] = [];
@ -56,7 +57,7 @@ function TorrentTableRow({ torrent }: { torrent: NostrEvent }) {
))}
</Td>
<Td maxW="lg" overflow="hidden" isTruncated>
<Link as={RouterLink} to={`/torrents/${getSharableEventAddress(torrent)}`}>
<Link as={RouterLink} to={`/torrents/${address}`}>
{getTorrentTitle(torrent)}
</Link>
</Td>

View File

@ -42,7 +42,7 @@ import { getThreadReferences } from "../../helpers/nostr/event";
import MessageTextCircle01 from "../../components/icons/message-text-circle-01";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
import NoteZapButton from "../../components/note/note-zap-button";
import QuoteRepostButton from "../../components/note/quote-repost-button";
import QuoteEventButton from "../../components/note/quote-event-button";
import { TextNoteContents } from "../../components/note/timeline-note/text-note-contents";
function TorrentDetailsPage({ torrent }: { torrent: NostrEvent }) {
@ -75,7 +75,7 @@ function TorrentDetailsPage({ torrent }: { torrent: NostrEvent }) {
</Flex>
<ButtonGroup variant="ghost" size="sm">
<NoteZapButton event={torrent} />
<QuoteRepostButton event={torrent} />
<QuoteEventButton event={torrent} />
<Button as={Link} leftIcon={<Magnet boxSize={5} />} href={getTorrentMagnetLink(torrent)} isExternal>
Download torrent
</Button>

View File

@ -12,7 +12,7 @@ import TrackDownloadButton from "./track-download-button";
import TrackPlayer from "./track-player";
import UserDnsIdentity from "../../../components/user/user-dns-identity";
import TrackMenu from "./track-menu";
import QuoteRepostButton from "../../../components/note/quote-repost-button";
import QuoteEventButton from "../../../components/note/quote-event-button";
import NoteZapButton from "../../../components/note/note-zap-button";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
@ -45,7 +45,7 @@ export default function TrackCard({ track, ...props }: { track: NostrEvent } & O
<Button leftIcon={<ReplyIcon />} isDisabled>
Comment
</Button>
<QuoteRepostButton event={track} />
<QuoteEventButton event={track} />
<NoteZapButton event={track} />
</ButtonGroup>
<ButtonGroup size="sm" ml="auto">

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import {
Button,
Flex,
@ -22,14 +23,13 @@ import useUserProfileBadges from "../../../hooks/use-user-profile-badges";
import { getBadgeDescription, getBadgeImage, getBadgeName } from "../../../helpers/nostr/badges";
import { getEventCoordinate } from "../../../helpers/nostr/event";
import { NostrEvent } from "../../../types/nostr-event";
import { getSharableEventAddress } from "../../../helpers/nip19";
import UserAvatarLink from "../../../components/user/user-avatar-link";
import UserLink from "../../../components/user/user-link";
import Timestamp from "../../../components/timestamp";
import { useState } from "react";
import relayHintService from "../../../services/event-relay-hint";
function Badge({ pubkey, badge, award }: { pubkey: string; badge: NostrEvent; award: NostrEvent }) {
const naddr = getSharableEventAddress(badge);
const naddr = relayHintService.getSharableEventAddress(badge);
const modal = useDisclosure();
const description = getBadgeDescription(badge);

View File

@ -4,8 +4,8 @@ import { Link as RouterLink } from "react-router-dom";
import { NostrEvent } from "../../../types/nostr-event";
import { getVideoDuration, getVideoImages, getVideoSummary, getVideoTitle } from "../../../helpers/nostr/flare";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import { getSharableEventAddress } from "../../../helpers/nip19";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
export default function VideoCard({ video, ...props }: Omit<CardProps, "children"> & { video: NostrEvent }) {
const title = getVideoTitle(video);
@ -14,6 +14,7 @@ export default function VideoCard({ video, ...props }: Omit<CardProps, "children
const summary = getVideoSummary(video);
const ref = useEventIntersectionRef(video);
const address = useShareableEventAddress(video);
return (
<Card as={LinkBox} {...props}>
@ -25,7 +26,7 @@ export default function VideoCard({ video, ...props }: Omit<CardProps, "children
backgroundSize="cover"
/>
<CardHeader p="2">
<HoverLinkOverlay as={RouterLink} to={`/videos/${getSharableEventAddress(video)}`}>
<HoverLinkOverlay as={RouterLink} to={`/videos/${address}`}>
<Heading size="sm" isTruncated>
{title}
</Heading>

View File

@ -28,7 +28,7 @@ import UserName from "../../components/user/user-name";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
import SimpleBookmarkButton from "../../components/simple-bookmark-button";
import NoteZapButton from "../../components/note/note-zap-button";
import QuoteRepostButton from "../../components/note/quote-repost-button";
import QuoteEventButton from "../../components/note/quote-event-button";
function VideoRecommendations({ video }: { video: NostrEvent }) {
const readRelays = useReadRelays();
@ -77,7 +77,7 @@ function VideoDetailsPage({ video }: { video: NostrEvent }) {
<UserFollowButton pubkey={video.pubkey} size="sm" />
<ButtonGroup ml="auto" size="sm" variant="ghost">
<SimpleBookmarkButton event={video} aria-label="Bookmark video" title="Bookmark video" />
<QuoteRepostButton event={video} />
<QuoteEventButton event={video} />
</ButtonGroup>
<VideoMenu video={video} aria-label="More options" size="sm" />
</Flex>

View File

@ -24,12 +24,14 @@ import MarkdownContent from "./components/markdown";
import { WIKI_RELAYS } from "../../const";
import UserName from "../../components/user/user-name";
import WikiPageMenu from "./components/wiki-page-menu";
import { getSharableEventAddress } from "../../helpers/nip19";
import { ExternalLinkIcon } from "../../components/icons";
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
function WikiComparePage({ base, diff }: { base: NostrEvent; diff: NostrEvent }) {
const vertical = useBreakpointValue({ base: true, lg: false }) ?? false;
const identical = base.content.trim() === diff.content.trim();
const baseAddress = useShareableEventAddress(base);
const diffAddress = useShareableEventAddress(diff);
return (
<VerticalPageLayout>
@ -40,7 +42,7 @@ function WikiComparePage({ base, diff }: { base: NostrEvent; diff: NostrEvent })
<ButtonGroup float="right" size="sm">
<IconButton
as={RouterLink}
to={`/wiki/page/${getSharableEventAddress(base)}`}
to={`/wiki/page/${baseAddress}`}
icon={<ExternalLinkIcon />}
aria-label="Open Page"
/>
@ -57,7 +59,7 @@ function WikiComparePage({ base, diff }: { base: NostrEvent; diff: NostrEvent })
<ButtonGroup float="right" size="sm">
<IconButton
as={RouterLink}
to={`/wiki/page/${getSharableEventAddress(diff)}`}
to={`/wiki/page/${diffAddress}`}
icon={<ExternalLinkIcon />}
aria-label="Open Page"
/>

View File

@ -10,11 +10,12 @@ import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu
import useCurrentAccount from "../../../hooks/use-current-account";
import { EditIcon } from "../../../components/icons";
import { getPageTopic } from "../../../helpers/nostr/wiki";
import { getSharableEventAddress } from "../../../helpers/nip19";
import GitBranch02 from "../../../components/icons/git-branch-02";
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
export default function WikiPageMenu({ page, ...props }: { page: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
const account = useCurrentAccount();
const address = useShareableEventAddress(page);
return (
<>
@ -26,7 +27,7 @@ export default function WikiPageMenu({ page, ...props }: { page: NostrEvent } &
</MenuItem>
)}
{account?.pubkey !== page.pubkey && (
<MenuItem as={RouterLink} to={`/wiki/create?fork=${getSharableEventAddress(page)}`} icon={<GitBranch02 />}>
<MenuItem as={RouterLink} to={`/wiki/create?fork=${address}`} icon={<GitBranch02 />}>
Fork Page
</MenuItem>
)}

View File

@ -4,13 +4,13 @@ import { NostrEvent, nip19 } from "nostr-tools";
import { Link as RouterLink } from "react-router-dom";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { getPageForks, getPageSummary, getPageTitle, getPageTopic } from "../../../helpers/nostr/wiki";
import UserLink from "../../../components/user/user-link";
import FileSearch01 from "../../../components/icons/file-search-01";
import GitBranch01 from "../../../components/icons/git-branch-01";
import UserName from "../../../components/user/user-name";
import UserAvatar from "../../../components/user/user-avatar";
import relayHintService from "../../../services/event-relay-hint";
export default function WikiPageResult({ page, compare }: { page: NostrEvent; compare?: NostrEvent }) {
const topic = getPageTopic(page);
@ -22,7 +22,7 @@ export default function WikiPageResult({ page, compare }: { page: NostrEvent; co
<Box overflow="hidden">
<Flex gap="2" wrap="wrap">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${relayHintService.getSharableEventAddress(page)}`}>
{getPageTitle(page)}
</HoverLinkOverlay>
</Heading>

View File

@ -24,7 +24,6 @@ import VerticalPageLayout from "../../components/vertical-page-layout";
import { removeNonASCIIChar } from "../../helpers/string";
import { usePublishEvent } from "../../providers/global/publish-provider";
import { WIKI_PAGE_KIND, getPageSummary, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
import { getSharableEventAddress } from "../../helpers/nip19";
import useCacheForm from "../../hooks/use-cache-form";
import MarkdownEditor from "./components/markdown-editor";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
@ -33,6 +32,7 @@ import UserName from "../../components/user/user-name";
import { getEventCoordinate } from "../../helpers/nostr/event";
import FormatButton from "./components/format-toolbar";
import dictionaryService from "../../services/dictionary";
import relayHintService from "../../services/event-relay-hint";
export default function CreateWikiPageView() {
const toast = useToast();
@ -111,7 +111,7 @@ export default function CreateWikiPageView() {
const pub = await publish("Publish Page", draft, WIKI_RELAYS, false);
dictionaryService.handleEvent(pub.event);
clearFormCache();
navigate(`/wiki/page/${getSharableEventAddress(pub.event)}`, { replace: true });
navigate(`/wiki/page/${relayHintService.getSharableEventAddress(pub.event)}`, { replace: true });
} catch (error) {
if (error instanceof Error) toast({ description: error.message, status: "error" });
}

View File

@ -19,7 +19,6 @@ import useCacheForm from "../../hooks/use-cache-form";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import useCurrentAccount from "../../hooks/use-current-account";
import { WIKI_PAGE_KIND, getPageSummary, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
import { getSharableEventAddress } from "../../helpers/nip19";
import { usePublishEvent } from "../../providers/global/publish-provider";
import VerticalPageLayout from "../../components/vertical-page-layout";
import MarkdownEditor from "./components/markdown-editor";
@ -27,6 +26,7 @@ import { ErrorBoundary } from "../../components/error-boundary";
import { cloneEvent, replaceOrAddSimpleTag } from "../../helpers/nostr/event";
import FormatButton from "./components/format-toolbar";
import dictionaryService from "../../services/dictionary";
import relayHintService from "../../services/event-relay-hint";
function EditWikiPagePage({ page }: { page: NostrEvent }) {
const toast = useToast();
@ -64,7 +64,7 @@ function EditWikiPagePage({ page }: { page: NostrEvent }) {
const pub = await publish("Publish Page", draft, WIKI_RELAYS, false);
dictionaryService.handleEvent(pub.event);
clearFormCache();
navigate(`/wiki/page/${getSharableEventAddress(pub.event)}`, { replace: true });
navigate(`/wiki/page/${relayHintService.getSharableEventAddress(pub.event)}`, { replace: true });
} catch (error) {
if (error instanceof Error) toast({ description: error.message, status: "error" });
}

View File

@ -31,14 +31,14 @@ import { ExternalLinkIcon } from "../../components/icons";
import FileSearch01 from "../../components/icons/file-search-01";
import NoteZapButton from "../../components/note/note-zap-button";
import ZapBubbles from "../../components/note/timeline-note/components/zap-bubbles";
import QuoteRepostButton from "../../components/note/quote-repost-button";
import QuoteEventButton from "../../components/note/quote-event-button";
import WikiPageMenu from "./components/wiki-page-menu";
import EventVoteButtons from "../../components/reactions/event-vote-buttions";
import useCurrentAccount from "../../hooks/use-current-account";
import dictionaryService from "../../services/dictionary";
import { useReadRelays } from "../../hooks/use-client-relays";
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
import { getSharableEventAddress } from "../../helpers/nip19";
import relayHintService from "../../services/event-relay-hint";
function ForkAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
const topic = getPageTopic(page);
@ -111,7 +111,11 @@ export function WikiPagePage({ page }: { page: NostrEvent }) {
</Button>
)}
{page.pubkey !== account?.pubkey && (
<Button as={RouterLink} colorScheme="primary" to={`/wiki/create?fork=${getSharableEventAddress(page)}`}>
<Button
as={RouterLink}
colorScheme="primary"
to={`/wiki/create?fork=${relayHintService.getSharableEventAddress(page)}`}
>
Fork
</Button>
)}
@ -119,7 +123,7 @@ export function WikiPagePage({ page }: { page: NostrEvent }) {
<Flex alignItems="flex-end" gap="2" ml="auto">
<EventVoteButtons event={page} inline chevrons={false} />
<ButtonGroup size="sm">
<QuoteRepostButton event={page} />
<QuoteEventButton event={page} />
<NoteZapButton event={page} showEventPreview={false} />
<WikiPageMenu page={page} aria-label="Page Options" />
</ButtonGroup>