mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 18:39:14 +03:00
commit
ffe520b983
@ -1,5 +1,5 @@
|
||||
{
|
||||
"printWidth": 150,
|
||||
"printWidth": 120,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import { h } from "hastscript";
|
||||
import { toString } from "hast-util-to-string";
|
||||
import { escape } from "html-escaper";
|
||||
|
||||
/**
|
||||
* Integrations
|
||||
*/
|
||||
@ -24,6 +19,14 @@ import remarkSmartypants from "remark-smartypants";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||
|
||||
/**
|
||||
* Other required modules
|
||||
*/
|
||||
import { defineConfig } from "astro/config";
|
||||
import { h } from "hastscript";
|
||||
import { toString } from "hast-util-to-string";
|
||||
import { escape } from "html-escaper";
|
||||
|
||||
const createAnchorLabel = (heading) => {
|
||||
const node = h("span.anchor-label", escape(heading));
|
||||
node.properties["is:raw"] = true;
|
||||
@ -41,11 +44,14 @@ export default defineConfig({
|
||||
locales: {
|
||||
ru: "ru",
|
||||
en: "en",
|
||||
de: "de",
|
||||
},
|
||||
},
|
||||
}),
|
||||
image(),
|
||||
compress(),
|
||||
compress({
|
||||
img: false,
|
||||
}),
|
||||
mdx({
|
||||
remarkPlugins: [remarkGfm, remarkSmartypants],
|
||||
rehypePlugins: [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yarkov.tech",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"description": "Personal website created with Astro.",
|
||||
"author": {
|
||||
@ -15,7 +15,7 @@
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --host",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,mdx}\"",
|
||||
"format": "prettier --write ./src/\"**/*.{js,jsx,ts,tsx,json,md,mdx,astro}\"",
|
||||
"lint": "eslint '**/*.{js,mjs,ts,tsx}'"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
30
src/components/AlternateLangLink/AlternateLangLink.astro
Normal file
30
src/components/AlternateLangLink/AlternateLangLink.astro
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
/**
|
||||
* This components is used in article layout to display links in alternative lanugages.
|
||||
*/
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
|
||||
export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
|
||||
lang: LanguageKeys;
|
||||
}
|
||||
|
||||
const { pathname } = Astro.url;
|
||||
const siteLang = getLanguageFromURL(pathname);
|
||||
const { lang, ...attrs } = Astro.props as Props;
|
||||
let href: string;
|
||||
if (lang === DEFAULT_LANGUAGE) {
|
||||
href = `/${pathname.split("/").slice(2).join("/")}`;
|
||||
} else {
|
||||
href = `/${lang}/`;
|
||||
if (siteLang !== DEFAULT_LANGUAGE) {
|
||||
href += `${pathname.split("/").slice(2).join("/")}`;
|
||||
} else {
|
||||
href += `${pathname.split("/").slice(1).join("/")}`;
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<a href={href} {...attrs}>
|
||||
<slot />
|
||||
</a>
|
||||
1
src/components/AlternateLangLink/index.ts
Normal file
1
src/components/AlternateLangLink/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./AlternateLangLink.astro";
|
||||
@ -11,16 +11,22 @@ export type ArticleItemProps = {
|
||||
|
||||
export const ArticleItem: Component<ArticleItemProps> = (props) => {
|
||||
const isNonDefaultLanguage = props.data.lang && props.data.lang !== props.lang;
|
||||
const hrefLangPrefix = props.data.lang && props.data.lang !== DEFAULT_LANGUAGE ? "/en" : "";
|
||||
const hrefLangPrefix = props.data.lang && props.data.lang !== DEFAULT_LANGUAGE ? `/${props.data.lang}` : "";
|
||||
return (
|
||||
<article class="article-item">
|
||||
<a href={`${hrefLangPrefix}/blog/${new Date(props.data.published_at).toLocaleDateString("en-CA")}/${props.data.slug}/`}>
|
||||
<a
|
||||
href={`${hrefLangPrefix}/blog/${new Date(props.data.published_at).toLocaleDateString("en-CA")}/${
|
||||
props.data.slug
|
||||
}/`}
|
||||
>
|
||||
<div class="flex">
|
||||
<h3>{props.data.title}</h3>
|
||||
{isNonDefaultLanguage && <sup>{props.data.lang.toLocaleUpperCase()}</sup>}
|
||||
</div>
|
||||
<p>
|
||||
<time datetime={props.data.published_at.toString()}>{createDateFormatter(props.lang).format(new Date(props.data.published_at))}</time>
|
||||
<time datetime={props.data.published_at.toString()}>
|
||||
{createDateFormatter(props.lang).format(new Date(props.data.published_at))}
|
||||
</time>
|
||||
</p>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
@ -21,7 +21,9 @@ const { title, coverImage, tags, author, published_at } = data;
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<li>
|
||||
<TranslatedLink rel="tag" to={`/articles/?tags=${tag.toLocaleLowerCase()}`}>{`${tag}${tags.at(-1) === tag ? "" : ","}`}</TranslatedLink>
|
||||
<TranslatedLink rel="tag" to={`/articles/?tags=${tag.toLocaleLowerCase()}`}>{`${tag}${
|
||||
tags.at(-1) === tag ? "" : ","
|
||||
}`}</TranslatedLink>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
@ -31,7 +33,9 @@ const { title, coverImage, tags, author, published_at } = data;
|
||||
<h1 itemprop="headline">{title}</h1>
|
||||
</div>
|
||||
<div class="published">
|
||||
<time itemprop="datePublished" datetime={published_at}>{createDateFormatter(lang).format(new Date(published_at))}</time>
|
||||
<time itemprop="datePublished" datetime={published_at}
|
||||
>{createDateFormatter(lang).format(new Date(published_at))}
|
||||
</time>
|
||||
<span itemprop="author">{author}</span>
|
||||
</div>
|
||||
{
|
||||
|
||||
@ -84,7 +84,10 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
|
||||
return (
|
||||
<div class="articles-list__wrapper">
|
||||
<h2>{year}</h2>
|
||||
<For each={sortArtcilesByDate(articles()[year])} children={(article) => <ArticleItem lang={props.lang} data={article} />} />
|
||||
<For
|
||||
each={sortArtcilesByDate(articles()[year])}
|
||||
children={(article) => <ArticleItem lang={props.lang} data={article} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@ -30,8 +30,20 @@ const { title = SITE_METADATA.title, description, canonicalUrl } = Astro.props a
|
||||
<meta name="description" content={description || t("site.description")} />
|
||||
<meta name="theme-color" content={SITE_METADATA.themeColor} />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png" media="(prefers-color-scheme: light)" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-light-16x16.png" media="(prefers-color-scheme: light)" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-light-32x32.png"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
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="manifest" href="/site.webmanifest" />
|
||||
|
||||
@ -19,7 +19,14 @@ const t = useTranslation(Astro);
|
||||
</div>
|
||||
</div>
|
||||
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required />
|
||||
<textarea name="message" placeholder={t("input.messageField")} id="message" autocomplete="off" cols={30} rows={10} required></textarea>
|
||||
<textarea
|
||||
name="message"
|
||||
placeholder={t("input.messageField")}
|
||||
id="message"
|
||||
autocomplete="off"
|
||||
cols={30}
|
||||
rows={10}
|
||||
required></textarea>
|
||||
<div class="footer">
|
||||
<Button type="submit">{t("btn.submit")}</Button>
|
||||
</div>
|
||||
|
||||
@ -24,7 +24,10 @@ const { pathname } = Astro.url;
|
||||
{
|
||||
nav.map((link) => (
|
||||
<li>
|
||||
<TranslatedLink class={pathname.includes(removeSlashes(link.url)) && pathname.split("/").length < 5 && "active"} to={link.url}>
|
||||
<TranslatedLink
|
||||
class={pathname.includes(removeSlashes(link.url)) && pathname.split("/").length < 5 && "active"}
|
||||
to={link.url}
|
||||
>
|
||||
<UIString key={link.key} />
|
||||
</TranslatedLink>
|
||||
</li>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { Icon } from "astro-icon";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/config";
|
||||
import languages from "@i18n/languages";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
|
||||
@ -11,7 +11,7 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
<Icon name="translate" width="21" height="21" />
|
||||
<select id="languageSelect" value={lang} aria-label="Select language">
|
||||
{
|
||||
Object.entries(languages).map(([code]) => (
|
||||
SUPPORTED_LANGUAGES.map((code) => (
|
||||
<option value={code} selected={lang === code}>
|
||||
<span>{code.toLocaleUpperCase()}</span>
|
||||
</option>
|
||||
@ -71,7 +71,15 @@ const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
const changeLanguageHandler = (e) => {
|
||||
const newLang = e.target.value;
|
||||
const pathname = window.location.pathname;
|
||||
const url = newLang === DEFAULT_LANGUAGE ? pathname.slice(3) : `${newLang}${pathname}`;
|
||||
let url;
|
||||
|
||||
if (lang !== DEFAULT_LANGUAGE) {
|
||||
url = pathname.split("/").slice(2).join("/");
|
||||
} else {
|
||||
url = pathname.slice(1);
|
||||
}
|
||||
|
||||
if (newLang !== DEFAULT_LANGUAGE) url = `${newLang}/${url}`;
|
||||
|
||||
/* Move user to articles page with new language */
|
||||
if (window.location.pathname.includes("/blog/")) {
|
||||
|
||||
@ -3,6 +3,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 SUPPORTED_LANGUAGES: SupportedLanguages = ["ru", "en"];
|
||||
|
||||
export const SITE_METADATA: SiteMetadataType = {
|
||||
title: "Sergey Yarkov",
|
||||
name: "yarkov.tech",
|
||||
@ -14,10 +16,19 @@ export const SITE_METADATA: SiteMetadataType = {
|
||||
telegramUrl: "https://t.me/sergeyyarkov",
|
||||
},
|
||||
nav: [
|
||||
{ key: "navigation.articles", url: "/articles/" },
|
||||
{ key: "navigation.projects", url: "/projects/" },
|
||||
{
|
||||
key: "navigation.articles",
|
||||
url: "/articles/",
|
||||
},
|
||||
{
|
||||
key: "navigation.projects",
|
||||
url: "/projects/",
|
||||
},
|
||||
{ key: "navigation.about", url: "/about/" },
|
||||
{ key: "navigation.contacts", url: "/contacts/" },
|
||||
{
|
||||
key: "navigation.contacts",
|
||||
url: "/contacts/",
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
@ -112,11 +123,20 @@ export const SITE_METADATA: SiteMetadataType = {
|
||||
en: "Programming",
|
||||
},
|
||||
list: [
|
||||
{ icon: "nodejs", name: "Javascript/Typescript" },
|
||||
{ icon: "language-c", name: "C Language" },
|
||||
{
|
||||
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: "avr",
|
||||
name: "AVR instruction set",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -141,7 +161,10 @@ export const SITE_METADATA: SiteMetadataType = {
|
||||
list: [
|
||||
{ icon: "nodejs", name: "Node.js" },
|
||||
{ icon: "docker", name: "Docker" },
|
||||
{ icon: "git-branch", name: "Git/gitflow" },
|
||||
{
|
||||
icon: "git-branch",
|
||||
name: "Git/gitflow",
|
||||
},
|
||||
{ icon: "sass", name: "SASS" },
|
||||
{ icon: "webpack", name: "Webpack" },
|
||||
{ icon: "vite", name: "Vite.js" },
|
||||
@ -153,7 +176,10 @@ export const SITE_METADATA: SiteMetadataType = {
|
||||
en: "Databases",
|
||||
},
|
||||
list: [
|
||||
{ icon: "postgresql", name: "PostgreSQL" },
|
||||
{
|
||||
icon: "postgresql",
|
||||
name: "PostgreSQL",
|
||||
},
|
||||
{ icon: "mongodb", name: "MongoDB" },
|
||||
{ icon: "redis", name: "Redis" },
|
||||
],
|
||||
|
||||
@ -13,7 +13,8 @@ export default {
|
||||
"pageTitle.projects": "Projects",
|
||||
"pageTitle.contacts": "Contacts",
|
||||
/* Page descriptions */
|
||||
"pageDescription.articles": "Technical articles, tutorials and various reference materials related to IT topics can be found here.",
|
||||
"pageDescription.articles":
|
||||
"Technical articles, tutorials and various reference materials related to IT topics can be found here.",
|
||||
"pageDescription.projects": "A few of my main projects that have been developed and published in open access.",
|
||||
"pageDescription.contacts": "Use the feedback form or write to me on social networks.",
|
||||
/* Navigation */
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export default {
|
||||
ru: "Русский",
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
};
|
||||
|
||||
@ -13,7 +13,8 @@ export default {
|
||||
"pageTitle.projects": "Проекты",
|
||||
"pageTitle.contacts": "Контакты",
|
||||
/* Page descriptions */
|
||||
"pageDescription.articles": "Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
|
||||
"pageDescription.articles":
|
||||
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
|
||||
"pageDescription.projects": "Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
|
||||
"pageDescription.contacts": "Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
|
||||
/* Navigation */
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { AstroGlobal, MDXInstance } from "astro";
|
||||
import * as utils from "@utils/url";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@root/config";
|
||||
import languages from "./languages";
|
||||
|
||||
type TranslatedArticlesType = Record<string, Record<LanguageKeys, MDXInstance<ArticleType>> | Record<string, never>>;
|
||||
@ -21,7 +21,7 @@ export function formatArticlesByLangs(data: Record<string, MDXInstance<ArticleTy
|
||||
}
|
||||
|
||||
export function getLanguageKeys(): Array<string> {
|
||||
return Object.keys(languages);
|
||||
return Object.keys(languages).filter((lang) => SUPPORTED_LANGUAGES.includes(lang as LanguageKeys));
|
||||
}
|
||||
|
||||
function transformExports<T>(modules: Record<string, { default: T }>) {
|
||||
|
||||
@ -16,7 +16,11 @@ const { title, description, lang = "ru", noIndex = false, canonicalUrl } = Astro
|
||||
---
|
||||
|
||||
<html lang={lang}>
|
||||
<BaseHead title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title} description={description} canonicalUrl={canonicalUrl}>
|
||||
<BaseHead
|
||||
title={title ? `${title} | ${SITE_METADATA.title}` : SITE_METADATA.title}
|
||||
description={description}
|
||||
canonicalUrl={canonicalUrl}
|
||||
>
|
||||
{noIndex && <meta name="robots" content="noindex" />}
|
||||
</BaseHead>
|
||||
<BaseLayout>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import type { MDXInstance } from "astro";
|
||||
|
||||
import ArticleLayout from "@layouts/article.astro";
|
||||
import AlternateLangLink from "@components/AlternateLangLink";
|
||||
import { getLanguageFromURL } from "@root/utils";
|
||||
import { DEFAULT_LANGUAGE } from "@root/config";
|
||||
import { formatArticlesByLangs, useTranslation } from "@i18n/utils";
|
||||
@ -37,8 +38,6 @@ const {
|
||||
article: { Content, frontmatter },
|
||||
alternateLangs,
|
||||
} = Astro.props as Props;
|
||||
|
||||
const { pathname } = Astro.url;
|
||||
---
|
||||
|
||||
<ArticleLayout lang={siteLang} content={frontmatter} canonicalUrl={canonicalUrl.href}>
|
||||
@ -48,9 +47,7 @@ const { pathname } = Astro.url;
|
||||
{t("article.availableOn")}:
|
||||
{alternateLangs
|
||||
.filter((lang) => lang !== siteLang)
|
||||
.map((lang) => (
|
||||
<a href={DEFAULT_LANGUAGE === lang ? `/${pathname.split("/").slice(2).join("/")}` : `/${lang}${pathname}`}>{languages[lang]}</a>
|
||||
))
|
||||
.map((lang) => <AlternateLangLink lang={lang}>{languages[lang]}</AlternateLangLink>)
|
||||
.reduce((prev, curr) => [prev, ", ", curr])}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -4,7 +4,9 @@ import rss from "@astrojs/rss";
|
||||
import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/config";
|
||||
import * as utils from "@i18n/utils";
|
||||
|
||||
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", { eager: true });
|
||||
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", {
|
||||
eager: true,
|
||||
});
|
||||
const articles = Object.values(utils.formatArticlesByLangs(data));
|
||||
const items = [];
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@ import { getLanguageFromURL } from "@utils/url";
|
||||
|
||||
class ArticleService {
|
||||
public getTranslatedArticles(Astro: Readonly<AstroGlobal>): MDXInstance<ArticleType>[] {
|
||||
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", { eager: true });
|
||||
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", {
|
||||
eager: true,
|
||||
});
|
||||
const modules = formatArticlesByLangs(data);
|
||||
const siteLang = getLanguageFromURL(Astro.url.pathname);
|
||||
const articles: MDXInstance<ArticleType>[] = [];
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
---
|
||||
import { SITE_METADATA } from "@root/config";
|
||||
import type { MDXInstance } from "astro";
|
||||
|
||||
import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/config";
|
||||
import PageLayout from "@layouts/default.astro";
|
||||
import PageHeading from "@components/PageHeading";
|
||||
import SkillsList from "@components/SkillsList";
|
||||
@ -11,7 +13,14 @@ import { useTranslation } from "@i18n/utils";
|
||||
|
||||
const lang = getLanguageFromURL(Astro.url.pathname);
|
||||
const t = useTranslation(Astro);
|
||||
const { Content } = await import(`../../content/about/${lang}/index.mdx`);
|
||||
let markdown: MDXInstance<{}> | undefined;
|
||||
|
||||
try {
|
||||
markdown = (await import(`../../content/about/${lang}/index.mdx`)) as MDXInstance<{}>;
|
||||
} catch (error) {
|
||||
markdown = (await import(`../../content/about/${DEFAULT_LANGUAGE}/index.mdx`)) as MDXInstance<{}>;
|
||||
}
|
||||
|
||||
const { skills } = SITE_METADATA;
|
||||
---
|
||||
|
||||
@ -19,7 +28,7 @@ const { skills } = SITE_METADATA;
|
||||
<PageHeading heading={t("pageTitle.about")} />
|
||||
<section>
|
||||
<ArticleContent>
|
||||
<Content />
|
||||
{markdown && <markdown.Content />}
|
||||
</ArticleContent>
|
||||
<SkillsList>
|
||||
{skills.map((block) => <SkillsBlock data={block} />)}
|
||||
|
||||
@ -15,7 +15,12 @@ const articles = transformArticles(data);
|
||||
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
|
||||
---
|
||||
|
||||
<PageLayout lang={lang} title={t("pageTitle.articles")} description={t("pageDescription.articles")} canonicalUrl={canonicalUrl.href}>
|
||||
<PageLayout
|
||||
lang={lang}
|
||||
title={t("pageTitle.articles")}
|
||||
description={t("pageDescription.articles")}
|
||||
canonicalUrl={canonicalUrl.href}
|
||||
>
|
||||
<PageHeading heading={t("pageTitle.articles")} description={t("pageDescription.articles")} />
|
||||
<section>
|
||||
<ArticlesList
|
||||
|
||||
20
src/types/main.d.ts
vendored
20
src/types/main.d.ts
vendored
@ -5,7 +5,10 @@ export {};
|
||||
declare global {
|
||||
interface Window {
|
||||
theme?: ThemeModeType | undefined;
|
||||
addEventListener<K extends keyof CustomEventMap>(type: K, listener: (this: Window, ev: CustomEventMap[K]) => void): void;
|
||||
addEventListener<K extends keyof CustomEventMap>(
|
||||
type: K,
|
||||
listener: (this: Window, ev: CustomEventMap[K]) => void
|
||||
): void;
|
||||
}
|
||||
|
||||
interface CustomEventMap {
|
||||
@ -14,6 +17,7 @@ declare global {
|
||||
|
||||
type LanguageKeys = keyof typeof languages;
|
||||
type UIDictionaryKeys = keyof typeof uiKeys;
|
||||
type SupportedLanguages = LanguageKeys[];
|
||||
|
||||
type SiteMetadataType = {
|
||||
title: string;
|
||||
@ -25,7 +29,10 @@ declare global {
|
||||
githubUrl: string;
|
||||
telegramUrl: string;
|
||||
};
|
||||
nav: Array<{ key: UIDictionaryKeys; url: string }>;
|
||||
nav: Array<{
|
||||
key: UIDictionaryKeys;
|
||||
url: string;
|
||||
}>;
|
||||
projects: Array<ProjectType>;
|
||||
skills: Array<SkillsType>;
|
||||
};
|
||||
@ -46,15 +53,18 @@ declare global {
|
||||
icon: string;
|
||||
name: string;
|
||||
year: string;
|
||||
description: Record<LanguageKeys, string>;
|
||||
description: Record<string, string>;
|
||||
sourceUrl?: string | undefined;
|
||||
articleUrl?: string | undefined;
|
||||
demoUrl?: string | undefined;
|
||||
};
|
||||
|
||||
type SkillsType = {
|
||||
title: Record<LanguageKeys, string>;
|
||||
list: Array<{ icon?: string | undefined; name: string }>;
|
||||
title: Record<string, string>;
|
||||
list: Array<{
|
||||
icon?: string | undefined;
|
||||
name: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type ContactFieldsType = {
|
||||
|
||||
@ -7,5 +7,7 @@ export function isEmail(email: string): boolean {
|
||||
}
|
||||
|
||||
export function isURL(url: string): boolean {
|
||||
return new RegExp(/(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\\/~+#-]*[\w@?^=%&\\/~+#-])/).test(url);
|
||||
return new RegExp(/(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\\/~+#-]*[\w@?^=%&\\/~+#-])/).test(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,3 +12,7 @@ export const getLanguageFromURL = (pathname: string): LanguageKeys => {
|
||||
if (Object.hasOwn(languages, lang)) return lang;
|
||||
return DEFAULT_LANGUAGE;
|
||||
};
|
||||
|
||||
export const removeLangFromURL = (pathname: string): string => {
|
||||
return pathname.split("/").slice(2).join("/");
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user