From 58f30a0a6d2ed873cb0f0ccefa999c81f4d6927e Mon Sep 17 00:00:00 2001 From: Luke Manyamazi Date: Tue, 21 Apr 2026 17:40:19 +0200 Subject: [PATCH 1/2] Fix hashtag navigation and rendering for hashtag timeline view --- front-end/components/bloom.mjs | 66 ++++++++++++++++++++-------------- front-end/views/hashtag.mjs | 22 ++++++++---- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs index 0b4166c..3a4e1f4 100644 --- a/front-end/components/bloom.mjs +++ b/front-end/components/bloom.mjs @@ -2,16 +2,13 @@ * 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"} - + * @returns {DocumentFragment} */ +import { apiService } from "../index.mjs"; + const createBloom = (template, bloom) => { if (!bloom) return; + const bloomFrag = document.getElementById(template).content.cloneNode(true); const bloomParser = new DOMParser(); @@ -20,24 +17,52 @@ const createBloom = (template, bloom) => { const bloomTime = bloomFrag.querySelector("[data-time]"); const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])"); const bloomContent = bloomFrag.querySelector("[data-content]"); + const rebloomButton = bloomFrag.querySelector("[data-action='rebloom']"); + const rebloomCount = bloomFrag.querySelector("[data-rebloom-count]"); + const rebloomLabel = bloomFrag.querySelector("[data-rebloom-label]"); 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 + .body.childNodes, ); + // Handle rebloom click + rebloomButton?.addEventListener("click", async () => { + try { + const result = await apiService.rebloom(bloom.id); + + if (result.success) { + rebloomButton.textContent = "🔁 Re-bloomed"; + rebloomButton.disabled = true; + + if (rebloomCount) { + const currentCount = bloom.rebloom_count || 0; + rebloomCount.textContent = `🔁 ${currentCount + 1}`; + } + if (bloom.rebloomed_by) { + rebloomLabel.textContent = `${bloom.rebloomed_by} re-bloomed`; + } + } + } catch (error) { + console.error("Failed to rebloom:", error); + alert("Failed to rebloom"); + } + }); + return bloomFrag; }; function _formatHashtags(text) { if (!text) return text; + return text.replace( - /\B#[^#]+/g, + /#[A-Za-z0-9_]+/g, (match) => `${match}` ); } @@ -50,30 +75,17 @@ function _formatTimestamp(timestamp) { const now = new Date(); const diffSeconds = Math.floor((now - date) / 1000); - // Less than a minute - if (diffSeconds < 60) { - return `${diffSeconds}s`; - } + if (diffSeconds < 60) return `${diffSeconds}s`; - // Less than an hour const diffMinutes = Math.floor(diffSeconds / 60); - if (diffMinutes < 60) { - return `${diffMinutes}m`; - } + if (diffMinutes < 60) return `${diffMinutes}m`; - // Less than a day const diffHours = Math.floor(diffMinutes / 60); - if (diffHours < 24) { - return `${diffHours}h`; - } + if (diffHours < 24) return `${diffHours}h`; - // Less than a week const diffDays = Math.floor(diffHours / 24); - if (diffDays < 7) { - return `${diffDays}d`; - } + if (diffDays < 7) return `${diffDays}d`; - // Format as month and day for older dates return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", @@ -84,4 +96,4 @@ function _formatTimestamp(timestamp) { } } -export {createBloom}; +export { createBloom }; diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 7b7e996..773731a 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -12,12 +12,18 @@ import {createLogout, handleLogout} from "../components/logout.mjs"; import {createBloom} from "../components/bloom.mjs"; import {createHeading} from "../components/heading.mjs"; -// Hashtag view: show all tweets containing this tag - +// Hashtag view: show all blooms containing this tag function hashtagView(hashtag) { destroy(); - apiService.getBloomsByHashtag(hashtag); + const blooms = []; + + // Only fetch if this hashtag isn't already loaded + if (state.currentHashtag !== `#${hashtag}`) { + apiService.getBloomsByHashtag(hashtag); + } else { + blooms.push(...(state.hashtagBlooms || [])); + } renderOne( state.isLoggedIn, @@ -28,6 +34,7 @@ function hashtagView(hashtag) { document .querySelector("[data-action='logout']") ?.addEventListener("click", handleLogout); + renderOne( state.isLoggedIn, getLoginContainer(), @@ -35,8 +42,8 @@ function hashtagView(hashtag) { createLogin ); document - .querySelector("[data-action='login']") - ?.addEventListener("click", handleLogin); + .querySelector("[data-form='login']") + ?.addEventListener("submit", handleLogin); renderOne( state.currentHashtag, @@ -44,12 +51,13 @@ function hashtagView(hashtag) { "heading-template", createHeading ); + renderEach( - state.hashtagBlooms || [], + blooms, getTimelineContainer(), "bloom-template", createBloom ); } -export {hashtagView}; +export {hashtagView }; \ No newline at end of file From c87bc64ec71d63da25936d288d9f4b90eb9107ac Mon Sep 17 00:00:00 2001 From: Luke Manyamazi Date: Thu, 23 Apr 2026 07:54:41 +0200 Subject: [PATCH 2/2] refactor remove rebloom code --- front-end/components/bloom.mjs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs index 3a4e1f4..bd999cc 100644 --- a/front-end/components/bloom.mjs +++ b/front-end/components/bloom.mjs @@ -17,9 +17,6 @@ const createBloom = (template, bloom) => { const bloomTime = bloomFrag.querySelector("[data-time]"); const bloomTimeLink = bloomFrag.querySelector("a:has(> [data-time])"); const bloomContent = bloomFrag.querySelector("[data-content]"); - const rebloomButton = bloomFrag.querySelector("[data-action='rebloom']"); - const rebloomCount = bloomFrag.querySelector("[data-rebloom-count]"); - const rebloomLabel = bloomFrag.querySelector("[data-rebloom-label]"); bloomArticle.setAttribute("data-bloom-id", bloom.id); bloomUsername.setAttribute("href", `/profile/${bloom.sender}`); @@ -32,29 +29,6 @@ const createBloom = (template, bloom) => { .body.childNodes, ); - // Handle rebloom click - rebloomButton?.addEventListener("click", async () => { - try { - const result = await apiService.rebloom(bloom.id); - - if (result.success) { - rebloomButton.textContent = "🔁 Re-bloomed"; - rebloomButton.disabled = true; - - if (rebloomCount) { - const currentCount = bloom.rebloom_count || 0; - rebloomCount.textContent = `🔁 ${currentCount + 1}`; - } - if (bloom.rebloomed_by) { - rebloomLabel.textContent = `${bloom.rebloomed_by} re-bloomed`; - } - } - } catch (error) { - console.error("Failed to rebloom:", error); - alert("Failed to rebloom"); - } - }); - return bloomFrag; }; @@ -96,4 +70,4 @@ function _formatTimestamp(timestamp) { } } -export { createBloom }; +export { createBloom }; \ No newline at end of file