mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 10:29:21 +03:00
format
This commit is contained in:
parent
fbe085d9b8
commit
5908df1500
@ -4,7 +4,7 @@ import { toString } from "hast-util-to-string";
|
||||
import { escape } from "html-escaper";
|
||||
import solid from "@astrojs/solid-js";
|
||||
import image from "@astrojs/image";
|
||||
import compress from "astro-compress";
|
||||
// import compress from "astro-compress";
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
const createAnchorLabel = (heading) => {
|
||||
@ -16,15 +16,7 @@ const createAnchorLabel = (heading) => {
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://yarkov.tech",
|
||||
integrations: [
|
||||
solid(),
|
||||
compress({
|
||||
img: false,
|
||||
html: false,
|
||||
}),
|
||||
image(),
|
||||
mdx(),
|
||||
],
|
||||
integrations: [solid(), image(), mdx()],
|
||||
markdown: {
|
||||
syntaxHighlight: "prism",
|
||||
remarkPlugins: ["remark-gfm", "remark-smartypants"],
|
||||
|
||||
2395
package-lock.json
generated
2395
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -19,11 +19,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/image": "^0.3.2",
|
||||
"@astrojs/mdx": "^0.7.0",
|
||||
"@astrojs/mdx": "^0.8.0",
|
||||
"@astrojs/solid-js": "^0.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"astro": "^1.0.0-rc.7",
|
||||
"@types/mdx": "^2.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"astro": "^1.0.0-rc.8",
|
||||
"astro-compress": "^0.0.15",
|
||||
"astro-icon": "^0.7.3",
|
||||
"eslint": "^8.21.0",
|
||||
@ -32,7 +33,7 @@
|
||||
"hastscript": "^7.0.2",
|
||||
"html-escaper": "^3.0.3",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-astro": "^0.3.0",
|
||||
"prettier-plugin-astro": "^0.4.1",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
|
||||
@ -6,17 +6,17 @@
|
||||
*/
|
||||
|
||||
window.theme = (() => {
|
||||
const local = localStorage.getItem('theme');
|
||||
if (local && (local === 'dark' || local === 'light')) return local;
|
||||
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
return isDark ? 'dark' : 'light';
|
||||
const local = localStorage.getItem("theme");
|
||||
if (local && (local === "dark" || local === "light")) return local;
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
return isDark ? "dark" : "light";
|
||||
})();
|
||||
|
||||
document.documentElement.setAttribute('data-theme', window.theme);
|
||||
document.documentElement.setAttribute("data-theme", window.theme);
|
||||
|
||||
/* Sync with OS theme */
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches: isDark }) => {
|
||||
window.theme = isDark ? 'dark' : 'light';
|
||||
localStorage.setItem('theme', window.theme);
|
||||
document.documentElement.setAttribute('data-theme', window.theme);
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ({ matches: isDark }) => {
|
||||
window.theme = isDark ? "dark" : "light";
|
||||
localStorage.setItem("theme", window.theme);
|
||||
document.documentElement.setAttribute("data-theme", window.theme);
|
||||
});
|
||||
|
||||
@ -7,30 +7,30 @@
|
||||
if (window.theme) {
|
||||
/* the {theme} in the path will be dynamically changed */
|
||||
const PATH = `/assets/css/code-highlight-{theme}.min.css`;
|
||||
const $head = document.getElementsByTagName('head')[0];
|
||||
const $head = document.getElementsByTagName("head")[0];
|
||||
const getPath = (theme) => PATH.replace(`{theme}`, theme);
|
||||
const createLink = (theme, media) => {
|
||||
const $link = document.createElement('link');
|
||||
$link.rel = 'stylesheet';
|
||||
$link.type = 'text/css';
|
||||
const $link = document.createElement("link");
|
||||
$link.rel = "stylesheet";
|
||||
$link.type = "text/css";
|
||||
$link.href = getPath(theme);
|
||||
$link.media = media;
|
||||
return $link;
|
||||
};
|
||||
|
||||
/* init links to load them first */
|
||||
const $light = createLink('light', 'none');
|
||||
const $dark = createLink('dark', 'none');
|
||||
const $light = createLink("light", "none");
|
||||
const $dark = createLink("dark", "none");
|
||||
|
||||
const toggleMedia = (theme) => {
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
$light.media = 'all';
|
||||
$dark.media = 'none';
|
||||
case "light":
|
||||
$light.media = "all";
|
||||
$dark.media = "none";
|
||||
break;
|
||||
case 'dark':
|
||||
$light.media = 'none';
|
||||
$dark.media = 'all';
|
||||
case "dark":
|
||||
$light.media = "none";
|
||||
$dark.media = "all";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -41,8 +41,8 @@ if (window.theme) {
|
||||
$head.appendChild($light);
|
||||
$head.appendChild($dark);
|
||||
|
||||
window.addEventListener('onthemetoggled', ({ detail: newTheme }) => toggleMedia(newTheme));
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches: isDark }) => {
|
||||
return isDark ? toggleMedia('dark') : toggleMedia('light');
|
||||
window.addEventListener("onthemetoggled", ({ detail: newTheme }) => toggleMedia(newTheme));
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ({ matches: isDark }) => {
|
||||
return isDark ? toggleMedia("dark") : toggleMedia("light");
|
||||
});
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ArticleContent.astro';
|
||||
export { default } from "./ArticleContent.astro";
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
}
|
||||
& p {
|
||||
font-size: var(--font-size-xs);
|
||||
font-family: monospace;
|
||||
font-family: var(--font-family-monospace);
|
||||
color: var(--font-color-gray);
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from './ArticleItem';
|
||||
export * from "./ArticleItem";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ArticlePost.astro';
|
||||
export { default } from "./ArticlePost.astro";
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Component } from 'solid-js';
|
||||
import { Component } from "solid-js";
|
||||
|
||||
import { createSignal, createEffect, For } from 'solid-js';
|
||||
import { search, selectedTags } from '@stores/searchStore';
|
||||
import { findArticlesBySearch, findArticlesByTags, sortArtcilesByDate } from '@utils/articles';
|
||||
import { ArticleItem } from '@components/ArticleItem';
|
||||
import SearchInput from '@components/SearchInput';
|
||||
import TagsList from '@components/TagsList';
|
||||
import './ArticlesList.scss';
|
||||
import { createSignal, createEffect, For } from "solid-js";
|
||||
import { search, selectedTags } from "@stores/searchStore";
|
||||
import { findArticlesBySearch, findArticlesByTags, sortArtcilesByDate } from "@utils/articles";
|
||||
import { ArticleItem } from "@components/ArticleItem";
|
||||
import SearchInput from "@components/SearchInput";
|
||||
import TagsList from "@components/TagsList";
|
||||
import "./ArticlesList.scss";
|
||||
|
||||
type ArticlesListProps = {
|
||||
data: {
|
||||
@ -69,7 +69,7 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
|
||||
<div class="articles-list">
|
||||
{isEmpty() ? (
|
||||
<div class="articles-list__empty">
|
||||
<p>{props.emptyPlaceholder || 'No articles found...'}</p>
|
||||
<p>{props.emptyPlaceholder || "No articles found..."}</p>
|
||||
</div>
|
||||
) : (
|
||||
<For
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ArticlesList';
|
||||
export { default } from "./ArticlesList";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import { SITE_METADATA } from '@root/config';
|
||||
import { SITE_METADATA } from "@root/config";
|
||||
|
||||
export interface Props {
|
||||
title?: string | undefined;
|
||||
@ -11,7 +11,8 @@ const { title = SITE_METADATA.title, description } = Astro.props as Props;
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono&display=swap" rel="stylesheet"
|
||||
/>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@ -21,5 +22,7 @@ const { title = SITE_METADATA.title, description } = Astro.props as Props;
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description || SITE_METADATA.description} />
|
||||
<meta name="theme-color" content={SITE_METADATA.themeColor} />
|
||||
<script is:inline src="/assets/js/detect-theme.js" />
|
||||
<script is:inline src="/assets/js/detect-theme.js">
|
||||
|
||||
</script>
|
||||
<slot />
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './BaseLayout.astro';
|
||||
export { default } from "./BaseLayout.astro";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './Button';
|
||||
export { default } from "./Button";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ContactForm';
|
||||
export { default } from "./ContactForm";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ContactsList.astro';
|
||||
export { default } from "./ContactsList.astro";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './Counter';
|
||||
export { default } from "./Counter";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './Footer.astro';
|
||||
export { default } from "./Footer.astro";
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
---
|
||||
import './Header.scss';
|
||||
import { SITE_METADATA } from '@root/config';
|
||||
import ThemeSwitcher from '@components/ThemeSwticher';
|
||||
import { Icon } from 'astro-icon';
|
||||
import { removeSlashes } from '@utils/string'
|
||||
import NavToggle from './NavToggle';
|
||||
import { Icon } from "astro-icon";
|
||||
import { SITE_METADATA } from "@root/config";
|
||||
import ThemeSwitcher from "@components/ThemeSwticher";
|
||||
import { removeSlashes } from "@utils/string";
|
||||
import NavToggle from "./NavToggle";
|
||||
import "./Header.scss";
|
||||
|
||||
const { name, nav, contacts } = SITE_METADATA;
|
||||
const { pathname } = Astro.canonicalURL;
|
||||
const { name, nav, contacts } = SITE_METADATA;
|
||||
const { pathname } = Astro.url;
|
||||
---
|
||||
|
||||
<header class='header'>
|
||||
<div class='container'>
|
||||
<div class='header__logo'>
|
||||
<a href='/'>{name}</a>
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<div class="header__logo">
|
||||
<a href="/">{name}</a>
|
||||
</div>
|
||||
<nav class='header__nav'>
|
||||
<ul class='header__nav-links'>
|
||||
<nav class="header__nav">
|
||||
<ul class="header__nav-links">
|
||||
{nav.map((link) => (
|
||||
<li>
|
||||
<a class={removeSlashes(pathname)===removeSlashes(link.url) && 'active' } href={link.url}>
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class={removeSlashes(pathname) === removeSlashes(link.url) && "active"} href={link.url}>
|
||||
{link.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div class='header__nav-contacts'>
|
||||
<a href={'mailto:' + contacts.email}>
|
||||
<div class="header__nav-contacts">
|
||||
<a href={"mailto:" + contacts.email}>
|
||||
Почта
|
||||
<Icon name='email' width='18' height='18' />
|
||||
<Icon name="email" width="18" height="18" />
|
||||
</a>
|
||||
<a href={contacts.githubUrl}>
|
||||
Github
|
||||
<Icon name='github_filled' width='18' height='18' />
|
||||
<Icon name="github_filled" width="18" height="18" />
|
||||
</a>
|
||||
<a href={contacts.telegramUrl}>
|
||||
Телеграм
|
||||
<Icon name='telegram' width='18' height='18' />
|
||||
<Icon name="telegram" width="18" height="18" />
|
||||
</a>
|
||||
</div>
|
||||
<ThemeSwitcher client:visible />
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import type { Component } from "solid-js";
|
||||
|
||||
import { createSignal, createEffect } from 'solid-js';
|
||||
import './NavToggle.scss';
|
||||
import { createSignal, createEffect } from "solid-js";
|
||||
import "./NavToggle.scss";
|
||||
|
||||
const MenuIcon: Component = () => (
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="24px" height="24px">
|
||||
@ -24,11 +24,11 @@ const NavToggle: Component = () => {
|
||||
const body = document.body;
|
||||
|
||||
if (isNavShown()) {
|
||||
body.setAttribute('data-is-mobile-open', 'true');
|
||||
body.setAttribute("data-is-mobile-open", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
body.removeAttribute('data-is-mobile-open');
|
||||
body.removeAttribute("data-is-mobile-open");
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './NavToggle';
|
||||
export { default } from "./NavToggle";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './Header.astro';
|
||||
export { default } from "./Header.astro";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import './PageHeading.scss';
|
||||
import "./PageHeading.scss";
|
||||
|
||||
export interface Props {
|
||||
heading: string;
|
||||
@ -9,8 +9,8 @@ export interface Props {
|
||||
const { heading, description } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<div class='page-heading'>
|
||||
<h1 style={!description && 'margin-bottom: 2.5rem;'}>
|
||||
<div class="page-heading">
|
||||
<h1 style={!description && "margin-bottom: 2.5rem;"}>
|
||||
{heading}
|
||||
</h1>
|
||||
{description && <p>{description}</p>}
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './PageHeading.astro';
|
||||
export { default } from "./PageHeading.astro";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ProjectItem.astro';
|
||||
export { default } from "./ProjectItem.astro";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ProjectsList.astro';
|
||||
export { default } from "./ProjectsList.astro";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import './RecentArticles.scss';
|
||||
import { ArticleItem } from '@components/ArticleItem';
|
||||
import { sortArtcilesByDate } from '@utils/articles';
|
||||
import "./RecentArticles.scss";
|
||||
import { ArticleItem } from "@components/ArticleItem";
|
||||
import { sortArtcilesByDate } from "@utils/articles";
|
||||
|
||||
export interface Props {
|
||||
articles: ArticleType[];
|
||||
@ -13,13 +13,13 @@ const { articles, length = 5 } = Astro.props as Props;
|
||||
const recent = sortArtcilesByDate(articles);
|
||||
---
|
||||
|
||||
<section class='recent'>
|
||||
<div class='recent-heading'>
|
||||
<section class="recent">
|
||||
<div class="recent-heading">
|
||||
<h2>недавние публикации</h2>
|
||||
<a href='/articles'>смотреть все</a>
|
||||
<a href="/articles">смотреть все</a>
|
||||
</div>
|
||||
|
||||
<div class='recent-list'>
|
||||
<div class="recent-list">
|
||||
{recent.length !== 0 ? (
|
||||
recent.slice(0, length).map((article) => <ArticleItem data={article} />)
|
||||
) : (
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './RecentArticles.astro';
|
||||
export { default } from "./RecentArticles.astro";
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
import { createEffect } from 'solid-js';
|
||||
import { search, setSearch } from '@stores/searchStore';
|
||||
import * as utils from '@utils/history';
|
||||
import './SearchInput.scss';
|
||||
import { createEffect } from "solid-js";
|
||||
import { search, setSearch } from "@stores/searchStore";
|
||||
import * as utils from "@utils/history";
|
||||
import "./SearchInput.scss";
|
||||
|
||||
type SearchInputProps = {
|
||||
count: number;
|
||||
@ -15,10 +15,10 @@ const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
* Reset state and delete `search` param from url
|
||||
*/
|
||||
const resetSearch = () => {
|
||||
const url = utils.deleteParamFromUrl('search');
|
||||
const url = utils.deleteParamFromUrl("search");
|
||||
|
||||
setSearch('');
|
||||
history.replaceState(null, '', url);
|
||||
setSearch("");
|
||||
history.replaceState(null, "", url);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -32,9 +32,9 @@ const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = utils.setParamToUrl('search', value.trim());
|
||||
const url = utils.setParamToUrl("search", value.trim());
|
||||
|
||||
history.replaceState(null, '', `${location.pathname}?${url}`);
|
||||
history.replaceState(null, "", `${location.pathname}?${url}`);
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
@ -42,7 +42,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
* Set the signal state by reading the `search` parameter from the URL.
|
||||
*/
|
||||
createEffect(() => {
|
||||
const param = new URLSearchParams(location.search).get('search');
|
||||
const param = new URLSearchParams(location.search).get("search");
|
||||
|
||||
if (!param) {
|
||||
resetSearch();
|
||||
@ -63,7 +63,7 @@ const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
value={search()}
|
||||
type="text"
|
||||
role="search"
|
||||
placeholder={props.placeholder || 'Search article by title or description...'}
|
||||
placeholder={props.placeholder || "Search article by title or description..."}
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
/>
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './SearchInput';
|
||||
export { default } from "./SearchInput";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './SkillsBlock.astro';
|
||||
export { default } from "./SkillsBlock.astro";
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './SkillsList.astro';
|
||||
export { default } from "./SkillsList.astro";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import './Splash.scss';
|
||||
import "./Splash.scss";
|
||||
---
|
||||
|
||||
<section class='splash'>
|
||||
<slot></slot>
|
||||
<section class="splash">
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import './SplashHeading.scss';
|
||||
import "./SplashHeading.scss";
|
||||
---
|
||||
|
||||
<header class='splash-heading'>
|
||||
<slot></slot>
|
||||
<header class="splash-heading">
|
||||
<slot />
|
||||
</header>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import './SplashLinks.scss';
|
||||
import "./SplashLinks.scss";
|
||||
---
|
||||
|
||||
<nav class='splash-links'>
|
||||
<slot></slot>
|
||||
<nav class="splash-links">
|
||||
<slot />
|
||||
</nav>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export { default as Splash } from './Splash.astro';
|
||||
export { default as SplashHeading } from './SplashHeading.astro';
|
||||
export { default as SplashLinks } from './SplashLinks.astro';
|
||||
export { default as Link } from './Link.astro';
|
||||
export { default as Splash } from "./Splash.astro";
|
||||
export { default as SplashHeading } from "./SplashHeading.astro";
|
||||
export { default as SplashLinks } from "./SplashLinks.astro";
|
||||
export { default as Link } from "./Link.astro";
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
import { For, createEffect } from 'solid-js';
|
||||
import { selectedTags, setSelectedTags } from '@stores/searchStore';
|
||||
import * as utils from '@utils/history';
|
||||
import { For, createEffect } from "solid-js";
|
||||
import { selectedTags, setSelectedTags } from "@stores/searchStore";
|
||||
import * as utils from "@utils/history";
|
||||
|
||||
type TagsListProps = {
|
||||
data: string[];
|
||||
@ -15,14 +15,14 @@ const TagsList: Component<TagsListProps> = (props) => {
|
||||
* Reset signal state and `tags` property in history state
|
||||
*/
|
||||
const resetSelected = () => {
|
||||
const url = utils.deleteParamFromUrl('tags');
|
||||
const url = utils.deleteParamFromUrl("tags");
|
||||
|
||||
setSelectedTags([]);
|
||||
history.replaceState(null, '', url);
|
||||
history.replaceState(null, "", url);
|
||||
};
|
||||
|
||||
const onSelect: JSX.EventHandler<HTMLLIElement, PointerEvent> = (e) => {
|
||||
const tag = e.target.getAttribute('data-tag');
|
||||
const tag = e.target.getAttribute("data-tag");
|
||||
|
||||
/**
|
||||
* Delete tag from selected tags if already selected
|
||||
@ -30,7 +30,7 @@ const TagsList: Component<TagsListProps> = (props) => {
|
||||
*/
|
||||
if (isTagSelected(tag)) {
|
||||
const updated = selectedTags().filter((selectedTag) => selectedTag !== tag);
|
||||
const url = utils.setParamToUrl('tags', updated.join(','));
|
||||
const url = utils.setParamToUrl("tags", updated.join(","));
|
||||
|
||||
if (updated.length === 0) {
|
||||
resetSelected();
|
||||
@ -38,7 +38,7 @@ const TagsList: Component<TagsListProps> = (props) => {
|
||||
}
|
||||
|
||||
setSelectedTags(updated);
|
||||
history.replaceState(null, '', `${location.pathname}?${url}`);
|
||||
history.replaceState(null, "", `${location.pathname}?${url}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,14 +46,14 @@ const TagsList: Component<TagsListProps> = (props) => {
|
||||
* Add selected tag to state and update url
|
||||
*/
|
||||
const updated = [...selectedTags(), tag];
|
||||
const url = utils.setParamToUrl('tags', updated.join(','));
|
||||
const url = utils.setParamToUrl("tags", updated.join(","));
|
||||
|
||||
setSelectedTags(updated);
|
||||
history.replaceState(null, '', `${location.pathname}?${url}`);
|
||||
history.replaceState(null, "", `${location.pathname}?${url}`);
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
let param: string | string[] = new URLSearchParams(location.search).get('tags');
|
||||
let param: string | string[] = new URLSearchParams(location.search).get("tags");
|
||||
|
||||
if (!param) {
|
||||
resetSelected();
|
||||
@ -61,7 +61,7 @@ const TagsList: Component<TagsListProps> = (props) => {
|
||||
}
|
||||
|
||||
/* to array */
|
||||
param = param.split(',');
|
||||
param = param.split(",");
|
||||
|
||||
if (param.length === 0) {
|
||||
resetSelected();
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './TagsList';
|
||||
export { default } from "./TagsList";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Component, JSX } from 'solid-js';
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
import { createSignal, onMount, Switch, Match } from 'solid-js';
|
||||
import './ThemeSwitcher.scss';
|
||||
import { createSignal, onMount, Switch, Match } from "solid-js";
|
||||
import "./ThemeSwitcher.scss";
|
||||
|
||||
const MoonIcon: Component = () => (
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@ -22,19 +22,19 @@ const SunIcon: Component = () => (
|
||||
);
|
||||
|
||||
const ThemeSwitcher: Component = () => {
|
||||
const [theme, setTheme] = createSignal<ThemeModeType>('light');
|
||||
const [theme, setTheme] = createSignal<ThemeModeType>("light");
|
||||
const [isReady, setIsReady] = createSignal<boolean>(false);
|
||||
|
||||
const toggleTheme: JSX.EventHandler<HTMLButtonElement, MouseEvent> = () => {
|
||||
if (!window.theme) return;
|
||||
|
||||
const newTheme = theme() === 'light' ? 'dark' : 'light';
|
||||
const toggleEvent = new CustomEvent('onthemetoggled', { detail: newTheme });
|
||||
const newTheme = theme() === "light" ? "dark" : "light";
|
||||
const toggleEvent = new CustomEvent("onthemetoggled", { detail: newTheme });
|
||||
|
||||
setTheme(newTheme);
|
||||
window.dispatchEvent(toggleEvent);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
document.documentElement.setAttribute("data-theme", newTheme);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@ -42,20 +42,20 @@ const ThemeSwitcher: Component = () => {
|
||||
if (window.theme) {
|
||||
setTheme(window.theme);
|
||||
/* Update theme state on changing OS theme */
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches: isDark }) => {
|
||||
const theme = isDark ? 'dark' : 'light';
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ({ matches: isDark }) => {
|
||||
const theme = isDark ? "dark" : "light";
|
||||
setTheme(theme);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<button onClick={toggleTheme} class={isReady() ? 'theme-switcher ready' : 'theme-switcher'} aria-label={theme()}>
|
||||
<button onClick={toggleTheme} class={isReady() ? "theme-switcher ready" : "theme-switcher"} aria-label={theme()}>
|
||||
<Switch
|
||||
children={
|
||||
<>
|
||||
<Match when={theme() === 'light'} children={<MoonIcon />} />
|
||||
<Match when={theme() === 'dark'} children={<SunIcon />} />
|
||||
<Match when={theme() === "light"} children={<MoonIcon />} />
|
||||
<Match when={theme() === "dark"} children={<SunIcon />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1 +1 @@
|
||||
export { default } from './ThemeSwitcher';
|
||||
export { default } from "./ThemeSwitcher";
|
||||
|
||||
158
src/config.ts
158
src/config.ts
@ -1,132 +1,132 @@
|
||||
export const SITE_METADATA: SiteMetadataType = {
|
||||
title: 'Sergey Yarkov',
|
||||
name: 'yarkov.tech',
|
||||
author: 'Sergey Yarkov',
|
||||
title: "Sergey Yarkov",
|
||||
name: "yarkov.tech",
|
||||
author: "Sergey Yarkov",
|
||||
description:
|
||||
'Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.',
|
||||
themeColor: '#1e2327',
|
||||
"Здесь я публикую статьи и туториалы на различные IT темы, а также рассказываю о своих проектах которые я сделал за последние несколько лет.",
|
||||
themeColor: "#1e2327",
|
||||
contacts: {
|
||||
email: 'serzh.yarkov@gmail.com',
|
||||
githubUrl: 'https://github.com/sergeyyarkov',
|
||||
telegramUrl: 'https://t.me/sergeyyarkov',
|
||||
email: "serzh.yarkov@gmail.com",
|
||||
githubUrl: "https://github.com/sergeyyarkov",
|
||||
telegramUrl: "https://t.me/sergeyyarkov",
|
||||
},
|
||||
nav: [
|
||||
{ name: 'Статьи', url: '/articles' },
|
||||
{ name: 'Проекты', url: '/projects' },
|
||||
{ name: 'Обо мне', url: '/about' },
|
||||
{ name: 'Контакты', url: '/contacts' },
|
||||
{ name: "Статьи", url: "/articles" },
|
||||
{ name: "Проекты", url: "/projects" },
|
||||
{ name: "Обо мне", url: "/about" },
|
||||
{ name: "Контакты", url: "/contacts" },
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
icon: 'book',
|
||||
name: 'yarkov.tech',
|
||||
year: '2022',
|
||||
description: 'Мой персональный веб-сайт где находятся информация обо мне и список различных статей, туториалов...',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/yarkov.tech',
|
||||
demoUrl: 'https://yarkov.tech',
|
||||
icon: "book",
|
||||
name: "yarkov.tech",
|
||||
year: "2022",
|
||||
description: "Мой персональный веб-сайт где находятся информация обо мне и список различных статей, туториалов...",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/yarkov.tech",
|
||||
demoUrl: "https://yarkov.tech",
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'triangle_filled',
|
||||
name: 'devthread',
|
||||
year: '2021',
|
||||
description: 'Статический веб-сайт для публикации различных статей, разработанный при помощи Gatsby.js и NetlifyCMS',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/devthread.ru',
|
||||
demoUrl: 'https://devthread.netlify.app',
|
||||
icon: "triangle_filled",
|
||||
name: "devthread",
|
||||
year: "2021",
|
||||
description: "Статический веб-сайт для публикации различных статей, разработанный при помощи Gatsby.js и NetlifyCMS",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/devthread.ru",
|
||||
demoUrl: "https://devthread.netlify.app",
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'round-school',
|
||||
name: 'educt',
|
||||
year: '2022',
|
||||
description: 'Система управления обучением (LMS) для публикации ваших курсов, лекций, уроков',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/educt',
|
||||
demoUrl: 'https://client-educt-lms.netlify.app',
|
||||
icon: "round-school",
|
||||
name: "educt",
|
||||
year: "2022",
|
||||
description: "Система управления обучением (LMS) для публикации ваших курсов, лекций, уроков",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/educt",
|
||||
demoUrl: "https://client-educt-lms.netlify.app",
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'round-shop-two',
|
||||
name: 'digital-store',
|
||||
year: '2019',
|
||||
description: 'Настраиваемый интернет-магазин с панелью управления для продажи цифровых товаров',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/digital-store/tree/digital-store-docker',
|
||||
demoUrl: 'https://digital-store.herokuapp.com',
|
||||
icon: "round-shop-two",
|
||||
name: "digital-store",
|
||||
year: "2019",
|
||||
description: "Настраиваемый интернет-магазин с панелью управления для продажи цифровых товаров",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/digital-store/tree/digital-store-docker",
|
||||
demoUrl: "https://digital-store.herokuapp.com",
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'chat',
|
||||
name: 'react-chat-app',
|
||||
year: '2021',
|
||||
description: 'Клиент-серверное веб приложение чата разработанное при помощи Node.js и Socket.io',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/react-chat-app',
|
||||
icon: "chat",
|
||||
name: "react-chat-app",
|
||||
year: "2021",
|
||||
description: "Клиент-серверное веб приложение чата разработанное при помощи Node.js и Socket.io",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/react-chat-app",
|
||||
demoUrl: undefined,
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'nodejs',
|
||||
name: 'node-docker-example',
|
||||
year: '2022',
|
||||
description: 'Пример использования Docker для развертывания Node.js приложения',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/node-docker-example',
|
||||
demoUrl: 'https://website-simple-blog.herokuapp.com',
|
||||
icon: "nodejs",
|
||||
name: "node-docker-example",
|
||||
year: "2022",
|
||||
description: "Пример использования Docker для развертывания Node.js приложения",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/node-docker-example",
|
||||
demoUrl: "https://website-simple-blog.herokuapp.com",
|
||||
articleUrl: undefined,
|
||||
},
|
||||
{
|
||||
icon: 'chip',
|
||||
name: 'memory-led-game',
|
||||
year: '2022',
|
||||
description: 'Аналог популярной игры “Саймон говорит” на AVR микроконтроллере',
|
||||
sourceUrl: 'https://github.com/sergeyyarkov/attiny24a_memory-led-game',
|
||||
icon: "chip",
|
||||
name: "memory-led-game",
|
||||
year: "2022",
|
||||
description: "Аналог популярной игры “Саймон говорит” на AVR микроконтроллере",
|
||||
sourceUrl: "https://github.com/sergeyyarkov/attiny24a_memory-led-game",
|
||||
demoUrl: undefined,
|
||||
articleUrl: undefined,
|
||||
},
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
title: 'Программирование',
|
||||
title: "Программирование",
|
||||
list: [
|
||||
{ icon: 'nodejs', name: 'Javascript/Typescript' },
|
||||
{ icon: 'language-c', name: 'C Language' },
|
||||
{ icon: 'mysql', name: 'SQL' },
|
||||
{ icon: 'graphql', name: 'GraphQL' },
|
||||
{ icon: 'avr', name: 'AVR instruction set' },
|
||||
{ icon: "nodejs", name: "Javascript/Typescript" },
|
||||
{ icon: "language-c", name: "C Language" },
|
||||
{ icon: "mysql", name: "SQL" },
|
||||
{ icon: "graphql", name: "GraphQL" },
|
||||
{ icon: "avr", name: "AVR instruction set" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Фреймворки',
|
||||
title: "Фреймворки",
|
||||
list: [
|
||||
{ icon: 'adonisjs', name: 'Adonis.js' },
|
||||
{ icon: 'expressjs', name: 'Express.js' },
|
||||
{ icon: 'socket-io', name: 'Socket.io' },
|
||||
{ icon: 'astro_filled', name: 'Astro' },
|
||||
{ icon: 'reactjs', name: 'React.js' },
|
||||
{ icon: 'gatsbyjs', name: 'Gatsby.js' },
|
||||
{ icon: "adonisjs", name: "Adonis.js" },
|
||||
{ icon: "expressjs", name: "Express.js" },
|
||||
{ icon: "socket-io", name: "Socket.io" },
|
||||
{ icon: "astro_filled", name: "Astro" },
|
||||
{ icon: "reactjs", name: "React.js" },
|
||||
{ icon: "gatsbyjs", name: "Gatsby.js" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Инструменты',
|
||||
title: "Инструменты",
|
||||
list: [
|
||||
{ icon: 'nodejs', name: 'Node.js' },
|
||||
{ icon: 'docker', name: 'Docker' },
|
||||
{ icon: 'git-branch', name: 'Git/gitflow' },
|
||||
{ icon: 'sass', name: 'SASS' },
|
||||
{ icon: 'webpack', name: 'Webpack' },
|
||||
{ icon: 'vite', name: 'Vite.js' },
|
||||
{ icon: "nodejs", name: "Node.js" },
|
||||
{ icon: "docker", name: "Docker" },
|
||||
{ icon: "git-branch", name: "Git/gitflow" },
|
||||
{ icon: "sass", name: "SASS" },
|
||||
{ icon: "webpack", name: "Webpack" },
|
||||
{ icon: "vite", name: "Vite.js" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Базы данных',
|
||||
title: "Базы данных",
|
||||
list: [
|
||||
{ icon: 'postgresql', name: 'PostgreSQL' },
|
||||
{ icon: 'mongodb', name: 'MongoDB' },
|
||||
{ icon: 'redis', name: 'Redis' },
|
||||
{ icon: "postgresql", name: "PostgreSQL" },
|
||||
{ icon: "mongodb", name: "MongoDB" },
|
||||
{ icon: "redis", name: "Redis" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Языки',
|
||||
title: "Языки",
|
||||
list: [
|
||||
{ icon: undefined, name: 'Русский (родной)' },
|
||||
{ icon: undefined, name: 'Английский (B1)' },
|
||||
{ icon: undefined, name: "Русский (родной)" },
|
||||
{ icon: undefined, name: "Английский (B1)" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import NotFound from '@templates/NotFound.astro';
|
||||
import NotFound from "@templates/NotFound.astro";
|
||||
---
|
||||
|
||||
<NotFound />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import About from '@templates/About.astro';
|
||||
import About from "@templates/About.astro";
|
||||
---
|
||||
|
||||
<About />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Articles from '@templates/Articles.astro';
|
||||
import Articles from "@templates/Articles.astro";
|
||||
---
|
||||
|
||||
<Articles />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Contacts from '@templates/Contacts.astro';
|
||||
import Contacts from "@templates/Contacts.astro";
|
||||
---
|
||||
|
||||
<Contacts />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Home from '@templates/Home.astro';
|
||||
import Home from "@templates/Home.astro";
|
||||
---
|
||||
|
||||
<Home />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import Projects from '@templates/Projects.astro';
|
||||
import Projects from "@templates/Projects.astro";
|
||||
---
|
||||
|
||||
<Projects />
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from './searchStore';
|
||||
export * from "./searchStore";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { createSignal } from "solid-js";
|
||||
|
||||
export const [search, setSearch] = createSignal<string>('');
|
||||
export const [search, setSearch] = createSignal<string>("");
|
||||
export const [selectedTags, setSelectedTags] = createSignal<string[]>([]);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
/* Fonts */
|
||||
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||
--font-family-base: "Rubik", var(--font-fallback);
|
||||
--font-family-monospace: "IBM Plex Mono", Menlo, monospace;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-md: 1rem;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
---
|
||||
import * as utils from '@utils/index';
|
||||
import PageLayout from '@layouts/default.astro';
|
||||
import RecentArticles from '@components/RecentArticles';
|
||||
import { Splash, SplashHeading, SplashLinks, Link } from '@components/Splash';
|
||||
import * as utils from "@utils/index";
|
||||
import PageLayout from "@layouts/default.astro";
|
||||
import RecentArticles from "@components/RecentArticles";
|
||||
import { Splash, SplashHeading, SplashLinks, Link } from "@components/Splash";
|
||||
|
||||
const data = await Astro.glob<ArticleType>('../../content/articles/**/*.mdx');
|
||||
const data = await Astro.glob<ArticleType>("../../content/articles/**/*.mdx");
|
||||
const articles = utils.transformArticles(data);
|
||||
---
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MarkdownInstance } from 'astro';
|
||||
import { MarkdownInstance } from "astro";
|
||||
|
||||
export const transformArticles = (articles: MarkdownInstance<ArticleType>[]): ArticleType[] =>
|
||||
articles.map((article) => ({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export const DateFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
timeZone: 'UTC',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
day: 'numeric',
|
||||
timeZone: "UTC",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
@ -2,14 +2,14 @@ export const extendHistoryState = (state: object, url?: URL | string | undefined
|
||||
const { history } = window;
|
||||
const updated = Object.assign({}, history.state, state);
|
||||
|
||||
history.replaceState(updated, '', url);
|
||||
history.replaceState(updated, "", url);
|
||||
};
|
||||
|
||||
export const deleteParamFromUrl = (param: string): string => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.delete(param);
|
||||
|
||||
const url = !params.toString() ? window.location.pathname : '?' + params.toString();
|
||||
const url = !params.toString() ? window.location.pathname : "?" + params.toString();
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export * from './articles';
|
||||
export * from './date';
|
||||
export * from './history';
|
||||
export * from "./articles";
|
||||
export * from "./date";
|
||||
export * from "./history";
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"types": ["vite/client", "@astrojs/image/client", "mdx"],
|
||||
"types": ["vite/client", "@astrojs/image/client", "@types/mdx"],
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"jsx": "preserve",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user