More changes to structure and UI

This commit is contained in:
Paul W. 2022-04-27 21:55:18 -04:00
parent 2d4a2809b4
commit 6ba7d561fb
22 changed files with 257 additions and 218 deletions

2
.gitignore vendored
View File

@ -2,4 +2,4 @@ node_modules/
dist/ dist/
.next/ .next/
.DS_Store .DS_Store
public/posts.json .cache/

View File

@ -1,22 +1,24 @@
import FuzzyBar from './fuzzy-bar';
import Meta from './meta'; import Meta from './meta';
import Title from './title'; import Title from './title';
// import FuzzyBar from './fuzzy-bar';
type layoutProps = { type ChildrenType = JSX.Element | Array<ChildrenType>;
type LayoutProps = {
name: string, name: string,
title?: string, title?: string,
children?: JSX.Element | JSX.Element[],
ancestors?: Array<{ name: string, path: string }> ancestors?: Array<{ name: string, path: string }>
children?: ChildrenType,
}; };
function Layout(props: layoutProps) { function Layout({ name, title, children, ancestors }: LayoutProps) {
return ( return (
<> <>
<Meta name={props.name} ancestors={props.ancestors} /> <Meta name={name} ancestors={ancestors} />
<Title title={props.title} name={props.name} ancestors={props.ancestors} /> <Title title={title} name={name} ancestors={ancestors} />
<FuzzyBar /> {/* <FuzzyBar /> */}
<div className='container'> <div className='container'>
{props.children} {children}
</div> </div>
</> </>
); );

View File

@ -80,7 +80,7 @@ export function mapChild(obj: listItem | string, level: number) {
title = React.createElement('strong', {}, obj.title); title = React.createElement('strong', {}, obj.title);
return ( return (
<section className={level < 4 ? 'block' : ''}> <section className={level < 4 && `block ${style.block}` || ''}>
{title} {title}
{obj.description ? <p className={style.desc}>{obj.description}</p> : <></>} {obj.description ? <p className={style.desc}>{obj.description}</p> : <></>}
<div> <div>

View File

@ -1,16 +1,17 @@
import Head from 'next/head'; import Head from 'next/head';
export default function Meta(props: { name: string, ancestors?: Array<{ name: string, path: string }> }) { function Meta({name, ancestors}
: {name: string, ancestors?: Array<{ name: string, path: string }> }) {
const path = () => { const path = () => {
if (!props.ancestors) if (!ancestors)
return props.name; return name;
let path = ''; let path = '';
props.ancestors.map((obj) => { ancestors.map((obj) => {
path = `${path}${obj.name} /`; path = `${path}${obj.name} /`;
}); });
return `${path} ${props.name}`; return `${path} ${name}`;
}; };
return ( return (
@ -19,3 +20,5 @@ export default function Meta(props: { name: string, ancestors?: Array<{ name: st
</Head> </Head>
); );
} }
export default Meta;

View File

@ -0,0 +1,21 @@
import Link from 'next/link';
import Pages from '../public/pages.json';
import style from '../styles/quick-links.module.css'
function QuickLinks() {
return (<>
<div className='h2'>Quick Links</div>
{
Pages.map((obj, i) => {
const extern = obj.link.match(/^http/) && `blue ${style.blueButton}` || '';
return (
<Link key={i} href={obj.link}>
<a className={`${extern} ${style.button} button`}>{obj.title}</a>
</Link>
);
})
}
</>);
}
export default QuickLinks;

View File

@ -0,0 +1,34 @@
import Link from "next/link";
import date from "../util/date";
import { PostMeta } from "../util/slug";
import style from '../styles/recent-posts.module.css';
function RecentPosts({ postsMeta }: { postsMeta: PostMeta[] }) {
return (<>
<div className='h2'>Recent Posts</div>
<div className={style.container}>
{postsMeta?.slice(0, 10)
.map((post: any) => {
return <div className={style.block} key={post.slug}>
<Link href={`/posts/${post.slug}`}>
<a className={`${style.postTitle} h5`}>{post.title}</a>
</Link>
<span className={style.postDate}>
{date.prettyPrint(new Date(post.created_at))}
</span>
</div>
})}
</div>
{
postsMeta.length > 10 &&
<div className={style.more}>
<Link href='/posts'>
<a className='h5'>More...</a>
</Link>
</div>
}
</>
);
}
export default RecentPosts;

View File

@ -7,42 +7,33 @@ type propsObj = {
ancestors?: Array<{ name: string, path: string }> ancestors?: Array<{ name: string, path: string }>
}; };
function Title(props: propsObj) { function createPathElements(ancestors: Array<{ name: string, path: string }>) {
let currentPath = '';
const path = () => { return ancestors.map((ancestor) => {
if (!props.ancestors) currentPath += `/${ancestor.path}`
return (<></>); return (
<>
let currentPath = ''; <Link href={currentPath}>
return (<> <a>{ancestor.name}</a>
{ </Link>
props.ancestors.map(ancestor => { <> / </>
currentPath += `/${ancestor.path}` </>
return (
<>
<Link href={currentPath} key=''>
<a>{ancestor.name}</a>
</Link>
<> / </>
</>
);
})
}
</>
); );
}; });
};
function Title({ name, title, ancestors }: propsObj) {
const pathElements = ancestors && createPathElements(ancestors) || <></>;
return ( return (
<> <>
<h1 className={style.container}> <h1 className={style.container}>
{props.title || props.name} {title || name}
</h1> </h1>
<div className={style.nav + ' h1'}> <div className={`${style.nav} h1`}>
{ {name
props.name === '' ? <><Link href='/'><a>PaulW.XYZ</a></Link> / {pathElements}{name}</>
? <>PaulW.XYZ / {props.name}</> : <>PaulW.XYZ /</>}
: <><Link href='/'><a>PaulW.XYZ</a></Link> / {path()}{props.name}</>
}
</div> </div>
</> </>
); );

View File

@ -6,13 +6,13 @@ module.exports = {
webpack: (config, options) => { webpack: (config, options) => {
config.experiments = { asset: true }; config.experiments = { asset: true };
const { cachePostsMeta } = require('./util/slug'); const { cache } = require('./util/slug');
config.plugins.push( config.plugins.push(
{ {
apply: (compiler) => { apply: (compiler) => {
compiler.hooks.initialize.tap('cachePostDataBC', _ => { compiler.hooks.beforeCompile.tap('cachePostDataBC', _ => {
cachePostsMeta(); cache();
}); });
} }
} }

View File

@ -1,46 +1,17 @@
import Link from 'next/link';
import React from 'react'; import React from 'react';
import Layout from '../components/layout'; import Layout from '../components/layout';
import Pages from '../public/pages.json'; import QuickLinks from '../components/quick-links';
import style from '../styles/home.module.css'; import RecentPosts from '../components/recent-posts';
import prettyDatePrint from '../util/pretty-date';
import { getPostsMeta, PostMeta } from '../util/slug'; import { getPostsMeta, PostMeta } from '../util/slug';
function HomePage(props: { postsMeta: PostMeta[] }) { function HomePage({ postsMeta }: { postsMeta: PostMeta[] }) {
props.postsMeta.sort((x, y) => { return (x.title).localeCompare(y.title) });
return ( return (
<Layout name='' title='PaulW.XYZ'> <Layout name='' title='PaulW.XYZ'>
<section className='block'> <section className='block'>
<div className='h2'>Welcome!</div> <QuickLinks />
{
Pages.map(obj => {
return <span key={obj.link}>
<Link href={obj.link}>
{
obj.link.match(/^http/)
? <a className={`button blue ${style.button} ${style.blueButton}`}>{obj.title}</a>
: <a className={`${style.button} button`}>{obj.title}</a>
}
</Link>
</span>
})
}
</section> </section>
<section className='block'> <section className='block'>
<table style={{ width: '100%' }}> <RecentPosts postsMeta={postsMeta} />
<th className='h2'>Posts</th> <th>Posted</th>
{props.postsMeta?.map((post: any) => {
return <tr key={post.slug}>
<td className='h5'>
<Link href={`posts/${post.slug}`}>
{post.title}
</Link>
</td>
<td>{prettyDatePrint(new Date(post.created_at))}</td>
</tr>
})}
</table>
</section> </section>
</Layout> </Layout>
) )

View File

@ -1,70 +1,19 @@
import React, { ReactElement } from 'react'; import React, { ReactElement } from 'react';
import Layout from '../components/layout'; import Layout from '../components/layout';
import { mapChild, toListItem } from '../components/lists';
import pl from '../public/playlists.yaml'; import pl from '../public/playlists.yaml';
type listItem = {
children?: listItem[];
url?: string;
title: string;
};
function toListItem(record: Record<string, any>): listItem | null {
if (!record.title)
return null;
let children: listItem[] = [];
if (record.children)
for (const child of record.children) {
const lChild = toListItem(child);
if (lChild)
children.push(lChild);
}
return {
title: record.title,
url: record.url,
children: children.length ? children : undefined,
};
}
const list: listItem[] = [];
function mapChild(obj: listItem, level: number) {
if (obj.url)
return <li key=''><a href={obj.url}>{obj.title}</a></li>
if (!obj.children)
return <></> // ignore playlists without links
let title: ReactElement;
if (level >= 0 && level <= 3)
title = React.createElement(`h${level + 3}`, {}, obj.title);
else
title = React.createElement('strong', {}, obj.title);
return (
<>
{title}
<ul>
{obj.children.map(l => mapChild(l, level + 1))}
</ul>
</>
);
}
function Playlists() { function Playlists() {
return ( return (
<Layout name='Playlists'> <Layout name='Playlists'>
<section className='block'> <h2>Music</h2>
<h2>Music</h2> {
{ pl.map((item: Record<string, any>) => {
pl.map((item: Record<string, any>) => { const lItem = toListItem(item)
const lItem = toListItem(item) if (lItem)
if (lItem) return mapChild(lItem, 0)
return mapChild(lItem, 0) })
}) }
}
</section>
</Layout> </Layout>
); );
} }

View File

@ -1,28 +1,30 @@
import Link from 'next/link'; import Link from 'next/link';
import React from 'react';
import Layout from '../../components/layout'; import Layout from '../../components/layout';
import prettyDatePrint from '../../util/pretty-date'; import date from '../../util/date';
import { getPostsMeta, PostMeta } from '../../util/slug'; import { getPostsMeta, PostMeta } from '../../util/slug';
function HomePage(props: {postsMeta: PostMeta[]}) { function HomePage({ postsMeta }: { postsMeta: PostMeta[] }) {
props.postsMeta.sort((x: any, y: any) => { return (x as any).title.localeCompare((y as any).title) });
// todo: create a table-like user interface // todo: create a table-like user interface
return ( return ( // wow this is horrible
<Layout name='Posts'> <Layout name='Posts'>
<>
<section className='h4 block'> <section className='h4 block'>
Post Name <span style={{float: 'right', margin: 'auto 1rem'}}> Created on </span> <span style={{float: 'right', margin: 'auto 1rem'}}>Last Updated </span> Post Name
<span style={{ float: 'right', margin: 'auto 1rem' }}> Created on </span>
<span style={{ float: 'right', margin: 'auto 1rem' }}>Last Updated </span>
</section> </section>
{props.postsMeta.map((post: any) => { {postsMeta.map((post: PostMeta, i) => {
return <section key='' className='h5 block'> return <section key={i} className='h5 block'>
<Link href={`posts/${post.slug}`}> <Link href={`/posts/${post.slug}`}>
{post.title} {post.title}
</Link> </Link>
<span className='h6' style={{float: 'right', margin: 'auto 1rem'}}>{prettyDatePrint(new Date(post.created_at))}</span> <span className='h6' style={{ float: 'right', margin: 'auto 1rem' }}>
{post.last_updated ? <span className='h6' style={{float: 'right', margin: 'auto 1rem'}}>{prettyDatePrint(new Date(post.last_updated))}</span> : ''} {date.prettyPrint(new Date(post.created_at))}
</span>
{post.last_updated && <span className='h6' style={{ float: 'right', margin: 'auto 1rem' }}>
{date.prettyPrint(new Date(post.last_updated))}
</span>}
</section> </section>
})} })}
</>
</Layout> </Layout>
) )
} }

View File

@ -1,6 +1,6 @@
import Layout from '../components/layout'; import Layout from '../components/layout';
import rec from '../public/recommended.yaml'; import rec from '../public/recommended.yaml';
import { toListItem, mapChild } from '../util/resrec'; import { toListItem, mapChild } from '../components/lists';
function Recommended() { function Recommended() {
return ( return (

View File

@ -1,6 +1,6 @@
import Layout from '../components/layout'; import Layout from '../components/layout';
import res from '../public/resources.yaml'; import res from '../public/resources.yaml';
import { toListItem, mapChild } from '../util/resrec'; import { toListItem, mapChild } from '../components/lists';
function Resources() { function Resources() {
return ( return (

View File

@ -8,4 +8,21 @@
url: https://youtube.com/playlist?list=PLSU6wJEYct5Etx0WAXUQ7YXe84Fp5E142 url: https://youtube.com/playlist?list=PLSU6wJEYct5Etx0WAXUQ7YXe84Fp5E142
- title: "[Youtube] Wolfgang Amadeus Mozart" - title: "[Youtube] Wolfgang Amadeus Mozart"
url: https://youtube.com/playlist?list=PLSU6wJEYct5EJsE-9Zh-jWckBuZAmIt8Q url: https://youtube.com/playlist?list=PLSU6wJEYct5EJsE-9Zh-jWckBuZAmIt8Q
- Große Fuge Op. 133
- KV 387
- KV 448
- KV 626
- Piano Sonata No. 2 Mvmt. 3 (Chopin)
- BWV 1048
- Prelude in G Minor (Op. 23 No. 5)
- String Quartet, Op. 20 No. 2 (Haydn)
- Arabesque No. 1 (Debussy)
- title: Other
children:
- title: Rock
children:
- American Pie by Don McLean
- title: Multimedia OST
children:
- L'Ultima Diligenza by Ennio Morricone

View File

@ -71,26 +71,6 @@
- Dune (2021) - Dune (2021)
- Hot Fuzz - Hot Fuzz
- Snatch - Snatch
- title: Music
description: This is probably the most incomplete list. I have a playlists page so maybe I should move these there.
children:
- title: Rock
children:
- American Pie by Don McLean
- title: Multimedia OST
children:
- L'Ultima Diligenza by Ennio Morricone
- title: Classical
children:
- Große Fuge Op. 133
- KV 387
- KV 448
- KV 626
- Piano Sonata No. 2 Mvmt. 3 (Chopin)
- BWV 1048
- Prelude in G Minor (Op. 23 No. 5)
- String Quartet, Op. 20 No. 2 (Haydn)
- Arabesque No. 1 (Debussy)
- title: Video Games - title: Video Games
children: children:
- "The Legend of Zelda: Breath of the Wild" - "The Legend of Zelda: Breath of the Wild"

View File

@ -180,24 +180,6 @@ section {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.block .block {
margin: 0 ;
border-radius: 0;
border-right: none;
border-bottom: none;
border-top: 1px dashed var(--main-border-color);
border-left: 1px dashed var(--main-border-color);
}
.block .block:first-of-type {
border-top: none;
}
.block .block:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom: 1px dashed var(--main-border-color);
}
code { code {
overflow-x: scroll; overflow-x: scroll;
max-width: 100%; max-width: 100%;

View File

@ -19,3 +19,20 @@
font-size: 0.95rem; font-size: 0.95rem;
} }
.block .block {
margin: 0 ;
border-radius: 0;
border-right: none;
border-bottom: none;
border-top: 1px dashed var(--main-border-color);
border-left: 1px dashed var(--main-border-color);
}
.block .block:first-of-type {
border-top: none;
}
.block .block:last-of-type {
border-bottom-left-radius: 1rem;
border-bottom: 1px dashed var(--main-border-color);
}

View File

@ -1,12 +1,17 @@
.button::after { .button::after {
content: ' \2192'; content: ' \2192';
margin-left: 0.5rem; display: inline-block;
transition: 100ms ease-in-out all;
margin-left: 0.3rem;
}
.button:hover {
display: inline-block; display: inline-block;
transition: 100ms ease-in-out all; transition: 100ms ease-in-out all;
} }
.button:hover::after { .button:hover::after {
transform: translateX(0.2rem) scale(1.1); transform: translateX(0.2rem) scale(1.3);
} }
.blueButton:hover::after { .blueButton:hover::after {

View File

@ -0,0 +1,34 @@
.container {
border-bottom-left-radius: 1rem;
border-bottom: 1px dashed var(--main-border-color);
border-left: 1px dashed var(--main-border-color);
padding-top: 1.5rem;
margin-left: 0.5rem;
}
.block {
padding: 0.25rem 0.5rem;
margin: 0 0.5rem 0.25rem 0;
border-top: 1px dashed var(--main-border-color);
position: relative;
display: flex;
align-items: center;
}
.postTitle {
flex-grow: 8;
padding: 0.1rem 0.2rem;
}
.postDate {
font-style: italic;
font-size: 0.95rem;
}
.more {
text-align: right;
}
.more a {
text-decoration: none;
}

View File

@ -15,6 +15,6 @@
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true "importHelpers": true
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "util/post-cache.js", "util/slug.js"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "util/slug.js"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View File

@ -13,11 +13,13 @@ const months = [
'December' 'December'
]; ];
const suffixes = ['','st','nd','rd','th']; const ordSfx = ['','st','nd','rd','th'];
function prettyPrint(date: Date | string): string {
const oDate = (typeof date === 'string')? new Date(date): date;
export default function prettyDatePrint(date: Date) {
const now = new Date(); const now = new Date();
const diff = now.getTime() - date.getTime(); const diff = now.getTime() - oDate.getTime();
let tdiff = Math.floor(diff/1000); let tdiff = Math.floor(diff/1000);
@ -38,13 +40,27 @@ export default function prettyDatePrint(date: Date) {
return `Yesterday`; return `Yesterday`;
} }
const year = date.getFullYear(); const year = oDate.getFullYear();
const month = months[date.getMonth()]; const month = months[oDate.getMonth()];
const day = date.getDate(); const day = oDate.getDate();
let sfx; let sfx;
if (day >= 1 && day <= 3) if (day >= 1 && day <= 3)
sfx = suffixes[day]; sfx = ordSfx[day];
else else
sfx = suffixes[4]; sfx = ordSfx[4];
return `${day}${sfx} ${month} ${year}`; if (year != now.getFullYear())
return `${day}${sfx} ${month} ${year}`;
return `${day}${sfx} ${month}`;
} }
function isValid(date: any) {
return (new Date(date)).toString() === 'Invalid Date';
}
const d = {
prettyPrint,
isValid
};
export default d;

View File

@ -3,6 +3,7 @@ const matter = require('gray-matter');
const { join } = require('path'); const { join } = require('path');
const postsDir = join(process.cwd(), 'posts'); const postsDir = join(process.cwd(), 'posts');
const cacheDir = join(process.cwd(), '.cache');
function getPost(rawslug, filter = []) { function getPost(rawslug, filter = []) {
const slug = rawslug.replace(/\.md$/, ''); const slug = rawslug.replace(/\.md$/, '');
@ -11,7 +12,7 @@ function getPost(rawslug, filter = []) {
const { data, content } = matter(file); const { data, content } = matter(file);
if (data['last_updated'] === undefined) if (data['last_updated'] === undefined)
data['last_updated'] = data['created_at']; data['last_updated'] = '';
if (filter.length === 0) if (filter.length === 0)
return { ...data, content, slug, rawslug }; return { ...data, content, slug, rawslug };
@ -41,13 +42,24 @@ function getAllPosts(filter = []) {
.filter(c => (!c.match(/^\.]/) && c.match(/\.md$/))) .filter(c => (!c.match(/^\.]/) && c.match(/\.md$/)))
.map(file => { .map(file => {
return getPost(file, filter) return getPost(file, filter)
})
.sort((a, b) => {
const dA = new Date(a['created_at']);
const dB = new Date(b['created_at']);
return dB - dA;
}); });
} }
const postMetaCacheFile = join(cacheDir, 'posts.meta.json');
function cachePostsMeta() { // public access cache function cachePostsMeta() { // public access cache
const posts = getAllPosts(['title', 'slug', 'created_at', 'last_updated']); const posts = getAllPosts(['title', 'slug', 'created_at', 'last_updated']);
fs.writeFile(join(postsDir, 'meta.json'), JSON.stringify(posts), (e) => {
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir);
}
fs.writeFile(postMetaCacheFile, JSON.stringify(posts), (e) => {
if (e) if (e)
console.error(e); console.error(e);
}); });
@ -55,13 +67,16 @@ function cachePostsMeta() { // public access cache
} }
function getPostsMeta() { function getPostsMeta() {
const file = fs.readFileSync(join(postsDir, 'meta.json'), 'utf-8'); try {
const file = fs.readFileSync(postMetaCacheFile, 'utf-8');
if (!file) { return JSON.parse(file);
} catch (e) {
return cachePostsMeta(); return cachePostsMeta();
} }
return JSON.parse(file);
} }
module.exports = { getAllPosts, getPost, getPostsMeta, cachePostsMeta }; function cache() {
cachePostsMeta();
}
module.exports = { getAllPosts, getPost, getPostsMeta, cache };