chore: updated feeds and blogroll 4d8b3384
Steve · 2026-04-28 19:51 4 file(s) · +81 −142
packages/client/src/components/layout/Header.astro +0 −114
4 4
const url = new URL(Astro.request.url);
5 5
---
6 6
7 -
<script>
8 -
  function favicons(this: any) {
9 -
    this.hidden = "hidden";
10 -
    this.visibilityChange = "visibilitychange";
11 -
    this.favicon = document.querySelector("[rel='shortcut icon']")
12 -
      ? (document.querySelector("[rel='shortcut icon']") as HTMLLinkElement).href
13 -
      : "";
14 -
    this.title = document.title;
15 -
    this.wasSpoofed = false;
16 -
    this.spoofed = [];
17 -
18 -
    this.services = {
19 -
      sy: () => ({ title: "Official Church of Scientology: Difficulties on the Job - Online Course", favicon: "/favicons/sy.png" }),
20 -
      cdc: () => ({ title: "Ask HN: How could I safely contact drug cartels?", favicon: "/favicons/hn.png" }),
21 -
      pul: () => ({ title: "Pick up lines suggestions - ChatGPT", favicon: "/favicons/cgpt.png" }),
22 -
      fes: () => ({ title: "The Flat Earth Society", favicon: "/favicons/fes.png" }),
23 -
      tsm: () => ({ title: "Amazon.com: taylor swift merch", favicon: "/favicons/az.ico" }),
24 -
      rwsb: () => ({ title: "r/wallstreetbets on Reddit", favicon: "/favicons/rd.png" }),
25 -
      iw: () => ({ title: "Infowars: There's a War on For Your Mind!", favicon: "/favicons/iw.png" }),
26 -
      tac: () => ({ title: "The Anarchist Cookbook by William Powell | Goodreads", favicon: "/favicons/gr.png" }),
27 -
      nggyu: () => ({ title: "Rick Astley - Never Gonna Give You Up - YouTube", favicon: "/favicons/yt.ico" }),
28 -
      ftx: () => ({ title: "FTX Cryptocurrency Exchange", favicon: "/favicons/ftx.png" }),
29 -
    };
30 -
31 -
    this.enabledServices = Object.keys(this.services);
32 -
33 -
    this.init = function () {
34 -
      if (typeof (document as any).mozHidden !== "undefined") {
35 -
        this.hidden = "mozHidden";
36 -
        this.visibilityChange = "mozvisibilitychange";
37 -
      } else if (typeof (document as any).msHidden !== "undefined") {
38 -
        this.hidden = "msHidden";
39 -
        this.visibilityChange = "msvisibilitychange";
40 -
      } else if (typeof (document as any).webkitHidden !== "undefined") {
41 -
        this.hidden = "webkitHidden";
42 -
        this.visibilityChange = "webkitvisibilitychange";
43 -
      }
44 -
      document.addEventListener(this.visibilityChange, this.handler.bind(this), false);
45 -
    };
46 -
47 -
    this.default = function () {
48 -
      this.update({ title: this.title, favicon: this.favicon });
49 -
    };
50 -
51 -
    this.update = function (data: { title: string; favicon: string }) {
52 -
      const cacheBuster = "?v=" + Math.round(Math.random() * 10000000);
53 -
      const link = document.createElement("link");
54 -
      link.type = "image/x-icon";
55 -
      link.rel = "shortcut icon";
56 -
      link.href = data.favicon + cacheBuster;
57 -
      const head = document.getElementsByTagName("head")[0];
58 -
      const existing = head.querySelector("[rel='shortcut icon']");
59 -
      if (existing) existing.remove();
60 -
      head.appendChild(link);
61 -
      document.title = data.title;
62 -
      if (this.wasSpoofed === true) {
63 -
        const el = document.getElementById("disablejs");
64 -
        if (el) {
65 -
          el.style.display = "block";
66 -
          el.innerHTML = `
67 -
            <p><strong>(CLICK/TAP THIS OVERLAY ANYWHERE TO CLOSE IT)</strong></p>
68 -
            <br/>
69 -
            <p>Ah, yes. That moment. The one that sends a chill down your spine and makes you do a quick, frantic scan of your surroundings, hoping nobody noticed that brief, undeniable flash of panic on your face. You know <strong>exactly</strong> what I'm talking about: That split second when you spot <em>that website</em> in your browser's tab bar.</p>
70 -
            <p>Heart pounding, you dart a glance at your coworkers, your friends, your partner, or anyone in the vicinity, searching for signs of judgment or, worse, curiosity. No one's looking, but somehow, you feel like everyone is. It's like the universe knows, and it's giggling behind its hand. You quickly click over to the tab, praying, hoping it's not what you think it is.</p>
71 -
            <p>And then, oh sweet relief, it's not <strong>that</strong>. But now, a whole new, equally horrible truth sinks in. You've just been pranked by the cruel, merciless soul who crafted this infernal website. You, my friend, have just experienced the finest torture modern web technology has to offer: Unwarranted suspense, followed by the revelation that <em>nothing is as it seems</em>.</p>
72 -
            <p>JavaScript, you son of a smoking gun. The great trickster of the web, slinking in the background, making you believe that your browsing experience is smooth and simple, only to slap you with a pop-up, a subtle redirect, or worse, a blinking ad that's seemingly impossible to close.</p>
73 -
            <p>And here you are, caught in the endless cycle of knowing you should turn JavaScript off but just <strong>not</strong> caring enough to actually do it. It's like knowing you should stop eating those extra chips but doing it anyway. But this? This is the universe giving you a little nudge, perhaps a <em>not-so-subtle</em> one, reminding you of your folly.</p>
74 -
            <p>So, here it is, loud and clear: <strong>Turn JavaScript off, now, and only allow it on websites you trust!</strong> Save your sanity, preserve your dignity, and maybe give your browser a fighting chance at actually doing what <strong>you</strong> want it to do. Because if you don't, the next time you see that icon, your heart might not only drop, it might skip a beat or two.</p>
75 -
            <br/>
76 -
            <p><a href="https://disable-javascript.org" target="_self" style="text-decoration:underline;padding-top:2rem;" title="disable-javascript.org">More information here.</a></p>
77 -
            <br/>
78 -
            <p><strong>(CLICK/TAP THIS OVERLAY ANYWHERE TO CLOSE IT)</strong></p>
79 -
          `;
80 -
        }
81 -
      }
82 -
    };
83 -
84 -
    this.spoof = function () {
85 -
      let i = 0;
86 -
      if (this.spoofed.length === this.enabledServices.length) {
87 -
        this.spoofed.length = 0;
88 -
      }
89 -
      for (let es = 0; es < this.enabledServices.length; es++) {
90 -
        i = Math.round(Math.random() * (this.enabledServices.length - 1));
91 -
        if (this.spoofed.includes(i) === false) break;
92 -
      }
93 -
      this.spoofed.push(i);
94 -
      const service = this.enabledServices[i];
95 -
      if (service && this.services[service]) {
96 -
        this.update(this.services[service]());
97 -
      }
98 -
      this.wasSpoofed = true;
99 -
    };
100 -
101 -
    this.handler = function () {
102 -
      if ((document as any)[this.hidden]) {
103 -
        this.spoof();
104 -
      } else {
105 -
        this.default();
106 -
      }
107 -
    };
108 -
109 -
    this.init();
110 -
  }
