feat: added feeds page eecede86
Steve · 2025-10-05 21:53 4 file(s) · +182 −2
package.json +1 −1
5 5
	"type": "module",
6 6
	"scripts": {
7 7
		"build": "bun scripts/build-site",
8 -
		"dev": "bun site/index.html --console"
8 +
		"dev": "bun site/*.html --console"
9 9
	},
10 10
	"devDependencies": {
11 11
		"@types/bun": "latest",
scripts/build-site.ts +12 −0
6 6
await $`cp -r site site-dist`;
7 7
// Compile tailwindcss
8 8
await $`bunx @tailwindcss/cli -i ./site/input.css -o ./site-dist/output.css `;
9 +
9 10
// Read index file
10 11
const htmlContent = await Bun.file("site-dist/index.html").text();
11 12
// Update script tags and css link
16 17
	);
17 18
// Write file
18 19
await Bun.write("site-dist/index.html", updatedHtml);
20 +
21 +
// Read feeds file
22 +
const feedsContent = await Bun.file("site-dist/feeds.html").text();
23 +
// Update script tags and css link
24 +
const updatedFeedsHtml = feedsContent
25 +
	.replace(
26 +
		`<link rel="stylesheet" href="tailwindcss" />`,
27 +
		`<link rel="stylesheet" href="/output.css" />`,
28 +
	);
