From 982cc9ffa58768b34b798a6704f7452d0160a0b7 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Sun, 19 Feb 2023 11:14:09 -0600 Subject: [PATCH] fix timeline load more bug --- src/app.tsx | 2 - src/classes/nostr-request.ts | 77 +++++++++++---------------- src/classes/relay.ts | 12 +++-- src/classes/thread-loader.ts | 4 +- src/classes/timeline-loader.ts | 15 +++--- src/components/note/note-contents.tsx | 15 ++++-- src/components/note/quote-note.tsx | 10 ++++ src/components/page/index.tsx | 2 + src/components/post-modal/index.tsx | 49 ++++++++--------- src/hooks/use-is-mobile.ts | 13 +++-- src/hooks/use-timeline-loader.ts | 6 +-- src/views/home/discover-tab.tsx | 2 +- src/views/home/following-tab.tsx | 2 +- src/views/user/index.tsx | 1 - src/views/user/notes.tsx | 13 +++-- src/views/user/replies.tsx | 31 ----------- 16 files changed, 120 insertions(+), 134 deletions(-) create mode 100644 src/components/note/quote-note.tsx delete mode 100644 src/views/user/replies.tsx diff --git a/src/app.tsx b/src/app.tsx index 50cbc4919..175e5343e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -13,7 +13,6 @@ import { GlobalTab } from "./views/home/global-tab"; import { normalizeToHex } from "./helpers/nip-19"; import UserView from "./views/user"; import UserNotesTab from "./views/user/notes"; -import UserRepliesTab from "./views/user/replies"; import UserFollowersTab from "./views/user/followers"; import UserRelaysTab from "./views/user/relays"; import UserFollowingTab from "./views/user/following"; @@ -86,7 +85,6 @@ const router = createBrowserRouter([ children: [ { path: "", element: }, { path: "notes", element: }, - { path: "replies", element: }, { path: "followers", element: }, { path: "following", element: }, { path: "relays", element: }, diff --git a/src/classes/nostr-request.ts b/src/classes/nostr-request.ts index 267414cf7..ec7cef56c 100644 --- a/src/classes/nostr-request.ts +++ b/src/classes/nostr-request.ts @@ -7,7 +7,7 @@ import createDefer from "./deferred"; let lastId = 0; -const REQUEST_DEFAULT_TIMEOUT = 1000 * 20; +const REQUEST_DEFAULT_TIMEOUT = 1000 * 5; export class NostrRequest { static IDLE = "idle"; static RUNNING = "running"; @@ -16,7 +16,6 @@ export class NostrRequest { id: string; timeout: number; relays: Set; - relayCleanup = new Map(); state = NostrRequest.IDLE; onEvent = new Subject(); onComplete = createDefer(); @@ -27,76 +26,64 @@ export class NostrRequest { this.relays = new Set(relayUrls.map((url) => relayPoolService.requestRelay(url))); for (const relay of this.relays) { - const handleEOSE = (event: IncomingEOSE) => { - if (event.subId === this.id) { - this.handleEndOfEvents(relay); - } - }; - relay.onEOSE.subscribe(handleEOSE); - - const handleEvent = (event: IncomingEvent) => { - if (this.state === NostrRequest.RUNNING && event.subId === this.id && !this.seenEvents.has(event.body.id)) { - this.onEvent.next(event.body); - this.seenEvents.add(event.body.id); - } - }; - relay.onEvent.subscribe(handleEvent); - - this.relayCleanup.set(relay, () => { - relay.onEOSE.unsubscribe(handleEOSE); - relay.onEvent.unsubscribe(handleEvent); - }); + relay.onEOSE.subscribe(this.handleEOSE, this); + relay.onEvent.subscribe(this.handleEvent, this); } this.timeout = timeout ?? REQUEST_DEFAULT_TIMEOUT; } - handleEndOfEvents(relay: Relay) { - this.relays.delete(relay); - relay.send(["CLOSE", this.id]); + handleEOSE(eose: IncomingEOSE) { + if (eose.subId === this.id) { + const relay = eose.relay; + this.relays.delete(relay); + relay.send(["CLOSE", this.id]); - const cleanup = this.relayCleanup.get(relay); - if (cleanup) cleanup(); + relay.onEOSE.unsubscribe(this.handleEOSE, this); + relay.onEvent.unsubscribe(this.handleEvent, this); - if (this.relays.size === 0) { - this.state = NostrRequest.COMPLETE; - this.onComplete.resolve(); + if (this.relays.size === 0) { + this.state = NostrRequest.COMPLETE; + this.onComplete.resolve(); + } + } + } + handleEvent(incomingEvent: IncomingEvent) { + if ( + this.state === NostrRequest.RUNNING && + incomingEvent.subId === this.id && + !this.seenEvents.has(incomingEvent.body.id) + ) { + this.onEvent.next(incomingEvent.body); + this.seenEvents.add(incomingEvent.body.id); } } start(query: NostrQuery) { - if (this.state !== NostrRequest.IDLE) return this; + if (this.state !== NostrRequest.IDLE) { + throw new Error("cant restart a nostr request"); + } this.state = NostrRequest.RUNNING; for (const relay of this.relays) { relay.send(["REQ", this.id, query]); } - setTimeout(() => { - console.log(`NostrRequest: ${this.id} timed out`); - this.cancel(); - }, this.timeout); - - console.log(`NostrRequest: ${this.id} started`); + setTimeout(() => this.complete(), this.timeout); return this; } - cancel() { - if (this.state !== NostrRequest.COMPLETE) return this; + complete() { + if (this.state === NostrRequest.COMPLETE) return this; this.state = NostrRequest.COMPLETE; for (const relay of this.relays) { relay.send(["CLOSE", this.id]); + relay.onEOSE.unsubscribe(this.handleEOSE, this); + relay.onEvent.unsubscribe(this.handleEvent, this); } - for (const [relay, cleanup] of this.relayCleanup) { - if (cleanup) cleanup(); - } - this.relayCleanup = new Map(); - this.relays = new Set(); this.onComplete.resolve(); - console.log(`NostrRequest: ${this.id} complete`); - return this; } } diff --git a/src/classes/relay.ts b/src/classes/relay.ts index 8a84d772c..ae76e18bd 100644 --- a/src/classes/relay.ts +++ b/src/classes/relay.ts @@ -6,14 +6,17 @@ export type IncomingEvent = { type: "EVENT"; subId: string; body: NostrEvent; + relay: Relay; }; export type IncomingNotice = { type: "NOTICE"; message: string; + relay: Relay; }; export type IncomingEOSE = { type: "EOSE"; subId: string; + relay: Relay; }; // NIP-20 export type IncomingCommandResult = { @@ -21,6 +24,7 @@ export type IncomingCommandResult = { eventId: string; status: boolean; message?: string; + relay: Relay; }; export enum RelayMode { @@ -125,16 +129,16 @@ export class Relay { switch (type) { case "EVENT": - this.onEvent.next({ type, subId: data[1], body: data[2] }); + this.onEvent.next({ relay: this, type, subId: data[1], body: data[2] }); break; case "NOTICE": - this.onNotice.next({ type, message: data[1] }); + this.onNotice.next({ relay: this, type, message: data[1] }); break; case "EOSE": - this.onEOSE.next({ type, subId: data[1] }); + this.onEOSE.next({ relay: this, type, subId: data[1] }); break; case "OK": - this.onCommandResult.next({ type, eventId: data[1], status: data[2], message: data[3] }); + this.onCommandResult.next({ relay: this, type, eventId: data[1], status: data[2], message: data[3] }); break; } } catch (e) { diff --git a/src/classes/thread-loader.ts b/src/classes/thread-loader.ts index 8009045c1..ca408cc33 100644 --- a/src/classes/thread-loader.ts +++ b/src/classes/thread-loader.ts @@ -33,7 +33,7 @@ export class ThreadLoader { this.checkAndUpdateRoot(); - request.cancel(); + request.complete(); this.loading.next(false); }); request.start({ ids: [this.focusId.value] }); @@ -60,7 +60,7 @@ export class ThreadLoader { request.onEvent.subscribe((event) => { this.events.next({ ...this.events.value, [event.id]: event }); - request.cancel(); + request.complete(); }); request.start({ ids: [this.rootId.value] }); } diff --git a/src/classes/timeline-loader.ts b/src/classes/timeline-loader.ts index cd68d95ea..78b912fea 100644 --- a/src/classes/timeline-loader.ts +++ b/src/classes/timeline-loader.ts @@ -19,7 +19,8 @@ export class TimelineLoader { events = new PersistentSubject([]); loading = new PersistentSubject(false); page = new PersistentSubject(0); - private seenEvents = new Set(); + + private eventDir = new Map(); private subscription: NostrMultiSubscription; private opts: Options = { pageSize: moment.duration(1, "hour").asSeconds() }; @@ -48,9 +49,9 @@ export class TimelineLoader { } private handleEvent(event: NostrEvent) { - if (!this.seenEvents.has(event.id)) { - this.events.next(this.events.value.concat(event).sort((a, b) => b.created_at - a.created_at)); - this.seenEvents.add(event.id); + if (!this.eventDir.has(event.id)) { + this.eventDir.set(event.id, event); + this.events.next(Array.from(this.eventDir.values()).sort((a, b) => b.created_at - a.created_at)); if (this.loading.value) this.loading.next(false); } } @@ -69,7 +70,7 @@ export class TimelineLoader { loadMore() { if (this.loading.value) return; - const query = { ...this.query, ...this.getPageDates(this.page.value ?? 0) }; + const query = { ...this.query, ...this.getPageDates(this.page.value) }; const request = new NostrRequest(this.relays); request.onEvent.subscribe(this.handleEvent, this); request.onComplete.then(() => { @@ -78,12 +79,12 @@ export class TimelineLoader { request.start(query); this.loading.next(true); - this.page.next(this.page.value ?? 0 + 1); + this.page.next(this.page.value + 1); } forgetEvents() { this.events.next([]); - this.seenEvents.clear(); + this.eventDir.clear(); this.subscription.forgetEvents(); } open() { diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx index 809cc1fad..42a939d3f 100644 --- a/src/components/note/note-contents.tsx +++ b/src/components/note/note-contents.tsx @@ -14,10 +14,11 @@ import { InlineInvoiceCard } from "../inline-invoice-card"; import { TweetEmbed } from "../tweet-embed"; import { UserLink } from "../user-link"; import { normalizeToHex } from "../../helpers/nip-19"; -import { NostrEvent } from "../../types/nostr-event"; +import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; import { NoteLink } from "../note-link"; import settings from "../../services/settings"; import styled from "@emotion/styled"; +import QuoteNote from "./quote-note"; // import { ExternalLinkIcon } from "../icons"; const BlurredImage = (props: ImageProps) => { @@ -31,7 +32,7 @@ const BlurredImage = (props: ImageProps) => { type EmbedType = { regexp: RegExp; - render: (match: RegExpMatchArray, event?: NostrEvent, trusted?: boolean) => JSX.Element | string; + render: (match: RegExpMatchArray, event?: NostrEvent | DraftNostrEvent, trusted?: boolean) => JSX.Element | string; name?: string; isMedia: boolean; }; @@ -202,7 +203,7 @@ const embeds: EmbedType[] = [ return key ? : match[0]; case "note1": const noteId = normalizeToHex(match[1]); - return noteId ? : match[0]; + return noteId ? : match[0]; default: return match[0]; } @@ -251,7 +252,11 @@ const MediaEmbed = ({ children, type }: { children: JSX.Element | string; type: ); }; -function embedContent(content: string, event?: NostrEvent, trusted: boolean = false): (string | JSX.Element)[] { +function embedContent( + content: string, + event?: NostrEvent | DraftNostrEvent, + trusted: boolean = false +): (string | JSX.Element)[] { for (const embedType of embeds) { const match = content.match(embedType.regexp); @@ -278,7 +283,7 @@ const GradientOverlay = styled.div` `; export type NoteContentsProps = { - event: NostrEvent; + event: NostrEvent | DraftNostrEvent; trusted?: boolean; maxHeight?: number; }; diff --git a/src/components/note/quote-note.tsx b/src/components/note/quote-note.tsx new file mode 100644 index 000000000..dd0dd6ac6 --- /dev/null +++ b/src/components/note/quote-note.tsx @@ -0,0 +1,10 @@ +import { useReadRelayUrls } from "../../hooks/use-client-relays"; +import { NoteLink } from "../note-link"; + +const QuoteNote = ({ noteId, relay }: { noteId: string; relay?: string }) => { + const relays = useReadRelayUrls(relay ? [relay] : []); + + return ; +}; + +export default QuoteNote; diff --git a/src/components/page/index.tsx b/src/components/page/index.tsx index 399ee07df..0dfb6474c 100644 --- a/src/components/page/index.tsx +++ b/src/components/page/index.tsx @@ -22,6 +22,8 @@ const FollowingSideNav = () => { export const Page = ({ children }: { children: React.ReactNode }) => { const isMobile = useIsMobile(); + console.log(isMobile); + return ( { - const isMobile = useIsMobile(); - const pad = isMobile ? "2" : "4"; - const { requestSignature } = useSigningContext(); const writeRelays = useWriteRelayUrls(); const [waiting, setWaiting] = useState(false); const [signedEvent, setSignedEvent] = useState(null); const [results, resultsActions] = useList(); + const { isOpen: showPreview, onToggle: togglePreview } = useDisclosure(); const [draft, setDraft] = useState(() => Object.assign(emptyDraft(), initialDraft)); const handleContentChange: React.ChangeEventHandler = (event) => { @@ -71,32 +71,27 @@ export const PostModal = ({ isOpen, onClose, initialDraft }: PostModalProps) => const renderContent = () => { if (signedEvent) { - return ( - - - - ); + return ; } return ( <> - - {refs.replyId && ( - - Replying to: - - )} + {refs.replyId && ( + + Replying to: + + )} + {showPreview ? ( + + ) : (