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/
.next/
.DS_Store
public/posts.json
.cache/

View File

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

View File

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

View File

@ -1,16 +1,17 @@
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 = () => {
if (!props.ancestors)
return props.name;
if (!ancestors)
return name;
let path = '';
props.ancestors.map((obj) => {
ancestors.map((obj) => {
path = `${path}${obj.name} /`;
});
return `${path} ${props.name}`;
return `${path} ${name}`;
};
return (
@ -19,3 +20,5 @@ export default function Meta(props: { name: string, ancestors?: Array<{ name: st
</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 }>
};
function Title(props: propsObj) {
const path = () => {
if (!props.ancestors)
return (<></>);
function createPathElements(ancestors: Array<{ name: string, path: string }>) {
let currentPath = '';
return (<>
{
props.ancestors.map(ancestor => {
return ancestors.map((ancestor) => {
currentPath += `/${ancestor.path}`
return (
<>
<Link href={currentPath} key=''>
<Link href={currentPath}>
<a>{ancestor.name}</a>
</Link>
<> / </>
</>
);
})
}
</>
);
};
});
};
function Title({ name, title, ancestors }: propsObj) {
const pathElements = ancestors && createPathElements(ancestors) || <></>;
return (
<>
<h1 className={style.container}>
{props.title || props.name}
{title || name}
</h1>
<div className={style.nav + ' h1'}>
{
props.name === ''
? <>PaulW.XYZ / {props.name}</>
: <><Link href='/'><a>PaulW.XYZ</a></Link> / {path()}{props.name}</>
}
<div className={`${style.nav} h1`}>
{name
? <><Link href='/'><a>PaulW.XYZ</a></Link> / {pathElements}{name}</>
: <>PaulW.XYZ /</>}
</div>
</>
);

View File

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

View File

@ -1,46 +1,17 @@
import Link from 'next/link';
import React from 'react';
import Layout from '../components/layout';
import Pages from '../public/pages.json';
import style from '../styles/home.module.css';
import prettyDatePrint from '../util/pretty-date';
import QuickLinks from '../components/quick-links';
import RecentPosts from '../components/recent-posts';
import { getPostsMeta, PostMeta } from '../util/slug';
function HomePage(props: { postsMeta: PostMeta[] }) {
props.postsMeta.sort((x, y) => { return (x.title).localeCompare(y.title) });
function HomePage({ postsMeta }: { postsMeta: PostMeta[] }) {
return (
<Layout name='' title='PaulW.XYZ'>
<section className='block'>
<div className='h2'>Welcome!</div>
{
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>
})
}
<QuickLinks />
</section>
<section className='block'>
<table style={{ width: '100%' }}>
<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>
<RecentPosts postsMeta={postsMeta} />
</section>
</Layout>
)

View File

@ -1,61 +1,11 @@
import React, { ReactElement } from 'react';
import Layout from '../components/layout';
import { mapChild, toListItem } from '../components/lists';
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() {
return (
<Layout name='Playlists'>
<section className='block'>
<h2>Music</h2>
{
pl.map((item: Record<string, any>) => {
@ -64,7 +14,6 @@ function Playlists() {
return mapChild(lItem, 0)
})
}
</section>
</Layout>
);
}

View File

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

View File

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

View File

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

View File

@ -8,4 +8,21 @@
url: https://youtube.com/playlist?list=PLSU6wJEYct5Etx0WAXUQ7YXe84Fp5E142
- title: "[Youtube] Wolfgang Amadeus Mozart"
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)
- Hot Fuzz
- 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
children:
- "The Legend of Zelda: Breath of the Wild"

View File

@ -180,24 +180,6 @@ section {
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 {
overflow-x: scroll;
max-width: 100%;

View File

@ -19,3 +19,20 @@
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 {
content: ' \2192';
margin-left: 0.5rem;
display: inline-block;
transition: 100ms ease-in-out all;
margin-left: 0.3rem;
}
.button:hover {
display: inline-block;
transition: 100ms ease-in-out all;
}
.button:hover::after {
transform: translateX(0.2rem) scale(1.1);
transform: translateX(0.2rem) scale(1.3);
}
.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",
"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"]
}

View File

@ -13,11 +13,13 @@ const months = [
'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 diff = now.getTime() - date.getTime();
const diff = now.getTime() - oDate.getTime();
let tdiff = Math.floor(diff/1000);
@ -38,13 +40,27 @@ export default function prettyDatePrint(date: Date) {
return `Yesterday`;
}
const year = date.getFullYear();
const month = months[date.getMonth()];
const day = date.getDate();
const year = oDate.getFullYear();
const month = months[oDate.getMonth()];
const day = oDate.getDate();
let sfx;
if (day >= 1 && day <= 3)
sfx = suffixes[day];
sfx = ordSfx[day];
else
sfx = suffixes[4];
sfx = ordSfx[4];
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 postsDir = join(process.cwd(), 'posts');
const cacheDir = join(process.cwd(), '.cache');
function getPost(rawslug, filter = []) {
const slug = rawslug.replace(/\.md$/, '');
@ -11,7 +12,7 @@ function getPost(rawslug, filter = []) {
const { data, content } = matter(file);
if (data['last_updated'] === undefined)
data['last_updated'] = data['created_at'];
data['last_updated'] = '';
if (filter.length === 0)
return { ...data, content, slug, rawslug };
@ -41,13 +42,24 @@ function getAllPosts(filter = []) {
.filter(c => (!c.match(/^\.]/) && c.match(/\.md$/)))
.map(file => {
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
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)
console.error(e);
});
@ -55,13 +67,16 @@ function cachePostsMeta() { // public access cache
}
function getPostsMeta() {
const file = fs.readFileSync(join(postsDir, 'meta.json'), 'utf-8');
if (!file) {
try {
const file = fs.readFileSync(postMetaCacheFile, 'utf-8');
return JSON.parse(file);
} catch (e) {
return cachePostsMeta();
}
return JSON.parse(file);
}
module.exports = { getAllPosts, getPost, getPostsMeta, cachePostsMeta };
function cache() {
cachePostsMeta();
}
module.exports = { getAllPosts, getPost, getPostsMeta, cache };