Compare commits

..

131 Commits

Author SHA1 Message Date
paul c4e7c710d1 Update notes/references.md 2026-04-29 06:53:44 +00:00
paul 7f04a28832 Update notes/resources.md 2026-04-29 06:52:44 +00:00
paul a6a540b2fc github->gitea 2025-10-12 17:34:43 -04:00
paul a32d49deeb More note updates 2025-10-04 04:00:13 -04:00
paul 6dc4a036fd Update software.md 2025-09-16 18:17:15 -04:00
paul 9636d3b70e Correct date gen. for /notes 2025-09-16 17:42:55 -04:00
paul d3fcb015ab Add retro games 2025-09-16 17:26:52 -04:00
paul ca4ebb5203 Fix nav title size 2025-05-31 22:27:33 -04:00
paul 2ea4bab316 remove turbopack option and fix type issues 2025-05-31 22:21:52 -04:00
paul f477ba21ea turbopack and app router; very annoying to work with! 2025-05-31 22:06:24 -04:00
paul 657f4b1e41 Update github info
Signed-off-by: Paul W. <contact@paulw.xyz>
2025-04-15 22:15:42 -04:00
paul 2514aae4b0 Close p tag
Signed-off-by: Paul W. <contact@paulw.xyz>
2025-04-01 18:39:42 -04:00
paul faf8c5890a Update links
Signed-off-by: Paul W. <contact@paulw.xyz>
2025-04-01 18:36:29 -04:00
paul b6d3d2ba9b Fix metagen by using gitea as a source
Signed-off-by: Paul W. <contact@paulw.xyz>
2025-01-04 18:25:49 -05:00
paul 84d71525e0 Add prebuild instead of manual metagen
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-28 16:51:52 -05:00
paul 0d0f089de2 Consolidate steam notes
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-28 16:42:00 -05:00
paul 99fbe7203b Update metadata
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-28 12:34:49 -05:00
paul b7a5b7f4ef Use a more fitting title for software notes
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-28 12:32:44 -05:00
paul 22e3c294b0 dep ver bump; clean-up; minor updates
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-28 12:23:50 -05:00
paul 619d2771cc react: fix incorrect use of keys
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-12-06 13:52:40 -05:00
paul 706100ccce Fix type and other minor issues 2024-10-10 02:50:21 -04:00
paul d87f11a5d6 Update metadata 2024-10-10 02:04:28 -04:00
paul d04528941a Add last updated to note pages 2024-10-10 01:58:33 -04:00
paul ba52847b76 Re-add GitHub-flavored MD 2024-10-10 01:08:53 -04:00
paul e7220f8ae7 Update prgm-resources with new stuff 2024-10-10 01:05:27 -04:00
paul 44d8fed1c7 Update steam tool info 2024-10-10 00:56:24 -04:00
paul ff9582c630 Add new links, fix title-gen edge case 2024-10-10 00:51:55 -04:00
paul b442413e32 Add new notes, fix minor issues 2024-10-02 22:47:34 -04:00
paul ccdd2bd307 Merge branch 'master' of github.com:LambdaPaul/www
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-04-20 16:03:38 -04:00
paul 34a8f687fc Update readme, about, and some minor things
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-04-20 16:00:22 -04:00
paul fb3929eb40 Fix home page list formatting
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-03-09 23:34:45 -05:00
paul fcccc3f7ae Update sitemap.json 2024-03-05 12:26:02 -05:00
paul 0ecd0808dc Update notes.json; should probably automate this 2024-03-05 11:35:36 -05:00
paul c349a8b77c delete notes/nintendo.md 2024-03-05 11:32:59 -05:00
paul fece0bc86c Add programming-resources; swi->generic Nintendo;
Remove custom html from markdown, clean-up UI (again)

Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2024-02-13 18:01:07 -05:00
paul 366f09864f Add font-display to custom fonts to avoid FOIT
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-11-02 17:14:15 -04:00
paul 85fdc83a92 Change post formatting
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-11-02 17:09:23 -04:00
paul 7380350068 Minor typo fix
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-11-01 18:27:47 -04:00
paul 77bcb3fc09 Fix button focus for active and rm unused import
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-10-31 18:26:52 -04:00
paul 37485aac96 Minor UI changes
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-10-30 21:50:20 -04:00
paul d9afe45fd2 Fix markdown library incompatibility; fix 404
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-10-30 00:55:45 -04:00
paul e470224031 Bump script ver and refactor md metadata gen
Signed-off-by: Paul W. <lambdapaul@protonmail.com>
2023-10-30 00:18:38 -04:00
paul 2bc72ce3e9 next 13, remove unused, update incorrect/outdated 2023-09-20 00:25:34 -04:00
paul a1fb018bdb Add nav key prop 2022-10-04 23:46:16 -04:00
paul 160f23cb01 Refactor, update UI, and add notes 2022-10-04 23:41:59 -04:00
paul fde6c7fa69 Create card component 2022-09-28 00:01:03 -04:00
paul b0c3b9b1e1 Update next and other deps 2022-09-27 22:04:44 -04:00
paul 94599da215 Add music to playlist, add more github info and css 2022-09-27 20:52:24 -04:00
paul 268386879b Add basic GitHub acc. info on about page 2022-09-10 02:43:46 -04:00
paul 28cbe47195 Remove "logo.svg" 2022-09-10 02:08:05 -04:00
paul cceb1b7316 Remove bloat
Keep rec and res files to not remove the pages themselves
2022-09-10 02:03:52 -04:00
paul 3fe3d7a5cc Create .gitkeep 2022-08-30 18:11:47 -04:00
paul 90c127d1f5 Delete thoughts-on-baba-is-you.md
Not sure why I had this here.
2022-08-30 17:19:07 -04:00
paul c9424d5d3f Remove links to sites that are barely used 2022-08-06 14:47:40 -04:00
paul 6410660b30 Update 6502 note 2022-05-16 14:26:38 -04:00
paul 42fae219a7 Remove canvas 2022-05-16 14:24:17 -04:00
paul 7dfac9aaac Update z80 note 2022-05-16 14:23:33 -04:00
paul c99cc6a61b Update steam note 2022-05-16 14:21:25 -04:00
paul 60376abcb9 Fix svg color 2022-05-16 12:19:26 -04:00
paul 62f2c4129a Simplify logo svg 2022-05-16 12:15:47 -04:00
paul 6e471223c4 Add mono font; fix pre for mobile 2022-05-15 18:55:12 -04:00
paul 7aadc47a3e Add license; make corrections; cleanup 2022-05-15 09:56:45 -04:00
paul 4e2a1adf9b Delete monogatari-watch-order.md 2022-05-13 15:45:16 -04:00
paul e7e1080d12 Add monogatari note 2022-05-04 03:15:25 -04:00
paul 1d41f7a15b Merge pull request #9 from LambdaPaul/dev
UI enhancements
2022-04-30 10:01:35 -04:00
paul 65de429197 UI enhancements 2022-04-30 09:56:18 -04:00
paul 0f5cb314b5 Merge pull request #8 from LambdaPaul/dev 2022-04-28 12:40:26 -04:00
paul a212076ce7 New notes 2022-04-28 12:37:12 -04:00
paul fda1c71b42 Parameterize css colors 2022-04-28 10:13:27 -04:00
paul 96e99e18bb More UI changes 2022-04-28 10:05:26 -04:00
paul f0b364c6a7 Give btns active colors 2022-04-28 09:24:17 -04:00
paul 9d761d0d3d More changes to structure and UI 2022-04-27 21:55:18 -04:00
paul 3d4b1b4f54 Make prop requests to static 2022-04-27 06:39:10 -04:00
paul a4a91cb881 Change gen dir for deploy purposes 2022-04-27 06:28:15 -04:00
paul cbffb30194 Remove file gen placeholder 2022-04-27 05:38:48 -04:00
paul b969aedf3c Merge branch 'master' of github.com:LambdaPaul/www 2022-04-27 05:37:53 -04:00
paul 48421d9fdb Content update and minor UI changes 2022-04-27 05:36:54 -04:00
paul e006a7af5a UI updates 2022-04-27 05:10:49 -04:00
paul d444e4da7d ignore dynamically generated import [bleh] 2022-04-27 04:07:40 -04:00
paul 6f37de0c2d Cleanup post caching 2022-04-27 04:03:21 -04:00
paul 98e904282b Create posts.json 2022-04-24 00:43:03 -04:00
paul 2cc267adda type error hotfixes 2022-04-24 00:40:26 -04:00
paul ec9a63570f Webpack hook for jsongen, [somewhat broken] UI changes 2022-04-24 00:27:51 -04:00
paul 79068f24ee Type error and other fixes 2022-04-23 20:35:53 -04:00
paul 60ed3b289d Accessibility changes 2022-04-23 19:26:23 -04:00
paul 5ea55565e6 Yamlify page objects 2022-04-23 19:03:43 -04:00
paul d31af55b7d Add orient express 2022-04-20 16:12:27 -04:00
paul 7a0b5da37e Change 404 tag 2022-02-15 22:24:17 -05:00
paul ada333ff7e Remove grade-calc 2022-02-15 22:19:49 -05:00
paul 3b9cd24663 Use higher res cover for Baba 2022-02-14 15:48:51 -05:00
paul b5c6a29524 Minor dep. updates 2022-02-14 15:45:27 -05:00
paul a40f30f5ec Add posts and old grade-calc
Fix small bugs
>bad commit
2022-02-14 15:32:58 -05:00
paul 3e6a540fce Update playlists.tsx 2022-01-24 19:58:01 -05:00
paul 424435ce75 Add Twitter 2021-12-17 20:27:27 -05:00
paul 66d117cc23 Update about.tsx 2021-12-13 13:35:49 -05:00
paul a338dc50c0 Remove redundant sort 2021-12-12 22:19:49 -05:00
paul 7e513dbcc2 Update rec. list 2021-12-08 03:54:18 -05:00
paul 60c7e5ff4c Add Cantarell font variations 2021-12-08 03:07:00 -05:00
paul 241048076d Add normalize.css 2021-12-08 02:53:04 -05:00
paul ae11dc6274 Add lang attr. for better accessibility 2021-12-08 02:20:30 -05:00
paul d549994656 move misplaced file 2021-12-08 02:02:43 -05:00
paul 5e257e8989 Fix keybind on Win changing focus to addrbar 2021-12-08 02:00:34 -05:00
paul 7bcdd7cc43 Fix single quotes in js obj 2021-12-08 01:14:11 -05:00
paul fd45209986 Update dependencies 2021-12-08 01:13:39 -05:00
paul 87d0af0c21 Merge pull request #6 from LambdaPaul/debug
Add resources
2021-12-07 23:31:34 -05:00
paul 723ac89a56 Display resources page data 2021-12-07 23:30:34 -05:00
paul 66e35084e4 Remove instructions that do not work 2021-12-07 23:23:56 -05:00
paul 4ad80d3f13 Merge pull request #5 from LambdaPaul/debug
Switch to next.js from vanilla
2021-12-07 22:42:40 -05:00
paul 2db4c28590 Init next.js port 2021-12-07 22:38:31 -05:00
paul 6fde6b4f15 Remove vanilla code 2021-12-06 19:23:41 -05:00
paul 515d5ed7e4 Make site.json for better automation 2021-10-01 02:39:41 -04:00
paul 24f2185746 Reduce result links size 2021-10-01 02:05:15 -04:00
paul dd535d2bed Fix fuzzy init sort 2021-10-01 02:03:22 -04:00
paul 1435d1893d Add playlists 2021-10-01 01:51:58 -04:00
paul cc8e6a3030 Add backspace help 2021-10-01 01:23:35 -04:00
paul 3fc09d4d2c Add netlify rel 2021-10-01 01:23:17 -04:00
paul 08c49b722a Delete CNAME 2021-10-01 00:49:06 -04:00
paul 4a2561318e Add about page 2021-10-01 00:28:24 -04:00
paul 1fd70d1280 Minify lambda SVG 2021-10-01 00:28:00 -04:00
paul feba1d5eed Fix arrow key scrolling and make it work on keydown instead of up
There is an issue with the new key based nav system. When control elements lose their focus, there is no good way to bring it back.
2021-09-24 01:34:34 -04:00
paul 5ca5b6c41a Improve UX and add more page links 2021-09-24 00:08:37 -04:00
paul af4a1f83e1 Add readme to home, fuzzy search instruction and make UI a tad bit better 2021-09-22 23:53:57 -04:00
paul f0ed92aab1 Remove redundant messages from README.md 2021-09-22 01:49:16 -04:00
paul d134f74dc0 Add README.md 2021-09-14 00:22:00 -04:00
paul 6cbcc4a791 Remove preset pages; force users to look pages up; fix bugs 2021-09-13 23:55:02 -04:00
paul 667a21b0d7 Add '.DS_Store' to ignore list 2021-09-13 22:52:58 -04:00
paul 53d4c9af12 Deemphasize persia.css footer 2021-08-08 13:32:28 -04:00
paul 0036ebe61f Make the styling consistent for all pages 2021-08-07 21:43:23 -04:00
paul a458adda33 Add favicon.ico 2021-08-07 19:11:35 -04:00
paul 437ee98f99 Fix broken README.md url 2021-08-07 18:52:30 -04:00
paul c610113083 Init www 2021-06-02 21:57:01 -04:00
59 changed files with 1015 additions and 1018 deletions
+3
View File
@@ -6,3 +6,6 @@ dist/
*.bun *.bun
**/.*.md **/.*.md
.env .env
public/posts.json
public/notes.json
public/sitemap.json
BIN
View File
Binary file not shown.
-13
View File
@@ -1,13 +0,0 @@
export type ChildrenType = JSX.Element | Array<ChildrenType>;
function Container(props: { children?: ChildrenType, ignore?: boolean }) {
if (props.ignore)
return <>{props.children}</>;
return (
<div className='container'>
{props.children}
</div>
);
}
export default Container;
-20
View File
@@ -1,20 +0,0 @@
import Title from './title';
import Container, { ChildrenType } from './container';
type LayoutProps = {
children?: ChildrenType,
removeContainer?: boolean,
};
function Layout(props: LayoutProps) {
return (
<>
<Title />
<Container ignore={props.removeContainer}>
{props.children}
</Container>
</>
);
}
export default Layout;
-25
View File
@@ -1,25 +0,0 @@
import Link from 'next/link';
import Pages from '../public/external.json';
function QuickLinks() {
return (
<div className='block'>
<h2>Quick Links</h2>
{
Object.entries(Pages).map(([title, link], i) => {
const extern = link.match(/^http/) && `blue extern` || '';
return (
<Link
key={i}
href={link}
className={`${extern} link button`}>
{title}
</Link>
);
})
}
</div>
);
}
export default QuickLinks;
-50
View File
@@ -1,50 +0,0 @@
import Link from "next/link";
import { toRelativeDate } from "../lib/date";
import style from '../styles/recent-posts.module.css';
import PostsInfo from '../public/posts.json';
function PostBlock({ slug, otime, title }: { slug: string, otime: string, title: string }) {
return (
<div className={style.block}>
<span className={style.postDate}>
{toRelativeDate(new Date(otime))}
</span>
<div className={style.postTitle}>
<Link href={`/posts/${slug}`}>
{title}
</Link>
</div>
</div>
);
}
function RecentPosts() {
const posts = Object.entries(PostsInfo).reverse();
if (!posts.length)
return <></>;
return (
<div className='block'>
<h2>Recent Posts</h2>
<div className={style.container}>
{posts?.slice(0, 10)
.map(([slug, post]: any, i: number) => {
return (
<PostBlock
key={i}
slug={slug}
title={post.title}
otime={post.otime} />
);
})}
</div>
{
posts.length > 10 &&
<div className={style.more}>
<Link href='/posts' >More...</Link>
</div>
}
</div>
);
}
export default RecentPosts;
+1 -1
View File
@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+21 -18
View File
@@ -1,15 +1,23 @@
const config = { import type {NextConfig } from 'next';
i18n: { import NextBundleAnalyzer from '@next/bundle-analyzer';
locales: ['en-US'],
defaultLocale: 'en-US' let config: NextConfig = {
reactStrictMode: true,
turbopack: {
rules: {
'*.txt': {
as: '*.js',
loaders: ['raw-loader'],
},
'*.md': {
as: '*.js',
loaders: ['raw-loader'],
}
},
resolveExtensions: ['.txt', '.md', '.tsx', '.ts', '.js']
}, },
webpack: (config, _options) => { webpack: (config, _options) => {
config.module.rules.push( config.module.rules.push(
{
test: /\.ya?ml$/,
use: 'js-yaml-loader',
},
{ {
test: /\.svg$/, test: /\.svg$/,
use: [{ loader: '@svgr/webpack' }], use: [{ loader: '@svgr/webpack' }],
@@ -26,10 +34,6 @@ const config = {
test: /\.txt$/, test: /\.txt$/,
type: 'asset/source', type: 'asset/source',
}, },
{
resourceQuery: /raw/,
type: 'asset/source',
},
); );
return config; return config;
@@ -37,10 +41,9 @@ const config = {
}; };
if (process.env.ANALYZE) { if (process.env.ANALYZE) {
const bundleAnalyzer = require('@next/bundle-analyzer')({ config = NextBundleAnalyzer({
enabled: true enabled: true
}); })(config);
module.exports = bundleAnalyzer(config);
} else {
module.exports = config;
} }
export default config;
+1 -5
View File
@@ -1,15 +1,11 @@
# Lua Programming Language # Lua Programming Language
<!-- TODO ## Lua 5.4 C API-->
## Lua 5.4 C API
## Lua 5.4 Bytecode ## Lua 5.4 Bytecode
> [!note]
> These are **unstable** and may differ in different versions of the language. > These are **unstable** and may differ in different versions of the language.
> They are not part of the language specification but an implementation detail, which in this case is the reference implementation. > They are not part of the language specification but an implementation detail, which in this case is the reference implementation.
> [!note]
> The reference implementation used to have a stack based but now uses a register based VM similar to how modern real computer architectures. > The reference implementation used to have a stack based but now uses a register based VM similar to how modern real computer architectures.
The instructions are 32 bits wide; every instruction has an opcode that takes up 7 bits, which leaves out 25 bits for the addresses and values. The instructions are 32 bits wide; every instruction has an opcode that takes up 7 bits, which leaves out 25 bits for the addresses and values.
-52
View File
@@ -1,52 +0,0 @@
# Programming Resources
A handy list of blog posts, articles, videos, and books that I would probably
refer to someone or within something.
- Untangling Lifetimes: The Arena Allocator
Making performant dynamic manual memory management in C feel almost like
garbage collection.
https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator
- Ryan Fleury's UI Series
Semi-paywalled
https://www.rfleury.com/p/ui-series-table-of-contents
- Immediate-Mode Graphical User Interfaces (2005)
https://caseymuratori.com/blog_0001
https://www.youtube.com/watch?v=Z1qyvQsjK5Y
- What Color is Your Function?
https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
- Real-time audio programming 101: time waits for nothing
http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
- Triangulation
https://www.humus.name/index.php?ID=228
- Quantifying the Performance of Garbage Collection vs. Explicit Memory
Management
https://people.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf
- Typing is Hard
https://3fx.ch/typing-is-hard.html
- The Aggregate Magic Algorithms
http://aggregate.org/MAGIC/
- Memory Allocation Strategies
https://www.gingerbill.org/series/memory-allocation-strategies/
+7
View File
@@ -0,0 +1,7 @@
# References
## [Intel® 64 and IA-32 Architectures Software Developers Manual](https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4)
## [CUDA C++ Programming Guide](https://docs.nvidia.com/cuda/pdf/CUDA_C_Programming_Guide.pdf)
## [Windows Internals Book](https://learn.microsoft.com/en-us/sysinternals/resources/windows-internals)
+95
View File
@@ -0,0 +1,95 @@
# Resources
A handy list of blog posts, articles, videos, and books that I would probably
refer to someone or within something.
## Programming
### What Every C Programmer Should Know About Undefined Behavior
- https://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
### Optimizing Software Occlusion Culling
- https://fgiesen.wordpress.com/2013/02/17/optimizing-sw-occlusion-culling-index/
### Mipmap selection in too much detail
- https://pema.dev/2025/05/09/mipmaps-too-much-detail/
### Memory Allocation Strategies
- https://www.gingerbill.org/series/memory-allocation-strategies/
### Immediate-Mode Graphical User Interfaces (2005)
- https://caseymuratori.com/blog_0001
- https://www.youtube.com/watch?v=Z1qyvQsjK5Y
### What Color is Your Function?
- https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
### Real-time audio programming 101: time waits for nothing
- http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
### Triangulation
- https://www.humus.name/index.php?ID=228
### Quantifying the Performance of Garbage Collection vs. Explicit Memory Management
- https://people.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf
### Typing is Hard
- https://3fx.ch/typing-is-hard.html
### Easy Scalable Text Rendering on the GPU
- https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac
### C and C++ Prioritize Performance over Correctness
- https://research.swtch.com/ub [[PDF](https://research.swtch.com/ub.pdf)]
### The Aggregate Magic Algorithms
- http://aggregate.org/MAGIC/
### You Could Have Invented Monads! (And Maybe You Already Have.)
- http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
### Fix Your Timestep!
- https://gafferongames.com/post/fix_your_timestep/
### UTF-8 Everywhere
- http://utf8everywhere.org
### Parsing Gigabytes of JSON per Second
- https://arxiv.org/abs/1902.08318 [[PDF](https://arxiv.org/pdf/1902.08318)]
### What are OKLCH colors?
- https://jakub.kr/components/oklch-colors
### Software Foundations series
- https://softwarefoundations.cis.upenn.edu/
### Software Rendering Alpha-Blending Tricks
- https://gist.github.com/mattiasgustavsson/c11e824e3d603d0c86e5e0dde4ecf839
### A Relational Model of Data for Large Shared Data Banks
- https://fermatslibrary.com/s/a-relational-model-of-data-for-large-shared-data-banks
### Powersort
- https://www.wild-inter.net/publications/munro-wild-2018.pdf
+33
View File
@@ -0,0 +1,33 @@
# Retro Gaming
The use of the term retro is debatable here as the term is used quite inconsistently. To be consistent, _retro_ here refers to anything older than the 7th generation of gaming consoles (x360, ps3) which came out in the mid-2000s.
## Recompilations
### Zelda64Recomp
- https://github.com/Zelda64Recomp/Zelda64Recomp
## Open-source Recreations
### OpenMW
- https://github.com/OpenMW/openmw
### OpenTTD
- https://github.com/OpenTTD/OpenTTD
### NFSIISE
- https://github.com/zaps166/NFSIISE
### RE3
- https://github.com/github/dmca/blob/master/2021/02/2021-02-19-take-two.md
- :(
## WidescreenFixesPack
- https://thirteenag.github.io/wfp
- https://github.com/ThirteenAG/WidescreenFixesPack
## Linux Arm Handhelds
- https://portmaster.games/
- native ports of the reimplementations of many old-skool games
+27 -8
View File
@@ -1,13 +1,26 @@
# Operating Systems # Software
## Cross-platform
### Media
- [mpv](https://mpv.io/)
## Windows ## Windows
- [PowerToys](https://learn.microsoft.com/en-us/windows/powertoys/)
- it's kind of hard to use Windows once you get used to using this
### Archive Utility
- [NanaZip](https://github.com/M2Team/NanaZip)
- 7-zip fork for Windows w/ zstd (which I use a lot), brotli, etc.
### Package Managers ### Package Managers
- Chocolatey
- requires Administrator permissions
- Winget - Winget
- comes with Windows - comes with Windows
- (it doesn't work half of the time for me) - Chocolatey
- requires Administrator permissions
### Mounting ISO, CUE images ### Mounting ISO, CUE images
@@ -26,6 +39,15 @@ Open it by exceuting the following command or saving it as a shortcut: `explorer
## MacOS ## MacOS
### Clipboard Management
- [maccy](https://maccy.app/)
- not sure why macOs doesn't have a native clipboard manager like Windows and KDE
### Terminal Emulator
- [iTerm2](https://iterm2.com/)
- terminal with features you'd probably want
### Package Manager ### Package Manager
- [HomeBrew](https://brew.sh) - [HomeBrew](https://brew.sh)
@@ -33,8 +55,5 @@ Open it by exceuting the following command or saving it as a shortcut: `explorer
### Video Players ### Video Players
- IINA - [IINA](https://iina.io/)
- video player based on mpv with native macOS UI - video player based on mpv with native macOS UI
- mpv
- mpv doesn't have a brew cask for Apple silicon; stolen-mpv exists but it is x86 only
- mpv brew formula is the cli tool which works pretty well but it is not as nice as packaged applications
-71
View File
@@ -1,71 +0,0 @@
# Steam Deck
- [Official Website](https://www.steamdeck.com/)
## Third-party Software
- [Decky Plugin Loader](https://decky.xyz/)
- Source: [GitHub / SteamDeckHomebrew](https://github.com/SteamDeckHomebrew)
- Installer:
[decky_installer.desktop](https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop)
## Console-like Youtube in Gaming Mode
- Using Chromium's undocumented command-line options, the user agent can be
changed to PlayStation's, Xbox's or Tizen's (Samsung's TV OS) and the
application can be launched in full screen by using the `--kiosk` flag. The
following XDG Desktop Configuration, for example, can be used and added as a
non-Steam game while in Desktop mode for access in gaming mode
```cfg
#!/usr/bin/env xdg-open
[Desktop]
Version=1.0
Type=Application
Name=YouTube TV
GenericName=Online Video Platform
Comment=An online video-sharing, social media platform
Exec=/usr/bin/flatpak run --branch=master --arch=x86_64 --file-forwarding org.chromium.Chrome @@ %F @@ --user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02' --kiosk 'https://www.youtube.com/tv'
Terminal=false
MimeType=text/plain; # $XDG_PATH contains the paths used to fetch icons, extensions for supported formats are optional Icon=com.youtube.tv
```
- Firefox can also be used however the supported command-line options are
limited
- The URL for the TV user interface is https://www.youtube.com/tv
- Without the user agent change, the above URL is inaccessible and will redirect
you to the desktop version of the website
- Adblockers like uBlock Origin, AdBlock Plus (both tested) do not remove ads
even if they work with the desktop version
- Choosing an Xbox user agent is recommended as button prompts match the Steam
Deck's `ABXY` button layout
- The Electron framework can be used to build a wrapper for the URL
- This is the preferable method as it supports exiting from within the
application, while browsers only support manual termination from the Steam
menu.
- Sample code for the electron app (assuming you can build linux binaries
for the target platform):
```javascript
// sample code to get started
const { app, BrowserWindow } = require('electron');
app
.whenReady()
.then(() => {
const win = new BrowserWindow({
backgroundColor: '#2e2c29',
kiosk: true,
});
win.maximize();
win.loadURL('https://youtube.com/tv');
win.webContents.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) '
+ 'AppleWebKit/537.36 (KHTML, like Gecko) '
+ 'Chrome/48.0.2564.82 Safari/537.36 Edge/20.02';
})
.catch(() => { });
```
## Miscellaneous
- When using a dock or a hub to connect to an external display, ensure the
display supports the refresh rate set on the device; some TVs and some
monitors only support refresh rates that are multiples of 30Hz
+113 -35
View File
@@ -1,33 +1,6 @@
# Steam Client # Steam
- [Steam Store](https://store.steampowered.com) - [Steam Store](https://store.steampowered.com)
- [SteamCMD](https://developer.valvesoftware.com/wiki/SteamCMD)
## Accessing the Console
- Use the following URIs on a browser or a file manager to open GUI client with
the console:
- `steam://nav/console`
- `steam://open/console`
- will not work if the Steam client is running in the background
- The `-console` flag can be used with the client executable
- Alternatively, SteamCMD, a command-line only version of the Steam client, can
be used
- [Windows
Binary](https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip)
- [Linux
Binary](https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz)
- [macOS
Binary](https://steamcdn-a.akamaihd.net/client/installer/steamcmd_osx.tar.gz)
## Downloading Older Depots
Download a single depot (used to download older versions of applications/games):
`download_depot <appid> <depotid> [<target manifestid>] [<delta manifestid>][<depot flags filter>]`
[SteamDB](https://steamdb.info/) can be used to find the required argument
values.
## Resources ## Resources
@@ -39,6 +12,10 @@ values.
- [SteamDB](https://steamdb.info/) - [SteamDB](https://steamdb.info/)
- tracks depot changes, price history, everything steam - tracks depot changes, price history, everything steam
- [gg.deals](https://gg.deals)
- tracks game deals for steam, steam key stores and other platforms
- [IsThereAnyDeal](https://isthereanydeal.com)
- similar to gg.deals except it does not support key seller tracking
- [SteamGifts](https://steamgifts.com/) - [SteamGifts](https://steamgifts.com/)
- giveaway Steam keys or take part in giveaways - giveaway Steam keys or take part in giveaways
- [SteamTradeMatcher](https://steamtradematcher.com/) - [SteamTradeMatcher](https://steamtradematcher.com/)
@@ -46,11 +23,112 @@ values.
- [ArchiSteamFarm](https://asf.justarchi.net) - [ArchiSteamFarm](https://asf.justarchi.net)
- useful bot written in C# to farm trading cards for owned games that can be - useful bot written in C# to farm trading cards for owned games that can be
sold sold
- [IsThereAnyDeal](https://isthereanydeal.com)
- tracks game deals for steam, steam key stores and other platforms
- somewhat broken although it is being migrated and modernized, see [New
ITAD](https://new.isthereanydeal.com)
- [gg.deals](https://gg.deals)
- newer than and similar to IsThereAnyDeal with modern UI
- [SteamGridDB](https://steamgriddb.com/) - [SteamGridDB](https://steamgriddb.com/)
- custom video game assets for games available and not available on steam - custom video game assets for games available and not available on Steam
- [ProtonDB](https://www.protondb.com/)
- community-sourced Linux and Steam Deck compatibility tracker
## Steam Client
- [Steam Client Valve Wiki Page](https://developer.valvesoftware.com/wiki/Steam)
- [SteamCMD Valve Wiki Page](https://developer.valvesoftware.com/wiki/SteamCMD)
### Accessing the Console
- Use the following URIs on a browser or a file manager to open GUI client with
the console:
- `steam://nav/console`
- `steam://open/console`
- will not work if the Steam client is running in the background
- The `-console` flag can be used with the client executable
- Alternatively, SteamCMD, a command-line only version of the Steam client, can
be used
- [Windows
Binary (.zip)](https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip)
- [Linux
Binary (.zip)](https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz)
- [macOS
Binary (.zip)](https://steamcdn-a.akamaihd.net/client/installer/steamcmd_osx.tar.gz)
### Downloading Older Depots
Download a single depot (used to download older versions of applications/games):
`download_depot <appid> <depotid> [<target manifestid>] [<delta manifestid>][<depot flags filter>]`
[SteamDB](https://steamdb.info/) can be used to find the required argument
values.
## Steam Deck
- [Official Website](https://www.steamdeck.com/)
### Third-party Software
- [Decky Plugin Loader](https://decky.xyz/)
- Source: [GitHub / SteamDeckHomebrew](https://github.com/SteamDeckHomebrew)
- Installer:
[decky_installer.desktop](https://github.com/SteamDeckHomebrew/decky-installer/releases/latest/download/decky_installer.desktop)
### Console-like Youtube in Gaming Mode
- Using Chromium's undocumented command-line options, the user agent can be
changed to PlayStation's, Xbox's or Tizen's (Samsung's TV OS) and the
application can be launched in full screen by using the `--kiosk` flag. The
following XDG Desktop Configuration, for example, can be used and added as a
non-Steam game while in Desktop mode for access in gaming mode
```ini
#!/usr/bin/env xdg-open
[Desktop]
Version=1.0
Type=Application
Name=YouTube TV
GenericName=Online Video Platform
Comment=An online video-sharing, social media platform
Exec=/usr/bin/flatpak run --branch=master --arch=x86_64 --file-forwarding org.chromium.Chrome @@ %F @@ --user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02' --kiosk 'https://www.youtube.com/tv'
Terminal=false
MimeType=text/plain;
# $XDG_PATH contains the paths used to fetch icons, extensions for supported formats are optional Icon=com.youtube.tv
```
- Firefox can also be used however the supported command-line options are
limited
- The URL for the TV user interface is https://www.youtube.com/tv
- Without the user agent change, the above URL is inaccessible and will redirect
you to the desktop version of the website
- Adblockers like uBlock Origin, AdBlock Plus (both tested) do not remove ads
even if they work with the desktop version
- Choosing an Xbox user agent is recommended as button prompts match the Steam
Deck's `ABXY` button layout
- The Electron framework can be used to build a wrapper for the URL
- This is the preferable method as it supports exiting from within the
application, while browsers only support manual termination from the Steam
menu.
- Sample code for the electron app (assuming you can build linux binaries
for the target platform):
```javascript
// sample code to get started
const { app, BrowserWindow } = require('electron');
app
.whenReady()
.then(() => {
const win = new BrowserWindow({
backgroundColor: '#2e2c29',
kiosk: true,
});
win.maximize();
win.loadURL('https://youtube.com/tv');
win.webContents.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) '
+ 'AppleWebKit/537.36 (KHTML, like Gecko) '
+ 'Chrome/48.0.2564.82 Safari/537.36 Edge/20.02';
})
.catch(() => { });
```
### Miscellaneous
- When using a dock or a hub to connect to an external display, ensure the
display supports the refresh rate set on the device; some TVs and some
monitors only support refresh rates that are multiples of 30Hz
+23 -16
View File
@@ -1,5 +1,7 @@
{ {
"private": true,
"scripts": { "scripts": {
"prebuild": "node ./scripts/generate-metadata.js",
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
@@ -7,28 +9,33 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"js-yaml-loader": "^1.2.2", "highlight.js": "^11.10.0",
"next": "^13.5.1", "next": "^15.3.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"react": "^18.2.0", "raw-loader": "^4.0.2",
"react-dom": "^18.2.0", "react": "^19.0.0",
"react-markdown": "^9.0.0", "react-dom": "^19.0.0",
"react-syntax-highlighter": "^15.5.0", "react-markdown": "^9.0.1",
"rehype-autolink-headings": "^7.1.0",
"rehype-highlight": "^7.0.0",
"rehype-highlight-code-lines": "^1.0.4",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-directive": "^3.0.0", "rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"remark-github-admonitions-to-directives": "^2.0.0", "remark-loader": "^6.0.0",
"remark-math": "^6.0.0",
"uri-js": "^4.4.1" "uri-js": "^4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^14.0.1", "@next/bundle-analyzer": "^15.0.4",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^8.1.0",
"@types/node": "^18.17.17", "@types/node": "^22.7.4",
"@types/react": "^18.2.22", "@types/react": "^19.0.1",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^19.0.1",
"@types/react-syntax-highlighter": "^15.5.7", "@types/react-syntax-highlighter": "^15.5.7",
"eslint": "^8.49.0", "eslint": "^9.11.1",
"eslint-config-next": "^13.5.1", "eslint-config-next": "^15.0.4",
"typescript": "^4.9.5" "typescript": "^5.6.2"
} }
} }
-37
View File
@@ -1,37 +0,0 @@
import Head from 'next/head';
import Link from 'next/link';
import style from '../styles/title.module.css';
function NotFoundPage() {
// clean this page up
return (
<>
<Head>
<title>404: Not Found | PaulW.XYZ</title>
</Head>
<div className={style.container}>
<h1 className={style.title}>
Page Not Found
</h1>
</div>
<div className={`${style.nav} h1`}>
<Link href='/'>PaulW.XYZ</Link> / ... ??? / 404: Not Found </div>
<div className='container'>
<section className='block text center'>
<h1>Error 404</h1>
<p>
<strong>Uh oh! The page you are looking for does not exist...</strong><br />
</p>
<Link href='/' className='button green back link'>Go Home</Link>
<a className='button blue link extern' href='https://en.wikipedia.org/wiki/List_of_HTTP_status_codes'>
More on HTTP status codes
</a>
</section>
</div>
</>
);
}
export default NotFoundPage;
-7
View File
@@ -1,7 +0,0 @@
import type { AppProps } from 'next/app'
import 'normalize.css';
import '../styles/global.css';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
-37
View File
@@ -1,37 +0,0 @@
import ReactMarkdown from 'react-markdown';
import ReadmeMd from '../README.md';
import License from '../LICENSE.txt';
import Layout from '../components/layout';
function AboutPage() {
return (
<Layout >
<section className='block'>
<p>Paul&apos;s Personal Website. I go by <a href='https://github.com/LambdaPaul'>@LambdaPaul</a> on GitHub and <a href='https://x.com/lambda_paul'>@lambda_paul</a> on X/Twitter.</p>
<p>Why did I create this?
The original motivation was to just play with Next.js as it pretty much did the things I wanted web pages to do. But it came at the cost of needless complexity. As I use the JavaScript/ECMAScript/Whatever-you-want-to-call-it-script more and more, I am convinced that it is not a platform worth pursuing because the more complex it gets, the less control I have over what it does and this platform and its users seems to be okay with that sort of loss. I have been instead pivoting toward things that impressed and got me interested in working with computers.</p>
<p>Most services/products are keen on going against what Steph Ango calls <a href='https://stephango.com/file-over-app'>File over app</a>, a philosophy in which you prioritize data over software, and anticipate and embrace the eventual death of software. People instead want subscription services that barely support open formats and sometimes do not support exporting data to commonly used formats. The goal here is to avoid storing artifacts under locations that are easily not accessible, not under my control, and does not lock me out of using it with other software. The only reason I have not completely abandoned this is thanks to my decision to rely on Markdown files alone. Had it been reliant on any cloud software, I would have started over.</p>
<p>Got any questions, concerns, or issues? Contact me via email: <code>lambdapaul [at] pm [dot] me</code>.</p>
</section>
<hr />
<section className='block'>
<p>Source for this site is available on GitHub: <a href='https://github.com/LambdaPaul/www'>https://github.com/LambdaPaul/www</a></p>
<p>Relevant information regarding the source is available on the repo and is also provided below.</p>
</section>
<section className='block'>
<h2>README</h2>
<ReactMarkdown>
{ReadmeMd.replace(/^#{1,5} /g, (s: string) => { return `#${s}` })}
</ReactMarkdown>
</section>
<section className='block'>
<h2>LICENSE</h2>
<pre className='license'>{License}</pre>
</section>
</Layout>
);
}
export default AboutPage;
-34
View File
@@ -1,34 +0,0 @@
import React from 'react';
import Link from 'next/link';
import Layout from '../components/layout';
import QuickLinks from '../components/quick-links';
import RecentNotes from '../components/recent-notes';
import RecentPosts from '../components/recent-posts';
import RootInfo from '../public/home.json';
function Nav() {
const nav = RootInfo;
return (
<div className='block'>
<h2>Navigation</h2>
{
Object.entries(nav).map(([slug, info], i) => {
return <Link key={i} href={slug} className='button green'>{info.title}</Link>
})
}
</div>
)
}
function HomePage() {
return (
<Layout>
<QuickLinks />
<RecentPosts />
<RecentNotes />
<Nav />
</Layout>
)
}
export default HomePage;
-90
View File
@@ -1,90 +0,0 @@
import ReactMarkdown from 'react-markdown';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { monokaiSublime as hlTheme } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import remarkDirective from 'remark-directive';
import remarkGithubAdmonitionsToDirectives from 'remark-github-admonitions-to-directives';
import Layout from '../../components/layout';
import readMarkdown from '../../lib/read-markdown';
import NotesInfo from '../../public/notes.json';
interface Note {
title: string,
mtime: string,
}
interface Notes {
[slug: string]: Note;
}
function Markdown({ content }: any) {
return <ReactMarkdown
remarkPlugins={[remarkGithubAdmonitionsToDirectives, remarkDirective]}
rehypePlugins={[rehypeRaw]}
components={{
code({ node, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return match
? (
<SyntaxHighlighter
showLineNumbers={true}
language={match[1]}
//@ts-ignore
style={hlTheme}
PreTag='div'
codeTagProps={{ style: { display: 'block' } }}
customStyle={{ padding: '0', borderRadius: '1rem' }}
{...props}
>{String(children).replace(/\n$/, '')}</SyntaxHighlighter>
)
: <code className={className} {...props}>
{children}
</code>
}
}}
>{content}</ReactMarkdown>
}
function Note({ note }: any) {
return (<>
<Layout >
<section className='block'>
<Markdown content={note.content} />
</section>
</Layout>
</>
);
}
export async function getStaticProps({ params }: any) {
const note: string = params.note;
const notesInfo: Notes = NotesInfo;
const noteInfo: Note = notesInfo[note];
return {
props: {
note: {
...noteInfo,
content: await readMarkdown('notes', note, true)
}
}
}
}
export async function getStaticPaths() {
return {
paths: Object.keys(NotesInfo).map((note: string) => {
return {
params: {
note
}
}
}),
fallback: false
};
}
export default Note;
-58
View File
@@ -1,58 +0,0 @@
import Link from 'next/link';
import Layout from '../../components/layout';
import { toRelativeDate } from '../../lib/date';
import NotesInfo from '../../public/notes.json';
function NoteEntry({ note }: { note: { title: string, mtime: string, slug: string } }) {
return (
<tr>
<td style={{ flex: '1 0 50%' }}>
<Link href={`/notes/${note.slug}`}>
{note.title}
</Link>
</td>
<td style={{ fontStyle: 'italic' }}>
{note.mtime && toRelativeDate(note.mtime)}
</td>
</tr>
);
}
function NotesPage() {
const notes = Object.entries(NotesInfo)
.map(([slug, note]) => {
return {
slug,
title: note.title,
mtime: new Date(note.mtime)
}
})
.sort(
(a, b) => {
return b.mtime.getTime() - a.mtime.getTime();
}
);
return (
<Layout>
{
!notes || notes.length === 0
&& <>No notes found</>
|| <table>
<tbody>
{notes.map(
(note: any, i: number) => {
return (<NoteEntry note={note} key={i} />);
}
)}
</tbody>
</table>
}
</Layout>
)
}
export default NotesPage;
-105
View File
@@ -1,105 +0,0 @@
import Layout from '../../components/layout';
import ReactMarkdown from 'react-markdown';
import style from '../../styles/post.module.css';
import PostsInfo from '../../public/posts.json';
import readMarkdown from '../../lib/read-markdown';
import DateTool from '../../lib/date';
interface Post {
title: string;
mtime: string;
otime?: string;
}
interface Posts {
[slug: string]: Post
}
function TimeBlock({ mtime, otime }: { mtime: string, otime: string }) {
const ampm = (h: number) => { if (h >= 12) return 'p.m.'; return 'a.m.'; };
const mdate = new Date(mtime);
const odate = new Date(otime);
const format = (date: Date) => {
const day = date.getDay();
const ord = <sup>{DateTool.getOrdinalDaySuffix(date.getDay())}</sup>;
const month = DateTool.getFullMonth(date.getMonth());
const year = date.getFullYear();
const hours = date.getHours() > 12 ? date.getHours() - 12 : date.getHours();
const minPrefix = date.getMinutes() < 10 ? '0' : '';
const minutes = date.getMinutes();
const twelveSfx = ampm(date.getHours());
return <>{day}{ord} {month} {year} at {hours}:{minPrefix}{minutes} {twelveSfx}</>
};
return (
<div style={{ textAlign: 'right', fontSize: '16px', fontFamily: 'Cantarell', fontStyle: 'italic' }}>
{
mtime ?
<div className='mtime' data-text={mdate.toISOString()}>
Last updated: {format(mdate)}
</div>
:
<></>
}
<div className='otime'>
{format(odate)}
</div>
</div>
);
}
function Post({ post }: { post: Post & { content: string, cover?: string, otime: string, mtime?: string } }) {
return (<>
<Layout removeContainer={true} >
{<div className={style.imageBlock}
style={{
backgroundImage:
post.cover ?
`url(/assets/images/${post.cover})` :
'linear-gradient(to bottom right, rgb(5, 51, 11), rgb(5, 45, 13) 15%, rgb(5, 39,15) 40%, rgb(0, 30, 16) 80%)'
}}></div>}
<div className={`${style.spacer} ${post.cover ? style.background : ''}`}></div>
<section className={`${style.block} block`}>
<div className='container'>
<TimeBlock mtime={post.mtime} otime={post.otime} />
<ReactMarkdown>{post.content}</ReactMarkdown>
</div>
</section>
<div className={style.spacer}></div>
</Layout>
</>
);
}
export async function getStaticProps({ params }: any) {
const postsInfo: Posts = PostsInfo;
const post: Post = postsInfo[params.post];
return {
props: {
post: {
...post,
content: await readMarkdown('posts', params.post, true)
}
}
}
}
export async function getStaticPaths() {
return {
paths: Object.keys(PostsInfo).map((post: string) => {
return {
params: {
post
}
}
}),
fallback: false
};
}
export default Post;
-53
View File
@@ -1,53 +0,0 @@
import Link from 'next/link';
import Layout from '../../components/layout';
import date from '../../lib/date';
import PostsInfo from '../../public/posts.json';
function PostsPage() {
return (
<Layout>
{Object.keys(PostsInfo).length && <Posts /> || <NoPosts />}
</Layout>
)
}
function NoPosts() {
return (<><div className='text center'>
<div>**crickets**</div>
<div>No posts found...</div>
<div><Link href='/' className='link button green back'>Go Home</Link></div>
</div></>);
}
function Posts() {
const posts = Object.entries(PostsInfo);
return (
<>
<table>
<tbody>
{
posts.map(([slug, post]: any, i: number) => {
return <tr key={i} style={{ alignItems: 'center' }}>
<td style={{ display: 'inline-block', textAlign: 'right', fontSize: '0.9rem' }}>
<div style={{ fontStyle: 'italics', fontSize: '.8rem' }}>{
post.mtime && (post.mtime != post.otime) && `Updated ${date.toRelativeDate(new Date(post.mtime))}`
}</div>
<div>{date.toRelativeDate(new Date(post.otime))}</div>
</td>
<td style={{
flex: '1 1 60%',
alignItems: 'center',
fontFamily: `'EB Garamond', 'Garamond', 'Times New Roman', Times, serif`
}}>
<Link href={`/posts/${slug}`} style={{ textDecoration: 'none' }}>{post.title}</Link>
</td>
</tr>
})}
</tbody>
</table>
</>
)
}
export default PostsPage;
-33
View File
@@ -1,33 +0,0 @@
import Link from 'next/link';
import Layout from '../components/layout';
import { Site } from '../lib/site';
import SiteMap from '../public/sitemap.json';
function traverseMap(head: Site, cwd = '', depth = 0) {
if (head.subpages === undefined)
return [];
let elements = [];
for (const [slug, info] of Object.entries(head.subpages)) {
if (slug === 'sitemap')
continue;
const path = `${cwd}/${slug}`;
const children = (<><dl style={{marginLeft: '3rem'}}> {traverseMap(info, path, depth + 1)}</dl></>);
elements.push(<>
<dt>{info.title}</dt>
<dd><Link href={path}>paulw.xyz{path}</Link></dd>
{children}
</>);
}
return elements;
}
function SiteMapPage() {
return <Layout>
<dl>{traverseMap(SiteMap)}</dl>
</Layout>;
}
export default SiteMapPage;
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"GitHub": "https://github.com/lambdapaul", "Git": "https://git.paulw.xyz/xyz",
"Twitter/X": "https://x.com/lambda_paul" "Twitter/X": "https://x.com/paulw_xyz"
} }
-1
View File
@@ -1 +0,0 @@
{"mos-6502":{"title":"MOS 6502 Microprocessor","mtime":"2023-10-29T18:05:52.439Z"},"zilog-z80":{"title":"Zilog Z80 Microprocessor","mtime":"2023-10-29T18:07:08.579Z"},"steam":{"title":"Steam Client","mtime":"2024-02-13T22:34:17.609Z"},"steam-deck":{"title":"Steam Deck","mtime":"2024-02-13T22:53:51.919Z"},"programming-resources":{"title":"Programming Resources","mtime":"2024-04-20T19:59:46.944Z"},"os":{"title":"Operating Systems","mtime":"2024-05-10T16:07:32.581Z"},"lua":{"title":"Lua Programming Language","mtime":"2024-09-13T08:45:18.515Z"},"browsers":{"title":"Web Browsers","mtime":"2024-09-13T08:47:57.942Z"}}
-1
View File
@@ -1 +0,0 @@
{}
-1
View File
@@ -1 +0,0 @@
{"title":"PaulW.XYZ","subpages":{"posts":{"title":"Posts","subpages":{}},"notes":{"title":"Notes","subpages":{"mos-6502":{"title":"MOS 6502 Microprocessor","mtime":"2023-10-29T18:05:52.439Z"},"zilog-z80":{"title":"Zilog Z80 Microprocessor","mtime":"2023-10-29T18:07:08.579Z"},"steam":{"title":"Steam Client","mtime":"2024-02-13T22:34:17.609Z"},"steam-deck":{"title":"Steam Deck","mtime":"2024-02-13T22:53:51.919Z"},"programming-resources":{"title":"Programming Resources","mtime":"2024-04-20T19:59:46.944Z"},"os":{"title":"Operating Systems","mtime":"2024-05-10T16:07:32.581Z"},"lua":{"title":"Lua Programming Language","mtime":"2024-09-13T08:45:18.515Z"},"browsers":{"title":"Web Browsers","mtime":"2024-09-13T08:47:57.942Z"}}},"about":{"title":"About"},"sitemap":{"title":"Site Map"}}}
+76 -81
View File
@@ -1,136 +1,131 @@
const fs = require('fs/promises'); const path = require('path')
const { createReadStream } = require('fs'); const fs = require('fs/promises')
const path = require('path');
const readline = require('readline/promises'); const gitRef = process.env.WWW_GIT_REF ?? 'master'
// const { info } = require('console'); const giteaApiRepo = `https://git.paulw.xyz/api/v1/repos/xyz/www/`
async function readFirstLines(filePath, lineCount = 1) { async function readFirstLines(filePath, lineCount = 1) {
return new Promise((resolve, reject) => { const gitFileFetch = await fetch(`${giteaApiRepo}raw/${filePath}?ref=${gitRef}`)
try { if (!gitFileFetch.ok) return null
const stream = createReadStream(filePath, { encoding: 'utf-8' }); const file = await gitFileFetch.text()
const rl = readline.createInterface({ input: stream }); const lines = file.split('\n')
let counter = 0; const out = []
const lines = []; for (let i = 0; i < lineCount && i < lines.length; i++) {
rl.on('line', (line) => { out.push(lines[i])
counter++;
lines.push(line);
if (counter >= lineCount) {
rl.close();
rl.removeAllListeners();
} }
}); return out
rl.on('close', () => {
resolve(lines)
});
} catch (e) {
reject(e)
}
});
} }
async function getTitle(filePath) { async function getTitle(filePath) {
const firstLines = await readFirstLines(filePath); const firstLines = await readFirstLines(filePath)
if (firstLines === undefined || firstLines.length === 0) if (firstLines === null || firstLines === undefined || firstLines.length === 0) return null
return null; let title = firstLines[0]
let title = firstLines[0];
if (title.substring(0, 2) !== '# ') if (title.substring(0, 2) !== '# ') return null
return null;
title = title title = title
.substring(1, firstLines[0].length) .substring(1, firstLines[0].length)
.trim(); .trim()
if (title.length < 3) if (title.length < 3)
return null; return null
return title; return title
} }
async function getMarkdownMetadata(dir) { async function getMarkdownMetadata(dir) {
const dirPath = path.join(process.cwd(), dir); const dirGitInfoFetch = await fetch(`${giteaApiRepo}contents/${dir}/?ref=${gitRef}`)
const files = (await fs.readdir(dirPath, 'utf-8')) if (!dirGitInfoFetch.ok) return {}
.filter((file) => {
return /^[^.].*.md$/.test(file); const commits = {}
}) const out = {}
const dirGitInfo = await dirGitInfoFetch.json()
for (const file of dirGitInfo) {
if (file.name.startsWith('.') || !file.name.endsWith('.md')) continue
const title = await getTitle(file.path)
if (title === null) continue
const slug = file.name.replace(/\.md$/, '')
let mtime = new Date(); // better to have an incorrect recent date than the more incorrect unix time 0 (assuming the host doesn't have messed up clock)
const out = {}; if (!(file.last_commit_sha in commits)) {
for (const file of files) { const lastCommitSha = await fetch(`${giteaApiRepo}/git/commits/${file.last_commit_sha}`)
const filePath = path.join(dirPath, file); if (lastCommitSha.ok) {
const title = await getTitle(filePath); const commitJson = await lastCommitSha.json()
if (title === null) commits[commitJson.sha] = (new Date(commitJson.created))
continue; }
}
mtime = commits[file.last_commit_sha]
const slug = file.replace(/\.md$/, '');
// const pagePath = path.join('/', dir, slug);
out[slug] = { out[slug] = {
title: title, title: title,
// path: pagePath, mtime: mtime.toISOString(),
mtime: (await fs.stat(filePath)).mtime,
};
} }
return out; }
return out
} }
async function readFilesMetadata(dir) { async function readFilesMetadata(dir) {
const filePath = jsonFilePath(dir); const filePath = jsonFilePath(dir)
try { try {
const fileContent = await fs.readFile(filePath, 'utf-8'); const fileContent = await fs.readFile(filePath, 'utf-8')
const metadata = JSON.parse(fileContent); const metadata = JSON.parse(fileContent)
return metadata; return metadata
} catch { } catch {
return []; return []
} }
} }
async function writeFilesMetadata(filePath, metadata) { async function writeFilesMetadata(filePath, metadata) {
try { try {
await fs.writeFile(filePath, JSON.stringify(metadata), 'utf-8'); await fs.writeFile(filePath, JSON.stringify(metadata, null, 4), 'utf-8')
} catch (error) { } catch (error) {
console.error(error); console.error(error)
} }
} }
function jsonFilePath(dir) { function jsonFilePath(dir) {
return path.join(process.cwd(), 'public', `${dir}.json`); return path.join(process.cwd(), 'public', `${dir}.json`); // ehh
} }
async function generateNotesMetadata() { async function generateNotesMetadata() {
const dir = 'notes'; const dir = 'notes'
await writeFilesMetadata(jsonFilePath(dir), await getMarkdownMetadata(dir)); await writeFilesMetadata(jsonFilePath(dir), await getMarkdownMetadata(dir))
} }
async function generatePostsMetadata() { async function generatePostsMetadata() {
const dir = 'posts'; const dir = 'posts'
const currMetadata = await readFilesMetadata(dir); const currMetadata = await readFilesMetadata(dir)
const generatedMetadata = await getMarkdownMetadata(dir); const generatedMetadata = await getMarkdownMetadata(dir)
const newMetadata = {}; const newMetadata = {}
for (const [name, data] of Object.entries(generatedMetadata)) { for (const [name, data] of Object.entries(generatedMetadata)) {
let otime; let otime = new Date()
if (currMetadata[name] !== undefined && currMetadata[name].otime !== undefined) if (currMetadata[name]?.otime !== undefined && currMetadata[name]?.otime !== null)
otime = currMetadata[name].otime otime = currMetadata[name].otime ?? otime
else else
otime = data.mtime; otime = data.mtime ?? otime
newMetadata[name] = { ...data, otime } newMetadata[name] = { ...data, otime }
} }
await writeFilesMetadata(jsonFilePath(dir), newMetadata); await writeFilesMetadata(jsonFilePath(dir), newMetadata)
} }
async function generateSiteMap() { async function generateSiteMap() {
await generateNotesMetadata(); await generateNotesMetadata()
await generatePostsMetadata(); await generatePostsMetadata()
const sitemap = { const sitemap = {
title: 'PaulW.XYZ', title: 'PaulW.XYZ',
subpages: await readFilesMetadata('home') pages: await readFilesMetadata('home')
};
const pages = ['posts', 'notes'];
for (const page of pages) {
sitemap.subpages[page].subpages = await readFilesMetadata(page);
} }
await writeFilesMetadata(jsonFilePath('sitemap'), sitemap); const pages = ['posts', 'notes']
for (const page of pages) {
sitemap.pages[page].pages = await readFilesMetadata(page)
}
await writeFilesMetadata(jsonFilePath('sitemap'), sitemap)
} }
generateSiteMap(); generateSiteMap()
Vendored
-5
View File
@@ -1,8 +1,3 @@
declare module '*.yaml' {
const record: Record<string, any>;
export default record;
}
declare module '*.md' { declare module '*.md' {
const rawmd: string; const rawmd: string;
export default rawmd; export default rawmd;
+43
View File
@@ -0,0 +1,43 @@
import ReactMarkdown from 'react-markdown';
import ReadmeMd from '../../../README.md';
import License from '../../../LICENSE.txt';
function AboutPage() {
return (
<>
<section className='block'>
<p>Paul&apos;s Personal Website.</p>
<p> You can find me on the following platforms:</p>
<ul>
<li>X/Twitter: <a href='https://x.com/paulw_xyz'>paulw_xyz</a></li>
<li>GitHub: <a href='https://github.com/paulwxyz'>paulwxyz</a></li>
{/* <li>BlueSky (unused): <a href='https://bsky.app/profile/@paulw.xyz'>@paulw.xyz</a></li> */}
<li><a href='https://git.paulw.xyz/xyz'>git.paulw.xyz</a></li>
</ul>
<p>
The original motivation was to just play with Next.js as it pretty much did the things I wanted web pages to do. But it came at the cost of needless complexity. As I use the JavaScript/ECMAScript/Whatever-you-want-to-call-it-script more and more, I am convinced that it is not a platform worth pursuing because the more complex it gets, the less control I have over what it does and this platform and its users seems to be okay with that sort of loss. I have been instead pivoting toward things that impressed and got me interested in working with computers.</p>
<p>Most services/products are keen on going against the <a href='https://stephango.com/file-over-app'>file over app</a> philosophy which entails prioritizing data over software and anticipate and embrace the eventual death of software. People instead want subscription services that barely support open formats and sometimes do not support exporting data to commonly used formats. The goal here is to avoid storing artifacts under locations that are easily not accessible, not under my control, and does not lock me out of using it with other software. The only reason I have not completely abandoned this is thanks to my decision to rely on Markdown files alone. Had it been reliant on any cloud software, I would have started over.</p>
<p>Got any questions, concerns, or issues? Contact me via email: <code>contact [at] paulw [dot] xyz</code>.</p>
</section>
<hr />
<section className='block'>
<p>Source for this site is available on GitHub: <a href='https://github.com/paulwxyz/www'>paulwxyz/www</a> and <a href='https://git.paulw.xyz/xyz/www'>git.paulw.xyz/xyz/www</a></p>
<p>Relevant information regarding the source is available on the repo and is also provided below.</p>
</section>
<section className='block'>
<h2>README</h2>
<ReactMarkdown>
{ReadmeMd.replace(/^#{6}\s+(.*)\s+$/gm, (s: string, a) => `**${a}**\n`).replace(/^#{1,5} /gm, (s: string) => { return `##${s}` })}
</ReactMarkdown>
</section>
<section className='block'>
<h2>LICENSE</h2>
<pre className='license'>{License}</pre>
</section>
</>
);
}
export default AboutPage;
+9
View File
@@ -0,0 +1,9 @@
export default function Container(props: { children?: React.ReactNode, ignore?: boolean }) {
if (props.ignore)
return <>{props.children}</>;
return (
<div className='container'>
{props.children}
</div>
);
}
+17
View File
@@ -0,0 +1,17 @@
'use client'
import Link from 'next/link';
import { toRelativeDate } from '../lib/date';
export function NoteEntry({ note }: { note: { title: string, mtime: string, slug: string } }) {
return (
<tr>
<td style={{ flex: '1 0 50%' }}>
<Link href={`/notes/${note.slug}`}>
{note.title}
</Link>
</td>
<td style={{ fontStyle: 'italic' }}>
{note.mtime && toRelativeDate(note.mtime)}
</td>
</tr>
);
}
+24
View File
@@ -0,0 +1,24 @@
import Link from 'next/link';
import Pages from '../../../public/external.json';
function QuickLinks() {
return (
<div className='block'>
{
Object.entries(Pages).map(([title, link]) => {
const extern = link.match(/^http/) && `blue extern` || '';
return (
<Link
key={link}
href={link}
className={`${extern} link button`}>
{title}
</Link>
);
})
}
</div>
);
}
export default QuickLinks;
@@ -1,5 +1,5 @@
import Link from "next/link"; import Link from "next/link";
import NotesInfo from '../public/notes.json'; import NotesInfo from '../../../public/notes.json';
function RecentNotes() { function RecentNotes() {
const notes = Object.entries(NotesInfo) const notes = Object.entries(NotesInfo)
@@ -22,7 +22,7 @@ function RecentNotes() {
{notes?.slice(0, 5) {notes?.slice(0, 5)
.map(({slug, title, mtime}) => { .map(({slug, title, mtime}) => {
return ( return (
<li key={mtime.getTime()} > <li key={slug} >
<Link href={`/notes/${slug}`}>{title}</Link> <Link href={`/notes/${slug}`}>{title}</Link>
</li> </li>
); );
+50
View File
@@ -0,0 +1,50 @@
import Link from "next/link";
import { toRelativeDate } from "../lib/date";
import style from './recent-posts.module.css';
import PostsInfo from '../../../public/posts.json';
function PostBlock({ slug, otime, title }: { slug: string, otime: string, title: string }) {
return (
<div className={style.block}>
<span className={style.postDate}>
{toRelativeDate(new Date(otime))}
</span>
<div className={style.postTitle}>
<Link href={`/posts/${slug}`}>
{title}
</Link>
</div>
</div>
);
}
function RecentPosts() {
const posts = Object.entries(PostsInfo).reverse();
if (!posts.length)
return <></>;
return (
<div className='block'>
<h2>Recent Posts</h2>
<div className={style.container}>
{posts?.slice(0, 10)
.map(([slug, post]: any, i: number) => {
return (
<PostBlock
key={slug}
slug={slug}
title={post.title}
otime={post.otime} />
);
})}
</div>
{
posts.length > 10 &&
<div className={style.more}>
<Link href='/posts' >More...</Link>
</div>
}
</div>
);
}
export default RecentPosts;
@@ -1,42 +1,44 @@
'use client'
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { usePathname } from 'next/navigation';
import { Fragment } from 'react';
import style from '../styles/title.module.css'; import style from './title.module.css';
import SiteMap from '../public/sitemap.json'; import SiteMap from '../../../public/sitemap.json';
import Head from 'next/head'; import { Sites } from '../lib/site';
import { SiteSubPages } from '../lib/site';
function createPathElements(ancestors: Array<{ name: string, path: string }>) { function createPathElements(ancestors: Array<{ name: string, path: string }>) {
let currentPath = ''; let currentPath = '';
return ancestors.map((ancestor, id) => { return ancestors.map((ancestor, id) => {
currentPath += `/${ancestor.path}` currentPath += `/${ancestor.path}`
return ( return (
<> <Fragment key={currentPath} >
<Link key={id + 1} href={currentPath}>{ancestor.name}</Link> <Link href={currentPath}>{ancestor.name}</Link>
<> / </> <> / </>
</> </Fragment>
); );
}); });
} }
function Title() { export default function Title() {
const pagePath = usePathname();
const router = useRouter();
const pagePath = router.asPath;
const splitPath: Array<{ name: string, path: string }> = []; const splitPath: Array<{ name: string, path: string }> = [];
let currRoot: SiteSubPages = SiteMap.subpages; // TODO(Paul): clean this up
let currRoot: Sites = SiteMap.pages;
let title: string | null = null; let title: string | null = null;
if (pagePath !== '/') { if (pagePath && pagePath !== '/') {
const subPaths = pagePath.split('?')[0].split('#')[0].split('/'); const subPaths = pagePath.split('?')[0].split('#')[0].split('/');
for (const p of subPaths.slice(1, subPaths.length)) { for (const p of subPaths.slice(1, subPaths.length)) {
if (!p || !currRoot[p])
continue;
splitPath.push({ name: currRoot[p].title, path: p }); splitPath.push({ name: currRoot[p].title, path: p });
if (currRoot === undefined if (currRoot === undefined
|| currRoot[p] === undefined || currRoot[p] === undefined
|| currRoot[p].subpages === undefined) || currRoot[p].pages === undefined)
break; break;
currRoot = currRoot[p].subpages!; currRoot = currRoot[p].pages!;
} }
if (splitPath !== undefined && splitPath.length > 0) if (splitPath !== undefined && splitPath.length > 0)
title = splitPath.pop()!.name; title = splitPath.pop()!.name;
@@ -46,15 +48,15 @@ function Title() {
const pathElements = splitPath && createPathElements(splitPath) || <></>; const pathElements = splitPath && createPathElements(splitPath) || <></>;
return ( return (
<> <>
<Head> {/* <head>
<title>{title && `${title} | PaulW.XYZ` || 'PaulW.XYZ'}</title> <title>{title && `${title} | PaulW.XYZ` || 'PaulW.XYZ'}</title>
</Head> </head> */}
<div className={style.container}> <div className={style.container}>
<h1 className={style.title}> <h1 className={style.title}>
{title || 'PaulW.XYZ'} {title || 'PaulW.XYZ'}
</h1> </h1>
</div> </div>
<div className={`${style.nav} h1`}> <div className={style.nav}>
{ {
title title
? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{title}</> ? <><Link href='/'>PaulW.XYZ</Link> / {pathElements}{title}</>
@@ -64,5 +66,3 @@ function Title() {
</> </>
); );
} }
export default Title;
+3 -11
View File
@@ -235,9 +235,8 @@ code {
} }
table { table {
display: flex; margin: 1rem auto;
flex-direction: column; width:100%;
margin: 1rem 0;
overflow: hidden; overflow: hidden;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@@ -258,11 +257,6 @@ table tr:last-of-type
border-bottom-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem;
} }
table thead tr,
table tbody tr {
display: flex;
}
table tbody tr:nth-of-type(2n) { table tbody tr:nth-of-type(2n) {
background-color: var(--table-even-color); background-color: var(--table-even-color);
} }
@@ -273,12 +267,10 @@ table tbody tr:nth-of-type(2n+1) {
table thead tr th, table thead tr th,
table tbody tr td { table tbody tr td {
display: flex;
flex: 1;
padding: .25rem 0.75rem; padding: .25rem 0.75rem;
} }
li { ul li {
list-style-type: square; list-style-type: square;
} }
+18
View File
@@ -0,0 +1,18 @@
import type {Metadata} from 'next'
import 'normalize.css'
import './global.css'
import Container from './components/container'
import Title from './components/title'
export default function RootLayout({children,}: Readonly<{children: React.ReactNode}>) {
return (
<html lang='en'>
<body>
<Title />
<Container>
{children}
</Container>
</body>
</html>
)
}
+32 -5
View File
@@ -14,22 +14,43 @@ const months = [
'December' 'December'
]; ];
function get12HourTime(pdate: Date | string): string {
const date = (typeof pdate === 'string') ? new Date(pdate) : pdate;
let hours = date.getHours();
const minutes = date.getMinutes();
let meridiem = 'A.M.';
let strhours = ''
if (hours > 12) {
hours -= 12;
meridiem = 'P.M.';
}
if (hours === 0)
hours = 12;
return `${hours}:${minutes < 10 ? '0' : ''}${minutes} ${meridiem}`;
}
function toHumanReadableDate(date: Date | string, disable?: { year?: boolean, month?: boolean, day?: boolean }) { function toHumanReadableDate(date: Date | string, disable?: { year?: boolean, month?: boolean, day?: boolean }) {
const oDate = (typeof date === 'string') ? new Date(date) : date; const oDate = (typeof date === 'string') ? new Date(date) : date;
const year = oDate.getFullYear(); const year = oDate.getFullYear();
const month = months[oDate.getMonth()]; const month = months[oDate.getMonth()];
const day = oDate.getDate(); const day = oDate.getDate();
const suffix = getOrdinalDaySuffix(day)
let out = !disable?.day ? `${day}${getOrdinalDaySuffix(day)}` : ''; let out = '';
out = !disable?.month ? `${out} ${month}` : out; out = !disable?.month ? `${month}` : '';
out = !disable?.year ? `${out} ${year}` : out; out = !disable?.day ? `${out} ${day}${suffix}` : out;
out = !disable?.year ? `${out}, ${year}` : out;
return out; return out;
} }
export function getOrdinalDaySuffix(day: number) { export function getOrdinalDaySuffix(day: number): string {
switch (day) { switch (day) {
case 1: case 1:
case 21: case 21:
@@ -46,6 +67,11 @@ export function getOrdinalDaySuffix(day: number) {
} }
} }
export function toLocaleString(pdate: Date | string): string {
const date = (typeof pdate === 'string') ? new Date(pdate) : pdate;
return `${toHumanReadableDate(date)} at ${get12HourTime(date)}`;
}
export function toRelativeDate(date: Date | string): string { export function toRelativeDate(date: Date | string): string {
const oDate = (typeof date === 'string') ? new Date(date) : date; const oDate = (typeof date === 'string') ? new Date(date) : date;
@@ -100,6 +126,7 @@ const DateTool = {
getFullMonth, getFullMonth,
isValid, isValid,
getOrdinalDaySuffix, getOrdinalDaySuffix,
toLocaleString,
}; };
export default DateTool; export default DateTool;
+2 -2
View File
@@ -1,11 +1,11 @@
export interface Site { export interface Site {
title: string; title: string;
subpages?: SiteSubPages; pages?: Sites;
mtime?: string; mtime?: string;
otime?: string; otime?: string;
} }
export interface SiteSubPages { export interface Sites {
[slug: string]: Site; [slug: string]: Site;
} }
+35
View File
@@ -0,0 +1,35 @@
import Link from 'next/link';
import style from '../components/title.module.css';
function NotFoundPage() {
// TODO: figure out a way to somehow get next to ignore layout in special cases. tried /not-found/page.tsx but it doesn't work :X
return (
<>
{/* <head>
<title>404: Not Found | PaulW.XYZ</title>
</head>
<div className={style.container}>
<h1 className={style.title}>
Page Not Found
</h1>
</div>
<div className={`${style.nav} h1`}><Link href='/'>PaulW.XYZ</Link> / ... ??? / 404: Not Found</div>
<div className='container'>*/}
<section className='block text center'>
<h1>Error 404</h1>
<p>
<strong>Uh oh! The page you are looking for does not exist...</strong><br />
</p>
<Link href='/' className='button green back link'>Go Home</Link>
<a className='button blue link extern' href='https://en.wikipedia.org/wiki/List_of_HTTP_status_codes'>
More on HTTP status codes
</a>
</section>
{/*</div>*/}
</>
);
}
export default NotFoundPage;
+7
View File
@@ -0,0 +1,7 @@
.last-updated {
text-align: right;
display: block;
font-style: italic;
font-size: 1rem;
margin: 0.5rem 0.75rem;
}
+66
View File
@@ -0,0 +1,66 @@
import ReactMarkdown from 'react-markdown';
import { PluggableList } from 'unified';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeHighlight from 'rehype-highlight';
import rehypeHighlightCodeLines, { type HighlightLinesOptions } from 'rehype-highlight-code-lines';
import readMarkdown from '../../lib/read-markdown';
import { toLocaleString } from '../../lib/date';
import NotesInfo from '../../../../public/notes.json';
import style from './note.module.css';
import 'highlight.js/styles/monokai-sublime.css';
import 'katex/dist/katex.min.css';
interface Note {
title: string,
mtime: string,
content?: string,
}
interface Notes {
[slug: string]: Note;
}
function Markdown({ content }: any) {
const remarkPlugins: PluggableList = [
remarkGfm,
remarkMath,
];
const rehypePlugins: PluggableList = [
rehypeSlug,
rehypeAutolinkHeadings,
rehypeRaw,
rehypeHighlight,
rehypeKatex,
];
return <ReactMarkdown remarkPlugins={remarkPlugins} rehypePlugins={rehypePlugins}>
{content}
</ReactMarkdown>
}
export default async function Note({params}: {params: Promise<{note: string}>}) {
const note = (await params).note
const n = await getNotes(note)
return (<>
<span className={style['last-updated']}>
Last updated: {toLocaleString(n.mtime)}
</span>
<section className='block'>
<Markdown content={n.content} />
</section>
</>
);
}
async function getNotes(name: string) {
const notesInfo: Notes = NotesInfo;
return {...notesInfo[name], content: await readMarkdown('notes', name, true)}
}
+39
View File
@@ -0,0 +1,39 @@
import NotesInfo from '../../../public/notes.json';
import { NoteEntry } from '../components/note-entry';
function NotesPage() {
const notes = Object.entries(NotesInfo)
.map(([slug, note]) => {
return {
slug,
title: note.title,
mtime: new Date(note.mtime)
}
})
.sort(
(a, b) => {
return b.mtime.getTime() - a.mtime.getTime();
}
);
return (
<>
{
!notes || notes.length === 0
&& <>No notes found</>
|| <table>
<tbody>
{notes.map(
(note: any) => {
return (<NoteEntry note={note} key={note.slug} />);
}
)}
</tbody>
</table>
}
</>
)
}
export default NotesPage;
+33
View File
@@ -0,0 +1,33 @@
import React from 'react';
import Link from 'next/link';
import QuickLinks from './components/quick-links';
import RecentNotes from './components/recent-notes';
import RecentPosts from './components/recent-posts';
import RootInfo from '../../public/home.json';
function Nav() {
const nav = Object.entries(RootInfo);
return (
<div className='block'>
<h2>Navigation</h2>
{
nav.map(([slug, info]) => {
return <Link key={slug} href={slug} className='button green'>{info.title}</Link>
})
}
</div>
)
}
function HomePage() {
return (
<>
<QuickLinks />
<RecentPosts />
<RecentNotes />
<Nav />
</>
)
}
export default HomePage;
+85
View File
@@ -0,0 +1,85 @@
import ReactMarkdown from 'react-markdown';
import style from '../../styles/post.module.css';
import PostsInfo from '../../../../public/posts.json';
import readMarkdown from '../../lib/read-markdown';
import DateTool, { toLocaleString } from '../../lib/date';
interface IPost {
title: string;
mtime: string;
otime?: string;
}
function TimeBlock({ mtime, otime }: { mtime: string, otime: string }) {
const ampm = (h: number) => { if (h >= 12) return 'p.m.'; return 'a.m.'; };
const mdate = new Date(mtime);
const odate = new Date(otime);
const format = (date: Date) => {
const day = date.getDay();
const ord = <sup>{DateTool.getOrdinalDaySuffix(date.getDay())}</sup>;
const month = DateTool.getFullMonth(date.getMonth());
const year = date.getFullYear();
const hours = date.getHours() > 12 ? date.getHours() - 12 : date.getHours();
const minPrefix = date.getMinutes() < 10 ? '0' : '';
const minutes = date.getMinutes();
const twelveSfx = ampm(date.getHours());
return <>{day}{ord} {month} {year} at {hours}:{minPrefix}{minutes} {twelveSfx}</>
};
return (
<div style={{ textAlign: 'right', fontSize: '16px', fontFamily: 'Cantarell', fontStyle: 'italic' }}>
{
mtime ?
<div className='mtime' data-text={mdate.toISOString()}>
Last updated: {format(mdate)}
</div>
:
<></>
}
<div className='otime'>
{format(odate)}
</div>
</div>
);
}
// post: IPost & { content: string, cover?: string, otime: string, mtime?: string }
export default async function Post({ params }: {params: Promise<{post: string}>}) {
const post = await getPost((await params).post);
if (!post)
return <></>;
return (<>
<div className='container'>
{ post.otime !== post.mtime && post.mtime &&
<span className={style.time}>
Last updated: {toLocaleString(post.mtime)}
</span>
}
<span className={style.time}>
{toLocaleString(post.otime)}
</span>
</div>
{<div className={style.imageBlock}
style={{
backgroundImage:
post.cover ?
`url(/assets/images/${post.cover})` :
'linear-gradient(to bottom right, rgb(5, 51, 11), rgb(5, 45, 13) 15%, rgb(5, 39,15) 40%, rgb(0, 30, 16) 80%)'
}}></div>}
<div className={`${style.spacer} ${post.cover ? style.background : ''}`}></div>
<section className={`${style.block} block`}>
<div className='container'>
<ReactMarkdown>{post.content}</ReactMarkdown>
</div>
</section>
<div className={style.spacer}></div>
</>
);
}
async function getPost(n: string) {
const postsInfo: Record<string, (IPost & { cover?: string, otime: string, mtime?: string })> = PostsInfo;
return {...postsInfo[n], content: await readMarkdown('posts', n, true)};
}
+49
View File
@@ -0,0 +1,49 @@
import Link from 'next/link';
import date from '../lib/date';
import PostsInfo from '../../../public/posts.json';
function PostsPage() {
return (<>
{Object.keys(PostsInfo).length && <Posts /> || <NoPosts />}
</>
)
}
function NoPosts() {
return (<div className='text center'>
<div>**crickets**</div>
<div>No posts found...</div>
<div><Link href='/' className='link button green back'>Go Home</Link></div>
</div>);
}
function Posts() {
const posts = Object.entries(PostsInfo);
return (
<table>
<tbody>
{
posts.map(([slug, post]: [string, any]) => {
return (<tr key={slug} style={{ alignItems: 'center' }}>
<td style={{ display: 'inline-block', textAlign: 'right', fontSize: '0.9rem' }}>
<div style={{ fontStyle: 'italics', fontSize: '.8rem' }}>{
post.mtime && (post.mtime != post.otime) && `Updated ${date.toRelativeDate(new Date(post.mtime))}`
}</div>
<div>{date.toRelativeDate(new Date(post.otime))}</div>
</td>
<td style={{
fontFamily: `'EB Garamond', 'Garamond', 'Times New Roman', Times, serif`
, fontSize: '1.25rem'
}}>
<Link href={`/posts/${slug}`} style={{ textDecoration: 'none' }}>{post.title}</Link>
</td>
</tr>)
})
}
</tbody>
</table>
)
}
export default PostsPage;
+41
View File
@@ -0,0 +1,41 @@
import Link from 'next/link';
import { Sites } from '../lib/site';
import SiteMap from '../../../public/sitemap.json';
function Desc(props: any) {
return (
<dl style={props.style}>
<dt>{props.term}</dt>
<dd>{props.details}</dd>
{props.children}
</dl>
);
}
function traverseMap(head?: Sites, cwd = '', depth = 0) {
if (!head) return [];
let elements = [];
for (const [slug, site] of Object.entries(head)) {
if (slug === 'sitemap')
continue;
let details;
let list;
const path = `${cwd}/${slug}`;
details = <Link href={path}>paulw.xyz{path}</Link>;
list = traverseMap(site.pages, path, depth + 1);
elements.push(<Desc style={{marginLeft: '3rem'}} key={site.title} term={site.title} details={details}>{list}</Desc>)
}
return elements;
}
function SiteMapPage() {
return <>
{traverseMap(SiteMap.pages)}
</>;
}
export default SiteMapPage;
@@ -31,3 +31,11 @@
.background.spacer { .background.spacer {
height: 25rem; height: 25rem;
} }
.time {
text-align: center;
display: block;
font-style: italic;
font-size: 1rem;
margin: 0.5rem 0.75rem;
}
-55
View File
@@ -1,55 +0,0 @@
.avatarContainer {
padding: 0.5rem;
text-align: center;
overflow: hidden;
}
.avatarContainer img {
border-radius: 1rem;
}
.cardLabel {
display: block;
font-weight: 700;
font-size: 1rem;
padding: .5rem;
}
.cardLabel:after {
content: ':';
}
.cardValue {
background-color: #303436;
font-size: 1rem;
padding: 0.25rem 1rem;
margin: 0.25rem 0;
border: none;
display: block;
border-radius: 0.5rem;
}
@media screen and (min-width: 800px) {
.card {
display: flex;
}
.cardTable {
flex: 1 1;
padding: 0.5rem;
align-items: stretch;
}
.cardRow {
display: flex;
}
.cardLabel {
flex: .2 1;
}
.cardValue {
flex: 1 1;
}
}
-39
View File
@@ -1,39 +0,0 @@
.desc {
font-size: 0.95rem;
}
.listItem {
padding: 0.25rem;
display: block;
}
.listItem::before {
content: '■';
padding: 0 0.5rem;
}
.listItemDesc {
display: inline-block;
padding: 0.25rem;
margin-left: 2.5rem;
font-size: 0.95rem;
}
.block .block {
margin: 0;
margin-left: 0.5rem;
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);
}
+14 -9
View File
@@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2017", "target": "es2023",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@@ -13,18 +9,27 @@
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"incremental": true "incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
"lib/slug.js" "lib/slug.js",
".next/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"