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 GithubLogo from 'phosphor-svelte/lib/GithubLogo';
|
||||||
import LinkedinLogo from 'phosphor-svelte/lib/LinkedinLogo';
|
import LinkedinLogo from 'phosphor-svelte/lib/LinkedinLogo';
|
||||||
import MastodonLogo from 'phosphor-svelte/lib/MastodonLogo';
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -22,7 +26,7 @@
|
||||||
<meta property="og:image" content={`https://bgenc.net${JpegFull}`} />
|
<meta property="og:image" content={`https://bgenc.net${JpegFull}`} />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div data-pagefind-body>
|
<section data-pagefind-body>
|
||||||
<picture>
|
<picture>
|
||||||
<source srcset={Avif} type="image/avif" />
|
<source srcset={Avif} type="image/avif" />
|
||||||
<source srcset={WebP} type="image/webp" />
|
<source srcset={WebP} type="image/webp" />
|
||||||
|
@ -95,7 +99,17 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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">
|
<style lang="scss">
|
||||||
@import '../vars';
|
@import '../vars';
|
||||||
|
@ -129,4 +143,9 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.all-posts-button {
|
||||||
|
max-width: 12rem;
|
||||||
|
margin: 2rem auto 0 auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,6 +7,7 @@ tags:
|
||||||
- dev
|
- dev
|
||||||
- web
|
- 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!
|
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
|
The HTML elements `input` and `textarea` include a `placeholder` property. This
|
||||||
|
|
|
@ -8,6 +8,7 @@ tags:
|
||||||
- dev
|
- dev
|
||||||
- react
|
- 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!
|
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
|
I've been building a web app using React and TailwindCSS, with DaisyUI. But
|
||||||
|
|
|
@ -6,6 +6,7 @@ tags:
|
||||||
- svelte
|
- svelte
|
||||||
- typescript
|
- 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.
|
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
|
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'
|
title: 'Using Shadow DOM to isolate injected browser extension components'
|
||||||
date: 2024-05-18T23:08:16-05:00
|
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).'
|
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
|
I recently started experimenting with building a browser extension. I'm using
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import ArrowRight from 'phosphor-svelte/lib/ArrowRight';
|
import ArrowRight from 'phosphor-svelte/lib/ArrowRight';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import type { Post } from './+page.server';
|
import type { Post } from './+page.server';
|
||||||
|
import BlogPostList from './BlogPostList.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
|
@ -26,35 +27,14 @@
|
||||||
<title>Kaan Barmore-Genç's Blog Posts</title>
|
<title>Kaan Barmore-Genç's Blog Posts</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<ul class="years">
|
<ol class="years">
|
||||||
{#each groupPosts(data.posts).entries() as [year, posts]}
|
{#each groupPosts(data.posts).entries() as [year, posts]}
|
||||||
<li>
|
<li>
|
||||||
<h2>{year}</h2>
|
<h2>{year}</h2>
|
||||||
<ol class="posts">
|
<BlogPostList {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>
|
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ol>
|
||||||
|
|
||||||
<div class="pagination-controls">
|
<div class="pagination-controls">
|
||||||
<Button disabled={data.page < 3} href={'/posts/'}
|
<Button disabled={data.page < 3} href={'/posts/'}
|
||||||
|
@ -75,18 +55,6 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../../vars';
|
@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 {
|
.pagination-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -102,43 +70,16 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 0.5rem 1rem;
|
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 {
|
li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
.years,
|
.years {
|
||||||
.posts {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.years {
|
.years {
|
||||||
gap: 4rem;
|
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>
|
</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