Add highlighted posts to the home page
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
6c50ff1b84
commit
e26a954193
41
src/routes/+page.server.ts
Normal file
41
src/routes/+page.server.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { readingTime } from 'reading-time-estimator';
|
||||
import path from 'path';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { slugify } from '$lib/slugify.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
export async function load() {
|
||||
const allPostFiles = import.meta.glob<SvelteAllProps>('/src/routes/posts/*.md');
|
||||
const iterablePostFiles = Object.entries(allPostFiles);
|
||||
|
||||
const markdownFilesPath = path.resolve(
|
||||
process.env.npm_package_json ? path.dirname(process.env.npm_package_json) : '.',
|
||||
'src',
|
||||
'routes',
|
||||
'posts',
|
||||
);
|
||||
|
||||
const posts = await Promise.all(
|
||||
iterablePostFiles.map(async ([filePath, resolver]) => {
|
||||
const { metadata } = await resolver();
|
||||
|
||||
const slug = slugify(filePath);
|
||||
const contents = await readFile(path.join(markdownFilesPath, `${slug}.md`), 'utf8');
|
||||
|
||||
return {
|
||||
meta: metadata,
|
||||
path: slug,
|
||||
readingTime: readingTime(contents),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const highlightedPosts = _.reverse(
|
||||
_.sortBy(
|
||||
posts.filter((post) => post.meta.highlighted),
|
||||
({ meta }) => meta.date,
|
||||
),
|
||||
);
|
||||
|
||||
return { highlightedPosts };
|
||||
}
|
|
@ -10,6 +10,10 @@
|
|||
import GithubLogo from 'phosphor-svelte/lib/GithubLogo';
|
||||
import LinkedinLogo from 'phosphor-svelte/lib/LinkedinLogo';
|
||||
import MastodonLogo from 'phosphor-svelte/lib/MastodonLogo';
|
||||
import BlogPostList from './posts/[[page=integer]]/BlogPostList.svelte';
|
||||
import Button from '$lib/Button.svelte';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
@ -22,7 +26,7 @@
|
|||
<meta property="og:image" content={`https://bgenc.net${JpegFull}`} />
|
||||
</svelte:head>
|
||||
|
||||
<div data-pagefind-body>
|
||||
<section data-pagefind-body>
|
||||
<picture>
|
||||
<source srcset={Avif} type="image/avif" />
|
||||
<source srcset={WebP} type="image/webp" />
|
||||
|
@ -95,7 +99,17 @@
|
|||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if data.highlightedPosts.length > 0}
|
||||
<section>
|
||||
<h2>Highlighted Blog Posts</h2>
|
||||
<BlogPostList showYear showReadingTime={false} posts={data.highlightedPosts} />
|
||||
<div class="all-posts-button">
|
||||
<Button href="/posts/">All Posts</Button>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@import '../vars';
|
||||
|
@ -129,4 +143,9 @@
|
|||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.all-posts-button {
|
||||
max-width: 12rem;
|
||||
margin: 2rem auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,7 @@ tags:
|
|||
- dev
|
||||
- web
|
||||
description: Here is how to make a text area that resizes automatically and has placeholder text, with no JavaScript with this clever CSS trick!
|
||||
highlighted: true
|
||||
---
|
||||
|
||||
The HTML elements `input` and `textarea` include a `placeholder` property. This
|
||||
|
|
|
@ -8,6 +8,7 @@ tags:
|
|||
- dev
|
||||
- react
|
||||
description: This post shows you how to use JavaScript to read CSS variables and create dynamic UIs, using SWR for seamless light & dark mode switching!
|
||||
highlighted: true
|
||||
---
|
||||
|
||||
I've been building a web app using React and TailwindCSS, with DaisyUI. But
|
||||
|
|
|
@ -6,6 +6,7 @@ tags:
|
|||
- svelte
|
||||
- typescript
|
||||
description: Making customizable components in Svelte can be hard, but check out this post on how to use Svelte with TailwindCSS to make them possible at no performance penalty.
|
||||
highlighted: true
|
||||
---
|
||||
|
||||
I really like [Svelte](https://svelte.dev), but it has one big limitation
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
title: 'Using Shadow DOM to isolate injected browser extension components'
|
||||
date: 2024-05-18T23:08:16-05:00
|
||||
description: 'I spent a weekend building a browser extension with React and TailwindCSS, but injected components can be a lot of trouble. This post explores how Shadow DOM saves the day (and your CSS sanity).'
|
||||
highlighted: true
|
||||
---
|
||||
|
||||
I recently started experimenting with building a browser extension. I'm using
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import ArrowRight from 'phosphor-svelte/lib/ArrowRight';
|
||||
import { format } from 'date-fns';
|
||||
import type { Post } from './+page.server';
|
||||
import BlogPostList from './BlogPostList.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
|
@ -26,35 +27,14 @@
|
|||
<title>Kaan Barmore-Genç's Blog Posts</title>
|
||||
</svelte:head>
|
||||
|
||||
<ul class="years">
|
||||
<ol class="years">
|
||||
{#each groupPosts(data.posts).entries() as [year, posts]}
|
||||
<li>
|
||||
<h2>{year}</h2>
|
||||
<ol class="posts">
|
||||
{#each posts as post}
|
||||
<a href={`/${post.path}`}>
|
||||
<li>
|
||||
<div>
|
||||
<h5 class="post-title">
|
||||
{post.meta.title}
|
||||
</h5>
|
||||
{#if post.meta.description}
|
||||
<p class="description">{post.meta.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="meta">
|
||||
<time datetime={post.meta.date}>
|
||||
{format(new Date(post.meta.date), 'MMMM d')}
|
||||
</time>
|
||||
<div>About {post.readingTime.text}</div>
|
||||
</div>
|
||||
</li>
|
||||
</a>
|
||||
{/each}
|
||||
</ol>
|
||||
<BlogPostList {posts} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</ol>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<Button disabled={data.page < 3} href={'/posts/'}
|
||||
|
@ -75,18 +55,6 @@
|
|||
<style lang="scss">
|
||||
@import '../../../vars';
|
||||
|
||||
/* Restore the color of everything under the post links, except the titles */
|
||||
.posts a {
|
||||
color: $color-text;
|
||||
}
|
||||
.posts a .post-title {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
time {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -102,43 +70,16 @@
|
|||
user-select: none;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
.post-title {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
transition: all var(--animation-speed) var(--animation-type);
|
||||
}
|
||||
a:hover .post-title {
|
||||
text-decoration-color: unset;
|
||||
}
|
||||
.posts a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.years,
|
||||
.posts {
|
||||
.years {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.years {
|
||||
gap: 4rem;
|
||||
}
|
||||
.posts {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.9rem;
|
||||
margin-top: -1.4rem;
|
||||
}
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
|
|
87
src/routes/posts/[[page=integer]]/BlogPostList.svelte
Normal file
87
src/routes/posts/[[page=integer]]/BlogPostList.svelte
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script lang="ts">
|
||||
import { format } from 'date-fns';
|
||||
import type { Post } from './+page.server';
|
||||
import { readingTime } from 'reading-time-estimator';
|
||||
|
||||
export let showYear: boolean = false;
|
||||
export let showReadingTime: boolean = true;
|
||||
export let posts: Post[] = [];
|
||||
</script>
|
||||
|
||||
<ol class="posts">
|
||||
{#each posts as post}
|
||||
<a href={`/${post.path}`}>
|
||||
<li>
|
||||
<div>
|
||||
<h5 class="post-title">
|
||||
{post.meta.title}
|
||||
</h5>
|
||||
{#if post.meta.description}
|
||||
<p class="description">{post.meta.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="meta">
|
||||
<time datetime={post.meta.date}>
|
||||
{format(new Date(post.meta.date), showYear ? 'MMMM d, yyyy' : 'MMMM d')}
|
||||
</time>
|
||||
{#if showReadingTime}
|
||||
<div>{post.readingTime.words} words, {post.readingTime.text}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
</a>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../../vars';
|
||||
|
||||
/* Restore the color of everything under the post links, except the titles */
|
||||
.posts a {
|
||||
color: $color-text;
|
||||
}
|
||||
.posts a .post-title {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
time {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
transition: all var(--animation-speed) var(--animation-type);
|
||||
}
|
||||
a:hover .post-title {
|
||||
text-decoration-color: unset;
|
||||
}
|
||||
.posts a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.posts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.posts {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.9rem;
|
||||
margin-top: -1.4rem;
|
||||
}
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue