First version of webring with simple landing page and control functions.

This commit is contained in:
DS 2024-09-27 16:34:11 -07:00
commit 554960fbef
22 changed files with 3621 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

29
.gitignore vendored Normal file
View file

@ -0,0 +1,29 @@
node_modules/
logs
_.log
npm-debug.log_
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Output of 'npm pack'
\*.tgz
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# wrangler project
.dev.vars
.wrangler/
# Others
.direnv/
# Template rendering artifacts
public/*.html

0
README.md Normal file
View file

104
flake.lock generated Normal file
View file

@ -0,0 +1,104 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nil": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1726716330,
"narHash": "sha256-mIuOP4I51eFLquRaxMKx67pHmhatZrcVPjfHL98v/M8=",
"owner": "oxalica",
"repo": "nil",
"rev": "c8e8ce72442a164d89d3fdeaae0bcc405f8c015a",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "nil",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1727348695,
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nil": "nil",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nil",
"nixpkgs"
]
},
"locked": {
"lastModified": 1726453838,
"narHash": "sha256-pupsow4L79SBfNwT6vh/5RAbVZuhngIA0RTCZksXmZY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "ca2e79cd22625d214b8437c2c4080ce79bd9f7d2",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

32
flake.nix Normal file
View file

@ -0,0 +1,32 @@
{
description = "Epesooj webring.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nil = {
url = "github:oxalica/nil";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nil, ... }:
let
pkgs = import nixpkgs {
system = "x86_64-linux";
};
in
{
devShells.x86_64-linux = {
default = pkgs.mkShell {
packages = with pkgs; [
nodePackages_latest.nodejs
# Both of these used with VSCode.
nixpkgs-fmt
nil.packages.${system}.default
];
};
};
};
}

8
functions/next.ts Normal file
View file

@ -0,0 +1,8 @@
import { next } from '../src/webring';
export const onRequestGet: PagesFunction = async (context) => {
const id = new URL(context.request.url).searchParams.get('id') ?? 'main';
const item = next(id);
const url = item?.url ?? new URL(context.request.url).origin;
return Response.redirect(url, 303);
};

8
functions/previous.ts Normal file
View file

@ -0,0 +1,8 @@
import { previous } from '../src/webring';
export const onRequestGet: PagesFunction = async (context) => {
const id = new URL(context.request.url).searchParams.get('id') ?? 'main';
const item = previous(id);
const url = item?.url ?? new URL(context.request.url).origin;
return Response.redirect(url, 303);
};

6
functions/random.ts Normal file
View file

@ -0,0 +1,6 @@
import { randomEntry } from '../src/webring';
export const onRequestGet: PagesFunction = async (context) => {
const url = randomEntry()?.url ?? new URL(context.request.url).origin;
return Response.redirect(url, 303);
};

3213
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "epesooj-webring",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler pages dev",
"start": "wrangler pages dev",
"test": "vitest",
"cf-typegen": "wrangler types",
"render-templates": "tsx src/render_templates.ts"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.5.12",
"@cloudflare/workers-types": "^4.20240925.0",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"vitest": "^2.1.1",
"wrangler": "^3.78.12"
}
}

5
public/_routes.json Normal file
View file

@ -0,0 +1,5 @@
{
"version": 1,
"include": ["/previous", "/random", "/next"],
"exclude": ["/"]
}

15
src/render_templates.ts Normal file
View file

@ -0,0 +1,15 @@
import { WebringData, WebringEntry } from './webring';
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
function generateEntryHTML(entry: WebringEntry) {
return `<div><a href="${entry.url}">${entry.title}</a><span>by ${entry.author}</span></div>`;
}
let index = readFileSync(
join(import.meta.dirname, '../templates/index.html'),
'utf8'
);
const renderedEntries = WebringData.map(generateEntryHTML).join('\n');
index = index.replace('{{WEBRING_ENTRIES}}', renderedEntries);
writeFileSync(join(import.meta.dirname, '../public/index.html'), index);

6
src/tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["node"]
}
}

43
src/webring.ts Normal file
View file

@ -0,0 +1,43 @@
import WebringData from '../webring_data.json';
export { WebringData };
export type WebringEntry = (typeof WebringData)[number];
export function randomEntry() {
const selectedIndex = Math.floor(Math.random() * WebringData.length);
return WebringData[selectedIndex];
}
export function previous(id: string) {
id = id.toLowerCase();
if (id === 'main') {
return WebringData[WebringData.length - 1];
}
let itemIndex = WebringData.findIndex((e) => e.id.toLowerCase() === id);
if (itemIndex === -1) {
return randomEntry();
}
itemIndex += WebringData.length - 1; // `length` is added to force the index to wrap around in case it goes negative. The `- 1` is what makes us go to the previous entry.
return WebringData[itemIndex % WebringData.length];
}
export function next(id: string) {
id = id.toLowerCase();
if (id === 'main') {
return WebringData[0];
}
let itemIndex = WebringData.findIndex((e) => e.id.toLowerCase() === id);
if (itemIndex === -1) {
return randomEntry();
}
itemIndex += 1;
return WebringData[itemIndex % WebringData.length];
}

60
templates/index.html Normal file
View file

@ -0,0 +1,60 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
overflow: hidden;
padding: 0;
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: sans-serif;
}
.controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
column-gap: 1rem;
}
a {
text-decoration: none;
color: rgb(0, 80, 183);
}
h2 {
font-size: 1.25rem;
}
.directory > div {
display: flex;
flex-direction: column;
align-items: center;
}
.directory > div > a {
font-size: 1.1rem;
font-weight: 700;
}
.directory > div > span {
font-size: 0.8rem;
}
</style>
</head>
<body>
<nav class="controls" aria-label="webring-controls">
<a href="/previous?id=main">Previous site</a>
<a href="/random">Random site</a>
<a href="/next?id=main">Next site</a>
</nav>
<h2>Epesooj webring</h2>
<h2 id="webring-directory">Directory</h2>
<nav class="directory" aria-labelledby="webring-directory">
{{WEBRING_ENTRIES}}
</nav>
</body>
</html>

7
test/index.spec.ts Normal file
View file

@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest';
describe('Hello World worker', () => {
it('succeeds simple test', async () => {
expect(1 + 1).toBe(2);
});
});

8
test/tsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers"]
},
"include": ["./**/*.ts", "../functions/env.d.ts"],
"exclude": []
}

22
tsconfig.json Normal file
View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["esnext"],
"module": "esnext",
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"types": ["@cloudflare/workers-types/2023-07-01"],
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"noEmit": true,
"isolatedModules": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true
},
"exclude": ["test"],
"include": ["worker-configuration.d.ts", "src/**/*.ts", "functions/**/*.ts"]
}

11
vitest.config.mts Normal file
View file

@ -0,0 +1,11 @@
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});

8
webring_data.json Normal file
View file

@ -0,0 +1,8 @@
[
{
"id": "dslog",
"title": "DS Log",
"author": "Bernardo Vecchia Stein / Daniel Sidhion",
"url": "https://sidhion.com/blog"
}
]

5
worker-configuration.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
// Generated by Wrangler by running `wrangler types`
interface Env {
NODE_VERSION: "22.8.0";
}

8
wrangler.toml Normal file
View file

@ -0,0 +1,8 @@
#:schema node_modules/wrangler/config-schema.json
name = "epesooj-webring"
pages_build_output_dir = "./public"
compatibility_date = "2024-09-27"
compatibility_flags = ["nodejs_compat"]
[vars]
NODE_VERSION = "22.8.0"