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 steamdown page (#3)

This supports converting Markdown to Steam's markup.

authored by

Spenser Black and committed by
GitHub
(Jul 14, 2026, 12:55 PM EDT) b03cba35 9d992e75

+225
+2
package.json
··· 16 16 "format-check": "prettier --check ." 17 17 }, 18 18 "dependencies": { 19 + "@steamdown/core": "1.0.0-beta.2", 20 + "@steamdown/html": "1.0.0-beta.2", 19 21 "astro": "^7.0.2" 20 22 }, 21 23 "devDependencies": {
+35
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 + '@steamdown/core': 12 + specifier: 1.0.0-beta.2 13 + version: 1.0.0-beta.2 14 + '@steamdown/html': 15 + specifier: 1.0.0-beta.2 16 + version: 1.0.0-beta.2(@steamdown/core@1.0.0-beta.2) 11 17 astro: 12 18 specifier: ^7.0.2 13 19 version: 7.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) ··· 651 657 '@shikijs/vscode-textmate@10.0.2': 652 658 resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} 653 659 660 + '@steamdown/core@1.0.0-beta.2': 661 + resolution: {integrity: sha512-9dpmTEmcRy0+5xr95W7yzaci8ka+Nc1HC8NnU5v8RdGkvDmeD02WbIJ0a/AJ/loffQX35hrL1q8t8C+37lU4ZA==} 662 + 663 + '@steamdown/html@1.0.0-beta.2': 664 + resolution: {integrity: sha512-yL9J8710BoLOLPg9mvSo519DksLOaAv0kZAGeSLjsqpiWnQH0GWbOZR/3Pe3Aia5I00F8Yxj4xjyvw6DHtImoQ==} 665 + peerDependencies: 666 + '@steamdown/core': ^1.0.0-beta.2 667 + 654 668 '@tybys/wasm-util@0.10.3': 655 669 resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} 656 670 ··· 828 842 engines: {node: '>=18'} 829 843 hasBin: true 830 844 845 + escape-goat@4.0.0: 846 + resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} 847 + engines: {node: '>=12'} 848 + 849 + escape-string-regexp@5.0.0: 850 + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 851 + engines: {node: '>=12'} 852 + 831 853 estree-walker@2.0.2: 832 854 resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 833 855 ··· 1886 1908 1887 1909 '@shikijs/vscode-textmate@10.0.2': {} 1888 1910 1911 + '@steamdown/core@1.0.0-beta.2': 1912 + dependencies: 1913 + escape-string-regexp: 5.0.0 1914 + 1915 + '@steamdown/html@1.0.0-beta.2(@steamdown/core@1.0.0-beta.2)': 1916 + dependencies: 1917 + '@steamdown/core': 1.0.0-beta.2 1918 + escape-goat: 4.0.0 1919 + 1889 1920 '@tybys/wasm-util@0.10.3': 1890 1921 dependencies: 1891 1922 tslib: 2.8.1 ··· 2149 2180 '@esbuild/win32-arm64': 0.28.1 2150 2181 '@esbuild/win32-ia32': 0.28.1 2151 2182 '@esbuild/win32-x64': 0.28.1 2183 + 2184 + escape-goat@4.0.0: {} 2185 + 2186 + escape-string-regexp@5.0.0: {} 2152 2187 2153 2188 estree-walker@2.0.2: {} 2154 2189
+1
src/components/Header.astro
··· 2 2 const pages: [description: string, link: string][] = [ 3 3 ["Homepage", "/"], 4 4 ["Projects", "/projects/"], 5 + ["Markdown for Steam", "/steamdown/"], 5 6 ]; 6 7 --- 7 8
+164
src/pages/steamdown.astro
··· 1 + --- 2 + import BaseLayout from "../layouts/BaseLayout.astro"; 3 + import ExternalLink from "../components/ExternalLink.astro"; 4 + const title = "Steamdown"; 5 + const markupLink = 6 + "https://steamcommunity.com/comment/Recommendation/formattinghelp"; 7 + const placeholder = `# Hello, Steam! 8 + 9 + This is my review about the __SOME GAME__! I like it *a lot!* 10 + 11 + Spoiler alert: >!the good guys win!<.`; 12 + --- 13 + 14 + <BaseLayout title={title}> 15 + <h2> 16 + Markdown to <ExternalLink link={markupLink}>Steam Markup</ExternalLink> converter 17 + </h2> 18 + <p> 19 + This is a tool I wrote to convert Markdown (well, a Markdown-<em>like</em> syntax) 20 + to <ExternalLink link={markupLink}>Steam's markup language</ExternalLink>. 21 + Check out <a href="/projects">my projects</a> for more information. 22 + </p> 23 + <form id="steamdown-form"> 24 + <label for="steamdown-source">Source</label> 25 + <textarea 26 + id="steamdown-source" 27 + name="steamdown-source" 28 + autocomplete="off" 29 + autocapitalize="sentences" 30 + autofocus 31 + placeholder={placeholder} 32 + spellcheck="true" 33 + wrap="soft" 34 + cols="90" 35 + rows="10">{placeholder}</textarea 36 + > 37 + <fieldset class="inline"> 38 + <legend>Output style</legend> 39 + <div class="option-input"> 40 + <input 41 + type="radio" 42 + id="radio-markup" 43 + name="output" 44 + value="markup" 45 + checked 46 + /> 47 + <label for="radio-markup">Markup</label> 48 + </div> 49 + <div class="option-input"> 50 + <input type="radio" id="radio-preview" name="output" value="preview" /> 51 + <label for="radio-preview">Preview</label> 52 + </div> 53 + </fieldset> 54 + </form> 55 + 56 + <h3>Output</h3> 57 + <div id="outputs"> 58 + <output id="output-markup" for="steamdown-source radio-markup"> 59 + <pre id="output-markup-content"></pre> 60 + </output> 61 + <output id="output-preview" for="steamdown-source radio-preview"></output> 62 + </div> 63 + </BaseLayout> 64 + 65 + <script> 66 + const form = document.getElementById("steamdown-form") as HTMLFormElement; 67 + form.addEventListener("submit", (e) => { 68 + e.preventDefault(); 69 + }); 70 + </script> 71 + 72 + <script> 73 + import { parse, render } from "@steamdown/core"; 74 + import { render as renderHTML } from "@steamdown/html"; 75 + 76 + const sourceInput = document.getElementById( 77 + "steamdown-source", 78 + ) as HTMLTextAreaElement; 79 + const radioMarkup = document.getElementById( 80 + "radio-markup", 81 + ) as HTMLInputElement; 82 + const radioPreview = document.getElementById( 83 + "radio-preview", 84 + ) as HTMLInputElement; 85 + const outputMarkup = document.getElementById( 86 + "output-markup", 87 + ) as HTMLOutputElement; 88 + const outputPreview = document.getElementById( 89 + "output-preview", 90 + ) as HTMLOutputElement; 91 + const markupContent = document.getElementById( 92 + "output-markup-content", 93 + ) as HTMLPreElement; 94 + 95 + const toggleOutput = (output: "markup" | "preview") => { 96 + if (output === "markup") { 97 + outputMarkup.style.display = ""; 98 + outputPreview.style.display = "none"; 99 + } else { 100 + outputMarkup.style.display = "none"; 101 + outputPreview.style.display = ""; 102 + } 103 + }; 104 + 105 + toggleOutput("markup"); 106 + radioMarkup.addEventListener("input", () => { 107 + toggleOutput("markup"); 108 + }); 109 + radioPreview.addEventListener("input", () => { 110 + toggleOutput("preview"); 111 + }); 112 + 113 + const renderMarkdown = () => { 114 + const source = sourceInput.value; 115 + const [tree, context] = parse(source); 116 + const markup = render(tree, context); 117 + const html = renderHTML(tree, context); 118 + 119 + const codeLines = markup.split("\n").flatMap((line) => { 120 + const code = document.createElement("code"); 121 + code.textContent = line; 122 + const newline = document.createTextNode("\n"); 123 + return [code, newline]; 124 + }); 125 + markupContent.replaceChildren(...codeLines); 126 + outputPreview.innerHTML = html; 127 + }; 128 + renderMarkdown(); 129 + sourceInput.addEventListener("input", renderMarkdown); 130 + </script> 131 + 132 + <style is:global> 133 + .spoiler { 134 + color: white; 135 + background-color: white; 136 + } 137 + .spoiler:hover { 138 + color: black; 139 + } 140 + 141 + :root[data-theme="light"] .spoiler { 142 + color: black; 143 + background-color: black; 144 + } 145 + :root[data-theme="light"] .spoiler:hover { 146 + color: white; 147 + } 148 + :root[data-theme="dark"] .spoiler { 149 + color: white; 150 + background-color: white; 151 + } 152 + :root[data-theme="dark"] .spoiler:hover { 153 + color: black; 154 + } 155 + @media (prefers-color-scheme: light) { 156 + .spoiler { 157 + color: black; 158 + background-color: black; 159 + } 160 + .spoiler:hover { 161 + color: white; 162 + } 163 + } 164 + </style>
+23
src/styles/global.css
··· 129 129 .hide { 130 130 display: none; 131 131 } 132 + 133 + /* Forms: {{{ */ 134 + label { 135 + font-weight: 600; 136 + display: block; 137 + } 138 + .option-input { 139 + display: block; 140 + } 141 + .option-input input[type="radio"], 142 + form .inline input[type="checkbox"] { 143 + display: inline-block; 144 + margin-right: 0.25em; 145 + } 146 + .option-input label { 147 + display: inline-block; 148 + } 149 + @media (min-width: 640px) { 150 + .option-input { 151 + display: inline-block; 152 + } 153 + } 154 + /* }}} */