Early draft of website content
This commit is contained in:
parent
ef8aaa6f6d
commit
f7bc525310
8 changed files with 163 additions and 23 deletions
|
@ -14,7 +14,10 @@ export function BasePage(props: BaseProps) {
|
||||||
<body>
|
<body>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/index.html">Home</a> <a href="/updates.html">Updates</a> <a href="/projects.html">Projects</a>
|
<a href="/index.html">Home</a>
|
||||||
|
<a href="/updates.html">Updates</a>
|
||||||
|
<a href="/words.html">Words</a>
|
||||||
|
<a href="/projects.html">Projects</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
18
content/ideas.txt
Normal file
18
content/ideas.txt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
- Words
|
||||||
|
- Making a website with HTML
|
||||||
|
- What you want to have
|
||||||
|
- A way to write components that can be reused in pages with an HTML like syntax.
|
||||||
|
- You need this to maintain headers, footers, widgets and consistent site wide content without going insane.
|
||||||
|
- You want it to be HTML like so that you don't have to learn another language to write and inspect your HTML.
|
||||||
|
- A way to easily render those components into HTML files that you can open with your browser to test.
|
||||||
|
- A programming language you can use to write arbitrary code to combines your HTML components with data.
|
||||||
|
- A system that watches for changes during development and shows it in the browser immediately.
|
||||||
|
- How I implemented this with Node.js and TypeScript.
|
||||||
|
- Mention React+JSX, Vue.js, and Svelte.
|
||||||
|
- Projects
|
||||||
|
- Buddhabrot renderer.
|
||||||
|
- Wodden Drawing Board.
|
||||||
|
- Flying Hornwitser papercraft.
|
||||||
|
- Prototype Soren Plush.
|
||||||
|
- Blender to CSS export script.
|
||||||
|
- Download Archive: A zip file containing every page so that you can browse the site locally and keep it.
|
|
@ -1,12 +1,15 @@
|
||||||
import type { Page } from "./types.js";
|
import type { Page } from "./types.js";
|
||||||
import { index } from "./index.js";
|
import { index } from "./index.js";
|
||||||
import { updates, updatesIndex } from "./updates.js";
|
import { updates, updatesIndex } from "./updates.js";
|
||||||
|
import { words, wordsIndex } from "./words.js";
|
||||||
import { projects, projectsIndex } from "./projects.js";
|
import { projects, projectsIndex } from "./projects.js";
|
||||||
|
|
||||||
export const pages: Page[] = [
|
export const pages: Page[] = [
|
||||||
index,
|
index,
|
||||||
updatesIndex,
|
updatesIndex,
|
||||||
...updates,
|
...updates,
|
||||||
|
wordsIndex,
|
||||||
|
...words,
|
||||||
projectsIndex,
|
projectsIndex,
|
||||||
...projects,
|
...projects,
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,33 +1,15 @@
|
||||||
import { BasePage } from "./bases.js";
|
import { BasePage } from "./bases.js";
|
||||||
import type { Page } from "./types.js";
|
import type { Page } from "./types.js";
|
||||||
|
import mySite from "./projects/my-site.js";
|
||||||
|
|
||||||
export const projects: Page[] = [
|
export const projects: Page[] = [
|
||||||
{
|
mySite,
|
||||||
title: "Buddhabrot renderer",
|
|
||||||
ref: "/projects/buddhabrot.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Wooden Drawing Board",
|
|
||||||
ref: "/projects/drafting-board.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Flying Hornwitser Paper Craft",
|
|
||||||
ref: "/projects/paper-hornwitser.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Prototype Soren Plush",
|
|
||||||
ref: "/projects/plush-soren.html",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Blender to CSS export script",
|
|
||||||
ref: "/projects/blender-css.html",
|
|
||||||
},
|
|
||||||
].map(page => ({
|
].map(page => ({
|
||||||
title: page.title,
|
title: page.title,
|
||||||
ref: page.ref,
|
ref: page.ref,
|
||||||
content: <BasePage title={page.title}>
|
content: <BasePage title={page.title}>
|
||||||
<h1>{page.title}</h1>
|
<h1>{page.title}</h1>
|
||||||
<p>Placeholder content</p>
|
{page.content}
|
||||||
</BasePage>
|
</BasePage>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
50
content/projects/my-site.tsx
Normal file
50
content/projects/my-site.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import type { ProjectMeta } from "../types.js";
|
||||||
|
const project: ProjectMeta = {
|
||||||
|
status: "draft",
|
||||||
|
title: "My Website",
|
||||||
|
ref: "/projects/my-site.html",
|
||||||
|
startedAt: "2025-01-20",
|
||||||
|
};
|
||||||
|
const content = <>
|
||||||
|
<p>
|
||||||
|
A decade ago I tried making my own website. I got a domain, wrote some code to generate my site, put some content on it and had big aspirations for what I would do with my very own place on the internet. Unfortunately that project lost steam more or less as soon as it started and was left abandoned gathering dust ever since.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is my second attempt at making a website for myself. Inspired in large part when I surfed the small web and discovered personal pages like <a href="https://melvian.xyz/">Melvian's beautiful site</a>, the insanity that is <a href="https://melonland.net/">MelonLand</a>, and whole communities centred around small personal pages and projects like <a href="https://nekoweb.org/">Nekoweb</a>, and <a href="https://tildeverse.org/">tidleverse.org</a>.
|
||||||
|
</p>
|
||||||
|
<h2>Architecture</h2>
|
||||||
|
<p>
|
||||||
|
I've decided to use my website as a place to experiment with a hybrid architecture that sits in-between a static site generator and a fully dynamic site. Instead of having custom code generate the whole response on every request, my code instead generates the HTML and then writes it to a folder just like a static site generator would, but in response to requests from web clients.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This has some interesting advantages:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
HTML content is only generated when it's changed, rather than being redone on every request.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
There's a high barrier against implementing features that would make every request return a different response, as that doesn't play nice with the system. This helps to make the site highly cacheable.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Caching works without me having to implement any special logic for it. The webserver detects when files are changed and handles correctly replying to clients with cached responses.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
A complete archive of the site is always available as a bunch of HTML files in the root of the webserver's directory. This makes creating snapshots of the site trivial, just archive the directory!
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Of course, this would only work for a site who's content changes only rarely. Which is precisely what I expect the vast majority of the content on my site to be: Content that once made is rarely revised.
|
||||||
|
</p>
|
||||||
|
<h2>Frameworks on a diet</h2>
|
||||||
|
<p>
|
||||||
|
I thought about options for languages and frameworks to build my site on and eventually settled on using TypeScript with JSX running on Node.js to generate static pages. I wanted my site to be as close to working with plain HTML as practical, while still being easy to compose and reuse different parts together.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I opted to use JSX because I don't want to gouge my eyes out writing HTML with pure JavaScript notation (something I incidentally have tried to do before). To represent the HTML as data I wrote <a href="https://github.com/Hornwitser/antihtml">antihtml</a>, a tiny library that only does one thing: Treat HTML as a data structure that can be serialised.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
What that means is that I write my content mostly as if it was plain HTML, but get to combine the parts that make up my site using JavaScript logic.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
export default { ...project, content };
|
|
@ -1,7 +1,29 @@
|
||||||
import { Element } from "antihtml";
|
import type { Node, Element } from "antihtml";
|
||||||
|
|
||||||
export interface Page {
|
export interface Page {
|
||||||
title: string,
|
title: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
content: Element,
|
content: Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProjectMeta {
|
||||||
|
status: 'draft' | 'published',
|
||||||
|
title: string,
|
||||||
|
ref: string,
|
||||||
|
startedAt: string,
|
||||||
|
endedAt?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Project extends ProjectMeta {
|
||||||
|
content: Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface WordsMeta {
|
||||||
|
status: "draft" | "published",
|
||||||
|
title: string,
|
||||||
|
ref: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Words extends WordsMeta {
|
||||||
|
content: Node,
|
||||||
|
};
|
||||||
|
|
29
content/words.tsx
Normal file
29
content/words.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { BasePage } from "./bases.js";
|
||||||
|
import type { Page } from "./types.js";
|
||||||
|
import uselessDashboard from "./words/useless-dashboard.js";
|
||||||
|
|
||||||
|
export const words: Page[] = [
|
||||||
|
uselessDashboard,
|
||||||
|
].map(page => ({
|
||||||
|
title: page.title,
|
||||||
|
ref: page.ref,
|
||||||
|
content: <BasePage title={page.title}>
|
||||||
|
<h1>{page.title}</h1>
|
||||||
|
{page.content}
|
||||||
|
</BasePage>
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
const title = "Ramblings and other wordy content";
|
||||||
|
export const wordsIndex: Page = {
|
||||||
|
title,
|
||||||
|
ref: "/words.html",
|
||||||
|
content: <BasePage title={title}>
|
||||||
|
<main>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<ul>
|
||||||
|
{ words.map(word => <li><a href={word.ref}>{word.title}</a></li>)}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</BasePage>
|
||||||
|
}
|
33
content/words/useless-dashboard.tsx
Normal file
33
content/words/useless-dashboard.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import type { WordsMeta } from "../types.js";
|
||||||
|
const words: WordsMeta = {
|
||||||
|
status: "draft",
|
||||||
|
title: "Useless Dashboards",
|
||||||
|
ref: "/words/useless-dashboard.html",
|
||||||
|
};
|
||||||
|
const content = <>
|
||||||
|
<p>
|
||||||
|
I have had the misfortune of trying to work with the Google Workspace's Admin panel and it was not a pleasant experience.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Key points
|
||||||
|
- Slow and sluggish to use, seemingly every interaction is behind extra clicks on panels that take seconds to load.
|
||||||
|
- No way to get a list of overrides for permissions. You need to click through every section of every sub-category of every app to just see if a group has different configs!
|
||||||
|
- No way to list the groups an external account is member of, you have to go wade through every group to check, or write code to do it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A few notable bugs:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
If I'm logged into multiple Google accounts and not using my primary account and I click on a link in that takes me somewhere else, it often tries to open the panel as my primary user. Which of course doesn't have access to that part and redirect me to some unhelpful error page instead. This isn't just the admin panel that has this problem, seemingly every google product does this, and it's so bad you're better of using a separate browser profile for each Google account just so that links between Google apps will work.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
I needed to be a member of a specific group to be able to create shared drives, as that group was the only one configured to not deny creating them. So I added myself to the group and I still couldn't create shared drives. The option was greyed out, and no amount of refreshing or logging out and in fix it. But of course when I toggled the permission off and on again it magically worked immediately. Makes me wonder what other access control can get stuck with the wrong value.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The API endpoint for checking if a member is part of a group gives you access denied if you try to use it on an external account that's part of the group. But other related endpoints work fine on external accounts.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>;
|
||||||
|
|
||||||
|
export default { ...words, content };
|
Loading…
Add table
Add a link
Reference in a new issue