refactor: refactored articles search & articles list

This commit is contained in:
Sergey Yarkov 2022-07-24 20:34:42 +03:00
parent 9b24143229
commit f2552eda17
10 changed files with 99 additions and 56 deletions

View File

@ -3,17 +3,23 @@ import type { ArticleBlockType } from '@root/types';
import { createSignal, createEffect, For } from 'solid-js';
import { search, selectedTags } from '@stores/searchStore';
import { ArticleItem } from '../ArticleItem';
import { ArticleItem } from '@components/ArticleItem';
import * as utils from '@utils/articles';
import './ArticlesList.scss';
import SearchInput from '../SearchInput';
import TagsList from '../TagsList';
type ArticlesListProps = {
data: ArticleBlockType;
data: {
articles: ArticleBlockType;
tags: string[];
};
emptyPlaceholder?: string | undefined;
};
const ArticlesList: Component<ArticlesListProps> = (props) => {
const [articles, setArticles] = createSignal<ArticleBlockType>(props.data);
// eslint-disable-next-line solid/reactivity
const [articles, setArticles] = createSignal<ArticleBlockType>(props.data.articles);
/**
* Sort articles by year in descending order
@ -25,9 +31,10 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
};
const isEmpty = () => Object.keys(articles()).length === 0;
const getLength = () => Object.values(articles()).flat().length;
createEffect(() => {
let filtered = props.data;
let filtered = props.data.articles;
/**
* Find articles by title
@ -47,17 +54,25 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
return;
}
filtered = utils.findArticlesBySearch(search(), props.data);
/**
* Set filtered variable as default value
*/
if (!search() && selectedTags().length === 0) {
filtered = props.data.articles;
setArticles(filtered);
}
});
return (
<>
<SearchInput count={getLength()} placeholder="поиск по названию или описанию..." />
<TagsList data={props.data.tags} />
<div class="articles-list">
{isEmpty() && (
{isEmpty() ? (
<div class="articles-list__empty">
<p>{props.emptyPlaceholder || 'No articles found...'}</p>
</div>
)}
) : (
<For
each={sortYears(articles())}
children={(year) => {
@ -69,7 +84,9 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
);
}}
/>
)}
</div>
</>
);
};

View File

@ -3,7 +3,7 @@ import './Header.scss';
import { SITE_METADATA } from '@root/config';
import ThemeSwitcher from '@components/ThemeSwticher';
import { Icon } from 'astro-icon';
import { NavToggle } from './NavToggle/NavToggle.tsx';
import NavToggle from './NavToggle';
const { name, nav, contacts } = SITE_METADATA;
const { pathname } = Astro.canonicalURL;
@ -40,6 +40,6 @@ const { pathname } = Astro.canonicalURL;
</div>
<ThemeSwitcher />
</nav>
<NavToggle client:idle />
<NavToggle client:media='(max-width: 768px)' />
</div>
</header>

View File

@ -1,7 +1,9 @@
import './NavToggle.scss';
import { Component, createSignal, createEffect } from 'solid-js';
import type { Component } from 'solid-js';
export const NavToggle: Component = () => {
import { createSignal, createEffect } from 'solid-js';
import './NavToggle.scss';
const NavToggle: Component = () => {
const [isNavShown, setIsNavShown] = createSignal<boolean>(false);
const toggleNav = () => setIsNavShown(!isNavShown());
@ -25,3 +27,5 @@ export const NavToggle: Component = () => {
</button>
);
};
export default NavToggle;

View File

@ -0,0 +1 @@
export { default } from './NavToggle';

View File

@ -1,4 +1,4 @@
import type { Component, JSX } from 'solid-js';
import { Component, JSX } from 'solid-js';
import { createEffect } from 'solid-js';
import { search, setSearch } from '@stores/searchStore';
@ -6,7 +6,7 @@ import * as utils from '@utils/history';
import './SearchInput.scss';
type SearchInputProps = {
length: number;
count: number;
placeholder?: string | undefined;
};
@ -58,7 +58,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
<path d="M10.6026 3.60001C11.8798 3.59993 13.1316 3.94283 14.2172 4.5901C15.3028 5.23736 16.179 6.16331 16.7472 7.26368C17.3154 8.36405 17.5531 9.59517 17.4334 10.8184C17.3137 12.0417 16.8415 13.2085 16.0699 14.1876L21.2427 19.164C21.4085 19.3248 21.5053 19.5399 21.5135 19.7662C21.5218 19.9926 21.4411 20.2137 21.2874 20.3853C21.1337 20.5569 20.9184 20.6665 20.6845 20.6921C20.4505 20.7178 20.2151 20.6576 20.0253 20.5236L19.9205 20.436L14.7476 15.4596C13.8802 16.0922 12.8687 16.5172 11.7968 16.6994C10.7248 16.8815 9.62324 16.8157 8.58298 16.5074C7.54272 16.199 6.59366 15.6569 5.81422 14.9259C5.03478 14.1949 4.44733 13.296 4.10042 12.3034C3.7535 11.3108 3.65708 10.2531 3.81911 9.21757C3.98114 8.18204 4.39698 7.19847 5.03226 6.34812C5.66755 5.49778 6.50404 4.80506 7.47263 4.32721C8.44122 3.84936 9.51411 3.60009 10.6026 3.60001V3.60001ZM10.6026 5.40001C9.27934 5.40001 8.01025 5.90572 7.07454 6.80589C6.13884 7.70607 5.61316 8.92697 5.61316 10.2C5.61316 11.473 6.13884 12.6939 7.07454 13.5941C8.01025 14.4943 9.27934 15 10.6026 15C11.9259 15 13.195 14.4943 14.1307 13.5941C15.0664 12.6939 15.5921 11.473 15.5921 10.2C15.5921 8.92697 15.0664 7.70607 14.1307 6.80589C13.195 5.90572 11.9259 5.40001 10.6026 5.40001Z" />
</svg>
<input onInput={onSearch} value={search()} type="text" placeholder={props.placeholder || 'Search article by title or description...'} />
<span>{props.length}</span>
<span>{props.count}</span>
</div>
);
};

