diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs
index 0b4166c..3eef0d1 100644
--- a/front-end/components/bloom.mjs
+++ b/front-end/components/bloom.mjs
@@ -11,77 +11,78 @@
*/
const createBloom = (template, bloom) => {
- if (!bloom) return;
- const bloomFrag = document.getElementById(template).content.cloneNode(true);
- const bloomParser = new DOMParser();
+ 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 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]");
- 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
- );
+ 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
+ );
- return bloomFrag;
+ return bloomFrag;
};
function _formatHashtags(text) {
- if (!text) return text;
- return text.replace(
- /\B#[^#]+/g,
- (match) => `${match}`
- );
+ if (!text) return text;
+ return text.replace(
+ /\B#[a-zA-Z0-9_]+/g,
+
+ (match) => `${match}`
+ );
}
function _formatTimestamp(timestamp) {
- if (!timestamp) return "";
+ if (!timestamp) return "";
- try {
- const date = new Date(timestamp);
- const now = new Date();
- const diffSeconds = Math.floor((now - date) / 1000);
+ 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 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 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 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`;
- }
+ // 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 "";
- }
+ // 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 "";
+ }
}
-export {createBloom};
+export { createBloom };
diff --git a/front-end/components/login.mjs b/front-end/components/login.mjs
index 165b16a..038bbc6 100644
--- a/front-end/components/login.mjs
+++ b/front-end/components/login.mjs
@@ -1,4 +1,4 @@
-import {apiService} from "../index.mjs";
+import { apiService } from "../index.mjs";
/**
* Create a login component
@@ -7,36 +7,36 @@ import {apiService} from "../index.mjs";
* @returns {DocumentFragment} - The login fragment
*/
function createLogin(template, isLoggedIn) {
- if (isLoggedIn) return;
- const loginElement = document
- .getElementById(template)
- .content.cloneNode(true);
+ if (isLoggedIn) return;
+ const loginElement = document
+ .getElementById(template)
+ .content.cloneNode(true);
- return loginElement;
+ return loginElement;
}
// HANDLER
async function handleLogin(event) {
- event.preventDefault();
- const form = event.target;
- const submitButton = form.querySelector("[data-submit]");
- const originalText = submitButton.textContent;
+ event.preventDefault();
+ const form = event.target;
+ const submitButton = form.querySelector("[data-submit]");
+ const originalText = submitButton.textContent;
- try {
- form.inert = true;
- submitButton.textContent = "Logging in...";
+ try {
+ form.inert = true;
+ submitButton.textContent = "Logging in...";
- const formData = new FormData(form);
- const username = formData.get("username");
- const password = formData.get("password");
+ const formData = new FormData(form);
+ const username = formData.get("username");
+ const password = formData.get("password");
- await apiService.login(username, password);
- } catch (error) {
- throw error;
- } finally {
- // Always reset UI state regardless of success/failure
- submitButton.textContent = originalText;
- form.inert = false;
- }
+ await apiService.login(username, password);
+ } catch (error) {
+ throw error;
+ } finally {
+ // Always reset UI state regardless of success/failure
+ submitButton.textContent = originalText;
+ form.inert = false;
+ }
}
-export {createLogin, handleLogin};
+export { createLogin, handleLogin };
diff --git a/front-end/components/profile.mjs b/front-end/components/profile.mjs
index ec4f200..b931e89 100644
--- a/front-end/components/profile.mjs
+++ b/front-end/components/profile.mjs
@@ -1,4 +1,4 @@
-import {apiService} from "../index.mjs";
+import { apiService } from "../index.mjs";
/**
* Create a profile component
@@ -6,64 +6,68 @@ import {apiService} from "../index.mjs";
* @param {Object} profileData - The profile data to display
* @returns {DocumentFragment} - The profile UI
*/
-function createProfile(template, {profileData, whoToFollow, isLoggedIn}) {
- if (!template || !profileData) return;
- const profileElement = document
- .getElementById(template)
- .content.cloneNode(true);
+function createProfile(template, { profileData, whoToFollow, isLoggedIn }) {
+ if (!template || !profileData) return;
+ const profileElement = document
+ .getElementById(template)
+ .content.cloneNode(true);
- const usernameEl = profileElement.querySelector("[data-username]");
- const bloomCountEl = profileElement.querySelector("[data-bloom-count]");
- const followingCountEl = profileElement.querySelector(
- "[data-following-count]"
- );
- const followerCountEl = profileElement.querySelector("[data-follower-count]");
- const followButtonEl = profileElement.querySelector("[data-action='follow']");
- const whoToFollowContainer = profileElement.querySelector(".profile__who-to-follow");
- // Populate with data
- usernameEl.querySelector("h2").textContent = profileData.username || "";
- usernameEl.setAttribute("href", `/profile/${profileData.username}`);
- bloomCountEl.textContent = profileData.total_blooms || 0;
- followerCountEl.textContent = profileData.followers?.length || 0;
- followingCountEl.textContent = profileData.follows?.length || 0;
- followButtonEl.setAttribute("data-username", profileData.username || "");
- followButtonEl.hidden = profileData.is_self || profileData.is_following;
- followButtonEl.addEventListener("click", handleFollow);
- if (!isLoggedIn) {
- followButtonEl.style.display = "none";
- }
+ const usernameEl = profileElement.querySelector("[data-username]");
+ const bloomCountEl = profileElement.querySelector("[data-bloom-count]");
+ const followingCountEl = profileElement.querySelector(
+ "[data-following-count]"
+ );
+ const followerCountEl = profileElement.querySelector("[data-follower-count]");
+ const followButtonEl = profileElement.querySelector("[data-action='follow']");
+ const whoToFollowContainer = profileElement.querySelector(
+ ".profile__who-to-follow"
+ );
+ // Populate with data
+ usernameEl.querySelector("h2").textContent = profileData.username || "";
+ usernameEl.setAttribute("href", `/profile/${profileData.username}`);
+ bloomCountEl.textContent = profileData.total_blooms || 0;
+ followerCountEl.textContent = profileData.followers?.length || 0;
+ followingCountEl.textContent = profileData.follows?.length || 0;
+ followButtonEl.setAttribute("data-username", profileData.username || "");
+ followButtonEl.hidden = profileData.is_self || profileData.is_following;
+ followButtonEl.addEventListener("click", handleFollow);
+ if (!isLoggedIn) {
+ followButtonEl.style.display = "none";
+ }
- if (whoToFollow.length > 0) {
- const whoToFollowList = whoToFollowContainer.querySelector("[data-who-to-follow]");
- const whoToFollowTemplate = document.querySelector("#who-to-follow-chip");
- for (const userToFollow of whoToFollow) {
- const wtfElement = whoToFollowTemplate.content.cloneNode(true);
- const usernameLink = wtfElement.querySelector("a[data-username]");
- usernameLink.innerText = userToFollow.username;
- usernameLink.setAttribute("href", `/profile/${userToFollow.username}`);
- const followButton = wtfElement.querySelector("button");
- followButton.setAttribute("data-username", userToFollow.username);
- followButton.addEventListener("click", handleFollow);
- if (!isLoggedIn) {
- followButton.style.display = "none";
- }
+ if (whoToFollow.length > 0) {
+ const whoToFollowList = whoToFollowContainer.querySelector(
+ "[data-who-to-follow]"
+ );
+ const whoToFollowTemplate = document.querySelector("#who-to-follow-chip");
+ for (const userToFollow of whoToFollow) {
+ const wtfElement = whoToFollowTemplate.content.cloneNode(true);
+ const usernameLink = wtfElement.querySelector("a[data-username]");
+ usernameLink.innerText = userToFollow.username;
+ usernameLink.setAttribute("href", `/profile/${userToFollow.username}`);
+ const followButton = wtfElement.querySelector("button");
+ followButton.setAttribute("data-username", userToFollow.username);
+ followButton.addEventListener("click", handleFollow);
+ if (!isLoggedIn) {
+ followButton.style.display = "none";
+ }
- whoToFollowList.appendChild(wtfElement);
- }
- } else {
- whoToFollowContainer.innerText = "";
- }
+ whoToFollowList.appendChild(wtfElement);
+ }
+ } else {
+ whoToFollowContainer.innerText = "";
+ }
- return profileElement;
+ return profileElement;
}
async function handleFollow(event) {
- const button = event.target;
- const username = button.getAttribute("data-username");
- if (!username) return;
+ const button = event.target;
+ const username = button.getAttribute("data-username");
+ if (!username) return;
- await apiService.followUser(username);
- await apiService.getWhoToFollow();
+ await apiService.followUser(username);
+ await apiService.getWhoToFollow();
}
-export {createProfile, handleFollow};
+export { createProfile, handleFollow };
diff --git a/front-end/views/profile.mjs b/front-end/views/profile.mjs
index dd2b92a..0b36f5b 100644
--- a/front-end/views/profile.mjs
+++ b/front-end/views/profile.mjs
@@ -1,66 +1,66 @@
-import {renderEach, renderOne, destroy} from "../lib/render.mjs";
+import { renderEach, renderOne, destroy } from "../lib/render.mjs";
import {
- apiService,
- state,
- getLogoutContainer,
- getLoginContainer,
- getProfileContainer,
- getTimelineContainer,
+ apiService,
+ state,
+ getLogoutContainer,
+ getLoginContainer,
+ getProfileContainer,
+ getTimelineContainer,
} from "../index.mjs";
-import {createLogin, handleLogin} from "../components/login.mjs";
-import {createLogout, handleLogout} from "../components/logout.mjs";
-import {createProfile, handleFollow} from "../components/profile.mjs";
-import {createBloom} from "../components/bloom.mjs";
+import { createLogin, handleLogin } from "../components/login.mjs";
+import { createLogout, handleLogout } from "../components/logout.mjs";
+import { createProfile, handleFollow } from "../components/profile.mjs";
+import { createBloom } from "../components/bloom.mjs";
// Profile view - just this person's blooms and their profile
function profileView(username) {
- destroy();
+ destroy();
- const existingProfile = state.profiles.find((p) => p.username === username);
+ const existingProfile = state.profiles.find((p) => p.username === username);
- // Only fetch profile if we don't have it or if it's incomplete
- if (!existingProfile || !existingProfile.recent_blooms) {
- apiService.getProfile(username);
- }
+ // Only fetch profile if we don't have it or if it's incomplete
+ if (!existingProfile || !existingProfile.recent_blooms) {
+ apiService.getProfile(username);
+ }
- renderOne(
- state.isLoggedIn,
- getLogoutContainer(),
- "logout-template",
- createLogout
- );
- document
- .querySelector("[data-action='logout']")
- ?.addEventListener("click", handleLogout);
- renderOne(
- state.isLoggedIn,
- getLoginContainer(),
- "login-template",
- createLogin
- );
- document
- .querySelector("[data-action='login']")
- ?.addEventListener("click", handleLogin);
+ renderOne(
+ state.isLoggedIn,
+ getLogoutContainer(),
+ "logout-template",
+ createLogout
+ );
+ document
+ .querySelector("[data-action='logout']")
+ ?.addEventListener("click", handleLogout);
+ renderOne(
+ state.isLoggedIn,
+ getLoginContainer(),
+ "login-template",
+ createLogin
+ );
+ document
+ .querySelector("[data-action='login']")
+ ?.addEventListener("click", handleLogin);
- const profileData = state.profiles.find((p) => p.username === username);
- if (profileData) {
- renderOne(
- {
- profileData,
- whoToFollow: state.isLoggedIn ? state.whoToFollow : [],
- isLoggedIn: state.isLoggedIn,
- },
- getProfileContainer(),
- "profile-template",
- createProfile
- );
- renderEach(
- profileData.recent_blooms || [],
- getTimelineContainer(),
- "bloom-template",
- createBloom
- );
- }
+ const profileData = state.profiles.find((p) => p.username === username);
+ if (profileData) {
+ renderOne(
+ {
+ profileData,
+ whoToFollow: state.isLoggedIn ? state.whoToFollow : [],
+ isLoggedIn: state.isLoggedIn,
+ },
+ getProfileContainer(),
+ "profile-template",
+ createProfile
+ );
+ renderEach(
+ profileData.recent_blooms || [],
+ getTimelineContainer(),
+ "bloom-template",
+ createBloom
+ );
+ }
}
-export {profileView};
+export { profileView };