-
-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathbloom.mjs
More file actions
142 lines (117 loc) · 4.03 KB
/
bloom.mjs
File metadata and controls
142 lines (117 loc) · 4.03 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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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"}
*/
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 rebloomEl = bloomFrag.querySelector("[data-rebloom]")
const rebloomCount = bloomFrag.querySelector("[data-rebloom-count]");
const rebloomBtn = bloomFrag.querySelector("[data-rebloom-button]");
bloomArticle.setAttribute("data-bloom-id", bloom.id);
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
);
rebloomBtn.addEventListener("click", async (e) => handleRebloomClick(e, bloom));
// Show "originally bloomed by"
if (bloom.rebloomed_by) {
rebloomEl.replaceChildren();
const originalBloomerLink = document.createElement("a");
originalBloomerLink.href = `/profile/${bloom.rebloom_from}`;
originalBloomerLink.textContent = bloom.rebloom_from;
originalBloomerLink.style.fontWeight = "bold";
rebloomEl.append( "Originally bloomed by ", originalBloomerLink );
} else {
rebloomEl.hidden = true;
}
// Count how many times it has been rebloomed
const count = Number(bloom.rebloom_count || 0);
if (rebloomCount) {
if (count > 0) {
rebloomCount.hidden = false;
rebloomCount.textContent = `Rebloomed ${String(count)} times`;
} else {
rebloomCount.hidden = true;
}
}
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`;
}
// Less than an hour
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}
// Less than a day
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${diffHours}h`;
}
// Less than a week
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) {
return `${diffDays}d`;
}
// Format as month and day for older dates
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
}).format(date);
} catch (error) {
console.error("Failed to format timestamp:", error);
return "";
}
}
async function handleRebloomClick(event, bloom) {
event.preventDefault();
const button = event.currentTarget;
const originalText = button.textContent;
try {
// Make button inert while calling backend
button.inert = true;
button.textContent = "Reblooming...";
await apiService.postRebloom(bloom);
} catch (error) {
console.error("Rebloom failed:", error);
} finally {
// Restore UI state
button.textContent = originalText;
button.inert = false;
}
}
export {createBloom};