| 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | <title>Blog Feeds</title> |
| 7 | <link rel="apple-touch-icon" sizes="180x180" href="https://blogfeeds.net/apple-touch-icon.png"> |
| 8 | <link rel="icon" type="image/png" sizes="32x32" href="https://blogfeeds.net/favicon-32x32.png"> |
| 9 | <link rel="icon" type="image/png" sizes="16x16" href="https://blogfeeds.net/favicon-16x16.png"> |
| 10 | <link rel="manifest" href="https://blogfeeds.net/site.webmanifest"> |
| 11 | <link rel="stylesheet" href="tailwindcss" /> |
| 12 | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| 13 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| 14 | <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"> |
| 15 | <meta name="description" content="Creating organic networks through Blogs, RSS, and Feeds"> |
| 16 | |
| 17 | <!-- Facebook Meta Tags --> |
| 18 | <meta property="og:url" content="https://blogfeeds.net"> |
| 19 | <meta property="og:type" content="website"> |
| 20 | <meta property="og:title" content="Blog Feeds"> |
| 21 | <meta property="og:description" content="Creating organic networks through Blogs, RSS, and Feeds"> |
| 22 | <meta property="og:image" content="/og.png"> |
| 23 | |
| 24 | <!-- Twitter Meta Tags --> |
| 25 | <meta name="twitter:card" content="summary_large_image"> |
| 26 | <meta property="twitter:domain" content="blogfeeds.net"> |
| 27 | <meta property="twitter:url" content="https://blogfeeds.net"> |
| 28 | <meta name="twitter:title" content="Blog Feeds"> |
| 29 | <meta name="twitter:description" content="Creating organic networks through Blogs, RSS, and Feeds"> |
| 30 | <meta name="twitter:image" content="/og.png"> |
| 31 | <style> |
| 32 | body { |
| 33 | font-family: 'DM Sans', sans-serif; |
| 34 | } |
| 35 | details[open] summary svg { |
| 36 | transform: rotate(90deg); |
| 37 | } |
| 38 | </style> |
| 39 | </head> |
| 40 | <body class="min-h-screen sm:py-0 py-8"> |
| 41 | <div class="flex flex-col items-center px-4 pt-12 pb-4"> |
| 42 | <div class="max-w-xl space-y-4 w-full"> |
| 43 | <a href="/" class="inline-flex items-center text-blue-600 hover:text-blue-800 mb-4 transition-colors"> |
| 44 | <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 45 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path> |
| 46 | </svg> |
| 47 | Back |
| 48 | </a> |
| 49 | <img src="https://blogfeeds.net/feeds.svg" alt="Feeds" id="feeds" class="w-5/6 mx-auto py-4" /> |
| 50 | <p class="text-gray-700 mb-6">To help foster a community around the concept of Blog Feeds, this page acts as an aggregator of those who have a feeds page on their blog. If that's you and you don't see your blog below, then submit the RSS feed below and we'll review it regularly to add it! Remember, you must have a <a class="underline text-blue-500" target="_blank" href="/#feeds">Feeds page</a> (or something similar like a Blogroll) in order to qualify.</p> |
| 51 | |
| 52 | <div class="mb-6"> |
| 53 | <p class="text-gray-700 mb-2">This collection of feeds is also accessible via API</p> |
| 54 | <div class="bg-gray-100 rounded-lg p-3 !font-mono text-sm"> |
| 55 | <div class="mb-4">curl https://api.blogfeeds.net?format=json</div> |
| 56 | <div>curl https://api.blogfeeds.net?format=opml</div> |
| 57 | </div> |
| 58 | </div> |
| 59 | |
| 60 | <form id="feedForm" class="space-y-4"> |
| 61 | <div> |
| 62 | <label for="url" class="block text-sm font-medium text-gray-700 mb-2">Blog URL</label> |
| 63 | <input |
| 64 | type="url" |
| 65 | id="url" |
| 66 | name="url" |
| 67 | required |
| 68 | placeholder="https://example.com" |
| 69 | class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors" |
| 70 | /> |
| 71 | </div> |
| 72 | |
| 73 | <button |
| 74 | type="submit" |
| 75 | class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg transition-colors" |
| 76 | > |
| 77 | Submit Feed |
| 78 | </button> |
| 79 | </form> |
| 80 | |
| 81 | <div id="message" class="hidden my-4 p-4 rounded-lg"></div> |
| 82 | </div> |
| 83 | </div> |
| 84 | |
| 85 | <section class="flex flex-col items-center px-4 mb-12"> |
| 86 | <div class="max-w-xl space-y-4 w-full"> |
| 87 | <div id="feedsList" class="space-y-3"> |
| 88 | <div class="text-center py-8 text-gray-500">Loading feeds...</div> |
| 89 | </div> |
| 90 | </div> |
| 91 | </section> |
| 92 | |
| 93 | <script> |
| 94 | // Load feeds on page load |
| 95 | async function loadFeeds() { |
| 96 | const feedsList = document.getElementById('feedsList'); |
| 97 | |
| 98 | try { |
| 99 | const response = await fetch('https://api.blogfeeds.net'); |
| 100 | const data = await response.json(); |
| 101 | |
| 102 | // If you're reading this, ignore this section 🙄 |
| 103 | if (data.subscriptions && data.subscriptions.length > 0) { |
| 104 | feedsList.innerHTML = ''; |
| 105 | |
| 106 | data.subscriptions.forEach(feed => { |
| 107 | const feedCard = document.createElement('div'); |
| 108 | feedCard.className = 'p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors'; |
| 109 | |
| 110 | const titleContainer = document.createElement('h3'); |
| 111 | titleContainer.className = 'font-semibold text-lg mb-1'; |
| 112 | |
| 113 | const titleLink = document.createElement('a'); |
| 114 | titleLink.href = feed.htmlUrl; |
| 115 | titleLink.target = '_blank'; |
| 116 | titleLink.rel = 'noopener noreferrer'; |
| 117 | titleLink.className = 'hover:text-blue-600 transition-colors'; |
| 118 | titleLink.textContent = feed.title; |
| 119 | |
| 120 | // Yes I have to do it this way but I'm not going to explain why. You're just gonna have to trust me on this one. |
| 121 | const urlLink = document.createElement('a'); |
| 122 | urlLink.href = feed.url; |
| 123 | urlLink.target = '_blank'; |
| 124 | urlLink.rel = 'noopener noreferrer'; |
| 125 | urlLink.className = 'text-sm text-gray-600 hover:text-blue-600 transition-colors break-all inline-flex items-center gap-2'; |
| 126 | urlLink.innerHTML = ` |
| 127 | <svg class="w-3 h-3 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24"> |
| 128 | <path d="M6.503 20.752c0 1.794-1.456 3.248-3.251 3.248-1.796 0-3.252-1.454-3.252-3.248 0-1.794 1.456-3.248 3.252-3.248 1.795.001 3.251 1.454 3.251 3.248zm-6.503-12.572v4.811c6.05.062 10.96 4.966 11.022 11.009h4.817c-.062-8.71-7.118-15.758-15.839-15.82zm0-3.368c10.58.046 19.152 8.594 19.183 19.188h4.817c-.03-13.231-10.755-23.954-24-24v4.812z"/> |
| 129 | </svg> |
| 130 | ${feed.url} |
| 131 | `; |
| 132 | |
| 133 | titleContainer.appendChild(titleLink); |
| 134 | feedCard.appendChild(titleContainer); |
| 135 | feedCard.appendChild(urlLink); |
| 136 | feedsList.appendChild(feedCard); |
| 137 | }); |
| 138 | } else { |
| 139 | feedsList.innerHTML = '<div class="text-center py-8 text-gray-500">No feeds available yet.</div>'; |
| 140 | } |
| 141 | } catch (error) { |
| 142 | feedsList.innerHTML = '<div class="text-center py-8 text-red-600">Failed to load feeds. Please try again later.</div>'; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // Load feeds when page loads |
| 147 | loadFeeds(); |
| 148 | |
| 149 | document.getElementById('feedForm').addEventListener('submit', async (e) => { |
| 150 | e.preventDefault(); |
| 151 | |
| 152 | const url = document.getElementById('url').value; |
| 153 | const messageDiv = document.getElementById('message'); |
| 154 | const submitBtn = e.target.querySelector('button[type="submit"]'); |
| 155 | |
| 156 | // Show loading state |
| 157 | submitBtn.disabled = true; |
| 158 | submitBtn.textContent = 'Submitting...'; |
| 159 | messageDiv.classList.add('hidden'); |
| 160 | |
| 161 | try { |
| 162 | const response = await fetch('https://api.blogfeeds.net/add-feed', { |
| 163 | method: 'POST', |
| 164 | headers: { |
| 165 | 'Content-Type': 'application/json', |
| 166 | }, |
| 167 | body: JSON.stringify({ url }) |
| 168 | }); |
| 169 | |
| 170 | const data = await response.json(); |
| 171 | |
| 172 | if (response.ok) { |
| 173 | messageDiv.className = 'mt-4 p-4 rounded-lg bg-green-100 text-green-800'; |
| 174 | messageDiv.textContent = 'Feed submitted for review!'; |
| 175 | document.getElementById('url').value = ''; |
| 176 | } else { |
| 177 | messageDiv.className = 'mt-4 p-4 rounded-lg bg-red-100 text-red-800'; |
| 178 | messageDiv.textContent = data.error || 'Failed to submit feed. Please try again.'; |
| 179 | } |
| 180 | } catch (error) { |
| 181 | messageDiv.className = 'mt-4 p-4 rounded-lg bg-red-100 text-red-800'; |
| 182 | messageDiv.textContent = 'Network error. Please try again.'; |
| 183 | } finally { |
| 184 | submitBtn.disabled = false; |
| 185 | submitBtn.textContent = 'Submit Feed'; |
| 186 | messageDiv.classList.remove('hidden'); |
| 187 | } |
| 188 | }); |
| 189 | </script> |
| 190 | </body> |
| 191 | </html> |