diff --git a/.changeset/shaggy-toys-explode.md b/.changeset/shaggy-toys-explode.md new file mode 100644 index 000000000..cfb6ea45c --- /dev/null +++ b/.changeset/shaggy-toys-explode.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add support for playing back stream recordings diff --git a/.changeset/smart-students-peel.md b/.changeset/smart-students-peel.md new file mode 100644 index 000000000..a153ecce3 --- /dev/null +++ b/.changeset/smart-students-peel.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Correctly handle replaceable events in timeline loader diff --git a/src/app.tsx b/src/app.tsx index 545d5e681..4d03b4dda 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -35,6 +35,8 @@ import UserLikesTab from "./views/user/likes"; import useSetColorMode from "./hooks/use-set-color-mode"; import UserStreamsTab from "./views/user/streams"; import { PageProviders } from "./providers"; +import { NostrRequest } from "./classes/nostr-request"; +import { NostrEvent } from "./types/nostr-event"; const StreamsView = React.lazy(() => import("./views/streams")); const StreamView = React.lazy(() => import("./views/streams/stream")); diff --git a/src/classes/event-store.ts b/src/classes/event-store.ts new file mode 100644 index 000000000..87f3221bb --- /dev/null +++ b/src/classes/event-store.ts @@ -0,0 +1,60 @@ +import { getEventUID } from "../helpers/nostr/event"; +import { NostrEvent } from "../types/nostr-event"; +import Subject from "./subject"; + +type EventFilter = (event: NostrEvent) => boolean; +export default class EventStore { + name?: string; + events = new Map(); + + constructor(name?: string) { + this.name = name; + } + + getSortedEvents() { + return Array.from(this.events.values()).sort((a, b) => b.created_at - a.created_at); + } + + onEvent = new Subject(); + onClear = new Subject(); + + addEvent(event: NostrEvent) { + const id = getEventUID(event); + const existing = this.events.get(id); + if (!existing || event.created_at > existing.created_at) { + this.events.set(id, event); + this.onEvent.next(event); + return true; + } + return false; + } + + clear() { + this.events.clear(); + this.onClear.next(null); + } + + connect(other: EventStore) { + other.onEvent.subscribe(this.addEvent, this); + } + disconnect(other: EventStore) { + other.onEvent.unsubscribe(this.addEvent, this); + } + + getFirstEvent(nth = 0, filter?: EventFilter) { + const events = this.getSortedEvents(); + const filteredEvents = filter ? events.filter(filter) : events; + for (let i = 0; i <= nth; i++) { + const event = filteredEvents[i]; + if (event) return event; + } + } + getLastEvent(nth = 0, filter?: EventFilter) { + const events = this.getSortedEvents(); + const filteredEvents = filter ? events.filter(filter) : events; + for (let i = nth; i >= 0; i--) { + const event = filteredEvents[filteredEvents.length - 1 - i]; + if (event) return event; + } + } +} diff --git a/src/classes/thread-loader.ts b/src/classes/thread-loader.ts index ca408cc33..f22e3fe6a 100644 --- a/src/classes/thread-loader.ts +++ b/src/classes/thread-loader.ts @@ -1,4 +1,4 @@ -import { getReferences } from "../helpers/nostr-event"; +import { getReferences } from "../helpers/nostr/event"; import { NostrEvent } from "../types/nostr-event"; import { NostrRequest } from "./nostr-request"; import { NostrMultiSubscription } from "./nostr-multi-subscription"; diff --git a/src/classes/timeline-loader.ts b/src/classes/timeline-loader.ts index b04fc99db..52416cd9d 100644 --- a/src/classes/timeline-loader.ts +++ b/src/classes/timeline-loader.ts @@ -1,12 +1,13 @@ import dayjs from "dayjs"; import { utils } from "nostr-tools"; -import debug, { Debug, Debugger } from "debug"; +import { Debugger } from "debug"; import { NostrEvent } from "../types/nostr-event"; import { NostrQuery, NostrRequestFilter } from "../types/nostr-query"; import { NostrRequest } from "./nostr-request"; import { NostrMultiSubscription } from "./nostr-multi-subscription"; import Subject, { PersistentSubject } from "./subject"; import { logger } from "../helpers/debug"; +import EventStore from "./event-store"; function addToQuery(filter: NostrRequestFilter, query: NostrQuery) { if (Array.isArray(filter)) { @@ -28,11 +29,10 @@ class RelayTimelineLoader { private log: Debugger; loading = false; - events: NostrEvent[] = []; + events: EventStore; /** set to true when the next block produces 0 events */ complete = false; - onEvent = new Subject(); onBlockFinish = new Subject(); constructor(relay: string, query: NostrRequestFilter, name: string, log?: Debugger) { @@ -41,19 +41,24 @@ class RelayTimelineLoader { this.name = name; this.log = log || logger.extend(name); + this.events = new EventStore(relay); } loadNextBlock() { this.loading = true; let query: NostrRequestFilter = addToQuery(this.query, { limit: this.blockSize }); - if (this.events[this.events.length - 1]) { - query = addToQuery(query, { until: this.events[this.events.length - 1].created_at - 1 }); + let oldestEvent = this.getLastEvent(); + if (oldestEvent) { + query = addToQuery(query, { until: oldestEvent.created_at - 1 }); } const request = new NostrRequest([this.relay], undefined, this.name + "-" + this.requestId++); let gotEvents = 0; request.onEvent.subscribe((e) => { + // if(oldestEvent && e.created_at(); private handleEvent(event: NostrEvent) { - if (!this.seenEvents.has(event.id)) { - this.seenEvents.add(event.id); - this.events = utils.insertEventIntoDescendingList(Array.from(this.events), event); - this.onEvent.next(event); - return true; - } - return false; + return this.events.addEvent(event); } getLastEvent(nth = 0, filter?: EventFilter) { - const events = filter ? this.events.filter(filter) : this.events; - for (let i = nth; i >= 0; i--) { - const event = events[events.length - 1 - i]; - if (event) return event; - } + return this.events.getLastEvent(nth, filter); } } @@ -93,7 +87,7 @@ export class TimelineLoader { query?: NostrRequestFilter; relays: string[] = []; - events = new PersistentSubject([]); + events: EventStore; timeline = new PersistentSubject([]); loading = new PersistentSubject(false); complete = new PersistentSubject(false); @@ -110,20 +104,23 @@ export class TimelineLoader { constructor(name: string) { this.name = name; this.log = logger.extend("TimelineLoader:" + name); + this.events = new EventStore(name); + this.subscription = new NostrMultiSubscription([], undefined, name); this.subscription.onEvent.subscribe(this.handleEvent, this); + + // update the timeline when there are new events + this.events.onEvent.subscribe(this.updateTimeline, this); + this.events.onClear.subscribe(this.updateTimeline, this); } - private seenEvents = new Set(); + private updateTimeline() { + if (this.eventFilter) { + this.timeline.next(this.events.getSortedEvents().filter(this.eventFilter)); + } else this.timeline.next(this.events.getSortedEvents()); + } private handleEvent(event: NostrEvent) { - if (!this.seenEvents.has(event.id)) { - this.seenEvents.add(event.id); - this.events.next(utils.insertEventIntoDescendingList(Array.from(this.events.value), event)); - - if (!this.eventFilter || this.eventFilter(event)) { - this.timeline.next(utils.insertEventIntoDescendingList(Array.from(this.timeline.value), event)); - } - } + this.events.addEvent(event); } private createLoaders() { @@ -133,7 +130,7 @@ export class TimelineLoader { if (!this.relayTimelineLoaders.has(relay)) { const loader = new RelayTimelineLoader(relay, this.query, this.name, this.log.extend(relay)); this.relayTimelineLoaders.set(relay, loader); - loader.onEvent.subscribe(this.handleEvent, this); + this.events.connect(loader.events); loader.onBlockFinish.subscribe(this.updateLoading, this); loader.onBlockFinish.subscribe(this.updateComplete, this); } @@ -142,9 +139,9 @@ export class TimelineLoader { private removeLoaders(filter?: (loader: RelayTimelineLoader) => boolean) { for (const [relay, loader] of this.relayTimelineLoaders) { if (!filter || filter(loader)) { - loader?.onEvent.unsubscribe(this.handleEvent, this); - loader?.onBlockFinish.unsubscribe(this.updateLoading, this); - loader?.onBlockFinish.unsubscribe(this.updateComplete, this); + this.events.disconnect(loader.events); + loader.onBlockFinish.unsubscribe(this.updateLoading, this); + loader.onBlockFinish.unsubscribe(this.updateComplete, this); this.relayTimelineLoaders.delete(relay); } } @@ -168,9 +165,8 @@ export class TimelineLoader { this.removeLoaders(); this.query = query; - this.events.next([]); + this.events.clear(); this.timeline.next([]); - this.seenEvents.clear(); this.createLoaders(); this.updateComplete(); @@ -181,9 +177,7 @@ export class TimelineLoader { } setFilter(filter?: (event: NostrEvent) => boolean) { this.eventFilter = filter; - if (this.eventFilter) { - this.timeline.next(this.events.value.filter(this.eventFilter)); - } + this.updateTimeline(); } setCursor(cursor: number) { @@ -250,9 +244,8 @@ export class TimelineLoader { // TODO: this is only needed because the current logic dose not remove events when the relay they where fetched from is removed /** @deprecated */ forgetEvents() { - this.events.next([]); + this.events.clear(); this.timeline.next([]); - this.seenEvents.clear(); this.subscription.forgetEvents(); } } diff --git a/src/components/debug-modals/note-debug-modal.tsx b/src/components/debug-modals/note-debug-modal.tsx index e8dba9237..d18f54472 100644 --- a/src/components/debug-modals/note-debug-modal.tsx +++ b/src/components/debug-modals/note-debug-modal.tsx @@ -1,7 +1,7 @@ import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, Flex } from "@chakra-ui/react"; import { ModalProps } from "@chakra-ui/react"; import { Bech32Prefix, hexToBech32 } from "../../helpers/nip19"; -import { getReferences } from "../../helpers/nostr-event"; +import { getReferences } from "../../helpers/nostr/event"; import { NostrEvent } from "../../types/nostr-event"; import RawJson from "./raw-json"; import RawValue from "./raw-value"; diff --git a/src/components/layout/profile-link.tsx b/src/components/layout/profile-link.tsx index 4a00bee6c..c29b27462 100644 --- a/src/components/layout/profile-link.tsx +++ b/src/components/layout/profile-link.tsx @@ -3,7 +3,7 @@ import { Link as RouterLink, useLocation } from "react-router-dom"; import { UserAvatar } from "../user-avatar"; import { useUserMetadata } from "../../hooks/use-user-metadata"; import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19"; -import { truncatedId } from "../../helpers/nostr-event"; +import { truncatedId } from "../../helpers/nostr/event"; import { useCurrentAccount } from "../../hooks/use-current-account"; function ProfileButton() { diff --git a/src/components/live-video-player.tsx b/src/components/live-video-player.tsx index 4047f10e0..f6d0eae82 100644 --- a/src/components/live-video-player.tsx +++ b/src/components/live-video-player.tsx @@ -44,15 +44,6 @@ export function LiveVideoPlayer({ return ( - - {status} - + + + {title} + + + {stream.tags.length > 0 && ( + + {stream.tags.map((tag) => ( + {tag} + ))} + + )} + {stream.starts && Started: {dayjs.unix(stream.starts).fromNow()}} + + + + + + + + + ); } diff --git a/src/views/streams/components/stream-debug-button.tsx b/src/views/streams/components/stream-debug-button.tsx new file mode 100644 index 000000000..9883c60be --- /dev/null +++ b/src/views/streams/components/stream-debug-button.tsx @@ -0,0 +1,47 @@ +import { + Flex, + IconButton, + IconButtonProps, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + useDisclosure, +} from "@chakra-ui/react"; +import { CodeIcon } from "../../../components/icons"; +import RawValue from "../../../components/debug-modals/raw-value"; +import RawJson from "../../../components/debug-modals/raw-json"; +import { ParsedStream } from "../../../helpers/nostr/stream"; +import useEventNaddr from "../../../hooks/use-event-naddr"; + +export default function StreamDebugButton({ + stream, + ...props +}: { stream: ParsedStream } & Omit) { + const debugModal = useDisclosure(); + const naddr = useEventNaddr(stream.event); + + return ( + <> + } aria-label="Show raw event" onClick={debugModal.onOpen} {...props} /> + + + + + Raw event + + + + + + + + + + + + + ); +} diff --git a/src/views/streams/index.tsx b/src/views/streams/index.tsx index 38285ad40..6db5bd269 100644 --- a/src/views/streams/index.tsx +++ b/src/views/streams/index.tsx @@ -5,7 +5,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer" import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import useSubject from "../../hooks/use-subject"; import StreamCard from "./components/stream-card"; -import { ParsedStream, STREAM_KIND, getATag, parseStreamEvent } from "../../helpers/nostr/stream"; +import { ParsedStream, STREAM_KIND, parseStreamEvent } from "../../helpers/nostr/stream"; import { NostrEvent } from "../../types/nostr-event"; import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider"; @@ -15,8 +15,7 @@ import PeopleListProvider, { usePeopleListContext } from "../../components/peopl import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status"; function StreamsPage() { - // hard code damus and snort relays for finding streams - const readRelays = useRelaySelectionRelays(); //useReadRelayUrls(["wss://relay.damus.io", "wss://relay.snort.social"]); + const relays = useRelaySelectionRelays(); const [filterStatus, setFilterStatus] = useState("live"); const eventFilter = useCallback( @@ -38,25 +37,22 @@ function StreamsPage() { { "#p": people, kinds: [STREAM_KIND] }, ] : { kinds: [STREAM_KIND] }; - const timeline = useTimelineLoader(`streams`, readRelays, query, { eventFilter }); + const timeline = useTimelineLoader(`streams`, relays, query, { eventFilter }); - useRelaysChanged(readRelays, () => timeline.reset()); + useRelaysChanged(relays, () => timeline.reset()); const callback = useTimelineCurserIntersectionCallback(timeline); const events = useSubject(timeline.timeline); const streams = useMemo(() => { - const parsedStreams: Record = {}; + const parsedStreams: ParsedStream[] = []; for (const event of events) { try { const parsed = parseStreamEvent(event); - const aTag = getATag(parsed); - if (!parsedStreams[aTag] || parsed.event.created_at > parsedStreams[aTag].event.created_at) { - parsedStreams[aTag] = parsed; - } + parsedStreams.push(parsed); } catch (e) {} } - return Array.from(Object.values(parsedStreams)).sort((a, b) => (b.starts ?? 0) - (a.starts ?? 0)); + return parsedStreams.sort((a, b) => (b.starts ?? 0) - (a.starts ?? 0)); }, [events]); return ( @@ -83,7 +79,7 @@ function StreamsPage() { export default function StreamsView() { return ( diff --git a/src/views/streams/stream/index.tsx b/src/views/streams/stream/index.tsx index 36c160220..93d1c785e 100644 --- a/src/views/streams/stream/index.tsx +++ b/src/views/streams/stream/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useScroll } from "react-use"; import { Box, Button, ButtonGroup, Flex, Heading, Spacer, Spinner, Text } from "@chakra-ui/react"; import { Link as RouterLink, useParams, Navigate, useSearchParams } from "react-router-dom"; @@ -6,7 +6,6 @@ import { nip19 } from "nostr-tools"; import { Global, css } from "@emotion/react"; import { ParsedStream, STREAM_KIND, parseStreamEvent } from "../../../helpers/nostr/stream"; -import { NostrRequest } from "../../../classes/nostr-request"; import { useReadRelayUrls } from "../../../hooks/use-client-relays"; import { unique } from "../../../helpers/array"; import { LiveVideoPlayer } from "../../../components/live-video-player"; @@ -14,12 +13,15 @@ import StreamChat, { ChatDisplayMode } from "./stream-chat"; import { UserAvatarLink } from "../../../components/user-avatar-link"; import { UserLink } from "../../../components/user-link"; import { useIsMobile } from "../../../hooks/use-is-mobile"; -import { AdditionalRelayProvider } from "../../../providers/additional-relay-context"; import StreamSummaryContent from "../components/stream-summary-content"; import { ArrowDownSIcon, ArrowUpSIcon, ExternalLinkIcon } from "../../../components/icons"; import useSetColorMode from "../../../hooks/use-set-color-mode"; import { CopyIconButton } from "../../../components/copy-icon-button"; -import { NoteRelays } from "../../../components/note/note-relays"; +import StreamDebugButton from "../components/stream-debug-button"; +import replaceableEventLoaderService from "../../../services/replaceable-event-requester"; +import useSubject from "../../../hooks/use-subject"; +import RelaySelectionButton from "../../../components/relay-selection/relay-selection-button"; +import RelaySelectionProvider from "../../../providers/relay-selection-provider"; function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode?: ChatDisplayMode }) { const isMobile = useIsMobile(); @@ -91,7 +93,12 @@ function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode )} {!displayMode && ( - + @@ -101,7 +108,8 @@ function StreamPage({ stream, displayMode }: { stream: ParsedStream; displayMode {stream.title} - + + @@ -131,33 +139,40 @@ export default function StreamView() { if (!naddr) return ; const readRelays = useReadRelayUrls(); - const [stream, setStream] = useState(); - const [relays, setRelays] = useState([]); + const [streamRelays, setStreamRelays] = useState([]); - useEffect(() => { + const subject = useMemo(() => { try { const parsed = nip19.decode(naddr); if (parsed.type !== "naddr") throw new Error("Invalid stream address"); if (parsed.data.kind !== STREAM_KIND) throw new Error("Invalid stream kind"); - const request = new NostrRequest(unique([...readRelays, ...(parsed.data.relays ?? [])])); - request.onEvent.subscribe((event) => { - setStream(parseStreamEvent(event)); - if (parsed.data.relays) setRelays(parsed.data.relays); - }); - request.start({ kinds: [parsed.data.kind], "#d": [parsed.data.identifier], authors: [parsed.data.pubkey] }); + const addrRelays = parsed.data.relays ?? []; + return replaceableEventLoaderService.requestEvent( + unique([...readRelays, ...streamRelays, ...addrRelays]), + parsed.data.kind, + parsed.data.pubkey, + parsed.data.identifier, + true + ); } catch (e) { console.log(e); } - }, [naddr]); + }, [naddr, streamRelays.join("|")]); + + const streamEvent = useSubject(subject); + const stream = useMemo(() => streamEvent && parseStreamEvent(streamEvent), [streamEvent]); + + // refetch the stream from the correct relays when its loaded to ensure we have the latest + useEffect(() => { + if (stream?.relays) setStreamRelays(stream.relays); + }, [stream?.relays]); if (!stream) return ; return ( // add snort and damus relays so zap.stream will always see zaps - + - + ); } diff --git a/src/views/streams/stream/stream-chat/index.tsx b/src/views/streams/stream/stream-chat/index.tsx index 6616997d8..75d1916d2 100644 --- a/src/views/streams/stream/stream-chat/index.tsx +++ b/src/views/streams/stream/stream-chat/index.tsx @@ -34,11 +34,12 @@ import { useSigningContext } from "../../../../providers/signing-provider"; import { useTimelineCurserIntersectionCallback } from "../../../../hooks/use-timeline-cursor-intersection-callback"; import useSubject from "../../../../hooks/use-subject"; import { useTimelineLoader } from "../../../../hooks/use-timeline-loader"; -import { truncatedId } from "../../../../helpers/nostr-event"; +import { truncatedId } from "../../../../helpers/nostr/event"; import { css } from "@emotion/react"; import TopZappers from "./top-zappers"; import { parseZapEvent } from "../../../../helpers/zaps"; import { Kind } from "nostr-tools"; +import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider"; const hideScrollbar = css` scrollbar-width: 0; @@ -57,13 +58,14 @@ export default function StreamChat({ ...props }: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) { const toast = useToast(); - const contextRelays = useAdditionalRelayContext(); - const readRelays = useReadRelayUrls(contextRelays); + const streamRelays = useRelaySelectionRelays(); const hostReadRelays = useUserRelays(stream.host) .filter((r) => r.mode & RelayMode.READ) .map((r) => r.url); - const timeline = useTimelineLoader(`${truncatedId(stream.event.id)}-chat`, readRelays, { + const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]); + + const timeline = useTimelineLoader(`${truncatedId(stream.identifier)}-chat`, streamRelays, { "#a": [getATag(stream)], kinds: [STREAM_CHAT_MESSAGE_KIND, Kind.Zap], }); @@ -92,7 +94,7 @@ export default function StreamChat({ const draft = buildChatMessage(stream, values.content); const signed = await requestSignature(draft); if (!signed) throw new Error("Failed to sign"); - nostrPostAction(unique([...contextRelays, ...hostReadRelays]), signed); + nostrPostAction(relays, signed); reset(); } catch (e) { if (e instanceof Error) toast({ description: e.message, status: "error" }); @@ -181,7 +183,7 @@ export default function StreamChat({ }} onClose={zapModal.onClose} initialComment={getValues().content} - additionalRelays={contextRelays} + additionalRelays={relays} /> )} diff --git a/src/views/user/about.tsx b/src/views/user/about.tsx index 3d75d88ac..4ffbbf2a5 100644 --- a/src/views/user/about.tsx +++ b/src/views/user/about.tsx @@ -30,7 +30,7 @@ import { EmbedableContent, embedUrls } from "../../helpers/embeds"; import { ArrowDownSIcon, ArrowUpSIcon, AtIcon, ExternalLinkIcon, KeyIcon, LightningIcon } from "../../components/icons"; import { normalizeToBech32 } from "../../helpers/nip19"; import { Bech32Prefix } from "../../helpers/nip19"; -import { truncatedId } from "../../helpers/nostr-event"; +import { truncatedId } from "../../helpers/nostr/event"; import { CopyIconButton } from "../../components/copy-icon-button"; import { QrIconButton } from "./components/share-qr-button"; import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon"; diff --git a/src/views/user/components/user-profile-menu.tsx b/src/views/user/components/user-profile-menu.tsx index 26fcdb1ab..74fd2be3b 100644 --- a/src/views/user/components/user-profile-menu.tsx +++ b/src/views/user/components/user-profile-menu.tsx @@ -10,7 +10,7 @@ import { RelayMode } from "../../../classes/relay"; import UserDebugModal from "../../../components/debug-modals/user-debug-modal"; import { useCopyToClipboard } from "react-use"; import { useSharableProfileId } from "../../../hooks/use-shareable-profile-id"; -import { truncatedId } from "../../../helpers/nostr-event"; +import { truncatedId } from "../../../helpers/nostr/event"; export const UserProfileMenu = ({ pubkey, diff --git a/src/views/user/followers.tsx b/src/views/user/followers.tsx index 7de43cb14..87632df79 100644 --- a/src/views/user/followers.tsx +++ b/src/views/user/followers.tsx @@ -6,7 +6,7 @@ import { UserCard, UserCardProps } from "./components/user-card"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; -import { truncatedId } from "../../helpers/nostr-event"; +import { truncatedId } from "../../helpers/nostr/event"; import useSubject from "../../hooks/use-subject"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer"; diff --git a/src/views/user/likes.tsx b/src/views/user/likes.tsx index ef10894eb..f9abb1b56 100644 --- a/src/views/user/likes.tsx +++ b/src/views/user/likes.tsx @@ -2,7 +2,7 @@ import { useRef } from "react"; import { useOutletContext } from "react-router-dom"; import { Box, Flex, SkeletonText, Spacer, Text } from "@chakra-ui/react"; import { Kind } from "nostr-tools"; -import { getReferences, truncatedId } from "../../helpers/nostr-event"; +import { getReferences, truncatedId } from "../../helpers/nostr/event"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { NostrEvent } from "../../types/nostr-event"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; diff --git a/src/views/user/notes.tsx b/src/views/user/notes.tsx index ac3e21f2d..9fa2a37d6 100644 --- a/src/views/user/notes.tsx +++ b/src/views/user/notes.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { Flex, FormControl, FormLabel, Spacer, Switch, useDisclosure } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; import { Kind } from "nostr-tools"; -import { isReply, isRepost, truncatedId } from "../../helpers/nostr-event"; +import { isReply, isRepost, truncatedId } from "../../helpers/nostr/event"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; import { RelayIconStack } from "../../components/relay-icon-stack"; import { NostrEvent } from "../../types/nostr-event"; diff --git a/src/views/user/reports.tsx b/src/views/user/reports.tsx index ba71c31f7..6a1f6e371 100644 --- a/src/views/user/reports.tsx +++ b/src/views/user/reports.tsx @@ -2,7 +2,7 @@ import { Flex, Text } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; import { NoteLink } from "../../components/note-link"; import { UserLink } from "../../components/user-link"; -import { filterTagsByContentRefs, truncatedId } from "../../helpers/nostr-event"; +import { filterTagsByContentRefs, truncatedId } from "../../helpers/nostr/event"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { isETag, isPTag, NostrEvent } from "../../types/nostr-event"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; diff --git a/src/views/user/streams.tsx b/src/views/user/streams.tsx index 98b6cb879..496673817 100644 --- a/src/views/user/streams.tsx +++ b/src/views/user/streams.tsx @@ -1,6 +1,6 @@ import { Flex } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; -import { truncatedId } from "../../helpers/nostr-event"; +import { truncatedId } from "../../helpers/nostr/event"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status"; import IntersectionObserverProvider from "../../providers/intersection-observer"; diff --git a/src/views/user/zaps.tsx b/src/views/user/zaps.tsx index b5d8f94b1..7ecd6c899 100644 --- a/src/views/user/zaps.tsx +++ b/src/views/user/zaps.tsx @@ -8,7 +8,7 @@ import { NoteLink } from "../../components/note-link"; import { UserAvatarLink } from "../../components/user-avatar-link"; import { UserLink } from "../../components/user-link"; import { readablizeSats } from "../../helpers/bolt11"; -import { truncatedId } from "../../helpers/nostr-event"; +import { truncatedId } from "../../helpers/nostr/event"; import { isProfileZap, isNoteZap, parseZapEvent, totalZaps } from "../../helpers/zaps"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { NostrEvent } from "../../types/nostr-event";