the home site for me: also iteration 3 or 4 of my site
1// Taken from Vale's 404 Guesser 2// https://vale.rocks/assets/scripts/404-guesser.js 3// which was based on Gwern's 404 Error Page URL Suggester 4// https://gwern.net/static/js/404-guesser.js 5 6class URLSuggester { 7 constructor() { 8 this.maxDistance = 8; 9 this.urls = []; 10 } 11 12 async initialize() { 13 try { 14 const sitemapText = await this.fetchSitemap(); 15 if (sitemapText) { 16 this.urls = this.parseUrls(sitemapText); 17 const currentPath = window.location.pathname; 18 if (!currentPath.endsWith("/404")) { 19 const suggestions = this.findSimilarUrls(currentPath); 20 this.injectSuggestions(currentPath, suggestions); 21 } 22 } 23 } catch (error) { 24 console.error("Error initializing URL suggester:", error); 25 } 26 } 27 28 async fetchSitemap() { 29 try { 30 const response = await fetch("/sitemap.xml"); 31 return await response.text(); 32 } catch (error) { 33 console.error("Error fetching sitemap:", error); 34 return null; 35 } 36 } 37 38 parseUrls(sitemapText) { 39 const parser = new DOMParser(); 40 const xmlDoc = parser.parseFromString(sitemapText, "text/xml"); 41 const urlNodes = xmlDoc.getElementsByTagName("url"); 42 return Array.from(urlNodes).map( 43 (node) => 44 new URL(node.getElementsByTagName("loc")[0].textContent).pathname, 45 ); 46 } 47 48 boundedLevenshteinDistance(a, b, maxDistance) { 49 if (Math.abs(a.length - b.length) > maxDistance) return maxDistance + 1; 50 const matrix = Array(b.length + 1) 51 .fill(null) 52 .map((_, i) => [i]); 53 for (let j = 1; j <= a.length; j++) { 54 matrix[0][j] = j; 55 } 56 for (let i = 1; i <= b.length; i++) { 57 let minDistance = maxDistance + 1; 58 for (let j = 1; j <= a.length; j++) { 59 if (b.charAt(i - 1) === a.charAt(j - 1)) { 60 matrix[i][j] = matrix[i - 1][j - 1]; 61 } else { 62 matrix[i][j] = Math.min( 63 matrix[i - 1][j - 1] + 1, 64 matrix[i][j - 1] + 1, 65 matrix[i - 1][j] + 1, 66 ); 67 } 68 minDistance = Math.min(minDistance, matrix[i][j]); 69 } 70 if (minDistance > maxDistance) { 71 return maxDistance + 1; 72 } 73 } 74 return matrix[b.length][a.length]; 75 } 76 77 findSimilarUrls(targetUrl) { 78 const targetPath = new URL(targetUrl, location.origin).pathname; 79 80 if (targetPath.startsWith("/posts/")) { 81 const exactMatch = this.urls.find((url) => url === targetPath); 82 if (exactMatch) { 83 return [location.origin + exactMatch]; 84 } 85 } 86 87 const potentialMatches = this.urls.filter( 88 (url) => 89 Math.abs(url.length - targetPath.length) <= this.maxDistance && 90 !url.endsWith("/404.html"), 91 ); 92 93 const similarUrls = potentialMatches 94 .map((url) => ({ 95 url, 96 distance: this.boundedLevenshteinDistance( 97 url, 98 targetPath, 99 this.maxDistance, 100 ), 101 })) 102 .filter((item) => item.distance <= this.maxDistance) 103 .sort((a, b) => a.distance - b.distance); 104 105 const seenUrls = new Set(); 106 const uniqueSimilarUrls = similarUrls 107 .filter((item) => { 108 if (seenUrls.has(item.url)) return false; 109 seenUrls.add(item.url); 110 return true; 111 }) 112 .slice(0, 10); 113 114 return uniqueSimilarUrls.map((item) => location.origin + item.url); 115 } 116 117 injectSuggestions(currentPath, suggestions) { 118 const app = document.querySelector("#suggestions"); 119 if (!app) return; 120 121 if (suggestions.length > 0) { 122 const p = document.createElement("p"); 123 124 p.innerHTML = "I did however find some URLs that might be relevant?"; 125 app.appendChild(p); 126 127 for (const url of suggestions) { 128 const a = document.createElement("a"); 129 const cleanUrl = url.replace(/\.html$/, ""); 130 a.href = cleanUrl; 131 a.textContent = cleanUrl; 132 app.appendChild(a); 133 } 134 135 const endText = document.createElement("p"); 136 app.appendChild(endText); 137 } else { 138 const p = document.createElement("p"); 139 p.innerHTML = `Couldn't find any URLs similar to <code>${currentPath}</code>. I guess it's time to find something new`; 140 app.appendChild(p); 141 } 142 143 app.className = "url-suggestions"; 144 } 145} 146 147document.addEventListener("DOMContentLoaded", () => { 148 new URLSuggester().initialize(); 149});