diff --git a/.gitignore b/.gitignore index 4cfe6a4..52e5845 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules/ dist/ .next/ .DS_Store -public/posts.json \ No newline at end of file +.cache/ \ No newline at end of file diff --git a/components/layout.tsx b/components/layout.tsx index e478d05..f146879 100644 --- a/components/layout.tsx +++ b/components/layout.tsx @@ -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; + +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 ( <> - - - <FuzzyBar /> + <Meta name={name} ancestors={ancestors} /> + <Title title={title} name={name} ancestors={ancestors} /> + {/* <FuzzyBar /> */} <div className='container'> - {props.children} + {children} </div> </> ); diff --git a/util/resrec.tsx b/components/lists.tsx similarity index 97% rename from util/resrec.tsx rename to components/lists.tsx index 95aa1f3..25ab4b5 100644 --- a/util/resrec.tsx +++ b/components/lists.tsx @@ -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> diff --git a/components/meta.tsx b/components/meta.tsx index 8dcb3f6..11f4057 100644 --- a/components/meta.tsx +++ b/components/meta.tsx @@ -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 ( @@ -18,4 +19,6 @@ export default function Meta(props: { name: string, ancestors?: Array<{ name: st <title>PaulW.XYZ / {path()} ); -} \ No newline at end of file +} + +export default Meta; \ No newline at end of file diff --git a/components/quick-links.tsx b/components/quick-links.tsx new file mode 100644 index 0000000..1feb515 --- /dev/null +++ b/components/quick-links.tsx @@ -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 (<> +
Quick Links
+ { + Pages.map((obj, i) => { + const extern = obj.link.match(/^http/) && `blue ${style.blueButton}` || ''; + return ( + + {obj.title} + + ); + }) + } + ); +} + +export default QuickLinks; \ No newline at end of file diff --git a/components/recent-posts.tsx b/components/recent-posts.tsx new file mode 100644 index 0000000..410f823 --- /dev/null +++ b/components/recent-posts.tsx @@ -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 (<> +
Recent Posts
+
+ {postsMeta?.slice(0, 10) + .map((post: any) => { + return
+ + {post.title} + + + {date.prettyPrint(new Date(post.created_at))} + +
+ })} +
+ { + postsMeta.length > 10 && +
+ + More... + +
+ } + + ); +} + +export default RecentPosts; \ No newline at end of file diff --git a/components/title.tsx b/components/title.tsx index 604a46b..80fb887 100644 --- a/components/title.tsx +++ b/components/title.tsx @@ -7,42 +7,33 @@ type propsObj = { ancestors?: Array<{ name: string, path: string }> }; -function Title(props: propsObj) { - - const path = () => { - if (!props.ancestors) - return (<>); - - let currentPath = ''; - return (<> - { - props.ancestors.map(ancestor => { - currentPath += `/${ancestor.path}` - return ( - <> - - {ancestor.name} - - <> / - - ); - }) - } - +function createPathElements(ancestors: Array<{ name: string, path: string }>) { + let currentPath = ''; + return ancestors.map((ancestor) => { + currentPath += `/${ancestor.path}` + return ( + <> + + {ancestor.name} + + <> / + ); - }; + }); +}; + +function Title({ name, title, ancestors }: propsObj) { + const pathElements = ancestors && createPathElements(ancestors) || <>; return ( <>

- {props.title || props.name} + {title || name}

-
- { - props.name === '' - ? <>PaulW.XYZ / {props.name} - : <>PaulW.XYZ / {path()}{props.name} - } +
+ {name + ? <>PaulW.XYZ / {pathElements}{name} + : <>PaulW.XYZ /}
); diff --git a/next.config.js b/next.config.js index 971cbbe..0f451c7 100755 --- a/next.config.js +++ b/next.config.js @@ -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(); }); } } diff --git a/pages/index.tsx b/pages/index.tsx index ae89850..cf35d96 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -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 (
-
Welcome!
- { - Pages.map(obj => { - return - - { - obj.link.match(/^http/) - ? {obj.title} - : {obj.title} - } - - - }) - } +
- - - {props.postsMeta?.map((post: any) => { - return - - - - - })} -
Posts Posted
- - {post.title} - - {prettyDatePrint(new Date(post.created_at))}
+
) diff --git a/pages/playlists.tsx b/pages/playlists.tsx index 81f16ab..1e2aabf 100644 --- a/pages/playlists.tsx +++ b/pages/playlists.tsx @@ -1,70 +1,19 @@ 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): 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
  • {obj.title}
  • - - 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} -
      - {obj.children.map(l => mapChild(l, level + 1))} -
    - - ); -} - function Playlists() { return ( -
    -

    Music

    - { - pl.map((item: Record) => { - const lItem = toListItem(item) - if (lItem) - return mapChild(lItem, 0) - }) - } -
    +

    Music

    + { + pl.map((item: Record) => { + const lItem = toListItem(item) + if (lItem) + return mapChild(lItem, 0) + }) + }
    ); } diff --git a/pages/posts/index.tsx b/pages/posts/index.tsx index 0bc4d34..f9ee4dd 100644 --- a/pages/posts/index.tsx +++ b/pages/posts/index.tsx @@ -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 - <>
    - Post Name Created on Last Updated + Post Name + Created on + Last Updated
    - {props.postsMeta.map((post: any) => { - return
    - + {postsMeta.map((post: PostMeta, i) => { + return
    + {post.title} - {prettyDatePrint(new Date(post.created_at))} - {post.last_updated ? {prettyDatePrint(new Date(post.last_updated))} : ''} + + {date.prettyPrint(new Date(post.created_at))} + + {post.last_updated && + {date.prettyPrint(new Date(post.last_updated))} + }
    })} - ) } diff --git a/pages/recommended.tsx b/pages/recommended.tsx index 6ddd050..af545cf 100644 --- a/pages/recommended.tsx +++ b/pages/recommended.tsx @@ -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 ( diff --git a/pages/resources.tsx b/pages/resources.tsx index f568ab1..01a2765 100644 --- a/pages/resources.tsx +++ b/pages/resources.tsx @@ -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 ( diff --git a/public/playlists.yaml b/public/playlists.yaml index b6898b7..72242f4 100644 --- a/public/playlists.yaml +++ b/public/playlists.yaml @@ -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 \ No newline at end of file diff --git a/public/recommended.yaml b/public/recommended.yaml index 3efa6e6..95f8fb8 100644 --- a/public/recommended.yaml +++ b/public/recommended.yaml @@ -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" diff --git a/styles/global.css b/styles/global.css index d23589d..6dc8c00 100644 --- a/styles/global.css +++ b/styles/global.css @@ -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%; diff --git a/styles/lists.module.css b/styles/lists.module.css index 21396b0..5646c45 100644 --- a/styles/lists.module.css +++ b/styles/lists.module.css @@ -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); +} diff --git a/styles/home.module.css b/styles/quick-links.module.css similarity index 56% rename from styles/home.module.css rename to styles/quick-links.module.css index 8144b1f..1c2fe6f 100644 --- a/styles/home.module.css +++ b/styles/quick-links.module.css @@ -1,14 +1,19 @@ .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 { transform: rotateZ(-45deg) scale(1.5); -} \ No newline at end of file +} diff --git a/styles/recent-posts.module.css b/styles/recent-posts.module.css new file mode 100644 index 0000000..e0cd539 --- /dev/null +++ b/styles/recent-posts.module.css @@ -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; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1362332..bc6d653 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] } diff --git a/util/pretty-date.ts b/util/date.ts similarity index 51% rename from util/pretty-date.ts rename to util/date.ts index 9c34fc8..523478b 100644 --- a/util/pretty-date.ts +++ b/util/date.ts @@ -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]; - return `${day}${sfx} ${month} ${year}`; -} \ No newline at end of file + 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; \ No newline at end of file diff --git a/util/slug.js b/util/slug.js index 297c5ec..3b39735 100644 --- a/util/slug.js +++ b/util/slug.js @@ -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 }; \ No newline at end of file +function cache() { + cachePostsMeta(); +} + +module.exports = { getAllPosts, getPost, getPostsMeta, cache }; \ No newline at end of file