refactor 0_0

This commit is contained in:
Sergey Yarkov 2023-02-17 20:48:22 +03:00
parent 001e8eba08
commit 943a074058
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
49 changed files with 756 additions and 1574 deletions

View File

@ -3,7 +3,6 @@ import { defineConfig } from "astro/config";
/** /**
* Integrations * Integrations
*/ */
// import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import solidJs from "@astrojs/solid-js"; import solidJs from "@astrojs/solid-js";
import image from "@astrojs/image"; import image from "@astrojs/image";
@ -65,28 +64,31 @@ export default defineConfig({
* Articles * Articles
*/ */
{ {
name: "posts", name: "articles",
label: "Публикации", label: "Публикации",
label_singular: "публикацию", label_singular: "публикацию",
description: description:
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой", "Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой",
folder: "src/content/blog", folder: "src/content/blog",
slug: "{{year}}-{{month}}-{{day}}_{{slug}}", slug: "{{slug}}",
create: true, create: true,
delete: true, delete: true,
i18n: true,
fields: [ fields: [
{ {
name: "coverImage", name: "coverImage",
widget: "image", widget: "image",
label: "Постер", label: "Постер",
required: false,
i18n: true,
}, },
{ {
name: "draft", name: "draft",
label: "В черновике", label: "В черновике",
widget: "boolean", widget: "boolean",
required: true,
default: true,
hint: "Опубликована ли статья", hint: "Опубликована ли статья",
i18n: true,
required: false,
}, },
{ {
name: "author", name: "author",
@ -95,19 +97,19 @@ export default defineConfig({
search_fields: ["firstName", "lastName"], search_fields: ["firstName", "lastName"],
collection: "authors", collection: "authors",
label: "Автор", label: "Автор",
required: true, i18n: true,
}, },
{ {
name: "title", name: "title",
widget: "string", widget: "string",
label: "Заголовок", label: "Заголовок",
required: true, i18n: true,
}, },
{ {
name: "description", name: "description",
widget: "string", widget: "string",
label: "Описание", label: "Описание",
required: true, i18n: true,
}, },
{ {
name: "tags", name: "tags",
@ -117,25 +119,27 @@ export default defineConfig({
multiple: true, multiple: true,
value_field: "title", value_field: "title",
search_fields: ["title"], search_fields: ["title"],
i18n: true,
}, },
{ {
name: "pubDate", name: "pubDate",
widget: "datetime", widget: "datetime",
label: "Дата публикации", label: "Дата публикации",
required: true,
picker_utc: true, picker_utc: true,
i18n: true,
}, },
{ {
name: "updatedDate", name: "updatedDate",
widget: "datetime", widget: "datetime",
label: "Дата обновления", label: "Дата обновления",
required: true,
picker_utc: true, picker_utc: true,
i18n: true,
}, },
{ {
name: "body", name: "body",
widget: "markdown", widget: "markdown",
label: "Содержание", label: "Содержание",
i18n: true,
}, },
], ],
}, },
@ -171,24 +175,28 @@ export default defineConfig({
folder: "src/content/authors", folder: "src/content/authors",
create: true, create: true,
delete: true, delete: true,
i18n: true,
fields: [ fields: [
{ {
label: "Имя", label: "Имя",
name: "firstName", name: "firstName",
widget: "string", widget: "string",
required: true, required: true,
i18n: true,
}, },
{ {
label: "Фамилия", label: "Фамилия",
name: "lastName", name: "lastName",
widget: "string", widget: "string",
required: true, required: true,
i18n: true,
}, },
{ {
label: "Об авторе", label: "Об авторе",
name: "body", name: "body",
widget: "markdown", widget: "markdown",
required: false, required: false,
i18n: true,
}, },
], ],
}, },
@ -199,6 +207,7 @@ export default defineConfig({
name: "projects", name: "projects",
label: "Проекты", label: "Проекты",
label_singular: "проект", label_singular: "проект",
description: "Основные проекты, которые были разработаны в выложены в открытый доступ",
folder: "src/content/projects", folder: "src/content/projects",
create: true, create: true,
delete: true, delete: true,
@ -211,18 +220,9 @@ export default defineConfig({
required: true, required: true,
i18n: true, i18n: true,
}, },
{
name: "draft",
label: "В черновике",
widget: "boolean",
required: true,
i18n: true,
default: true,
},
{ {
name: "title", name: "title",
label: "Название", label: "Название",
default: "my-project",
widget: "string", widget: "string",
required: true, required: true,
i18n: true, i18n: true,
@ -231,7 +231,6 @@ export default defineConfig({
name: "year", name: "year",
label: "В каком году разработал", label: "В каком году разработал",
widget: "number", widget: "number",
default: 2023,
required: true, required: true,
i18n: true, i18n: true,
}, },
@ -308,9 +307,9 @@ export default defineConfig({
label: "Настройки", label: "Настройки",
files: [ files: [
{ {
name: "site-settings", name: "siteconfig",
label: "Настройки веб-сайта", label: "Настройки веб-сайта",
file: "src/content/site-settings.md", file: "siteconfig.json",
fields: [ fields: [
{ {
label: "Глобальный заголовок", label: "Глобальный заголовок",
@ -320,8 +319,9 @@ export default defineConfig({
}, },
{ {
label: "В разработке", label: "В разработке",
name: "isUnderConstruction", name: "isSiteUnderConstruction",
widget: "boolean", widget: "boolean",
required: false,
default: false, default: false,
hint: "Отображает баннер на всех старницах о том, что веб-сайт находится в разработке", hint: "Отображает баннер на всех старницах о том, что веб-сайт находится в разработке",
}, },

1178
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,11 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/image": "^0.14.1", "@astrojs/image": "^0.14.1",
"@astrojs/mdx": "^0.16.0",
"@astrojs/rss": "^2.1.0", "@astrojs/rss": "^2.1.0",
"@astrojs/sitemap": "^1.0.1", "@astrojs/sitemap": "^1.0.1",
"@astrojs/solid-js": "^2.0.2", "@astrojs/solid-js": "^2.0.2",
"@netlify/functions": "^1.4.0", "@netlify/functions": "^1.4.0",
"@sindresorhus/slugify": "^2.2.0",
"astro": "^2.0.10", "astro": "^2.0.10",
"astro-icon": "^0.8.0", "astro-icon": "^0.8.0",
"astro-netlify-cms": "^0.5.3", "astro-netlify-cms": "^0.5.3",

7
siteconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"globalTitle": "Sergey Yarkov",
"articles": {
"recentLimit": 5
},
"isSiteUnderConstruction": false
}

View File

@ -38,6 +38,10 @@ const { url, id } = Astro.props as Props;
window.addEventListener( window.addEventListener(
"onthemetoggled", "onthemetoggled",
() => () =>
document.readyState == "complete" && setTimeout(() => DISQUS.reset({ reload: true, config: disqus_config }), 100) document.readyState == "complete" &&
setTimeout(
() => "DISQUS" in window && DISQUS.reset({ reload: true, config: disqus_config }),
100
)
); );
</script> </script>

View File

@ -0,0 +1,225 @@
<div itemprop="articleBody" class="article-content"><slot /></div>
<style lang="scss" is:global>
.article-content {
& h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 3rem;
margin-bottom: 1rem;
padding-bottom: 5px;
}
& p,
em,
q {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& p {
margin: 1.5rem 0;
}
& ul,
ol {
padding-left: 1.5rem;
line-height: var(--line-height-base);
margin: 1.5rem 0;
& li {
font-size: var(--font-size-lg);
line-height: var(--line-height-base);
margin-bottom: 0.5rem;
padding-left: 0.25rem;
}
}
& q {
font-style: italic;
quotes: "« " " »";
}
& a {
color: var(--font-color);
text-decoration: underline;
text-decoration-color: var(--color-blue-500);
text-underline-offset: 2.2px;
text-decoration-thickness: 2px;
font-weight: 500;
&:hover {
color: var(--color-blue-500);
text-decoration-thickness: 2px;
}
}
& figure {
margin: 0 0 1em;
& figcaption {
color: var(--color-gray-400);
}
}
& hr {
background-color: var(--border-color);
margin: 40px 0;
border: none;
height: 1px;
}
& pre {
font-size: var(--font-size-sm);
border-radius: var(--border-radius-md);
}
& :not(pre) > code {
-webkit-font-smoothing: subpixel-antialiased;
background: var(--background-inline-code);
border-radius: var(--border-radius-lg);
font-size: 1rem;
font-weight: 500;
font-family: "IBM Plex Mono", monospace;
padding: 0.2rem 0.3rem;
}
& figcaption {
font-style: italic;
font-size: 1em;
text-align: center;
margin: 0.7rem 0;
}
& .table-wrapper {
overflow-x: auto;
margin-bottom: 1.25rem;
}
& table {
display: table;
border-radius: var(--border-radius-md);
min-width: 100%;
font-size: var(--font-size-md);
border: 1px solid var(--border-color);
border-collapse: separate;
border-spacing: 0;
margin: 5px 0;
& thead th {
background-color: var(--background-card);
border-top-left-radius: var(--border-radius-md);
border-top-right-radius: var(--border-radius-md);
}
& td,
th {
border-bottom: 1px solid var(--border-color);
padding: 0.5rem;
text-align: left;
}
& tbody tr:last-child td {
border: none;
}
}
& li {
list-style: disc;
& li {
list-style-type: circle;
}
&::marker {
color: var(--color-blue-500);
font-size: 1.3rem;
line-height: var(--line-height-none);
}
}
& blockquote {
margin: 1.5rem 0;
font-style: italic;
border-left: 0.25rem solid var(--border-color);
padding-left: 1rem;
}
& .heading-wrapper {
border-bottom: 1px solid var(--border-color);
margin: 3rem 0 1rem 0;
padding-bottom: 0.35rem;
& h2 {
font-size: var(--font-size-2xl);
}
& h3 {
font-size: var(--font-size-xl);
}
& h4 {
font-size: var(--font-size-lg);
}
& h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
}
& .heading-wrapper {
display: flex;
align-items: center;
flex-direction: row-reverse;
justify-content: flex-end;
margin-inline-start: 1px;
& .anchor-link {
position: relative;
display: inline-flex;
text-decoration: none;
justify-content: center;
align-items: center;
opacity: 0;
& .anchor-icon {
position: absolute;
left: -25px;
top: -13px;
}
& svg {
vertical-align: middle;
}
}
& .anchor-label {
position: absolute;
width: 0;
height: 0;
padding: 0;
overflow: hidden;
white-space: nowrap;
}
&:hover {
& .anchor-link {
opacity: 1;
}
}
}
& .languages-available {
margin: 25px 0;
font-size: var(--font-size-lg);
}
}
@media screen and (max-width: 800px) {
.article-content {
& .heading-wrapper {
flex-direction: row;
justify-content: flex-start;
& .anchor-link {
& .anchor-icon {
left: 5px;
}
}
}
}
}
</style>

View File

@ -1,5 +0,0 @@
---
import "./ArticleContent.scss";
---
<div itemprop="articleBody" class="article-content"><slot /></div>

View File

@ -1,221 +0,0 @@
.article-content {
& h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 3rem;
margin-bottom: 1rem;
padding-bottom: 5px;
}
& p,
em,
q {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& p {
margin: 1.5rem 0;
}
& ul,
ol {
padding-left: 1.5rem;
line-height: var(--line-height-base);
margin: 1.5rem 0;
& li {
font-size: var(--font-size-lg);
line-height: var(--line-height-base);
margin-bottom: 0.5rem;
padding-left: 0.25rem;
}
}
& q {
font-style: italic;
quotes: "« " " »";
}
& a {
color: var(--font-color);
text-decoration: underline;
text-decoration-color: var(--color-blue-500);
text-underline-offset: 2.2px;
text-decoration-thickness: 2px;
font-weight: 500;
&:hover {
color: var(--color-blue-500);
text-decoration-thickness: 2px;
}
}
& figure {
margin: 0 0 1em;
& figcaption {
color: var(--color-gray-400);
}
}
& hr {
background-color: var(--border-color);
margin: 40px 0;
border: none;
height: 1px;
}
& pre {
font-size: var(--font-size-sm);
border-radius: var(--border-radius-md);
}
& :not(pre) > code {
-webkit-font-smoothing: subpixel-antialiased;
background: var(--background-inline-code);
border-radius: var(--border-radius-lg);
font-size: 1rem;
font-weight: 500;
font-family: "IBM Plex Mono", monospace;
padding: 0.2rem 0.3rem;
}
& figcaption {
font-style: italic;
font-size: 1em;
text-align: center;
margin: 0.7rem 0;
}
& .table-wrapper {
overflow-x: auto;
margin-bottom: 1.25rem;
}
& table {
display: table;
border-radius: var(--border-radius-md);
min-width: 100%;
font-size: var(--font-size-md);
border: 1px solid var(--border-color);
border-collapse: separate;
border-spacing: 0;
margin: 5px 0;
& thead th {
background-color: var(--background-card);
border-top-left-radius: var(--border-radius-md);
border-top-right-radius: var(--border-radius-md);
}
& td,
th {
border-bottom: 1px solid var(--border-color);
padding: 0.5rem;
text-align: left;
}
& tbody tr:last-child td {
border: none;
}
}
& li {
list-style: disc;
& li {
list-style-type: circle;
}
&::marker {
color: var(--color-blue-500);
font-size: 1.3rem;
line-height: var(--line-height-none);
}
}
& blockquote {
margin: 1.5rem 0;
font-style: italic;
border-left: 0.25rem solid var(--border-color);
padding-left: 1rem;
}
& .heading-wrapper {
border-bottom: 1px solid var(--border-color);
margin: 3rem 0 1rem 0;
padding-bottom: 0.35rem;
& h2 {
font-size: var(--font-size-2xl);
}
& h3 {
font-size: var(--font-size-xl);
}
& h4 {
font-size: var(--font-size-lg);
}
& h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
}
& .heading-wrapper {
display: flex;
align-items: center;
flex-direction: row-reverse;
justify-content: flex-end;
margin-inline-start: 1px;
& .anchor-link {
position: relative;
display: inline-flex;
text-decoration: none;
justify-content: center;
align-items: center;
opacity: 0;
& .anchor-icon {
position: absolute;
left: -25px;
top: -13px;
}
& svg {
vertical-align: middle;
}
}
& .anchor-label {
position: absolute;
width: 0;
height: 0;
padding: 0;
overflow: hidden;
white-space: nowrap;
}
&:hover {
& .anchor-link {
opacity: 1;
}
}
}
& .languages-available {
margin: 25px 0;
font-size: var(--font-size-lg);
}
}
@media screen and (max-width: 800px) {
.article-content {
& .heading-wrapper {
flex-direction: row;
justify-content: flex-start;
& .anchor-link {
& .anchor-icon {
left: 5px;
}
}
}
}
}

View File

@ -0,0 +1,34 @@
---
import { Picture } from "@astrojs/image/components";
export interface Props {
src: string;
alt?: string;
}
const { src, alt } = Astro.props as Props;
---
<div class="cover">
<figure>
<Picture
itemprop="image"
src={src}
sizes="(max-width: 450px) 450px, 700px"
widths={[450, 700]}
formats={["webp"]}
aspectRatio={"16:9"}
alt={alt ? alt : ""}
background={"chocolate"}
/>
</figure>
</div>
<style lang="scss">
.cover {
margin: 20px 0;
& figure {
margin: 0;
}
}
</style>

View File

@ -1,29 +1,44 @@
--- ---
import { getLanguageFromURL } from "@i18n/utils"; import type { CollectionEntry } from "astro:content";
import slugify from "@sindresorhus/slugify";
import { URL_BLOG_PREFIX } from "@root/pages/[...blog].astro";
import { DEFAULT_LANGUAGE } from "@root/constants"; import { DEFAULT_LANGUAGE } from "@root/constants";
import { getLanguageFromURL } from "@i18n/utils";
import { createDateFormatter } from "@root/utils"; import { createDateFormatter } from "@root/utils";
export interface Props { export interface Props {
href: string; entry: CollectionEntry<"blog">;
title: string;
date: Date;
} }
const lang = getLanguageFromURL(Astro.url.pathname); const pageLang = getLanguageFromURL(Astro.url.pathname);
// const isNonDefaultLanguage = DEFAULT_LANGUAGE !== lang; const {
entry: {
const { href, title, date } = Astro.props as Props; id,
data: { title, pubDate },
},
} = Astro.props as Props;
const slug = slugify(title, { lowercase: true });
const articleLang = id.split("/")[0];
--- ---
<article itemprop="blogPosts" itemscope itemtype="http://schema.org/BlogPosting" class="article-item"> <article
<a href={href}> itemprop="blogPosts"
itemscope
itemtype="http://schema.org/BlogPosting"
class="article-item"
>
<a
href={articleLang === DEFAULT_LANGUAGE
? `/${URL_BLOG_PREFIX}/${pubDate.toISOString().split("T")[0]}/${slug}`
: `/${articleLang}/${URL_BLOG_PREFIX}/${pubDate.toISOString().split("T")[0]}/${slug}`}
>
<div class="flex"> <div class="flex">
<h3 itemprop="headline">{title}</h3> <h3 itemprop="headline">{title}</h3>
<!-- {isNonDefaultLanguage && <sup>{lang.toLocaleUpperCase()}</sup>} --> {articleLang !== pageLang && <sup>{articleLang.toLocaleUpperCase()}</sup>}
</div> </div>
<p> <p>
<time datetime={date.toString()}> <time datetime={pubDate.toISOString()}>
{createDateFormatter(lang).format(date)} {createDateFormatter(pageLang).format(pubDate)}
</time> </time>
</p> </p>
</a> </a>

View File

@ -1,23 +1,24 @@
--- ---
import { Picture } from "@astrojs/image/components"; import type { CollectionEntry } from "astro:content";
import { Icon } from "astro-icon"; import { Icon } from "astro-icon";
import { createDateFormatter } from "@root/utils"; import { createDateFormatter } from "@root/utils";
// import { isURL } from "@utils/string"; import ArticleCover from "@components/ArticleCover.astro";
import ArticleComments from "@components/ArticleComments.astro"; import ArticleComments from "@components/ArticleComments.astro";
// import TranslatedLink from "../TranslatedLink"; // import TranslatedLink from "../TranslatedLink";
import type { CollectionEntry } from "astro:content"; // import { useTranslation } from "@i18n/utils";
// import { useTranslation } from "@root/i18n/utils";
import "./ArticlePost.scss";
export type Props = { export type Props = {
article: CollectionEntry<"blog">; article: CollectionEntry<"blog">;
lang: string; lang: string;
}; };
const { article, lang } = Astro.props as Props; const { article, lang } = Astro.props as Props;
const canonicalUrl = new URL(Astro.url.pathname, Astro.site); const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const { const {
data: { coverImage, title, author, description, tags, pubDate }, data: { coverImage, title, author, description, tags, pubDate },
slug, slug,
} = article; } = article;
// const t = useTranslation(Astro); // const t = useTranslation(Astro);
--- ---
@ -53,24 +54,7 @@ const {
> >
<span itemprop="author">{author}</span> <span itemprop="author">{author}</span>
</div> </div>
{ {coverImage && <ArticleCover src={coverImage} alt={title} />}
coverImage && (
<div class="cover">
<figure>
<Picture
itemprop="image"
src={coverImage}
sizes="(max-width: 450px) 450px, 700px"
widths={[450, 700]}
formats={["webp"]}
aspectRatio={"16:9"}
alt={title}
background={"chocolate"}
/>
</figure>
</div>
)
}
<div class="description"> <div class="description">
<p> <p>
{description} {description}
@ -81,8 +65,9 @@ const {
<ArticleComments url={canonicalUrl.href} id={slug} /> <ArticleComments url={canonicalUrl.href} id={slug} />
</article> </article>
<script define:vars={{ slug }}> <script define:vars={{ slug, IS_DEV: import.meta.env.DEV }}>
(async () => { (async () => {
if (IS_DEV) return;
const response = await fetch(`/api/views?slug=${slug}`, { method: "POST" }); const response = await fetch(`/api/views?slug=${slug}`, { method: "POST" });
if (!response.ok) return; if (!response.ok) return;
const views = await response.text(); const views = await response.text();
@ -91,3 +76,98 @@ const {
if ($views) $views.textContent = views; if ($views) $views.textContent = views;
})(); })();
</script> </script>
<style lang="scss" is:global>
.article {
& header {
& div:nth-child(1) {
display: flex;
justify-content: space-between;
align-items: center;
& .views {
display: flex;
align-items: center;
& span {
margin-left: 3.5px;
font-size: var(--font-size-sm);
color: var(--font-color-grayer);
}
}
}
& .heading {
margin-top: 1.125rem;
& h1 {
font-weight: 600;
font-size: 2.125rem;
}
& p {
font-size: 1.125rem;
line-height: var(--line-height-base);
}
}
& .tags {
font-size: var(--font-size-sm);
text-transform: uppercase;
& ul {
display: flex;
flex-wrap: wrap;
column-gap: 7px;
row-gap: 7px;
}
}
& .published {
margin-bottom: 1.25em;
color: var(--font-color-gray);
}
}
& .cover {
margin: 20px 0;
& figure {
margin: 0;
}
}
& .description p {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& footer {
margin-top: 5rem;
}
& img {
border-radius: var(--border-radius-lg);
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
&:not([src*=".svg"]) {
filter: var(--image-filter);
}
}
}
@media screen and (max-width: 560px) {
.article {
& header {
& div:nth-child(1) {
flex-direction: column;
align-items: flex-start;
& .tags {
margin-bottom: 15px;
}
}
& .heading h1 {
font-size: 2rem;
}
}
}
}
</style>

View File

@ -1,92 +0,0 @@
.article {
& header {
& div:nth-child(1) {
display: flex;
justify-content: space-between;
align-items: center;
& .views {
display: flex;
align-items: center;
& span {
margin-left: 3.5px;
font-size: var(--font-size-sm);
color: var(--font-color-grayer);
}
}
}
& .heading {
margin-top: 1.125rem;
& h1 {
font-weight: 600;
font-size: 2.125rem;
}
& p {
font-size: 1.125rem;
line-height: var(--line-height-base);
}
}
& .tags {
font-size: var(--font-size-sm);
text-transform: uppercase;
& ul {
display: flex;
flex-wrap: wrap;
column-gap: 7px;
row-gap: 7px;
}
}
& .published {
margin-bottom: 1.25em;
color: var(--font-color-gray);
}
}
& .cover {
margin: 20px 0;
& figure {
margin: 0;
}
}
& .description p {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& footer {
margin-top: 5rem;
}
& img {
border-radius: var(--border-radius-lg);
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
&:not([src*=".svg"]) {
filter: var(--image-filter);
}
}
}
@media screen and (max-width: 560px) {
.article {
& header {
& div:nth-child(1) {
flex-direction: column;
align-items: flex-start;
& .tags {
margin-bottom: 15px;
}
}
& .heading h1 {
font-size: 2rem;
}
}
}
}

View File

@ -9,7 +9,6 @@ export interface Props {
} }
const canonicalURL = new URL(Astro.url.pathname, Astro.site); const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description } = Astro.props; const { title, description } = Astro.props;
--- ---

View File

@ -2,11 +2,12 @@
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import ArticleItem from "@components/ArticleItem.astro"; import ArticleItem from "@components/ArticleItem.astro";
import EmptyList from "@components/EmptyList.astro"; import EmptyList from "@components/EmptyList.astro";
import siteconfig from "siteconfig.json";
export interface Props { export interface Props {
articles: CollectionEntry<"blog">[]; articles: CollectionEntry<"blog">[];
length?: number | undefined; length?: number | undefined;
} }
const { articles, length = 5 } = Astro.props as Props; const { articles, length = siteconfig.articles.recentLimit } = Astro.props as Props;
const isEmpty = articles.length === 0; const isEmpty = articles.length === 0;
--- ---
@ -19,11 +20,7 @@ const isEmpty = articles.length === 0;
<div itemscope itemtype="http://schema.org/Blog"> <div itemscope itemtype="http://schema.org/Blog">
{ {
!isEmpty ? ( !isEmpty ? (
articles articles.slice(0, length).map((article) => <ArticleItem entry={article} />)
.slice(0, length)
.map((article) => (
<ArticleItem href={`/articles/${article.slug}`} title={article.data.title} date={article.data.pubDate} />
))
) : ( ) : (
<EmptyList /> <EmptyList />
) )

View File

@ -0,0 +1,69 @@
---
import { Icon } from "astro-icon";
import type { CollectionEntry } from "astro:content";
export interface Props {
data: CollectionEntry<"skills">["data"];
}
const {
data: { title, list },
} = Astro.props as Props;
---
<div class="skills-block">
<div>
<h3>{title}</h3>
</div>
<div>
<ul>
{
list.map((entry) => (
<li>
{entry.icon && <Icon name={entry.icon} width="20" height="20" />}
{entry.title}
</li>
))
}
</ul>
</div>
</div>
<style lang="scss">
.skills-block {
& h3 {
font-size: var(--font-size-md);
color: var(--font-color-grayer);
font-weight: 500;
}
& ul {
margin: 0;
padding: 0;
list-style: none;
& li {
font-weight: 400;
margin-bottom: 10px;
font-size: var(--font-size-sm);
display: flex;
align-items: center;
column-gap: 5px;
}
}
}
@media screen and (max-width: 500px) {
.skills-block {
& h3 {
text-align: center;
}
& ul {
display: flex;
flex-direction: column;
align-items: center;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
---
---
<div class="skills-list"><slot /></div>
<style lang="scss">
.skills-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
column-gap: 40px;
row-gap: 30px;
margin-top: 5rem;
}
@media screen and (max-width: 500px) {
.skills-list {
display: flex;
flex-direction: column;
}
}
</style>

View File

@ -1,13 +1,10 @@
import siteconfig from "../siteconfig.json";
export const DEFAULT_LANGUAGE: LanguageKeys = "ru"; export const DEFAULT_LANGUAGE: LanguageKeys = "ru";
export const IS_SITE_UNDER_CONSTRUCTION = siteconfig.isSiteUnderConstruction;
/* Show banner about website development */
export const IS_SITE_UNDER_CONSTRUCTION =
import.meta.env.IS_SITE_UNDER_CONSTRUCTION === "true" ? true : false;
export const SUPPORTED_LANGUAGES: SupportedLanguages = ["ru", "en"]; export const SUPPORTED_LANGUAGES: SupportedLanguages = ["ru", "en"];
export const SITE_METADATA: SiteMetadataType = { export const SITE_METADATA: SiteMetadataType = {
title: "Sergey Yarkov", title: siteconfig.globalTitle,
name: "yarkov.tech", name: "yarkov.tech",
author: "Sergey Yarkov", author: "Sergey Yarkov",
themeColor: "#1e2327", themeColor: "#1e2327",

View File

@ -0,0 +1,24 @@
---
firstName: Sergey
lastName: Yarkov
---
My name is Sergey and I am a web developer. My main activity is creating web sites. I am interested in areas such as programming, embedded systems, and electronics.
I started making web sites in 2017, starting with simple HTML pages, and in 2019 I started learning programming. At present, I have experience in **full-stack** web development of client-server applications that solve a wide range of tasks.
Some of my personal [projects](/projects/) I put in public access on [Github](https://github.com/sergeyyarkov).
## Purpose of this site
I originally created this website for myself, to consolidate my knowledge by writing articles.
Since 2022, I have been posting materials on this website, and now at any time I can return to any topic and learn it again.
My articles can also help someone with mastering a particular topic. The main direction of this site is IT,
and [here](/articles/) I publish materials on topics that interest me, as well as talk about my [projects](/projects/).
I am absolutely calm about any criticism and I do not claim to be the best programmer in the world,
so do not judge the materials of this resource harshly.
## Skills
This list shows the skills and technologies that I have used/use in my projects.
For creating web projects, I use **Node.js** and the **Adonis.js** framework for the server-side.
For the client-side, I develop using **React.js** or **Alpine.js**.
<q>this website is made using the [**Astro.js**](https://astro.build) framework and [**Solid.js**](https://www.solidjs.com/) library.</q>

View File

@ -0,0 +1,28 @@
---
firstName: Сергей
lastName: Ярков
---
Мое имя **Сергей** я являюсь веб-разработчиком. Основной деятельностью моей работы является создание веб-сайтов.
Мне интересны такие сферы как программирование, встраиваемые системы и электроника.
Веб-сайты я начал делать с 2017 года начиная с верстки простых HTML страничек и с 2019 года я начал
обучаться программированием и на сегодняшний момент я имею опыт в **full-stack** веб-разработке клиент-серверных приложений,
которые решают самые разные задачи.
Некоторые из своих личных [проектов](/projects/) я выкладываю в общий доступ на [Github](https://github.com/sergeyyarkov).
## Зачем этот сайт
Первоначально я создал этот сайт для себя, чтобы закреплять свои знания путем написания статей. С 2022 года я начал
выкладывать материалы на этот сайт и сейчас в любой момент я могу вернуться к какой-либо теме и еще раз её усвоить.
Мои статьи также смогут кому-то помочь с освоением какой-либо темы.
Главным направлением этого сайта является IT, и [здесь](/articles/) я публикую материалы на темы, которые меня интересуют,
а также рассказываю о своих [проектах](/projects/).
Я отношусь абсолютно спокойно к любой критике и я не претендую на звание лучшего программиста в мире, поэтому
не судите строго материалы этого ресурса.
## Навыки
Этот список показывает имеющиеся у меня навыки и технологии которые я когда-либо использовал/использую в своих проектах.
Для создания веб-проектов в качестве серверной части я использую **Node.js** и фреймворк **Adonis.js**.
Клиентскую часть я разрабатываю на **React.js** или **Alpine.js**.
<q>этот веб-сайт сделан с помощью фреймворка **[Astro.js](https://astro.build)** и библиотеки **[Solid.js](https://www.solidjs.com/)**</q>

View File

@ -1,4 +0,0 @@
---
firstName: Sergey
lastName: Yarkov
---

View File

@ -0,0 +1,17 @@
---
coverImage: /media/serial-port.jpg
draft: false
author: Sergey Yarkov
title: Using the WebSerial API to communicate with the microcontroller
description: We will talk about what is WebSerial API, how it can be used in a
browser to read and write data to a serial device and where it can be applied.
tags:
- JavaScript
- AVR
- WebSerial API
- Tutorial
pubDate: 2022-11-04T21:00:00.000Z
updatedDate: 2023-02-13T16:34:23.326Z
---
# ...

View File

@ -0,0 +1,16 @@
---
coverImage: null
draft: false
author: Sergey Yarkov
title: 74HC595 shift register control by microcontroller
description: This tutorial shows how to control the shift register of the
74HC595 in conjunction with the AVR microcontroller.
tags:
- AVR
- Digital Electronics
- Tutorial
pubDate: 2023-02-14T19:23:55.622Z
updatedDate: 2023-02-14T19:23:55.646Z
---
...

View File

@ -0,0 +1,15 @@
---
draft: false
author: Sergey Yarkov
title: Управление сдвиговым регистром 74HC595 микрокотнороллером
description: В этом туториале рассматривается то, как управлять сдвиговым
регистром 74HC595 в связке с микроконтроллером AVR.
tags:
- AVR
- Digital Electronics
- Tutorial
pubDate: 2023-02-14T19:23:55.622Z
updatedDate: 2023-02-14T19:23:55.646Z
---
...

View File

@ -3,7 +3,8 @@ import { defineCollection, z } from "astro:content";
const blog = defineCollection({ const blog = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
coverImage: z.string().optional(), draft: z.boolean().optional(),
coverImage: z.string().nullable().optional(),
author: z.string(), author: z.string(),
tags: z.string().array().optional(), tags: z.string().array().optional(),
description: z.string(), description: z.string(),
@ -16,7 +17,6 @@ const blog = defineCollection({
.or(z.date()) .or(z.date())
.optional() .optional()
.transform((str) => (str ? new Date(str) : undefined)), .transform((str) => (str ? new Date(str) : undefined)),
draft: z.boolean(),
}), }),
}); });
@ -42,7 +42,6 @@ const projects = defineCollection({
sourceUrl: z.string(), sourceUrl: z.string(),
demoUrl: z.string().nullable().optional(), demoUrl: z.string().nullable().optional(),
articleUrl: z.string().nullable().optional(), articleUrl: z.string().nullable().optional(),
draft: z.boolean(),
}), }),
}); });

View File

@ -1,6 +1,5 @@
--- ---
icon: triangle_filled icon: triangle_filled
draft: false
title: devthread title: devthread
year: 2021 year: 2021
description: Static website for publishing various articles, developed with description: Static website for publishing various articles, developed with

View File

@ -1,6 +1,5 @@
--- ---
icon: round-shop-two icon: round-shop-two
draft: false
title: digital-store title: digital-store
year: 2019 year: 2019
description: Customizable online store with control panel for selling digital goods description: Customizable online store with control panel for selling digital goods

View File

@ -1,6 +1,5 @@
--- ---
icon: round-school icon: round-school
draft: false
title: educt title: educt
year: 2022 year: 2022
description: Learning Management System (LMS) to publish your courses, lectures, lessons description: Learning Management System (LMS) to publish your courses, lectures, lessons

View File

@ -1,6 +1,5 @@
--- ---
icon: chip icon: chip
draft: false
title: memory-led-game title: memory-led-game
year: 2022 year: 2022
description: Analogue of the popular game "Simon says" on the AVR microcontroller description: Analogue of the popular game "Simon says" on the AVR microcontroller

View File

@ -1,6 +1,5 @@
--- ---
icon: nodejs icon: nodejs
draft: false
title: node-docker-example title: node-docker-example
year: 2022 year: 2022
description: An example of using Docker to deploy a Node.js application description: An example of using Docker to deploy a Node.js application

View File

@ -1,6 +1,5 @@
--- ---
icon: chat icon: chat
draft: false
title: react-chat-app title: react-chat-app
year: 2021 year: 2021
description: Client-server web chat application developed with Node.js and Socket.io description: Client-server web chat application developed with Node.js and Socket.io

View File

@ -1,6 +1,5 @@
--- ---
icon: serial icon: serial
draft: false
title: web-serial-example title: web-serial-example
year: 2022 year: 2022
description: An example of using the Web Serial API to interact with the description: An example of using the Web Serial API to interact with the

View File

@ -1,6 +1,5 @@
--- ---
icon: book icon: book
draft: false
title: yarkov.tech title: yarkov.tech
year: 2023 year: 2023
description: My personal website where you can find information about me and a description: My personal website where you can find information about me and a

View File

@ -1,6 +1,5 @@
--- ---
icon: triangle_filled icon: triangle_filled
draft: false
title: devthread title: devthread
year: 2021 year: 2021
description: Статический веб-сайт для публикации различных статей, разработанный description: Статический веб-сайт для публикации различных статей, разработанный

View File

@ -1,6 +1,5 @@
--- ---
icon: round-shop-two icon: round-shop-two
draft: false
title: digital-store title: digital-store
year: 2019 year: 2019
description: Настраиваемый интернет-магазин с панелью управления для продажи description: Настраиваемый интернет-магазин с панелью управления для продажи

View File

@ -1,6 +1,5 @@
--- ---
icon: round-school icon: round-school
draft: false
title: educt title: educt
year: 2022 year: 2022
description: Система управления обучением (LMS) для публикации ваших курсов, лекций, уроков description: Система управления обучением (LMS) для публикации ваших курсов, лекций, уроков

View File

@ -1,6 +1,5 @@
--- ---
icon: chip icon: chip
draft: false
title: memory-led-game title: memory-led-game
year: 2022 year: 2022
description: Аналог популярной игры "Саймон говорит" на AVR микроконтроллере description: Аналог популярной игры "Саймон говорит" на AVR микроконтроллере

View File

@ -1,6 +1,5 @@
--- ---
icon: nodejs icon: nodejs
draft: false
title: node-docker-example title: node-docker-example
year: 2022 year: 2022
description: Пример использования Docker для развертывания Node.js приложения description: Пример использования Docker для развертывания Node.js приложения

View File

@ -1,6 +1,5 @@
--- ---
icon: chat icon: chat
draft: false
title: react-chat-app title: react-chat-app
year: 2021 year: 2021
description: Клиент-серверное веб приложение чата разработанное при помощи description: Клиент-серверное веб приложение чата разработанное при помощи

View File

@ -1,6 +1,5 @@
--- ---
icon: serial icon: serial
draft: false
title: web-serial-example title: web-serial-example
year: 2022 year: 2022
description: Пример использования Web Serial API для взаимодействия с description: Пример использования Web Serial API для взаимодействия с

View File

@ -1,6 +1,5 @@
--- ---
icon: book icon: book
draft: false
title: yarkov.tech title: yarkov.tech
year: 2023 year: 2023
description: Мой персональный веб-сайт где находятся информация обо мне и список description: Мой персональный веб-сайт где находятся информация обо мне и список

View File

@ -1,5 +0,0 @@
---
globalTitle: yarkov.tech
articles:
recentLimit: 5
---

View File

@ -3,8 +3,8 @@ 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/ArticleContent.astro"; import ArticleContent from "@components/ArticleContent.astro";
import ArticlePost from "@components/ArticlePost/ArticlePost.astro"; import ArticlePost from "@components/ArticlePost.astro";
import OpenGraph from "@components/OpenGraph.astro"; import OpenGraph from "@components/OpenGraph.astro";
export interface Props { export interface Props {

36
src/pages/[...blog].astro Normal file
View File

@ -0,0 +1,36 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import ArticleLayout from "@root/layouts/article.astro";
import { DEFAULT_LANGUAGE } from "@root/constants";
import slugify from "@sindresorhus/slugify";
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
* and `http://website.com/{language-code}/blog/2023-01-01/my-article` with other languages.
*/
export async function getStaticPaths() {
const collection = await getCollection("blog", (e) => !e.data.draft);
return collection.map((article) => {
const [languageCode] = article.slug.split("/");
const slug = slugify(article.data.title, { lowercase: true });
const pubDate = article.data.pubDate.toISOString().split("T")[0];
const url =
DEFAULT_LANGUAGE !== languageCode
? `${languageCode}/${URL_BLOG_PREFIX}/${pubDate}/${slug}`
: `${URL_BLOG_PREFIX}/${pubDate}/${slug}`;
return { params: { blog: url }, props: { article } };
});
}
type Props = { article: CollectionEntry<"blog"> };
const { Content } = await Astro.props.article.render();
---
<ArticleLayout article={Astro.props.article}>
<Content />
</ArticleLayout>

View File

@ -1,22 +0,0 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import ArticleLayout from "@root/layouts/article.astro";
export async function getStaticPaths() {
const articles = await getCollection("blog");
return articles
.filter((a) => !a.data.draft)
.map((article) => ({
params: { slug: `articles/${article.slug}` },
props: { article },
}));
}
type Props = { article: CollectionEntry<"blog"> };
const { Content } = await Astro.props.article.render();
---
<ArticleLayout article={Astro.props.article}>
<Content />
</ArticleLayout>

View File

@ -3,6 +3,7 @@ import { getLanguageKeys } from "@i18n/utils";
import { DEFAULT_LANGUAGE } from "@root/constants"; import { DEFAULT_LANGUAGE } from "@root/constants";
import About from "@templates/About.astro"; import About from "@templates/About.astro";
// TODO: do not generate pages without required data
export function getStaticPaths() { export function getStaticPaths() {
return getLanguageKeys() return getLanguageKeys()
.filter((lang) => lang !== DEFAULT_LANGUAGE) .filter((lang) => lang !== DEFAULT_LANGUAGE)

View File

@ -1,26 +1,28 @@
--- ---
import PageLayout from "@layouts/default.astro"; import { getCollection, getEntryBySlug } from "astro:content";
import { getLanguageFromURL } from "@i18n/utils"; import { getLanguageFromURL } from "@i18n/utils";
// import { useTranslation } from "@i18n/utils"; import PageLayout from "@layouts/default.astro";
// import * as API from "@root/lib/api";
import PageHeading from "@components/PageHeading.astro"; import PageHeading from "@components/PageHeading.astro";
// import SkillsList from "@components/SkillsList"; import SkillsList from "@components/SkillsList.astro";
// import SkillsBlock from "@components/SkillsBlock"; import SkillsBlock from "@components/SkillsBlock.astro";
// import ArticleContent from "@components/ArticleContent"; import ArticleContent from "@components/ArticleContent.astro";
const lang = getLanguageFromURL(Astro.url.pathname);
const pageLang = getLanguageFromURL(Astro.url.pathname);
const skills = await getCollection("skills", (e) => e.id.startsWith(`${pageLang}/`));
const about = await getEntryBySlug("authors", `${pageLang}/сергей-ярков`);
const data = await about?.render();
// const t = useTranslation(Astro); // const t = useTranslation(Astro);
// const data = await API.fetchAboutMe(Astro);
// const { skills } = SITE_METADATA;
--- ---
<PageLayout noIndex lang={lang} title={"Обо мне"}> <PageLayout noIndex lang={pageLang} title={"Обо мне"}>
<PageHeading heading={"Обо мне"} /> <PageHeading heading={"Обо мне"} />
<section> <section>
<!-- <ArticleContent> <ArticleContent>
{data && <data.Content />} {data && <data.Content />}
</ArticleContent> --> </ArticleContent>
<!-- <SkillsList> <SkillsList>
{skills.map((block) => <SkillsBlock data={block} />)} {skills.map((block) => <SkillsBlock data={block.data} />)}
</SkillsList> --> </SkillsList>
</section> </section>
</PageLayout> </PageLayout>

View File

@ -1,15 +1,15 @@
--- ---
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 "@root/components/Splash/SplashHeading.astro"; import SplashHeading from "@root/components/Splash/SplashHeading.astro";
import SplashLinks from "@root/components/Splash/SplashLinks.astro"; import SplashLinks from "@root/components/Splash/SplashLinks.astro";
import SplashLink from "@root/components/Splash/SplashLink.astro"; import SplashLink from "@root/components/Splash/SplashLink.astro";
import { getCollection } from "astro:content";
import RecentArticles from "@root/components/RecentArticles.astro"; import RecentArticles from "@root/components/RecentArticles.astro";
const articles = (await getCollection("blog")) const articles = (await getCollection("blog", (e) => !e.data.draft)).sort(
.filter((p) => !p.data.draft) (a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
.sort((a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()); );
--- ---
<PageLayout description="Home page" lang={"ru"}> <PageLayout description="Home page" lang={"ru"}>

View File

@ -8,6 +8,6 @@ const lang = getLanguageFromURL(Astro.url.pathname);
<PageLayout lang={lang}> <PageLayout lang={lang}>
<div class="container"> <div class="container">
<h1 style="font-size:var(--font-size-5xl);">404</h1> <h1 style="font-size:var(--font-size-5xl);">404</h1>
<p>{"Страница не найдена"}</p> <p>{"Страницу которую вы пытаетесь найти не существует."}</p>
</div> </div>
</PageLayout> </PageLayout>