From a609d89adc16afd70dfd3d62f38f8234c35b2cd4 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:29:12 -0800 Subject: [PATCH 01/17] so far got the basics down, categories and lists are being populated for the most part, just need to go through and check them all and improve the visual to make it more pleasing --- index.html | 54 +++++++++ script.js | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 213 +++++++++++++++++++++++++++++++++++ 3 files changed, 587 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 styles.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..20c7e51 --- /dev/null +++ b/index.html @@ -0,0 +1,54 @@ + + + + + + Awesome Nostr Resources + + + + +
+ + + + +
+
+
+ +
+ +
+
+
+ + +
+
+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..dd64bea --- /dev/null +++ b/script.js @@ -0,0 +1,320 @@ +// Dark mode toggle +const darkModeToggle = document.getElementById('darkModeToggle'); +const body = document.body; + +darkModeToggle.addEventListener('click', () => { + body.dataset.theme = body.dataset.theme === 'dark' ? 'light' : 'dark'; + darkModeToggle.innerHTML = body.dataset.theme === 'dark' + ? '' + : ''; +}); + +// Mobile menu toggle +const menuToggle = document.getElementById('menuToggle'); +const sidebar = document.querySelector('.sidebar'); + +menuToggle.addEventListener('click', () => { + sidebar.classList.toggle('active'); +}); + +// Search functionality +const searchInput = document.getElementById('search'); +const resourceCards = document.querySelectorAll('.resource-card'); + +searchInput.addEventListener('input', (e) => { + const searchTerm = e.target.value.toLowerCase(); + resourceCards.forEach(card => { + const text = card.textContent.toLowerCase(); + card.style.display = text.includes(searchTerm) ? 'block' : 'none'; + }); +}); + +// Sort functionality +const sortSelect = document.getElementById('sortSelect'); + +sortSelect.addEventListener('change', (e) => { + const sortBy = e.target.value; + const container = document.getElementById('resources-container'); + const cards = Array.from(container.getElementsByClassName('resource-card')); + + cards.sort((a, b) => { + if (sortBy === 'stars') { + return parseInt(b.dataset.stars) - parseInt(a.dataset.stars); + } else if (sortBy === 'name') { + return a.querySelector('h3').textContent.localeCompare(b.querySelector('h3').textContent); + } + return 0; + }); + + container.innerHTML = ''; + cards.forEach(card => container.appendChild(card)); +}); + +// Function to parse resources from README content +function parseResources(content) { + const resources = { + 'most-popular': [], + 'protocol': [], + 'relays': [], + 'clients': [], + 'libraries': [], + 'bridges-and-gateways': [], + 'cache-services': [], + 'tools': [], + 'nip-05-identity-services': [], + 'offline-signers': [], + 'vanity-pubkey-mining': [], + 'peer-to-peer-markets': [], + 'nip-07-browser-extensions': [], + 'nip-47-nostr-wallet-connect-nwc-implementations': [], + 'nip-57-zaps-compatible-wallets-and-solutions': [], + 'nip-90-data-vending-machines': [], + 'nip-96-file-storage-servers': [], + 'nostr-web-services-nws': [], + 'adjacent-protocols': [], + 'games-on-nostr': [], + 'communities': [], + 'tutorials': [], + 'recommended-reading-watching': [], + 'podcasts': [], + 'other-links': [], + 'deprecated': [], + 'related-resources': [], + 'contributing': [], + 'contributors': [] + }; + + // Split content by lines and process each line + const lines = content.split('\n'); + let currentMainSection = ''; + let currentSubSection = ''; + + lines.forEach(line => { + // Detect main section headers (##) + if (line.startsWith('## ')) { + currentMainSection = line.slice(3).trim(); + currentSubSection = ''; + } + // Detect subsection headers (###) + else if (line.startsWith('### ')) { + currentSubSection = line.slice(4).trim(); + } + // Special handling for contributors section with HTML content + else if (currentMainSection.toLowerCase() === 'contributors' && line.includes('${resource.name} + ${resource.link ? ` + + ` : ''} + ${resource.description ? ` +
+ ${resource.description} +
+ ` : ''} + ${resource.stars ? ` +
+ + ${resource.stars} +
+ ` : ''} + `; + + card.innerHTML = formattedContent; + return card; +} + +// Function to populate resources container +function populateResources(categoryId, resources) { + const container = document.getElementById('resources-container'); + container.innerHTML = ''; // Clear existing content + + resources[categoryId]?.forEach(resource => { + const card = createResourceCard(resource); + container.appendChild(card); + }); +} + +// Function to get icon for category +function getCategoryIcon(category) { + const iconMap = { + 'most-popular': 'fa-star', + 'protocol': 'fa-book', + 'relays': 'fa-server', + 'clients': 'fa-mobile-alt', + 'libraries': 'fa-code', + 'bridges-and-gateways': 'fa-bridge', + 'cache-services': 'fa-database', + 'tools': 'fa-wrench', + 'nip-05-identity-services': 'fa-id-card', + 'offline-signers': 'fa-signature', + 'vanity-pubkey-mining': 'fa-hammer', + 'peer-to-peer-markets': 'fa-store', + 'nip-07-browser-extensions': 'fa-puzzle-piece', + 'nip-47-nostr-wallet-connect-nwc-implementations': 'fa-wallet', + 'nip-57-zaps-compatible-wallets-and-solutions': 'fa-bolt', + 'nip-90-data-vending-machines': 'fa-store-alt', + 'nip-96-file-storage-servers': 'fa-folder', + 'nostr-web-services-nws': 'fa-globe', + 'adjacent-protocols': 'fa-link', + 'games-on-nostr': 'fa-gamepad', + 'communities': 'fa-users', + 'tutorials': 'fa-graduation-cap', + 'recommended-reading-watching': 'fa-book-reader', + 'podcasts': 'fa-podcast', + 'other-links': 'fa-external-link-alt', + 'deprecated': 'fa-archive', + 'related-resources': 'fa-project-diagram', + 'contributing': 'fa-hands-helping', + 'contributors': 'fa-users-cog', + // Default icon if category not found + 'default': 'fa-circle' + }; + + const key = category.toLowerCase().replace(/[^a-z0-9]+/g, '-'); + return iconMap[key] || iconMap.default; +} + +// Function to generate navigation from content +function generateNavigation(content) { + const navList = document.querySelector('.nav-links'); + const categories = []; + let currentCategory = ''; + + // Split content by lines and find all ## headers + const lines = content.split('\n'); + lines.forEach(line => { + if (line.startsWith('## ')) { + currentCategory = line.slice(3).trim(); + const categoryId = currentCategory.toLowerCase().replace(/[^a-z0-9]+/g, '-'); + const icon = getCategoryIcon(currentCategory.toLowerCase()); + + categories.push({ + id: categoryId, + name: currentCategory, + icon: icon + }); + } + }); + + // Generate navigation HTML + navList.innerHTML = categories.map(category => ` +
  • + + + ${category.name} + +
  • + `).join(''); + + // Add click event listeners to the new navigation items + document.querySelectorAll('.nav-links a').forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const categoryId = e.target.getAttribute('href').replace('#', ''); + populateResources(categoryId, window.parsedResources); + }); + }); + + return categories; +} + +// Update the fetch call to generate navigation +fetch('./README.md') + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.text(); + }) + .then(content => { + console.log('README content loaded:', content.substring(0, 200) + '...'); + + // Generate navigation first + const categories = generateNavigation(content); + + // Then parse and populate resources + window.parsedResources = parseResources(content); + console.log('Parsed resources:', window.parsedResources); + + // Load initial category (first one in the list) + if (categories.length > 0) { + populateResources(categories[0].id, window.parsedResources); + } + }) + .catch(error => { + console.error('Error loading README:', error); + document.getElementById('resources-container').innerHTML = + `
    Error loading resources: ${error.message}
    `; + }); \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..8e196d0 --- /dev/null +++ b/styles.css @@ -0,0 +1,213 @@ +:root { + --primary-color: #6e5494; + --background-color: #ffffff; + --text-color: #333333; + --card-background: #f5f5f5; + --sidebar-background: #f8f9fa; + --hover-color: #563d7c; +} + +/* Dark theme variables */ +[data-theme="dark"] { + --background-color: #1a1a1a; + --text-color: #ffffff; + --card-background: #2d2d2d; + --sidebar-background: #252525; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: var(--background-color); + color: var(--text-color); + line-height: 1.6; +} + +.container { + display: flex; + min-height: 100vh; +} + +/* Sidebar Styles */ +.sidebar { + width: 280px; + background-color: var(--sidebar-background); + padding: 1rem; + position: fixed; + height: 100vh; + overflow-y: auto; + transition: transform 0.3s ease; +} + +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.search-box { + position: relative; + margin-bottom: 1.5rem; +} + +.search-box input { + width: 100%; + padding: 0.5rem 2rem 0.5rem 1rem; + border: 1px solid #ddd; + border-radius: 4px; + background-color: var(--background-color); + color: var(--text-color); +} + +.search-box i { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + color: #666; +} + +.nav-links { + list-style: none; +} + +.nav-links li { + margin-bottom: 0.5rem; +} + +.nav-links a { + display: flex; + align-items: center; + padding: 0.5rem; + color: var(--text-color); + text-decoration: none; + border-radius: 4px; + transition: background-color 0.2s; +} + +.nav-links a:hover { + background-color: var(--primary-color); + color: white; +} + +.nav-links i { + margin-right: 0.5rem; + width: 20px; + text-align: center; +} + +/* Main Content Styles */ +.main-content { + margin-left: 280px; + padding: 2rem; + flex: 1; +} + +.content-header { + display: flex; + justify-content: flex-end; + margin-bottom: 2rem; +} + +.view-controls { + display: flex; + gap: 1rem; + align-items: center; +} + +/* Resource Cards */ +.resource-card { + background: var(--card-bg); + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.resource-title { + font-size: 1.2em; + margin-bottom: 8px; + color: var(--text-primary); +} + +.resource-link { + margin-bottom: 12px; +} + +.resource-link a { + color: var(--link-color); + text-decoration: none; + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9em; +} + +.resource-link a:hover { + text-decoration: underline; +} + +.resource-description { + color: var(--text-secondary); + margin-bottom: 12px; + line-height: 1.5; + font-size: 0.95em; + padding-left: 8px; + border-left: 3px solid var(--border-color); +} + +.resource-stars { + color: var(--star-color); + font-size: 0.9em; + display: flex; + align-items: center; + gap: 4px; +} + +.fa-star { + color: #f1c40f; +} + +.fa-external-link-alt { + font-size: 0.9em; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + z-index: 1000; + } + + .sidebar.active { + transform: translateX(0); + } + + .main-content { + margin-left: 0; + } + + .menu-toggle { + display: block; + } +} + +.markdown-content { + padding: 10px; + line-height: 1.5; +} + +.markdown-content a { + color: #0366d6; + text-decoration: none; +} + +.markdown-content a:hover { + text-decoration: underline; +} \ No newline at end of file From e390d63ba462a0f656e44f12b4bd36a443e50a19 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:28:52 -0800 Subject: [PATCH 02/17] fixed contributers/contributing displays and got the search working --- script.js | 220 +++++++++++++++++++++++++++++++++++++++++++++++------ styles.css | 104 ++++++++++++++++++++++++- 2 files changed, 300 insertions(+), 24 deletions(-) diff --git a/script.js b/script.js index dd64bea..34fa263 100644 --- a/script.js +++ b/script.js @@ -19,13 +19,82 @@ menuToggle.addEventListener('click', () => { // Search functionality const searchInput = document.getElementById('search'); -const resourceCards = document.querySelectorAll('.resource-card'); searchInput.addEventListener('input', (e) => { const searchTerm = e.target.value.toLowerCase(); - resourceCards.forEach(card => { - const text = card.textContent.toLowerCase(); - card.style.display = text.includes(searchTerm) ? 'block' : 'none'; + const container = document.getElementById('resources-container'); + + if (!searchTerm) { + // If search is empty, restore current category view + const currentCategory = document.querySelector('.nav-links a.active')?.getAttribute('href')?.replace('#', ''); + if (currentCategory) { + populateResources(currentCategory, window.parsedResources); + } + return; + } + + // Clear current container + container.innerHTML = ''; + + // Search through all categories + Object.entries(window.parsedResources).forEach(([category, resources]) => { + resources.forEach(resource => { + let shouldShow = false; + + if (resource.type) { + // Handle special sections (contributors, contributing) + if (resource.type === 'markdown') { + shouldShow = resource.content.toLowerCase().includes(searchTerm); + } else if (resource.type === 'github-contributors') { + // Skip contributors section in search + return; + } + } else { + // Regular resources + const searchableText = [ + resource.name, + resource.description, + resource.link + ].filter(Boolean).join(' ').toLowerCase(); + + shouldShow = searchableText.includes(searchTerm); + } + + if (shouldShow) { + let card; + if (resource.type) { + card = createSpecialSectionCard(resource); + } else { + card = createResourceCard(resource); + } + + // Add category label to card + const categoryLabel = document.createElement('div'); + categoryLabel.className = 'category-label'; + categoryLabel.textContent = category.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + card.insertBefore(categoryLabel, card.firstChild); + + container.appendChild(card); + } + }); + }); + + // Show "no results" message if nothing found + if (!container.children.length) { + container.innerHTML = ` +
    + No resources found matching "${searchTerm}" +
    + `; + } +}); + +// Add active class handling for navigation +document.querySelectorAll('.nav-links a').forEach(link => { + link.addEventListener('click', () => { + document.querySelectorAll('.nav-links a').forEach(l => l.classList.remove('active')); + link.classList.add('active'); + searchInput.value = ''; // Clear search when changing categories }); }); @@ -50,7 +119,42 @@ sortSelect.addEventListener('change', (e) => { cards.forEach(card => container.appendChild(card)); }); -// Function to parse resources from README content +// Add this function to fetch contributors from GitHub API +async function fetchContributors(owner, repo) { + try { + let page = 1; + let allContributors = []; + + while (true) { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=100&page=${page}` + ); + + if (!response.ok) throw new Error('Failed to fetch contributors'); + + const contributors = await response.json(); + if (contributors.length === 0) break; // No more contributors + + allContributors = [...allContributors, ...contributors]; + page++; + + // Check if we've reached the last page + const linkHeader = response.headers.get('Link'); + if (!linkHeader || !linkHeader.includes('rel="next"')) { + break; + } + } + + console.log(`Total contributors fetched: ${allContributors.length}`); + return allContributors; + + } catch (error) { + console.error('Error fetching contributors:', error); + return []; + } +} + +// Modify the parseResources function to handle contributors differently function parseResources(content) { const resources = { 'most-popular': [], @@ -84,31 +188,36 @@ function parseResources(content) { 'contributors': [] }; - // Split content by lines and process each line const lines = content.split('\n'); let currentMainSection = ''; let currentSubSection = ''; + let contributingContent = ''; + + // Extract repo info from contributors section + let repoInfo = null; + const repoRegex = /github\.com\/([\w-]+)\/([\w-]+)\/graphs\/contributors/; lines.forEach(line => { - // Detect main section headers (##) if (line.startsWith('## ')) { currentMainSection = line.slice(3).trim(); currentSubSection = ''; + } else if (currentMainSection.toLowerCase() === 'contributors') { + const match = line.match(repoRegex); + if (match) { + repoInfo = { + owner: match[1], + repo: match[2] + }; + } + } else if (currentMainSection.toLowerCase() === 'contributing') { + if (line.trim() && !line.startsWith('##')) { + contributingContent += line + '\n'; + } } // Detect subsection headers (###) else if (line.startsWith('### ')) { currentSubSection = line.slice(4).trim(); } - // Special handling for contributors section with HTML content - else if (currentMainSection.toLowerCase() === 'contributors' && line.includes('Contributors +
    +
    Loading contributors...
    +
    + `; + + if (resource.repoInfo) { + console.log('Fetching contributors for:', resource.repoInfo); // Debug log + const contributors = await fetchContributors(resource.repoInfo.owner, resource.repoInfo.repo); + console.log('Number of contributors:', contributors.length); // Debug log + const contributorsHtml = contributors.map(contributor => ` + + ${contributor.login} + ${contributor.login} + + `).join(''); + + const grid = card.querySelector('.contributors-grid'); + grid.innerHTML = contributorsHtml || 'No contributors found'; + } + } else if (resource.type === 'markdown') { + // For contributing section + const formattedContent = resource.content + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + card.innerHTML = ` +
    How to Contribute
    +
    + ${formattedContent} +
    + `; + } + + return card; +} + +// Update populateResources to handle both special and regular resources +async function populateResources(categoryId, resources) { const container = document.getElementById('resources-container'); - container.innerHTML = ''; // Clear existing content + container.innerHTML = ''; + + const categoryResources = resources[categoryId]; + if (!categoryResources) return; - resources[categoryId]?.forEach(resource => { - const card = createResourceCard(resource); + for (const resource of categoryResources) { + let card; + if (resource.type) { + // Handle special sections (contributors and contributing) + card = await createSpecialSectionCard(resource); + } else { + // Handle regular resource cards + card = createResourceCard(resource); + } container.appendChild(card); - }); + } } // Function to get icon for category diff --git a/styles.css b/styles.css index 8e196d0..c8b547c 100644 --- a/styles.css +++ b/styles.css @@ -107,6 +107,8 @@ body { margin-left: 280px; padding: 2rem; flex: 1; + min-height: 100vh; + overflow: auto; } .content-header { @@ -123,11 +125,14 @@ body { /* Resource Cards */ .resource-card { - background: var(--card-bg); + background: var(--card-background); border-radius: 8px; padding: 16px; margin-bottom: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); + width: 100%; + height: auto; + overflow: visible; } .resource-title { @@ -210,4 +215,101 @@ body { .markdown-content a:hover { text-decoration: underline; +} + +/* Update contributors grid styles */ +.contributors-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + gap: 16px; + padding: 20px; + width: 100%; + height: auto; + min-height: 100px; + justify-items: center; + overflow: visible; +} + +.contributors-grid a { + display: flex; + flex-direction: column; + align-items: center; + text-decoration: none; + color: var(--text-color); + font-size: 0.8em; + text-align: center; +} + +.contributors-grid img { + border-radius: 50%; + width: 64px; + height: 64px; + transition: transform 0.2s; + margin-bottom: 8px; +} + +.contributors-grid a:hover { + text-decoration: none; +} + +.contributors-grid a:hover img { + transform: scale(1.1); +} + +.contributors-grid .loading { + grid-column: 1 / -1; + text-align: center; + padding: 20px; + color: var(--text-color); + font-style: italic; +} + +/* Update the resource card for contributors */ +.resource-card.contributors-card { + max-width: none; + width: 100%; + height: auto; + min-height: 200px; +} + +.resource-description a { + color: var(--primary-color); + text-decoration: none; +} + +.resource-description a:hover { + text-decoration: underline; +} + +#resources-container { + width: 100%; + height: auto; + min-height: 200px; + overflow: visible; +} + +/* Category label styles */ +.category-label { + font-size: 0.8em; + color: var(--primary-color); + text-transform: capitalize; + margin-bottom: 8px; + padding: 2px 8px; + background-color: rgba(110, 84, 148, 0.1); + border-radius: 4px; + display: inline-block; +} + +/* No results styles */ +.no-results { + text-align: center; + padding: 2rem; + color: var(--text-color); + font-style: italic; +} + +/* Active nav link style */ +.nav-links a.active { + background-color: var(--primary-color); + color: white; } \ No newline at end of file From 4a5bfedd1f2781baaa6f82695ab5826b783bd335 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:48:08 -0800 Subject: [PATCH 03/17] added mark.js and now it looks and works even better --- index.html | 2 + script.js | 258 +++++++++++++++++++++++++++++++++++++++-------------- styles.css | 21 +++-- 3 files changed, 211 insertions(+), 70 deletions(-) diff --git a/index.html b/index.html index 20c7e51..224b883 100644 --- a/index.html +++ b/index.html @@ -49,6 +49,8 @@

    Nostr Resources

    + + \ No newline at end of file diff --git a/script.js b/script.js index 34fa263..4c8202b 100644 --- a/script.js +++ b/script.js @@ -201,6 +201,7 @@ function parseResources(content) { if (line.startsWith('## ')) { currentMainSection = line.slice(3).trim(); currentSubSection = ''; + console.log('Processing section:', currentMainSection); // Debug log } else if (currentMainSection.toLowerCase() === 'contributors') { const match = line.match(repoRegex); if (match) { @@ -228,9 +229,13 @@ function parseResources(content) { .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, ''); + console.log('Parsed resource:', categoryId, resource); // Debug log + // Add resource to appropriate category if it exists if (resources[categoryId]) { resources[categoryId].push(resource); + } else { + console.warn('Category not found:', categoryId); // Debug log } } } @@ -254,29 +259,33 @@ function parseResources(content) { // Function to parse a single resource line function parseResourceLine(line) { - // Regular expressions to extract information + // Updated regex patterns to better handle various markdown formats const nameRegex = /\[(.*?)\]/; const linkRegex = /\((.*?)\)/; const starsRegex = /!\[stars\].*?stars\/(.*?)\/.*?style=social/; - const descriptionRegex = /\) - (.*?)(?=\[|$)/; + // Updated description regex to handle descriptions after stars badge + const descriptionRegex = /style=social\) - (.*?)(?=(?:\[|\n|$))|(?:\) - )(.*?)(?=(?:\[|\n|$))/; try { const name = nameRegex.exec(line)?.[1]; const link = linkRegex.exec(line)?.[1]; const stars = starsRegex.exec(line)?.[1]; - const description = descriptionRegex.exec(line)?.[1]?.trim(); + + // More robust description extraction + const descMatch = descriptionRegex.exec(line); + const description = (descMatch?.[1] || descMatch?.[2] || '').trim(); - if (name && (link || description)) { + if (name && link) { return { name, link, stars: stars || 0, description: description || '', - raw: line.trim() // Keep the original markdown line + raw: line.trim() }; } } catch (error) { - console.error('Error parsing resource line:', error); + console.error('Error parsing resource line:', error, line); } return null; } @@ -287,7 +296,6 @@ function createResourceCard(resource) { card.className = 'resource-card'; card.dataset.stars = resource.stars || 0; - // Create a formatted version of the markdown content const formattedContent = `
    ${resource.name}
    ${resource.link ? ` @@ -421,74 +429,194 @@ function getCategoryIcon(category) { } // Function to generate navigation from content -function generateNavigation(content) { +function generateNavigation(sectionNames) { const navList = document.querySelector('.nav-links'); - const categories = []; - let currentCategory = ''; - - // Split content by lines and find all ## headers - const lines = content.split('\n'); - lines.forEach(line => { - if (line.startsWith('## ')) { - currentCategory = line.slice(3).trim(); - const categoryId = currentCategory.toLowerCase().replace(/[^a-z0-9]+/g, '-'); - const icon = getCategoryIcon(currentCategory.toLowerCase()); - - categories.push({ - id: categoryId, - name: currentCategory, - icon: icon - }); - } - }); - - // Generate navigation HTML - navList.innerHTML = categories.map(category => ` -
  • - - - ${category.name} + navList.innerHTML = ''; // Clear existing navigation + + sectionNames.forEach(section => { + // Create URL-friendly ID + const sectionId = section + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, ''); + + const li = document.createElement('li'); + li.innerHTML = ` + + + ${section} -
  • - `).join(''); + `; - // Add click event listeners to the new navigation items - document.querySelectorAll('.nav-links a').forEach(link => { - link.addEventListener('click', (e) => { + // Add click handler + li.querySelector('a').addEventListener('click', (e) => { e.preventDefault(); - const categoryId = e.target.getAttribute('href').replace('#', ''); - populateResources(categoryId, window.parsedResources); + // Remove active class from all links + document.querySelectorAll('.nav-links a').forEach(a => + a.classList.remove('active') + ); + // Add active class to clicked link + e.target.classList.add('active'); + // Display the section + displaySection(section, window.parsedResources); }); + + navList.appendChild(li); }); - return categories; + // Set first item as active + const firstLink = navList.querySelector('a'); + if (firstLink) { + firstLink.classList.add('active'); + } } -// Update the fetch call to generate navigation -fetch('./README.md') - .then(response => { +// Remove the old fetch call and replace with this initialization +document.addEventListener('DOMContentLoaded', () => { + // Test if marked is loaded + if (typeof marked === 'undefined') { + console.error('marked.js is not loaded!'); + document.getElementById('resources-container').innerHTML = ` +
    + Error: marked.js library is not loaded properly. +
    `; + return; + } + + // Test marked with a simple markdown string + console.log('marked.js test:', marked.parse('# Test\nThis is a *test* of **marked.js**')); + + // If everything is working, proceed with main functionality + parseAndDisplayContent() + .then(() => console.log('Content successfully parsed and displayed')) + .catch(error => { + console.error('Error in main content processing:', error); + document.getElementById('resources-container').innerHTML = ` +
    + Error loading content: ${error.message} +
    `; + }); +}); + +async function parseAndDisplayContent() { + try { + const response = await fetch('./README.md'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - return response.text(); - }) - .then(content => { - console.log('README content loaded:', content.substring(0, 200) + '...'); - - // Generate navigation first - const categories = generateNavigation(content); - - // Then parse and populate resources - window.parsedResources = parseResources(content); - console.log('Parsed resources:', window.parsedResources); - - // Load initial category (first one in the list) - if (categories.length > 0) { - populateResources(categories[0].id, window.parsedResources); + const content = await response.text(); + + // Parse the markdown into HTML + const htmlContent = marked.parse(content); + + // Create a temporary element to parse the HTML + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = htmlContent; + + // Get all sections (h2 headers and their content) + const sections = {}; + let currentSection = null; + let currentSectionContent = []; + + Array.from(tempDiv.children).forEach(element => { + if (element.tagName === 'H2') { + // If we have a previous section, save it + if (currentSection) { + sections[currentSection] = currentSectionContent; + } + // Start new section + currentSection = element.textContent.trim(); + currentSectionContent = []; + } else if (currentSection) { + currentSectionContent.push({ + type: element.tagName === 'UL' ? 'resources' : 'content', + element: element + }); + } + }); + + // Save the last section + if (currentSection) { + sections[currentSection] = currentSectionContent; } - }) - .catch(error => { - console.error('Error loading README:', error); - document.getElementById('resources-container').innerHTML = - `
    Error loading resources: ${error.message}
    `; - }); \ No newline at end of file + + // Store sections globally + window.parsedResources = sections; + + // Generate navigation + const sectionNames = Object.keys(sections); + generateNavigation(sectionNames); + + // Display initial section + if (sectionNames.length > 0) { + displaySection(sectionNames[0], sections); + } + + } catch (error) { + console.error('Error processing markdown:', error); + throw error; // Re-throw to be caught by the main error handler + } +} + +function displaySection(sectionName, sections) { + const container = document.getElementById('resources-container'); + container.innerHTML = ''; + + // Add section header + const header = document.createElement('h2'); + header.textContent = sectionName; + container.appendChild(header); + + // Display section content + sections[sectionName].forEach(item => { + if (item.type === 'content') { + // Regular markdown content + const contentDiv = document.createElement('div'); + contentDiv.className = 'markdown-content'; + contentDiv.innerHTML = item.element.outerHTML; + container.appendChild(contentDiv); + } else if (item.type === 'resources') { + // Resource list + Array.from(item.element.children).forEach(li => { + const card = createResourceCard({ + name: li.querySelector('a')?.textContent || '', + link: li.querySelector('a')?.href || '', + description: li.textContent.split('- ')[1]?.trim() || '', + stars: li.querySelector('img[alt="stars"]') + ? parseInt(li.querySelector('img[alt="stars"]').src.match(/stars\/(\d+)/)?.[1]) || 0 + : 0 + }); + container.appendChild(card); + }); + } + }); +} + +function createResourceCard(resource) { + const card = document.createElement('div'); + card.className = 'resource-card'; + + card.innerHTML = ` +
    +

    + + ${resource.name} + + +

    + ${resource.stars ? ` +
    + + ${resource.stars} +
    + ` : ''} +
    + ${resource.description ? ` +
    + ${resource.description} +
    + ` : ''} + `; + + return card; +} \ No newline at end of file diff --git a/styles.css b/styles.css index c8b547c..b6724ee 100644 --- a/styles.css +++ b/styles.css @@ -146,7 +146,7 @@ body { } .resource-link a { - color: var(--link-color); + color: var(--primary-color); text-decoration: none; display: flex; align-items: center; @@ -159,12 +159,14 @@ body { } .resource-description { - color: var(--text-secondary); - margin-bottom: 12px; + color: var(--text-color); + margin: 12px 0; line-height: 1.5; font-size: 0.95em; - padding-left: 8px; - border-left: 3px solid var(--border-color); + padding: 8px 12px; + background: rgba(110, 84, 148, 0.05); + border-radius: 4px; + border-left: 3px solid var(--primary-color); } .resource-stars { @@ -312,4 +314,13 @@ body { .nav-links a.active { background-color: var(--primary-color); color: white; +} + +.error-message { + padding: 1rem; + margin: 1rem; + background-color: #fff3f3; + border-left: 4px solid #ff4444; + color: #dc3545; + border-radius: 4px; } \ No newline at end of file From 584adc80868b2c6aed73281388bbfcfb6b63d25b Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:26:27 -0800 Subject: [PATCH 04/17] some CSS improvements and search functions fixes --- script.js | 90 ++++++++++++++++++++++++++++++------------------------ styles.css | 21 ++++++++++--- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/script.js b/script.js index 4c8202b..3c4c10f 100644 --- a/script.js +++ b/script.js @@ -26,9 +26,9 @@ searchInput.addEventListener('input', (e) => { if (!searchTerm) { // If search is empty, restore current category view - const currentCategory = document.querySelector('.nav-links a.active')?.getAttribute('href')?.replace('#', ''); + const currentCategory = document.querySelector('.nav-links a.active')?.textContent; if (currentCategory) { - populateResources(currentCategory, window.parsedResources); + displaySection(currentCategory, window.parsedResources); } return; } @@ -36,45 +36,55 @@ searchInput.addEventListener('input', (e) => { // Clear current container container.innerHTML = ''; - // Search through all categories - Object.entries(window.parsedResources).forEach(([category, resources]) => { - resources.forEach(resource => { - let shouldShow = false; - - if (resource.type) { - // Handle special sections (contributors, contributing) - if (resource.type === 'markdown') { - shouldShow = resource.content.toLowerCase().includes(searchTerm); - } else if (resource.type === 'github-contributors') { - // Skip contributors section in search - return; - } - } else { - // Regular resources - const searchableText = [ - resource.name, - resource.description, - resource.link - ].filter(Boolean).join(' ').toLowerCase(); - - shouldShow = searchableText.includes(searchTerm); - } - - if (shouldShow) { - let card; - if (resource.type) { - card = createSpecialSectionCard(resource); - } else { - card = createResourceCard(resource); + // Search through all sections + 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); + } + }); + } else if (item.type === 'content') { + // Search through regular content + const contentText = item.element.textContent.toLowerCase(); + if (contentText.includes(searchTerm)) { + const contentDiv = document.createElement('div'); + contentDiv.className = 'markdown-content'; + contentDiv.innerHTML = item.element.outerHTML; + + // Add section label + const sectionLabel = document.createElement('div'); + sectionLabel.className = 'category-label'; + sectionLabel.textContent = sectionName; + + container.appendChild(sectionLabel); + container.appendChild(contentDiv); } - - // Add category label to card - const categoryLabel = document.createElement('div'); - categoryLabel.className = 'category-label'; - categoryLabel.textContent = category.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); - card.insertBefore(categoryLabel, card.firstChild); - - container.appendChild(card); } }); }); diff --git a/styles.css b/styles.css index b6724ee..8813abe 100644 --- a/styles.css +++ b/styles.css @@ -5,6 +5,9 @@ --card-background: #f5f5f5; --sidebar-background: #f8f9fa; --hover-color: #563d7c; + --text-primary: var(--text-color); + --link-color: var(--primary-color); + --star-color: #f1c40f; } /* Dark theme variables */ @@ -13,6 +16,8 @@ --text-color: #ffffff; --card-background: #2d2d2d; --sidebar-background: #252525; + --text-primary: var(--text-color); + --link-color: #ffa500; } * { @@ -138,7 +143,7 @@ body { .resource-title { font-size: 1.2em; margin-bottom: 8px; - color: var(--text-primary); + color: var(--text-color); } .resource-link { @@ -146,7 +151,7 @@ body { } .resource-link a { - color: var(--primary-color); + color: var(--link-color); text-decoration: none; display: flex; align-items: center; @@ -178,7 +183,7 @@ body { } .fa-star { - color: #f1c40f; + color: var(--star-color); } .fa-external-link-alt { @@ -211,7 +216,7 @@ body { } .markdown-content a { - color: #0366d6; + color: var(--link-color); text-decoration: none; } @@ -275,7 +280,7 @@ body { } .resource-description a { - color: var(--primary-color); + color: var(--link-color); text-decoration: none; } @@ -323,4 +328,10 @@ body { border-left: 4px solid #ff4444; color: #dc3545; border-radius: 4px; +} + +/* Add H3 styling for dark mode */ +[data-theme="dark"] h3, +[data-theme="dark"] .markdown-content h3 { + color: #f1c40f; } \ No newline at end of file From f9e57d7fb0f977da006b137d413dcfd113b8a72b Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:43:22 -0800 Subject: [PATCH 05/17] SEO improvments --- index.html | 46 ++++++++++++++++------ script.js | 63 ++++++++++++++++++------------ styles.css | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 37 deletions(-) diff --git a/index.html b/index.html index 224b883..93b14cd 100644 --- a/index.html +++ b/index.html @@ -3,20 +3,38 @@ - Awesome Nostr Resources + + + + + + + + + + + + + + + + + + + Awesome Nostr Resources | Comprehensive Guide to Nostr Protocol
    - -
    -
    +
    +
    -
    -
    -
    + - -
    +
    + +
    +

    © 2024 Awesome Nostr Resources. All rights reserved.

    +
    diff --git a/script.js b/script.js index 3c4c10f..d7392bf 100644 --- a/script.js +++ b/script.js @@ -304,32 +304,37 @@ function parseResourceLine(line) { function createResourceCard(resource) { const card = document.createElement('div'); card.className = 'resource-card'; - card.dataset.stars = resource.stars || 0; - - const formattedContent = ` -
    ${resource.name}
    - ${resource.link ? ` - ${resource.description ? ` -
    +
    ${resource.description}
    ` : ''} - ${resource.stars ? ` -
    - - ${resource.stars} -
    - ` : ''} `; - card.innerHTML = formattedContent; return card; } @@ -606,23 +611,31 @@ function createResourceCard(resource) { const card = document.createElement('div'); card.className = 'resource-card'; + // Add schema.org structured data + card.setAttribute('itemscope', ''); + card.setAttribute('itemtype', 'https://schema.org/SoftwareApplication'); + card.innerHTML = `

    - + ${resource.name} - +

    ${resource.stars ? ` -
    - - ${resource.stars} +
    + + ${resource.stars} +
    ` : ''}
    ${resource.description ? ` -
    +
    ${resource.description}
    ` : ''} diff --git a/styles.css b/styles.css index 8813abe..2fb3962 100644 --- a/styles.css +++ b/styles.css @@ -47,6 +47,8 @@ body { height: 100vh; overflow-y: auto; transition: transform 0.3s ease; + border-right: 1px solid rgba(110, 84, 148, 0.15); + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); } .sidebar-header { @@ -107,6 +109,32 @@ body { text-align: center; } +/* Custom Scrollbar Styles */ +.sidebar::-webkit-scrollbar { + width: 8px; +} + +.sidebar::-webkit-scrollbar-track { + background: var(--sidebar-background); +} + +.sidebar::-webkit-scrollbar-thumb { + background: var(--primary-color); + opacity: 0.5; + border-radius: 4px; + transition: background 0.2s ease; +} + +.sidebar::-webkit-scrollbar-thumb:hover { + background: var(--hover-color); +} + +/* Firefox scrollbar styles */ +.sidebar { + scrollbar-width: thin; + scrollbar-color: var(--primary-color) var(--sidebar-background); +} + /* Main Content Styles */ .main-content { margin-left: 280px; @@ -334,4 +362,86 @@ body { [data-theme="dark"] h3, [data-theme="dark"] .markdown-content h3 { color: #f1c40f; +} + +/* Main content scrollbar styles */ +.main-content::-webkit-scrollbar { + width: 8px; +} + +.main-content::-webkit-scrollbar-track { + background: var(--background-color); +} + +.main-content::-webkit-scrollbar-thumb { + background: var(--primary-color); + opacity: 0.5; + border-radius: 4px; + transition: background 0.2s ease; +} + +.main-content::-webkit-scrollbar-thumb:hover { + background: var(--hover-color); +} + +/* Firefox main content scrollbar styles */ +.main-content { + scrollbar-width: thin; + scrollbar-color: var(--primary-color) var(--background-color); +} + +/* Dark theme scrollbar adjustments */ +[data-theme="dark"] .sidebar::-webkit-scrollbar-track, +[data-theme="dark"] .main-content::-webkit-scrollbar-track { + background: var(--sidebar-background); +} + +[data-theme="dark"] .sidebar::-webkit-scrollbar-thumb, +[data-theme="dark"] .main-content::-webkit-scrollbar-thumb { + background: rgba(110, 84, 148, 0.6); +} + +[data-theme="dark"] .sidebar::-webkit-scrollbar-thumb:hover, +[data-theme="dark"] .main-content::-webkit-scrollbar-thumb:hover { + background: rgba(110, 84, 148, 0.8); +} + +/* Screen reader only class */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Improve link accessibility */ +a:focus { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +/* Improve button accessibility */ +button:focus { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +/* Add focus styles for dark mode */ +[data-theme="dark"] a:focus, +[data-theme="dark"] button:focus { + outline-color: var(--link-color); +} + +/* Footer styles */ +.site-footer { + background-color: var(--sidebar-background); + color: var(--text-color); + text-align: center; + padding: 1rem; + margin-top: auto; } \ No newline at end of file From bcf564a72b25253a8db611fa3d0c02fc59d802ba Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:19:36 -0800 Subject: [PATCH 06/17] some CSS changes on titel and links colors --- index.html | 8 -------- script.js | 21 --------------------- styles.css | 23 +++++++++++++++++++++++ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/index.html b/index.html index 93b14cd..bb88206 100644 --- a/index.html +++ b/index.html @@ -53,14 +53,6 @@

    Nostr Resources

    -
    - - -
    diff --git a/script.js b/script.js index d7392bf..63d1bbf 100644 --- a/script.js +++ b/script.js @@ -108,27 +108,6 @@ document.querySelectorAll('.nav-links a').forEach(link => { }); }); -// Sort functionality -const sortSelect = document.getElementById('sortSelect'); - -sortSelect.addEventListener('change', (e) => { - const sortBy = e.target.value; - const container = document.getElementById('resources-container'); - const cards = Array.from(container.getElementsByClassName('resource-card')); - - cards.sort((a, b) => { - if (sortBy === 'stars') { - return parseInt(b.dataset.stars) - parseInt(a.dataset.stars); - } else if (sortBy === 'name') { - return a.querySelector('h3').textContent.localeCompare(b.querySelector('h3').textContent); - } - return 0; - }); - - container.innerHTML = ''; - cards.forEach(card => container.appendChild(card)); -}); - // Add this function to fetch contributors from GitHub API async function fetchContributors(owner, repo) { try { diff --git a/styles.css b/styles.css index 2fb3962..3c9ff89 100644 --- a/styles.css +++ b/styles.css @@ -174,6 +174,25 @@ body { color: var(--text-color); } +.resource-title a { + color: var(--primary-color); + text-decoration: none; + transition: color 0.2s ease; +} + +.resource-title a:hover { + color: var(--hover-color); +} + +/* Dark theme adjustment */ +[data-theme="dark"] .resource-title a { + color: #a48ec7; /* A lighter shade of purple for dark mode */ +} + +[data-theme="dark"] .resource-title a:hover { + color: #b9a8d7; /* Even lighter shade for hover in dark mode */ +} + .resource-link { margin-bottom: 12px; } @@ -364,6 +383,10 @@ body { color: #f1c40f; } +#resources-container h2 { + color: var(--primary-color); +} + /* Main content scrollbar styles */ .main-content::-webkit-scrollbar { width: 8px; From cc89534da65af2165ae39621c38841535cd4daf3 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:23:05 -0800 Subject: [PATCH 07/17] Users light/dark preferrence will be stored so it doesn't change when they refresh --- script.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/script.js b/script.js index 63d1bbf..1381ce0 100644 --- a/script.js +++ b/script.js @@ -2,13 +2,34 @@ const darkModeToggle = document.getElementById('darkModeToggle'); const body = document.body; -darkModeToggle.addEventListener('click', () => { - body.dataset.theme = body.dataset.theme === 'dark' ? 'light' : 'dark'; +// Initialize theme from localStorage or system preference +document.addEventListener('DOMContentLoaded', () => { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + body.dataset.theme = savedTheme; + } else { + // Check system preference if no saved theme + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + body.dataset.theme = prefersDark ? 'dark' : 'light'; + localStorage.setItem('theme', body.dataset.theme); + } + + // Update toggle button icon darkModeToggle.innerHTML = body.dataset.theme === 'dark' ? '' : ''; }); +darkModeToggle.addEventListener('click', () => { + const newTheme = body.dataset.theme === 'dark' ? 'light' : 'dark'; + body.dataset.theme = newTheme; + localStorage.setItem('theme', newTheme); + + darkModeToggle.innerHTML = newTheme === 'dark' + ? '' + : ''; +}); + // Mobile menu toggle const menuToggle = document.getElementById('menuToggle'); const sidebar = document.querySelector('.sidebar'); From 3d44f538ae1bc12dde6e37e8fe67711577510481 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:28:49 -0800 Subject: [PATCH 08/17] added top nav bar --- index.html | 12 +++++++--- styles.css | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index bb88206..dd4212b 100644 --- a/index.html +++ b/index.html @@ -26,6 +26,14 @@ +