-
-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathbloom.mjs
More file actions
121 lines (96 loc) · 3.48 KB
/
bloom.mjs
File metadata and controls
121 lines (96 loc) · 3.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { apiService } from "../index.mjs";
/**
* Create a bloom component
* @param {string} template - The ID of the template to clone
* @param {Object} bloom - The bloom data
* @returns {DocumentFragment} - The bloom fragment of UI, for items in the Timeline
* btw a bloom object is composed thus
* {"id": Number,
* "sender": username,
* "content": "string from textarea",
* "sent_timestamp": "datetime as ISO 8601 formatted string"},
* "reblooms": "reblooms count",
* "original_bloom_id": "id of the rebloomed post"
*/
const createBloom = (template, bloom) => {
if (!bloom) return;
const bloomFrag = document.getElementById(template).content.cloneNode(true);
const bloomParser = new DOMParser();
const bloomArticle = bloomFrag.querySelector("[data-bloom]");
const bloomUsername = bloomFrag.querySelector("[data-username]");
const bloomTime = bloomFrag.querySelector("[data-time]");
const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])");
const bloomContent = bloomFrag.querySelector("[data-content]");
const rebloomButtonEl = bloomFrag.querySelector(
"[data-action='share-bloom']"
);
const rebloomCountEl = bloomFrag.querySelector("[data-rebloom-count]");
const rebloomInfoEl = bloomFrag.querySelector("[data-rebloom-info]");
bloomUsername.setAttribute("href", `/profile/${bloom.sender}`);
bloomUsername.textContent = bloom.sender;
bloomTime.textContent = _formatTimestamp(bloom.sent_timestamp);
bloomTimeLink.setAttribute("href", `/bloom/${bloom.id}`);
bloomContent.replaceChildren(
...bloomParser.parseFromString(_formatHashtags(bloom.content), "text/html")
.body.childNodes
);
rebloomCountEl.textContent = `Rebloomed ${bloom.reblooms} times`;
rebloomCountEl.hidden = bloom.reblooms == 0;
rebloomButtonEl.setAttribute("data-id", bloom.id || "");
rebloomButtonEl.addEventListener("click", handleRebloom);
rebloomInfoEl.hidden = bloom.original_bloom_id === null;
if (bloom.original_bloom_id !== null) {
apiService
.fetchBloomData(bloom.original_bloom_id)
.then((originalBloom) => {
const timeStamp = _formatTimestamp(originalBloom.sent_timestamp);
rebloomInfoEl.innerHTML = `↪ Rebloom of the ${originalBloom.sender}'s post, posted ${timeStamp} ago`;
});
}
return bloomFrag;
};
function _formatHashtags(text) {
if (!text) return text;
return text.replace(
/\B#[^#]+/g,
(match) => `<a href="/hashtag/${match.slice(1)}">${match}</a>`
);
}
function _formatTimestamp(timestamp) {
if (!timestamp) return "";
try {
const date = new Date(timestamp);
const now = new Date();
const diffSeconds = Math.floor((now - date) / 1000);
// Less than a minute
if (diffSeconds < 60) {
return `${diffSeconds}s`;
}
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${diffHours}h`;
}
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) {
return `${diffDays}d`;
}
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
}).format(date);
} catch (error) {
console.error("Failed to format timestamp:", error);
return "";
}
}
async function handleRebloom(event) {
const button = event.target;
const id = button.getAttribute("data-id");
if (!id) return;
await apiService.postRebloom(id);
}
export { createBloom, handleRebloom };