mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 10:29:21 +03:00
search articles
This commit is contained in:
parent
25c37af405
commit
87ece33516
@ -1,100 +1,98 @@
|
|||||||
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)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return filter((year) => {
|
||||||
|
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(() => {
|
||||||
// const filtered = Object.values(articles()).flat(1)
|
setArticles(searchArticles({ params: { search: search(), tags: selectedTags() } }));
|
||||||
});
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
let filtered = props.data.articles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 (
|
||||||
<div itemscope itemtype="http://schema.org/Blog" class="articles-list">
|
<>
|
||||||
{!isEmpty() ? (
|
<SearchInput i18n={{ "input.search": props.i18n["input.search"] }} />
|
||||||
<For each={sortYears(articles())}>
|
<TagsList data={props.tags} />
|
||||||
{(year) => (
|
<div itemscope itemtype="http://schema.org/Blog" class="articles-list">
|
||||||
<div class="articles-list__wrapper">
|
{!isEmpty() ? (
|
||||||
<h2>{year}</h2>
|
<For each={sortYears(articles())}>
|
||||||
<For each={articles()[year]}>
|
{(year) => (
|
||||||
{(a) => (
|
<div class="articles-list__wrapper">
|
||||||
<ArticleItem
|
<h2>{year}</h2>
|
||||||
id={a.id}
|
<For each={articles()[year]}>
|
||||||
title={a.title}
|
{({ id, title, pubDate }) => (
|
||||||
pubDate={a.pubDate}
|
<ArticleItem
|
||||||
pageLang={props.data.pageLang}
|
id={id}
|
||||||
/>
|
title={title}
|
||||||
)}
|
pubDate={pubDate}
|
||||||
</For>
|
pageLang={props.pageLang}
|
||||||
</div>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
) : (
|
</div>
|
||||||
<div class="articles-list__empty">
|
)}
|
||||||
<p>{props.i18n["articles.empty"]}</p>
|
</For>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div class="articles-list__empty">
|
||||||
</div>
|
<p>{props.i18n["articles.empty"]}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user