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
|
.DS_Store
|
||||||
.cache/
|
.cache/
|
||||||
*.bun
|
*.bun
|
||||||
**/*.wip.md
|
**/.*.md
|
||||||
.env
|
.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';
|
import Title from './title';
|
||||||
|
|
||||||
type ChildrenType = JSX.Element | Array<ChildrenType>;
|
type ChildrenType = JSX.Element | Array<ChildrenType>;
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
name: string,
|
|
||||||
title?: string,
|
|
||||||
ancestors?: Array<{ name: string, path: string }>
|
|
||||||
children?: ChildrenType,
|
children?: ChildrenType,
|
||||||
removeContainer?: boolean,
|
removeContainer?: boolean,
|
||||||
};
|
};
|
||||||
@ -22,8 +18,7 @@ function Container(props: {children?: ChildrenType, ignore?: boolean}) {
|
|||||||
function Layout(props : LayoutProps) {
|
function Layout(props : LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Meta name={props.name} ancestors={props.ancestors} />
|
<Title />
|
||||||
<Title title={props.title} name={props.name} ancestors={props.ancestors} />
|
|
||||||
<Container ignore={props.removeContainer}>{props.children}</Container>
|
<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(
|
export function mapChild(
|
||||||
obj: listItem | string,
|
obj: listItem | string,
|
||||||
level: number,
|
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 Link from 'next/link';
|
||||||
import Pages from '../public/pages.json';
|
import Pages from '../public/external.json';
|
||||||
|
|
||||||
function QuickLinks() {
|
function QuickLinks() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import Link from "next/link";
|
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 (
|
return (
|
||||||
<div className='block'>
|
<div className='block'>
|
||||||
<div className='h2'>Recent Notes</div>
|
<div className='h2'>Recent Notes</div>
|
||||||
{notesMeta?.slice(0, 10)
|
{notes?.slice(0, 10)
|
||||||
.map((note: any) => {
|
.map(([slug, note]: any) => {
|
||||||
return <Link key={note.slug} href={`/notes/${note.slug}`} className={`button link`}>{note.title}</Link>
|
return <Link key={slug} href={`/notes/${slug}`} className={`button link`}>{note.title}</Link>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
notesMeta.length > 10 &&
|
notes.length > 10 &&
|
||||||
<div>
|
<div>
|
||||||
<Link href='/notes' className='h5'>More...</Link>
|
<Link href='/notes' className='h5'>More...</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import date from "../lib/date";
|
import date from "../lib/date";
|
||||||
import { IPostMeta } from "../lib/slug";
|
|
||||||
import style from '../styles/recent-posts.module.css';
|
import style from '../styles/recent-posts.module.css';
|
||||||
|
import PostsInfo from '../public/posts.json';
|
||||||
|
|
||||||
function RecentPosts({ postsMeta }: { postsMeta: IPostMeta[] }) {
|
function RecentPosts() {
|
||||||
if (!postsMeta.length)
|
const posts = Object.entries(PostsInfo);
|
||||||
|
if (!posts.length)
|
||||||
return <></>;
|
return <></>;
|
||||||
return (
|
return (
|
||||||
<div className='block'>
|
<div className='block'>
|
||||||
<div className='h2'>Recent Posts</div>
|
<div className='h2'>Recent Posts</div>
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
{postsMeta?.slice(0, 10)
|
{posts?.slice(0, 10)
|
||||||
.map((post: any) => {
|
.map(([slug, post]: any) => {
|
||||||
return <div className={style.block} key={post.slug}>
|
return <div className={style.block} key={post.slug}>
|
||||||
<span className={style.postDate}>
|
<span className={style.postDate}>
|
||||||
{date.toRelativeDate(new Date(post.created_at))}
|
{date.toRelativeDate(new Date(post.otime))}
|
||||||
</span>
|
</span>
|
||||||
<div className={style.postTitle}>
|
<div className={style.postTitle}>
|
||||||
<Link href={`/posts/${post.slug}`}>
|
<Link href={`/posts/${slug}`}>
|
||||||
{post.title}
|
{post.title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -25,7 +26,7 @@ function RecentPosts({ postsMeta }: { postsMeta: IPostMeta[] }) {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
postsMeta.length > 10 &&
|
posts.length > 10 &&
|
||||||
<div className={style.more}>
|
<div className={style.more}>
|
||||||
<Link href='/posts' className='h5'>More...</Link>
|
<Link href='/posts' className='h5'>More...</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
import style from '../styles/title.module.css';
|
import style from '../styles/title.module.css';
|
||||||
|
import SiteMap from '../public/sitemap.json';
|
||||||
type propsObj = {
|
import Head from 'next/head';
|
||||||
name: string,
|
import { SiteSubPages } from '../lib/site';
|
||||||
title?: string,
|
|
||||||
ancestors?: Array<{ name: string, path: string }>
|
|
||||||
};
|
|
||||||
|
|
||||||
function createPathElements(ancestors: Array<{ name: string, path: string }>) {
|
function createPathElements(ancestors: Array<{ name: string, path: string }>) {
|
||||||
let currentPath = '';
|
let currentPath = '';
|
||||||
@ -21,19 +19,42 @@ function createPathElements(ancestors: Array<{ name: string, path: string }>) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function Title({ name, title, ancestors }: propsObj) {
|
function Title() {
|
||||||
const pathElements = ancestors && createPathElements(ancestors) || <></>;
|
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{title && `${title} | PaulW.XYZ` || 'PaulW.XYZ'}</title>
|
||||||
|
</Head>
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<h1 className={style.title}>
|
<h1 className={style.title}>
|
||||||
{title || name}
|
{title || 'PaulW.XYZ'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${style.nav} h1`}>
|
<div className={`${style.nav} h1`}>
|
||||||
{name
|
{title
|
||||||
? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{name}</>
|
? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{title}</>
|
||||||
: <>PaulW.XYZ /</>}
|
: <>PaulW.XYZ /</>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -75,6 +75,6 @@ function isValid(date: any) {
|
|||||||
const DateTool = {
|
const DateTool = {
|
||||||
toRelativeDate,
|
toRelativeDate,
|
||||||
isValid
|
isValid
|
||||||
};;
|
};
|
||||||
|
|
||||||
export default DateTool;
|
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'
|
defaultLocale: 'en-US'
|
||||||
},
|
},
|
||||||
webpack: (config, _options) => {
|
webpack: (config, _options) => {
|
||||||
const { cache } = require('./lib/slug');
|
|
||||||
|
|
||||||
config.plugins.push(
|
|
||||||
{
|
|
||||||
apply: (compiler) => {
|
|
||||||
compiler.hooks.beforeCompile.tap('cachePostDataBC', _ => {
|
|
||||||
cache();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
config.module.rules.push(
|
config.module.rules.push(
|
||||||
{
|
{
|
||||||
test: /\.ya?ml$/,
|
test: /\.ya?ml$/,
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
---
|
# MOS 6502 Microprocessor
|
||||||
title: MOS 6502 Microprocessor
|
|
||||||
last_updated: '2022-09-29T02:41:26.738Z'
|
|
||||||
---
|
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
---
|
# Nintendo Switch
|
||||||
title: Nintendo Switch
|
|
||||||
last_updated: '2022-10-02T23:01:34.000Z'
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href='https://www.nintendo.com/switch' class='link button extern blue'>Official Website</a>
|
<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>
|
<a href='https://developer.nintendo.com/' class='link button extern blue'>Developer Portal</a>
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
---
|
# Steam Deck
|
||||||
title: Steam Deck
|
|
||||||
last_updated: '2022-09-29T03:55:40.476Z'
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href='https://www.steamdeck.com/' class='link button extern blue'>Official Website</a>
|
<a href='https://www.steamdeck.com/' class='link button extern blue'>Official Website</a>
|
||||||
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
---
|
# Steam Client
|
||||||
title: Steam Client
|
|
||||||
last_updated: '2022-09-29T03:15:58.777Z'
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href='https://store.steampowered.com' class='link button extern blue'>Steam Store</a>
|
<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>
|
<a href='https://developer.valvesoftware.com/wiki/SteamCMD' class='link button extern blue'>SteamCMD</a>
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
---
|
# Zilog Z80 Microprocessor
|
||||||
title: Zilog Z80 Microprocessor
|
|
||||||
last_updated: '2022-09-29T02:41:18.085Z'
|
|
||||||
---
|
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<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": {
|
"dependencies": {
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"gray-matter": "^4.0.3",
|
|
||||||
"js-yaml-loader": "^1.2.2",
|
"js-yaml-loader": "^1.2.2",
|
||||||
"next": "^13.5.1",
|
"next": "^13.5.1",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
@ -3,7 +3,9 @@ import Layout from '../components/layout';
|
|||||||
|
|
||||||
function NotFoundPage() {
|
function NotFoundPage() {
|
||||||
return (
|
return (
|
||||||
<Layout title='Page Not Found' name='... ??? / 404: Not Found'>
|
<Layout
|
||||||
|
// title='Page Not Found' name='... ??? / 404: Not Found'
|
||||||
|
>
|
||||||
<section className='block text center'>
|
<section className='block text center'>
|
||||||
<h1>Error 404</h1>
|
<h1>Error 404</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -3,11 +3,10 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import ReadmeMd from '../README.md';
|
import ReadmeMd from '../README.md';
|
||||||
import License from '../LICENSE.txt';
|
import License from '../LICENSE.txt';
|
||||||
import Layout from '../components/layout';
|
import Layout from '../components/layout';
|
||||||
import GitHubProfile from '../components/github-profile';
|
|
||||||
|
|
||||||
function AboutPage({usr}: any) {
|
function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<Layout name='About' title='About PaulW.XYZ'>
|
<Layout >
|
||||||
<section className='block'>
|
<section className='block'>
|
||||||
<p>This is a personal website written by <a href='https://github.com/LambdaPaul'>@LambdaPaul</a>.</p>
|
<p>This is a personal website written by <a href='https://github.com/LambdaPaul'>@LambdaPaul</a>.</p>
|
||||||
<p>Why did I write this?
|
<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>
|
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>
|
<p>Got any questions, concerns, or issues? Contact me via email: <code>lambdapaul [at] pm [dot] me</code>.</p>
|
||||||
</section>
|
</section>
|
||||||
<section className='block'>
|
|
||||||
{usr && <GitHubProfile user={usr} />}
|
|
||||||
</section>
|
|
||||||
<hr />
|
<hr />
|
||||||
<section className='block'>
|
<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>
|
<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;
|
export default AboutPage;
|
||||||
|
@ -4,36 +4,30 @@ import Layout from '../components/layout';
|
|||||||
import QuickLinks from '../components/quick-links';
|
import QuickLinks from '../components/quick-links';
|
||||||
import RecentNotes from '../components/recent-notes';
|
import RecentNotes from '../components/recent-notes';
|
||||||
import RecentPosts from '../components/recent-posts';
|
import RecentPosts from '../components/recent-posts';
|
||||||
import { getNotesMeta, getPostsMeta, INoteMeta, IPostMeta } from '../lib/slug';
|
import RootInfo from '../public/home.json';
|
||||||
|
|
||||||
function Nav() {
|
function Nav() {
|
||||||
const nav = { 'Posts': '/posts', 'Notes': '/notes', 'About': '/about', };
|
const nav = RootInfo;
|
||||||
return (
|
return (
|
||||||
<div className='block' style={{ textAlign: 'center' }}>
|
<div className='block' style={{ textAlign: 'center' }}>
|
||||||
{
|
{
|
||||||
Object.entries(nav).map(([k, v], i) => {
|
Object.entries(nav).map(([slug, info], i) => {
|
||||||
return <Link key={i} href={v} className='button green'>{k}</Link>
|
return <Link key={i} href={slug} className='button green'>{info.title}</Link>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomePage({ postsMeta, notesMeta }: { postsMeta: IPostMeta[], notesMeta: INoteMeta[] }) {
|
function HomePage() {
|
||||||
return (
|
return (
|
||||||
<Layout name='' title='PaulW.XYZ'>
|
<Layout>
|
||||||
<Nav />
|
<Nav />
|
||||||
<QuickLinks />
|
<QuickLinks />
|
||||||
<RecentNotes notesMeta={notesMeta} />
|
<RecentNotes />
|
||||||
<RecentPosts postsMeta={postsMeta} />
|
<RecentPosts />
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps() {
|
|
||||||
return {
|
|
||||||
props: { postsMeta: getPostsMeta(), notesMeta: getNotesMeta() }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HomePage;
|
export default HomePage;
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import Layout from '../../components/layout';
|
import Layout from '../../components/layout';
|
||||||
import { getAllNotes, getNote } from '../../lib/slug';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||||
import { monokaiSublime as hlTheme } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
import { monokaiSublime as hlTheme } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import rehypeRaw from 'rehype-raw';
|
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
|
return <ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
rehypePlugins={[rehypeRaw]}
|
rehypePlugins={[rehypeRaw]}
|
||||||
@ -36,7 +47,7 @@ function Markdown({content}: any) {
|
|||||||
|
|
||||||
function Note({ note }: any) {
|
function Note({ note }: any) {
|
||||||
return (<>
|
return (<>
|
||||||
<Layout name={note.title} title={note.title} ancestors={[{ name: 'Notes', path: 'notes' }]}>
|
<Layout >
|
||||||
<section className='block'>
|
<section className='block'>
|
||||||
<Markdown content={note.content} />
|
<Markdown content={note.content} />
|
||||||
</section>
|
</section>
|
||||||
@ -46,20 +57,26 @@ function Note({ note }: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ params }: 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 {
|
return {
|
||||||
props: { note }
|
props: {
|
||||||
};
|
note: {
|
||||||
|
...noteInfo,
|
||||||
|
content: await readMarkdown('notes', note, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const notes = getAllNotes();
|
|
||||||
return {
|
return {
|
||||||
paths: notes.map((note: any) => {
|
paths: Object.keys(NotesInfo).map((note: string) => {
|
||||||
return {
|
return {
|
||||||
params: {
|
params: {
|
||||||
note: note.slug
|
note
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -1,36 +1,40 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Layout from '../../components/layout';
|
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 (
|
return (
|
||||||
<Layout name='Notes'>
|
<tr>
|
||||||
<table>
|
<td style={{ flex: '1 0 50%' }}>
|
||||||
<tbody>
|
<Link href={props.path}>
|
||||||
{notesMeta && notesMeta.map((note: INoteMeta, i) => {
|
{props.note.title}
|
||||||
return (
|
|
||||||
<tr key={i}>
|
|
||||||
<td style={{flex: '1 0 50%'}}>
|
|
||||||
<Link href={`/notes/${note.slug}`}>
|
|
||||||
{note.title}
|
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td style={{fontStyle: 'italic'}}>
|
<td style={{ fontStyle: 'italic' }}>
|
||||||
{note.last_updated && date.toRelativeDate(new Date(note.last_updated))}
|
{props.note.mtime && date.toRelativeDate(new Date(props.note.mtime))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>}
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps() {
|
|
||||||
return {
|
|
||||||
props: { notesMeta: getNotesMeta() }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NotesPage;
|
export default NotesPage;
|
@ -1,13 +1,25 @@
|
|||||||
import Layout from '../../components/layout';
|
import Layout from '../../components/layout';
|
||||||
import { getAllPosts, getPost } from '../../lib/slug';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import style from '../../styles/post.module.css';
|
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 (<>
|
return (<>
|
||||||
<Layout removeContainer={true} name={post.title} title={post.title} ancestors={[{ name: 'Posts', path: 'posts' }]}>
|
<Layout removeContainer={true} >
|
||||||
{<div className={style.imageBlock}
|
{<div className={style.imageBlock}
|
||||||
style={{ backgroundImage:
|
style={{
|
||||||
|
backgroundImage:
|
||||||
post.cover ?
|
post.cover ?
|
||||||
`url(/assets/images/${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%)'
|
'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) {
|
export async function getStaticProps({ params }: any) {
|
||||||
const post = getPost(params.post);
|
const postsInfo: Posts = PostsInfo;
|
||||||
|
const post: Post = postsInfo[params.post];
|
||||||
return {
|
return {
|
||||||
props: { post }
|
props: {
|
||||||
};
|
post: {
|
||||||
|
...post,
|
||||||
|
content: await readMarkdown('posts', params.post, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = getAllPosts();
|
|
||||||
return {
|
return {
|
||||||
paths: posts.map((post: any) => {
|
paths: Object.keys(PostsInfo).map((post: string) => {
|
||||||
return {
|
return {
|
||||||
params: {
|
params: {
|
||||||
post: post.slug
|
post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -1,47 +1,53 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Layout from '../../components/layout';
|
import Layout from '../../components/layout';
|
||||||
import date from '../../lib/date';
|
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 (
|
return (
|
||||||
<Layout name='Posts'>
|
<Layout>
|
||||||
<table>
|
{Object.keys(PostsInfo).length && <Posts /> || <NoPosts />}
|
||||||
<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>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps() {
|
function NoPosts() {
|
||||||
return {
|
return (<><div className='text center'>
|
||||||
props: { postsMeta: getPostsMeta() }
|
<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;
|
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": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "es2017",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable",
|
"dom.iterable",
|
||||||
|
Loading…
Reference in New Issue
Block a user