Webpack hook for jsongen, [somewhat broken] UI changes

This commit is contained in:
Paul W. 2022-04-24 00:27:51 -04:00
parent 103eec9551
commit d189096e96
23 changed files with 199 additions and 96 deletions

2
.gitignore vendored
View File

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

View File

@ -6,6 +6,18 @@ module.exports = {
webpack: (config, options) => { webpack: (config, options) => {
config.experiments = { asset: true }; config.experiments = { asset: true };
const { cachePostLinkData } = require('./util/post-cache');
config.plugins.push(
{
apply: (compiler) => {
compiler.hooks.initialize.tap('cachePostLinkDataInit', _ => {
cachePostLinkData();
});
}
}
)
config.module.rules.push( config.module.rules.push(
{ {
test: /\.ya?ml$/, test: /\.ya?ml$/,

View File

@ -3,5 +3,5 @@ import 'normalize.css';
import '../styles/global.css'; import '../styles/global.css';
export default function MyApp({ Component, pageProps }: AppProps) { export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {... pageProps} /> return <Component {...pageProps} />
} }

View File

@ -17,7 +17,7 @@ function AboutPage() {
Got any questions, concerns, or issues? Feel free to contact me via my email: <code>lambdapaul [at] pm [dot] me</code>. Got any questions, concerns, or issues? Feel free to contact me via my email: <code>lambdapaul [at] pm [dot] me</code>.
</section> </section>
<section className='block'> <section className='block'>
<ReactMarkdown>{ReadmeMd.replace(/#{1,5} /g, (s: string) => {return `#${s}`})}</ReactMarkdown> <ReactMarkdown>{ReadmeMd.replace(/#{1,5} /g, (s: string) => { return `#${s}` })}</ReactMarkdown>
</section> </section>
</Layout> </Layout>
) )

View File

@ -2,46 +2,44 @@ 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 Pages from '../public/pages.json';
import cachePostLinkData from '../util/post-cache'; import Posts from '../public/posts.json';
import style from '../styles/home.module.css';
import prettyDatePrint from '../util/pretty-date';
function HomePage({ posts }: any) {
function HomePage({posts}: any) { Pages.sort((x, y) => { return (x.title).localeCompare(y.title) });
Pages.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> <div className='h2'>Welcome!</div>
{ {
Pages.map(obj => { Pages.map(obj => {
return <div key='' className='h5'> return <span key={obj.link}>
<Link href={obj.link}> <Link href={obj.link}>
<a>{obj.title}{obj.link.match('^http*')? ' ↗' : ''}</a> <a className={style.button}>{obj.title}</a>
</Link> </Link>
</div> </span>
}) })
} }
</section> </section>
<section className='block'> <section className='block'>
<div className='h2'>Posts</div> <table style={{ width: '100%' }}>
<div> <th className='h2'>Posts</th> <th>Posted</th>
{posts?.map((post: any) => { {Posts?.map((post: any) => {
return <div key={post.slug} className='h5'> return <tr key={post.slug}>
[{ (new Date(post.last_updated)).toLocaleString()}] <Link href={`posts/${post.slug}`}> <td className='h5'>
<Link href={`posts/${post.slug}`}>
{post.title} {post.title}
</Link> </Link>
</div> </td>
<td>{prettyDatePrint(new Date(post.created_at))}</td>
</tr>
})} })}
</div> </table>
</section> </section>
</Layout> </Layout>
) )
} }
export async function getStaticProps() {
// make this webpack plugin
return {
props: {posts: cachePostLinkData()}
};
}
export default HomePage; export default HomePage;

View File

@ -12,7 +12,7 @@ function toListItem(record: Record<string, any>): listItem | null {
if (!record.title) if (!record.title)
return null; return null;
let children: listItem[]= []; let children: listItem[] = [];
if (record.children) if (record.children)
for (const child of record.children) { for (const child of record.children) {
const lChild = toListItem(child); const lChild = toListItem(child);
@ -23,7 +23,7 @@ function toListItem(record: Record<string, any>): listItem | null {
return { return {
title: record.title, title: record.title,
url: record.url, url: record.url,
children: children.length? children : undefined, children: children.length ? children : undefined,
}; };
} }
@ -38,7 +38,7 @@ function mapChild(obj: listItem, level: number) {
let title: ReactElement; let title: ReactElement;
if (level >= 0 && level <= 3) if (level >= 0 && level <= 3)
title = React.createElement(`h${level+3}`, {}, obj.title); title = React.createElement(`h${level + 3}`, {}, obj.title);
else else
title = React.createElement('strong', {}, obj.title); title = React.createElement('strong', {}, obj.title);

View File

@ -3,34 +3,36 @@ import { useRouter } from 'next/router';
import { getAllPosts, getPost } from '../../util/slug'; import { getAllPosts, getPost } from '../../util/slug';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import Image from 'next/image'; import Image from 'next/image';
import style from '../../styles/post.module.css';
function Post({post} : any) { // eh function Post({ post }: any) { // eh
const router = useRouter(); const router = useRouter();
return ( return (<>
<Layout name={post.title} title={post.title} ancestors={[{name:'Posts', path: 'posts'}]}> <Layout name={post.title} title={post.title} ancestors={[{ name: 'Posts', path: 'posts' }]}>
<section className='block'> <div className={style.imageBlock} style={{ backgroundImage: post.cover ? `url(/assets/images/${post.cover})` : '' }}>
<div className="block" style={{position: 'relative', height: '360px', width: '640px'}}> <div className={style.spacer}></div>
{post.cover ? <Image width={640} height={360} layout="fill" src={`/assets/images/${post.cover}`} alt={`${post.title} Cover Image`} /> : ''} <section className={`${style.block} block`}>
</div>
<ReactMarkdown>{post.content}</ReactMarkdown> <ReactMarkdown>{post.content}</ReactMarkdown>
</section> </section>
<div className={style.spacer}></div>
</div>
</Layout> </Layout>
</>
); );
} }
export async function getStaticProps({params}: any) { export async function getStaticProps({ params }: any) {
const post = getPost(params.page); const post = getPost(params.page);
return { return {
props: {post} props: { post }
}; };
} }
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = getAllPosts(); const posts = getAllPosts();
return { return {
paths: posts.map(post => { paths: posts.map((post: any) => {
return { return {
params: { params: {
page: post.slug page: post.slug

View File

@ -1,31 +1,30 @@
import Link from 'next/link'; 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 Posts from '../../public/posts.json';
import cachePostLinkData from '../../util/post-cache'; import prettyDatePrint from '../../util/pretty-date';
function HomePage({posts}: any) { function HomePage({posts}: any) {
Pages.sort((x, y) => { return ('' + x.title).localeCompare(y.title) }); Posts.sort((x, y) => { return x.title.localeCompare(y.title) });
// todo: create a table-like interface
return ( return (
<Layout name='Posts'> <Layout name='Posts'>
{posts.map((post: any) => { <>
<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>
</section>
{Posts.map((post: any) => {
return <section key='' className='h5 block'> return <section key='' className='h5 block'>
<Link href={`posts/${post.slug}`}> <Link href={`posts/${post.slug}`}>
{post.title} {post.title}
</Link> </Link>
<div>[{ (new Date(post.last_updated)).toLocaleString()}]</div> <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> : ''}
</section> </section>
})} })}
</>
</Layout> </Layout>
) )
} }
export async function getStaticProps() {
return {
props: {posts: cachePostLinkData()}
};
}
export default HomePage; export default HomePage;

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 '../util/resrec';
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 '../util/resrec';
function Resources() { function Resources() {
return ( return (

View File

@ -1 +0,0 @@
[{"title":"Thoughts on Baba Is You","slug":"thoughts-on-baba-is-you","last_updated":"2021-10-30T00:43:00.000Z"}]

View File

@ -11,6 +11,8 @@
.search { .search {
display: block; display: block;
width: 100%; width: 100%;
max-width: 1018px;
margin: 0 auto;
font-size: 2rem; font-size: 2rem;
background: linear-gradient(to bottom right, #406b39, #225546) no-repeat center center fixed; background: linear-gradient(to bottom right, #406b39, #225546) no-repeat center center fixed;
box-shadow: inset 0 4px 8px 0 rgba(0, 0, 0, 0.2); box-shadow: inset 0 4px 8px 0 rgba(0, 0, 0, 0.2);
@ -26,8 +28,9 @@
} }
.results { .results {
max-width: 1018px;
padding: 1rem; padding: 1rem;
margin: 2rem; margin: 2rem auto;
} }
.hyperlink { .hyperlink {

View File

@ -140,11 +140,6 @@ section {
margin: 0.5rem; margin: 0.5rem;
} }
img {
border: 1px solid #FFFFFF;
border-radius: 1rem;
}
.lambda-logo { .lambda-logo {
width: 256px; width: 256px;
height: 256px; height: 256px;

22
styles/home.module.css Normal file
View File

@ -0,0 +1,22 @@
.button {
padding: 0.2rem 1rem;
margin: 0.3rem 0.3rem;
background:#1a3a15;
color: rgba(255, 255, 255);
display: inline-block;
text-decoration: none;
transition: 100ms ease-in-out all;
border: 1px solid #ffffff;
border-radius: 0.5rem;
}
.button:hover {
text-decoration: none;
background:#099945;
}
.button:active {
text-decoration: none;
box-shadow: none;
transform: translate(1px, 1px);
}

16
styles/post.module.css Normal file
View File

@ -0,0 +1,16 @@
.imageBlock {
position: absolute;
right: 0;
left: 0;
background-size: cover;
min-height: 100%;
background-attachment: fixed;
}
.block {
background-color: rgba(13, 17, 23, 0.9);
}
.spacer {
height: 5rem;
}

View File

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

1
util/post-cache.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export default function cachePostLinkData(): any;

16
util/post-cache.js Normal file
View File

@ -0,0 +1,16 @@
const fs = require('fs');
const { getAllPosts } = require('./slug');
const { join } = require('path');
const publicDir = join(process.cwd(), 'public');
module.exports = {
cachePostLinkData: () => {
const posts = getAllPosts(['title', 'slug', 'created_at', 'last_updated']);
fs.writeFile(`${publicDir}/posts.json`, JSON.stringify(posts), (e) => {
if (e)
console.error(e);
});
return posts;
}
}

View File

@ -1,14 +0,0 @@
import fs from 'fs';
import { getAllPosts } from './slug';
import { join } from 'path';
const publicDir = join(process.cwd(), 'public');
export default function cachePostLinkData() {
const posts = getAllPosts(['title', 'slug', 'last_updated']);
fs.writeFile(`${publicDir}/posts.json`, JSON.stringify(posts), (e) => {
if (e)
console.error(e);
});
return posts;
}

50
util/pretty-date.ts Normal file
View File

@ -0,0 +1,50 @@
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
const suffixes = ['','st','nd','rd','th'];
export default function prettyDatePrint(date: Date) {
const now = new Date();
const diff = now.getTime() - date.getTime();
let tdiff = Math.floor(diff/1000);
if (tdiff < 60) {
return `${tdiff} seconds ago`;
}
tdiff = Math.floor(tdiff/60);
if (tdiff < 60) {
return `${tdiff} minute${tdiff === 1? '' : 's'} ago`;
}
tdiff = Math.floor(tdiff/60);
if (tdiff < 24) {
return `${tdiff} hour${tdiff === 1? '' : 's'} ago`;
}
if (tdiff < 48) {
return `Yesterday`;
}
const year = date.getFullYear();
const month = months[date.getMonth()];
const day = date.getDate();
let sfx;
if (day >= 1 && day <= 3)
sfx = suffixes[day];
else
sfx = suffixes[4];
return `${day}${sfx} ${month} ${year}`;
}

View File

@ -31,16 +31,16 @@ export function toListItem(record: Record<string, any>): listItem | null {
children = schildren; children = schildren;
} }
else { else {
children = [...lchildren, ... schildren.map((s: string): listItem => { children = [...lchildren, ...schildren.map((s: string): listItem => {
return { title: s }; return { title: s };
}) ]; })];
} }
} }
return { return {
title: record.title, title: record.title,
url: record.url, url: record.url,
children: children.length? children : undefined, children: children.length ? children : undefined,
description: record.description, description: record.description,
}; };
} }

4
util/slug.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
interface Post { slug?: string, rawslug?: string, content?: string, title?: string };
export function getAllPosts(filter: Array<any> = []): Post[];
export function getPost(rawslug: string, filter: Array<any> = []): Post;

View File

@ -1,10 +1,10 @@
import fs from 'fs' const fs = require('fs');
import matter from 'gray-matter'; const matter = require('gray-matter');
import { join } from 'path'; const { join } = require('path');
const postsDirectory = join(process.cwd(), 'posts'); const postsDirectory = join(process.cwd(), 'posts');
export function getPost(rawslug: string, filter: Array<any> = []) { function getPost(rawslug, filter = []) {
const slug = rawslug.replace(/\.md$/, ''); const slug = rawslug.replace(/\.md$/, '');
const path = join(postsDirectory, `${slug}.md`); const path = join(postsDirectory, `${slug}.md`);
const file = fs.readFileSync(path, 'utf-8'); const file = fs.readFileSync(path, 'utf-8');
@ -16,7 +16,7 @@ export function getPost(rawslug: string, filter: Array<any> = []) {
if (filter.length === 0) if (filter.length === 0)
return { ...data, content, slug, rawslug }; return { ...data, content, slug, rawslug };
let post: { slug?: string, rawslug?: string, content?: string, title?: string } | any = {}; let post = {};
for (const [_, entry] of filter.entries()) { for (const [_, entry] of filter.entries()) {
if (entry === 'slug') if (entry === 'slug')
post[entry] = slug; post[entry] = slug;
@ -27,8 +27,6 @@ export function getPost(rawslug: string, filter: Array<any> = []) {
if (entry === 'content') if (entry === 'content')
post[entry] = content; post[entry] = content;
if (typeof data[entry] !== 'undefined') { if (typeof data[entry] !== 'undefined') {
post[entry] = data[entry] post[entry] = data[entry]
} }
@ -36,7 +34,7 @@ export function getPost(rawslug: string, filter: Array<any> = []) {
return post; return post;
} }
export function getAllPosts(filter: Array<any> = []) { function getAllPosts(filter = []) {
const files = fs.readdirSync(postsDirectory); const files = fs.readdirSync(postsDirectory);
return files return files
@ -45,3 +43,5 @@ export function getAllPosts(filter: Array<any> = []) {
return getPost(file, filter) return getPost(file, filter)
}); });
} }
module.exports = { getAllPosts, getPost };