Add library feature (#1)
Co-authored-by: Aaron Yarborough <3855819+AaronJY@users.noreply.github.com> Reviewed-on: #1
This commit is contained in:
parent
5a28e13e51
commit
205522acc9
48 changed files with 13286 additions and 1165 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -36,3 +36,5 @@ yarn-error.log*
|
|||
next-env.d.ts
|
||||
|
||||
deploy-vars.sh
|
||||
node_modules
|
||||
.env
|
10
content/books/1984.md
Normal file
10
content/books/1984.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: '1984'
|
||||
author: George Orwell
|
||||
stars: 3
|
||||
readDate: 2023-07-31T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/6ff3f487-8d37-4ac5-8190-6622d6562639'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/v43bj24inkwioiogb5uz8nqmpnpw'
|
||||
tags: 'fiction, dystopian, classics'
|
||||
---
|
||||
|
10
content/books/A-Monster-Calls.md
Normal file
10
content/books/A-Monster-Calls.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: A Monster Calls
|
||||
author: Patrick Ness
|
||||
stars: 4
|
||||
readDate: 2024-05-31T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/170c1204-1410-4246-babb-c80e31aebea9'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/50qkuazqx2lidfd0hk74ltifz9nf'
|
||||
tags: 'fiction, young adult'
|
||||
---
|
||||
|
10
content/books/Diary-of-An-Oxygen-Thief.md
Normal file
10
content/books/Diary-of-An-Oxygen-Thief.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Diary of An Oxygen Thief
|
||||
author: Anonymous
|
||||
stars: 4
|
||||
readDate: 2024-03-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/9ce581f2-d9ac-4be1-abf7-4597684bab7f'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/l5zkpt8v2wj76ri6nkq7ismqsskl'
|
||||
tags: 'romance, fiction'
|
||||
---
|
||||
|
10
content/books/No-God-But-God.md
Normal file
10
content/books/No-God-But-God.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: No God But God
|
||||
author: Reza Aslan
|
||||
stars: 4
|
||||
readDate: 2023-01-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/27cb3f11-77c6-4eca-9344-d64885e6f1c4'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/gf3c92roqrgdbdsgphns5n111t93'
|
||||
tags: 'non-fiction, religion, history'
|
||||
---
|
||||
|
10
content/books/Sex--Punishment.md
Normal file
10
content/books/Sex--Punishment.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Sex & Punishment
|
||||
author: Eric Berkowitz
|
||||
stars: 3
|
||||
readDate: 2024-05-31T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/79ffd129-2325-45d0-826d-c6969a09e239'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/lqtuj9d7fdj1qw1lei01yby42h9z'
|
||||
tags: 'non-fiction, history'
|
||||
---
|
||||
|
10
content/books/The-Nature-of-Alexander.md
Normal file
10
content/books/The-Nature-of-Alexander.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: The Nature of Alexander
|
||||
author: Mary Renault
|
||||
stars: 4.5
|
||||
readDate: 2023-03-31T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/bc111e8b-1b3f-466a-9efd-c3316ae4b533'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/68zjkhp67u2bi6qh3melbyqaly06'
|
||||
tags: 'non-fiction, history, biography'
|
||||
---
|
||||
|
10
content/books/a-night-to-remember.md
Normal file
10
content/books/a-night-to-remember.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
tags: 'non-fiction, classics, history'
|
||||
title: A Night To Remember
|
||||
author: Walter Lord
|
||||
stars: 3.5
|
||||
readDate: 2024-06-30T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/5192ee9e-ffb2-4c72-a7c3-8051d950d66f'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/pdwshwni6jyc7ivp55j6tsxzngfl'
|
||||
---
|
||||
|
10
content/books/alice-in-wonderland.md
Normal file
10
content/books/alice-in-wonderland.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Alice's Adventures in Wonderland
|
||||
author: Lewis Carroll
|
||||
stars: 3
|
||||
readDate: 2023-04-30T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/83b0e44a-06fe-4042-9d2d-e4f41244fb9c'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/l83t3e6wh6tq7dqxbvrba34ee2nb'
|
||||
tags: 'fiction, classics, fantasy'
|
||||
---
|
||||
|
10
content/books/animal-farm.md
Normal file
10
content/books/animal-farm.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Animal Farm
|
||||
author: George Orwell
|
||||
stars: 4
|
||||
readDate: 2023-01-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/f4b706d9-4ed9-4b15-85e0-2493581de818'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/jztibk5xvnynw7mh7hfw0orhbzuh'
|
||||
tags: 'fiction, classics, dystopian'
|
||||
---
|
||||
|
10
content/books/cities-that-shaped-the-ancient-world.md
Normal file
10
content/books/cities-that-shaped-the-ancient-world.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Cities That Shaped The Ancient World
|
||||
author: John Julius Norwich
|
||||
stars: 3.5
|
||||
readDate: 2023-02-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/20bc1ff4-56bb-4e88-a403-bc150d45f9d2'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/i8znw2yrd6p4m76dx6rvyuyd48h2'
|
||||
tags: 'non-fiction, history'
|
||||
---
|
||||
|
11
content/books/song-of-achilles.md
Normal file
11
content/books/song-of-achilles.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: The Song of Achilles
|
||||
author: Madline Miller
|
||||
stars: 2.5
|
||||
readDate: 2025-03-22T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/9202845d-26cd-4a85-b15f-408116117028'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/m8cw3kb3qx4h2jl8kg0u4m3txhie'
|
||||
tags: 'fiction, fantasy, romance'
|
||||
---
|
||||
|
||||
This one really tailed off half-way through. I found the characters very one-dimensional (Patrcolus pines after Achilles, Achilles has muscles and can swing a sword fast), but the story took me at least up until the half-way mark. Weirdly I wasn't interested too much when the actual Iliad story line started to get going - maybe because I've heard it a million times before - and their essentially non-existent romance (more accurately an inch-deep obsession, I'd argue) didn't exactly inspire me to carry on reading.
|
10
content/books/starmaker.md
Normal file
10
content/books/starmaker.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Star Maker
|
||||
author: Olaf Stapledon
|
||||
stars: 4.5
|
||||
readDate: 2023-03-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/c1d60727-8e24-4a1f-9f0e-974d68a934d2'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/bln6q5k1v7ealnwss4msv0jixlhk'
|
||||
tags: 'fiction, classics, science fiction'
|
||||
---
|
||||
|
10
content/books/stasiland.md
Normal file
10
content/books/stasiland.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: 'Stasiland: Stories from Behind the Berlin Wall'
|
||||
author: Anna Funder
|
||||
stars: 4
|
||||
readDate: 2023-02-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/9202845d-26cd-4a85-b15f-408116117028'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/gphzjvwbhr8d5agrieobx0yy2xhb'
|
||||
tags: 'non-fiction, history, politics'
|
||||
---
|
||||
|
10
content/books/stray-reflections.md
Normal file
10
content/books/stray-reflections.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Stray Reflections
|
||||
author: Muhammad Iqbal
|
||||
stars: 5
|
||||
readDate: 2023-03-01T00:00:00.000Z
|
||||
url: null
|
||||
thumbnailUrl: null
|
||||
tags: 'non-fiction, politics, philosophy'
|
||||
---
|
||||
|
10
content/books/the-marmalade-diaries.md
Normal file
10
content/books/the-marmalade-diaries.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: The Marmalade Diaries
|
||||
author: Ben Aitken
|
||||
stars: 4.5
|
||||
readDate: 2023-01-01T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/406fe719-07de-44ba-b4e7-86714f638cf9'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/ou589mae0cxxup4cqq1mlnqdaj62'
|
||||
tags: non-fiction
|
||||
---
|
||||
|
10
content/books/the-midnight-library.md
Normal file
10
content/books/the-midnight-library.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: The Midnight Library
|
||||
author: Matt Haig
|
||||
stars: 4
|
||||
readDate: 2024-06-30T23:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/d9c7ed04-6148-4e01-a118-d96cba16f507'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/b9g1h8bhrqz7qs2fagzhsixbp6xy'
|
||||
tags: 'fiction, literary, science fiction'
|
||||
---
|
||||
|
11
content/books/to-be-taught-if-fortunate.md
Normal file
11
content/books/to-be-taught-if-fortunate.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: 'To Be Taught, If Fortunate'
|
||||
author: Becky Chambers
|
||||
stars: 4.5
|
||||
readDate: 2025-03-22T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/fca2631f-b3e2-4b9d-a2fd-4c1ec5e4d3f7'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/8ep9zjc581zefkzfyhtizqjnz8u5'
|
||||
tags: 'fiction, science fiction'
|
||||
---
|
||||
|
||||
Really enjoyed this! It was reflective yet lighthearted. I also love this specific flavour of sci-fi, where vastly different species of aliens and worlds are thought up and articulated beautifully by the author.
|
11
content/books/when-the-moon-hits-your-eye.md
Normal file
11
content/books/when-the-moon-hits-your-eye.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: When the Moon Hits Your Eye
|
||||
author: John Scalzi
|
||||
stars: 2.5
|
||||
readDate: 2025-03-26T00:00:00.000Z
|
||||
url: 'https://app.thestorygraph.com/books/4f3c3b02-3d2b-4765-93ac-4656133bbec5'
|
||||
thumbnailUrl: 'https://cdn.thestorygraph.com/718cb49yqfu02zekeq22yl8lgm8v'
|
||||
tags: 'fiction, science fiction'
|
||||
---
|
||||
|
||||
Some interesting chapters in here exploring how governments and various public/private institutions could deal with the moon randomly turning to choose, but a lot of fluff in between them. Can't really recommend, unless you want to skip to the good bits like I did.
|
|
@ -2,7 +2,9 @@
|
|||
title: Attitudes to reading, and how mine have changed
|
||||
pubdate: 2025-03-18T00:00:00.000Z
|
||||
desc: I was discussing reading habits with my good friend Beth, specifically around reading multiple books at once vs. reading a single book at a time...
|
||||
tags: reading, books
|
||||
tags:
|
||||
- reading
|
||||
- books
|
||||
---
|
||||
I was discussing reading habits with my good friend Beth, specifically around reading multiple books at once vs. reading a single book at a time (we're both very members of the former group), and it got me thinking as to why there seems to me to be this split in reading habits, and where it might come from.
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
title: Migrating from GitHub to Forgejo
|
||||
pubdate: 2025-03-16T00:00:00.000Z
|
||||
desc: I recently moved all of my reposfrom GitHub to a self-hosted Forgejo instance running on my own servers.
|
||||
tags: tech, hosting
|
||||
tags:
|
||||
- tech
|
||||
- hosting
|
||||
---
|
||||
|
||||
I recently moved all of my repos (public and private) from GitHub to a self-hosted [Forgejo](https://forgejo.org/) instance running on my own servers.
|
||||
|
|
|
@ -3,7 +3,10 @@ title: Performance considerations when writing a TCP game server in dotnet
|
|||
pubdate: 2025-02-23T21:12:37.864Z
|
||||
moddate: 2025-03-21T21:12:47.864Z
|
||||
desc: While writing a TCP game server in dotnet for a hobby project, I learned a few ways to improve the efficiency and scalability of the server while running into some performance issues. Here's what I learned!
|
||||
tags: tech, programming, dotnet
|
||||
tags:
|
||||
- tech
|
||||
- programming
|
||||
- dotnet
|
||||
---
|
||||
|
||||
While writing a TCP game server in dotnet for a hobby project (check it out [here](https://github.com/AaronJY/GServer)), I learned a few ways to improve the efficiency and scalability of the server while running into some performance issues.
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
title: "Quickwrite: A reflection on Wintering by Katherine May"
|
||||
pubdate: 2025-03-09T00:00:00.000Z
|
||||
desc: "Katherine May draws a unique parallel in her book ‘Wintering’ between the coming of and living through the winter months, and those liminal times in your life – when a gap opens up underfoot and swallows us whole...."
|
||||
tags: books, reading
|
||||
tags:
|
||||
- books
|
||||
- reading
|
||||
---
|
||||
|
||||
Katherine May draws a unique parallel in her book ‘Wintering’ between the coming of and living through the winter months, and those liminal times in your life – when a gap opens up underfoot and swallows us whole. It’s never clear when such a gap appears to steal us away, and how long exactly we’ll spend in the liminality we fall into, and most of us claw at the walls in an attempt to scale up and out, back into the warm light of day – how things used to be. Katherine’s book is an account of how she learned to see it more as a wave to ride than a locked room to break free from. Her ultimate message is an argument to reframe it, or to see it for what it really is; not as a bleak, timeless realm devoid of hope and light, but of a necessary state we all find ourselves in at various points in our lives, and that this realm offers its own medicines for those who care to look. These seasonal and spiritual changes Katherine refers to as ‘Wintering’ – aptly a verb to articulate its ephemeral nature – serve much in the same way a good night’s sleep does: while a deep and peaceful sleep purges metabolic waste and toxins from our brains, a wintering can offer a more spiritual cleanse; a hibernation after a hot and unrelenting summer; a mirror held up in front to break our blind and frenzied sprint towards a goal we’ve long forgotten; a beloved teacher from our school days with a soft voice, reassuring smile and placations to calm our nerves.
|
|
@ -2,7 +2,9 @@
|
|||
title: Deploying aaronjy.me on a Google Storage bucket
|
||||
pubdate: 2024-05-01T00:00:00.000Z
|
||||
desc: "Google Cloud Storage is an effective solution for hosting static sites, offering a simple and scalable way to manage web assets. A manual deployment strategy involves four key steps: backing up existing files to a backup bucket, removing sensitive files for security, uploading the latest site files from the build directory, and invalidating Google’s global cache to ensure users access updated content."
|
||||
tags: tech, hosting
|
||||
tags:
|
||||
- tech
|
||||
- hosting
|
||||
---
|
||||
Google actually has [documentation](https://cloud.google.com/storage/docs/hosting-static-website) on how to deploy a static site to a storage bucket, but I wanted to talk about how I handle deployments, as Google doesn't covert that!
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
title: Supporting content file structure changes on a static site
|
||||
pubdate: 2024-03-18T16:47:32.150Z
|
||||
desc: Static site generators (SSGs) convert complex site sources into HTML, CSS, and JS, allowing flexible hosting options. While they offer benefits like speed and low costs, updating content can be challenging for non-technical users. A solution involves assigning unique identifiers to articles and creating a URL mapping file to simplify restructuring and managing content links.
|
||||
tags: tech
|
||||
tags:
|
||||
- tech
|
||||
---
|
||||
Static site generators (SSGs) are great. They take your complex site source and distil it down to the web's native language: HTML, CSS and JS. You can host your files anywhere: in cloud-native storage buckets; on low-cost CPanel hosting; on global CDNs; your old Lenovo ThinkPad in your cupboard running an Apache server that hasn't been patched since 2008; the list goes on. Wanna go further and throw away your CMS? Cool, you can use markdown files and a text editor as your CMS.
|
||||
|
|
@ -2,7 +2,10 @@
|
|||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: 'export',
|
||||
trailingSlash: true // ensure pages get their own directory in output
|
||||
trailingSlash: true, // ensure pages get their own directory in output
|
||||
images: {
|
||||
unoptimized: true
|
||||
}
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
13833
package-lock.json
generated
13833
package-lock.json
generated
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "www-aaronjy-2024",
|
||||
"version": "1.6.3.0",
|
||||
"name": "www-aaronjy-me",
|
||||
"version": "1.7.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "TINA_PUBLIC_IS_LOCAL=true tinacms dev -c \"next dev\"",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.cjs",
|
||||
"start": "next start",
|
||||
|
@ -14,16 +14,21 @@
|
|||
"test": "jest --verbose --passWithNoTests",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/tina/**/*"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@highlightjs/cdn-assets": "^11.11.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"feather-icons": "^4.29.2",
|
||||
"highlight.js": "^11.11.0",
|
||||
"next": "^14.2.6",
|
||||
"next-seo": "^6.5.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"react-dom": "^18",
|
||||
"tinacms": "^2.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
|
@ -32,6 +37,7 @@
|
|||
"@commitlint/config-conventional": "^19.1.0",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@tinacms/cli": "^1.9.3",
|
||||
"@types/jest": "^29.5.12",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^9.9.0",
|
||||
|
|
2
public/admin/.gitignore
vendored
Normal file
2
public/admin/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
index.html
|
||||
assets/
|
BIN
public/img/book-placeholder.jpg
Normal file
BIN
public/img/book-placeholder.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
48
src/components/Book/Book.jsx
Normal file
48
src/components/Book/Book.jsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { formatDate } from '@/lib/helpers'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import style from './Book.module.css'
|
||||
import ExternalLink from '../ExternalLink/ExternalLink'
|
||||
|
||||
function Book ({ attributes, html }) {
|
||||
return (
|
||||
<>
|
||||
<h1>{attributes.title}<br /><small>by {attributes.author}</small></h1>
|
||||
<Link href='./'>Back...</Link>
|
||||
|
||||
<article>
|
||||
<NextSeo
|
||||
title={attributes.title} description={attributes.desc} openGraph={
|
||||
{
|
||||
title: attributes.title,
|
||||
description: attributes.desc,
|
||||
type: 'article',
|
||||
article: {
|
||||
publishedTime: attributes.pubdate ?? null
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div className={style.layout}>
|
||||
<Image src={attributes.thumbnailUrl} width={250} height={580} alt='' className={style.thumbnail} />
|
||||
<div>
|
||||
<div data-test='content' dangerouslySetInnerHTML={{ __html: html || '<p>(no review)</p>' }} />
|
||||
<p>
|
||||
<span className='bold'>Rating:</span> {attributes.stars}/5<br />
|
||||
<span className='bold'>Read on:</span> {formatDate(attributes.readDate)}
|
||||
</p>
|
||||
<p><ExternalLink href={attributes.url}>View on The StoryGraph</ExternalLink></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Book
|
34
src/components/Book/Book.module.css
Normal file
34
src/components/Book/Book.module.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.25rem;
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
.layout:first-child {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.layout:last-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.layout p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
.layout {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
24
src/components/BookListItem/BookListItem.jsx
Normal file
24
src/components/BookListItem/BookListItem.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import style from './BookListItem.module.css'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function BookListItem ({ href, title, author, stars, readDate, url, thumbnailUrl, tags, review }) {
|
||||
return (
|
||||
<div className={style.item}>
|
||||
<Link href={href}>
|
||||
<div
|
||||
className={style.thumb} style={{
|
||||
backgroundImage: `url(${thumbnailUrl ?? '/img/book-placeholder.jpg'})`
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<div>
|
||||
<h2 className={style.heading}>
|
||||
<Link href={href}>{title}</Link>
|
||||
</h2>
|
||||
<p className={style.author}>{author}</p>
|
||||
<p>{tags}</p>
|
||||
{/* <p>{stars}/5</p> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
26
src/components/BookListItem/BookListItem.module.css
Normal file
26
src/components/BookListItem/BookListItem.module.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
.item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.author {
|
||||
margin-top: 5px;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
|
||||
|
||||
.thumb {
|
||||
width: auto;
|
||||
height: 290px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 6px;
|
||||
}
|
11
src/components/Grid/Grid.jsx
Normal file
11
src/components/Grid/Grid.jsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import style from './Grid.module.css'
|
||||
|
||||
export default function Grid ({ columns, children }) {
|
||||
return (
|
||||
<div
|
||||
className={style.grid}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
5
src/components/Grid/Grid.module.css
Normal file
5
src/components/Grid/Grid.module.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
|
@ -11,6 +11,7 @@ function Header () {
|
|||
<Link href='/writing'>Writing</Link>{', '}
|
||||
<Link href='/tags'>Tags</Link>{', '}
|
||||
<Link href='/cv'>CV</Link>{', '}
|
||||
<Link href='/library'>Library</Link>{', '}
|
||||
<Link href='/about'>About</Link>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { formatDate } from '@/lib/helpers'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function StaticContentList ({ entries, urlPrefix }) {
|
||||
export default function StaticContentList ({ entries, urlPrefix, max = 0 }) {
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
|
@ -12,7 +12,7 @@ export default function StaticContentList ({ entries, urlPrefix }) {
|
|||
<Link href={`${urlPrefix}${e.slug}`}>{e.attributes.title}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
)).slice(0, max > 0 ? max : entries.length)}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
|
|
|
@ -56,8 +56,6 @@ export function getStaticEntries (contentPath) {
|
|||
const directoryItems = fs.readdirSync(contentPath, { withFileTypes: true })
|
||||
return directoryItems.map((dirent) =>
|
||||
getMarkdownEntry(`${dirent.path}/${dirent.name}`)
|
||||
).sort((a, b) =>
|
||||
new Date(b.attributes.pubdate).getTime() - new Date(a.attributes.pubdate).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -68,8 +66,7 @@ export function getContentTags (contentPath) {
|
|||
for (const entry of entries) {
|
||||
if (!entry.attributes.tags) { continue }
|
||||
|
||||
const tags = entry.attributes.tags.split(', ')
|
||||
|
||||
const tags = entry.attributes.tags
|
||||
for (const tag of tags) {
|
||||
allTags[tag] = !allTags[tag] ? 1 : allTags[tag] + 1
|
||||
}
|
||||
|
|
|
@ -7,3 +7,13 @@ export function toSlug (input) {
|
|||
export function formatDate (date) {
|
||||
return dateFns.format(Date.parse(date), 'PPP')
|
||||
}
|
||||
|
||||
/**
|
||||
* Silliness to make sure dates don't get passed to the
|
||||
* page function below as [object Object]
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
export function stringifyAndParse (obj) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function Home ({ postEntries }) {
|
|||
|
||||
<section>
|
||||
<h2>Recent posts</h2>
|
||||
<StaticContentList entries={postEntries} urlPrefix='writing/' />
|
||||
<StaticContentList entries={postEntries} urlPrefix='writing/' max={5} />
|
||||
</section>
|
||||
|
||||
</DefaultLayout>
|
||||
|
|
17
src/pages/library/[slug].js
Normal file
17
src/pages/library/[slug].js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from 'react'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/DefaultLayout'
|
||||
import { getStaticEntryPaths, getStaticEntryProps } from '@/lib/content'
|
||||
import Book from '@/components/Book/Book'
|
||||
import { stringifyAndParse } from '@/lib/helpers'
|
||||
|
||||
export const getStaticPaths = () => getStaticEntryPaths('./content/books')
|
||||
export const getStaticProps = (ctx) =>
|
||||
stringifyAndParse(getStaticEntryProps('./content/books', ctx))
|
||||
|
||||
export default function LibrarySingle ({ attributes, html }) {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<Book attributes={attributes} html={html} />
|
||||
</DefaultLayout>
|
||||
)
|
||||
}
|
46
src/pages/library/index.js
Normal file
46
src/pages/library/index.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import BookListItem from '@/components/BookListItem/BookListItem'
|
||||
import Grid from '@/components/Grid/Grid'
|
||||
import DefaultLayout from '@/layouts/DefaultLayout/DefaultLayout'
|
||||
import { getStaticEntries } from '@/lib/content'
|
||||
import { stringifyAndParse } from '@/lib/helpers'
|
||||
import { NextSeo } from 'next-seo'
|
||||
|
||||
export const Title = 'Library'
|
||||
|
||||
export const getStaticProps = () => {
|
||||
const bookEntries = getStaticEntries('./content/books')
|
||||
.sort((a, b) => {
|
||||
return b.attributes.readDate - a.attributes.readDate
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
bookEntries: stringifyAndParse(bookEntries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function Library ({ bookEntries }) {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<NextSeo
|
||||
title={Title}
|
||||
openGraph={
|
||||
{
|
||||
title: Title
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<h1>{Title}</h1>
|
||||
|
||||
<section>
|
||||
<Grid columns={5}>
|
||||
{bookEntries.map((entry, _) => (
|
||||
<BookListItem key={entry.attributes.title} {...entry.attributes} href={`/library/${entry.slug}`} />
|
||||
))}
|
||||
</Grid>
|
||||
</section>
|
||||
</DefaultLayout>
|
||||
)
|
||||
}
|
|
@ -7,6 +7,9 @@ import StaticContentList from '@/components/StaticContentList/StaticContentList'
|
|||
export const getStaticProps = () => ({
|
||||
props: {
|
||||
postEntries: getStaticEntries('./content/writing')
|
||||
.sort((a, b) =>
|
||||
new Date(b.attributes.pubdate).getTime() - new Date(a.attributes.pubdate).getTime()
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -14,3 +14,11 @@ tr {
|
|||
td:first-child {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
1
tina/.gitignore
vendored
Normal file
1
tina/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
__generated__
|
136
tina/config.js
Normal file
136
tina/config.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
import { defineConfig } from 'tinacms'
|
||||
|
||||
// Your hosting provider likely exposes this as an environment variable
|
||||
const branch =
|
||||
process.env.GITHUB_BRANCH ||
|
||||
process.env.VERCEL_GIT_COMMIT_REF ||
|
||||
process.env.HEAD ||
|
||||
'main'
|
||||
|
||||
export default defineConfig({
|
||||
branch,
|
||||
|
||||
// Get this from tina.io
|
||||
clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,
|
||||
// Get this from tina.io
|
||||
token: process.env.TINA_TOKEN,
|
||||
|
||||
build: {
|
||||
outputFolder: 'admin',
|
||||
publicFolder: 'public'
|
||||
},
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: '',
|
||||
publicFolder: 'public'
|
||||
}
|
||||
},
|
||||
// See docs on content modeling for more info on how to setup new content models: https://tina.io/docs/schema/
|
||||
schema: {
|
||||
collections: [
|
||||
{
|
||||
name: 'books',
|
||||
label: 'Library',
|
||||
path: 'content/books',
|
||||
match: {
|
||||
include: '*'
|
||||
},
|
||||
format: 'md',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'author',
|
||||
label: 'Author',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'stars',
|
||||
label: 'Stars',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'datetime',
|
||||
name: 'readDate',
|
||||
label: 'Read date',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'url',
|
||||
label: 'URL',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'thumbnailUrl',
|
||||
label: 'Thumbnail URL',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'writing',
|
||||
label: 'Writing',
|
||||
path: 'content/writing',
|
||||
match: {
|
||||
include: '*'
|
||||
},
|
||||
format: 'md',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'datetime',
|
||||
name: 'pubdate',
|
||||
label: 'Publish Date'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
ui: {
|
||||
component: 'textarea'
|
||||
},
|
||||
name: 'desc',
|
||||
label: 'Description'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
list: true
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
1
tina/tina-lock.json
Normal file
1
tina/tina-lock.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue