feat: add global search input and search page
This commit is contained in:
parent
07a8f62954
commit
ccf4bdcde0
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -8,6 +8,7 @@ export const useGetFeed = (data?: {
|
|||
limit?: number
|
||||
noteIds?: string[]
|
||||
daysInterval?: number
|
||||
keyword?: string
|
||||
}) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["getFeed", data],
|
||||
|
|
|
|||
Loading…
Reference in New Issue