This commit is contained in:
Sergey Yarkov 2025-11-20 16:28:31 +03:00
parent 35806aa60d
commit c5d7b7a7ba
15 changed files with 270 additions and 4389 deletions

View File

@ -8,27 +8,12 @@ import sitemap from "@astrojs/sitemap";
import solidJs from "@astrojs/solid-js"; import solidJs from "@astrojs/solid-js";
import icon from "astro-icon"; import icon from "astro-icon";
/**
* Rehype plugins
*/
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
/** /**
* Other required modules * Other required modules
*/ */
import { h } from "hastscript";
import { toString } from "hast-util-to-string";
import { escape } from "html-escaper";
import languages from "./src/i18n/languages"; import languages from "./src/i18n/languages";
import { DEFAULT_LANGUAGE } from "./src/constants"; import { DEFAULT_LANGUAGE } from "./src/constants";
const createAnchorLabel = (heading) => {
const node = h("span.anchor-label", escape(heading));
if (node.properties) node.properties["is:raw"] = true;
return node;
};
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://yarkov.tech", site: "https://yarkov.tech",
@ -53,49 +38,6 @@ export default defineConfig({
icon(), icon(),
solidJs(), solidJs(),
], ],
markdown: {
syntaxHighlight: "prism",
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
properties: {
class: "anchor-link",
},
behavior: "after",
group: ({ tagName }) =>
h(`div.heading-wrapper.level-${tagName}`, {
tabIndex: -1,
}),
content: (heading) => [
h(
`span.anchor-icon`,
undefined,
h(
"svg",
{
width: 18,
height: 18,
version: 1.1,
viewBox: "0 0 16 16",
xlmns: "http://www.w3.org/2000/svg",
ariaHidden: "true",
"data-filter": "none",
},
h("path", {
fillRule: "evenodd",
fill: "currentcolor",
d: "M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z",
})
)
),
createAnchorLabel(toString(heading)),
],
},
],
],
},
trailingSlash: "ignore", trailingSlash: "ignore",
scopedStyleStrategy: "where", scopedStyleStrategy: "where",
}); });

View File

@ -1,12 +1,14 @@
import { createDirectus, graphql, staticToken } from "@directus/sdk"; import { createDirectus, graphql, staticToken } from "@directus/sdk";
import { DIRECTUS_TOKEN, DIRECTUS_URL } from "astro:env/server"; import { DIRECTUS_TOKEN, DIRECTUS_URL } from "astro:env/server";
import type { ArticleTranslation, Schema } from "./directus-schema"; import type { Article, ArticleTranslation, Schema, Tag } from "./directus-schema";
const directus = createDirectus<Schema>(DIRECTUS_URL) const directus = createDirectus<Schema>(DIRECTUS_URL)
.with(staticToken(DIRECTUS_TOKEN)) .with(staticToken(DIRECTUS_TOKEN))
.with(graphql()); .with(graphql());
export async function fetchArticleBySlug(slug: string): Promise<ArticleTranslation | null> { export async function fetchArticleTranslationsBySlug(
slug: string
): Promise<ArticleTranslation | null> {
const data = await directus.query<{ article_translations: ArticleTranslation[] }>(` const data = await directus.query<{ article_translations: ArticleTranslation[] }>(`
query Article_translations { query Article_translations {
article_translations( article_translations(
@ -16,6 +18,7 @@ export async function fetchArticleBySlug(slug: string): Promise<ArticleTranslati
} }
} }
) { ) {
id
title title
tags { tags {
tag_id { tag_id {
@ -33,18 +36,111 @@ export async function fetchArticleBySlug(slug: string): Promise<ArticleTranslati
views views
languages_code { languages_code {
code code
name
} }
cover_image { cover_image {
title title
filename_disk filename_disk
} }
article_id {
translations {
slug
languages_code {
code
name
}
pub_date
}
}
} }
} }
`); `);
if (data.article_translations.length === 0) return null; if (data.article_translations.length === 0) return null;
return data.article_translations[0]; return { ...data.article_translations[0] };
}
export async function fetchArticles(): Promise<Article[]> {
const data = await directus.query<{ article: Article[] }>(`
query Article {
article {
translations(
filter: {
status: { _eq: "published" }
}
) {
id
title
tags {
tag_id {
title
}
}
description
slug
pub_date
languages_code {
code
name
}
}
}
}
`);
return data.article;
}
export async function fetchRecentArticles(): Promise<Article[]> {
const data = await directus.query<{ article: Article[] }>(`
query Article {
article {
translations(
filter: {
status: { _eq: "published" }
}
) {
title
cover_image {
id
title
}
slug
pub_date
views
languages_code { code }
}
}
}
`);
return data.article;
}
export async function incArticleTranslationViews(
article: Pick<ArticleTranslation, "id" | "views">
): Promise<number> {
const data = await directus.query<{ update_article_translations_item: { views: number } }>(`
mutation Update_article_translations_item {
update_article_translations_item(id: ${article.id}, data: { views: ${article.views + 1} }) {
views
}
}
`);
return data.update_article_translations_item.views;
}
export async function fetchTags(): Promise<Tag[]> {
const data = await directus.query<{ tag: Tag[] }>(`
query Tag {
tag {
id
title
}
}
`);
return data.tag;
} }
export default directus; export default directus;

