feat: lots of bits n pieces

This commit is contained in:
Aaron Yarborough 2025-05-03 15:44:10 +01:00
parent 8f27f005ef
commit 0243c7f5b1
22 changed files with 2476 additions and 14349 deletions

View file

@ -1,7 +1,3 @@
{
"extends": "next/core-web-vitals",
"rules": {
"@stylistic/jsx/jsx-pascal-case": "off",
"@next/next/no-html-link-for-pages": "off"
}
"extends": ["next/core-web-vitals"]
}

View file

@ -1,3 +1,3 @@
echo Formatting...
# npm run format
npm run lint
echo Successfully formatted.

2
.nvmrc
View file

@ -1 +1 @@
v23.4.0
v22.15.0

View file

@ -27,7 +27,6 @@
],
"files.autoSave": "off",
"standard.autoFixOnSave": true,
"prettier.enable": false,
"editor.defaultFormatter": "standard.vscode-standard",
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
@ -41,5 +40,11 @@
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
}

View file

@ -15,7 +15,7 @@ module.exports = {
allow: '/'
}
]
},
}
// transform: async (config, path) => {
// const metadata = {
// loc: path

16540
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "www-aaronjy-me",
"version": "2.0.0.0",
"version": "2.1.0",
"private": true,
"type": "module",
"scripts": {
@ -8,27 +8,22 @@
"build": "next build",
"postbuild": "next-sitemap --config next-sitemap.config.cjs",
"start": "next start",
"link": "npx standard",
"format": "npx standard --fix",
"link": "echo NOT CONFIGURED",
"prepare": "husky",
"test": "jest --verbose --passWithNoTests",
"lint": "next lint",
"export:books": "node ./util/books-as-json.js > ./tmp/books.json",
"export:writing": "node ./util/writing-as-json.js > ./tmp/writing.json"
},
"standard": {
"ignore": [
"/tina/**/*"
]
},
"dependencies": {
"@highlightjs/cdn-assets": "^11.11.1",
"@mdx-js/mdx": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"camelcase-keys": "^9.1.3",
"date-fns": "^4.1.0",
"highlight.js": "^11.11.0",
"i": "^0.3.7",
"next": "^14.2.6",
"next": "^15.3.1",
"next-mdx-remote-client": "^1.1.0",
"next-seo": "^6.5.0",
"node-html-parser": "^7.0.1",
@ -37,8 +32,7 @@
"react-dom": "^18",
"rehype-code-titles": "^1.2.0",
"rehype-prism-plus": "^2.0.1",
"remark-gfm": "^4.0.1",
"tinacms": "^2.7.3"
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@babel/core": "^7.24.0",
@ -46,12 +40,13 @@
"@babel/preset-react": "^7.24.7",
"@commitlint/cli": "^19.1.0",
"@commitlint/config-conventional": "^19.1.0",
"@next/eslint-plugin-next": "^15.3.1",
"@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",
"eslint": "8.57.1",
"eslint-config-next": "15.3.1",
"front-matter": "^4.0.2",
"frontmatter-markdown-loader": "^3.7.0",
"husky": "^9.0.11",
@ -60,7 +55,6 @@
"js-yaml": "^4.1.0",
"next-sitemap": "^4.0.9",
"react-test-renderer": "^18.3.1",
"showdown": "^2.1.0",
"standard": "^17.1.0"
"showdown": "^2.1.0"
}
}

View file

@ -1,2 +0,0 @@
index.html
assets/

View file

@ -4,37 +4,4 @@
<url><loc>https://www.aaronjy.me/library</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/tags</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-alchemist</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-invisible-man</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/wintering-the-power-of-rest-and-retreat-in-difficult-times</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-time-machine</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/when-the-moon-hits-your-eye</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-song-of-achilles</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/to-be-taught-if-fortunate</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/on-tyranny</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-dangers-of-smoking-in-bed</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-midnight-library</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/a-night-to-remember</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/sex-punishment</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/a-monster-calls</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/diary-of-an-oxygen-thief</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/1984</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/alices-adventures-in-wonderland</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/the-nature-of-alexander</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/eleven-kinds-of-loneliness</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/star-maker</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/stray-reflections</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/stasiland-stories-from-behind-the-berlin-wall</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/cities-that-shaped-the-ancient-world</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/animal-farm</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/one-thousand-and-one-nights-a-retelling</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/library/childhoods-end</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/migrating-from-github-to-forgejo</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/performance-considerations-when-writing-a-tcp-game-server-in-dotnet</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/quickwrite-a-reflection-on-wintering-by-katherine-may</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/deploying-aaronjy-me-on-a-google-storage-bucket</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/supporting-content-file-structure-changes-on-a-static-site</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/writing/attitudes-to-reading-and-how-mine-have-changed</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me/about</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
<url><loc>https://www.aaronjy.me</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>
</urlset>

View file

