Mirror of my GitHub Pages site www.spenser.black/
0

Configure Feed

Select the types of activity you want to include in your feed.

Add option to generate nickname from ID (#16)

authored by

Spenser Black and committed by
GitHub
(Jul 18, 2026, 6:37 PM UTC) fbec6c6b 90a6d4d0

+204 -50
+2 -1
package.json
··· 22 22 "@steamdown/html": "1.0.0-beta.2", 23 23 "astro": "^7.1.1", 24 24 "svelte": "^5.56.6", 25 - "typescript": "^6.0.3" 25 + "typescript": "^6.0.3", 26 + "uuid": "^14.0.1" 26 27 }, 27 28 "devDependencies": { 28 29 "cspell": "^10.0.1",
+9
pnpm-lock.yaml
··· 26 26 typescript: 27 27 specifier: ^6.0.3 28 28 version: 6.0.3 29 + uuid: 30 + specifier: ^14.0.1 31 + version: 14.0.1 29 32 devDependencies: 30 33 cspell: 31 34 specifier: ^10.0.1 ··· 1822 1825 optional: true 1823 1826 uploadthing: 1824 1827 optional: true 1828 + 1829 + uuid@14.0.1: 1830 + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} 1831 + hasBin: true 1825 1832 1826 1833 vfile-location@5.0.3: 1827 1834 resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} ··· 3645 3652 node-fetch-native: 1.6.7 3646 3653 ofetch: 1.5.1 3647 3654 ufo: 1.6.4 3655 + 3656 + uuid@14.0.1: {} 3648 3657 3649 3658 vfile-location@5.0.3: 3650 3659 dependencies:
+43 -44
src/components/WmNickname.svelte
··· 1 1 <script lang="ts"> 2 - import { coinFlip } from "../scripts/chance"; 3 - import { 4 - prefix as getPrefix, 5 - name as createName, 6 - suffix as getSuffix, 7 - } from "../scripts/nicknames"; 2 + import Seeded from "./WmNicknameSeeded.svelte"; 3 + import Randomized from "./WmNicknameRandomized.svelte"; 8 4 9 - /** 10 - * Creates a randomized name getter that has a chance to return null. 11 - * @param maker The callable that gets a randomized name. 12 - * @param nullChance If set, this is the chance that the result will be null. Defaults to a 50/50 chance. 13 - */ 14 - const createOptionalNameMaker = 15 - (maker: () => string, nullChance?: number) => () => 16 - coinFlip(nullChance) ? null : maker(); 17 - const createPrefix = createOptionalNameMaker(getPrefix, 0.25); 18 - const createSuffix = createOptionalNameMaker(getSuffix); 5 + type Generator = "seeded" | "randomized"; 6 + let generator = $state<Generator>("seeded"); 19 7 20 8 let prefix = $state<string | null>(null); 21 - let name = $state(createName()); 9 + let name = $state<undefined | string>(""); 22 10 let suffix = $state<string | null>(null); 23 11 24 - const setNameDecorators = () => { 25 - suffix = createSuffix(); 26 - // NOTE If the suffix is null, guarantee a prefix to ensure that the full nickname 27 - // is unique. 28 - if (suffix == null) { 29 - prefix = getPrefix(); 30 - } else { 31 - prefix = createPrefix(); 32 - } 33 - }; 34 - setNameDecorators(); 35 - 36 - const onSubmit = (e: SubmitEvent) => { 12 + const onSubmit = (e: SubmitEvent): void => { 37 13 e.preventDefault(); 38 - name = createName(); 39 - setNameDecorators(); 40 14 }; 41 15 </script> 42 16 43 - <form id="nickname-form" onsubmit={onSubmit}> 44 - <button type="submit" id="nickname-generate" class="primary" 45 - >(Re)generate</button 46 - > 17 + <form id="form-nickname-generator" onsubmit={onSubmit}> 18 + <div class="option-input"> 19 + <input 20 + type="radio" 21 + id="radio-seeded" 22 + name="generator" 23 + value="seeded" 24 + bind:group={generator} 25 + /> 26 + <label for="radio-seeded">From ID</label> 27 + </div> 28 + <div class="option-input"> 29 + <input 30 + type="radio" 31 + id="radio-randomized" 32 + name="generator" 33 + value="randomized" 34 + bind:group={generator} 35 + /> 36 + <label for="radio-randomized">Randomized</label> 37 + </div> 47 38 </form> 39 + {#if generator === "seeded"} 40 + <Seeded bind:prefix bind:name bind:suffix /> 41 + {:else} 42 + <Randomized bind:prefix bind:name bind:suffix /> 43 + {/if} 48 44 49 - <output for="nickname-generate"> 50 - <p> 51 - <strong id="nickname" 52 - >{#if prefix != null}<u>{prefix}</u>{" "}{/if}<u>{name}</u 53 - >{#if suffix != null}{" "}<u>{suffix}</u>{/if}</strong 54 - > 55 - </p> 56 - </output> 45 + <p> 46 + {#if name} 47 + Your nickname is 48 + <output for="nickname-generate"> 49 + <strong id="nickname" 50 + >{#if prefix != null}<u>{prefix}</u>{" "}{/if}<u>{name}</u 51 + >{#if suffix != null}{" "}<u>{suffix}</u>{/if}</strong 52 + > 53 + </output> 54 + {/if} 55 + </p>
+60
src/components/WmNicknameRandomized.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from "svelte"; 3 + import { coinFlip } from "../scripts/chance"; 4 + import { 5 + prefix as getPrefix, 6 + name as createName, 7 + suffix as getSuffix, 8 + } from "../scripts/nicknames"; 9 + 10 + /** 11 + * Creates a randomized name getter that has a chance to return null. 12 + * @param maker The callable that gets a randomized name. 13 + * @param nullChance If set, this is the chance that the result will be null. Defaults to a 50/50 chance. 14 + */ 15 + const createOptionalNameMaker = 16 + (maker: () => string, nullChance?: number) => () => 17 + coinFlip(nullChance) ? null : maker(); 18 + const createPrefix = createOptionalNameMaker(getPrefix, 0.25); 19 + const createSuffix = createOptionalNameMaker(getSuffix); 20 + 21 + const initialName = createName(); 22 + const initialSuffix = createSuffix(); 23 + // NOTE If the suffix is null, guarantee a prefix to ensure that the full nickname is 24 + // unique. 25 + const initialPrefix = initialSuffix == null ? getPrefix() : createPrefix(); 26 + 27 + let { 28 + prefix = $bindable<string | null>(initialPrefix), 29 + name = $bindable(initialName), 30 + suffix = $bindable<string | null>(initialSuffix), 31 + } = $props(); 32 + 33 + const setNameDecorators = () => { 34 + suffix = createSuffix(); 35 + // NOTE If the suffix is null, guarantee a prefix to ensure that the full nickname 36 + // is unique. 37 + if (suffix == null) { 38 + prefix = getPrefix(); 39 + } else { 40 + prefix = createPrefix(); 41 + } 42 + }; 43 + onMount(setNameDecorators); 44 + 45 + const onSubmit = (e: SubmitEvent) => { 46 + e.preventDefault(); 47 + name = createName(); 48 + setNameDecorators(); 49 + }; 50 + </script> 51 + 52 + <form id="nickname-form" onsubmit={onSubmit}> 53 + <p> 54 + Click the button below to generate your nickname. Feel free to keep clicking 55 + until you get one you like. 56 + </p> 57 + <button type="submit" id="nickname-generate" class="primary" 58 + >(Re)generate</button 59 + > 60 + </form>
+64
src/components/WmNicknameSeeded.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from "svelte"; 3 + import { prefixes, names, suffixes } from "../scripts/nicknames"; 4 + import { uuidV5 } from "../scripts/uuid"; 5 + 6 + let { 7 + prefix = $bindable<string | null>(null), 8 + name = $bindable(""), 9 + suffix = $bindable<string | null>(null), 10 + } = $props(); 11 + let seed = $state(""); 12 + 13 + /** 14 + * Limits an unbound index to the length of the given array, returning the indexed value. 15 + */ 16 + const arrGetBound = <T,>(arr: T[], unbound: number): T => 17 + arr[unbound % arr.length]; 18 + 19 + const setSeed = (value: string): void => { 20 + seed = value; 21 + if (value === "") { 22 + prefix = null; 23 + name = ""; 24 + suffix = null; 25 + } else { 26 + const buffer = uuidV5(value, true); 27 + 28 + // NOTE Naive implementation of transforming a UUID into a single numerical value. 29 + const hash = buffer.reduce((hash, b) => ((hash << 8) | b) % 0xfffff, 0); 30 + 31 + // NOTE Very simple and naive implementation of chances. 32 + suffix = hash % 2 === 0 ? null : arrGetBound(suffixes, hash); 33 + prefix = 34 + hash % 4 === 0 && suffix != null ? null : arrGetBound(prefixes, hash); 35 + name = arrGetBound(names, hash); 36 + } 37 + }; 38 + 39 + const onSubmit = (e: SubmitEvent): void => { 40 + e.preventDefault(); 41 + }; 42 + 43 + // NOTE Reset nickname on mount to clear out the nickname from the randomized version. 44 + onMount(() => { 45 + prefix = null; 46 + name = ""; 47 + suffix = null; 48 + }); 49 + </script> 50 + 51 + <form id="nickname-form" onsubmit={onSubmit}> 52 + <p> 53 + Enter a <em>public</em> identifier (like your name or a 54 + <abbr title="Decentralized Identifier">DID</abbr>). 55 + <em>Don't use a private identifier!</em> 56 + </p> 57 + <label for="input-seed">ID</label> 58 + <input 59 + type="text" 60 + id="input-seed" 61 + name="seed" 62 + bind:value={() => seed, setSeed} 63 + /> 64 + </form>
+2 -2
src/pages/wm-nickname.astro
··· 7 7 <BaseLayout title={title}> 8 8 <p> 9 9 This is a nickname generator inspired by the nicknames of the 108 heroes of 10 - my favorite book, Water Margin. If you're not satisfied with your nickname, 11 - feel free to click the regenerate button. 10 + my favorite book, Water Margin. Pick a type of nickname generator and then 11 + generate your nickname! 12 12 </p> 13 13 <WmNickname client:only="svelte" /> 14 14 </BaseLayout>
+3 -3
src/scripts/nicknames.ts
··· 4 4 */ 5 5 import { createPick } from "./chance"; 6 6 7 - const prefixes: string[] = [ 7 + export const prefixes: string[] = [ 8 8 "Divine", 9 9 "Daylight", 10 10 "The", ··· 88 88 "Old", 89 89 ]; 90 90 91 - const names: string[] = [ 91 + export const names: string[] = [ 92 92 "Doctor", 93 93 "Rat", 94 94 "God", ··· 156 156 "Sword", 157 157 ]; 158 158 159 - const suffixes: string[] = [ 159 + export const suffixes: string[] = [ 160 160 "of Three Mountains", 161 161 "of Death", 162 162 "of Disorder",
+21
src/scripts/uuid.ts
··· 1 + /** 2 + * @module uuid UUID helpers. 3 + */ 4 + import { v5 as uuidV5Impl } from "uuid"; 5 + 6 + const namespace = uuidV5Impl("https://www.spenser.black/", uuidV5Impl.URL); 7 + 8 + /** 9 + * 10 + * @param value The value to use for generating a UUID v5. 11 + * @param raw Set to `true` to return a `Uint8Array` rather than a `string`. 12 + */ 13 + export function uuidV5(value: string, raw?: false): string; 14 + export function uuidV5(value: string, raw: true): Uint8Array; 15 + export function uuidV5(value: string, raw?: boolean): string | Uint8Array { 16 + if (raw) { 17 + const buffer = new Uint8Array(16); 18 + return uuidV5Impl(value, namespace, buffer); 19 + } 20 + return uuidV5Impl(value, namespace); 21 + }