chore: refactored to use a tags 18b7b581
Steve · 2026-01-03 16:27 2 file(s) · +69 −73
src/pages/now.astro +10 −5
57 57
          const post = record.value;
58 58
          const createdAt = new Date(post.createdAt).toLocaleDateString();
59 59
60 +
          // Extract rkey from the record URI (format: at://did/collection/rkey)
61 +
          const rkey = record.uri.split('/').pop();
62 +
60 63
          // Handle images
61 64
          let imagesHTML = '';
62 65
          if (post.embed && post.embed.$type === 'app.bsky.embed.images' && post.embed.images) {
86 89
          }
87 90
88 91
          return `
89 -
            <article class="border-b pb-6 mb-6 last:border-b-0">
90 -
              <p class="mb-2">${post.text}</p>
91 -
              ${imagesHTML}
92 -
              <time class="text-sm text-gray-500 mt-2 block">${createdAt}</time>
93 -
            </article>
92 +
            <a href="/pds?rkey=${rkey}" class="block border-b pb-6 mb-6 last:border-b-0">
93 +
              <article>
94 +
                <p class="mb-2">${post.text}</p>
95 +
                ${imagesHTML}
96 +
                <time class="text-sm text-gray-500 mt-2 block">${createdAt}</time>
97 +
              </article>
98 +
            </a>
94 99
          `;
95 100
        }).join('');
96 101
src/pages/pds/index.astro +59 −68
12 12
  </div>
13 13
14 14
  <script>
15 -
    import { AtpAgent } from '@atproto/api';
15 +
    const DID = 'did:plc:ia2zdnhjaokf5lazhxrmj6eu';
16 +
    const PDS_URL = 'https://polybius.social';
16 17
17 18
    const urlParams = new URLSearchParams(window.location.search);
18 19
    const rkey = urlParams.get('rkey');
19 -
    const YOUR_DID = 'did:plc:ia2zdnhjaokf5lazhxrmj6eu';
20 -
    const atUri = `at://${YOUR_DID}/app.bsky.feed.post/${rkey}`;
21 20
22 -
    const agent = new AtpAgent({ service: 'https://public.api.bsky.app' });
