the home site for me: also iteration 3 or 4 of my site

feat: add now log

dunkirk.sh b40b3200 01f1da39

verified
Changed files
+210
content
templates
shortcodes
+1
config.toml
···
header_nav = [
{ url = "/", name = "/root" },
{ url = "/verify", name = "/verify" },
+
{ url = "/now", name = "/now" },
{ url = "/blog", name = "/blog" },
]
+9
content/now.md
···
+
+++
+
title = "Now"
+
description = "My status updates"
+
template = "page.html"
+
+++
+
+
## Status Updates
+
+
{{ now_status() }}
+200
templates/shortcodes/now_status.html
···
+
{% set api_url = "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update&limit=50" %}
+
{% set response = load_data(url=api_url, format="json") %}
+
+
<style>
+
#status-updates-container {
+
display: flex;
+
flex-direction: column;
+
gap: 1.5rem;
+
width: 100%;
+
margin-bottom: 2rem;
+
}
+
+
.bsky-post {
+
border-left: 0.375rem solid var(--accent);
+
padding: 0.7em 1em;
+
font-size: 1rem;
+
background-color: var(--bg-light);
+
border-radius: 0.375rem;
+
}
+
+
.bsky-post-content {
+
margin-bottom: 0.75rem;
+
line-height: 1.4;
+
}
+
+
.bsky-post-footer {
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
color: var(--text-light);
+
}
+
+
.bsky-post-footer cite {
+
display: inline-flex;
+
align-items: center;
+
gap: 0.4rem;
+
}
+
+
.bsky-post-time {
+
font-size: 0.8rem;
+
color: var(--text-light);
+
}
+
</style>
+
+
<div id="status-updates-container">
+
{% if response.records %}
+
{% for record in response.records | sort(attribute="value.createdAt") | reverse %}
+
{% set created_at = record.value.createdAt %}
+
{% set status_text = record.value.text %}
+
<div class="bsky-post" data-cid="{{ record.cid }}" data-created="{{ created_at }}">
+
<div class="bsky-post-content">{{ status_text }}</div>
+
<div class="bsky-post-footer">
+
<cite>
+
<img
+
src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:3h24oe2owgmqpulq6dwwnsph/bafkreiaosnd5uyvwfii4ecb7zks67vwdiovnulsjnr6kb3azbfigjcaw5u@jpeg"
+
alt="Kieran's avatar"
+
class="avatar"
+
/>
+
<a href="https://bsky.app/@doing.dunkirk.sh" target="_blank" rel="noopener">@doing.dunkirk.sh</a>
+
</cite>
+
<span class="bsky-post-time">
+
{{ record.value.createdAt | date(format="%b %d, %Y") }}
+
</span>
+
</div>
+
</div>
+
{% endfor %}
+
{% else %}
+
<div class="bsky-post">
+
<div class="bsky-post-content">No status updates found.</div>
+
</div>
+
{% endif %}
+
</div>
+
+
<script>
+
document.addEventListener("DOMContentLoaded", () => {
+
const container = document.getElementById("status-updates-container");
+
const API_URL = "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update&limit=50";
+
const existingPosts = new Map();
+
+
// Collect existing posts by CID
+
document.querySelectorAll('.bsky-post[data-cid]').forEach(post => {
+
existingPosts.set(post.dataset.cid, {
+
element: post,
+
created: new Date(post.dataset.created)
+
});
+
});
+
+
// Format time relative to now
+
function formatTimeAgo(date) {
+
const now = new Date();
+
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", {
+
month: "short",
+
day: "numeric"
+
}).format(date);
+
}
+
+
// Update timestamps on existing posts
+
function updateTimestamps() {
+
existingPosts.forEach((post) => {
+
const timeElement = post.element.querySelector('.bsky-post-time');
+
if (timeElement) {
+
timeElement.textContent = formatTimeAgo(post.created);
+
}
+
});
+
}
+
+
// Create a new post element
+
function createPostElement(record) {
+
const createdDate = new Date(record.value.createdAt);
+
const postElement = document.createElement('div');
+
postElement.className = 'bsky-post';
+
postElement.dataset.cid = record.cid;
+
postElement.dataset.created = record.value.createdAt;
+
+
postElement.innerHTML = `
+
<div class="bsky-post-content">${record.value.text}</div>
+
<div class="bsky-post-footer">
+
<cite>
+
<img src="https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:3h24oe2owgmqpulq6dwwnsph/bafkreiaosnd5uyvwfii4ecb7zks67vwdiovnulsjnr6kb3azbfigjcaw5u@jpeg" alt="Kieran's avatar" class="avatar" />
+
<a href="https://bsky.app/@doing.dunkirk.sh" target="_blank" rel="noopener">@doing.dunkirk.sh</a>
+
</cite>
+
<span class="bsky-post-time">${formatTimeAgo(createdDate)}</span>
+
</div>
+
`;
+
+
return postElement;
+
}
+
+
// Fetch and update posts
+
function fetchAndUpdatePosts() {
+
fetch(API_URL)
+
.then(response => response.json())
+
.then(data => {
+
if (!data.records || data.records.length === 0) {
+
if (existingPosts.size === 0) {
+
container.innerHTML = '<div class="bsky-post"><div class="bsky-post-content">No status updates found.</div></div>';
+
}
+
return;
+
}
+
+
// Sort newest first
+
const sortedRecords = data.records.sort((a, b) => {
+
return new Date(b.value.createdAt) - new Date(a.value.createdAt);
+
});
+
+
// Track if we need to reorder
+
let needsReordering = false;
+
+
// Add new posts
+
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)
+
container.insertBefore(newPostElement, container.firstChild);
+
existingPosts.set(record.cid, {
+
element: newPostElement,
+
created: new Date(record.value.createdAt)
+
});
+
needsReordering = true;
+
}
+
}
+
+
// If we added new posts, reorder everything
+
if (needsReordering) {
+
const sortedElements = [...existingPosts.entries()]
+
.sort((a, b) => b[1].created - a[1].created)
+
.map(entry => entry[1].element);
+
+
// Reattach in correct order
+
sortedElements.forEach(element => {
+
container.appendChild(element);
+
});
+
}
+
+
// Update all timestamps
+
updateTimestamps();
+
})
+
.catch(error => {
+
console.error("Error fetching status updates:", error);
+
});
+
}
+
+
// Initial update
+
fetchAndUpdatePosts();
+
+
// Update timestamps every minute
+
setInterval(updateTimestamps, 60000);
+
+
// Fetch new posts every 5 minutes
+
setInterval(fetchAndUpdatePosts, 300000);
+
});
+
</script>