mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 13:20:37 +02:00
hide notes below timeline
This commit is contained in:
parent
ea4a9c1e61
commit
a3a7cfd26d
@ -3,7 +3,7 @@ import { Modal, ModalOverlay, ModalContent, ModalBody, ModalCloseButton, Flex, B
|
||||
import { ModalProps } from "@chakra-ui/react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { getContentTagRefs, getReferences } from "../../helpers/nostr/events";
|
||||
import { getContentTagRefs, getThreadReferences } from "../../helpers/nostr/events";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import RawJson from "./raw-json";
|
||||
import RawValue from "./raw-value";
|
||||
@ -33,7 +33,7 @@ export default function NoteDebugModal({ event, ...props }: { event: NostrEvent
|
||||
<RawValue heading="NIP-19 Pointer" value={getSharableEventAddress(event)} />
|
||||
<RawPre heading="Content" value={event.content} />
|
||||
<RawJson heading="JSON" json={event} />
|
||||
<RawJson heading="Thread Tags" json={getReferences(event)} />
|
||||
<RawJson heading="Thread Tags" json={getThreadReferences(event)} />
|
||||
<RawJson heading="Tags referenced in content" json={getContentTagRefs(event.content, event.tags)} />
|
||||
{/* TODO: extract this out */}
|
||||
<Button onClick={broadcast} ml="auto" colorScheme="primary" isLoading={loading}>
|
||||
|
@ -11,7 +11,7 @@ import { TrustProvider } from "../../../providers/local/trust";
|
||||
import Timestamp from "../../timestamp";
|
||||
import { CompactNoteContent } from "../../compact-note-content";
|
||||
import HoverLinkOverlay from "../../hover-link-overlay";
|
||||
import { getReferences } from "../../../helpers/nostr/events";
|
||||
import { getThreadReferences } from "../../../helpers/nostr/events";
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
import { getTorrentTitle } from "../../../helpers/nostr/torrents";
|
||||
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
||||
@ -24,7 +24,7 @@ export default function EmbeddedTorrentComment({
|
||||
}: Omit<CardProps, "children"> & { comment: NostrEvent }) {
|
||||
const navigate = useNavigateInDrawer();
|
||||
const { showSignatureVerification } = useSubject(appSettings);
|
||||
const refs = getReferences(comment);
|
||||
const refs = getThreadReferences(comment);
|
||||
const torrent = useSingleEvent(refs.root?.e?.id, refs.root?.e?.relays);
|
||||
const linkToTorrent = refs.root?.e && `/torrents/${nip19.neventEncode(refs.root.e)}`;
|
||||
|
||||
|
@ -36,7 +36,7 @@ import BookmarkButton from "./components/bookmark-button";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import NoteReactions from "./components/note-reactions";
|
||||
import ReplyForm from "../../views/thread/components/reply-form";
|
||||
import { getReferences, truncatedId } from "../../helpers/nostr/events";
|
||||
import { getThreadReferences, truncatedId } from "../../helpers/nostr/events";
|
||||
import Timestamp from "../timestamp";
|
||||
import OpenInDrawerButton from "../open-in-drawer-button";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
@ -91,7 +91,7 @@ function ReplyToA({ pointer }: { pointer: AddressPointer }) {
|
||||
}
|
||||
|
||||
function ReplyLine({ event }: { event: NostrEvent }) {
|
||||
const refs = getReferences(event);
|
||||
const refs = getThreadReferences(event);
|
||||
if (!refs.reply) return null;
|
||||
|
||||
return (
|
||||
@ -203,7 +203,7 @@ export const Note = React.memo(
|
||||
</ExpandProvider>
|
||||
{replyForm.isOpen && (
|
||||
<ReplyForm
|
||||
item={{ event, replies: [], refs: getReferences(event) }}
|
||||
item={{ event, replies: [], refs: getThreadReferences(event) }}
|
||||
onCancel={replyForm.onClose}
|
||||
onSubmitted={replyForm.onClose}
|
||||
/>
|
||||
|
@ -45,15 +45,19 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
},
|
||||
[cachedLocationKey, timeline],
|
||||
);
|
||||
|
||||
const [pinDate, setPinDate] = useState(getCachedNumber("pin") ?? events[NOTE_BUFFER]?.created_at ?? 0);
|
||||
|
||||
const [maxDate, setMaxDate] = useState(getCachedNumber("max") ?? Infinity);
|
||||
const [minDate, setMinDate] = useState(getCachedNumber("min") ?? events[NOTE_BUFFER]?.created_at ?? -Infinity);
|
||||
const [minDate, setMinDate] = useState(getCachedNumber("min") ?? events[NOTE_BUFFER]?.created_at ?? 0);
|
||||
|
||||
// reset the latest and minDate when timeline changes
|
||||
useEffect(() => {
|
||||
setLatest(dayjs().unix());
|
||||
setMaxDate(getCachedNumber("max") ?? Infinity);
|
||||
setMinDate(getCachedNumber("min") ?? timeline.timeline.value[NOTE_BUFFER]?.created_at ?? 0);
|
||||
}, [timeline, setMinDate, setLatest, getCachedNumber]);
|
||||
setPinDate(getCachedNumber("min") ?? timeline.timeline.value[NOTE_BUFFER]?.created_at ?? 0);
|
||||
setPinDate(getCachedNumber("pin") ?? timeline.timeline.value[NOTE_BUFFER]?.created_at ?? 0);
|
||||
}, [timeline, setPinDate, setLatest, getCachedNumber]);
|
||||
|
||||
const updateNoteMinHeight = useCallback(
|
||||
(id: string, element: Element) => {
|
||||
@ -78,7 +82,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
|
||||
let max: number = -Infinity;
|
||||
let min: number = Infinity;
|
||||
let preload = NOTE_BUFFER;
|
||||
let minBuffer = NOTE_BUFFER;
|
||||
let foundVisible = false;
|
||||
for (const event of timeline.timeline.value) {
|
||||
if (event.created_at > latest) continue;
|
||||
@ -87,7 +91,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
if (!isIntersecting) {
|
||||
if (foundVisible) {
|
||||
// found an event below the view
|
||||
if (preload-- < 0) break;
|
||||
if (minBuffer-- < 0) break;
|
||||
if (event.created_at < min) min = event.created_at;
|
||||
} else {
|
||||
// found an event above the view
|
||||
@ -97,15 +101,20 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
// found visible event
|
||||
foundVisible = true;
|
||||
|
||||
// find the event that is x indexes back
|
||||
const bufferEvent = timeline.timeline.value[timeline.timeline.value.indexOf(event) - NOTE_BUFFER];
|
||||
if (bufferEvent && bufferEvent.created_at > max) max = bufferEvent.created_at;
|
||||
}
|
||||
}
|
||||
|
||||
if (min !== Infinity) {
|
||||
setMinDate((v) => {
|
||||
setCachedNumber("min", min);
|
||||
setMinDate(min);
|
||||
|
||||
// only set the pin date if its less than before (the timeline only get longer)
|
||||
setPinDate((v) => {
|
||||
const value = Math.min(v, min);
|
||||
setCachedNumber("min", value);
|
||||
setCachedNumber("pin", value);
|
||||
return value;
|
||||
});
|
||||
}
|
||||
@ -123,8 +132,9 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
intersectionSubject.unsubscribe(listener);
|
||||
};
|
||||
}, [
|
||||
setMinDate,
|
||||
setPinDate,
|
||||
setMaxDate,
|
||||
setMinDate,
|
||||
intersectionSubject,
|
||||
intersectionEntryCache,
|
||||
updateNoteMinHeight,
|
||||
@ -137,7 +147,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
const notes: NostrEvent[] = [];
|
||||
for (const note of events) {
|
||||
if (note.created_at > latest) newNotes.push(note);
|
||||
else if (note.created_at >= minDate) notes.push(note);
|
||||
else if (note.created_at >= pinDate) notes.push(note);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -159,7 +169,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
<TimelineItem
|
||||
key={note.id}
|
||||
event={note}
|
||||
visible={note.created_at <= maxDate}
|
||||
visible={note.created_at <= maxDate && note.created_at >= minDate}
|
||||
minHeight={getCachedNumber(getEventUID(note))}
|
||||
/>
|
||||
))}
|
||||
|
@ -3,11 +3,11 @@ import { memo, useRef } from "react";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/local/intersection-observer";
|
||||
import Note from "../../note";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
|
||||
function ReplyNote({ event }: { event: NostrEvent }) {
|
||||
// if there is a parent intersection observer, register this card
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, event.id);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
|
@ -6,6 +6,7 @@ import { getMatchNostrLink } from "../regexp";
|
||||
import { AddressPointer, EventPointer } from "nostr-tools/lib/types/nip19";
|
||||
import { safeJson } from "../parse";
|
||||
import { safeDecode } from "../nip19";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
|
||||
export function truncatedId(str: string, keep = 6) {
|
||||
if (str.length < keep * 2 + 3) return str;
|
||||
@ -32,18 +33,10 @@ export function pointerMatchEvent(event: NostrEvent, pointer: AddressPointer | E
|
||||
return false;
|
||||
}
|
||||
|
||||
// used to get a unique Id for each event, should take into account replaceable events
|
||||
export function getEventUID(event: NostrEvent) {
|
||||
if (isReplaceable(event.kind)) {
|
||||
return getEventCoordinate(event);
|
||||
}
|
||||
return event.id;
|
||||
}
|
||||
|
||||
export function isReply(event: NostrEvent | DraftNostrEvent) {
|
||||
if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) return false;
|
||||
// TODO: update this to only look for a "root" or "reply" tag
|
||||
return !!getReferences(event).reply;
|
||||
return !!getThreadReferences(event).reply;
|
||||
}
|
||||
export function isMentionedInContent(event: NostrEvent | DraftNostrEvent, pubkey: string) {
|
||||
return filterTagsByContentRefs(event.content, event.tags).some((t) => t[1] === pubkey);
|
||||
@ -96,24 +89,13 @@ export function getContentTagRefs(content: string, tags: Tag[]) {
|
||||
return Array.from(foundTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns all tags that are referenced in the content
|
||||
*/
|
||||
/** returns all tags that are referenced in the content */
|
||||
export function filterTagsByContentRefs(content: string, tags: Tag[], referenced = true) {
|
||||
const contentTagRefs = getContentTagRefs(content, tags);
|
||||
return tags.filter((t) => contentTagRefs.includes(t) === referenced);
|
||||
}
|
||||
|
||||
export function eTagToEventPointer(tag: ETag): EventPointer {
|
||||
return { id: tag[1], relays: tag[2] ? [tag[2]] : [] };
|
||||
}
|
||||
export function aTagToAddressPointer(tag: ATag): AddressPointer {
|
||||
const cord = parseCoordinate(tag[1], true, false);
|
||||
if (tag[2]) cord.relays = [tag[2]];
|
||||
return cord;
|
||||
}
|
||||
|
||||
export function interpretTags(event: NostrEvent | DraftNostrEvent) {
|
||||
export function interpretThreadTags(event: NostrEvent | DraftNostrEvent) {
|
||||
const eTags = event.tags.filter(isETag);
|
||||
const aTags = event.tags.filter(isATag);
|
||||
|
||||
@ -165,9 +147,9 @@ export function interpretTags(event: NostrEvent | DraftNostrEvent) {
|
||||
};
|
||||
}
|
||||
|
||||
export type EventReferences = ReturnType<typeof getReferences>;
|
||||
export function getReferences(event: NostrEvent | DraftNostrEvent) {
|
||||
const tags = interpretTags(event);
|
||||
export type EventReferences = ReturnType<typeof getThreadReferences>;
|
||||
export function getThreadReferences(event: NostrEvent | DraftNostrEvent) {
|
||||
const tags = interpretThreadTags(event);
|
||||
|
||||
return {
|
||||
root: tags.root && {
|
||||
@ -205,6 +187,7 @@ export function getEventCoordinate(event: NostrEvent) {
|
||||
const d = event.tags.find(isDTag)?.[1];
|
||||
return d ? `${event.kind}:${event.pubkey}:${d}` : `${event.kind}:${event.pubkey}`;
|
||||
}
|
||||
|
||||
export function getEventAddressPointer(event: NostrEvent): AddressPointer {
|
||||
const { kind, pubkey } = event;
|
||||
if (!isReplaceable(kind)) throw new Error("Event is not replaceable");
|
||||
@ -212,11 +195,23 @@ export function getEventAddressPointer(event: NostrEvent): AddressPointer {
|
||||
if (!identifier) throw new Error("Missing identifier");
|
||||
return { kind, pubkey, identifier };
|
||||
}
|
||||
export function pointerToATag(pointer: AddressPointer): ATag {
|
||||
|
||||
export function eTagToEventPointer(tag: ETag): EventPointer {
|
||||
return { id: tag[1], relays: tag[2] ? [tag[2]] : [] };
|
||||
}
|
||||
export function aTagToAddressPointer(tag: ATag): AddressPointer {
|
||||
const cord = parseCoordinate(tag[1], true, false);
|
||||
if (tag[2]) cord.relays = [tag[2]];
|
||||
return cord;
|
||||
}
|
||||
export function addressPointerToATag(pointer: AddressPointer): ATag {
|
||||
const relay = pointer.relays?.[0];
|
||||
const coordinate = `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
|
||||
return relay ? ["a", coordinate, relay] : ["a", coordinate];
|
||||
}
|
||||
export function eventPointerToETag(pointer: EventPointer): ETag {
|
||||
return pointer.relays?.length ? ["e", pointer.id, pointer.relays[0]] : ["e", pointer.id];
|
||||
}
|
||||
|
||||
export type CustomAddressPointer = Omit<AddressPointer, "identifier"> & {
|
||||
identifier?: string;
|
||||
@ -270,3 +265,5 @@ export function parseHardcodedNoteContent(event: NostrEvent) {
|
||||
export function sortByDate(a: NostrEvent, b: NostrEvent) {
|
||||
return b.created_at - a.created_at;
|
||||
}
|
||||
|
||||
export { getEventUID };
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DraftNostrEvent, NostrEvent, Tag } from "../../types/nostr-event";
|
||||
import { getMatchEmoji, getMatchHashtag, getMatchNostrLink } from "../regexp";
|
||||
import { getReferences } from "./events";
|
||||
import { getThreadReferences } from "./events";
|
||||
import { getPubkeyFromDecodeResult, safeDecode } from "../nip19";
|
||||
import { Emoji } from "../../providers/global/emoji-provider";
|
||||
import { EventSplit } from "./zaps";
|
||||
@ -40,7 +40,7 @@ function AddEtag(tags: Tag[], eventId: string, relayHint?: string, type?: string
|
||||
export function addReplyTags(draft: DraftNostrEvent, replyTo: NostrEvent) {
|
||||
const updated: DraftNostrEvent = { ...draft, tags: Array.from(draft.tags) };
|
||||
|
||||
const refs = getReferences(replyTo);
|
||||
const refs = getThreadReferences(replyTo);
|
||||
const rootId = refs.root?.e?.id ?? replyTo.id;
|
||||
const rootRelayHint = refs.root?.e?.relays?.[0];
|
||||
const replyId = replyTo.id;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { getReferences, sortByDate } from "./nostr/events";
|
||||
import { getThreadReferences, sortByDate } from "./nostr/events";
|
||||
|
||||
const DAY_IN_SECONDS = 60 * 60 * 24;
|
||||
|
||||
@ -17,7 +17,7 @@ export function groupByTime(events: NostrEvent[], time = DAY_IN_SECONDS) {
|
||||
export function groupByRoot(events: NostrEvent[]) {
|
||||
const grouped = new SuperMap<string, NostrEvent[]>(() => []);
|
||||
for (const event of events) {
|
||||
const refs = getReferences(event);
|
||||
const refs = getThreadReferences(event);
|
||||
if (refs.root?.e?.id) grouped.get(refs.root.e.id).push(event);
|
||||
}
|
||||
for (const [_, groupedEvents] of grouped) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { EventReferences, getReferences } from "./nostr/events";
|
||||
import { EventReferences, getThreadReferences } from "./nostr/events";
|
||||
|
||||
export function countReplies(replies: ThreadItem[]): number {
|
||||
return replies.reduce((c, item) => c + countReplies(item.replies), 0) + replies.length;
|
||||
@ -37,7 +37,7 @@ export function buildThread(events: NostrEvent[]) {
|
||||
|
||||
const replies = new Map<string, ThreadItem>();
|
||||
for (const event of events) {
|
||||
const refs = getReferences(event);
|
||||
const refs = getThreadReferences(event);
|
||||
|
||||
if (refs.reply?.e) {
|
||||
idToChildren[refs.reply.e.id] = idToChildren[refs.reply.e.id] || [];
|
||||
|
@ -5,7 +5,7 @@ import useSubject from "./use-subject";
|
||||
import useSingleEvent from "./use-single-event";
|
||||
import singleEventService from "../services/single-event";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { getReferences } from "../helpers/nostr/events";
|
||||
import { getThreadReferences } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { unique } from "../helpers/array";
|
||||
|
||||
@ -14,7 +14,7 @@ export default function useThreadTimelineLoader(
|
||||
relays: string[],
|
||||
kind: number = kinds.ShortTextNote,
|
||||
) {
|
||||
const refs = focusedEvent && getReferences(focusedEvent);
|
||||
const refs = focusedEvent && getThreadReferences(focusedEvent);
|
||||
const rootPointer = refs?.root?.e || (focusedEvent && { id: focusedEvent?.id });
|
||||
|
||||
const readRelays = unique([...relays, ...(rootPointer?.relays ?? [])]);
|
||||
|
@ -7,7 +7,7 @@ import { NostrEvent, isATag, isETag } from "../../types/nostr-event";
|
||||
import { useRegisterIntersectionEntity } from "../../providers/local/intersection-observer";
|
||||
import { parseZapEvent } from "../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import { getEventUID, getReferences, isMentionedInContent, parseCoordinate } from "../../helpers/nostr/events";
|
||||
import { getEventUID, getThreadReferences, isMentionedInContent, parseCoordinate } from "../../helpers/nostr/events";
|
||||
import { EmbedEvent, EmbedEventPointer } from "../../components/embed-event";
|
||||
import EmbeddedUnknown from "../../components/embed-event/event-types/embedded-unknown";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
@ -34,7 +34,7 @@ export const ExpandableToggleButton = ({
|
||||
|
||||
const NoteNotification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const account = useCurrentAccount()!;
|
||||
const refs = getReferences(event);
|
||||
const refs = getThreadReferences(event);
|
||||
const parent = useSingleEvent(refs.reply?.e?.id);
|
||||
|
||||
const isReplyingToMe = !!refs.reply?.e?.id && (parent ? parent.pubkey === account.pubkey : true);
|
||||
|
@ -41,7 +41,7 @@ import TorrentMenu from "./components/torrent-menu";
|
||||
import QuoteRepostButton from "../../components/note/components/quote-repost-button";
|
||||
import TorrentComments from "./components/torrents-comments";
|
||||
import ReplyForm from "../thread/components/reply-form";
|
||||
import { getReferences } from "../../helpers/nostr/events";
|
||||
import { getThreadReferences } from "../../helpers/nostr/events";
|
||||
import MessageTextCircle01 from "../../components/icons/message-text-circle-01";
|
||||
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
|
||||
|
||||
@ -127,7 +127,7 @@ function TorrentDetailsPage({ torrent }: { torrent: NostrEvent }) {
|
||||
</Flex>
|
||||
{replyForm.isOpen && (
|
||||
<ReplyForm
|
||||
item={{ event: torrent, refs: getReferences(torrent), replies: [] }}
|
||||
item={{ event: torrent, refs: getThreadReferences(torrent), replies: [] }}
|
||||
onCancel={replyForm.onClose}
|
||||
onSubmitted={replyForm.onClose}
|
||||
replyKind={TORRENT_COMMENT_KIND}
|
||||
|
Loading…
x
Reference in New Issue
Block a user