add embedded wiki page card

improve markdown formatting
fix wiki pages when logged out
handle wiki naddr
This commit is contained in:
hzrd149
2024-04-19 11:08:07 -05:00
parent ddf9665075
commit 9032a2a426
16 changed files with 364 additions and 77 deletions

View File

@@ -4,7 +4,7 @@ import { NostrEvent } from "../types/nostr-event";
import relayPoolService from "../services/relay-pool";
import { isFilterEqual } from "../helpers/nostr/filter";
import ControlledObservable from "./controlled-observable";
import { Filter, Relay, Subscription } from "nostr-tools";
import { AbstractRelay, Filter, Subscription } from "nostr-tools";
import { offlineMode } from "../services/offline-mode";
import RelaySet from "./relay-set";
@@ -17,8 +17,8 @@ export default class NostrMultiSubscription {
name?: string;
filters: Filter[] = [];
relays: Relay[] = [];
subscriptions = new Map<Relay, Subscription>();
relays: AbstractRelay[] = [];
subscriptions = new Map<AbstractRelay, Subscription>();
state = NostrMultiSubscription.INIT;
onEvent = new ControlledObservable<NostrEvent>();
@@ -34,10 +34,10 @@ export default class NostrMultiSubscription {
this.seenEvents.add(event.id);
}
private handleAddRelay(relay: Relay) {
private handleAddRelay(relay: AbstractRelay) {
relayPoolService.addClaim(relay.url, this);
}
private handleRemoveRelay(relay: Relay) {
private handleRemoveRelay(relay: AbstractRelay) {
relayPoolService.removeClaim(relay.url, this);
// close subscription
@@ -96,7 +96,8 @@ export default class NostrMultiSubscription {
subscription.filters = filters;
subscription.fire();
} else {
if (filters.length === 0) debugger;
if (!relay.connected) relayPoolService.requestConnect(relay);
subscription = relay.subscribe(filters, {
onevent: (event) => this.handleEvent(event),
onclose: () => {
@@ -112,7 +113,12 @@ export default class NostrMultiSubscription {
}
publish(event: NostrEvent) {
return Promise.allSettled(this.relays.map((r) => r.publish(event)));
return Promise.allSettled(
this.relays.map(async (r) => {
if (!r.connected) await relayPoolService.requestConnect(r);
return await r.publish(event);
}),
);
}
open() {

View File

@@ -1,4 +1,5 @@
import { AbstractRelay } from "nostr-tools";
import { logger } from "../helpers/debug";
import { validateRelayURL } from "../helpers/relay";
import { offlineMode } from "../services/offline-mode";
@@ -35,7 +36,20 @@ export default class RelayPool {
}
const relay = this.relays.get(key) as AbstractRelay;
if (connect && !relay.connected && !offlineMode.value) {
if (connect) this.requestConnect(relay);
return relay;
}
async requestConnect(relayOrUrl: string | URL | AbstractRelay) {
let relay: AbstractRelay | undefined = undefined;
if (typeof relayOrUrl === "string") relay = this.relays.get(relayOrUrl);
else if (relayOrUrl instanceof URL) relay = this.relays.get(relayOrUrl.toString());
else relay = relayOrUrl;
if (!relay) return;
if (!relay.connected && !offlineMode.value) {
try {
relay.connect();
} catch (e) {
@@ -43,7 +57,6 @@ export default class RelayPool {
this.log(e);
}
}
return relay;
}
pruneRelays() {

View File

@@ -0,0 +1,66 @@
import {
Button,
ButtonGroup,
Card,
CardBody,
CardFooter,
CardHeader,
CardProps,
Heading,
LinkBox,
Text,
} from "@chakra-ui/react";
import { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { NostrEvent } from "../../../types/nostr-event";
import UserLink from "../../user/user-link";
import { getPageForks, getPageSummary, getPageTitle } from "../../../helpers/nostr/wiki";
import HoverLinkOverlay from "../../hover-link-overlay";
import { getSharableEventAddress } from "../../../helpers/nip19";
import Timestamp from "../../timestamp";
import GitBranch01 from "../../icons/git-branch-01";
import UserName from "../../user/user-name";
export default function EmbeddedWikiPage({ page: page, ...props }: Omit<CardProps, "children"> & { page: NostrEvent }) {
const { address } = useMemo(() => getPageForks(page), [page]);
const showFooter = !!address;
return (
<Card as={LinkBox} {...props}>
<CardHeader p="2" pb="0" display="flex" gap="2" alignItems="center">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
{getPageTitle(page)}
</HoverLinkOverlay>
</Heading>
<Text>
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
</Text>
</CardHeader>
<CardBody p="2" overflow="hidden">
<Text color="GrayText" noOfLines={2}>
{getPageSummary(page)}
</Text>
</CardBody>
{showFooter && (
<CardFooter>
<ButtonGroup variant="link" mt="auto">
{address && (
<Button
as={RouterLink}
to={`/wiki/page/${nip19.naddrEncode(address)}`}
p="2"
colorScheme="blue"
leftIcon={<GitBranch01 />}
>
<UserName pubkey={address.pubkey} />
</Button>
)}
</ButtonGroup>
</CardFooter>
)}
</Card>
);
}

View File

@@ -41,6 +41,8 @@ import { FLARE_VIDEO_KIND } from "../../helpers/nostr/flare";
import EmbeddedFlareVideo from "./event-types/embedded-flare-video";
import LoadingNostrLink from "../loading-nostr-link";
import EmbeddedRepost from "./event-types/embedded-repost";
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
import EmbeddedWikiPage from "./event-types/embedded-wiki-page";
const EmbeddedStemstrTrack = lazy(() => import("./event-types/embedded-stemstr-track"));
export type EmbedProps = {
@@ -93,6 +95,8 @@ export function EmbedEvent({
case kinds.Repost:
case kinds.GenericRepost:
return <EmbeddedRepost repost={event} {...cardProps} />;
case WIKI_PAGE_KIND:
return <EmbeddedWikiPage page={event} {...cardProps} />;
}
return <EmbeddedUnknown event={event} {...cardProps} />;

View File

@@ -1,4 +1,4 @@
import { Flex, Tag, TagLabel } from "@chakra-ui/react";
import { Flex, FlexProps, Tag, TagLabel } from "@chakra-ui/react";
import { NostrEvent } from "nostr-tools";
import { getEventUID } from "nostr-idb";
import styled from "@emotion/styled";
@@ -16,7 +16,7 @@ const HiddenScrollbar = styled(Flex)`
}
`;
export default function ZapBubbles({ event }: { event: NostrEvent }) {
export default function ZapBubbles({ event, ...props }: { event: NostrEvent } & Omit<FlexProps, "children">) {
const zaps = useEventZaps(getEventUID(event));
if (zaps.length === 0) return null;
@@ -24,10 +24,10 @@ export default function ZapBubbles({ event }: { event: NostrEvent }) {
const sorted = zaps.sort((a, b) => (b.payment.amount ?? 0) - (a.payment.amount ?? 0));
return (
<HiddenScrollbar overflowY="hidden" overflowX="auto" gap="2">
<HiddenScrollbar overflowY="hidden" overflowX="auto" gap="2" {...props}>
{sorted.map((zap) => (
<Tag key={zap.event.id} borderRadius="full" py="1" flexShrink={0} variant="outline">
<LightningIcon mr="1" />
<LightningIcon mr="1" color="yellow.400" />
<TagLabel fontWeight="bold">{readablizeSats((zap.payment.amount ?? 0) / 1000)}</TagLabel>
<UserAvatar pubkey={zap.request.pubkey} size="xs" square={false} ml="2" />
</Tag>

View File

@@ -19,8 +19,8 @@ export function getPageSummary(page: NostrEvent) {
}
export function getPageForks(page: NostrEvent) {
const addressFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3]);
const eventFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3]);
const addressFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
const eventFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
const address = addressFork ? parseCoordinate(addressFork[1], true) ?? undefined : undefined;
const event: nip19.EventPointer | undefined = eventFork ? { id: eventFork[1] } : undefined;
@@ -28,6 +28,16 @@ export function getPageForks(page: NostrEvent) {
return { event, address };
}
export function getPageDefer(page: NostrEvent) {
const addressTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
const eventTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
const address = addressTag ? parseCoordinate(addressTag[1], true) ?? undefined : undefined;
const event: nip19.EventPointer | undefined = eventTag ? { id: eventTag[1] } : undefined;
if (event || address) return { event, address };
}
export function isPageFork(page: NostrEvent) {
return page.tags.some((t) => (t[0] === "a" || t[0] === "e") && t[3] === "fork");
}

View File

@@ -158,7 +158,7 @@ export function subscribeMany(relays: string[], filters: Filter[], params: Subsc
let relay: AbstractRelay;
try {
relay = relayPoolService.requestRelay(url);
await relay.connect();
await relayPoolService.requestConnect(relay);
// changed from nostr-tools
// relay = await this.ensureRelay(url, {
// connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1000) : undefined,

View File

@@ -9,7 +9,7 @@ import replaceableEventsService from "./replaceable-events";
import { getPubkeysFromList } from "../helpers/nostr/lists";
const log = logger.extend("web-of-trust");
let webOfTrust: PubkeyGraph;
let webOfTrust = new PubkeyGraph("");
let newEvents = 0;
const throttleUpdateWebOfTrust = _throttle(() => {

View File

@@ -1,6 +1,7 @@
import { Alert, AlertIcon, AlertTitle } from "@chakra-ui/react";
import { Navigate, useParams } from "react-router-dom";
import { kinds, nip19 } from "nostr-tools";
import { STREAM_KIND } from "../../helpers/nostr/stream";
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
@@ -8,6 +9,8 @@ import { ErrorBoundary } from "../../components/error-boundary";
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
import { FLARE_VIDEO_KIND } from "../../helpers/nostr/flare";
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
import { EmbedEventPointer } from "../../components/embed-event";
function NostrLinkPage() {
const { link } = useParams() as { link?: string };
@@ -41,15 +44,17 @@ function NostrLinkPage() {
if (decoded.data.kind === FLARE_VIDEO_KIND) return <Navigate to={`/videos/${cleanLink}`} replace />;
if (decoded.data.kind === kinds.ChannelCreation) return <Navigate to={`/channels/${cleanLink}`} replace />;
if (decoded.data.kind === kinds.ShortTextNote) return <Navigate to={`/n/${cleanLink}`} replace />;
// if there is no kind redirect to the thread view
return <Navigate to={`/n/${cleanLink}`} replace />;
if (decoded.data.kind === WIKI_PAGE_KIND) return <Navigate to={`/wiki/page/${cleanLink}`} replace />;
}
return (
<Alert status="warning">
<AlertIcon />
<AlertTitle>Unknown type {JSON.stringify(decoded.data)}</AlertTitle>
</Alert>
<>
<Alert status="warning">
<AlertIcon />
<AlertTitle>Unknown event kind</AlertTitle>
</Alert>
<EmbedEventPointer pointer={decoded} />
</>
);
}

View File

@@ -1,8 +1,81 @@
import { Image, Link, LinkProps, Text, TextProps } from "@chakra-ui/react";
import {
Code,
CodeProps,
Heading,
HeadingProps,
Image,
Link,
LinkProps,
ListItem,
OrderedList,
Table,
TableContainer,
TableProps,
Tbody,
Td,
Text,
TextProps,
Tfoot,
Th,
Thead,
Tr,
UnorderedList,
} from "@chakra-ui/react";
import styled from "@emotion/styled";
import { NostrEvent } from "nostr-tools";
import Markdown, { Components } from "react-markdown";
import remarkGfm from "remark-gfm";
const StyledMarkdown = styled(Markdown)`
pre > code {
display: block;
padding-block: var(--chakra-space-2);
padding-inline: var(--chakra-space-4);
}
`;
function H1({ children, ...props }: HeadingProps) {
return (
<Heading as="h1" size="xl" mt="6" mb="2" {...props}>
{children}
</Heading>
);
}
function H2({ children, ...props }: HeadingProps) {
return (
<Heading as="h2" size="lg" mt="6" mb="2" {...props}>
{children}
</Heading>
);
}
function H3({ children, ...props }: HeadingProps) {
return (
<Heading as="h3" size="md" mt="4" mb="2" {...props}>
{children}
</Heading>
);
}
function H4({ children, ...props }: HeadingProps) {
return (
<Heading as="h4" size="sm" my="2" {...props}>
{children}
</Heading>
);
}
function H5({ children, ...props }: HeadingProps) {
return (
<Heading as="h5" size="xs" my="2" {...props}>
{children}
</Heading>
);
}
function H6({ children, ...props }: HeadingProps) {
return (
<Heading as="h6" size="xs" my="2" {...props}>
{children}
</Heading>
);
}
function A({ children, ...props }: LinkProps) {
return (
<Link color="blue.500" isExternal {...props}>
@@ -12,22 +85,48 @@ function A({ children, ...props }: LinkProps) {
}
function P({ children, ...props }: TextProps) {
return (
<Text py="2" {...props}>
<Text my="2" {...props}>
{children}
</Text>
);
}
function TableWithContainer({ children, ...props }: TableProps) {
return (
<TableContainer>
<Table size="sm" mb="4" {...props}>
{children}
</Table>
</TableContainer>
);
}
const components: Partial<Components> = {
h1: H1,
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6,
a: A,
img: Image,
p: P,
ul: UnorderedList,
ol: OrderedList,
li: ListItem,
table: TableWithContainer,
thead: Thead,
tbody: Tbody,
tfoot: Tfoot,
tr: Tr,
td: Td,
th: Th,
code: Code,
};
export default function MarkdownContent({ event }: { event: NostrEvent }) {
return (
<Markdown remarkPlugins={[remarkGfm]} components={components}>
<StyledMarkdown remarkPlugins={[remarkGfm]} components={components}>
{event.content}
</Markdown>
</StyledMarkdown>
);
}

View File

@@ -19,21 +19,19 @@ export default function WikiPageResult({ page, compare }: { page: NostrEvent; co
return (
<Flex as={LinkBox} py="2" px="4" direction="column">
<Flex gap="2" alignItems="center">
<Box overflow="hidden">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
{getPageTitle(page)}
</HoverLinkOverlay>
</Heading>
<Text>
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
</Text>
<Text color="GrayText" noOfLines={2}>
{getPageSummary(page)}
</Text>
</Box>
</Flex>
<Box overflow="hidden">
<Heading size="md">
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
{getPageTitle(page)}
</HoverLinkOverlay>
</Heading>
<Text>
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
</Text>
<Text color="GrayText" noOfLines={2}>
{getPageSummary(page)}
</Text>
</Box>
<ButtonGroup variant="link" mt="auto">
{address && (
<Button

View File

@@ -7,8 +7,7 @@ export default function WikiSearchForm({ ...props }: Omit<FlexProps, "children">
const { register, handleSubmit } = useForm({ defaultValues: { search: "" } });
const onSubmit = handleSubmit((values) => {
// navigate(`/wiki/search?q=${encodeURIComponent(values.search)}`);
navigate(`/wiki/topic/${values.search}`);
navigate(`/wiki/search?q=${encodeURIComponent(values.search)}`);
});
return (

View File

@@ -0,0 +1,24 @@
import { NostrEvent } from "nostr-tools";
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
export default function WikiPageMenu({
page,
...props
}: { page: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
return (
<>
<DotsMenuButton {...props}>
<OpenInAppMenuItem event={page} />
<CopyShareLinkMenuItem event={page} />
<CopyEmbedCodeMenuItem event={page} />
<DebugEventMenuItem event={page} />
</DotsMenuButton>
</>
);
}

View File

@@ -14,6 +14,7 @@ import WikiPageResult from "./components/wiki-page-result";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import { ErrorBoundary } from "../../components/error-boundary";
import { WIKI_RELAYS } from "../../const";
import { ExternalLinkIcon } from "../../components/icons";
function eventFilter(event: NostrEvent) {
if (!validatePage(event)) return false;
@@ -29,13 +30,16 @@ export default function WikiHomeView() {
return (
<VerticalPageLayout>
<Flex mx="auto" mt="10vh" mb="10vh" direction="column" alignItems="center" maxW="full" gap="4">
<Flex mx="auto" mt="10vh" mb="10vh" direction="column" alignItems="center" maxW="full">
<Heading>
<Link as={RouterLink} to="/wiki/topic/wikifreedia">
Wikifreedia
</Link>
</Heading>
<WikiSearchForm maxW="full" />
<Link isExternal color="blue.500" href="https://wikifreedia.xyz/">
wikifreedia.xyz <ExternalLinkIcon />
</Link>
<WikiSearchForm maxW="full" mt="4" />
</Flex>
<Heading size="md" mt="4">

View File

@@ -16,7 +16,7 @@ import { Link as RouterLink } from "react-router-dom";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
import { getPageDefer, getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
import MarkdownContent from "./components/markdown";
import UserLink from "../../components/user/user-link";
import { getWebOfTrust } from "../../services/web-of-trust";
@@ -33,6 +33,54 @@ import FileSearch01 from "../../components/icons/file-search-01";
import NoteZapButton from "../../components/note/note-zap-button";
import ZapBubbles from "../../components/note/timeline-note/components/zap-bubbles";
import QuoteRepostButton from "../../components/note/quote-repost-button";
import WikiPageMenu from "./components/wioki-page-menu";
function ForkAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
const topic = getPageTopic(page);
return (
<Alert status="info" display="flex" flexWrap="wrap">
<AlertIcon>
<GitBranch01 boxSize={5} />
</AlertIcon>
<Text>
This page was forked from <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
</Text>
<ButtonGroup variant="link" ml="auto">
<Button leftIcon={<ExternalLinkIcon />} as={RouterLink} to={`/wiki/page/${nip19.naddrEncode(address)}`}>
Original
</Button>
<Button
leftIcon={<FileSearch01 />}
as={RouterLink}
to={`/wiki/compare/${topic}/${address.pubkey}/${page.pubkey}`}
>
Compare
</Button>
</ButtonGroup>
</Alert>
);
}
function DeferAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
return (
<Alert status="warning" display="flex" flexWrap="wrap">
<AlertIcon />
<Text>
The author of this page has deferred to <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
</Text>
<Button
leftIcon={<ExternalLinkIcon />}
as={RouterLink}
to={`/wiki/page/${nip19.naddrEncode(address)}`}
variant="link"
ml="4"
>
View
</Button>
</Alert>
);
}
function WikiPagePage({ page }: { page: NostrEvent }) {
const topic = getPageTopic(page);
@@ -40,6 +88,7 @@ function WikiPagePage({ page }: { page: NostrEvent }) {
const pages = useSubject(timeline.timeline).filter((p) => p.pubkey !== page.pubkey);
const { address } = getPageForks(page);
const defer = getPageDefer(page);
const forks = getWebOfTrust().sortByDistanceAndConnections(
pages.filter((p) => getPageForks(p).address?.pubkey === page.pubkey),
@@ -58,37 +107,17 @@ function WikiPagePage({ page }: { page: NostrEvent }) {
<ButtonGroup float="right">
<QuoteRepostButton event={page} />
<NoteZapButton event={page} showEventPreview={false} />
<DebugEventButton event={page} />
<WikiPageMenu page={page} aria-label="Page Options" />
</ButtonGroup>
<Heading>{getPageTitle(page)}</Heading>
<Text>
by <UserLink pubkey={page.pubkey} /> - <Timestamp timestamp={page.created_at} />
</Text>
{address && (
<Alert status="info" display="flex" flexWrap="wrap">
<AlertIcon>
<GitBranch01 boxSize={5} />
</AlertIcon>
<Text>
This page was forked from <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
</Text>
<ButtonGroup variant="link" ml="auto">
<Button leftIcon={<ExternalLinkIcon />} as={RouterLink} to={`/wiki/page/${nip19.naddrEncode(address)}`}>
Original
</Button>
<Button
leftIcon={<FileSearch01 />}
as={RouterLink}
to={`/wiki/compare/${topic}/${address.pubkey}/${page.pubkey}`}
>
Compare
</Button>
</ButtonGroup>
</Alert>
)}
{address && <ForkAlert page={page} address={address} />}
{defer?.address && <DeferAlert page={page} address={defer.address} />}
<Divider my="2" />
<MarkdownContent event={page} />
<ZapBubbles event={page} />
<ZapBubbles event={page} mt="4" />
</Box>
{forks.length > 0 && (

View File

@@ -2,14 +2,18 @@ import { useEffect, useState } from "react";
import { Navigate } from "react-router-dom";
import { Button, Flex, Heading, Input, Link } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { NostrEvent } from "nostr-tools";
import { Filter, NostrEvent } from "nostr-tools";
import { useForm } from "react-hook-form";
import { Subscription, getEventUID } from "nostr-idb";
import VerticalPageLayout from "../../components/vertical-page-layout";
import useRouteSearchValue from "../../hooks/use-route-search-value";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { subscribeMany } from "../../helpers/relay";
import { SEARCH_RELAYS } from "../../const";
import { SEARCH_RELAYS, WIKI_RELAYS } from "../../const";
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
import { localRelay } from "../../services/local-relay";
import { getWebOfTrust } from "../../services/web-of-trust";
import WikiPageResult from "./components/wiki-page-result";
export default function WikiSearchView() {
const { value: query, setValue: setQuery } = useRouteSearchValue("q");
@@ -22,9 +26,32 @@ export default function WikiSearchView() {
const [results, setResults] = useState<NostrEvent[]>([]);
// useEffect(() => {
// const sub = subscribeMany([SEARCH_RELAYS]);
// }, [query]);
useEffect(() => {
setResults([]);
const filter: Filter = { kinds: [WIKI_PAGE_KIND], search: query };
const seen = new Set<string>();
const handleEvent = (event: NostrEvent) => {
if (seen.has(getEventUID(event))) return;
setResults((arr) => arr.concat(event));
seen.add(getEventUID(event));
};
const remoteSearchSub = subscribeMany([...SEARCH_RELAYS, ...WIKI_RELAYS], [filter], {
onevent: handleEvent,
oneose: () => remoteSearchSub.close(),
});
if (localRelay) {
const localSearchSub: Subscription = localRelay.subscribe([filter], {
onevent: handleEvent,
oneose: () => localSearchSub.close(),
});
}
}, [query, setResults]);
const sorted = getWebOfTrust().sortByDistanceAndConnections(results, (p) => p.pubkey);
return (
<VerticalPageLayout>
@@ -49,6 +76,9 @@ export default function WikiSearchView() {
</Button>
</Flex>
</Flex>
{sorted.map((page) => (
<WikiPageResult key={page.id} page={page} />
))}
</VerticalPageLayout>
);
}