feat: enhance layout of search
This commit is contained in:
@ -34,6 +34,16 @@ export const menus: Record<string, Menu> = {
|
||||
return !getCookie("session_cookie");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
icon: "IconSearch",
|
||||
cb: () => {
|
||||
window.location.href += "?q=";
|
||||
},
|
||||
visible: () => {
|
||||
return !!getCookie("session_cookie") && window.location.search === "";
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Logout",
|
||||
icon: "IconLogout",
|
||||
|
@ -45,6 +45,6 @@ export const addMovieInfos: MenuEntry = {
|
||||
visible: () => {
|
||||
const loc = globalThis["location"];
|
||||
if (!getCookie("session_cookie")) return false;
|
||||
return loc?.pathname?.includes("movie");
|
||||
return loc?.pathname?.includes("movie") && !loc.pathname.endsWith("movies");
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +1,15 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import useDebouncedCallback from "@lib/hooks/useDebouncedCallback.ts";
|
||||
import { IconSearch } from "@components/icons.tsx";
|
||||
import { IconGhost, IconLoader2, IconSearch } from "@components/icons.tsx";
|
||||
import { useEventListener } from "@lib/hooks/useEventListener.ts";
|
||||
import { SearchResponse } from "https://raw.githubusercontent.com/bradenmacdonald/typesense-deno/main/src/Typesense/Documents.ts";
|
||||
import { Recipe } from "@lib/resource/recipes.ts";
|
||||
import { Movie } from "@lib/resource/movies.ts";
|
||||
import { Article } from "@lib/resource/articles.ts";
|
||||
import { useTraceUpdate } from "@lib/hooks/useTraceProps.ts";
|
||||
import { SearchResult } from "@lib/types.ts";
|
||||
import { resources } from "@lib/resources.ts";
|
||||
import { isLocalImage } from "@lib/string.ts";
|
||||
|
||||
export const RedirectSearchHandler = () => {
|
||||
useEventListener("keydown", (e: KeyboardEvent) => {
|
||||
@ -18,11 +26,63 @@ export const RedirectSearchHandler = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const SearchResultImage = ({ src }: { src: string }) => {
|
||||
const imageSrc = isLocalImage(src)
|
||||
? `/api/images?image=${src}&width=50&height=50`
|
||||
: src;
|
||||
|
||||
return (
|
||||
<img
|
||||
class="object-cover w-12 h-12 rounded-full"
|
||||
src={imageSrc}
|
||||
alt="preview image"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchResultItem = (
|
||||
{ item, showEmoji = false }: {
|
||||
item: NonNullable<SearchResult["hits"]>[number];
|
||||
showEmoji?: boolean;
|
||||
},
|
||||
) => {
|
||||
const doc = item.document;
|
||||
const resourceType = resources[doc.type];
|
||||
const href = (resourceType) ? `${resourceType.link}/${doc.id}` : "";
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
class="p-2 text-white flex gap-4 items-center rounded-2xl hover:bg-gray-700"
|
||||
>
|
||||
{doc?.image && <SearchResultImage src={doc.image} />}
|
||||
{`${showEmoji && resourceType ? resourceType.emoji : ""}`} {doc?.name}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchResultList = (
|
||||
{ result, showEmoji }: { result: SearchResult; showEmoji?: boolean },
|
||||
) => {
|
||||
return (
|
||||
<div class="mt-4">
|
||||
{result?.hits
|
||||
? (
|
||||
<div class="flex flex-col gap-4">
|
||||
{result.hits.map((hit) => (
|
||||
<SearchResultItem item={hit} showEmoji={showEmoji} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
: <div style={{ color: "#818181" }}>No Results</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchComponent = (
|
||||
{ q, type }: { q: string; type?: string },
|
||||
) => {
|
||||
const [searchQuery, setSearchQuery] = useState(q);
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [data, setData] = useState<SearchResult>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
if ("history" in globalThis) {
|
||||
@ -38,8 +98,8 @@ const SearchComponent = (
|
||||
setIsLoading(true);
|
||||
const fetchUrl = new URL(window.location.href);
|
||||
fetchUrl.pathname = "/api/resources";
|
||||
if (searchQuery) {
|
||||
fetchUrl.searchParams.set("q", encodeURIComponent(searchQuery));
|
||||
if (query) {
|
||||
fetchUrl.searchParams.set("q", encodeURIComponent(query));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -66,29 +126,48 @@ const SearchComponent = (
|
||||
|
||||
const handleInputChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
setSearchQuery(target.value);
|
||||
debouncedFetchData(target.value); // Call the debounced fetch function with the updated search query
|
||||
if (target.value !== searchQuery) {
|
||||
setSearchQuery(target.value);
|
||||
debouncedFetchData(target.value); // Call the debounced fetch function with the updated search query
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
debouncedFetchData(q);
|
||||
}, []);
|
||||
|
||||
console.log({ data, isLoading });
|
||||
return (
|
||||
<div class="max-w-full">
|
||||
<div class="bg-white flex items-center gap-1 w-full py-3 px-4 pr-12 rounded-xl border border-gray-300 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-600 focus:border-blue-600">
|
||||
<IconSearch class="w-4 h-4" />
|
||||
<div class="mt-2">
|
||||
<div
|
||||
class="flex items-center gap-1 rounded-xl w-full shadow-2xl"
|
||||
style={{ background: "#2B2930", color: "#818181" }}
|
||||
>
|
||||
{isLoading
|
||||
? <IconLoader2 class="w-4 h-4 ml-4 mr-2 animate-spin" />
|
||||
: <IconSearch class="w-4 h-4 ml-4 mr-2" />}
|
||||
<input
|
||||
type="text"
|
||||
class=""
|
||||
style={{ fontSize: "1.2em" }}
|
||||
class="bg-transparent py-3 w-full"
|
||||
ref={inputRef}
|
||||
value={searchQuery}
|
||||
onInput={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{isLoading ? <div>Loading...</div> : (
|
||||
<div>
|
||||
{data.map((d) => (
|
||||
<pre class="text-white">{JSON.stringify(d.document, null, 2)}</pre>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{data?.hits?.length && !isLoading
|
||||
? <SearchResultList showEmoji={!type} result={data} />
|
||||
: isLoading
|
||||
? <div />
|
||||
: (
|
||||
<div
|
||||
class="flex items-center gap-2 p-2 my-4 mx-3"
|
||||
style={{ color: "#818181" }}
|
||||
>
|
||||
<IconGhost class="animate-hover" />
|
||||
No Results
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user