refactor: moved some elements into components

This commit is contained in:
Sergey Yarkov 2022-07-19 01:03:31 +03:00
parent d0e299e194
commit 85e854cc71
49 changed files with 701 additions and 677 deletions

View File

@ -1,5 +1,5 @@
{
"printWidth": 150,
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,

View File

@ -8,7 +8,7 @@ Personal website created with Astro.
### Todo
- [x] HTML layout
- [ ] Adaptive & Responsive layout
- [x] Adaptive & Responsive layout
- [ ] Dark/Light Theme
- [ ] Multilang web-site
- [ ] Form handling via Netlify Forms

View File

@ -1,31 +1,31 @@
{
"name": "yarkov.tech",
"version": "0.0.1",
"private": true,
"description": "Personal website created with Astro.",
"author": {
"name": "Sergey Yarkov",
"email": "serzh.yarkov@gmail.com",
"url": "https://yarkov.tech"
},
"license": "MIT",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"devDependencies": {
"@astrojs/solid-js": "^0.2.1",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"astro": "^1.0.0-beta.63",
"astro-icon": "^0.7.1",
"eslint": "^8.19.0",
"eslint-plugin-solid": "^0.7.1",
"prettier": "^2.7.1",
"prettier-plugin-astro": "^0.1.1",
"sass": "^1.53.0",
"solid-js": "^1.4.7"
}
"name": "yarkov.tech",
"version": "0.0.1",
"private": true,
"description": "Personal website created with Astro.",
"author": {
"name": "Sergey Yarkov",
"email": "serzh.yarkov@gmail.com",
"url": "https://yarkov.tech"
},
"license": "MIT",
"scripts": {
"dev": "astro dev --host",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview --host"
},
"devDependencies": {
"@astrojs/solid-js": "^0.2.1",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"astro": "^1.0.0-beta.63",
"astro-icon": "^0.7.1",
"eslint": "^8.19.0",
"eslint-plugin-solid": "^0.7.1",
"prettier": "^2.7.1",
"prettier-plugin-astro": "^0.1.1",
"sass": "^1.53.0",
"solid-js": "^1.4.7"
}
}

View File

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

View File

@ -0,0 +1,84 @@
.article-content {
& p,
& em,
& q {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& ul {
padding-left: 1.5rem;
line-height: var(--line-height-base);
margin-bottom: 1.5rem;
margin-top: 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: '«' '»';
}
& h1,
& h2,
& h3,
& h4 {
margin-top: 3rem;
margin-bottom: 1rem;
padding-bottom: 0.35rem;
}
& h1:not(:first-child),
& h2:not(:first-child),
& h3:not(:first-child),
& h4:not(:first-child) {
margin-top: 3rem;
}
& h2 {
font-size: var(--font-size-2xl);
border-bottom: 1px solid var(--border-color);
}
& h3 {
font-size: var(--font-size-xl);
}
& h4 {
font-size: var(--font-size-lg);
}
& a {
color: var(--color-blue-500);
text-underline-offset: 2.5px;
text-decoration-color: var(--color-blue-500);
font-weight: 500;
&:hover {
color: var(--color-blue-500);
text-decoration-thickness: 2px;
}
}
& figure {
margin: 0 0 1em;
}
& figcaption {
font-style: italic;
font-size: 1em;
text-align: center;
margin: 0.7rem 0;
}
& img {
display: block;
border-radius: var(--border-radius-md);
max-width: 100%;
margin: 0 auto;
}
}

View File

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

View File

@ -6,7 +6,6 @@
justify-content: space-between;
align-items: center;
column-gap: 10px;
row-gap: 5px;
color: var(--font-color);
padding: 8px 15px;
& h3 {
@ -22,6 +21,7 @@
margin: 0;
text-align: right;
white-space: nowrap;
margin-bottom: 10px;
}
&:hover {
text-decoration: none;
@ -34,6 +34,7 @@
@media screen and (max-width: 560px) {
.article-item {
margin-bottom: 10px;
& a {
flex-direction: column-reverse;
align-items: flex-start;

View File

@ -1,11 +1,11 @@
import type { IArticle } from '@root/interfaces';
import type { ArticleType } from '@root/types';
import './ArticleItem.scss';
import { Component } from 'solid-js';
import { DateFormatter } from '@utils/date';
export type ArticleItemProps = {
data: IArticle;
data: ArticleType;
};
export const ArticleItem: Component<ArticleItemProps> = (props) => {

View File

@ -1,97 +1,85 @@
import "./ArticlesList.scss";
import { Component, createSignal, Show, For } from "solid-js";
import { PageHeading } from "@components/PageHeading";
import type { IArticle } from "@root/interfaces";
import * as utils from "@root/utils/articles";
import { ArticleItem } from "../ArticleItem";
import './ArticlesList.scss';
import { Component, createSignal, Show, For } from 'solid-js';
import type { ArticleType } from '@root/types';
import PageHeading from '@components/PageHeading';
import * as utils from '@root/utils/articles';
import { ArticleItem } from '../ArticleItem';
export type ArticlesListProps = {
data: IArticle[];
tags: string[];
data: ArticleType[];
tags: string[];
};
export const ArticlesList: Component<ArticlesListProps> = (props) => {
const [articles, setArticles] = createSignal(utils.filterArticlesByYear(props.data));
const [selectedTags, setSelectedTags] = createSignal<string[]>([]);
const [filteredArticles, setFilteredArticles] = createSignal<IArticle[]>([]);
const [articles, setArticles] = createSignal(utils.filterArticlesByYear(props.data));
const [selectedTags, setSelectedTags] = createSignal<string[]>([]);
const [filteredArticles, setFilteredArticles] = createSignal<ArticleType[]>([]);
return (
<section class="articles container">
<PageHeading
heading="Статьи"
description="Технические статьи, туториалы и различные справочные материалы которые
return (
<section class="articles container">
<PageHeading
heading="Статьи"
description="Технические статьи, туториалы и различные справочные материалы которые
связаны с IT тематикой находятся здесь."
/>
<div class="articles-search">
<svg
width="25"
height="24"
viewBox="0 0 25 24"
xmlns="http://www.w3.org/2000/svg">
<path
d="M10.6026 3.60001C11.8798 3.59993 13.1316 3.94283 14.2172 4.5901C15.3028 5.23736 16.179 6.16331 16.7472 7.26368C17.3154 8.36405 17.5531 9.59517 17.4334 10.8184C17.3137 12.0417 16.8415 13.2085 16.0699 14.1876L21.2427 19.164C21.4085 19.3248 21.5053 19.5399 21.5135 19.7662C21.5218 19.9926 21.4411 20.2137 21.2874 20.3853C21.1337 20.5569 20.9184 20.6665 20.6845 20.6921C20.4505 20.7178 20.2151 20.6576 20.0253 20.5236L19.9205 20.436L14.7476 15.4596C13.8802 16.0922 12.8687 16.5172 11.7968 16.6994C10.7248 16.8815 9.62324 16.8157 8.58298 16.5074C7.54272 16.199 6.59366 15.6569 5.81422 14.9259C5.03478 14.1949 4.44733 13.296 4.10042 12.3034C3.7535 11.3108 3.65708 10.2531 3.81911 9.21757C3.98114 8.18204 4.39698 7.19847 5.03226 6.34812C5.66755 5.49778 6.50404 4.80506 7.47263 4.32721C8.44122 3.84936 9.51411 3.60009 10.6026 3.60001V3.60001ZM10.6026 5.40001C9.27934 5.40001 8.01025 5.90572 7.07454 6.80589C6.13884 7.70607 5.61316 8.92697 5.61316 10.2C5.61316 11.473 6.13884 12.6939 7.07454 13.5941C8.01025 14.4943 9.27934 15 10.6026 15C11.9259 15 13.195 14.4943 14.1307 13.5941C15.0664 12.6939 15.5921 11.473 15.5921 10.2C15.5921 8.92697 15.0664 7.70607 14.1307 6.80589C13.195 5.90572 11.9259 5.40001 10.6026 5.40001Z"></path>
</svg>
<input type="text" placeholder="поиск по названию или описанию..." />
<span>{props.data.length}</span>
</div>
<div class="articles-tags">
<nav>
<ul class='list-links'>
<For each={props.tags}>
{(tag) => (
<li
style={
selectedTags().includes(tag) &&
"border-color: var(--border-color-hover);"
}
class="btn-link"
onClick={() => {
if (selectedTags().includes(tag)) {
setSelectedTags(
selectedTags().filter(
(selectedTag) => selectedTag !== tag
)
);
setFilteredArticles(
filteredArticles().filter((a) => !a.tags.includes(tag))
);
return;
}
setSelectedTags([...selectedTags(), tag]);
props.data.forEach((article) => {
if (article.tags.includes(tag)) {
setFilteredArticles([...filteredArticles(), article]);
}
});
}}
data-tag={tag.toLocaleLowerCase()}>
{tag}
</li>
)}
</For>
</ul>
</nav>
</div>
<div class="articles-list">
<Show when={filteredArticles().length === 0}>
{Object.keys(articles())
.map(Number)
.sort((a, b) => b - a)
.map((year) => (
<div class="articles-list__wrapper">
<h2>{year}</h2>
{utils.sortArtcilesByDate(articles()[year]).map((article) => (
<ArticleItem data={article} />
))}
</div>
))}
</Show>
<Show when={filteredArticles().length !== 0}>
{utils.sortArtcilesByDate(filteredArticles()).map((article) => (
<ArticleItem data={article} />
))}
</Show>
</div>
</section>
);
/>
<div class="articles-search">
<svg width="25" height="24" viewBox="0 0 25 24" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6026 3.60001C11.8798 3.59993 13.1316 3.94283 14.2172 4.5901C15.3028 5.23736 16.179 6.16331 16.7472 7.26368C17.3154 8.36405 17.5531 9.59517 17.4334 10.8184C17.3137 12.0417 16.8415 13.2085 16.0699 14.1876L21.2427 19.164C21.4085 19.3248 21.5053 19.5399 21.5135 19.7662C21.5218 19.9926 21.4411 20.2137 21.2874 20.3853C21.1337 20.5569 20.9184 20.6665 20.6845 20.6921C20.4505 20.7178 20.2151 20.6576 20.0253 20.5236L19.9205 20.436L14.7476 15.4596C13.8802 16.0922 12.8687 16.5172 11.7968 16.6994C10.7248 16.8815 9.62324 16.8157 8.58298 16.5074C7.54272 16.199 6.59366 15.6569 5.81422 14.9259C5.03478 14.1949 4.44733 13.296 4.10042 12.3034C3.7535 11.3108 3.65708 10.2531 3.81911 9.21757C3.98114 8.18204 4.39698 7.19847 5.03226 6.34812C5.66755 5.49778 6.50404 4.80506 7.47263 4.32721C8.44122 3.84936 9.51411 3.60009 10.6026 3.60001V3.60001ZM10.6026 5.40001C9.27934 5.40001 8.01025 5.90572 7.07454 6.80589C6.13884 7.70607 5.61316 8.92697 5.61316 10.2C5.61316 11.473 6.13884 12.6939 7.07454 13.5941C8.01025 14.4943 9.27934 15 10.6026 15C11.9259 15 13.195 14.4943 14.1307 13.5941C15.0664 12.6939 15.5921 11.473 15.5921 10.2C15.5921 8.92697 15.0664 7.70607 14.1307 6.80589C13.195 5.90572 11.9259 5.40001 10.6026 5.40001Z"></path>
</svg>
<input type="text" placeholder="поиск по названию или описанию..." />
<span>{props.data.length}</span>
</div>
<div class="articles-tags">
<nav>
<ul class="list-links">
<For each={props.tags}>
{(tag) => (
<li
style={selectedTags().includes(tag) && 'border-color: var(--border-color-hover);'}
class="btn-link"
onClick={() => {
if (selectedTags().includes(tag)) {
setSelectedTags(selectedTags().filter((selectedTag) => selectedTag !== tag));
setFilteredArticles(filteredArticles().filter((a) => !a.tags.includes(tag)));
return;
}
setSelectedTags([...selectedTags(), tag]);
props.data.forEach((article) => {
if (article.tags.includes(tag)) {
setFilteredArticles([...filteredArticles(), article]);
}
});
}}
data-tag={tag.toLocaleLowerCase()}
>
{tag}
</li>
)}
</For>
</ul>
</nav>
</div>
<div class="articles-list">
<Show when={filteredArticles().length === 0}>
{Object.keys(articles())
.map(Number)
.sort((a, b) => b - a)
.map((year) => (
<div class="articles-list__wrapper">
<h2>{year}</h2>
{utils.sortArtcilesByDate(articles()[year]).map((article) => (
<ArticleItem data={article} />
))}
</div>
))}
</Show>
<Show when={filteredArticles().length !== 0}>
{utils.sortArtcilesByDate(filteredArticles()).map((article) => (
<ArticleItem data={article} />
))}
</Show>
</div>
</section>
);
};

View File

@ -19,4 +19,4 @@ const { title = SITE_METADATA.title, description } = Astro.props as Props;
{title}
</title>
<meta name='title' content={title} />
{description && <meta name="description" content={description} />}
<meta name="description" content={description || SITE_METADATA.description} />

View File

@ -3,3 +3,9 @@
width: 100%;
margin-top: 5rem;
}
@media screen and (max-width: 768px) {
.site-content {
margin-top: 3rem;
}
}

View File

@ -0,0 +1,23 @@
.contact-form {
display: flex;
flex-direction: column;
row-gap: 7px;
margin-bottom: 3.125rem;
& div:first-of-type {
display: flex;
column-gap: 10px;
}
& div:last-of-type {
text-align: center;
margin-top: 15px;
}
}
@media screen and (max-width: 560px) {
.contact-form div:first-of-type {
flex-direction: column;
row-gap: 7px;
}
}

View File

@ -0,0 +1,27 @@
import type { Component } from 'solid-js';
import './ContactForm.scss';
type InputFieldsType = {
name: string;
email: string;
subject: string;
message: string;
};
const ContactForm: Component = () => {
return (
<form class="contact-form" name="contact" method="post" data-netlify="true">
<div>
<input name="name" placeholder="Ваше имя" type="text" />
<input name="email" placeholder="example@email.com" type="text" />
</div>
<input name="subject" placeholder="Тема сообщения" type="text" />
<textarea name="message" placeholder="Ваше сообщение..." id="message" cols={30} rows={10} />
<div>
<button type="submit">Отправить</button>
</div>
</form>
);
};
export default ContactForm;

View File

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

View File

@ -0,0 +1,24 @@
---
import { SITE_METADATA } from '@root/config';
import { Icon } from 'astro-icon';
import './ContactsList.scss';
const {
contacts: { email, githubUrl, telegramUrl },
} = SITE_METADATA;
---
<div class='contacts-list'>
<a href={'mailto:' + email}
>Email
<Icon name='arrow-up-right' width='15' height='15' />
</a>
<a href={githubUrl} target='_blank'
>Github
<Icon name='arrow-up-right' width='15' height='15' />
</a>
<a href={telegramUrl} target='_blank'
>Telegram
<Icon name='arrow-up-right' width='15' height='15' />
</a>
</div>

View File

@ -0,0 +1,11 @@
.contacts-list {
display: flex;
flex-wrap: wrap;
column-gap: 40px;
row-gap: 30px;
& a {
display: flex;
column-gap: 5px;
}
}

View File

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

View File

@ -135,7 +135,10 @@
z-index: 9999;
& .container {
flex-direction: column;
row-gap: 2.5rem;
row-gap: 3rem;
}
&__logo {
align-self: flex-start;
}
&__nav {
display: block;
@ -144,9 +147,9 @@
text-align: center;
margin: 0;
& li {
margin-bottom: 1.2rem;
margin-bottom: 30px;
& a {
font-size: var(--font-size-lg);
font-size: var(--font-size-xl);
}
}
}
@ -164,7 +167,7 @@
display: block;
position: absolute;
bottom: 45px;
right: var(--content-padding);
right: 2.5rem;
& svg {
width: 36px;
height: 36px;

View File

@ -18,7 +18,7 @@ export const NavToggle: Component = () => {
});
return (
<button id="nav-toggle" class="header__button" onClick={toggleNav} type="button" aria-pressed={isNavShown() ? 'true' : 'false'}>
<button id="nav-toggle" class="header__button" onClick={toggleNav} type="button" aria-pressed={isNavShown() ? 'true' : 'false'} aria-label="Menu">
<span />
<span />
<span />

View File

@ -0,0 +1,17 @@
---
import './PageHeading.scss';
export interface Props {
heading: string;
description?: string | undefined;
}
const { heading, description } = Astro.props as Props;
---
<header class='page-heading'>
<h1 style={!description && 'margin-bottom: 2.5rem;'}>
{heading}
</h1>
{description && <p>{description}</p>}
</header>

View File

@ -1,13 +0,0 @@
.PageHeading {
font-size: var(--font-size-4xl);
text-align: center;
margin-bottom: 15px;
}
.PageDescription {
font-size: var(--font-size-lg);
text-align: center;
max-width: 650px;
margin: 0 auto;
margin-bottom: 2.5rem;
}

View File

@ -0,0 +1,14 @@
.page-heading {
& h1 {
font-size: var(--font-size-4xl);
text-align: center;
margin-bottom: 15px;
}
& p {
font-size: var(--font-size-lg);
text-align: center;
max-width: 650px;
margin: 0 auto;
margin-bottom: 2.5rem;
}
}

View File

@ -1,20 +0,0 @@
import { Component } from 'solid-js';
import styles from './PageHeading.module.scss';
export type PageHeadingProps = {
heading: string;
description?: string | undefined;
};
export const PageHeading: Component<PageHeadingProps> = (props) => {
return (
<header>
<h1
class={styles.PageHeading}
style={!props.description && 'margin-bottom: 2.5rem;'}>
{props.heading}
</h1>
{props.description && <p class={styles.PageDescription}>{props.description}</p>}
</header>
);
};

View File

@ -1 +1 @@
export * from './PageHeading';
export { default } from './PageHeading.astro';

View File

@ -0,0 +1,45 @@
---
import type { ProjectType } from '@root/types';
import './ProjectItem.scss';
import { Icon } from 'astro-icon';
export interface Props {
data: ProjectType;
}
const { data } = Astro.props as Props;
const { icon, name, year, description, articleUrl, sourceUrl, demoUrl } = data;
---
<div class='project-item'>
<div class='project-item__heading'>
<h2>
<Icon name={icon} width='20' height='20' />
{name}
</h2>
<span>{year}</span>
</div>
<div class='project-item__description'>
<p>{description}</p>
</div>
<div class='project-item__links'>
{articleUrl && (
<a class="btn-link" href={articleUrl} target="_blank">
Статья
</a>
)}
{sourceUrl && (
<a class="btn-link" href={sourceUrl} target="_blank">
Исходник
<Icon name="link-out" width="15" height="15" />
</a>
)}
{demoUrl && (
<a class="btn-link" href={demoUrl} target="_blank">
Демо
<Icon name="link-out" width="15" height="15" />
</a>
)}
</div>
</div>

View File

@ -0,0 +1,36 @@
.project-item {
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
padding: 15px;
&__heading {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
& h2 {
display: flex;
align-items: center;
font-size: 1.2rem;
font-weight: 600;
column-gap: 10px;
margin: 0;
padding: 0;
}
& span {
font-size: var(--font-size-sm);
color: var(--font-color-gray);
}
}
&__description {
margin-bottom: 15px;
}
&__links {
display: flex;
column-gap: 5px;
}
}

View File

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

View File

@ -0,0 +1,7 @@
---
import './ProjectsList.scss';
---
<div class='projects-list'>
<slot></slot>
</div>

View File

@ -0,0 +1,5 @@
.projects-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 10px;
}

View File

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

View File

@ -1,12 +1,12 @@
---
import type { IArticle } from '@root/interfaces';
import type { ArticleType } from '@root/types';
import './RecentArticles.scss';
import { ArticleItem } from '@components/ArticleItem';
import { sortArtcilesByDate } from '@utils/articles';
export interface Props {
articles: IArticle[];
articles: ArticleType[];
length?: number | undefined;
}

View File

@ -0,0 +1,30 @@
---
import type { SkillsType } from '@root/types';
import { Icon } from 'astro-icon';
import './SkillsBlock.scss';
export interface Props {
data: SkillsType;
}
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.name}
</li>
))}
</ul>
</div>
</div>

View File

@ -0,0 +1,36 @@
.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;
}
}
}

