Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/components/ReferenceDirectory/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
import type { ReferenceDocContentItem } from "@/src/content/types";
import flask from "@src/content/ui/images/icons/flask.svg?raw";
import warning from "@src/content/ui/images/icons/warning.svg?raw";

type ReferenceDirectoryEntry = ReferenceDocContentItem & {
data: {
path: string;
title: string;
description: string;
beta?: boolean;
deprecated?: boolean;
};
};

interface Props {
categoryData: {
name: string;
subcats: {
name: string;
entry?: ReferenceDirectoryEntry;
entries: ReferenceDirectoryEntry[];
}[];
}[];
}

const { categoryData } = Astro.props;

const getOneLineDescription = (description: string): string => {
const firstParagraphRegex = /^<p>(.*?)<\/p>/;
let [oneLineDescription] =
description.replace(/\n/g, " ").trim().match(firstParagraphRegex) ?? [];

if (!oneLineDescription && description) oneLineDescription = description;

return (
oneLineDescription
?.replace(/^<p>|<\/p>$/g, "")
.replace(/<\/?code>/g, "")
.replace(/<var>(\d+?)<sup>(\d+?)<\/sup><\/var>/g, "$1^$2")
.replace(/<a href=".*?">|<\/a>/g, "")
.split(/\.\s|\?\s|!\s|।\s|。/)[0] ?? ""
);
};
---
<aside class="-top-[75px] mx-5 min-h-[50vh] md:mx-lg">
<nav>
{categoryData.map((category) => (
<section id={category.name} key={category.name} aria-labelledby={`heading-${category.name}`}>
<h2>{category.name}</h2>

{category.subcats.map((subcat) => (
<div key={subcat.name}>
{subcat.name && <h3 id={subcat.name}>{subcat.name}</h3>}

<div class="content-grid">
{subcat.entries.map((entry) => (
<div class="col-span-3 w-full overflow-hidden" key={entry.id}>
<a
href={`/reference/${entry.data.path}/`}
class="group hover:no-underline"
aria-label={entry.data.title}
>
<span class="text-body-mono group-hover:underline">
{entry.data.beta && (
<span
class="inline-block h-[16px] w-[16px]"
set:html={flask}
/>
)}
{entry.data.deprecated && (
<span
class="inline-block h-[16px] w-[16px]"
set:html={warning}
/>
)}
<span set:html={entry.data.title} />
</span>

<p class="mt-1 text-sm">
{getOneLineDescription(entry.data.description)}
</p>
</a>
</div>
))}
</div>
</div>
))}
</section>
))}
</nav>
</aside>
195 changes: 3 additions & 192 deletions src/components/ReferenceDirectoryWithFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -1,202 +1,17 @@
import type { ReferenceDocContentItem } from "@/src/content/types";
import { useMemo, useRef, useState } from "preact/hooks";
import { useRef, useState } from "preact/hooks";
import { type JSX } from "preact";
import { Icon } from "../Icon";
import flask from "@src/content/ui/images/icons/flask.svg?raw";
import warning from "@src/content/ui/images/icons/warning.svg?raw";

type ReferenceDirectoryEntry = ReferenceDocContentItem & {
data: {
path: string;
title: string;
description: string;
};
};

type FilteredCategoryData = {
name: string;
subcats: {
name: string;
entries: ReferenceDirectoryEntry[];
}[];
};

type ReferenceDirectoryWithFilterProps = {
categoryData: {
name: string;
subcats: {
name: string;
entry?: ReferenceDirectoryEntry;
entries: ReferenceDirectoryEntry[];
}[];
}[];
uiTranslations: { [key: string]: string };
};

