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,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"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 { escape } from "html-escaper";
|
||||
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 node = h("span.anchor-label", escape(heading));
|
||||
@ -48,11 +48,11 @@ export default defineConfig({
|
||||
preview: false,
|
||||
},
|
||||
display_url: "https://yarkov.tech",
|
||||
// i18n: {
|
||||
// locales: SUPPORTED_LANGUAGES,
|
||||
// default_locale: DEFAULT_LANGUAGE,
|
||||
// structure: "multiple_files",
|
||||
// },
|
||||
i18n: {
|
||||
locales: SUPPORTED_LANGUAGES,
|
||||
default_locale: DEFAULT_LANGUAGE,
|
||||
structure: "multiple_folders",
|
||||
},
|
||||
locale: DEFAULT_LANGUAGE,
|
||||
backend: {
|
||||
name: "test-repo",
|
||||
@ -61,6 +61,9 @@ export default defineConfig({
|
||||
media_folder: "public/media",
|
||||
public_folder: "/media",
|
||||
collections: [
|
||||
/**
|
||||
* Articles
|
||||
*/
|
||||
{
|
||||
name: "posts",
|
||||
label: "Публикации",
|
||||
@ -75,6 +78,14 @@ export default defineConfig({
|
||||
widget: "image",
|
||||
label: "Постер",
|
||||
},
|
||||
{
|
||||
name: "draft",
|
||||
label: "В черновике",
|
||||
widget: "boolean",
|
||||
required: true,
|
||||
default: true,
|
||||
hint: "Опубликована ли статья",
|
||||
},
|
||||
{
|
||||
name: "author",
|
||||
widget: "relation",
|
||||
@ -107,9 +118,17 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
name: "pubDate",
|
||||
widget: "date",
|
||||
widget: "datetime",
|
||||
label: "Дата публикации",
|
||||
required: true,
|
||||
picker_utc: true,
|
||||
},
|
||||
{
|
||||
name: "updatedDate",
|
||||
widget: "datetime",
|
||||
label: "Дата обновления",
|
||||
required: true,
|
||||
picker_utc: true,
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
@ -118,6 +137,9 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Tags
|
||||
*/
|
||||
{
|
||||
name: "tags",
|
||||
label: "Тэги",
|
||||
@ -135,6 +157,9 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Authors
|
||||
*/
|
||||
{
|
||||
name: "authors",
|
||||
label: "Авторы",
|
||||
@ -159,12 +184,88 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
label: "Об авторе",
|
||||
name: "about",
|
||||
name: "body",
|
||||
widget: "markdown",
|
||||
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",
|
||||
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",
|
||||
"html-escaper": "^3.0.3",
|
||||
"ioredis": "^5.3.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"quicklink": "^2.3.0",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
@ -29,6 +30,7 @@
|
||||
"solid-js": "^1.6.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"sass": "^1.58.0"
|
||||
}
|
||||
},
|
||||
@ -1689,6 +1691,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.3.tgz",
|
||||
"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": {
|
||||
"version": "0.7.31",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
"hastscript": "^7.2.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
"ioredis": "^5.3.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"quicklink": "^2.3.0",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
@ -38,6 +39,7 @@
|
||||
"solid-js": "^1.6.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"sass": "^1.58.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export type Props = {
|
||||
const { article, lang } = Astro.props as Props;
|
||||
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||
const {
|
||||
data: { coverImage, title, description, tags, pubDate },
|
||||
data: { coverImage, title, author, description, tags, pubDate },
|
||||
slug,
|
||||
} = article;
|
||||
// const t = useTranslation(Astro);
|
||||
@ -48,8 +48,10 @@ const {
|
||||
<h1 itemprop="headline">{title}</h1>
|
||||
</div>
|
||||
<div class="published">
|
||||
<time itemprop="datePublished" datetime={pubDate.toString()}>{createDateFormatter(lang).format(pubDate)}</time>
|
||||
<span itemprop="author">{"Sergey Yarkov"}</span>
|
||||
<time itemprop="datePublished" datetime={pubDate.toString()}
|
||||
>{createDateFormatter(lang).format(pubDate)}</time
|
||||
>
|
||||
<span itemprop="author">{author}</span>
|
||||
</div>
|
||||
{
|
||||
coverImage && (
|
||||
|
||||
@ -4,7 +4,7 @@ import { SITE_METADATA } from "@root/constants";
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ const { title, description } = Astro.props;
|
||||
<!-- Metadata -->
|
||||
<meta name="title" content={title || SITE_METADATA.title} />
|
||||
<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="msapplication-TileColor" content="#da532c" />
|
||||
|
||||
@ -55,25 +55,22 @@ const { title, description } = Astro.props;
|
||||
href="/favicon-light-16x16.png"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
<link 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="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="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 />
|
||||
<script is:inline src="/assets/js/detect-theme.js"></script>
|
||||
<!-- <script src="../quicklink.js"></script> -->
|
||||
|
||||
@ -17,17 +17,17 @@ import NavToggle from "@components/NavToggle";
|
||||
{
|
||||
(
|
||||
<li>
|
||||
<HeaderLink itemprop={"url"} href={"#"}>
|
||||
<HeaderLink itemprop={"url"} href={"/articles"}>
|
||||
статьи
|
||||
</HeaderLink>
|
||||
</li>
|
||||
<li>
|
||||
<HeaderLink itemprop={"url"} href={"#"}>
|
||||
<HeaderLink itemprop={"url"} href={"/projects"}>
|
||||
проекты
|
||||
</HeaderLink>
|
||||
</li>
|
||||
<li>
|
||||
<HeaderLink itemprop={"url"} href={"#"}>
|
||||
<HeaderLink itemprop={"url"} href={"/about"}>
|
||||
обо мне
|
||||
</HeaderLink>
|
||||
</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";
|
||||
|
||||
/* 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"];
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
coverImage: /media/serial-port.jpg
|
||||
draft: false
|
||||
author: Sergey Yarkov
|
||||
title: Использование WebSerial API для общения с микроконтроллером
|
||||
description: Речь пойдет о том, что такое WebSerial API, как его можно
|
||||
@ -10,9 +11,10 @@ tags:
|
||||
- AVR
|
||||
- WebSerial API
|
||||
- Tutorial
|
||||
pubDate: 2022-11-04
|
||||
draft: false
|
||||
pubDate: 2022-11-04T21:00:00.000Z
|
||||
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.
|
||||
@ -47,17 +49,17 @@ WebSerial API - это браузерный API, который предоста
|
||||
|
||||
UART передает данные последовательно по одному биту в одном из трех режимов:
|
||||
|
||||
* односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
|
||||
* полудуплексный режим - устройства могут передавать и принимать данные по очереди
|
||||
* полнодуплексный режим - устройства могут передавать и принимать данные одновременно
|
||||
- односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
|
||||
- полудуплексный режим - устройства могут передавать и принимать данные по очереди
|
||||
- полнодуплексный режим - устройства могут передавать и принимать данные одновременно
|
||||
|
||||
Так как в этом проекте предполагается использование асинхронной версии UART, то соответственно необходимо устанавливать **одинаковую** скорость передачи битов в секунду (`baud rate`, `bps`) на двух устройствах. Наиболее распространенные скорости являются: **4800, 9600, 19.2 К, 57.6К и 115.2К.**
|
||||
|
||||
Данные передаются в виде так называемых `пакетов`, где каждый пакет содержит в себе:
|
||||
|
||||
* стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
|
||||
* биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
|
||||
* бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
|
||||
- стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
|
||||
- биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
|
||||
- бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
|
||||
|
||||
<figure>
|
||||
<img src="/media/uart-packet.jpg" width="700" height="300" alt="Структура UART пакета" format="webp">
|
||||
@ -82,7 +84,7 @@ UART передает данные последовательно по одно
|
||||
|
||||
Как я упомянул ранее, прошивка будет написана для микроконтроллера AVR128DA48. На самом деле неважно какой микроконтроллер вы используете, вам достаточно нужно уметь отправлять и принимать данные используя UART периферию вашего микроконтроллера.
|
||||
|
||||
Суть программы будет очень проста: необходимо написать некий ***обработчик команд*** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
|
||||
Суть программы будет очень проста: необходимо написать некий **_обработчик команд_** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
|
||||
|
||||
Постановка задачи ясна, теперь рассмотрим реализацию данной программы. Я буду использовать среду разработки MPLAB X IDE и язык C для написания прошивки.
|
||||
|
||||
@ -102,10 +104,10 @@ UART передает данные последовательно по одно
|
||||
#endif /* CONSTANTS_H */
|
||||
```
|
||||
|
||||
* `F_CPU` - тактовая частота микроконтроллера
|
||||
* `BAUD_RATE` - скорость передачи данных UART
|
||||
* `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
|
||||
* `EOT` - End-of-Transmission, ASCII символ конца передачи данных
|
||||
- `F_CPU` - тактовая частота микроконтроллера
|
||||
- `BAUD_RATE` - скорость передачи данных UART
|
||||
- `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
|
||||
- `EOT` - End-of-Transmission, ASCII символ конца передачи данных
|
||||
|
||||
## Инициализация USART
|
||||
|
||||
@ -155,11 +157,11 @@ void USART1_Initialize(void) {
|
||||
|
||||
Для полнодуплексного режима инициализация асинхронной версии USART происходит следующим образом:
|
||||
|
||||
* Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
|
||||
* Конфигурация размера `фрейма`, в нашем случае это 8 бит.
|
||||
* Конфигурация пина `TX` на выход и пина `RX` на вход.
|
||||
* Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
|
||||
* Включение приемника и передатчика.
|
||||
- Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
|
||||
- Конфигурация размера `фрейма`, в нашем случае это 8 бит.
|
||||
- Конфигурация пина `TX` на выход и пина `RX` на вход.
|
||||
- Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
|
||||
- Включение приемника и передатчика.
|
||||
|
||||
Теперь, рассмотрим функции отправки данных:
|
||||
|
||||
@ -211,9 +213,9 @@ void command_list(void);
|
||||
|
||||
Для решения этой задачи я предлагаю создавать массив структур, где в каждой структуре будет содержаться указатель на функцию которую нам нужно выполнить, и название самой команды.
|
||||
|
||||
* `command_define` будет создавать новую структуру в массиве.
|
||||
* `command_process` будет вызывать соответствующую функцию по имени команды.
|
||||
* `command_list` будет отправлять по UART информацию о существующих командах.
|
||||
- `command_define` будет создавать новую структуру в массиве.
|
||||
- `command_process` будет вызывать соответствующую функцию по имени команды.
|
||||
- `command_list` будет отправлять по UART информацию о существующих командах.
|
||||
|
||||
Реализовать задуманное можно следующим образом создав файл `command.c`:
|
||||
|
||||
@ -367,9 +369,9 @@ void hello_world(void) {
|
||||
|
||||
С помощью функции `command_define` мы можем определить, по какой переданной команде по USART запускать функцию:
|
||||
|
||||
* `led_toggle` будет переключать светодиод.
|
||||
* `hello_world` выводить сообщение “Hello world”.
|
||||
* `command_list` будет показывать список имеющихся команд.
|
||||
- `led_toggle` будет переключать светодиод.
|
||||
- `hello_world` выводить сообщение “Hello world”.
|
||||
- `command_list` будет показывать список имеющихся команд.
|
||||
|
||||
## Демонстрация работы
|
||||
|
||||
@ -377,10 +379,10 @@ void hello_world(void) {
|
||||
|
||||
Выберете ваше устройство из списка (в моем случае это `/dev/ttyACM1`) и установите следующие настройки:
|
||||
|
||||
* Скорость: **9600** бод
|
||||
* Количество битов: **8**
|
||||
* Четность: **None**
|
||||
* Стоповые биты: **1**
|
||||
- Скорость: **9600** бод
|
||||
- Количество битов: **8**
|
||||
- Четность: **None**
|
||||
- Стоповые биты: **1**
|
||||
|
||||
<figure>
|
||||
<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).
|
||||
* [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder#examples) наоборот, декодирует в строку. Экземпляры этих классов нам понадобятся, т.к мы будет оперировать данными с типом `Uint8Array`.
|
||||
* Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
|
||||
* Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
|
||||
* Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
|
||||
* Функция `setupListeners` просто будет добавлять обработчики событий.
|
||||
- [`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`.
|
||||
- Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
|
||||
- Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
|
||||
- Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
|
||||
- Функция `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):
|
||||
|
||||
* `baudRate` : скорость передачи данных.
|
||||
* `dataBits` : количество бит данных во фрейме (7 или 8).
|
||||
* `stopBits` : количество стоповых битов в конце пакета (1 или 2).
|
||||
* `parity` : режим четности ( `none` , `even` или `odd` ).
|
||||
* `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
|
||||
* `flowControl` : режим управления потоком ( `none` или `hardware` ).
|
||||
- `baudRate` : скорость передачи данных.
|
||||
- `dataBits` : количество бит данных во фрейме (7 или 8).
|
||||
- `stopBits` : количество стоповых битов в конце пакета (1 или 2).
|
||||
- `parity` : режим четности ( `none` , `even` или `odd` ).
|
||||
- `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
|
||||
- `flowControl` : режим управления потоком ( `none` или `hardware` ).
|
||||
|
||||
Свойство `baudRate` является единственным обязательным параметром, остальные же являются необязательными и имеют [значения по умолчанию](https://wicg.github.io/serial/#serialoptions-dictionary).
|
||||
|
||||
@ -891,8 +893,8 @@ setInterval(async () => {
|
||||
|
||||
# Полезные материалы
|
||||
|
||||
* [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
|
||||
* [Документация к WebSerial API](https://wicg.github.io/serial/)
|
||||
* [Подробное руководство по Stream API](https://web.dev/streams/)
|
||||
* [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
|
||||
* [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)
|
||||
- [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
|
||||
- [Документация к WebSerial API](https://wicg.github.io/serial/)
|
||||
- [Подробное руководство по Stream API](https://web.dev/streams/)
|
||||
- [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
|
||||
- [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)
|
||||
|
||||
@ -13,8 +13,10 @@ const blog = defineCollection({
|
||||
.transform((val) => new Date(val)),
|
||||
updatedDate: z
|
||||
.string()
|
||||
.or(z.date())
|
||||
.optional()
|
||||
.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 {
|
||||
ru: "Русский",
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { AstroGlobal, MDXInstance } from "astro";
|
||||
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 }>) {
|
||||
const translations: Record<string, T> = {};
|
||||
@ -11,6 +10,12 @@ function transformExports<T>(modules: Record<string, { default: T }>) {
|
||||
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 => {
|
||||
const lang = pathname.split("/")[1] as LanguageKeys;
|
||||
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 BaseLayout from "@components/BaseLayout.astro";
|
||||
import ArticleContent from "@components/ArticleContent/ArticleContent.astro";
|
||||
import ArticlePost from "@components/ArticlePost/ArticlePost.astro";
|
||||
import { SITE_METADATA } from "@root/constants";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import OpenGraph from "@components/OpenGraph.astro";
|
||||
|
||||
export interface Props {
|
||||
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}>
|
||||
<BaseHead title={content.data.title} description={content.data.description}>
|
||||
<meta property="og:title" content={content.data.title} />
|
||||
<meta property="og:site_name" content={SITE_METADATA.name} />
|
||||
<meta property="og:description" content={content.data.description} />
|
||||
<meta property="og:type" content="article" />
|
||||
<!-- <meta property="og:url" content={canonicalUrl} /> -->
|
||||
<!-- {content.coverImage && (
|
||||
<meta property="og:image" content={isURL(content.coverImage) ? content.coverImage : `${Astro.url.origin}${content.coverImage}`} />
|
||||
<meta property="og:image:width" content="700" />
|
||||
<meta property="og:image:height" content="400" />
|
||||
<meta property="og:image:type" content={`${mime.lookup(extname(content.coverImage))}`} />
|
||||
)} -->
|
||||
<BaseHead title={title} description={description}>
|
||||
<OpenGraph
|
||||
title={title}
|
||||
siteName={SITE_METADATA.name}
|
||||
description={description}
|
||||
type="article"
|
||||
url={canonicalUrl.href}
|
||||
image={coverImage
|
||||
? { path: coverImage, width: "700", height: "400", type: coverImage }
|
||||
: undefined}
|
||||
/>
|
||||
<script is:inline defer src="/assets/js/load-code-highlight.js"></script>
|
||||
</BaseHead>
|
||||
|
||||
<BaseLayout>
|
||||
<div class="container">
|
||||
<ArticlePost lang={lang} article={content}>
|
||||
<ArticlePost lang={lang} article={article}>
|
||||
<ArticleContent>
|
||||
<slot />
|
||||
</ArticleContent>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
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 BaseLayout from "@components/BaseLayout.astro";
|
||||
|
||||
@ -10,11 +10,14 @@ export interface Props extends BaseHeadProps {
|
||||
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}>
|
||||
<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" />}
|
||||
</BaseHead>
|
||||
<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 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"}>
|
||||
@ -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>
|
||||
</hgroup>
|
||||
<p>
|
||||
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую статьи и туториалы на
|
||||
разные IT темы.
|
||||
Я занимаюсь разработкой веб-сайтов и веб-приложений разной сложности, а также публикую
|
||||
статьи и туториалы на разные IT темы.
|
||||
</p>
|
||||
</SplashHeading>
|
||||
<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