mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
cms config
This commit is contained in:
parent
598afe2e32
commit
2dbf994ec6
12
.prettierrc
12
.prettierrc
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 120,
|
"printWidth": 100,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"useTabs": true
|
"useTabs": true
|
||||||
}
|
}
|
||||||
|
|||||||
153
astro.config.mjs
153
astro.config.mjs
@ -22,7 +22,7 @@ import { h } from "hastscript";
|
|||||||
import { toString } from "hast-util-to-string";
|
import { toString } from "hast-util-to-string";
|
||||||
import { escape } from "html-escaper";
|
import { escape } from "html-escaper";
|
||||||
import languages from "./src/i18n/languages";
|
import languages from "./src/i18n/languages";
|
||||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "./src/constants";
|
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, SITE_METADATA } from "./src/constants";
|
||||||
|
|
||||||
const createAnchorLabel = (heading) => {
|
const createAnchorLabel = (heading) => {
|
||||||
const node = h("span.anchor-label", escape(heading));
|
const node = h("span.anchor-label", escape(heading));
|
||||||
@ -48,11 +48,11 @@ export default defineConfig({
|
|||||||
preview: false,
|
preview: false,
|
||||||
},
|
},
|
||||||
display_url: "https://yarkov.tech",
|
display_url: "https://yarkov.tech",
|
||||||
// i18n: {
|
i18n: {
|
||||||
// locales: SUPPORTED_LANGUAGES,
|
locales: SUPPORTED_LANGUAGES,
|
||||||
// default_locale: DEFAULT_LANGUAGE,
|
default_locale: DEFAULT_LANGUAGE,
|
||||||
// structure: "multiple_files",
|
structure: "multiple_folders",
|
||||||
// },
|
},
|
||||||
locale: DEFAULT_LANGUAGE,
|
locale: DEFAULT_LANGUAGE,
|
||||||
backend: {
|
backend: {
|
||||||
name: "test-repo",
|
name: "test-repo",
|
||||||
@ -61,6 +61,9 @@ export default defineConfig({
|
|||||||
media_folder: "public/media",
|
media_folder: "public/media",
|
||||||
public_folder: "/media",
|
public_folder: "/media",
|
||||||
collections: [
|
collections: [
|
||||||
|
/**
|
||||||
|
* Articles
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
name: "posts",
|
name: "posts",
|
||||||
label: "Публикации",
|
label: "Публикации",
|
||||||
@ -75,6 +78,14 @@ export default defineConfig({
|
|||||||
widget: "image",
|
widget: "image",
|
||||||
label: "Постер",
|
label: "Постер",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "draft",
|
||||||
|
label: "В черновике",
|
||||||
|
widget: "boolean",
|
||||||
|
required: true,
|
||||||
|
default: true,
|
||||||
|
hint: "Опубликована ли статья",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "author",
|
name: "author",
|
||||||
widget: "relation",
|
widget: "relation",
|
||||||
@ -107,9 +118,17 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pubDate",
|
name: "pubDate",
|
||||||
widget: "date",
|
widget: "datetime",
|
||||||
label: "Дата публикации",
|
label: "Дата публикации",
|
||||||
required: true,
|
required: true,
|
||||||
|
picker_utc: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updatedDate",
|
||||||
|
widget: "datetime",
|
||||||
|
label: "Дата обновления",
|
||||||
|
required: true,
|
||||||
|
picker_utc: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "body",
|
name: "body",
|
||||||
@ -118,6 +137,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Tags
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
name: "tags",
|
name: "tags",
|
||||||
label: "Тэги",
|
label: "Тэги",
|
||||||
@ -135,6 +157,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Authors
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
name: "authors",
|
name: "authors",
|
||||||
label: "Авторы",
|
label: "Авторы",
|
||||||
@ -159,12 +184,88 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Об авторе",
|
label: "Об авторе",
|
||||||
name: "about",
|
name: "body",
|
||||||
widget: "markdown",
|
widget: "markdown",
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Projects
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
name: "projects",
|
||||||
|
label: "Проекты",
|
||||||
|
label_singular: "проект",
|
||||||
|
folder: "src/content/projects",
|
||||||
|
create: true,
|
||||||
|
delete: true,
|
||||||
|
i18n: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
label: "Иконка",
|
||||||
|
widget: "string",
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "draft",
|
||||||
|
label: "В черновике",
|
||||||
|
widget: "boolean",
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
label: "Название",
|
||||||
|
default: "my-project",
|
||||||
|
widget: "string",
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "year",
|
||||||
|
label: "В каком году разработал",
|
||||||
|
widget: "number",
|
||||||
|
default: 2023,
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
label: "Описание",
|
||||||
|
widget: "string",
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sourceUrl",
|
||||||
|
label: "Ссылка на исходник",
|
||||||
|
widget: "string",
|
||||||
|
required: true,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "demoUrl",
|
||||||
|
label: "Ссылка на демонстрацию",
|
||||||
|
widget: "string",
|
||||||
|
required: false,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "articleUrl",
|
||||||
|
label: "Ссылка на статью",
|
||||||
|
widget: "string",
|
||||||
|
required: false,
|
||||||
|
i18n: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Skills
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
name: "skills",
|
name: "skills",
|
||||||
label: "Навыки",
|
label: "Навыки",
|
||||||
@ -179,6 +280,42 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Settings
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
name: "settings",
|
||||||
|
label: "Настройки",
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
name: "site-settings",
|
||||||
|
label: "Настройки веб-сайта",
|
||||||
|
file: "src/content/site-settings.yml",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: "Глобальный заголовок",
|
||||||
|
name: "globalTitle",
|
||||||
|
widget: "string",
|
||||||
|
hint: "Отображется во вкладке браузера.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Статьи",
|
||||||
|
name: "articles",
|
||||||
|
widget: "object",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "recentLimit",
|
||||||
|
label: "Лимит на отображение",
|
||||||
|
widget: "number",
|
||||||
|
hint: "Кол-во публикаций на гл.странице",
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"hastscript": "^7.2.0",
|
"hastscript": "^7.2.0",
|
||||||
"html-escaper": "^3.0.3",
|
"html-escaper": "^3.0.3",
|
||||||
"ioredis": "^5.3.0",
|
"ioredis": "^5.3.0",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"quicklink": "^2.3.0",
|
"quicklink": "^2.3.0",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-slug": "^5.1.0",
|
"rehype-slug": "^5.1.0",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"solid-js": "^1.6.10"
|
"solid-js": "^1.6.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/mime-types": "^2.1.1",
|
||||||
"sass": "^1.58.0"
|
"sass": "^1.58.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1689,6 +1691,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz",
|
||||||
"integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ=="
|
"integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/mime-types": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.31",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||||
@ -14506,6 +14514,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz",
|
||||||
"integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ=="
|
"integrity": "sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ=="
|
||||||
},
|
},
|
||||||
|
"@types/mime-types": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/ms": {
|
"@types/ms": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.31",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
"hastscript": "^7.2.0",
|
"hastscript": "^7.2.0",
|
||||||
"html-escaper": "^3.0.3",
|
"html-escaper": "^3.0.3",
|
||||||
"ioredis": "^5.3.0",
|
"ioredis": "^5.3.0",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"quicklink": "^2.3.0",
|
"quicklink": "^2.3.0",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-slug": "^5.1.0",
|
"rehype-slug": "^5.1.0",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"solid-js": "^1.6.10"
|
"solid-js": "^1.6.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/mime-types": "^2.1.1",
|
||||||
"sass": "^1.58.0"
|
"sass": "^1.58.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export type Props = {
|
|||||||
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, description, tags, pubDate },
|
data: { coverImage, title, author, description, tags, pubDate },
|
||||||
slug,
|
slug,
|
||||||
} = article;
|
} = article;
|
||||||
// const t = useTranslation(Astro);
|
// const t = useTranslation(Astro);
|
||||||
@ -48,8 +48,10 @@ const {
|
|||||||
<h1 itemprop="headline">{title}</h1>
|
<h1 itemprop="headline">{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="published">
|
<div class="published">
|
||||||
<time itemprop="datePublished" datetime={pubDate.toString()}>{createDateFormatter(lang).format(pubDate)}</time>
|
<time itemprop="datePublished" datetime={pubDate.toString()}
|
||||||
<span itemprop="author">{"Sergey Yarkov"}</span>
|
>{createDateFormatter(lang).format(pubDate)}</time
|
||||||
|
>
|
||||||
|
<span itemprop="author">{author}</span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
coverImage && (
|
coverImage && (
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { SITE_METADATA } from "@root/constants";
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
description: string;
|
description?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ const { title, description } = Astro.props;
|
|||||||
<!-- Metadata -->
|
<!-- Metadata -->
|
||||||
<meta name="title" content={title || SITE_METADATA.title} />
|
<meta name="title" content={title || SITE_METADATA.title} />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<meta name="description" content={description} />
|
{description && <meta name="description" content={description} />}
|
||||||
<meta name="theme-color" content={SITE_METADATA.themeColor} />
|
<meta name="theme-color" content={SITE_METADATA.themeColor} />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
|
|
||||||
@ -55,25 +55,22 @@ const { title, description } = Astro.props;
|
|||||||
href="/favicon-light-16x16.png"
|
href="/favicon-light-16x16.png"
|
||||||
media="(prefers-color-scheme: light)"
|
media="(prefers-color-scheme: light)"
|
||||||
/>
|
/>
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-dark-32x32.png" media="(prefers-color-scheme: dark)" />
|
<link
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-dark-16x16.png" media="(prefers-color-scheme: dark)" />
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon-dark-32x32.png"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-dark-16x16.png"
|
||||||
|
media="(prefers-color-scheme: dark)"
|
||||||
|
/>
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
|
||||||
<!-- <meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content={Astro.url} />
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<meta property="og:image" content={new URL(image, Astro.url)} /> -->
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
|
||||||
<!-- <meta property="twitter:card" content="summary_large_image" />
|
|
||||||
<meta property="twitter:url" content={Astro.url} />
|
|
||||||
<meta property="twitter:title" content={title} />
|
|
||||||
<meta property="twitter:description" content={description} />
|
|
||||||
<meta property="twitter:image" content={new URL(image, Astro.url)} /> -->
|
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
<script is:inline src="/assets/js/detect-theme.js"></script>
|
<script is:inline src="/assets/js/detect-theme.js"></script>
|
||||||
<!-- <script src="../quicklink.js"></script> -->
|
<!-- <script src="../quicklink.js"></script> -->
|
||||||
|
|||||||
@ -17,17 +17,17 @@ import NavToggle from "@components/NavToggle";
|
|||||||
{
|
{
|
||||||
(
|
(
|
||||||
<li>
|
<li>
|
||||||
<HeaderLink itemprop={"url"} href={"#"}>
|
<HeaderLink itemprop={"url"} href={"/articles"}>
|
||||||
статьи
|
статьи
|
||||||
</HeaderLink>
|
</HeaderLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<HeaderLink itemprop={"url"} href={"#"}>
|
<HeaderLink itemprop={"url"} href={"/projects"}>
|
||||||
проекты
|
проекты
|
||||||
</HeaderLink>
|
</HeaderLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<HeaderLink itemprop={"url"} href={"#"}>
|
<HeaderLink itemprop={"url"} href={"/about"}>
|
||||||
обо мне
|
обо мне
|
||||||
</HeaderLink>
|
</HeaderLink>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
33
src/components/OpenGraph.astro
Normal file
33
src/components/OpenGraph.astro
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
import mime from 'mime-types'
|
||||||
|
import { extname } from 'node:path'
|
||||||
|
import { isURL } from "@utils/string";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
siteName: string;
|
||||||
|
description: string;
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
image?: {
|
||||||
|
path: string;
|
||||||
|
width: string;
|
||||||
|
height: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {title, siteName, description, url, image} = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:site_name" content={siteName} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:url" content={url} />
|
||||||
|
{image && (
|
||||||
|
<meta property="og:image" content={isURL(image.path) ? image.path : `${Astro.url.origin}${image.path}`} />
|
||||||
|
<meta property="og:image:width" content="700" />
|
||||||
|
<meta property="og:image:height" content="400" />
|
||||||
|
<meta property="og:image:type" content={`${mime.lookup(extname(image.path))}`} />
|
||||||
|
)}
|
||||||
91
src/components/ProjectItem.astro
Normal file
91
src/components/ProjectItem.astro
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
import { Icon } from "astro-icon";
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
// import UIString from "../UIString";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
data: CollectionEntry<"projects">["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = Astro.props as Props;
|
||||||
|
// const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
const { icon, title, year, description, articleUrl, sourceUrl, demoUrl } = data;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="project-item">
|
||||||
|
<div class="project-item__heading">
|
||||||
|
<h2>
|
||||||
|
<Icon name={icon} width="20" height="20" />
|
||||||
|
{title}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__links {
|
||||||
|
display: flex;
|
||||||
|
margin-top: auto;
|
||||||
|
column-gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
14
src/components/ProjectsList.astro
Normal file
14
src/components/ProjectsList.astro
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,7 +1,8 @@
|
|||||||
export const DEFAULT_LANGUAGE: LanguageKeys = "ru";
|
export const DEFAULT_LANGUAGE: LanguageKeys = "ru";
|
||||||
|
|
||||||
/* Show banner about website development */
|
/* Show banner about website development */
|
||||||
export const IS_SITE_UNDER_CONSTRUCTION = import.meta.env.IS_SITE_UNDER_CONSTRUCTION === "true" ? true : false;
|
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"];
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
coverImage: /media/serial-port.jpg
|
coverImage: /media/serial-port.jpg
|
||||||
|
draft: false
|
||||||
author: Sergey Yarkov
|
author: Sergey Yarkov
|
||||||
title: Использование WebSerial API для общения с микроконтроллером
|
title: Использование WebSerial API для общения с микроконтроллером
|
||||||
description: Речь пойдет о том, что такое WebSerial API, как его можно
|
description: Речь пойдет о том, что такое WebSerial API, как его можно
|
||||||
@ -10,9 +11,10 @@ tags:
|
|||||||
- AVR
|
- AVR
|
||||||
- WebSerial API
|
- WebSerial API
|
||||||
- Tutorial
|
- Tutorial
|
||||||
pubDate: 2022-11-04
|
pubDate: 2022-11-04T21:00:00.000Z
|
||||||
draft: false
|
updatedDate: 2023-02-13T16:34:23.326Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# Что вы узнаете
|
# Что вы узнаете
|
||||||
|
|
||||||
По итогу вы узнаете, как отправлять и принимать данные от устройства с последовательным портом в браузере, разработаете демонстрационное веб-приложение, которое отправляет команды в микроконтроллер с последовательным портом и напишите прошивку для микроконтроллера [AVR128DA48](http://ww1.microchip.com/downloads/en/DeviceDoc/40002183A.pdf) для обработки этих команд. Готовый вариант проекта можно посмотреть [здесь](https://web-serial-example.netlify.app/), исходники загружены в [репозиторий](https://github.com/sergeyyarkov/avr128da48_web-serial-example) на GitHub.
|
По итогу вы узнаете, как отправлять и принимать данные от устройства с последовательным портом в браузере, разработаете демонстрационное веб-приложение, которое отправляет команды в микроконтроллер с последовательным портом и напишите прошивку для микроконтроллера [AVR128DA48](http://ww1.microchip.com/downloads/en/DeviceDoc/40002183A.pdf) для обработки этих команд. Готовый вариант проекта можно посмотреть [здесь](https://web-serial-example.netlify.app/), исходники загружены в [репозиторий](https://github.com/sergeyyarkov/avr128da48_web-serial-example) на GitHub.
|
||||||
@ -47,17 +49,17 @@ WebSerial API - это браузерный API, который предоста
|
|||||||
|
|
||||||
UART передает данные последовательно по одному биту в одном из трех режимов:
|
UART передает данные последовательно по одному биту в одном из трех режимов:
|
||||||
|
|
||||||
* односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
|
- односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
|
||||||
* полудуплексный режим - устройства могут передавать и принимать данные по очереди
|
- полудуплексный режим - устройства могут передавать и принимать данные по очереди
|
||||||
* полнодуплексный режим - устройства могут передавать и принимать данные одновременно
|
- полнодуплексный режим - устройства могут передавать и принимать данные одновременно
|
||||||
|
|
||||||
Так как в этом проекте предполагается использование асинхронной версии UART, то соответственно необходимо устанавливать **одинаковую** скорость передачи битов в секунду (`baud rate`, `bps`) на двух устройствах. Наиболее распространенные скорости являются: **4800, 9600, 19.2 К, 57.6К и 115.2К.**
|
Так как в этом проекте предполагается использование асинхронной версии UART, то соответственно необходимо устанавливать **одинаковую** скорость передачи битов в секунду (`baud rate`, `bps`) на двух устройствах. Наиболее распространенные скорости являются: **4800, 9600, 19.2 К, 57.6К и 115.2К.**
|
||||||
|
|
||||||
Данные передаются в виде так называемых `пакетов`, где каждый пакет содержит в себе:
|
Данные передаются в виде так называемых `пакетов`, где каждый пакет содержит в себе:
|
||||||
|
|
||||||
* стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
|
- стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
|
||||||
* биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
|
- биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
|
||||||
* бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
|
- бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="/media/uart-packet.jpg" width="700" height="300" alt="Структура UART пакета" format="webp">
|
<img src="/media/uart-packet.jpg" width="700" height="300" alt="Структура UART пакета" format="webp">
|
||||||
@ -82,7 +84,7 @@ UART передает данные последовательно по одно
|
|||||||
|
|
||||||
Как я упомянул ранее, прошивка будет написана для микроконтроллера AVR128DA48. На самом деле неважно какой микроконтроллер вы используете, вам достаточно нужно уметь отправлять и принимать данные используя UART периферию вашего микроконтроллера.
|
Как я упомянул ранее, прошивка будет написана для микроконтроллера AVR128DA48. На самом деле неважно какой микроконтроллер вы используете, вам достаточно нужно уметь отправлять и принимать данные используя UART периферию вашего микроконтроллера.
|
||||||
|
|
||||||
Суть программы будет очень проста: необходимо написать некий ***обработчик команд*** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
|
Суть программы будет очень проста: необходимо написать некий **_обработчик команд_** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
|
||||||
|
|
||||||
Постановка задачи ясна, теперь рассмотрим реализацию данной программы. Я буду использовать среду разработки MPLAB X IDE и язык C для написания прошивки.
|
Постановка задачи ясна, теперь рассмотрим реализацию данной программы. Я буду использовать среду разработки MPLAB X IDE и язык C для написания прошивки.
|
||||||
|
|
||||||
@ -102,10 +104,10 @@ UART передает данные последовательно по одно
|
|||||||
#endif /* CONSTANTS_H */
|
#endif /* CONSTANTS_H */
|
||||||
```
|
```
|
||||||
|
|
||||||
* `F_CPU` - тактовая частота микроконтроллера
|
- `F_CPU` - тактовая частота микроконтроллера
|
||||||
* `BAUD_RATE` - скорость передачи данных UART
|
- `BAUD_RATE` - скорость передачи данных UART
|
||||||
* `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
|
- `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
|
||||||
* `EOT` - End-of-Transmission, ASCII символ конца передачи данных
|
- `EOT` - End-of-Transmission, ASCII символ конца передачи данных
|
||||||
|
|
||||||
## Инициализация USART
|
## Инициализация USART
|
||||||
|
|
||||||
@ -155,11 +157,11 @@ void USART1_Initialize(void) {
|
|||||||
|
|
||||||
Для полнодуплексного режима инициализация асинхронной версии USART происходит следующим образом:
|
Для полнодуплексного режима инициализация асинхронной версии USART происходит следующим образом:
|
||||||
|
|
||||||
* Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
|
- Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
|
||||||
* Конфигурация размера `фрейма`, в нашем случае это 8 бит.
|
- Конфигурация размера `фрейма`, в нашем случае это 8 бит.
|
||||||
* Конфигурация пина `TX` на выход и пина `RX` на вход.
|
- Конфигурация пина `TX` на выход и пина `RX` на вход.
|
||||||
* Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
|
- Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
|
||||||
* Включение приемника и передатчика.
|
- Включение приемника и передатчика.
|
||||||
|
|
||||||
Теперь, рассмотрим функции отправки данных:
|
Теперь, рассмотрим функции отправки данных:
|
||||||
|
|
||||||
@ -211,9 +213,9 @@ void command_list(void);
|
|||||||
|
|
||||||
Для решения этой задачи я предлагаю создавать массив структур, где в каждой структуре будет содержаться указатель на функцию которую нам нужно выполнить, и название самой команды.
|
Для решения этой задачи я предлагаю создавать массив структур, где в каждой структуре будет содержаться указатель на функцию которую нам нужно выполнить, и название самой команды.
|
||||||
|
|
||||||
* `command_define` будет создавать новую структуру в массиве.
|
- `command_define` будет создавать новую структуру в массиве.
|
||||||
* `command_process` будет вызывать соответствующую функцию по имени команды.
|
- `command_process` будет вызывать соответствующую функцию по имени команды.
|
||||||
* `command_list` будет отправлять по UART информацию о существующих командах.
|
- `command_list` будет отправлять по UART информацию о существующих командах.
|
||||||
|
|
||||||
Реализовать задуманное можно следующим образом создав файл `command.c`:
|
Реализовать задуманное можно следующим образом создав файл `command.c`:
|
||||||
|
|
||||||
@ -367,9 +369,9 @@ void hello_world(void) {
|
|||||||
|
|
||||||
С помощью функции `command_define` мы можем определить, по какой переданной команде по USART запускать функцию:
|
С помощью функции `command_define` мы можем определить, по какой переданной команде по USART запускать функцию:
|
||||||
|
|
||||||
* `led_toggle` будет переключать светодиод.
|
- `led_toggle` будет переключать светодиод.
|
||||||
* `hello_world` выводить сообщение “Hello world”.
|
- `hello_world` выводить сообщение “Hello world”.
|
||||||
* `command_list` будет показывать список имеющихся команд.
|
- `command_list` будет показывать список имеющихся команд.
|
||||||
|
|
||||||
## Демонстрация работы
|
## Демонстрация работы
|
||||||
|
|
||||||
@ -377,10 +379,10 @@ void hello_world(void) {
|
|||||||
|
|
||||||
Выберете ваше устройство из списка (в моем случае это `/dev/ttyACM1`) и установите следующие настройки:
|
Выберете ваше устройство из списка (в моем случае это `/dev/ttyACM1`) и установите следующие настройки:
|
||||||
|
|
||||||
* Скорость: **9600** бод
|
- Скорость: **9600** бод
|
||||||
* Количество битов: **8**
|
- Количество битов: **8**
|
||||||
* Четность: **None**
|
- Четность: **None**
|
||||||
* Стоповые биты: **1**
|
- Стоповые биты: **1**
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img
|
<img
|
||||||
@ -584,12 +586,12 @@ class SerialPortHandler {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) является классом, который кодирует строку в массив без знаковых 8-ми битных целых чисел [`Uint8Array`](https://webidl.spec.whatwg.org/#idl-Uint8Array).
|
- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) является классом, который кодирует строку в массив без знаковых 8-ми битных целых чисел [`Uint8Array`](https://webidl.spec.whatwg.org/#idl-Uint8Array).
|
||||||
* [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder#examples) наоборот, декодирует в строку. Экземпляры этих классов нам понадобятся, т.к мы будет оперировать данными с типом `Uint8Array`.
|
- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder#examples) наоборот, декодирует в строку. Экземпляры этих классов нам понадобятся, т.к мы будет оперировать данными с типом `Uint8Array`.
|
||||||
* Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
|
- Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
|
||||||
* Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
|
- Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
|
||||||
* Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
|
- Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
|
||||||
* Функция `setupListeners` просто будет добавлять обработчики событий.
|
- Функция `setupListeners` просто будет добавлять обработчики событий.
|
||||||
|
|
||||||
Далее рассматривается реализация этих методов.
|
Далее рассматривается реализация этих методов.
|
||||||
|
|
||||||
@ -638,12 +640,12 @@ const port = await navigator.serial.requestPort({
|
|||||||
|
|
||||||
После того как пользователь выбрал устройство, метод `requestPort` возвращает [`port`](https://wicg.github.io/serial/#serialport-interface), который можно открыть передав туда [параметры](https://wicg.github.io/serial/#serialoptions-dictionary):
|
После того как пользователь выбрал устройство, метод `requestPort` возвращает [`port`](https://wicg.github.io/serial/#serialport-interface), который можно открыть передав туда [параметры](https://wicg.github.io/serial/#serialoptions-dictionary):
|
||||||
|
|
||||||
* `baudRate` : скорость передачи данных.
|
- `baudRate` : скорость передачи данных.
|
||||||
* `dataBits` : количество бит данных во фрейме (7 или 8).
|
- `dataBits` : количество бит данных во фрейме (7 или 8).
|
||||||
* `stopBits` : количество стоповых битов в конце пакета (1 или 2).
|
- `stopBits` : количество стоповых битов в конце пакета (1 или 2).
|
||||||
* `parity` : режим четности ( `none` , `even` или `odd` ).
|
- `parity` : режим четности ( `none` , `even` или `odd` ).
|
||||||
* `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
|
- `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
|
||||||
* `flowControl` : режим управления потоком ( `none` или `hardware` ).
|
- `flowControl` : режим управления потоком ( `none` или `hardware` ).
|
||||||
|
|
||||||
Свойство `baudRate` является единственным обязательным параметром, остальные же являются необязательными и имеют [значения по умолчанию](https://wicg.github.io/serial/#serialoptions-dictionary).
|
Свойство `baudRate` является единственным обязательным параметром, остальные же являются необязательными и имеют [значения по умолчанию](https://wicg.github.io/serial/#serialoptions-dictionary).
|
||||||
|
|
||||||
@ -891,8 +893,8 @@ setInterval(async () => {
|
|||||||
|
|
||||||
# Полезные материалы
|
# Полезные материалы
|
||||||
|
|
||||||
* [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
|
- [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
|
||||||
* [Документация к WebSerial API](https://wicg.github.io/serial/)
|
- [Документация к WebSerial API](https://wicg.github.io/serial/)
|
||||||
* [Подробное руководство по Stream API](https://web.dev/streams/)
|
- [Подробное руководство по Stream API](https://web.dev/streams/)
|
||||||
* [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
|
- [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
|
||||||
* [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)
|
- [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)
|
||||||
|
|||||||
@ -13,8 +13,10 @@ const blog = defineCollection({
|
|||||||
.transform((val) => new Date(val)),
|
.transform((val) => new Date(val)),
|
||||||
updatedDate: z
|
updatedDate: z
|
||||||
.string()
|
.string()
|
||||||
|
.or(z.date())
|
||||||
.optional()
|
.optional()
|
||||||
.transform((str) => (str ? new Date(str) : undefined)),
|
.transform((str) => (str ? new Date(str) : undefined)),
|
||||||
|
draft: z.boolean(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,4 +33,17 @@ const authors = defineCollection({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const collections = { blog, tags, authors };
|
const projects = defineCollection({
|
||||||
|
schema: z.object({
|
||||||
|
icon: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
year: z.number(),
|
||||||
|
description: z.string(),
|
||||||
|
sourceUrl: z.string(),
|
||||||
|
demoUrl: z.string().nullable().optional(),
|
||||||
|
articleUrl: z.string().nullable().optional(),
|
||||||
|
draft: z.boolean(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { blog, tags, authors, projects };
|
||||||
|
|||||||
11
src/content/projects/en/devthread.md
Normal file
11
src/content/projects/en/devthread.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
icon: triangle_filled
|
||||||
|
draft: false
|
||||||
|
title: devthread
|
||||||
|
year: 2021
|
||||||
|
description: Static website for publishing various articles, developed with
|
||||||
|
Gatsby.js and NetlifyCMS
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/devthread.ru
|
||||||
|
demoUrl: https://devthread.yarkov.tech
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/en/digital-store.md
Normal file
10
src/content/projects/en/digital-store.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: round-shop-two
|
||||||
|
draft: false
|
||||||
|
title: digital-store
|
||||||
|
year: 2019
|
||||||
|
description: Customizable online store with control panel for selling digital goods
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/digital-store/tree/digital-store-docker
|
||||||
|
demoUrl: https://digital-store.herokuapp.com
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/en/educt.md
Normal file
10
src/content/projects/en/educt.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: round-school
|
||||||
|
draft: false
|
||||||
|
title: educt
|
||||||
|
year: 2022
|
||||||
|
description: Learning Management System (LMS) to publish your courses, lectures, lessons
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/educt
|
||||||
|
demoUrl: https://educt.yarkov.tech
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/en/memory-led-game.md
Normal file
10
src/content/projects/en/memory-led-game.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: chip
|
||||||
|
draft: false
|
||||||
|
title: memory-led-game
|
||||||
|
year: 2022
|
||||||
|
description: Analogue of the popular game "Simon says" on the AVR microcontroller
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/attiny24a_memory-led-game
|
||||||
|
demoUrl: null
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/en/node-docker-example.md
Normal file
10
src/content/projects/en/node-docker-example.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: nodejs
|
||||||
|
draft: false
|
||||||
|
title: node-docker-example
|
||||||
|
year: 2022
|
||||||
|
description: An example of using Docker to deploy a Node.js application
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/node-docker-example
|
||||||
|
demoUrl: https://node-docker-example.onrender.com
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/en/react-chat-app.md
Normal file
10
src/content/projects/en/react-chat-app.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: chat
|
||||||
|
draft: false
|
||||||
|
title: react-chat-app
|
||||||
|
year: 2021
|
||||||
|
description: Client-server web chat application developed with Node.js and Socket.io
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/react-chat-app
|
||||||
|
demoUrl: null
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
11
src/content/projects/en/web-serial-example.md
Normal file
11
src/content/projects/en/web-serial-example.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
icon: serial
|
||||||
|
draft: false
|
||||||
|
title: web-serial-example
|
||||||
|
year: 2022
|
||||||
|
description: An example of using the Web Serial API to interact with the
|
||||||
|
microcontroller through a browser
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/avr128da48_web-serial-example
|
||||||
|
demoUrl: https://web-serial-example.netlify.app
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
11
src/content/projects/en/yarkov-tech.md
Normal file
11
src/content/projects/en/yarkov-tech.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
icon: book
|
||||||
|
draft: false
|
||||||
|
title: yarkov.tech
|
||||||
|
year: 2023
|
||||||
|
description: My personal website where you can find information about me and a
|
||||||
|
list of various articles and tutorials...
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/yarkov.tech
|
||||||
|
demoUrl: https://yarkov.tech
|
||||||
|
articleUrl: null
|
||||||
|
---
|
||||||
10
src/content/projects/ru/devthread.md
Normal file
10
src/content/projects/ru/devthread.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: triangle_filled
|
||||||
|
draft: false
|
||||||
|
title: devthread
|
||||||
|
year: 2021
|
||||||
|
description: Статический веб-сайт для публикации различных статей, разработанный
|
||||||
|
при помощи Gatsby.js и NetlifyCMS
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/devthread.ru
|
||||||
|
demoUrl: https://devthread.yarkov.tech
|
||||||
|
---
|
||||||
10
src/content/projects/ru/digital-store.md
Normal file
10
src/content/projects/ru/digital-store.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: round-shop-two
|
||||||
|
draft: false
|
||||||
|
title: digital-store
|
||||||
|
year: 2019
|
||||||
|
description: Настраиваемый интернет-магазин с панелью управления для продажи
|
||||||
|
цифровых товаров
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/digital-store/tree/digital-store-docker
|
||||||
|
demoUrl: https://digital-store.herokuapp.com
|
||||||
|
---
|
||||||
9
src/content/projects/ru/educt.md
Normal file
9
src/content/projects/ru/educt.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
icon: round-school
|
||||||
|
draft: false
|
||||||
|
title: educt
|
||||||
|
year: 2022
|
||||||
|
description: Система управления обучением (LMS) для публикации ваших курсов, лекций, уроков
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/educt
|
||||||
|
demoUrl: https://educt.yarkov.tech
|
||||||
|
---
|
||||||
8
src/content/projects/ru/memory-led-game.md
Normal file
8
src/content/projects/ru/memory-led-game.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
icon: chip
|
||||||
|
draft: false
|
||||||
|
title: memory-led-game
|
||||||
|
year: 2022
|
||||||
|
description: Аналог популярной игры "Саймон говорит" на AVR микроконтроллере
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/attiny24a_memory-led-game
|
||||||
|
---
|
||||||
9
src/content/projects/ru/node-docker-example.md
Normal file
9
src/content/projects/ru/node-docker-example.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
icon: nodejs
|
||||||
|
draft: false
|
||||||
|
title: node-docker-example
|
||||||
|
year: 2022
|
||||||
|
description: Пример использования Docker для развертывания Node.js приложения
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/node-docker-example
|
||||||
|
demoUrl: https://node-docker-example.onrender.com
|
||||||
|
---
|
||||||
9
src/content/projects/ru/react-chat-app.md
Normal file
9
src/content/projects/ru/react-chat-app.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
icon: chat
|
||||||
|
draft: false
|
||||||
|
title: react-chat-app
|
||||||
|
year: 2021
|
||||||
|
description: Клиент-серверное веб приложение чата разработанное при помощи
|
||||||
|
Node.js и Socket.io
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/react-chat-app
|
||||||
|
---
|
||||||
11
src/content/projects/ru/web-serial-example.md
Normal file
11
src/content/projects/ru/web-serial-example.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
icon: serial
|
||||||
|
draft: false
|
||||||
|
title: web-serial-example
|
||||||
|
year: 2022
|
||||||
|
description: Пример использования Web Serial API для взаимодействия с
|
||||||
|
микроконтроллером через браузер.
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/avr128da48_web-serial-example
|
||||||
|
demoUrl: https://web-serial-example.netlify.app
|
||||||
|
articleUrl: https://yarkov.tech/blog/2022-11-04/ispolzovanie-webserial-api-dlya-obshcheniya-s-mikrokontrollerom/
|
||||||
|
---
|
||||||
10
src/content/projects/ru/yarkov-tech.md
Normal file
10
src/content/projects/ru/yarkov-tech.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
icon: book
|
||||||
|
draft: false
|
||||||
|
title: yarkov.tech
|
||||||
|
year: 2023
|
||||||
|
description: Мой персональный веб-сайт где находятся информация обо мне и список
|
||||||
|
различных статей и туториалов...
|
||||||
|
sourceUrl: https://github.com/sergeyyarkov/yarkov.tech
|
||||||
|
demoUrl: https://yarkov.tech
|
||||||
|
---
|
||||||
3
src/content/site-settings.yml
Normal file
3
src/content/site-settings.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
globalTitle: yarkov.tech
|
||||||
|
articles:
|
||||||
|
recentLimit: 5
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export default {
|
export default {
|
||||||
ru: "Русский",
|
ru: "Русский",
|
||||||
en: "English",
|
en: "English",
|
||||||
de: "Deutsch",
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { AstroGlobal, MDXInstance } from "astro";
|
|
||||||
import languages from "./languages";
|
import languages from "./languages";
|
||||||
import { DEFAULT_LANGUAGE } from "@root/constants";
|
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/constants";
|
||||||
|
|
||||||
function transformExports<T>(modules: Record<string, { default: T }>) {
|
function transformExports<T>(modules: Record<string, { default: T }>) {
|
||||||
const translations: Record<string, T> = {};
|
const translations: Record<string, T> = {};
|
||||||
@ -11,6 +10,12 @@ function transformExports<T>(modules: Record<string, { default: T }>) {
|
|||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLanguageKeys(): Array<string> {
|
||||||
|
return Object.keys(languages).filter((lang) =>
|
||||||
|
SUPPORTED_LANGUAGES.includes(lang as LanguageKeys)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const getLanguageFromURL = (pathname: string): LanguageKeys => {
|
export const getLanguageFromURL = (pathname: string): LanguageKeys => {
|
||||||
const lang = pathname.split("/")[1] as LanguageKeys;
|
const lang = pathname.split("/")[1] as LanguageKeys;
|
||||||
if (Object.hasOwn(languages, lang)) return lang;
|
if (Object.hasOwn(languages, lang)) return lang;
|
||||||
|
|||||||
@ -1,38 +1,42 @@
|
|||||||
---
|
---
|
||||||
import type { Props as BaseHeadProps } from "@root/components/BaseHead.astro";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
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/ArticleContent.astro";
|
||||||
import ArticlePost from "@components/ArticlePost/ArticlePost.astro";
|
import ArticlePost from "@components/ArticlePost/ArticlePost.astro";
|
||||||
import { SITE_METADATA } from "@root/constants";
|
import OpenGraph from "@components/OpenGraph.astro";
|
||||||
import type { CollectionEntry } from "astro:content";
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
lang?: string | undefined;
|
lang?: string | undefined;
|
||||||
content: CollectionEntry<"blog">;
|
article: CollectionEntry<"blog">;
|
||||||
}
|
}
|
||||||
const { lang = "ru", content } = Astro.props as Props;
|
|
||||||
|
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||||
|
const { lang = DEFAULT_LANGUAGE, article } = Astro.props as Props;
|
||||||
|
const {
|
||||||
|
data: { title, description, coverImage },
|
||||||
|
} = article;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang={lang}>
|
<html lang={lang}>
|
||||||
<BaseHead title={content.data.title} description={content.data.description}>
|
<BaseHead title={title} description={description}>
|
||||||
<meta property="og:title" content={content.data.title} />
|
<OpenGraph
|
||||||
<meta property="og:site_name" content={SITE_METADATA.name} />
|
title={title}
|
||||||
<meta property="og:description" content={content.data.description} />
|
siteName={SITE_METADATA.name}
|
||||||
<meta property="og:type" content="article" />
|
description={description}
|
||||||
<!-- <meta property="og:url" content={canonicalUrl} /> -->
|
type="article"
|
||||||
<!-- {content.coverImage && (
|
url={canonicalUrl.href}
|
||||||
<meta property="og:image" content={isURL(content.coverImage) ? content.coverImage : `${Astro.url.origin}${content.coverImage}`} />
|
image={coverImage
|
||||||
<meta property="og:image:width" content="700" />
|
? { path: coverImage, width: "700", height: "400", type: coverImage }
|
||||||
<meta property="og:image:height" content="400" />
|
: undefined}
|
||||||
<meta property="og:image:type" content={`${mime.lookup(extname(content.coverImage))}`} />
|
/>
|
||||||
)} -->
|
|
||||||
<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={content}>
|
<ArticlePost lang={lang} article={article}>
|
||||||
<ArticleContent>
|
<ArticleContent>
|
||||||
<slot />
|
<slot />
|
||||||
</ArticleContent>
|
</ArticleContent>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import type { Props as BaseHeadProps } from "@components/BaseHead.astro";
|
import type { Props as BaseHeadProps } from "@components/BaseHead.astro";
|
||||||
import { 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";
|
||||||
|
|
||||||
@ -10,11 +10,14 @@ export interface Props extends BaseHeadProps {
|
|||||||
canonicalUrl?: string | undefined;
|
canonicalUrl?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, description, lang = "ru", noIndex = false } = Astro.props as Props;
|
const { title, description, lang = DEFAULT_LANGUAGE, noIndex = false } = Astro.props as Props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang={lang}>
|
<html lang={lang}>
|
||||||
<BaseHead title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title} description={description}>
|
<BaseHead
|
||||||
|
title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title}
|
||||||
|
description={description}
|
||||||
|
>
|
||||||
{noIndex && <meta name="robots" content="noindex" />}
|
{noIndex && <meta name="robots" content="noindex" />}
|
||||||
</BaseHead>
|
</BaseHead>
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
|||||||
5
src/pages/404.astro
Normal file
5
src/pages/404.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import NotFound from "@templates/NotFound.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<NotFound />
|
||||||
22
src/pages/[...slug].astro
Normal file
22
src/pages/[...slug].astro
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
13
src/pages/[lang]/about.astro
Normal file
13
src/pages/[lang]/about.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { getLanguageKeys } from "@i18n/utils";
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import About from "@templates/About.astro";
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return getLanguageKeys()
|
||||||
|
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||||
|
.map((lang) => ({ params: { lang } }));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<About />
|
||||||
13
src/pages/[lang]/articles.astro
Normal file
13
src/pages/[lang]/articles.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { getLanguageKeys } from "@i18n/utils";
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import Articles from "@templates/Articles.astro";
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return getLanguageKeys()
|
||||||
|
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||||
|
.map((lang) => ({ params: { lang } }));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Articles />
|
||||||
13
src/pages/[lang]/contacts.astro
Normal file
13
src/pages/[lang]/contacts.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { getLanguageKeys } from "@i18n/utils";
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import Contacts from "@templates/Contacts.astro";
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return getLanguageKeys()
|
||||||
|
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||||
|
.map((lang) => ({ params: { lang } }));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Contacts />
|
||||||
13
src/pages/[lang]/index.astro
Normal file
13
src/pages/[lang]/index.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { getLanguageKeys } from "@i18n/utils";
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import Home from "@templates/Home.astro";
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return getLanguageKeys()
|
||||||
|
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||||
|
.map((lang) => ({ params: { lang } }));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Home />
|
||||||
13
src/pages/[lang]/projects.astro
Normal file
13
src/pages/[lang]/projects.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import { getLanguageKeys } from "@i18n/utils";
|
||||||
|
import { DEFAULT_LANGUAGE } from "@root/constants";
|
||||||
|
import Projects from "@templates/Projects.astro";
|
||||||
|
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return getLanguageKeys()
|
||||||
|
.filter((lang) => lang !== DEFAULT_LANGUAGE)
|
||||||
|
.map((lang) => ({ params: { lang } }));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Projects />
|
||||||
5
src/pages/about.astro
Normal file
5
src/pages/about.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import About from "@templates/About.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<About />
|
||||||
5
src/pages/articles.astro
Normal file
5
src/pages/articles.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Articles from "@templates/Articles.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Articles />
|
||||||
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
import { CollectionEntry, getCollection } from "astro:content";
|
|
||||||
import ArticleLayout from "@root/layouts/article.astro";
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const posts = await getCollection("blog");
|
|
||||||
return posts.map((post) => ({
|
|
||||||
params: { slug: post.slug },
|
|
||||||
props: post,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = CollectionEntry<"blog">;
|
|
||||||
|
|
||||||
const post = Astro.props;
|
|
||||||
const { Content } = await post.render();
|
|
||||||
---
|
|
||||||
|
|
||||||
<ArticleLayout content={post}>
|
|
||||||
<Content />
|
|
||||||
</ArticleLayout>
|
|
||||||
11
src/pages/ping.astro
Normal file
11
src/pages/ping.astro
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>pong</title>
|
||||||
|
</head>
|
||||||
|
<body>pong</body>
|
||||||
|
</html>
|
||||||
5
src/pages/projects.astro
Normal file
5
src/pages/projects.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import Projects from "@root/templates/Projects.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Projects />
|
||||||
26
src/templates/About.astro
Normal file
26
src/templates/About.astro
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from "@layouts/default.astro";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
// import { useTranslation } from "@i18n/utils";
|
||||||
|
// import * as API from "@root/lib/api";
|
||||||
|
import PageHeading from "@components/PageHeading.astro";
|
||||||
|
// import SkillsList from "@components/SkillsList";
|
||||||
|
// import SkillsBlock from "@components/SkillsBlock";
|
||||||
|
// import ArticleContent from "@components/ArticleContent";
|
||||||
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
// const t = useTranslation(Astro);
|
||||||
|
// const data = await API.fetchAboutMe(Astro);
|
||||||
|
// const { skills } = SITE_METADATA;
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout noIndex lang={lang} title={"Обо мне"}>
|
||||||
|
<PageHeading heading={"Обо мне"} />
|
||||||
|
<section>
|
||||||
|
<!-- <ArticleContent>
|
||||||
|
{data && <data.Content />}
|
||||||
|
</ArticleContent> -->
|
||||||
|
<!-- <SkillsList>
|
||||||
|
{skills.map((block) => <SkillsBlock data={block} />)}
|
||||||
|
</SkillsList> -->
|
||||||
|
</section>
|
||||||
|
</PageLayout>
|
||||||
39
src/templates/Articles.astro
Normal file
39
src/templates/Articles.astro
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from "@layouts/default.astro";
|
||||||
|
import PageHeading from "@components/PageHeading.astro";
|
||||||
|
// import ArticlesList from "@components/ArticlesList";
|
||||||
|
// import * as utils from "@utils/articles";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
// import { useTranslation } from "@i18n/utils";
|
||||||
|
// import * as API from "@root/lib/api";
|
||||||
|
// const t = useTranslation(Astro);
|
||||||
|
// const data = API.fetchTranslatedArticles(Astro);
|
||||||
|
// const tags = new Set(data.map((article) => article.frontmatter.tags).flat());
|
||||||
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
// const articles = utils.transformArticles(data);
|
||||||
|
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout
|
||||||
|
lang={lang}
|
||||||
|
title={"Статьи"}
|
||||||
|
description={"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь."}
|
||||||
|
canonicalUrl={canonicalUrl.href}
|
||||||
|
>
|
||||||
|
<PageHeading
|
||||||
|
heading={"Статьи"}
|
||||||
|
description={"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь."}
|
||||||
|
/>
|
||||||
|
<section>
|
||||||
|
...
|
||||||
|
<!-- <ArticlesList
|
||||||
|
client:load
|
||||||
|
lang={lang}
|
||||||
|
i18n={{
|
||||||
|
"input.search": t("input.search"),
|
||||||
|
"articles.empty": t("articles.empty"),
|
||||||
|
}}
|
||||||
|
data={{ articles: utils.formatToArticleBlocks(articles), tags: [...tags] }}
|
||||||
|
/> -->
|
||||||
|
</section>
|
||||||
|
</PageLayout>
|
||||||
@ -7,7 +7,9 @@ import SplashLink from "@root/components/Splash/SplashLink.astro";
|
|||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import RecentArticles from "@root/components/RecentArticles.astro";
|
import RecentArticles from "@root/components/RecentArticles.astro";
|
||||||
|
|
||||||
const articles = (await getCollection("blog")).sort((a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf());
|
const articles = (await getCollection("blog"))
|
||||||
|
.filter((p) => !p.data.draft)
|
||||||
|
.sort((a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf());
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageLayout description="Home page" lang={"ru"}>
|
<PageLayout description="Home page" lang={"ru"}>
|
||||||
@ -18,8 +20,8 @@ const articles = (await getCollection("blog")).sort((a, b) => a.data.pubDate.val
|
|||||||
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
|
<h2>Fullstack <b>web-developer</b> & <b>freelancer</b></h2>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
<p>
|
<p>
|
||||||
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую статьи и туториалы на
|
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую
|
||||||
разные IT темы.
|
статьи и туториалы на разные IT темы.
|
||||||
</p>
|
</p>
|
||||||
</SplashHeading>
|
</SplashHeading>
|
||||||
<SplashLinks>
|
<SplashLinks>
|
||||||
|
|||||||
13
src/templates/NotFound.astro
Normal file
13
src/templates/NotFound.astro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from "@layouts/default.astro";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
// const t = useTranslation(Astro);
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout lang={lang}>
|
||||||
|
<div class="container">
|
||||||
|
<h1 style="font-size:var(--font-size-5xl);">404</h1>
|
||||||
|
<p>{"Страница не найдена"}</p>
|
||||||
|
</div>
|
||||||
|
</PageLayout>
|
||||||
31
src/templates/Projects.astro
Normal file
31
src/templates/Projects.astro
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
import PageLayout from "@layouts/default.astro";
|
||||||
|
import ProjectsList from "@components/ProjectsList.astro";
|
||||||
|
import ProjectItem from "@components/ProjectItem.astro";
|
||||||
|
import PageHeading from "@components/PageHeading.astro";
|
||||||
|
import { getLanguageFromURL } from "@i18n/utils";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
// import { useTranslation } from "@i18n/utils";
|
||||||
|
// const t = useTranslation(Astro);
|
||||||
|
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||||
|
const projects = (await getCollection("projects")).filter(
|
||||||
|
(p) => !p.data.draft && p.id.split(".")[1] === lang
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<PageLayout
|
||||||
|
noIndex
|
||||||
|
lang={lang}
|
||||||
|
title={"Проекты"}
|
||||||
|
description={"Несколько моих основных проектов которые были разработаны и выложены в открытый доступ."}
|
||||||
|
>
|
||||||
|
<PageHeading
|
||||||
|
heading={"Проекты"}
|
||||||
|
description={"Несколько моих основных проектов которые были разработаны и выложены в открытый доступ."}
|
||||||
|
/>
|
||||||
|
<section>
|
||||||
|
<ProjectsList>
|
||||||
|
{projects.map((project) => <ProjectItem data={project.data} />)}
|
||||||
|
</ProjectsList>
|
||||||
|
</section>
|
||||||
|
</PageLayout>
|
||||||
Loading…
x
Reference in New Issue
Block a user