Post rendering -> module; articles list on /blog; enhanced MenuItem

This commit is contained in:
DarkCat09 2023-06-22 22:02:24 +04:00
parent f325cebda0
commit 994a41f9e3
6 changed files with 252 additions and 66 deletions

View file

@ -0,0 +1,44 @@
---
import { AstroComponentFactory } from 'astro/dist/runtime/server';
export interface Props {
PostContent: AstroComponentFactory;
title: string;
description: string;
readingTime: number;
date: Date;
image: string;
preview?: boolean;
}
const {
PostContent, title, description,
readingTime, date, image,
preview = false,
} = Astro.props;
const locale = 'ru';
---
<header>
<h1>{title}</h1>
<address>
on
<time datetime={date?.toISOString()}>{date?.toLocaleDateString(locale)}</time>
by
<a href="https://t.me/dcat09" rel="author">DarkCat09</a>
//
<span id="reading-time">
{readingTime.toFixed(1)} min read
</span>
</address>
</header>
{
!preview ?
<article>
<PostContent />
</article> :
<div>
{description}
</div>
}

View file

@ -0,0 +1,113 @@
---
import Article from './Article.astro';
import MenuItem from './MenuItem.astro';
import postRenderer from '../post-renderer.mjs';
import { postType } from '../post-renderer.mjs';
export interface Props {
post: postType;
}
const { post } = Astro.props;
const {
PostContent, title, description,
readingTime, date, image,
} = await postRenderer(post);
const link = `/blog/${post.slug}`;
---
<div class="card card-elevated" id={post.slug}>
<Article
PostContent={PostContent}
title={title}
description={description}
readingTime={readingTime}
date={date}
image={image}
preview={true}
/>
<ul>
<MenuItem
name={/*Comments*/""}
link={`/blog/comments#${post.slug}`}
icon="&#xf075;"
atLeft={false}
/>
<MenuItem
name={/*Copy Link*/""}
link="javascript:void(0)"
icon="&#xf0c5;"
atLeft={false}>
<span slot="js-dataset"
data-btn="copylink"
data-url={link}>
</span>
</MenuItem>
<MenuItem
name={/*Share*/""}
link="javascript:void(0)"
icon="&#xf064;"
atLeft={false}>
<span slot="js-dataset"
data-btn="share"
data-title={title}
data-url={link}>
</span>
</MenuItem>
<MenuItem
name="Read"
link={link}
icon="&#xf105;"
atLeft={false}
/>
</ul>
</div>
<style>
.card {
width: 100%;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
justify-content: end;
}
</style>
<script>
const onclickCopy = (ev: Event) => {
const li = ev.currentTarget as HTMLLIElement
const js = li.querySelector('[data-btn]') as HTMLElement
navigator.clipboard.writeText(
location.origin + js.dataset.url
)
}
const onclickShare = (ev: Event) => {
const li = ev.currentTarget as HTMLLIElement
const js = li.querySelector('[data-btn]') as HTMLElement
const data = js.dataset
navigator.share({
title: "Share the article",
text: data.title,
url: data.url,
})
}
const btnsCopy = document.querySelectorAll('[data-btn="copylink"]')
const btnsShare = document.querySelectorAll('[data-btn="share"]')
for (let el of btnsCopy) {
(el.parentElement as HTMLLIElement).addEventListener('click', onclickCopy)
}
for (let el of btnsShare) {
(el.parentElement as HTMLLIElement).addEventListener('click', onclickShare)
}
</script>

View file

@ -5,35 +5,42 @@ export interface Props {
name: string; name: string;
link: string; link: string;
icon: string; icon: string;
atLeft?: boolean;
} }
const { name, link, icon } = Astro.props; const { name, link, icon, atLeft = true } = Astro.props;
--- ---
<li> <li>
<slot name="js-dataset"></slot>
<a href={link}> <a href={link}>
<span class="icon-wrapper"> {!atLeft ? name : ""}
<span class:list={["icon-wrapper", {"highlight-icon": name == ""}]}>
<Icon code={icon} /> <Icon code={icon} />
</span> </span>
{name} {atLeft ? name : ""}
</a> </a>
</li> </li>
<style lang="less"> <style lang="less">
li {
font-size: 1.15rem;
}
a { a {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
font-size: 1.15rem;
} }
.icon-wrapper { .icon-wrapper {
margin-right: 0.2rem; margin-right: 0.2rem;
font-size: 1.5rem;
width: 1.5rem; width: 1.5rem;
font-size: 1.5rem;
color: var(--fg-sec); color: var(--fg-sec);
&.highlight-icon:hover {
color: var(--fg);
}
} }
</style> </style>

View file

