The code for my personal website, powered by Jekyll. arthr.me
jekyll-site personal-website
0
fork

Configure Feed

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

adds wander

+542
+514
wander/index.html
··· 1 + <!-- 2 + Wander 0.2.0.dev8 3 + Source: https://codeberg.org/susam/wander 4 + Licence: MIT 5 + --> 6 + <!DOCTYPE html> 7 + <html lang="en"> 8 + <head> 9 + <title>Wander Console - Browse the Small Web</title> 10 + <meta charset="UTF-8"> 11 + <meta name="viewport" content="width=device-width, initial-scale=1"> 12 + <style> 13 + /* Colours */ 14 + body { 15 + background: #696; 16 + } 17 + button, input, noscript, dialog { 18 + background: #bdb; 19 + border-color: #363; 20 + color: #030 21 + } 22 + dialog { 23 + background: #cec; 24 + } 25 + button:hover { 26 + background: #9c9; 27 + } 28 + button:active { 29 + background: #8c8; 30 + } 31 + dialog::backdrop { 32 + background: #0009; 33 + } 34 + /* Dimensions and decorations */ 35 + body { 36 + line-height: 1.5; 37 + margin: 0; 38 + padding: 1em; 39 + } 40 + button, input, noscript, dialog { 41 + font-family: courier, monospace; 42 + font-size: medium; 43 + font-weight: bold; 44 + } 45 + button, input { 46 + box-sizing: border-box; 47 + margin-bottom: 1em; 48 + min-height: 2.5em; 49 + width: 6em; 50 + } 51 + button { 52 + cursor: pointer; 53 + } 54 + input { 55 + padding-left: 0.5em; 56 + width: 18.5em; 57 + } 58 + header input:not(:last-of-type), 59 + header button:not(:last-of-type) { 60 + margin-right: 0.25em; 61 + } 62 + dialog { 63 + border: thick solid; 64 + max-width: 42em; 65 + max-height: 80vh; 66 + overflow: auto; 67 + } 68 + dialog header, dialog footer { 69 + text-align: center; 70 + } 71 + dialog header h1, dialog header form { 72 + display: inline-block; 73 + vertical-align: top; 74 + } 75 + dialog header h1 { 76 + font-size: 1.5em; 77 + margin: 0; 78 + padding-left: 2rem; 79 + text-align: center; 80 + width: calc(100% - 4rem); 81 + } 82 + dialog header button { 83 + box-sizing: border-box; 84 + height: auto; 85 + margin: 0; 86 + min-height: 0; 87 + padding: 0.25em; 88 + vertical-align: top; 89 + width: 2em; 90 + } 91 + dialog h2 { 92 + font-size: 1.2em; 93 + margin-bottom: 0; 94 + } 95 + dialog h2 + p { 96 + margin-top: 0.5em; 97 + } 98 + dialog footer button { 99 + margin-bottom: 0; 100 + } 101 + iframe { 102 + background: #fff; 103 + border: thick solid #363; 104 + box-sizing: border-box; 105 + display: none; 106 + height: calc(100vh - 12.5em); 107 + width: 100%; 108 + } 109 + #url-wide-input, #reload-button, #go-button, #open-button { 110 + display: none; 111 + } 112 + #url-narrow-input { 113 + margin-right: 0; 114 + } 115 + noscript { 116 + border: medium double #600; 117 + color: #600; 118 + display: block; 119 + font-size: x-large; 120 + margin-bottom: 1em; 121 + padding: 1em; 122 + text-align: center; 123 + } 124 + @media (min-width: 70em) { 125 + iframe { 126 + height: calc(100vh - 5.5em); 127 + } 128 + #url-narrow-input { 129 + display: none; 130 + } 131 + #reload-button, #go-button, #open-button, #url-wide-input { 132 + display: inline-block; 133 + } 134 + #url-wide-input { 135 + width: 25em; 136 + } 137 + } 138 + </style> 139 + <script src="wander.js"></script> 140 + <script> 141 + const LOGGING = true 142 + const VERSION = '0.2.0.dev8' 143 + const KEEP_CONSOLES = 1000 144 + 145 + const consoles = [] 146 + const pages = [] 147 + const ignores = [] 148 + const history = [] 149 + 150 + let consolesFound = 0 151 + 152 + function init () { 153 + log(`Wander ${VERSION}`) 154 + initCustomStyle() 155 + initCustomScript() 156 + initListeners() 157 + initConsoleList() 158 + initVersion() 159 + collectIgnores(wander.ignore) 160 + collectConsoles(window.location.href, wander.consoles) 161 + collectPages(window.location.href, wander.pages) 162 + loadNewPage() 163 + log('Initialised') 164 + } 165 + 166 + function initCustomStyle () { 167 + const styles = wander.styles || [] 168 + for (const href of styles) { 169 + const link = document.createElement('link') 170 + link.rel = 'stylesheet' 171 + link.href = href 172 + document.head.append(link) 173 + } 174 + } 175 + 176 + function initCustomScript () { 177 + const scripts = wander.scripts || [] 178 + for (const src of scripts) { 179 + const script = document.createElement('script') 180 + script.src = src 181 + document.head.append(script) 182 + } 183 + } 184 + 185 + function initListeners () { 186 + document.getElementById('about-button').addEventListener('click', showAboutDialog) 187 + document.getElementById('console-button').addEventListener('click', showConsoleDialog) 188 + document.getElementById('open-button').addEventListener('click', openURL) 189 + document.getElementById('go-button').addEventListener('click', gotoURL) 190 + document.getElementById('reload-button').addEventListener('click', reloadPage) 191 + document.getElementById('url-narrow-input').addEventListener('keydown', handleURLInput) 192 + document.getElementById('url-wide-input').addEventListener('keydown', handleURLInput) 193 + document.getElementById('wander-button').addEventListener('click', loadNewPage) 194 + document.getElementById('wander-iframe').addEventListener('load', wanderDone) 195 + window.addEventListener('message', collectConsoleData) 196 + } 197 + 198 + function initConsoleList () { 199 + const ul = document.getElementById('console-dialog-list') 200 + for (const url of wander.consoles) { 201 + const li = document.createElement('li') 202 + const a = document.createElement('a') 203 + li.append(a) 204 + ul.append(li) 205 + a.href = url 206 + a.textContent = url 207 + } 208 + } 209 + 210 + function initVersion () { 211 + for (const element of document.querySelectorAll('.version')) { 212 + element.textContent = VERSION 213 + } 214 + } 215 + 216 + function collectIgnores (patterns) { 217 + for (const pattern of patterns) { 218 + ignores.push(new RegExp(pattern)) 219 + } 220 + } 221 + 222 + function collectConsoles (consoleURL, urls) { 223 + for (const url of urls) { 224 + const normURL = normaliseWanderURL(normaliseURL(url)) 225 + if (duplicateURL(consoles, normURL)) { 226 + log(`Skipped duplicate console URL: ${consoleURL} => ${normURL}`) 227 + continue 228 + } 229 + if (ignoredURL(ignores, normURL)) { 230 + log(`Skipped ignored console URL: ${consoleURL} => ${normURL}`) 231 + continue 232 + } 233 + consoles.push({ referrer: consoleURL, url: normURL }) 234 + consolesFound++ 235 + log(`Added console URL: ${consoleURL} => ${normURL} ` + 236 + `(total ${consolesFound}, cached ${consoles.length})`) 237 + } 238 + if (consoles.length > KEEP_CONSOLES) { 239 + consoles.splice(0, consoles.length - KEEP_CONSOLES) 240 + } 241 + log('Trimmed console cache ' + 242 + `(total ${consolesFound}, cached ${consoles.length})`) 243 + } 244 + 245 + function collectPages (consoleURL, urls) { 246 + const goodPages = [] 247 + for (const url of urls) { 248 + const normURL = normaliseURL(url) 249 + if (ignoredURL(ignores, normURL)) { 250 + log(`Skipped ignored page URL: ${consoleURL} => ${normURL}`) 251 + continue 252 + } 253 + goodPages.push({ referrer: consoleURL, url: normURL }) 254 + log(`Added page URL: ${consoleURL} => ${normURL} (cached ${goodPages.length})`) 255 + } 256 + if (goodPages.length > 0) { 257 + pages.length = 0 258 + goodPages.forEach(function (item) { pages.push(item) }) 259 + } 260 + } 261 + 262 + function duplicateURL (cache, url) { 263 + return cache.some(function (item) { 264 + return item.url === url 265 + }) 266 + } 267 + 268 + function ignoredURL (ignores, url) { 269 + return ignores.some(function (re) { 270 + return re.test(url) 271 + }) 272 + } 273 + 274 + function loadNewPage () { 275 + const iframe = document.getElementById('wander-iframe') 276 + const page = pick(filter(pages)) 277 + document.getElementById('url-narrow-input').value = page.url 278 + document.getElementById('url-wide-input').value = page.url 279 + iframe.src = page.url 280 + history.push(page) 281 + log(`Loading page: ${page.referrer} => ${page.url}`) 282 + } 283 + 284 + function wanderDone () { 285 + const iframe = document.getElementById('wander-iframe') 286 + log('Loaded new page') 287 + if (iframe.style.display !== 'block') { 288 + iframe.style.display = 'block' 289 + log('Displayed iframe') 290 + } 291 + iframe.focus() 292 + pickNewConsole() 293 + } 294 + 295 + function showConsoleDialog () { 296 + document.getElementById('host-console-link').textContent = window.location.href 297 + document.getElementById('host-console-link').href = window.location.href 298 + const ul = document.getElementById('history-list') 299 + ul.textContent = '' 300 + for (const item of history.slice().reverse()) { 301 + const li = document.createElement('li') 302 + const p = document.createElement('p') 303 + const a1 = document.createElement('a') 304 + const a2 = document.createElement('a') 305 + const a3 = document.createElement('a') 306 + const br = document.createElement('br') 307 + ul.append(li) 308 + li.append(p) 309 + p.append(a1, br, a2, ' (', a3, ')') 310 + a1.href = a1.textContent = item.url 311 + a2.href = item.referrer 312 + a2.textContent = item.referrer 313 + a3.href = item.referrer + 'wander.js' 314 + a3.textContent = 'js' 315 + } 316 + document.getElementById('console-dialog').showModal() 317 + } 318 + 319 + function pickNewConsole () { 320 + const console = pick(filter(consoles)) 321 + const jsURL = console.url + 'wander.js' 322 + const consoleURLAsCode = JSON.stringify(console.url) 323 + const sandbox = document.getElementById('sandbox-iframe') 324 + log('Loading next console:', console.url) 325 + sandbox.srcdoc = ` 326 + <script src="${jsURL}"><\/script> 327 + <script>parent.postMessage({wander: wander, url: ${consoleURLAsCode}}, '*')<\/script> 328 + ` 329 + } 330 + 331 + function collectConsoleData (e) { 332 + log('Loaded next console:', e.data.url) 333 + collectConsoles(e.data.url, e.data.wander.consoles) 334 + collectPages(e.data.url, e.data.wander.pages) 335 + } 336 + 337 + function normaliseWanderURL (url) { 338 + if (url.endsWith('index.html')) { 339 + url = url.slice(0, -'index.html'.length) 340 + } 341 + if (!url.endsWith('/')) { 342 + url += '/' 343 + } 344 + return url 345 + } 346 + 347 + function reloadPage () { 348 + const iframe = document.getElementById('wander-iframe') 349 + const src = iframe.src 350 + iframe.src = src 351 + } 352 + 353 + function handleURLInput (e) { 354 + if (e.key === 'Enter') { 355 + gotoURL() 356 + } 357 + } 358 + 359 + function gotoURL () { 360 + const value = normaliseURL(visibleInput().value) 361 + log('Navigating to user entered URL:', value) 362 + document.getElementById('url-narrow-input').value = value 363 + document.getElementById('url-wide-input').value = value 364 + document.getElementById('wander-iframe').src = value 365 + } 366 + 367 + function matchingProtocol () { 368 + let protocol = window.location.protocol 369 + if (protocol !== 'http:' && protocol !== 'https:') { 370 + protocol = 'https:' 371 + } 372 + return protocol 373 + } 374 + 375 + function openURL () { 376 + window.open(visibleInput().value) 377 + } 378 + 379 + function showAboutDialog () { 380 + document.getElementById('about-dialog').showModal() 381 + } 382 + 383 + function pick (items) { 384 + return items[Math.floor(Math.random() * items.length)] 385 + } 386 + 387 + function filter (items) { 388 + return items.filter(function (item) { 389 + const diffProtocol = !item.url.startsWith(matchingProtocol()) 390 + const ignored = ignores.some(function (regex) { 391 + return regex.test(item.url) 392 + }) 393 + if (diffProtocol) { 394 + log('Ignored due to protocol mismatch:', item.url) 395 + return false 396 + } 397 + if (ignored) { 398 + log('Ignored due to explicit ignore:', item.url) 399 + return false 400 + } 401 + return true 402 + }) 403 + } 404 + 405 + function normaliseURL (url) { 406 + let result = url.trim() 407 + const re = /^[A-Za-z][A-Za-z\d+\-.]*:\/\// // RFC 3986, section 3.1. 408 + if (!re.test(result)) { 409 + result = matchingProtocol() + '//' + result 410 + } 411 + return new URL(result).href 412 + } 413 + 414 + function visibleInput () { 415 + const urlNarrowInput = document.getElementById('url-narrow-input') 416 + const urlWideInput = document.getElementById('url-wide-input') 417 + return urlNarrowInput.offsetParent ? urlNarrowInput : urlWideInput 418 + } 419 + 420 + function log () { 421 + if (LOGGING) { 422 + const args = Array.prototype.slice.call(arguments) 423 + args.unshift('[Wander]') 424 + console.log.apply(console, args) 425 + } 426 + } 427 + 428 + window.addEventListener('load', init) 429 + </script> 430 + </head> 431 + <body> 432 + <noscript>JavaScript is required to use this application.</noscript> 433 + <header> 434 + <button id="wander-button">Wander</button><!-- 435 + --><button id="console-button">Console</button><!-- 436 + --><button id="reload-button">Reload</button><!-- 437 + --><input id="url-wide-input" autocomplete="off"><!-- 438 + --><button id="go-button">Go</button><!-- 439 + --><button id="open-button">Open</button><!-- 440 + --><button id="about-button">About</button><!-- 441 + --><input id="url-narrow-input"> 442 + </header> 443 + <iframe id="wander-iframe"></iframe> 444 + <iframe id="sandbox-iframe" sandbox="allow-scripts"></iframe> 445 + <dialog id="console-dialog"> 446 + <header> 447 + <h1>Console Explorer</h1><form method="dialog"><button>x</button></form> 448 + </header> 449 + <h2>Host Console</h2> 450 + <p> 451 + You are currently on <a id="host-console-link"></a>. 452 + This console is helping you navigate the Wander network. 453 + </p> 454 + <h2>Console Neighbourhood</h2> 455 + <p> 456 + Your host console lists the following consoles as its 457 + neighbours: 458 + </p> 459 + <ul id="console-dialog-list"></ul> 460 + <p> 461 + You can wander to a neighbouring console by clicking one of 462 + the links above. Note that you do not really need to change 463 + console to browse the Wander network. The host console can 464 + fetch recommendations from other consoles, then from the 465 + consoles they link to and so on recursively. 466 + </p> 467 + <h2>Wandering History</h2> 468 + <p> 469 + Your wandering history in reverse chronological order: 470 + </p> 471 + <ul id="history-list"></ul> 472 + <p> 473 + Each item above includes three links: the page recommended to 474 + you, the console that recommended it and the wander.js file of 475 + that console. 476 + </p> 477 + <footer><form method="dialog"><button>Close</button></form></footer> 478 + </dialog> 479 + <dialog id="about-dialog"> 480 + <header> 481 + <h1>Wander Console</h1><form method="dialog"><button>x</button></form> 482 + <div>Version <span class="version">&nbsp;</span></div> 483 + </header> 484 + <p> 485 + Hello! You are currently on a Wander console! A Wander 486 + console lets you browse random websites and pages from the 487 + Wander community. The Wander community consists of 488 + individuals who develop and maintain their own personal 489 + websites. 490 + </p> 491 + <p> 492 + To set up your own Wander console, download 493 + <a href="https://codeberg.org/susam/wander/archive/wander.zip">this ZIP file</a>, 494 + extract index.html and wander.js, and place them 495 + in the /wander/ directory of your website. Then edit 496 + wander.js by following the directions at 497 + <a href="https://codeberg.org/susam/wander#readme">codeberg.org/susam/wander</a>. 498 + </p> 499 + <p> 500 + That's it! Once your /wander/ directory is ready on your web 501 + server, you can share a link to your Wander console in 502 + <a href="https://codeberg.org/susam/wander/issues/1">this 503 + community thread</a>. Hopefully, someone will add your 504 + console to theirs and you will become part of the Wander 505 + network. 506 + </p> 507 + <p> 508 + For more information about Wander, please 509 + see <a href="https://codeberg.org/susam/wander#readme">codeberg.org/susam/wander</a>. 510 + </p> 511 + <footer><form method="dialog"><button>Close</button></form></footer> 512 + </dialog> 513 + </body> 514 + </html>
+28
wander/wander.js
··· 1 + const wander = { 2 + // Other Wander consoles that visitors can reach from my console. 3 + consoles: [ 4 + 'https://susam.net/wander/', 5 + ], 6 + 7 + // My favourite websites and pages I recommend to the Wander community. 8 + pages: [ 9 + 'https://boingboing.net/', 10 + 'https://www.swiss-miss.com/', 11 + 'https://waxy.org/', 12 + 'https://le-chouchou.ghost.io/', 13 + ], 14 + 15 + // Websites and consoles to ignore. When this console serves as 16 + // your host console, it will never contact consoles or recommend 17 + // web pages with addresses that match the following regular 18 + // expression patterns. 19 + ignore: [ 20 + // Off-topic since these are commercial services, not personal websites. 21 + '.*://medium\\.com/.*', 22 + '.*://.*\\.substack\\.com/.*', 23 + 24 + // These do not load in the console due to frame-embedding restrictions. 25 + '.*://cari\\.institute/.*', 26 + '.*://wdl\\.mcdaniel\\.edu/.*', 27 + ] 28 + }