forked from Kieran/snort
refactor: hashtags timeline weaver to worker relay
This commit is contained in:
parent
f9d08267a6
commit
9a0bbb8b74
@ -7,11 +7,9 @@ import { FormattedMessage } from "react-intl";
|
|||||||
|
|
||||||
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
|
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
|
||||||
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
|
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
|
||||||
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
|
|
||||||
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "@/Feed/TimelineFeed";
|
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "@/Feed/TimelineFeed";
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import useModeration from "@/Hooks/useModeration";
|
import { dedupeByPubkey } from "@/Utils";
|
||||||
import { dedupeByPubkey, findTag } from "@/Utils";
|
|
||||||
|
|
||||||
export interface TimelineProps {
|
export interface TimelineProps {
|
||||||
postsOnly: boolean;
|
postsOnly: boolean;
|
||||||
@ -43,7 +41,6 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
const displayAsInitial = props.displayAs ?? login.feedDisplayAs ?? "list";
|
const displayAsInitial = props.displayAs ?? login.feedDisplayAs ?? "list";
|
||||||
const [displayAs, setDisplayAs] = useState<DisplayAs>(displayAsInitial);
|
const [displayAs, setDisplayAs] = useState<DisplayAs>(displayAsInitial);
|
||||||
|
|
||||||
const { muted, isEventMuted } = useModeration();
|
|
||||||
const filterPosts = useCallback(
|
const filterPosts = useCallback(
|
||||||
(nts: readonly TaggedNostrEvent[]) => {
|
(nts: readonly TaggedNostrEvent[]) => {
|
||||||
const checkFollowDistance = (a: TaggedNostrEvent) => {
|
const checkFollowDistance = (a: TaggedNostrEvent) => {
|
||||||
@ -56,9 +53,9 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
const a = [...nts.filter(a => a.kind !== EventKind.LiveEvent)];
|
const a = [...nts.filter(a => a.kind !== EventKind.LiveEvent)];
|
||||||
return a
|
return a
|
||||||
?.filter(a => (props.postsOnly ? !a.tags.some(b => b[0] === "e") : true))
|
?.filter(a => (props.postsOnly ? !a.tags.some(b => b[0] === "e") : true))
|
||||||
.filter(a => (props.ignoreModeration || !isEventMuted(a)) && checkFollowDistance(a));
|
.filter(a => props.ignoreModeration && checkFollowDistance(a));
|
||||||
},
|
},
|
||||||
[props.postsOnly, muted, props.ignoreModeration, props.followDistance],
|
[props.postsOnly, props.ignoreModeration, props.followDistance],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainFeed = useMemo(() => {
|
const mainFeed = useMemo(() => {
|
||||||
@ -67,9 +64,6 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
const latestFeed = useMemo(() => {
|
const latestFeed = useMemo(() => {
|
||||||
return filterPosts(feed.latest ?? []).filter(a => !mainFeed.some(b => b.id === a.id));
|
return filterPosts(feed.latest ?? []).filter(a => !mainFeed.some(b => b.id === a.id));
|
||||||
}, [feed, filterPosts]);
|
}, [feed, filterPosts]);
|
||||||
const liveStreams = useMemo(() => {
|
|
||||||
return (feed.main ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
|
||||||
}, [feed]);
|
|
||||||
|
|
||||||
const latestAuthors = useMemo(() => {
|
const latestAuthors = useMemo(() => {
|
||||||
return dedupeByPubkey(latestFeed).map(e => e.pubkey);
|
return dedupeByPubkey(latestFeed).map(e => e.pubkey);
|
||||||
@ -84,7 +78,6 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LiveStreams evs={liveStreams} />
|
|
||||||
<DisplayAsSelector
|
<DisplayAsSelector
|
||||||
show={props.showDisplayAsSelector}
|
show={props.showDisplayAsSelector}
|
||||||
activeSelection={displayAs}
|
activeSelection={displayAs}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import "./Timeline.css";
|
import "./Timeline.css";
|
||||||
|
|
||||||
import { unixNow } from "@snort/shared";
|
|
||||||
import { EventKind, NostrEvent, TaggedNostrEvent } from "@snort/system";
|
import { EventKind, NostrEvent, TaggedNostrEvent } from "@snort/system";
|
||||||
import { ReactNode, useCallback, useMemo, useState } from "react";
|
import { ReactNode, useCallback, useMemo, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -9,12 +8,9 @@ import { ShowMoreInView } from "@/Components/Event/ShowMore";
|
|||||||
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
|
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
|
||||||
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
|
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
|
||||||
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
|
import { LiveStreams } from "@/Components/LiveStream/LiveStreams";
|
||||||
import useHashtagsFeed from "@/Feed/HashtagsFeed";
|
import useTimelineFeed, { TimelineFeedOptions, TimelineSubject } from "@/Feed/TimelineFeed";
|
||||||
import { useFollowsTimelineView } from "@/Feed/WorkerRelayView";
|
|
||||||
import useHistoryState from "@/Hooks/useHistoryState";
|
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import useModeration from "@/Hooks/useModeration";
|
import { dedupeByPubkey } from "@/Utils";
|
||||||
import { dedupeByPubkey, findTag, orderDescending } from "@/Utils";
|
|
||||||
|
|
||||||
export interface TimelineFollowsProps {
|
export interface TimelineFollowsProps {
|
||||||
postsOnly: boolean;
|
postsOnly: boolean;
|
||||||
@ -33,12 +29,21 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const displayAsInitial = props.displayAs ?? login.feedDisplayAs ?? "list";
|
const displayAsInitial = props.displayAs ?? login.feedDisplayAs ?? "list";
|
||||||
const [displayAs, setDisplayAs] = useState<DisplayAs>(displayAsInitial);
|
const [displayAs, setDisplayAs] = useState<DisplayAs>(displayAsInitial);
|
||||||
const [latest, setLatest] = useHistoryState(unixNow(), "TimelineFollowsLatest");
|
const subject = useMemo(
|
||||||
const [limit, setLimit] = useState(50);
|
() =>
|
||||||
const feed = useFollowsTimelineView(limit);
|
({
|
||||||
const { muted, isEventMuted } = useModeration();
|
type: "pubkey",
|
||||||
|
items: login.follows.item,
|
||||||
const oldest = useMemo(() => feed.at(-1)?.created_at, [feed]);
|
discriminator: login.publicKey?.slice(0, 12),
|
||||||
|
extra: rb => {
|
||||||
|
if (login.tags.item.length > 0) {
|
||||||
|
rb.withFilter().kinds([EventKind.TextNote]).tag("t", login.tags.item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}) as TimelineSubject,
|
||||||
|
[login.follows.item, login.tags.item],
|
||||||
|
);
|
||||||
|
const feed = useTimelineFeed(subject, { method: "TIME_RANGE" } as TimelineFeedOptions);
|
||||||
|
|
||||||
const postsOnly = useCallback(
|
const postsOnly = useCallback(
|
||||||
(a: NostrEvent) => (props.postsOnly ? !a.tags.some(b => b[0] === "e" || b[0] === "a") : true),
|
(a: NostrEvent) => (props.postsOnly ? !a.tags.some(b => b[0] === "e" || b[0] === "a") : true),
|
||||||
@ -50,49 +55,26 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
const a = nts.filter(a => a.kind !== EventKind.LiveEvent);
|
const a = nts.filter(a => a.kind !== EventKind.LiveEvent);
|
||||||
return a
|
return a
|
||||||
?.filter(postsOnly)
|
?.filter(postsOnly)
|
||||||
.filter(a => !isEventMuted(a) && login.follows.item.includes(a.pubkey) && (props.noteFilter?.(a) ?? true));
|
.filter(a => props.noteFilter?.(a) ?? true)
|
||||||
|
.filter(a => login.follows.item.includes(a.pubkey) || a.tags.filter(a => a[0] === "t").length < 5);
|
||||||
},
|
},
|
||||||
[postsOnly, muted, login.follows.timestamp],
|
[postsOnly, props.noteFilter, login.follows.timestamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mixin = useHashtagsFeed();
|
|
||||||
const mainFeed = useMemo(() => {
|
const mainFeed = useMemo(() => {
|
||||||
return filterPosts((feed ?? []).filter(a => a.created_at <= latest));
|
return filterPosts(feed.main ?? []);
|
||||||
}, [feed, filterPosts, latest, login.follows.timestamp]);
|
}, [feed.main, filterPosts]);
|
||||||
|
|
||||||
const findHashTagContext = (a: NostrEvent) => {
|
|
||||||
const tag = a.tags.filter(a => a[0] === "t").find(a => login.tags.item.includes(a[1].toLowerCase()))?.[1];
|
|
||||||
return tag;
|
|
||||||
};
|
|
||||||
const mixinFiltered = useMemo(() => {
|
|
||||||
const mainFeedIds = new Set(mainFeed.map(a => a.id));
|
|
||||||
return (mixin.data ?? [])
|
|
||||||
.filter(a => !mainFeedIds.has(a.id) && postsOnly(a) && !isEventMuted(a))
|
|
||||||
.filter(a => a.tags.filter(a => a[0] === "t").length < 5)
|
|
||||||
.filter(a => !oldest || a.created_at >= oldest)
|
|
||||||
.map(
|
|
||||||
a =>
|
|
||||||
({
|
|
||||||
...a,
|
|
||||||
context: findHashTagContext(a),
|
|
||||||
}) as TaggedNostrEvent,
|
|
||||||
);
|
|
||||||
}, [mixin, mainFeed, postsOnly, isEventMuted]);
|
|
||||||
|
|
||||||
const latestFeed = useMemo(() => {
|
const latestFeed = useMemo(() => {
|
||||||
return filterPosts((feed ?? []).filter(a => a.created_at > latest));
|
return filterPosts(feed.latest ?? []);
|
||||||
}, [feed, latest]);
|
}, [feed.latest]);
|
||||||
|
|
||||||
const liveStreams = useMemo(() => {
|
|
||||||
return (feed ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
|
||||||
}, [feed]);
|
|
||||||
|
|
||||||
const latestAuthors = useMemo(() => {
|
const latestAuthors = useMemo(() => {
|
||||||
return dedupeByPubkey(latestFeed).map(e => e.pubkey);
|
return dedupeByPubkey(latestFeed).map(e => e.pubkey);
|
||||||
}, [latestFeed]);
|
}, [latestFeed]);
|
||||||
|
|
||||||
function onShowLatest(scrollToTop = false) {
|
function onShowLatest(scrollToTop = false) {
|
||||||
setLatest(unixNow());
|
feed.showLatest();
|
||||||
if (scrollToTop) {
|
if (scrollToTop) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
@ -100,14 +82,14 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(props.liveStreams ?? true) && <LiveStreams evs={liveStreams} />}
|
{(props.liveStreams ?? true) && <LiveStreams />}
|
||||||
<DisplayAsSelector
|
<DisplayAsSelector
|
||||||
show={props.showDisplayAsSelector}
|
show={props.showDisplayAsSelector}
|
||||||
activeSelection={displayAs}
|
activeSelection={displayAs}
|
||||||
onSelect={(displayAs: DisplayAs) => setDisplayAs(displayAs)}
|
onSelect={(displayAs: DisplayAs) => setDisplayAs(displayAs)}
|
||||||
/>
|
/>
|
||||||
<TimelineRenderer
|
<TimelineRenderer
|
||||||
frags={[{ events: orderDescending(mainFeed.concat(mixinFiltered)), refTime: latest }]}
|
frags={[{ events: mainFeed, refTime: 0 }]}
|
||||||
latest={latestAuthors}
|
latest={latestAuthors}
|
||||||
showLatest={t => onShowLatest(t)}
|
showLatest={t => onShowLatest(t)}
|
||||||
noteOnClick={props.noteOnClick}
|
noteOnClick={props.noteOnClick}
|
||||||
@ -119,10 +101,10 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
}}
|
}}
|
||||||
displayAs={displayAs}
|
displayAs={displayAs}
|
||||||
/>
|
/>
|
||||||
{feed.length > 0 && (
|
{(feed.main?.length ?? 0) > 0 && (
|
||||||
<ShowMoreInView
|
<ShowMoreInView
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLimit(s => s + 20);
|
onShowLatest(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
import "./LiveStreams.css";
|
import "./LiveStreams.css";
|
||||||
|
|
||||||
import { NostrEvent, NostrLink } from "@snort/system";
|
import { unixNow } from "@snort/shared";
|
||||||
|
import { EventKind, NostrEvent, NostrLink, RequestBuilder } from "@snort/system";
|
||||||
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { CSSProperties, useMemo } from "react";
|
import { CSSProperties, useMemo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import Icon from "@/Components/Icons/Icon";
|
import Icon from "@/Components/Icons/Icon";
|
||||||
import useImgProxy from "@/Hooks/useImgProxy";
|
import useImgProxy from "@/Hooks/useImgProxy";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import { findTag } from "@/Utils";
|
import { findTag } from "@/Utils";
|
||||||
|
|
||||||
export function LiveStreams({ evs }: { evs: Array<NostrEvent> }) {
|
export function LiveStreams() {
|
||||||
const streams = useMemo(() => {
|
const follows = useLogin(s => s.follows.item);
|
||||||
return [...evs].sort((a, b) => {
|
const sub = useMemo(() => {
|
||||||
const aStarts = Number(findTag(a, "starts") ?? a.created_at);
|
const since = unixNow() - 60 * 60 * 24;
|
||||||
const bStarts = Number(findTag(b, "starts") ?? b.created_at);
|
const rb = new RequestBuilder("follows:streams");
|
||||||
return aStarts > bStarts ? -1 : 1;
|
rb.withFilter().kinds([EventKind.LiveEvent]).authors(follows).since(since);
|
||||||
});
|
rb.withFilter().kinds([EventKind.LiveEvent]).tag("p", follows).since(since);
|
||||||
}, [evs]);
|
return rb;
|
||||||
|
}, [follows]);
|
||||||
|
|
||||||
|
const streams = useRequestBuilder(sub);
|
||||||
if (streams.length === 0) return null;
|
if (streams.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,6 +4,7 @@ import { useRequestBuilderAdvanced } from "@snort/system-react";
|
|||||||
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
||||||
|
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import useModeration from "@/Hooks/useModeration";
|
||||||
import useTimelineWindow from "@/Hooks/useTimelineWindow";
|
import useTimelineWindow from "@/Hooks/useTimelineWindow";
|
||||||
import { SearchRelays } from "@/Utils/Const";
|
import { SearchRelays } from "@/Utils/Const";
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ export interface TimelineSubject {
|
|||||||
discriminator: string;
|
discriminator: string;
|
||||||
items: string[];
|
items: string[];
|
||||||
relay?: Array<string>;
|
relay?: Array<string>;
|
||||||
streams?: boolean;
|
extra?: (rb: RequestBuilder) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TimelineFeed = ReturnType<typeof useTimelineFeed>;
|
export type TimelineFeed = ReturnType<typeof useTimelineFeed>;
|
||||||
@ -29,6 +30,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
now: options.now ?? unixNow(),
|
now: options.now ?? unixNow(),
|
||||||
});
|
});
|
||||||
const pref = useLogin(s => s.appData.item.preferences);
|
const pref = useLogin(s => s.appData.item.preferences);
|
||||||
|
const { isEventMuted } = useModeration();
|
||||||
|
|
||||||
const createBuilder = useCallback(() => {
|
const createBuilder = useCallback(() => {
|
||||||
if (subject.type !== "global" && subject.items.length === 0) {
|
if (subject.type !== "global" && subject.items.length === 0) {
|
||||||
@ -71,49 +73,24 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subject.streams && subject.type === "pubkey") {
|
subject.extra?.(b);
|
||||||
b.withFilter()
|
return b;
|
||||||
.kinds([EventKind.LiveEvent])
|
}, [subject.type, subject.items, subject.discriminator, subject.extra]);
|
||||||
.authors(subject.items)
|
|
||||||
.since(now - 60 * 60 * 24);
|
|
||||||
b.withFilter().kinds([EventKind.LiveEvent]).tag("p", subject.items);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
builder: b,
|
|
||||||
filter: f,
|
|
||||||
};
|
|
||||||
}, [subject.type, subject.items, subject.discriminator]);
|
|
||||||
|
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const rb = createBuilder();
|
const rb = createBuilder();
|
||||||
console.debug(rb?.builder.id, options);
|
|
||||||
if (rb) {
|
if (rb) {
|
||||||
if (options.method === "LIMIT_UNTIL") {
|
for (const filter of rb.filterBuilders) {
|
||||||
rb.filter.until(until).limit(50);
|
if (options.method === "LIMIT_UNTIL") {
|
||||||
} else {
|
filter.until(until).limit(50);
|
||||||
rb.filter.since(since).until(until);
|
} else {
|
||||||
if (since === undefined) {
|
filter.since(since).until(until);
|
||||||
rb.filter.limit(50);
|
if (since === undefined) {
|
||||||
|
filter.limit(50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return rb;
|
||||||
if (pref.autoShowLatest) {
|
|
||||||
// copy properties of main sub but with limit 0
|
|
||||||
// this will put latest directly into main feed
|
|
||||||
rb.builder
|
|
||||||
.withOptions({
|
|
||||||
leaveOpen: true,
|
|
||||||
})
|
|
||||||
.withFilter()
|
|
||||||
.authors(rb.filter.filter.authors)
|
|
||||||
.kinds(rb.filter.filter.kinds)
|
|
||||||
.tag("p", rb.filter.filter["#p"])
|
|
||||||
.tag("t", rb.filter.filter["#t"])
|
|
||||||
.search(rb.filter.filter.search)
|
|
||||||
.limit(1)
|
|
||||||
.since(now);
|
|
||||||
}
|
|
||||||
return rb.builder;
|
|
||||||
}
|
}
|
||||||
}, [until, since, options.method, pref, createBuilder]);
|
}, [until, since, options.method, pref, createBuilder]);
|
||||||
|
|
||||||
@ -131,12 +108,14 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
const subRealtime = useMemo(() => {
|
const subRealtime = useMemo(() => {
|
||||||
const rb = createBuilder();
|
const rb = createBuilder();
|
||||||
if (rb && !pref.autoShowLatest && options.method !== "LIMIT_UNTIL") {
|
if (rb && !pref.autoShowLatest && options.method !== "LIMIT_UNTIL") {
|
||||||
rb.builder.withOptions({
|
rb.withOptions({
|
||||||
leaveOpen: true,
|
leaveOpen: true,
|
||||||
});
|
});
|
||||||
rb.builder.id = `${rb.builder.id}:latest`;
|
rb.id = `${rb.id}:latest`;
|
||||||
rb.filter.limit(1).since(now);
|
for (const filter of rb.filterBuilders) {
|
||||||
return rb.builder;
|
filter.limit(1).since(now);
|
||||||
|
}
|
||||||
|
return rb;
|
||||||
}
|
}
|
||||||
}, [pref.autoShowLatest, createBuilder]);
|
}, [pref.autoShowLatest, createBuilder]);
|
||||||
|
|
||||||
@ -152,8 +131,8 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
main: main,
|
main: main?.filter(a => !isEventMuted(a)),
|
||||||
latest: latest,
|
latest: latest?.filter(a => !isEventMuted(a)),
|
||||||
loadMore: () => {
|
loadMore: () => {
|
||||||
if (main) {
|
if (main) {
|
||||||
console.debug("Timeline load more!");
|
console.debug("Timeline load more!");
|
||||||
|
@ -2,26 +2,8 @@ import { EventKind, RequestBuilder } from "@snort/system";
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
//import { LRUCache } from "typescript-lru-cache";
|
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
|
||||||
//const cache = new LRUCache<string, NostrEvent[]>({ maxSize: 100 });
|
|
||||||
|
|
||||||
export function useFollowsTimelineView(limit = 20) {
|
|
||||||
const follows = useLogin(s => s.follows.item);
|
|
||||||
const kinds = [EventKind.TextNote, EventKind.Repost, EventKind.Polls];
|
|
||||||
|
|
||||||
const req = useMemo(() => {
|
|
||||||
const rb = new RequestBuilder("follows-timeline");
|
|
||||||
rb.withOptions({
|
|
||||||
leaveOpen: true,
|
|
||||||
});
|
|
||||||
rb.withFilter().kinds(kinds).authors(follows).limit(limit);
|
|
||||||
return rb;
|
|
||||||
}, [follows, limit]);
|
|
||||||
return useRequestBuilder(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNotificationsView() {
|
export function useNotificationsView() {
|
||||||
const publicKey = useLogin(s => s.publicKey);
|
const publicKey = useLogin(s => s.publicKey);
|
||||||
const kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt];
|
const kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt];
|
||||||
|
@ -9,56 +9,56 @@ import Icon from "@/Components/Icons/Icon";
|
|||||||
import Avatar from "@/Components/User/Avatar";
|
import Avatar from "@/Components/User/Avatar";
|
||||||
import { ProfileLink } from "@/Components/User/ProfileLink";
|
import { ProfileLink } from "@/Components/User/ProfileLink";
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import { HasNotificationsMarker } from "@/Pages/Layout/HasNotificationsMarker";
|
import { HasNotificationsMarker } from "@/Pages/Layout/HasNotificationsMarker";
|
||||||
import { WalletBalance } from "@/Pages/Layout/WalletBalance";
|
import { WalletBalance } from "@/Pages/Layout/WalletBalance";
|
||||||
import { subscribeToNotifications } from "@/Utils/Notifications";
|
import { subscribeToNotifications } from "@/Utils/Notifications";
|
||||||
import { getCurrentSubscription } from "@/Utils/Subscription";
|
import { getCurrentSubscription } from "@/Utils/Subscription";
|
||||||
|
|
||||||
import useLogin from "../../Hooks/useLogin";
|
|
||||||
import { LogoHeader } from "./LogoHeader";
|
import { LogoHeader } from "./LogoHeader";
|
||||||
|
|
||||||
const MENU_ITEMS = [
|
const MENU_ITEMS = [
|
||||||
{
|
{
|
||||||
label: "Home",
|
label: <FormattedMessage defaultMessage="Home" id="ejEGdx" />,
|
||||||
icon: "home",
|
icon: "home",
|
||||||
link: "/",
|
link: "/",
|
||||||
nonLoggedIn: true,
|
nonLoggedIn: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Search",
|
label: <FormattedMessage defaultMessage="Search" id="xmcVZ0" />,
|
||||||
icon: "search",
|
icon: "search",
|
||||||
link: "/search",
|
link: "/search",
|
||||||
nonLoggedIn: true,
|
nonLoggedIn: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Notifications",
|
label: <FormattedMessage defaultMessage="Notifications" id="NAidKb" />,
|
||||||
icon: "bell",
|
icon: "bell",
|
||||||
link: "/notifications",
|
link: "/notifications",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Messages",
|
label: <FormattedMessage defaultMessage="Messages" id="hMzcSq" />,
|
||||||
icon: "mail",
|
icon: "mail",
|
||||||
link: "/messages",
|
link: "/messages",
|
||||||
hideReadOnly: true,
|
hideReadOnly: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Deck",
|
label: <FormattedMessage defaultMessage="Deck" id="o/gK53" />,
|
||||||
icon: "deck",
|
icon: "deck",
|
||||||
link: "/deck",
|
link: "/deck",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Social Graph",
|
label: <FormattedMessage defaultMessage="Social Graph" id="CzHZoc" />,
|
||||||
icon: "graph",
|
icon: "graph",
|
||||||
link: "/graph",
|
link: "/graph",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "About",
|
label: <FormattedMessage defaultMessage="About" id="g5pX+a" />,
|
||||||
icon: "info",
|
icon: "info",
|
||||||
link: "/donate",
|
link: "/donate",
|
||||||
nonLoggedIn: true,
|
nonLoggedIn: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Settings",
|
label: <FormattedMessage defaultMessage="Settings" id="D3idYv" />,
|
||||||
icon: "settings",
|
icon: "settings",
|
||||||
link: "/settings",
|
link: "/settings",
|
||||||
},
|
},
|
||||||
@ -126,7 +126,7 @@ export default function NavSidebar({ narrow = false }: { narrow: boolean }) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (item.label === "Notifications" && publisher) {
|
if (item.link === "/notifications" && publisher) {
|
||||||
subscribeToNotifications(publisher);
|
subscribeToNotifications(publisher);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -138,7 +138,7 @@ export default function NavSidebar({ narrow = false }: { narrow: boolean }) {
|
|||||||
className={({ isActive }) => getNavLinkClass(isActive, narrow)}>
|
className={({ isActive }) => getNavLinkClass(isActive, narrow)}>
|
||||||
<Icon name={`${item.icon}-outline`} className="icon-outline" size={24} />
|
<Icon name={`${item.icon}-outline`} className="icon-outline" size={24} />
|
||||||
<Icon name={`${item.icon}-solid`} className="icon-solid" size={24} />
|
<Icon name={`${item.icon}-solid`} className="icon-solid" size={24} />
|
||||||
{item.label === "Notifications" && <HasNotificationsMarker />}
|
{item.link === "/notifications" && <HasNotificationsMarker />}
|
||||||
{!narrow && <span className="hidden xl:inline ml-3">{item.label}</span>}
|
{!narrow && <span className="hidden xl:inline ml-3">{item.label}</span>}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
@ -1214,6 +1214,9 @@
|
|||||||
"egib+2": {
|
"egib+2": {
|
||||||
"defaultMessage": "{n,plural,=1{& {n} other} other{& {n} others}}"
|
"defaultMessage": "{n,plural,=1{& {n} other} other{& {n} others}}"
|
||||||
},
|
},
|
||||||
|
"ejEGdx": {
|
||||||
|
"defaultMessage": "Home"
|
||||||
|
},
|
||||||
"f1OxTe": {
|
"f1OxTe": {
|
||||||
"defaultMessage": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title."
|
"defaultMessage": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title."
|
||||||
},
|
},
|
||||||
@ -1482,6 +1485,9 @@
|
|||||||
"nwZXeh": {
|
"nwZXeh": {
|
||||||
"defaultMessage": "{n} blocked"
|
"defaultMessage": "{n} blocked"
|
||||||
},
|
},
|
||||||
|
"o/gK53": {
|
||||||
|
"defaultMessage": "Deck"
|
||||||
|
},
|
||||||
"o7e+nJ": {
|
"o7e+nJ": {
|
||||||
"defaultMessage": "{n} followers"
|
"defaultMessage": "{n} followers"
|
||||||
},
|
},
|
||||||
|
@ -400,6 +400,7 @@
|
|||||||
"eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.",
|
"eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.",
|
||||||
"eXT2QQ": "Group Chat",
|
"eXT2QQ": "Group Chat",
|
||||||
"egib+2": "{n,plural,=1{& {n} other} other{& {n} others}}",
|
"egib+2": "{n,plural,=1{& {n} other} other{& {n} others}}",
|
||||||
|
"ejEGdx": "Home",
|
||||||
"f1OxTe": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title.",
|
"f1OxTe": "Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title.",
|
||||||
"f2CAxA": "Dump",
|
"f2CAxA": "Dump",
|
||||||
"fBI91o": "Zap",
|
"fBI91o": "Zap",
|
||||||
@ -489,6 +490,7 @@
|
|||||||
"nUT0Lv": "Tools",
|
"nUT0Lv": "Tools",
|
||||||
"nihgfo": "Listen to this article",
|
"nihgfo": "Listen to this article",
|
||||||
"nwZXeh": "{n} blocked",
|
"nwZXeh": "{n} blocked",
|
||||||
|
"o/gK53": "Deck",
|
||||||
"o7e+nJ": "{n} followers",
|
"o7e+nJ": "{n} followers",
|
||||||
"oJ+JJN": "Nothing found :/",
|
"oJ+JJN": "Nothing found :/",
|
||||||
"odFwjL": "Follows only",
|
"odFwjL": "Follows only",
|
||||||
|
@ -92,6 +92,10 @@ export class RequestBuilder {
|
|||||||
return this.#builders.length;
|
return this.#builders.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get filterBuilders() {
|
||||||
|
return this.#builders;
|
||||||
|
}
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
return this.#options;
|
return this.#options;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user