tangled
alpha
login
or
join now
dunkirk.sh
/
zera
5
fork
atom
the home site for me: also iteration 3 or 4 of my site
5
fork
atom
overview
issues
pulls
pipelines
feat: add time elements
dunkirk.sh
3 months ago
d37d46f8
7ceabef6
verified
This commit was signed with the committer's
known signature
.
dunkirk.sh
SSH Key Fingerprint:
SHA256:DqcG0RXYExE26KiWo3VxJnsxswN1QNfTBvB+bdSpk80=
+94
-61
3 changed files
expand all
collapse all
unified
split
static
js
relative-time.js
templates
head.html
shortcodes
is.md
+77
static/js/relative-time.js
···
1
1
+
const rtf = new Intl.RelativeTimeFormat(navigator.language, {
2
2
+
numeric: "auto",
3
3
+
style: "long"
4
4
+
});
5
5
+
6
6
+
function formatRelativeTime(date) {
7
7
+
const now = new Date();
8
8
+
const diffInMs = now - date;
9
9
+
const diffInMins = Math.floor(diffInMs / (1000 * 60));
10
10
+
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60));
11
11
+
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
12
12
+
13
13
+
if (diffInMins < 1) {
14
14
+
return rtf.format(0, "minute");
15
15
+
} else if (diffInMins < 5) {
16
16
+
return rtf.format(-1, "minute");
17
17
+
} else if (diffInMins < 60) {
18
18
+
return rtf.format(-diffInMins, "minute");
19
19
+
} else if (diffInHours < 3) {
20
20
+
return rtf.format(-diffInHours, "hour");
21
21
+
} else if (diffInHours < 24 && now.getDate() === date.getDate()) {
22
22
+
const hour = date.getHours();
23
23
+
if (hour < 12) return "this morning";
24
24
+
if (hour < 17) return "this afternoon";
25
25
+
return "this evening";
26
26
+
} else if (diffInDays < 2) {
27
27
+
return rtf.format(-1, "day");
28
28
+
} else if (diffInDays < 7) {
29
29
+
return rtf.format(-diffInDays, "day");
30
30
+
} else {
31
31
+
const dateFormatter = new Intl.DateTimeFormat(navigator.language, {
32
32
+
month: 'short',
33
33
+
day: 'numeric'
34
34
+
});
35
35
+
return `on ${dateFormatter.format(date)}`;
36
36
+
}
37
37
+
}
38
38
+
39
39
+
function updateTimeElements() {
40
40
+
document.querySelectorAll("time[datetime]").forEach(el => {
41
41
+
const datetime = el.getAttribute("datetime");
42
42
+
if (!datetime) return;
43
43
+
44
44
+
const date = new Date(datetime);
45
45
+
if (isNaN(date.getTime())) return;
46
46
+
47
47
+
const maxAge = el.dataset.maxAge;
48
48
+
if (maxAge) {
49
49
+
const diffInMs = Date.now() - date.getTime();
50
50
+
const maxAgeMs = parseInt(maxAge, 10) * 1000;
51
51
+
if (diffInMs > maxAgeMs) {
52
52
+
el.style.display = "none";
53
53
+
return;
54
54
+
}
55
55
+
}
56
56
+
57
57
+
el.textContent = formatRelativeTime(date);
58
58
+
el.style.display = "";
59
59
+
});
60
60
+
}
61
61
+
62
62
+
const observer = new MutationObserver((mutations) => {
63
63
+
for (const mutation of mutations) {
64
64
+
if (mutation.type === "attributes" && mutation.attributeName === "datetime") {
65
65
+
updateTimeElements();
66
66
+
return;
67
67
+
}
68
68
+
}
69
69
+
});
70
70
+
observer.observe(document.documentElement, {
71
71
+
subtree: true,
72
72
+
attributes: true,
73
73
+
attributeFilter: ["datetime"]
74
74
+
});
75
75
+
76
76
+
document.addEventListener("DOMContentLoaded", updateTimeElements);
77
77
+
setInterval(updateTimeElements, 60000);
+4
templates/head.html
···
84
84
base64=true) %}
85
85
<script src="{{ get_url(path='lightbox.js?' ~ lightboxJsHash, trailing_slash=false) | safe }}" defer></script>
86
86
87
87
+
{% set relativeTimeJsHash = get_hash(path="js/relative-time.js", sha_type=256,
88
88
+
base64=true) %}
89
89
+
<script src="{{ get_url(path='js/relative-time.js?' ~ relativeTimeJsHash, trailing_slash=false) | safe }}" defer></script>
90
90
+
87
91
<script type="speculationrules">
88
92
{
89
93
"prerender": [
+13
-61
templates/shortcodes/is.md
···
1
1
<div class="bubble" style="visibility: hidden; opacity: 0;">
2
2
-
<span><a href="https://bsky.app/@doing.dunkirk.sh" id="verb-link">Kieran is</a> <i id="status-text"></i> - <span id="time-ago"></span></span>
2
2
+
<span><a href="https://bsky.app/@doing.dunkirk.sh" id="verb-link">Kieran is</a> <i id="status-text"></i> - <time id="time-ago" datetime="" data-max-age="43200"></time></span>
3
3
</div>
4
4
5
5
<script>
6
6
document.addEventListener("DOMContentLoaded", () => {
7
7
-
// Initialize RelativeTimeFormat with user's locale
8
8
-
const rtf = new Intl.RelativeTimeFormat(navigator.language, {
9
9
-
numeric: "auto",
10
10
-
style: "long"
11
11
-
});
12
12
-
13
7
fetch(
14
8
"https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=dunkirk.sh&collection=a.status.update",
15
9
)
···
21
15
})
22
16
.then((statusData) => {
23
17
if (statusData.records && statusData.records.length > 0) {
24
24
-
// Calculate time difference
25
18
if (statusData.records[0].value.createdAt) {
26
26
-
const createdDate = new Date(statusData.records[0].value.createdAt);
19
19
+
const createdAt = statusData.records[0].value.createdAt;
20
20
+
const createdDate = new Date(createdAt);
27
21
const now = new Date();
28
22
const diffInMs = now - createdDate;
29
23
const diffInMins = Math.floor(diffInMs / (1000 * 60));
30
24
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60));
31
31
-
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
32
32
-
33
33
-
// Ignore if older than 12 hours
34
25
if (diffInHours > 12) {
35
26
return;
36
27
}
37
37
-
38
28
const latestStatus = `"${statusData.records[0].value.text}"`;
39
29
document.getElementById("status-text").textContent = latestStatus;
40
40
-
41
41
-
// Format time contextually using Intl.RelativeTimeFormat
42
42
-
let timeAgoText;
43
43
-
const createdHour = createdDate.getHours();
44
44
-
const isToday = diffInHours < 24 && now.getDate() === createdDate.getDate();
45
45
-
const isYesterday = diffInHours >= 24 && diffInHours < 48 &&
46
46
-
(now.getDate() - createdDate.getDate() === 1 ||
47
47
-
(now.getDate() === 1 && new Date(now.getFullYear(), now.getMonth(), 0).getDate() === createdDate.getDate()));
48
48
-
49
49
-
if (diffInMins < 1) {
50
50
-
timeAgoText = rtf.format(0, "minute"); // "now" in the user's language
51
51
-
} else if (diffInMins < 5) {
52
52
-
timeAgoText = rtf.format(-1, "minute"); // "1 minute ago" in the user's language
53
53
-
} else if (diffInMins < 60) {
54
54
-
timeAgoText = rtf.format(-diffInMins, "minute");
55
55
-
} else if (diffInHours < 3) {
56
56
-
timeAgoText = rtf.format(-diffInHours, "hour");
57
57
-
} else if (isToday) {
58
58
-
// Time of day context, but still localized
59
59
-
if (createdHour < 12) {
60
60
-
timeAgoText = "this morning";
61
61
-
} else if (createdHour < 17) {
62
62
-
timeAgoText = "this afternoon";
63
63
-
} else {
64
64
-
timeAgoText = "this evening";
65
65
-
}
66
66
-
} else if (isYesterday) {
67
67
-
timeAgoText = rtf.format(-1, "day"); // "yesterday" in the user's language
68
68
-
} else if (diffInDays < 7) {
69
69
-
timeAgoText = rtf.format(-diffInDays, "day");
70
70
-
} else {
71
71
-
// For older posts, use a date formatter
72
72
-
const dateFormatter = new Intl.DateTimeFormat(navigator.language, {
73
73
-
month: 'short',
74
74
-
day: 'numeric'
75
75
-
});
76
76
-
timeAgoText = `on ${dateFormatter.format(createdDate)}`;
77
77
-
}
78
78
-
79
79
-
document.getElementById("time-ago").textContent = timeAgoText;
80
80
-
81
81
-
// Change "is" to "was" based on recency
30
30
+
const timeEl = document.getElementById("time-ago");
31
31
+
timeEl.setAttribute("datetime", createdAt);
32
32
+
timeEl.textContent = new Intl.DateTimeFormat(navigator.language, {
33
33
+
month: 'short',
34
34
+
day: 'numeric',
35
35
+
hour: 'numeric',
36
36
+
minute: 'numeric'
37
37
+
}).format(createdDate);
82
38
const verbLink = document.getElementById("verb-link");
83
39
if (diffInMins > 30) {
84
40
verbLink.textContent = "Kieran was";
85
41
}
86
86
-
87
87
-
// Show and animate the bubble since we have a valid status
88
42
const bubble = document.querySelector(".bubble");
89
43
bubble.style.visibility = "visible";
90
44
bubble.classList.add("animate-in");
91
91
-
92
92
-
// For reduced motion preferences, ensure the bubble is always visible
93
45
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
94
94
-
bubble.style.transform = "none"; // Ensure no transform is applied
95
95
-
bubble.style.opacity = "1"; // Ensure content is visible
46
46
+
bubble.style.transform = "none";
47
47
+
bubble.style.opacity = "1";
96
48
}
97
49
}
98
50
}