29 +
// Write file
30 +
await Bun.write("site-dist/feeds.html", updatedFeedsHtml);
site/feeds.html (added) +166 −0
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 +
      * {
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="/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 thsoe 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!</p>
51 +
52 +
      <form id="feedForm" class="space-y-4">
53 +
        <div>
54 +
          <label for="url" class="block text-sm font-medium text-gray-700 mb-2">Blog URL</label>
55 +
          <input
56 +
            type="url"
57 +
            id="url"
58 +
            name="url"
59 +
            required
60 +
            placeholder="https://example.com"
61 +
            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"
62 +
          />
63 +
        </div>
64 +
65 +
        <button
66 +
          type="submit"
67 +
          class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-lg transition-colors"
68 +
        >
69 +
          Submit Feed
70 +
        </button>
71 +
      </form>
72 +
73 +
      <div id="message" class="hidden my-4 p-4 rounded-lg"></div>
74 +
    </div>
75 +
  </div>
76 +
77 +
  <section class="flex flex-col items-center px-4 mb-12">
78 +
    <div class="max-w-xl space-y-4 w-full">
79 +
      <div id="feedsList" class="space-y-3">
80 +
        <div class="text-center py-8 text-gray-500">Loading feeds...</div>
81 +
      </div>
82 +
    </div>
83 +
  </section>
84 +
85 +
  <script>
86 +
    // Load feeds on page load
87 +
    async function loadFeeds() {
88 +
      const feedsList = document.getElementById('feedsList');
89 +
90 +
      try {
91 +
        const response = await fetch('https://api.blogfeeds.net');
92 +
        const data = await response.json();
93 +
94 +
        if (data.subscriptions && data.subscriptions.length > 0) {
95 +
          feedsList.innerHTML = data.subscriptions.map(feed => {
96 +
            const blogUrl = new URL(feed.htmlUrl);
97 +
            const rootUrl = `${blogUrl.protocol}//${blogUrl.host}`;
98 +
99 +
            return `
100 +
              <div class="p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
101 +
                <h3 class="font-semibold text-lg mb-1">
102 +
                  <a href="${rootUrl}" target="_blank" class="hover:text-blue-600 transition-colors">${feed.title}</a>
103 +
                </h3>
104 +
                <a href="${feed.url}" target="_blank" class="text-sm text-gray-600 hover:text-blue-600 transition-colors break-all inline-flex items-center gap-2">
105 +
                  <svg class="w-3 h-3 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
106 +
                    <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"/>
107 +
                  </svg>
108 +
                  ${feed.url}
109 +
                </a>
110 +
              </div>
111 +
            `;
112 +
          }).join('');
113 +
        } else {
114 +
          feedsList.innerHTML = '<div class="text-center py-8 text-gray-500">No feeds available yet.</div>';
115 +
        }
116 +
      } catch (error) {
117 +
        feedsList.innerHTML = '<div class="text-center py-8 text-red-600">Failed to load feeds. Please try again later.</div>';
118 +
      }
119 +
    }
120 +
121 +
    // Load feeds when page loads
122 +
    loadFeeds();
123 +
124 +
    document.getElementById('feedForm').addEventListener('submit', async (e) => {
125 +
      e.preventDefault();
126 +
127 +
      const url = document.getElementById('url').value;
128 +
      const messageDiv = document.getElementById('message');
129 +
      const submitBtn = e.target.querySelector('button[type="submit"]');
130 +
131 +
      // Show loading state
132 +
      submitBtn.disabled = true;
133 +
      submitBtn.textContent = 'Submitting...';
134 +
      messageDiv.classList.add('hidden');
135 +
136 +
      try {
137 +
        const response = await fetch('https://api.blogfeeds.net/add-feed', {
138 +
          method: 'POST',
139 +
          headers: {
140 +
            'Content-Type': 'application/json',
141 +
          },
142 +
          body: JSON.stringify({ url })
143 +
        });
144 +
145 +
        const data = await response.json();
146 +
147 +
        if (response.ok) {
148 +
          messageDiv.className = 'mt-4 p-4 rounded-lg bg-green-100 text-green-800';
149 +
          messageDiv.textContent = 'Feed submitted successfully!';
150 +
          document.getElementById('url').value = '';
151 +
        } else {
152 +
          messageDiv.className = 'mt-4 p-4 rounded-lg bg-red-100 text-red-800';
153 +
          messageDiv.textContent = data.error || 'Failed to submit feed. Please try again.';
154 +
        }
155 +
      } catch (error) {
156 +
        messageDiv.className = 'mt-4 p-4 rounded-lg bg-red-100 text-red-800';
157 +
        messageDiv.textContent = 'Network error. Please try again.';
158 +
      } finally {
159 +
        submitBtn.disabled = false;
160 +
        submitBtn.textContent = 'Submit Feed';
161 +
        messageDiv.classList.remove('hidden');
162 +
      }
163 +
    });
164 +
  </script>
165 +
</body>
166 +
</html>
site/index.html +3 −1
127 127
        <img src="feeds.svg" alt="Feeds" id="feeds" class="w-5/6 mx-auto py-4" />
128 128
129 129
        <p>This takes us to our final point: Feeds. You can probably get away with just the first two items and then sharing it with people you already know, but what about meeting or talking to people you don't know? That's where Feeds come in. The idea is to create another page on your blog that has all the RSS feeds you're subscribed to. By keeping this public and always up to date, someone can visit your page, find someone new and follow them. Perhaps that person also has a feeds page, and the cycle continues until there is a natural and organic network of people all sharing with each other. So if you have a blog, consider making a feeds page and sharing it! If your RSS reader supports OPML file exports and imports, perhaps you can share that file as well to make it easier to share your feeds.</p>
130 -
        <p>Here's an <a class="underline" href="https://bearblog.stevedylan.dev/feeds" target="_blank">example Feeds Page</a> which should help get the idea across!</p>
130 +
        <a class="bg-orange-100 hover:bg-orange-200 text-orange-800 px-3 py-1 rounded-full text-sm font-medium transition-colors" href="https://bearblog.stevedylan.dev/feeds" target="_blank">example feed</a>
131 +
132 +
        <a href="/feeds" class="bg-orange-100 hover:bg-orange-200 text-orange-800 px-3 py-1 rounded-full text-sm font-medium transition-colors">explore other feeds</a>
131 133
      </div>
132 134
    </section>
133 135