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 };