21 +
    async function fetchPost() {
22 +
      if (!rkey) {
23 +
        document.getElementById('post-container').innerHTML = '<p>No post specified.</p>';
24 +
        return;
25 +
      }
23 26
24 -
    function renderPost(post) {
25 -
      const createdAt = new Date(post.record.createdAt).toLocaleDateString();
27 +
      try {
28 +
        const response = await fetch(
29 +
          `${PDS_URL}/xrpc/com.atproto.repo.getRecord?` +
30 +
          new URLSearchParams({
31 +
            repo: DID,
32 +
            collection: 'app.bsky.feed.post',
33 +
            rkey: rkey
34 +
          })
35 +
        );
26 36
27 -
      // Handle images and other embeds
28 -
      let embedHTML = '';
29 -
      if (post.embed) {
37 +
        if (!response.ok) {
38 +
          throw new Error(`HTTP error! status: ${response.status}`);
39 +
        }
40 +
41 +
        const data = await response.json();
42 +
        const post = data.value;
43 +
        const createdAt = new Date(post.createdAt).toLocaleDateString();
44 +
30 45
        // Handle images
31 -
        if (post.embed.images) {
32 -
          const imageElements = post.embed.images.map(image => `
33 -
            <img
34 -
              src="${image.fullsize}"
35 -
              alt="${image.alt || 'Image from post'}"
36 -
              class="rounded-lg max-w-full h-auto cursor-pointer"
37 -
              loading="lazy"
38 -
              onclick="window.open('${image.fullsize}', '_blank')"
39 -
            />
40 -
          `).join('');
46 +
        let imagesHTML = '';
47 +
        if (post.embed && post.embed.$type === 'app.bsky.embed.images' && post.embed.images) {
48 +
          const imageElements = post.embed.images.map(image => {
49 +
            // Construct blob URL - images are stored as blobs on the PDS
50 +
            const blobUrl = `${PDS_URL}/xrpc/com.atproto.sync.getBlob?` +
51 +
              new URLSearchParams({
52 +
                did: DID,
53 +
                cid: image.image.ref.$link
54 +
              });
41 55
42 -
          embedHTML = `
43 -
            <div class="mt-4 grid gap-2 ${post.embed.images.length === 1 ? 'grid-cols-1' : 'grid-cols-2'}">
56 +
            return `
57 +
              <img
58 +
                src="${blobUrl}"
59 +
                alt="${image.alt || 'Image from post'}"
60 +
                class="max-w-full h-auto"
61 +
                loading="lazy"
62 +
              />
63 +
            `;
64 +
          }).join('');
65 +
66 +
          imagesHTML = `
67 +
            <div class="mt-3 grid gap-2 ${post.embed.images.length === 1 ? 'grid-cols-1' : 'grid-cols-2'}">
44 68
              ${imageElements}
45 69
            </div>
46 70
          `;
47 71
        }
48 -
        // Handle external links
49 -
        else if (post.embed.external) {
50 -
          const external = post.embed.external;
51 -
          embedHTML = `
52 -
            <a href="${external.uri}" target="_blank" rel="noopener noreferrer"
53 -
               class="block mt-4 p-4 border rounded-lg hover:bg-gray-50 transition-colors">
54 -
              ${external.thumb ? `<img src="${external.thumb}" alt="Link preview" class="w-full h-32 object-cover rounded mb-3" />` : ''}
55 -
              <div class="font-medium text-lg">${external.title || 'External Link'}</div>
56 -
              ${external.description ? `<div class="text-sm text-gray-600 mt-1">${external.description}</div>` : ''}
57 -
              <div class="text-xs text-gray-400 mt-2">${external.uri}</div>
58 -
            </a>
59 -
          `;
60 -
        }
61 -
        // Handle quoted posts
62 -
        else if (post.embed.record) {
63 -
          const quotedPost = post.embed.record;
64 -
          embedHTML = `
65 -
            <div class="mt-4 p-4 border-l-4 border-blue-400 bg-gray-50 rounded">
66 -
              <div class="text-sm text-gray-600 mb-2">Quoting:</div>
67 -
              <div>${quotedPost.value?.text || 'Quoted post'}</div>
68 -
              <div class="text-xs text-gray-400 mt-2">
69 -
                ${quotedPost.value?.createdAt ? new Date(quotedPost.value.createdAt).toLocaleDateString() : ''}
70 -
              </div>
71 -
            </div>
72 -
          `;
73 -
        }
74 -
      }
75 72
76 -
      return `
77 -
        <article class="max-w-2xl mx-auto">
78 -
          <h1 class="mb-4">${post.record.text}</h1>
79 -
          ${embedHTML}
80 -
          <div class="mt-6 pt-4 border-t">
81 -
            <time class="text-sm text-gray-500">${createdAt}</time>
82 -
          </div>
83 -
        </article>
84 -
      `;
85 -
    }
73 +
        document.getElementById('post-container').innerHTML = `
74 +
          <article class="max-w-2xl mx-auto">
75 +
            <p class="mb-2">${post.text}</p>
76 +
            ${imagesHTML}
77 +
            <time class="text-sm text-gray-500 mt-2 block">${createdAt}</time>
78 +
          </article>
79 +
        `;
86 80
87 -
    agent.app.bsky.feed.getPostThread({ uri: atUri })
88 -
      .then(({ data }) => {
89 -
        const post = data.thread.post;
90 -
91 -
        document.getElementById('post-container').innerHTML = renderPost(post);
92 -
        document.title = post.record.text;
93 -
      })
94 -
      .catch(err => {
81 +
        document.title = post.text;
82 +
      } catch (err) {
95 83
        console.error('Error fetching post:', err);
96 84
        document.getElementById('post-container').innerHTML =
97 -
          `<p class="text-red-600">Post not found: ${err.message}</p>`;
98 -
      });
85 +
          '<p>Error loading post. Make sure your PDS is accessible.</p>';
86 +
      }
87 +
    }
88 +
89 +
    fetchPost();
99 90
  </script>
100 91
101 92
</PageLayout>