More changes to structure and UI
This commit is contained in:
parent
2d4a2809b4
commit
6ba7d561fb
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
.next/
|
.next/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
public/posts.json
|
.cache/
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
@ -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;
|
21
components/quick-links.tsx
Normal file
21
components/quick-links.tsx
Normal 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;
|
34
components/recent-posts.tsx
Normal file
34
components/recent-posts.tsx
Normal 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;
|
@ -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 }>) {
|
||||||
|
|
||||||
const path = () => {
|
|
||||||
if (!props.ancestors)
|
|
||||||
return (<></>);
|
|
||||||
|
|
||||||
let currentPath = '';
|
let currentPath = '';
|
||||||
return (<>
|
return ancestors.map((ancestor) => {
|
||||||
{
|
|
||||||
props.ancestors.map(ancestor => {
|
|
||||||
currentPath += `/${ancestor.path}`
|
currentPath += `/${ancestor.path}`
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link href={currentPath} key=''>
|
<Link href={currentPath}>
|
||||||
<a>{ancestor.name}</a>
|
<a>{ancestor.name}</a>
|
||||||
</Link>
|
</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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -1,61 +1,11 @@
|
|||||||
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>) => {
|
||||||
@ -64,7 +14,6 @@ function Playlists() {
|
|||||||
return mapChild(lItem, 0)
|
return mapChild(lItem, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</section>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
|
|
@ -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"
|
||||||
|
@ -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%;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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 {
|
34
styles/recent-posts.module.css
Normal file
34
styles/recent-posts.module.css
Normal 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;
|
||||||
|
}
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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];
|
||||||
|
if (year != now.getFullYear())
|
||||||
return `${day}${sfx} ${month} ${year}`;
|
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;
|
31
util/slug.js
31
util/slug.js
@ -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 };
|
Loading…
Reference in New Issue
Block a user