111 -
112 -
  new (favicons as any)();
113 -
</script>
114 -
115 -
<div
116 -
  id="disablejs"
117 -
  onclick="this.style.display='none'"
118 -
  style="display:none;position:fixed;inset:0;z-index:9999;overflow-y:auto;background:rgba(0,0,0,0.92);color:#fff;font-family:monospace;padding:2rem;cursor:pointer"
119 -
></div>
120 -
121 7
<header
122 8
  id="main-header"
123 9
  class="relative mb-14"
packages/client/src/data/constants.ts +28 −0
32 32
    path: "/feeds",
33 33
  },
34 34
  {
35 +
    title: "Blogroll",
36 +
    path: "/blogroll"
37 +
  },
38 +
  {
35 39
    title: "Library",
36 40
    path: "/library",
37 41
  },
62 66
  {
63 67
    title: "Links",
64 68
    path: "/links"
69 +
  }
70 +
];
71 +
72 +
export type RssFeed = {
73 +
  name: string;
74 +
  href: string;
75 +
};
76 +
77 +
export const RSS_FEEDS: RssFeed[] = [
78 +
  {
79 +
    name: "Blog Posts",
80 +
    href: "/rss.xml",
81 +
  },
82 +
  {
83 +
    name: "Now Updates",
84 +
    href: "/now/rss.xml",
85 +
  },
86 +
  {
87 +
    name: "Photos",
88 +
    href: "https://steve.photo/rss.xml"
89 +
  },
90 +
  {
91 +
    name: "Wine Log",
92 +
    href: "https://cellar.stevedylan.dev/feed.xml"
65 93
  }
66 94
];
67 95
packages/client/src/pages/blogroll.astro (added) +45 −0
1 +
---
2 +
import PageLayout from "@/layouts/Base.astro";
3 +
4 +
const meta = {
5 +
	title: "Blogroll",
6 +
	description: "A collection of RSS feeds I'm subscribed to",
7 +
};
8 +
9 +
// Fetch the feeds data
10 +
const response = await fetch("https://feeds.stevedylan.dev/feeds?format=json");
11 +
const data = await response.json();
12 +
13 +
// Sort subscriptions by title
14 +
const sortedFeeds = data.subscriptions.sort((a, b) =>
15 +
	a.title.localeCompare(b.title),
16 +
);
17 +
---
18 +
<PageLayout meta={meta}>
19 +
  <div class="space-y-6">
