diff --git a/README.md b/README.md index f0ad134..2a7b631 100644 --- a/README.md +++ b/README.md @@ -478,6 +478,7 @@ Websites with lists of relays and their performance/health: - [tostr](https://github.com/slaninas/tostr)![stars](https://img.shields.io/github/stars/slaninas/tostr.svg?style=social) - a twitter to nostr bot - [Undelete my Nostr](https://yonle.github.io/undelete-my-nostr)![stars](https://img.shields.io/github/stars/Yonle/undelete-my-nostr.svg?style=social) - Simple tool for restoring deleted nostr account. - [Zapper](https://zapper.nostrapps.org) - Nostr micro-app for zapping +- [YEGHRO unFollow Tool](https://unfollow.yeghro.com) - A tool that allows you to unfollow inactive users on nostr. ## NIP-05 identity services diff --git a/script.js b/script.js index e261c84..b93c4d2 100644 --- a/script.js +++ b/script.js @@ -371,35 +371,8 @@ searchInput.addEventListener('input', (e) => { Object.entries(window.parsedResources).forEach(([sectionName, sectionContent]) => { sectionContent.forEach(item => { if (item.type === 'resources') { - // Search through resource lists - Array.from(item.element.children).forEach(li => { - const resourceName = li.querySelector('a')?.textContent || ''; - const resourceLink = li.querySelector('a')?.href || ''; - const resourceDescription = li.textContent.split('- ')[1]?.trim() || ''; - - const searchableText = [resourceName, resourceDescription, resourceLink] - .join(' ') - .toLowerCase(); - - if (searchableText.includes(searchTerm)) { - const card = createResourceCard({ - name: resourceName, - link: resourceLink, - description: resourceDescription, - stars: li.querySelector('img[alt="stars"]') - ? parseInt(li.querySelector('img[alt="stars"]').src.match(/stars\/(\d+)/)?.[1]) || 0 - : 0 - }); - - // Add section label to card - const sectionLabel = document.createElement('div'); - sectionLabel.className = 'category-label'; - sectionLabel.textContent = sectionName; - card.insertBefore(sectionLabel, card.firstChild); - - container.appendChild(card); - } - }); + // Process both top-level and nested items for search + searchResourceList(item.element, container, searchTerm, sectionName); } else if (item.type === 'content') { // Search through regular content const contentText = item.element.textContent.toLowerCase(); @@ -430,6 +403,51 @@ searchInput.addEventListener('input', (e) => { } }); +// New helper function to search through resource lists recursively +function searchResourceList(ulElement, container, searchTerm, sectionName) { + Array.from(ulElement.children).forEach(li => { + // Search in the main item if it has a link + if (li.querySelector(':scope > a')) { + const resourceName = li.querySelector(':scope > a')?.textContent || ''; + const resourceLink = li.querySelector(':scope > a')?.href || ''; + const description = li.textContent + .replace(resourceName, '') // Remove the resource name + .replace(/^\s*-\s*/, '') // Remove leading dash + .replace(/\s*\[!\[.*?\]\(.*?\)\]\(.*?\)\s*/, '') // Remove GitHub stars badge if present + .trim(); + + const searchableText = [resourceName, description, resourceLink] + .join(' ') + .toLowerCase(); + + if (searchableText.includes(searchTerm)) { + const card = createResourceCard({ + name: resourceName, + link: resourceLink, + description: description, + stars: li.querySelector(':scope > img[alt="stars"]') + ? parseInt(li.querySelector(':scope > img[alt="stars"]').src.match(/stars\/(\d+)/)?.[1]) || 0 + : 0 + }); + + // Add section label to card + const sectionLabel = document.createElement('div'); + sectionLabel.className = 'category-label'; + sectionLabel.textContent = sectionName; + card.insertBefore(sectionLabel, card.firstChild); + + container.appendChild(card); + } + } + + // Search in nested items if they exist + const nestedUl = li.querySelector(':scope > ul'); + if (nestedUl) { + searchResourceList(nestedUl, container, searchTerm, sectionName); + } + }); +} + // Add active class handling for navigation document.querySelectorAll('.nav-links a').forEach(link => { link.addEventListener('click', () => { @@ -579,11 +597,10 @@ function parseResources(content) { // Function to parse a single resource line function parseResourceLine(line) { - // Updated regex patterns to better handle various markdown formats + // Same regex patterns as before const nameRegex = /\[(.*?)\]/; const linkRegex = /\((.*?)\)/; const starsRegex = /!\[stars\].*?stars\/(.*?)\/.*?style=social/; - // Updated description regex to handle descriptions after stars badge const descriptionRegex = /style=social\) - (.*?)(?=(?:\[|\n|$))|(?:\) - )(.*?)(?=(?:\[|\n|$))/; try { @@ -591,7 +608,6 @@ function parseResourceLine(line) { const link = linkRegex.exec(line)?.[1]; const stars = starsRegex.exec(line)?.[1]; - // More robust description extraction const descMatch = descriptionRegex.exec(line); const description = (descMatch?.[1] || descMatch?.[2] || '').trim(); @@ -599,7 +615,7 @@ function parseResourceLine(line) { return { name, link, - stars: stars || 0, + stars: stars ? parseInt(stars) : 0, description: description || '', raw: line.trim() }; @@ -619,18 +635,59 @@ function createResourceCard(resource) { card.setAttribute('itemscope', ''); card.setAttribute('itemtype', 'https://schema.org/SoftwareApplication'); - // Extract domain for favicon + // Extract domain and build multiple fallback favicon URLs let faviconUrl = ''; try { const url = new URL(resource.link); - faviconUrl = `https://www.google.com/s2/favicons?domain=${url.hostname}&sz=64`; + const domain = url.hostname; + + // Try multiple favicon sources + const faviconSources = [ + `https://www.google.com/s2/favicons?domain=${domain}&sz=64`, + `https://${domain}/favicon.ico`, + `https://${domain}/favicon.png`, + `https://${domain}/apple-touch-icon.png`, + `https://${domain}/apple-touch-icon-precomposed.png` + ]; + + // Create image element with fallback chain + const img = document.createElement('img'); + img.className = 'resource-favicon'; + img.alt = ''; + + // Set first source as initial + img.src = faviconSources[0]; + + // Add error handling to try next source + let sourceIndex = 0; + img.onerror = () => { + sourceIndex++; + if (sourceIndex < faviconSources.length) { + img.src = faviconSources[sourceIndex]; + } else { + // If all sources fail, use a default icon + img.src = 'data:image/svg+xml,' + encodeURIComponent(` + + + + ${resource.name.charAt(0).toUpperCase()} + + + `); + img.onerror = null; // Remove error handler once default is shown + } + }; + + faviconUrl = img.outerHTML; } catch (e) { console.warn('Invalid URL:', resource.link); + // Use default icon for invalid URLs + faviconUrl = `
${resource.name.charAt(0).toUpperCase()}
`; } card.innerHTML = `
- ${faviconUrl ? `` : ''} + ${faviconUrl}