4212
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,8 +12,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build", "build": "astro check && astro build",
"start": "node --env-file=.env ./dist/server/entry.mjs",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"directus:gen-types": "node --env-file=.env ./scripts/gen-types.js" "directus:gen-types": "node --env-file=.env ./scripts/gen-types.js"
@ -26,18 +26,13 @@
"@astrojs/solid-js": "^5.1.3", "@astrojs/solid-js": "^5.1.3",
"@directus/sdk": "^20.1.1", "@directus/sdk": "^20.1.1",
"@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi": "^1.2.3",
"@netlify/functions": "^2.1.0",
"@sindresorhus/slugify": "^2.2.1",
"astro": "^5.15.8", "astro": "^5.15.8",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"hast-util-select": "^6.0.4", "hast-util-select": "^6.0.4",
"hast-util-to-string": "^3.0.0", "hast-util-to-string": "^3.0.0",
"hastscript": "^8.0.0", "hastscript": "^8.0.0",
"html-escaper": "^3.0.3", "html-escaper": "^3.0.3",
"i": "^0.3.7",
"marked": "^17.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"npm": "^11.6.2",
"quicklink": "^3.0.1", "quicklink": "^3.0.1",
"rehype-autolink-headings": "^7.0.0", "rehype-autolink-headings": "^7.0.0",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",

View File