/**
* Convert Reference description to one-line description
* @param description String description
* @returns One-line description
*/
const getOneLineDescription = (description: string): string => {
// Matches first paragraph tag, remove HTML tags, then trim to first fullstop
const firstParagraphRegex = /^<p>(.*?)<\/p>/;
let [oneLineDescription] =
description.replace(/\n/g, " ").trim().match(firstParagraphRegex) ?? [];

if (!oneLineDescription && description) {
oneLineDescription = description;
}

if (oneLineDescription) {
oneLineDescription = oneLineDescription
.replace(/^<p>|<\/p>$/g, "")
.replace(/<\/?code>/g, "")
.replace(/<var>(\d+?)<sup>(\d+?)<\/sup><\/var>/g, "$1^$2")
.replace(/<a href=".*?">|<\/a>/g, "")
.split(/\.\s|\?\s|!\s|।\s|。/)[0];
}

return oneLineDescription ?? "";
};

export const ReferenceDirectoryWithFilter = ({
categoryData,
uiTranslations,
}: ReferenceDirectoryWithFilterProps) => {
const [searchKeyword, setSearchKeyword] = useState("");
const inputRef = useRef<HTMLInputElement>(null);

const filteredEntries = useMemo(() => {
if (!searchKeyword) return categoryData;

return categoryData.reduce((acc: FilteredCategoryData[], category) => {
const filteredSubcats = category.subcats.reduce(
(subAcc: typeof category.subcats, subcat) => {
const filteredEntries = subcat.entries.filter((entry) =>
entry.data.title
.toLowerCase()
.includes(searchKeyword.toLowerCase()),
);
if (
subcat.entry &&
subcat.entry.data.title
.toLowerCase()
.includes(searchKeyword.toLowerCase())
) {
filteredEntries.push(subcat.entry);
}

if (filteredEntries.length > 0) {
subAcc.push({ ...subcat, entries: filteredEntries });
}
return subAcc;
},
[],
);

if (filteredSubcats.length > 0) {
acc.push({ ...category, subcats: filteredSubcats });
}
return acc;
}, []);
}, [categoryData, searchKeyword]);

const renderEntries = (entries: ReferenceDirectoryEntry[]) =>
entries.length === 0 ? null : (
<div class="content-grid">
{entries.map((entry) => (
<div class="col-span-3 w-full overflow-hidden" key={entry.id}>
<a
href={`/reference/${entry.data.path}/`}
class="group hover:no-underline"
aria-label={entry.data.title}
aria-describedby={`${entry.data.title}-description`}
>
<span class="text-body-mono group-hover:underline">
{entry.data.beta && (
<div
className="mb-[-2px] mr-2 inline-block h-[16px] w-[16px]"
dangerouslySetInnerHTML={{ __html: flask }}
/>
)}
{entry.data.deprecated && (
<div
className="mb-[-2px] mr-2 inline-block h-[16px] w-[16px]"
dangerouslySetInnerHTML={{ __html: warning }}
/>
)}
<span dangerouslySetInnerHTML={{ __html: entry.data.title }} />
</span>
<p
class="mt-1 text-sm"
id={`${entry.data.title}-description`}
>{`${getOneLineDescription(entry.data.description)}`}</p>
</a>
</div>
))}
</div>
);

const subcatShouldHaveHeading = (
subcat: { name: string },
category: { name: string },
) => {
return !(!subcat.name || !category.name);
};

const getSubcatHeading = (
subcat: { name: string; entry?: any },
category: { name: string },
) => {
if (!subcatShouldHaveHeading(subcat, category)) {
return null;
}

return (
<>
{subcat.entry ? (
<a
id={subcat.name}
href={`/reference/${category.name === "p5.sound" ? "p5.sound" : "p5"}/${subcat.name}/`}
>
<h3 className="m-0 py-gutter-md">{subcat.name}</h3>
</a>
) : (
<h3 className="m-0 py-gutter-md" id={subcat.name}>
{subcat.name}
</h3>
)}
</>
);
};

const renderCategoryData = () => {
if (filteredEntries.length === 0) {
return <div class="mt-lg">{uiTranslations["No Results"]}</div>;
}
return filteredEntries.map((category) => (
<section key={category.name}>
<h2
class={
subcatShouldHaveHeading(category.subcats[0], category)
? "mb-0"
: "mb-[var(--gutter-md)]"
}
id={category.name}
>
{category.name}
</h2>
{category.subcats.map((subcat) => (
<div key={subcat.name}>
{getSubcatHeading(subcat, category)}
{renderEntries(subcat.entries)}
</div>
))}
</section>
));
};

const clearInput = () => {
if (inputRef.current) {
inputRef.current.value = "";
Expand All @@ -207,7 +22,7 @@ export const ReferenceDirectoryWithFilter = ({
return (
<div>
<div class="h-0 overflow-visible">
<div class="content-grid-simple absolute -left-0 -right-0 -top-[60px] h-[75px] border-b border-sidebar-type-color bg-accent-color px-5 pb-lg md:px-lg ">
<div class="content-grid-simple absolute -left-0 -right-0 -top-[60px] h-[75px] border-b border-sidebar-type-color bg-accent-color px-5 pb-lg md:px-lg">
<div class="text-body col-span-2 flex w-full max-w-[750px] border-b border-accent-type-color text-accent-type-color">
<input
type="text"
Expand All @@ -217,13 +32,12 @@ export const ReferenceDirectoryWithFilter = ({
placeholder={uiTranslations["Filter by keyword"]}
onKeyUp={(e: JSX.TargetedKeyboardEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement;
setSearchKeyword(target?.value);
setSearchKeyword(target.value);
}}
/>
{searchKeyword.length > 0 && (
<button
type="reset"
class=""
onClick={clearInput}
aria-label="Clear search input"
>
Expand All @@ -233,9 +47,6 @@ export const ReferenceDirectoryWithFilter = ({
</div>
</div>
</div>
<div class="-top-[75px] mx-5 min-h-[50vh] md:mx-lg">
{renderCategoryData()}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/content/ui/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ exampleCategories:
Classes And Objects: "Classes And Objects"
Loading And Saving Data: "Loading And Saving Data"
Math And Physics: "Math And Physics"
Parallel Loading Promise: "Parallel Loading Promise"
referenceCategories:
modules:
Foundation: Foundation
Expand Down
18 changes: 17 additions & 1 deletion src/content/ui/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,23 @@ briefPageDescriptions:
People: Conoce al equipo de p5.js.

exampleCategories:
Featured: Destacado
Featured: "Destacado"
Shapes And Color: "Formas y Color"
Animation And Variables: "Animación y Variables"
Imported Media: "Medios Importados"
Input Elements: "Elementos de Entrada"
Transformation: "Transformación"
Calculating Values: "Cálculo de Valores"
Repetition: "Repetición"
Listing Data with Arrays: "Listar Datos con Arreglos"
Angles And Motion: "Ángulos y Movimiento"
Games: "Juegos"
3D: "3D"
Advanced Canvas Rendering: "Renderizado Avanzado de Canvas"
Classes And Objects: "Clases y Objetos"
Loading And Saving Data: "Cargar y Guardar Datos"
Math And Physics: "Matemáticas y Física"
Parallel Loading Promise: "Carga Paralela con Promise"

referenceCategories:
modules:
Expand Down
19 changes: 17 additions & 2 deletions src/content/ui/hi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,23 @@ briefPageDescriptions:
People: p5.js टीम को जानें।

exampleCategories:
Featured: प्रमुख

"Featured": "विशेष"
"Shapes And Color": "आकार और रंग"
"Animation And Variables": "एनीमेशन और चर"
"Imported Media": "आयातित मीडिया"
"Input Elements": "इनपुट तत्व"
"Transformation": "रूपांतरण"
"Calculating Values": "मानों की गणना"
"Repetition": "पुनरावृत्ति"
"Listing Data with Arrays": "एरे के साथ डेटा सूचीबद्ध करना"
"Angles And Motion": "कोण और गति"
"Games": "खेल"
"3D": "3D"
"Advanced Canvas Rendering": "उन्नत कैनवास रेंडरिंग"
"Classes And Objects": "क्लास और ऑब्जेक्ट्स"
"Loading And Saving Data": "डेटा लोड करना और सहेजना"
"Math And Physics": "गणित और भौतिकी"
"Parallel Loading Promise": "समानांतर लोडिंग प्रॉमिस"
referenceCategories:
modules:
Typography: टाइपोग्राफी
Expand Down
Loading
Loading