···
1
-
{% set api_url = "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update&limit=50" %}
2
-
{% set response = load_data(url=api_url, format="json") %}
2
+
"https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update"
3
+
%} {% set response = load_data(url=api_url, format="json") %}
#status-updates-container {
···
<div id="status-updates-container">
46
-
{% if response.records %}
47
-
{% for record in response.records | sort(attribute="value.createdAt") | reverse %}
48
-
{% set created_at = record.value.createdAt %}
49
-
{% set status_text = record.value.text %}
50
-
<div class="bsky-post" data-cid="{{ record.cid }}" data-created="{{ created_at }}">
51
-
<div class="bsky-post-content">{{ status_text }}</div>
52
-
<div class="bsky-post-footer">
55
-
src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:3h24oe2owgmqpulq6dwwnsph/bafkreiaosnd5uyvwfii4ecb7zks67vwdiovnulsjnr6kb3azbfigjcaw5u@jpeg"
56
-
alt="Kieran's avatar"
59
-
<a href="https://bsky.app/@doing.dunkirk.sh" target="_blank" rel="noopener">@doing.dunkirk.sh</a>
61
-
<span class="bsky-post-time">
62
-
{{ record.value.createdAt | date(format="%b %d, %Y") }}
47
+
{% if response.records %} {% for record in response.records |
48
+
sort(attribute="value.createdAt") | reverse %} {% set created_at =
49
+
record.value.createdAt %} {% set status_text = record.value.text %}
52
+
data-cid="{{ record.cid }}"
53
+
data-created="{{ created_at }}"
55
+
<div class="bsky-post-content">Kieran was {{ status_text }}</div>
56
+
<div class="bsky-post-footer">
59
+
src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:3h24oe2owgmqpulq6dwwnsph/bafkreiaosnd5uyvwfii4ecb7zks67vwdiovnulsjnr6kb3azbfigjcaw5u@jpeg"
60
+
alt="Kieran's avatar"
64
+
href="https://bsky.app/@doing.dunkirk.sh"
67
+
>@doing.dunkirk.sh</a
70
+
<span class="bsky-post-time">
71
+
{{ record.value.createdAt | date(format="%b %d, %Y") }}
68
-
<div class="bsky-post">
69
-
<div class="bsky-post-content">No status updates found.</div>
75
+
{% endfor %} {% else %}
76
+
<div class="bsky-post">
77
+
<div class="bsky-post-content">No status updates found.</div>
document.addEventListener("DOMContentLoaded", () => {
const container = document.getElementById("status-updates-container");
77
-
const API_URL = "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update&limit=50";
86
+
"https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update";
const existingPosts = new Map();
// Collect existing posts by CID
81
-
document.querySelectorAll('.bsky-post[data-cid]').forEach(post => {
90
+
document.querySelectorAll(".bsky-post[data-cid]").forEach((post) => {
existingPosts.set(post.dataset.cid, {
84
-
created: new Date(post.dataset.created)
93
+
created: new Date(post.dataset.created),
···
const diffInMs = now - date;
const diffInMins = Math.floor(diffInMs / (1000 * 60));
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60));
if (diffInMins < 1) return "just now";
if (diffInMins < 60) return `${Math.round(diffInMins)}m`;
if (diffInHours < 24) return `${Math.round(diffInHours)}h`;
return new Intl.DateTimeFormat("en", {
105
-
// Update timestamps on existing posts
114
+
// Update timestamps and verbs on existing posts
function updateTimestamps() {
existingPosts.forEach((post) => {
108
-
const timeElement = post.element.querySelector('.bsky-post-time');
117
+
const timeElement =
118
+
post.element.querySelector(".bsky-post-time");
119
+
const contentElement =
120
+
post.element.querySelector(".bsky-post-content");
timeElement.textContent = formatTimeAgo(post.created);
125
+
// Update the is/was verb based on post age
126
+
const now = new Date();
127
+
const diffInMs = now - post.created;
128
+
const diffInMins = diffInMs / (1000 * 60);
129
+
const verb = diffInMins < 30 ? "is" : "was";
131
+
// Get the status text (everything after "Kieran was/is ")
132
+
if (contentElement) {
133
+
const text = contentElement.textContent;
134
+
const statusText = text.replace(/^Kieran (is|was) /, "");
135
+
contentElement.textContent = `Kieran ${verb} ${statusText}`;
// Create a new post element
function createPostElement(record) {
const createdDate = new Date(record.value.createdAt);
118
-
const postElement = document.createElement('div');
119
-
postElement.className = 'bsky-post';
143
+
const postElement = document.createElement("div");
144
+
postElement.className = "bsky-post";
postElement.dataset.cid = record.cid;
postElement.dataset.created = record.value.createdAt;
148
+
// Determine if status is recent (within 30 minutes)
149
+
const now = new Date();
150
+
const diffInMs = now - createdDate;
151
+
const diffInMins = diffInMs / (1000 * 60);
152
+
const verb = diffInMins < 30 ? "is" : "was";
postElement.innerHTML = `
124
-
<div class="bsky-post-content">${record.value.text}</div>
155
+
<div class="bsky-post-content">Kieran ${verb} ${record.value.text}</div>
<div class="bsky-post-footer">
<img src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:3h24oe2owgmqpulq6dwwnsph/bafkreiaosnd5uyvwfii4ecb7zks67vwdiovnulsjnr6kb3azbfigjcaw5u@jpeg" alt="Kieran's avatar" class="avatar" />
···
<span class="bsky-post-time">${formatTimeAgo(createdDate)}</span>
// Fetch and update posts
function fetchAndUpdatePosts() {
140
-
.then(response => response.json())
171
+
.then((response) => response.json())
if (!data.records || data.records.length === 0) {
if (existingPosts.size === 0) {
144
-
container.innerHTML = '<div class="bsky-post"><div class="bsky-post-content">No status updates found.</div></div>';
175
+
container.innerHTML =
176
+
'<div class="bsky-post"><div class="bsky-post-content">No status updates found.</div></div>';
const sortedRecords = data.records.sort((a, b) => {
151
-
return new Date(b.value.createdAt) - new Date(a.value.createdAt);
184
+
new Date(b.value.createdAt) -
185
+
new Date(a.value.createdAt)
// Track if we need to reorder
let needsReordering = false;
for (const record of sortedRecords) {
if (!existingPosts.has(record.cid)) {
const newPostElement = createPostElement(record);
// Always insert at the beginning for now (we'll reorder if needed)
162
-
container.insertBefore(newPostElement, container.firstChild);
197
+
container.insertBefore(
199
+
container.firstChild,
existingPosts.set(record.cid, {
165
-
created: new Date(record.value.createdAt)
203
+
created: new Date(record.value.createdAt),
// If we added new posts, reorder everything
const sortedElements = [...existingPosts.entries()]
.sort((a, b) => b[1].created - a[1].created)
175
-
.map(entry => entry[1].element);
213
+
.map((entry) => entry[1].element);
// Reattach in correct order
178
-
sortedElements.forEach(element => {
216
+
sortedElements.forEach((element) => {
container.appendChild(element);
224
+
.catch((error) => {
console.error("Error fetching status updates:", error);
// Update timestamps every minute
setInterval(updateTimestamps, 60000);
// Fetch new posts every 5 minutes
setInterval(fetchAndUpdatePosts, 300000);