mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-20 13:01:07 +02:00
Add option to search notes in search view
This commit is contained in:
5
.changeset/smooth-kings-obey.md
Normal file
5
.changeset/smooth-kings-obey.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add option to search notes in search view
|
@@ -1,31 +1,34 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Box, Button, Flex, IconButton, Input, Link, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import { Box, Button, ButtonGroup, Flex, IconButton, Input, Link, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import { CopyToClipboardIcon, QrCodeIcon } from "../../components/icons";
|
||||
import QrScannerModal from "../../components/qr-scanner-modal";
|
||||
import { SEARCH_RELAYS } from "../../const";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { safeDecode } from "../../helpers/nip19";
|
||||
import { getMatchHashtag } from "../../helpers/regexp";
|
||||
import { parseKind0Event } from "../../helpers/user-metadata";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import { searchParamsToJson } from "../../helpers/url";
|
||||
import { EmbedableContent, embedUrls } from "../../helpers/embeds";
|
||||
import { CopyToClipboardIcon, NotesIcon, QrCodeIcon } from "../../components/icons";
|
||||
import QrScannerModal from "../../components/qr-scanner-modal";
|
||||
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
|
||||
import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { parseKind0Event } from "../../helpers/user-metadata";
|
||||
import { UserAvatar } from "../../components/user-avatar";
|
||||
import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
import { EmbedableContent, embedUrls } from "../../helpers/embeds";
|
||||
import { UserDnsIdentityIcon } from "../../components/user-dns-identity-icon";
|
||||
import { embedNostrLinks, renderGenericUrl } from "../../components/embed-types";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import trustedUserStatsService, { NostrBandUserStats } from "../../services/trusted-user-stats";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { SEARCH_RELAYS } from "../../const";
|
||||
import User01 from "../../components/icons/user-01";
|
||||
import GenericNoteTimeline from "../../components/timeline-page/generic-note-timeline";
|
||||
|
||||
function ProfileResult({ profile }: { profile: NostrEvent }) {
|
||||
const metadata = parseKind0Event(profile);
|
||||
@@ -57,11 +60,11 @@ function ProfileResult({ profile }: { profile: NostrEvent }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SearchResults({ search }: { search: string }) {
|
||||
function ProfileSearchResults({ search }: { search: string }) {
|
||||
const searchRelays = useRelaySelectionRelays();
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
`${search}-search`,
|
||||
`${search}-profile-search`,
|
||||
searchRelays,
|
||||
{ search: search || "", kinds: [Kind.Metadata] },
|
||||
{ enabled: !!search },
|
||||
@@ -98,12 +101,39 @@ function SearchResults({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function NoteSearchResults({ search }: { search: string }) {
|
||||
const searchRelays = useRelaySelectionRelays();
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
`${search}-note-search`,
|
||||
searchRelays,
|
||||
{ search: search || "", kinds: [Kind.Text] },
|
||||
{ enabled: !!search },
|
||||
);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<GenericNoteTimeline timeline={timeline} />
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function SearchPage() {
|
||||
const navigate = useNavigate();
|
||||
const qrScannerModal = useDisclosure();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const mergeSearchParams = useCallback(
|
||||
(params: Record<string, any>) => {
|
||||
setSearchParams((p) => ({ ...searchParamsToJson(p), ...params }), { replace: true });
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
const [searchInput, setSearchInput] = useState(searchParams.get("q") ?? "");
|
||||
|
||||
const type = searchParams.get("type") ?? "users";
|
||||
const search = searchParams.get("q");
|
||||
|
||||
// update the input value when search changes
|
||||
@@ -121,11 +151,11 @@ export function SearchPage() {
|
||||
|
||||
const hashTagMatch = getMatchHashtag().exec(cleanText);
|
||||
if (hashTagMatch) {
|
||||
navigate({ pathname: "/t/" + hashTagMatch[2].toLocaleLowerCase() });
|
||||
navigate({ pathname: "/t/" + hashTagMatch[2].toLocaleLowerCase() }, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchParams({ q: cleanText }, { replace: true });
|
||||
mergeSearchParams({ q: cleanText });
|
||||
};
|
||||
|
||||
const readClipboard = useCallback(async () => {
|
||||
@@ -138,6 +168,8 @@ export function SearchPage() {
|
||||
handleSearchText(searchInput);
|
||||
};
|
||||
|
||||
const SearchResults = type === "users" ? ProfileSearchResults : NoteSearchResults;
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<QrScannerModal isOpen={qrScannerModal.isOpen} onClose={qrScannerModal.onClose} onData={handleSearchText} />
|
||||
@@ -152,10 +184,29 @@ export function SearchPage() {
|
||||
<Input type="search" value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
|
||||
<Button type="submit">Search</Button>
|
||||
</Flex>
|
||||
<RelaySelectionButton />
|
||||
</Flex>
|
||||
</form>
|
||||
|
||||
<Flex gap="2">
|
||||
<ButtonGroup size="sm" isAttached variant="outline">
|
||||
<Button
|
||||
leftIcon={<User01 />}
|
||||
colorScheme={type === "users" ? "primary" : undefined}
|
||||
onClick={() => mergeSearchParams({ type: "users" })}
|
||||
>
|
||||
Users
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<NotesIcon />}
|
||||
colorScheme={type === "notes" ? "primary" : undefined}
|
||||
onClick={() => mergeSearchParams({ type: "notes" })}
|
||||
>
|
||||
Notes
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<RelaySelectionButton ml="auto" size="sm" />
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="8">
|
||||
{search ? (
|
||||
<SearchResults search={search} />
|
||||
|
Reference in New Issue
Block a user