feat: unify search with browse page
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { MessageStream } from "../../components/messageStream/MessageStream";
|
||||
import { useI18n } from "../../i18n";
|
||||
|
||||
export function Browse() {
|
||||
const { t } = useI18n();
|
||||
const [sp] = useSearchParams();
|
||||
const q = sp.get("q") || "";
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<h1 className="mx-auto max-w-full px-3 text-2xl font-bold md:max-w-[820px] lg:max-w-[1080px] xl:max-w-[1180px]">
|
||||
{t("all")}
|
||||
{q ? `${t("search")}: ${q}` : t("all")}
|
||||
</h1>
|
||||
<MessageStream scope={{ kind: "all" }} />
|
||||
</section>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { RecommendedCard } from "../../components/RecommendedCard";
|
||||
import { SectionHeader } from "../../components/SectionHeader";
|
||||
import { langQuery, useI18n } from "../../i18n";
|
||||
import { sourceLanguageQuery } from "../../i18nLanguages";
|
||||
import { categoryCardLines } from "../../utils/categoryDisplay";
|
||||
import {
|
||||
postToResource,
|
||||
@@ -28,11 +29,14 @@ export function Home() {
|
||||
const [canScrollRec, setCanScrollRec] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const q = `?lang=${encodeURIComponent(langQuery(lang))}`;
|
||||
const langParam = encodeURIComponent(langQuery(lang));
|
||||
const languageParam = encodeURIComponent(sourceLanguageQuery(lang));
|
||||
const catQ = `?lang=${langParam}`;
|
||||
const postQ = `?lang=${langParam}&language=${languageParam}`;
|
||||
Promise.all([
|
||||
getJSON<Category[]>(`/api/categories${q}`),
|
||||
getJSON<{ items: Post[] }>(`/api/posts/recommended${q}&limit=12`),
|
||||
getJSON<{ items: Post[] }>(`/api/posts/latest${q}&limit=8`),
|
||||
getJSON<Category[]>(`/api/categories${catQ}`),
|
||||
getJSON<{ items: Post[] }>(`/api/posts/recommended${postQ}&limit=12`),
|
||||
getJSON<{ items: Post[] }>(`/api/posts/latest${postQ}&limit=8`),
|
||||
])
|
||||
.then(([c, r, l]) => {
|
||||
setCats(itemsOrEmpty(c));
|
||||
|
||||
@@ -1,126 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { getJSON, itemsOrEmpty, postJSON } from "../../api";
|
||||
import { langQuery, useI18n } from "../../i18n";
|
||||
import {
|
||||
LANG_OPTIONS,
|
||||
languageLabel,
|
||||
sourceLanguageQuery,
|
||||
} from "../../i18nLanguages";
|
||||
import { MessageBubble } from "../../components/messageStream/MessageBubble";
|
||||
import { typeFilterLabel } from "../../resourceTypeLabels";
|
||||
import type { Post } from "../../types/post";
|
||||
|
||||
const types = [
|
||||
"all",
|
||||
"image",
|
||||
"video",
|
||||
"music",
|
||||
"ppt",
|
||||
"pdf",
|
||||
"text",
|
||||
"link",
|
||||
"archive",
|
||||
] as const;
|
||||
const resourceLangCodes = ["", ...LANG_OPTIONS.map((x) => x.code)] as const;
|
||||
import { Navigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
export function SearchPage() {
|
||||
const { t, lang } = useI18n();
|
||||
const [sp, setSp] = useSearchParams();
|
||||
const q = sp.get("q") || "";
|
||||
const type = sp.get("type") || "all";
|
||||
const resourceLang = sp.get("language") || "";
|
||||
|
||||
const [items, setItems] = useState<Post[]>([]);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
const query = useMemo(() => {
|
||||
const p = new URLSearchParams();
|
||||
p.set("lang", langQuery(lang));
|
||||
p.set("limit", "50");
|
||||
p.set("q", q);
|
||||
if (type && type !== "all") p.set("type", type);
|
||||
if (resourceLang) p.set("language", sourceLanguageQuery(resourceLang));
|
||||
return p.toString();
|
||||
}, [lang, q, type, resourceLang]);
|
||||
|
||||
useEffect(() => {
|
||||
setErr(null);
|
||||
if (!q.trim()) {
|
||||
setItems([]);
|
||||
return;
|
||||
}
|
||||
postJSON("/api/search-log", { query: q }).catch(() => {});
|
||||
getJSON<{ items: Post[] }>(`/api/posts/search?${query}`)
|
||||
.then((r) => setItems(itemsOrEmpty(r.items)))
|
||||
.catch((e) => setErr(String(e)));
|
||||
}, [query, q]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-[640px] space-y-4 px-3">
|
||||
<h1 className="text-2xl font-bold">
|
||||
{t("search")}: {q || "—"}
|
||||
</h1>
|
||||
|
||||
{q ? (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{types.map((tp) => (
|
||||
<button
|
||||
key={tp}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const n = new URLSearchParams(sp);
|
||||
if (tp === "all") n.delete("type");
|
||||
else n.set("type", tp);
|
||||
setSp(n, { replace: true });
|
||||
}}
|
||||
className={`rounded-full border px-3 py-1 text-xs transition ${
|
||||
type === tp || (tp === "all" && !sp.get("type"))
|
||||
? "border-ark-gold text-ark-gold2"
|
||||
: "border-ark-line text-neutral-300"
|
||||
}`}
|
||||
>
|
||||
{typeFilterLabel(t, tp)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{resourceLangCodes.map((code) => (
|
||||
<button
|
||||
key={code || "all"}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const n = new URLSearchParams(sp);
|
||||
if (!code) n.delete("language");
|
||||
else n.set("language", code);
|
||||
setSp(n, { replace: true });
|
||||
}}
|
||||
className={`rounded-full border px-3 py-1 text-xs transition ${
|
||||
(code === "" && !resourceLang) || resourceLang === code
|
||||
? "border-ark-gold text-ark-gold2"
|
||||
: "border-ark-line text-neutral-300"
|
||||
}`}
|
||||
>
|
||||
{languageLabel(t, code)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{err ? <div className="text-red-300">{err}</div> : null}
|
||||
{!q ? <p className="text-neutral-400">{t("noResults")}</p> : null}
|
||||
{q && items.length === 0 && !err ? (
|
||||
<p className="text-neutral-400">{t("noResults")}</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{items.map((post) => (
|
||||
<MessageBubble key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const [sp] = useSearchParams();
|
||||
const query = sp.toString();
|
||||
return <Navigate to={`/browse${query ? `?${query}` : ""}`} replace />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user