Compare commits

..

1 commit

Author SHA1 Message Date
e8fc4ce653
refac: use vuepress 2024-07-24 17:50:51 +03:00
76 changed files with 2810 additions and 534 deletions

6
.gitignore vendored
View file

@ -12,6 +12,6 @@ node_modules/
# custom # custom
.DS_Store .DS_Store
# vuepress # vuepress
/docs/.vuepress/.cache docs/.vuepress/.cache
/docs/.vuepress/.temp docs/.vuepress/.temp
/docs/.vuepress/dist docs/.vuepress/dist

View file

@ -1,16 +1,14 @@
PREFIX ?= dist NPM ?= npm
help: help:
cat Makefile cat makefile
build: build:
pnpm run docs:build --dest $(PREFIX) $(NPM) run docs:build
install: build
dev: dev:
pnpm run docs:dev $(NPM) run docs:dev
deps: deps:
pnpm install $(NPM) install

43
dist/404.html vendored
View file

@ -1,43 +0,0 @@
<!doctype html>
<html lang="ru-RU">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="generator" content="VuePress 2.0.0-rc.14" />
<style>
:root {
--c-bg: #fff;
}
html.dark {
--c-bg: #22272e;
}
html,
body {
background-color: var(--c-bg);
}
</style>
<script>
const userMode = localStorage.getItem('vuepress-color-scheme')
const systemDarkMode =
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
if (userMode === 'light') {
document.documentElement.dataset.theme = 'light'
} else if (userMode === 'dark' || systemDarkMode) {
document.documentElement.classList.toggle('dark', true)
document.documentElement.dataset.theme = 'dark'
}
</script>
<meta property="og:url" content="https://pleshevski.ru/404.html"><meta property="og:site_name" content="Дмитрий Плешевский"><meta property="og:description" content="404 Not Found"><meta property="og:type" content="website"><meta property="og:locale" content="ru-RU"><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"","description":"404 Not Found"}</script><title>Дмитрий Плешевский</title><meta name="description" content="404 Not Found">
<link rel="preload" href="/assets/style-BnNdFOI8.css" as="style"><link rel="stylesheet" href="/assets/style-BnNdFOI8.css">
<link rel="modulepreload" href="/assets/app-Dw1tezwH.js"><link rel="modulepreload" href="/assets/404.html-C-XD0uTd.js">
<link rel="prefetch" href="/assets/index.html-Bgp5oohT.js" as="script"><link rel="prefetch" href="/assets/works.html-sG2zmfnZ.js" as="script"><link rel="prefetch" href="/assets/index.html-Bmcvetlf.js" as="script"><link rel="prefetch" href="/assets/works.html-DIDCwbnz.js" as="script">
</head>
<body>
<div id="app"><!--[--><div class="vp-theme-container" data-v-99131a8e><main class="page" data-v-99131a8e><div class="theme-default-content" data-v-99131a8e><h1 data-v-99131a8e>404</h1><blockquote data-v-99131a8e>Мы потеряли страницу...</blockquote><a class="route-link" href="/" data-v-99131a8e>Вернуться на главную</a></div></main></div><!--[--><!----><!--]--><!--]--></div>
<script type="module" src="/assets/app-Dw1tezwH.js" defer></script>
</body>
</html>

View file

@ -1 +0,0 @@
import{_ as t,c as e,o,a as n}from"./app-Dw1tezwH.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function s(p,l){return o(),e("div",null,c)}const d=t(r,[["render",s],["__file","404.html.vue"]]),m=JSON.parse('{"path":"/404.html","title":"","lang":"ru-RU","frontmatter":{"layout":"NotFound","description":"404 Not Found","head":[["meta",{"property":"og:url","content":"https://pleshevski.ru/404.html"}],["meta",{"property":"og:site_name","content":"Дмитрий Плешевский"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"ru-RU"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"git":{},"autoDesc":true,"filePathRelative":null}');export{d as comp,m as data};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

43
dist/eng/index.html vendored

File diff suppressed because one or more lines are too long

43
dist/eng/works.html vendored

File diff suppressed because one or more lines are too long

43
dist/index.html vendored

File diff suppressed because one or more lines are too long

5
dist/robots.txt vendored
View file

@ -1,5 +0,0 @@
User-agent:*
Disallow:
Sitemap: https://pleshevski.ru/sitemap.xml

17
dist/rus/index.html vendored
View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<meta http-equiv="refresh" content="0; url=/index.html">
<link rel="canonical" href="/index.html">
<title>Redirecting...</title>
<script>
const anchor = window.location.hash.substring(1);
location.href = `/index.html${anchor? `#${anchor}`: ""}`;
</script>
</head>
<body>
<p>Redirecting...</p>
</body>
</html>

17
dist/rus/works.html vendored
View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<meta http-equiv="refresh" content="0; url=/works.html">
<link rel="canonical" href="/works.html">
<title>Redirecting...</title>
<script>
const anchor = window.location.hash.substring(1);
location.href = `/works.html${anchor? `#${anchor}`: ""}`;
</script>
</head>
<body>
<p>Redirecting...</p>
</body>
</html>

3
dist/sitemap.xml vendored
View file

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://pleshevski.ru/</loc><lastmod>2024-07-24T14:15:05.000Z</lastmod><changefreq>daily</changefreq><xhtml:link rel="alternate" hreflang="ru-RU" href="https://pleshevski.ru/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://pleshevski.ru/eng/"/></url><url><loc>https://pleshevski.ru/works.html</loc><lastmod>2024-07-24T14:15:05.000Z</lastmod><changefreq>daily</changefreq><xhtml:link rel="alternate" hreflang="ru-RU" href="https://pleshevski.ru/works.html"/><xhtml:link rel="alternate" hreflang="en-US" href="https://pleshevski.ru/eng/works.html"/></url><url><loc>https://pleshevski.ru/eng/</loc><lastmod>2024-07-24T14:15:05.000Z</lastmod><changefreq>daily</changefreq><xhtml:link rel="alternate" hreflang="ru-RU" href="https://pleshevski.ru/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://pleshevski.ru/eng/"/></url><url><loc>https://pleshevski.ru/eng/works.html</loc><lastmod>2024-07-24T14:15:05.000Z</lastmod><changefreq>daily</changefreq><xhtml:link rel="alternate" hreflang="ru-RU" href="https://pleshevski.ru/works.html"/><xhtml:link rel="alternate" hreflang="en-US" href="https://pleshevski.ru/eng/works.html"/></url></urlset>

207
dist/sitemap.xsl vendored
View file

@ -1,207 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XML Sitemap</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0;" />
<style>
:root {
--bg-color: #f8f8f8;
--bg-color-secondary: #fff;
--text-color: #2c3e50;
--border-color: #eaecef;
--brand-color: #3eaf7c;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #0d1117;
--bg-color-secondary: #161b22;
--text-color: #ccc;
--border-color: #30363d;
}
}
html,
body {
margin: 0;
padding: 0;
background: var(--bg-color);
}
html {
font-size: 14px;
}
body {
min-height: 100vh;
color: var(--text-color);
text-align: center;
}
#content {
max-width: 960px;
margin: 0 auto;
}
h1 {
margin-top: 1rem;
font-size: 2rem;
}
@media (max-width: 419px) {
h1 {
font-size: 1.5rem;
}
}
a {
color: var(--text-color);
font-weight: 500;
overflow-wrap: break-word;
}
table {
width: 100%;
border-radius: 8px;
border-collapse: collapse;
text-align: center;
overflow: hidden;
}
@media (max-width: 419px) {
table {
border-radius: 0;
}
}
th {
min-width: 56px;
padding: 0.6em 1em;
background-color: var(--brand-color);
color: var(--bg-color);
font-weight: bold;
font-size: 16px;
}
@media (max-width: 719px) {
th {
font-size: 14px;
}
}
th:first-child {
text-align: start;
}
tr:nth-child(odd) {
background: var(--bg-color-secondary);
}
tr:hover {
background-color: #e8e8e8;
}
@media (prefers-color-scheme: dark) {
tr:hover {
background-color: #333;
}
}
td {
padding: 0.6em 1em;
}
@media (max-width: 719px) {
td {
font-size: 12px;
}
}
td:first-child {
text-align: start;
}
footer {
margin-top: 10px;
padding: 4px;
color: #888;
font-size: 12px;
text-align: center;
}
</style>
</head>
<body>
<div id="content">
<h1>Sitemap</h1>
<table>
<thead>
<tr>
<th>
<xsl:value-of select="concat('URL (', count(sitemap:urlset/sitemap:url), ')')" />
</th>
<th>Priority</th>
<th>Change Frequency</th>
<th>Last Updated Time</th>
</tr>
</thead>
<tbody>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:for-each select="sitemap:urlset/sitemap:url">
<tr>
<td>
<xsl:variable name="itemURL">
<xsl:value-of select="sitemap:loc" />
</xsl:variable>
<a href="{$itemURL}" target="_blank">
<xsl:value-of select="sitemap:loc" />
</a>
</td>
<td>
<xsl:choose>
<xsl:when test="sitemap:priority">
<xsl:value-of select="concat(sitemap:priority*100,'%a')" />
</xsl:when>
<xsl:otherwise>
<xsl:text>0.5</xsl:text>
</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:choose>
<xsl:when test="sitemap:changefreq">
<xsl:value-of select="concat(translate(substring(sitemap:changefreq, 1, 1),concat($lower, $upper),concat($upper, $lower)),substring(sitemap:changefreq, 2))" />
</xsl:when>
<xsl:otherwise>
<xsl:text>-</xsl:text>
</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)))" />
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
<footer>
Generatd by <a href="https://ecosystem.vuejs.press/plugins/sitemap/">@vuepress/plugin-sitemap</a>
</footer>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

43
dist/works.html vendored

File diff suppressed because one or more lines are too long

View file

