update to new arch #2
27 changed files with 94 additions and 3481 deletions
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
!/tsconfig.json
|
|
||||||
|
|
||||||
# node modules
|
|
||||||
!/package.json
|
|
||||||
!/package-lock.json
|
|
||||||
|
|
||||||
# sources
|
|
||||||
!/src
|
|
||||||
!/static
|
|
|
@ -1,28 +0,0 @@
|
||||||
parser: "@typescript-eslint/parser"
|
|
||||||
env:
|
|
||||||
es6: true
|
|
||||||
node: true
|
|
||||||
parserOptions:
|
|
||||||
ecmaVersion: 2020
|
|
||||||
sourceType: "module"
|
|
||||||
extends:
|
|
||||||
- prettier
|
|
||||||
- plugin:prettier/recommended
|
|
||||||
- "plugin:@typescript-eslint/recommended"
|
|
||||||
rules:
|
|
||||||
"@typescript-eslint/no-unused-vars":
|
|
||||||
- error
|
|
||||||
- vars: all
|
|
||||||
args: after-used
|
|
||||||
argsIgnorePattern: ^_
|
|
||||||
varsIgnorePattern: ^_
|
|
||||||
ignoreRestSiblings: true
|
|
||||||
"@typescript-eslint/no-empty-interface": off
|
|
||||||
"@typescript-eslint/no-explicit-any": off
|
|
||||||
"@typescript-eslint/explicit-function-return-type":
|
|
||||||
- warn
|
|
||||||
- allowExpressions: false
|
|
||||||
allowTypedFunctionExpressions: true
|
|
||||||
allowHigherOrderFunctions: true
|
|
||||||
"@typescript-eslint/camelcase": off
|
|
||||||
"@typescript-eslint/no-use-before-define": off
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
||||||
!/*file
|
!/*file
|
||||||
!/*.yml
|
!/*.yml
|
||||||
!/*.json
|
!/*.json
|
||||||
|
!/*.nix
|
||||||
|
|
||||||
# sources
|
# sources
|
||||||
!/src
|
!/src
|
||||||
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"editor.tabSize": 2
|
|
||||||
}
|
|
27
Dockerfile
27
Dockerfile
|
@ -1,24 +1,13 @@
|
||||||
FROM node:16.14.2-alpine3.14
|
FROM denoland/deno:alpine-1.22.1
|
||||||
|
|
||||||
|
EXPOSE 33334
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk update \
|
USER deno
|
||||||
&& apk upgrade \
|
|
||||||
&& apk add --no-cache bash git openssh
|
|
||||||
|
|
||||||
COPY package*.json ./
|
ADD . .
|
||||||
|
# Compile the main app so that it doesn't need to be compiled each startup/entry.
|
||||||
|
RUN deno cache server.ts
|
||||||
|
|
||||||
RUN npm install \
|
CMD ["run", "-A", "server.ts"]
|
||||||
&& apk del bash git openssh
|
|
||||||
|
|
||||||
COPY tsconfig.json ./
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
RUN npm run build \
|
|
||||||
&& npm prune --production
|
|
||||||
|
|
||||||
COPY static ./static/
|
|
||||||
|
|
||||||
EXPOSE 30000
|
|
||||||
|
|
||||||
CMD npm run start
|
|
||||||
|
|
6
deno.json
Normal file
6
deno.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["deno.ns", "dom"]
|
||||||
|
},
|
||||||
|
"importMap": "./import_map.json"
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
version: "3.9"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
rp_public:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
site:
|
|
||||||
image: pleshevski
|
|
||||||
networks:
|
|
||||||
- rp_public
|
|
||||||
deploy:
|
|
||||||
replicas: 1
|
|
||||||
endpoint_mode: vip
|
|
||||||
update_config:
|
|
||||||
order: start-first
|
|
||||||
rollback_config:
|
|
||||||
order: start-first
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=rp_public
|
|
||||||
- traefik.constraint-label=rp_public
|
|
||||||
- traefik.http.routers.pleshevski_http.rule=Host(`pleshevski.ru`)
|
|
||||||
- traefik.http.routers.pleshevski_http.entrypoints=http
|
|
||||||
- traefik.http.routers.pleshevski_http.middlewares=https_redirect
|
|
||||||
- traefik.http.routers.pleshevski_https.rule=Host(`pleshevski.ru`)
|
|
||||||
- traefik.http.routers.pleshevski_https.entrypoints=https
|
|
||||||
- traefik.http.routers.pleshevski_https.tls=true
|
|
||||||
- traefik.http.routers.pleshevski_https.tls.certresolver=le
|
|
||||||
- traefik.http.services.pleshevski.loadbalancer.server.port=30000
|
|
||||||
|
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1654593855,
|
||||||
|
"narHash": "sha256-c+SyXvj7THre87OyIdZfRVR+HhI/g1ZDrQ3VUtTuHkU=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "033bd4fa9a8fbe0c68a88e925d9a884161044b25",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1653893745,
|
||||||
|
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
22
flake.nix
Normal file
22
flake.nix
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
description = "Pleshevski personal site";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {self, nixpkgs, utils}:
|
||||||
|
let out = system:
|
||||||
|
let pkgs = nixpkgs.legacyPackages."${system}";
|
||||||
|
in {
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
gnumake
|
||||||
|
nodePackages.sass
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in with utils.lib; eachSystem defaultSystems out;
|
||||||
|
|
||||||
|
}
|
5
import_map.json
Normal file
5
import_map.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"ren/": "https://git.pleshevski.ru/pleshevskiy/ren/raw/branch/main/ren/"
|
||||||
|
}
|
||||||
|
}
|
30
makefile
30
makefile
|
@ -1,10 +1,15 @@
|
||||||
PAR := $(MAKE) -j 128
|
PAR := $(MAKE) -j 128
|
||||||
DOCKER_NAME := pleshevski
|
DOCKER_NAME := recipes
|
||||||
DOCKER_TAG := pleshevski
|
DOCKER_TAG := recipes
|
||||||
|
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
$(PAR) hr ts-w
|
${PAR} deno-w sass-w
|
||||||
|
|
||||||
|
deno-w:
|
||||||
|
deno run -A --watch server.ts
|
||||||
|
|
||||||
|
sass-w:
|
||||||
|
sass -w styles/main.scss public/styles/main.css
|
||||||
|
|
||||||
docker-restart: docker-stop docker-run
|
docker-restart: docker-stop docker-run
|
||||||
|
|
||||||
|
@ -16,20 +21,3 @@ docker-run:
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build -t ${DOCKER_TAG} .
|
docker build -t ${DOCKER_TAG} .
|
||||||
|
|
||||||
build: ts
|
|
||||||
|
|
||||||
start:
|
|
||||||
npm run start
|
|
||||||
|
|
||||||
hr:
|
|
||||||
deno run -A ~/sandbox/hr/server.ts target static
|
|
||||||
|
|
||||||
ts:
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
ts-w:
|
|
||||||
NODE_ENV=develop npx tsc-watch --onSuccess "make start"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf target
|
|
||||||
|
|
2849
package-lock.json
generated
2849
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
20
package.json
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"start": "node target/scripts/main.mjs"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^17.0.21",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
|
||||||
"@typescript-eslint/parser": "^5.14.0",
|
|
||||||
"eslint": "^8.10.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
|
||||||
"prettier": "^2.5.1",
|
|
||||||
"tsc-watch": "^4.6.0",
|
|
||||||
"typescript": "^4.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ren": "github:pleshevskiy/ren"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { AnyNode, E, Ea, Elem } from "ren";
|
|
||||||
import { config } from "../config.mjs";
|
|
||||||
import { div } from "../utils.mjs";
|
|
||||||
|
|
||||||
export function Layout(page: AnyNode): Elem {
|
|
||||||
return Ea("html", { lang: "ru" }, [
|
|
||||||
E("head", [
|
|
||||||
Ea("meta", { charset: "utf-8" }),
|
|
||||||
Ea("meta", {
|
|
||||||
name: "viewport",
|
|
||||||
content: "width=device-width, initial-scale=1",
|
|
||||||
}),
|
|
||||||
Ea("link", {
|
|
||||||
rel: "stylesheet",
|
|
||||||
href: "/static/styles.css",
|
|
||||||
}),
|
|
||||||
E("title", "pleshevski"),
|
|
||||||
]),
|
|
||||||
E("body", [div({ id: "root" }, page), config.isDev && HotReloadScript()]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function HotReloadScript(): Elem {
|
|
||||||
return E(
|
|
||||||
"script",
|
|
||||||
`const ws = new WebSocket("ws://localhost:30001");
|
|
||||||
ws.addEventListener("message", (m) => {
|
|
||||||
if (m.data === "RELOAD") location.reload();
|
|
||||||
});`
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { AnyNode, Ea, Elem } from "ren";
|
|
||||||
import { Context } from "../context.mjs";
|
|
||||||
import { div } from "../utils.mjs";
|
|
||||||
|
|
||||||
export function PageLayout(ctx: Context, ...children: AnyNode[]): Elem {
|
|
||||||
return Ea("div", { id: "main" }, [
|
|
||||||
Header(ctx.locPath),
|
|
||||||
Ea("div", { class: "content" }, children),
|
|
||||||
// Footer(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Header(locPath: string): Elem {
|
|
||||||
return Ea("header", { class: "header" }, [
|
|
||||||
div({ class: "content-width" }, HeaderNav(locPath)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeaderNav(locPath: string): Elem {
|
|
||||||
return Ea("nav", { class: "main-menu" }, [
|
|
||||||
NavLink(locPath, "/", "Обо мне"),
|
|
||||||
NavLink(locPath, "/works", "Работы"),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NavLink(locPath: string, href: string, text: string): Elem {
|
|
||||||
const attrs = { href };
|
|
||||||
if (locPath === href) attrs["aria-current"] = "true";
|
|
||||||
return Ea("a", attrs, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Footer(): Elem {
|
|
||||||
return Ea("footer", { class: "footer" }, "footer");
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
export const config = createConfig();
|
|
||||||
|
|
||||||
export function createConfig(): Config {
|
|
||||||
return {
|
|
||||||
isDev: process.env.NODE_ENV === "develop",
|
|
||||||
server: { port: 30000 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
isDev: boolean;
|
|
||||||
server: ServerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerConfig {
|
|
||||||
port: number;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export interface Context {
|
|
||||||
locPath: string;
|
|
||||||
}
|
|
13
src/lang.mts
13
src/lang.mts
|
@ -1,13 +0,0 @@
|
||||||
export function isNil<T>(v: Nilable<T>): v is Nil {
|
|
||||||
return v == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Nullable<T> = T | null;
|
|
||||||
|
|
||||||
export type Nilable<T> = T | Nil;
|
|
||||||
|
|
||||||
export type Nil = null | undefined;
|
|
||||||
|
|
||||||
export function isBool(v: unknown): v is boolean {
|
|
||||||
return typeof v === "boolean";
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function info(...args: unknown[]): void {
|
|
||||||
console.log("[INFO]", ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debug(...args: unknown[]): void {
|
|
||||||
console.log("[DEBUG]", ...args);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { createServer } from "./server.mjs";
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
||||||
function main(): void {
|
|
||||||
createServer();
|
|
||||||
}
|
|
110
src/server.mts
110
src/server.mts
|
@ -1,110 +0,0 @@
|
||||||
import * as http from "http";
|
|
||||||
import * as fs from "fs/promises";
|
|
||||||
import * as path from "path";
|
|
||||||
import { Layout } from "./components/layout.mjs";
|
|
||||||
import { config } from "./config.mjs";
|
|
||||||
import { debug, info } from "./log.mjs";
|
|
||||||
import { StrRenderer } from "ren";
|
|
||||||
import { AboutPage } from "./views/about.mjs";
|
|
||||||
import { E404 } from "./views/e404.mjs";
|
|
||||||
import { WorksPage } from "./views/works.mjs";
|
|
||||||
import { Context } from "./context.mjs";
|
|
||||||
|
|
||||||
export function createServer(): void {
|
|
||||||
const server = http.createServer(handleHttpReq);
|
|
||||||
server.listen(config.server.port, () => {
|
|
||||||
info(
|
|
||||||
"[server]",
|
|
||||||
`Server listening at http://localhost:${config.server.port}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleHttpReq(
|
|
||||||
httpReq: http.IncomingMessage,
|
|
||||||
httpRes: http.ServerResponse
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
const req = tryIntoAppServerRequest(httpReq);
|
|
||||||
|
|
||||||
debug("[server]", { req });
|
|
||||||
|
|
||||||
if (req.url.startsWith("/static")) {
|
|
||||||
const relFilePath = path.join(process.cwd(), req.url.slice(1));
|
|
||||||
const mimeType = mimeTypeByExt.get(path.extname(relFilePath));
|
|
||||||
|
|
||||||
const fileContent = await fs
|
|
||||||
.readFile(relFilePath, { encoding: "utf-8" })
|
|
||||||
.catch((_e) => null);
|
|
||||||
|
|
||||||
if (fileContent && mimeType) {
|
|
||||||
httpRes.writeHead(200, { "content-type": mimeType }).end(fileContent);
|
|
||||||
} else {
|
|
||||||
httpRes.writeHead(404).end("Not found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const ctx: Context = { locPath: req.url };
|
|
||||||
const ren = new StrRenderer();
|
|
||||||
if (/^[/](?:about[/]?)?$/.test(req.url)) {
|
|
||||||
httpRes
|
|
||||||
.writeHead(200, { "content-type": "text/html" })
|
|
||||||
.end(ren.render(Layout(AboutPage(ctx))));
|
|
||||||
} else if (/^[/]works[/]?$/.test(req.url)) {
|
|
||||||
httpRes
|
|
||||||
.writeHead(200, { "content-type": "text/html" })
|
|
||||||
.end(ren.render(Layout(WorksPage(ctx))));
|
|
||||||
} else {
|
|
||||||
httpRes
|
|
||||||
.writeHead(404, { "content-type": "text/html" })
|
|
||||||
.end(ren.render(Layout(E404(ctx))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof InvalidServerRequest) {
|
|
||||||
httpRes.writeHead(400).end("Bad request");
|
|
||||||
} else if (err instanceof UnsupportedRestMethod) {
|
|
||||||
httpRes.writeHead(405).end("Method not allowed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mimeTypeByExt = new Map([[".css", "text/css"]]);
|
|
||||||
|
|
||||||
export function tryIntoAppServerRequest(
|
|
||||||
req: http.IncomingMessage
|
|
||||||
): ServerRequest {
|
|
||||||
if (!req.method || !req.url) throw new InvalidServerRequest();
|
|
||||||
|
|
||||||
return {
|
|
||||||
method: tryIntoAppRestMethod(req.method),
|
|
||||||
url: req.url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerRequest {
|
|
||||||
method: RestMethod;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InvalidServerRequest extends Error {}
|
|
||||||
|
|
||||||
export function tryIntoAppRestMethod(rest: string): RestMethod {
|
|
||||||
switch (rest.toUpperCase()) {
|
|
||||||
case "GET":
|
|
||||||
return RestMethod.Get;
|
|
||||||
default:
|
|
||||||
throw new UnsupportedRestMethod();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum RestMethod {
|
|
||||||
Get = "GET",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnsupportedRestMethod extends Error {}
|
|
||||||
|
|
||||||
export interface ServerResponse {
|
|
||||||
statusCode?: number;
|
|
||||||
headers?: Headers;
|
|
||||||
body: string;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { E, Ea, Et } from "ren";
|
|
||||||
|
|
||||||
export const p = Et.bind(null, "p");
|
|
||||||
export const h2 = Et.bind(null, "h2");
|
|
||||||
export const h3 = Et.bind(null, "h3");
|
|
||||||
|
|
||||||
export const div = Ea.bind(null, "div");
|
|
||||||
export const ul = E.bind(null, "ul");
|
|
||||||
export const li = E.bind(null, "li");
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
|
||||||
import { AnyNode } from "ren";
|
|
||||||
import { div, h3, li, p, ul } from "../utils.mjs";
|
|
||||||
import { Context } from "../context.mjs";
|
|
||||||
|
|
||||||
export function AboutPage(ctx: Context): AnyNode {
|
|
||||||
return PageLayout(
|
|
||||||
ctx,
|
|
||||||
div({ class: "content-width responsive-typography" }, [
|
|
||||||
div({}, [
|
|
||||||
p("Привет!"),
|
|
||||||
p("Меня зовут Дмитрий Плешевский."),
|
|
||||||
p(
|
|
||||||
"Я ведущий разработчик программного обеспечения, архитектор,",
|
|
||||||
"руководитель команды, а так же ментор."
|
|
||||||
),
|
|
||||||
p(
|
|
||||||
"Open-source проекты – моя страсть! Придумываю, экспериментирую,",
|
|
||||||
"воплощаю, улучшаю проекты в свое свободное время"
|
|
||||||
),
|
|
||||||
p(
|
|
||||||
"Помимо программирования я люблю готовить и проводить время со своей",
|
|
||||||
"любимой семьей!"
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
div({}, [
|
|
||||||
h3("Языки программирования"),
|
|
||||||
p("Предпочитаю: Rust, TS"),
|
|
||||||
p("Огромный опыт: Rust, TS, JS, Python"),
|
|
||||||
p("Ограниченный опыт: Haskell, Java, C#"),
|
|
||||||
]),
|
|
||||||
div({}, [
|
|
||||||
h3("Базы данных"),
|
|
||||||
p("Предпочитаю: Postgres"),
|
|
||||||
p("Огромный опыт: Postgres, MySQL, Sqlite, mongo"),
|
|
||||||
]),
|
|
||||||
div({}, [
|
|
||||||
h3("Создание приложений"),
|
|
||||||
ul([
|
|
||||||
li("Традиционное (SSR + Forms)"),
|
|
||||||
li("API (REST/GraphQL/WebSocket/EventSource)"),
|
|
||||||
li("Динамическое (SPA)"),
|
|
||||||
li("Гибридное (SSR + SPA)"),
|
|
||||||
li("Консольные"),
|
|
||||||
li("Кроссплатформенные"),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
|
||||||
import { AnyNode, Ea } from "ren";
|
|
||||||
import { div } from "../utils.mjs";
|
|
||||||
import { Context } from "../context.mjs";
|
|
||||||
|
|
||||||
export function E404(ctx: Context): AnyNode {
|
|
||||||
return PageLayout(
|
|
||||||
ctx,
|
|
||||||
div(
|
|
||||||
{ class: "content-width" },
|
|
||||||
Ea("h3", { class: "font-h3" }, "Страница не найдена")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { PageLayout } from "../components/page_layout.mjs";
|
|
||||||
import { AnyNode, Ea } from "ren";
|
|
||||||
import { div, h3, li, ul } from "../utils.mjs";
|
|
||||||
import { Context } from "../context.mjs";
|
|
||||||
|
|
||||||
export function WorksPage(ctx: Context): AnyNode {
|
|
||||||
return PageLayout(
|
|
||||||
ctx,
|
|
||||||
div({ class: "content-width responsive-typography" }, [
|
|
||||||
h3("Одни из моих последних работ"),
|
|
||||||
ul([
|
|
||||||
li(Ea("a", { href: "https://github.com/pleshevskiy/ren" }, "ren")),
|
|
||||||
li(Ea("a", { href: "https://github.com/pleshevskiy/hwt" }, "hwt")),
|
|
||||||
li(
|
|
||||||
Ea(
|
|
||||||
"a",
|
|
||||||
{ href: "https://github.com/pleshevskiy/sonic-channel" },
|
|
||||||
"sonic-channel"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
li(Ea("a", { href: "https://github.com/pleshevskiy/migra" }, "migra")),
|
|
||||||
li(
|
|
||||||
Ea(
|
|
||||||
"a",
|
|
||||||
{ href: "https://github.com/pleshevskiy/itconfig-rs" },
|
|
||||||
"itconfig-rs"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
:root {
|
|
||||||
--min-width: 320px;
|
|
||||||
--max-width: 1024px;
|
|
||||||
|
|
||||||
--default-color-white: #ffffff;
|
|
||||||
--color-blue: #1966df;
|
|
||||||
|
|
||||||
--default-font-size: 16px;
|
|
||||||
--font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto,Oxygen-Sans, Ubuntu, Cantarell, "Segoe UI", Verdana, sans-serif;
|
|
||||||
--font-weight-regular: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *::before, *::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
background-color: var(--default-color-white);
|
|
||||||
font-size: var(--default-font-size);
|
|
||||||
line-height: 1;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-moz-text-size-adjust: 100%;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-weight: var(--font-weight-regular);
|
|
||||||
font-family: var(--font-family);
|
|
||||||
min-width: var(--min-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, ol {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
min-height: 100vh;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root,
|
|
||||||
#main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main,
|
|
||||||
.content {
|
|
||||||
flex: 1 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-menu {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
// anim
|
|
||||||
.main-menu > a {
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-menu > a {
|
|
||||||
color: var(--color-blue);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid var(--color-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-blue);
|
|
||||||
}
|
|
||||||
.main-menu > a:not(:last-child) {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-width {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--max-width);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
padding-right: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography h3 {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography > div,
|
|
||||||
.responsive-typography p,
|
|
||||||
.responsive-typography li {
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography ul,
|
|
||||||
.responsive-typography ol {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography ul li,
|
|
||||||
.responsive-typography ol li {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
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 {
|
|
||||||
background-color: var(--color-blue);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography > * + * {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-typography > div + div,
|
|
||||||
.responsive-typography p + p {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
.responsive-typography li + li {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "target/scripts",
|
|
||||||
"target": "esnext",
|
|
||||||
"module": "esnext",
|
|
||||||
"lib": ["dom", "esnext"],
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"rootDir": "src",
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noImplicitThis": true,
|
|
||||||
"noImplicitAny": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.mts"
|
|
||||||
],
|
|
||||||
}
|
|
Loading…
Reference in a new issue