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