20 +
    <h1 class="title">Blogroll</h1>
21 +
    <p>Help build a blog network with me! Check out some of the people I follow below, and if you end up following them too, create a `Blogroll` page of your own with your list of people!</p>
22 +
    <h2 class="text-xl font-semibold text-accent-2">Links</h2>
23 +
    <ul class="list-disc pl-4 space-y-4 sm:grid sm:grid-cols-2 sm:gap-y-4 sm:space-y-0">
24 +
      {sortedFeeds.map((feed) => (
25 +
        <li>
26 +
          <a
27 +
            href={feed.htmlUrl}
28 +
            target="_blank"
29 +
            rel="noopener noreferrer"
30 +
            class="style-link"
31 +
          >
32 +
            {feed.title}
33 +
          </a>
34 +
        </li>
35 +
      ))}
36 +
    </ul>
37 +
    <h2 class="text-xl font-semibold text-accent-2 pt-8">API</h2>
38 +
    <p>I'm currently using my own RSS aggregator and reader called <a href="https://andromeda.build/apps/feeds/" target="_blank" rel="noopener noreferrer" class="style-link">Feeds</a> to manage my feeds, which inlcudes an API to programmatically share them as a list.</p>
39 +
    <pre class="whitespace-pre-wrap break-all"><code># JSON
40 +
curl https://feeds.stevedylan.dev/feeds?format=json
41 +
42 +
# OPML File
43 +
curl https://feeds.stevedylan.dev/feeds?format=opml -o feed.opml</code></pre>
44 +
  </div>
45 +
</PageLayout>
packages/client/src/pages/feeds.astro +8 −28
1 1
---
2 2
import PageLayout from "@/layouts/Base.astro";
3 +
import { RSS_FEEDS } from "@/data/constants";
3 4
4 5
const meta = {
5 6
	title: "Feeds",
6 -
	description: "A collection of RSS feeds I'm subscribed to",
7 +
	description: "RSS feeds available on this site",
7 8
};
8 -
9 -
// Fetch the feeds data
10 -
const response = await fetch("https://feeds.stevedylan.dev/feeds?format=json");
11 -
const data = await response.json();
12 -
13 -
// Sort subscriptions by title
14 -
const sortedFeeds = data.subscriptions.sort((a, b) =>
15 -
	a.title.localeCompare(b.title),
16 -
);
17 9
---
18 10
<PageLayout meta={meta}>
19 11
  <div class="space-y-6">
20 12
    <h1 class="title">Feeds</h1>
21 -
    <p>Help build a blog network with me! Check out some of the people I follow below, and if you end up following them too, create a `Feeds` page of your own with your list of people!</p>
22 -
    <h2 class="text-xl font-semibold text-accent-2">Links</h2>
23 -
    <ul class="list-disc pl-4 space-y-4 sm:grid sm:grid-cols-2">
24 -
      {sortedFeeds.map((feed) => (
13 +
    <p>RSS feeds available on this site and some of my other apps. Drop any of these into your reader of choice.</p>
14 +
    <ul class="space-y-4">
15 +
      {RSS_FEEDS.map((feed) => (
25 16
        <li>
26 -
          <a
27 -
            href={feed.htmlUrl}
28 -
            target="_blank"
29 -
            rel="noopener noreferrer"
30 -
            class="style-link"
31 -
          >
32 -
            {feed.title}
17 +
          <p>{feed.name}</p>
18 +
          <a href={feed.href} target="_blank" rel="noreferrer" class="style-link">
19 +
            {feed.href}
33 20
          </a>
34 21
        </li>
35 22
      ))}
36 23
    </ul>
37 -
    <h2 class="text-xl font-semibold text-accent-2 pt-8">API</h2>
38 -
    <p>I'm currently using my own RSS aggregator and reader called <a href="https://andromeda.build/apps/feeds/" target="_blank" rel="noopener noreferrer" class="style-link">Feeds</a> to manage my feeds, which inlcudes an API to programmatically share them as a list.</p>
39 -
    <pre class="whitespace-pre-wrap break-all"><code># JSON
40 -
curl https://feeds.stevedylan.dev/feeds?format=json
41 -
42 -
# OPML File
43 -
curl https://feeds.stevedylan.dev/feeds?format=opml -o feed.opml</code></pre>
44 24
  </div>
45 25
</PageLayout>