From 25c37af4051a7babb354ec39ea8010630483395c Mon Sep 17 00:00:00 2001 From: sergeyyarkov Date: Tue, 21 Feb 2023 18:53:43 +0300 Subject: [PATCH] render article items --- astro.config.mjs | 7 ++ siteconfig.json | 3 +- src/components/ArticleItem.astro | 83 -------------- src/components/ArticleItem/ArticleItem.scss | 43 ++++++++ src/components/ArticleItem/ArticleItem.tsx | 42 ++++++++ src/components/ArticleItem/index.ts | 1 + src/components/ArticlesList/ArticlesList.scss | 18 ++++ src/components/ArticlesList/ArticlesList.tsx | 101 ++++++++++++++++++ src/components/ArticlesList/index.ts | 1 + src/components/ContactForm.astro | 2 +- src/components/Footer.astro | 2 +- src/components/LanguageSelect.astro | 3 +- src/components/RecentArticles.astro | 21 +++- src/components/SearchInput/SearchInput.scss | 27 +++++ src/components/SearchInput/SearchInput.tsx | 76 +++++++++++++ src/components/SearchInput/index.ts | 1 + src/components/TagsList/TagsList.tsx | 100 +++++++++++++++++ src/components/TagsList/index.ts | 1 + src/constants.ts | 3 +- src/content/blog/ru/тестовая-статьия.md | 10 ++ src/content/config.ts | 2 +- src/i18n/en/ui.ts | 2 +- src/i18n/ru/ui.ts | 2 +- src/pages/[...blog].astro | 15 +-- src/stores/index.ts | 1 + src/stores/searchStore.ts | 5 + src/templates/Articles.astro | 25 +++-- src/templates/Contacts.astro | 1 + src/templates/Home.astro | 2 +- src/templates/NotFound.astro | 7 +- src/utils/article.ts | 34 ++++-- 31 files changed, 514 insertions(+), 127 deletions(-) delete mode 100644 src/components/ArticleItem.astro create mode 100644 src/components/ArticleItem/ArticleItem.scss create mode 100644 src/components/ArticleItem/ArticleItem.tsx create mode 100644 src/components/ArticleItem/index.ts create mode 100644 src/components/ArticlesList/ArticlesList.scss create mode 100644 src/components/ArticlesList/ArticlesList.tsx create mode 100644 src/components/ArticlesList/index.ts create mode 100644 src/components/SearchInput/SearchInput.scss create mode 100644 src/components/SearchInput/SearchInput.tsx create mode 100644 src/components/SearchInput/index.ts create mode 100644 src/components/TagsList/TagsList.tsx create mode 100644 src/components/TagsList/index.ts create mode 100644 src/content/blog/ru/тестовая-статьия.md create mode 100644 src/stores/index.ts create mode 100644 src/stores/searchStore.ts diff --git a/astro.config.mjs b/astro.config.mjs index e1378ac..6233900 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -311,6 +311,13 @@ export default defineConfig({ label: "Настройки веб-сайта", file: "siteconfig.json", fields: [ + { + label: "Название сайта", + name: "name", + widget: "string", + hint: "Отображается в шапке.", + default: "yarkov.tech", + }, { label: "Глобальный заголовок", name: "globalTitle", diff --git a/siteconfig.json b/siteconfig.json index f750c79..f2f93a5 100644 --- a/siteconfig.json +++ b/siteconfig.json @@ -9,5 +9,6 @@ "telegram": "https://t.me/sergeyyarkov", "github": "https://github.com/sergeyyarkov" }, - "ymCounter": 90034656 + "ymCounter": 90034656, + "name": "yarkov.tech" } diff --git a/src/components/ArticleItem.astro b/src/components/ArticleItem.astro deleted file mode 100644 index fdb7ffb..0000000 --- a/src/components/ArticleItem.astro +++ /dev/null @@ -1,83 +0,0 @@ ---- -import type { CollectionEntry } from "astro:content"; -import { URL_BLOG_PREFIX } from "@root/pages/[...blog].astro"; -import { getLanguageFromURL } from "@i18n/utils"; -import { createDateFormatter, createRelativeArticleUrl } from "@root/utils"; - -export interface Props { - entry: CollectionEntry<"blog">; -} - -const pageLang = getLanguageFromURL(Astro.url.pathname); -const { entry } = Astro.props as Props; -const { - id, - data: { title, pubDate }, -} = entry; -const articleLang = id.split("/")[0]; ---- - -
- -
-

{title}

- {articleLang !== pageLang && {articleLang.toLocaleUpperCase()}} -
-

- -

-
-
- - diff --git a/src/components/ArticleItem/ArticleItem.scss b/src/components/ArticleItem/ArticleItem.scss new file mode 100644 index 0000000..94116ee --- /dev/null +++ b/src/components/ArticleItem/ArticleItem.scss @@ -0,0 +1,43 @@ +.article-item { + border-radius: var(--border-radius-md); + text-decoration: none; + & a { + display: flex; + justify-content: space-between; + align-items: center; + column-gap: 10px; + color: var(--font-color); + padding: 8px 0; + & sup { + margin-left: 5px; + font-size: 0.7em; + } + & h3 { + font-size: var(--font-size-md); + font-weight: normal; + margin: 0; + } + & p { + font-size: var(--font-size-xs); + font-family: var(--font-family-monospace); + color: var(--font-color-gray); + text-transform: uppercase; + text-align: right; + white-space: nowrap; + margin-bottom: 0; + } + } +} + +@media screen and (max-width: 560px) { + .article-item { + margin-bottom: 10px; + & a { + flex-direction: column-reverse; + align-items: flex-start; + & p { + margin-bottom: 5px; + } + } + } +} diff --git a/src/components/ArticleItem/ArticleItem.tsx b/src/components/ArticleItem/ArticleItem.tsx new file mode 100644 index 0000000..b26b400 --- /dev/null +++ b/src/components/ArticleItem/ArticleItem.tsx @@ -0,0 +1,42 @@ +import type { Component } from "solid-js"; +import { createDateFormatter, createRelativeArticleUrl } from "@root/utils"; +import { URL_BLOG_PREFIX } from "@root/constants"; +import "./ArticleItem.scss"; + +export type ArticleItemProps = { + id: string; + title: string; + pubDate: Date; + pageLang: string; +}; + +const ArticleItem: Component = (props) => { + const articleLang = props.id.split("/")[0]; + const href = createRelativeArticleUrl( + { id: props.id, title: props.title, pubDate: props.pubDate }, + URL_BLOG_PREFIX + ); + + return ( + + ); +}; + +export default ArticleItem; diff --git a/src/components/ArticleItem/index.ts b/src/components/ArticleItem/index.ts new file mode 100644 index 0000000..05e3596 --- /dev/null +++ b/src/components/ArticleItem/index.ts @@ -0,0 +1 @@ +export { default } from "./ArticleItem"; diff --git a/src/components/ArticlesList/ArticlesList.scss b/src/components/ArticlesList/ArticlesList.scss new file mode 100644 index 0000000..1119765 --- /dev/null +++ b/src/components/ArticlesList/ArticlesList.scss @@ -0,0 +1,18 @@ +.articles { + &-list { + &__wrapper { + margin-top: 40px; + & h2 { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border-color); + } + } + &__empty { + color: var(--font-color-gray); + text-align: center; + user-select: none; + padding: 3rem 0; + } + } +} diff --git a/src/components/ArticlesList/ArticlesList.tsx b/src/components/ArticlesList/ArticlesList.tsx new file mode 100644 index 0000000..8680091 --- /dev/null +++ b/src/components/ArticlesList/ArticlesList.tsx @@ -0,0 +1,101 @@ +import type { Component } from "solid-js"; +import { createSignal, createEffect, For } from "solid-js"; +import { search, selectedTags } from "@stores/searchStore"; +import ArticleItem from "@components/ArticleItem"; +import "./ArticlesList.scss"; + +type UiStringsType = { "articles.empty": string }; + +type ArticleType = { + id: string; + title: string; + pubDate: Date; +}; + +type ArticlesType = Record; + +type ArticlesListProps = { + data: { articles: ArticlesType; pageLang: string }; + i18n: UiStringsType; +}; + +const ArticlesList: Component = (props) => { + const [articles, setArticles] = createSignal(props.data.articles); + + /** + * Sort articles by year in descending order + */ + const sortYears = (articles: ArticlesType) => { + return Object.keys(articles) + .map(Number) + .sort((a, b) => b - a); + }; + + const isEmpty = () => Object.keys(articles()).length === 0; + const getLength = () => Object.values(articles()).flat().length; + + createEffect(() => { + // const filtered = Object.values(articles()).flat(1) + }); + + 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 ( +
+ {!isEmpty() ? ( + + {(year) => ( +
+

{year}

+ + {(a) => ( + + )} + +
+ )} +
+ ) : ( +
+

{props.i18n["articles.empty"]}

+
+ )} +
+ ); +}; + +export default ArticlesList; diff --git a/src/components/ArticlesList/index.ts b/src/components/ArticlesList/index.ts new file mode 100644 index 0000000..17b28e3 --- /dev/null +++ b/src/components/ArticlesList/index.ts @@ -0,0 +1 @@ +export { default } from "./ArticlesList"; diff --git a/src/components/ContactForm.astro b/src/components/ContactForm.astro index 670ecfa..4071a86 100644 --- a/src/components/ContactForm.astro +++ b/src/components/ContactForm.astro @@ -71,7 +71,7 @@ const t = useTranslation(Astro); border: 1px solid var(--color-error); } & .footer { - text-align: center; + text-align: right; } } @media screen and (max-width: 560px) { diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 1bb8b2a..89a2394 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -9,7 +9,7 @@ import UIString from "./UIString.astro";
- : + : {SITE_METADATA.author} © {new Date().getFullYear()} diff --git a/src/components/LanguageSelect.astro b/src/components/LanguageSelect.astro index 4c4d483..02bd794 100644 --- a/src/components/LanguageSelect.astro +++ b/src/components/LanguageSelect.astro @@ -1,7 +1,6 @@ --- import { Icon } from "astro-icon"; -import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants"; -import { URL_BLOG_PREFIX } from "@root/pages/[...blog].astro"; +import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, URL_BLOG_PREFIX } from "@root/constants"; import { getLanguageFromURL } from "@i18n/utils"; const lang = getLanguageFromURL(Astro.url.pathname); diff --git a/src/components/RecentArticles.astro b/src/components/RecentArticles.astro index fb81519..ec9b88f 100644 --- a/src/components/RecentArticles.astro +++ b/src/components/RecentArticles.astro @@ -3,7 +3,7 @@ import type { CollectionEntry } from "astro:content"; import siteconfig from "siteconfig.json"; import { getLanguageFromURL } from "@i18n/utils"; import * as utils from "@utils/index"; -import ArticleItem from "@components/ArticleItem.astro"; +import ArticleItem from "@components/ArticleItem"; import EmptyList from "@components/EmptyList.astro"; import UIString from "@components/UIString.astro"; import TranslatedLink from "@components/TranslatedLink.astro"; @@ -24,12 +24,25 @@ const isEmpty = articles.length === 0;

- +
- {!isEmpty ? filtered.map((article) => ) : } + { + !isEmpty ? ( + filtered.map((article) => ( + + )) + ) : ( + + ) + }
@@ -40,7 +53,7 @@ const isEmpty = articles.length === 0; align-items: center; column-gap: 15px; row-gap: 5px; - flex-wrap: wrap; + article-wrap: wrap; justify-content: space-between; border-bottom: 1px solid var(--border-color); padding-bottom: 10px; diff --git a/src/components/SearchInput/SearchInput.scss b/src/components/SearchInput/SearchInput.scss new file mode 100644 index 0000000..22fac36 --- /dev/null +++ b/src/components/SearchInput/SearchInput.scss @@ -0,0 +1,27 @@ +.search-input { + position: relative; + display: flex; + align-items: center; + column-gap: 15px; + margin-bottom: 10px; + + & svg { + position: absolute; + left: 8px; + fill: var(--font-color-grayer); + filter: none; + } + + & input { + padding-left: 40px; + color: var(--font-color-gray); + background-color: var(--background-color-hover); + } + & span { + font-size: var(--font-size-lg); + font-weight: 500; + color: var(--font-color-gray); + min-width: 20px; + text-align: center; + } +} diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx new file mode 100644 index 0000000..247928c --- /dev/null +++ b/src/components/SearchInput/SearchInput.tsx @@ -0,0 +1,76 @@ +import type { Component, JSX } from "solid-js"; +import { createEffect } from "solid-js"; +import { search, setSearch, count } from "@stores/searchStore"; +import * as utils from "@utils/history"; +import "./SearchInput.scss"; + +type UiStringsType = { "input.search": string }; +type SearchInputProps = { i18n: UiStringsType }; + +const SearchInput: Component = (props) => { + /** + * Reset state and delete `search` param from url + */ + const resetSearch = () => { + const url = utils.deleteParamFromUrl("search"); + setSearch(""); + history.replaceState(null, "", url); + }; + + /** + * Update state and set url param + */ + const onSearch: JSX.EventHandler = (e) => { + const value = e.currentTarget.value; + + if (value.trim().length === 0) { + resetSearch(); + return; + } + + const url = utils.setParamToUrl("search", value.trim()); + history.replaceState(null, "", `${location.pathname}?${url}`); + setSearch(value); + + // TODO: search articles + }; + + /** + * Set the signal state by reading the `search` parameter from the URL. + */ + createEffect(() => { + const param = new URLSearchParams(location.search).get("search"); + + if (!param) { + resetSearch(); + return; + } + + setSearch(param); + }); + + return ( +
+ + + + + {count()} +
+ ); +}; + +export default SearchInput; diff --git a/src/components/SearchInput/index.ts b/src/components/SearchInput/index.ts new file mode 100644 index 0000000..1a2fa40 --- /dev/null +++ b/src/components/SearchInput/index.ts @@ -0,0 +1 @@ +export { default } from "./SearchInput"; diff --git a/src/components/TagsList/TagsList.tsx b/src/components/TagsList/TagsList.tsx new file mode 100644 index 0000000..f3b7525 --- /dev/null +++ b/src/components/TagsList/TagsList.tsx @@ -0,0 +1,100 @@ +import type { Component, JSX } from "solid-js"; +import { For, createEffect } from "solid-js"; +import { selectedTags, setSelectedTags } from "@stores/searchStore"; +import * as utils from "@utils/history"; + +type TagsListProps = { data: string[] }; + +const TagsList: Component = (props) => { + const isTagSelected = (tag: string) => selectedTags().includes(tag.toLocaleLowerCase()); + + /** + * Reset signal state and `tags` property in history state + */ + const resetSelected = () => { + const url = utils.deleteParamFromUrl("tags"); + + setSelectedTags([]); + history.replaceState(null, "", url); + }; + + const onSelect: JSX.EventHandler = (e) => { + const tag = e.target.getAttribute("data-tag"); + + /** + * Delete tag from selected tags if already selected + * and update param in url + */ + if (isTagSelected(tag)) { + const updated = selectedTags().filter((selectedTag) => selectedTag !== tag); + const url = utils.setParamToUrl("tags", updated.join(",")); + + if (updated.length === 0) { + resetSelected(); + return; + } + + setSelectedTags(updated); + history.replaceState(null, "", `${location.pathname}?${url}`); + return; + } + + /** + * Add selected tag to state and update url + */ + const updated = [...selectedTags(), tag]; + const url = utils.setParamToUrl("tags", updated.join(",")); + + setSelectedTags(updated); + history.replaceState(null, "", `${location.pathname}?${url}`); + }; + + createEffect(() => { + let param: string | string[] = new URLSearchParams(location.search).get("tags"); + + if (!param) { + resetSelected(); + return; + } + + /* to array */ + param = param.split(","); + + if (param.length === 0) { + resetSelected(); + return; + } + + const tags: string[] = []; + + for (const tag of props.data) { + const isTagExist = param.includes(tag.toLocaleLowerCase()); + + if (isTagExist) { + tags.push(tag.toLocaleLowerCase()); + } + } + + setSelectedTags(tags); + }); + + return ( + + ); +}; + +export default TagsList; diff --git a/src/components/TagsList/index.ts b/src/components/TagsList/index.ts new file mode 100644 index 0000000..c010bfe --- /dev/null +++ b/src/components/TagsList/index.ts @@ -0,0 +1 @@ +export { default } from "./TagsList"; diff --git a/src/constants.ts b/src/constants.ts index 8e4ffbb..b6f5a78 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,9 +3,10 @@ import siteconfig from "../siteconfig.json"; export const DEFAULT_LANGUAGE: LanguageKeys = "ru"; export const IS_SITE_UNDER_CONSTRUCTION = siteconfig.isSiteUnderConstruction; export const SUPPORTED_LANGUAGES: SupportedLanguages = ["ru", "en"]; +export const URL_BLOG_PREFIX = "blog"; export const SITE_METADATA: SiteMetadataType = { title: siteconfig.globalTitle, - name: "yarkov.tech", + name: siteconfig.name, author: "Sergey Yarkov", themeColor: "#1e2327", contacts: siteconfig.contacts, diff --git a/src/content/blog/ru/тестовая-статьия.md b/src/content/blog/ru/тестовая-статьия.md new file mode 100644 index 0000000..ab2dda3 --- /dev/null +++ b/src/content/blog/ru/тестовая-статьия.md @@ -0,0 +1,10 @@ +--- +author: Сергей Ярков +title: Тестовая статьия +description: фывфы +tags: + - Digital Electronics +pubDate: 2023-02-19T10:46:31.109Z +updatedDate: 2023-02-19T10:46:31.127Z +--- +123 \ No newline at end of file diff --git a/src/content/config.ts b/src/content/config.ts index 6d13e53..7b40cd3 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -6,7 +6,7 @@ const blog = defineCollection({ draft: z.boolean().nullable().optional(), coverImage: z.string().nullable().optional(), author: z.string(), - tags: z.string().array().optional(), + tags: z.string().array(), description: z.string(), pubDate: z .string() diff --git a/src/i18n/en/ui.ts b/src/i18n/en/ui.ts index 37bd267..0a7a174 100644 --- a/src/i18n/en/ui.ts +++ b/src/i18n/en/ui.ts @@ -47,7 +47,7 @@ export default { "input.nameField": "Your name", "input.subjectField": "Message subject", "input.messageField": "Your message...", - "input.search": "search by title or description...", + "input.search": "search publications by title...", /* Forms */ "form.contactSuccess": "The message was submitted successfully!", "form.contactError": "An error occurred while sending. Please try again later.", diff --git a/src/i18n/ru/ui.ts b/src/i18n/ru/ui.ts index 08464fa..77f4fa2 100644 --- a/src/i18n/ru/ui.ts +++ b/src/i18n/ru/ui.ts @@ -48,7 +48,7 @@ export default { "input.nameField": "Ваше имя", "input.subjectField": "Тема сообщения", "input.messageField": "Ваше сообщение...", - "input.search": "поиск по названию или описанию...", + "input.search": "поиск публикаций по названию...", /* Forms */ "form.contactSuccess": "Сообщение было успешно отправлено!", "form.contactError": "При отправке произошла ошибка. Повторите запрос позже.", diff --git a/src/pages/[...blog].astro b/src/pages/[...blog].astro index cd3afe8..9fa0c1b 100644 --- a/src/pages/[...blog].astro +++ b/src/pages/[...blog].astro @@ -3,13 +3,12 @@ import { CollectionEntry, getCollection } from "astro:content"; import ArticleLayout from "@root/layouts/article.astro"; import { createRelativeArticleUrl, removeLanguageCodeFromPath } from "@root/utils"; import { getLanguageFromURL, useTranslation } from "@i18n/utils"; +import { URL_BLOG_PREFIX } from "@root/constants"; import languages from "@i18n/languages"; const t = useTranslation(Astro); const pageLang = getLanguageFromURL(Astro.url.pathname); -export const URL_BLOG_PREFIX = "blog"; - /** * Generate articles pages from `blog` collection. * The final url will be `http://website.com/blog/2023-01-01/my-article` with default language @@ -18,7 +17,11 @@ export const URL_BLOG_PREFIX = "blog"; export async function getStaticPaths() { const collection = await getCollection("blog", (e) => !e.data.draft); return collection.map((article) => { - const url = createRelativeArticleUrl(article); + const { + id, + data: { title, pubDate }, + } = article; + const url = createRelativeArticleUrl({ id, title, pubDate }); return { params: { blog: url.slice(1) }, props: { @@ -44,9 +47,9 @@ const { article, availableOnLang } = Astro.props; {t("article.availableOn")}:{" "} {availableOnLang .filter((a) => !a.id.startsWith(`${pageLang}/`)) - .map((a) => ( - {`${ - languages[a.id.split("/")[0] as LanguageKeys] + .map(({ id, data: { title, pubDate } }) => ( + {`${ + languages[id.split("/")[0] as LanguageKeys] }`} ))}
diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..4aa0760 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1 @@ +export * from "./searchStore"; diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts new file mode 100644 index 0000000..382faa5 --- /dev/null +++ b/src/stores/searchStore.ts @@ -0,0 +1,5 @@ +import { createSignal } from "solid-js"; + +export const [search, setSearch] = createSignal(""); +export const [count, setCount] = createSignal(0); +export const [selectedTags, setSelectedTags] = createSignal([]); diff --git a/src/templates/Articles.astro b/src/templates/Articles.astro index 5e28bff..787fb2a 100644 --- a/src/templates/Articles.astro +++ b/src/templates/Articles.astro @@ -1,10 +1,18 @@ --- +import { getCollection } from "astro:content"; +import { useTranslation, getLanguageFromURL } from "@i18n/utils"; +import * as utils from "@utils/index"; import PageLayout from "@layouts/default.astro"; import PageHeading from "@components/PageHeading.astro"; -import { useTranslation, getLanguageFromURL } from "@i18n/utils"; +import ArticlesList from "@root/components/ArticlesList"; +import SearchInput from "@components/SearchInput"; +import TagsList from "@components/TagsList"; const t = useTranslation(Astro); const pageLang = getLanguageFromURL(Astro.url.pathname); +const articles = await getCollection("blog", (e) => !e.data.draft); +const tags = utils.getUniqueTags(articles); +const filtered = utils.removeDuplicates(articles, pageLang); const canonicalUrl = new URL(Astro.url.pathname, Astro.site); --- @@ -16,15 +24,12 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site); >
- ... - + i18n={{ "articles.empty": t("articles.empty") }} + data={{ articles: utils.formatToArticleBlocks(filtered), pageLang }} + />
diff --git a/src/templates/Contacts.astro b/src/templates/Contacts.astro index e507ddc..aff33ea 100644 --- a/src/templates/Contacts.astro +++ b/src/templates/Contacts.astro @@ -4,6 +4,7 @@ import PageHeading from "@components/PageHeading.astro"; import ContactForm from "@components/ContactForm.astro"; import ContactsList from "@components/ContactsList.astro"; import { getLanguageFromURL, useTranslation } from "@i18n/utils"; + const t = useTranslation(Astro); const lang = getLanguageFromURL(Astro.url.pathname); --- diff --git a/src/templates/Home.astro b/src/templates/Home.astro index e3770d2..9ecda01 100644 --- a/src/templates/Home.astro +++ b/src/templates/Home.astro @@ -9,7 +9,7 @@ import RecentArticles from "@components/RecentArticles.astro"; import UIString from "@components/UIString.astro"; const articles = (await getCollection("blog", (e) => !e.data.draft)).sort( - (a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf() + (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf() ); --- diff --git a/src/templates/NotFound.astro b/src/templates/NotFound.astro index ec66ccd..9459452 100644 --- a/src/templates/NotFound.astro +++ b/src/templates/NotFound.astro @@ -1,13 +1,14 @@ --- import PageLayout from "@layouts/default.astro"; import { getLanguageFromURL } from "@i18n/utils"; +import UIString from "@root/components/UIString.astro"; + const lang = getLanguageFromURL(Astro.url.pathname); -// const t = useTranslation(Astro); ---
-

404

-

{"Страницу которую вы пытаетесь найти не существует."}

+

+

diff --git a/src/utils/article.ts b/src/utils/article.ts index afe29c4..c451b20 100644 --- a/src/utils/article.ts +++ b/src/utils/article.ts @@ -7,10 +7,6 @@ import { removeLanguageCodeFromPath } from "./url"; /** * If there is a translation of an article in the current language of the page, * the remaining articles with other translations should be removed. - * - * @param entries Array of articles - * @param pageLang Current language on page - * @returns */ export function removeDuplicates( entries: CollectionEntry<"blog">[], @@ -34,14 +30,30 @@ export function removeDuplicates( return entries; } +/** + * This will create a relative url based on article entry. + * `/en/blog/2023-01-01/my-first-article` + */ export function createRelativeArticleUrl( - article: CollectionEntry<"blog">, + params: { id: string; title: string; pubDate: Date }, prefix: string = "blog" ): string { - const articleLang = article.id.split("/")[0]; - const date = article.data.pubDate.toISOString().split("T")[0]; - return `${articleLang !== DEFAULT_LANGUAGE ? "/" + articleLang : ""}/${prefix}/${date}/${slugify( - article.data.title, - { lowercase: true } - )}`; + const articleLang = params.id.split("/")[0]; + const date = params.pubDate.toISOString().split("T")[0]; + const lang = articleLang !== DEFAULT_LANGUAGE ? "/" + articleLang : ""; + return `${lang}/${prefix}/${date}/${slugify(params.title, { lowercase: true })}`; +} + +export function formatToArticleBlocks(articles: CollectionEntry<"blog">[]) { + const blocks: Record = {}; + articles.forEach((a) => { + const year = a.data.pubDate.getFullYear(); + if (!blocks[year]) blocks[year] = []; + blocks[year].push({ id: a.id, title: a.data.title, pubDate: a.data.pubDate }); + }); + return blocks; +} + +export function getUniqueTags(articles: CollectionEntry<"blog">[]): string[] { + return Array.from(new Set(articles.map((a) => a.data.tags).flat())); }