feat: add global search input and search page

This commit is contained in:
DIYgod 2023-04-11 23:49:26 +01:00
parent 07a8f62954
commit ccf4bdcde0
No known key found for this signature in database
6 changed files with 152 additions and 6 deletions

View File

@ -0,0 +1,62 @@
import { Profile } from "~/lib/types"
import { useAccountSites } from "~/queries/site"
import { useForm } from "react-hook-form"
import { useAccountState } from "@crossbell/connect-kit"
import { useState, useEffect } from "react"
import { useCommentPage, useUpdateComment } from "~/queries/page"
import { useRouter } from "next/router"
import { useTranslation } from "next-i18next"
export const SearchInput: React.FC<{
characterId?: string
value?: string
}> = ({ characterId, value }) => {
const account = useAccountState((s) => s.computed.account)
const userSites = useAccountSites()
const commentPage = useCommentPage()
const updateComment = useUpdateComment()
const router = useRouter()
const [viewer, setViewer] = useState<Profile | null>(null)
const { t } = useTranslation(["common", "site"])
useEffect(() => {
if (userSites.isSuccess && userSites.data?.length) {
setViewer(userSites.data[0])
}
}, [userSites, router])
const form = useForm({
defaultValues: {
content: value || "",
},
})
const handleSubmit = form.handleSubmit(async (values) => {
router.push(`/search?q=${values.content}`)
})
useEffect(() => {
if (commentPage.isSuccess || updateComment.isSuccess) {
form.reset()
}
}, [commentPage.isSuccess, updateComment.isSuccess, form])
return (
<div className="xlog-comment-input flex">
<form className="w-full relative" onSubmit={handleSubmit}>
<div
className="absolute left-0 top-1/2 -translate-y-1/2 text-2xl text-zinc-500 h-11 w-14 flex items-center justify-center cursor-pointer"
onClick={handleSubmit}
>
<i className="i-mingcute:search-line block"></i>
</div>
<input
id="content"
className="rounded-full w-full pl-12 pr-5 h-11 border outline-none hover:shadow-md focus:shadow-md transition-shadow"
placeholder={t("Search for your interest", { ns: "site" }) || ""}
{...form.register("content", {})}
/>
</form>
</div>
)
}

View File

@ -140,7 +140,8 @@ const Post = ({
export const MainFeed: React.FC<{
type?: FeedType
noteIds?: string[]
}> = ({ type, noteIds }) => {
keyword?: string
}> = ({ type, noteIds, keyword }) => {
const { t } = useTranslation(["common", "site"])
const currentCharacterId = useAccountState(
@ -154,6 +155,7 @@ export const MainFeed: React.FC<{
characterId: currentCharacterId,
noteIds: noteIds,
daysInterval: hotInterval,
keyword: keyword,
})
const hasFiltering = type === "latest"

View File

@ -11,8 +11,9 @@ import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
import { useAccountSites, useSubscribeToSites } from "~/queries/site"
import { useTranslation } from "next-i18next"
import topics from "../../../data/topics.json"
import { SearchInput } from "~/components/common/SearchInput"
export function MainSidebar() {
export function MainSidebar({ hideSearch }: { hideSearch?: boolean }) {
const showcaseSites = useGetSites(showcase)
const { t } = useTranslation("index")
@ -36,9 +37,10 @@ export function MainSidebar() {
const [showcaseMore, setShowcaseMore] = useState(false)
return (
<div className="w-80 pl-10 pt-4 hidden sm:block">
<div className="w-80 pl-10 hidden sm:block space-y-10">
{!hideSearch && <SearchInput />}
<div className="text-center">
<div className="mb-10 text-zinc-700 space-y-3">
<div className="text-zinc-700 space-y-3">
<p className="font-bold text-lg">{t("Hot Topics")}</p>
<ul className="overflow-y-clip relative text-left space-y-2">
{topics.map((topic: any) => (
@ -58,7 +60,7 @@ export function MainSidebar() {
</div>
</div>
<div className="text-center">
<div className="mb-10 text-zinc-700 space-y-3">
<div className="text-zinc-700 space-y-3">
<p className="font-bold text-lg">{t("Suggested creators for you")}</p>
<Button
onClick={followAll}

View File

@ -58,7 +58,7 @@ const expandPage = async (
return page
}
export type FeedType = "latest" | "following" | "topic" | "hot"
export type FeedType = "latest" | "following" | "topic" | "hot" | "search"
export async function getFeed({
type,
@ -67,6 +67,7 @@ export async function getFeed({
characterId,
noteIds,
daysInterval,
keyword,
}: {
type?: FeedType
cursor?: string
@ -74,7 +75,11 @@ export async function getFeed({
characterId?: number
noteIds?: string[]
daysInterval?: number
keyword?: string
}) {
if (type === "search" && !keyword) {
type = "latest"
}
switch (type) {
case "latest": {
const result = await indexer.getNotes({
@ -265,5 +270,25 @@ export async function getFeed({
count: list?.length || 0,
}
}
case "search": {
const result = await indexer.searchNotes(keyword!, {
sources: ["xlog"],
tags: ["post"],
limit: limit,
cursor,
})
const list = await Promise.all(
result.list.map(async (page: any) => {
return await expandPage(page)
}),
)
return {
list,
cursor: result.cursor,
count: result.count,
}
}
}
}

54
src/pages/search.tsx Normal file
View File

@ -0,0 +1,54 @@
import { GetServerSideProps } from "next"
import { ReactElement, useState } from "react"
import { MainLayout } from "~/components/main/MainLayout"
import { dehydrate, QueryClient } from "@tanstack/react-query"
import { prefetchGetSites } from "~/queries/site.server"
import showcase from "../../data/showcase.json"
import topics from "../../data/topics.json"
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
import { languageDetector } from "~/lib/language-detector"
import { MainFeed } from "~/components/main/MainFeed"
import { MainSidebar } from "~/components/main/MainSidebar"
import { useRouter } from "next/router"
import { SearchInput } from "~/components/common/SearchInput"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const queryClient = new QueryClient()
await prefetchGetSites(showcase, queryClient)
return {
props: {
...(await serverSideTranslations(languageDetector(ctx), [
"common",
"index",
"dashboard",
])),
dehydratedState: dehydrate(queryClient),
},
}
}
function Search() {
const router = useRouter()
const keyword = router.query.q as string
return (
<section className="pt-24">
<div className="max-w-screen-lg px-5 mx-auto flex">
<div className="flex-1 min-w-[300px]">
<SearchInput value={keyword} />
<div className="mt-10">
<MainFeed type="search" keyword={keyword} />
</div>
</div>
<MainSidebar hideSearch={true} />
</div>
</section>
)
}
Search.getLayout = (page: ReactElement) => {
return <MainLayout>{page}</MainLayout>
}
export default Search

View File

@ -8,6 +8,7 @@ export const useGetFeed = (data?: {
limit?: number
noteIds?: string[]
daysInterval?: number
keyword?: string
}) => {
return useInfiniteQuery({
queryKey: ["getFeed", data],