View File

@ -19,3 +19,10 @@
line-height: 1.5;
}
}
@media screen and (max-width: 560px) {
.splash-heading hgroup h1 {
font-size: var(--font-size-4xl);
margin-bottom: 0.85rem;
}
}

View File

@ -3,9 +3,7 @@ import type { ArticleType } from '@root/types';
import PageLayout from '@layouts/default.astro';
import ArticlesList from '@components/ArticlesList';
import SearchInput from '@components/SearchInput';
import TagsList from '@components/TagsList';
import { transformArticles, filterArticlesByYear } from '@utils/articles';
import { transformArticles, formatToArticleBlocks } from '@utils/articles';
const articles = await Astro.glob<ArticleType>('../../content/articles/**/*.md');
const tags = new Set(articles.map((article) => article.frontmatter.tags).flat());
@ -16,8 +14,13 @@ const tags = new Set(articles.map((article) => article.frontmatter.tags).flat())
description='Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.'
>
<section>
<SearchInput client:idle length={articles.length} placeholder='поиск по названию или описанию...' />
<TagsList client:idle data={[...tags]} />
<ArticlesList client:idle data={filterArticlesByYear(transformArticles(articles))} emptyPlaceholder='Не найдено ни одной статьи...' />
<ArticlesList
client:load
data={{
articles: formatToArticleBlocks(transformArticles(articles)),
tags: [...tags],
}}
emptyPlaceholder='Не найдено ни одной статьи...'
/>
</section>
</PageLayout>

View File

@ -1,3 +0,0 @@
import { createSignal } from 'solid-js';
export const [search, setSearch] = createSignal<string>('');

View File

@ -85,7 +85,7 @@
/* Sizes */
--content-width: 760px;
--content-padding: 15px;
--content-padding: 24px;
/* Elements */
--background-body-color: var(--color-white);

View File

@ -14,32 +14,46 @@ export const sortArtcilesByDate = (articles: ArticleType[]): ArticleType[] => {
return articles.sort((a, b) => new Date(b.published_at).valueOf() - new Date(a.published_at).valueOf());
};
export const filterArticlesByYear = (articles: ArticleType[]): { [year: number]: ArticleType[] } => {
const filtered = {};
/**
* This function will distribute the array of articles by years.
*/
export const formatToArticleBlocks = (articles: ArticleType[]): ArticleBlockType => {
const blocks = {};
for (const article of articles) {
const year = new Date(article.published_at).getFullYear();
if (!filtered[year]) filtered[year] = [];
filtered[year].push(article);
if (!blocks[year]) blocks[year] = [];
blocks[year].push(article);
}
return filtered;
return blocks;
};
export const findArticlesBySearch = (search: string, articles: ArticleBlockType): ArticleBlockType => {
const filtered = Object.values(articles)
.flat()
.filter((article) => article.title.toLocaleLowerCase().includes(search.toLocaleLowerCase()));
const matched: Array<ArticleType> = [];
return filterArticlesByYear(filtered);
for (const year in articles) {
for (const article of articles[year]) {
const match = article.title.toLocaleLowerCase().includes(search.toLocaleLowerCase());
if (match) matched.push(article);
}
}
return formatToArticleBlocks(matched);
};
export const findArticlesByTags = (tags: string[], articles: ArticleBlockType): ArticleBlockType => {
const filtered = Object.values(articles)
.flat()
.filter((article) => article.tags.map((tag) => tag.toLocaleLowerCase()).some((t) => tags.includes(t)));
const matched: Array<ArticleType> = [];
return filterArticlesByYear(filtered);
for (const year in articles) {
for (const article of articles[year]) {
const entries = article.tags.map((tag) => tag.toLocaleLowerCase());
const match = entries.some((tag) => tags.includes(tag));
if (match) matched.push(article);
}
}
return formatToArticleBlocks(matched);
};