Compare commits
5 commits
e5b08e772a
...
0af9af9157
Author | SHA1 | Date | |
---|---|---|---|
0af9af9157 | |||
aab08105c9 | |||
fef04a8829 | |||
be5d01a844 | |||
562caea0ff |
16 changed files with 618 additions and 481 deletions
|
@ -30,4 +30,7 @@ export default defineConfig({
|
|||
rehypeFigure,
|
||||
],
|
||||
},
|
||||
redirects: {
|
||||
"/blog": "/blog/page1",
|
||||
}
|
||||
});
|
675
package-lock.json
generated
675
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^2.3.0",
|
||||
"astro": "^2.9.0",
|
||||
"astro-compress": "^1.1.42",
|
||||
"less": "^4.1.3",
|
||||
"reading-time": "^1.5.0",
|
||||
|
|
127
src/components/GridItem.astro
Normal file
127
src/components/GridItem.astro
Normal file
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
text: string;
|
||||
url: string;
|
||||
elemClass?: Array<String>;
|
||||
btnClass?: Array<string>;
|
||||
dataJs?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title, text, url,
|
||||
elemClass = [],
|
||||
btnClass = [],
|
||||
dataJs = "",
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class:list={["gi", ...elemClass]}>
|
||||
<a href={url} target="_blank" class="gi-main">
|
||||
<span class="icon-wrapper">
|
||||
<slot name="icon-primary" />
|
||||
</span>
|
||||
<span class="content">
|
||||
<span class="title">
|
||||
<slot name="title">
|
||||
{title}
|
||||
</slot>
|
||||
</span>
|
||||
<span class="text">
|
||||
<slot name="text">
|
||||
{text}
|
||||
</slot>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="javascript:void(0)" class:list={["gi-btn", ...btnClass]} data-js={dataJs}>
|
||||
<span class="icon-wrapper">
|
||||
<slot name="icon-btn" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
@import "/src/styles/grid_calc.less";
|
||||
|
||||
.gi {
|
||||
// vertical horizontal
|
||||
padding: 0.4rem @gi-horiz-padding;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
width: @gi-width;
|
||||
min-height: 3rem;
|
||||
border-radius: var(--bdrs);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
}
|
||||
|
||||
a.gi-main {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
|
||||
.icon-wrapper {
|
||||
width: @gi-icon-primary;
|
||||
font-size: @gi-icon-primary;
|
||||
margin-right: @gi-icon-margin;
|
||||
|
||||
& > :global(img) {
|
||||
width: @gi-icon-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.gi-btn {
|
||||
position: relative;
|
||||
font-size: 1.1rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrapper {
|
||||
width: @gi-icon-btn;
|
||||
font-size: @gi-icon-btn;
|
||||
margin-left: @gi-icon-margin;
|
||||
|
||||
& > :global(img) {
|
||||
width: @gi-icon-btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: @gi-text-width;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--fg-sec);
|
||||
}
|
||||
}
|
||||
</style>
|
34
src/components/HostedService.astro
Normal file
34
src/components/HostedService.astro
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
import GridItem from "./GridItem.astro";
|
||||
|
||||
export interface Props {
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
iconUrl?: string;
|
||||
}
|
||||
|
||||
const { name, description, url, iconUrl = `${url}/favicon.ico` } = Astro.props;
|
||||
---
|
||||
|
||||
<GridItem
|
||||
title={name}
|
||||
text={description}
|
||||
url={url}
|
||||
elemClass={["gi-service"]}
|
||||
>
|
||||
<img src={iconUrl} slot="icon-primary" />
|
||||
</GridItem>
|
||||
|
||||
<style lang="less" is:global>
|
||||
@import "/src/styles/grid_calc.less";
|
||||
|
||||
// Overwriting default GridItem's width
|
||||
.gi-service {
|
||||
width: @service-width;
|
||||
|
||||
.content {
|
||||
width: @service-text-width;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import GridItem from './GridItem.astro';
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
export interface Props {
|
||||
|
@ -12,28 +13,21 @@ export interface Props {
|
|||
const { app, url, name, icon, copyName = false } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="profile">
|
||||
<a href={url} target="_blank" class="profile-main">
|
||||
<span class="icon-wrapper">
|
||||
<Icon code={icon} />
|
||||
</span>
|
||||
<span class="content">
|
||||
<span class="app">{app}</span>
|
||||
<span class="nickname">{name}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="javascript:void(0)" class="copy" data-url={copyName ? name : url}>
|
||||
<span class="icon-wrapper">
|
||||
<Icon code="" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<GridItem
|
||||
title={app} text={name} url={url}
|
||||
elemClass={["gi-profile"]}
|
||||
btnClass={["profile-copy"]}
|
||||
dataJs={copyName ? name : url}
|
||||
>
|
||||
<Icon code={icon} slot="icon-primary" />
|
||||
<Icon code="" slot="icon-btn" />
|
||||
</GridItem>
|
||||
|
||||
<script>
|
||||
const copy = document.querySelectorAll('.copy')
|
||||
const copy = document.querySelectorAll('.profile-copy')
|
||||
const copyClickHandler = (ev: Event) => {
|
||||
const elem = ev.currentTarget as HTMLElement
|
||||
const link = elem.dataset.url
|
||||
const link = elem.dataset.js
|
||||
if (!link) return
|
||||
navigator.clipboard.writeText(link)
|
||||
}
|
||||
|
@ -42,70 +36,14 @@ const { app, url, name, icon, copyName = false } = Astro.props;
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "/src/styles/profile_calc.less";
|
||||
|
||||
.profile {
|
||||
// vertical horizontal
|
||||
padding: 0.4rem @profile-horiz-padding;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
<style lang="less" is:global>
|
||||
@import "/src/styles/grid_calc.less";
|
||||
|
||||
.gi-profile {
|
||||
width: @profile-width;
|
||||
border-radius: var(--bdrs);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
}
|
||||
|
||||
a.profile-main {
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
|
||||
.icon-wrapper {
|
||||
font-size: @profile-icon-app;
|
||||
margin-right: @profile-icon-margin;
|
||||
}
|
||||
}
|
||||
|
||||
a.copy {
|
||||
|
||||
position: relative;
|
||||
font-size: 1.1rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrapper {
|
||||
font-size: @profile-icon-copy;
|
||||
margin-left: @profile-icon-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: @profile-text-width;
|
||||
overflow: hidden;
|
||||
|
||||
.app {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 0.9rem;
|
||||
color: var(--fg-sec);
|
||||
.content {
|
||||
width: @profile-text-width;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -247,6 +247,11 @@ import TextBox from "./TextBox.astro";
|
|||
}
|
||||
|
||||
function reloadTheme() {
|
||||
if (!lessLoaded) {
|
||||
setTimeout(reloadTheme, 150)
|
||||
lessLoaded = true
|
||||
return
|
||||
}
|
||||
const accent = '#' + accentOpt.value
|
||||
const bdrs = bdrsOpt.value
|
||||
const purebg = purebgOpt.value
|
||||
|
|
13
src/layouts/Grid.astro
Normal file
13
src/layouts/Grid.astro
Normal file
|
@ -0,0 +1,13 @@
|
|||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
<style lang="less">
|
||||
section {
|
||||
margin: 0.4rem 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -48,16 +48,18 @@ const { title, description } = Astro.props;
|
|||
margin: auto;
|
||||
padding: @main-padding;
|
||||
max-width: @max-width;
|
||||
/*
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
*/
|
||||
}
|
||||
|
||||
article {
|
||||
max-width: 100vw;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
|
||||
&.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
|
|
|
@ -15,15 +15,11 @@ export async function getStaticPaths() {
|
|||
}));
|
||||
}
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { post } = Astro.props;
|
||||
|
||||
const {
|
||||
PostContent, title, description,
|
||||
readingTime, date, image,
|
||||
} = await postRenderer(post);
|
||||
|
||||
const locale = 'ru-RU';
|
||||
---
|
||||
|
||||
<BlogLayout title={title} description={description} article={true}>
|
||||
|
|
|
@ -4,8 +4,22 @@ import ArticleCard from "../../components/ArticleCard.astro";
|
|||
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
const posts = await getCollection('blog');
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
const POSTS_ON_PAGE = 15;
|
||||
|
||||
const posts = (await getCollection('blog'))
|
||||
.sort( // sort by slug (which includes date), desc
|
||||
(a, b) => (a.slug < b.slug) ? 1 : -1
|
||||
)
|
||||
.map( // convert to objects
|
||||
p => { return { post: p } }
|
||||
);
|
||||
|
||||
// use Astro's function
|
||||
return paginate(posts, { pageSize: POSTS_ON_PAGE });
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const description =
|
||||
"DarkCat09's blog. " +
|
||||
"Reading a QR code without a phone, " +
|
||||
|
@ -15,8 +29,11 @@ const description =
|
|||
---
|
||||
|
||||
<BlogLayout title="Homepage" description={description}>
|
||||
<Fragment slot="metadata">
|
||||
<meta name="robots" content="noindex, follow" />
|
||||
</Fragment>
|
||||
<div id="articles">
|
||||
{posts.map(post => <ArticleCard post={post} />)}
|
||||
{page.data.map(({ post }) => <ArticleCard post={post} />)}
|
||||
</div>
|
||||
</BlogLayout>
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
---
|
||||
import MainLayout from '../layouts/MainLayout.astro';
|
||||
import Grid from '../layouts/Grid.astro';
|
||||
import Profile from '../components/Profile.astro';
|
||||
import ListItem from '../components/ListItem.astro';
|
||||
|
||||
const description =
|
||||
"Hi! I'm Andrew aka DarkCat09, " +
|
||||
"a 13-years-old fullstack developer from Russia";
|
||||
"a 14-years-old fullstack developer from Russia";
|
||||
---
|
||||
|
||||
<MainLayout page="Homepage" description={description}>
|
||||
<article class="centered">
|
||||
<hgroup>
|
||||
<h1>Hi! I'm Andrew.</h1>
|
||||
<h2><span id="age">13</span>-years-old fullstack dev</h2>
|
||||
<h2><span id="age">14</span>-years-old fullstack dev</h2>
|
||||
</hgroup>
|
||||
<section id="profiles">
|
||||
<Grid>
|
||||
<Profile app="Gitea" icon="" name="DarkCat09" url="https://git.dc09.ru/DarkCat09" />
|
||||
<Profile app="GitHub" icon="" name="DarkCat09" url="https://github.com/DarkCat09" />
|
||||
<Profile app="E-mail" icon="" name="darkcat09@vivaldi.net" url="mailto:darkcat09@vivaldi.net" copyName />
|
||||
<Profile app="Telegram" icon="" name="@darkcat09" url="https://t.me/darkcat09" />
|
||||
<Profile app="Matrix" icon="" name="darkcat09:dc09.ru" url="https://matrix.to/#/@darkcat09:dc09.ru" copyName />
|
||||
<Profile app="Discord" icon="" name="DarkCat09#5587" url="https://discord.com/app" copyName />
|
||||
</section>
|
||||
</Grid>
|
||||
</article>
|
||||
<article class="card card-dashed" id="about">
|
||||
<ul>
|
||||
|
@ -47,7 +48,7 @@ const description =
|
|||
</ul>
|
||||
</ListItem>
|
||||
<ListItem icon="">
|
||||
Favorite OS: Manjaro Linux
|
||||
Favorite OS: Arch Linux
|
||||
</ListItem>
|
||||
<ListItem icon="">
|
||||
<code>
|
||||
|
@ -61,21 +62,6 @@ const description =
|
|||
</MainLayout>
|
||||
|
||||
<style lang="less">
|
||||
@import "/src/styles/profile_calc.less";
|
||||
|
||||
#profiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, auto);
|
||||
|
||||
@media (min-width: @profile-width * 2) {
|
||||
grid-template-columns: repeat(2, auto);
|
||||
}
|
||||
|
||||
@media (min-width: @profile-width * 3) {
|
||||
grid-template-columns: repeat(3, auto);
|
||||
}
|
||||
}
|
||||
|
||||
#about > ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
@ -95,9 +81,14 @@ const description =
|
|||
// in years
|
||||
let age = ageMs / (1000 * 60 * 60 * 24 * 365.25)
|
||||
|
||||
// fallback to default value
|
||||
// + rounding
|
||||
age = (age < 13 ? 13 : Math.floor(age))
|
||||
// if age < 14, date on user's PC is incorrect,
|
||||
// so it's better to not update age.
|
||||
|
||||
document.getElementById('age').innerText = String(age)
|
||||
// otherwise, update:
|
||||
if (age >= 14) {
|
||||
// rounding
|
||||
age = Math.floor(age)
|
||||
|
||||
document.getElementById('age').innerText = String(age)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
import MainLayout from "../layouts/MainLayout.astro";
|
||||
import Grid from "../layouts/Grid.astro";
|
||||
import HostedService from "../components/HostedService.astro";
|
||||
|
||||
const description =
|
||||
"Status and description of services " +
|
||||
|
@ -7,5 +9,14 @@ const description =
|
|||
---
|
||||
|
||||
<MainLayout page="Services" description={description}>
|
||||
// soon
|
||||
<Grid>
|
||||
<HostedService name="SearXNG" description="Privacy-respecting hackable metasearch engine" url="https://searx.dc09.ru" />
|
||||
<HostedService name="Gitea" description="Painless self-hosted software development service" url="https://git.dc09.ru" />
|
||||
<HostedService name="Piped" description="Privacy-friendly alternative YouTube frontend, API and proxy" url="https://yt.dc09.ru" />
|
||||
<HostedService name="Invidious" description="An open source alternative frontend to YouTube" url="https://inv.dc09.ru" />
|
||||
<HostedService name="Shlink" description="The definitive self-hosted URL shortener written in PHP" url="https://url.dc09.ru" />
|
||||
<HostedService name="SFTPGo" description="Fully featured and highly configurable SFTP server" url="https://cloud.dc09.ru" iconUrl="https://cloud.dc09.ru/static/favicon.ico" />
|
||||
<HostedService name="Element" description="A glossy Matrix collaboration client for the web" url="https://elem.dc09.ru" iconUrl="https://elem.dc09.ru/themes/element/img/logos/element-logo.svg" />
|
||||
<HostedService name="Cinny" description="A matrix client with simple, elegant and secure interface" url="https://cinny.dc09.ru" iconUrl="https://cinny.dc09.ru/assets/favicon-e5e25737.ico" />
|
||||
</Grid>
|
||||
</MainLayout>
|
||||
|
|
45
src/styles/grid_calc.less
Normal file
45
src/styles/grid_calc.less
Normal file
|
@ -0,0 +1,45 @@
|
|||
@import "/src/styles/max_width.less";
|
||||
|
||||
@gi-icon-primary: 2.3rem;
|
||||
@gi-icon-btn: 1.8rem;
|
||||
|
||||
@gi-text-width: 9.5rem;
|
||||
@profile-text-width: 9rem;
|
||||
@service-text-width: 14rem;
|
||||
|
||||
@gi-icon-margin: 0.4rem;
|
||||
@gi-horiz-padding: 0.6rem;
|
||||
|
||||
// GridItem width without text
|
||||
@gi-width-wo-text: @gi-horiz-padding + @gi-icon-primary + @gi-icon-margin + @gi-icon-margin + @gi-icon-btn + @gi-horiz-padding;
|
||||
|
||||
@gi-width: @gi-width-wo-text + @gi-text-width;
|
||||
@profile-width: @gi-width-wo-text + @profile-text-width;
|
||||
@service-width: @gi-width-wo-text + @service-text-width;
|
||||
|
||||
.grid-mixin(@custom-width) {
|
||||
/*
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, auto);
|
||||
|
||||
@width-for-2: @custom-width * 2 + @all-padding;
|
||||
@width-for-3: @custom-width * 3 + @all-padding;
|
||||
|
||||
& when (@width-for-2 < @max-width) {
|
||||
@media (min-width: @width-for-2) {
|
||||
grid-template-columns: repeat(2, auto);
|
||||
}
|
||||
}
|
||||
|
||||
& when (@width-for-3 < @max-width) {
|
||||
@media (min-width: @width-for-3) {
|
||||
grid-template-columns: repeat(3, auto);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
@max-width: 800px;
|
||||
@max-width: 50rem;
|
||||
@body-padding: 0.5rem;
|
||||
@main-padding: 0.25rem;
|
||||
@all-padding: @body-padding * 2 + @main-padding * 2;
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
@profile-icon-app: 2.3rem;
|
||||
@profile-icon-copy: 1.8rem;
|
||||
|
||||
@profile-text-width: 9.5rem;
|
||||
|
||||
@profile-icon-margin: 0.4rem;
|
||||
@profile-horiz-padding: 0.6rem;
|
||||
|
||||
@profile-width: @profile-horiz-padding + @profile-icon-app + @profile-icon-margin + @profile-text-width + @profile-icon-margin + @profile-icon-copy + @profile-horiz-padding;
|
Loading…
Add table
Reference in a new issue