@ -1,5 +1,4 @@
--- ---
import type { CollectionEntry } from "astro:content";
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import { createDateFormatter } from "@root/utils"; import { createDateFormatter } from "@root/utils";
import ArticleCover from "@components/ArticleCover.astro"; import ArticleCover from "@components/ArticleCover.astro";
@ -12,29 +11,29 @@ import { DIRECTUS_URL } from "astro:env/server";
export type Props = { export type Props = {
article: ArticleTranslation; article: ArticleTranslation;
availableOnLangs?: CollectionEntry<"blog">[];
lang: string; lang: string;
}; };
const { article, availableOnLangs } = Astro.props as Props; const { article } = Astro.props as Props;
const pageLang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
const canonicalUrl = new URL(Astro.url.pathname, Astro.site); const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const { cover_image, title, author, description, tags, pub_date, slug, views } = article; const { cover_image, title, author, description, tags, pub_date, slug, views, article_id } =
article;
const t = useTranslation(Astro); const t = useTranslation(Astro);
--- ---
<article itemscope itemtype='http://schema.org/BlogPosting' class='article'> <article itemscope itemtype="http://schema.org/BlogPosting" class="article">
<header> <header>
<div> <div>
{ {
tags && ( tags && (
<div class='tags'> <div class="tags">
<ul itemprop='keywords'> <ul itemprop="keywords">
{tags.map( {tags.map(
(tag) => (tag) =>
typeof tag === "object" && ( typeof tag === "object" && (
<li> <li>
<TranslatedLink rel='tag' to={`/articles/?tags=${tag.tag_id.title}`}> <TranslatedLink rel="tag" to={`/articles/?tags=${tag.tag_id.title}`}>
{tag.tag_id.title} {tag.tag_id.title}
</TranslatedLink> </TranslatedLink>
</li> </li>
@ -44,37 +43,37 @@ const t = useTranslation(Astro);
</div> </div>
) )
} }
<div class='views'> <div class="views">
<Icon name='eye' width='21' height='21' /> <Icon name="eye" width="21" height="21" />
<span><span>{views}</span>&nbsp;{t("article.views")}</span> <span><span>{views}</span>&nbsp;{t("article.views")}</span>
</div> </div>
</div> </div>
<div class='heading'> <div class="heading">
<h1 itemprop='headline'>{title}</h1> <h1 itemprop="headline">{title}</h1>
</div> </div>
<div class='published'> <div class="published">
<time itemprop='datePublished' datetime={pub_date}> <time itemprop="datePublished" datetime={pub_date}>
{createDateFormatter(pageLang).format(new Date(pub_date))}&nbsp; {createDateFormatter(pageLang).format(new Date(pub_date))}&nbsp;
</time> </time>
<span itemprop='author'>{`${author.first_name} ${author.last_name}`}</span> <span itemprop="author">{`${author.first_name} ${author.last_name}`}</span>
</div> </div>
{ {
cover_image && ( cover_image && (
<ArticleCover src={`${DIRECTUS_URL}/assets/${cover_image.filename_disk}`} alt={title} /> <ArticleCover src={`${DIRECTUS_URL}/assets/${cover_image.filename_disk}`} alt={title} />
) )
} }
<div class='description'> <div class="description">
<p> <p>
{description} {description}
</p> </p>
</div> </div>
{availableOnLangs && <AvailableOnLangs articles={availableOnLangs} />} <AvailableOnLangs articles={article_id.translations} />
</header> </header>
<slot /> <slot />
<ArticleComments url={canonicalUrl.href} id={slug} /> <ArticleComments url={canonicalUrl.href} id={slug} />
</article> </article>
<style lang='scss' is:global> <style lang="scss" is:global>
.article { .article {
& header { & header {
& div:nth-child(1) { & div:nth-child(1) {

View File

@ -5,17 +5,11 @@ import SearchInput from "./SearchInput";
import TagsList from "./TagsList"; import TagsList from "./TagsList";
import ArticleItem from "@components/ArticleItem"; import ArticleItem from "@components/ArticleItem";
import "./ArticlesList.scss"; import "./ArticlesList.scss";
import type { ArticleTranslation } from "@/directus-schema";
type UiStringsType = { "articles.empty": string; "input.search": string }; type UiStringsType = { "articles.empty": string; "input.search": string };
type ArticlesType = Array<{ type ArticlesBlockType = Record<string, ArticleTranslation[]>;
id: string;
title: string;
tags: string[];
pubDate: Date;
}>;
type ArticlesBlockType = Record<string, ArticlesType>;
type ArticlesListProps = { type ArticlesListProps = {
articles: ArticlesBlockType; articles: ArticlesBlockType;
@ -40,7 +34,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
}: { }: {
params: { search: string; tags?: string[] }; params: { search: string; tags?: string[] };
}): ArticlesBlockType => { }): ArticlesBlockType => {
const filter = (cb: (articles: ArticlesType) => ArticlesType) => { const filter = (cb: (articles: ArticleTranslation[]) => ArticleTranslation[]) => {
return Object.fromEntries( return Object.fromEntries(
Object.keys(props.articles) Object.keys(props.articles)
.map((year) => [year, cb(props.articles[year])]) .map((year) => [year, cb(props.articles[year])])
@ -75,16 +69,9 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
{(year) => ( {(year) => (
<div class="articles-list__wrapper"> <div class="articles-list__wrapper">
<h2>{year}</h2> <h2>{year}</h2>
{/* <For each={articles()[year]}> <For each={articles()[year]}>
{({ id, title, pubDate }) => ( {(data) => <ArticleItem data={data} pageLang={props.pageLang} />}
<ArticleItem </For>
id={id}
title={title}
pubDate={pubDate}
pageLang={props.pageLang}
/>
)}
</For> */}
</div> </div>
)} )}
</For> </For>

View File

@ -23,6 +23,8 @@ const SearchInput: Component<SearchInputProps> = (props) => {
const onSearch: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => { const onSearch: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => {
const value = e.currentTarget.value; const value = e.currentTarget.value;
console.log(value);
if (value.trim().length === 0) { if (value.trim().length === 0) {
resetSearch(); resetSearch();
return; return;
@ -62,6 +64,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
onInput={onSearch} onInput={onSearch}
value={search()} value={search()}
type="search" type="search"
name="search"
placeholder={props.i18n["input.search"] || "Search article by title..."} placeholder={props.i18n["input.search"] || "Search article by title..."}
autocapitalize="off" autocapitalize="off"
autocomplete="off" autocomplete="off"

View File

@ -1,12 +1,10 @@
--- ---
import type { CollectionEntry } from "astro:content";
import { getLanguageFromURL, useTranslation } from "@root/i18n/utils"; import { getLanguageFromURL, useTranslation } from "@root/i18n/utils";
import { URL_BLOG_PREFIX } from "@root/constants";
import { createRelativeArticleUrl } from "@root/utils"; import { createRelativeArticleUrl } from "@root/utils";
import languages from "@i18n/languages"; import type { ArticleTranslation } from "@/directus-schema";
export interface Props { export interface Props {
articles: CollectionEntry<"blog">[]; articles: ArticleTranslation[];
} }
const pageLang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
@ -19,15 +17,17 @@ const { articles } = Astro.props;
<div> <div>
{t("article.availableOn")}:{" "} {t("article.availableOn")}:{" "}
{articles {articles
.filter((a) => !a.id.startsWith(`${pageLang}/`)) .filter((a) => a.languages_code.code.split("-")[0] !== pageLang)
.map(({ id, data: { title, pubDate } }) => ( .map((a) => (
<a href={`...`}>{`${languages[id.split("/")[0] as LanguageKeys]}`}</a> <a
href={`${createRelativeArticleUrl({ slug: a.slug, articleLang: a.languages_code.code.split('-')[0], pubDate: a.pub_date })}`}
>{`${a.languages_code.name}`}</a>
))} ))}
</div> </div>
) )
} }
<style lang='scss'> <style lang="scss">
div { div {
margin: 25px 0; margin: 25px 0;
font-size: var(--font-size-lg); font-size: var(--font-size-lg);

View File

@ -5,44 +5,16 @@ import ArticleItem from "@components/ArticleItem";
import EmptyList from "@components/EmptyList.astro"; import EmptyList from "@components/EmptyList.astro";
import UIString from "@components/UIString.astro"; import UIString from "@components/UIString.astro";
import TranslatedLink from "@components/TranslatedLink.astro"; import TranslatedLink from "@components/TranslatedLink.astro";
import directus from "@/directus"; import { fetchRecentArticles } from "@/directus";
import type { Article } from "@/directus-schema";
export interface Props { export interface Props {
length?: number | undefined; limit?: number;
} }
type RecentArticlesQueryResult = { const { limit = 1 } = Astro.props;
article: Array<Article>;
};
const pageLang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
const data = await fetchRecentArticles();
const data = await directus.query<RecentArticlesQueryResult>(` const articles = data.flatMap((a) => utils.removeArticleDuplicates(a.translations, pageLang));
query Article {
article {
translations(
filter: {
status: { _eq: "published" }
}
) {
title
cover_image {
id
title
}
slug
pub_date
views
languages_code { code }
}
}
}
`);
const articles = data.article.flatMap((a) =>
utils.removeArticleDuplicates(a.translations, pageLang)
);
const isEmpty = articles.length === 0; const isEmpty = articles.length === 0;
--- ---
@ -58,7 +30,9 @@ const isEmpty = articles.length === 0;
<div itemscope itemtype="http://schema.org/Blog"> <div itemscope itemtype="http://schema.org/Blog">
{ {
!isEmpty ? ( !isEmpty ? (
articles.map((article) => article && <ArticleItem data={article} pageLang={pageLang} />) articles
.slice(0, limit)
.map((article) => article && <ArticleItem data={article} pageLang={pageLang} />)
) : ( ) : (
<EmptyList /> <EmptyList />
) )

View File

@ -1,25 +1,23 @@
--- ---
import type { MarkdownHeading } from "astro"; import type { MarkdownHeading } from "astro";
import type { CollectionEntry } from "astro:content";
import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/constants"; import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/constants";
import BaseHead from "@components/BaseHead.astro"; import BaseHead from "@components/BaseHead.astro";
import BaseLayout from "@components/BaseLayout.astro"; import BaseLayout from "@components/BaseLayout.astro";
import ArticleContent from "@components/ArticleContent.astro"; import ArticleContent from "@components/ArticleContent.astro";
import ArticlePost from "@components/ArticlePost.astro"; import ArticlePost from "@components/ArticlePost.astro";
import OpenGraph from "@components/OpenGraph.astro"; import OpenGraph from "@components/OpenGraph.astro";
import TableOfContents from "@root/components/TableOfContents.astro"; // import TableOfContents from "@root/components/TableOfContents.astro";
import type { ArticleTranslation } from "@/directus-schema"; import type { ArticleTranslation } from "@/directus-schema";
import * as utils from "@utils/article"; import * as utils from "@utils/article";
export interface Props { export interface Props {
lang?: string | undefined; lang?: string | undefined;
article: ArticleTranslation; article: ArticleTranslation;
availableOnLangs?: CollectionEntry<"blog">[];
headings: MarkdownHeading[]; headings: MarkdownHeading[];
} }
const canonicalUrl = new URL(Astro.url.pathname, Astro.site); const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const { lang = DEFAULT_LANGUAGE, article, availableOnLangs } = Astro.props as Props; const { lang = DEFAULT_LANGUAGE, article } = Astro.props as Props;
const { title, description, cover_image, content } = article; const { title, description, cover_image, content } = article;
const htmlContent = await utils.markdownToHTML(content); const htmlContent = await utils.markdownToHTML(content);
--- ---
@ -30,18 +28,18 @@ const htmlContent = await utils.markdownToHTML(content);
title={title} title={title}
siteName={SITE_METADATA.name} siteName={SITE_METADATA.name}
description={description} description={description}
type='article' type="article"
url={canonicalUrl.href} url={canonicalUrl.href}
image={cover_image image={cover_image
? { path: cover_image.filename_disk, width: "700", height: "400", type: cover_image.type } ? { path: cover_image.filename_disk, width: "700", height: "400", type: cover_image.type }
: undefined} : undefined}
/> />
<script is:inline defer src='/assets/js/load-code-highlight.js'></script> <script is:inline defer src="/assets/js/load-code-highlight.js"></script>
</BaseHead> </BaseHead>
<BaseLayout> <BaseLayout>
<div class='container'> <div class="container">
<ArticlePost lang={lang} article={article} availableOnLangs={availableOnLangs}> <ArticlePost lang={lang} article={article}>
<ArticleContent set:html={htmlContent} /> <ArticleContent set:html={htmlContent} />
</ArticlePost> </ArticlePost>
</div> </div>

View File

@ -1,15 +0,0 @@
---
import ArticleLayout from "@root/layouts/article.astro";
import { fetchArticleBySlug } from "@/directus";
const { path } = Astro.params as { blog: string; path: string };
const slug = path.split("/").at(-1);
if (!slug) return Astro.redirect("/404");
const article = await fetchArticleBySlug(slug);
if (!article) return Astro.redirect("/404");
---
<ArticleLayout lang={article.languages_code.code} article={article} />

View File

@ -0,0 +1,17 @@
---
import ArticleLayout from "@root/layouts/article.astro";
import { fetchArticleTranslationsBySlug, incArticleTranslationViews } from "@/directus";
const { url } = Astro.params as { blog: string; url: string };
const slug = url.split("/").at(-1);
if (!slug) return Astro.redirect("/404");
const article = await fetchArticleTranslationsBySlug(slug);
if (!article) return Astro.redirect("/404");
if (import.meta.env.PROD) await incArticleTranslationViews(article);
---
<ArticleLayout lang={article.languages_code.code} article={article} />

View File

@ -1,31 +1,19 @@
--- ---
import { getCollection } from "astro:content";
import { useTranslation, getLanguageFromURL } from "@i18n/utils"; import { useTranslation, getLanguageFromURL } from "@i18n/utils";
import * as utils from "@utils/index"; 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 directus from "@/directus"; import { fetchTags, fetchArticles } from "@/directus";
const t = useTranslation(Astro); const t = useTranslation(Astro);
const pageLang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
// const articles = await getCollection("blog", (e) => !e.data.draft);
// const filtered = utils.removeDuplicates(articles, pageLang);
const canonicalUrl = new URL(Astro.url.pathname, Astro.site); const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const tags = (await directus.query(`query Tag { tag { title } }`)).tag.map((t) => t.title); const tags = (await fetchTags()).map((t) => t.title);
const articles = (await fetchArticles()).flatMap((a) =>
const { article_translations } = await directus.query(` utils.removeArticleDuplicates(a.translations, pageLang)
query Article_translations { );
article_translations { const articleBlocks = utils.formatToArticleBlocks(articles);
languages_code {
code
}
slug
status
pub_date
}
}
`);
--- ---
<PageLayout <PageLayout
@ -39,7 +27,7 @@ const { article_translations } = await directus.query(`
<ArticlesList <ArticlesList
client:load client:load
i18n={{ "articles.empty": t("articles.empty"), "input.search": t("input.search") }} i18n={{ "articles.empty": t("articles.empty"), "input.search": t("input.search") }}
articles={utils.formatToArticleBlocks(article_translations)} articles={articleBlocks}
tags={tags} tags={tags}
pageLang={pageLang} pageLang={pageLang}
/> />

View File

@ -1,5 +1,4 @@
--- ---
import { getCollection } from "astro:content";
import PageLayout from "@layouts/default.astro"; import PageLayout from "@layouts/default.astro";
import Splash from "@components/Splash/Splash.astro"; import Splash from "@components/Splash/Splash.astro";
import SplashHeading from "@components/Splash/SplashHeading.astro"; import SplashHeading from "@components/Splash/SplashHeading.astro";
@ -23,11 +22,11 @@ const t = useTranslation(Astro);
<p><UIString key={"splash.description"} /></p> <p><UIString key={"splash.description"} /></p>
</SplashHeading> </SplashHeading>
<SplashLinks> <SplashLinks>
<SplashLink icon='information' href='/about/'><UIString key={"splash.about"} /></SplashLink> <SplashLink icon="information" href="/about/"><UIString key={"splash.about"} /></SplashLink>
<SplashLink icon='email_filled' href='/contacts/'> <SplashLink icon="email_filled" href="/contacts/">
<UIString key={"splash.contact"} /> <UIString key={"splash.contact"} />
</SplashLink> </SplashLink>
</SplashLinks> </SplashLinks>
</Splash> </Splash>
<RecentArticles /> <RecentArticles limit={5} />
</PageLayout> </PageLayout>

View File

@ -13,27 +13,27 @@ import rehypeStringify from "rehype-stringify";
import { escape } from "html-escaper"; import { escape } from "html-escaper";
import { h } from "hastscript"; import { h } from "hastscript";
import { toString } from "hast-util-to-string"; import { toString } from "hast-util-to-string";
import { selectAll } from "hast-util-select"; // import { selectAll } from "hast-util-select";
/** /**
* If there is a translation of an article in the current language of the page, * If there is a translation of an article in the current language of the page,
* the remaining articles with other translations should be removed. * the remaining articles with other translations should be removed.
*/ */
export function removeArticleDuplicates( export function removeArticleDuplicates(
articles: ArticleTranslation[] | null, articleTranslations: ArticleTranslation[] | null,
pageLang: LanguageKeys pageLang: LanguageKeys
): ArticleTranslation[] { ): ArticleTranslation[] {
if (!articles) return []; if (!articleTranslations) return [];
if (articles && articles.length >= 2) { if (articleTranslations && articleTranslations.length >= 2) {
const pageLangArticles = articles.filter((a) => { const pageLangArticles = articleTranslations.filter((a) => {
return ( return (
typeof a.languages_code === "object" && a.languages_code.code.split("-")[0] === pageLang typeof a.languages_code === "object" && a.languages_code.code.split("-")[0] === pageLang
); );
}); });
if (pageLangArticles.length !== 0) return pageLangArticles; if (pageLangArticles.length !== 0) return pageLangArticles;
return articles; return articleTranslations;
} }
return articles; return articleTranslations;
} }
/** /**
@ -50,8 +50,9 @@ export function createRelativeArticleUrl(
return `${lang}/${prefix}/${date}/${slug}`; return `${lang}/${prefix}/${date}/${slug}`;
} }
export function formatToArticleBlocks(articles: CollectionEntry<"blog">[]) { export function formatToArticleBlocks(articles: ArticleTranslation[]) {
const blocks: Record<string, any[]> = {}; const blocks: Record<string, ArticleTranslation[]> = {};
articles.forEach((a) => { articles.forEach((a) => {
const year = new Date(a.pub_date).getFullYear(); const year = new Date(a.pub_date).getFullYear();
if (!blocks[year]) blocks[year] = []; if (!blocks[year]) blocks[year] = [];
@ -59,7 +60,8 @@ export function formatToArticleBlocks(articles: CollectionEntry<"blog">[]) {
id: a.id, id: a.id,
title: a.title, title: a.title,
// tags: a.tags, // tags: a.tags,
pubDate: a.pub_date, slug: a.slug,
pub_date: a.pub_date,
}); });
}); });
return blocks; return blocks;