View File

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

View File

@ -0,0 +1,5 @@
---
import './SkillsList.scss';
---
<div class='skills-list'><slot></slot></div>

View File

@ -0,0 +1,14 @@
.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;
}
}

View File

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

View File

@ -2,6 +2,8 @@ export const SITE_METADATA = {
title: 'Sergey Yarkov',
name: 'yarkov.tech',
author: 'Sergey Yarkov',
description:
'Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.',
contacts: {
email: 'serzh.yarkov@gmail.com',
githubUrl: 'https://github.com/sergeyyarkov',
@ -13,6 +15,75 @@ export const SITE_METADATA = {
{ name: 'Обо мне', url: '/about' },
{ name: 'Контакты', url: '/contacts' },
],
projects: [],
skills: [],
projects: [
{
icon: 'nodejs',
name: 'yarkov.tech',
year: '2022',
description:
'Мой персональный веб-сайт где находятся информация обо мне и список различных статей, туториалов...',
sourceUrl: 'https://github.com/sergeyyarkov/yarkov.tech',
demoUrl: 'https://yarkov.tech',
articleUrl: undefined,
},
{
icon: 'triangle_filled',
name: 'devthread',
year: '2021',
description:
'Статический веб-сайт для публикации различных статей, разработанный при помощи Gatsby.js и NetlifyCMS',
sourceUrl: 'https://github.com/sergeyyarkov/devthread.ru',
demoUrl: 'https://devthread.netlify.app',
articleUrl: undefined,
},
],
skills: [
{
title: 'Программирование',
list: [
{ icon: 'nodejs', name: 'Javascript/Typescript' },
{ icon: 'language-c', name: 'C Language' },
{ icon: 'mysql', name: 'SQL' },
{ icon: 'graphql', name: 'GraphQL' },
{ icon: 'avr', name: 'AVR instruction set' },
],
},
{
title: 'Фреймворки',
list: [
{ icon: 'adonisjs', name: 'Adonis.js' },
{ icon: 'expressjs', name: 'Express.js' },
{ icon: 'socket-io', name: 'Socket.io' },
{ icon: 'astro_filled', name: 'Astro' },
{ icon: 'reactjs', name: 'React.js' },
{ icon: 'gatsbyjs', name: 'Gatsby.js' },
],
},
{
title: 'Инструменты',
list: [
{ icon: 'nodejs', name: 'Node.js' },
{ icon: 'docker', name: 'Docker' },
{ icon: 'git-branch', name: 'Git/gitflow' },
{ icon: 'sass', name: 'SASS' },
{ icon: 'webpack', name: 'Webpack' },
{ icon: 'vite', name: 'Vite.js' },
],
},
{
title: 'Базы данных',
list: [
{ icon: 'postgresql', name: 'PostgreSQL' },
{ icon: 'mongodb', name: 'MongoDB' },
{ icon: 'redis', name: 'Redis' },
],
},
{
title: 'Языки',
list: [
{ icon: undefined, name: 'Русский (родной)' },
{ icon: undefined, name: 'Английский (B1)' },
],
},
],
};

View File

@ -1,6 +0,0 @@
export interface IArticle {
title: string;
author: string;
tags: Array<string>;
published_at: number;
}

View File

@ -2,6 +2,7 @@
import '../styles/prism-one-light.css';
import PageLayout from '@root/layouts/default.astro';
import { Markdown } from 'astro/components';
import ArticleContent from '@components/ArticleContent';
export function getStaticPaths() {
return [{ params: { article: 'article' } }];
@ -37,7 +38,7 @@ export function getStaticPaths() {
</figure>
</div>
</header>
<div class='article-content'>
<ArticleContent>
<Markdown>
## Introduction
@ -99,7 +100,7 @@ export function getStaticPaths() {
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus.
Sed dignissim, metus nec fringilla accumsan, risus sem sollicitudin lacus, ut interdum tellus elit sed risus.
</Markdown>
</div>
</ArticleContent>
<footer>
<h2>Comments</h2>
</footer>
@ -112,8 +113,6 @@ export function getStaticPaths() {
justify-content: space-between;
align-items: flex-start;
column-gap: 5px;
row-gap: 1.2rem;
& ul {
max-width: 350px;
}
@ -122,6 +121,7 @@ export function getStaticPaths() {
display: flex;
flex-direction: column;
text-align: right;
white-space: nowrap;
}
}
@ -172,6 +172,7 @@ export function getStaticPaths() {
& div {
align-self: flex-end;
margin-bottom: 1.2rem;
}
}
}

View File

@ -1,200 +1,28 @@
---
import PageLayout from '@layouts/default.astro';
import { Markdown } from 'astro/components';
import { PageHeading } from '@root/components/PageHeading';
import { Icon } from 'astro-icon'
import { SITE_METADATA } from '@root/config';
import { Markdown } from 'astro/components';
import PageLayout from '@layouts/default.astro';
import PageHeading from '@components/PageHeading';
import SkillsList from '@components/SkillsList';
import SkillsBlock from '@components/SkillsBlock';
// TODO: import content based on user ui language
import * as aboutContent from '../../content/about/ru/index.md';
// TODO: import content based on user ui language
import * as aboutContent from '../../content/about/ru/index.md';
import ArticleContent from '@components/ArticleContent';
const { skills } = SITE_METADATA;
---
<PageLayout title='Обо мне | Sergey Yarkov'>
<section class='about container'>
<PageHeading heading='Обо мне' />
<div class='article-content'>
<Markdown content={aboutContent.rawContent()} />
</div>
<div class='about-skills'>
<div class='skills-list'>
<div class='skills-list__heading'>
<h3>Программирование</h3>
</div>
<div class='skills-list__content'>
<ul>
<li>
<Icon name='nodejs' width='20' height='20' />
Javascript/Typescript
</li>
<li>
<Icon name='language-c' width='20' height='20' />
C Language
</li>
<li>
<Icon name='mysql' width='20' height='20' />
SQL
</li>
<li>
<Icon name='graphql' width='20' height='20' />
GraphQL
</li>
<li>
<Icon name='avr' width='20' height='20' />
AVR instruction set
</li>
</ul>
</div>
</div>
<div class='skills-list'>
<div class='skills-list__heading'>
<h3>Фреймворки</h3>
</div>
<div class='skills-list__content'>
<ul>
<li>
<Icon name='adonisjs' width='20' height='20' />
Adonis.js
</li>
<li>
<Icon name='expressjs' width='20' height='20' />
Express.js
</li>
<li>
<Icon name='socket-io' width='20' height='20' />
Socket.io
</li>
<li>
<Icon name='reactjs' width='20' height='20' />
React.js
</li>
<li>
<Icon name='astro_filled' width='20' height='20' />
Astro.js
</li>
<li>
<Icon name='gatsbyjs' width='20' height='20' />
Gastby.js
</li>
</ul>
</div>
</div>
<div class='skills-list'>
<div class='skills-list__heading'>
<h3>Инструменты</h3>
</div>
<div class='skills-list__content'>
<ul>
<li>
<Icon name='nodejs' width='20' height='20' />
Node.js
</li>
<li>
<Icon name='docker' width='20' height='20' />
Docker
</li>
<li>
<Icon name='git-branch' width='20' height='20' />
Git/gitflow
</li>
<li>
<Icon name='sass' width='20' height='20' />
SASS
</li>
<li>
<Icon name='vite' width='20' height='20' />
Vite.js
</li>
<li>
<Icon name='webpack' width='20' height='20' />
Webpack
</li>
</ul>
</div>
</div>
<div class='skills-list'>
<div class='skills-list__heading'>
<h3>Базы данных</h3>
</div>
<div class='skills-list__content'>
<ul>
<li>
<Icon name='postgresql' width='20' height='20' />
PostgreSQL
</li>
<li>
<Icon name='mongodb' width='20' height='20' />
MongoDB
</li>
<li>
<Icon name='redis' width='20' height='20' />
Redis
</li>
</ul>
</div>
</div>
<div class='skills-list'>
<div class='skills-list__heading'>
<h3>Языки</h3>
</div>
<div class='skills-list__content'>
<ul>
<li>Русский (родной)</li>
<li>Английский (B1)</li>
</ul>
</div>
</div>
</div>
</section>
<style lang='scss'>
.about {
&-skills {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
column-gap: 40px;
row-gap: 30px;
margin-top: 5rem;
<section class='container'>
<PageHeading heading='Обо мне' />
<ArticleContent>
<Markdown content={aboutContent.rawContent()}>
& .skills-list {
& 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) {
.about-skills {
display: flex;
flex-direction: column;
& .skills-list {
& h3 {
text-align: center;
}
& ul {
display: flex;
flex-direction: column;
align-items: center;
}
}
}
}
</style>
</Markdown>
</ArticleContent>
<SkillsList>
{skills.map((block) => <SkillsBlock data={block} />)}
</SkillsList>
</section>
</PageLayout>

View File

@ -1,15 +1,13 @@
---
import PageLayout from '@layouts/default.astro';
import { ArticlesList } from '@components/ArticlesList';
import { IArticle } from '@root/interfaces';
import * as utils from '@utils/index';
import PageLayout from '@layouts/default.astro';
import { ArticlesList } from '@components/ArticlesList';
import { ArticleType } from '@root/types';
import * as utils from '@utils/index';
const articles = await Astro.glob<IArticle>('../../content/articles/**/*.md');
const tags = new Set(
articles.map((article) => article.frontmatter.tags).flat()
);
const articles = await Astro.glob<ArticleType>('../../content/articles/**/*.md');
const tags = new Set(articles.map((article) => article.frontmatter.tags).flat());
---
<PageLayout title='Статьи | Sergey Yarkov'>
<ArticlesList client:load data={utils.transformArticles(articles)} tags={[...tags]} />
<!-- <ArticlesList client:load data={utils.transformArticles(articles)} tags={[...tags]} /> -->
</PageLayout>

View File

@ -1,78 +1,14 @@
---
import PageLayout from '@layouts/default.astro';
import { PageHeading } from '@root/components/PageHeading';
import { Icon } from 'astro-icon';
import { SITE_METADATA } from '@root/config';
import PageHeading from '@components/PageHeading';
import ContactForm from '@components/ContactForm';
import ContactsList from '@components/ContactsList';
---
<PageLayout title='Контакты | Sergey Yarkov'>
<section class='contacts container'>
<section class='container'>
<PageHeading heading='Контакты' description='Воспользуйтесь формой обратной связи или напишите мне в социальные сети.' />
<div class='contacts-form'>
<form name='contact' method='POST' data-netlify='true'>
<div>
<input name='name' placeholder='Ваше имя' type='text' />
<input name='email' placeholder='example@email.com' type='text' />
</div>
<input name='subject' placeholder='Тема сообщения' type='text' />
<textarea name='message' placeholder='Ваше сообщение...' id='message' cols={30} rows={10}></textarea>
<div>
<button type='submit'>Отправить</button>
</div>
</form>
</div>
<div class='contacts-link'>
<a href={'mailto:' + SITE_METADATA.contacts.email}
>Email
<Icon name='arrow-up-right' width='15' height='15' />
</a>
<a href={SITE_METADATA.contacts.githubUrl} target='_blank'
>Github
<Icon name='arrow-up-right' width='15' height='15' />
</a>
<a href={SITE_METADATA.contacts.telegramUrl} target='_blank'
>Telegram
<Icon name='arrow-up-right' width='15' height='15' />
</a>
</div>
<ContactForm client:idle />
<ContactsList />
</section>
<style lang='scss'>
.contacts {
& form {
display: flex;
flex-direction: column;
row-gap: 7px;
margin-bottom: 3.125rem;
& div:first-of-type {
display: flex;
column-gap: 10px;
}
& div:last-of-type {
text-align: center;
margin-top: 15px;
}
}
&-link {
display: flex;
flex-wrap: wrap;
column-gap: 40px;
row-gap: 30px;
& a {
display: flex;
column-gap: 5px;
}
}
}
@media screen and (max-width: 500px) {
.contacts form div:first-of-type {
flex-direction: column;
row-gap: 7px;
}
}
</style>
</PageLayout>

View File

@ -1,12 +1,12 @@
---
import type { IArticle } from '@root/interfaces';
import type { ArticleType } from '@root/types';
import PageLayout from '@layouts/default.astro';
import { Icon } from 'astro-icon';
import * as utils from '@utils/index';
import RecentArticles from '@root/components/RecentArticles';
import PageLayout from '@layouts/default.astro';
import RecentArticles from '@components/RecentArticles';
const data = await Astro.glob<IArticle>('../../content/articles/**/*.md');
const data = await Astro.glob<ArticleType>('../../content/articles/**/*.md');
const articles = utils.transformArticles(data);
---
@ -18,7 +18,8 @@ const articles = utils.transformArticles(data);
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
</hgroup>
<p class='welcome-heading__description'>
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую различные статьи и туториалы на разные IT темы.
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую
различные статьи и туториалы на разные IT темы.
</p>
</header>
<nav class='welcome-links'>

View File

@ -1,116 +1,18 @@
---
import PageLayout from '@layouts/default.astro';
import { PageHeading } from '@root/components/PageHeading';
import { SITE_METADATA } from '@root/config';
import { Icon } from 'astro-icon'
import { SITE_METADATA } from '@root/config';
import PageLayout from '@layouts/default.astro';
import PageHeading from '@root/components/PageHeading';
import ProjectsList from '@root/components/ProjectsList';
import ProjectItem from '@root/components/ProjectItem';
const { projects } = SITE_METADATA;
---
<PageLayout title='Проекты | Sergey Yarkov'>
<section class='projects container'>
<PageHeading heading='Проекты'
description='Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.' />
<div class='projects-heading'>
<a href={SITE_METADATA.contacts.githubUrl + '?tab=repositories' } target='_blank'>
смотреть все
</a>
</div>
<div class='projects-list'>
<div class='project-item'>
<div class='project-item__heading'>
<h2>
<Icon name='nodejs' width='20' height='20' />
yarkov.tech
</h2>
<span>2022</span>
</div>
<div class='project-item__description'>
<p>
Мой персональный веб-сайт где находятся информация обо мне и список
различных статей, туториалов...
</p>
</div>
<div class='project-item__links'>
<a class='btn-link' href='#'>
Исходник
<Icon name='link-out' width='15' height='15' />
</a>
</div>
</div>
<div class='project-item'>
<div class='project-item__heading'>
<h2>
<Icon name='triangle_filled' width='20' height='20' />
devthread
</h2>
<span>2021</span>
</div>
<div class='project-item__description'>
<p>
Статический веб-сайт для публикации различных статей, разработанный
при помощи Gatsby.js и NetlifyCMS
</p>
</div>
<div class='project-item__links'>
<a class='btn-link' href='#'>
Статья
</a>
<a class='btn-link' href='#'>
Исходник
<Icon name='link-out' width='15' height='15' />
</a>
</div>
</div>
</div>
</section>
<style lang='scss'>
.projects {
&-heading {
text-align: right;
margin-bottom: 10px;
}
& .projects-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 10px;
& .project-item {
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
padding: 15px;
&__heading {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
& h2 {
display: flex;
align-items: center;
font-size: 1.2rem;
font-weight: 600;
column-gap: 10px;
margin: 0;
padding: 0;
}
& span {
font-size: var(--font-size-sm);
color: var(--font-color-gray);
}
}
&__description {
margin-bottom: 15px;
}
&__links {
display: flex;
column-gap: 5px;
}
}
}
}
</style>
<section class='container'>
<PageHeading heading='Проекты' description='Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.' />
<ProjectsList>
{projects.map((project) => <ProjectItem data={project} />)}
</ProjectsList>
</section>
</PageLayout>

View File

@ -177,6 +177,7 @@ body {
color: var(--font-color);
font-size: var(--font-size-sm);
font-weight: 400;
white-space: nowrap;
&:hover {
text-decoration: none;
color: inherit;
@ -184,91 +185,6 @@ body {
}
}
.article-content {
& p,
& em,
& q {
line-height: var(--line-height-base);
font-size: var(--font-size-lg);
}
& ul {
padding-left: 1.5rem;
line-height: var(--line-height-base);
margin-bottom: 1.5rem;
margin-top: 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: '«' '»';
}
& h1,
& h2,
& h3,
& h4 {
margin-top: 3rem;
margin-bottom: 1rem;
padding-bottom: 0.35rem;
}
& h1:not(:first-child),
& h2:not(:first-child),
& h3:not(:first-child),
& h4:not(:first-child) {
margin-top: 3rem;
}
& h2 {
font-size: var(--font-size-3xl);
border-bottom: 1px solid var(--border-color);
}
& h3 {
font-size: var(--font-size-2xl);
}
& h4 {
font-size: var(--font-size-xl);
}
& a {
color: var(--color-blue-500);
text-underline-offset: 2.5px;
text-decoration-color: var(--color-blue-500);
font-weight: 500;
&:hover {
color: var(--color-blue-500);
text-decoration-thickness: 2px;
}
}
& figure {
margin: 0 0 1em;
}
& figcaption {
font-style: italic;
font-size: 1em;
text-align: center;
margin: 0.7rem 0;
}
& img {
display: block;
border-radius: var(--border-radius-md);
max-width: 100%;
margin: 0 auto;
}
}
li::marker {
color: var(--color-blue-500);
font-size: 1.3rem;
@ -298,6 +214,7 @@ h5 {
input,
textarea {
-webkit-appearance: none;
font-family: var(--font-family-base);
padding: 8px 15px;
border: 1px solid var(--border-color);

21
src/types/index.ts Normal file
View File

@ -0,0 +1,21 @@
export type ArticleType = {
title: string;
author: string;
tags: Array<string>;
published_at: number;
};
export type ProjectType = {
icon: string;
name: string;
year: string;
description: string;
sourceUrl?: string | undefined;
articleUrl?: string | undefined;
demoUrl?: string | undefined;
};
export type SkillsType = {
title: string;
list: Array<{ icon?: string | undefined; name: string }>;
};

View File

@ -1,8 +1,8 @@
import type { IArticle } from '@root/interfaces';
import type { ArticleType } from '@root/types';
import { MarkdownInstance } from 'astro';
export const transformArticles = (articles: MarkdownInstance<IArticle>[]): IArticle[] =>
export const transformArticles = (articles: MarkdownInstance<ArticleType>[]): ArticleType[] =>
articles.map((article) => ({
title: article.frontmatter.title,
author: article.frontmatter.author,
@ -10,11 +10,11 @@ export const transformArticles = (articles: MarkdownInstance<IArticle>[]): IArti
published_at: article.frontmatter.published_at,
}));
export const sortArtcilesByDate = (articles: IArticle[]): IArticle[] => {
export const sortArtcilesByDate = (articles: ArticleType[]): ArticleType[] => {
return articles.sort((a, b) => new Date(b.published_at).valueOf() - new Date(a.published_at).valueOf());
};
export const filterArticlesByYear = (articles: IArticle[]): { [year: number]: IArticle[] } => {
export const filterArticlesByYear = (articles: ArticleType[]): { [year: number]: ArticleType[] } => {
const filtered = {};
for (const article of articles) {

View File

@ -1,22 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["vite/client"],
"sourceMap": true,
"baseUrl": ".",
"jsx": "preserve",
"paths": {
"@root/*": ["./src/*"],
"@styles/*": ["./src/styles/*"],
"@components/*": ["./src/components/*"],
"@layouts/*": ["./src/layouts/*"],
"@utils/*": ["./src/utils/*"]
},
"outDir": "./dist"
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"]
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["vite/client"],
"sourceMap": true,
"baseUrl": ".",
"jsx": "preserve",
"paths": {
"@root/*": ["./src/*"],
"@styles/*": ["./src/styles/*"],
"@components/*": ["./src/components/*"],
"@layouts/*": ["./src/layouts/*"],
"@utils/*": ["./src/utils/*"]
},
"outDir": "./dist"
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"]
}