Bump script ver and refactor md metadata gen
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
This commit is contained in:
parent
b5ca20b93d
commit
35d56f5cde
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,5 +4,5 @@ dist/
|
||||
.DS_Store
|
||||
.cache/
|
||||
*.bun
|
||||
**/*.wip.md
|
||||
**/.*.md
|
||||
.env
|
||||
|
@ -1,49 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
import { parse as URIparse } from 'uri-js';
|
||||
import date from '../lib/date';
|
||||
import style from '../styles/github-profile.module.css';
|
||||
|
||||
function CardRow({label, children}: {label: string, children: JSX.Element | string}) {
|
||||
return !children? <></> : (
|
||||
<div className={style.cardRow}>
|
||||
<span className={style.cardLabel}>{label}</span>
|
||||
<span className={style.cardValue}>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GitHubProfile({user}: any) {
|
||||
return (<>
|
||||
<div className={style.card}>
|
||||
<div className={style.avatarContainer}>
|
||||
<Image
|
||||
layout='fixed'
|
||||
width={256}
|
||||
height={256}
|
||||
src={user.avatar_url}
|
||||
alt={`${user.login}'s GitHub profile avatar`}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.cardTable}>
|
||||
<CardRow label='Name'>{user.name}</CardRow>
|
||||
<CardRow label='Username'>{user.login}</CardRow>
|
||||
<CardRow label='URL'><a href={user.html_url}>{user.html_url}</a></CardRow>
|
||||
{user.blog && <CardRow label='Blog'>
|
||||
<a href={ !URIparse(user.blog).scheme ? `//${user.blog}`: user.blog}>{user.blog}</a>
|
||||
</CardRow>}
|
||||
<CardRow label='Location'>{user.location}</CardRow>
|
||||
<CardRow label='About'>{user.bio}</CardRow>
|
||||
<CardRow label='Created'>{user.created_at ? date.toRelativeDate(user.created_at) : ''}</CardRow>
|
||||
{user.updated_at && <CardRow label='Last Updated'>{user.updated_at ? date.toRelativeDate(user.updated_at) : ''}</CardRow>}
|
||||
{user.twitter_username && <CardRow label='Twitter'>
|
||||
<a className='link extern blue button'
|
||||
href={`https://twitter.com/${user.twitter_username}`}>
|
||||
@{user.twitter_username}
|
||||
</a>
|
||||
</CardRow>}
|
||||
</div>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default GitHubProfile;
|
@ -1,12 +1,8 @@
|
||||
import Meta from './meta';
|
||||
import Title from './title';
|
||||
|
||||
type ChildrenType = JSX.Element | Array<ChildrenType>;
|
||||
|
||||
type LayoutProps = {
|
||||
name: string,
|
||||
title?: string,
|
||||
ancestors?: Array<{ name: string, path: string }>
|
||||
children?: ChildrenType,
|
||||
removeContainer?: boolean,
|
||||
};
|
||||
@ -22,8 +18,7 @@ function Container(props: {children?: ChildrenType, ignore?: boolean}) {
|
||||
function Layout(props : LayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<Meta name={props.name} ancestors={props.ancestors} />
|
||||
<Title title={props.title} name={props.name} ancestors={props.ancestors} />
|
||||
<Title />
|
||||
<Container ignore={props.removeContainer}>{props.children}</Container>
|
||||
</>
|
||||
);
|
||||
|
@ -48,11 +48,6 @@ export function toListItem(record: Record<string, any>): listItem | null {
|
||||
});
|
||||
}
|
||||
|
||||
const s = {
|
||||
"af": 123,
|
||||
"asdf" : 123
|
||||
}
|
||||
|
||||
export function mapChild(
|
||||
obj: listItem | string,
|
||||
level: number,
|
||||
|
@ -1,24 +0,0 @@
|
||||
import Head from 'next/head';
|
||||
|
||||
function Meta({name, ancestors}
|
||||
: {name: string, ancestors?: Array<{ name: string, path: string }> }) {
|
||||
function path(): string {
|
||||
if (!ancestors)
|
||||
return name;
|
||||
|
||||
let path = '';
|
||||
ancestors.forEach((obj) => {
|
||||
path = `${path}${obj.name} /`;
|
||||
});
|
||||
|
||||
return `PaulW.XYZ / ${path} ${name}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>{path()}</title>
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
export default Meta;
|
@ -1,5 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import Pages from '../public/pages.json';
|
||||
import Pages from '../public/external.json';
|
||||
|
||||
function QuickLinks() {
|
||||
return (
|
||||
|
@ -1,17 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import { INoteMeta } from "../lib/slug";
|
||||
import NotesInfo from '../public/notes.json';
|
||||
|
||||
function RecentNotes({ notesMeta }: { notesMeta: INoteMeta[] }) {
|
||||
function RecentNotes() {
|
||||
const notes = Object.entries(NotesInfo);
|
||||
return (
|
||||
<div className='block'>
|
||||
<div className='h2'>Recent Notes</div>
|
||||
{notesMeta?.slice(0, 10)
|
||||
.map((note: any) => {
|
||||
return <Link key={note.slug} href={`/notes/${note.slug}`} className={`button link`}>{note.title}</Link>
|
||||
{notes?.slice(0, 10)
|
||||
.map(([slug, note]: any) => {
|
||||
return <Link key={slug} href={`/notes/${slug}`} className={`button link`}>{note.title}</Link>
|
||||
})
|
||||
}
|
||||
{
|
||||
notesMeta.length > 10 &&
|
||||
notes.length > 10 &&
|
||||
<div>
|
||||
<Link href='/notes' className='h5'>More...</Link>
|
||||
</div>
|
||||
|
@ -1,23 +1,24 @@
|
||||
import Link from "next/link";
|
||||
import date from "../lib/date";
|
||||
import { IPostMeta } from "../lib/slug";
|
||||
import style from '../styles/recent-posts.module.css';
|
||||
import PostsInfo from '../public/posts.json';
|
||||
|
||||
function RecentPosts({ postsMeta }: { postsMeta: IPostMeta[] }) {
|
||||
if (!postsMeta.length)
|
||||
function RecentPosts() {
|
||||
const posts = Object.entries(PostsInfo);
|
||||
if (!posts.length)
|
||||
return <></>;
|
||||
return (
|
||||
<div className='block'>
|
||||
<div className='h2'>Recent Posts</div>
|
||||
<div className={style.container}>
|
||||
{postsMeta?.slice(0, 10)
|
||||
.map((post: any) => {
|
||||
{posts?.slice(0, 10)
|
||||
.map(([slug, post]: any) => {
|
||||
return <div className={style.block} key={post.slug}>
|
||||
<span className={style.postDate}>
|
||||
{date.toRelativeDate(new Date(post.created_at))}
|
||||
{date.toRelativeDate(new Date(post.otime))}
|
||||
</span>
|
||||
<div className={style.postTitle}>
|
||||
<Link href={`/posts/${post.slug}`}>
|
||||
<Link href={`/posts/${slug}`}>
|
||||
{post.title}
|
||||
</Link>
|
||||
</div>
|
||||
@ -25,7 +26,7 @@ function RecentPosts({ postsMeta }: { postsMeta: IPostMeta[] }) {
|
||||
})}
|
||||
</div>
|
||||
{
|
||||
postsMeta.length > 10 &&
|
||||
posts.length > 10 &&
|
||||
<div className={style.more}>
|
||||
<Link href='/posts' className='h5'>More...</Link>
|
||||
</div>
|
||||
|
@ -1,12 +1,10 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import style from '../styles/title.module.css';
|
||||
|
||||
type propsObj = {
|
||||
name: string,
|
||||
title?: string,
|
||||
ancestors?: Array<{ name: string, path: string }>
|
||||
};
|
||||
import SiteMap from '../public/sitemap.json';
|
||||
import Head from 'next/head';
|
||||
import { SiteSubPages } from '../lib/site';
|
||||
|
||||
function createPathElements(ancestors: Array<{ name: string, path: string }>) {
|
||||
let currentPath = '';
|
||||
@ -21,19 +19,42 @@ function createPathElements(ancestors: Array<{ name: string, path: string }>) {
|
||||
});
|
||||
};
|
||||
|
||||
function Title({ name, title, ancestors }: propsObj) {
|
||||
const pathElements = ancestors && createPathElements(ancestors) || <></>;
|
||||
function Title() {
|
||||
|
||||
const router = useRouter();
|
||||
const pagePath = router.asPath;
|
||||
const splitPath: Array<{ name: string, path: string }> = [];
|
||||
|
||||
let currRoot: SiteSubPages = SiteMap.subpages;
|
||||
let title: string | null = null;
|
||||
if (pagePath !== '/') {
|
||||
const subPaths = pagePath.split('/');
|
||||
for (const p of subPaths.slice(1, subPaths.length)) {
|
||||
splitPath.push({ name: currRoot[p].title, path: p });
|
||||
if (currRoot === undefined
|
||||
|| currRoot[p] === undefined
|
||||
|| currRoot[p].subpages !== undefined)
|
||||
currRoot = currRoot[p].subpages!;
|
||||
}
|
||||
if (splitPath !== undefined && splitPath.length > 0)
|
||||
title = splitPath.pop()!.name;
|
||||
|
||||
}
|
||||
|
||||
const pathElements = splitPath && createPathElements(splitPath) || <></>;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title && `${title} | PaulW.XYZ` || 'PaulW.XYZ'}</title>
|
||||
</Head>
|
||||
<div className={style.container}>
|
||||
<h1 className={style.title}>
|
||||
{title || name}
|
||||
{title || 'PaulW.XYZ'}
|
||||
</h1>
|
||||
</div>
|
||||
<div className={`${style.nav} h1`}>
|
||||
{name
|
||||
? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{name}</>
|
||||
{title
|
||||
? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{title}</>
|
||||
: <>PaulW.XYZ /</>}
|
||||
</div>
|
||||
</>
|
||||
|
@ -75,6 +75,6 @@ function isValid(date: any) {
|
||||
const DateTool = {
|
||||
toRelativeDate,
|
||||
isValid
|
||||
};;
|
||||
};
|
||||
|
||||
export default DateTool;
|
9
lib/read-markdown.ts
Normal file
9
lib/read-markdown.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
export default async function readMarkdown(directory: string, slug: string, withoutTitle: boolean = false): Promise<string> {
|
||||
const content = await readFile(path.join(process.cwd(), directory, `${slug}.md`), 'utf-8');
|
||||
if (withoutTitle)
|
||||
return content.substring(content.indexOf('\n') + 1, content.length);
|
||||
return content;
|
||||
}
|
11
lib/site.ts
Normal file
11
lib/site.ts
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export interface Site {
|
||||
title: string;
|
||||
subpages?: SiteSubPages;
|
||||
mtime?: string;
|
||||
otime?: string;
|
||||
}
|
||||
|
||||
export interface SiteSubPages {
|
||||
[slug: string]: Site;
|
||||
}
|
35
lib/slug.d.ts
vendored
35
lib/slug.d.ts
vendored
@ -1,35 +0,0 @@
|
||||
interface IPost {
|
||||
slug: string;
|
||||
rawslug: string;
|
||||
content: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface INote {
|
||||
slug: string;
|
||||
rawslug: string;
|
||||
content: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface INoteMeta {
|
||||
title: string;
|
||||
slug: string;
|
||||
last_updated?: string;
|
||||
}
|
||||
|
||||
interface IPostMeta {
|
||||
title: string;
|
||||
slug: string;
|
||||
created_at: string;
|
||||
last_updated?: string;
|
||||
}
|
||||
|
||||
export function getAllPosts(filter?: Array<any>): IPost[];
|
||||
export function getAllNotes(filter?: Array<any>): INote[];
|
||||
|
||||
export function getPost(rawslug: string, filter?: Array<any>): IPost;
|
||||
export function getNote(rawslug: string, filter?: Array<any>): INote;
|
||||
|
||||
export function getPostsMeta(): IPostMeta[];
|
||||
export function getNotesMeta(): INoteMeta[];
|
139
lib/slug.js
139
lib/slug.js
@ -1,139 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const matter = require('gray-matter');
|
||||
const { join } = require('path');
|
||||
|
||||
const cacheDir = join(process.cwd(), '.cache');
|
||||
|
||||
function getDir(name) {
|
||||
return join(process.cwd(), name);
|
||||
}
|
||||
|
||||
function getCacheFileName(name) {
|
||||
return join(cacheDir, `${name}.cache.json`)
|
||||
}
|
||||
|
||||
function get(dir, rawslug, filter = []) {
|
||||
const slug = rawslug.replace(/\.md$/, '');
|
||||
const path = join(dir, `${slug}.md`);
|
||||
const file = fs.readFileSync(path, 'utf-8');
|
||||
const { data, content } = matter(file);
|
||||
|
||||
if (data['last_updated'] === undefined)
|
||||
data['last_updated'] = '';
|
||||
|
||||
if (filter.length === 0)
|
||||
return { ...data, content, slug, rawslug };
|
||||
|
||||
let post = {};
|
||||
for (const [_, entry] of filter.entries()) {
|
||||
if (entry === 'slug')
|
||||
post[entry] = slug;
|
||||
|
||||
if (entry === 'rawslug')
|
||||
post[entry] = rawslug;
|
||||
|
||||
if (entry === 'content')
|
||||
post[entry] = content;
|
||||
|
||||
if (typeof data[entry] !== 'undefined') {
|
||||
post[entry] = data[entry]
|
||||
}
|
||||
}
|
||||
return post;
|
||||
}
|
||||
|
||||
function getAllPosts(filter = []) {
|
||||
const files = fs.readdirSync(getDir('posts'));
|
||||
|
||||
return files
|
||||
.filter(c => (!c.match(/^\.]/) && c.match(/\.md$/)))
|
||||
.map(file => {
|
||||
return get(getDir('posts'), file, filter)
|
||||
})
|
||||
.filter(c => (c.title && c.slug && c.created_at && (new Date(c.created_at)).toString() !== 'Invalid Date'))
|
||||
.sort((a, b) => {
|
||||
const dA = new Date(a['created_at']);
|
||||
const dB = new Date(b['created_at']);
|
||||
return dB - dA;
|
||||
});
|
||||
}
|
||||
|
||||
function getAllNotes(filter = []) {
|
||||
const files = fs.readdirSync(getDir('notes'));
|
||||
|
||||
return files
|
||||
.filter(c => (!c.match(/^\.]/) && c.match(/\.md$/)))
|
||||
.map(file => {
|
||||
return get(getDir('notes'), file, filter)
|
||||
})
|
||||
.filter(c => (c.title && c.slug && c.last_updated && (new Date(c.last_updated)).toString() !== 'Invalid Date'))
|
||||
.sort((a, b) => {
|
||||
const dA = new Date(a['last_updated']);
|
||||
const dB = new Date(b['last_updated']);
|
||||
return dB - dA;
|
||||
});
|
||||
}
|
||||
|
||||
const cats = {
|
||||
notes: {
|
||||
name: 'notes',
|
||||
getAll: getAllNotes,
|
||||
filter: ['title', 'slug', 'last_updated'],
|
||||
},
|
||||
posts: {
|
||||
name: 'posts',
|
||||
getAll: getAllPosts,
|
||||
filter: ['title', 'slug', 'created_at', 'last_updated'],
|
||||
}
|
||||
};
|
||||
|
||||
function cacheMeta({name, getAll, filter}) {
|
||||
const items = getAll(filter);
|
||||
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir);
|
||||
}
|
||||
|
||||
fs.writeFile(getCacheFileName(name), JSON.stringify(items), (e) => {
|
||||
if (e)
|
||||
console.error(e);
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function getMeta(cat) {
|
||||
try {
|
||||
const file = fs.readFileSync(getCacheFileName(cat.name), 'utf-8');
|
||||
return JSON.parse(file);
|
||||
} catch (e) {
|
||||
if (cat.name)
|
||||
return cacheMeta(cat);
|
||||
}
|
||||
}
|
||||
|
||||
function getPostsMeta() {
|
||||
return getMeta(cats.posts);
|
||||
};
|
||||
|
||||
function getNotesMeta() {
|
||||
return getMeta(cats.notes);
|
||||
};
|
||||
|
||||
function cache() {
|
||||
Object.entries(cats).map(([_, v]) => {
|
||||
return cacheMeta(v);
|
||||
});
|
||||
}
|
||||
|
||||
const getPost = (s, f) => {return get(getDir('posts'), s, f)};
|
||||
const getNote = (s, f) => {return get(getDir('notes'), s, f)};
|
||||
|
||||
module.exports = {
|
||||
getAllPosts,
|
||||
getAllNotes,
|
||||
getPostsMeta,
|
||||
getNotesMeta,
|
||||
getPost,
|
||||
getNote,
|
||||
cache
|
||||
};
|
@ -4,18 +4,6 @@ module.exports = {
|
||||
defaultLocale: 'en-US'
|
||||
},
|
||||
webpack: (config, _options) => {
|
||||
const { cache } = require('./lib/slug');
|
||||
|
||||
config.plugins.push(
|
||||
{
|
||||
apply: (compiler) => {
|
||||
compiler.hooks.beforeCompile.tap('cachePostDataBC', _ => {
|
||||
cache();
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
|
@ -1,8 +1,4 @@
|
||||
---
|
||||
title: MOS 6502 Microprocessor
|
||||
last_updated: '2022-09-29T02:41:26.738Z'
|
||||
---
|
||||
|
||||
# MOS 6502 Microprocessor
|
||||
<table>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
|
@ -1,7 +1,4 @@
|
||||
---
|
||||
title: Nintendo Switch
|
||||
last_updated: '2022-10-02T23:01:34.000Z'
|
||||
---
|
||||
# Nintendo Switch
|
||||
|
||||
<a href='https://www.nintendo.com/switch' class='link button extern blue'>Official Website</a>
|
||||
<a href='https://developer.nintendo.com/' class='link button extern blue'>Developer Portal</a>
|
||||
|
@ -1,7 +1,4 @@
|
||||
---
|
||||
title: Steam Deck
|
||||
last_updated: '2022-09-29T03:55:40.476Z'
|
||||
---
|
||||
# Steam Deck
|
||||
|
||||
<a href='https://www.steamdeck.com/' class='link button extern blue'>Official Website</a>
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
---
|
||||
title: Steam Client
|
||||
last_updated: '2022-09-29T03:15:58.777Z'
|
||||
---
|
||||
# Steam Client
|
||||
|
||||
<a href='https://store.steampowered.com' class='link button extern blue'>Steam Store</a>
|
||||
<a href='https://developer.valvesoftware.com/wiki/SteamCMD' class='link button extern blue'>SteamCMD</a>
|
||||
|
@ -1,8 +1,4 @@
|
||||
---
|
||||
title: Zilog Z80 Microprocessor
|
||||
last_updated: '2022-09-29T02:41:18.085Z'
|
||||
---
|
||||
|
||||
# Zilog Z80 Microprocessor
|
||||
<table>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
|
8230
package-lock.json
generated
8230
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.3.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"next": "^13.5.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
@ -3,7 +3,9 @@ import Layout from '../components/layout';
|
||||
|
||||
function NotFoundPage() {
|
||||
return (
|
||||
<Layout title='Page Not Found' name='... ??? / 404: Not Found'>
|
||||
<Layout
|
||||
// title='Page Not Found' name='... ??? / 404: Not Found'
|
||||
>
|
||||
<section className='block text center'>
|
||||
<h1>Error 404</h1>
|
||||
<p>
|
||||
|
@ -3,11 +3,10 @@ import ReactMarkdown from 'react-markdown';
|
||||
import ReadmeMd from '../README.md';
|
||||
import License from '../LICENSE.txt';
|
||||
import Layout from '../components/layout';
|
||||
import GitHubProfile from '../components/github-profile';
|
||||
|
||||
function AboutPage({usr}: any) {
|
||||
function AboutPage() {
|
||||
return (
|
||||
<Layout name='About' title='About PaulW.XYZ'>
|
||||
<Layout >
|
||||
<section className='block'>
|
||||
<p>This is a personal website written by <a href='https://github.com/LambdaPaul'>@LambdaPaul</a>.</p>
|
||||
<p>Why did I write this?
|
||||
@ -16,9 +15,6 @@ function AboutPage({usr}: any) {
|
||||
It seems wise to have things up here even though they may not be worthwhile, as many things ultimately are not.</p>
|
||||
<p>Got any questions, concerns, or issues? Contact me via email: <code>lambdapaul [at] pm [dot] me</code>.</p>
|
||||
</section>
|
||||
<section className='block'>
|
||||
{usr && <GitHubProfile user={usr} />}
|
||||
</section>
|
||||
<hr />
|
||||
<section className='block'>
|
||||
<p>Source for this site is available at <a className='button link extern blue' href='https://github.com/LambdaPaul/www'>GitHub / LambdaPaul / www</a></p>
|
||||
@ -38,16 +34,4 @@ function AboutPage({usr}: any) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
try {
|
||||
const res = await fetch('https://api.github.com/users/lambdapaul');
|
||||
const usr = await res.json();
|
||||
return {
|
||||
props: { usr }
|
||||
};
|
||||
} catch (e) {
|
||||
return {props: {}}
|
||||
}
|
||||
}
|
||||
|
||||
export default AboutPage;
|
||||
|
@ -4,36 +4,30 @@ import Layout from '../components/layout';
|
||||
import QuickLinks from '../components/quick-links';
|
||||
import RecentNotes from '../components/recent-notes';
|
||||
import RecentPosts from '../components/recent-posts';
|
||||
import { getNotesMeta, getPostsMeta, INoteMeta, IPostMeta } from '../lib/slug';
|
||||
import RootInfo from '../public/home.json';
|
||||
|
||||
function Nav() {
|
||||
const nav = { 'Posts': '/posts', 'Notes': '/notes', 'About': '/about', };
|
||||
const nav = RootInfo;
|
||||
return (
|
||||
<div className='block' style={{ textAlign: 'center' }}>
|
||||
{
|
||||
Object.entries(nav).map(([k, v], i) => {
|
||||
return <Link key={i} href={v} className='button green'>{k}</Link>
|
||||
Object.entries(nav).map(([slug, info], i) => {
|
||||
return <Link key={i} href={slug} className='button green'>{info.title}</Link>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function HomePage({ postsMeta, notesMeta }: { postsMeta: IPostMeta[], notesMeta: INoteMeta[] }) {
|
||||
function HomePage() {
|
||||
return (
|
||||
<Layout name='' title='PaulW.XYZ'>
|
||||
<Layout>
|
||||
<Nav />
|
||||
<QuickLinks />
|
||||
<RecentNotes notesMeta={notesMeta} />
|
||||
<RecentPosts postsMeta={postsMeta} />
|
||||
<RecentNotes />
|
||||
<RecentPosts />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: { postsMeta: getPostsMeta(), notesMeta: getNotesMeta() }
|
||||
};
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
|
@ -1,12 +1,23 @@
|
||||
import Layout from '../../components/layout';
|
||||
import { getAllNotes, getNote } from '../../lib/slug';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { monokaiSublime as hlTheme } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import readMarkdown from '../../lib/read-markdown';
|
||||
|
||||
function Markdown({content}: any) {
|
||||
import NotesInfo from '../../public/notes.json';
|
||||
|
||||
interface Note {
|
||||
title: string,
|
||||
mtime: string,
|
||||
}
|
||||
|
||||
interface Notes {
|
||||
[slug: string]: Note;
|
||||
}
|
||||
|
||||
function Markdown({ content }: any) {
|
||||
return <ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
@ -36,7 +47,7 @@ function Markdown({content}: any) {
|
||||
|
||||
function Note({ note }: any) {
|
||||
return (<>
|
||||
<Layout name={note.title} title={note.title} ancestors={[{ name: 'Notes', path: 'notes' }]}>
|
||||
<Layout >
|
||||
<section className='block'>
|
||||
<Markdown content={note.content} />
|
||||
</section>
|
||||
@ -46,20 +57,26 @@ function Note({ note }: any) {
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }: any) {
|
||||
const note = getNote(params.note);
|
||||
const note: string = params.note;
|
||||
const notesInfo: Notes = NotesInfo;
|
||||
const noteInfo: Note = notesInfo[note];
|
||||
|
||||
return {
|
||||
props: { note }
|
||||
};
|
||||
props: {
|
||||
note: {
|
||||
...noteInfo,
|
||||
content: await readMarkdown('notes', note, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const notes = getAllNotes();
|
||||
return {
|
||||
paths: notes.map((note: any) => {
|
||||
paths: Object.keys(NotesInfo).map((note: string) => {
|
||||
return {
|
||||
params: {
|
||||
note: note.slug
|
||||
note
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -1,36 +1,40 @@
|
||||
import Link from 'next/link';
|
||||
import Layout from '../../components/layout';
|
||||
import date from '../../lib/date';
|
||||
import { getNotesMeta, INoteMeta } from '../../lib/slug';
|
||||
|
||||
function NotesPage({ notesMeta }: { notesMeta: INoteMeta[] }) {
|
||||
import date from '../../lib/date';
|
||||
import NotesInfo from '../../public/notes.json';
|
||||
|
||||
function NoteEntry(props: { path: string, note: { title: string, mtime: string } }) {
|
||||
return (
|
||||
<Layout name='Notes'>
|
||||
<table>
|
||||
<tbody>
|
||||
{notesMeta && notesMeta.map((note: INoteMeta, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td style={{flex: '1 0 50%'}}>
|
||||
<Link href={`/notes/${note.slug}`}>
|
||||
{note.title}
|
||||
<tr>
|
||||
<td style={{ flex: '1 0 50%' }}>
|
||||
<Link href={props.path}>
|
||||
{props.note.title}
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{fontStyle: 'italic'}}>
|
||||
{note.last_updated && date.toRelativeDate(new Date(note.last_updated))}
|
||||
<td style={{ fontStyle: 'italic' }}>
|
||||
{props.note.mtime && date.toRelativeDate(new Date(props.note.mtime))}
|
||||
</td>
|
||||
</tr>
|
||||
)})}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function NotesPage() {
|
||||
const notes = Object.entries(NotesInfo);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{!notes || notes.length === 0 && <>No notes found</> || <table>
|
||||
<tbody>
|
||||
{notes.map(([slug, note]: any, i: number) => {
|
||||
return <NoteEntry path={`/notes/${slug}`} note={note} key={i} />
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: { notesMeta: getNotesMeta() }
|
||||
};
|
||||
}
|
||||
|
||||
export default NotesPage;
|
@ -1,13 +1,25 @@
|
||||
import Layout from '../../components/layout';
|
||||
import { getAllPosts, getPost } from '../../lib/slug';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import style from '../../styles/post.module.css';
|
||||
import PostsInfo from '../../public/posts.json';
|
||||
import readMarkdown from '../../lib/read-markdown';
|
||||
|
||||
function Post({ post }: any) { // eh
|
||||
interface Post {
|
||||
title: string;
|
||||
mtime: string;
|
||||
otime?: string;
|
||||
}
|
||||
|
||||
interface Posts {
|
||||
[slug: string]: Post
|
||||
}
|
||||
|
||||
function Post({ post }: { post: Post & { content: string, cover?: string } }) {
|
||||
return (<>
|
||||
<Layout removeContainer={true} name={post.title} title={post.title} ancestors={[{ name: 'Posts', path: 'posts' }]}>
|
||||
<Layout removeContainer={true} >
|
||||
{<div className={style.imageBlock}
|
||||
style={{ backgroundImage:
|
||||
style={{
|
||||
backgroundImage:
|
||||
post.cover ?
|
||||
`url(/assets/images/${post.cover})` :
|
||||
'linear-gradient(to bottom right, #565a0f, #08432c 15%, rgb(5, 39, 10) 40%, rgb(0, 22, 46) 80%)'
|
||||
@ -25,20 +37,24 @@ function Post({ post }: any) { // eh
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }: any) {
|
||||
const post = getPost(params.post);
|
||||
|
||||
const postsInfo: Posts = PostsInfo;
|
||||
const post: Post = postsInfo[params.post];
|
||||
return {
|
||||
props: { post }
|
||||
};
|
||||
props: {
|
||||
post: {
|
||||
...post,
|
||||
content: await readMarkdown('posts', params.post, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = getAllPosts();
|
||||
return {
|
||||
paths: posts.map((post: any) => {
|
||||
paths: Object.keys(PostsInfo).map((post: string) => {
|
||||
return {
|
||||
params: {
|
||||
post: post.slug
|
||||
post
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -1,47 +1,53 @@
|
||||
import Link from 'next/link';
|
||||
import Layout from '../../components/layout';
|
||||
import date from '../../lib/date';
|
||||
import { getPostsMeta, IPostMeta } from '../../lib/slug';
|
||||
import PostsInfo from '../../public/posts.json';
|
||||
|
||||
function PostsPage({ postsMeta }: { postsMeta: IPostMeta[] }) {
|
||||
function PostsPage() {
|
||||
return (
|
||||
<Layout name='Posts'>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
postsMeta.length &&
|
||||
postsMeta.map((post: IPostMeta, i) => {
|
||||
return <tr key={i} style={{alignItems: 'center'}}>
|
||||
<td style={{display: 'inline-block', textAlign: 'right', fontSize: '0.9rem'}}>
|
||||
<div style={{fontStyle: 'italics', fontSize: '.8rem'}}>{
|
||||
post.last_updated && `updated ${date.toRelativeDate(new Date(post.last_updated))}`
|
||||
}</div>
|
||||
<div>{ date.toRelativeDate(new Date(post.created_at)) }</div>
|
||||
</td>
|
||||
<td style={{
|
||||
flex: '1 1 60%',
|
||||
alignItems: 'center',
|
||||
fontFamily: `'EB Garamond', 'Garamond', 'Times New Roman', Times, serif`}}>
|
||||
<Link href={`/posts/${post.slug}`} style={{textDecoration: 'none'}}>{post.title}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
}) ||
|
||||
<div className='text center'>
|
||||
<div>**crickets**</div>
|
||||
<div>No posts found...</div>
|
||||
<div><Link href='/' className='link button green back'>Go Home</Link></div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<Layout>
|
||||
{Object.keys(PostsInfo).length && <Posts /> || <NoPosts />}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: { postsMeta: getPostsMeta() }
|
||||
};
|
||||
function NoPosts() {
|
||||
return (<><div className='text center'>
|
||||
<div>**crickets**</div>
|
||||
<div>No posts found...</div>
|
||||
<div><Link href='/' className='link button green back'>Go Home</Link></div>
|
||||
</div></>);
|
||||
}
|
||||
|
||||
function Posts() {
|
||||
const posts = Object.entries(PostsInfo);
|
||||
return (
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
posts.map(([slug, post]: any, i: number) => {
|
||||
return <tr key={i} style={{ alignItems: 'center' }}>
|
||||
<td style={{ display: 'inline-block', textAlign: 'right', fontSize: '0.9rem' }}>
|
||||
<div style={{ fontStyle: 'italics', fontSize: '.8rem' }}>{
|
||||
post.mtime && `Updated ${date.toRelativeDate(new Date(post.mtime))}`
|
||||
}</div>
|
||||
<div>{date.toRelativeDate(new Date(post.otime))}</div>
|
||||
</td>
|
||||
<td style={{
|
||||
flex: '1 1 60%',
|
||||
alignItems: 'center',
|
||||
fontFamily: `'EB Garamond', 'Garamond', 'Times New Roman', Times, serif`
|
||||
}}>
|
||||
<Link href={`/posts/${slug}`} style={{ textDecoration: 'none' }}>{post.title}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default PostsPage;
|
||||
|
31
pages/sitemap.tsx
Normal file
31
pages/sitemap.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import Link from 'next/link';
|
||||
import Layout from '../components/layout';
|
||||
import { Site } from '../lib/site';
|
||||
import SiteMap from '../public/sitemap.json';
|
||||
|
||||
function traverseMap(head: Site, cwd = '', depth = 0) {
|
||||
if (head.subpages === undefined)
|
||||
return [];
|
||||
let elements = [];
|
||||
for (const [slug, info] of Object.entries(head.subpages)) {
|
||||
const path = `${cwd}/${slug}`;
|
||||
const children = (<><ul> {traverseMap(info, path, depth + 1)}</ul></>);
|
||||
elements.push(<>
|
||||
<li>
|
||||
<Link className='button' href={path}>{info.title}</Link> {children}
|
||||
</li>
|
||||
</>);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
function SiteMapPage() {
|
||||
|
||||
|
||||
return <Layout>
|
||||
<ul>{traverseMap(SiteMap)}</ul>
|
||||
</Layout>;
|
||||
}
|
||||
|
||||
export default SiteMapPage;
|
||||
|
14
public/home.json
Normal file
14
public/home.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"posts": {
|
||||
"title": "Posts"
|
||||
},
|
||||
"notes": {
|
||||
"title": "Notes"
|
||||
},
|
||||
"about": {
|
||||
"title": "About"
|
||||
},
|
||||
"sitemap": {
|
||||
"title": "Site Map"
|
||||
}
|
||||
}
|
1
public/notes.json
Normal file
1
public/notes.json
Normal file
@ -0,0 +1 @@
|
||||
{"mos-6502":{"title":"MOS 6502 Microprocessor","mtime":"2023-10-29T18:05:52.440Z"},"nintendo-switch":{"title":"Nintendo Switch","mtime":"2023-10-29T18:06:23.793Z"},"steam-deck":{"title":"Steam Deck","mtime":"2023-10-29T18:06:45.197Z"},"steam":{"title":"Steam Client","mtime":"2023-10-29T18:06:53.897Z"},"zilog-z80":{"title":"Zilog Z80 Microprocessor","mtime":"2023-10-29T18:07:08.580Z"}}
|
1
public/posts.json
Normal file
1
public/posts.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
public/sitemap.json
Normal file
1
public/sitemap.json
Normal file
@ -0,0 +1 @@
|
||||
{"title":"PaulW.XYZ","subpages":{"posts":{"title":"Posts","subpages":{}},"notes":{"title":"Notes","subpages":{"mos-6502":{"title":"MOS 6502 Microprocessor","mtime":"2023-10-29T18:05:52.440Z"},"nintendo-switch":{"title":"Nintendo Switch","mtime":"2023-10-29T18:06:23.793Z"},"steam-deck":{"title":"Steam Deck","mtime":"2023-10-29T18:06:45.197Z"},"steam":{"title":"Steam Client","mtime":"2023-10-29T18:06:53.897Z"},"zilog-z80":{"title":"Zilog Z80 Microprocessor","mtime":"2023-10-29T18:07:08.580Z"}}},"about":{"title":"About"},"sitemap":{"title":"Site Map"}}}
|
136
scripts/generate-metadata.js
Normal file
136
scripts/generate-metadata.js
Normal file
@ -0,0 +1,136 @@
|
||||
const fs = require('fs/promises');
|
||||
const { createReadStream, write } = require('fs');
|
||||
const path = require('path');
|
||||
const readline = require('readline/promises');
|
||||
const { info } = require('console');
|
||||
|
||||
async function readFirstLines(filePath, lineCount = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const stream = createReadStream(filePath, 'utf-8');
|
||||
const rl = readline.createInterface({ input: stream });
|
||||
let counter = 0;
|
||||
const lines = [];
|
||||
rl.on('line', (line) => {
|
||||
counter++;
|
||||
lines.push(line);
|
||||
if (counter >= lineCount) {
|
||||
rl.close();
|
||||
rl.removeAllListeners();
|
||||
}
|
||||
});
|
||||
rl.on('close', () => {
|
||||
resolve(lines)
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getTitle(filePath) {
|
||||
const firstLines = await readFirstLines(filePath);
|
||||
if (firstLines === undefined || firstLines.length === 0)
|
||||
return null;
|
||||
let title = firstLines[0];
|
||||
if (title.substring(0, 2) !== '# ')
|
||||
return null;
|
||||
title = title
|
||||
.substring(1, firstLines[0].length)
|
||||
.trim();
|
||||
if (title.length < 3)
|
||||
return null;
|
||||
return title;
|
||||
}
|
||||
|
||||
async function getMarkdownMetadata(dir) {
|
||||
const dirPath = path.join(process.cwd(), dir);
|
||||
const files = (await fs.readdir(dirPath, 'utf-8'))
|
||||
.filter((file) => {
|
||||
return /^[^.].*.md$/.test(file);
|
||||
})
|
||||
|
||||
|
||||
const out = {};
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const title = await getTitle(filePath);
|
||||
if (title === null)
|
||||
continue;
|
||||
|
||||
const slug = file.replace(/\.md$/, '');
|
||||
// const pagePath = path.join('/', dir, slug);
|
||||
out[slug] = {
|
||||
title: title,
|
||||
// path: pagePath,
|
||||
mtime: (await fs.stat(filePath)).mtime,
|
||||
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function readFilesMetadata(dir) {
|
||||
const filePath = jsonFilePath(dir);
|
||||
try {
|
||||
const fileContent = await fs.readFile(filePath, 'utf-8');
|
||||
const metadata = JSON.parse(fileContent);
|
||||
return metadata;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function writeFilesMetadata(filePath, metadata) {
|
||||
try {
|
||||
await fs.writeFile(filePath, JSON.stringify(metadata), 'utf-8');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function jsonFilePath(dir) {
|
||||
return path.join(process.cwd(), 'public', `${dir}.json`);
|
||||
}
|
||||
|
||||
async function generateNotesMetadata() {
|
||||
const dir = 'notes';
|
||||
await writeFilesMetadata(jsonFilePath(dir), await getMarkdownMetadata(dir));
|
||||
}
|
||||
|
||||
async function generatePostsMetadata() {
|
||||
const dir = 'posts';
|
||||
const currMetadata = await readFilesMetadata(dir);
|
||||
const generatedMetadata = await getMarkdownMetadata(dir);
|
||||
const newMetadata = {};
|
||||
|
||||
for (const [name, data] of Object.entries(generatedMetadata)) {
|
||||
let otime;
|
||||
if (currMetadata[name] !== undefined && currMetadata[name].otime !== undefined)
|
||||
otime = currMetadata[name].otime
|
||||
else
|
||||
otime = data.mtime;
|
||||
|
||||
newMetadata[name] = { ...data, otime }
|
||||
}
|
||||
await writeFilesMetadata(jsonFilePath(dir), newMetadata);
|
||||
}
|
||||
|
||||
async function generateSiteMap() {
|
||||
await generateNotesMetadata();
|
||||
await generatePostsMetadata();
|
||||
|
||||
const sitemap = {
|
||||
title: 'PaulW.XYZ',
|
||||
subpages: await readFilesMetadata('home')
|
||||
};
|
||||
|
||||
const pages = ['posts', 'notes'];
|
||||
for (const page of pages) {
|
||||
sitemap.subpages[page].subpages = await readFilesMetadata(page);
|
||||
}
|
||||
|
||||
await writeFilesMetadata(jsonFilePath('sitemap'), sitemap);
|
||||
}
|
||||
|
||||
generateSiteMap();
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"target": "es2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
|
Loading…
Reference in New Issue
Block a user