First version of webring with simple landing page and control functions.
This commit is contained in:
commit
554960fbef
22 changed files with 3621 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal 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
0
README.md
Normal file
104
flake.lock
generated
Normal file
104
flake.lock
generated
Normal 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
32
flake.nix
Normal 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
8
functions/next.ts
Normal 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
8
functions/previous.ts
Normal 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
6
functions/random.ts
Normal 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
3213
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal 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
5
public/_routes.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"include": ["/previous", "/random", "/next"],
|
||||||
|
"exclude": ["/"]
|
||||||
|
}
|
15
src/render_templates.ts
Normal file
15
src/render_templates.ts
Normal 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
6
src/tsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
43
src/webring.ts
Normal file
43
src/webring.ts
Normal 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
60
templates/index.html
Normal 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
7
test/index.spec.ts
Normal 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
8
test/tsconfig.json
Normal 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
22
tsconfig.json
Normal 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
11
vitest.config.mts
Normal 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
8
webring_data.json
Normal 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
5
worker-configuration.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Generated by Wrangler by running `wrangler types`
|
||||||
|
|
||||||
|
interface Env {
|
||||||
|
NODE_VERSION: "22.8.0";
|
||||||
|
}
|
8
wrangler.toml
Normal file
8
wrangler.toml
Normal 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"
|
Loading…
Add table
Reference in a new issue