@ -9,32 +9,13 @@
outputs = { self, nixpkgs, flake-utils }: outputs = { self, nixpkgs, flake-utils }:
let let
out = system: out = system:
let let pkgs = import nixpkgs { inherit system; };
inherit (builtins) substring; in {
pkgs = import nixpkgs { inherit system; };
version = "0.0.1+${substring 0 8 self.lastModifiedDate}_${self.shortRev or "dirty"}";
in
{
packages.default = with pkgs; stdenv.mkDerivation (finalAttrs: {
pname = "pleshevski_site";
inherit version;
src = ./.;
dontBuild = true;
installPhase = ''
mkdir -p $out
cp -r dist/* $out
'';
});
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
nodejs_22 nodejs_22
gnumake gnumake
pnpm nodePackages.pnpm
nodePackages.typescript-language-server # typescript nodePackages.typescript-language-server # typescript
nodePackages.vscode-langservers-extracted # html, css, json, eslint nodePackages.vscode-langservers-extracted # html, css, json, eslint
]; ];

13
old/.dockerignore Normal file
View file

@ -0,0 +1,13 @@
/*
!/data
!/public
!/styles
!/translates
!/views
!/uikit
!/modules
!/*.ts
!/*.json

19
old/.woodpecker.yml Normal file
View file

@ -0,0 +1,19 @@
when:
branch: main
steps:
- name: build-docker-image
image: plugins/docker
settings:
repo: ${ORG_REGISTRY}/${CI_REPO}
tags:
- "${CI_COMMIT_BRANCH}"
- "${CI_COMMIT_SHA:0:8}"
- name: deploy
image: ${ORG_REGISTRY}/drone_plugins/docker_stack
pull: true
environment:
PLESHEVSKI_IMAGE: ${ORG_REGISTRY}/${CI_REPO}:${CI_COMMIT_SHA:0:8}
settings:
name: pleshevski

13
old/Dockerfile Normal file
View file

@ -0,0 +1,13 @@
FROM denoland/deno:alpine-1.22.1
EXPOSE 33334
WORKDIR /app
USER deno
ADD . .
# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache server.ts
CMD ["run", "-A", "server.ts"]

25
old/context.ts Normal file
View file

@ -0,0 +1,25 @@
import { Translations } from "./translates/rus.ts";
export interface Context {
title?: string;
locPath: string;
lang: Lang;
tr: Translations;
}
export function getLangHref(lang: Lang, url: string): string {
return getLangUrlPrefix(lang) + url;
}
export function getLangUrlPrefix(lang: Lang): string {
return `/${lang}`;
}
export function iterLangs(): Lang[] {
return [Lang.Eng, Lang.Rus];
}
export enum Lang {
Rus = "rus",
Eng = "eng",
}

105
old/data/about/eng.md Normal file
View file

@ -0,0 +1,105 @@
###
Always up-to-date link to [resume](https://pleshevski.ru/eng/).
### Overview
My name is Dmitriy Pleshevskiy.
I'm an open source software enthusiast, a lead software developer, architect,
team leader and also mentor.
### Skills
Programming Languages:
- TypeScript (prefer, solid 9-year exp)
- SQL (prefer, solid 8-year exp)
- Rust (prefer, solid 5-year exp)
- Python (solid 9-year exp)
- Haskell
- Bash
- Java
- C#
- C++
Databases:
- PostgreSQL (prefer, solid 7-year exp)
- MySQL
- Sqlite
- MsSQL
- MongoDB
- Reddis
I also have extensive experience in creating the following applications:
- Traditional (SSR + Forms)
- API (REST/GraphQL/WebSocket/EventSource)
- Dynamic (SPA)
- Hybrid (SSR + SPA)
- Console
- Crossplatform
### Stack
Backend (Rust)
- axum (prefer, solid 2-year exp)
- async-graphql (prefer, solid 2-year exp)
- shaku (prefer, solid 2-year exp)
- bb8 + postgres-types (prefer, solid 5-year exp)
- diesel (2-year exp)
Backend (Node.JS)
- Apollo (solid 5-year exp)
- Express (solid 9-year exp)
- Nest.JS
- Knex.js / Objection.js (solid 5-year exp)
- Sequelize
Frontend
- React (solid 8-year exp)
- VueJS (prefer, solid 3-year exp)
- Cypress (prefer, solid 3-year exp)
- JQuery
- Antd / Antdv
- PostCSS (prefer, solid 5-year exp)
- Sass (prefer, solid 8-year exp)
- Less (weak 4-year exp)
DevOps
- NixOS / NixOps / Nix dev shell (prefer, solid 2-year exp)
- Docker Swarm (prefer, solid 5-year exp)
- Kubernetes (weak 4-year exp)
- Woodpecker CI (prefer, solid 3-year exp)
- Drone CI (solid 3-year exp)
- Gitlab CI (solid 7-year exp)
- GitHub Actions (3-year exp)
### Interests
Open-source projects are my passion! I develop, maintain and improve projects in
my spare time.
Besides programming, I love to cook and spend time with my beloved family!
### Contacts
SimpleX:
[Dmitriy Pleshevskiy](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FSkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w%3D%40smp9.simplex.im%2FLfKyG0YgW5eRO-z8vrEyvnNfV2EKDfBv%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMRpR3YB10GVzc-asfqY2oIFkipx5RQm4DZRabzjfPHo%253D%26srv%3Djssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion)
Telegram: [Dmitriy Pleshevskiy](https://telegram.me/da_pranaya)
Matrix: @pleshevskiy:matrix.org
Email: dmitriy[at]pleshevski[dot]ru
### Links
[My Git Repo](https://git.pleshevski.ru/)
[My Github (Suspended due to sanctions)](https://github.com/pleshevskiy)

107
old/data/about/rus.md Normal file
View file

@ -0,0 +1,107 @@
###
Всегда актуальная ссылка на [резюме](https://pleshevski.ru/rus/).
### Общие сведения
Меня зовут Дмитрий Плешевский.
Я энтузиаст программного обеспечения с открытым исходным кодом, ведущий
разработчик програмного обеспечения, архитектор, руководитель команды, а так же
ментор.
### Умения
Языки программирования:
- TypeScript (предпочитаю, твёрдый 9-летний опыт)
- SQL (предпочитаю, твёрдый 8-летний опыт)
- Rust (предпочитаю, 5-летний опыт)
- Python (твёрдый 9-летний опыт)
- Haskell
- Bash
- Java
- C#
- C++
Базы данных:
- PostgreSQL (предпочитаю, твёрдый 7-летний опыт)
- MySQL
- Sqlite
- MsSQL
- MongoDB
- Reddis
Я так же имею большой опыт в создании следующих типов приложений:
- Традиционные (SSR + Forms)
- API (REST/GraphQL/WebSocket/EventSource)
- Динамическое (SPA)
- Гибридное (SSR + SPA)
- Консольные
- Кроссплатформенные
### Stack
Backend (Rust)
- axum (предпочитаю, твёрдый 2-летний опыт)
- async-graphql (предпочитаю, твёрдый 2-летний опыт)
- shaku (предпочитаю, твёрдый 2-летний опыт)
- bb8 + postgres-types (предпочитаю, твёрдый 5-летний опыт)
- diesel (2-летний опыт)
Backend (Node.JS)
- Apollo (твёрдый 5-летний опыт)
- Express (твёрдый 9-летний опыт)
- Nest.JS
- Knex.js / Objection.js (твёрдый 5-летний опыт)
- Sequelize
Frontend
- React (твёрдый 8-летний опыт)
- VueJS (предпочитаю, твёрдый 3-летний опыт)
- Cypress (предпочитаю, твёрдый 3-летний опыт)
- JQuery
- Antd / Antdv
- PostCSS (предпочитаю, твёрдый 5-летний опыт)
- Sass (предпочитаю, твёрдый 8-летний опыт)
- Less (слабый 4-летний опыт)
DevOps
- NixOS / NixOps / Nix dev shell (предпочитаю, твёрдый 2-летний опыт)
- Docker Swarm (предпочитаю, твёрдый 5-летний опыт)
- Kubernetes (слабый 4-летний опыт)
- Woodpecker CI (предпочитаю, твёрдый 3-летний опыт)
- Drone CI (твёрдый 3-летний опыт)
- Gitlab CI (твёрдый 7-летний опыт)
- GitHub Actions (3-летний опыт)
### Интересы
Open-source проекты - моя страсть! Разрабатываю, поддерживаю и улучшаю проекты в
своё свободное время.
Помимо программирования я люблю готовить и проводить время со своей любимой
семьей!
### Контакты
SimpleX:
[Dmitriy Pleshevskiy](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FSkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w%3D%40smp9.simplex.im%2FLfKyG0YgW5eRO-z8vrEyvnNfV2EKDfBv%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMRpR3YB10GVzc-asfqY2oIFkipx5RQm4DZRabzjfPHo%253D%26srv%3Djssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion)
Telegram: [Dmitriy Pleshevskiy](https://telegram.me/da_pranaya)
Matrix: @pleshevskiy:matrix.org
Email: dmitriy[at]pleshevski[dot]ru
### Ссылки
[My Git Repo](https://git.pleshevski.ru/)
[My Github (Приостановлен из-за санкций)](https://github.com/pleshevskiy)

64
old/data/works/eng.md Normal file
View file

@ -0,0 +1,64 @@
### Highlighted working experience
#### Binary Management
- Dates: August 2018 currently
- Roles: Lead Fullstack Developer, Team Lead, Architect
Development of a project management tool for interior designers
- Development of the GraphQL API (Node.JS, Apollo, PostgreSQL, Redis, BullMQ).
Moved database triggers to business logic. Wrote integration tests on 70% api.
- Development of the frontend (React, Antd). Formed uikit, shared components,
redesigned the page generation gathering. Completely changed work with API on
the frontend. Introduced the practice of writing integration tests using
cypress
- Completely ported the project to TypeScript. I have formed isolated modules of
the system.
- As a team leader, I brought the critical chain method, the buffer method, and
the planning method to the project from the end. Helped the team get into a
rhythm to make releases each week in small batches. A couple of times I also
prepared an individual development plan for team members.
#### Master Progress
- Dates: May 2018 - currently (Passively maintained)
- Role: Tech Lead
Development web infrastructure of the educational center Master Progress
- Development of [the main site](https://masterprogress.ru) (Python, Flask).
- Development of [Student's cabinet](https://cabinet.masterprogress.ru) (Python,
Flask, TypeScript, React).
- Development of [a tool for rosmintrud](https://rosmintrud.masterprogress.ru)
(Deno, Vue, Typescript)
- Created a complete infrastructure on Woodpecker CI and Docker swarm.
#### Core Spirit
- Dates: August 2018 - May 2020
- Role: Lead Fullstack Developer
Development of Social platform focusing on human and planetary enhancement
- Development of the REST API (Node.JS, Express, PostgreSQL) for main site and
backoffice.
- Development of an auto poster to various social networks and messengers
(Facebook, LinkedIn, Twitter, Telegram).
- Development of a neural network for automatic categorization of articles.
#### MERLION
- Dates: March 2016 May 2018
- Role: Senior Fullstack developer
In this company there were 6 considerable projects I have successfully
completed:
- optimize the creation of promotional pages (PHP, JavaScript)
- support main traditional site <https://citilink.ru> (PHP, JavaScript)
- development of parsing to monitor products for changes in price,
quantity/availability in stock, rating and other fields based on data from 55
websites (Node.JS, Express)
- work with neural networks for matching of goods
- development face recognition apps for Android (Java)

65
old/data/works/rus.md Normal file
View file

@ -0,0 +1,65 @@
### Выделенный опыт работы
#### Binary Management
- Даты: Август 2018 по настоящее время
- Роли: Lead Fullstack Developer, Team Lead, Architect
Разработка инструмента управления проектами для дизайнеров интерьера
- Разработка GraphQL API (Node.JS, Apollo, PostgreSQL, Redis, BullMQ). Перенес
триггеры базы данных в бизнес-логику. Написал интеграционные тесты на 70% api.
- Разработка фронтенда (React, Antd). Сформировал uikit и общие компоненты,
оптимизировал сложные и нагруженные компоненты. Полностью изменил работу с API
на фронтенде. Внедрил практику написания интеграционных тестов с помощью
cypress.
- Полностью перенес проект на TypeScript. Сформировал изолированные модули
системы.
- Как руководитель команды, я привнес в проект метод критической цепи, метод
буфера и метод планирования с конца. Помог команде войти в ритм, чтобы
выпускать релизы каждую неделю небольшими партиями. Я также несколько раз
составлял индивидуальный план развития для членов команды.
#### Master Progress
- Даты: Май 2018 - по настоящее время (Пассивная поддержка)
- Роль: Tech Lead
Разработка веб-инфраструктуры образовательного центра Мастер Прогресс
- Разработка [главного сайта](https://masterprogress.ru) (Python, Flask).
- Разработка [кабинета студента](https://cabinet.masterprogress.ru) (Python,
Flask, TypeScript, React).
- Разработка
[инструмента для работы с rosmintrud](https://rosmintrud.masterprogress.ru)
(Deno, Vue, Typescript)
- Создана полная инфраструктура на Woodpecker CI и Docker swarm.
#### Core Spirit
- Даты: Август 2018 - May 2020
- Роль: Lead Fullstack Developer
Разработка социальной платформы, сфокусированной на улучшении человека и планеты
- Разработка REST API (Node.JS, Express, PostgreSQL) для основного сайта и
бэк-офиса.
- Разработка автопостера в различные социальные сети и мессенджеры (Facebook,
LinkedIn, Twitter, Telegram).
- Разработка нейронной сети для автоматической категоризации статей.
#### MERLION
- Dates: March 2016 May 2018
- Role: Senior Fullstack developer
В этой компании было 6 значительных проектов, которые я успешно завершил:
- Оптимизация создания рекламных страниц (PHP, JavaScript)
- Поддержка основного традиционного сайта <https://citilink.ru> (PHP,
JavaScript)
- Разработка парсинга для мониторинга товаров на предмет изменения цены,
количества/наличия на складе, рейтинга и других полей на основе данных с 55+
сайтов (Node.js, Express)
- Работа с нейронными сетями для подбора товаров
- Разработка приложений для распознавания лиц для Android (Java)

6
old/deno.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"lib": ["deno.ns", "dom"]
},
"importMap": "./import_map.json"
}

13
old/deno.lock Normal file
View file

@ -0,0 +1,13 @@
{
"version": "2",
"remote": {
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/core/node.ts": "0857215f4ddbc5ef661af1eef8010376914373b9040d11ac777923651f59db08",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/core/utils.ts": "136b4b594befe6f1157741932aaea86f00f5ca6b1f7ee3e84059e9140a87e82d",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/par/md.ts": "0d4264ee133a883372664ac4b5e2dcb66a41a10a914de089e96b4ebb6757c641",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/par/types.ts": "f114cafde896121b9db754ffb5f2778edb3d414c78e3782ce1fb50a2ec8e2708",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/ren/attrs.ts": "a8f118423567bc64fd53ffd472bba4417df7de4df726c122c2ae8f37e921329a",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/ren/html_str.ts": "6052fa5b65ae0d8b2d5f6cb82d365e8b9a01f6070d55015db8386175386b8cca",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/ren/node.ts": "85d81ee3adc506f7ae90db31ed52336034766430ec8a8d6ae5d9951206f989fc",
"https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/ren/types.ts": "f1f561397a8326ddfcfefb00c3c9ea7e4e5467021fa89795628daa656d90727b"
}
}

29
old/docker-compose.yml Normal file
View file

@ -0,0 +1,29 @@
version: "3.8"
networks:
traefik_public:
external: true
services:
site:
image: $PLESHEVSKI_IMAGE
networks:
- traefik_public
deploy:
replicas: 1
endpoint_mode: vip
update_config:
order: start-first
rollback_config:
order: start-first
labels:
- traefik.enable=true
- traefik.constraint-label=magenta_public
- traefik.http.routers.to_pleshevski_site.rule=Host(`pleshevski.ru`)
- traefik.http.routers.to_pleshevski_site.entrypoints=https
- traefik.http.routers.to_pleshevski_site.tls=true
- traefik.http.routers.to_pleshevski_site.tls.certresolver=le
- traefik.http.services.pleshevski_site.loadbalancer.server.port=33334
placement:
constraints:
- node.role == worker

1
old/global.ts Normal file
View file

@ -0,0 +1 @@
export type NonEmptyArray<T> = [T, ...T[]];

6
old/import_map.json Normal file
View file

@ -0,0 +1,6 @@
{
"imports": {
"par/": "https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/par/",
"ren/": "https://git.pleshevski.ru/pleshevskiy/paren/raw/commit/257079305813c4c0a71c16a89164c20bbce1a5e2/ren/"
}
}

14
old/log.ts Normal file
View file

@ -0,0 +1,14 @@
export function error(...args: unknown[]): void {
console.log("[ERROR]", ...args);
}
export function info(...args: unknown[]): void {
console.log("[INFO]", ...args);
}
export function debug(...args: unknown[]): void {
// choose better name for this env
if (Deno.env.get("DEBUG") === "1") {
console.log("[DEBUG]", ...args);
}
}

View file

@ -0,0 +1,54 @@
import { AnyNode, E } from "ren/node.ts";
import { WorkLink } from "../mod.ts";
import { renderDate } from "../../../render.ts";
import { CHRONOLOGICAL_WORKS } from "../data.ts";
import { RoleList } from "./RoleList.ts";
import { TechnologyList } from "./TechnologyList.ts";
export type ChronologicalWorksTableTranslations = Readonly<
Record<
| "Name"
| "Description"
| "Role"
| "Technologies"
| "Start"
| "Status_or_End",
string
>
>;
const tr = E.bind(null, "tr", []);
const td = E.bind(null, "td", []);
const th = E.bind(null, "th", []);
export const ChronologicalWorksTable = (
i18n: ChronologicalWorksTableTranslations,
): AnyNode =>
E("table", [], [
E("thead", [], [
tr([
th(i18n.Name),
th(i18n.Description),
th(i18n.Role),
th(i18n.Technologies),
th(i18n.Start),
th(i18n.Status_or_End),
]),
]),
E(
"tbody",
[],
CHRONOLOGICAL_WORKS.map((work) =>
tr([
td([WorkLink(work)]),
td(work.description),
td([RoleList(work.roles)]),
td([TechnologyList(work.technologies)]),
td(renderDate(work.startDate)),
td(
work.endDate ? renderDate(work.endDate) : work.status,
),
])
),
),
]);

View file

@ -0,0 +1,6 @@
import { NonEmptyArray } from "../../../global.ts";
import { Role } from "../domain/mod.ts";
import { AnyNode, TextNode } from "ren/node.ts";
export const RoleList: (roles: NonEmptyArray<Role>) => AnyNode = (roles) =>
new TextNode(roles.join(", "));

View file

@ -0,0 +1,7 @@
import { NonEmptyArray } from "../../../global.ts";
import { Technology } from "../domain/mod.ts";
import { AnyNode, TextNode } from "ren/node.ts";
export const TechnologyList: (techs: NonEmptyArray<Technology>) => AnyNode = (
techs,
) => new TextNode(techs.join(", "));

View file

@ -0,0 +1 @@
export { ChronologicalWorksTable } from "./ChronologicalWorksTable.ts";

View file

@ -0,0 +1,6 @@
import { AnyNode } from "ren/node.ts";
import { Work, work as w } from "./domain/mod.ts";
import { Link } from "../../uikit/link.ts";
export const WorkLink: (work: Work) => AnyNode = (work) =>
Link(work.name, { href: w.getExternalLink(work) });

368
old/modules/work/data.ts Normal file
View file

@ -0,0 +1,368 @@
import { Role } from "./domain/Role.ts";
import { Status } from "./domain/Status.ts";
import { Technology } from "./domain/Technology.ts";
import { Work } from "./domain/Work.ts";
export const CHRONOLOGICAL_WORKS: Work[] = [
{
name: "picsg",
url: "/pleshevskiy/picsg",
description:
"A tool for steganographing information in a picture encoded using the Vernam cipher.",
roles: [Role.Author],
technologies: [Technology.Haskell],
startDate: new Date("2024-04-13"),
status: Status.AsIs,
},
{
name: "Mindustry tools",
url: "/pleshevskiy/mindustry-tools",
description: "Tools for the Mindustry game",
roles: [Role.Author],
technologies: [
Technology.Nix,
Technology.Godot,
],
startDate: new Date("2024-01-07"),
status: Status.PassivelyMaintained,
},
{
name: "Master Progress Rosmintrud tools",
url: "https://rosmintrud.masterprogress.ru",
description:
"Internal service to prepare documents for the rosmintrud (SPA)",
roles: [Role.TechLead],
technologies: [
Technology.Deno,
Technology.Sqlite,
Technology.TypeScript,
Technology.Vue,
Technology.Docker,
Technology.Woodpecker,
Technology.Nix,
],
startDate: new Date("2023-07-03"),
status: Status.PassivelyMaintained,
},
{
name: "yandexgpt_tg_bot",
url: "/pleshevskiy/yandexgpt_tg_bot",
description: "The Telegram bot to describe article with link by YandexGPT.",
roles: [Role.Author],
technologies: [Technology.JavaScript, Technology.NodeJS, Technology.Nix],
startDate: new Date("2023-06-27"),
status: Status.PassivelyMaintained,
},
{
name: "tree-sitter-plpgsql",
url: "/pleshevskiy/tree-sitter-plpgsql",
description: "plpgsql grammar for tree-sitter",
roles: [Role.Author],
technologies: [
Technology.C,
Technology.JavaScript,
Technology.TreeSitter,
Technology.Nix,
],
startDate: new Date("2023-01-05"),
status: Status.PassivelyMaintained,
},
{
name: "wd2",
url: "/pleshevskiy/wd2",
description:
"A wrapper over d2 which allows to use additional configs from d2 file",
roles: [Role.Author],
technologies: [Technology.Bash, Technology.Nix],
startDate: new Date("2022-12-12"),
endDate: new Date("2023-07-31"),
status: Status.AsIs,
},
{
name: "tree-sitter-d2",
url: "/pleshevskiy/tree-sitter-d2",
description: "d2 grammar for tree-sitter",
roles: [Role.Author],
technologies: [
Technology.C,
Technology.JavaScript,
Technology.TreeSitter,
Technology.Nix,
],
startDate: new Date("2022-12-04"),
status: Status.ActiveDeveloped,
},
{
name: "nix2lua",
url: "/mynix/nix2lua",
description:
"This is a small but functional library that converts your nix configurations into lua format.",
roles: [Role.Author],
technologies: [Technology.Nix, Technology.Lua],
startDate: new Date("2022-11-22"),
status: Status.PassivelyMaintained,
},
{
name: "vnetod",
url: "/pleshevskiy/vnetod",
description: "Dotenv section switcher",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2022-07-29"),
status: Status.PassivelyMaintained,
},
{
name: "estring",
url: "/pleshevskiy/estring",
description: "A simple way to parse a string using type annotations.",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2022-07-23"),
status: Status.PassivelyMaintained,
},
{
name: "enve",
url: "/pleshevskiy/enve",
description:
"It helps you work with environment variables and convert it to any type using only type annotations",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2022-07-18"),
status: Status.PassivelyMaintained,
},
{
name: "docker stack drone plugin",
url: "/drone_plugins/docker_stack",
description: "Deploy to production using `docker stack deploy`",
roles: [Role.Author],
technologies: [
Technology.Docker,
Technology.Drone,
Technology.Woodpecker,
],
startDate: new Date("2022-06-06"),
status: Status.PassivelyMaintained,
},
{
name: "dexios",
url: "/github/dexios",
description:
"Dexios is a fast, secure, and open source command-line encryption tool.",
roles: [Role.Collaborator],
technologies: [Technology.Rust],
startDate: new Date("2022-06-01"),
endDate: new Date("2023-02-28"),
},
{
name: "recipes",
url: "/pleshevskiy/recipes",
description: "Site with recipes which cares about privacy",
roles: [Role.Author],
technologies: [Technology.TypeScript, Technology.Deno, Technology.Rust],
startDate: new Date("2022-05-04"),
status: Status.PassivelyMaintained,
},
{
name: "pleshevski.ru",
url: "/pleshevskiy/pleshevski.ru",
description: "Source code of my personal site",
roles: [Role.Author],
technologies: [
Technology.TypeScript,
Technology.Deno,
Technology.Docker,
Technology.Woodpecker,
],
startDate: new Date("2022-03-16"),
status: Status.PassivelyMaintained,
},
{
name: "paren",
url: "/pleshevskiy/paren",
description: "Library for parsing and rendering information.",
roles: [Role.Author],
technologies: [Technology.TypeScript, Technology.Deno],
startDate: new Date("2022-03-14"),
status: Status.Experimental,
},
{
name: "hwt",
url: "/pleshevskiy/hwt",
description:
"healthy workaholic timer A tool that keeps you from breaking your health by working all day.",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2022-02-04"),
status: Status.AsIs,
},
{
name: "ood_persistence",
url: "/pleshevskiy/ood_persistence",
description:
"Asynchronous and synchronous interfaces and persistence implementations for your OOD architecture ",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2021-10-12"),
status: Status.Deprecated,
},
{
name: "migra",
url: "/pleshevskiy/migra",
description: "Simple SQL migration manager for your project.",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2021-01-31"),
status: Status.AsIs,
},
{
name: "espruino-starter",
url: "/pleshevskiy/espruino-starter",
description:
"Quickly start creating your new project on the espruino board or a board based on it.",
roles: [Role.Author],
technologies: [Technology.JavaScript],
startDate: new Date("2021-08-23"),
status: Status.AsIs,
},
{
name: "react-rest-request",
url: "/pleshevskiy/react-rest-request",
description: "Minimalistic REST API client for React inspired by Apollo.",
roles: [Role.Author],
technologies: [Technology.TypeScript, Technology.React],
startDate: new Date("2020-10-04"),
status: Status.Deprecated,
},
{
name: "sonic-channel",
url: "/pleshevskiy/sonic-channel",
description: "Rust client for sonic search backend.",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2020-07-18"),
status: Status.PassivelyMaintained,
},
{
name: "itconfig",
url: "/pleshevskiy/itconfig",
description:
"Easy build a configs from environment variables and use it in globally.",
roles: [Role.Author],
technologies: [Technology.Rust],
startDate: new Date("2019-12-22"),
status: Status.Deprecated,
},
{
name: "it-fsm",
url: "/pleshevskiy/it-fsm",
description: "Simple full-featured finite state machine for your project",
roles: [Role.Author],
technologies: [Technology.TypeScript, Technology.NodeJS, Technology.Deno],
startDate: new Date("2019"),
status: Status.PassivelyMaintained,
},
{
name: "Cabinet Master Progress",
url: "https://cabinet.masterprogress.ru",
description:
"Student's cabinet of the educational center Master Progress (SSR + SPA)",
roles: [Role.TechLead],
technologies: [
Technology.Python,
Technology.Flask,
Technology.Postgresql,
Technology.TypeScript,
Technology.React,
Technology.Docker,
Technology.Woodpecker,
Technology.Nix,
],
startDate: new Date("2019-09-22"),
status: Status.PassivelyMaintained,
},
{
name: "genrss",
url: "/pleshevskiy/genrss",
description: "RSS generator for python",
roles: [Role.Author],
technologies: [Technology.Python],
startDate: new Date("2019-07-23"),
status: Status.AsIs,
},
{
name: "marshmallow_pageinfo",
url: "/pleshevskiy/marshmallow_pageinfo",
description: "Page info marshmallow schema for api",
roles: [Role.Author],
technologies: [Technology.Python],
startDate: new Date("2019-10-05"),
status: Status.AsIs,
},
{
name: "Binary Management",
url: "https://www.binarymanagement.com",
description: "Project management tool for interior designers",
roles: [Role.Developer, Role.TechLead, Role.TeamLead],
technologies: [
Technology.TypeScript,
Technology.NodeJS,
Technology.React,
Technology.Antd,
Technology.Docker,
Technology.Drone,
Technology.Rust,
Technology.Nix,
],
startDate: new Date("2018-09-15"),
status: Status.ActiveDeveloped,
},
{
name: "Core Spirit",
url: "https://corespirit.com",
description: "Social platform focusing on human and planetary enhancement",
roles: [Role.Developer],
technologies: [
Technology.TypeScript,
Technology.NodeJS,
Technology.React,
Technology.Docker,
Technology.Drone,
],
startDate: new Date("2018-09-05"),
endDate: new Date("2019-12-31"),
},
{
name: "Master Progress",
url: "https://masterprogress.ru",
description:
"Main website of the educational center Master Progress (SSR + Forms)",
roles: [Role.TechLead],
technologies: [
Technology.Python,
Technology.Flask,
Technology.JavaScript,
Technology.Docker,
Technology.Woodpecker,
],
startDate: new Date("2018-04-10"),
status: Status.PassivelyMaintained,
},
{
name: "ictmpl",
url: "/pleshevskiy/ictmpl",
description: "Generate projects from templates",
roles: [Role.Author],
technologies: [Technology.Python],
startDate: new Date("2018-06-30"),
status: Status.AsIs,
},
{
name: "jjcrypto",
url: "/pleshevskiy/jjcrypto",
description: "Javascript encoder and decoder",
roles: [Role.Author],
technologies: [Technology.Php],
startDate: new Date("2015-11-01"),
status: Status.AsIs,
},
];

View file

@ -0,0 +1,7 @@
export enum Role {
Collaborator = "collaborator",
Author = "author",
TechLead = "tech&nbsp;lead",
TeamLead = "team&nbsp;lead",
Developer = "developer",
}

View file

@ -0,0 +1,25 @@
export enum Status {
// New features are being added and bugs are being fixed.
ActiveDeveloped = "actively-developed",
// There are no plans for new features, but the maintainer intends to respond
// to issues that get filed.
PassivelyMaintained = "passively-maintained",
// The package is feature complete, the maintainer does not intend to continue
// working on it or providing support, but it works for the purposes it was
// designed for.
AsIs = "as-is",
// The author wants to share it with the community but is not intending to
// meet anyone's particular use case.
Experimental = "experimental",
// The current maintainer would like to transfer the package to someone else.
LookingForMaintainer = "looking-for-maintainer",
// The maintainer does not recommend using this package (the description of the
// package can describe why, there could be a better solution available or
// there could be problems with the package that the author does not want to fix).
Deprecated = "deprecated",
}

View file

@ -0,0 +1,25 @@
export enum Technology {
C = "C",
JavaScript = "JS",
TypeScript = "TS",
Rust = "Rust",
Python = "Python",
Php = "PHP",
Deno = "Deno",
NodeJS = "NodeJS",
Flask = "Flask",
React = "React",
Antd = "Antd",
Postgresql = "PostgreSQL",
Docker = "Docker",
Drone = "Drone CI",
Woodpecker = "Woodpecker CI",
Bash = "Bash",
TreeSitter = "TreeSitter",
Nix = "Nix",
Lua = "Lua",
Sqlite = "Sqlite",
Vue = "Vue",
Godot = "Godot",
Haskell = "Haskell",
}

View file

@ -0,0 +1,20 @@
import { NonEmptyArray } from "../../../global.ts";
import { Role } from "./Role.ts";
import { Status } from "./Status.ts";
import { Technology } from "./Technology.ts";
export interface Work {
name: string;
url: string;
description: string;
roles: NonEmptyArray<Role>;
technologies: NonEmptyArray<Technology>;
startDate: Date;
endDate?: Date;
status?: Status;
}
export const getExternalLink: (work: Pick<Work, "url">) => string = (work) =>
work.url.startsWith("https://")
? work.url
: new URL(work.url, "https://git.pleshevski.ru").toString();

View file

@ -0,0 +1,5 @@
export { Role } from "./Role.ts";
export { Status } from "./Status.ts";
export { Technology } from "./Technology.ts";
export type { Work } from "./Work.ts";
export * as work from "./Work.ts";

1
old/modules/work/mod.ts Normal file
View file

@ -0,0 +1 @@
export { WorkLink } from "./WorkLink.ts";

570
old/public/styles/main.css Normal file
View file

@ -0,0 +1,570 @@
@charset "UTF-8";
:root {
--default-color-black: #000000;
--default-color-black-0: hsla(0, 0%, 0%, 0);
--default-color-black-0x15: hsla(0, 0%, 0%, 0.15);
--default-color-black-0x6: hsla(0, 0%, 0%, 0.6);
--default-color-white: #ffffff;
--default-color-warning: #ffee58;
--default-color-error: #b00008;
--default-color-success: #417505;
--color-brand-blue: #1966df;
--color-brand-faded-blue: #f5f5ff;
--color-graphite: #212121;
--color-warm-gray: #757575;
--color-pale: #b6b6b6;
--color-faded: #e0e0e0;
--max-content-width: 1440px;
--min-content-width: 320px;
--rad-std-half: 0.125rem;
--rad-std: 0.25rem;
--rad-std-x2: 0.5rem;
--rad-std-x3: 0.75rem;
--default-font-size: 16px;
--f-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto,Oxygen-Sans, Ubuntu, Cantarell, "Segoe UI", Verdana, sans-serif;
--f-wei-thin: 100;
--f-wei-reg: 400;
--f-wei-bold: 700;
--f-wei-black: 800;
--z-ind-background: -100;
--z-ind-backward: -1;
--z-ind-select: 50;
--z-ind-tooltip: 75;
--z-ind-high: 100;
--z-ind-overlay: 500;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
background-color: var(--default-color-white);
font-size: var(--default-font-size);
line-height: 1;
height: 100%;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
color: var(--color-graphite);
font-weight: var(--f-wei-regular);
font-family: var(--f-family);
min-width: var(--min-content-width);
width: 100%;
height: 100%;
}
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
details > summary {
cursor: pointer;
list-style: none;
}
details > summary::before, details > summary::-webkit-details-marker {
display: none;
}
audio, canvas, progress, video {
display: inline-block;
vertical-align: baseline;
}
audio:not([controls]) {
display: none;
height: 0;
}
address, caption, cite, code, dfn, strong, th, var {
font-style: normal;
font-weight: var(--f-wei-regular);
}
h1, h2, h3, h4, h5, h6 {
font-weight: var(--f-wei-bold);
line-height: 1;
}
b, strong, optgroup {
font-weight: var(--f-wei-bold);
}
dfn, em, i {
font-style: italic;
}
iframe, abbr, acronym, img {
border: 0;
outline: 0;
}
mark {
background: var(--default-color-warning);
color: var(--default-color-black);
}
small {
font-size: 80%;
}
sub, sup {
font-size: 80%;
vertical-align: baseline;
line-height: 0;
position: relative;
}
sup {
top: -0.25em;
}
sub {
bottom: -0.25em;
}
q::before, q::after {
content: "";
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
code, kbd, pre, samp {
font-family: monospace;
font-size: 1em;
}
pre {
overflow: auto;
}
figure {
margin: 0;
}
fieldset {
border: 1px solid var(--color-pale);
padding: 0.25rem 0.75rem;
}
legend {
border: 0;
color: inherit;
display: table;
word-spacing: normal;
max-width: 100%;
}
button, input, optgroup, select, textarea {
color: inherit;
font: inherit;
}
button, input {
overflow: visible;
}
button, select {
text-transform: none;
}
input {
line-height: 1;
}
button::-moz-focus-inner,
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type=button]:-moz-focusring,
[type=reset]:-moz-focusring,
[type=submit]:-moz-focusring {
outline: 1px dotted ButtonText;
}
input, input:focus,
button, textarea,
select, a:focus {
outline: 0;
border: 0;
}
input::-webkit-input-placeholder,
input:-moz-placeholder,
textarea::-webkit-input-placeholder,
textarea:-moz-placeholder {
color: var(--color-pale);
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
height: auto;
}
input[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
color: inherit;
}
button,
input[type=button],
input[type=reset],
input[type=submit] {
-webkit-appearance: button;
cursor: pointer;
}
button[disabled], input[disabled] {
cursor: no-drop;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
outline: 0;
}
textarea {
overflow: auto;
resize: none;
}
a {
cursor: pointer;
text-decoration: none;
color: var(--color-brand-blue);
border-bottom: dashed 1px var(--color-brand-blue);
}
a:hover, a:focus {
border-bottom-style: solid;
}
table {
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
}
ul, ol {
list-style: none;
margin: 0;
padding: 0;
}
[hidden], template {
display: none;
}
svg:not(:root) {
overflow: hidden;
}
::selection {
background-color: var(--default-color-black);
color: var(--default-color-white);
}
.footer {
border-top: 1px solid var(--color-faded);
}
.maw-100p {
max-width: 100%;
}
.maw-content, .content-width {
max-width: var(--max-content-width);
}
.miw-content, #main {
min-width: var(--min-content-width);
}
.w-100p, .responsive-typography ul li, .responsive-typography ol li, .responsive-typography ul, .responsive-typography ol, #root, #main, .content, .content-width {
width: 100%;
}
.mah-100p {
max-height: 100%;
}
.mah-100vh {
max-height: 100vh;
}
.mih-100vh, #root {
min-height: 100vh;
}
.mar-ha, .content-width {
margin-left: auto;
margin-right: auto;
}
.pad-0x5, .main-menu > a, .responsive-typography th, .responsive-typography td {
padding: 0.5rem;
}
.pad-h-1x25, .content-width {
padding-right: 1.25rem;
padding-left: 1.25rem;
}
.pad-v-1, .header, .footer {
padding-top: 1rem;
padding-bottom: 1rem;
}
.gap-v-1x5 > :not([hidden]):not(.hidden-input) + :not([hidden]):not(.hidden-input), #main > :not([hidden]):not(.hidden-input) + :not([hidden]):not(.hidden-input) {
margin-top: 1.5rem;
}
.gap-h-0x5 > :not(:last-child):not(:only-child) {
margin-right: 0.5rem;
}
.gap-h-1 > :not(:last-child):not(:only-child), .main-menu > :not(:last-child):not(:only-child) {
margin-right: 1rem;
}
.row-sta-sta, .main-menu {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.row-sta-bet {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.col-sta-sta {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
.col-str-sta, #root, #main {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
}
.flex-1, #main, .content {
-webkit-box-flex: 1 0;
-ms-flex: 1 0;
flex: 1 0;
}
.responsive-typography h3 {
font-size: 24px;
margin: 1.5rem 0;
}
.responsive-typography > div, .responsive-typography p, .responsive-typography li {
font-size: 18px;
line-height: 1.5;
}
.responsive-typography ul li, .responsive-typography ol li {
position: relative;
min-height: 1.5rem;
padding-left: 1.5rem;
}
.responsive-typography ul li::before, .responsive-typography ol li::before {
content: "";
position: absolute;
top: 0;
left: 0;
}
.responsive-typography ul > li::before, .responsive-typography ol > li::before {
background-color: var(--color-brand-blue);
border-radius: 50%;
width: 0.5rem;
height: 0.5rem;
margin-top: 0.5rem;
margin-left: 0.25rem;
}
.responsive-typography ul > * + *, .responsive-typography ol > * + * {
margin-top: 2rem;
}
.responsive-typography p + ul,
.responsive-typography ul + p,
.responsive-typography p + p {
margin-top: 1rem;
}
.responsive-typography li + li {
margin-top: 0.5rem;
}
.responsive-typography table {
table-layout: fixed;
border-collapse: collapse;
line-height: 1.5;
}
.responsive-typography thead {
background-color: var(--color-brand-faded-blue);
}
.responsive-typography tbody tr {
border-top: solid 1px var(--color-pale);
}
.responsive-typography th, .responsive-typography td {
text-align: initial;
}
.responsive-typography th:not(:first-of-type), .responsive-typography td:not(:first-of-type) {
border-left: solid 1px var(--color-pale);
}
.responsive-typography td:nth-child(n+3) {
color: var(--color-warm-gray);
}
.anim, .main-menu > a, a, .anim::before, .main-menu > a::before, a::before, .anim::after, .main-menu > a::after, a::after {
transition: all 0.2s ease-in-out;
}
.main-menu > a {
color: var(--color-brand-blue);
border-radius: 6px;
border: 1px solid var(--color-brand-blue);
text-decoration: none;
}
.main-menu > a:hover, .main-menu > a[aria-current]:not([aria-current=""]) {
color: var(--default-color-white);
background-color: var(--color-brand-blue);
}
/* dropdown
* Source: https://codepen.io/markcaron/pen/wdVmpB
*
* TODO: change styles
* */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown > input[type=checkbox] {
position: absolute;
left: -100vw;
}
.dropdown > label {
display: inline-block;
padding: 6px 15px;
color: #333;
line-height: 1.5em;
text-decoration: none;
border: 1px solid #8c8c8c;
cursor: pointer;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.dropdown > label:hover {
border-color: #333;
}
.dropdown > label:after {
content: "▲";
font-size: 10px;
display: inline-block;
margin-left: 6px;
vertical-align: top;
}
.dropdown > ul {
position: absolute;
z-index: 999;
display: block;
left: -100vw;
bottom: calc(1.5em + 14px);
border: 1px solid #8c8c8c;
background: #fff;
padding: 6px 0;
margin: 0;
list-style: none;
width: 100%;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}
.dropdown > ul a {
display: block;
padding: 6px 15px;
text-decoration: none;
color: #333;
}
.dropdown > ul a:hover, .dropdown > ul a:focus {
background: #ececec;
}
.dropdown > input[type=checkbox]:checked ~ ul {
left: 0;
}
.dropdown > input[type=checkbox]:checked + label:after {
content: "▼";
}
/*# sourceMappingURL=main.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../../styles/base/reset.scss","../../styles/base/layout.scss","../../styles/atoms/sizes.scss","../../styles/atoms/white-spaces.scss","../../styles/mixins/white-spaces.scss","../../styles/atoms/flex.scss","../../styles/mixins/flex.scss","../../styles/atoms/typography.scss","../../styles/atoms/misc.scss","../../styles/uikit/main-menu.scss","../../styles/uikit/dropdown.scss"],"names":[],"mappings":";AAEA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;EAGE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAKF;EACE;EACA;;AAEA;EAEE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;;;AAGF;AAAA;AAAA;EAGE;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;;;AAGF;AAAA;EAEE;;;AAGF;EACE;EACA;;;AAGF;AAAA;EAEE;;;AAGF;EACE;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;EACE;;;AAGF;AAAA;EAEE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAKA;EACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;ACzSF;EAEE;;;ACTF;EAAe;;;AACf;EAAe;;;AACf;EAAe;;;AACf;EAAU;;;AAEV;EAAa;;;AACb;EAAa;;;AACb;EAAa;;;ACPb;ECQgB;EAAmB;;;ADNnC;ECQmB;;;ADPnB;ECWqB;EAEA;;;ADZrB;ECSqB;EAEA;;;AAInB;EAfmB;;;AAmBnB;EAlBmB;;;AAkBnB;EAlBmB;;;ACLrB;ECCE;EACA;EACA;EAWA;EACA;EACA;EACA;EA4CA;EACA;EACA;EAoCA;EACA;EACA;;;ADpGF;ECAE;EACA;EACA;EAWA;EACA;EACA;EACA;EAmEA;EACA;EACA;EAaA;EACA;EACA;;;ADnGF;ECDE;EACA;EACA;EAmBA;EACA;EACA;EACA;EAoCA;EACA;EACA;EAoCA;EACA;EACA;;;ADlGF;ECFE;EACA;EACA;EAmBA;EACA;EACA;EACA;EAoCA;EACA;EACA;EAsDA;EACA;EACA;;;ADlHF;ECuCE,kBDvCsB;ECwCtB,UDxCsB;ECyCtB,MDzCsB;;;AEHtB;EACE;EACA;;AAGF;EACE;EACA;;AAMA;EAGE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;AAAA;AAAA;EAGE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EAEE;;AAEA;EACE;;AAIJ;EACE;;;AC9EJ;EACE;;;ACGA;EAIE;EACA;EACA;EACA;;AAEA;EAEE;EACA;;;ACfN;AAAA;AAAA;AAAA;AAAA;AAMA;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAMJ;EACE;;AAGF;EACE","file":"main.css"}

7
old/render.ts Normal file
View file

@ -0,0 +1,7 @@
export function renderDate(date: Date): string {
return date.toLocaleDateString(undefined, {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}

246
old/server.ts Normal file
View file

@ -0,0 +1,246 @@
import { MarkdownParser } from "par/md.ts";
import { HtmlStrRenderer } from "ren/html_str.ts";
import * as log from "./log.ts";
import rusTranslates from "./translates/rus.ts";
import type { Translations } from "./translates/rus.ts";
import { Context, getLangHref, getLangUrlPrefix, Lang } from "./context.ts";
import { E404Page } from "./views/pages/e404.ts";
import { E500Page } from "./views/pages/e500.ts";
import { WorksPage } from "./views/pages/works.ts";
import { Layout } from "./views/comp/layout.ts";
import { ContentPage } from "./views/pages/content.ts";
if (import.meta.main) {
await main();
}
async function main() {
await startServer({ port: 33334 });
}
async function startServer(cfg: ServerConfig) {
const srv = Deno.listen({ hostname: "0.0.0.0", port: cfg.port });
log.info(`Server listening at http://localhost:${cfg.port}`);
for await (const conn of srv) {
serveHttp(conn);
}
}
interface ServerConfig {
port: number;
}
async function serveHttp(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const reqEvt of httpConn) {
const res = await handleRequest(reqEvt.request);
reqEvt.respondWith(res);
}
}
async function handleRequest(req: Request): Promise<Response> {
log.info({ method: req.method, url: req.url });
if (req.method === "GET") {
return await handleGet(req);
} else {
return new Response("Method Not Allowed", { status: 405 });
}
}
async function handleGet(req: Request) {
const restCtx = createRestContextFromRequest(req);
try {
const res = await tryCreateFileResponse(restCtx.locPath);
return res;
} catch (_) {
if (restCtx.lang == null && restCtx.newLang) {
return new Response(null, {
status: 301,
headers: {
location: getLangUrlPrefix(restCtx.newLang) + restCtx.locPath,
},
});
}
const ctx = intoAppContext(restCtx);
if (restCtx.lang !== Lang.Rus) {
await loadAndUpdateTranslations(ctx);
}
log.debug({ context: restCtx });
const par = new MarkdownParser();
const ren = new HtmlStrRenderer({
wrapNode: Layout.bind(null, ctx),
onVisitAttr: ([key, value]) => {
if (key === "lhref" && typeof value === "string") {
return ["href", getLangHref(ctx.lang, value)];
} else {
return [key, value];
}
},
});
try {
if (restCtx.locPath === "/" || restCtx.locPath === "/about") {
const res = par.parse(
await readMarkdownFile("data/about", ctx.lang),
);
return createHtmlResponse(ren.render(ContentPage(ctx, res)));
} else if (restCtx.locPath === "/works") {
const res = par.parse(
await readMarkdownFile("data/works", ctx.lang),
);
return createHtmlResponse(ren.render(WorksPage(ctx, res)));
} else {
return createHtmlResponse(ren.render(E404Page(ctx)), 404);
}
} catch (e) {
log.error(e);
return createHtmlResponse(ren.render(E500Page(ctx)), 500);
}
}
}
async function readMarkdownFile(dirPath: string, lang: Lang): Promise<string> {
return await Deno.readTextFile(`${dirPath}/${lang}.md`)
.catch((_) => Deno.readTextFile(`${dirPath}/${Lang.Rus}.md`));
}
async function loadAndUpdateTranslations(ctx: Context) {
try {
const translates = await import(`./translates/${ctx.lang}.ts`);
ctx.tr = Object.entries(translates.default as Partial<Translations>)
.reduce(
(acc, [key, val]) => ({
...acc,
[key as keyof Translations]: val,
}),
{ ...ctx.tr } as Translations,
);
} catch (err) {
log.debug({ err });
/* ignore */
}
}
function intoAppContext(restCtx: RestContext): Context {
return {
locPath: restCtx.locPath,
lang: restCtx.lang || Lang.Rus,
tr: restCtx.tr,
};
}
function createRestContextFromRequest(req: Request): RestContext {
log.debug(req.headers);
const locUrl = new URL(req.url);
const lang = tryIntoAppLangFromUrl(locUrl);
return {
lang,
newLang: getPreferRequestLang(req.headers) ?? Lang.Rus,
locPath: stripPrefix(`/${lang}`, locUrl.pathname),
tr: rusTranslates,
};
}
interface RestContext {
locPath: string;
lang: Lang | null;
newLang: Lang;
tr: Translations;
}
function getPreferRequestLang(headers: Headers): Lang | null {
const acceptLanguageHeader = headers.get("accept-language");
if (!acceptLanguageHeader) return null;
const acceptLanguages = acceptLanguageHeader
.split(/,\s*/)
.map((part) => part.split(";q=")[0])
.map(tryIntoAppLangFromAcceptLangCode)
.filter((lang): lang is Lang => !!lang);
return acceptLanguages[0] ?? null;
}
function tryIntoAppLangFromAcceptLangCode(lang: string): Lang | null {
return lang === "*"
? Lang.Rus
: lang.startsWith("en")
? Lang.Eng
: lang.startsWith("ru")
? Lang.Rus
: null;
}
function tryIntoAppLangFromUrl(url: URL): Lang | null {
return url.pathname.startsWith("/eng/")
? Lang.Eng
: url.pathname.startsWith("/rus/")
? Lang.Rus
: null;
}
function stripPrefix(prefix: string, val: string): string {
return val.startsWith(prefix) ? val.slice(prefix.length) : val;
}
function createHtmlResponse(body: string, status = 200): Response {
return new Response(body, {
status,
headers: getContentTypeHeader("html"),
});
}
async function tryCreateFileResponse(urlPath: string): Promise<Response> {
const filePath = extractFilePath(urlPath);
log.debug({ filePath });
if (!filePath) throw new SkipFile();
const content = await Deno.readTextFile(filePath).catch(() => {
throw new SkipFile();
});
return createFileResponse(content, getFileExt(filePath));
}
class SkipFile extends Error {}
function createFileResponse(content: string, fileExt: string): Response {
return new Response(content, {
headers: getContentTypeHeader(fileExt),
});
}
function extractFilePath(urlPath: string): string | null {
const relPath = urlPath.slice(1);
if (relPath.startsWith("styles/")) {
return `public/${relPath}`;
}
return null;
}
function getContentTypeHeader(fileExt: string): Record<string, string> {
return { "content-type": getContentTypeByExt(fileExt) };
}
function getContentTypeByExt(fileExt: string): string {
switch (fileExt) {
case "html":
return "text/html";
case "css":
return "text/css";
default:
return "text/plain";
}
}
function getFileExt(filePath: string): string {
return filePath.slice((filePath.lastIndexOf(".") - 1 >>> 0) + 2);
}

View file

@ -0,0 +1,6 @@
.row-sta-sta { @include flex-layout(row, sta, sta) }
.row-sta-bet { @include flex-layout(row, sta, bet) }
.col-sta-sta { @include flex-layout(col, sta, sta) }
.col-str-sta { @include flex-layout(col, str, sta) }
.flex-1 { @include flex(1 0) }

View file

@ -0,0 +1,3 @@
.anim, .anim::before, .anim::after {
transition: all 0.2s ease-in-out;
}

View file

@ -0,0 +1,5 @@
@import 'sizes';
@import 'white-spaces';
@import 'flex';
@import 'typography';
@import 'misc';

View file

@ -0,0 +1,9 @@
.maw-100p { max-width: 100% }
.maw-content { max-width: var(--max-content-width) }
.miw-content { min-width: var(--min-content-width) }
.w-100p { width: 100% }
.mah-100p { max-height: 100% }
.mah-100vh { max-height: 100vh }
.mih-100vh { min-height: 100vh }

View file

@ -0,0 +1,86 @@
.responsive-typography {
h3 {
font-size: 24px;
margin: 1.5rem 0;
}
> div, p, li {
font-size: 18px;
line-height: 1.5;
}
ul, ol {
@extend .w-100p;
li {
@extend .w-100p;
position: relative;
min-height: 1.5rem;
padding-left: 1.5rem;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
}
}
> li::before {
background-color: var(--color-brand-blue);
border-radius: 50%;
width: 0.5rem;
height: 0.5rem;
margin-top: 0.5rem;
margin-left: 0.25rem;
}
> * + * {
margin-top: 2rem;
}
}
p + ul,
ul + p,
p + p {
margin-top: 1rem;
}
li + li {
margin-top: 0.5rem;
}
table {
table-layout: fixed;
border-collapse: collapse;
line-height: 1.5;
}
thead {
background-color: var(--color-brand-faded-blue);
}
tbody tr {
border-top: solid 1px var(--color-pale);
}
th, td {
@extend .pad-0x5;
text-align: initial;
&:not(:first-of-type) {
border-left: solid 1px var(--color-pale);
}
}
td:nth-child(n+3) {
color: var(--color-warm-gray)
}
}

View file

@ -0,0 +1,11 @@
.mar-ha { @include mar-ha }
.pad-0x5 { @include pad(0.5) }
.pad-h-1x25 { @include pad-h(1.25) }
.pad-v-1 { @include pad-v(1) }
.gap-v-1x5 { @include gap-v(1.5) }
.gap-h-0x5 { @include gap-h(0.5) }
.gap-h-1 { @include gap-h(1) }

View file

@ -0,0 +1,11 @@
#root { @extend .col-str-sta, .w-100p, .mih-100vh }
#main { @extend .col-str-sta, .flex-1, .miw-content, .w-100p, .gap-v-1x5 }
.content { @extend .flex-1, .w-100p }
.content-width { @extend .maw-content, .w-100p, .mar-ha, .pad-h-1x25 }
.header { @extend .pad-v-1 }
.footer {
@extend .pad-v-1;
border-top: 1px solid var(--color-faded);
}

307
old/styles/base/reset.scss Normal file
View file

@ -0,0 +1,307 @@
@use "sass:math";
:root {
// Default Colors
--default-color-black: #000000;
--default-color-black-0: hsla(0, 0%, 0%, 0);
--default-color-black-0x15: hsla(0, 0%, 0%, 0.15);
--default-color-black-0x6: hsla(0, 0%, 0%, 0.6);
--default-color-white: #ffffff;
--default-color-warning: #ffee58;
--default-color-error: #b00008;
--default-color-success: #417505;
// Project Colors
--color-brand-blue: #1966df;
--color-brand-faded-blue: #f5f5ff;
--color-graphite: #212121;
--color-warm-gray: #757575;
--color-pale: #b6b6b6;
--color-faded: #e0e0e0;
// Layout
--max-content-width: #{$page-max-width + px};
--min-content-width: #{$page-min-width + px};
// Borders
--rad-std-half: #{math.div($radius, 2)};
--rad-std: #{$radius};
--rad-std-x2: #{$radius * 2};
--rad-std-x3: #{$radius * 3};
// Font
--default-font-size: 16px;
--f-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto,Oxygen-Sans, Ubuntu, Cantarell, "Segoe UI", Verdana, sans-serif;
--f-wei-thin: 100;
--f-wei-reg: 400; // Normal = Regular
--f-wei-bold: 700;
--f-wei-black: 800; // Extra Bold = Black
// Z-index
--z-ind-background: -100;
--z-ind-backward: -1;
--z-ind-select: 50;
--z-ind-tooltip: 75;
--z-ind-high: 100;
--z-ind-overlay: 500;
}
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
background-color: var(--default-color-white);
font-size: var(--default-font-size);
line-height: 1;
height: 100%;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
color: var(--color-graphite);
font-weight: var(--f-wei-regular);
font-family: var(--f-family);
min-width: var(--min-content-width);
width: 100%;
height: 100%;
}
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
summary {}
details > summary {
cursor: pointer;
list-style: none;
&::before,
&::-webkit-details-marker {
display: none;
}
}
audio, canvas, progress, video {
display: inline-block;
vertical-align: baseline;
}
audio:not([controls]) {
display: none;
height: 0;
}
address, caption, cite, code, dfn, strong, th, var {
font-style: normal;
font-weight: var(--f-wei-regular);
}
h1, h2, h3, h4, h5, h6 {
font-weight: var(--f-wei-bold);
line-height: 1;
}
b, strong, optgroup {
font-weight: var(--f-wei-bold);
}
dfn, em, i {
font-style: italic;
}
iframe, abbr, acronym, img {
border: 0;
outline: 0;
}
mark {
background: var(--default-color-warning);
color: var(--default-color-black);
}
small {
font-size: 80%;
}
sub, sup {
font-size: 80%;
vertical-align: baseline;
line-height: 0;
position: relative;
}
sup {
top: -0.25em;
}
sub {
bottom: -0.25em;
}
q::before, q::after {
content: '';
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
code, kbd, pre, samp {
font-family: monospace;
font-size: 1em;
}
pre {
overflow: auto;
}
figure {
margin: 0;
}
fieldset {
border: 1px solid var(--color-pale);
padding: 0.25rem 0.75rem;
}
legend {
border: 0;
color: inherit;
display: table;
word-spacing: normal;
max-width: 100%;
}
button, input, optgroup, select, textarea {
color: inherit;
font: inherit;
}
button, input {
overflow: visible;
}
button, select {
text-transform: none;
}
input {
line-height: 1;
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}
input, input:focus,
button, textarea,
select, a:focus {
outline: 0;
border: 0;
}
input::-webkit-input-placeholder,
input:-moz-placeholder,
textarea::-webkit-input-placeholder,
textarea:-moz-placeholder {
color: var(--color-pale);
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
height: auto;
}
input[type='search'] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
color: inherit;
}
button,
input[type='button'],
input[type='reset'],
input[type='submit'] {
-webkit-appearance: button;
cursor: pointer;
}
button[disabled], input[disabled] {
cursor: no-drop;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
outline: 0;
}
textarea {
overflow: auto;
resize: none;
}
a {
cursor: pointer;
text-decoration: none;
color: var(--color-brand-blue);
border-bottom: dashed 1px var(--color-brand-blue);
// TODO: move to other place
@extend .anim;
&:hover, &:focus {
border-bottom-style: solid;
}
}
table {
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
}
ul, ol {
list-style: none;
margin: 0;
padding: 0;
}
[hidden], template {
display: none;
}
svg:not(:root) {
overflow: hidden;
}
::selection {
background-color: var(--default-color-black);
color: var(--default-color-white);
}

6
old/styles/main.scss Normal file
View file

@ -0,0 +1,6 @@
@import 'variables';
@import 'mixins/mod';
@import 'base/reset';
@import 'base/layout';
@import 'atoms/mod';
@import 'uikit/mod';

165
old/styles/mixins/flex.scss Normal file
View file

@ -0,0 +1,165 @@
@mixin dis-flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
@mixin dis-inl-flex {
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
}
@mixin flex-dir-row {
@include dis-flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
}
@mixin flex-dir-col {
@include dis-flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
@mixin flex-dir($val) {
@if $val == row {@include flex-dir-row;}
@else if $val == col {@include flex-dir-col;}
@else {@error 'unknown flex-direction: #{$val}';}
}
@mixin flex-wrap($val: wrap) {
-ms-flex-wrap: $val;
flex-wrap: $val;
}
@mixin flex-nowrap {
@include flex-wrap(nowrap);
}
@mixin flex($val) {
-webkit-box-flex: $val;
-ms-flex: $val;
flex: $val;
}
@mixin flex-shrink($val) {
-ms-flex-negative: $val;
flex-shrink: $val;
}
@mixin ord($val) {
-webkit-box-ordinal-group: $val + 1;
-ms-flex-order: $val;
order: $val;
}
@mixin flex-jus-sta {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
}
@mixin flex-jus-cen {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
@mixin flex-jus-end {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
@mixin flex-jus-aro {
-ms-flex-pack: distribute;
justify-content: space-around;
}
@mixin flex-jus-bet {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
@mixin flex-jus($val) {
@if $val == sta {@include flex-jus-sta;}
@else if $val == cen {@include flex-jus-cen;}
@else if $val == end {@include flex-jus-end;}
@else if $val == aro {@include flex-jus-aro;}
@else if $val == bet {@include flex-jus-bet;}
@else {@error 'unknown flex-jus (justify-content) property: #{$val}';}
}
@mixin flex-ali-sta {
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
}
@mixin flex-ali-cen {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
@mixin flex-ali-end {
-webkit-box-align: end;
-ms-flex-align: end;
align-items: flex-end;
}
@mixin flex-ali-str {
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
}
@mixin flex-ali($val) {
@if $val == sta {@include flex-ali-sta;}
@else if $val == cen {@include flex-ali-cen;}
@else if $val == end {@include flex-ali-end;}
@else if $val == str {@include flex-ali-str;}
@else {@error 'unknown flex-ali (align-items) property: #{$val}';}
}
@mixin ali-self-sta {
-ms-flex-item-align: start;
align-self: flex-start;
}
@mixin ali-self-cen {
-ms-flex-item-align: center;
-ms-grid-row-align: center;
align-self: center;
}
@mixin ali-self-end {
-ms-flex-item-align: end;
align-self: flex-end;
}
@mixin ali-self-str {
-ms-flex-item-align: stretch;
-ms-grid-row-align: stretch;
align-self: stretch;
}
@mixin ali-self($val) {
@if $val == sta {@include ali-self-sta;}
@else if $val == cen {@include ali-self-cen;}
@else if $val == end {@include ali-self-end;}
@else if $val == str {@include ali-self-str;}
@else {@error 'unknown ali-self (align-self) property: #{$val}';}
}
@mixin flex-layout($dir: none, $ali: none, $jus: none) {
@if $dir != none {@include flex-dir($dir);}
@if $jus != none {@include flex-jus($jus);}
@if $ali != none {@include flex-ali($ali);}
}

View file

@ -0,0 +1,2 @@
@import 'flex';
@import 'white-spaces';

View file

@ -0,0 +1,29 @@
@mixin mar($val) { margin: #{$val}rem }
@mixin mar-v($val) { @include mar-t($val); @include mar-b($val) }
@mixin mar-h($val) { @include mar-r($val); @include mar-l($val) }
@mixin mar-t($val) { margin-top: #{$val}rem }
@mixin mar-r($val) { margin-right: #{$val}rem }
@mixin mar-b($val) { margin-bottom: #{$val}rem }
@mixin mar-l($val) { margin-left: #{$val}rem }
@mixin mar-ha { margin-left: auto; margin-right: auto }
@mixin pad($val) { padding: #{$val}rem }
@mixin pad-v($val) { @include pad-t($val); @include pad-b($val) }
@mixin pad-h($val) { @include pad-r($val); @include pad-l($val) }
@mixin pad-t($val) { padding-top: #{$val}rem }
@mixin pad-r($val) { padding-right: #{$val}rem }
@mixin pad-b($val) { padding-bottom: #{$val}rem }
@mixin pad-l($val) { padding-left: #{$val}rem }
@mixin gap-v($val) {
> :not([hidden]):not(.hidden-input) + :not([hidden]):not(.hidden-input) { @include mar-t($val) }
}
@mixin gap-h($val) {
> :not(:last-child):not(:only-child) { @include mar-r($val) }
}
@mixin gap-h-l($val) {
> :not([hidden]):not(.hidden-input) + :not([hidden]):not(.hidden-input) { @include mar-l($val) }
}

View file

@ -0,0 +1,83 @@
/* dropdown
* Source: https://codepen.io/markcaron/pen/wdVmpB
*
* TODO: change styles
* */
.dropdown {
position: relative;
display: inline-block;
& > input[type="checkbox"] {
position: absolute;
left: -100vw;
}
& > label {
display: inline-block;
padding: 6px 15px;
color: #333;
line-height: 1.5em;
text-decoration: none;
border: 1px solid #8c8c8c;
cursor: pointer;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
&:hover {
border-color: #333;
}
&:after {
content: "";
font-size: 10px;
display: inline-block;
margin-left: 6px;
vertical-align: top;
}
}
& > ul {
position: absolute;
z-index: 999;
display: block;
left: -100vw;
bottom: calc(1.5em + 14px);
border: 1px solid #8c8c8c;
background: #fff;
padding: 6px 0;
margin: 0;
list-style: none;
width: 100%;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 3px 8px rgba(0,0,0,.15);
-moz-box-shadow: 0 3px 8px rgba(0,0,0,.15);
box-shadow: 0 3px 8px rgba(0,0,0,.15);
a {
display: block;
padding: 6px 15px;
text-decoration: none;
color: #333;
&:hover, &:focus {
background: #ececec;
}
}
}
& > input[type="checkbox"]:checked {
& ~ ul {
left: 0;
}
& + label:after {
content: "";
}
}
}

View file

@ -0,0 +1,21 @@
.main-menu {
@extend .row-sta-sta, .gap-h-1;
> a {
@extend .pad-0x5, .anim;
// TODO: move to atoms
color: var(--color-brand-blue);
border-radius: 6px;
border: 1px solid var(--color-brand-blue);
text-decoration: none;
&:hover,
&[aria-current]:not([aria-current=""]) {
color: var(--default-color-white);
background-color: var(--color-brand-blue);
}
}
}

View file

@ -0,0 +1,2 @@
@import 'main-menu';
@import 'dropdown';

View file

@ -0,0 +1,9 @@
@use "sass:math";
$radius: 0.25rem;
$col: 12;
$col-gutter: 24;
$page-max-width: 1440;
$page-min-width: 320;
$content-width: $page-max-width - ($col-gutter * 2);
$col-width: math.div($content-width - $col-gutter * $col - 1, $col);

16
old/translates/eng.ts Normal file
View file

@ -0,0 +1,16 @@
import { Translations } from "./rus.ts";
export default {
About: "About",
Works: "Works",
Chronological: "Chronological",
Source_code: "Source code",
Page_not_found: "Page not found",
Internal_server_error: "Internal server error",
Name: "Name",
Description: "Description",
Role: "Role",
Technologies: "Technologies",
Start: "Start",
Status_or_End: "Status/End",
} as Translations;

18
old/translates/rus.ts Normal file
View file

@ -0,0 +1,18 @@
export const rus = {
About: "Обо мне",
Works: "Работы",
Chronological: "Хронология",
Source_code: "Исходный код",
Page_not_found: "Страница не найдена",
Internal_server_error: "Внутренняя ошибка сервера",
Name: "Название",
Description: "Описание",
Role: "Роль",
Technologies: "Технологии",
Start: "Начало",
Status_or_End: "Статус/Окончание",
};
export default rus;
export type Translations = typeof rus;

20
old/uikit/link.ts Normal file
View file

@ -0,0 +1,20 @@
import { AnyNode, Attrs, E } from "ren/node.ts";
export function Link(
text: string,
sourceAttrs: Attrs | Attrs[],
): AnyNode {
const attrs = Array.isArray(sourceAttrs) ? sourceAttrs : [sourceAttrs];
const isExternal = attrs.some((attr) =>
typeof attr.href === "string" && attr.href?.startsWith("http")
);
if (isExternal) {
attrs.push({
target: "_blank",
rel: "external nofollow noopener noreferrer",
});
}
return E("a", attrs, text);
}

6
old/uikit/typo.ts Normal file
View file

@ -0,0 +1,6 @@
import { classNames } from "ren/attrs.ts";
import { E, Elem } from "ren/node.ts";
export function H3(text: string): Elem {
return E("h3", classNames("font-h3"), text);
}

19
old/views/comp/layout.ts Normal file
View file

@ -0,0 +1,19 @@
import { AnyNode, E } from "ren/node.ts";
import { Context } from "../../context.ts";
export function Layout(ctx: Context, page: AnyNode): AnyNode {
return E("html", { lang: ctx.lang }, [
E("head", [], [
E("meta", { charset: "utf-8" }),
E("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
E("link", { rel: "stylesheet", href: "/styles/main.css" }),
E("title", [], ctx.title ?? "Pleshevski"),
]),
E("body", [], [
E("div", { id: "root" }, [page]),
]),
]);
}

View file

@ -0,0 +1,72 @@
import { AnyNode, Attrs, E, Elem } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context, getLangHref, iterLangs, Lang } from "../../context.ts";
import { Link } from "../../uikit/link.ts";
import { renderDate } from "../../render.ts";
const SITE_UPDATED_AT = new Date();
export function PageLayout(ctx: Context, children: AnyNode[]): Elem {
return E("div", { id: "main" }, [
Header(ctx),
E("div", classNames("content"), children),
Footer(ctx),
]);
}
export function Header(ctx: Context): AnyNode {
return E("header", classNames("header gap-v-1x5"), [
E("div", classNames("content-width"), [HeaderNav(ctx)]),
]);
}
export function HeaderNav(ctx: Context): AnyNode {
return E("nav", classNames("main-menu"), [
Link(ctx.tr.About, navLink("/", ctx)),
Link(ctx.tr.Works, navLink("/works", ctx)),
]);
}
function navLink(lhref: string, ctx?: Context): Attrs {
const attrs: Attrs = { lhref };
if (ctx?.locPath === lhref) attrs["aria-current"] = "true";
return attrs;
}
export function Footer(ctx: Context): AnyNode {
return E("footer", classNames("footer"), [
E("div", classNames("content-width row-sta-bet"), [
E("div", classNames("gap-v-1x5"), [
E("div", [], [
E("b", [], "Updated At:"),
renderDate(SITE_UPDATED_AT),
]),
E("div", [], [
Link(ctx.tr.Source_code, {
href: "https://git.pleshevski.ru/pleshevskiy/pleshevski.ru",
}),
]),
]),
ChangeLang(ctx),
]),
]);
}
export function ChangeLang(ctx: Context): AnyNode {
const dropdownId = "change_langs";
return E("div", classNames("dropdown"), [
E("input", { id: dropdownId, type: "checkbox" }),
E("label", { for: dropdownId }, ctx.lang),
E(
"ul",
[],
iterLangs().filter((l) => l !== ctx.lang).map((l) =>
ChangeLangBtn(ctx, l)
),
),
]);
}
export function ChangeLangBtn(ctx: Context, lang: Lang): AnyNode {
return Link(lang, { "href": getLangHref(lang, ctx.locPath) });
}

View file

@ -0,0 +1,12 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../../context.ts";
export function ContentPage(ctx: Context, content: AnyNode): AnyNode {
ctx.title = "About | Pleshevski";
return PageLayout(ctx, [
E("div", classNames("content-width responsive-typography"), [content]),
]);
}

17
old/views/pages/e404.ts Normal file
View file

@ -0,0 +1,17 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../../context.ts";
import { H3 } from "../../uikit/typo.ts";
export function E404Page(ctx: Context): AnyNode {
ctx.title = "Not Found - 404 | Pleshevski";
return PageLayout(ctx, [E404(ctx)]);
}
export function E404(ctx: Context): AnyNode {
return E("div", classNames("content-width gap-v-1x5"), [
H3(ctx.tr.Page_not_found),
]);
}

17
old/views/pages/e500.ts Normal file
View file

@ -0,0 +1,17 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../../context.ts";
import { H3 } from "../../uikit/typo.ts";
export function E500Page(ctx: Context): AnyNode {
ctx.title = "Internal Server Error - 500 | Pleshevski";
return PageLayout(ctx, [E500(ctx)]);
}
export function E500(ctx: Context): AnyNode {
return E("div", classNames("content-width gap-v-1x5"), [
H3(ctx.tr.Internal_server_error),
]);
}

18
old/views/pages/works.ts Normal file
View file

@ -0,0 +1,18 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../../context.ts";
import { ChronologicalWorksTable } from "../../modules/work/ChronologicalWorksTable/mod.ts";
import { H3 } from "../../uikit/typo.ts";
export function WorksPage(ctx: Context, content: AnyNode): AnyNode {
ctx.title = "Works | Pleshevski";
return PageLayout(ctx, [
E("div", classNames("content-width gap-v-1x5 responsive-typography"), [
content,
H3(ctx.tr.Chronological),
ChronologicalWorksTable(ctx.tr),
]),
]);
}