Implement pagination, update the logo again
This commit is contained in:
parent
cf751dd397
commit
5e8eb94d6b
.prettierrc
src
static
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "all",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
|
16
src/app.css
16
src/app.css
|
@ -1,9 +1,9 @@
|
||||||
html {
|
html {
|
||||||
--color-primary-h: 49;
|
--color-primary-h: 218;
|
||||||
--color-primary-s: 79%;
|
--color-primary-s: 81%;
|
||||||
--color-primary-l: 53%;
|
--color-primary-l: 24%;
|
||||||
--color-primary: hsl(var(--color-primary-h), var(--color-primary-s), var(--color-primary-l));
|
--color-primary: hsl(var(--color-primary-h), var(--color-primary-s), var(--color-primary-l));
|
||||||
--color-secondary-h: 218;
|
--color-secondary-h: 100;
|
||||||
--color-secondary-s: 81%;
|
--color-secondary-s: 81%;
|
||||||
--color-secondary-l: 24%;
|
--color-secondary-l: 24%;
|
||||||
--color-secondary: hsl(
|
--color-secondary: hsl(
|
||||||
|
@ -59,6 +59,9 @@ html {
|
||||||
|
|
||||||
--z-index-modal: 100;
|
--z-index-modal: 100;
|
||||||
|
|
||||||
|
--clip-path: polygon(0% 0%, 75% 0%, 100% 100%, 25% 100%);
|
||||||
|
--clip-path-button: polygon(0px 0px, calc(100% - 10px) 0px, 100% 40px, 10px 40px);
|
||||||
|
|
||||||
background-color: var(--color-bg);
|
background-color: var(--color-bg);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
@ -104,6 +107,11 @@ svg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
input,
|
input,
|
||||||
button,
|
button,
|
||||||
textarea,
|
textarea,
|
||||||
|
|
37
src/lib/Button.svelte
Normal file
37
src/lib/Button.svelte
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let disabled = false;
|
||||||
|
export let href: string | undefined;
|
||||||
|
let className = '';
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if disabled}
|
||||||
|
<button class={className} disabled><slot /></button>
|
||||||
|
{:else}
|
||||||
|
<a class={className} {href}><slot /></a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
display: inline-block;
|
||||||
|
color: hsl(var(--color-text-h), var(--color-text-s), calc(100% - var(--color-text-l)));
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
clip-path: var(--clip-path-button);
|
||||||
|
transition: all var(--animation-speed) var(--animation-type);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
a:hover,
|
||||||
|
a:focus-visible {
|
||||||
|
opacity: 1;
|
||||||
|
text-decoration-color: unset;
|
||||||
|
}
|
||||||
|
</style>
|
5
src/params/integer.ts
Normal file
5
src/params/integer.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { ParamMatcher } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const match: ParamMatcher = (param) => {
|
||||||
|
return /^\d+$/.test(param);
|
||||||
|
};
|
|
@ -24,16 +24,25 @@ React, SvelteKit, TailwindCSS, Rust, Docker, SQL, and AWS technologies
|
||||||
|
|
||||||
I'm an active contributor to the open-source world: check out
|
I'm an active contributor to the open-source world: check out
|
||||||
my [Github profile](https://github.com/SeriousBug)! For a closer look at my work,
|
my [Github profile](https://github.com/SeriousBug)! For a closer look at my work,
|
||||||
visit my [portfolio](/portfolio) where I
|
visit my [portfolio](/portfolio/) where I
|
||||||
highlight my favorite projects. I sometimes write about the tools
|
highlight my favorite projects. I sometimes write about the tools
|
||||||
I use or challenges I've solved on my [blog](/posts). I'm always open to feedback, reach out to me through my socials linked below.
|
I use or challenges I've solved on my [blog](/posts/). I'm always open to feedback, reach out to me through my socials linked below.
|
||||||
|
|
||||||
|
<div class="socials">
|
||||||
|
<a target="_blank" rel="noopener" href="mailto:kaan@bgenc.net">Email</a>
|
||||||
|
<a target="_blank" rel="noopener me" href="https://github.com/SeriousBug">Github</a>
|
||||||
|
<a target="_blank" rel="noopener me" href="https://fosstodon.org/@kaan">Mastodon</a>
|
||||||
|
<a target="_blank" rel="noopener me" href="https://www.linkedin.com/in/kaan-barmore-genc">LinkedIn</a>
|
||||||
|
<a target="_blank" rel="noopener me" href="/static/cv.pdf">CV</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
picture {
|
picture {
|
||||||
|
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
clip-path: polygon(0% 0%, 75% 0%, 100% 100%, 25% 100%);
|
clip-path: var(--clip-path);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -45,12 +45,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity var(--animation-speed) var(--animation-type);
|
|
||||||
}
|
|
||||||
.row:hover,
|
|
||||||
.row:focus-within {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
.row > * {
|
.row > * {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import Button from '$lib/Button.svelte';
|
||||||
import Spacer from '$lib/Spacer.svelte';
|
import Spacer from '$lib/Spacer.svelte';
|
||||||
|
|
||||||
import Avif from './logo.avif';
|
import Avif from './logo.avif';
|
||||||
|
@ -19,13 +20,12 @@
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<a href="/posts">Blog</a>
|
<Button href="/posts/">Blog</Button>
|
||||||
<a href="/portfolio">Portfolio</a>
|
<Button href="/portfolio/">Portfolio</Button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
max-width: calc(var(--size-container) * 2);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export async function load({ params }) {
|
export async function load({ params }) {
|
||||||
console.log('params', params);
|
|
||||||
const post = await import(`../posts/${params.slug}.md`);
|
const post = await import(`../posts/${params.slug}.md`);
|
||||||
const { title, date } = post.metadata;
|
const { title, date } = post.metadata;
|
||||||
const content = post.default;
|
const content = post.default;
|
||||||
|
|
BIN
src/routes/logo.avif
(Stored with Git LFS)
BIN
src/routes/logo.avif
(Stored with Git LFS)
Binary file not shown.
|
@ -1,59 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
|
|
||||||
export let data;
|
|
||||||
function groupPosts(posts: any[]) {
|
|
||||||
const postsByYear = new Map<string, any[]>();
|
|
||||||
posts.forEach((post) => {
|
|
||||||
const year = format(new Date(post.meta.date), 'yyyy');
|
|
||||||
if (!postsByYear.has(year)) {
|
|
||||||
postsByYear.set(year, []);
|
|
||||||
}
|
|
||||||
postsByYear.get(year)!.push(post);
|
|
||||||
});
|
|
||||||
return postsByYear;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ul class="years">
|
|
||||||
{#each groupPosts(data.posts).entries() as [year, posts]}
|
|
||||||
<li>
|
|
||||||
<h2>{year}</h2>
|
|
||||||
<ul class="posts">
|
|
||||||
{#each posts as post}
|
|
||||||
<a href={`/${post.path}`}>
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
{post.meta.title}
|
|
||||||
</div>
|
|
||||||
<time datetime={post.meta.date}>
|
|
||||||
{format(new Date(post.meta.date), 'MMMM d')}
|
|
||||||
</time>
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
time {
|
|
||||||
width: 8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
.years,
|
|
||||||
.posts {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.years {
|
|
||||||
gap: 4rem;
|
|
||||||
}
|
|
||||||
.posts {
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
115
src/routes/posts/[[page=integer]]/+page.svelte
Normal file
115
src/routes/posts/[[page=integer]]/+page.svelte
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from '$lib/Button.svelte';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
function groupPosts(posts: any[]) {
|
||||||
|
const postsByYear = new Map<string, any[]>();
|
||||||
|
posts.forEach((post) => {
|
||||||
|
const year = format(new Date(post.meta.date), 'yyyy');
|
||||||
|
if (!postsByYear.has(year)) {
|
||||||
|
postsByYear.set(year, []);
|
||||||
|
}
|
||||||
|
postsByYear.get(year)!.push(post);
|
||||||
|
});
|
||||||
|
return postsByYear;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul 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>
|
||||||
|
<h5 class="post-title">
|
||||||
|
{post.meta.title}
|
||||||
|
</h5>
|
||||||
|
<time datetime={post.meta.date}>
|
||||||
|
{format(new Date(post.meta.date), 'MMMM d')}
|
||||||
|
</time>
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<Button disabled={data.page < 3} href={'/posts/'}>{data.page < 3 ? '_' : data.page - 1}</Button>
|
||||||
|
<Button disabled={data.page === 1} href={data.page === 2 ? '/posts/' : `/posts/${data.page - 1}`}
|
||||||
|
>{data.page === 1 ? '_' : data.page - 1}</Button
|
||||||
|
>
|
||||||
|
<span class="current-page">{data.page}</span>
|
||||||
|
<Button disabled={!data.hasMore} href="/posts/{data.page + 1}"
|
||||||
|
>{data.hasMore ? data.page + 1 : '_'}</Button
|
||||||
|
>
|
||||||
|
<Button disabled={data.page > data.pageCount - 2} href="/posts/{data.pageCount}"
|
||||||
|
>{data.page > data.pageCount - 2 ? '_' : data.pageCount}</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
time {
|
||||||
|
width: 8rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
:global(.pagination-controls > :first-child) {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
:global(.pagination-controls > :last-child) {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-page {
|
||||||
|
display: inline-block;
|
||||||
|
color: hsl(var(--color-text-h), var(--color-text-s), calc(100% - var(--color-text-l)));
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: unset;
|
||||||
|
user-select: none;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
clip-path: var(--clip-path-button);
|
||||||
|
transition: all var(--animation-speed) var(--animation-type);
|
||||||
|
}
|
||||||
|
.post-title {
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.years,
|
||||||
|
.posts {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.years {
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
.posts {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,8 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export async function load() {
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
const allPostFiles = import.meta.glob<SvelteAllProps>('/src/routes/posts/*.md');
|
const allPostFiles = import.meta.glob<SvelteAllProps>('/src/routes/posts/*.md');
|
||||||
const iterablePostFiles = Object.entries(allPostFiles);
|
const iterablePostFiles = Object.entries(allPostFiles);
|
||||||
|
|
||||||
|
@ -13,10 +15,21 @@ export async function load() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
meta: metadata,
|
meta: metadata,
|
||||||
path: slug
|
path: slug,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { posts: _.reverse(_.sortBy(posts, ({ meta }) => meta.date)) };
|
// Get the page number, converting it to a 0-based index
|
||||||
|
const page = (params.page ? parseInt(params.page, 10) : 1) - 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
posts: _.reverse(_.sortBy(posts, ({ meta }) => meta.date)).slice(
|
||||||
|
page * PAGE_SIZE,
|
||||||
|
(page + 1) * PAGE_SIZE,
|
||||||
|
),
|
||||||
|
hasMore: posts.length > (page + 1) * PAGE_SIZE,
|
||||||
|
page: page + 1,
|
||||||
|
pageCount: Math.ceil(posts.length / PAGE_SIZE),
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ h6 {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: var(--color-secondary);
|
color: var(--color-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration-color: transparent;
|
text-decoration-color: transparent;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
BIN
static/android-chrome-192x192.png
(Stored with Git LFS)
BIN
static/android-chrome-192x192.png
(Stored with Git LFS)
Binary file not shown.
BIN
static/android-chrome-512x512.png
(Stored with Git LFS)
BIN
static/android-chrome-512x512.png
(Stored with Git LFS)
Binary file not shown.
BIN
static/apple-touch-icon.png
(Stored with Git LFS)
BIN
static/apple-touch-icon.png
(Stored with Git LFS)
Binary file not shown.
BIN
static/favicon-16x16.png
(Stored with Git LFS)
BIN
static/favicon-16x16.png
(Stored with Git LFS)
Binary file not shown.
BIN
static/favicon-32x32.png
(Stored with Git LFS)
BIN
static/favicon-32x32.png
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Before Width: 48px | Height: 48px | Size: 15 KiB After Width: 48px | Height: 48px | Size: 15 KiB |
BIN
static/favicon.png
(Stored with Git LFS)
BIN
static/favicon.png
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in a new issue