This commit is contained in:
Sergey Yarkov 2022-08-28 14:27:47 +03:00
parent f072df8345
commit 01bb18b774
15 changed files with 71 additions and 18 deletions

View File

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

View File

@ -15,7 +15,7 @@
"start": "astro dev", "start": "astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview --host", "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}'" "lint": "eslint '**/*.{js,mjs,ts,tsx}'"
}, },
"devDependencies": { "devDependencies": {

View File

@ -14,13 +14,19 @@ export const ArticleItem: Component<ArticleItemProps> = (props) => {
const hrefLangPrefix = props.data.lang && props.data.lang !== DEFAULT_LANGUAGE ? "/en" : ""; const hrefLangPrefix = props.data.lang && props.data.lang !== DEFAULT_LANGUAGE ? "/en" : "";
return ( return (
<article class="article-item"> <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"> <div class="flex">
<h3>{props.data.title}</h3> <h3>{props.data.title}</h3>
{isNonDefaultLanguage && <sup>{props.data.lang.toLocaleUpperCase()}</sup>} {isNonDefaultLanguage && <sup>{props.data.lang.toLocaleUpperCase()}</sup>}
</div> </div>
<p> <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> </p>
</a> </a>
</article> </article>

View File

@ -21,7 +21,9 @@ const { title, coverImage, tags, author, published_at } = data;
{ {
tags.map((tag) => ( tags.map((tag) => (
<li> <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> </li>
)) ))
} }
@ -31,7 +33,9 @@ const { title, coverImage, tags, author, published_at } = data;
<h1 itemprop="headline">{title}</h1> <h1 itemprop="headline">{title}</h1>
</div> </div>
<div class="published"> <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> <span itemprop="author">{author}</span>
</div> </div>
{ {

View File

@ -84,7 +84,10 @@ const ArticlesList: Component<ArticlesListProps> = (props) => {
return ( return (
<div class="articles-list__wrapper"> <div class="articles-list__wrapper">
<h2>{year}</h2> <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> </div>
); );
}} }}

View File

@ -30,8 +30,20 @@ const { title = SITE_METADATA.title, description, canonicalUrl } = Astro.props a
<meta name="description" content={description || t("site.description")} /> <meta name="description" content={description || t("site.description")} />
<meta name="theme-color" content={SITE_METADATA.themeColor} /> <meta name="theme-color" content={SITE_METADATA.themeColor} />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <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
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-light-16x16.png" media="(prefers-color-scheme: light)" /> 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="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="16x16" href="/favicon-dark-16x16.png" media="(prefers-color-scheme: dark)" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />

View File

@ -19,7 +19,14 @@ const t = useTranslation(Astro);
</div> </div>
</div> </div>
<input name="subject" placeholder={t("input.subjectField")} type="text" autocomplete="off" required /> <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"> <div class="footer">
<Button type="submit">{t("btn.submit")}</Button> <Button type="submit">{t("btn.submit")}</Button>
</div> </div>

View File

@ -24,7 +24,10 @@ const { pathname } = Astro.url;
{ {
nav.map((link) => ( nav.map((link) => (
<li> <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} /> <UIString key={link.key} />
</TranslatedLink> </TranslatedLink>
</li> </li>

View File

@ -13,7 +13,8 @@ export default {
"pageTitle.projects": "Projects", "pageTitle.projects": "Projects",
"pageTitle.contacts": "Contacts", "pageTitle.contacts": "Contacts",
/* Page descriptions */ /* 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.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.", "pageDescription.contacts": "Use the feedback form or write to me on social networks.",
/* Navigation */ /* Navigation */

View File

@ -13,7 +13,8 @@ export default {
"pageTitle.projects": "Проекты", "pageTitle.projects": "Проекты",
"pageTitle.contacts": "Контакты", "pageTitle.contacts": "Контакты",
/* Page descriptions */ /* Page descriptions */
"pageDescription.articles": "Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.", "pageDescription.articles":
"Технические статьи, туториалы и различные справочные материалы которые связаны с IT тематикой находятся здесь.",
"pageDescription.projects": "Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.", "pageDescription.projects": "Несколько моих основных проектов которые были разработаны и выложены в открытый доступ.",
"pageDescription.contacts": "Воспользуйтесь формой обратной связи или напишите мне в социальные сети.", "pageDescription.contacts": "Воспользуйтесь формой обратной связи или напишите мне в социальные сети.",
/* Navigation */ /* Navigation */

View File

@ -16,7 +16,11 @@ const { title, description, lang = "ru", noIndex = false, canonicalUrl } = Astro
--- ---
<html lang={lang}> <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" />} {noIndex && <meta name="robots" content="noindex" />}
</BaseHead> </BaseHead>
<BaseLayout> <BaseLayout>

View File

@ -49,7 +49,9 @@ const { pathname } = Astro.url;
{alternateLangs {alternateLangs
.filter((lang) => lang !== siteLang) .filter((lang) => lang !== siteLang)
.map((lang) => ( .map((lang) => (
<a href={DEFAULT_LANGUAGE === lang ? `/${pathname.split("/").slice(2).join("/")}` : `/${lang}${pathname}`}>{languages[lang]}</a> <a href={DEFAULT_LANGUAGE === lang ? `/${pathname.split("/").slice(2).join("/")}` : `/${lang}${pathname}`}>
{languages[lang]}
</a>
)) ))
.reduce((prev, curr) => [prev, ", ", curr])} .reduce((prev, curr) => [prev, ", ", curr])}
</div> </div>

View File

@ -15,7 +15,12 @@ const articles = transformArticles(data);
const canonicalUrl = new URL(Astro.url.pathname, Astro.site); 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")} /> <PageHeading heading={t("pageTitle.articles")} description={t("pageDescription.articles")} />
<section> <section>
<ArticlesList <ArticlesList

5
src/types/main.d.ts vendored
View File

@ -5,7 +5,10 @@ export {};
declare global { declare global {
interface Window { interface Window {
theme?: ThemeModeType | undefined; 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 { interface CustomEventMap {

View File

@ -7,5 +7,7 @@ export function isEmail(email: string): boolean {
} }
export function isURL(url: 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
);
} }