mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-27 18:22:02 +01:00
fix reaction bug
This commit is contained in:
parent
bb16bddb9f
commit
954ec50afc
5
.changeset/lemon-seahorses-grow.md
Normal file
5
.changeset/lemon-seahorses-grow.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Fix bug with reactions showing on wrong notes
|
@ -27,7 +27,7 @@ import NostrLinkView from "./views/link";
|
||||
import UserReportsTab from "./views/user/reports";
|
||||
import ToolsHomeView from "./views/tools";
|
||||
import UserAboutTab from "./views/user/about";
|
||||
import UserLikesTab from "./views/user/likes";
|
||||
import UserReactionsTab from "./views/user/reactions";
|
||||
import useSetColorMode from "./hooks/use-set-color-mode";
|
||||
import UserStreamsTab from "./views/user/streams";
|
||||
import { PageProviders } from "./providers";
|
||||
@ -97,7 +97,7 @@ const router = createHashRouter([
|
||||
{ path: "notes", element: <UserNotesTab /> },
|
||||
{ path: "streams", element: <UserStreamsTab /> },
|
||||
{ path: "zaps", element: <UserZapsTab /> },
|
||||
{ path: "likes", element: <UserLikesTab /> },
|
||||
{ path: "likes", element: <UserReactionsTab /> },
|
||||
{ path: "lists", element: <UserListsTab /> },
|
||||
{ path: "followers", element: <UserFollowersTab /> },
|
||||
{ path: "following", element: <UserFollowingTab /> },
|
||||
|
@ -2,6 +2,8 @@ import { getEventUID } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import Subject from "./subject";
|
||||
|
||||
export type EventFilter = (event: NostrEvent) => boolean;
|
||||
|
||||
export default class EventStore {
|
||||
name?: string;
|
||||
events = new Map<string, NostrEvent>();
|
||||
@ -25,8 +27,8 @@ export default class EventStore {
|
||||
this.onEvent.next(event);
|
||||
}
|
||||
}
|
||||
getEvent(id:string){
|
||||
return this.events.get(id)
|
||||
getEvent(id: string) {
|
||||
return this.events.get(id);
|
||||
}
|
||||
|
||||
clear() {
|
||||
@ -41,18 +43,28 @@ export default class EventStore {
|
||||
other.onEvent.unsubscribe(this.addEvent, this);
|
||||
}
|
||||
|
||||
getFirstEvent(nth = 0) {
|
||||
getFirstEvent(nth = 0, filter?: EventFilter) {
|
||||
const events = this.getSortedEvents();
|
||||
for (let i = 0; i <= nth; i++) {
|
||||
const event = events[i];
|
||||
if (event) return event;
|
||||
|
||||
let i = 0;
|
||||
while (true) {
|
||||
const event = events.shift();
|
||||
if (!event) return;
|
||||
if (filter && !filter(event)) continue;
|
||||
if (i === nth) return event;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
getLastEvent(nth = 0) {
|
||||
getLastEvent(nth = 0, filter?: EventFilter) {
|
||||
const events = this.getSortedEvents();
|
||||
for (let i = nth; i >= 0; i--) {
|
||||
const event = events[events.length - 1 - i];
|
||||
if (event) return event;
|
||||
|
||||
let i = 0;
|
||||
while (true) {
|
||||
const event = events.pop();
|
||||
if (!event) return;
|
||||
if (filter && !filter(event)) continue;
|
||||
if (i === nth) return event;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ function addToQuery(filter: NostrRequestFilter, query: NostrQuery) {
|
||||
|
||||
const BLOCK_SIZE = 30;
|
||||
|
||||
export type EventFilter = (event: NostrEvent) => boolean;
|
||||
|
||||
export class RelayTimelineLoader {
|
||||
relay: string;
|
||||
query: NostrRequestFilter;
|
||||
@ -72,11 +74,11 @@ export class RelayTimelineLoader {
|
||||
return this.events.addEvent(event);
|
||||
}
|
||||
|
||||
getFirstEvent(nth = 0) {
|
||||
return this.events.getFirstEvent(nth);
|
||||
getFirstEvent(nth = 0, filter?: EventFilter) {
|
||||
return this.events.getFirstEvent(nth, filter);
|
||||
}
|
||||
getLastEvent(nth = 0) {
|
||||
return this.events.getLastEvent(nth);
|
||||
getLastEvent(nth = 0, filter?: EventFilter) {
|
||||
return this.events.getLastEvent(nth, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +93,7 @@ export class TimelineLoader {
|
||||
complete = new PersistentSubject(false);
|
||||
|
||||
loadNextBlockBuffer = 2;
|
||||
eventFilter?: (event: NostrEvent) => boolean;
|
||||
eventFilter?: EventFilter;
|
||||
|
||||
name: string;
|
||||
private log: Debugger;
|
||||
@ -192,7 +194,7 @@ export class TimelineLoader {
|
||||
let triggeredLoad = false;
|
||||
for (const [relay, loader] of this.relayTimelineLoaders) {
|
||||
if (loader.complete || loader.loading) continue;
|
||||
const event = loader.getLastEvent(this.loadNextBlockBuffer);
|
||||
const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter);
|
||||
if (!event || event.created_at >= this.cursor) {
|
||||
loader.loadNextBlock();
|
||||
triggeredLoad = true;
|
||||
|
@ -5,7 +5,7 @@ import relayScoreboardService from "../services/relay-scoreboard";
|
||||
import { NostrEvent, isDTag } from "../types/nostr-event";
|
||||
import { getEventUID } from "./nostr/events";
|
||||
|
||||
export function isHex(key?: string) {
|
||||
export function isHexKey(key?: string) {
|
||||
if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/)) return true;
|
||||
return false;
|
||||
}
|
||||
@ -15,7 +15,7 @@ export function isBech32Key(bech32String: string) {
|
||||
try {
|
||||
const { prefix } = bech32.decode(bech32String.toLowerCase());
|
||||
if (!prefix) return false;
|
||||
if (!isHex(bech32ToHex(bech32String))) return false;
|
||||
if (!isHexKey(bech32ToHex(bech32String))) return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
@ -59,7 +59,7 @@ export function getPubkey(result: nip19.DecodeResult) {
|
||||
}
|
||||
|
||||
export function normalizeToHex(hex: string) {
|
||||
if (isHex(hex)) return hex;
|
||||
if (isHexKey(hex)) return hex;
|
||||
if (isBech32Key(hex)) return bech32ToHex(hex);
|
||||
return null;
|
||||
}
|
||||
|
@ -20,14 +20,14 @@ export function groupReactions(reactions: NostrEvent[]) {
|
||||
return Array.from(Object.values(groups)).sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export function draftEventReaction(event: NostrEvent, emoji = "+", url?: string) {
|
||||
export function draftEventReaction(reacted: NostrEvent, emoji = "+", url?: string) {
|
||||
// only keep the e, and p tags on the parent event
|
||||
const inheritedTags = reacted.tags.filter((tag) => tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"));
|
||||
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: Kind.Reaction,
|
||||
content: url ? ":" + emoji + ":" : emoji,
|
||||
tags: [
|
||||
["e", event.id],
|
||||
["p", event.pubkey], // TODO: pick a relay for the user
|
||||
],
|
||||
tags: [...inheritedTags, ["e", reacted.id], ["p", reacted.pubkey]],
|
||||
created_at: dayjs().unix(),
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useAsync } from "react-use";
|
||||
import singleEventService from "../services/single-event";
|
||||
|
||||
export default function useSingleEvent(id: string, relays: string[] = []) {
|
||||
const { loading, value: event } = useAsync(() => singleEventService.requestEvent(id, relays), [id, relays.join("|")]);
|
||||
export default function useSingleEvent(id?: string, relays: string[] = []) {
|
||||
const { loading, value: event } = useAsync(async () => {
|
||||
if (id) return singleEventService.requestEvent(id, relays);
|
||||
}, [id, relays.join("|")]);
|
||||
|
||||
return {
|
||||
event,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Kind } from "nostr-tools";
|
||||
import { Kind, nip25 } from "nostr-tools";
|
||||
|
||||
import { NostrRequest } from "../classes/nostr-request";
|
||||
import Subject from "../classes/subject";
|
||||
import { SuperMap } from "../classes/super-map";
|
||||
import { getReferences } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
type eventId = string;
|
||||
@ -26,11 +26,10 @@ class EventReactionsService {
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
if (event.kind !== Kind.Reaction) return;
|
||||
const refs = getReferences(event);
|
||||
const id = refs.events[0];
|
||||
if (!id) return;
|
||||
const pointer = nip25.getReactedEventPointer(event);
|
||||
if (!pointer?.id) return;
|
||||
|
||||
const subject = this.subjects.get(id);
|
||||
const subject = this.subjects.get(pointer.id);
|
||||
if (!subject.value) {
|
||||
subject.next([event]);
|
||||
} else if (!subject.value.some((e) => e.id === event.id)) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import db from "./db";
|
||||
import { fetchWithCorsFallback } from "../helpers/cors";
|
||||
import { isHex } from "../helpers/nip19";
|
||||
import { isHexKey } from "../helpers/nip19";
|
||||
|
||||
export type RelayInformationDocument = {
|
||||
name: string;
|
||||
@ -15,7 +15,7 @@ export type RelayInformationDocument = {
|
||||
};
|
||||
|
||||
function sanitizeInfo(info: RelayInformationDocument) {
|
||||
if (info.pubkey && !isHex(info.pubkey)) {
|
||||
if (info.pubkey && !isHexKey(info.pubkey)) {
|
||||
delete info.pubkey;
|
||||
}
|
||||
return info;
|
||||
|
@ -2,13 +2,13 @@ import { Flex, Spinner } from "@chakra-ui/react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Note } from "../../components/note";
|
||||
import { isHex } from "../../helpers/nip19";
|
||||
import { isHexKey } from "../../helpers/nip19";
|
||||
import { useThreadLoader } from "../../hooks/use-thread-loader";
|
||||
import { ThreadPost } from "./components/thread-post";
|
||||
|
||||
function useNotePointer() {
|
||||
const { id } = useParams() as { id: string };
|
||||
if (isHex(id)) return { id, relays: [] };
|
||||
if (isHexKey(id)) return { id, relays: [] };
|
||||
const pointer = nip19.decode(id);
|
||||
|
||||
switch (pointer.type) {
|
||||
|
@ -197,7 +197,6 @@ export function RelayPaidTag({ url }: { url: string }) {
|
||||
}
|
||||
|
||||
export default function RelayCard({ url, ...props }: { url: string } & Omit<CardProps, "children">) {
|
||||
const { info } = useRelayInfo(url);
|
||||
return (
|
||||
<>
|
||||
<Card variant="outline" {...props}>
|
||||
|
@ -10,12 +10,12 @@ import AddCustomRelayModal from "./components/add-custom-modal";
|
||||
import RelayCard from "./components/relay-card";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
|
||||
export default function RelaysView() {
|
||||
const [search, setSearch] = useState("");
|
||||
const deboundedSearch = useDeferredValue(search);
|
||||
const isSearching = deboundedSearch.length > 2;
|
||||
const showAll = useDisclosure();
|
||||
const addRelayModal = useDisclosure();
|
||||
|
||||
const clientRelays = useClientRelays().map((r) => r.url);
|
||||
@ -33,16 +33,13 @@ export default function RelaysView() {
|
||||
return onlineRelays.filter((url) => url.includes(deboundedSearch));
|
||||
}
|
||||
|
||||
return showAll.isOpen ? onlineRelays : clientRelays;
|
||||
}, [isSearching, deboundedSearch, onlineRelays, clientRelays, showAll.isOpen]);
|
||||
return clientRelays;
|
||||
}, [isSearching, deboundedSearch, onlineRelays, clientRelays]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" p="2">
|
||||
<Flex alignItems="center" gap="2" wrap="wrap">
|
||||
<Input type="search" placeholder="search" value={search} onChange={(e) => setSearch(e.target.value)} w="auto" />
|
||||
<Switch isChecked={showAll.isOpen} onChange={showAll.onToggle}>
|
||||
Show All
|
||||
</Switch>
|
||||
<Spacer />
|
||||
<Button as={RouterLink} to="/relays/reviews">
|
||||
Browse Reviews
|
||||
@ -53,7 +50,9 @@ export default function RelaysView() {
|
||||
</Flex>
|
||||
<SimpleGrid columns={[1, 1, 1, 2, 3]} spacing="2">
|
||||
{filteredRelays.map((url) => (
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
@ -63,7 +62,9 @@ export default function RelaysView() {
|
||||
<Heading size="lg">Discovered Relays</Heading>
|
||||
<SimpleGrid columns={[1, 1, 1, 2, 3]} spacing="2">
|
||||
{discoveredRelays.map((url) => (
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
<ErrorBoundary>
|
||||
<RelayCard key={url} url={url} variant="outline" />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
import { Outlet, useMatches, useNavigate, useParams } from "react-router-dom";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
import { isHex } from "../../helpers/nip19";
|
||||
import { isHexKey } from "../../helpers/nip19";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { Suspense, useState } from "react";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
@ -56,7 +56,7 @@ const tabs = [
|
||||
|
||||
function useUserPointer() {
|
||||
const { pubkey } = useParams() as { pubkey: string };
|
||||
if (isHex(pubkey)) return { pubkey, relays: [] };
|
||||
if (isHexKey(pubkey)) return { pubkey, relays: [] };
|
||||
const pointer = nip19.decode(pubkey);
|
||||
|
||||
switch (pointer.type) {
|
||||
|
@ -2,7 +2,8 @@ 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/events";
|
||||
import { nip25 } from "nostr-tools";
|
||||
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
||||
@ -18,16 +19,15 @@ import { UserAvatar } from "../../components/user-avatar";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { NoteMenu } from "../../components/note/note-menu";
|
||||
|
||||
const Like = ({ event }: { event: NostrEvent }) => {
|
||||
const Reaction = ({ event }: { event: NostrEvent }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, event.id);
|
||||
|
||||
const contextRelays = useAdditionalRelayContext();
|
||||
const readRelays = useReadRelayUrls(contextRelays);
|
||||
|
||||
const refs = getReferences(event);
|
||||
const eventId: string | undefined = refs.events[0];
|
||||
const { event: note } = useSingleEvent(eventId, readRelays);
|
||||
const pointer = nip25.getReactedEventPointer(event);
|
||||
const { event: note } = useSingleEvent(pointer?.id, readRelays);
|
||||
|
||||
var content = <></>;
|
||||
if (!note) return <SkeletonText />;
|
||||
@ -51,7 +51,7 @@ const Like = ({ event }: { event: NostrEvent }) => {
|
||||
return <Box ref={ref}>{content}</Box>;
|
||||
};
|
||||
|
||||
export default function UserLikesTab() {
|
||||
export default function UserReactionsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const contextRelays = useAdditionalRelayContext();
|
||||
const readRelays = useReadRelayUrls(contextRelays);
|
||||
@ -67,7 +67,7 @@ export default function UserLikesTab() {
|
||||
<TrustProvider trust>
|
||||
<Flex direction="column" gap="2" p="2" pb="8">
|
||||
{likes.map((event) => (
|
||||
<Like event={event} />
|
||||
<Reaction event={event} />
|
||||
))}
|
||||
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
@ -13,6 +13,7 @@ import { RelayDebugButton, RelayJoinAction, RelayMetadata, RelayShareButton } fr
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { useRelayInfo } from "../../hooks/use-relay-info";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
|
||||
function Relay({ url, reviews }: { url: string; reviews: NostrEvent[] }) {
|
||||
const { info } = useRelayInfo(url);
|
||||
@ -75,7 +76,9 @@ const UserRelaysTab = () => {
|
||||
<IntersectionObserverProvider<string> callback={callback}>
|
||||
<VStack divider={<StackDivider />} py="2" align="stretch">
|
||||
{userRelays.map((relayConfig) => (
|
||||
<Relay url={relayConfig.url} reviews={getRelayReviews(relayConfig.url, reviews)} />
|
||||
<ErrorBoundary>
|
||||
<Relay key={relayConfig.url} url={relayConfig.url} reviews={getRelayReviews(relayConfig.url, reviews)} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</VStack>
|
||||
{otherReviews.length > 0 && (
|
||||
|
Loading…
x
Reference in New Issue
Block a user