Features - devrica-collections/Gritsquare-femcoders-FE25 GitHub Wiki

Features

Table of contents

Feature Description Contributor
Write a message and post it on homepage Users can create and submit positive messages to the message board Elin - Core Team
Like others messages Users can like messages from other community members Jenny - Core Team
Remove messages Users can delete their own messages from the board Jenny - Core Team
Like animation When users like a post there is an animation Bianca - Core Team
Bubble sound when new message gets added Audio feedback plays when a new message is posted Elin - Core Team
Pick a gif with your message Users can attach GIFs to their messages Erica - Core team
Pick a color of your post it note When typing message, users can choose the color of their post it note Emma - Core Team
Guidelines popup A guideline popup that shows rules and how to act Emma - Core Team
Profanity filter* Filters out inappropriate language in messages Mohammed - External
Likes leaderboard* Displays top liked messages and users in a leaderboard Isak - External
Button to spawn bubbles that can be popped* Interactive bubble elements that users can click to pop Elias - External
Add emojis to your message* Users can add emoji reactions to their messages Jack - External
Translate the page feature* Allows users to translate the entire page into different languages Ali - External
Delay on sending messages, Anti-Spam* Implements a cooldown period between message submissions to prevent spam Ali - External
Word counter* Displays the character or word count for messages Isabelle - External
Nickname* Choose nickname and display it Isabelle - External

*Added by contributor

Core team features

Message Board

<form id="msgForm">
  <input type="text" id="messageInput" placeholder="Write something..." required />
  <button type="submit">Send</button>
</form>
const form = document.querySelector('#msgForm');
const input = document.querySelector('#messageInput');

form.addEventListener("submit", (e) => {
  e.preventDefault();

  const text = input.value.trim();
  if (!text) return;

  form.reset();
});

