search articles

This commit is contained in:
Sergey Yarkov 2023-02-21 22:39:31 +03:00
parent 25c37af405
commit 87ece33516
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
8 changed files with 84 additions and 88 deletions

View File

@ -1,75 +1,72 @@
import type { Component } from "solid-js"; import type { Component } from "solid-js";
import { createSignal, createEffect, For } from "solid-js"; import { createSignal, createEffect, For } from "solid-js";
import { search, selectedTags } from "@stores/searchStore"; import { search, selectedTags } from "@stores/searchStore";
import SearchInput from "./SearchInput";
import TagsList from "./TagsList";
import ArticleItem from "@components/ArticleItem"; import ArticleItem from "@components/ArticleItem";
import "./ArticlesList.scss"; import "./ArticlesList.scss";
type UiStringsType = { "articles.empty": string }; type UiStringsType = { "articles.empty": string; "input.search": string };
type ArticleType = { type ArticlesType = Array<{
id: string; id: string;
title: string; title: string;
tags: string[];
pubDate: Date; pubDate: Date;
}; }>;
type ArticlesType = Record<string, ArticleType[]>; type ArticlesBlockType = Record<string, ArticlesType>;
type ArticlesListProps = { type ArticlesListProps = {
data: { articles: ArticlesType; pageLang: string }; articles: ArticlesBlockType;
tags: string[];
pageLang: string;
i18n: UiStringsType; i18n: UiStringsType;
}; };
const ArticlesList: Component<ArticlesListProps> = (props) => { const ArticlesList: Component<ArticlesListProps> = (props) => {
const [articles, setArticles] = createSignal<ArticlesType>(props.data.articles); const [articles, setArticles] = createSignal<ArticlesBlockType>(props.articles);
/** const isEmpty = () => Object.keys(articles()).length === 0;
* Sort articles by year in descending order
*/ const sortYears = (articles: ArticlesBlockType) => {
const sortYears = (articles: ArticlesType) => {
return Object.keys(articles) return Object.keys(articles)
.map(Number) .map(Number)
.sort((a, b) => b - a); .sort((a, b) => b - a);
}; };
const isEmpty = () => Object.keys(articles()).length === 0; const searchArticles = ({
const getLength = () => Object.values(articles()).flat().length; params: { search, tags },
}: {
params: { search: string; tags?: string[] };
}) => {
const filter = (cb: (year: string) => ArticlesType) => {
return Object.fromEntries(
Object.keys(props.articles)
.map((year) => [year, cb(year)])
.filter((articles) => articles[1].length !== 0)
);
};
createEffect(() => { return filter((year) => {
// const filtered = Object.values(articles()).flat(1) let filtered = props.articles[year].filter((a) =>
a.title.toLocaleLowerCase().includes(search)
);
if (tags && tags.length > 0) {
filtered = filtered.filter((a) => tags.some((t) => a.tags.includes(t)));
}
return filtered;
}); });
};
createEffect(() => { createEffect(() => {
let filtered = props.data.articles; setArticles(searchArticles({ params: { search: search(), tags: selectedTags() } }));
/**
* Find articles by title
*/
if (search()) {
// filtered = findArticlesBySearch(search().trim(), filtered);
// setArticles(filtered);
console.log(search());
}
/**
* Find articles by selected tags
*/
if (selectedTags().length !== 0) {
// filtered = findArticlesByTags(selectedTags(), filtered);
// setArticles(filtered);
return;
}
/**
* Set filtered variable as default value
*/
if (!search() && selectedTags().length === 0) {
filtered = props.data.articles;
setArticles(filtered);
}
}); });
return ( return (
<>
<SearchInput i18n={{ "input.search": props.i18n["input.search"] }} />
<TagsList data={props.tags} />
<div itemscope itemtype="http://schema.org/Blog" class="articles-list"> <div itemscope itemtype="http://schema.org/Blog" class="articles-list">
{!isEmpty() ? ( {!isEmpty() ? (
<For each={sortYears(articles())}> <For each={sortYears(articles())}>
@ -77,12 +74,12 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
<div class="articles-list__wrapper"> <div class="articles-list__wrapper">
<h2>{year}</h2> <h2>{year}</h2>
<For each={articles()[year]}> <For each={articles()[year]}>
{(a) => ( {({ id, title, pubDate }) => (
<ArticleItem <ArticleItem
id={a.id} id={id}
title={a.title} title={title}
pubDate={a.pubDate} pubDate={pubDate}
pageLang={props.data.pageLang} pageLang={props.pageLang}
/> />
)} )}
</For> </For>
@ -95,6 +92,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
</div> </div>
)} )}
</div> </div>
</>
); );
}; };

View File