@ -1,24 +1,9 @@
--- ---
import BlogLayout from "../../layouts/BlogLayout.astro"; import BlogLayout from "../../layouts/BlogLayout.astro";
import Article from "../../components/Article.astro";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import postRenderer from "../../post-renderer.mjs";
import { AstroComponentFactory } from "astro/dist/runtime/server";
import { MarkdownHeading } from "astro";
type postType = {
id: string;
slug: string;
body: string;
collection: "blog";
data: any;
} & {
render(): Promise<{
Content: AstroComponentFactory;
headings: MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
};
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection("blog"); const posts = await getCollection("blog");
@ -31,29 +16,14 @@ export async function getStaticPaths() {
} }
const { slug } = Astro.params; const { slug } = Astro.params;
const post: postType = Astro.props.post; const { post } = Astro.props;
const postObj = await post.render(); const {
const fm = postObj.remarkPluginFrontmatter; PostContent, title, description,
readingTime, date, image,
} = await postRenderer(post);
const title = postObj.headings[0]?.text || ''; const locale = 'ru-RU';
const description = fm.description;
const readingTime = fm.readingTime;
let dateStr = slug.split("-", 1)[0];
let date: Date | undefined;
if (isNaN(Number(dateStr))) {
dateStr = undefined;
}
else {
date = new Date(
`${dateStr.slice(0,4)}-` +
`${dateStr.slice(4,6)}-` +
`${dateStr.slice(6,8)}`
)
}
const image = (post.body.match(/(?:\s|^)!\[.*\]\((.*?)\)/) || [])[1];
--- ---
<BlogLayout title={title} description={description}> <BlogLayout title={title} description={description}>
@ -69,28 +39,18 @@ const image = (post.body.match(/(?:\s|^)!\[.*\]\((.*?)\)/) || [])[1];
<meta property="twitter:description" content={description} /> <meta property="twitter:description" content={description} />
<meta property="twitter:image" content={image} /> <meta property="twitter:image" content={image} />
</Fragment> </Fragment>
<header> <Article
<h1>{title}</h1> PostContent={PostContent}
<address> title={title}
on description={description}
<time datetime={date?.toISOString()}>{date?.toLocaleDateString()}</time> readingTime={readingTime}
by date={date}
<a href="https://t.me/dcat09" rel="author">DarkCat09</a> image={image}
// />
<span id="reading-time">
{readingTime}
</span>
</address>
</header>
<article>
<postObj.Content />
</article>
</BlogLayout> </BlogLayout>
<style lang="less" is:global> <style lang="less" is:global>
article { article {
//padding: 0 0.75rem;
& > h1:first-of-type { & > h1:first-of-type {
display: none; display: none;
} }

View file

@ -1,14 +1,30 @@
--- ---
import BlogLayout from "../../layouts/BlogLayout.astro"; import BlogLayout from "../../layouts/BlogLayout.astro";
import ArticleCard from "../../components/ArticleCard.astro";
import { getCollection } from "astro:content";
const posts = await getCollection('blog');
const description = const description =
"DarkCat09's blog. " + "DarkCat09's blog. " +
"Reading a QR code without a phone, " + "Reading a QR code without a phone, " +
"breaking a password on Windows, " + "breaking Windows password, " +
"coding your own browser... " + "coding your own browser... " +
"The simpliest things!"; "The simpliest things!";
--- ---
<BlogLayout title="Homepage" description={description}> <BlogLayout title="Homepage" description={description}>
// soon <div id="articles">
{posts.map(post => <ArticleCard post={post} />)}
</div>
</BlogLayout> </BlogLayout>
<style lang="less">
#articles {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.8rem;
}
</style>

46
src/post-renderer.mts Normal file
View file

@ -0,0 +1,46 @@
import { AstroComponentFactory } from "astro/dist/runtime/server"
import { MarkdownHeading } from "astro"
export type postType = {
id: string
slug: string
body: string
collection: "blog"
data: any
} & {
render(): Promise<{
Content: AstroComponentFactory
headings: MarkdownHeading[]
remarkPluginFrontmatter: Record<string, any>
}>
}
export default async function postRenderer(post: postType) {
const postObj = await post.render()
const fm = postObj.remarkPluginFrontmatter
const title = postObj.headings[0]?.text || ''
const description: string = fm.description
const readingTime: number = fm.readingTime
const dateStr = post.slug.split("-", 1)[0]
let date: Date | undefined
if (!isNaN(Number(dateStr))) {
date = new Date(
`${dateStr.slice(0,4)}-` +
`${dateStr.slice(4,6)}-` +
`${dateStr.slice(6,8)}`
)
}
const image = (post.body.match(/(?:\s|^)!\[.*\]\((.*?)\)/) || [])[1]
return {
PostContent: postObj.Content,
title: title,
description: description,
readingTime: readingTime,
date: date,
image: image,
}
}