This feature allows users to write and send positive messages through a text input field. When submitted, the message is processed and added to the application. The form has a textfield (#messageinput) and a send button. It collects the value from the input field. The form resets after it’s been sent. 
 The application uses Firebase Realtime Database to store and retrieve messages.

Like Feature

Users can not only spread good vibes with positive messages but also likes! Users can spread unlimited good vibes with unlimited likes for every message. Not only can the user read the positive messages but also see how much appreciation the messages they post get!

likeBtn.addEventListener('click', async () => {
       const postRef = ref(db, `messages/${id}/likes`);
           await setTimeout(()=>{
               noteCard.style.overflow="overflow" 
               runTransaction(postRef, (currentLikes) => {
           return (currentLikes || 0) + 1;
       })
})

The like button is created in a function called render. It is handled with an event-listener. When the user interacts with the button the amount of likes goes up by one. By default when a message is created it has 0 likes.

Delete Feature

Although the website is enforced with a profanity filter, sometimes bad vibes can slip in. If there is a message that makes the user feel bad, the user has full control to remove it from the website. Plus we all have accidentally sent/posted something at least once in our lives. But on our website an accident is not the end of the world with a delete button.

const deleteBtn = document.createElement('button');
   deleteBtn.className = 'delete-btn';
   deleteBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#BB271A"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>';
   deleteBtn.title = 'Delete message';


   deleteBtn.addEventListener('click', async () => {
       const confirmDelete = confirm("Are you sure you want to remove the good vibes? This cannot be undone.");
      
       if (confirmDelete) {
           try {
               const postRef = ref(db, `messages/${id}`);
               await remove(postRef);
           } catch (error) {
               console.error("Error deleting message:", error);
               alert("Could not delete message. Try again later.");
           }
       }
   });
   noteCard.appendChild(deleteBtn);

The delete button is represented by an SVG-icon that looks like a trashcan, to give users familiarity to what the button does. SVG-icons are practical because they are scalable and customizable. The delete button is also handled with an eventlistener. When the button is pressed the user will be alerted by what is about to happen so there won’t be accidental deletions. Once the user agree to proceed the program will look for the right message id and remove it from the firebase.

Like animation

This code generates a heart explosion animation when the like button is clicked. The heart explosion container is appended to each (noteCard) article element.

 function heartExplosion (container, options = {}){
    
        if(!container) return;
        const num = options.num || 35;
        const sizeMin = options.sizeMin || 10;
        const sizeMax = options.sizeMax || 20;
        const spread = options.spread ||300;
        const colors = options.colors || ["#ff4d6d", "#ff758f", "#ff8fa3"];
        const duration = options.duration || 900;

 await   heartExplosion(likeBtn, {
            num: 40,
            sizeMin: 8,
            sizeMax: 18,
            spread: 300,
            duration: 500,
            colors: ["#ff4d6d", "#ff758f", "#ff8fa3"]});

Bubble notification

<audio id="notifSound" src="./sound/Bubbles-sound-effect.mp3" preload="auto"></audio>
const notifSound = document.getElementById("notifSound");notifSound.currentTime = 0; 
 notifSound.play().catch(() => {});

The code above generates a bubble sound notification every time a message is displayed in the chat. The element is used to load the bubble sound effect. It is preloaded so it can play immediately when a message appears.

The audio file (Bubbles-sound-effect.mp3) is gathered from https://pixabay.com/sound-effects/search/bubbles/

Search for gif and adding to message

    if (gifUrl) {
        const gifImg = document.createElement('img');
        gifImg.src = gifUrl;
        gifImg.style.maxWidth = '100%';
        gifImg.style.borderRadius = '8px';
        gifImg.style.marginBottom = '10px';
        noteCard.appendChild(gifImg);
    }

    searchGifBtn.addEventListener('click', async () => {
    clearSelectedGif();
    const search = gifSearchInput.value.trim();
    const gifs = await searchGifs(search);
    displayGifResults(gifs, '#gifResults')
})

The code above uses the exported functions from modules/gifapi and its a search button that calls the exported functions. On the main page the gifs are being rendered via liveupdate.

Used with klipy

Change color of post it

This code allows you to choose a color for your post-it note before sending it. The colors the user can choose between are selected and put in to stay on the theme of the website, but still make the chatforum a little bit more fun and realistic with the different colored post-it notes.

<div id="selectColor">
<div id="color-1" class="color-option selected" 
     data-color="#d6c5ef"></div>
      <div id="color-2" class="color-option" 
     data-color="#edcbf6"></div>
	<!-- More color options.. -->
</div>

Color options listed as separate empty div elements with the color code for each option, later to be styled in the css code

let selectedColor = '#d6c5ef';


function initColorPicker() {
   const colorOptions = document.querySelectorAll('.color-option');


   colorOptions.forEach(option => {
       option.style.backgroundColor = option.dataset.color;
      
       option.addEventListener('click', () => {
           colorOptions.forEach(opt => opt.classList.remove('selected'));
           option.classList.add('selected');
           selectedColor = option.dataset.color;
       });
   });
}
initColorPicker();

selectedColor is put in functions that decides what data is stored in the database

Guidelines popup

// Open and close message form + guidelines
const openBtn = document.querySelector('#openCard');
const card = document.querySelector('#card');
const guidelinesCard = document.querySelector('#guidelines');
const closeBtn = document.querySelector('#closeBtn');
const readGuidelinesBtn = document.getElementById('readGuidelinesBtn');
// Function to check if 24h has passed since guidelines was closed
function hasOneDayPassed() {
   const lastClosed = localStorage.getItem("guidelinesClosedAt");
   const oneDay = 24 * 60 * 60 * 1000; // 24 timmar i ms
   const now = Date.now();
// If no timestamp → assume never shown → show guidelines
   if (!lastClosed) return true;
   return (now - lastClosed) > oneDay;
}
// When “Write a message” button is clicked 
openBtn.addEventListener('click', () => {
   const cardWasHidden = card.classList.contains('hidden');
   card.classList.toggle('hidden');
   if (cardWasHidden) {
// Automatically show guidelines IF 24h has passed
       if (hasOneDayPassed()) {
           guidelinesCard.classList.remove('hidden');
       }
   } else {
// If form is manually closed → guidelines is closed
       guidelinesCard.classList.add('hidden');
   }
});
// Guidelines manually closed
closeBtn.addEventListener('click', () => {
   guidelinesCard.classList.add('hidden');
// Timestamp is saved when user manually closes guidelines
   localStorage.setItem("guidelinesClosedAt", Date.now());
});
//Open to read guidelines regardless of 24h timer (“Guidelines” button)
readGuidelinesBtn.addEventListener('click', () => {
   guidelinesCard.classList.remove('hidden');
});

The guidelines feature shows up when the user clicks on the “Write a message” button for the first time then the timestamp is stored in Local Storage and the guidelines feature will automatically show up again like the first time after 24 hours. During the 24h period the user can still access the guidelines manually by clicking the “Guidelines” button at the top of the “Write a message” form. When closing the guidelines by clicking the X button the 24h timer restarts regardless of whether it opens automatically or manually.

The guidelines js code is somewhat integrated with the forms code since guidelines is connected to sending messages

Contributor Features

Profanity Filter

profanityfilter.mp4
import { profanityList } from "./profanityList.js";

function escapeRegex(str) {


  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

export function censorBadWords(text = "") {
  const escapedWords = profanityList.map(escapeRegex);
  const regex = new RegExp(`\\b(${escapedWords.join("|")})\\b`, "gi");
  return text.replace(regex, (match) => "*".repeat(match.length));
}

This functions parameter is the original text input. The text goes through a filter that replaces the bad words with stars. RegEx is used to fight "leetspeak", trying to circumvent regular characters. Filter function

Possible to add censored words via Profanitylist

Made by Mohammed

Likes Leaderboard

leaderboard.mp4
onValue(leaderBoardQuery, (snapshot) => {
    const data = snapshot.val()
    if (!data) return;



    const leaderboard = Object.entries(data)
        .map(([id, msg]) => ({
            id,
            text: msg.text,
            likes: msg.likes

        }))
        .sort((a, b) => b.likes - a.likes)
    console.log(leaderboard)

    const listEl = document.querySelector('#leaderBoardList')
    listEl.innerHTML = ''
    leaderboard.forEach((item, index) => {
        const li = document.createElement('li')
        li.textContent = `${ index + 1 }. ${ item.text } - ❤️ ${ item.likes }`
        listEl.appendChild(li)
    })
})

The like leaderboard uses firebase method orderByChild to sort the messages from most likes to least likes

leaderboard function Made by Isak

Spawn bubbles and pop them

bubble.mp4

Bubble function

Made by Elias

Add emojis to message

emoji.mp4
selectEl.addEventListener('change', () => {
    const emoji = selectEl.value
    if (!emoji) return

    const start = message.selectionStart
    const end = message.selectionEnd
    const text = message.value

    if (Array.from(message.value).length >= message.maxLength) return

    message.value = text.slice(0, start) + emoji + text.slice(end)

    message.focus()
    message.selectionStart = message.selectionEnd = start + emoji.length
    message.dispatchEvent(new Event('input'))
})

Emoji Javascript

Made by Jack

Translate page

translate.mp4
function loadGoogleScript() {
  const script = document.createElement('script');

  script.src = "https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
  script.async = true;
  script.defer = true;

  document.body.appendChild(script);
}

Translate javascript

Made by Ali

Anti-Spam

anti-spam.mp4

Anti-Spam

Made by Ali

Character limiter

characterlimit.mp4
const input = document.getElementById("messageInput");
const counter = document.getElementById("counter");
const maxLength = 200;

input.addEventListener("input", () => {
  if (input.value.length > maxLength) {
    input.value = input.value.slice(0, maxLength);
  }

  counter.textContent = `${input.value.length}/${maxLength}`;
});

Character limiter

Made by Isabelle

Add nickname

image

Add nickname

Made by Isabelle

⚠️ **GitHub.com Fallback** ⚠️