@ -21,7 +21,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
* Update state and set url param * Update state and set url param
*/ */
const onSearch: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => { const onSearch: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => {
const value = e.currentTarget.value; const value = e.currentTarget.value.trim().toLocaleLowerCase();
if (value.trim().length === 0) { if (value.trim().length === 0) {
resetSearch(); resetSearch();
@ -31,8 +31,6 @@ const SearchInput: Component<SearchInputProps> = (props) => {
const url = utils.setParamToUrl("search", value.trim()); const url = utils.setParamToUrl("search", value.trim());
history.replaceState(null, "", `${location.pathname}?${url}`); history.replaceState(null, "", `${location.pathname}?${url}`);
setSearch(value); setSearch(value);
// TODO: search articles
}; };
/** /**

View File

@ -6,7 +6,7 @@ import * as utils from "@utils/history";
type TagsListProps = { data: string[] }; type TagsListProps = { data: string[] };
const TagsList: Component<TagsListProps> = (props) => { const TagsList: Component<TagsListProps> = (props) => {
const isTagSelected = (tag: string) => selectedTags().includes(tag.toLocaleLowerCase()); const isTagSelected = (tag: string) => selectedTags().includes(tag);
/** /**
* Reset signal state and `tags` property in history state * Reset signal state and `tags` property in history state
@ -21,6 +21,8 @@ const TagsList: Component<TagsListProps> = (props) => {
const onSelect: JSX.EventHandler<HTMLLIElement, PointerEvent> = (e) => { const onSelect: JSX.EventHandler<HTMLLIElement, PointerEvent> = (e) => {
const tag = e.target.getAttribute("data-tag"); const tag = e.target.getAttribute("data-tag");
if (!tag) return;
/** /**
* Delete tag from selected tags if already selected * Delete tag from selected tags if already selected
* and update param in url * and update param in url
@ -68,10 +70,10 @@ const TagsList: Component<TagsListProps> = (props) => {
const tags: string[] = []; const tags: string[] = [];
for (const tag of props.data) { for (const tag of props.data) {
const isTagExist = param.includes(tag.toLocaleLowerCase()); const isTagExist = param.includes(tag);
if (isTagExist) { if (isTagExist) {
tags.push(tag.toLocaleLowerCase()); tags.push(tag);
} }
} }
@ -83,12 +85,7 @@ const TagsList: Component<TagsListProps> = (props) => {
<For <For
each={props.data} each={props.data}
children={(tag) => ( children={(tag) => (
<li <li onClick={onSelect} class="btn-link" data-tag={tag} data-active={isTagSelected(tag)}>
onClick={onSelect}
class="btn-link"
data-tag={tag.toLocaleLowerCase()}
data-active={isTagSelected(tag)}
>
{tag} {tag}
</li> </li>
)} )}

View File

@ -5,8 +5,6 @@ import * as utils from "@utils/index";
import PageLayout from "@layouts/default.astro"; import PageLayout from "@layouts/default.astro";
import PageHeading from "@components/PageHeading.astro"; import PageHeading from "@components/PageHeading.astro";
import ArticlesList from "@root/components/ArticlesList"; import ArticlesList from "@root/components/ArticlesList";
import SearchInput from "@components/SearchInput";
import TagsList from "@components/TagsList";
const t = useTranslation(Astro); const t = useTranslation(Astro);
const pageLang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
@ -24,12 +22,12 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
> >
<PageHeading heading={t("pageTitle.articles")} description={t("pageDescription.articles")} /> <PageHeading heading={t("pageTitle.articles")} description={t("pageDescription.articles")} />
<section> <section>
<SearchInput i18n={{ "input.search": t("input.search") }} client:load />
<TagsList data={tags} client:load />
<ArticlesList <ArticlesList
client:load client:load
i18n={{ "articles.empty": t("articles.empty") }} i18n={{ "articles.empty": t("articles.empty"), "input.search": t("input.search") }}
data={{ articles: utils.formatToArticleBlocks(filtered), pageLang }} articles={utils.formatToArticleBlocks(filtered)}
tags={tags}
pageLang={pageLang}
/> />
</section> </section>
</PageLayout> </PageLayout>

View File

@ -49,7 +49,12 @@ export function formatToArticleBlocks(articles: CollectionEntry<"blog">[]) {
articles.forEach((a) => { articles.forEach((a) => {
const year = a.data.pubDate.getFullYear(); const year = a.data.pubDate.getFullYear();
if (!blocks[year]) blocks[year] = []; if (!blocks[year]) blocks[year] = [];
blocks[year].push({ id: a.id, title: a.data.title, pubDate: a.data.pubDate }); blocks[year].push({
id: a.id,
title: a.data.title,
tags: a.data.tags,
pubDate: a.data.pubDate,
});
}); });
return blocks; return blocks;
} }