@ -6,7 +6,7 @@ import React, { useEffect } from 'react'
import 'highlight.js/styles/atom-one-dark.css'
import hljs from 'highlight.js'
function Article ({ title, excerpt, date_published, tags, html }) {
function Article ({ title, excerpt, datePublished, tags, html }) {
useEffect(() => {
hljs.highlightAll()
}, [html])
@ -21,14 +21,14 @@ function Article ({ title, excerpt, date_published, tags, html }) {
description: excerpt,
type: 'article',
article: {
publishedTime: date_published ?? null
publishedTime: datePublished ?? null
}
}
}
/>
<div>
<Link href='./'>Back...</Link>
{date_published && <p>{formatDate(date_published)}</p>}
{datePublished && <p>{formatDate(datePublished)}</p>}
<div data-test='content' dangerouslySetInnerHTML={{ __html: html }} />
{tags && <p>Tags: {tags.join(', ')}</p>}
</div>

View file

@ -7,7 +7,7 @@ import style from './BookReview.module.css'
import ExternalLink from '../ExternalLink/ExternalLink'
function BookReview ({ review, html }) {
const { title, image, author, description, url, tags, rating, read_date } = review
const { title, image, author, description, url, tags, rating, readDate } = review
const imageUrl = image ? `${process.env.NEXT_PUBLIC_CONTENT_API_BASE_URL}/assets/${image}` : undefined
@ -24,7 +24,7 @@ function BookReview ({ review, html }) {
description,
type: 'article',
article: {
publishedTime: read_date ?? null
publishedTime: readDate ?? null
}
}
}
@ -41,7 +41,7 @@ function BookReview ({ review, html }) {
<span className='bold'>Genres:</span>&nbsp;{tags.join(',')}<br />
</>}
<span className='bold'>Rating:</span>&nbsp;{rating}/5<br />
<span className='bold'>Read on:</span>&nbsp;{formatDate(read_date)}
<span className='bold'>Read on:</span>&nbsp;{formatDate(readDate)}
</p>
<p><ExternalLink href={url}>View on The StoryGraph</ExternalLink></p>
</div>

View file

@ -7,7 +7,7 @@ export default function StaticContentList ({ entries, urlPrefix, max = 0 }) {
<tbody>
{entries.map((e) => (
<tr key={e.slug}>
<td>{!!e.date_published && <span>{formatDate(e.date_published)}</span>}</td>
<td>{!!e.datePublished && <span>{formatDate(e.datePublished)}</span>}</td>
<td>
<Link href={`${urlPrefix}${e.slug}`}>{e.title}</Link>
</td>

View file

@ -13,7 +13,7 @@ export const mdxComponents = {
(async function () {
const res = await fetchPosts([])
const json = await res.json()
setItems(json.data.sort((a, b) => a.date_published < b.date_published) ?? [])
setItems(json.data.sort((a, b) => a.datePublished < b.datePublished) ?? [])
setLoading(false)
})()
break
@ -21,11 +21,14 @@ export const mdxComponents = {
default:
throw `Could not render StaticContentList: content type ${type} not supported.`
}
}, [])
})
return (
<>
{isLoading && <p>Loading...</p> }
{!isLoading && <StaticContentList entries={items} urlPrefix={urlPrefix} max={max} />}
</>

View file

@ -62,8 +62,8 @@ export async function getServerSideProps ({ params }) {
}
export default function BasicPage ({ title, mdxSource }) {
if (!mdxSource || "error" in mdxSource) {
return <p>Something went wrong: {mdxSource?.error ?? "???"}</p>
if (!mdxSource || 'error' in mdxSource) {
return <p>Something went wrong: {mdxSource?.error ?? '???'}</p>
}
return (

View file

@ -5,7 +5,8 @@ export default function App ({ Component, pageProps }) {
return (
<>
<DefaultSeo defaultTitle='Aaron Yarborough' titleTemplate='%s | Aaron Yarborough' />
<Component {...pageProps} />
<Component
{...pageProps} />
</>
)
}

View file

@ -16,7 +16,7 @@ export const getServerSideProps = async () => {
const json = await res.json()
const posts = json.data
.sort((a, b) => new Date(b.date_published).getTime() - new Date(a.date_published).getTime())
.sort((a, b) => new Date(b.datePublished).getTime() - new Date(a.datePublished).getTime())
return {
props: {

View file

@ -1,4 +1,5 @@
import showdown from 'showdown'
import camelcaseKeys from 'camelcase-keys'
const baseUrl = process.env.NEXT_PUBLIC_CONTENT_API_BASE_URL
@ -8,9 +9,23 @@ export const fetchBookReviews = async (...args) => fetchItems('book_review', ...
export const fetchBasicPages = async (...args) => fetchItems('basic_pages', ...args)
export const fetchCV = async (...args) => fetchItems('cv', ...args)
const originalFetch = globalThis.fetch
globalThis.fetch = async (...args) => {
const response = await originalFetch(...args)
const originalJson = response.json
response.json = async function () {
const data = await originalJson.call(this)
return camelcaseKeys(data, { deep: true })
}
return response
}
export async function fetchItems (type, fields = undefined, filter = undefined) {
const url = new URL(`${baseUrl}/items/${type}`)
// console.trace(`Getting items '${type}' with fields`, fields, 'and filter', filter)
if (fields?.length) {
url.searchParams.append('fields', fields.join(','))
@ -50,9 +65,7 @@ export function markdownToHtml (content) {
}
async function apiFetch (...args) {
const url = args[0]
// @ts-ignore
const res = await fetch(...args)
// console.log('GET (fetch)', url, res.status, res.statusText)
return res
}

1
tina/.gitignore vendored
View file

@ -1 +0,0 @@
__generated__

View file

@ -1,136 +0,0 @@
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
}
]
}
]
}
})

File diff suppressed because one or more lines are too long