mirror of
https://github.com/nix-community/noogle.git
synced 2024-09-11 09:15:53 +03:00
improve: position & filter navigation
This commit is contained in:
parent
4ebffe7cf3
commit
f954db4fbb
@ -1,16 +1,16 @@
|
||||
import { HighlightBaseline } from "@/components/HighlightBaseline";
|
||||
import { ShareButton } from "@/components/ShareButton";
|
||||
import { BackButton } from "@/components/BackButton";
|
||||
import { Doc, FilePosition, data } from "@/models/data";
|
||||
import { Doc, data } from "@/models/data";
|
||||
import { getPrimopDescription } from "@/models/primop";
|
||||
import { extractHeadings, mdxRenderOptions } from "@/utils";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { Box, Button, Divider, Typography, Link, Chip } from "@mui/material";
|
||||
import { Box, Divider, Typography, Link, Chip } from "@mui/material";
|
||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||
import { findType, interpretType } from "@/models/nix";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import { FilterProvider } from "@/components/layout/filterContext";
|
||||
import { Suspense } from "react";
|
||||
import { PositionLink } from "@/components/PositionLink";
|
||||
import { SearchNav } from "@/components/SearchNav";
|
||||
|
||||
// Important the key ("path") in the returned dict MUST match the dynamic path segment ([...path])
|
||||
export async function generateStaticParams(): Promise<{ path: string[] }[]> {
|
||||
@ -22,17 +22,6 @@ export async function generateStaticParams(): Promise<{ path: string[] }[]> {
|
||||
return paths;
|
||||
}
|
||||
|
||||
const getSourcePosition = (baseUrl: string, position: FilePosition): string => {
|
||||
const filename = position?.file.split("/").slice(4).join("/");
|
||||
const line = position?.line;
|
||||
const column = position?.column;
|
||||
let res = `${baseUrl}`;
|
||||
if (filename && line && column) {
|
||||
res += `/${filename}#L${line}:C${column}`;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
interface TocProps {
|
||||
mdxSource: string;
|
||||
}
|
||||
@ -58,6 +47,11 @@ const Toc = async (props: TocProps) => {
|
||||
>
|
||||
<Typography variant="subtitle1">On this page</Typography>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
{!headings.length && (
|
||||
<Typography variant="body2" sx={{ color: "text.secondary", py: 1 }}>
|
||||
No content
|
||||
</Typography>
|
||||
)}
|
||||
{headings.map((h, idx) => (
|
||||
<Link key={idx} href={`#${h.id}`}>
|
||||
<Typography
|
||||
@ -79,6 +73,51 @@ const Toc = async (props: TocProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: figure out why this causes hydration errors
|
||||
const MDX = ({ source }: { source: string }) => (
|
||||
<MDXRemote
|
||||
options={{
|
||||
parseFrontmatter: true,
|
||||
mdxOptions: mdxRenderOptions,
|
||||
}}
|
||||
source={source}
|
||||
components={{
|
||||
a: (p) => (
|
||||
// @ts-ignore
|
||||
<Box
|
||||
sx={{
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
}}
|
||||
component="a"
|
||||
{...p}
|
||||
/>
|
||||
),
|
||||
// @ts-ignore
|
||||
h1: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="h3" component={"h2"} {...p} />
|
||||
),
|
||||
// @ts-ignore
|
||||
h2: (p) => <Typography variant="h4" component={"h3"} {...p} />,
|
||||
// @ts-ignore
|
||||
h3: (p) => <Typography variant="h5" component={"h4"} {...p} />,
|
||||
// @ts-ignore
|
||||
h4: (p) => <Typography variant="h6" component={"h5"} {...p} />,
|
||||
// @ts-ignore
|
||||
h5: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="subtitle1" component={"h6"} {...p} />
|
||||
),
|
||||
// @ts-ignore
|
||||
h6: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="subtitle2" component={"h6"} {...p} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// Multiple versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
export default async function Page(props: { params: { path: string[] } }) {
|
||||
@ -95,20 +134,7 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
signature
|
||||
);
|
||||
|
||||
const position =
|
||||
meta?.content_meta?.position ||
|
||||
meta?.attr_position ||
|
||||
(meta?.count_applied == 0 && meta?.lambda_position);
|
||||
|
||||
const raw_position =
|
||||
meta?.content_meta?.position ||
|
||||
meta?.attr_position ||
|
||||
meta?.lambda_position;
|
||||
|
||||
const source =
|
||||
meta?.is_primop && meta?.primop_meta
|
||||
? getPrimopDescription(meta.primop_meta) + mdxSource
|
||||
: mdxSource;
|
||||
const source = mdxSource;
|
||||
// Skip generating this builtin.
|
||||
// It is internal information of noogle.
|
||||
if (meta?.title === "builtins.lambdaMeta") {
|
||||
@ -117,7 +143,7 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toc mdxSource={mdxSource} />
|
||||
<Toc mdxSource={source} />
|
||||
<Box
|
||||
component="main"
|
||||
data-pagefind-body
|
||||
@ -146,7 +172,7 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={<BackButton />}>
|
||||
<Suspense fallback={""}>
|
||||
<FilterProvider>
|
||||
<BackButton />
|
||||
</FilterProvider>
|
||||
@ -177,6 +203,7 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
<ShareButton />
|
||||
</Box>
|
||||
<Divider flexItem sx={{ mt: 2 }} />
|
||||
|
||||
<Box sx={{ display: "block" }}>
|
||||
{argTypes.map((t, i) => (
|
||||
<meta key={i} data-pagefind-filter={`from:${t}`} />
|
||||
@ -195,147 +222,20 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
No documentation found yet.
|
||||
</Typography>
|
||||
|
||||
{!position && (
|
||||
<div data-pagefind-ignore="all">
|
||||
<Typography variant="h5" sx={{ pt: 2 }}>
|
||||
{"Noogle's tip"}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
component={"span"}
|
||||
gutterBottom
|
||||
sx={{ py: 2 }}
|
||||
>
|
||||
<div>
|
||||
Position of the source could not be detected
|
||||
automatically.
|
||||
</div>
|
||||
<div>
|
||||
Sometimes the documentation is missing or the extraction
|
||||
of the documentation fails. In these cases, it is
|
||||
advisable to look for the recognized position in the
|
||||
source code
|
||||
</div>
|
||||
{raw_position && (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={getSourcePosition(
|
||||
"https://github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments",
|
||||
raw_position
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
data-pagefind-ignore="all"
|
||||
variant="text"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
my: 1,
|
||||
placeSelf: "start",
|
||||
}}
|
||||
startIcon={<LinkIcon />}
|
||||
>
|
||||
Original/underlying function
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<div>You may find further instructions there</div>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
{position && (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={getSourcePosition(
|
||||
"https://github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments",
|
||||
position
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
data-pagefind-ignore="all"
|
||||
variant="text"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
my: 1,
|
||||
placeSelf: "start",
|
||||
}}
|
||||
startIcon={<LinkIcon />}
|
||||
>
|
||||
Original/underlying function
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: "text.secondary", py: 2 }}
|
||||
>
|
||||
Contribute now!
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<MDXRemote
|
||||
options={{
|
||||
parseFrontmatter: true,
|
||||
mdxOptions: mdxRenderOptions,
|
||||
}}
|
||||
source={source}
|
||||
components={{
|
||||
a: (p) => (
|
||||
// @ts-ignore
|
||||
<Box
|
||||
sx={{
|
||||
color: "inherit",
|
||||
textDecoration: "none",
|
||||
}}
|
||||
component="a"
|
||||
{...p}
|
||||
/>
|
||||
),
|
||||
// @ts-ignore
|
||||
h1: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="h3" component={"h2"} {...p} />
|
||||
),
|
||||
// @ts-ignore
|
||||
h2: (p) => <Typography variant="h4" component={"h3"} {...p} />,
|
||||
// @ts-ignore
|
||||
h3: (p) => <Typography variant="h5" component={"h4"} {...p} />,
|
||||
// @ts-ignore
|
||||
h4: (p) => <Typography variant="h6" component={"h5"} {...p} />,
|
||||
// @ts-ignore
|
||||
h5: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="subtitle1" component={"h6"} {...p} />
|
||||
),
|
||||
// @ts-ignore
|
||||
h6: (p) => (
|
||||
// @ts-ignore
|
||||
<Typography variant="subtitle2" component={"h6"} {...p} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{position && (
|
||||
<div data-pagefind-ignore="all">
|
||||
{!source && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: "text.secondary", py: 2 }}
|
||||
>
|
||||
Contribute now!
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="subtitle2" sx={{ color: "text.secondary" }}>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={getSourcePosition(
|
||||
"https://github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments",
|
||||
position
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
sx={{ textTransform: "none", my: 1, placeSelf: "start" }}
|
||||
startIcon={<Edit />}
|
||||
>
|
||||
Edit source
|
||||
</Button>
|
||||
</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
{meta?.primop_meta && (
|
||||
<MDX source={getPrimopDescription(meta?.primop_meta)} />
|
||||
)}
|
||||
<Divider />
|
||||
<MDX source={source} />
|
||||
{meta && <PositionLink meta={meta} />}
|
||||
<div data-pagefind-ignore="all">
|
||||
{(!!meta?.aliases?.length || !!signature) && (
|
||||
<>
|
||||
@ -380,17 +280,17 @@ export default async function Page(props: { params: { path: string[] } }) {
|
||||
>
|
||||
Detected Type
|
||||
</Typography>
|
||||
<MDXRemote
|
||||
options={{
|
||||
mdxOptions: mdxRenderOptions,
|
||||
}}
|
||||
source={`\`\`\`haskell\n${signature.trim()}\n\`\`\`\n`}
|
||||
/>
|
||||
<MDX source={`\`\`\`haskell\n${signature.trim()}\n\`\`\`\n`} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
<Divider flexItem sx={{ mt: 2 }} />
|
||||
<Suspense fallback={""}>
|
||||
<FilterProvider>
|
||||
<SearchNav />
|
||||
</FilterProvider>
|
||||
</Suspense>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
@ -64,7 +64,6 @@ export const usePagefindSearch = (): PagefindHooks => {
|
||||
}, []);
|
||||
|
||||
// @ts-ignore
|
||||
console.log("usePagefind", { pagefind });
|
||||
return {
|
||||
// @ts-ignore
|
||||
search: pagefind?.search,
|
||||
|
@ -21,7 +21,8 @@ import { useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { PagefindResult, RawResult, usePagefindSearch } from "./Pagefind";
|
||||
import { Clear } from "@mui/icons-material";
|
||||
import { useFilter } from "./layout/filterContext";
|
||||
import { FilterOptions, useFilter } from "./layout/filterContext";
|
||||
import { useSessionStorage } from "usehooks-ts";
|
||||
|
||||
// import d from "./example.json";
|
||||
|
||||
@ -36,6 +37,11 @@ export function PagefindResults() {
|
||||
const params = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
const [, persistFilterOptions] = useSessionStorage<FilterOptions>(
|
||||
"currentFilterOptions",
|
||||
{}
|
||||
);
|
||||
|
||||
const query = useMemo(() => new URLSearchParams(params), [params]);
|
||||
|
||||
const page = +params.get("page")! || 1;
|
||||
@ -67,6 +73,11 @@ export function PagefindResults() {
|
||||
) => {
|
||||
query.set("limit", event.target.value);
|
||||
query.set("page", "1");
|
||||
persistFilterOptions((s) => ({
|
||||
...s,
|
||||
limit: +event.target.value,
|
||||
page: 1,
|
||||
}));
|
||||
router.push(`?${query.toString()}`);
|
||||
};
|
||||
|
||||
@ -81,7 +92,6 @@ export function PagefindResults() {
|
||||
const loadData = async () => {
|
||||
let items = await Promise.all(pageItems.map(async (r) => await r.data()));
|
||||
setItems(items);
|
||||
// setItems(d);
|
||||
};
|
||||
loadData();
|
||||
}, [pageItems]);
|
||||
@ -91,6 +101,10 @@ export function PagefindResults() {
|
||||
value: number
|
||||
) => {
|
||||
query.set("page", (value + 1).toString());
|
||||
persistFilterOptions((s) => ({
|
||||
...s,
|
||||
page: value + 1,
|
||||
}));
|
||||
router.push(`?${query.toString()}`);
|
||||
};
|
||||
|
||||
|
31
website/src/components/PositionInisghts.tsx
Normal file
31
website/src/components/PositionInisghts.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { DocMeta } from "@/models/data";
|
||||
import { Box, Button, Collapse, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
|
||||
export const PositionInsights = ({ meta }: { meta: DocMeta }) => {
|
||||
// const { attr_position, lambda_position, count_applied, content_meta } = meta;
|
||||
|
||||
const [open, setOpen] = useState(true);
|
||||
// const is_inherited =
|
||||
// JSON.stringify(content_meta?.path) !== JSON.stringify(meta.path);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ width: "100%", display: "flex" }}>
|
||||
<Button
|
||||
onClick={() => setOpen((s) => !s)}
|
||||
sx={{ ml: "auto" }}
|
||||
startIcon={<CodeIcon />}
|
||||
>
|
||||
Position Infos
|
||||
</Button>
|
||||
</Box>
|
||||
<Collapse in={open}>
|
||||
<Typography>is_inherited</Typography>
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
136
website/src/components/PositionLink.tsx
Normal file
136
website/src/components/PositionLink.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import { DocMeta, FilePosition } from "@/models/data";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
|
||||
const getSourcePosition = (baseUrl: string, position: FilePosition): string => {
|
||||
const filename = position?.file.split("/").slice(4).join("/");
|
||||
const line = position?.line;
|
||||
const column = position?.column;
|
||||
let res = `${baseUrl}`;
|
||||
if (filename && line && column) {
|
||||
res += `/${filename}#L${line}:C${column}`;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export const PositionLink = ({ meta }: { meta: DocMeta }) => {
|
||||
const { attr_position, lambda_position, count_applied, content_meta } = meta;
|
||||
|
||||
const contentPosition = content_meta?.position;
|
||||
const position = attr_position || lambda_position;
|
||||
|
||||
const is_inherited =
|
||||
JSON.stringify(content_meta?.path) !== JSON.stringify(meta.path);
|
||||
return (
|
||||
<div data-pagefind-ignore="all">
|
||||
{!position && (
|
||||
<Typography variant="subtitle1" sx={{ color: "text.secondary", pb: 2 }}>
|
||||
This function is not declared in a .nix file
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography variant="subtitle2" sx={{ color: "text.secondary" }}>
|
||||
{contentPosition && (
|
||||
<Link
|
||||
target="_blank"
|
||||
href={getSourcePosition(
|
||||
"https://github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments",
|
||||
contentPosition
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
sx={{ textTransform: "none", my: 1, placeSelf: "start" }}
|
||||
startIcon={<EditIcon />}
|
||||
>
|
||||
Edit source
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
{!contentPosition && position && (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={getSourcePosition(
|
||||
"https://github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments",
|
||||
position
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
data-pagefind-ignore="all"
|
||||
variant="text"
|
||||
sx={{
|
||||
textTransform: "none",
|
||||
my: 1,
|
||||
placeSelf: "start",
|
||||
}}
|
||||
startIcon={<LinkIcon />}
|
||||
>
|
||||
Underlying function
|
||||
</Button>
|
||||
</Link>
|
||||
{count_applied && count_applied > 0 && (
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ color: "text.secondary" }}
|
||||
>
|
||||
{`Applied ${count_applied} times`}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" sx={{ color: "text.secondary" }}>
|
||||
{contentPosition &&
|
||||
is_inherited &&
|
||||
`(${content_meta?.path?.join(".")})`}
|
||||
</Typography>
|
||||
</Box>
|
||||
{!contentPosition && (
|
||||
<>
|
||||
<Typography variant="h5" sx={{ pt: 2 }}>
|
||||
{"Contribute"}
|
||||
</Typography>
|
||||
<Typography variant="body1" gutterBottom sx={{ py: 2 }}>
|
||||
<Box>
|
||||
Sometimes documentation is missing or tooling support from noogle
|
||||
is missing.
|
||||
</Box>
|
||||
<List sx={{ width: "100%" }}>
|
||||
<ListItem>
|
||||
<Link href="/tutorials/documentation" target="_blank">
|
||||
<ListItemButton>
|
||||
<ListItemText
|
||||
primary="Write API documentation for this function"
|
||||
secondary="Learn how to write documentation"
|
||||
/>
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
{/* <Link href={"/tutorials/noogle"} target="_blank"> */}
|
||||
{/* <ListItemButton> */}
|
||||
<ListItemText
|
||||
primary="Improve position tracking"
|
||||
secondary="Contribute to Noogle"
|
||||
/>
|
||||
{/* </ListItemButton> */}
|
||||
{/* </Link> */}
|
||||
</ListItem>
|
||||
</List>
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
141
website/src/components/SearchNav.tsx
Normal file
141
website/src/components/SearchNav.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
import { ChevronLeft, ChevronRight } from "@mui/icons-material";
|
||||
import { Box, Button, Link, Typography } from "@mui/material";
|
||||
import { useSessionStorage } from "usehooks-ts";
|
||||
import { FilterOptions } from "./layout/filterContext";
|
||||
// import { useRouter } from "next/navigation";
|
||||
|
||||
// import d from "./example.json";
|
||||
import { PagefindResult, usePagefindSearch } from "./Pagefind";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export const SearchNav = () => {
|
||||
const [filterOptions] = useSessionStorage<FilterOptions | null>(
|
||||
"currentFilterOptions",
|
||||
null
|
||||
);
|
||||
if (!filterOptions) {
|
||||
return null;
|
||||
}
|
||||
return <Navigation filterOptions={filterOptions} />;
|
||||
};
|
||||
|
||||
// const prev = {
|
||||
// url: "/f/builtins/genList.html",
|
||||
// content:
|
||||
// "builtins.genList Primop. Takes 2 arguments. generator, length. Generate list of size length, with each element i equal to the value returned by generator i. For example, builtins.genList (x: x * x) 5 returns the list [ 0 1 4 9 16 ]. Noogle also knows. Aliases. lib.genList. lib.lists.genList. lib.strings.genList.",
|
||||
// word_count: 49,
|
||||
// filters: {
|
||||
// "type-to": ["Any"],
|
||||
// "type-from": ["Any"],
|
||||
// },
|
||||
// meta: {
|
||||
// title: "builtins.genList Primop.",
|
||||
// },
|
||||
// anchors: [],
|
||||
// weighted_locations: [],
|
||||
// locations: [],
|
||||
// raw_content:
|
||||
// "builtins.genList Primop. Takes 2 arguments. generator, length. Generate list of size length, with each element i equal to the value returned by generator i. For example, builtins.genList (x: x * x) 5 returns the list [ 0 1 4 9 16 ]. Noogle also knows. Aliases. lib.genList. lib.lists.genList. lib.strings.genList.",
|
||||
// raw_url: "/f/builtins/genList.html",
|
||||
// excerpt:
|
||||
// "builtins.genList Primop. Takes 2 arguments. generator, length. Generate list of size length, with each element i equal to the value returned by generator i. For example, builtins.genList (x: x *",
|
||||
// sub_results: [],
|
||||
// };
|
||||
// const next = prev;
|
||||
|
||||
export const Navigation = ({
|
||||
filterOptions,
|
||||
}: {
|
||||
filterOptions: FilterOptions;
|
||||
}) => {
|
||||
const { filter, input } = filterOptions;
|
||||
|
||||
const term = input || null;
|
||||
const from = filter?.from || null;
|
||||
const to = filter?.to || null;
|
||||
|
||||
const { search } = usePagefindSearch();
|
||||
const pathname = usePathname();
|
||||
|
||||
const [items, setItems] = useState<PagefindResult[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
console.log({ search, term, filters: { from, to } });
|
||||
if (search) {
|
||||
let raw =
|
||||
(await search(term, { filters: { from, to } }))?.results || [];
|
||||
|
||||
let items = await Promise.all(raw.map(async (r) => await r.data()));
|
||||
setItems(items);
|
||||
}
|
||||
};
|
||||
init();
|
||||
}, [term, from, to, search]);
|
||||
|
||||
// TODO: show skeleton
|
||||
const { prev, next } = useMemo(() => {
|
||||
if (items == null) {
|
||||
return {
|
||||
loading: true,
|
||||
prev: undefined,
|
||||
next: undefined,
|
||||
};
|
||||
}
|
||||
const currIdx = items.findIndex((item) => item.url.includes(pathname));
|
||||
return {
|
||||
prev: currIdx - 1 >= 0 ? items[currIdx - 1] : null,
|
||||
next: currIdx <= items.length ? items[currIdx + 1] : null,
|
||||
loading: false,
|
||||
};
|
||||
}, [pathname, items]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
{prev && (
|
||||
<Link
|
||||
href={prev.url}
|
||||
sx={{
|
||||
my: 2,
|
||||
py: 1,
|
||||
px: 2,
|
||||
display: "block",
|
||||
borderStyle: "solid",
|
||||
borderColor: "primary.main",
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
}}
|
||||
>
|
||||
<Button aria-label="Prev result" startIcon={<ChevronLeft />}>
|
||||
Previous
|
||||
</Button>
|
||||
<Typography variant="subtitle1">{prev.meta.title}</Typography>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{next && (
|
||||
<Link
|
||||
href={next.url}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
my: 2,
|
||||
py: 1,
|
||||
px: 2,
|
||||
display: "block",
|
||||
borderStyle: "solid",
|
||||
borderColor: "primary.main",
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
}}
|
||||
>
|
||||
<Button aria-label="Prev result" endIcon={<ChevronRight />}>
|
||||
Next
|
||||
</Button>
|
||||
<Typography variant="subtitle1">{next.meta.title}</Typography>
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -12,7 +12,7 @@ import {
|
||||
} from "react";
|
||||
import { useSessionStorage } from "usehooks-ts";
|
||||
|
||||
export const FilterContext = createContext<UseFilter>({} as UseFilter);
|
||||
export const FilterContext = createContext<UseFilter | null>(null);
|
||||
|
||||
export type UseFilter = {
|
||||
showFilter: boolean;
|
||||
@ -29,6 +29,8 @@ export type UseFilter = {
|
||||
export type FilterOptions = {
|
||||
input?: string;
|
||||
filter?: { from: string; to: string };
|
||||
page?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export const FilterProvider = ({ children }: { children: ReactNode }) => {
|
||||
@ -47,12 +49,11 @@ export const FilterProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [to, setTo] = useState(params.get("to") || "any");
|
||||
const [term, setTerm] = useState(params.get("term") || "");
|
||||
|
||||
const submit = ({ input, filter }: FilterOptions) => {
|
||||
const submit = ({ input, filter, page, limit }: FilterOptions) => {
|
||||
const _term = input !== undefined ? input : term;
|
||||
const _from = filter?.from || from;
|
||||
const _to = filter?.to || to;
|
||||
|
||||
console.log({ _term });
|
||||
if (_term && _term.trim() !== "") {
|
||||
query.set("term", _term);
|
||||
} else {
|
||||
@ -69,7 +70,14 @@ export const FilterProvider = ({ children }: { children: ReactNode }) => {
|
||||
} else {
|
||||
query.delete("to");
|
||||
}
|
||||
persistFilterOptions({ input: _term, filter: { from: _from, to: _to } });
|
||||
persistFilterOptions({
|
||||
input: _term,
|
||||
filter: { from: _from, to: _to },
|
||||
page: +params.get("page")! || undefined,
|
||||
limit: +params.get("limit")! || undefined,
|
||||
});
|
||||
if (page) query.set("page", page.toString());
|
||||
if (limit) query.set("page", limit.toString());
|
||||
router.push(`/q?${query.toString()}`);
|
||||
};
|
||||
return (
|
||||
@ -91,4 +99,10 @@ export const FilterProvider = ({ children }: { children: ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useFilter = () => useContext(FilterContext);
|
||||
export const useFilter = () => {
|
||||
const filter = useContext(FilterContext);
|
||||
if (filter === null) {
|
||||
throw "UseFilter used outside of FilterContext!";
|
||||
}
|
||||
return filter;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { Autocomplete, Badge, Divider, Input } from "@mui/material";
|
||||
import { Autocomplete, Badge, Divider, TextField } from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import React from "react";
|
||||
@ -9,6 +9,12 @@ import { data } from "@/models/data";
|
||||
import TuneIcon from "@mui/icons-material/Tune";
|
||||
import { useFilter } from "../layout/filterContext";
|
||||
|
||||
// import dynamic from "next/dynamic";
|
||||
|
||||
// const data = dynamic(() => import("@/models/data"), {
|
||||
// loading: () => <p>Loading...</p>,
|
||||
// });
|
||||
|
||||
export interface SearchInputProps {
|
||||
placeholder: string;
|
||||
}
|
||||
@ -80,28 +86,28 @@ export function SearchInput(props: SearchInputProps) {
|
||||
} as React.ChangeEvent<HTMLInputElement>);
|
||||
}}
|
||||
value={term}
|
||||
renderInput={(params) => {
|
||||
return (
|
||||
<Input
|
||||
disableUnderline
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
ml: 1,
|
||||
flex: 1,
|
||||
backgroundColor: "paper.main",
|
||||
px: 1,
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
value={term}
|
||||
onChange={(e) => handleType(e)}
|
||||
placeholder={placeholder}
|
||||
{...params}
|
||||
{...params.InputProps}
|
||||
endAdornment={undefined}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
type: "search",
|
||||
disableUnderline: true,
|
||||
endAdornment: undefined,
|
||||
placeholder: placeholder,
|
||||
}}
|
||||
value={term}
|
||||
onChange={(e) => handleType(e)}
|
||||
variant="standard"
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "paper.main",
|
||||
px: 1,
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<IconButton
|
||||
aria-haspopup="true"
|
||||
|
Loading…
Reference in New Issue
Block a user