diff --git a/backend/data/blooms.py b/backend/data/blooms.py index 7e280cf..22fbe6b 100644 --- a/backend/data/blooms.py +++ b/backend/data/blooms.py @@ -16,6 +16,9 @@ class Bloom: def add_bloom(*, sender: User, content: str) -> Bloom: + if len(content) > 280: + raise ValueError("Content exceeds 280 character limit.") + hashtags = [word[1:] for word in content.split(" ") if word.startswith("#")] now = datetime.datetime.now(tz=datetime.UTC) diff --git a/backend/requirements.txt b/backend/requirements.txt index 798ddc0..cc015c0 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,3 +12,4 @@ pycparser==2.22 PyJWT==2.10.1 python-dotenv==1.0.1 Werkzeug==3.1.3 +psycopg2==2.9.9 diff --git a/front-end/components/bloom-form.mjs b/front-end/components/bloom-form.mjs index e047f9a..5007975 100644 --- a/front-end/components/bloom-form.mjs +++ b/front-end/components/bloom-form.mjs @@ -27,6 +27,12 @@ async function handleBloomSubmit(event) { const textarea = form.querySelector("textarea"); const content = textarea.value.trim(); + const maxLength = 280; + if (content.length > maxLength) { + alert(`Content must be ${maxLength} characters or less.`); + return; + } + try { // Make form inert while we call the back end form.inert = true; diff --git a/front-end/components/bloom.mjs b/front-end/components/bloom.mjs index 0b4166c..c37c1f0 100644 --- a/front-end/components/bloom.mjs +++ b/front-end/components/bloom.mjs @@ -35,11 +35,11 @@ const createBloom = (template, bloom) => { }; function _formatHashtags(text) { - if (!text) return text; - return text.replace( - /\B#[^#]+/g, - (match) => `${match}` - ); + if (!text) return text; + return text.replace(/\B#(\w+)/g, (match, tag) => { + const normalizedTag = tag.toLowerCase(); + return `${match}`; + }); } function _formatTimestamp(timestamp) { diff --git a/front-end/index.html b/front-end/index.html index 89d6b13..abe4205 100644 --- a/front-end/index.html +++ b/front-end/index.html @@ -1,261 +1,199 @@ - - - - Purple Forest - - - -
-

- Purple Forest - PurpleForest -

-
-
-
-
-
-
-
-
-
-
- -
- - - - - - - - + + + + + + + + - + + - - - + \ No newline at end of file diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 7b7e996..ff035f4 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -1,55 +1,35 @@ -import {renderOne, renderEach, destroy} from "../lib/render.mjs"; +import { renderOne, renderEach, destroy } from '../lib/render.mjs'; import { - state, - apiService, - getLogoutContainer, - getLoginContainer, - getTimelineContainer, - getHeadingContainer, -} from "../index.mjs"; -import {createLogin, handleLogin} from "../components/login.mjs"; -import {createLogout, handleLogout} from "../components/logout.mjs"; -import {createBloom} from "../components/bloom.mjs"; -import {createHeading} from "../components/heading.mjs"; + state, + apiService, + getLogoutContainer, + getLoginContainer, + getTimelineContainer, + getHeadingContainer, +} from '../index.mjs'; +import { createLogin, handleLogin } from '../components/login.mjs'; +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 function hashtagView(hashtag) { - destroy(); + destroy(); - apiService.getBloomsByHashtag(hashtag); + const normalizedHashtag = hashtag.startsWith('#') ? hashtag : `#${hashtag}`; - 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); + if (normalizedHashtag !== state.currentHashtag) { + apiService.getBloomsByHashtag(normalizedHashtag); + } - renderOne( - state.currentHashtag, - getHeadingContainer(), - "heading-template", - createHeading - ); - renderEach( - state.hashtagBlooms || [], - getTimelineContainer(), - "bloom-template", - createBloom - ); + 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.currentHashtag, getHeadingContainer(), 'heading-template', createHeading); + renderEach(state.hashtagBlooms || [], getTimelineContainer(), 'bloom-template', createBloom); } -export {hashtagView}; +export { hashtagView };